Compare commits

...
8 changed files with 383 additions and 287 deletions

View file

@ -354,6 +354,8 @@ docs/
web-ui/agent.md (header, terminal, composer,
inbox, live view, per-agent endpoints, stats)
turn-loop.md claude invocation, wake prompt, MCP tool surface
tools/ per-group tool docs (bash.md, lifecycle.md,
scheduling.md, matrix.md)
approvals.md approval flow, manager policy, helper events
persistence.md sqlite dbs, retention, state dir layout
terminal-rendering.md per-agent terminal row taxonomy (as built)

65
docs/tools/bash.md Normal file
View file

@ -0,0 +1,65 @@
# Bash execution tools
Background shell execution via `hive-bash-mcp`. Tools land as
`mcp__bash__<tool>` (the MCP server name is `bash`, not `hyperhive`).
Available on every agent unconditionally — `harness-base.nix` always
injects bash into `hyperhive.extraMcpServers` (with `allowedTools =
["*"]`), so `mcp__bash__*` is in `--allowedTools` for every claude
invocation regardless of tool groups.
## Tools
### `run(cmd, timeout_secs?, wait_seconds?)`
Submit a shell command for background execution (`sh -c <cmd>`).
Stdout and stderr stream to `harness/bash-tasks/<id>.{out,err}`.
When the task completes (or times out, or the process errors), the
harness fires a wake with `from: "bash-task-<id>"` and the exit code
+ last stdout lines in the body; handle it on a future turn.
- `timeout_secs` — kill the task after N seconds and mark it
`timed_out`. Omit for no timeout (runs until natural exit).
- `wait_seconds` — inline poll before returning (capped at 30).
When the task finishes within the window the full status is
returned immediately and no wake is fired; when the window expires
the task keeps running and the normal `task started: id=<id>`
response is returned. **Defaults to 3** — pass `wait_seconds: 0`
to disable inline waiting and always get the immediate response.
Exposed as `mcp__bash__run`.
### `status(id, wait_seconds?)`
Poll the status of a task submitted with `run`. Returns:
- `status``pending` / `running` / `done` / `timed_out` / `interrupted`
- `exit_code` — set when done
- run duration
- last 4 KiB of stdout and stderr (full output in the `.out` / `.err` files)
`wait_seconds` — optional inline poll (capped at 30): when the task
finishes within the window the full status is returned immediately.
Useful to avoid a separate round-trip when the task is expected to
finish soon.
Tasks marked `interrupted` had their process killed by a harness
restart; a best-effort wake was still sent so the agent is not
silently blocked.
Exposed as `mcp__bash__status`.
## Namespace note
`run` and `status` live in the `bash` MCP server, not `hyperhive`. So
the tool names in claude are `mcp__bash__run` and `mcp__bash__status`.
The `Bash` built-in tool is blocked — all shell execution goes through
this structured path so tasks get task-id tracking and structured output.
## Relationship to the `execution` tool group
`ToolGroup::Execution` exists and appears in `AGENT_DEFAULT`, but its
`tools()` returns `["run", "status"]` which the harness expands to
`mcp__hyperhive__run` / `mcp__hyperhive__status` — tools that don't
exist in the hyperhive MCP server (dead entries). Removing `execution`
from an agent's groups has no effect on bash availability. Bash is
registered separately via the `extraMcpServers` path described above.

91
docs/tools/lifecycle.md Normal file
View file

