hive-ag3nt + docs: extract harness binary shape prose (#716 batch 5)

`bin/hive.rs` carries ~15 attribution cookies (#598, #676, #692,
#693, #778, #788, #682, #688) plus structural prose about the
single-binary architecture (role-from-env), the `Surface` trait
pattern (zero-sized type tags + generic dispatch), boot wiring
(label fallback, plugin install → send_to_parent failure routing),
and turn-outcome branching (ack / requeue / wait_for_login / parent
notify).

Migrated to a new `docs/turn-loop.md::Harness binary shape`
subsection covering:

- Single-binary rationale (server-side privilege boundary on the
  broker socket means no escalation risk in shipping both wire
  surfaces in the same process).
- Three subcommands (`serve` / `mcp` / `wake`).
- Surface trait + zero-sized type tags (AgentSurface,
  ManagerSurface) — picks per-role FLAVOR / FORGE_IS_MANAGER and
  exposes the broker wire ops behind trait methods so the turn
  loop is written once.
- Boot wiring (HIVE_PORT, HIVE_LABEL fallback, plugin failures
  routed via send_to_parent, web_ui + forge_notify spawn).
- Turn outcomes table (Ok/Compacted/RateLimited/AuthFailed/Failed)
  + continue-sentinel pickup.

In-code rustdocs reduced to 1-line summaries + doc pointers;
inline cookies stripped from boot wiring + plugin install +
sentinel cleanup paths.

15 → 0 cookies in bin/hive.rs; cargo check -p hive-ag3nt passes.
This commit is contained in:
iris 2026-05-31 17:15:05 +02:00 committed by mara
commit 94c110fd5f
2 changed files with 114 additions and 53 deletions

View file

@ -52,6 +52,86 @@ binary, not two) runs:
7. Emit `LiveEvent::TurnEnd { ok, note }`. Sleep `poll_ms` to avoid
tight loops on transient failures.
## Harness binary shape
One `hive` binary serves both roles. The split into
`hive-ag3nt` + `hive-m1nd` was collapsed because the privilege
boundary lives server-side at the broker socket
(`/run/hive/mcp.sock`): an agent-flavor socket refuses
`ManagerRequest` calls regardless of who sends them, so there's no
escalation risk in shipping the same code to both. `main()` reads
`$HIVE_ROLE` (set by `harness-base.nix` from `hyperhive.role`;
defaults to `"agent"` for standalone `nix run` invocations) and
dispatches.
Three subcommands:
- `serve` — long-running harness loop (the inbox poll +
claude-pump + ack/requeue cycle described above).
- `mcp` — stdio MCP server claude spawns via `--mcp-config` per
turn. Same binary, different mode.
- `wake --from <name> --body <body>` — push a message into our own
inbox so the next turn fires with the given body. Used by
co-process daemons (matrix bridge, scraper, webhook listeners)
to nudge claude on external events. `--body -` reads from stdin.
### `Surface` trait + zero-sized type tags
`AgentRequest` / `AgentResponse` and `ManagerRequest` /
`ManagerResponse` are wire-disjoint, but the turn loop itself
(boot → recv → drive → ack/requeue → stats → continue-sentinel)
is identical regardless of role. `bin/hive.rs` factors that
sameness through a `Surface` trait with two zero-sized impls
(`AgentSurface`, `ManagerSurface`) wrapping:
- Per-role MCP `Flavor` constant (picks which system-prompt block
+ tool registration goes into the spawned claude).
- Per-role `forge_notify::run` flag (picks `AgentRequest::Wake`
vs `ManagerRequest::Wake` so the broker socket accepts the
push).
- One async method per wire op: `ack_turn`, `requeue_inflight`,
`inbox_unread`, `post_turn_counts`, `send_to_parent`,
`self_wake`, `recv_next`, `wake_external`.
`main()`'s dispatch picks `serve_main::<AgentSurface>` vs
`serve_main::<ManagerSurface>` and the turn logic stays in
lockstep by construction — there's no separate per-role copy of
`serve_loop` / `handle_turn` / `wake`.
### Boot wiring
`serve_main` reads `HIVE_PORT` (default `DEFAULT_WEB_PORT`) +
`HIVE_LABEL` (default `"hive"` for standalone runs; the meta
flake sets it unconditionally for any container-deployed agent;
see `docs/conventions.md::Hive identity` for the env stack),
opens turn-stats sqlite, prepares the on-boot files (see below),
installs claude plugins, spawns `forge_notify::run` + `web_ui::serve`,
and either drops into `serve_loop` directly (`Online`) or parks on
the login flow first (`NeedsLogin`).
Plugin install failures are not fatal: each entry comes back as a
human-readable failure string that gets routed via
`Surface::send_to_parent` to the agent's topology parent (the
broker resolves `<parent>` per `topology::parent_of`; root agents
and the manager fall through to operator).
### Turn outcomes
`turn::TurnOutcome` drives the post-claude branch:
| Outcome | Action |
| --- | --- |
| `Ok` / `Compacted` | `ack_turn` |
| `RateLimited` | sleep `HIVE_RATE_LIMIT_SLEEP_SECS` (default 300), requeue inflight, status back to `online` |
| `AuthFailed` | emit `needs_login_idle` sentinel, requeue inflight, park in `wait_for_login` |
| `Failed(err)` | route `[system] \`<qualified-label>\` claude turn failed:\n<err>` to `<parent>` via `send_to_parent` |
After the outcome handler, the stats sink records a row and the
`hyperhive-continue` sentinel (dropped by the `request_next_turn`
MCP tool) is consumed if present, firing `self_wake` so the next
turn starts with `{ from: "self", body: "continue" }` even if the
inbox is empty.
## The claude invocation
```