Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-runtime/README.md
atlas 8d206f124f hive-runtime: kill an ACP agent's whole process group, not just its pid
The ACP agent was spawned with only `kill_on_drop`, which SIGKILLs the
agent's own pid. Commands the agent had started (a `nix build`, a test
run) were reparented and kept running after the agent was killed: when a
subagent run ends after `interrupt`, when an agent ignores
`session/cancel` past the grace, and when the harness drops the runtime.

The agent now leads its own process group (`process_group(0)`), and
dropping its `Connection` SIGKILLs that group. The soft path is unchanged:
`interrupt` / `Canceller::cancel` still sends `session/cancel`.

Refs #4999. The claude half needs hive-claude 0.1.3, which is not on
crates.io yet.
2026-10-10 00:06:42 +02:00

72 lines
3.6 KiB
Markdown

# hive-runtime
The layer an agent's turns are driven through: one `Runtime` interface
(`run`, `compact`, `archive`, `canceller`, `choices`) with a backend per runtime.
- **claude** — `claude --print` through the `hive-claude` crate's
`InfiniteSession`. A pass-through: same spawn, same session handling, same
errors.
- **acp** — any [Agent Client Protocol](https://agentclientprotocol.com)
agent, spawned from a command, args and env handed to it (`RuntimeSpec`,
read from `HIVE_RUNTIME` / `HIVE_ACP_COMMAND` / `HIVE_ACP_ARGS` /
`HIVE_ACP_ENV`). It knows no agent by name; which agent runs, and how it is
configured, is decided in nix (`services.hyperhive.agent.runtime`,
`services.hyperhive.agent.acp.*`).
Both backends report a turn through `hive_claude::Sink` in claude's
`stream-json` shape. The ACP backend translates `session/update`
notifications into it (text and thought chunks as whole blocks, tool calls as
`tool_use` + `tool_result`, MCP tools named `mcp__<server>__<tool>`), so the
harness's stream consumers read either backend unchanged. Context usage comes
from ACP `usage_update`.
The crate depends on no hyperhive binary crate, so `hive-agent` and
`hive-subagent-mcp` can both drive turns through it.
## ACP backend: what it needs from the agent
- `mcpCapabilities.http` in its `initialize` response. The hyperhive tools
are only served over HTTP, so an agent without it is refused at startup.
- `loadSession`, to pick its session back up after a harness restart.
Without it every restart starts a new session.
## ACP backend: stopping a turn
A turn is stopped with `session/cancel`: by the `Canceller` handle, or by the
idle watchdog once no `session/update` has arrived for
`Config::idle_timeout`. An agent that has not answered the prompt 10s later
is killed, and the next turn respawns it. A watchdog stop fails the turn
with `AcpError::IdleTimeout` (`IdleKilled` if the agent was killed). This is
the only way a provider error the agent retries without reporting it, such as
an HTTP 429, ends the turn.
The agent leads its own process group. Killing it, and dropping the runtime,
`SIGKILL`s that group, so the commands the agent started die with it.
## ACP backend: compaction
The same `CompactionPolicy` as the claude backend decides when: after a turn
past the watermark, or on `compact` (the operator's `/compact`, the agent's
`compact` tool).
- If the agent advertises a `compact` command (`available_commands_update`),
it is sent as the prompt `/compact` on the same session, as the ACP spec
runs any advertised command. A proactive compaction runs the policy's
checkpoint turn first, as on claude.
- Otherwise the policy's checkpoint turn runs, then the session is archived,
and the next turn starts a new one, carrying the system prompt again.
- A `compact` command that fails, or sends nothing for 180s (the turn's idle
window if that is shorter), is cancelled and handled as the case above; the
checkpoint turn is not run a second time. So a compaction always leaves a
smaller session behind, and a failed one is not retried on the next turn.
## ACP backend: model and effort
Before each prompt, `Config::model` and then `Config::effort` are set on the
session with `session/set_config_option`, as the value of its first `model`
and `thought_level` config option. A value the session does not offer, or
already holds, is not sent, so a claude alias such as `haiku` does nothing
on an ACP agent. Model goes first because the effort levels on offer depend
on it. The `Choices` handle (`Runtime::choices`) reads what the session
offers now, including after a `config_option_update`. It is empty until a
session is attached.