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

@ -1,7 +1,20 @@
# Turn loop + MCP
How the harness wakes up, what it asks claude to do, and what tools
claude has access to in return.
How the harness wakes up, what it asks the agent's runtime to do, and what
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
@ -22,21 +35,22 @@ agents) runs:
2. Pop one message. Peek the remaining inbox depth with `Status`.
3. Emit `LiveEvent::TurnStart { from, body, unread }` onto the SSE
bus.
4. Spawn claude (one process per turn) and pipe the wake prompt
over stdin.
5. Stream stdout (JSON lines) into the bus as
4. Hand the wake prompt to the runtime: on `claude`, spawn
`claude --print` and pipe it over stdin; on `acp`, send it as a
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`.
6. Wait for claude to exit and classify the turn's outcome from the
stream + exit — success, compaction, rate-limit, auth-failure, or
hard failure. The outcome drives the post-turn action (see
6. Wait for the turn to end and classify its outcome from the
stream + result — success, compaction, rate-limit, auth-failure, stall,
or hard failure. The outcome drives the post-turn action (see
[Turn outcomes](#turn-outcomes)); the session handles compaction
internally (see
[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
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
`{"type":"error"}` rate-limit event on stdout (conversation-text
@ -65,13 +79,16 @@ agents) runs:
## Harness binary shape
Two sibling crates, both role-agnostic (there is one role: agent —
the privilege boundary lives server-side at the broker socket
(`/run/hive/mcp.sock`), which refuses privileged `Request` variants
regardless of who sends them):
Two sibling binaries over one runtime library, all role-agnostic (there
is one role: agent — the privilege boundary lives server-side at the
broker socket (`/run/hive/mcp.sock`), which refuses privileged `Request`
variants regardless of who sends them):
- `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.
Run with `--http <addr>` as a persistent streamable-HTTP daemon (the
`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` |
`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
driver (`Config::idle_timeout`, enforced around `child.wait()`): the timer
the stream goes silent for minutes. On `claude` the idle watchdog lives in
`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 —
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

View file

@ -1,20 +1,26 @@
# 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> \
--effort <level> --resume <title> # or --name <title> on first use \
--system-prompt-file /run/hive/claude-system-prompt.md \
--mcp-config /run/hive/claude-mcp-config.json --strict-mcp-config \
--system-prompt-file /run/hive-config/claude-system-prompt.md \
--mcp-config /run/hive-config/claude-mcp-config.json --strict-mcp-config \
--tools <builtins> --allowedTools <builtins+mcp>
# wake prompt piped over stdin
```
**Crate split.** The generic subprocess mechanics — spawning
`claude --print`, streaming + classifying stream-json, session
lookup/archive, and the durable-session compaction loop — live in the
reusable **`hive-claude`** crate (`hive_claude::{Claude, InfiniteSession,
Attach, CompactionPolicy, PercentPolicy, Telemetry, Sink, SessionStore}`;
see `hive-claude/README.md`). `hive-agent`'s `turn` module is the
**Crate split.** `hive-agent` runs every turn through `hive-runtime`,
whose `ClaudeRuntime` is a pass-through to the external **`hive-claude`**
crate (a crates.io dependency, not in this repo). `hive-claude` holds the
generic subprocess mechanics — spawning `claude --print`, streaming +
classifying stream-json, session lookup/archive, and the durable-session
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
the bus, bridges the output stream onto the event bus (`BusSink`), and
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
`//!` doc comment (`hive-agent/src/turn.rs`) for the exact call shape.
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.