| Filename | Latest commit message | Latest commit date |
|---|---|---|
claude's Bash tool starts every command in a new session, so a signal to claude's process group never reaches it. claude stops those commands itself when SIGINT makes it shut down. A SIGKILL leaves them running, re-parented to init. Per the ruling on #4999, every stop is SIGINT and nothing escalates. - Subagent `interrupt` always sends SIGINT. The `force` argument is gone from the MCP tool and from `session::interrupt`. A caller that still sends `force: true` gets SIGINT; the argument is ignored. - `interrupt` used to free the session name before it signalled, so a `continue` could start a second claude on the session while the first was still exiting. The `running` map now holds a three-state `Slot` (starting, running, stopping). An interrupted claude turn is `Stopping` until the turn task's own `finish_turn` clears it. `status` says "stopping", and `start`/`continue` on that name are refused. `interrupt` still returns at once. The ACP arm is unchanged: its name is freed when `session/cancel` is sent. - ACP `Connection` drop sends its process group SIGINT, not SIGKILL, and the spawn no longer sets tokio's `kill_on_drop`, which SIGKILLed the leader after the `Drop` body. The test fixture's background grandchild resets SIGINT to default, since `&` starts it ignored. Tests: a fake claude that records SIGINT, interrupted, fails with `cancel(true)` (the old `force: true` path). An interrupted fake claude that keeps running keeps its name claimed and `continue` is refused, which fails when the slot is freed before the signal. A fake ACP agent that records SIGINT, dropped, fails with SIGKILL in `Drop`, and also with SIGINT in `Drop` but `kill_on_drop(true)` left in place. Not changed: hive-claude's idle watchdog (still SIGKILL, separate hive-claude change), the units' `KillSignal`, `machinectl kill`, and the login helper. |
||
| .. | ||
| src | ||
| Cargo.toml | ||
| README.md | ||
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 --printthrough thehive-claudecrate'sInfiniteSession. A pass-through: same spawn, same session handling, same errors. - acp — any Agent Client Protocol
agent, spawned from a command, args and env handed to it (
RuntimeSpec, read fromHIVE_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.httpin itsinitializeresponse. 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 sent SIGINT, and the next turn respawns it. A watchdog stop fails the turn
with AcpError::IdleTimeout (IdleKilled if the agent was sent SIGINT). 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. Stopping it, and dropping the runtime, sends that group SIGINT, so the commands the agent started get it too. Nothing escalates to SIGKILL: an agent that ignores SIGINT keeps running, and the next turn starts a new one beside 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
compactcommand (available_commands_update), it is sent as the prompt/compacton 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
compactcommand 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.