Files
claude-projects/agent-builder/GAMEPLAN_agent-sudo_secrets-proxy.md
Backtalk6858 1d37e453ec docs: gameplan to fully-deployed Agent-Sudo + secrets-proxy, plus S1/A1/A7 agent prompts
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>
2026-07-15 20:13:46 -05:00

17 KiB
Raw Permalink Blame History

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 04 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 005c214 on origin/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:8082 on primary. No cutover has happened. (#146)
  • D-CB6 breaker log is now persistent on BOTH hosts/var/lib/agent-sudo/breaker.log, dir 0700 root:root, on a bind mount, verified resolving inside the container. docker restart can no longer reset a tripped breaker. This closed a hole that was OPEN IN PRODUCTION. breaker.log does not exist yet — correct, created 0600 on first append.
  • 85/85 tests pass on both hosts.
  • Capability gates: SANDBOX_ENABLED=false, SNAPSHOT_ENABLED=false on 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_only rule 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:

  1. docker-compose.yml hardcoded primary's port 8084. 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).
  2. agent-sudo-daemon.service carries SERVER_ID + VAULT_ADDR. The tracked copy says SERVER_ID=primary and omits VAULT_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 1 from REQUIRES_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):

  1. SNAPSHOT_ENABLED=true in server-01's .envdocker compose up -d → exercise a tier-3 command → verify a timeshift snapshot is actually created AND that undo restores.
  2. 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_networking documents that Docker's FORWARD DROP breaks 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 via rule_match (explicit SUDO.md tier-4 rule) and miss_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 excludes miss_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:

  1. 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.
  2. What is incomplete in app.py / mirror/? What did sprint-11 #1 actually scope?
  3. Does it start cleanly today? What breaks if it does?
  4. Is its docker-compose.yml carrying the §2 per-host-value bug? Check before any second host.
  5. 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 (D1D10, D-CB1D-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