Watch
0
0
Fork
You've already forked hyperhive
0
hyperhive/hive-runtime
Repository files (latest commit first)
Filename Latest commit message Latest commit date
atlas fa56931a99 hive-runtime: respawn the ACP agent when its provider key changed after a failed turn
The provider key was resolved once, at spawn. A provider rejecting it
shows only as an empty end_turn or an idle timeout, neither of which
drops the agent, so a key rotated in the store never reached a running
opencode until the process died for another reason.

After a turn fails with EmptyEndTurn or IdleTimeout, the runtime now
resolves the key once more. When that yields a key that differs from
the one the agent was spawned with, the agent is dropped (SIGINT to its
process group, as every other respawn) and the next turn spawns it with
the new key and reloads the recorded session. No key read (inherited
env value, no store, a failed or timed-out read, nothing stored) keeps
the running agent. The failed turn's error is returned unchanged.

Part of #5017 (slice A).
2026-10-13 08:07:38 +02:00
..
src hive-runtime: respawn the ACP agent when its provider key changed after a failed turn 2026-10-13 08:07:38 +02:00
Cargo.toml hive-runtime: kill an ACP agent's whole process group, not just its pid 2026-10-10 00:06:42 +02:00
README.md hive-runtime: TurnStop replaces the subagent daemon's Stopper 2026-10-11 09:56:33 +02:00

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 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 (TurnStop wraps it, and claude's cancel handle, for callers that run either), 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 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.