Aller au contenu
astorlm
Langue: Français
← Carte

Niveau 15

Les sous-agents

Certaines missions sont lourdes et autonomes. Confiez-les à un autre agent : il démarre avec un historique propre, fait les fouilles, et ne renvoie que ce dont vous avez besoin.
1/22 Plis du bandonéon :
  • user
  • assistant
  • tool_result
Une agence de détectives. En haut, l'Oracle et Astor au QG. En bas, deux fenêtres de terrain vides : personne n'est encore sur une affaire.

EventBus

Le problème

Beaucoup de demandes cachent des missions à l'intérieur : éplucher vingt annonces, lire une longue page, parcourir quarante avis, fouiller une base de code pour trouver une fonction. L'agent a besoin de la réponse à chaque mission. Il n'a pas besoin des fouilles.

C'est Hoarder, l'agent qui fait chaque course lui-même. Chaque résultat de recherche et chaque page rejoignent son historique, et la boucle renvoie tout cela à chaque tour suivant. Quand il revient enfin à votre question, il la lit sous une pile d'annonces dont il n'a eu besoin qu'une minute. Les requêtes sont lourdes, le modèle se disperse, et une mission de plus le pousse au-delà de la fenêtre.

La compaction (niveau 7) peut élaguer la pile après coup. Mieux vaut ne pas la construire du tout.

La solution

Confiez la mission à un sous-agent. Un sous-agent est un agent complet, avec sa propre boucle, ses propres appels au modèle et ses propres outils, que le parent voit comme un seul outil. Quand le parent l'appelle, un agent neuf démarre avec un historique vide et un message : le brief rédigé par le parent. Il fait les fouilles, répond, et il est jeté. Le parent ne reçoit que cette réponse, comme un résultat d'outil ordinaire.

  • Tout faire soi-même

    Un agent, tous les outils. Il lance chaque recherche et lit chaque page lui-même.

    Chaque résultat brut reste dans son historique, et chaque tour suivant le renvoie. Les missions enterrent la question.

  • Sous-agent

    Confier la mission à un autre agent, exposé comme un outil. Il démarre vide, reçoit un brief et ses propres outils, et répond en quelques lignes.

    L'agent parent reste petit et concentré. Le prix : plus d'appels au modèle au total, et le sous-agent ne sait que ce que dit le brief.

  • Workflow

    Votre code appelle les agents, dans un ordre que vous avez écrit (niveau 1). Personne ne décide de déléguer : vous l'avez décidé, à l'avance.

    Prévisible et facile à raisonner. Ça ne marche que si vous connaissez les étapes avant que la demande n'arrive.

Deux choses viennent gratuitement. Si le modèle demande deux sous-agents dans le même message, la boucle les exécute en parallèle, comme n'importe quels deux appels d'outils. Et chaque sous-agent peut avoir un system prompt différent, un jeu d'outils plus restreint, voire un modèle plus petit : l'éclaireur qui lit des avis n'a aucune raison de réserver quoi que ce soit.

Les agents de code s'en servent sans arrêt : « explore le dépôt et dis-moi où l'authentification est gérée » part chez un sous-agent qui fait un grep dans cinquante fichiers et revient avec trois lignes.

Les personnages

Les mêmes personnages que d'habitude, cette fois dans une agence de détectives.

Le QG l'agent parent
La boucle du niveau 2 : Astor, l'Oracle et le bandonéon. Ses seuls outils, ce sont les deux éclaireurs.
Le télégraphe les outils sous-agents
Là où tournent milonga_scout et food_scout. Un brief descend le fil comme entrée de l'outil ; un télégramme remonte comme son résultat.
Une fenêtre de terrain une exécution de sous-agent
Un agent complet : un éclaireur, son propre Oracle, ses propres outils (les deux boutiques) et son propre bandonéon. Le compteur de la fenêtre, c'est son contexte. Quand il répond, il disparaît.
L'épaisseur des plis tokens
Dans ce niveau, un pli est aussi épais que son message est lourd. Un télégramme de trois lignes est une lamelle. Une page d'avis est un pavé.

Regardez les deux barres du haut. Parent, c'est ce que pèse vraiment la requête du parent. All in 1, c'est ce qu'elle pèserait si le parent avait fait les deux missions lui-même, avec chaque page dans son propre historique : elle finit au-delà de la ligne de compaction.

Les lignes subagent du journal d'événements sont les événements propres aux éclaireurs. L'EventBus du parent ne les voit jamais : il ne reçoit que le début et la fin de chaque outil sous-agent.

Le code

Avec astorlm : createSubagentTool enveloppe un fournisseur, un system prompt et un jeu d'outils en un seul outil que le parent peut appeler. Chaque appel lance un nouvel agent enfant, exécute le brief jusqu'au bout et renvoie son texte final. Annuler le parent annule l'enfant.

À partir de zéro : La boucle du niveau 2, qui prend ses outils en argument. Un sous-agent est un outil dont le corps rappelle cette boucle, avec de nouveaux messages et moins d'outils.

import { OpenAIProvider, createLocalAgent, createSubagentTool, tool } from 'astorlm'
import { z } from 'zod'

// Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
const LLM = { baseURL: 'http://localhost:11434/v1', apiKey: 'YOUR_API_KEY' } // local servers usually ignore the key
const provider = new OpenAIProvider({ ...LLM, model: 'your-model' }) // e.g. 'llama3.1', 'gpt-4o-mini'

// The heavy tools: each one returns whole listings, pages or reviews.
const searchEvents = tool({
  name: 'search_events',
  description: 'Search tango events by neighborhood and date. Returns every match with its blurb.',
  schema: z.object({ neighborhood: z.string(), date: z.string() }),
  execute: async ({ neighborhood, date }) => eventsApi.search(neighborhood, date), // your code
})
const readPage = tool({
  name: 'read_page',
  description: 'Read a web page and return its text.',
  schema: z.object({ url: z.string() }),
  execute: async ({ url }) => fetchText(url), // your code
})
const searchPlaces = tool({
  name: 'search_places',
  description: 'Search restaurants near a street, with their opening hours.',
  schema: z.object({ near: z.string() }),
  execute: async ({ near }) => placesApi.search(near), // your code
})
const readReviews = tool({
  name: 'read_reviews',
  description: 'Read the latest reviews of one restaurant.',
  schema: z.object({ place: z.string() }),
  execute: async ({ place }) => placesApi.reviews(place), // your code
})

// Each subagent is a whole agent, handed to the parent as ONE tool.
// It gets its own system prompt, only the tools it needs, and a fresh history on every call.
const milongaScout = createSubagentTool({
  name: 'milonga_scout',
  description: 'Finds tango events. Give it a full brief: it knows nothing else about the conversation.',
  provider, // could be a smaller, cheaper model
  systemPrompt: 'You find milongas in Buenos Aires. Reply in 3 lines: name, address, times. No lists, no links.',
  tools: [searchEvents, readPage],
  maxTurns: 6,
})
const foodScout = createSubagentTool({
  name: 'food_scout',
  description: 'Finds places to eat. Give it a full brief: it knows nothing else about the conversation.',
  provider,
  systemPrompt: 'You find restaurants in Buenos Aires. Reply in 3 lines: name, address, why.',
  tools: [searchPlaces, readReviews],
  maxTurns: 6,
})

// The parent only sees two tools. It never gets the listings, pages or reviews: just each scout's final text.
const agent = await createLocalAgent({
  provider,
  systemPrompt: 'You plan evenings out. Send the scouts out with a clear brief each, then put their answers together.',
  tools: [milongaScout, foodScout],
  maxTurns: 6,
})

const answer = await agent.run('I’m staying in San Telmo. Find me a milonga for Saturday night, and somewhere to eat nearby before it.')
console.log(answer.content)
// Both scouts were asked for in one message, so the loop ran them in parallel.
// Cancelling the parent (abortSignal) cancels any scout still out.

Points de vigilance

  • Le brief, c'est tout ce qu'il sait. Le sous-agent n'a jamais vu la conversation. « Trouve celui dont on a parlé » ne veut rien dire pour lui. Demandez au parent, dans la description de l'outil, d'écrire un brief complet : l'objectif, les contraintes, et ce à quoi ressemble une bonne réponse.
  • Demandez une forme courte et fixe. Tout l'intérêt, c'est un petit résultat. Un system prompt du genre « réponds en 3 lignes : nom, adresse, horaires » évite que l'éclaireur recolle sa pile chez le parent.
  • Ça économise du contexte, pas de l'argent. Les fouilles ont quand même lieu, dans les requêtes d'un autre agent. Souvent, ça coûte plus cher au total. Utilisez des sous-agents quand la concentration du parent en vaut la peine, et donnez-leur un modèle moins cher quand la mission le permet.
  • Ne découpez que ce qui est indépendant. Deux éclaireurs peuvent tourner côte à côte parce qu'aucun n'a besoin de l'autre. Si la deuxième mission a besoin de la réponse de la première, appelez-les l'un après l'autre, ou gardez tout dans un seul agent.
  • Restreignez ses outils, et plafonnez la profondeur. Ne donnez à chaque sous-agent que les outils dont sa mission a besoin, et réfléchissez à deux fois avant de lui donner ses propres sous-agents. Chaque niveau multiplie les appels, et un échec tout au fond arrive sous la forme d'une seule ligne confuse.