docs: handbook states what the code does (fact sweep fixes)
This commit is contained in:
parent
1042bf65db
commit
409e7ee5a0
4 changed files with 64 additions and 69 deletions
|
|
@ -41,8 +41,8 @@ should cover._ `topology::all_agents` is the only reader that matters.
|
|||
|
||||
That reader is a permission boundary: the set it returns is what an agent
|
||||
holding the `ManageRootAgent` permission gets bind-mounted (each other
|
||||
agent's `state` read-write and `config` read-only; never `harness`). An
|
||||
agent holding no permission sees its own dirs and nothing else:
|
||||
agent's `state` dir, read-write, at `/agents/<name>/state`; never `config` or
|
||||
`harness`). An agent holding no permission sees its own dirs and nothing else:
|
||||
`ManageRootAgent` is the only grant that reaches another agent's state
|
||||
dir. See
|
||||
[`persistence.md`](persistence.md)'s _Cross-agent access to state._
|
||||
|
|
@ -66,12 +66,14 @@ system-level facts live.
|
|||
degrades safely to "no agents" (covers a fresh install that hasn't
|
||||
synced yet).
|
||||
- **Reconcile** — runs alongside the periodic meta/flake regeneration.
|
||||
Adds newly spawned agents, drops removed ones. Reconcile keeps agents
|
||||
whose config repo exists but that haven't spawned yet, so the gap until
|
||||
the container appears doesn't churn the file.
|
||||
Adds newly spawned agents, drops removed ones. The live set is the
|
||||
hive's existing agent containers plus the agent that spawns now; reconcile
|
||||
drops an agent whose config repo exists but whose container doesn't.
|
||||
|
||||
No write API and no operator verb reach this file. Reconcile derives it
|
||||
from which agents exist, so the next pass overwrites a hand edit.
|
||||
from which agents exist, so the next pass overwrites a hand edit that
|
||||
parses. It refuses to write over a file it can't parse and fails
|
||||
instead, so a broken hand edit stays until someone fixes it.
|
||||
|
||||
See `hive-c0re/src/agent_config/topology.rs` and `hive-c0re/src/meta.rs`'s
|
||||
module docs for the exact call chain.
|
||||
|
|
@ -99,9 +101,9 @@ state dirs work as for every other agent.
|
|||
[`docs/4-implementation/turn-loop/mcp.md`](../../4-implementation/turn-loop/mcp.md#waking-the-agent-from-inside-the-container)).
|
||||
- **Storage/mounts** — the `manage_root_agent` permission, which the swarm
|
||||
UI's setup assistant creates ruth with, gets a container
|
||||
`/var/lib/hyperhive/agents` bind-mounted RW at `/agents` (so it can
|
||||
manage any agent's state dir; config isn't mounted, since a real
|
||||
config change is a PR from a clone), plus an RO mount for
|
||||
each other agent's `state` dir bind-mounted RW at
|
||||
`/agents/<name>/state` (so it can manage any agent's state dir; config
|
||||
isn't mounted, since a real config change is a PR from a clone), plus an RO mount for
|
||||
`/meta` (the hive's topology and limits files). Any agent holding it gets the same mounts.
|
||||
- **Prompt** — identical for every container but one block (below):
|
||||
`prompts/system.md`
|
||||
|
|
|
|||
|
|
@ -70,26 +70,21 @@ merged by an operator on the forge (see [Config changes](#config-changes)).
|
|||
### Reminder delivery: file-path semantics
|
||||
|
||||
A reminder may carry a `file_path` (the agent-visible path inside its
|
||||
container, for example `/agents/<name>/state/foo.md`). On delivery hive-c0re:
|
||||
container, for example `/agents/<name>/state/foo.md`). The agent's own
|
||||
harness (`hive-agent`, inside the container) delivers the reminder when it
|
||||
falls due:
|
||||
|
||||
1. **Translates** the container path to the host path
|
||||
(`/var/lib/hyperhive/agents/<name>/state/foo.md`) so c0re can write
|
||||
from outside the container.
|
||||
2. **Validates** the path: rejects anything outside the agent's own state
|
||||
subtree, containing `..` (path traversal), or with an empty relative
|
||||
tail. On rejection hive-c0re skips the write and delivers the
|
||||
original message inline with a warning — the reminder still fires.
|
||||
3. **Defends against symlink escape**: after `create_dir_all`, hive-c0re
|
||||
canonicalizes the parent dir and re-verifies it lives under the agent's host
|
||||
state root. hive-c0re opens the final file with
|
||||
`O_NOFOLLOW | O_CREAT | O_TRUNC` so an existing symlink at the
|
||||
basename can't redirect the write to an arbitrary host path.
|
||||
4. **Writes the body to disk** and delivers a short pointer message in its
|
||||
place, keeping the agent's inbox / wake-prompt small while the agent
|
||||
reads the bulky payload out of band.
|
||||
1. **Validates** the path: it must resolve under the agent's own state
|
||||
directory, with no `..` components and a non-empty relative tail. On
|
||||
rejection, or on a write failure, the harness delivers the original
|
||||
message inline with a warning note — the reminder still fires.
|
||||
2. **Writes the body to disk** at that path and delivers a short pointer
|
||||
message in its place, keeping the agent's inbox / wake-prompt small
|
||||
while the agent reads the bulky payload out of band.
|
||||
|
||||
`Broker::deliver_reminders_batch` handles atomicity of the inbox INSERT + `reminders.sent_at` UPDATE; the scheduler only computes the
|
||||
body strings before calling it.
|
||||
The write happens inside the agent's container, so hive-c0re takes no
|
||||
part in it and there is no symlink-escape defence: the harness is the
|
||||
agent, and the container boundary is the sandbox.
|
||||
|
||||
### Destroy semantics
|
||||
|
||||
|
|
@ -122,8 +117,9 @@ deploys.
|
|||
|
||||
What `hive-c0re` still keeps under `/var/lib/hyperhive/meta/` is plain
|
||||
state, bound read-only into the root agent's container as `/meta`:
|
||||
`topology.json`, the swarm resource limits, and the permission seed files of a
|
||||
hive that predates the swarm's permissions. `agent_set::refresh(agents:
|
||||
`topology.json` and the swarm resource limits. The permissions the swarm
|
||||
grants live outside it, in `/var/lib/hyperhive/swarm-agent-permissions.json`.
|
||||
`agent_set::refresh(agents:
|
||||
&[AgentSpec])` keeps `topology.json`, the gateway's agent-socket map and its
|
||||
nginx include in line with the live agents. `provision_container`, `destroy`,
|
||||
`rebuild` and the startup migration call it. It refuses an empty agent list,
|
||||
|
|
@ -243,8 +239,8 @@ mounts via `set_nspawn_flags`:
|
|||
- `/var/lib/hyperhive/meta/` → `/meta/` (RO) — the hive's plain state
|
||||
(topology, swarm limits).
|
||||
|
||||
An agent without the permission only has its direct children's config
|
||||
dirs.
|
||||
An agent without the permission gets only its own `state`, `harness` and
|
||||
`work` dirs: no other agent's dirs and no `/meta`.
|
||||
|
||||
⚠️ nspawn bakes bind flags at container start, so hive-c0re restarts
|
||||
an agent, after its current turn, when its `manage_root_agent` changes.
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ keeps its state, purging it doesn't.**
|
|||
|
||||
- **`DESTR0Y`** (the default action) stops and removes the container
|
||||
but keeps everything on disk — config history, claude login, `/state/`
|
||||
notes, harness data. The agent shows up as a tombstone (K3PT ST4T3 on
|
||||
the C0R3 page); `swarmctl agent create` with the same name and hive
|
||||
notes, harness data. The agent shows up as a tombstone (the K3PT ST4T3
|
||||
tab of the dashboard); `swarmctl agent create` with the same name and hive
|
||||
recreates it from the kept state, **no re-login needed**.
|
||||
- **`PURG3`** (opt-in, from the dashboard or `hivectl agent <name>
|
||||
destroy --purge`) is `DESTR0Y` plus wiping all of it — config
|
||||
|
|
@ -235,8 +235,8 @@ Unusually, it's read and written from **both** sides of the harness
|
|||
bind-mount, and that's the whole design: the harness stats it
|
||||
in-container via `hive-agent`'s `paths::paused_marker`, while hive-c0re
|
||||
stats it on the host (`Coordinator::is_paused`) to populate the
|
||||
`paused` field on the agent card, and creates/removes it
|
||||
(`Coordinator::set_paused`) for `hivectl agent <name> pause|resume` and the
|
||||
`paused` field on the agent card, and asks hive-priv to create or remove it
|
||||
(`Coordinator::set_paused`; hive-c0re itself is unprivileged) for `hivectl agent <name> pause|resume` and the
|
||||
dashboard toggle. Because the file itself is the only shared state
|
||||
there's no protocol between them, no round-trip into the container, and
|
||||
pause keeps working when the harness is wedged or the container is
|
||||
|
|
|
|||
|
|
@ -44,10 +44,16 @@ at a given viewport width. Two columns:
|
|||
- **Pills cluster** (`.agent-header-pills`, right-aligned): status
|
||||
badges (`StatusChips.tsx`) and flyout triggers together in one row,
|
||||
so the header reads as one identity zone + one status/actions zone:
|
||||
- **Alive badge**: `● alive` (green) / `⊘ rate limited` (red) /
|
||||
`◌ needs login` / `◌ logging in` / `○ offline` / `… connecting`.
|
||||
- **State badge**: `💤 idle` / `🧠 thinking` / `📦 compacting` /
|
||||
`○ offline` / `… booting` + age suffix.
|
||||
- **Status badge**, one badge for liveness and turn state: `● alive`
|
||||
(green) / `⊘ rate limited` (red) / `◌ needs login` / `◌ logging in` /
|
||||
`○ offline` / `… connecting` / `💤 idle` / `🧠 thinking` /
|
||||
`📦 compacting` / `… booting` + age suffix, or paused. Selecting it
|
||||
opens a menu: `⏸ pause` / `▶ resume` (via the same hive-c0re-owned
|
||||
`/api/pause/<name>` / `/api/resume/<name>` endpoints the dashboard
|
||||
uses; pausing needs a write this unprivileged process can't make
|
||||
directly), `■ cancel turn` (only while a turn is running),
|
||||
`↻ new session` and `🔓 logout`. Cancel turn, new session and logout
|
||||
need a second selection to confirm.
|
||||
- **Model and effort badges** (`model · <name>`, `effort · <level>`):
|
||||
display only. Each tooltip says where the value comes from
|
||||
(`state.model_source` / `state.effort_source`: set on the swarm,
|
||||
|
|
@ -65,10 +71,6 @@ at a given viewport width. Two columns:
|
|||
- **Ctx / cost badges**: `ctx · 142k` (last inference's prompt size,
|
||||
tooltip shows % of context window) / `cost · 1.3M` (cumulative
|
||||
tokens billed across every inference in the last turn).
|
||||
- **Pause badge** (`⏸ pause` / `▶ resume`): toggles via the same
|
||||
hive-c0re-owned `/api/pause/<name>` / `/api/resume/<name>`
|
||||
endpoints the dashboard uses (pausing needs a write this
|
||||
unprivileged process can't make directly).
|
||||
- **Inbox / todos pills** (`📬 inbox · N` / `📋 todos · N`): hidden
|
||||
when empty; selecting it opens the matching flyout in the side panel.
|
||||
- **Profile avatar**: the signed-in user's initial, opening a popover with
|
||||
|
|
@ -162,7 +164,7 @@ If the todo store errors or doesn't answer for any id, the endpoint returns
|
|||
## Live view
|
||||
|
||||
Each agent runs an `events::Bus`: a `tokio::sync::broadcast<LiveEvent>`
|
||||
plus a sqlite-backed history at `/state/hyperhive-events.sqlite`.
|
||||
plus a sqlite-backed history at `/harness/hyperhive-events.sqlite`.
|
||||
The harness emits `TurnStart { from, body, unread }`,
|
||||
`Stream(value)` (one per parsed stream-json line), `Note`,
|
||||
`TurnEnd { ok, note }`. Each event also carries a `ts` (unix
|
||||
|
|
@ -238,10 +240,9 @@ on this page calls it now.
|
|||
|
||||
Successful POSTs return 200 (no 303 redirects). Error responses
|
||||
use semantic status codes: **400** for missing/malformed input
|
||||
(`body` required, unknown model name, unrecognised effort level),
|
||||
**409** for retryable state conflicts (turn in flight when
|
||||
`/compact` fires), **500** only for genuine server/transport
|
||||
failures. The matching
|
||||
(`/api/todos/mark-done` with no todo ids), **500** only for genuine
|
||||
server/transport failures. `/compact` never conflicts: it queues
|
||||
and runs at the end of the current turn. The matching
|
||||
mutations fire `LiveEvent` variants on the per-agent bus, so the
|
||||
client doesn't refetch `/api/state` on submit — the SSE stream
|
||||
delivers the new state faster anyway.
|
||||
|
|
|
|||
Loading…
Reference in a new issue