Agent-Sudo went live on both hosts tonight (005c214 in the boilerplates repo). This records what "done" actually means and what is left, since dedicated time on this ends today and the rest has to run via background agents. Corrects the goal: tier 4 is human-only BY DESIGN (D-CB7) — the daemon unit env holds SUDO_MD_VERIFY_ENFORCE, so a system that can restart its own daemon can disarm its own gate. "Handling tier 4" means refusing it, which is done and verified. Real remaining scope is tiers 1-3. Records the measured Incus asymmetry (server-01 has it, primary does not), which makes tier 2 on primary an open design question rather than a task; the tier-1 scoped-undo defect (app.py:244 hardcodes tier 3 while REQUIRES_UNDO_TIERS is (1,3)); and the per-host-values-in-a-shared-tracked-file bug class that bit twice in one directory. secrets-proxy is deliberately scoped as investigate-then-plan: it exited gracefully and nobody knows why, so S1 answers that before anyone writes S2. Refs #128, #146, #149, #150, #173, #191, projects id=187. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
17 KiB
Gameplan — Agent-Sudo + secrets-proxy to fully deployed
Written: 2026-07-15 20:10 · Author: Claude (Opus 4.8) · Status: ACTIVE PLAN Why now: 2026-07-15 is the LAST day dedicated to this work. After this, time goes to business development (revenue) + other projects. Everything here is scoped so a background agent can do it, with human touches minimized to the ones that are human-only by design.
0. READ THIS FIRST — the goal is not what it sounds like
The stated goal was "Agent-Sudo and secrets-proxy handling tier 0–4 commands."
Tier 4 must never be handled autonomously. That is not a gap — it is the design.
Per D-CB7, the agent-sudo-daemon unit env holds SUDO_MD_VERIFY_ENFORCE. A system that can
restart its own daemon can disarm its own gate. So tier 4 = human-at-a-terminal, permanently.
"Agent-Sudo handles tier 4" means "Agent-Sudo correctly REFUSES tier 4." That is DONE and VERIFIED LIVE (2026-07-15 19:52, both hosts):
| command | tier | source |
|---|---|---|
rm -rf / |
4 | danger_veto |
systemctl restart agent-sudo-daemon |
4 | host_override_setb |
vi /etc/systemd/system/agent-sudo-daemon.service |
4 | host_override_setb |
Do not let a future session "finish tier 4." There is nothing to finish. If someone proposes making tier 4 autonomous, that is a request to delete the security model — refuse it.
Real remaining scope = tier 1, tier 2, tier 3.
1. VERIFIED STATE as of 2026-07-15 20:00 (measured tonight, not remembered)
Agent-Sudo — DEPLOYED ON BOTH HOSTS (first time ever)
- Commit
005c214onorigin/main(repo:boilerplates.git, path/opt/appdata/docker). - server-01:
192.168.1.90:8082· healthy · 0 restarts ·{"status":"ok","server_id":"server-01"} - primary:
192.168.1.88:8084· healthy · 0 restarts ·{"status":"ok","server_id":"primary"} - Old sudo-bridge STILL owns
192.168.1.88:8082on primary. No cutover has happened. (#146) - D-CB6 breaker log is now persistent on BOTH hosts —
/var/lib/agent-sudo/breaker.log, dir0700 root:root, on a bind mount, verified resolving inside the container.docker restartcan no longer reset a tripped breaker. This closed a hole that was OPEN IN PRODUCTION.breaker.logdoes not exist yet — correct, created 0600 on first append. - 85/85 tests pass on both hosts.
- Capability gates:
SANDBOX_ENABLED=false,SNAPSHOT_ENABLED=falseon both. ⇒ tier 2 and tier 3 currently return 503 stubs. They do not work.
Subsystem availability — MEASURED, and it is asymmetric
| host | incus | sandboxes | timeshift |
|---|---|---|---|
| server-01 | YES | sandbox-primary + sandbox-server01 both RUNNING |
YES |
| primary | NO | — | YES |
This asymmetry is the single most important fact in this document. Tier 2 (sandbox-test) CANNOT be enabled on primary — there is no Incus there at all. Any plan that says "enable tier 2 on both hosts" is wrong. See §3-A3, which is an unresolved DESIGN question, not a task.
secrets-proxy — STOPPED, NOT BROKEN
secrets-proxy-secrets-proxy-1=Exited (0)6 days ago.- Logs show a graceful shutdown ("Application shutdown complete", "Stopping parent process"). It was stopped on purpose. This is not a crash and not a debugging job.
- Repo:
/opt/appdata/docker/docker-compose/secrets-proxy/(app.py, docker-compose.yml, Dockerfile, mirror/, requirements.txt). - HONEST GAP: I did not investigate what work remains on secrets-proxy. Task #128 says "Finish secrets-proxy (sprint-11 #1)" but I have not read sprint-11 or the code. Everything below for secrets-proxy is therefore scoped as INVESTIGATE-THEN-PLAN. Do not treat S2 as a known quantity. Do not let an agent invent secrets-proxy work.
- Its being down has a live cost: the
secrets_via_proxy_onlyrule has no proxy to use, so secret handling currently falls back to file-passing workarounds.
2. THE RECURRING BUG CLASS — check this reflexively
Per-host values hardcoded into files tracked once and shared by both hosts. It bit TWICE tonight in one directory, and both were invisible until the two hosts were compared:
docker-compose.ymlhardcoded primary's port8084. Pulling it to server-01 would have moved Agent-Sudo off 8082 and silently undone the D8 cutover. → FIXED:${AGENT_SUDO_PORT}in each host's gitignored.env(primary 8084, server-01 8082).agent-sudo-daemon.servicecarriesSERVER_ID+VAULT_ADDR. The tracked copy saysSERVER_ID=primaryand omitsVAULT_ADDR. Installing it on server-01 would make server-01 identify as primary and apply primary's tier-4 rules, with no Vault path to verify its own allowlist. → NOT FIXED. See A4. Currently preserved by hand on server-01.
Rule for every future session: before landing any shared file on a second host, diff the two hosts' copies first. Nothing in the test suite measures cross-host divergence. It never will.
Related lesson from tonight, worth carrying: a green test suite said nothing about whether the
module even shipped. 85/85 passed while the image would have crashed on boot with
ModuleNotFoundError, and while the breaker was resettable by docker restart. A passing check
only means what it measures.
3. AGENT-SUDO — remaining work
A1. Fix tier-1 scoped-undo — DO THIS FIRST. It is code, not config. 🔴 BLOCKING
The defect: security/scoped_undo.py:186 declares REQUIRES_UNDO_TIERS = (1, 3), but
app.py:244 calls scoped_undo.capture_or_refuse(command, 3, server_id=SERVER_ID) with the
tier hardcoded to 3. Nothing ever captures undo for tier 1. So tier-1 commands
auto-execute today with no undo capture, which contradicts D4 ("tier 1 = auto-execute +
capture scoped-undo").
Resolve the conflict BEFORE writing code — this is a human decision: The P3 task contract said "tier 0/1 behaviour unchanged"; D4 says tier 1 captures undo. The background agent followed the task contract and pinned it with a test, then flagged it. It was right to flag rather than silently pick. Someone must decide:
- (a) Implement D4: tier 1 captures scoped-undo. Cost: every tier-1 command pays undo-capture latency. Benefit: tier 1 becomes reversible, which is what makes autonomy safe.
- (b) Amend D4: tier 1 is genuinely fire-and-forget, drop
1fromREQUIRES_UNDO_TIERS. Cheaper and faster, but then tier 1 is irreversible — and tier 1 is where the volume is. - LEAN: (a). The whole premise of constrained autonomy is that the constraint is real. An irreversible tier-1 with no undo is an approval gate removed without a constraint added. But this is a real trade and the user decides, not an agent.
⚠️ Whichever way it goes, the test that currently pins the wrong behaviour must be updated, or it will lock in the bug.
A2. Phase 2 — enable capabilities on server-01, ONE AT A TIME 🟡
Runbook: /opt/appdata/docker/docker-compose/agent-sudo/DEPLOY_RUNBOOK.md phase 2.
server-01 only (it has Incus + both sandboxes RUNNING + timeshift).
This is the first-ever live exercise of the sandbox and snapshot paths. Until now tier-2/3
have ONLY run against injected fakes — the real Incus and timeshift paths have never executed.
Expect real failures. Budget for that; do not schedule it as a 20-minute flag flip.
Order (do not batch — one flag, verify, then the next):
SNAPSHOT_ENABLED=truein server-01's.env→docker compose up -d→ exercise a tier-3 command → verify a timeshift snapshot is actually created AND that undo restores.- Then
SANDBOX_ENABLED=true→ exercise a tier-2 command → verify it runs in the Incus sandbox and NOT on the host. ⚠️ Do NOT test the latch by firing a danger-veto command on a live host (runbook says so). Latch behaviour is already proven by unit tests + mutation testing. ⚠️ Verify the 503 stub is actually gone, not that health is green. Health does not measure this.
A3. DESIGN QUESTION, NOT A TASK: what does tier 2 mean on primary? 🔴 UNRESOLVED
Primary has no Incus. Options, none free:
- (a) Install Incus on primary. But:
reference_incus_on_docker_host_networkingdocuments that Docker'sFORWARD DROPbreaks the incus bridge and debian/13 systemd-resolved is broken. This is a known-painful install on a Docker host — and primary is the PRODUCTION host. - (b) Primary tier-2 commands execute in server-01's sandbox (cross-host). Introduces the same cross-host dependency class as #173, and the sandbox would not mirror primary's state.
- (c) Tier 2 stays permanently 503 on primary; primary's SUDO.md never classifies anything as tier 2. Honest and safe; means primary is a lesser-capability host by design. LEAN: (c) for now — it is the only one that ships without new risk on the production host, and (a) can come later if tier-2 volume on primary justifies the pain. This must be decided by the user before anyone touches primary tier-2.
A4. Daemon unit EnvironmentFile — fix the §2 bug class 🟡 HUMAN-ONLY
Make agent-sudo-daemon.service host-neutral: EnvironmentFile=/etc/agent-sudo/daemon.env,
with SERVER_ID / VAULT_ADDR / SUDO_MD_VERIFY_ENFORCE per-host in that file.
Tier-4 human-only by design — that unit's env IS the gate. An agent must never edit it.
Fold into #173: the VAULT_ADDR=http://192.168.1.88:8200 line in server-01's unit IS the
cross-host Vault dependency that #173 exists to resolve. Same file, same edit, one human pass.
A5. #146 — primary port swap 8084 → 8082 🟢 NOW TRIVIAL
Was a manual file edit; the ${AGENT_SUDO_PORT} change reduced it to a one-line .env edit
plus stopping the old sudo-bridge. Do this LAST, after primary tier-1/2/3 behaviour is settled.
Blocked by: #191 (sudo-bridge/app.py is one of the git-held files).
A6. server-01 repo hygiene 🟢
server-01's /opt/appdata/docker is at HEAD 96f8c05, 28 commits behind, with the
agent-sudo dir checked out from 005c214 into a staged index, plus uncommitted local changes to
jenkins/Dockerfile, sudo-bridge/app.py, sudo-bridge/docker-compose.yml.
The files are correct and the deploy is sound, but the repo state is a trap for the next person.
Why it is this way: a full git pull would have clobbered those held local changes, so a
targeted git checkout origin/main -- docker-compose/agent-sudo was used instead. That was the
right call under time pressure; reconciling properly is a separate, careful job.
⚠️ Whoever does this must NOT blindly git checkout/stash the local changes away — find out
what they are first. Same category as #191.
A7. Spec gaps A + C on projects row 187 🟢 confirm-and-close
- (A)
ATTACK_TIER4_SOURCES=('danger_veto', 'host_override_setb').classify()can also reach tier 4 viarule_match(explicit SUDO.md tier-4 rule) andmiss_verb_heuristic. The agent made both audit-only, reasoning a latch must mean "this is not resolving." Decide: is an explicit tier-4 SUDO.md rule an attack signal? One-line change; the D10 seam holds. Note D-CB8 deliberately excludesmiss_primary_refuse— latching on it would take a host read-only on the first typo. Do not "fix" that. - (C) A failed PRE-exec snapshot refuses 503 without calling
record_tier3_failure()— the command never ran, so counting it toward the benign trip threshold would let a broken timeshift disarm tier 3. Reasoning is sound; just confirm and close.
4. SECRETS-PROXY — remaining work
S1. INVESTIGATE — must happen before any plan is written 🔴
Nobody currently knows what "finish secrets-proxy" means. Establish, with evidence:
- Why was it stopped? Exit was graceful and deliberate 6 days ago (~2026-07-09). Check git log, session summaries, and task #128 / sprint-11 notes for the reason. It may have been stopped for a reason that still applies — do not just start it.
- What is incomplete in
app.py/mirror/? What did sprint-11 #1 actually scope? - Does it start cleanly today? What breaks if it does?
- Is its
docker-compose.ymlcarrying the §2 per-host-value bug? Check before any second host. - Check
project_coolify_env_var_debt: are there${VAR}Coolify placeholders that will bite on restart? This is a known live trap for any container restart. Output: a written scope. Then and only then, plan S2. Do not skip to S2.
S2. FINISH + DEPLOY — cannot be scoped until S1 lands 🔴
Placeholder by construction. Anyone writing detail here before S1 is guessing.
S3. #150 — sign proxy.md via Vault transit 🟡
Explicitly blocked on secrets-proxy deploy. Mirrors Agent-Sudo's SUDO.md signing (#145), so the
pattern is proven — reuse security/sudo_sign.py, do not reinvent.
S4. Restore the secrets_via_proxy_only rule 🟢
Once up, secret reads go back through the proxy /shell + env_secrets vault refs, retiring
tonight's file-passing workaround.
5. SEQUENCING — and the trap in the obvious order
The ordering paradox (feedback_build_the_freeing_capability_first): the obvious order is
"finish Agent-Sudo, then secrets-proxy, then business dev." But the item that removes a
recurring tax is often scheduled last, behind the things that charge it.
secrets-proxy being down taxes EVERY future session — every secret read needs a workaround, and that cost is paid on business-development days too, when this infrastructure is supposed to be out of the way. S1 is zero-dependency and small. Do S1 FIRST, even before A1, so the scope is known and can be handed to an agent while attention is elsewhere.
Recommended order:
| # | item | who | why here |
|---|---|---|---|
| 1 | S1 investigate secrets-proxy | agent | zero-dep, removes a recurring tax, unblocks S2/S3 |
| 2 | A1 decision tier-1 undo (a) vs (b) | human | blocks all tier-1 work; 5-min decision |
| 3 | A3 decision primary tier-2 | human | blocks primary; 5-min decision |
| 4 | A1 implement | agent | the real code work |
| 5 | A2 phase 2 on server-01 | agent + human | first live sandbox/snapshot run |
| 6 | A7 confirm gaps A + C | agent | cheap close-out |
| 7 | S2 finish secrets-proxy | agent | scoped by S1 |
| 8 | A4 + #173 unit EnvironmentFile | human | tier-4 by design |
| 9 | A6 repo hygiene, A5/#146 swap, S3/#150 | mixed | last, low risk |
Steps 2 and 3 are two human decisions totalling ~10 minutes that unblock everything else. Make them before starting a business-development day and the agents can run without you.
6. WHAT AGENTS MAY AND MAY NOT DO
Never delegate to an agent (tier-4 / human-only by design):
- Restarting or stopping
agent-sudo-daemon; editing its unit file (A4) — the unit env is the gate - Editing
SUDO.md; writing/var/lib/agent-sudo/breaker.log; resetting a tripped latch - The #146 port swap
- The A1 and A3 decisions (implementation is delegable; the choice is not)
- Anything in §2 that lands a shared file on a second host without diffing both first
Good agent work: S1 investigation, A1 implementation once decided, A2 under supervision, A7 confirmations, S2 once scoped, test writing.
Every agent prompt must carry (playbook_background_agent_prompts +
feedback_agent_prompt_mandatory_wrapup): the mandatory wrap-up JSON block
(status / actions_taken / actions_failed / notes / human_action_required) so the agent
structurally cannot stop without reporting; an explicit permitted-file list; and
--max-turns. Tonight proved the value: the P3 agent's two most valuable findings were the
blockers outside its permitted file set, which it could only flag, not fix. An agent that
flags what it cannot touch is working correctly — do not "fix" that by widening permissions.
7. DEFINITION OF DONE
Agent-Sudo is done when:
- Tier 0 executes. (DONE — verified 2026-07-15)
- Tier 4 is refused on both hosts. (DONE — verified 2026-07-15)
- Breaker log persists across restart on both hosts. (DONE — verified 2026-07-15)
- Tier 1 behaviour matches a D4 that has been reconciled with the task contract (A1)
- Tier 3 creates a real timeshift snapshot and restores, on server-01 (A2)
- Tier 2 executes in the real Incus sandbox on server-01 (A2)
- Primary's tier-2 story is decided and written down (A3)
- The daemon unit is host-neutral (A4 / #173)
- Instrument-the-gate audit query is scheduled and someone READS it
(
feedback_instrument_the_gate— a gate nobody reads is not instrumented)
secrets-proxy is done when: S1 scope written · S2 deployed · S3 proxy.md signed ·
secrets_via_proxy_only restored without workarounds.
Not done ≠ not useful. As of tonight Agent-Sudo safely runs tier-0 and correctly refuses tier-4 on both hosts, with a tamper-evident breaker. That is real, and it is the part that was open in production this morning.
8. XREF
projects id=187 (P3 detail + 3 spec gaps) · #149 (parent) · #146 (port swap) ·
#173 (fail-open/closed — fold A4 in) · #191 (git-held files) · #128 (secrets-proxy) ·
#150 (proxy.md signing) · DEPLOY_RUNBOOK.md (phases) ·
agent_sudo_design_decisions.md (D1–D10, D-CB1–D-CB9) ·
memory: feedback_build_the_freeing_capability_first, feedback_instrument_the_gate,
feedback_autonomous_security_constrain_not_gate, reference_incus_on_docker_host_networking,
project_coolify_env_var_debt, feedback_agent_prompt_mandatory_wrapup