fix(agent): read-only plans execute without approval
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

SOUL.md step 4: all-read-only plans skip the approval wait and execute
immediately. Only config_mutation/destructive steps need operator approval.
set_goal + propose_plan return text updated to match.

Fixes 3/4 eval failures where the agent proposed a plan then waited
for approval on a read-only task.
This commit is contained in:
2026-07-15 10:12:51 +02:00
parent f1dda7290a
commit 844cfe5888
2 changed files with 17 additions and 11 deletions

View File

@@ -185,7 +185,7 @@ func (a *agent) handleTaskTool(ctx context.Context, sessionID, name string, args
// what the SOUL.md "approve the plan, not each step" model actually
// describes. set_goal records the goal + flips status to executing
// and nothing more.
return "Goal set: " + goal + ". NEXT: pre-plan with read-only tools (search_knowledge, get_entity, list_lxcs, get_relations), then propose_plan (mandatory — even read-only tasks need a one-step plan; the run handler refuses without one). Do not call run before propose_plan.", true
return "Goal set: " + goal + ". NEXT: pre-plan with read-only tools (search_knowledge, get_entity, list_lxcs, get_relations), then propose_plan (mandatory — even read-only tasks need a one-step plan; the run handler refuses without one). After propose_plan: if all steps are read-only, execute immediately (no approval needed). If any step is config_mutation/destructive, stop and wait for operator approval.", true
case "propose_plan":
raw, _ := args["steps"].([]any)
@@ -249,7 +249,7 @@ func (a *agent) handleTaskTool(ctx context.Context, sessionID, name string, args
// the agent forgot), so the old advisory nudge is replaced by the
// structural gate: D.1 refuses complete_task without the actual
// update_entity_attributes/create_relationship calls.
result := fmt.Sprintf("Plan set (%d steps)%s. STOP. Wait for operator approval — do not call run yet. Approval vocabulary: \"approved\", \"yes\", \"go\", \"proceed\", \"continue\", \"ok\", \"go ahead\". On approval, advance with update_plan_step + run. Do not call propose_plan again.", len(persisted), appendedNote)
result := fmt.Sprintf("Plan set (%d steps)%s. If all steps are read-only, execute now — call update_plan_step(running) + run for each step, no approval needed. If any step is config_mutation/destructive, STOP and wait for operator approval (\"approved\", \"yes\", \"go\", \"proceed\", \"continue\", \"ok\", \"go ahead\"). Do not call propose_plan again.", len(persisted), appendedNote)
return result, true
case "update_plan_step":