Transitions de paradigmes vers Touba
Un nouveau paradigme ne rend pas les anciens langages incapables. Un programme Python, Java ou TypeScript peut appeler un modèle, contrôler un secret, écrire une trace ou limiter un accès.
La différence est ailleurs : chaque paradigme déplace une partie de la complexité dans une abstraction plus lisible. Touba poursuit ce mouvement pour les systèmes agentiques : il place les responsabilités agent, tool, secret, mémoire, validation humaine et trace dans un contrat explicite plutôt que dans des conventions dispersées.
Les exemples ci-dessous sont pédagogiques. Ils montrent la même question à travers quatre styles :
| Étape | Langage utilisé | Ce qui est mis en avant |
|---|---|---|
| Script impératif | Python | l'ordre des actions est direct et rapide à lire |
| Objet | Java | les responsabilités deviennent des objets et services nommés |
| Fonctionnel typé | TypeScript | les transformations et types rendent les frontières plus visibles |
| Probabilistic Agentic Programming avec Touba | Touba | l'autonomie, les droits, les handlers et les traces deviennent des artefacts du programme |
Cas 1 - Contenu externe ou instruction agentique
Question : une page web contient du texte qui ressemble à une instruction. Comment éviter de le traiter comme une commande adressée à l'agent ?
Python impératif
def answer_from_page(url: str) -> str:
source_text = {
"kind": "external_content",
"content": fetch_url(url),
}
instruction = review_and_build_instruction(source_text)
return call_llm(instruction)
Python permet de résoudre le problème avec une discipline claire : on nomme source_text, on appelle une étape de revue, puis on appelle le modèle.
Responsabilité côté application : la frontière dépend des conventions du code. Rien n'empêche un autre appel de transmettre directement source_text["content"] au modèle.
Java objet
final class SourceText {
private final String content;
SourceText(String content) {
this.content = content;
}
String content() {
return content;
}
}
final class AgentInstruction {
private final String content;
private AgentInstruction(String content) {
this.content = content;
}
static AgentInstruction fromReviewedSource(SourceText source, HumanReview review) {
review.requireAccepted();
return new AgentInstruction("Summarize this reviewed source: " + source.content());
}
String content() {
return content;
}
}
L'objet absorbe une partie de la complexité : SourceText et AgentInstruction ne sont plus de simples chaînes interchangeables. La construction d'une instruction passe par une méthode nommée.
Responsabilité côté application : l'appel réel au modèle, les secrets, les droits et la trace restent dans le code hôte ou dans des frameworks autour.
TypeScript fonctionnel typé
type SourceText = Readonly<{
kind: "external_content";
content: string;
}>;
type AgentInstruction = Readonly<{
kind: "agent_instruction";
content: string;
reviewedBy: string;
}>;
const buildInstruction = (source: SourceText, reviewedBy: string): AgentInstruction => ({
kind: "agent_instruction",
content: `Summarize this reviewed source: ${source.content}`,
reviewedBy,
});
const callLlm = async (instruction: AgentInstruction): Promise<string> => {
return await llm(instruction.content);
};
Le style fonctionnel typé absorbe une autre complexité : la transformation devient une fonction explicite, et la signature de callLlm attend AgentInstruction, pas n'importe quel texte.
Responsabilité côté application : TypeScript aide au développement, mais l'exécution, les permissions, le manifest, les secrets et les traces structurées restent organisés ailleurs.
Touba
module examples.boundary
type SourceRequest
url is Text
type SourceText
content is Text
type AgentInstruction
content is Text
type LLMAnswer
content is Text
tool FetchExternalContent
takes request as SourceRequest
gives SourceText
requires network
tool BuildAgentInstruction
takes source as SourceText
gives AgentInstruction
tool CallLLM
takes instruction as AgentInstruction
gives LLMAnswer
requires network, secret_read
secrets LLM_API_KEY
skill AnswerWithReviewedSource
takes request as SourceRequest
gives LLMAnswer
uses FetchExternalContent
uses BuildAgentInstruction
uses CallLLM
do
FetchExternalContent
with request as request
into source
human.review
with context as source
into review
BuildAgentInstruction
with source as source
into instruction
CallLLM
with instruction as instruction
into answer
give answer
Touba met la frontière dans le programme : le contenu externe, l'instruction agentique, la revue humaine, le tool LLM, le secret et les requirements sont visibles dans le contrat .tb.
Ce que Touba absorbe : le runtime peut refuser un handler non déclaré, un secret non accordé ou un payload incompatible avant l'appel. La trace peut ensuite montrer les étapes et les refus sans devenir un journal de prompt complet.
Portée : cette séparation rend la confusion plus visible, testable et auditable. La feuille de route sécurité complète ce socle avec des politiques de payload, des validations externes et des intégrations d’observabilité.
Cas 2 - Tool réel, secret et droit d'accès
Question : un agent doit appeler un service externe avec une clé. Où déclare-t-on que le réseau et le secret sont nécessaires ?
Python impératif
import os
def summarize(query: str) -> str:
api_key = os.environ["LLM_API_KEY"]
search_result = search_web(query)
return call_llm(api_key=api_key, text=search_result["text"])
Le script est compact. Le développeur voit l'ordre d'exécution immédiatement.
Responsabilité côté application : l'accès réseau, le secret, la politique de clé, le timeout, la taille des payloads et l'audit sont des détails à maintenir dans le code ou dans l'environnement.
Java objet
final class SummarizeService {
private final SearchClient searchClient;
private final LlmClient llmClient;
private final SecretProvider secrets;
SummarizeService(SearchClient searchClient, LlmClient llmClient, SecretProvider secrets) {
this.searchClient = searchClient;
this.llmClient = llmClient;
this.secrets = secrets;
}
Summary summarize(String query) {
SearchResult result = searchClient.search(query);
String apiKey = secrets.resolve("LLM_API_KEY");
return llmClient.summarize(apiKey, result.text());
}
}
L'objet absorbe la complexité de câblage : les dépendances deviennent explicites dans le constructeur et le service porte une responsabilité lisible.
Responsabilité côté application : le langage ne sait pas que LLM_API_KEY est un handle agentique, que searchClient exige network, ou que ce service ne doit être appelé que par certains agents.
TypeScript fonctionnel typé
type Capability = "network" | "secret_read";
type SecretHandle = "LLM_API_KEY";
type Binding = Readonly<{
grants: readonly Capability[];
secrets: readonly SecretHandle[];
}>;
const requireBinding = (binding: Binding): void => {
if (!binding.grants.includes("network")) throw new Error("network grant missing");
if (!binding.grants.includes("secret_read")) throw new Error("secret grant missing");
if (!binding.secrets.includes("LLM_API_KEY")) throw new Error("secret handle missing");
};
const summarize = async (query: string, binding: Binding): Promise<string> => {
requireBinding(binding);
const source = await searchWeb(query);
return await callLlm("LLM_API_KEY", source.text);
};
Le typage rend les capabilities et handles plus lisibles. La vérification devient une fonction séparée et testable.
Responsabilité côté application : cette discipline reste portée par le code TypeScript. Elle ne crée pas automatiquement un AIR, un manifest, un refus runtime commun ou une surface de trace partagée.
Touba
module examples.secure_tool
type SearchRequest
query is Text
type SearchResult
text is Text
type Summary
text is Text
tool SearchWeb
takes request as SearchRequest
gives SearchResult
requires network
tool SummarizeWithLLM
takes result as SearchResult
gives Summary
requires network, secret_read
secrets LLM_API_KEY
skill BuildSummary
takes request as SearchRequest
gives Summary
uses SearchWeb
uses SummarizeWithLLM
do
SearchWeb
with request as request
into result
SummarizeWithLLM
with result as result
into summary
give summary
agent Analyst
responsibility "Fulfill the declared Analyst responsibility using only authorized capabilities."
uses BuildSummary
callable_by strategy RunSummary
strategy RunSummary
uses Analyst
do
Analyst
Touba sépare le contrat du handler. Le programme dit que SearchWeb exige network et que SummarizeWithLLM exige network, secret_read et le handle LLM_API_KEY. Le manifest doit ensuite accorder explicitement ces éléments au runtime.
Ce que Touba absorbe : le droit d'appeler un handler n'est pas seulement une convention de service. Il devient une relation entre source .tb, AIR, manifest, runtime et trace.
Portée : les grants du manifest expriment l’autorité Touba au niveau du contrat d’exécution. Les contrôles système, réseau et dépendances du handler restent portés par l’environnement hôte et la supply chain.
Cas 3 - Mémoire et auditabilité
Question : un agent lit une mémoire projet. Comment savoir qui a lu quoi, avec quel droit logique, et où observer l'accès ?
Python impératif
trace = []
def answer_with_memory(question: str) -> str:
trace.append({"event": "memory_read_requested", "query": question})
note = memory_read(question)
trace.append({"event": "memory_read_completed"})
return call_llm(f"Answer with project context: {note}")
Le script peut produire des événements lisibles.
Responsabilité côté application : la trace, la mémoire, le droit d'accès et le prompt sont des conventions que chaque fonction doit respecter.
Java objet
final class AuditedMemory {
private final MemoryRepository repository;
private final AuditLog auditLog;
AuditedMemory(MemoryRepository repository, AuditLog auditLog) {
this.repository = repository;
this.auditLog = auditLog;
}
MemoryNote read(String agentName, String query) {
auditLog.record("memory_read_requested", agentName);
MemoryNote note = repository.read(query);
auditLog.record("memory_read_completed", agentName);
return note;
}
}
L'objet absorbe la responsabilité d'audit autour du repository. Le code métier peut lire AuditedMemory au lieu d'appeler directement le backend.
Responsabilité côté application : le langage ne force pas tous les accès à passer par cet objet. Le lien entre agent, scope, backend et trace dépend de l'architecture de l'application.
TypeScript fonctionnel typé
type TraceEvent = Readonly<{ name: string; actor: string }>;
type MemoryNote = Readonly<{ content: string }>;
type RunState = Readonly<{ trace: readonly TraceEvent[] }>;
const addTrace = (state: RunState, event: TraceEvent): RunState => ({
trace: [...state.trace, event],
});
const readMemory = async (
state: RunState,
actor: string,
query: string,
): Promise<readonly [RunState, MemoryNote]> => {
const started = addTrace(state, { name: "memory_read_requested", actor });
const note = await memoryRead(query);
const completed = addTrace(started, { name: "memory_read_completed", actor });
return [completed, note];
};
Le style fonctionnel rend l'état de trace explicite : lire la mémoire retourne aussi un nouvel état de run. La mutation implicite diminue dans le code typé.
Responsabilité côté application : le programme porte lui-même les règles de scope, les grants, le binding mémoire et le format de trace commun.
Touba
module examples.memory_audit
define MemoryQuestion as Text = "incident policy"
type MemoryNote
content is Text
memory ProjectKnowledge
tool ReadProjectKnowledge
uses memory ProjectKnowledge read
takes query as Text
gives MemoryNote
requires memory_read
skill ReadContext
gives MemoryNote
memory ProjectKnowledge read
uses ReadProjectKnowledge
do
ReadProjectKnowledge
with query as MemoryQuestion
into note
give note
agent RiskAnalyst
responsibility "Fulfill the declared RiskAnalyst responsibility using only authorized capabilities."
memory ProjectKnowledge read
uses ReadContext
callable_by strategy RunMemoryCheck
strategy RunMemoryCheck
uses RiskAnalyst
do
RiskAnalyst
Touba place la mémoire dans le contrat agentique. La mémoire est logique, le tool déclare l'accès read, l'agent déclare son scope, et le runtime croise ces informations avec le manifest avant d'appeler le handler.
Ce que Touba absorbe : l'auditabilité ne repose pas seulement sur un logger écrit à la main. Les accès autorisés ou refusés peuvent être reliés à l'AIR, au manifest, à l'agent actif et à la trace.
Portée : Touba gouverne l’accès logique, la provenance et les traces. Les backends mémoire, la vérification du contenu et l’audit du handler restent des responsabilités d’intégration explicites.
Ce que la transition montre
| Passage | Abstraction ajoutée | Complexité absorbée |
|---|---|---|
| Impératif vers objet | responsabilités nommées | regrouper état, comportement et dépendances |
| Objet vers fonctionnel typé | transformations et types explicites | réduire les mutations implicites et rendre les frontières visibles |
| Fonctionnel typé vers Touba | contrat agentique exécutable | relier agents, tools, secrets, mémoires, validations et traces au runtime |
La valeur de Touba n'est donc pas de rendre impossible ce que les autres langages savent déjà faire. Sa valeur est de donner une place de premier ordre à des responsabilités qui, dans les paradigmes précédents, restent souvent réparties entre code hôte, configuration, discipline d'équipe et documentation.
Concrètement, ces responsabilités ressemblent souvent à ceci :
| Endroit habituel | Exemples de responsabilités |
|---|---|
| Code hôte | vérifier qu'un handler reçoit le bon payload, appliquer un timeout, éviter d'envoyer une valeur secrète dans un prompt, écrire les événements d'audit au bon endroit |
| Configuration | relier un tool à un handler, accorder network ou secret_read, choisir le profil de secrets, fixer les limites d'exécution |
| Discipline d'équipe | ne pas passer un contenu externe directement comme instruction, demander une revue humaine avant une conversion sensible, garder les handlers petits et relus |
| Documentation | expliquer quel agent peut appeler quel autre agent, quel secret est utilisé par quel tool, quelle mémoire peut être lue, quelle trace prouve un refus ou une validation |
Touba ne supprime pas ces responsabilités. Il les rapproche du programme : elles deviennent plus visibles dans le source .tb, l'AIR, le manifest, le runtime et les traces.
Sources de vérification des exemples externes
- Python : documentation officielle sur les fonctions et le contrôle de flux, docs.python.org.
- Java : tutoriel officiel Oracle sur les objets, classes et méthodes, concepts objet et classes.
- TypeScript : handbook officiel sur les types, fonctions et propriétés
readonly, types du quotidien, fonctions et types objet.