docs: describe hive-claude split, InfiniteSession, deferred compact, web_ui dir
This commit is contained in:
parent
7c851c5dd3
commit
4a404c1128
4 changed files with 85 additions and 50 deletions
13
CLAUDE.md
13
CLAUDE.md
|
|
@ -33,9 +33,16 @@ hand-maintained per-file tree drifts out of sync with the code.
|
|||
forge / matrix provisioning, per-container stats, and the axum
|
||||
operator dashboard (`dashboard.rs`). Largest crate.
|
||||
- **`hive-ag3nt/`** — in-container harness; one `hive` binary for every
|
||||
agent. Turn loop (`turn.rs`), embedded MCP server (`mcp.rs`), per-agent
|
||||
web UI (`web_ui.rs`), event + turn-stats sqlite sinks, login flow,
|
||||
system-prompt renderer, forge-notify subscriber.
|
||||
agent. Turn-loop *policy* layer (`turn.rs`) over the `hive-claude`
|
||||
driver, embedded MCP server (`mcp.rs`), per-agent web UI (`web_ui/`
|
||||
module dir), event + turn-stats sqlite sinks, login flow, system-prompt
|
||||
renderer, forge-notify subscriber.
|
||||
- **`hive-claude/`** — reusable, app-agnostic driver for headless
|
||||
`claude --print`: spawns the CLI, streams + classifies stream-json,
|
||||
parses per-turn `Telemetry`, and drives a durable self-compacting
|
||||
`InfiniteSession` (name + `SessionStore` + `CompactionPolicy`). Uses
|
||||
`thiserror` (it's a library); the `hive-*` binaries consume it with
|
||||
`anyhow`. See `hive-claude/README.md`.
|
||||
- **`hive-priv/`** — minimal root privileged-helper, socket-activated at
|
||||
`/run/hive/priv.sock`; performs the few root operations (bind-mount
|
||||
edits, nsenter) the unprivileged `hive-c0re` delegates to it. See
|
||||
|
|
|
|||
|
|
@ -144,6 +144,18 @@ claude --print --verbose --output-format stream-json --model <name> \
|
|||
# wake prompt piped over stdin
|
||||
```
|
||||
|
||||
**Crate split.** The generic subprocess mechanics — spawning
|
||||
`claude --print`, streaming + classifying stream-json, session
|
||||
lookup/archive, and the durable-session compaction loop — live in the
|
||||
reusable **`hive-claude`** crate (`hive_claude::{Claude, InfiniteSession,
|
||||
Attach, CompactionPolicy, PercentPolicy, Telemetry, Sink, SessionStore}`;
|
||||
see `hive-claude/README.md`). `hive_ag3nt::turn` is the hyperhive **policy
|
||||
layer** on top: it builds the per-turn config from the bus, bridges the
|
||||
output stream onto the event bus (`BusSink`), and owns the compaction /
|
||||
auto-reset / retry decisions in `drive_turn`. The lib returns everything it
|
||||
parsed from a turn (usage, cost, context window, resolved model) as
|
||||
`Telemetry`, which the policy layer applies to the bus.
|
||||
|
||||
Hive-enforced settings ship at `/etc/claude-code/managed-settings.json`
|
||||
(claude-code's canonical managed-settings path — precedence #1,
|
||||
read-only, un-overridable), wired in `nix/templates/harness-base.nix`
|
||||
|
|
@ -182,11 +194,13 @@ percentage-of-window ctx badge.
|
|||
|
||||
**Session identity — a constant title.** Every turn keys on one fixed,
|
||||
harness-owned session title (`turn::session_title()`, default
|
||||
`hive-session`, override `HIVE_SESSION_TITLE`). A turn `--resume <title>`s
|
||||
it; the *first* use (bootstrap, post-archive, post-purge) misses and
|
||||
`turn::run_claude_resume_or_create` re-runs the same prompt once with
|
||||
`--name <title>` to mint it. That single self-heal rule is the whole
|
||||
identity system — there is **no** scraped session-id file. Because the
|
||||
`hive-session`, override `HIVE_SESSION_TITLE`). The durable
|
||||
`hive_claude::InfiniteSession` (built once by the serve loop via
|
||||
`turn::make_session`, then reused) `--resume <title>`s it; the *first* use
|
||||
(bootstrap, post-archive, post-purge) misses and the session re-runs the
|
||||
same prompt once with `--name <title>` to mint it. That single self-heal
|
||||
rule is the whole identity system — there is **no** scraped session-id
|
||||
file. Because the
|
||||
title is constant, `/compact` and its post-compact retry provably target
|
||||
the same session (killing the old "compact ran on a different/empty
|
||||
session" bug), and a `choom` invocation in the same cwd can't hijack the
|
||||
|
|
@ -212,31 +226,37 @@ turn's `--resume <title>` then misses and self-heals into a fresh session.
|
|||
### Compaction
|
||||
|
||||
claude's own in-session auto-compact is off (via the managed settings
|
||||
at `/etc/claude-code/managed-settings.json`); hyperhive owns it
|
||||
explicitly in `turn::drive_turn`. There are two triggers:
|
||||
at `/etc/claude-code/managed-settings.json`); hyperhive owns it. The
|
||||
`hive_claude::InfiniteSession` keeps the session alive across the context
|
||||
window with two triggers baked into its `run`:
|
||||
|
||||
- **Reactive** — claude-code prints `Prompt is too long` (the
|
||||
`PROMPT_TOO_LONG_MARKER`). The session is *already* past the context
|
||||
window, so no turn can run on it — `drive_turn` runs `/compact`
|
||||
straight away and retries the same wake-up prompt once. No
|
||||
- **Reactive** — claude-code prints `Prompt is too long`. The session is
|
||||
*already* past the window, so no turn can run on it — the session
|
||||
`/compact`s straight away and retries the same wake-up prompt once. No
|
||||
notes-checkpoint turn is possible here: the detail is gone.
|
||||
- **Proactive** — a turn finishes cleanly but the last inference's
|
||||
context size (`Bus::last_ctx_usage().context_tokens()`) is at or
|
||||
above a watermark. While the session is still healthy, `drive_turn`
|
||||
injects one synthetic *notes-checkpoint* turn (`CHECKPOINT_PROMPT`
|
||||
— "context is filling up, flush durable state into `/state` now")
|
||||
and *then* runs `/compact`. This gives the agent a chance to
|
||||
persist in-flight task state, decisions, and file paths before the
|
||||
conversation detail collapses into a summary.
|
||||
- **Proactive** — a turn finishes cleanly but the last inference's context
|
||||
size crossed the policy watermark. While the session is still healthy it
|
||||
runs one synthetic *notes-checkpoint* turn (`CHECKPOINT_PROMPT` —
|
||||
"context is filling up, flush durable state into `/state` now") and
|
||||
*then* `/compact`s, so the agent can persist in-flight state before the
|
||||
detail collapses into a summary.
|
||||
|
||||
The compact watermark defaults to **75% of `context_window_tokens(model)`**
|
||||
(dynamically derived — 150k for haiku, 750k for sonnet/opus). Override
|
||||
with `HIVE_COMPACT_WATERMARK_TOKENS` (absolute token count); set to `0`
|
||||
to disable proactive compaction entirely (the reactive path always
|
||||
applies). The proactive path is best-effort — a failed checkpoint turn
|
||||
or `/compact` is surfaced as a `Note` but never fails the turn that
|
||||
already succeeded. The operator can also force a compaction any time
|
||||
via `/api/compact`.
|
||||
The **when** is a `hive_claude::CompactionPolicy` injected by the harness:
|
||||
`turn::make_session` builds a `PercentPolicy` that compacts once the
|
||||
model-reported context fill reaches `HIVE_COMPACT_WATERMARK_PERCENT`
|
||||
(default **75%**), falling back to `events::context_window_tokens(model)`
|
||||
for the window on turns the model didn't report one. `0` disables proactive
|
||||
compaction (the reactive path always applies). The proactive path is
|
||||
best-effort — a failed checkpoint or `/compact` never fails the turn that
|
||||
already succeeded.
|
||||
|
||||
The operator can force a compaction any time via `POST /api/compact`. It's
|
||||
**deferred**: the handler sets `Bus::request_compact()` and returns
|
||||
immediately; the harness runs the `/compact` at the next turn boundary
|
||||
(end of the in-flight turn in `drive_turn`, or — when the agent is idle —
|
||||
in `turn::run_pending_compact` on the serve loop's next empty poll). This
|
||||
lets `/api/compact` work mid-turn instead of only when idle, without racing
|
||||
a live claude process.
|
||||
|
||||
To disable proactive compaction for a specific agent, use the nix option:
|
||||
|
||||
|
|
@ -244,10 +264,11 @@ To disable proactive compaction for a specific agent, use the nix option:
|
|||
hyperhive.autoCompact = false; # default true
|
||||
```
|
||||
|
||||
Setting `autoCompact = false` is equivalent to `HIVE_COMPACT_WATERMARK_TOKENS=0`.
|
||||
Useful for agents running large-context models (sonnet/opus) where the 75%
|
||||
heuristic fires before the session is actually full — the reactive path
|
||||
(compact-on-overflow when the session hits the hard limit) still applies.
|
||||
Setting `autoCompact = false` sets `HIVE_COMPACT_WATERMARK_TOKENS=0`, which
|
||||
the percent resolver still honours as a disable. Useful for large-context
|
||||
models (sonnet/opus) where the 75% heuristic fires before the session is
|
||||
actually full — the reactive path (compact-on-overflow at the hard limit)
|
||||
still applies.
|
||||
|
||||
- **Auto session-reset** — a third path (`turn::maybe_auto_reset`,
|
||||
pre-turn) that fires when both conditions hold: context is ≥ a watermark
|
||||
|
|
@ -284,7 +305,7 @@ the embedded MCP server.
|
|||
Whenever hive-c0re starts / restarts / rebuilds a container, it
|
||||
also drops a `system` message into the agent's inbox via
|
||||
`Coordinator::kick_agent` — a one-line "you were just (re)started,
|
||||
check /state/ for your notes, --continue session is intact". The
|
||||
check /state/ for your notes, your session is intact". The
|
||||
next turn picks it up like any other inbox message.
|
||||
|
||||
### On-boot files
|
||||
|
|
@ -337,12 +358,16 @@ socket at `/run/hive/` once at startup:
|
|||
empty-string env vars and missing env vars round-trip the
|
||||
same way.
|
||||
|
||||
The shared per-turn plumbing lives in `hive_ag3nt::turn::{write_mcp_config,
|
||||
write_system_prompt, run_turn, drive_turn, emit_turn_end, wait_for_login,
|
||||
compact_session, session_title}` — the single code path every agent role
|
||||
runs through. Session identity is internal to the module:
|
||||
`run_claude_resume_or_create` (resume-or-create self-heal) and
|
||||
`archive_session` (turn-boundary reset) sit under `run_turn` / `drive_turn`.
|
||||
The per-turn plumbing lives in `hive_ag3nt::turn`: `write_mcp_config` /
|
||||
`write_system_prompt` (on-boot files), `make_session` (builds the durable
|
||||
`InfiniteSession`, once), `drive_turn` (the policy state machine —
|
||||
reset/auto-reset, the turn, 401-retry, deferred-compact-at-turn-end),
|
||||
`run_pending_compact` (idle operator compact), `BusSink` (stream → bus +
|
||||
`Telemetry` applied via `apply_telemetry`), `emit_turn_end`, `session_title`
|
||||
/ `session_store` / `archive_session` (identity + turn-boundary reset). The
|
||||
actual claude spawn, stream classification, and the reactive/proactive
|
||||
compaction loop are in the `hive-claude` crate. Login-wait
|
||||
(`wait_for_login`) lives in `hive_ag3nt::login`.
|
||||
|
||||
### Reference docs (`hyperhive.docs.enable`)
|
||||
|
||||
|
|
@ -707,9 +732,10 @@ addition to the broadcast channel and the events history. Variants:
|
|||
|
||||
The harness flips state at the relevant transitions
|
||||
(`set_state(Thinking)` before `drive_turn`, `set_state(Idle)`
|
||||
after; `set_state(Compacting)` around `compact_session`). Exposed
|
||||
via `/api/state.turn_state` + `turn_state_since` (unix seconds);
|
||||
the agent page renders this rather than deriving from SSE events.
|
||||
after; `set_state(Compacting)` around an idle operator compact in
|
||||
`turn::run_pending_compact`). Exposed via `/api/state.turn_state` +
|
||||
`turn_state_since` (unix seconds); the agent page renders this rather
|
||||
than deriving from SSE events.
|
||||
|
||||
### Tool envelope
|
||||
|
||||
|
|
|
|||
|
|
@ -261,9 +261,11 @@ Slash commands today:
|
|||
- `/cancel` — `POST /api/cancel` → host shellouts `pkill -INT
|
||||
claude`, emits a Note. Also surfaces as a `■ cancel turn`
|
||||
button in the state row while state=thinking.
|
||||
- `/compact` — `POST /api/compact` → host spawns
|
||||
`turn::compact_session` in the background; output streams into
|
||||
the live panel.
|
||||
- `/compact` — `POST /api/compact` → sets the deferred
|
||||
`Bus::request_compact()` flag and returns immediately. The harness
|
||||
runs the `/compact` at the next turn boundary (end of the in-flight
|
||||
turn, or `turn::run_pending_compact` when idle); output streams into
|
||||
the live panel. Works mid-turn, not only when idle.
|
||||
- `/model <name>` — `POST /api/model` flipping `Bus::set_model`.
|
||||
Takes effect on the next turn; persisted to
|
||||
`/state/hyperhive-model` so the override survives harness
|
||||
|
|
@ -450,7 +452,7 @@ token-efficiency chips derived from the bucket sums: **cache
|
|||
hit-rate** (`cache_read` over all input-side tokens) and
|
||||
**tokens/turn**. When `reminder_stats` is present (fetched via
|
||||
`ReminderRollup` RPC and merged into the snapshot in
|
||||
`web_ui.rs::api_stats`) three more chips appear: **reminders
|
||||
`web_ui/stats.rs::api_stats`) three more chips appear: **reminders
|
||||
scheduled / delivered / pending** for the window. When the
|
||||
per-session capture has data, a **first-turn ctx** chip shows the
|
||||
input tokens of the most recent fresh claude session's first turn —
|
||||
|
|
|
|||
|
|
@ -185,7 +185,7 @@ listener: read `data-confirm`, swap the button to a spinner, POST
|
|||
`application/x-www-form-urlencoded`, re-enable the button on success
|
||||
(refreshState may keep the form mounted, so we don't rely on a
|
||||
re-render), call `refreshState()`. State shapes live in
|
||||
`dashboard.rs::StateSnapshot` and `web_ui.rs::StateSnapshot` — when
|
||||
`dashboard.rs::StateSnapshot` and `web_ui/state.rs::StateSnapshot` — when
|
||||
adding state fields, plumb through the snapshot struct and the
|
||||
relevant domain module (`assets/swarm.js`, `assets/call.js`, etc.) render function.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue