mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-11 17:26:57 +02:00
Reprise de l'idée des sous-agents d'OpenFox, sur la mécanique déjà en place pour la passe de vérification : un runChat isolé, non persisté. L'outil `subagent` délègue une question bornée à un rôle qui travaille dans SON propre contexte et ne rend que sa réponse. Sur un modèle local, c'est la fenêtre de contexte qu'on sauve : « trouve où est géré le cache » coûte dix lectures de fichiers qui restaient ensuite dans l'historique jusqu'à la compaction, alors que seule la réponse comptait. Les rôles explorer et code-reviewer, jusqu'ici définis mais jamais appelés, deviennent utilisables. Tous les rôles délégués sont en LECTURE SEULE : pas de write/edit (ce qui modifie le dépôt reste dans le fil principal, sous les yeux de l'utilisateur), pas de subagent (aucune récursion), pas de mémoire ni de web. Seul le planner pose des critères — son prompt le lui demande — et marquer un critère « passé » reste le privilège de la passe de vérification. Le rôle verifier n'est PAS délégable : le builder se décernerait son propre satisfecit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K6CAgoLJzufeA8rZTSpSpY
57 lines
2.1 KiB
Go
57 lines
2.1 KiB
Go
package loki
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Les garde-fous de l'outil subagent doivent répondre SANS appeler le modèle :
|
|
// un rôle inconnu, une tâche vide ou un appel depuis un sous-agent sont refusés
|
|
// à l'entrée (sinon chaque faute de frappe coûte une génération complète).
|
|
func TestSubagentRefusesBeforeAnyInference(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
args map[string]any
|
|
parent Caps
|
|
want string
|
|
}{
|
|
{"rôle inconnu", map[string]any{"role": "architecte", "task": "x"}, Caps{Code: true}, "rôle inconnu"},
|
|
{"rôle verifier refusé", map[string]any{"role": "verifier", "task": "x"}, Caps{Code: true}, "rôle inconnu"},
|
|
{"tâche vide", map[string]any{"role": "explorer", "task": " "}, Caps{Code: true}, "tâche vide"},
|
|
{"pas de récursion", map[string]any{"role": "explorer", "task": "x"}, Caps{Code: true, Role: "explorer"}, "ne peut pas en appeler un autre"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := toolSubagent(context.Background(), tc.args, tc.parent)
|
|
if !strings.HasPrefix(got, "[erreur]") || !strings.Contains(got, tc.want) {
|
|
t.Fatalf("attendu un refus contenant %q, obtenu %q", tc.want, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Un rôle délégué travaille en LECTURE SEULE : ni write/edit (les modifications
|
|
// restent dans le fil principal), ni subagent (pas de récursion), et seul le
|
|
// planner pose des critères.
|
|
func TestSubagentRolesAreReadOnly(t *testing.T) {
|
|
forbidden := map[string]bool{"write": true, "edit": true, "subagent": true, "mem_add": true, "mem_edit": true, "web_search": true}
|
|
for _, role := range subagentRoles {
|
|
names := map[string]bool{}
|
|
for _, tool := range EnabledTools(Caps{Agent: true, Code: true, Role: role}) {
|
|
names[tool.Function.Name] = true
|
|
}
|
|
for f := range forbidden {
|
|
if names[f] {
|
|
t.Fatalf("le rôle %s ne devrait pas avoir l'outil %s", role, f)
|
|
}
|
|
}
|
|
if !names["read"] || !names["grep"] {
|
|
t.Fatalf("le rôle %s devrait pouvoir lire le code", role)
|
|
}
|
|
if got := names["criteria"]; got != (role == "planner") {
|
|
t.Fatalf("critères pour %s : %v (attendu %v)", role, got, role == "planner")
|
|
}
|
|
}
|
|
}
|