the existing ctx badge was misnamed: it summed `result.usage`, which is
the cumulative tokens billed across every inference in the turn. for
tool-heavy turns that easily exceeds the model's context window (a 600k
cached prefix × 15 sub-calls = 9M cache_read), making it useless as a
"should i compact?" signal.
now two separate badges:
ctx · N last inference's prompt size = actual context window in
use right now. parsed from each `assistant` event's
`.message.usage`; the harness tracks the most recent one
across the stream and snapshots it when the `result`
event lands.
cost · M cumulative tokens billed across the whole turn (the
previous behaviour, now correctly labelled).
both update via a single `TokenUsageChanged { ctx, cost }` SSE event at
turn-end. turn_stats grows four columns (`last_input_tokens`,
`last_output_tokens`, `last_cache_read_input_tokens`,
`last_cache_creation_input_tokens`) so the cold-load seed can paint both
badges on page load. migrations run try-and-ignore ALTERs so existing
agent dbs catch up; pre-migration rows have last-inference zeros and
yield no `ctx` seed (badge stays empty until next turn) rather than a
misleading 0.
new sqlite table at /state/hyperhive-turn-stats.sqlite on each
agent's state dir. one row per claude turn captures identity
(model, wake_from, result_kind), timing (started/ended_at,
duration_ms), cost (input/output/cache_read/cache_creation token
counts), behaviour (tool_call_count + per-tool breakdown JSON),
and post-turn snapshot metrics (open_threads_count,
open_reminders_count).
wire additions:
- AgentRequest/ManagerRequest::CountPendingReminders +
Broker::count_pending_reminders_for(agent)
- Bus::observe_stream + take_tool_calls — pumps the existing
stdout stream-json, picks out tool_use blocks, accumulates per
turn. bin loops fold the breakdown into each row.
- TurnStats::open_default + TurnStatRow + record() — best-effort
inserts; failures log + don't block the harness.
both ag3nt and m1nd bins capture started_at + duration via
Instant::elapsed, fetch open-thread + reminder counts from
hive-c0re via the existing socket (post-turn, best-effort), and
record one row at turn_end. record_kind splits ok / failed /
prompt_too_long; failures carry the error message in note.
todo entries for host-side vacuum sweep + reading the table back
into agent/dashboard badges.
new LiveEvent variants on the per-agent bus —
status_changed / model_changed / token_usage_changed /
turn_state_changed — replace the per-agent web UI's
/api/state polling for the badge row.
emit sites:
- Bus::set_model → model_changed
- Bus::record_usage → token_usage_changed
- Bus::set_state → turn_state_changed
- turn::wait_for_login → status_changed("online") on creds detect
- post_login_start / post_login_cancel → status_changed("needs_login_*")
per-agent endpoints (post_set_model / post_compact / post_new_session
/ post_cancel_turn / post_login_*) now all return 200; client
drops the post-submit refetch except on login transitions, which
still need /api/state to render the OAuth form + session stream.
client adds dispatch on the four new event kinds, threads
`currentLabel` through so the composer re-enables on a live
status flip, and no longer fires refreshState() from turn_end or
postModel — the events carry the same signal faster.
closes the per-agent half of the dashboard event-channel
refactor; TODO entry dropped.
install_configured now takes an optional notify recipient. on a
non-zero or spawn-failed 'claude plugin install', sub-agents send
the spec + stderr to manager via the hyperhive socket; manager
passes None so it doesn't message itself. boot still proceeds either
way — notification is best-effort.
new hyperhive.claudePlugins NixOS option (list of strings) rendered
to /etc/hyperhive/claude-plugins.json. both hive-ag3nt and hive-m1nd
shell out 'claude plugin install <spec>' for each entry once at
startup before the turn loop opens. failures log a warning but don't
abort boot.
old behavior: omitted wait_seconds fell through to the 30s
RECV_LONG_POLL_DEFAULT — claude calling 'is there anything in
my inbox right now?' between actions blocked the turn for half
a minute. flip the semantics: None (or 0) returns immediately,
positive value parks up to MAX (180s, unchanged). cleaner
'peek vs wait' distinction; tool descriptions + agent/manager
prompts updated to point at the new shape.
harness's own serve loops in hive-ag3nt + hive-m1nd relied on
the old default for their inbox poll. they now explicitly pass
wait_seconds: Some(180) to opt into the full park — same
effective behavior as before, just spelled out.
retires the matching TODO under Turn loop.
new TurnFiles bundle (mcp_config + settings + system_prompt +
flavor) materialised once per harness boot, passed to drive_turn
and compact_session alike. operator-initiated /compact now uses
the exact same session shape as a normal turn — same MCP
surface, same allowed tools, same role prompt — only the stdin
payload differs (/compact vs the wake-up body). web_ui's
AppState carries the TurnFiles instead of (label + socket +
flavor + ad-hoc file writes per click). bin/hive-ag3nt and
bin/hive-m1nd prepare TurnFiles before spawning the web UI and
pass them to both surfaces. web_ui::Flavor folds into a type
alias for mcp::Flavor — no two-stage enum mapping.
removes ClaudeMode + the run_claude variant fork (system prompt
was Option, mcp args were skipped on Compact). dead 'mode'
plumbing gone.
AgentRequest::Recv and ManagerRequest::Recv grow an optional
wait_seconds field (default None → 30s, capped at 60s server-side).
agent_server / manager_server clamp via recv_timeout(). MCP tool
schemas advertise the param so claude can pick its own poll window
— useful when an agent wants to throttle wakes without entering a
distinct nap state.
both harness loops still pass None, keeping the existing 30s
default behaviour for system-level Recvs.
new TurnState { Idle, Thinking, Compacting } on hive_ag3nt::events::Bus
with set_state + state_snapshot. the turn loops in hive-ag3nt and
hive-m1nd flip Thinking before drive_turn and Idle after; the
web_ui's /api/compact handler flips Compacting around compact_session.
per-agent /api/state grows turn_state + turn_state_since (unix
seconds). frontend prefers the server-reported state over the
client-derived one — setStateAbs takes the absolute since-time so
the 'last turn' chip reads the actual server-side duration instead
of the client's perceived gap between SSE events. SSE turn_start /
turn_end still drive state instantly between renders; /api/state
re-anchors on each turn_end refresh.
new compacting state gets its own purple badge with pulse
animation (mirrors thinking's amber). napping will slot in the
same way once the nap tool lands.
new wire request AgentRequest::Recent { limit } / ManagerRequest::Recent
(plus matching responses with Vec<InboxRow>). InboxRow moved to
hive-sh4re so it lives on both surfaces without an internal-to-wire
conversion. host-side dispatch in agent_server / manager_server
calls broker.recent_for(name, limit).
per-agent web_ui /api/state grew an inbox: Vec<InboxRow> populated
via the same per-agent socket (best-effort; transport failure
returns empty). frontend renders as a collapsible <details> section
between the state row and the terminal — fmt timestamp / from /
body in a tight grid, capped at 16em scrollable. only visible when
there are rows.
retention is a host concern — agents have no business doing their
own cleanup, and a misbehaving harness could skip it. drop
spawn_events_vacuum from both hive-ag3nt and hive-m1nd, drop the
matching Bus::vacuum + EventStore::vacuum methods. new
hive_c0re::events_vacuum module sweeps every existing
agents/<name>/state/hyperhive-events.sqlite on the same hourly
cadence as the broker vacuum. same two-stage delete (older than 7
days, trim to 2000 newest). called from main alongside broker
vacuum.
also: server-side state badge entered into todo.md (today's badge
is derived client-side from sse, fine for idle/thinking but a
state machine that grows compacting/napping wants authoritative
status from the harness).
hive_ag3nt::events::Bus replaces its in-memory VecDeque with a sqlite-
backed store at /state/hyperhive-events.sqlite (overridable via
HYPERHIVE_EVENTS_DB). emit() inserts a row; history() reads back the
most recent 2000 events. survives harness restart now — operator reload
mid-investigation no longer wipes the trail.
vacuum runs hourly (immediate first sweep): drop rows older than 7
days, then trim to 2000 newest. two-stage so a quiet agent keeps a
useful tail and a chatty one stays bounded. wired into both
hive-ag3nt and hive-m1nd via spawn_events_vacuum.
if the db open fails (e.g. no /state mount in dev), Bus runs in
no-store mode — events still broadcast, just nothing persisted.
drop the one-shot send/recv/kill/start/restart/request-spawn/request-
apply-commit subcommands from both in-container binaries. they were
debug-only — the host admin socket (`hive-c0re ...`) exposes the
same verbs and the manager mcp surface covers the rest from inside
claude. now each binary's --help shows just `serve` and `mcp`,
which are the only commands either is meant to be started with.
removes the `one_shot` helper and the `render` / `check` glue.
new manager tools mcp__hyperhive__{start,restart} that delegate to the
existing lifecycle::start / lifecycle::restart on the host. kill was
already at the manager's discretion; rounding out start + restart for
parity so day-to-day container care doesn't have to round-trip through
the operator.
guard: refuse self-targeting on kill/start/restart — the manager would
just be cutting its own legs. spawn (request_spawn) and config changes
(request_apply_commit) still go through the approval queue, since those
are the actual gate. prompt + claude.md updated to make the boundary
explicit. kill now also emits HelperEvent::Killed (it didn't before).
new mcp tool on the manager surface that queues a question on the
dashboard and returns the question id immediately. operator submits an
answer via /answer-question/<id>; the dashboard fires
HelperEvent::OperatorAnswered { id, question, answer } into the manager
inbox so the next turn picks it up.
also: fix async-form button stuck on spinner after successful submit
(refreshState skipped re-rendering, so the button was never re-enabled).
- tool_use renders per-tool (Read /path, Bash $ cmd, send → operator: ...)
- tool_result with >120 chars collapses into <details>; short ones inline
- session_init / result / rate_limit dropped from the panel
- thinking content shown inline if present, fallback indicator otherwise
- TurnStart carries unread count → header badge "· 3 unread"
- per-tool [status] line dropped from envelope; lives in wake prompt + UI
- send form moved below the live panel
- live panel themed as a terminal (crust bg, inset shadow, monospace)