mara/damocles wrote a fuller version of the same section at line 70
(`Per-agent unix-socket upstream (#784)`) as part of #832 — covers
the 4-step rollout flow including the .bound marker filter that
landed in #832. The atlas-side section at line 207 was the pre-#832
version with the eval-time path-exists gate notes; superseded.
Net effect: one canonical section on the unix-socket upstream story
rather than two with overlapping content.
Two new sections housing rationale that was inline in
`harness-base.nix`:
- `docs/conventions.md::Best-effort oneshot services` — shape
contract shared by `tea-login`, `forge-avatar-sync`, and
`matrix-avatar-sync`: always exit 0, no `set -e`, skip silently
on missing prerequisites, wired to multi-user.target,
re-runnable. Plus the artefact-under-agent-home + service-stays-
root posture and the operator-visible-via-journalctl trade-off.
- `docs/persistence.md::matrix-avatar-sync` — the two-step `media
upload` → `set avatar_url` matrix-spec dance + why
`RemainAfterExit = false` (so the `.path` watcher's re-fire on
token appearance actually re-executes the unit).
In-code comments trim to one-liner purpose + pointers; the script
bodies stay (they're the actual implementation) but their inline
`#` rationale comments collapse where the docs cover them.
`description = ''…''` blocks (operator-facing options docs)
preserved per iris #718.
`nix flake check` clean; `nix fmt` clean.
Switch per-agent gateway upstreams from TCP loopback to unix-domain
socket when the agent has opted in via `hyperhive.web.useUnixSocket`
(#822). Coexists with the TCP path during rollout.
Changes:
- New `agentSocketsFile` option (default
`/var/lib/hyperhive/agent-sockets.json`) — c0re writes the map
there via `hive_c0re::agent_sockets::write` (#809).
- `agentSocketsTable = lib.importJSON ...` (graceful empty when
file missing).
- `agentUpstreamFor name port` picks `http://unix:<path>:/` when the
socket has a JSON entry AND the file exists at eval time; else
`http://127.0.0.1:<port>/`. Path-exists gate guards against
c0re's blanket-emit shape during the canary window (agents in
`agent-sockets.json` who haven't actually flipped have no
bound socket on disk → fall back to TCP). Damocles will ship a
`.bound` marker filter on the c0re side (#784 step 2d
follow-up); once that's in, the path-exists check is redundant
but harmless. Step 4 drops it entirely along with the TCP
fallback.
- `containers.hive-gateway.bindMounts."/run/hive-agent"` —
read-only, unconditional. Inert when no agents have opted in.
Required so nginx inside the gateway container can `connect(2)`
to the per-agent sockets damocles's #813 bind-mounts into agent
containers at the same paths.
Docs:
- `docs/gateway.md::Per-agent UDS upstream (#784)` — full rollout
flow, subdir-bind rationale (damocles #813), eval-time gate
explainer, step 4 drop plan.
`nix flake check` clean; `nix fmt` clean.
Canary plan: once #822 (`useUnixSocket` option) lands + this PR
merges, manager flips atlas's agent.nix to `useUnixSocket = true`
via the config-update flow. End-to-end validation against atlas
before broader rollout.
Move the harness systemd unit rationale (role-driven unit name,
manager-only forge defaults, PATH wrapper-dir trick, env vars,
standalone-eval fallbacks, RuntimeDirectory + User= reasoning)
from `nix/templates/harness-base.nix` to a new
`docs/agent-hierarchy.md::Harness systemd unit shape (per-role)`
section.
In-code comments trim to short purpose statements + pointers; the
PATH /bin auto-append behaviour is already documented in
docs/gotchas.md, so the harness file just cross-refs both.
`description = ''…''` blocks (operator-facing options docs)
preserved per iris #718.
`nix flake check` clean; `nix fmt` clean (after formatter pass).
mara on #755: "e.g. /agent/name should show an error page stating
that the agent could not be found if missing in json or that it is
not reachable if we get a connection error. we dont want a fully
generic fallback, only for routes already special cased in the
nginx config."
Adds two static HTML pages built at deploy time via
`pkgs.runCommand "hyperhive-agent-error-pages"`:
- **not-found.html** — served when `/agent/<unknown>/...` hits the
bare `/agent/` catch-all. The catch-all `return 404`s, and
`error_page 404 = /__hive_agent_not_found` rewrites to the static
page.
- **unreachable.html** — served when `/agent/<known>/...` proxy_pass
to the harness returns 502 / 503 / 504. `proxy_intercept_errors
on` + `error_page 502 503 504 = /__hive_agent_unreachable` on each
per-agent location block rewrites to the static page.
Mechanics:
- `agentErrorPagesDir` (in the `let` block) is a `runCommand` that
emits two HTML files using a `<<EOF` heredoc — no template engine
needed.
- Two `internal` nginx locations (`= /__hive_agent_not_found`,
`= /__hive_agent_unreachable`) `alias` the exact files. `internal`
keeps the URIs unreachable from direct operator request — only
nginx's own error-handling can hit them.
- Per-agent location blocks pick up the `error_page` directive
through the existing `lib.mapAttrs'` over `agentPortsTable`. No
per-agent generated content; same static page for all.
- `/agent/` catch-all generates from a tiny optionalAttrs alongside
the per-agent block — both are no-op when the agent table is
empty (matches the pre-#15 shape).
Pages: minimal inline CSS, catppuccin palette matching the
dashboard (`#1e1e2e` bg, `#cdd6f4` text, `#cba6f7` not-found heading,
`#f9e2af` unreachable heading). No frontend-dist dependency — render
even when hive-c0re is down. Both link back to `/`.
Per mara's "only for routes already special cased" — scope stays
narrow. Forge / matrix / fluffychat keep nginx defaults; extending
the custom-error pattern to other vhosts is a separate follow-up
if/when needed.
Verified:
- nginx location attrset has `["/", "/agent/", "= /__hive_agent_not_found", "= /__hive_agent_unreachable"]`
- container toplevel builds clean (`nixos-system-hive-gateway-26.05pre-git`)
- `docs/gateway.md::Per-agent error pages` section captures the
design + rationale + intentional narrowness
Closes#755.
Continues #718 docs-extraction. Three more blocks moved to
`docs/gateway.md` (which already houses the gateway architecture
story from #775):
1. **Firewall posture (gateway on vs off)** — was a 22-line block
above `networking.firewall = lib.mkIf ...` in hive-c0re.nix.
Trimmed to 3-line ref. New `docs/gateway.md::Firewall posture
(host-level)` section covers the gateway-on / gateway-off
trade-off + why dashboard port stays loopback-only.
2. **`HIVE_FORGE_URL` loopback rationale** — was a 14-line block
above the env-var assignment. Trimmed to 5-line ref. New
`docs/gateway.md::HIVE_FORGE_URL: loopback for in-cluster,
sub-domain for the operator` section covers the in-cluster vs
external split + why agent containers can't use the sub-domain.
3. **hive-forge container shape** — was a 15-line top-of-`config`
block in hive-forge.nix explaining the nixos-container + host
netns choices. Trimmed to 4-line ref. New
`docs/gateway.md::hive-forge container shape` section captures
the same content with state-dir + wipe-via-destroy notes.
Net: hive-c0re.nix -29 lines, hive-forge.nix -11 lines, gateway.md
+44 lines. Same pattern as #782 (first pass) per iris's #10114
guidance — substantive WHY moves to docs as named sub-paragraphs,
in-code shrinks to `// see docs/<file>::<section>` refs.
Verified: `nix eval` on agent-base toplevel still resolves
cleanly; firewall posture unchanged (still 0 ports opened in the
gateway-on case + the same 8100..8999 range in the gateway-off
case).
Continues #718. Follow-up batches: remaining harness-base.nix
blocks, nix/docs/default.nix, nix/assets.nix, nix/templates/weston-vnc.nix.
Expanded the /screen endpoint description in docs/web-ui.md to
cover the substantive design rationale that lived in screen.html
comments. screen.html shrinks correspondingly.
Moved to docs/web-ui.md::Per-agent endpoints (GET /screen):
- Deliberate thinness — minimal RFB renderer; noVNC vendoring
path called out for production-grade replacement.
- Fit-mode flex-item min-width:auto clamp: a flex item's automatic
minimum size resolves to the canvas's intrinsic framebuffer
resolution and clamps CSS max-* back up, making fit mode a
silent no-op. The fix (flex: none + min-width: 0 + min-height: 0
+ explicit px sizing via relayoutCanvas()) is now documented.
- localStorage persistence for fit-mode (key screen-fit; default
on).
- Pointer rescale in sendPointer keeps clicks accurate.
- ExtendedDesktopSize pseudo-encoding (-308 rect) gates the
match-size button.
Collapsed in screen.html: 8 #NNN cookies scrubbed across all
inline comments. #133 (canvas-sizing fit bug — closed) ×5,
#52 (noVNC vendoring — closed) ×1, #14 (relative URL — closed
my piece) ×1, plus one CSS-block #133. Each comment shrinks to
a brief pointer.
screen.html: 8 → 0 #NNN cookies (100% reduction). Net ~26 lines
of substantive prose moved into docs/web-ui.md.
iris's #718 scope: move substantive design context from `#` comment
blocks in `nix/` to corresponding `docs/` files, leave short
references in code. iris handed it back to me on #10114 since
nix/ is my lane + #775 established the pattern.
First pass — three highest-density blocks in harness-base.nix:
1. **First-boot agent-user migration** (~70 lines → `~20 lines code +
short ref` in the activation script). Substantive prose moves to
new `docs/persistence.md::First-boot agent-user migration (post-#658)`
section explaining the 4 steps the script performs + the eventual
removability of the marker-guarded body.
2. **nix-daemon `sandbox-fallback = true`** (10-line block → 5-line
ref). New `docs/gotchas.md::Containerized nix-daemon needs
sandbox-fallback = true` section covers the user-namespaces
rationale + nixpkgs-default override.
3. **Matrix daemon + token-arrival trigger** (~50 lines across two
systemd units → ~10 lines code + short refs). New
`docs/persistence.md::Matrix per-agent daemon + token-arrival
trigger` covers the socket-path rationale, the runtime-dir
ownership story, and the first-boot ordering pattern.
Net: harness-base.nix -84 lines, docs +74 lines. Substantive design
context moves to durable docs; in-code refs follow iris's pattern
from her #712 batches (`see docs/<file>::<section>`).
Follow-ups: hive-c0re.nix, hive-forge.nix, hive-matrix.nix (already
trimmed via #775 but a couple of remaining blocks could go), and
the smaller files in #718's scope table. Shipping this first to get
the pattern reviewed before larger batches.
Verified: `nix eval` on agent-base toplevel still resolves.
Moves the #666 ask→operator inline-answer wiring rationale from
app.js into a new docs/web-ui.md::Per-agent page sub-paragraph
**Ask → operator inline-answer binding**. Substantial block —
the slot-registry / reconcileAskBinds / buildAnswerForm trio has
real design rationale (async question id, text-match pairing,
resolved-vs-cancelled-vs-expired neutrality, defensive prune,
no-regression-fallback to side-panel answer) that belongs in
docs, not three JSDoc blocks scattered through app.js.
Moved to docs (~30 lines of substantive prose):
- pendingAskBinds slot-registry rationale
- reconcileAskBinds matching algorithm (text-match, first-unbound
to first-unclaimed pairing stability)
- defensive prune for disconnected slots
- [resolved] vs ✓ rationale (answered / cancelled / TTL-expired
ambiguity — neutral label)
- fallback to side-panel answer when slot stays unbound
Collapsed in app.js: 3 JSDoc blocks + inline comments → ~10-line
total pointer. Drops #666 ×3, #559, #668 cookies en passant
(substance now lives in docs).
app.js: 24 → 19 refs in this commit; 27 → 19 across batch 1
(30% reduction so far). Net ~57 lines of substantive prose
migrated from app.js to docs/web-ui.md across both commits in
this PR.
First batch of #713 (agent per-container UI prose migration).
Moves 3 substantive WHY-explanations from app.js into
docs/web-ui.md::Per-agent page, replaces each in-code mention
with a brief pointer.
Moved to docs:
- **Terminal-wrap pill anchor** (~11 lines): why the `↓ N new`
pill is anchored in `.agent-main` rather than the default
`.terminal-wrap` parent — backdrop-filter creates a stacking
context, anchoring inside it traps the pill's z-index below the
composer.
- **OAuth code input** (~14 lines across two blocks): masked
password + reveal toggle + `autocomplete="one-time-code"`
rationale (shoulder-surfer / screenshot exposure + WHATWG
semantic value + suppresses browser save-password prompt).
- **NavLink kind resolution + XSS-safe DOM-build** (~8 lines):
Container → same-origin, Forge → `http://<host>:3000<url>`,
External → already absolute; agent-declared strings never reach
innerHTML.
Collapsed in app.js: each block becomes a 3-4 line pointer to the
matching docs section. Drops #262 / #568 / #592 cookies en passant
since their substance now lives in docs. The `#14` cookie on
`historyUrl` / `streamUrl` is also scrubbed — the path-relative
shape is the convention, not an open issue. `#375` (agent.css ID
ref) drops as part of the pill-anchor block collapse since it was
sole-purpose pointing inside that comment.
app.js: 27 → 24 refs. Net ~35 lines of substantive prose moved out
of app.js into docs/web-ui.md::Per-agent page, where it belongs.
mara on PR #775: "this is too much docs in code - move bigger picture
stuff to md files and put refs in code"
New `docs/gateway.md` consolidates the gateway architecture story
that was spreading across long inline comments in `hive-gateway.nix`,
`hive-matrix.nix`, and `hive-forge.nix`:
- vhost map (which URL serves what, which upstream, which option)
- matrix discovery flow (.well-known → sub-domain delegation
sequence)
- Accept-header SPA fallback pattern (#686 / #729 design history)
- local-dev `localHostsEntry` story
- sub-domain rationale (mara verdict tracking) + when sub-path is
right (hyperhive-internal apps)
- per-vhost tuning knobs (forge LFS, matrix long-poll, agent SSE)
- sequencing history (which PR added which routing piece)
In-code comments in the two nix modules get trimmed to short refs
into the doc — keeps the *why* in the markdown while the *what*
stays alongside the code:
- hive-gateway.nix: top-of-file comment, `agentPortsTable`,
`appendHttpConfig`, every location block + vhost
- hive-matrix.nix: `fluffychat-web-fixed`, `fluffychat-web-imaging`,
the dart compile postInstall
README.md gets a new row in the docs table pointing at gateway.md.
Verified `nix eval` still resolves the same vhost + location layout
after the comment trim — no behavioral change, just less in-code
prose.
Mara on #774: previous batches were cookie-stripping rather than
prose migration. This batch actually moves substantive comment
prose from tabs.js into docs/web-ui.md.
Moved to docs/web-ui.md:
- Container row → **Icon layout + load strategy** sub-paragraph
(new): absolute-positioning rationale (so img load state can't
reflow row), fire-and-forget load pattern (no pre-check
reachability), favicon fallback chain, icon-unreachable class,
the immediate-fallback-when-stopped optimisation.
- Container row → **Pending-state derivation** paragraph (new):
three-source priority order (transient → in-flight queue → none),
why ContainerStateChanged isn't enough, the opRunning flag's role
in driving the pending-running class + spinner.
Collapsed in tabs.js:
- Icon block (~14 lines of WHY comments + pointer to docs) →
4-line pointer + behavioural one-liner. Drops #177 / #195 / #202
cookies en passant since their substance now lives in docs.
- Pending-state block (~22 lines split across two paragraphs) →
4-line pointer. Drops #769 self-cookie (the queued vs running
split lives in docs::Container row now).
- SharedWorker EventSource (~7 lines) → 5-line pointer. Drops
#448 cookie (the SSE multiplexing paragraph in docs already
has the design + Firefox throttling rationale; the in-code
comment was duplicating).
- M4TR1X tab gating (~4 lines) → 2-line pointer. Drops #607 cookie
in both tabs.js + docs/web-ui.md::Tab strip (the substance was
already in docs, just had the cookie attached).
tabs.js: 11 → 6 refs (92% reduction from baseline 73). Net effect:
~47 lines of substantive prose moved out of tabs.js into
docs/web-ui.md, where it belongs.
#437 (multi-step progress on rebuild_queue entries — closed) ×2:
in tabs.js current-step comment + in docs/web-ui.md prose
referencing the phase annotation.
#501 (PR — merged for #437) ×1: paired with #437 in the same
tabs.js current-step comment.
#575 (X button to cancel pending builds — closed) ×2: in tabs.js
cancel-form comment + in docs/web-ui.md cancel-button prose.
#436 (route approval execution through rebuild_queue — closed)
×1 in docs/web-ui.md: scrubbed the parenthetical "; #436" next
to the approval source-chip description (small freebie since I
was editing that paragraph anyway). The approval-as-source-chip
is the implementation of #436; the cookie was just history.
Pre-scanned docs for matching cookies; caught all 3 doc cookies
in the same PR.
Container row's pending highlight fired for both queued + running
rebuild-queue entries, so a long queue painted half the SW4RM tab
amber. Mara on #769: don't highlight while queued, show running as
spinner on agent icon.
- tabs.js: derive opRunning (transient OR op.state === 'running')
separately from pending; add a pending-running row class.
Operator-initiated transients still count as running so the
rebuild-now-clicked → backend-picks-it-up window stays visually
consistent.
- dashboard.css: move the amber border + tint from .pending to
.pending-running (queued rows now keep their normal look, with
the badge text still saying "rebuild queued"). Add a 1s linear
rotating amber arc as .pending-running > .container-icon::after,
with overflow:visible so the ring sits just outside the icon and
composes with the mauve selected ring.
- docs/web-ui.md::Container row: describe the queued vs running
split in prose. Drop the orphaned (#398) cookie while I'm at it
— small #712 freebie.
#272 (show approval requested-at — closed) ×2 in tabs.js (live
ApprovalAdded fallback note + amber stale chip comment) + ×1 in
docs/web-ui.md (Approval card identity header). #275 (select all
for meta inputs — closed) ×1 in tabs.js (bulk-select rationale)
+ ×1 in docs/web-ui.md (M3T4 1NPUTS tree control note). The
amber-stale chip line in tabs.js now points at
docs/web-ui.md::Approval card for the rendered spec instead of
the closed issue.
Same theme as the tabs.js scrubs in this batch — drop the 7 #NNN
cookies (#444, #459, #460, #467, #474, #535, #564) the SCH3DUL3S
section was carrying. The substantive prose stands on its own;
the citations were just attribution tracking that belongs in the
git log + issue threads, not the user docs.
Mara on #744: 'no docs md changes needed?' — yes, this.
turn-loop.md: add optional hive_name / swarm_name fields to get_agent_meta
response shape; note they are omitted when the host options are unset.
CLAUDE.md: update hive-gateway.nix entry to mention per-agent routing
and .well-known; drop stale #609 cookie.
turn-loop.md:
- expand stale '{label} and {operator_pronouns}' note to all 5
current placeholders (#730 added hive_identity + swarm_identity)
- add 200-char + single-line validation note to set_status (#723)
mara's review nit: explaining the absence of a manager special-case
implicitly endorses the idea that special cases would be normal.
Default is no special case; the doc shouldn't dwell on it.
- ST0P comment block removed entirely (the bulk-button line above
is self-explanatory; the substantive 'c0re survives manager-down'
rationale lives in the host docs, not here).
- M0V3 helper comment reframed: trim the 'no special-case' framing
but keep the substantive note that the backend refuses moves it
can't satisfy and the failure surfaces in the bulk roll-up.
- docs/web-ui.md::Selection bar bullet list: drop the 'manager
included; no special-case' qualifiers; just describe the action.
M0V3→ROOT keeps the rationale that backend refusals surface in
the roll-up but stops calling out manager specifically.
selection + selection-bar in tabs.js carried six #443 cookies + two
#596 cookies — most marking the bulk-bar architectural decision
('actions live in bar, not on per-card buttons; manager not
special-cased') or the SW4RM-tab gate. All scrubbed; the canonical
docs/web-ui.md::Selection bar section already covered the bulk
actions in detail but was missing a preface for the **interaction
model** itself (how selection enters/exits, why per-card buttons
moved). Added that as the opening paragraph of the section so the
in-code refs have something to point at.
Net in this batch:
- tabs.js: -8 #NNN refs (#443 ×6, #596 ×2)
- docs/web-ui.md: +9 lines (interaction-model preface)
- functional code unchanged; build clean
refs #712