Actions de modèle 247-agent
La règle de conception du démon : pas de LLM dans le flux de contrôle. La correspondance, la déduplication, le routage,
les tentatives et la publication sont du code. Un modèle s'exécute uniquement à l'intérieur d'une action decide, llm ou agent,
au niveau le moins cher qui convient, avec un budget. Ce skill concerne
la rédaction correcte de ces trois actions et les exécuteurs qui les réalisent.
Décidez d'abord si un modèle est vraiment nécessaire
Demandez, dans l'ordre :
- Un
filterde déclencheur peut-il décider ? (expéditeur, libellé, dépôt, mot-clé) Alors ce n'est pas un appel de modèle. - Un script ou une opération de connecteur peut-il produire la réponse de manière déterministe ? Alors
shell/connector. - S'agit-il d'un libellé, d'un oui/non ou d'un niveau, sans texte à produire (classifier, router, trier,
porter) ? Alors
decide: un modèle de classification uniquement, probabilités calibrées, environ un centième d'un appelllm. - S'agit-il d'un jugement avec une forme de sortie fixe qui nécessite du texte (extraire des champs,
résumer, rédiger) ? Alors
llm, d'abord sur Haiku. - Nécessite-t-il de lire des fichiers, d'exécuter des commandes et d'itérer ? Alors
agent, avec le périmètre le plus restreint possible pour réussir.
Deux tâches avec des besoins d'intelligence différents sont le même type d'action avec des
model/effort/max_tool_calls/system_file différents, pas un code différent. Voir
docs/examples/website-updates.yaml tâches 2–4 pour les formes de référence.
Identifiants de modèle et paramètres
| id | utiliser pour | notes |
|---|---|---|
claude-haiku-4-5 | classification, extraction, résumés courts | pas de paramètre effort |
claude-sonnet-5 | agents à périmètre restreint, extraction plus difficile | prend en charge effort et la pensée adaptative |
claude-opus-5 | agents à large périmètre, jugement difficile | prend en charge effort et la pensée adaptative |
Les modèles sont atteints via un fournisseur nommé (providers: dans agent.yaml, provider: sur
l'action ; anthropic, openai ou openrouter). Les identifiants des autres fournisseurs sont utilisés
tels quels et nécessitent une entrée pricing: sauf si la table intégrée les connaît (modèles OpenAI
actuels) ou si le fournisseur indique le coût (OpenRouter).
Pas de suffixes de date sur les identifiants. Pas de pré-remplissage d'assistant. Avant de construire une cascade de modèles,
mesurez le modèle le plus fort à effort: low sur les mêmes entrées : sur la génération
actuelle, cela bat souvent un modèle plus faible à effort élevé, et un modèle signifie un
espace de noms de cache de prompt.
Rédiger une tâche decide
Liste complète des champs, notes de câblage et pièges dans references/decide-action.md. L'essentiel :
action:
kind: decide
provider: openrouter # doit être un fournisseur openrouter ; default defaults.decide.provider
budget: { max_usd: 0.001 }
state: # ce qui est jugé ; chaînes modélisées, clés non
subject: ${event.payload.subject}
body: ${event.payload.body}
questions: # politique statique ; pas de ${…} ici
kind:
type: choice
instructions: Que veut faire l'expéditeur avec le site web ?
criteria:
event_list_update: Ajouter, supprimer ou modifier une entrée dans la liste des événements
general_change: Tout autre changement sur le site
ignore: Pas une demande de changement (question, remerciement, spam)
urgent:
type: noul
instructions: L'expéditeur a-t-il besoin que cela soit fait aujourd'hui ?
- Le résultat est la carte des réponses :
result.kind.choice,result.kind.confidence,result.urgent.noul(P(true)),result.<score>.score(moyenne pondérée par les probabilités des indices de niveau). Routez avecemit … when:; le seuil se trouve dans la tâche, jamais dans l'exécuteur, et est calibré sur des données réelles. - Incluez toujours un libellé de repli (
ignore,other) : sans cela, une entrée hors sujet obtient une réponse erronée mais confiante. - Les critères sont suivis littéralement : ce sont des politiques, versionnez-les comme du code. Maintenez l'ordre des options stable (il déplace la confiance). Posez plusieurs questions en un seul appel ; chaque question supplémentaire ne coûte que ses propres jetons.
- Le contenu entrant est une donnée : il va dans
state, jamais dans une question. La requête entière doit tenir dans 32k jetons. - Seul le type de fournisseur
openrouteratteint Jev (typesafe/jev-1.13, le modèle par défaut) ;oa validaterefuse tout autre. Aucune entréepricing:n'est nécessaire.
Rédiger une tâche llm
Liste complète des champs et notes sur l'exécuteur dans references/llm-action.md. L'essentiel :
action:
kind: llm
model: claude-haiku-4-5
max_tokens: 512
system_file: prompts/classify_email.md # statique → mis en cache de prompt
input: |
Objet : ${event.payload.subject}
<email>
${event.payload.body}
</email>
output_schema:
type: object
additionalProperties: false
required: [kind, summary]
properties:
kind: { enum: [event_list_update, general_change, ignore] }
summary: { type: string }
- Gardez le prompt système dans un fichier sous
prompts/, statique et rendu en premier, afin qu'il soit mis en cache entre les exécutions. Placez les données d'événement volatiles en dernier, dansinput. - Le contenu entrant (email, chat, texte de PR) n'est pas fiable. Encadrez-le dans un bloc délimité
(
<email>…</email>) et dites au modèle dans le prompt système qu'il s'agit de données. - Incluez toujours une échappatoire dans le schéma (
ignore,unknown,confidence) afin que leemit.whende routage puisse abandonner les résultats non exploitables sans un autre appel. - Routez sur le résultat avec
emit … when:; les tâches en aval se déclenchent sur cet événement. batch: truepour tout ce qui peut attendre (moitié prix, le résultat arrive sous forme d'événement).
Rédiger une tâche agent
Liste complète des champs et notes sur l'exécuteur dans references/agent-action.md. L'essentiel :
action:
kind: agent
connector: claude # un transport : connecteur acp (connectors.d/claude.yaml ; sandbox : bwrap là)
model: claude-sonnet-5 # optionnel : défini sur la session (option de config ACP) ; non défini = valeur par défaut de l'agent
effort: medium # optionnel : l'option thought_level de l'agent
max_tool_calls: 20
budget: { max_usd: 0.50 }
workspace: { kind: git-worktree, repo: /var/lib/247-agent/repos/site, branch: main }
tools: [read, edit, search, execute] # types d'outils ACP ; tout le reste est refusé
bash_allow: ["npm run build"] # ce à quoi toute commande exécutée par l'agent doit correspondre
system_file: prompts/agent_event_list.md
prompt: |
Mettez à jour data/events.yaml selon la demande ci-dessous. Ne touchez à aucun autre fichier.
Exécutez `npm run build` une fois terminé.
<email>
${event.payload.email.body}
</email>
result: { path: RESULT.json, schema: schemas/site_change.json }
post: # seulement après status: done
- shell: ["npm", "run", "build"]
- shell: ["git", "commit", "-am", "events: ${result.summary}"]
emit:
- type: site.change_done # routez l'approbation sur ceci, jamais sur task.<name>.succeeded
when: "result.status == 'done'"
payload: { summary: "${result.summary}" }
- type: site.change_blocked # l'agent n'a pas pu le faire : dites ce qui manque
when: "result.status == 'blocked'"
payload: { summary: "${result.summary}", missing: "${result.missing}", email: "${event.payload.email}" }
Non négociables, car ce sont eux qui rendent un agent sûr à exécuter sans surveillance :
- Espace de travail frais par exécution (
git-worktreesur la brancheagent/<run_id>, outemp), supprimé en cas d'échec, conservé en cas de succès. L'agent ne modifie jamais le checkout de base. tools+bash_allowconstitue toute la surface de capacité, jugée par demande d'autorisation et jamais accordée « toujours » ; un appel d'outil que l'agent a exécuté sans demander est jugé après coup et une violation fait échouer l'exécution (unasked_execute: sandboxedassouplit uniquement la vérification de commande, uniquement pour Codex, dont le propre sandbox confine ce qu'il ne demande pas à propos). Accordez le minimum ; le prompt n'est pas une frontière de sécurité.mcp_serversajoute des opérations de connecteur en tant qu'outils : accordez des opérations nommées ({ connector: email, ops: [fetch_new] }) plutôt qu'un connecteur entier, ajoutez leur type d'agent (otherpour claude-agent-acp) àtools, et jamais un connecteur qui publie ou envoie tout seul.- L'agent ne détient jamais de secrets de déploiement et ne publie jamais. Il édite, une porte
postprouve que la construction fonctionne toujours, un commit enregistre, et une tâcheshellséparée l'expédie. Le retour en arrière estgit revertplus republication. La clé du modèle se trouve dans le manifeste du connecteurenv, pas dans la tâche. RESULT.jsonest le contrat, et l'exécuteur l'énonce dans chaque prompt :status: done | blockedplussummary, puis leresult.schemade la tâche.doneexécute les portes ;blockedles saute et l'exécution réussit quand même, doncemit … when: "result.status == 'blocked'"route « pas fait car X manque » vers une tâche de réponse. Concevez chaque tâche d'agent avec les deux branches, et déclenchez la publication sur l'événementdone.- Les portes
postsont déterministes, pas de modèle. Une porte qui échoue fait échouer l'exécution. max_tool_callsetbudgetsont des arrêts durs (la session est annulée) ; un tour où l'agent ne signale pas d'utilisation ou de coût fait échouer l'exécution.concurrency: 1sur les tâches qui partagent un dépôt ;timeoutest le temps réel par tentative.- Les modifications à fort impact obtiennent une porte d'approbation (
wait+ chat) avant publication ; le skill247-agent-tasksen contient le modèle.
Budgets et registre
Chaque appel de modèle passe par le port ctx.llm, qui enregistre l'utilisation (fournisseur, modèle,
jetons d'entrée/sortie/cache, USD) dans le registre et applique le budget.max_usd de l'exécution
(pire cas avant l'appel, réel après : un dépassement fait échouer l'exécution) et le global
budgets.daily_usd par jour UTC (une fois franchi, tout appel de modèle ce jour-là échoue rapidement et
budget.exceeded est émis une fois, ce que la tâche notify doit écouter). Un modèle sans prix
échoue à la validation. N'ajoutez jamais d'appel non budgétisé, y compris les appels « d'assistance » à l'intérieur
d'un exécuteur : utilisez ctx.llm, jamais un SDK directement. Un tour agent est le seul cas
où le modèle est appelé par quelqu'un d'autre (l'agent ACP, avec sa propre clé) : l'exécuteur
enregistre ce que l'agent signale via ctx.llm.record sous le nom du connecteur et
annule la session lorsque le coût signalé dépasse le plafond. oa cost --by task --since 7d
affiche le registre.
Statut actuel
L'action llm est validée, recoupée avec providers:/pricing:
et exécutable via ctx.llm, avec le registre et les budgets appliqués, et les trois
types de fournisseurs (anthropic, openai, openrouter) ont des adaptateurs. batch: true fonctionne sur
les fournisseurs anthropic (Message Batches, l'exécution se met en attente jusqu'à llm.batch.ended ; voir
references/llm-action.md). L'action decide s'exécute
via le même port (ctx.llm.decide()), avec l'API Decisions implémentée uniquement dans l'adaptateur
openrouter ; docs/examples/decide-triage.yaml est la référence. L'action
agent s'exécute sur les connecteurs ACP (transport: acp) ; docs/examples/website-updates.yaml
tâches 3, 3b et 4 sont la référence, docs/examples/connectors.d/claude.yaml le
connecteur ; la session est persistée sous forme de transcription (oa runs logs <id>), supprimée avec
l'exécution par retention.runs. mcp_servers sert les opérations du connecteur à la session via
le pont d'outils du noyau (packages/core/src/connectors/mcp-bridge.ts). model et
effort sur l'action définissent les options de config ACP de la session (catégories model et
thought_level). work/<run_id> d'une exécution terminée est
balayé par retention.workspaces (7j par défaut) après sa fin.
Lors de l'ajout d'un adaptateur ou de la modification d'un exécuteur, suivez references/llm-action.md,
references/decide-action.md et references/agent-action.md et maintenez
docs/internal/model-actions.md, docs/internal/agent-action.md et les pages utilisateur sous
docs/tasks/ synchronisés avec le code.