From c50e72e17b810fb127873d673359447c0e70aa8e Mon Sep 17 00:00:00 2001 From: Backtalk6858 Date: Thu, 9 Jul 2026 14:28:39 -0500 Subject: [PATCH] =?UTF-8?q?docs:=20agent-sudo=20grill-me=20complete=20?= =?UTF-8?q?=E2=80=94=20D1-D10,=20P0-P5=20build=20prompts,=20comparison?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Close Agent-Sudo (infra id=176) design grill-me; gap #5 cross-cutting locked - Add P0-P5 phased background-agent build prompts (Opus 4.8 upgrades applied) - Add agent_sudo_vs_sudo_bridge.md comparison doc - Update context.md: sprint day 11 session 4, Coolify/N8N/port-8084 corrections Co-Authored-By: Claude Opus 4.8 --- agent-builder/.claude/context.md | 15 ++ agent-builder/agent_prompts.md | 256 +++++++++++++++++++ agent-builder/agent_sudo_design_decisions.md | 134 ++++++++++ agent-builder/agent_sudo_vs_sudo_bridge.md | 53 ++++ 4 files changed, 458 insertions(+) create mode 100644 agent-builder/agent_sudo_design_decisions.md create mode 100644 agent-builder/agent_sudo_vs_sudo_bridge.md diff --git a/agent-builder/.claude/context.md b/agent-builder/.claude/context.md index c9ed577..d0b3108 100644 --- a/agent-builder/.claude/context.md +++ b/agent-builder/.claude/context.md @@ -549,5 +549,20 @@ Key finding this session: secrets-proxy /shell only accepts target=sandbox or ta 3. Launch Phase 2 (5 parallel agents): id=161, id=154, content repurposing, id=135, id=112 4. After Phase 2 id=154 completes: launch Phase 3 (id=155) +### 2026-07-08 Sprint Day 11 session 4 (evening) — Agent-Sudo grill-me COMPLETE + build prompts +- **Agent-Sudo (id=176) grill-me DONE** — D1–D10 + gap-#5 cross-cutting all locked. Design: `agent_sudo_design_decisions.md`. Phased build prompts **P0–P5 written to `agent_prompts.md`** (Opus 4.8 upgrades: locked-design guardrail, contract-not-brittle-script, right-sized turns, 4-level gate + agent_test_results logging, per-phase abort/rollback + idempotent pre-flight). **NOT dispatched — pending user review.** +- **Comparison doc written:** `agent_sudo_vs_sudo_bridge.md` (Agent-Sudo vs old bridge, side-by-side). +- **Live-verified corrections:** Agent-Sudo parallel port = **8084** (8083 is bitwarden-bridge; design-doc D8 was wrong); `agent_test_results` EXISTS in api_business (was unverified); `automation_ideas`/`behavior_changes` live in api_business (not the empty `automation_ideas` DB); postgres peer-auth works (no secret needed for psql). +- **Coolify reality-check:** Coolify is genuinely RETIRED (control plane `Exited` since 2026-06-25). `coolify.managed=true` labels on ~31 containers are VESTIGIAL static metadata, NOT active management. `coolify-proxy` = standalone `traefik:v3.6` (name kept for cloudflared tunnels → `http://coolify-proxy:80`). Stale memory `project_coolify_traefik_retirement.md` REWRITTEN to reflect executed retirement. +- **N8N caller audit:** all 12 production workflows checked — **ZERO call sudo-bridge**. They use the now-DEAD Coolify API (`172.16.16.1:8000/api/v1/deploy|services/.../envs`) + Vault + bitwarden-bridge. So Agent-Sudo cutover has zero N8N surface. + +#### ⚠️ REDUNDANCY FINDING (today's decisions vs remaining sprint-day-11 work) +Remaining sprint-day-11 priorities were: (1) fix secrets-proxy Dockerfile compose-plugin, (2) trigger sudo-bridge Jenkins jobs to deploy the **urgent-NTFY-priority** change to both bridges, (3) fix Jenkinsfile compound-command bug (behavior_changes id=1), (4) re-run jellyfin E2E. +- **#2 is now REDUNDANT** — its only purpose was making the tier-2 NTFY **approval** notifications reliable. **D4 ELIMINATES the tier-2 approval gate entirely** (broken + theatrical). The old bridges are being replaced by Agent-Sudo (which has no such gate) and decommissioned in P5. Rebuilding them just to ship urgent-NTFY-priority is wasted work → **DROP it.** (The high→urgent app.py change stays uncommitted/undeployed; harmless.) +- **#1, #3, #4 remain NECESSARY** — they're secrets-proxy / Jenkins-pipeline infra, orthogonal to Agent-Sudo (Agent-Sudo replaces sudo-bridge, not secrets-proxy; the Jenkinsfile compound-command bug is a secrets-proxy `/shell` shlex issue, unaffected). +- **New separate debt logged (not Agent-Sudo):** the ~7 N8N workflows calling the dead Coolify API are silently broken → need Jenkins migration (per project_cicd_jenkins). Candidate automation_ideas / cleanup pass. + +**NEXT SESSION (Agent-Sudo):** user reviews P0–P5 prompts → dispatch P0 (zero-risk, standalone) first, then P1→P5 in order. Switch back to Sonnet+medium (Opus 4.8/high was today-only). + ## Update instructions Update at the end of every agent-builder session. Keep agent status, key decisions, and prereq checklist current. diff --git a/agent-builder/agent_prompts.md b/agent-builder/agent_prompts.md index dab85cd..bcf0cc6 100644 --- a/agent-builder/agent_prompts.md +++ b/agent-builder/agent_prompts.md @@ -866,3 +866,259 @@ You are a database maintenance agent. The automation_ideas table has a required_ --max-turns 15 If you issue the same tool call or command twice with identical arguments, STOP immediately and output the mandatory wrap-up with status=partially_succeeded. ``` + +--- +--- + +# AGENT-SUDO BUILD — Phased Prompts P0–P5 +# Generated: 2026-07-08 (Opus 4.8 / high). Builds projects id=176 (infrastructure_projects DB). +# Design is LOCKED in agent_sudo_design_decisions.md (D1–D10 + gap-#5 cross-cutting). These prompts IMPLEMENT that design — they do not re-open it. +# DO NOT launch any agent yet — this set is pending user review. + +## What is different from the Sonnet-era prompts above (Opus 4.8 upgrades) +1. **Locked-design guardrail** — each prompt opens with a "DESIGN IS LOCKED — DO NOT REDESIGN" block pointing at agent_sudo_design_decisions.md. Agents implement; they never re-decide architecture, tiers, ports, or security model. +2. **Contract, not brittle script** — each prompt states the *contract* (what must be true when done: schema, API surface, file locations, invariants) plus a *reference approach*. The agent writes the code to satisfy the contract instead of pasting a fragile literal script. (The Sonnet prompts embedded whole verbatim `.py` files that rotted.) +3. **Right-sized turn budgets** — per-phase, not a flat 15. +4. **4-level test gate + agent_test_results logging baked in** — P2 and P4 log to `agent_test_results` (api_business) per the testing methodology. The Sonnet set omitted this. +5. **Per-phase abort/rollback + idempotent pre-flight** — each prompt has explicit pre-flight checks (safe to re-run) and an abort/rollback clause matching the gap-#5 blast-radius matrix. +6. **Structural wrap-up + loop detection retained.** + +## SHARED CONVENTIONS (referenced by every prompt below as "the shared block") + +### Verified infra facts (live-checked 2026-07-08 — do not re-derive; verify only if a pre-flight says to) +- **Postgres** (primary): container `postgres-lggkk0kcgwko440kk04wowgk` — resolve dynamically: `docker ps --format '{{.Names}}' | grep '^postgres-'`. Peer auth inside the container works: `docker exec psql -U postgres -d ` needs NO password. +- **DBs:** `projects` DB → table `infrastructure_projects` (Agent-Sudo = id=176). `api_business` DB → tables `agent_test_results` ✅, `automation_ideas`, `behavior_changes`. `sudo_bridge` DB → `executions`, `allowlist_changes` (both have `server_id` column, default 'primary'). +- **Old bridges (being replaced):** primary `sudo-bridge-drbjegv07256ki2lpfyr00n8` on **8082**; server-01 bridge on **8082** (@192.168.1.90). Shared image `gitea.local/backtalk6858/sudo-bridge:latest`. API keys: Vault `secret/data/sudo-bridge` (primary), `secret/data/sudo-bridge-server01` (server-01, field `api_key`). +- **Agent-Sudo parallel port = 8084** (⚠️ CORRECTION to design-doc D8 which said 8083 — 8083 is taken by bitwarden-bridge; 8084/8085/8086/8087 are free on primary. Pre-flight must re-confirm free before binding.) +- **Both hosts:** LMDE 7 "gigi" = Debian 13 (trixie), kernel 6.12.90+deb13.1-amd64. +- **Coolify is RETIRED** (control plane stopped 2026-06-25). Deploy Agent-Sudo as **plain docker-compose** using the server-01 template (`/opt/appdata/docker/non-docker-python-scripts/Docker Template/docker-compose.yml`, ports bound to the host LAN IP, no coolify network, no Traefik labels) driven by **Jenkins/Gitea** — NEVER Coolify. `coolify-proxy` is a standalone Traefik (name kept for cloudflared) — leave it alone. +- **Vault:** container `vault-iwaulpoi5hwirdlogshmul40` (IP drifts — resolve via `docker inspect ... NetworkSettings.Networks`). Primary AppRole creds: `/opt/appdata/docker/docker-compose/vault/approle/{role-id,secret-id}`. AppRole-only, orphan tokens, revoke-self after use (playbook_vault_token_rotation Step 0). + +### Privileged-execution path on server-01 (READ — this is the bootstrapping reality) +Agent-Sudo does not exist yet, so build agents that need root on server-01 use the **existing** `sudo-bridge-server01` (`http://192.168.1.90:8082`, Bearer `secret/data/sudo-bridge-server01#api_key`). Rules: +- `GET /allowlist` before any `POST /allowlist` (409 = already exists). New rules via `POST /allowlist` only (allowlist.json is root:root — cannot be edited directly). +- `sudo-bridge` uses `subprocess.run(shlex.split())` — **NO shell**: no `&&`, `|`, `$()`, `>`. Each command is a separate `POST /exec`. File writes = `python3 -c "..."` with base64 (single argv). +- **⚠️ TIER-2 NTFY APPROVAL IS BROKEN** (behavior_changes id=3 — notifications never arrive, command times out at 900s). **Do NOT design a build step that blocks on a tier-2 approval.** If a required privileged command would be tier-2, STOP and record it under `actions_failed` as "needs manual authorization: " so the reviewer can pre-add it as tier-1 or run it interactively. Prefer tier-1-eligible commands; batch anything needing genuine escalation into an explicit list for the user. + +### Mandatory wrap-up (every agent, on success OR failure, as the final message) +``` +{ + "status": "succeeded|partially_succeeded|failed", + "actions_taken": ["action — outcome"], + "actions_failed": ["action — reason (incl. any 'needs manual authorization' commands)"], + "notes": "phase-specific confirmations + anything the next phase needs" +} +``` +### Loop guard (every agent) +If you issue the same tool call/command twice with identical arguments, STOP and emit the wrap-up with status=partially_succeeded. Respect the per-phase `--max-turns`. + +--- + +## P0 — DB foundation (`command_audit` + training-signal schema) +**Blast radius: ZERO** (additive DDL only, no server touched). Safe to run first, independent of P1–P5. `--max-turns 10`. + +``` +You are a database schema agent on the primary server (192.168.1.88). Create the audit + training-data foundation for Agent-Sudo (projects id=176). + +## DESIGN IS LOCKED — DO NOT REDESIGN +The Agent-Sudo design is fixed in agent_sudo_design_decisions.md. Your job is only to create the schema below. Do not add, rename, or "improve" tiers, columns beyond the contract, or tables. If the contract seems wrong, record it in notes and proceed with the contract as written. + +## Use the shared block (verified facts, postgres access, wrap-up, loop guard). + +## Contract — what must be true when you finish +1. In the `projects` DB, a table `command_audit` exists with AT LEAST these columns (types are guidance; match intent): + - id BIGSERIAL PK + - ts TIMESTAMPTZ DEFAULT now() + - server_id TEXT NOT NULL -- 'primary' | 'server-01' + - command TEXT NOT NULL + - decision_type TEXT NOT NULL -- 'rule_match' | 'sandbox_test' | 'ai_eval_proposal' | 'execution' | 'circuit_breaker' | 'tier4_refuse' + - assigned_tier SMALLINT -- 0..4, null if refused pre-tier + - matched_rule TEXT -- the SUDO.md glob/rule that matched, if any + - host_override BOOLEAN DEFAULT false -- did a per-host override change the tier (D5) + - evidence JSONB -- sandbox exit/stderr, ai-eval reasoning, etc. + - human_verdict TEXT -- for ai_eval_proposal review: 'accepted'|'re_tiered'|'rejected'|null (the GOLD training label, gap-#5 item 3) + - exit_code INTEGER + - duration_ms INTEGER + - rollback_taken BOOLEAN DEFAULT false + - verify_passed BOOLEAN -- post-exec auto-verify result (tier 3) + - training_signal BOOLEAN DEFAULT true -- include this row in the local-model training export + - Helpful indexes: (server_id, ts), (decision_type), (assigned_tier). +2. Confirm `agent_test_results` already exists in `api_business` (it does — just verify + print its columns; do NOT recreate). +3. Nothing else is modified. Existing tables untouched. + +## Reference approach +Write the DDL as `CREATE TABLE IF NOT EXISTS ...` (idempotent) and run it via `docker exec psql -U postgres -d projects -f ` (use `docker cp` of a .sql file, not a heredoc — heredocs over docker exec are unreliable). Use IF NOT EXISTS everywhere so a re-run is a no-op. + +## Pre-flight (idempotent — safe on re-run) +- Resolve the postgres container. Confirm `projects` and `api_business` DBs are reachable. +- `SELECT to_regclass('public.command_audit')` — if it already exists, verify its columns match the contract and only ADD missing columns (ALTER TABLE ... ADD COLUMN IF NOT EXISTS); do not drop/recreate. + +## Abort / rollback +- Abort if either DB is unreachable (emit failed wrap-up; touch nothing). +- Rollback if needed = `DROP TABLE command_audit` (this table is brand-new and empty; safe). Never drop agent_test_results. + +## Wrap-up notes must confirm: command_audit columns created/verified; agent_test_results columns; row counts (should be 0 for command_audit). +``` + +--- + +## P1 — Incus + command-surface mirrors on server-01 +**Blast radius: server-01 sandbox-side only** (no production, no live data). `--max-turns 30` (many discrete privileged steps). + +``` +You are an infrastructure agent building the sandbox substrate for Agent-Sudo on server-01 (192.168.1.90). + +## DESIGN IS LOCKED — DO NOT REDESIGN (agent_sudo_design_decisions.md → D1, D7) +Container tech = Incus. Base = images:debian/13. Storage = btrfs pool (fallback: dir backend if root is not btrfs). Network = NAT via default incusbr0 (ZERO inbound LAN exposure — the sandboxes must NOT be reachable on 192.168.1.x). Two containers, BOTH on server-01: `sandbox-server01` (mirrors server-01) and `sandbox-primary` (mirrors PRIMARY's command surface, but runs on server-01 — no testing ever happens on primary). Limits each: 2 vCPU / 2GB RAM / 10GB disk. Mirror fidelity = COMMAND-SURFACE only (same OS/packages/paths/users/sudoers STRUCTURE — never secrets, never live data, never running prod containers). + +## Use the shared block. Privileged server-01 ops go through sudo-bridge-server01 per the shared block's rules. HEED the tier-2-NTFY-broken warning: installing Incus needs root apt — if `apt-get install -y incus` cannot run as a tier-1 allowlist entry, DO NOT block on tier-2; record it under actions_failed as "needs manual authorization: " and continue with whatever you can (e.g. produce the inventory + build script) so the reviewer can authorize the installs in one batch. + +## Contract — what must be true when you finish (or be cleanly deferred with a manual-auth list) +1. **Read-only host inventory captured** (the mirror spec, P1 prerequisite): for BOTH hosts — `dpkg --get-selections`, users/uids + groups + sudoers STRUCTURE (never secret values), and the relevant dir tree skeleton (`/opt/appdata`, `/data/coolify`, `/etc/...` — directory structure/paths only, NOT file contents). Save each host's inventory to a file on server-01 under `/opt/appdata/docker/agent-sudo/inventory/{primary,server-01}.json`. (Primary inventory is read-only from primary; you may gather it via the shared block's primary access.) +2. **Incus installed + initialized** on server-01: btrfs storage pool created (or `dir` fallback, noted), `incusbr0` NAT bridge present, both operations idempotent. +3. **`build-sandbox.sh `** written to `/opt/appdata/docker/agent-sudo/build-sandbox.sh` — ONE parameterized script that, given a host arg, launches/refreshes the matching Incus container from images:debian/13, applies that host's package set + path skeleton + users/sudoers structure from the inventory, sets the 2vCPU/2GB/10GB limits, and attaches only incusbr0. Re-running it rebuilds the container from baseline (D1 script-rebuildable; D5 loop will later append missing deps to this script). +4. **Both containers built** and confirmed: `incus list` shows `sandbox-server01` and `sandbox-primary` RUNNING, each with NO forwarded LAN ports, each able to reach the internet (apt) but not reachable from 192.168.1.x. + +## Reference approach +Prefer `incus` over legacy lxc. Gather inventories with read-only commands (dpkg, getent passwd/group, `cat /etc/sudoers.d/` STRUCTURE — pattern the rules, do not copy secrets). Generate build-sandbox.sh so it is declarative and re-runnable. Verify isolation by confirming the container's IP is on the incusbr0 subnet and no `incus config device` proxy/nic maps a LAN port. + +## Pre-flight (idempotent) +- `which incus` — skip install if present. `incus storage list` / `incus network list` — skip create if present. `incus list` — if a target container exists, refresh via build-sandbox.sh rather than duplicate. +- Confirm server-01 root fs is btrfs (`stat -f -c %T /`) to decide pool backend. + +## Abort / rollback +- Rollback = `incus delete --force sandbox-primary sandbox-server01` and remove the btrfs pool; nothing production is affected. +- Abort (record + stop) if: btrfs AND dir backend both fail; or Incus install needs tier-2 auth you cannot get (defer with manual-auth list). + +## Wrap-up notes must confirm: incus version; pool backend chosen; both containers' isolation (no LAN port, on incusbr0); build-sandbox.sh path; any "needs manual authorization" install commands. +``` + +--- + +## P2 — Agent-Sudo service (drop-in sudo-bridge replacement) +**Blast radius: NONE while built** (binds 8084; old bridge keeps 8082). `--max-turns 25`. + +``` +You are a service-implementation agent. Build the Agent-Sudo Python service — a DROP-IN replacement for sudo-bridge — as source in a Gitea repo. Do NOT deploy it live in this phase (that is P4). + +## DESIGN IS LOCKED — DO NOT REDESIGN (agent_sudo_design_decisions.md → D4, D5, D8, D9) +Tier model 0–4 (0 auto-safe / 1 auto-reversible / 2 sandbox-first / 3 snapshot+execute / 4 hard-refuse). NO human-approval gate — the old tier-2 NTFY approve/deny is ELIMINATED, not ported. Matching engine = ordered rule list, most-specific-first (first-match-wins), fnmatch globs, regex danger-veto = auto-Tier-4, optional per-host tier override + verb class per rule. Miss behavior: primary → hard-refuse (Tier-4); server-01 → verb-heuristic default. Dumb root executor + unprivileged brain stays (separation of privilege). Response contract identical to today's bridge. + +## Use the shared block. + +## Contract — what must be true when you finish +1. A Gitea repo (e.g. `agent-sudo` under Backtalk6858, mirror the existing sudo-bridge repo's layout) contains: + - `app.py` — unprivileged FastAPI brain. Endpoints preserve the OLD contract EXACTLY: `POST /exec` (Bearer) → `{exit_code, output, ...}`; `GET/POST /allowlist`; `GET /health`. REMOVE `/approve/{token}` and `/deny/{token}` and all APPROVAL_TIMEOUT logic (D4). A Tier-4 refuse returns 403 (same shape as today's allowlist-miss). Tiers 0–3 execute without any human gate. + - `daemon` (root executor) — keep the existing dumb-root-daemon model: independent re-validation of danger-veto + allowlist before running; `subprocess.run(shlex.split())`, no shell. It must NOT trust the brain — it re-checks. + - `SUDO.md` — the rule file: ordered globs → tier, with optional `host:` override and `verb:` class. Migrate the current allowlist.json into it per the D4 remap (old tier-1 → new 0/1 by read-vs-write; old tier-2 → new 2 or 3 by reproducible-vs-live-service; danger vetoes → 4). Seed with the researched common-command corpus (D5) — but every seeded rule with any uncertainty starts in `proposed` (inactive) state. + - Config for `server_id` ('primary'|'server-01'), the 8084 bind, and audit → `command_audit` (projects DB) with JSONL-first→Postgres fail-soft (same resilience as today). + - `docker-compose.yml` from the server-01 template (plain compose, host-LAN-bound, no Coolify), + `Jenkinsfile` for build→push→deploy. +2. Every decision path writes a `command_audit` row with the right `decision_type` (rule_match / execution / tier4_refuse / …) and training fields (P0 schema, gap-#5 item 3). +3. Unit-level self-check: a small `test_classify.py` that asserts representative commands land in the expected tier per host (read → 0; reversible write → 1; reproducible unknown → 2; docker/systemctl live-op → 3; danger regex → 4; primary miss → 4; server-01 miss → verb-heuristic). Log the run to `agent_test_results` (api_business) per the testing methodology (Structure + Smoke levels apply here). + +## Reference approach +Start from the CURRENT sudo-bridge app.py + sudo_bridge_daemon.py as the skeleton (same repo layout, same daemon socket model, same audit resilience) and SUBTRACT the approval-gate, ADD the 0–4 tiering + per-host override + verb-heuristic + SUDO.md loader. You are refactoring a known-good service, not greenfielding. Do not paste a giant literal here — read the existing files, then write the evolved versions. + +## Pre-flight (idempotent) +- Read the existing sudo-bridge repo/files first (structure + contract). Confirm the 0–4 remap against the live allowlist.json entries. +- Confirm 8084 is free on both hosts (design-doc D8 said 8083 — that is WRONG, bitwarden-bridge owns it). + +## Abort / rollback +- This phase writes only to a Gitea repo + runs a local classifier test. Rollback = delete the repo/branch. Nothing runs on a host. Abort if the old bridge source can't be read (needed as the contract reference). + +## Wrap-up notes must confirm: repo URL; endpoints present/removed; SUDO.md rule count (active vs proposed); test_classify pass/fail per tier; agent_test_results row id. +``` + +--- + +## P3 — Vault AppRole + Timeshift automation + AI evaluator +**Blast radius: server-01 config + additive Vault policy** (existing roles untouched). `--max-turns 25`. + +``` +You are a security-infrastructure agent. Stand up Agent-Sudo's autonomous-security pieces (agent_sudo_design_decisions.md → D5, D9). NOTE: Bitwarden and OS-sudo-password are DROPPED from the design — do not build them. + +## DESIGN IS LOCKED — DO NOT REDESIGN +Security = constrain-not-gate + execute-vs-expand split. Scoped short-TTL Vault AppRole (reuse existing rotate-and-revoke machinery; policy reads ONLY the one Agent-Sudo secret path). Signed/read-only SUDO.md via Vault transit (allowlist can't be silently widened). Scoped-undo rollback (file backup+restore / package record+remove / container via Jenkins redeploy) captured BEFORE execution; Timeshift snapshot on Tier-3; NEVER auto-restore Timeshift on primary (D3). AI evaluator produces a PROPOSED tier + audit evidence, writes the rule INACTIVE on both hosts, fires a LOW-priority NTFY (non-blocking — this is the async config review, NOT an execution gate). + +## Use the shared block. + +## Contract — what must be true when you finish +1. **Vault:** a new `agent-sudo` policy (read-only on exactly one secret path, e.g. `secret/data/agent-sudo`) + an AppRole bound to it, created via the rotate-and-revoke playbook (orphan tokens, revoke-self). Existing policies/roles (claude-policy, n8n-*) are provably untouched. Smoke-test: login → read the one path → confirm it CANNOT read another path → revoke. +2. **SUDO.md signing:** SUDO.md is signed with Vault transit; the daemon verifies the signature before loading (tamper → refuse to load, alert). Provide the sign + verify helpers. +3. **Scoped-undo library:** a module the service calls to capture a command-specific undo BEFORE a Tier-1/3 execution (file backup, package record, container-redeploy marker). If undo can't be captured → refuse to execute (per gap-#5 blast-radius). +4. **Timeshift automation:** take-snapshot helper for Tier-3 on server-01; on primary it may TAKE a snapshot but MUST refuse to auto-restore (hard-coded host guard). +5. **AI evaluator:** given an unknown reproducible command → route to the sandbox (P1) → analyze audit → emit a PROPOSED tier + evidence into SUDO.md (inactive) + write a `command_audit` ai_eval_proposal row + fire ONE low-priority NTFY pointing at the review conversation. It must be structurally impossible for the evaluator to mark a rule `active` (only human review does that — execute-vs-expand split). + +## Reference approach +Reuse the existing AppRole tooling verbatim (playbook_vault_token_rotation Step 0). For transit, use the existing Vault transit engine. Keep the evaluator's "propose only" boundary enforced in code (no active-flag write path). + +## Pre-flight (idempotent) +- Check whether the agent-sudo policy/role already exist (skip-create). Confirm Vault transit engine is enabled (enable idempotently if not). +- Confirm Timeshift present on server-01 (it is) and identify primary's snapshot config WITHOUT enabling any auto-restore. + +## Abort / rollback +- Rollback = delete the agent-sudo policy + role (additive — existing auth unaffected), remove the helper scripts. Abort if AppRole login smoke-test fails, or if the least-privilege check shows the role can read a second path (security failure — do not proceed). + +## Wrap-up notes must confirm: policy least-privilege proof (can read own path, cannot read another); transit sign+verify works; scoped-undo capture tested; primary auto-restore guard verified; evaluator cannot self-activate a rule. +``` + +--- + +## P4 — Deploy both servers (parallel), run the 4-level gate +**Blast radius: server-01 = the only autonomous live flip; primary flip is MANUAL/user** (D8). `--max-turns 20`. + +``` +You are a deployment + validation agent. Deploy Agent-Sudo in PARALLEL with the old bridge and run the full 4-level test gate. server-01 FIRST; primary is user-manual. + +## DESIGN IS LOCKED — DO NOT REDESIGN (agent_sudo_design_decisions.md → D8) +Order: server-01 FIRST, primary LAST. Agent-Sudo stands up on port 8084 while the old bridge keeps serving 8082 UNTOUCHED. Shadow-validate (4-level gate on 8084 + replay known-safe commands through BOTH 8082 and 8084 and diff results). Atomic swap ONLY when green: flip Agent-Sudo to 8082, demote old bridge to 8084 hot-standby (kept, not deleted), 14-day retention. server-01 swap = autonomous. **PRIMARY swap = MANUAL by the user — you must NOT flip primary; you prepare it and hand off.** + +## Use the shared block. + +## Contract — what must be true when you finish +1. **server-01:** Agent-Sudo deployed on 8084 via Jenkins/Gitea (plain compose). 4-level gate run and logged to `agent_test_results`: + - L1 Structure (compose valid, container up, /health 200) · L2 Deployment (endpoints present, auth works) · L3 Smoke (a tier-0 read, a tier-1 reversible write in sandbox) · L4 Assertion (replay a set of known-safe commands through 8082 AND 8084, assert identical exit_code/output; assert a danger command → 403 tier-4; assert removed /approve endpoint → 404). + - If ALL green → atomic swap on server-01 (8084→8082, old bridge→8084 standby). If ANY red → do NOT swap; leave 8082 serving; report. +2. **primary:** deploy Agent-Sudo on 8084 in parallel and run L1–L4 in shadow (diff against 8082) — but DO NOT swap. Produce a one-paragraph hand-off: exact manual command(s) for the user to flip primary, and the exact rollback command (flip back to old bridge on 8082). +3. NTFY on pass and fail per testing methodology. + +## Reference approach +Reuse the existing Jenkins job pattern for the old bridge (Jenkinsfile.server01 already exists in the sudo-bridge repo) as the deploy mechanism, pointed at the new image. Replay set = the current allowlist.json's known-safe (read) entries — these must behave identically on both ports. + +## Pre-flight (idempotent) +- Confirm 8084 free on the target host. Confirm old bridge on 8082 is healthy (the safety net) BEFORE deploying alongside. Confirm P0/P1/P2/P3 artifacts exist. + +## Abort / rollback +- server-01 rollback = flip 8082 back to the old bridge (one Jenkins/compose action); Agent-Sudo returns to 8084. Old bridge retained 14 days. +- ABORT the swap on ANY L4 divergence on known-safe replay. Never touch primary's 8082. Abort if old bridge isn't healthy at start (no safety net). + +## Wrap-up notes must confirm: server-01 gate results L1–L4 + swap done/blocked; primary shadow results + the exact manual swap + rollback commands for the user; agent_test_results row ids. +``` + +--- + +## P5 — Decommission old sudo-bridge +**Blast radius: removes the fallback** — runs ONLY after a clean 14-day window + explicit user go. `--max-turns 12`. + +``` +You are a decommission agent. Retire the OLD sudo-bridge on both servers after Agent-Sudo has run clean for the retention window. DO NOT run this without an explicit user go + a clean window. + +## DESIGN IS LOCKED — DO NOT REDESIGN (agent_sudo_design_decisions.md → D8; project_coolify_traefik_retirement) +Old bridge has been on 8084 hot-standby since P4's swap, 14-day retention. Coolify is RETIRED (control plane down since 2026-06-25) — there is NOTHING to deregister and nothing will resurrect a removed container. Decommission = plain docker stop + rm. + +## Use the shared block. + +## Contract — what must be true when you finish +1. Pre-condition PROVEN before removing anything: Agent-Sudo has been serving 8082 on the target host for ≥14 days with NO Tier-3 incident in `command_audit` and a healthy /health. If not proven → abort, remove nothing. +2. On each host (server-01 first, primary last — primary only with explicit user confirmation): `docker stop` then `docker rm` the old `sudo-bridge*` container; optionally remove its `/data/coolify/services//` dir; remove the old image ONLY if unused. The old bridge's restart policy is `unless-stopped` — a removed container is NOT recreated (Coolify is down), so stop+rm is permanent and clean. +3. Update projects id=176 status → completed; note the decommission in context.md. + +## Pre-flight (idempotent) +- Query `command_audit` for the retention window: any circuit-breaker trips or Tier-3 failures? Confirm Agent-Sudo /health on 8082. Confirm the old bridge is the 8084 standby (not still on 8082). + +## Abort / rollback +- If ANYTHING in the window looks wrong → abort, keep the old bridge. "Rollback" after removal = the retained image can be re-pulled + re-deployed to 8082 (so keep the image until the window is unambiguously clean). +- Never decommission primary without explicit user confirmation in the run request. + +## Wrap-up notes must confirm: window-clean proof (incident count = 0); what was stopped/removed per host; image retained or removed; id=176 status. +``` diff --git a/agent-builder/agent_sudo_design_decisions.md b/agent-builder/agent_sudo_design_decisions.md new file mode 100644 index 0000000..6148cd6 --- /dev/null +++ b/agent-builder/agent_sudo_design_decisions.md @@ -0,0 +1,134 @@ +# Agent-Sudo (projects DB id=176) — Design Grill-Me: Locked Decisions & Open Gaps + +**Status:** ✅ GRILL-ME COMPLETE (2026-07-08, Opus 4.8/high). All decisions D1–D10 + gap-#5 cross-cutting LOCKED. Phased build prompts P0–P5 written to `agent_prompts.md`. NEXT = user review of prompts, then dispatch (not yet launched). +**Started:** 2026-07-08 (Opus 4.8 / high). Design doc for the pre-existing id=176 design; this file records the sub-design decisions the DB row left unspecified. + +## Verified current state (checked live 2026-07-08) +- ❌ No Incus/LXC/LXD installed on server-01 (192.168.1.90) — P1 installs it +- ❌ No `command_audit` table in `projects` DB (table `infrastructure_projects` holds id=176) — P0 creates it +- ✅ `agent_test_results` table EXISTS in `api_business` DB (verified 2026-07-08); `automation_ideas` + `behavior_changes` also in `api_business` (NOT the empty `automation_ideas` DB) +- ✅ timeshift installed on server-01 (`/usr/bin/timeshift`) +- ✅ **Coolify RETIRED** (control plane stopped 2026-06-25) — deploy Agent-Sudo via plain docker-compose + Jenkins/Gitea, never Coolify; old bridge decommission (P5) = plain stop+rm, nothing to deregister. `coolify-proxy` = standalone Traefik (name kept for cloudflared). See [[project-coolify-traefik-retirement]]. +- ✅ **N8N is NOT a caller of the bridge** — all 12 production workflows checked 2026-07-08, zero reference sudo-bridge (they use the now-dead Coolify API + Vault + bitwarden-bridge). So the D8 cutover shadow-diff has ZERO N8N surface; real callers = claude-code / background-agents / (future) Hermes via PROXY_CALLERS. +- ⚠️ **PORT CORRECTION to D8:** Agent-Sudo parallel port = **8084**, NOT 8083 (8083 is bitwarden-bridge). 8084/8085/8086/8087 free on primary as of 2026-07-08. +- Existing sudo-bridge: primary `sudo-bridge-drbjegv07256ki2lpfyr00n8` (port 8082 @192.168.1.88); server-01 bridge (Coolify UUID o2kz1puml1mmneyiqd96mouj, port 8082 @192.168.1.90); shared image `gitea.local/backtalk6858/sudo-bridge:latest`; API keys in Vault `secret/data/sudo-bridge` and `secret/data/sudo-bridge-server01`. + +## LOCKED DECISIONS + +### D1 — LXC mirror fidelity = COMMAND-SURFACE mirror +Same OS + package versions, same directory structure/paths, same users/sudoers/groups. +**No** live data, **no** running containers. Static, script-rebuildable, zero production-data exposure in sandbox. +(Rejected: state-replica mirror — constant drift, high upkeep, leaks prod data.) + +### D2 — Tier-2 semantics = RE-TIER BY REPRODUCIBILITY +The current 4-level testing methodology tests *built automations*; sandbox-testing *arbitrary sudo commands* universally is impractical (would need the rejected faithful replica). So: +- **Reproducible ops** (file edits, package installs, permission/config changes) → run the **sandbox-execute** path in the command-surface mirror, using **static/dry-run validation** (parse, path resolution, forbidden-path check, `--dry-run`/`shellcheck`). Does not claim to prove live runtime behavior. +- **Live-service ops** (`docker`/`systemctl` restarts, network changes, live DB) → **skip the sandbox** (it structurally can't test them) → **Tier 3**: Timeshift snapshot → execute on target → full audit trail. If it fails, restore from snapshot and post-mortem from the audit record. +- Net: the LXC mirror earns its keep only for reproducible ops; live-service safety = snapshot + audit, not a theatrical sandbox run. + +### D3 — LOCKED (confirmed 2026-07-08): rollback is scoped, not Timeshift; primary is stricter +Emerged from the "primary has no failover" question. Confirmed by user. +**Tier-4-on-primary = HARD REFUSE, interactive-session-only (option a).** No remote/NTFY approval path for primary Tier-4 — a command dangerous enough to be Tier-4 on the most critical host requires a human at a real terminal. (server-01 may still allow refuse-and-notify at its own lower tiers.) +**Long-term (Horizon B / redundancy vision):** once a redundant failover setup exists (Proxmox mirror + server redundancy/k3s — see [[project_server_redundancy_vision]], [[project_proxmox_test_server]]), revisit allowing full Timeshift restore — with failover, actual downtime → ~0%, so the "full restore = total downtime" objection dissolves and the primary posture can relax. Until then: conservative-by-default. +- **Default Tier-3 rollback = scoped command-specific undo**, captured BEFORE execution (file backup+restore of only touched files; package record+remove; container recovery via **Jenkins redeploy**, not system restore). Blast radius = only what the command touched. +- **Timeshift full-restore = emergency last resort ONLY** (OS-level corruption: dpkg/systemd/libs). User-in-the-loop, NTFY-urgent, accept full downtime. Agent-Sudo may TAKE the snapshot but must NEVER auto-restore primary. Rationale: full restore = total downtime for ALL services (incl. Vault/Authelia/secrets-proxy/media) + reboot, AND reverting /var/lib/docker risks stateful DATA LOSS (Postgres). +- **Host-aware tiering:** primary classifier stricter than server-01. Same command may be Tier-3 on server-01 but **Tier-4 (refuse → require interactive session)** on primary — especially anything touching critical security infra or stateful DB volumes. +- Long-term real fix = Proxmox test-mirror + server redundancy/k3s failover (already in vision). Until then primary posture = conservative-by-default. +- Feeds SUDO.md design (gap #1): the classifier must be **host-aware** (per-host tier overrides). + +### D4 — LOCKED (2026-07-08): tier model has NO human-approval gate +The current sudo-bridge tier-2 "NTFY approve/deny then execute" gate is being **eliminated**, not preserved. Rationale (why — this is the whole point of Agent-Sudo): +- It didn't work: user misses NTFY notifications → command times out (900s APPROVAL_TIMEOUT), compounded by up to 5 parallel background agents competing for attention. +- It was theatrical: user was asked to approve commands they couldn't evaluate for safety → rubber-stamping, not a real gate. +- Real safety = testing/verifying the command (sandbox for reproducible ops; snapshot+audit+auto-verify for live ops), NOT a human who lacks context. +- Only human touchpoint remaining = Tier-4 hard-refuse → interactive session. Classifier must be tuned so Tier-4 is a near-zero-frequency event. + +**Canonical tier model (safe→dangerous ascending; inverts the live bridge's 0=refuse):** +| Tier | Name | Behavior | +|---|---|---| +| 0 | auto-safe (read-only) | auto-execute, no snapshot | +| 1 | auto-reversible | auto-execute + capture scoped-undo | +| 2 | sandbox-first (reproducible ops) | dry-run/validate in LXC mirror → execute → audit | +| 3 | snapshot+execute (live-service ops) | scoped-undo (+Timeshift snapshot) → execute → full audit → auto-verify | +| 4 | hard-refuse | refuse → require interactive session (host-aware: stricter on primary) | + +Migration note (P0/P4): existing allowlist.json uses live-bridge numbering (0=refuse,1=auto,2=NTFY-approve). Remap on cutover: live tier-1 → new tier-0/1 (by read vs write); live tier-2 → new tier-2 or 3 (by reproducible vs live-service); danger vetoes → new tier-4. + +### D5 — LOCKED (2026-07-08): matching engine + seed corpus + audit-driven self-completing loop +**Engine = option (a):** evolve the existing model — ordered rule list, most-specific-first (first-match-wins), `fnmatch` globs (existing allowlist.json migrates ~1:1), regex danger-veto = auto-Tier-4. Each rule gains optional per-host tier override + verb class. Verb-heuristic is a *fallback default only*, not the primary engine. +**Miss behavior (host-aware, per D3):** allowlist miss on **primary → hard-refuse (Tier-4)** (primary never guesses); on **server-01 → verb-heuristic default** allowed (sandbox-side may guess). Ambiguous even under heuristic → Tier-4. +**Seed corpus:** pre-populate SUDO.md via internet research on the common command set we actually run + what each does → assign each a tier up front. Reduces cold-start Tier-4 misses. +**Audit-driven classification loop (the self-completing part — this is the training-data mechanism):** +1. Unknown command arrives. Reproducible-verb unknowns route to the **sandbox** (Tier-2 treatment) — NOT the live target. Live-service unknowns on primary → Tier-4 until classified (no blind execution on primary). +2. Run in sandbox → analyze the audit trail (exit code, output, stderr). +3. If it executes cleanly → evidence supports a tier; propose the classification. +4. If it fails because the sandbox is missing something (path/package/config/user) → analyze the audit trail to identify the gap → add it to the command-surface mirror **rebuild script** → rebuild → re-run. This makes the LXC mirror (D1) self-completing over time. +5. The AI evaluator (P3) produces a **proposed** tier + audit evidence and writes the rule into SUDO.md in a **`proposed` (inactive) state on BOTH hosts** — a proposed rule cannot be used to execute until reviewed. It fires a **low-priority NTFY** ("new proposed Agent-Sudo rule — load conversation in an interactive session to review"). Rule becomes `active` only after the review discussion. Logged as training data throughout. + - **Why this does NOT contradict D4:** D4 killed the blocking *execution*-approval gate (timed out, on live commands the user couldn't evaluate). This is a **non-blocking async review of a config/rule change** — nothing is stalled waiting on it (no timeout failure mode), it's low-frequency (meta-level), and it happens in a session where the user CAN evaluate it with Claude's help. Different mechanism, different risk profile. + - Implementation detail (defer): NTFY must carry a stable conversation pointer for the review session (candidate: a dedicated `agent-sudo-rules` conversation, or the agent-builder conversation). + +### D6 — LOCKED (2026-07-08): operating principle = minimize human involvement, not eliminate it +Until human-level AGI exists, reducing the user's involvement to exactly zero is not achievable. Design target for every autonomous system = **minimize** required human touchpoints, not **eliminate** them. Legitimate residual touchpoints (Tier-4 interactive execution; proposed-rule review) are kept deliberately, made non-blocking and low-frequency, and each is justified. (Candidate universal feedback memory — applies beyond Agent-Sudo.) + +## OPEN GAPS (resume grill-me here) + +### D7 — LOCKED (2026-07-08): LXC architecture on server-01 +Verified live: both hosts = LMDE 7 "gigi" (Debian 13 trixie base), kernel 6.12.90; server-01 = 31GB RAM/27GB free, 173GB free disk, btrfs tooling present (no zfs), no LXC/LXD/Incus yet. Primary confirmed CLEAN of all testing infra (no LXC/sandbox/test containers; only prod postgres + vault; timeshift present = intended D3 snapshot tool). +- **Container tech = Incus** (Debian-trixie-native; profiles/limits/instant snapshots for cheap reset-to-baseline). +- **Base image = `images:debian/13` (trixie).** LMDE≈Debian under the hood; LMDE's deltas are desktop/theming, irrelevant to sudo command behavior. Confirmed acceptable by user. +- **Storage = btrfs pool** (loop-file or subvolume) for near-instant snapshot/rollback; fallback `dir` backend if root isn't btrfs. +- **Network = NAT via default `incusbr0`** — outbound works (nala/apt during classification tests), ZERO inbound LAN exposure (sandbox must not be reachable on 192.168.1.x). +- **Limits = 2 vCPU / 2GB RAM / 10GB disk each** (ephemeral scaffolds, no services). +- **Rebuild = one parameterized `build-sandbox.sh `** (D1 script-rebuildable; D5 loop appends missing deps). Differs per host only in package set + paths + users/sudoers. +- **P1 prerequisite = read-only host inventory** (the mirror spec): `dpkg --get-selections`, users/uids+groups+sudoers STRUCTURE (never secrets), relevant dir tree (`/opt/appdata`, `/data/coolify`, `/etc/...`). Both mirrors (`sandbox-primary`, `sandbox-server01`) run ON server-01; `sandbox-primary` mirrors production's command surface but never runs on production. + +### D9 — LOCKED (2026-07-08): privilege + security model = FULLY AUTONOMOUS, no human arming +Hard constraint (user): must be fully autonomous — if the user is unavailable, automation must not stall. So NO human-arming gate on the execution path. Once full autonomy is required, "can the machine escalate on its own?" = yes (settled); security moves OFF access-gating (theater) ONTO the constraint layer. +**The reframe (the crux):** separate two powers — +- **EXECUTE an already-vetted command** → fully autonomous, no human ever, always available (Tiers 0–3). +- **EXPAND what's allowed** (new command / raise tier / widen allowlist) → the ONLY human touchpoint; rare, async, non-blocking (D5 proposed-rule review). The autonomous system CANNOT widen its own allowlist. +Attacker win = "run arbitrary dangerous command" ⇒ requires widening the allowlist ⇒ impossible autonomously; executor enforces danger-veto regardless. Full compromise of the smart brain ⇒ only inherits "run already-safe/reversible/audited commands" = bounded blast radius. +**Unavailability is NOT a single point of failure:** all Tier 0–3 automation runs with user asleep/away; only (a) activating a brand-new capability and (b) catastrophic Tier-4 wait for a human — both SHOULD wait. +**Privilege mechanism:** dumb root daemon everywhere (auth-agnostic — survives user's fingerprint→privacyIDEA MFA→YubiKey roadmap; never calls sudo/PAM). Both hosts = always-on SCOPED short-TTL AppRole (reuse existing rotate-and-revoke machinery; policy reads ONLY the one Agent-Sudo secret path). Bitwarden + OS-sudo-password both DROPPED from design (Bitwarden was only for the abandoned OS-password pull). +**Autonomous-security stack (all human-free):** least-privilege allowlist · dumb minimal root executor · independent re-validation (already in code) · signed/read-only SUDO.md (Vault transit — allowlist can't be silently widened) · scoped short-TTL AppRole · executor sandboxing (AppArmor/seccomp/systemd) · egress lockdown (executor has no outbound net) · append-only audit shipped off-box · **anomaly circuit-breaker that self-disarms** (watches own command rate/mix/failure spike → auto-stops Tier-3 + alerts — replaces the human panic button) · **dual-service concurrence on Tier-3** (independent second classifier must agree). Primary stays stricter (D3): larger Tier-4 set, catastrophic → refuse-to-human. +**New tool? NO.** All pieces already exist (allowlist+executor, Vault AppRole + transit signing, AppArmor/seccomp/egress free on host). Falco (runtime anomaly detection) / OPA (policy-as-code) = documented FUTURE-ENHANCEMENT candidates only — new tools add attack surface + maintenance for a solo operator. + +### D10 — LOCKED (2026-07-08): Hermes as the always-on independent monitor (future layer) +Hermes (id=138, monitoring brain, local model on server-01, no Claude-API cost) is the natural home for the monitoring layers of D9 — layered on AFTER Agent-Sudo v1 ships. +**Security gains:** (1) Hermes IS the intelligent anomaly circuit-breaker/watchdog — reads the append-only audit stream, trips the breaker on anomaly, NTFYs user; being a SEPARATE process from Agent-Sudo = separation of duties (compromising Agent-Sudo ≠ compromising the watchdog). (2) The independent second opinion for dual-service Tier-3 concurrence. (3) Audit drift-detection + integrity monitoring (SUDO.md signature valid? AppArmor loaded? egress rules present? → tamper alert). +**Usability gains:** (1) triages/batches the two human touchpoints (proposed-rule activation, Tier-4) into digestible evidence-backed decisions. (2) Drives the D5 sandbox-test-and-analyze loop autonomously so unknown-command classification is one-click-confirm by review time. (3) Self-healing: detects failed side effects → triggers scoped-undo or Jenkins redeploy (per project_jenkins_hermes_boundary). +**HARD BOUNDARY:** Hermes is on the MONITORING side only — observe/analyze/alert/trip-breaker/trigger-pre-vetted-recovery. Hermes NEVER gets execution privilege, NEVER widens the allowlist, NEVER runs arbitrary Tier-3+. If Hermes could command the executor freely it becomes the soft underbelly (compromise Hermes = escalate). Keeps automated dual-control intact: Agent-Sudo (has root, dumb about judgment) + Hermes (no root, smart about judgment) check each other. +**Build order:** Agent-Sudo v1 ships with a simple self-contained rule-based circuit-breaker; Hermes later augments/replaces it as the intelligent watchdog. Ties to project_hermes_exploration_day (capability-map after id=138). + +## OPEN GAPS (resume grill-me here) +1. ✅ RESOLVED — SUDO.md classifier (see D4 tiers, D5 engine/seed/loop, D6 principle). +2. ✅ RESOLVED — LXC/Incus specifics (see D7). +3. ✅ RESOLVED — Cutover & rollback (D8). +4. ✅ DROPPED — Bitwarden ephemeral pull no longer in design (D9: Vault AppRole replaces it; no OS-password, no Bitwarden). + [Security model fully resolved: D9 + D10.] + +### D8 — LOCKED (2026-07-08): cutover sequence & rollback +Drop-in replacement: response contract stays identical (`POST /exec` Bearer → `{exit_code, output, ...}`; T4 refuse = 403 like today's allowlist-miss). Callers need zero changes; T2/T3 just add latency (sandbox/snapshot) transparently. D4 removes the tier-2 NTFY-approval block entirely. +- **Deploy order = server-01 FIRST, primary LAST** (user confirmed). server-01 = testing host, low blast radius, and hosts the sandbox LXCs (T2 local). Primary only after a clean 4-level pass on server-01. +- **Parallel run via ports:** Agent-Sudo stands up on **new port 8084** (⚠️ corrected 2026-07-08 from 8083 — 8083 is bitwarden-bridge) while old bridge keeps serving **8082** untouched. Shadow-validate (4-level gate on 8083 + replay known-safe commands through both, diff results). Callers stay on 8082 during validation → zero disruption. +- **Atomic swap when green:** flip Agent-Sudo to 8082, demote old bridge to 8083 hot-standby (kept running, not deleted). +- **Rollback = swap ports back** (old bridge one flip from resuming 8082). Retain old bridge container + image **14 days** before decommission (P5). +- **Primary cutover = MANUAL port-swap by user** in an interactive session (user confirmed). Agent-Sudo is built + validated autonomously everywhere; the final "make it live on production" flip on primary is user's hands on keyboard (per D3/D6). server-01 cutover = autonomous. +4. **Bitwarden ephemeral sudo password pull** — confirm path via bitwarden-bridge; never stored at rest. +5. ✅ RESOLVED (2026-07-08) — **Cross-cutting:** + - **Per-phase rollback/abort** — matrix baked into the P0–P5 prompts (P0 drop-table / P1 incus-delete / P2 delete-repo / P3 revoke-policy / P4 port-flip-back / P5 re-pull-image). No phase is destructive to the OLD bridge until P5. + - **Vault/N8N/Coolify dry-run paths** — Vault = additive `agent-sudo` policy + revoke-self smoke test (least-priv proof); N8N = ZERO surface (not a caller); Coolify = retired, Agent-Sudo deploys OUTSIDE it via Jenkins, P5 = plain stop+rm. + - **Training-data logging** — 6 decision points → `command_audit` rows; GOLD label = AI-eval-proposal → human review verdict (`human_verdict`). P0 schema carries the fields. + - **Blast radius per tier** — 0 minimal→3 medium/large→4 zero; capped on primary by shifting the dangerous middle up to refuse; **Tier-3-on-primary = highest-attention path** (most tests + dual-concurrence + tightest breaker). Full-compromise ⇒ inherits only already-vetted T0–3, cannot widen allowlist or reach T4. + - **Blocks/unblocks** — id=176 has NO blocker (buildable now); no DB row lists it as a blocker (link is architectural, not recorded). Unblocks: Hermes self-healing (id=138), the sandbox-testing program (it's the first test of the harness), and any Horizon-B autonomous automation needing unattended privileged exec. Does NOT unblock the 12 broken N8N workflows (those need the Jenkins/Coolify-API migration). + +## AFTER GRILL-ME COMPLETES +Write the **phased** background-agent prompt SET (NOT one monolith — see reasoning in session): +- P0: DB foundation (`command_audit` in projects + ensure `agent_test_results` in api_business) — design-complete, no server risk. +- P1: LXC/Incus setup + command-surface mirror build on server-01. +- P2: Python service = drop-in sudo-bridge replacement (same API), classifier reads SUDO.md, audit → command_audit. +- P3: Bitwarden ephemeral pull + Timeshift automation + AI evaluator (tier-2/3 flow). +- P4: deploy both servers behind old bridge, run 4-level gate. +- P5: decommission old sudo-bridge. + +Opus+high prompt-craft upgrades over the Sonnet-era prompts (agent_prompts.md): bake in the 4-level test gate + agent_test_results logging (Sonnet's omitted it); split locked-contract from reference-implementation instead of embedding brittle literal scripts; right-size per-phase turn budgets; explicit "design is locked — do not redesign" block; per-phase rollback/abort criteria + idempotent pre-flight checks. diff --git a/agent-builder/agent_sudo_vs_sudo_bridge.md b/agent-builder/agent_sudo_vs_sudo_bridge.md new file mode 100644 index 0000000..9bb108b --- /dev/null +++ b/agent-builder/agent_sudo_vs_sudo_bridge.md @@ -0,0 +1,53 @@ +# Agent-Sudo vs. the old sudo-bridge — what actually got better + +Written 2026-07-08. Companion to `agent_sudo_design_decisions.md` (design, D1–D10) and the P0–P5 build prompts in `agent_prompts.md`. + +Your instinct — *"more elaborate and complicated, but more eloquent and better-suited"* — is right, and here's the precise reason: **the extra machinery all buys back a single thing the old bridge could never have — safety WITHOUT a human in the loop.** The old bridge tried to be safe by *asking you*. Agent-Sudo is safe by *construction*. That's not a bigger version of the same idea; it's a different idea. + +--- + +## The one-sentence difference +- **sudo-bridge:** "Run it if it's on a list; for anything risky, ping Kaleb and wait." → safety depends on you being awake, reachable, and able to judge the command. +- **Agent-Sudo:** "Run it if it's provably safe/reversible; test it if it's unknown; snapshot it if it's live; refuse it if it's catastrophic — and never ask a human to rubber-stamp what they can't evaluate." → safety depends on nothing human at runtime. + +--- + +## Side-by-side + +| Dimension | Old sudo-bridge | Agent-Sudo | Why it matters | +|---|---|---|---| +| **Risk model** | 3 states: refuse / auto / **ask-human** | 5 tiers: auto-safe / auto-reversible / **sandbox-first** / **snapshot+execute** / hard-refuse | The middle of the old model was "interrupt Kaleb." The middle of the new model is "prove it's safe by testing or snapshotting." Judgment moved from you to evidence. | +| **The human gate** | Tier-2 = NTFY approve/deny, 900s timeout | **Eliminated.** Only residual human touch = a rare Tier-4 hard-refuse | The gate was *broken* (you missed NTFYs → timeouts, ×5 agents) AND *theatrical* (you approved commands you couldn't vet). Removing it is the whole point. | +| **Unknown command** | Not on list → refuse (dead end) | Reproducible → **test in an Incus mirror**, read the audit, self-classify; if the sandbox lacks a dep, **add it to the rebuild script and retry** | The old bridge never learned. Agent-Sudo's allowlist self-completes from evidence. | +| **Autonomy when you're away** | Stalls on anything tier-2 | Runs every Tier 0–3 unattended; only *expanding* what's allowed waits for you | Automations don't freeze at 3am because you're asleep. | +| **Live-service ops** (docker/systemctl/DB) | auto or ask — no safety net | **Timeshift snapshot + scoped-undo captured BEFORE execute + auto-verify after** | A bad restart is recoverable by construction, not by luck. | +| **Rollback** | none (a tier-1 command just ran) | **scoped command-specific undo** captured pre-execution (file backup / package record / Jenkins redeploy) | Blast radius = only what the command touched. | +| **Host awareness** | one flat list, same on both | **per-host tiering** — primary is stricter; a command that's Tier-3 on server-01 can be Tier-4 (refuse) on primary | Production (Vault/Authelia/media/DB) gets a bigger no-go set than the test box. | +| **Security philosophy** | gate access (ask a human) | **constrain capability** (least-priv allowlist it *cannot widen itself*) + execute-vs-expand split | Compromising the smart brain inherits only "do already-safe things" — it can't reach arbitrary commands or Tier-4. | +| **Config integrity** | allowlist.json, root-owned file | **SUDO.md signed via Vault transit**, daemon verifies before load | The ruleset can't be silently widened; tamper = refuse + alert. | +| **Credentials** | daemon runs as root, no auth on the exec path | dumb root executor **+ scoped short-TTL Vault AppRole** (reads exactly one secret path) + AppArmor/seccomp/egress-lockdown | Auth-agnostic and least-privilege; survives your fingerprint→privacyIDEA→YubiKey roadmap without redesign. | +| **Anomaly response** | none | **self-disarming circuit-breaker** (abnormal rate/mix/failure → auto-stop Tier-3 + alert) + **dual-service concurrence** on Tier-3 | The autonomous replacement for a human panic button. | +| **Watchdog** | none | **Hermes** (separate process, no exec power) as independent monitor/second-opinion/tamper-detector — later layer | Separation of duties: Agent-Sudo has hands + no judgment, Hermes has judgment + no hands; they check each other. | +| **Training data** | executions logged, never used | every classify/test/execute/review → labeled row in `command_audit`; **AI-proposal → your review verdict = gold label** for the local model | The system gets smarter over time and feeds the local brain. | +| **Audit** | JSONL→Postgres, fail-soft (good) | same resilience **+ append-only, shipped off-box** | Even a full host compromise can't erase the trail. | +| **Deploy/lifecycle** | Coolify-managed | plain docker-compose via **Jenkins/Gitea** (Coolify retired) | No dependency on retired infra; fits the current stack. | +| **API contract** | `POST /exec` etc. | **identical** (drop-in) — `/approve` `/deny` removed | Zero caller changes; T2/T3 just add latency transparently. | + +--- + +## What the old bridge did well (and Agent-Sudo keeps) +Not everything was wrong — these carry over unchanged: +- **Dumb root daemon + unprivileged brain** (separation of privilege) — kept and hardened. +- **Independent re-validation in the daemon** (defense in depth — the executor doesn't trust the brain) — kept. +- **Danger-pattern regex veto** — kept, now auto-routes to Tier-4. +- **fnmatch first-match-wins ordered list** — kept as the engine core, extended with per-host overrides + verb heuristic. +- **JSONL-first→Postgres fail-soft audit** — kept, extended to append-only/off-box + training fields. + +So it's an **evolution of a known-good core**, not a rewrite — which is exactly why the build (P2) refactors the existing `app.py`/`daemon` rather than greenfielding. + +--- + +## The honest cost of "more elaborate" +Agent-Sudo is genuinely more to build and run: an Incus sandbox substrate, a signed ruleset, a scoped-undo library, snapshot automation, an AI evaluator, a circuit-breaker, dual-concurrence. That complexity is real. But every piece exists to remove a human from the runtime path while *raising* safety — and the design deliberately **added no new external tool** (Incus, Vault, AppArmor, Timeshift, Jenkins already exist; Falco/OPA are parked as future options). The complexity lives in *our* code and config, not in new attack surface. + +**Bottom line:** the old bridge's ceiling was "a human bottleneck that half-works." Agent-Sudo's floor is "safe, autonomous, self-improving, and it can't widen its own powers." It's not that sudo-bridge was bad — it's that it was solving the problem with the one ingredient (you, at runtime) we most need to remove.