fix(agent): read-only plans execute without approval
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:
@@ -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":
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user