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 // what the SOUL.md "approve the plan, not each step" model actually
// describes. set_goal records the goal + flips status to executing // describes. set_goal records the goal + flips status to executing
// and nothing more. // 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": case "propose_plan":
raw, _ := args["steps"].([]any) 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 // the agent forgot), so the old advisory nudge is replaced by the
// structural gate: D.1 refuses complete_task without the actual // structural gate: D.1 refuses complete_task without the actual
// update_entity_attributes/create_relationship calls. // 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 return result, true
case "update_plan_step": case "update_plan_step":

View File

@@ -30,16 +30,22 @@ Call ONCE with EVERY step end-to-end. The LAST step MUST be:
Include target slugs on each step so the panel links them. If you omit the Include target slugs on each step so the panel links them. If you omit the
writeback step, one is auto-appended. writeback step, one is auto-appended.
### 4. GET APPROVAL — stop and wait ### 4. GET APPROVAL — only if the plan has config_mutation/destructive steps
After proposing the plan, END YOUR TURN. Do not call `run`. Do not execute. After proposing the plan, check the step risk classes:
Wait for the operator to approve. Approval vocabulary: "approved", "yes", - **All read-only plan?** No approval needed. Go straight to step 5 and
"go", "proceed", "continue", "ok", "go ahead". The plan window then execute — read-only `run` commands auto-run immediately once a plan
auto-approves all subsequent config_mutation commands. exists. Do NOT stop and wait.
- **Any config_mutation or destructive step?** END YOUR TURN. Do not call
`run`. Wait for the operator to approve. Approval vocabulary: "approved",
"yes", "go", "proceed", "continue", "ok", "go ahead". The assent window
then auto-approves subsequent config_mutation commands.
### 5. EXECUTE — `run` calls auto-run under the plan window ### 5. EXECUTE — `run` calls
Once approved, advance each step with `update_plan_step` (running → done) + Advance each step with `update_plan_step` (running → done) + `run`. Do NOT
`run`. Do NOT call `propose_plan` again — it is refused once a step has call `propose_plan` again — it is refused once a step has started.
started. Config_mutation commands auto-execute without per-action approval. Read-only commands auto-run (no approval). Config_mutation commands
auto-run under the assent window (after approval). Destructive commands
always need explicit typed confirmation.
### 6. WRITE BACK + COMPLETE — `complete_task` ### 6. WRITE BACK + COMPLETE — `complete_task`
Call `update_entity_attributes` for every entity you ran `run` against Call `update_entity_attributes` for every entity you ran `run` against