diff --git a/CLAUDE.md b/CLAUDE.md index 6ada7a4f..4cda3e3e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,9 +25,9 @@ hive-c0re/ host daemon + sibling operator CLI (lib + 2 bins) deny / destroy [--purge] / request-spawn / set-parent (--parent / --root); periodic vacuum tasks. - src/bin/hivectl.rs `hivectl` binary (#655): operator-facing host - CLI for ad-hoc administration that doesn't - go through the broker. v0 verbs: `forge + src/bin/hivectl.rs `hivectl` binary: operator-facing host CLI + for ad-hoc administration that doesn't go + through the broker. v0 verbs: `forge create-user `, `matrix create-user ` — wrap the same idempotent `forge::ensure_user_for` / @@ -39,15 +39,15 @@ hive-c0re/ host daemon + sibling operator CLI (lib + 2 bins) src/manager_server.rs manager-privileged socket (ManagerRequest) src/agent_ports.rs writes `/var/lib/hyperhive/agent-ports.json` on meta sync; gateway reads name→port map for - per-agent `/agent//` routing (#15). + per-agent `/agent//` routing. src/agent_sockets.rs writes `/var/lib/hyperhive/agent-sockets.json` on meta sync; gateway reads name→socket-path - map for unix-domain per-agent UI binding (#784). + map for unix-domain per-agent UI binding. Entries are filtered by a `.bound` marker file the harness drops next to its socket after a - successful `bind()` (#832) — pre-bind agents - stay out of the map so the gateway never races - a missing-socket upstream. A 10s poll loop + successful `bind()` — pre-bind agents stay out + of the map so the gateway never races a + missing-socket upstream. A 10s poll loop (`spawn_poll`) re-fires the writer so the map reconverges if a marker appears between meta-sync ticks. @@ -76,7 +76,7 @@ hive-c0re/ host daemon + sibling operator CLI (lib + 2 bins) src/scheduled_prompts.rs sqlite layer for scheduled prompts — schema, CRUD helpers (`cancel_all`, `cancel_targets`, `reap_cancelled`), catch-up - clamp on resume (#444) + clamp on resume src/scheduled_prompts_worker.rs 5s poll loop: fires due rows, fans out one Message per active target, re-arms recurring rows, deletes fired one-shots @@ -86,7 +86,7 @@ hive-c0re/ host daemon + sibling operator CLI (lib + 2 bins) when a previously-running container disappears without an operator-initiated transient (or a RECENT_TRANSIENT_GRACE tombstone within the - last 30s, closes #425) + last 30s) src/container_view.rs ContainerView struct + build_all helper; shared between dashboard.rs (cold-load via /api/state) and coordinator.rs's @@ -128,7 +128,7 @@ hive-c0re/ host daemon + sibling operator CLI (lib + 2 bins) and the eventual cap-enforcement plumbing all read). Reconciled by `meta::sync_agents`; operator/manager edits land via the - eventual write API (#361 follow-ups). + eventual write API. src/forge.rs optional Forgejo wiring: per-agent users + tokens, the `agent-configs` org (`push_config`), and meta read access; mirrors each applied repo @@ -142,7 +142,7 @@ hive-c0re/ host daemon + sibling operator CLI (lib + 2 bins) persists each agent's access_token to `/matrix-token`. Idempotent — skips registration when token file already exists. - No-op when `hive-matrix` container absent (#548). + No-op when `hive-matrix` container absent. src/dashboard.rs axum HTTP: /api/state JSON + actions + journald viewer + bind-with-retry (SO_REUSEADDR) + deployed_sha chip per container + @@ -169,7 +169,7 @@ frontend/ npm workspaces (esbuild → static dist). Built + agent.css → dist/{*.html, static/*}. hive-ag3nt/ in-container harness crate; produces ONE `hive` - binary (post-#598) picking role from `HIVE_ROLE` env + binary picking role from `HIVE_ROLE` env src/lib.rs re-exports + DEFAULT_SOCKET, DEFAULT_WEB_PORT src/client.rs generic JSON-line request/response over unix socket src/web_ui.rs per-container axum HTTP page (incl /api/cancel, @@ -194,15 +194,15 @@ hive-ag3nt/ in-container harness crate; produces ONE `hive` escaped) and surfaces any @mention lines that fell outside the truncation window in a trailing `mentions (truncated from body):` - block (#539 / #544). - src/identity.rs hive-qualified agent label (#589 phase A): - `label()` / `qualified_label()` / `qualify(label)`. - Reads `HYPERHIVE_HIVE_DOMAIN`; falls back to short - name when unset. Display-name accessors (#701): + block. + src/identity.rs hive-qualified agent label: `label()` / + `qualified_label()` / `qualify(label)`. Reads + `HYPERHIVE_HIVE_DOMAIN`; falls back to short + name when unset. Display-name accessors: `hive_name()` / `swarm_name()` read `HYPERHIVE_HIVE_NAME` / `HYPERHIVE_SWARM_NAME`, both `Option`. - src/login.rs probe $HOME/.claude/ (post-#658 `/home//.claude`) + src/login.rs probe $HOME/.claude/ (= `/home//.claude`) for a valid session src/login_session.rs drives `claude auth login` over stdio pipes src/prompt.rs system prompt renderer: filters the unified @@ -234,9 +234,9 @@ hive-forge/ Forgejo CLI wrapper (`hive-forge` binary) close, labels, list, milestone, branches, tree-sha, diff, subscription, attach-issue, attach-comment, lint). Replaces the 600-line - hive-forge-tools.nix bash script (closes #280). + hive-forge-tools.nix bash script. -hive-matrix-mcp/ per-agent matrix-sdk integration (#548 phase 3). +hive-matrix-mcp/ per-agent matrix-sdk integration. src/main.rs `hive-matrix-daemon` binary entry — long-running matrix-sdk Client + sync per agent; serves the MCP bridge over /run/hive-matrix/socket; emits @@ -277,9 +277,9 @@ nix/ modules/hive-matrix.nix optional in-container matrix-tuwunel homeserver (`services.hyperhive.matrix.enable`, default off); server_name defaults to bare - `services.hyperhive.domain` (#660); gateway serves + `services.hyperhive.domain`; gateway serves `.well-known/matrix/{client,server}` for - auto-discovery; federation on, e2ee deferred (#551) + auto-discovery; federation on, e2ee deferred templates/harness-base.nix shared harness for all containers; `hyperhive.role` (`"agent"` | `"manager"`) drives service unit + forge defaults; `hyperhive.model` option (HIVE_DEFAULT_MODEL) @@ -304,10 +304,10 @@ docs/ persistence.md sqlite dbs, retention, state dir layout terminal-rendering.md per-agent terminal row taxonomy (as built) boundary.md operator/agent trust model rationale - agent-hierarchy.md tree-shape topology design + manager-privilege audit (#361) + agent-hierarchy.md tree-shape topology design + manager-privilege audit damocles-migration.md future migration plan for damocles → hyperhive gateway.md nginx vhost map, matrix discovery flow, firewall posture, - HIVE_FORGE_URL loopback rationale (#764, #772, #793) + HIVE_FORGE_URL loopback rationale forge.md per-agent forge accounts + agent-configs mirror; forge_notify poller (gates, self-filter, body excerpt pipeline, wrapper formats, reason drop-list, @@ -315,7 +315,7 @@ docs/ matrix.md matrix container shape, serverName/gatewayHost split, firewall + federation, provisioning flow, fluffychat-web build network.md host-side bridge + dnsmasq resolver, v1/v2 roadmap, - foundation for #14 netns isolation (#805) + foundation for netns isolation ``` ## Reading paths diff --git a/README.md b/README.md index 4d7a35cc..62c5e29e 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ the wider swarm human-readable display labels. Distinct from the dashboard chrome and the per-agent system prompt; the domain is how things are addressed on the wire. Federated hives at different domains can share a swarm name. Both default to null — chrome falls -back to the domain, the prompt simply doesn't mention them (#701). +back to the domain, the prompt simply doesn't mention them. Optional: set `services.hyperhive.matrix.enable = true;` to spin up a private [matrix-tuwunel](https://github.com/matrix-construct/tuwunel) @@ -120,16 +120,17 @@ homeserver in a nixos-container. Requires either `services.hyperhive.domain` or `services.hyperhive.matrix.serverName` to be set (eval fails with a clear error if both are absent). The `server_name` (embedded irrevocably in every user and room ID) -defaults to `` (bare hive domain, since #660); the hive-gateway serves +defaults to `` (bare hive domain); the hive-gateway serves `.well-known/matrix/{client,server}` endpoints so clients auto-discover the homeserver without a subdomain. **Existing homeservers** must set `services.hyperhive.matrix.serverName = "matrix.";` explicitly to -preserve pre-#660 user/room IDs. Override with +preserve their existing user/room IDs (the `server_name` is embedded +into every ID). Override with `services.hyperhive.matrix.serverName = "chat.example.com";` for a custom server name. State lives at `/var/lib/nixos-containers/hive-matrix/`. Federation is enabled with an empty `trusted_servers` list; e2ee is deferred to -a follow-up (#551). +a follow-up. The hive-gateway nginx container serves a [fluffychat-web](https://fluffychat.im) matrix client at `/matrix/` diff --git a/docs/agent-hierarchy.md b/docs/agent-hierarchy.md index e7f16b9a..714371ee 100644 --- a/docs/agent-hierarchy.md +++ b/docs/agent-hierarchy.md @@ -251,9 +251,9 @@ boundary. ### Environment variables set on the unit - `HOME = /home/` — systemd defaults `HOME` to `/` for - services without `User=` set; with the per-agent user (#658) the - harness needs the right home so claude finds its bind-mounted - `~/.claude/` session dir. + services without `User=` set; with the per-agent user the harness + needs the right home so claude finds its bind-mounted `~/.claude/` + session dir. - `HIVE_STATIC_DIR = ` — `tower_http::ServeDir` root for the per-agent web UI; merged dist = agent default + every `hyperhive.frontend.extraFiles` overlay. @@ -275,9 +275,9 @@ resolve before bare nix-store binaries. NixOS's `systemd.services..path` appends `/bin` to every entry via `lib.makeBinPath`; passing `/run/wrappers/bin` directly produces `/run/wrappers/bin/bin` which doesn't exist (`docs/gotchas.md:: -systemd.services.*.path appends /bin to every entry`). Post-#658 -when the harness runs as the per-agent user this matters: without -the wrapper dir on PATH, `sudo` resolves to the un-setuid nix-store +systemd.services.*.path appends /bin to every entry`). With the +harness running as the per-agent user this matters: without the +wrapper dir on PATH, `sudo` resolves to the un-setuid nix-store binary and rejects with `must be owned by uid 0 and have the setuid bit set` regardless of `hyperhive.user.passwordlessSudo`. @@ -292,7 +292,7 @@ bit set` regardless of `hyperhive.user.passwordlessSudo`. `claude-{mcp-config,settings,system-prompt}` files there (`paths::config_dir`). Deliberately separate from `/run/hive`, which the host bind-mounts in root-owned and which holds - hive-c0re's `mcp.sock` (#658 fixup). + hive-c0re's `mcp.sock`. - `User = Group = userName` — drops root inside the container; sudo is the explicit escalation surface (`hyperhive.user.passwordlessSudo`). diff --git a/docs/conventions.md b/docs/conventions.md index 4a0d8a6b..20c1ca4a 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -350,8 +350,8 @@ Shape contract — every one of these: `systemctl restart` gymnastics). 5. **Re-runnable**: a second invocation produces the same final state (idempotent uploads, idempotent config rewrites). Used - by the `.path` watchers that re-fire on token appearance - (#571 — see `docs/persistence.md::matrix-avatar-sync`). + by the `.path` watchers that re-fire on token appearance (see + `docs/persistence.md::matrix-avatar-sync`). The artefact lives under the agent user's home where applicable (`~/.config/tea/config.yml`) and is chown'd to that user, but the diff --git a/docs/gateway.md b/docs/gateway.md index 5f1d5e25..5c61399d 100644 --- a/docs/gateway.md +++ b/docs/gateway.md @@ -7,15 +7,15 @@ Single nginx in front of every hyperhive web surface. Container `hive-gateway`, | URL | vhost | upstream | source | | --- | --- | --- | --- | | `/` | `_` (catch-all) | hive-c0re dashboard (`7000`) | always | -| `/agent//` | `_` | per-agent harness on `agent_web_port(name)` | `agentPortsFile` JSON, #15 | -| `/.well-known/matrix/{client,server}` | `_` | inline JSON (no upstream) | `matrix.enable && domain != null`, #660 / #747 | -| `/matrix/` (deprecated) | `_` | 301 → `matrix./` | `matrix.gui.enable`, #772 | -| `forge./` | `forge.` | forgejo (`3000`) | `forge.behindGateway`, #754 | -| `matrix./_matrix/*` | `matrix.` | tuwunel (`8008`) | `matrix.gatewayHost != null`, #764 | -| `matrix./` | `matrix.` | fluffychat-web static | `matrix.gui.enable`, #772 | -| `matrix./config.json` | `matrix.` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null`, #736 | +| `/agent//` | `_` | per-agent harness on `agent_web_port(name)` | `agentPortsFile` JSON | +| `/.well-known/matrix/{client,server}` | `_` | inline JSON (no upstream) | `matrix.enable && domain != null` | +| `/matrix/` (deprecated) | `_` | 301 → `matrix./` | `matrix.gui.enable` | +| `forge./` | `forge.` | forgejo (`3000`) | `forge.behindGateway` | +| `matrix./_matrix/*` | `matrix.` | tuwunel (`8008`) | `matrix.gatewayHost != null` | +| `matrix./` | `matrix.` | fluffychat-web static | `matrix.gui.enable` | +| `matrix./config.json` | `matrix.` | inline JSON (FluffyChat boot config) | `matrix.gui.enable && domain != null` | -Per-agent UIs stay sub-path because they're hyperhive-internal and base-path-aware (iris #731). External standard apps (forge / matrix) get sub-domains because their defaults work cleanly at sub-domain root + per-origin cookies / storage isolation matters. +Per-agent UIs stay sub-path because they're hyperhive-internal and base-path-aware. External standard apps (forge / matrix) get sub-domains because their defaults work cleanly at sub-domain root + per-origin cookies / storage isolation matters. ## Discovery flow (matrix) @@ -34,9 +34,9 @@ Federation peers fetch `.well-known/matrix/server` → `{"m.server":"matrix.` catch-all and the `matrix.` vhost both serve a flutter SPA (per-agent UI, fluffychat). Two requirements collide: - hard-refresh on a sub-route must serve `index.html` (SPA's client-side router takes over after JS bootstrap) -- missing assets must surface as 404, not as HTML with wrong content-type (the original #643 bug) +- missing assets must surface as 404, not as HTML with wrong content-type -Solution: an `nginx http`-context `map $http_accept $matrix_spa_target { ... }` keyed on the request's Accept header. Browser navigations (`Accept: text/html,...`) get `index.html`; asset fetches (`Accept: image/*`, `*/*`, etc.) get a sentinel nonexistent path → `try_files` falls through to `=404`. No extension allowlist, no `if` block, no regex heuristics. #686 + #729 thread for the design history. +Solution: an `nginx http`-context `map $http_accept $matrix_spa_target { ... }` keyed on the request's Accept header. Browser navigations (`Accept: text/html,...`) get `index.html`; asset fetches (`Accept: image/*`, `*/*`, etc.) get a sentinel nonexistent path → `try_files` falls through to `=404`. No extension allowlist, no `if` block, no regex heuristics. ## Local dev (`localHostsEntry`) @@ -50,14 +50,14 @@ Solution: an `nginx http`-context `map $http_accept $matrix_spa_target { ... }` ## Sub-domain shape (rationale) -mara verdict at #749:9609 + #747:9722: sub-domain over sub-path for forge + matrix, sub-path for per-agent UIs. +Operator decision: sub-domain over sub-path for forge + matrix, sub-path for per-agent UIs. - forgejo's default `ROOT_URL = http:///` works without any `X-Forwarded-Prefix` gymnastics — sub-domain hosting is the canonical Forgejo deploy shape. - matrix-spec deployments universally use `matrix.` for the actual API listener — federation already expects this. -- per-agent UIs are hyperhive-internal; iris's #731 made them base-path-aware specifically for `/agent//`. Sub-domain per agent would multiply DNS + TLS-per-subdomain cost without per-app config wins. +- per-agent UIs are hyperhive-internal and base-path-aware specifically for `/agent//`. Sub-domain per agent would multiply DNS + TLS-per-subdomain cost without per-app config wins. - cookie / storage isolation: a future forge XSS can't reach the dashboard session because they're different origins. -`services.hyperhive.{forge.domain,matrix.gatewayHost}` take the full hostname (`forge.darkest.space`, `git.example.com`) rather than a label that gets concatenated with hive-domain — mara on #754:9684 wanted operator control over the full shape, not a forced `