Lot 47 : outil subagent du mode Code exécuté par la façade

Le builder délègue à un rôle en lecture seule (explorer, code-reviewer,
planner) sans passer par /tool/exec : src/chat/subagent.rs porte
toolSubagent. Mêmes refus avant tout appel au moteur (rôle inconnu,
verifier compris, tâche vide, récursion), contexte isolé servi par le Go
(POST /internal/v1/turn/subagent : prompt du rôle + briefing machine,
outils, capacités ; subagentView et subagentFraming partagés), tour
imbriqué run_turn avec l'endpoint, le raisonnement, l'écho et l'arrêt du
tour qui délègue, température 0,6, travail annexe (second slot, effacé à
la fin), télémétrie à part. Seul le texte écrit après le dernier outil
revient, rogné, préfixé du rôle, tronqué à 6000 runes.

Le tour du rôle est porté par SubHost, qui délègue tout à son GoTurnHost
sauf un nouvel appel à subagent ; les outils de la façade passent dans
GoTurnHost::exec_tool_base, que seul le tour principal complète de
subagent (le type du tour imbriqué ne se contient pas lui-même).

Parité : journal tiré du Go contre un faux moteur scripté
(parity_subagent_test.go → tests/parity_subagent/subagent.json : refus,
réponse directe, rapport après deux outils, texte vide relancé, rapport
tronqué, cadrage du planner, moteur qui refuse ; requêtes du rôle),
rejoué par un tour complet de la façade (tests/lot6.rs) ;
TestSubagentVueInterne côté Go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
Claude committed 2026-10-16 07:12:29 +00:00
1 parent 92e9adee7d
commit ded1c97c17
16 files changed
+1846 -73

No files matched your search

+21 -10
View File
@@ -90,17 +90,10 @@ func toolSubagent(ctx context.Context, args map[string]any, parent Caps) string
return "[erreur] un sous-agent ne peut pas en appeler un autre — réponds avec ce que tu as"
}
caps := Caps{Agent: true, Code: true, Role: role, Mem: MemOff}
// Cadrage : le rôle ne voit ni la discussion ni l'utilisateur. Son prompt de
// rôle parle à un humain (« termine en demandant confirmation ») ; ici son
// interlocuteur est le builder, et sa réponse est un résultat d'outil.
framing := "\n\nAnswer with your findings only: what you found and where (file:line). No preamble."
if role == "planner" {
framing = "\n\nYou are a delegated pass: nobody will answer you. Record the acceptance criteria with the criteria tool, then give the ordered steps. Do not ask for confirmation."
}
caps, system := subagentView(role)
msgs := []Message{
{Role: "system", Content: rolePrompt(role) + "\n\n" + machineSystemPrompt(caps)},
{Role: "user", Content: task + framing},
{Role: "system", Content: system},
{Role: "user", Content: task + subagentFraming(role)},
}
var out strings.Builder
// Le sous-agent occupe le slot de la conversation du builder : une fois son
@@ -133,3 +126,21 @@ func toolSubagent(ctx context.Context, args map[string]any, parent Caps) string
}
return "[" + role + "]\n" + res
}
// subagentView : les capacités et le prompt système d'un rôle délégué —
// servis aussi à la façade (/internal/v1/turn/subagent, lot 47).
func subagentView(role string) (Caps, string) {
caps := Caps{Agent: true, Code: true, Role: role, Mem: MemOff}
return caps, rolePrompt(role) + "\n\n" + machineSystemPrompt(caps)
}
// subagentFraming : le cadrage ajouté à la tâche. Le rôle ne voit ni la
// discussion ni l'utilisateur. Son prompt de rôle parle à un humain
// (« termine en demandant confirmation ») ; ici son interlocuteur est le
// builder, et sa réponse est un résultat d'outil.
func subagentFraming(role string) string {
if role == "planner" {
return "\n\nYou are a delegated pass: nobody will answer you. Record the acceptance criteria with the criteria tool, then give the ordered steps. Do not ask for confirmation."
}
return "\n\nAnswer with your findings only: what you found and where (file:line). No preamble."
}