JJoeven

Projects/Autonomous Ops Agent/Part 4

HUMAN_APPROVAL Gate

Implement execute() so every mutate tool checks an approval map, records attempts, and never changes the world on deny or missing approval.

The gate is the load-bearing wall. If it is a comment in the prompt ("please don't rollback without asking"), it will fail. This part implements execute(proposal, approval_map) in Python. The invariant: WORLD does not change unless approval_map.get(proposal_id) == "allow" and the tool is in the mutate allowlist and args match the proposal (no bait-and-switch).

Bait-and-switch

A hostile policy proposes rollback to d43, gets approval, then executes rollback to d0-experimental. Prevent this by approving a hash of the proposal (tool + canonical JSON args), not a bare id. Store:

HUMAN_APPROVAL[action_id] = {"decision": "allow", "digest": sha256(canonical)}

Execute recomputes digest; mismatch → deny.

execute outcomes

CaseResultWorld
read toolrununchanged (reads)
mutate, no keyneeds_approvalunchanged
mutate, denydeniedunchanged
mutate, allow, digest matchappliedchanged
unknown toolunknown_toolunchanged
digest mismatchtamperunchanged

Simulate rollback effect

On approved rollback, set error_rate to 0.004, p95_ms to 220, deploy_id to to, append a log line rollback applied. That lets the loop detect resolution.

Live Pythonpython
Output
Run to execute this in your browser. Nothing is sent to a server.

Step-by-step gate wiring in the agent loop

  1. Diagnose + propose (part 3).
  2. Call execute. If needs_approval, finish with status awaiting_approval and include digest + proposal in the incident draft. Do not retry execute in a for-loop.
  3. A second run (new process or next user click) loads the same proposal and an updated HUMAN_APPROVAL.
  4. On applied, re-read metrics; if not burning, go to report.
  5. On denied / tamper, report and stop.

In the Try it box you can run both phases in one script: first execute empty approval, then set approval, execute again.

page_oncall

If you add it, decide: is it mutate? It pages a human — noisy, not destructive. You can allow it without digest, but rate-limit to 1 per incident. Still audit.

Watch out:Logging approval maps must not become a way to smuggle decision: allow from the model. The model must not write HUMAN_APPROVAL. Only the supervisor / UI writes it.

Dual control and expiry

Production gates add expiry: an approval older than 15 minutes is dead. Incidents drift; a rollback approved for d44 must not apply after someone already shipped d45. In this project you can store approved_at_step and reject if current_step - approved_at_step > 3. Also bind deploy_id at proposal time: if WORLD's current deploy changed, digest still matches the old args but you should re-check args["to"] == prev_deploy_id now. Stale approvals are a real outage class.

Dual control (two humans) is a product flag. Your tests should still pass with one fake human. The code path is the same: more keys in the approval record, all(decisions == allow).

Never implement "auto-approve if sev1." Severity is a paging signal, not a sudoers file. The eval that forbids mutate without approval must not have a severity exception, or you will ship the exception.

Exercise

Write a test: approval allow for act-01, execute act-01 with extra args key force: true → digest mismatch → tamper → world unchanged. Add restart_service impl that requires approval and sets a log line without changing deploy_id.

Check your understanding

What does the gate approve — the action id string alone?