Watch
0
0
Fork
You've already forked hyperhive
0

docs(turn-loop): facts + structure pass

Frame the turn loop runtime-neutrally: every turn runs through
hive-runtime, on claude (default) or an ACP agent. The loop steps,
harness binary shape and hive-agent README now say so; claude-only
failure detection gets its own heading; claude-invocation.md opens with
its scope and links the ACP side to hive-runtime/README.md.

Fact fixes: on-boot file paths (/run/hive-config, not /run/hive),
hive-claude is a crates.io dependency with no README here, hive-agent
has no client.rs or forge_notify.rs, the claude launch-config layer is
hive-agent's mcp_config.rs, agent forge/matrix accounts are
swarm-controller's, hive-c0re's dashboard is dashboard/, and the
deprecated hyperhive.gui.enable / hyperhive.extraMcpServers spellings.

Refs #3902
This commit is contained in:
atlas 2026-10-01 23:20:35 +02:00 • committed by mara
commit fd74cbd495
4 changed files with 74 additions and 48 deletions

View file

@ -33,8 +33,9 @@ hand-maintained per-file tree drifts out of sync with the code.
the separate `hivectl` crate, which talks to the daemon over the host the separate `hivectl` crate, which talks to the daemon over the host
admin socket. Owns the sqlite broker, approval + reminder + admin socket. Owns the sqlite broker, approval + reminder +
schedule queues, the meta flake, lifecycle (`nixos-container` schedule queues, the meta flake, lifecycle (`nixos-container`
shellouts), gateway / forge / matrix provisioning, per-container stats, shellouts), gateway / forge / matrix wiring (agent forge and matrix
and the axum operator dashboard (`dashboard.rs`). Largest crate. accounts are `swarm-controller`'s), per-container stats, and the axum
operator dashboard (`dashboard/`). Largest crate.
- **`hivectl/`** — standalone operator CLI (`hivectl` binary). Talks to - **`hivectl/`** — standalone operator CLI (`hivectl` binary). Talks to
the `hive-c0re` daemon over the host admin socket (`hive-host-sock` the `hive-c0re` daemon over the host admin socket (`hive-host-sock`
wire types) — does NOT link `hive-c0re`. Full, always-current verb wire types) — does NOT link `hive-c0re`. Full, always-current verb
@ -47,11 +48,10 @@ hand-maintained per-file tree drifts out of sync with the code.
- **`hive-agent/`** — the serve-loop binary: turn-loop *policy* layer - **`hive-agent/`** — the serve-loop binary: turn-loop *policy* layer
(`turn.rs`) over `hive-runtime`, per-agent web UI (`web_ui/` (`turn.rs`) over `hive-runtime`, per-agent web UI (`web_ui/`
module dir), event + turn-stats sqlite sinks, login flow, system-prompt module dir), event + turn-stats sqlite sinks, login flow, system-prompt
renderer. renderer, and the claude launch-config layer (`mcp_config.rs`:
tool-group/capability → `--allowedTools`, `--mcp-config` render).
- **`hive-agent-mcp/`** — the embedded MCP server (long-lived - **`hive-agent-mcp/`** — the embedded MCP server (long-lived
streamable-http listener, `hive-mcp-http` systemd unit) + its claude streamable-http listener, `hive-mcp-http` systemd unit).
launch-config layer (tool-group/capability → `--allowedTools`,
`--mcp-config` render).
- **`hive-runtime/`** — the runtime an agent's turns run on: one - **`hive-runtime/`** — the runtime an agent's turns run on: one
`Runtime` interface (`run`/`compact`/`archive`/`canceller`/`choices`) with a `claude` backend `Runtime` interface (`run`/`compact`/`archive`/`canceller`/`choices`) with a `claude` backend
(a pass-through to `hive-claude`) and an `acp` backend (any Agent Client (a pass-through to `hive-claude`) and an `acp` backend (any Agent Client
@ -89,7 +89,7 @@ hand-maintained per-file tree drifts out of sync with the code.
journal, so both layers together store every record twice. Deliberately journal, so both layers together store every record twice. Deliberately
single-purpose — do not grow it into a utility crate. single-purpose — do not grow it into a utility crate.
- **`hive-screen-mcp/`** — stdio MCP bridge for GUI agents - **`hive-screen-mcp/`** — stdio MCP bridge for GUI agents
(`hyperhive.gui.enable`): `screenshot` via `grim`, `type_text` / (`services.hyperhive.agent.gui.enable`): `screenshot` via `grim`, `type_text` /
`key_press` via `wtype` (Wayland virtual-keyboard protocol), and `key_press` via `wtype` (Wayland virtual-keyboard protocol), and
`mouse_move` / `mouse_click` as RFB pointer events to the local neatvnc `mouse_move` / `mouse_click` as RFB pointer events to the local neatvnc
server. All userspace, no daemon of its own. server. All userspace, no daemon of its own.
@ -152,7 +152,7 @@ hand-maintained per-file tree drifts out of sync with the code.
`hive-core-agent-sock` above: **this socket never leaves the container** `hive-core-agent-sock` above: **this socket never leaves the container**
and `hive-c0re` is not in the path at all — no broker round-trip, no and `hive-c0re` is not in the path at all — no broker round-trip, no
long-poll, no marker files. Also carries `extra_mcp`: the long-poll, no marker files. Also carries `extra_mcp`: the
`hyperhive.extraMcpServers` spec + parsing, shared by `hive-agent` and `services.hyperhive.agent.extraMcpServers` spec + parsing, shared by `hive-agent` and
`hive-subagent-mcp` — not a socket protocol, just the natural home since `hive-subagent-mcp` — not a socket protocol, just the natural home since
both already depend on this crate. both already depend on this crate.
- **`hive-types/`** — zero-dependency (bar serde) leaf crate holding the - **`hive-types/`** — zero-dependency (bar serde) leaf crate holding the

View file

@ -1,7 +1,20 @@
# Turn loop + MCP # Turn loop + MCP
How the harness wakes up, what it asks claude to do, and what tools How the harness wakes up, what it asks the agent's runtime to do, and what
claude has access to in return. tools the agent has access to in return.
## Runtimes
Every turn runs through `hive-runtime`, on the runtime
`services.hyperhive.agent.runtime` selects:
- **`claude`** (default) — one `claude --print` process per turn.
→ [claude-invocation.md](claude-invocation.md)
- **`acp`** — any Agent Client Protocol agent, run as one long-lived child
of the harness. → [`hive-runtime/README.md`](../../hive-runtime/README.md)
Both report the turn in claude's `stream-json` shape, so the loop below is
the same for either.
## The loop ## The loop
@ -22,21 +35,22 @@ agents) runs:
2. Pop one message. Peek the remaining inbox depth with `Status`. 2. Pop one message. Peek the remaining inbox depth with `Status`.
3. Emit `LiveEvent::TurnStart { from, body, unread }` onto the SSE 3. Emit `LiveEvent::TurnStart { from, body, unread }` onto the SSE
bus. bus.
4. Spawn claude (one process per turn) and pipe the wake prompt 4. Hand the wake prompt to the runtime: on `claude`, spawn
over stdin. `claude --print` and pipe it over stdin; on `acp`, send it as a
5. Stream stdout (JSON lines) into the bus as prompt on the agent's session.
5. Stream the turn's `stream-json` lines into the bus as
`LiveEvent::Stream(value)`. Pump stderr as `Note`. `LiveEvent::Stream(value)`. Pump stderr as `Note`.
6. Wait for claude to exit and classify the turn's outcome from the 6. Wait for the turn to end and classify its outcome from the
stream + exit — success, compaction, rate-limit, auth-failure, or stream + result — success, compaction, rate-limit, auth-failure, stall,
hard failure. The outcome drives the post-turn action (see or hard failure. The outcome drives the post-turn action (see
[Turn outcomes](#turn-outcomes)); the session handles compaction [Turn outcomes](#turn-outcomes)); the session handles compaction
internally (see internally (see
[Compaction](claude-invocation.md#compaction)). This page describes [Compaction](claude-invocation.md#compaction)). This page describes
rate-limit and auth-failure detection [below](#failure-detection-and-login). rate-limit and auth-failure detection [below](#failure-detection-and-login-claude).
7. Emit `LiveEvent::TurnEnd { ok, note }`. Sleep `poll_ms` to avoid 7. Emit `LiveEvent::TurnEnd { ok, note }`. Sleep `poll_ms` to avoid
tight loops on transient failures. tight loops on transient failures.
### Failure detection and login ### Failure detection and login (claude)
- **Rate limit** — a `429` / `rate_limit` marker on stderr, or a parsed - **Rate limit** — a `429` / `rate_limit` marker on stderr, or a parsed
`{"type":"error"}` rate-limit event on stdout (conversation-text `{"type":"error"}` rate-limit event on stdout (conversation-text
@ -65,13 +79,16 @@ agents) runs:
## Harness binary shape ## Harness binary shape
Two sibling crates, both role-agnostic (there is one role: agent — Two sibling binaries over one runtime library, all role-agnostic (there
the privilege boundary lives server-side at the broker socket is one role: agent — the privilege boundary lives server-side at the
(`/run/hive/mcp.sock`), which refuses privileged `Request` variants broker socket (`/run/hive/mcp.sock`), which refuses privileged `Request`
regardless of who sends them): variants regardless of who sends them):
- `hive-agent` — long-running harness loop (the inbox poll + - `hive-agent` — long-running harness loop (the inbox poll +
claude-pump + ack/requeue cycle described above). runtime turn + ack/requeue cycle described above).
- `hive-runtime` — the library every turn runs through, one `Runtime`
impl per runtime (`ClaudeRuntime`, `AcpRuntime`); `hive-subagent-mcp`
drives its nested sessions through it too.
- `hive-agent-mcp` — MCP server for the built-in `hyperhive` surface. - `hive-agent-mcp` — MCP server for the built-in `hyperhive` surface.
Run with `--http <addr>` as a persistent streamable-HTTP daemon (the Run with `--http <addr>` as a persistent streamable-HTTP daemon (the
`hive-mcp-http` systemd unit, on `services.hyperhive.agent.mcp.httpPort`, default `hive-mcp-http` systemd unit, on `services.hyperhive.agent.mcp.httpPort`, default
@ -130,8 +147,8 @@ else a `TurnError`) drives the post-claude branch:
| `Err(Failed(err))` | route `[system] \`<qualified-label>\` claude turn failed:\n<err>` to `operator` via `send_to_operator` | | `Err(Failed(err))` | route `[system] \`<qualified-label>\` claude turn failed:\n<err>` to `operator` via `send_to_operator` |
`ApiStall` catches an Anthropic API stall — a multi-retry connection storm where `ApiStall` catches an Anthropic API stall — a multi-retry connection storm where
the stream goes silent for minutes. The idle watchdog lives in `hive-claude`'s the stream goes silent for minutes. On `claude` the idle watchdog lives in
driver (`Config::idle_timeout`, enforced around `child.wait()`): the timer `hive-claude`'s driver (`Config::idle_timeout`, enforced around `child.wait()`): the timer
resets on every stdout line, so a large but still-streaming turn is never cut — resets on every stdout line, so a large but still-streaming turn is never cut —
only complete output silence for the window trips it. The harness sets the only complete output silence for the window trips it. The harness sets the
window from `HIVE_TURN_IDLE_SECS` (`0` disables) and maps the driver's window from `HIVE_TURN_IDLE_SECS` (`0` disables) and maps the driver's

View file

@ -1,20 +1,26 @@
# The claude invocation # The claude invocation
How a turn runs on the `claude` runtime, the default
(`services.hyperhive.agent.runtime`). An `acp` agent's turns:
→ [`hive-runtime/README.md`](../../hive-runtime/README.md).
``` ```
claude --print --verbose --output-format stream-json --model <name> \ claude --print --verbose --output-format stream-json --model <name> \
--effort <level> --resume <title> # or --name <title> on first use \ --effort <level> --resume <title> # or --name <title> on first use \
--system-prompt-file /run/hive/claude-system-prompt.md \ --system-prompt-file /run/hive-config/claude-system-prompt.md \
--mcp-config /run/hive/claude-mcp-config.json --strict-mcp-config \ --mcp-config /run/hive-config/claude-mcp-config.json --strict-mcp-config \
--tools <builtins> --allowedTools <builtins+mcp> --tools <builtins> --allowedTools <builtins+mcp>
# wake prompt piped over stdin # wake prompt piped over stdin
``` ```
**Crate split.** The generic subprocess mechanics — spawning **Crate split.** `hive-agent` runs every turn through `hive-runtime`,
`claude --print`, streaming + classifying stream-json, session whose `ClaudeRuntime` is a pass-through to the external **`hive-claude`**
lookup/archive, and the durable-session compaction loop — live in the crate (a crates.io dependency, not in this repo). `hive-claude` holds the
reusable **`hive-claude`** crate (`hive_claude::{Claude, InfiniteSession, generic subprocess mechanics — spawning `claude --print`, streaming +
Attach, CompactionPolicy, PercentPolicy, Telemetry, Sink, SessionStore}`; classifying stream-json, session lookup/archive, and the durable-session
see `hive-claude/README.md`). `hive-agent`'s `turn` module is the compaction loop (`hive_claude::{Claude, InfiniteSession, Attach,
CompactionPolicy, PercentPolicy, Telemetry, Sink, SessionStore}`).
`hive-agent`'s `turn` module is the
hyperhive **policy layer** on top: it builds the per-turn config from hyperhive **policy layer** on top: it builds the per-turn config from
the bus, bridges the output stream onto the event bus (`BusSink`), and the bus, bridges the output stream onto the event bus (`BusSink`), and
owns the compaction / autoreset / retry decisions in `drive_turn`. The owns the compaction / autoreset / retry decisions in `drive_turn`. The
@ -278,5 +284,6 @@ identity, the reset/autoreset/retry state machine, and the
telemetry-to-bus bridge — lives in `hive-agent`'s `turn` module; see its telemetry-to-bus bridge — lives in `hive-agent`'s `turn` module; see its
`//!` doc comment (`hive-agent/src/turn.rs`) for the exact call shape. `//!` doc comment (`hive-agent/src/turn.rs`) for the exact call shape.
The actual claude spawn, stream classification, and the The actual claude spawn, stream classification, and the
reactive/proactive compaction loop are in the `hive-claude` crate. reactive/proactive compaction loop are in the `hive-claude` crate, reached
through `hive-runtime`'s `ClaudeRuntime`.
Login-wait lives in `hive-agent`'s `login` module. Login-wait lives in `hive-agent`'s `login` module.

View file

@ -1,8 +1,9 @@
# hive-agent # hive-agent
The in-container harness serve-loop binary — one instance per agent. The in-container harness serve-loop binary — one instance per agent.
Long-polls the broker inbox and drives one `claude --print` turn per Long-polls the broker inbox and drives one turn per inbox message
inbox message, over the `hive-claude` driver. There is one role here through `hive-runtime`, on the agent's runtime (`claude --print` by
default, or an ACP agent). It has one role
(agent); the `Surface` trait + `AgentSurface` zero-sized type tag keep (agent); the `Surface` trait + `AgentSurface` zero-sized type tag keep
the turn loop generic and testable for future roles without a the turn loop generic and testable for future roles without a
parallel copy of the loop. parallel copy of the loop.
@ -11,20 +12,21 @@ parallel copy of the loop.
You don't call into this crate from elsewhere — it's the top-level You don't call into this crate from elsewhere — it's the top-level
binary systemd starts per agent container. Look here when you need to binary systemd starts per agent container. Look here when you need to
understand or change: what happens between "a message lands in the understand or change: what happens between a message landing in the
inbox" and "claude produces a reply", how login/auth is bootstrapped, inbox and the agent's reply, how the harness bootstraps login/auth,
how the per-agent web UI is served, or how turn/event stats get how it serves the per-agent web UI, or how turn/event stats get
recorded. Architecture detail lives in recorded. Architecture detail lives in
[`docs/turn-loop/`](../docs/turn-loop/README.md); this README is just the [`docs/turn-loop/`](../docs/turn-loop/README.md); this README is just the
map of the module tree. map of the module tree.
## Shape ## Shape
- **`main.rs`** — the serve loop and the `Surface` trait; broker calls
(inbox poll, ack, send) go through `hive-sock-client` with
`hive-core-agent-sock` wire types.
- **`turn.rs`** — the turn-loop policy layer: renders the system - **`turn.rs`** — the turn-loop policy layer: renders the system
prompt + MCP config, invokes `hive-claude`, classifies the outcome, prompt + MCP config, runs the turn through `hive-runtime`, classifies
and feeds the event/turn-stats sinks. the outcome, and feeds the event/turn-stats sinks.
- **`client.rs`** — broker client (inbox poll, ack, send) speaking the
`hive-sh4re` wire protocol.
- **`login.rs` / `login_session.rs`** — first-run and session-resume - **`login.rs` / `login_session.rs`** — first-run and session-resume
auth flow for the `claude` CLI. auth flow for the `claude` CLI.
- **`mcp_config.rs`** — renders the per-turn `--mcp-config` / - **`mcp_config.rs`** — renders the per-turn `--mcp-config` /
@ -37,8 +39,6 @@ map of the module tree.
- **`events.rs` / `turn_stats.rs` / `stats.rs`** — append-only event - **`events.rs` / `turn_stats.rs` / `stats.rs`** — append-only event
sink and per-turn telemetry recording (context usage, cost, tool sink and per-turn telemetry recording (context usage, cost, tool
favorites) under `harness/`. favorites) under `harness/`.
- **`forge_notify.rs`** — subscribes to forge notifications and wakes
the harness on new activity.
- **`prompt.rs`** — system-prompt renderer (persona + tool docs + - **`prompt.rs`** — system-prompt renderer (persona + tool docs +
environment facts). environment facts).
- **`web_ui/`** — the per-agent dashboard (terminal pane, status, - **`web_ui/`** — the per-agent dashboard (terminal pane, status,
@ -46,6 +46,8 @@ map of the module tree.
- **`paths.rs`** — canonical path resolution for state/harness dirs and - **`paths.rs`** — canonical path resolution for state/harness dirs and
the harness-local sqlite files. the harness-local sqlite files.
Sibling: **`hive-agent-mcp`** (the MCP server this loop points claude Siblings: **`hive-agent-mcp`** (the MCP server this loop points the
at every turn). Both are described together in agent at every turn) and **`hive-runtime`** (the library a turn runs
[`docs/turn-loop/::Harness binary shape`](../docs/turn-loop/README.md). through).
[`docs/turn-loop/::Harness binary shape`](../docs/turn-loop/README.md#harness-binary-shape)
covers all three together.