The claude invocation

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 \
  --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_ag3nt::turn 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 / auto-reset / retry decisions in drive_turn. The lib returns everything it parsed from a turn (usage, cost, context window, resolved model) as Telemetry, which the policy layer applies to the bus.

Hive-enforced settings ship at /etc/claude-code/managed-settings.json (claude-code's canonical managed-settings path — precedence #1, read-only, un-overridable), wired in nix/agent-modules/claude-settings.nix from the prompts/claude-settings.json asset. effortLevel is deliberately not in that file — effort is controlled live via the --effort flag (HIVE_DEFAULT_EFFORT / the per-agent UI slider), which managed scope would otherwise lock.

<name> is read from Bus::model() on each turn. The initial default is set by hyperhive.model in the agent's agent.nix (NixOS option; propagates via HIVE_DEFAULT_MODEL env var; falls back to "haiku" if unset). The operator can flip it at runtime with /model <name> in the web terminal — the next turn picks it up. The choice is persisted to /harness/hyperhive-model so it survives restart; override path: HYPERHIVE_MODEL_FILE env var for tests.

Context-window size is looked up per-model via events::context_window_tokens(model). Resolution order (first match wins):

  1. HIVE_CONTEXT_WINDOW_TOKENS_<KEY> env var, where KEY (lowercased) is a substring of the active model name. Injected by the meta flake from services.hyperhive.c0re.contextWindowTokens (host-level NixOS option, defaults: haiku=200k, sonnet=1M, opus=1M). Override these for all agents at once without a per-agent config change.
  2. HIVE_CONTEXT_WINDOW_TOKENS — single global override for any model (useful in dev / test).
  3. Hard fallback: 200_000 (conservative; only reached outside NixOS where the env vars aren't set).

The effective window drives watermarks and is exposed at runtime via /api/state.context_window_tokens so the UI can show a percentage-of-window ctx badge.

Session identity — a constant title. Every turn keys on one fixed, harness-owned session title (turn::session_title(), default hive-session, override HIVE_SESSION_TITLE). The durable hive_claude::InfiniteSession (built once by the serve loop via turn::make_session, then reused) --resume <title>s it; the first use (bootstrap, post-archive, post-purge) misses and the session re-runs the same prompt once with --name <title> to mint it. That single self-heal rule is the whole identity system — there is no scraped session-id file. Because the title is constant, /compact and its post-compact retry provably target the same session (killing the old "compact ran on a different/empty session" bug), and a choom invocation in the same cwd can't hijack the context (it won't carry our title). claude stores sessions in ~/.claude/projects/<cwd-slug>/<uuid>.jsonl (bind-mounted persistently); --name writes the title into the file as a custom-title event, which is what --resume <title> resolves against. We never pass bare --continue (it resumes the latest session in the cwd — the hijack vector). Auto-compact, auto-memory, and dynamic workflows (the /workflows feature) are disabled via the managed settings at /etc/claude-code/managed-settings.json: hyperhive owns compaction (see Compaction below), and disableWorkflows keeps the /workflows machinery from spawning sub-runs that burn usage on the harness's autonomous turns.

Session reset is available via POST /api/new-session (or /new-session slash command). It does not touch the session inline — that would race a mid-write claude process. Instead Bus::request_session_reset() sets a one-shot flag consumed at the next turn boundary by drive_turn, which archives the current session: the backing <uuid>.jsonl is renamed to <uuid>.jsonl.archived (dropped out of claude's *.jsonl resolution glob, history preserved on disk, only the file carrying our title — any choom session sharing the cwd is left alone). The next turn's --resume <title> then misses and self-heals into a fresh session.

Compaction

claude's own in-session auto-compact is off (via the managed settings at /etc/claude-code/managed-settings.json); hyperhive owns it. The hive_claude::InfiniteSession keeps the session alive across the context window with two triggers baked into its run:

The when is a hive_claude::CompactionPolicy injected by the harness: turn::make_session builds a PercentPolicy that compacts once the model-reported context fill reaches HIVE_COMPACT_WATERMARK_PERCENT (default 75%), falling back to events::context_window_tokens(model) for the window on turns the model didn't report one. 0 disables proactive compaction (the reactive path always applies). The proactive path is best-effort — a failed checkpoint or /compact never fails the turn that already succeeded.

The operator can force a compaction any time via POST /api/compact. It's deferred: the handler sets Bus::request_compact() and returns immediately; the harness runs the /compact at the next turn boundary (end of the in-flight turn in drive_turn, or — when the agent is idle — in turn::run_pending_compact on the serve loop's next empty poll). This lets /api/compact work mid-turn instead of only when idle, without racing a live claude process.

To disable proactive compaction for a specific agent, use the nix option:

hyperhive.autoCompact = false;  # default true

Setting autoCompact = false sets HIVE_COMPACT_WATERMARK_TOKENS=0, which the percent resolver still honours as a disable. Useful for large-context models (sonnet/opus) where the 75% heuristic fires before the session is actually full — the reactive path (compact-on-overflow at the hard limit) still applies.

The child runs with cwd = /state (when the bind exists; falls back to the parent's cwd in dev), so any relative path in a tool call (Read foo.md, Bash ls, Write notes.md) lands in the agent's durable bind-mounted dir. CLAUDE.md auto-load walks upward from /state — drop a per-agent CLAUDE.md there if you want long-term hints that survive destroy/recreate.

The wake prompt is intentionally minimal: the popped message's from/body, prefixed with a [msg #<id>] broker-row-id marker (so the agent knows what id to pass to ack_until for bulk triage; transient pings show no marker because their sentinel id 0 has nothing to ack), plus an inline ({unread} more pending — drain via …) hint when unread > 0. Claude drives any further recv/send itself via the embedded MCP server.

Whenever hive-c0re starts / restarts / rebuilds a container, it also drops a system message into the agent's inbox via Coordinator::kick_agent — a one-line "you were just (re)started, check /state/ for your notes, your session is intact". The next turn picks it up like any other inbox message.

On-boot files

hive_ag3nt::turn::write_* writes two files next to the per-agent socket at /run/hive/ once at startup:

The per-turn plumbing lives in hive_ag3nt::turn: write_mcp_config / write_system_prompt (on-boot files), make_session (builds the durable InfiniteSession, once), drive_turn (the policy state machine — reset/auto-reset, the turn, 401-retry, deferred-compact-at-turn-end), run_pending_compact (idle operator compact), BusSink (stream → bus + Telemetry applied via apply_telemetry), emit_turn_end, session_title / session_store / archive_session (identity + turn-boundary reset). The actual claude spawn, stream classification, and the reactive/proactive compaction loop are in the hive-claude crate. Login-wait (wait_for_login) lives in hive_ag3nt::login.