An ACP agent's `usage_update` carries `cost.{amount,currency}`, the
session's running total (opencode sums every assistant message in the
session). hive-runtime now reads it and turns the running total into
what each report added: a new session counts from zero, a session loaded
into a freshly started agent only baselines on its first report, and a
falling total adds nothing. The spend is held on the runtime until
`Runtime::take_reported_cost` drains it; claude's runtime reports none,
since the claude binary already exports `claude_code.cost.usage`.
hive-agent's existing turn-metrics meter records three new instruments:
- `hyperhive.agent.cost.usage` (counter, `model` + `currency`), ACP only;
- `hyperhive.agent.context.used` / `.size` (gauges, no attributes), for
every backend: the two numbers the web UI's ctx% divides.
The `hyperhive · agents` dashboard gets ACP cost panels on its cost tab
and a context-fill panel on its health tab.
Refs #4845
An ACP agent's model and effort pickers now list what its session offers
(its `model` and `thought_level` config options) instead of the claude
model list and EFFORT_LEVELS. A pick goes through the same Bus::set_model /
Bus::set_effort -> Config.model / Config.effort path as on claude; before
each prompt the ACP runtime sets it with `session/set_config_option`, model
first, and only when the session offers that value. Options are re-read
from the set response and from `config_option_update`, so the effort picker
disappears when the chosen model offers no effort levels, and is hidden
while a newly picked model waits for the next turn.
The session's options reach the web UI through a `Choices` handle from a
new `Runtime::choices`, registered on the bus the way `canceller` is.
/api/model and /api/effort accept only offered values on ACP. The claude
path is unchanged.
Refs #4391
A checkpoint or `compact` command turn can end with a bare `end_turn`
as a normal answer, since the agent does the work on its side. Treating
it as `EmptyEndTurn` made `compact_session` count every such compaction
as failed and archive the session. `prompt()` and `turn()` now take a
`TurnKind`; only `run()`'s ordinary turns get the check.
The test agent's `BLANK` env answers one prompt text with a bare
`end_turn`, for the compact and checkpoint tests.
opencode answers `end_turn` even when its provider rejected the request
with a non-retryable error (401, 400), and forwards nothing over ACP, so
the turn looked like an empty success. A turn that ends with `end_turn`,
no event, no `usage_update` and no `usage` in the prompt response now
fails with `AcpError::EmptyEndTurn`.
A turn that really produced nothing and reported no usage is reported
the same way; that false positive is accepted.
The test agent's plain `end_turn` replies now carry a response `usage`,
so its ordinary turns stay successes; a response `usage` feeds only cost
telemetry, not the compaction watermark. A new `blank` mode keeps the
empty reply for the error case.
An ACP agent's session is now compacted like a claude one: proactively
once a turn crosses the percent-of-window watermark, and on the
operator's /compact or the agent's compact tool. Before, the ACP
backend's compact returned Unsupported and no watermark applied to it.
- AcpRuntime takes the same CompactionPolicy as ClaudeRuntime;
make_session builds one PercentPolicy (with CHECKPOINT_PROMPT) and hands
it to whichever backend runs.
- The runtime keeps the commands each session advertises in
available_commands_update. If `compact` is among them, compaction sends
the prompt `/compact` on the same session, which is how the ACP spec
runs an advertised command. A proactive compaction runs the checkpoint
turn first, as InfiniteSession does.
- With no compact command, the checkpoint turn runs, the session is
archived, and the next turn starts a new one with the system prompt.
- Error::Unsupported had no producer left, so it and drive_turn's
"/compact skipped" arm are gone.
Refs #4391
The session id was written to the session file right after session/new,
but the system prompt rides on the first prompt only. If that prompt
failed, the next turn (or the next harness start) resumed the recorded
session as not-new and the system prompt never reached it.
The id is now written after the first session/prompt gets its reply.
A failed first prompt leaves nothing recorded, so the next turn starts
a new session and sends the system prompt again. Chosen over a separate
"system prompt delivered" marker: one file, and "recorded" already means
"usable".
Tests drive AcpRuntime against a scripted sh agent that fails the first
prompt: in-process and across a restart, the retry is a new session
carrying the system prompt.
Also: PermissionPolicy now sees a PermissionAsk (kind plus the MCP
server the tool belongs to, matched by name against the servers handed
to the session), so a caller can tell MCP tool calls from other `other`
requests.
Refs #4391
A `Runtime` trait (run / compact / archive) with two backends:
- claude: a pass-through to hive_claude's InfiniteSession and
SessionStore, so a claude turn is the same spawn, session handling
and errors as before.
- acp: a generic Agent Client Protocol client. It spawns the command,
args and env from RuntimeSpec (HIVE_RUNTIME / HIVE_ACP_COMMAND /
HIVE_ACP_ARGS / HIVE_ACP_ENV), refuses an agent whose
mcpCapabilities.http is not true, passes the claude --mcp-config
servers as ACP mcpServers, keeps one session id in a file
(session/load after a restart, session/new otherwise), and maps
session/update into claude stream-json events plus usage_update into
Telemetry. Permission requests are answered by a caller-supplied
policy on the ACP tool kind. compact returns Unsupported for now.
The crate depends on no hyperhive binary crate, so the subagent
daemon can move onto it without pulling in hive-agent.
Refs #4391