# Conventions Code-style and process expectations across the workspace. Most of these exist because something already went wrong without them. ## Naming - Containers are length-bounded by `nixos-container` (≤ 11 chars). - Sub-agents are `h-` with `` ≤ 9 chars. - The manager is `hm1nd` (no `h-` prefix, fixed name). - `MAX_AGENT_NAME` in `lifecycle.rs` enforces the cap. - Per-agent web UI port = `WEB_PORT_BASE + FNV1a(name) % WEB_PORT_RANGE` (8100..8999) for every agent including the manager; dashboard `cfg.dashboardPort` (default 7000). ## Identity = socket There are no auth tokens on the per-agent unix sockets. The socket *path* identifies the principal; perms come from "who has the bind-mount." A sub-agent only sees its own `/run/hive/mcp.sock`; the manager has access to its privileged socket; hive-c0re owns the host admin socket. ## Recipient sentinels A few recipient names are reserved by the broker and have special meaning that ordinary agent labels can never collide with — agent 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`). - `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 send time by `topology::resolve_recipient`: looks up `parent_of(sender)` and falls back to `operator` when the sender is a root agent (or absent from topology entirely). Lets agents address their parent without learning the label, so runtime reparenting propagates with zero agent-side restart. When the resolver rewrites ``, the broker stores the *resolved* label as the message's recipient — the dashboard and recv side both see the real route. The sentinel is purely a send-time addressing convenience. ## Wire protocol JSON line-delimited over unix sockets in both directions (host admin / manager / agent). SSE streams (`/dashboard/stream` on hive-c0re, `/events/stream` on the per-agent web UIs) are `text/event-stream`; each frame carries a `seq` field for the snapshot-dedupe dance (see `docs/web-ui.md`). Request/response types live in `hive-sh4re` — change them in one place. The dashboard event vocabulary lives in `hive-c0re::dashboard_events::DashboardEvent`. ### Broker delivery + ack cycle `AgentRequest::Recv` is the only path that delivers messages to an agent. Always returns a list (`Messages { messages }`) — empty when nothing's pending, single-pop when `max = None` (default 1, the single-message behaviour), batched up to `max` when caller asks for more (server-side cap is 32; values above clamp silently). `wait_seconds` long-polls for the first message; once one arrives — or one is already pending — the call drains up to `max` in total before returning, so a single `Recv` call coalesces a burst. Per-row bookkeeping inside the broker: - `delivered_at = NOW` set on every popped row. - Each recipient has an in-memory `unacked_ids` list of every row delivered since the last `AckTurn`. - `redelivered = true` on a row if `RequeueInflight` resurfaced it (the harness prepends a "may already be handled" hint when this flag is set so the per-message warning is visible). `AgentRequest::AckTurn` closes out the in-memory list — the harness fires it after `TurnOutcome::Ok`, marking every message popped since the last ack as fully handled. Claude doesn't see this surface; it's strictly a harness↔broker pairing. On `TurnOutcome::Failed` the harness intentionally skips the ack so the unacked rows stay in-flight in the DB and get picked up by the next requeue sweep. `AgentRequest::RequeueInflight` is the recovery pair: fired by the harness exactly once at boot, before the serve loop starts. Catches the crashed-mid-turn / OOM-killed / container-restarted cases where a previous harness session popped messages but never drove them to a clean turn-end. Resets `delivered_at` back to NULL on every unacked row (so the next `Recv` pops them again), and remembers each id in a per-recipient in-memory set so the next `Recv` can tag the row with `redelivered: true`. Idempotent + cheap when there's nothing in flight, so the at-boot fire is unconditional. ## Async forms Dashboard + per-agent mutating forms carry `data-async`; a delegated `submit` listener in `assets/tabs.js` 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 JS-side `confirm()` prompt) or `data-prompt="…"` (for a `window.prompt()` whose answer goes into a hidden input named by `data-prompt-field`, default `note`). `refreshState` defers automatically when `document.activeElement` sits inside a managed section so the operator's typing isn't lost; collapsible `
` survive the re-render 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. Anything that changes per-container state on the host should be re-applied here so a manual `↻ R3BU1LD` from the dashboard is sufficient to recover. ## Actions are factored `approve` / `deny` / `destroy` (and the lifecycle helper) live in `actions.rs` / `dashboard.rs`. The admin socket and the dashboard POST handlers both call into them so the two surfaces never drift. ## Commit messages Short, lowercase, no `Co-Authored-By` trailer. Imperative mood, no period. Body explains *why* if non-obvious; otherwise the subject alone is fine. Wrap at ~72 cols. ## Commit before test Stage and commit when work *looks* ready, then run validation (`cargo check`, `nix flake check`, real deploy). Failures get a follow-up commit rather than an amend. The commit history is the work log; rewriting it loses signal.