diff --git a/CLAUDE.md b/CLAUDE.md index b131fb4..b28be55 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,11 @@ hive-ag3nt/ in-container harness crate; produces TWO binaries src/login_session.rs drives `claude auth login` over stdio pipes src/bin/hive-ag3nt.rs sub-agent main src/bin/hive-m1nd.rs manager main - assets/ CSS + JS for the per-agent UI + assets/ index.html, agent.css, app.js (include_str!) + prompts/ static role/tools/settings for claude (include_str!): + agent.md — sub-agent system prompt + manager.md — manager system prompt + claude-settings.json — --settings JSON hive-sh4re/ wire types (HostRequest/Response, AgentRequest/Response, ManagerRequest/Response, Message, Approval, HelperEvent) @@ -71,11 +75,11 @@ nix/ - **Actions are factored.** `approve` / `deny` / `destroy` live in `actions.rs`; the admin socket and the dashboard POST handlers both call into them so the two surfaces never drift. -- **Async forms.** Dashboard mutating forms carry `data-async`; the - `assets/async_forms.js` helper intercepts submit, shows a spinner, and - fetches with `application/x-www-form-urlencoded` (axum `Form` extractor - rejects multipart). New mutating forms should add `data-async` and - optionally `data-confirm`. +- **Async forms.** Dashboard + per-agent mutating forms carry `data-async`; + a delegated `submit` listener in `assets/app.js` intercepts, shows a + spinner, POSTs with `application/x-www-form-urlencoded` (axum `Form` + extractor rejects multipart), calls `refreshState()` on success. New + mutating forms should add `data-async` and optionally `data-confirm`. ## Gotchas / lessons learned @@ -167,17 +171,27 @@ Manager additionally: config change for any agent (including `hm1nd` for self-mods). The shared per-turn plumbing lives in `hive_ag3nt::turn::{write_mcp_config, -run_turn, drive_turn, emit_turn_end, wait_for_login}` so the two binaries -can't drift. +write_settings, write_system_prompt, run_turn, drive_turn, emit_turn_end, +wait_for_login}` so the two binaries can't drift. + +On harness boot, three files get dropped next to the mcp socket at +`/run/hive/`: + +- `claude-mcp-config.json` — re-invokes the running binary as `mcp` child. +- `claude-settings.json` — `--settings` blob (auto-compact/auto-memory + off, effortLevel medium). +- `claude-system-prompt.md` — rendered from `prompts/{agent,manager}.md` + with `{label}` substituted. Passed via `--system-prompt-file`. Each turn: ``` claude --print --verbose --output-format stream-json --model haiku \ - --continue --settings '{"autoCompactEnabled":false,"autoMemoryEnabled":false}' \ - --mcp-config --strict-mcp-config \ + --continue --settings /run/hive/claude-settings.json \ + --system-prompt-file /run/hive/claude-system-prompt.md \ + --mcp-config /run/hive/claude-mcp-config.json --strict-mcp-config \ --tools --allowedTools -# prompt piped over stdin +# wake prompt piped over stdin — minimal, just from/body + optional unread hint ``` `--continue` keeps a persistent session per agent (claude stores sessions in @@ -187,14 +201,15 @@ overflow, retry once). **Loop control.** The harness pops one inbox message per cycle (the wake signal — Recv long-polls server-side for up to 30s waking instantly on a new -broker `Sent` event for this agent) and hands claude a prompt naming the -agent, the sender, the body, and the MCP tools. Claude drives any further -`recv`/`send` itself. +broker `Sent` event for this agent), peeks the remaining inbox depth with +`Status`, and emits `TurnStart { from, body, unread }`. The wake prompt +piped to claude includes a one-line `({unread} more pending — drain via …)` +hint when `unread > 0`. Claude drives any further `recv`/`send` itself. **Tool envelope** (`mcp::run_tool_envelope`): every MCP tool handler logs -the request, runs the body, appends a status line (e.g. -`[status] 3 unread message(s) in inbox` from a non-mutating `Status` peek), -logs the result. New tools call this helper. +the request, runs the body, logs the result. Pre-/post-log only; the old +`[status] N unread message(s)` appendage was removed once unread moved +into the wake prompt + UI header. New tools call this helper. **Tool whitelist** (`mcp::ALLOWED_BUILTIN_TOOLS`): - Allowed built-ins: `Bash`, `Edit`, `Glob`, `Grep`, `Read`, `TodoWrite`, @@ -207,9 +222,27 @@ logs the result. New tools call this helper. **Live view.** Each agent runs an `events::Bus` (a `tokio::sync::broadcast` wrapper). The harness emits -`TurnStart`, `Stream(value)` (one per parsed stream-json line), `Note`, -`TurnEnd`. The web UI subscribes via `/events/stream` (SSE) and a JS panel -appends rows. No full-page reload — operator input stays put. +`TurnStart { from, body, unread }`, `Stream(value)` (one per parsed +stream-json line), `Note`, `TurnEnd { ok, note }`. The web UI subscribes +via `/events/stream` (SSE) and a JS panel (terminal-themed: Crust bg, inset +shadow, monospace) renders rows: + +- `TurnStart` → `◆ TURN ← · N unread` header + indented body. +- `Stream` `tool_use` → `→ Read /path` / `→ Bash $ cmd` / + `→ send → operator: "..."` etc., per-tool pretty rather than raw JSON. +- `Stream` `tool_result` short → flat `← ...`; long → collapsed + `
` `▸ ← Nl · headline` (click to expand full body). +- `Stream` `thinking` → shows the thinking text if claude provided one, + otherwise the bare `· thinking …` indicator. +- `Stream` `system init`, `result`, `rate_limit_event` are dropped — too + noisy and `TurnEnd` already says the turn finished. +- `Note` → `· text`. +- `TurnEnd` → `✓ turn ok` / `✗ turn fail — note` and triggers a + `refreshState()` so the page form view reflects state transitions + (e.g. login just landed). + +The operator send form sits below the live panel, so the tail is what +you read first. ## Manager (hm1nd) is hive-c0re-managed @@ -237,6 +270,36 @@ Differences from sub-agents: block from your host NixOS config. hyperhive creates and updates the manager itself now. +**Manager policy** (from `prompts/manager.md`): the manager does NOT +rubber-stamp sub-agent config requests. It verifies (role match, package +legitimacy, cheaper alternative, blast radius) before committing + +calling `request_apply_commit`. For ambiguous cases or anything that +needs human signal, the manager forwards the question to the operator +via `send(to: "operator", ...)` — a dedicated `mcp__hyperhive__ask_operator` +tool with proper pause/resume semantics is in [TODO.md](TODO.md). + +## Helper events to the manager + +`Coordinator::notify_manager(&HelperEvent)` enqueues an inbox message +from sender `system` with the event JSON in the body. The manager +harness no longer short-circuits these — they drive a regular claude +turn so the manager can react. Variants +(`hive_sh4re::HelperEvent`): + +- `ApprovalResolved { id, agent, commit_ref, status, note }` — fired by + `actions::approve` + `actions::deny` whenever an approval transitions + to its terminal state. +- `Spawned { agent, ok, note }` — `actions::approve` (Spawn-kind) + + admin `HostRequest::Spawn`. +- `Rebuilt { agent, ok, note }` — `auto_update::rebuild_agent` (covers + startup scan + manual `/rebuild` from dashboard) + `actions::approve` + (ApplyCommit). +- `Killed { agent }` — admin `HostRequest::Kill` + dashboard `/kill`. +- `Destroyed { agent }` — `actions::destroy`. + +To add a new event: new `HelperEvent` variant + call sites + update +`prompts/manager.md` so the manager knows the new shape. + ## Auto-update on startup `hive-c0re serve` runs `auto_update::run` in a background task right after @@ -252,7 +315,23 @@ auto-update is a no-op — rebuild manually. The dashboard surfaces pending updates per agent: a clickable "needs update ↻" badge appears whenever the marker differs from current rev. The badge POSTs `/rebuild/`, calling the same `auto_update::rebuild_agent` -path so manual triggers and the startup scan can't drift. +path so manual triggers and the startup scan can't drift. When at least +one container is stale, a top-level `↻ UPD4TE 4LL` button appears that +loops over every stale container. + +## Dashboard action surface + +Container row buttons (rendered per-state by `assets/app.js`): + +- Always: `↻ R3BU1LD` (calls `lifecycle::rebuild`), and for sub-agents + `DESTR0Y` (container removed, state + creds kept). +- Running: `↺ R3ST4RT` + (sub-agents only) `■ ST0P`. +- Stopped: `▶ ST4RT`. +- Stale marker: clickable `needs update ↻` badge (same target as rebuild + but only shown when out of date). + +Top of the containers list: `↻ UPD4TE 4LL` (when any stale) + the +"R3QU3ST SP4WN" form for queuing a new agent through the approval flow. ## Approval flow diff --git a/TODO.md b/TODO.md index 8c1d1ae..d9d5647 100644 --- a/TODO.md +++ b/TODO.md @@ -25,9 +25,6 @@ Pick anything from here when relevant. Cross-cutting design notes live in ## UI / UX -- **Operator inbox view.** Drain replies addressed to `operator` and show - them on the dashboard. Today they accumulate in sqlite unread; you can - only see them by watching the live panel of the agent that sent them. - **Per-agent UI substance.** Show last N inbox messages, last turn timing, link back to dashboard. - **xterm.js terminal** embedded per-agent, attached to a PTY exposed by