77e31dd9
docs: the listing cost is a sum, and the upgrade answer is not chosen yet
a73x 2026-08-28 19:53
Commit message
CLAUDE.md
| Old | New | ||
|---|---|---|---|
| @@ -27,7 +27,7 @@ costs ~800k tokens; every token stays in context and is re-billed each turn. | |||
| 27 | siblings, `src/server_test_*.zig`, none over 1.6k; the agent relay and the | 27 | siblings, `src/server_test_*.zig`, none over 1.6k; the agent relay and the |
| 28 | session table are `src/server_agent.zig` and `src/server_sessions.zig`), | 28 | session table are `src/server_agent.zig` and `src/server_sessions.zig`), |
| 29 | `src/wallview.zig` (6.6k), `src/interact.zig` (4.5k), | 29 | `src/wallview.zig` (6.6k), `src/interact.zig` (4.5k), |
| 30 | `docs/decisions.md` (7.1k). `test/e2e.sh` is a 189-line runner now; the | 30 | `docs/decisions.md` (7.3k). `test/e2e.sh` is a 189-line runner now; the |
| 31 | bodies are `test/e2e_NN_*.sh` (`E2E_ONLY=<group>` runs one). | 31 | bodies are `test/e2e_NN_*.sh` (`E2E_ONLY=<group>` runs one). |
| 32 | Use `grep -n` for the symbol, then `sed -n 'A,Bp'` for a window. | 32 | Use `grep -n` for the symbol, then `sed -n 'A,Bp'` for a window. |
| 33 | `src/client.zig` is 1.9k now — cheap enough to read. | 33 | `src/client.zig` is 1.9k now — cheap enough to read. |
| @@ -169,8 +169,10 @@ real pty), `wsclient` (browser stand-in), `rawmode`, `delaypipe`, `render`. | |||
| 169 | stamps `Patch:` trailers. Each collab write costs a ~15s origin sync — batch. | 169 | stamps `Patch:` trailers. Each collab write costs a ~15s origin sync — batch. |
| 170 | - `make check` before commit, `make ci` before delivery; capture `$?` before | 170 | - `make check` before commit, `make ci` before delivery; capture `$?` before |
| 171 | piping (`make test | tail` reports tail's exit code, not the build's). | 171 | piping (`make test | tail` reports tail's exit code, not the build's). |
| 172 | - **Any hand-run rig exports an isolated `XDG_STATE_HOME`.** Every named | 172 | - **Any hand-run rig exports an isolated `XDG_STATE_HOME`.** Every `mux` |
| 173 | attach writes a host into `$XDG_STATE_HOME/mux/hosts`; the `make` scripts | 173 | that attaches writes its daemon into `$XDG_STATE_HOME/mux/hosts` — a bare |
| 174 | `mux` on an empty file included, since that records the local one. The | ||
| 175 | `make` scripts | ||
| 174 | all isolate it, but an ad-hoc repro or perf rig with the real `HOME` leaves | 176 | all isolate it, but an ad-hoc repro or perf rig with the real `HOME` leaves |
| 175 | a dead `--sock /tmp/...` line on the user's wall per throwaway socket (49 | 177 | a dead `--sock /tmp/...` line on the user's wall per throwaway socket (49 |
| 176 | of them found 2026-08-22, under the old per-attach file). `export | 178 | of them found 2026-08-22, under the old per-attach file). `export |
| @@ -186,7 +188,7 @@ real pty), `wsclient` (browser stand-in), `rawmode`, `delaypipe`, `render`. | |||
| 186 | ## Where the answers live | 188 | ## Where the answers live |
| 187 | 189 | ||
| 188 | `docs/roadmap.md` (~650 ln) the ranked queue, but stale past 2026-08-16 — | 190 | `docs/roadmap.md` (~650 ln) the ranked queue, but stale past 2026-08-16 — |
| 189 | `git-collab issue list` is the live order · `docs/decisions.md` (4.9k ln, grep | 191 | `git-collab issue list` is the live order · `docs/decisions.md` (7.3k ln, grep |
| 190 | only) every decision + measurement · `docs/handoff.md` (~200 ln) the original | 192 | only) every decision + measurement · `docs/handoff.md` (~200 ln) the original |
| 191 | design, historical · `README.md` user-facing usage. | 193 | design, historical · `README.md` user-facing usage. |
| 192 | 194 | ||
README.md
| Old | New | ||
|---|---|---|---|
| @@ -365,8 +365,12 @@ box [unreachable] | |||
| 365 | ``` | 365 | ``` |
| 366 | 366 | ||
| 367 | — the line, a tab, then its live session count or what went wrong. That | 367 | — the line, a tab, then its live session count or what went wrong. That |
| 368 | listing dials every host, so it takes as long as the slowest one answers (up | 368 | listing dials the hosts one at a time and waits up to two seconds for each, |
| 369 | to two seconds each). `mux hosts rm` reads the file verbatim too, so a line | 369 | so the cost is the SUM and not the slowest: two silent hosts is four |
| 370 | seconds. Only a host that accepts the dial and then says nothing costs the | ||
| 371 | full wait — a socket with nothing behind it is refused instantly (measured: | ||
| 372 | 2002 ms for one silent daemon, 4002 ms for two, ~1 ms for any number of | ||
| 373 | absent sockets). `mux hosts rm` reads the file verbatim too, so a line | ||
| 370 | you hand-edited into something no longer legal can be typed back byte for | 374 | you hand-edited into something no longer legal can be typed back byte for |
| 371 | byte and removed instead of wedging every command that touches the wall. | 375 | byte and removed instead of wedging every command that touches the wall. |
| 372 | 376 | ||
| @@ -380,12 +384,22 @@ leaves. Your own daemon is the exception that needs no stripe: a listed | |||
| 380 | local `--sock <default>` that nothing is serving is started for you, because | 384 | local `--sock <default>` that nothing is serving is started for you, because |
| 381 | it dies on every reboot while its line lives on. | 385 | it dies on every reboot while its line lives on. |
| 382 | 386 | ||
| 387 | A host that answers with an EMPTY list is a stripe reading `[no sessions]` | ||
| 388 | instead. You will not see it from your own machine — `muxd` exits when its | ||
| 389 | last session ends, so a local daemon that answers at all has at least | ||
| 390 | session `0` — but a remote daemon something else is holding open can sit | ||
| 391 | there with nothing to show. | ||
| 392 | |||
| 383 | **A daemon too old to be asked reads as `[unreachable]` too.** Listing a | 393 | **A daemon too old to be asked reads as `[unreachable]` too.** Listing a |
| 384 | daemon's live sessions is a new question on the wire, and a daemon from | 394 | daemon's live sessions is a new question on the wire, and a daemon from |
| 385 | before this change answers it with the silence it answers every unknown | 395 | before this change answers it with the silence it answers every unknown |
| 386 | frame with. It is up, `mux --sock PATH` still attaches to it, and the wall | 396 | frame with. It is up, `mux --sock PATH` still attaches to it, and the wall |
| 387 | still shows it as a stripe that never heals — so `muxd upgrade` (or a | 397 | still shows it as a stripe that never heals, so today `muxd upgrade` (or a |
| 388 | restart) on every box you put on the wall is part of taking this version. | 398 | restart) on every box you put on the wall is what clears it. Whether that |
| 399 | stays the answer is undecided: the alternative is to tell the two apart — | ||
| 400 | `[upgrade muxd]` when the dial succeeded and only the list timed out, | ||
| 401 | `[unreachable]` when nothing answered at all — which costs a word and a | ||
| 402 | branch and is not built until someone asks for it. | ||
| 389 | 403 | ||
| 390 | Restarting a daemon re-creates **nothing**. The old wall would have brought | 404 | Restarting a daemon re-creates **nothing**. The old wall would have brought |
| 391 | your sessions back from the file; this one cannot, because the file has no | 405 | your sessions back from the file; this one cannot, because the file has no |
docs/decisions.md
| Old | New | ||
|---|---|---|---|
| @@ -7215,8 +7215,10 @@ no sidecar. This was found the hard way — removing the old `hydrated` flag | |||
| 7215 | made every wall save and restore the layout sidecar, piped ones included, a | 7215 | made every wall save and restore the layout sidecar, piped ones included, a |
| 7216 | restored focus CLAIMED, the claim forced a redundant resize, and | 7216 | restored focus CLAIMED, the claim forced a redundant resize, and |
| 7217 | `01_boot`'s "quic delta resume" leg started wanting three snapshots where it | 7217 | `01_boot`'s "quic delta resume" leg started wanting three snapshots where it |
| 7218 | wants one. Bisected to the commit, not guessed. A wall of MANY does need a | 7218 | wants one. Bisected to `2782c6b`, not guessed — the commit that made a |
| 7219 | terminal and says `mux: wall needs a terminal`. | 7219 | host's live sessions its tiles, and removed `Entry.hydrated` on the way |
| 7220 | past. A wall of MANY does need a terminal and says `mux: wall needs a | ||
| 7221 | terminal`. | ||
| 7220 | 7222 | ||
| 7221 | **The sidecar is saved by every wall left on a terminal, including `mux | 7223 | **The sidecar is saved by every wall left on a terminal, including `mux |
| 7222 | TARGET`.** Under the old model `mux TARGET` was an argv wall and argv walls | 7224 | TARGET`.** Under the old model `mux TARGET` was an argv wall and argv walls |
| @@ -7263,10 +7265,21 @@ paints it a stripe that never heals. Measured side by side: one hosts file | |||
| 7263 | naming a tree-built daemon and an installed v0.0.1-15 daemon, both alive on | 7265 | naming a tree-built daemon and an installed v0.0.1-15 daemon, both alive on |
| 7264 | their own sockets, both answering `muxd stats` with `sessions=1` — `mux | 7266 | their own sockets, both answering `muxd stats` with `sessions=1` — `mux |
| 7265 | hosts` printed `1` for the first and `[unreachable]` for the second, after | 7267 | hosts` printed `1` for the first and `[unreachable]` for the second, after |
| 7266 | the full two-second wait. `muxd upgrade` on every box on the wall is part | 7268 | the full two-second wait. The xversion gate pins both halves of the story |
| 7267 | of taking this version, and the xversion gate pins both halves of the story | ||
| 7268 | (the stripe, and `Ctrl-\ x`'s `[daemon too old to end a session]`). | 7269 | (the stripe, and `Ctrl-\ x`'s `[daemon too old to end a session]`). |
| 7269 | 7270 | ||
| 7271 | **What to DO about that is parked, not decided.** Two options, and the | ||
| 7272 | choice is the user's. Accept it: every daemon on the wall must be upgraded, | ||
| 7273 | which this release arguably demands anyway since it adds wire verbs. Or | ||
| 7274 | distinguish the two silences — `[upgrade muxd]` when the dial succeeded and | ||
| 7275 | only the list timed out, `[unreachable]` when nothing answered — which costs | ||
| 7276 | one word, one branch in `hostsList` and the poller, and turns "your box is | ||
| 7277 | gone" into "your box is old". Nothing is built for the second: it was not | ||
| 7278 | asked for, and building an unasked-for mitigation into a release note is how | ||
| 7279 | a stopgap becomes the design. Recorded here so the choice is visible rather | ||
| 7280 | than made by default. Until it is made, the README says the first is what | ||
| 7281 | happens and names the second as open. | ||
| 7282 | |||
| 7270 | **What went, and the legs that went with it.** `wall.zig`'s `#SESSION` | 7283 | **What went, and the legs that went with it.** `wall.zig`'s `#SESSION` |
| 7271 | split and attach-history semantics; `client.recordOnState`; the CLI's use of | 7284 | split and attach-history semantics; `client.recordOnState`; the CLI's use of |
| 7272 | `client.hydratedCreates` and the saved-local-line attach-or-create rule with | 7285 | `client.hydratedCreates` and the saved-local-line attach-or-create rule with |