diff --git a/CLAUDE.md b/CLAUDE.md index eb4dbb56..4405782d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) diff --git a/docs/tools/bash.md b/docs/tools/bash.md new file mode 100644 index 00000000..873f5074 --- /dev/null +++ b/docs/tools/bash.md @@ -0,0 +1,49 @@ +# Bash execution tools (`execution` tool group) + +Background shell execution via `hive-bash-mcp`. Tools land as +`mcp__bash__` (the MCP server name is `bash`, not `hyperhive`). +Enabled for any agent whose tool groups include `execution` — the +default preset (`AGENT_DEFAULT`) includes it. + +## Tools + +### `run(cmd, timeout_secs?)` + +Submit a shell command for background execution (`sh -c `). +Returns a task ID immediately; the command runs asynchronously in a +harness-managed tokio task. Stdout and stderr stream to +`harness/bash-tasks/.{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). + +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` — set when done +- run duration +- 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`. + +## 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. + +## Tool whitelist cross-reference + +`mcp__bash__run` and `mcp__bash__status` are unconditionally in +`--allowedTools` whenever the harness spawns claude (they are not +gated by the `execution` group at the `--allowedTools` level — the +group only gates whether the MCP server registers the tools at all). diff --git a/docs/tools/lifecycle.md b/docs/tools/lifecycle.md new file mode 100644 index 00000000..c2135445 --- /dev/null +++ b/docs/tools/lifecycle.md @@ -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//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. diff --git a/docs/tools/matrix.md b/docs/tools/matrix.md new file mode 100644 index 00000000..3e0d460f --- /dev/null +++ b/docs/tools/matrix.md @@ -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__`: + +### 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 `/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] in : …`). + +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. = { 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.`) +and `--allowedTools` (as `mcp____`). + +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..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____*` (every tool from that server auto-approved). Restrict +to specific tool names when you want finer control. diff --git a/docs/tools/scheduling.md b/docs/tools/scheduling.md new file mode 100644 index 00000000..358389e1 --- /dev/null +++ b/docs/tools/scheduling.md @@ -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-`). `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`. diff --git a/docs/turn-loop.md b/docs/turn-loop.md index 929aefb8..492a7fce 100644 --- a/docs/turn-loop.md +++ b/docs/turn-loop.md @@ -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,74 @@ 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__`. -### 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: ""` 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: ""`. 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: ""` 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: ""`). 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 ]` 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//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 `). Returns a task ID immediately; - the command runs asynchronously in a harness-managed tokio task. Stdout - and stderr stream to `harness/bash-tasks/.{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 + ]`) or hard-delete a `reminder`. Agents may only cancel rows + they own. +- `remind` — schedule a reminder in this agent's own inbox. Large + payloads spill to `/agents//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 +369,22 @@ socket at `/run/hive/mcp.sock`. Two equivalent paths: - **Shell out to `hive wake --from