diff --git a/docs/conventions.md b/docs/conventions.md index 839d6eaa..8c156d30 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -8,7 +8,7 @@ exist because something already went wrong without them. - Containers are length-bounded by `nixos-container` (≤ 11 chars). - Sub-agents are `h-` with `` ≤ 9 chars. - One agent is the bootstrap/root container, with a fixed name (`ruth` today). -- `MAX_AGENT_NAME` in `lifecycle.rs` enforces the cap. +- `MAX_AGENT_NAME` in `hive-c0re/src/lifecycle/mod.rs` enforces the cap. - Per-agent web UI port = `WEB_PORT_BASE + FNV1a(name) % WEB_PORT_RANGE` (8100..8999) for every agent; dashboard `cfg.dashboardPort` (default 7000). @@ -83,7 +83,7 @@ name validation rejects any character outside `[a-z0-9_-]`, so the angle-bracket and asterisk shapes below are structurally safe. - `*` — broadcast: deliver to every running agent except the sender - (`agent_server::handle_send` fans out via `Coordinator::broadcast_send`). + (`socket_server::handle_send` fans out via `Coordinator::broadcast_send`). - `operator` — the human at the dashboard. Messages accumulate in the inbox view; no agent ever `recv`'s them. - `` — the sender's parent per `topology.json`. Rewritten at @@ -93,7 +93,7 @@ angle-bracket and asterisk shapes below are structurally safe. their parent without learning the label, so runtime reparenting propagates with zero agent-side restart. - `` — fan-out to every direct descendant of the sender per - `topology.json`. Resolved in `agent_server::handle_send` via + `topology.json`. Resolved in `socket_server::handle_send` via `topology::children_of(sender)`: one message is delivered to each child, bypassing the allow-list check (structural fan-out targets are never user-listed peers). No-op for leaf agents (returns `Ok` when the @@ -279,7 +279,7 @@ an agent. Self-introspection when `name = None` (replaces the older `Whoami` request); target query when `name = Some`. Response is `AgentMeta { name, running, hyperhive_rev, -status_text, status_set_at, hive_name, swarm_name }`: +status_text, status_set_at, hive_name, swarm_name, matrix_accounts }`: - `hyperhive_rev`: `None` only when the configured flake URL has no canonical path. Otherwise carries the rev the target is @@ -299,6 +299,9 @@ status_text, status_set_at, hive_name, swarm_name }`: `HYPERHIVE_HIVE_NAME` / `HYPERHIVE_SWARM_NAME` env (sourced from `services.hyperhive.hiveName` / `services.hyperhive.swarm.name`). Both `None` when the options aren't configured. +- `matrix_accounts`: one `MatrixIdentity` per configured + live matrix + account the agent can act as. Empty for agents with no matrix + provisioning. ### Timestamps on the wire @@ -323,14 +326,16 @@ binary flavor. | Group | Tools | |---|---| -| `messaging` | `send`, `recv`, `ask`, `answer` | +| `messaging` | `send`, `recv`, `ack_until`, `ask`, `answer` | | `meta` | `get_agent_meta` (`set_status` is always-on, see below) | | `inbox` | `get_loose_ends`, `cancel_loose_end`, `remind` | | `execution` | vestigial — `mcp__bash__run` / `mcp__bash__status` are always available unconditionally via `extraMcpServers`; this group's entries expand to non-existent `mcp__hyperhive__run` / `mcp__hyperhive__status` and have no effect. See `docs/tools/bash.md`. | -| `lifecycle` | `kill`, `start`, `restart`, `update` *(privileged)* | +| `lifecycle` | `kill`, `start`, `restart`, `update`, `list_containers` *(privileged)* | | `approvals` | `request_init_config`, `request_update_meta_inputs` *(privileged)* | | `scheduling` | `request_schedule_prompt`, `fire_schedule_now`, `cancel_schedule`, `edit_schedule`, `list_schedules` *(privileged)* | | `diagnostics` | `get_logs` *(privileged)* | +| `forge` | `create_repo` — create git repos through hive-c0re (operator-gated merge) | +| `web_tools` | none (gates the Claude built-ins `WebFetch`/`WebSearch`, not an MCP tool) | **Always-on tools** — `set_status`, `compact`, and `mark_todos_done` are exposed to every agent regardless of which groups it holds @@ -363,10 +368,10 @@ from `tool-groups.json`). Unrecognised tokens are logged and skipped. Falls back to `ToolGroup::AGENT_DEFAULT` (`messaging`, `meta`, `inbox`, `execution`) when the var is absent or empty. -**Updating the surface** — when a new `#[tool]` fn is added to `HiveServer` -in `hive-ag3nt/src/mcp.rs`, add its name to the matching `ToolGroup::tools()` -slice in `hive-sh4re/src/lib.rs`. That's the single source of truth; -`mcp_config::allowed_mcp_tools` (in `hive-ag3nt/src/mcp_config.rs`) reads it at +**Updating the surface** — when a new `#[tool]` fn is added to `AgentServer` +in `hive-agent-mcp/src/mcp/mod.rs`, add its name to the matching `ToolGroup::tools()` +slice in `hive-sh4re/src/permissions.rs`. That's the single source of truth; +`mcp_config::allowed_mcp_tools` (in `hive-agent/src/mcp_config.rs`) reads it at session start. ## Capabilities @@ -406,15 +411,18 @@ groups: an agent that could grant its own capabilities via a config commit would bypass the operator approval gate. **Adding a new capability** — add a variant to `Capability` in -`hive-sh4re/src/lib.rs` + an arm to `as_str`. Add it to `Capability::ALL` (the -source of truth for the permissions UI columns). Implement the access check in -the relevant handler (`agent_server.rs`, `mcp.rs`, or a handler under -`hive-c0re/src/dashboard/`). +`hive-sh4re/src/permissions.rs` + an arm to `as_str`. Add it to `Capability::ALL` +(the source of truth for the permissions UI columns). Implement the access +check in the relevant handler (`hive-c0re/src/socket_server/mod.rs`, +`hive-c0re/src/socket_server/lifecycle_handlers.rs`, `coordinator.rs`, or a +handler under `hive-c0re/src/dashboard/`). ## Async forms -Dashboard + per-agent mutating forms carry `data-async`; a delegated -`submit` listener in `assets/tabs.js` (+ `assets/app.js` for the per-agent UI) intercepts, shows a spinner, +Dashboard + per-agent mutating forms carry `data-async`; the shared +`bindAsyncForms` `submit` listener (`frontend/packages/shared/src/forms.js`, +imported as `@hive/shared/forms.js` and wired up from `tabs.js` on the +dashboard and `app.js` on the per-agent UI) intercepts, shows a spinner, POSTs `application/x-www-form-urlencoded` (axum's `Form` extractor rejects multipart), calls `refreshState()` on success. New mutating forms should add `data-async` and optionally `data-confirm` (for a @@ -429,11 +437,16 @@ via `snapshotOpenDetails` / `restoreOpenDetails`. ## `rebuild` is the reconcile verb -`lifecycle::rebuild` idempotently rewrites -`/etc/nixos-containers/.conf` (`PRIVATE_NETWORK=0`, clears -`HOST_ADDRESS` / `LOCAL_ADDRESS`, sets `EXTRA_NSPAWN_FLAGS`), -regenerates `applied//flake.nix`, writes the systemd limits -drop-in, then `nixos-container update` + stop + start. +`job_queue::templates::rebuild` builds the DAG that reconciles a +container to its wanted state: `write_dropins` (the nspawn-conf +rewrite — `PRIVATE_NETWORK=0`, clears `HOST_ADDRESS` / `LOCAL_ADDRESS`, +sets `EXTRA_NSPAWN_FLAGS` — plus the systemd resource-limits drop-in) +is folded into the `Swap` node, then `nixos-container update` + stop + +start runs across the `StopForUpdate → Swap → RebuildBookkeeping` +brace and the tail `Reconcile` node. `flake.nix` itself is no longer +regenerated host-side on rebuild — it's tracked in the agent's +proposed/applied repos and rides along on every fetch (see +`docs/approvals.md::Two repos per agent`). Anything that changes per-container state on the host should be re-applied here so a manual `↻ R3BU1LD` from the dashboard is