@ -0,0 +1,91 @@
# Lifecycle and approvals tools
Two tool groups govern agent lifecycle management and config changes.
Both are scoped to **direct children only** (topology-enforced: the
server rejects any name that is not a direct child of the calling
agent per `topology.json`). Privileged agents (e.g. ruth) may operate
on any sub-agent — the topology scope applies to all others.
## `lifecycle` tool group
No operator approval required. Direct children only.
### `kill(name)`
Graceful stop. Container state is preserved; recreating the agent
reuses prior config and credentials.
### `start(name)`
Start a stopped sub-agent.
### `restart(name)`
Stop + start in one call.
### `update(name)`
Rebuild: re-applies the current hyperhive flake + `agent.nix`,
then restarts. Idempotent — safe to call repeatedly. Used in response
to `needs_update` system events.
### `list_containers()`
List all **descendant** containers (children + their subtrees) with
running status. Topology-scoped to descendants only.
## `approvals` tool group
Config changes and new-agent spawns route through the operator
approval queue. Direct children only (topology-enforced).
### `request_init_config(name, description?)`
Step 1 of spawning a new direct child agent. Queues an `InitConfig`
approval; on operator approve, hive-c0re seeds the proposed config
repo at `/agents/<name>/config/agent.nix` with a default template and
delivers a `config_ready` system event. Then edit `agent.nix`, commit,
and call `request_apply_commit` with the commit sha — the first
`ApplyCommit` on a freshly-init'd config creates the container.
Fails if a proposed config repo for `name` already exists.
`name` is ≤ 9 characters. The operator can also spawn an empty agent
via the dashboard `◆ R3QU3ST SP4WN` button, which routes via
`HostRequest::RequestSpawn`.
### `request_apply_commit(agent, commit_ref, description?)`
Step 2 of spawning (or updating an existing child's config). Submit a
commit sha from the child's proposed config repo for operator
approval. On approve, hive-c0re rebuilds the container with the
pinned commit.
`commit_ref` must be a 7-40 char hex sha (branch/tag names are
rejected — the approval pins the exact commit). `agent` must be a
direct child. Topology-enforced.
### `request_update_meta_inputs(inputs?, description?)`
Queue an approval to run `nix flake update [inputs...]` on the meta
flake. Pass specific input names (e.g. `["bitburner-agent"]`) or omit
/ pass `[]` for all inputs. Returns immediately; the lock update runs
on operator approval.
Does NOT trigger container rebuilds — call `update(name)` on affected
agents after the approval resolves.
## Boundary summary
| Operation | Requires approval? | Scope |
|---|---|---|
| `kill` / `start` / `restart` / `update` | No | Direct children |
| `list_containers` | No | All descendants |
| `request_init_config` | Yes (InitConfig) | New direct child only |
| `request_apply_commit` | Yes (ApplyCommit) | Direct children |
| `request_update_meta_inputs` | Yes (MetaUpdate) | Meta flake (global) |
## See also
- [`docs/approvals.md`](../approvals.md) — full approval flow, kinds,
helper events (`config_ready`, `approval_resolved`), flake.lock
validation.

62
docs/tools/matrix.md Normal file
View file

@ -0,0 +1,62 @@
# Matrix MCP tools and extra MCP servers
## Built-in matrix MCP (`mcp__matrix__*`)
When `hyperhive.matrix.enable = true` and the host-level matrix
tuwunel is configured, the harness auto-injects `hive-matrix-mcp` as
a second stdio MCP server. Tools land as `mcp__matrix__<name>`:
### Messaging
- `send_message(room, body)` — send a markdown message; `room`
accepts `!id:server` or `#alias:server`, daemon resolves either
- `send_dm(user_id, body)` — send a direct message to a matrix user
- `send_reply(room, event_id, body)` — threaded reply to a specific
event
- `send_reaction(room, event_id, key)` — react to a message with an
emoji key
### Reading
- `read_room(room, limit?)` — recent timeline events
- `list_rooms()` — enumerate joined rooms
(`{ id, canonical_alias, name, member_count }` per room)
- `list_room_members(room)` — members of a room
### Receipts
- `mark_read(room, event_id)` — advance the read receipt
## Architecture
The daemon (`hive-matrix-daemon`) holds the long-running matrix-sdk
`Client` + sync loop; the stdio bridge (`hive-matrix-mcp`) is spawned
per turn and forwards tool calls over `/run/hive-matrix.sock`. Both
silently exit when `<state>/matrix-token` is absent (account not yet
provisioned).
Incoming room events wake the agent via `AgentRequest::Wake` with
`from: "matrix"` and a teaser body
(`[matrix] <sender> in <room>: <first-100c>…`).
See [`docs/matrix.md`](../matrix.md) for the homeserver setup,
provisioning flow, and federation config.
## Extra MCP servers (per-agent)
Each agent's NixOS config can declare additional MCP servers via
`hyperhive.extraMcpServers.<key> = { command, args, env,
allowedTools }`. The module writes the map to
`/etc/hyperhive/extra-mcp.json`; the harness reads it at boot and
merges every entry into `--mcp-config` (under `mcpServers.<key>`)
and `--allowedTools` (as `mcp__<key>__<pattern>`).
The agent's `flake.nix` forwards every flake input to `agent.nix` as
the `flakeInputs` module arg, so external MCP-server flakes are pulled
in by adding them to `inputs.*` and referenced as
`flakeInputs.<name>.packages.${pkgs.system}.default` — the resolved
sha lands in the agent's own `flake.lock` and rolls up to meta's.
`allowedTools` defaults to `["*"]`, which expands to
`mcp__<key>__*` (every tool from that server auto-approved). Restrict
to specific tool names when you want finer control.

68
docs/tools/scheduling.md Normal file
View file

@ -0,0 +1,68 @@
# Scheduling and diagnostics tools
## `scheduling` tool group
Scheduled prompts fan a message body out to one or more agent inboxes
at a future time, optionally recurring. All scheduling ops go through
the operator approval queue (even self-targeted schedules — use
`remind` for unapproved self-wake). Authorization for read/cancel/edit
ops: you can act on schedules you own or any owned by a sub-agent in
your topology subtree.
### `request_schedule_prompt(targets, body, first_fire_at_unix, interval_seconds?, description?)`
Queue an operator-approval for a scheduled prompt. On approve,
`body` is fanned out to each agent in `targets` at
`first_fire_at_unix` (Unix timestamp). Recurring when `interval_seconds`
is set, one-shot otherwise.
Catch-up clamp: if hive-c0re is down across multiple intervals, only
ONE delayed fire happens on resume (per recurring schedule). The
skipped-cycle count surfaces in the per-target `last_result` for
audit.
### `edit_schedule(id, body?, description?, interval_seconds?, next_fire_at_unix?, targets_add?, targets_remove?)`
Partial-update a schedule. Pass only the fields to change; absent
fields are left alone. `targets_add` / `targets_remove` mutate the
recipient list in the same transaction — re-adding a previously
cancelled target drops its tombstone and starts fresh. Clearing
`interval_seconds` to null flips recurring → one-shot. Refuses
cancelled rows (terminal state).
### `cancel_schedule(id, targets?)`
Cancel a schedule. Omit `targets` / pass empty to cancel the whole
schedule; pass a list to cancel just those recipients (the schedule
auto-cancels when every target is removed).
### `fire_schedule_now(id)`
Fire a scheduled prompt out of band immediately. Recurring schedules
keep their cadence — the manual fire is additive. One-shot schedules
are consumed by the manual fire and cancelled afterwards.
### `list_schedules()`
Snapshot every schedule (active + cancelled-but-not-reaped): id,
owner, body, per-target `last_fired_at` + `last_result`,
`next_fire_at_unix`, `interval_seconds`. Use to look up an id before
cancelling, or to audit upcoming wake-ups across the swarm.
## `diagnostics` tool group
### `get_logs(agent, lines?)`
Fetch recent journal lines for a sub-agent container. Useful for
diagnosing MCP-registration failures, startup crashes, plugin install
errors, or any harness issue you can't see from inside the container.
Pass the plain logical agent name (e.g. `"gui"`) — hive-c0re resolves
the machine name (`h-<name>`). `lines` defaults to 50, host-capped at 500.
## See also
- `remind` (no-approval self-wake path) — documented in
[`docs/turn-loop.md`](../turn-loop.md).
- [`docs/approvals.md`](../approvals.md) — approval flow for
`request_schedule_prompt`.

View file

@ -5,9 +5,8 @@ claude has access to in return.
## The loop
Each agent harness (`hive serve`, with role picked from `$HIVE_ROLE`
`"agent"` for sub-agents, `"manager"` for the manager — one
binary, not two) runs:
Each agent harness (`hive serve`, role set via `$HIVE_ROLE` — always
`"agent"`, one binary) runs:
1. Long-poll `Recv` on its socket. The host-side broker
(`broker.rs::recv_blocking_batch`) returns immediately if there's
@ -292,115 +291,75 @@ The harness ships an embedded MCP server (rmcp 1.7). Claude launches
it as a stdio child via `--mcp-config`. The hyperhive socket name is
`hyperhive`, so the tools land in claude as `mcp__hyperhive__<tool>`.
### Sub-agent tools
Tool access is gated by tool groups (`HIVE_TOOL_GROUPS`). The default
preset (`AGENT_DEFAULT`) includes `messaging`, `meta`, `inbox`, and
`execution`. Privileged groups (`lifecycle`, `approvals`, `scheduling`,
`diagnostics`) are opt-in via the P3RM1SS10NS tab.
- `send(to, body, in_reply_to?)` — message a peer (logical agent
name), another agent, or the operator (recipient `operator`,
surfaces in the dashboard inbox). Use `to: "<parent>"` to
address the agent's topology parent without knowing its label;
the broker resolves the sentinel at delivery time (falls back
to `operator` for root agents). Optional `in_reply_to: i64`
links this message to a prior message id for thread rendering
in the dashboard message flow and the per-agent inbox.
- `recv(wait_seconds?, max?)` — drain inbox messages. Without
`wait_seconds` (or with `0`) returns immediately, a cheap
"anything pending?" peek. Positive value parks the turn up
to that many seconds (cap 180) — incoming messages wake
instantly, otherwise returns empty at the timeout. `max`
(default 1, server-side cap 32) drains up to N popped rows
in one round-trip; `wait_seconds` applies to the *first*
message, then the call drains up to `max` total.
- `ask(question, options?, multi?, ttl_seconds?, to?)`
surface a structured question. Same shape as the manager's;
recipient defaults to the operator (dashboard) but can be set
to a peer agent name via `to: "<agent>"`. Answer routes back
to the asker's own inbox as `HelperEvent::QuestionAnswered`
via `coord.notify_agent`. For peer questions the recipient
sees a `HelperEvent::QuestionAsked` event and replies with
`answer(id, answer)`.
- `answer(id, answer)` — respond to a `question_asked` event
routed to this agent. Authorisation is strict: only the
declared target (or the operator via the dashboard) can
answer.
- `get_loose_ends()` — list everything still pending against
this agent: unanswered questions it asked / was asked, plus
reminders it scheduled. Each row carries an id + kind for
### Core tools (always available)
**Messaging** (`messaging` group): `send(to, body, in_reply_to?)`,
`recv(wait_seconds?, max?)`, `ask(question, options?, multi?,
ttl_seconds?, to?)`, `answer(id, answer)`.
- `send` — message a peer (logical name) or the operator
(`to: "operator"`). Use `to: "<parent>"` to address the topology
parent without hardcoding the label; the broker resolves the
sentinel at delivery time. Optional `in_reply_to: i64` links the
message to a prior id for thread rendering.
- `recv` — drain inbox. Without `wait_seconds` (or `0`) returns
immediately. Positive value parks the turn up to that many seconds
(cap 180) — incoming messages wake instantly. `max` (default 1, cap
32) drains up to N rows; `wait_seconds` applies to the first, then
drains up to `max` total.
- `ask` — surface a structured question to the operator (default) or
a peer agent (`to: "<agent>"`). Non-blocking — returns a question
id; the answer arrives as a `question_answered` system event in the
asker's inbox. `options` is advisory; `multi=true` renders as
checkboxes; `ttl_seconds` auto-cancels with answer `[expired]`.
- `answer` — respond to a `question_asked` event routed to this
agent. Strict authorisation: only the declared target can answer.
**Inbox** (`inbox` group): `get_loose_ends()`,
`cancel_loose_end(kind, id)`, `remind(message, delay_seconds? |
at_unix_timestamp?)`, `request_next_turn()`.
- `get_loose_ends` — list pending questions (asked/owed) and
scheduled reminders. Each row carries an id + kind for
`cancel_loose_end`.
- `cancel_loose_end(kind, id)` — withdraw a `question`
(posts `[cancelled by <self>]` to unblock the asker), a
`reminder` (hard-delete before fire), or (manager only) an
`approval` (transitions to `Cancelled`; sub-agents refused with a
clear error). Sub-agents may only cancel rows they own.
- `remind(message, due)` — schedule a reminder that lands in
this agent's own inbox at a future time (sender shows as
`reminder`). Large payloads spill to
`/agents/<self>/state/reminders/` with the inbox message a
short pointer. Each agent's pending-reminder count is capped
(default 50, override via `HIVE_REMIND_MAX_PENDING_PER_AGENT`);
scheduling a new one fails if the cap is already hit.
- `set_status(text)` — set a free-text status string visible on
the operator dashboard. Persisted to
`{state_dir}/hyperhive-status`; survives harness restarts. Pass
an empty string to clear. Validated: must be a single line and
≤ 200 Unicode characters; the server rejects multi-line or
over-length text with a clear error.
- `get_agent_meta(name?)` — fetch identity + status metadata for
an agent: `{ name, role, hyperhive_rev, running, status_text,
status_set_at, hive_name?, swarm_name? }`. Pass `name` to query
a peer (e.g. check whether a sub-agent is idle before sending it
work). Omit `name` to get your own identity stamp — replaces the
previous `whoami` tool. `running` is `true` when the container is
up. When `running` is `false` the host clears `status_text` /
`status_set_at` (they would be stale snapshots from before the
container stopped) before serving the response. Status fields are
also `None` when the target has never called `set_status` or has
cleared it. `hive_name` and `swarm_name` are present when
`services.hyperhive.hiveName` / `services.hyperhive.swarmName`
are configured on the host; omitted in single-hive deployments.
- `request_next_turn()` — ask the harness to start another turn
immediately after this one ends, even if the inbox is empty. Use for
multi-turn tasks (long builds, sequential steps) where you want to
continue without waiting for an external message. The next turn starts
with `from: "self"` and `body: "continue"`. No-op if new inbox
messages arrive before this turn ends. No args.
- `run(cmd, timeout_secs?)` — submit a shell command for
background execution (`sh -c <cmd>`). Returns a task ID immediately;
the command runs asynchronously in a harness-managed tokio task. Stdout
and stderr stream to `harness/bash-tasks/<id>.{out,err}`. When the
task completes (or times out, or the process errors), the harness wakes
the agent with a summary body — handle on a future turn. Optional
`timeout_secs`: pass a value for a deadline, or omit for no timeout
(runs until natural exit). Requires the `execution` tool group.
Exposed as `mcp__bash__run`.
- `status(id)` — poll the status of a task submitted with
`run`. Returns status (`pending`/`running`/`done`/`timed_out`/
`interrupted`), exit code, run duration, and the last 4 KiB of stdout
and stderr (full output in the `.out`/`.err` files). Tasks marked
`interrupted` had their process killed by a harness restart; a best-
effort wake was still sent so the agent is not silently blocked.
Exposed as `mcp__bash__status`.
- `cancel_loose_end` — withdraw a `question` (posts `[cancelled by
<self>]`), hard-delete a `reminder`, or cancel a pending `approval`
row. Agents may only cancel rows they own; the `approval` kind is
further restricted to the root agent (`ruth`) server-side.
- `remind` — schedule a reminder in this agent's own inbox. Large
payloads spill to `/agents/<self>/state/reminders/`. Pending count
capped at 50 per agent (`HIVE_REMIND_MAX_PENDING_PER_AGENT`).
- `request_next_turn` — ask the harness to start another turn
immediately after this one ends, even if the inbox is empty.
Next turn fires with `from: "self"` and `body: "continue"`.
**Agent lifecycle + config tools** (direct children only; requires
`lifecycle` or `approvals` tool group):
**Meta** (`meta` group): `set_status(text)`, `get_agent_meta(name?)`.
- `kill(name)`, `start(name)`, `restart(name)`, `update(name)` — manage
a direct child sub-agent (graceful stop, start, stop+start, rebuild).
No approval required. Topology-enforced: `name` must be a direct child
per `topology.json`. Server rejects all other agent names. Requires
`lifecycle` tool group.
- `list_containers()` — list all descendant containers with running
status. Topology-enforced (descendants only). Requires `lifecycle`
tool group.
- `request_init_config(name, description?)` — step 1 of spawning a new
direct child agent. Queues an `InitConfig` approval; on operator
approve, hive-c0re seeds the proposed config repo with a default
`agent.nix` template. `name` must be a direct child. Topology-enforced.
Fails if config already exists. Requires `approvals` tool group.
- `request_apply_commit(agent, commit_ref, description?)` — step 2 of
spawning a new direct child (or updating an existing child's config).
Submit a commit sha from the child's proposed config repo for operator
approval. `agent` must be a direct child. Topology-enforced. `commit_ref`
must be a 7-40 char hex sha. Requires `approvals` tool group.
- `set_status` — set a free-text status string visible on the
dashboard. Single line, ≤ 200 chars. Persisted to
`{state_dir}/hyperhive-status`. Pass `""` to clear.
- `get_agent_meta` — fetch identity + status metadata for an agent:
`{ name, hyperhive_rev, running, status_text, status_set_at,
hive_name?, swarm_name? }`. Omit `name` to query self.
### Privileged tools (by tool group)
- **Bash execution** (`execution`) — background shell tasks. See
[`docs/tools/bash.md`](tools/bash.md).
- **Lifecycle + config** (`lifecycle`, `approvals`) — manage child
agents, spawn new ones, apply config commits. See
[`docs/tools/lifecycle.md`](tools/lifecycle.md).
- **Scheduling + diagnostics** (`scheduling`, `diagnostics`) —
scheduled prompts, `get_logs`. See
[`docs/tools/scheduling.md`](tools/scheduling.md).
- **Matrix MCP + extra servers**`mcp__matrix__*` tools and
per-agent extra MCP config. See
[`docs/tools/matrix.md`](tools/matrix.md).
### Waking the agent from inside the container
@ -411,170 +370,22 @@ socket at `/run/hive/mcp.sock`. Two equivalent paths:
- **Shell out to `hive wake --from <label> --body <text>`**
(use `--body -` to read body from stdin). Already on the
container's `PATH` since the harness binary is in
`systemPackages`. Convenient for shell-script integrations.
Works for both `agent` and `manager` roles.
`systemPackages`. Convenient for shell-script integrations and
co-process daemons (matrix bridge, webhook listeners, scrapers).
- **Speak the wire protocol directly** — JSON-line over the
unix socket: `{"cmd":"wake","from":"matrix","body":"new dm
from @alice"}\n`. Same shape any other AgentRequest uses;
from @alice"}\n`. Same shape as any other `AgentRequest`;
see `hive-sh4re::AgentRequest::Wake`.
The wake event lands in the broker as `{from:<label>,
to:<agent>, body}`, which wakes whatever `recv` call the
harness is currently blocked on. Next turn fires with the
wake prompt formed from that message — claude sees "from:
matrix" (or whatever label) and reacts.
to:<agent>, body}`, waking whatever `recv` call the harness
is currently blocked on. The next turn fires with the wake
prompt formed from that message.
Identity = socket: anything that can connect to
`/run/hive/mcp.sock` is implicitly trusted to inject these,
which is fine because the bind-mount is the agent's own
container only.
### Built-in matrix MCP (`mcp__matrix__*`)
When `hyperhive.matrix.enable = true` (default) and the host-level
matrix tuwunel is configured, the harness auto-injects `hive-matrix-mcp`
as a second stdio MCP server. Tools land as `mcp__matrix__<name>`:
- `send_message(room, body)` — send a markdown message; `room` accepts
`!id:server` or `#alias:server`, daemon resolves either
- `send_dm(user_id, body)` — send a direct message to a matrix user
- `send_reaction(room, event_id, key)` — react to a message
- `send_reply(room, event_id, body)` — threaded reply
- `mark_read(room, event_id)` — advance the read receipt
- `list_rooms()` — enumerate joined rooms
(`{ id, canonical_alias, name, member_count }` per room)
- `list_room_members(room)` — members of a room
- `read_room(room, limit?)` — recent timeline events
The daemon (`hive-matrix-daemon`) holds the long-running matrix-sdk
`Client` + sync loop; the stdio bridge (`hive-matrix-mcp`) is spawned
per turn and forwards tool calls over `/run/hive-matrix.sock`. Both
silently exit when `<state>/matrix-token` is absent (account not yet
provisioned). Incoming room events wake the agent via `AgentRequest::Wake`
with `from: "matrix"` and a teaser body
(`[matrix] <sender> in <room>: <first-100c>…` where `<sender>` is
the matrix user id and `<room>` is the alias or id as reported by
the SDK).
### Extra MCP servers (per-agent)
Each agent's NixOS config can declare additional MCP servers via
`hyperhive.extraMcpServers.<key> = { command, args, env,
allowedTools }`. The module writes the map to
`/etc/hyperhive/extra-mcp.json`; the harness reads it at boot and
merges every entry into `--mcp-config` (under `mcpServers.<key>`)
and `--allowedTools` (as `mcp__<key>__<pattern>`). The agent's
flake.nix forwards every flake input to `agent.nix` as the
`flakeInputs` module arg, so external MCP-server flakes are pulled
in by adding them to `inputs.*` and referenced as
`flakeInputs.<name>.packages.${pkgs.system}.default` — the
resolved sha lands in the agent's own `flake.lock` and rolls up to
meta's.
### Manager tools (in addition to send/recv)
- `request_init_config(name, description?)` — first step of a
two-step spawn. Queues an `InitConfig` approval (≤9 char name);
on operator approve, hive-c0re seeds the proposed config repo
with a default `agent.nix` template and sends the manager a
`HelperEvent::ConfigReady { agent }`. The manager then edits
`agent.nix`, commits the changes, and calls `request_apply_commit`
with the commit sha — the first ApplyCommit on a freshly-init'd
config creates the container. Fails if a proposed repo for this
name already exists. (The operator can also direct-spawn an empty
agent from the dashboard's `◆ R3QU3ST SP4WN` button, which routes
via `HostRequest::RequestSpawn`.) Also available to agents for direct
children only (requires `approvals` tool group; see agent lifecycle
tools section above).
- `kill(name)` — graceful stop. No approval required. Also available to
agents for direct children only (requires `lifecycle` tool group; see
agent lifecycle tools section above).
- `start(name)` — start a stopped sub-agent. No approval. Also available
to agents for direct children only.
- `restart(name)` — stop + start. No approval. Also available to agents
for direct children only.
- `update(name)` — rebuild (re-applies the current hyperhive flake
+ agent.nix, restarts). No approval, idempotent. Manager calls
this on receipt of a `needs_update` system event. Also available to
agents for direct children only.
- `request_apply_commit(agent, commit_ref)` — submit a config
change for any agent (`root` for the manager's own config) for
operator approval. Also available to agents for direct children only
(requires `approvals` tool group; see agent lifecycle tools section
above).
- `request_update_meta_inputs(inputs?, description?)` — queue an
approval to run `nix flake update [inputs...]` on the meta flake.
Pass specific input names (e.g. `["bitburner-agent"]`) or omit
for all. Returns immediately; lock update runs on operator
approval. Does NOT trigger rebuilds — call `update(name)` on
affected agents after approval resolves.
- `ask(question, options?, multi?, ttl_seconds?, to?)`
surface a structured question to the operator (default) or a
sub-agent (`to: "<agent>"`). Non-blocking — returns the
queued question id; the answer arrives later as
`HelperEvent::QuestionAnswered { id, question, answer,
answerer }` in the asker's inbox. Options always render
alongside a free-text fallback; `multi=true` renders options
as checkboxes. `ttl_seconds` auto-cancels with answer
`[expired]` (and `answerer: "ttl-watchdog"`) after the
deadline (useful for time-sensitive decisions that become moot
if no one has responded). The operator can also manually
cancel with `[cancelled]` via the dashboard.
- `answer(id, answer)` — respond to a `question_asked` event
that was routed to the manager (a sub-agent did
`ask(to: "manager", ...)`). Surfaces in the asker's inbox as
the same `question_answered` event.
- `get_logs(agent, lines?)` — fetch recent journal lines for a
sub-agent container (diagnose MCP-registration failures,
startup crashes, etc.). Pass the plain logical agent name;
hive-c0re resolves the machine name (`h-<name>`, manager
`root`). `lines` defaults to 50, host-capped at 500.
- `request_schedule_prompt(targets, body, first_fire_at_unix, interval_seconds?, description?)`
queue an operator-approval for a scheduled prompt. On approve,
`body` is fanned out to each `targets` agent at
`first_fire_at_unix`; recurring if `interval_seconds` is set,
one-shot otherwise. Even self-targeted schedules go through
approval (use `remind` for unapproved self-wake). Long downtime
fires once per recurring row on resume (catch-up clamp).
- `edit_schedule(id, body?, description?, interval_seconds?, next_fire_at_unix?, targets_add?, targets_remove?)`
partial-update a schedule. Pass only the fields to
change; absent fields are left alone. `targets_add` / `targets_remove`
mutate the recipient list in the same transaction; re-adding a
previously-cancelled target drops its tombstone + history (fresh
start). Clearing a scalar (e.g. `interval_seconds: null`) flips
recurring→one-shot. Refuses cancelled rows. Same authorization as
`cancel_schedule`.
- `cancel_schedule(id, targets?)` — cancel a schedule. Omit
`targets` / pass empty to cancel the whole schedule; pass a list
to cancel just those recipients (auto-cancels when every target
is removed). Authorization: manager can cancel schedules it owns
or any owned by a sub-agent in its topology subtree.
- `fire_schedule_now(id)` — fire a scheduled prompt out of band.
Runs the per-target fan-out once immediately. Recurring schedules
keep their cadence (the manual fire is additive); one-shot
schedules are consumed by the fire and cancelled afterwards. Same
authorization rules as `cancel_schedule`.
- `list_schedules()` — snapshot every schedule (active +
cancelled-but-not-reaped): id, owner, body, per-target
`last_fired_at` + `last_result`, `next_fire_at_unix`,
`interval_seconds`. Use to look up an id before cancelling or to
audit upcoming wake-ups across the swarm.
- `remind` / `get_loose_ends` / `cancel_loose_end` / `set_status`
/ `get_agent_meta` — same as the sub-agent tools above.
`get_loose_ends` scopes to the manager's own items by default;
pass `agent: "*"` for a hive-wide view, or `agent: "<name>"`
to inspect one agent.
`cancel_loose_end` may cancel any agent's row.
The boundary: lifecycle ops on *existing* direct children
(`kill`/`start`/`restart`/`update`) are discretionary — no operator
approval required (manager can do so for any sub-agent; agents can do so
only for direct children with `lifecycle` tool group). Creating a new
agent (`request_init_config``request_apply_commit` for the first sha)
and changing any agent's config (`request_apply_commit`) still go through
the approval queue (same topology scoping for agents: direct children only
with `approvals` tool group).
`/run/hive/mcp.sock` is implicitly trusted to inject these —
the bind-mount is the agent's own container only.
### Authoritative state
@ -604,7 +415,7 @@ status hint moved to the wake prompt + UI header.
`web_tools` tool group is enabled — see P3RM1SS10NS tab).
- Denied by omission or `claude-settings.json` deny list: `Bash`,
`Task`, `NotebookEdit`, `TodoWrite`.
- Allowed MCP tools: as listed above per flavor.
- Allowed MCP tools: as listed above (by tool group).
`Bash` is disallowed — shell execution goes through
`mcp__bash__run` (background tasks with structured output +

View file

@ -8,7 +8,7 @@ Tools (hyperhive surface):
- `mcp__hyperhive__ask(question, options?, multi?, ttl_seconds?, to?)` — surface a structured question to the human operator (default, or `to: "operator"`) OR a peer agent (`to: "<agent-name>"`). Returns immediately with a question id — do NOT wait inline. When the recipient answers, a system message with event `question_answered { id, question, answer, answerer }` lands in your inbox; handle it on a future turn. Use this for clarifications, permission for risky actions, choice between options, or peer Q&A without burning regular inbox slots. `options` is advisory: a short fixed-choice list when applicable, otherwise leave empty for free text. `multi: true` lets the answerer pick multiple (checkboxes), answer comes back comma-joined. `ttl_seconds` auto-cancels with answer `[expired]` (and `answerer: "ttl-watchdog"`) when the decision becomes moot.
- `mcp__hyperhive__answer(id, answer)` — answer a question that was routed to YOU. You'll see one in your inbox as a `question_asked { id, asker, question, options, multi }` system event when a peer or the operator calls `ask(to: "<your-name>", ...)`. The answer surfaces in the asker's inbox as a `question_answered` event. Strict authorisation: you can only answer questions where you are the declared target.
- `mcp__hyperhive__get_loose_ends()` — list your loose ends: unanswered questions where you're asker (waiting on someone) or target (owing a reply), plus reminders you've scheduled that haven't fired. No args, cheap server-side sweep. Useful at turn start to remember what's outstanding without scanning inbox archaeology.
- `mcp__hyperhive__cancel_loose_end(kind, id)` — cancel one of your own open threads. `kind` is `"question"` (the asker — you, in this case — gets a `[cancelled by <you>]` answer so the waiter unblocks), `"reminder"` (hard-deleted before it fires), or `"approval"` (withdraws a pending approval you submitted that got superseded — operator-approved path, so requires the `approvals` tool group). `id` from the matching `get_loose_ends` row or the original submission reply.
- `mcp__hyperhive__cancel_loose_end(kind, id)` — cancel one of your own open threads. `kind` is `"question"` (the asker — you, in this case — gets a `[cancelled by <you>]` answer so the waiter unblocks), `"reminder"` (hard-deleted before it fires), or `"approval"` (withdraws a pending approval you submitted that got superseded — root agent only; the server rejects this kind for all other callers). `id` from the matching `get_loose_ends` row or the original submission reply.
- `mcp__hyperhive__remind(message, delay_seconds? | at_unix_timestamp?, file_path?)` — schedule a message to land in your *own* inbox at a future time (sender shows as `reminder`). Set exactly one of `delay_seconds` (relative) or `at_unix_timestamp` (absolute). Use for self-paced follow-ups instead of blocking a whole turn on a long `recv` wait. A large `message` auto-spills to a file under `/agents/{label}/state/reminders/`; pass `file_path` to point at one yourself. Each agent's pending-reminder count is capped (default 50) — the tool will error if the cap is already reached.
- `mcp__hyperhive__set_status(text)` — set a free-text status visible on the operator dashboard. **Call this at the start of every task** to say what you're working on (e.g. `"processing matrix messages"`, `"fixing #319 model priority"`, `"idle"`). Single line, ≤200 chars — the dashboard renders this as a short chip, so longer multi-line text is rejected. Pass an empty string to clear. Persists across harness restarts.
- `mcp__hyperhive__get_agent_meta(name?)` — fetch identity + status metadata for an agent: canonical `name`, current `hyperhive_rev`, plus self-reported `status` text (set via `set_status`) and how long ago it was set. Also returns the hive + swarm display names (`hive_name`, `swarm_name`) when the operator has configured `services.hyperhive.{hiveName, swarmName}`; both lines omitted when unset. Pass `name` to query a peer (e.g. check whether iris is idle before pinging them). Omit `name` to get your own trustworthy identity stamp — useful for state files, commit messages, cross-agent attribution that won't drift across renames or session-continue boundaries where the system-prompt label could be stale.

View file

@ -383,7 +383,7 @@ fn loose_end_kind_label(kind: hive_sh4re::CancelLooseEndKind) -> &'static str {
}
/// Format helper for `get_agent_meta`: renders an agent's identity +
/// current status as a short human-readable block. `name`, `role`,
/// current status as a short human-readable block. `name`,
/// `hyperhive_rev`, and `running` are always shown; `status` only
/// appears when one is set, otherwise the line reads `status: <none>`.
/// When `running` is false the host has already cleared `status_text`
@ -978,12 +978,10 @@ impl AgentServer {
// IMPORTANT: this tool is only available when the `lifecycle` tool group
// is granted to this agent. hive-c0re enforces the topology check
// server-side: the call is rejected unless `name` is a direct child.
#[tool(
description = "Stop a direct child sub-agent container (graceful). \
#[tool(description = "Stop a direct child sub-agent container (graceful). \
Only succeeds if `name` is a direct child of this agent in the topology \
tree the server enforces this. No approval required. \
State dir is kept; recreating the agent reuses prior config + credentials."
)]
State dir is kept; recreating the agent reuses prior config + credentials.")]
async fn kill(&self, Parameters(args): Parameters<KillArgs>) -> String {
let log = format!("{args:?}");
let name = args.name.clone();
@ -1012,7 +1010,10 @@ impl AgentServer {
let (resp, retries) = self
.dispatch(hive_sh4re::Request::Update { name: args.name })
.await;
annotate_retries(format_ack(resp, "update", format!("updated {name}")), retries)
annotate_retries(
format_ack(resp, "update", format!("updated {name}")),
retries,
)
})
.await
}
@ -1029,9 +1030,7 @@ impl AgentServer {
)]
async fn list_containers(&self) -> String {
run_tool_envelope("list_containers", String::new(), async move {
let (resp, retries) = self
.dispatch(hive_sh4re::Request::ListDescendants)
.await;
let (resp, retries) = self.dispatch(hive_sh4re::Request::ListDescendants).await;
let body = match resp {
Ok(SocketReply::Containers(containers)) => {
if containers.is_empty() {
@ -1176,11 +1175,9 @@ impl AgentServer {
// IMPORTANT: this tool is only available when the `lifecycle` tool group
// is granted to this agent. hive-c0re enforces the topology check
// server-side: the call is rejected unless `name` is a direct child.
#[tool(
description = "Start a stopped direct child sub-agent container. \
#[tool(description = "Start a stopped direct child sub-agent container. \
Only succeeds if `name` is a direct child of this agent in the topology \
tree the server enforces this. No approval required."
)]
tree the server enforces this. No approval required.")]
async fn start(&self, Parameters(args): Parameters<StartArgs>) -> String {
let log = format!("{args:?}");
let name = args.name.clone();
@ -1188,12 +1185,14 @@ impl AgentServer {
let (resp, retries) = self
.dispatch(hive_sh4re::Request::Start { name: args.name })
.await;
annotate_retries(format_ack(resp, "start", format!("started {name}")), retries)
annotate_retries(
format_ack(resp, "start", format!("started {name}")),
retries,
)
})
.await
}
#[tool(
description = "Fetch recent journal log lines for a sub-agent container. Useful \
for diagnosing MCP server registration failures, startup crashes, plugin install \
@ -1406,9 +1405,7 @@ impl AgentServer {
)]
async fn list_schedules(&self) -> String {
run_tool_envelope("list_schedules", String::new(), async move {
let (resp, retries) = self
.dispatch(hive_sh4re::Request::ListSchedules)
.await;
let (resp, retries) = self.dispatch(hive_sh4re::Request::ListSchedules).await;
let body = match resp {
Ok(SocketReply::Schedules(schedules)) => serde_json::to_string(&schedules)
.unwrap_or_else(|e| format!("list_schedules: serialise: {e:#}")),