Interactive cockpit for ALL ZCode sessions — read, steer, answer questions, archive, dock. Pairs with zcode-dispatch for orchestration.
  • Python 80.4%
  • HTML 19.3%
  • Shell 0.2%
  • Makefile 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-10-25 10:56:53 +00:00
.forgejo/workflows Pinky KKK: housekeeping — EEE-V ZCODEHOME fold + FV-6 yamllint (v1.68.0) (#98) 2026-10-13 06:27:17 +00:00
.github Pinky IIII: the drawer congress — self-healing view, one door, census completion, the anchor class (v1.92.0) (#127) 2026-10-16 11:17:58 +00:00
docs Pinky EEEE: the quiet QUALITY arc — README consolidation, INFO burn-down, suite hardening (v1.90.0) (#125) 2026-10-16 02:07:59 +00:00
scripts Pinky TT: the mobile deck + the role-true genesis slug — S-39, UU-1, UU-2 (v1.56.0) (#84) 2026-10-08 23:25:24 +00:00
tests Pinky AAAAA: the growth cure — indexes + first paint + action latency (v1.112.0) (#146) 2026-10-25 10:56:53 +00:00
.gitignore Pinky BBBBB: the start-plan family — layer capability + the switch commission (v1.110.0) (#145) 2026-10-25 03:17:57 +00:00
dashboard.html Pinky AAAAA: the growth cure — indexes + first paint + action latency (v1.112.0) (#146) 2026-10-25 10:56:53 +00:00
install.sh Pinky GG: canonical install target — ExecStart anchor, short-name/sibling guards, README verification recipe (v1.37.0) (#65) 2026-10-02 06:23:21 +00:00
LICENSE zcode-dispatch-observatory: live local dashboard for dispatcher sessions 2026-09-23 13:43:14 +02:00
Makefile Pinky YYYY: the truthful receipt + the turn-state display (v1.108.0) (#143) 2026-10-24 19:21:31 +00:00
observatory-restart.sh Restart script: match the symlink invocation in the legacy kill pattern (v1.25.1) (#51) 2026-09-26 00:22:55 +00:00
observatory.py Pinky AAAAA: the growth cure — indexes + first paint + action latency (v1.112.0) (#146) 2026-10-25 10:56:53 +00:00
README.md Pinky AAAAA: the growth cure — indexes + first paint + action latency (v1.112.0) (#146) 2026-10-25 10:56:53 +00:00
zcode-observatory.service Pinky X: service robustness — reboot-proof steer spawn + baked boot-start (v1.28.0) (#54) 2026-09-26 10:14:42 +00:00

zcode-dispatch-observatory

Checks License: MIT Python

zcode-dispatch observatory social preview: an astronomer-mage watching glowing session stars through a crystal telescope

An interactive cockpit for your ZCode sessions — all of them. Open it in a browser and every running and historical session is there: titles, turn counts, token totals, models with reasoning level, live context utilization, whole mission trees color-coded by lineage. Then go beyond watching — birth new sessions end-to-end (genesis: pick project, model and role, write the prompt, watch the session materialize), steer a session with a follow-up prompt (queue semantics: busy targets self-queue, your words echo optimistically and are never lost), answer its pending questions, archive it into the desktop's own archive, pin it, hide it, keep chats in a favorites dock — and read everything (response interface, transcript drawer, expandable tool parts) without ever restarting ZCode. Genesis + steering + bells together close the loop: the observatory becomes the command deck for the whole dispatcher hierarchy, no ZCode window required.

ZCode sessions ──▶ session DB (read-only) ──▶ observatory ──▶ cockpit HTML
      ▲                                                    │
      └──── zcode-dispatch (spawned detached) ◀── create / steer / answer ──┘

The name carries zcode-dispatch because that combination makes the orchestration strong — the steering console and the bell visibility ride on the dispatcher's architecture — but the observatory sees and interacts with every ZCode session on the station: desktop chats, dispatched workers, mission brains, alike. It is powered by zcode-dispatch and independently useful without it; together they are the dreamteam (see The orchestration it rides on).

observatory dashboard

Fixture-data screenshot, captured at exactly 1177 px inner width — the ideal window, where the live width indicator in the header reads mint. The clean global overview with the round-CC header: the title alone on the left, and on the right the feed controls over the ONE control band — the view/filter cluster (window selector, 🧠 brain toggles, ⧉ ideal window button, live width indicator) LEFT of the read-only status chips (residency note, mint 🧪 canary ✓, ⚡ push live, ⛽ quota) in a single right-aligned row. Below the header: the pending-dispatch strip (a healthy, a slow and a presumed-dead dispatch), the ONE merged Sessions table (v1.65.0: running rows float to the top under the filter chips ▶ running / ● unread / 🧠 brains; status is a column, never a section) with the mission family — a ⚡️🧠 masterbrain root with its 🧠 sub-brains and a pinky child sharing one lineage color — beside solo 🧠 brains, a running E2E gate and an amber question row, a hydration prediction on the idle chat, the usage-graph section over the tables, and the favorites dock at the bottom. No modal open — every other facet gets its own shot in the sections below.

Quick start

zcode-observatory            # installed command; or: python3 observatory.py
# then open http://127.0.0.1:8767 — the page refreshes itself every 3 s

The header orients you in one glance: the title left; the feed controls and ONE control band right — the view/filter cluster left of the read-only status chips, collapsible since v1.41.0 to a slim quick-stats line that keeps every control one click away (the 🛑 idle-fleet line, the starvation line and the 🚪 master door lead the band; see Collapsible surfaces and The ideal window). Everything else lives in the surface map below — What you see first, then Vitals, the census, the steer lifecycle, genesis, guarantees and the reference material.

What you see

  • Pending dispatches — a slim strip above the Sessions table (only when non-empty): dispatches that were fired but have no session yet, each a chip with project, model and age (see Dispatch lifecycle: the pending strip).
  • Sessions — ONE table (v1.65.0) — running rows and terminal rows side by side, the Status badge telling them apart (see One table). The natural newest-first order floats running rows to the top; filter chips (▶ running / ● unread / 🧠 brains) narrow the view in one click. Running rows carry the live-turn facts (pinned static title, cwd, tokens so far, models with reasoning level); a FIRST turn honestly shows a mint "live" in the tokens cell — turn_usage rows (and with them the token sums) only exist at completion; sums from earlier turns keep rendering as numbers. Running detection is LIVELINESS-FIRST (v1.86.0): life is proved by /proc process signals — a verified dispatcher driving the cwd, a --resume/--out sessionId match, a live app-server owning the cwd, or an ESTABLISHED provider socket — plus the older legs (a held dispatcher lock, PID-checked, and activity inside ZDO_RUNNING_GRACE_MS, default 5 min); a process-proven row stays running while the process lives, and a new terminal turn row ends running at once.
  • Tokens, cumulative + context % (v1.26.0) — the table's token cell shows ONE cumulative value (in + out, all time; the tooltip carries the in/out/reasoning split), and the freed column width hosts the context-window % per session — the steer line's own math (the latest model request's tokens.total over the provider-config window size), batched for every row. Sessions without a completed model request yet render an honest dash; a running first turn still reads "live". Both columns sort.
  • Non-running lanes — every listed session that is not running (dispatched workers, desktop chats, brains alike — see the listing rule at the end of this section), newest first, IN the same one table. The non-running lanes follow the ONE header window (see One window for everything) except the exempt classes — running, kept-close, toggled brains (running rows always stay listed).
  • Hidden — between the Sessions table and Archived, collapsed by default: sessions you deliberately moved out of sight (Hide on any Sessions-table row). Hiding is pure observatory view state — rows keep their full toolkit plus Unhide (back to the table) and Archive (see Hiding vs archiving).
  • Archived — the native ZCode archive — membership is the tasks.archived flag in the desktop's own task index (~/.zcode/v2/tasks-index.sqlite), byte-for-byte the store the ZCode sidebar's archive drives (see Archiving sessions). Every row affordance works there too — response modal, transcript, pinning — plus an Unarchive button; History rows carry the matching Archive button (behind a lightweight confirm).
  • Busy-state transparency — sessions run BACKGROUND turns (bell-wake processing, dispatch work) that are invisible in the desktop chat: desktop invisibility is NOT idleness. Rows whose transcript activity is fresher than the busy grace (30 s default) carry a subtle pulsing "bg active · Ns" hint (a breathing mint dot), and a session with steers waiting in the sweeper queue shows "📨 queued · waiting for the current turn to end" — both live off the normal poll (see Three states: idle / background active / queued deliveries).
  • Pending questions — any session paused on an AskUserQuestion glows amber (❓ badge on the row, count in the section header); the question card with answer controls lives in the response modal and transcript drawer (see Pending questions).
  • Owner-unread glow — the row badge glows while the session holds brain→owner words the owner has not read yet (v1.61.0, round FFF: the count comes from the ONE owner-unread computation — assistant substance since his last read, cross-domain steer words included, doorbells and machine traffic never; the tooltip names the count). The pre-v1.61 approximation (blue-dot completions off the task index with a 10-minute decay) is retired — reading now CLEARS (the durable read marker), nothing just fades.
  • Root-lineage family colors — every session's ROOT is resolved transitively over ALL parent-edge sources (native session.parent_id, doorbell edges parsed from "🔔 stream doorbell" user messages, bell-ledger spawn records from stream-bells.json, dispatcher v0.3.1+ — lineage stays visible even while a bell is still queued — and live spawn edges off the running child's own dispatcher command line: --notify-parent names the parent from the moment the child spawns, because the ledger only gains a record when the child completes — running children link too) and hashed into a fixed 12-hue palette: a root and ALL its descendants share one color on the row's left accent bar, tint and spanning line — whole work/mission trees read as coherent blocks, and hovering a row GLOWS in its family's color. A STRUCTURAL ROOT (no parent edge) is its own family from the first second (v1.26.0): the color is the hash of its own id, present before any child exists, and the Parent column marks it — ◉ root in the family color, or the 🧠 glyph for brain-typed roots — so roots are identifiable in the table exactly like in the graph. No legend by design (the color IS the family identity); hovering or clicking the parent column names the family ("family of ⟨root⟩ · N sessions") and highlights every member, and the Parent column shows the parent session's TITLE by name (resolved by edge-source precedence — native session.parent_id, bell-ledger/live-spawn, delivered doorbell; short id fallback — the parent's own row carries the copyable full id in its ID column, v1.21.0 removed the extra copy button), sortable so families group adjacently (see Root-lineage family colors, parent column). A parent with an undelivered bell shows a bell-state hint ("🔔 pending · 4 min", or "🔔 stuck — watchdog notified" when the spawn record is older than 5 min with no delivery and no queue entry left); without a ledger (older dispatchers) nothing changes.
  • Lineage graph (v1.38.0; node clicks fixed v1.38.1 — a pointer capture on the stage retargeted clicks away from the nodes on current Chrome and every node-click affordance was dead under real input; window keep-set unified with the tables v1.103.0) — between the tables and the quota gauge: the fleet as one pannable, zoomable SVG forest with role/status/window filters, aggregate family nodes past 12 sessions and the replay scrubber ("fleet at HH:MM", honest bounds labeled) (see Lineage graph).
  • Markdown code rendering (v1.39.0) — agent responses carrying fenced code blocks and inline code render as monospace blocks (with a per-block ⧉ copy button riding the house clipboard path) and styled inline spans in EVERY agent-text surface — message bodies in both panels and both views, reasoning text, and the steer response tail — through one shared, escape-everything-first renderer whose scope is exactly fences + inline spans, nothing else (see Markdown code rendering in agent text). The prompt console gained a ⤓ collapse toggle (next to ⤢) that folds the prompt area away for maximum chat size while the steer status strip keeps reporting below it (see Steering from the observatory).
  • Quota & cost (v1.33.0; budget lanes since v1.35.0) — the fuel gauge in TWO honestly-labeled scopes: a ⛽ chip in the header's control band (⛽ 62 % · ends ~HH:MM, amber under 4 h, red under 1 h or exhausted; hidden while the ceiling is unconfigured) and a section above the usage graph — scope 1, the usage window (v1.83.0: the burn display follows the table's time-window selector, usage window (N h), "all" caps at 7 d; the guard math stays config-anchored): burned tokens (every model_usage request, interactive sessions included), rate/h, burn-down bar, hourly sparkline; scope 2, Lanes / trees: the dispatcher's usage ledger aggregated by tree with the budget contract's used-formula, plus each configured soft/hard limit and state, ⟲ override badges, and a global budgets line (zcode-dispatch ≥ v1.0.16; read-only seams, absent = accounting only). The genesis review shows the forecast with an amber warning when headroom is tight — advisory only, spawning is never blocked (see Quota & cost).
  • Usage over time — the page closes with a full-width graph (collapsed by default since v1.83.0, alongside the lineage graph — quota & cost stays open; a manual fold persists per panel): every session's per-turn tokens as thin lines in their lineage color on a log axis, brain groups hue-sorted into a rainbow flow, and the count of Pinkies active at each moment as a softly glowing mint area behind the lines. Hover tooltips carry the in/out/reasoning split, legend chips isolate one family like the table rows, the window follows the ONE header selector ("all" falls back to 7 d here), and running sessions plus fired-but-sessionless dispatches appear as honest markers — never fake data (see Usage over time).
  • Hydration prediction — idle sessions with unseen external content (a doorbell that rang into an open desktop chat) show "⏳ hydrates between HH:MM and HH:MM · N new since HH:MM" on their spanning line — the 10-minute residency cycle, predicted from DB timestamps (see Hydration prediction).
  • Reads the ZCode session database mode=ro everywhere — the observatory never writes it. The one write surface is the native task index, and only through the archive buttons described below.

Sessions reach the table through any of: a pinned static title (title_source='custom', written by zcode-dispatch ≥ v0.1.2), activity inside the lookback window (ZDO_LOOKBACK_MS, default 1 h — ANY session, desktop or dispatched), or membership in the native archive. The archive section therefore includes desktop chats the dispatcher never drove, and every running or recently-active session is listed whatever drove it.

Unread is audience-aware (v1.80.0 / v1.81.0). Reports a lane's master already consumed (bell hand-offs, dispatcher-accounted injections) never invoice the owner, and bridge-born workers of an EXTERNAL orchestrator show their own 📤 tray glyph ("report messages delivered to the external audience") instead of owner-unread — unread counts the words waiting for YOU.

Vitals — the deck's instruments

How the fleet, the quota and the feed itself are doing — glanceable, honest, always current. The collapsible band carries the quick stats; the panels behind them carry the math.

Collapsible surfaces (v1.41.0)

Three folds that give the dashboard's space to reading: the steering console's input+answer region disappears behind ONE full-width restore bar, the whole page header collapses to a live quick-stats line, and the rainbow title becomes one continuous band.

  • The prompt fold now takes the steer-answer strip with it (S-10). The v1.39.0 ⤓ toggle folded input row, send tower and target text; the owner's live test showed the answer strip below (the ✅ completed ● new · <excerpt> ▸ chip block) still eating height. Collapsed now: the ENTIRE input+answer region gives way to the chat — and the restore button is no left-hugging sliver but a proper full-width bar ("⤒ restore prompt · steer status"), the one element left in its row. The strip keeps re-rendering hidden (its poll writes into the folded element), so its "● new" unread state stays live and surfaces as a mint ● dot pinned to the bar's right side — a new steer answer is never silently folded away; the dot's tooltip names the count, and clicking the bar restores everything (the strip returns in its exact pre-fold state, unread chip intact). Persistence semantics are unchanged: one zdo-prompt-collapsed localStorage flag shared by both consoles, applied at boot, surviving reloads, poll re-renders and the service restart, with the same scroll-compensation (an at-end chat stays at its new bottom).
  • A collapsible header with a live quick-stats line (S-11; controls and honest unread since v1.45.0). The ⤓ affordance at the far right of the feed controls folds the whole band — title, control band, chips, stamp — to ONE slim line (~35 px) that is a distilled status strip AND a capable control surface: running count · owner-unread total · canary ✓ · push live · quota % (quota hidden when unconfigured, the v1.33 rule). The unread stat is a LAUNCHER (v1.61.0): click it (or the ● chip in the expanded header's status chips) for the sessions with reading material — name + count + preview line; a row jumps to its session and marks it read. Since v1.66.0 the quota stat renders the REAL dual windows compact (⛽ 5h 62 % · 7d 34 %, the worst window driving the amber/red ladder) and the end cluster carries a compact ⧉ ideal-window snap beside the ⤒ expand toggle (S-42): one click snaps this window to the global 1177 px ideal through the very same snapIdealWindow path as the expanded header's ideal-window button (script-opened windows accept resizeTo; idempotent at the ideal width — icon only, the line's one-row geometry untouched). ALERT STATES TAKE PRECEDENCE on the line — and since v1.67.0 the 🚪 master door leads every warning (master door: N bells unconfirmed, red when a bell is stuck — the lane master may be sleeping with bells pending; see §"The master door"), ahead of a broken canary (⚠ canary BROKEN), an offline event bridge (⚠ push offline), a silent-but-connected one (🔇 push silent, v1.107.0 — the open-but-deaf class the old two-state chip could not name), a presumed-dead dispatch (💀 presumed dead, red) or a quota warning/critical/exhausted (⛽ NN % · warn → red ladder), all rendering as accent-colored wording left of the quiet stats — healthy stats stay dim, alerts shout (the healthy master door itself renders as a dim 🚪 master door: healthy stat that yields to every alert). The line updates LIVE while collapsed: it re-renders on every poll, every push-chip transition, every quota refresh and every mute refresh (the page-visibility/poll machinery and the SSE states are untouched by the fold). The collapsed line carries CONTROLS (steer #33/#34, round QQ): the notification dimensions each double as a stat-button — the three-state gate (🧠 smart / 🔔 all / 🔕 muted, click cycles; honest suffixes · default / · env per the live source) AND the master mute file (🔊 master on / 🔇 master muted — amber when the file exists), the ◷ <window> readout opens a compact popover with the same options as the expanded control band (24 h / 3 d / 7 d / all — derived from the expanded selector, so they can never drift — plus the 🧠 brains / 🧠↳ sub-brains lineage exemptions as pill rows; a window pick applies to the tables and the usage graph and closes, a brain toggle flips in place), and the line's END cluster carries 🌱 New Session (same dialog, all rails — birthing a session never requires expanding first) and ⤒ header, the ONE dedicated expand affordance (with clickable stats on the line, clicking a stat can never expand by accident). Persistence semantics are unchanged: zdo-header-collapsed for the fold, zdo-window, zdo-brains, zdo-subbrains for the view choices (house localStorage pattern — survive refresh and restart), and the expanded state is byte-for-byte the v1.33 band: the one control row at the 1177 px ideal window is re-proven, not redesigned. Below ~1010 px the QUIET stat twins (canary ✓, push live, quota %) fold away from the line (they are redundant with the expanded band; alerts always stay), and the stats' wrap is the last-resort safety valve — nothing ever clips or overlaps. The unread stat is owner-honest (v1.61.0, round FFF): unread is what the BRAIN writes to the owner — assistant substance since his last read of the session — never doorbells or machine traffic (the owner's ruling: "statusinfo xy zelebriert im Hintergrund" is not his unread). ONE server computation derives every surface (header total, launcher rows, row badges, dock badges, chat-line glow) from the same per-session counts and the same read markers, so the surfaces cannot disagree. VIEWING clears: opening a session (drawer, launcher jump, dock expansion) advances its durable, monotonic read marker (POST /api/read), and cross-domain steers from another brain (non-obs- ledger deliveries, e.g. the PDrive master steering into a lane) count as brain→owner words. The owner-conversation lane gate (v1.66.0, round EEE / S-43): only sessions the owner DIRECTLY steered or conversed with carry unread at all — worker output (pinkies, sub-brain children) reports to parent brains and never touches his unread anywhere (no count, no badge, no launcher row, no halo). Qualifying evidence, auditable: a delivered obs- console steer (the owner's words — EXCEPT children provably spawned by the observatory's own machines: the context guardian and the quota throttle record their notices durably and are excluded), or an owner-TYPED user message — a real-owner-input-class message outside every MACHINE window (delivered steers, external bridge-resume injections, master-side resume injections — the audience-aware extension of v1.80.0/v1.81.0) that is not the first (task-prompt) message of a dispatched session; interactive sessions count their opening prompt as typed.
  • One continuous rainbow title (S-12). "zcode-" and "observatory" used to each wear their OWN full-spectrum gradient — both starting at red, so the hue visibly restarted at the word seam. The robust fix: ONE linear-gradient with background-clip: text on ONE wrapping element (the h1 itself) — both words render transparent glyphs, the SAME band flows through them continuously across the seam at every width and font, and "dispatch" keeps painting its mint over the band (width: fit-content maps the spectrum to the text extent exactly). The collapsed header's slim line renders the title through the same .rbtitle class — same continuity, smaller size.

Cheap polls: an open dashboard is (nearly) free (v1.27.0)

The dashboard polls every 3 s, and until v1.27.0 every poll RECOMPUTED the full station scan — twice per cycle, because /api/sessions and /api/usage each ran it (~4.4 s of CPU per 3 s cycle on a 340-session station: one open tab held a core at ~170 % and the station at 90 °C). Two layers fix that, with semantics untouched:

  • Response cache — both poll endpoints serve the identical cached body while a cheap change key over every source is unchanged: the session DB and task index (file stats including the -wal sidecar — the main file's mtime does not move on WAL commits — plus SQLite's data_version commit counter off a dedicated read-only probe connection), the dispatcher ledgers (bells, sweeper queue, dispatch lifecycle, contract report), the questions side-channel, the observatory's own state file, the provider config cascade, and the live process world (running dispatchers' --notify-parent linkage off /proc, held cwd locks, in-flight steers — processes, not files, so their digest IS the signal). A mutation of ANY source class rebuilds on the very next poll (≤ one cycle — unit-tested per class, tests/test_poll_cache.py). Purely time-derived fields (bell ages, grace windows, the usage window) refresh on a TTL (default 8 s, ZDO_CACHE_TTL_S) so a quiet dashboard never serves a body older than that. ZDO_CACHE=0 restores uncached v1.26.0 behavior exactly.
  • Hot-path memoization — a rebuild costs ~what changed, not ~the whole station: per-session facts (context totals, pending questions, activity, hydration, models) are memoized keyed on session.time_updated (parts, messages and turn rows flush together with that row — verified against the live app), re-derived only for changed, new, or hot (running/driven) sessions; the doorbell lineage scan is rowid-incremental (insert-only rows); the two endpoints share ONE fetch per cycle. Measured on the station snapshot: idle rebuild 45 ms (was 2189 ms), first build after a restart ~2 s (one-time seed).

The dashboard joins in: a hidden tab pauses every poll timer — the main refresh, the drawer transcript, the response modal, the dock — and on return re-arms exactly what was armed and does one immediate refresh. The default rate stays 3 s (the live feel is unchanged while you watch); the CPU win comes from the server not re-deriving unchanged data, not from polling slower.

Push feed: event-driven refreshes over SSE (v1.30.0)

With the dispatcher's events bridge running (zcode-dispatch v1.0.8 stream.py events — the zcode/events/v1 SSE envelope, its docs/EVENTS.md is the contract), the dashboard stops polling on a clock and refreshes when something actually happened: an open tab holds one EventSource to the bridge and every mapped event coalesces into the standard full refresh (cheap since the v1.27.0 response cache — the DB change behind the event already invalidated it). The main poll cycle and the dock poll provably idle while the feed is healthy; bursts debounce to one refresh per ~200 ms.

Event The dashboard
session.turn-completed / turn-failed / turn-timeout refresh the tables; an open drawer/modal for that session reloads too
session.notification-delivered dock re-render + notification state
session.bell-stuck, dispatch.presumed-dead, dispatch.reaper-escalation refresh (dispatch strip / lifecycle)
contract.broken / contract.ok refresh (contract banner)
infra.mqtt-restarted, ping liveness only — no data action
bridge.hello resync from state on EVERY hello (v1.107.0: the every-hello law — ids reset per bridge process, never id arithmetic; replayTruncated and the bridge's idReset flag are subsumed; the resync on an already-live feed is exactly the fresh-guard heal's catch-up, which the pre-v1.107.0 gate used to skip)
bridge.dropped keep the connection, resync from state at once (the browser's own Last-Event-ID replay covers the dropped range on the next organic reconnect)

Degradation is honest, never silent — and since v1.107.0 it names THREE states, because connection liveness and delivery liveness are different truths (the 2026-10-16 blindness lesson):

  • ⚡ push live — connected AND delivering (events within the silence window). Only this state idles the polls.
  • 🔇 push silent (v1.107.0) — the open-but-deaf class: the connection is open and the bridge answers /healthz, but NOTHING has been delivered for 120 s (quiet bus or deaf consumer — the tooltip says both). The polls re-arm in full: worst-case staleness is the poll cadence again even on a dead-but-open feed, while the connection keeps trying in the background; the healthz probe runs once per silence episode and can NEVER reset the delivered-event clock (the pre-v1.107.0 probe reset laundered a deaf feed into a permanent "live" chip).
  • ⚠ push offline — bridge down, connection error, or an unhappy /healthz (v1.33.0: fixed-width — the reason and the polling detail moved to the tooltip so the header's one control band never wraps at the ideal width); re-arms the exact polling the page-visibility governor would — data keeps flowing at the v1.27.0 cadence and CPU envelope. When the bridge returns, bridge.hello clears the chip and re-idles the polls (~15 s watchdog-bounded). The drawer/modal transcript polls keep their own 5 s semantics in every state: mid-turn streaming parts reach the DB with no MQTT event (turn events fire at boundaries), so only their own cadence keeps an open panel live while a turn streams. One safety resync per quiet minute bounds staleness of unmapped signals (desktop-app sessions write the shared DB with no bus event) — and since v1.107.0 it is also the floor that keeps a SILENT-but-live feed fresh on a rate-off deck (the probe verdict can no longer starve it). The fresh-reconnect guard (the 2026-10-01 law shape: a revival whose connection had consumed framed ids may sit in a restarted bridge's dup-skip window) still self-heals with ONE header-less reconnect after 45 s without a delivery — and its own hello now resyncs too, closing the missed-window gap the old mustResync gate left behind.

The knob: ZDO_EVENTS_URL (default http://127.0.0.1:8788/events; empty or off disables SSE entirely — zero EventSource attempts, the pure v1.29 polling world). The page fetches it once at boot via GET /api/config ({"eventsUrl": … | null}) — the dashboard HTML stays a static file. The station cutover is done: the bridge runs in its own systemd user unit (the recipe lives in the dispatcher's docs/EVENTS.md) and the observatory rides the default URL — an install without a bridge calmly shows the degraded chip and polls, which remains the safe fallback state whenever the bridge is down.

The contract canary banner (v1.24.0)

the amber contract-canary banner, expanded, with the per-assumption breaks-feature list

Fixture data, captured at 1177 px inner width: the amber banner expanded, each failed assumption naming exactly which product feature it breaks, with the CLI version and the old → new drift observed at detection time in the header. The dispatcher's contract canary (zcode-dispatch v1.0.0) asserts every unpaid ZCode assumption — DB schema, protocol shapes, reverse-engineered product facts — against the live installation and writes its JSON report to ~/.config/zcode-dispatch/contract-status.json. The observatory reads that report in its existing /api/sessions poll (no extra requests) and renders the state the cockpit cannot afford to miss:

  • Amber banner (subtle glow, above Running) — the canary found broken upstream assumptions. The header carries the count, the CLI version and the old → new drift observed at detection time; clicking expands the per-assumption list, each row naming exactly which product feature it breaks (e.g. part.$.text — breaks: transcript view + response extraction dead) plus the probe detail. Click again to collapse; the expansion survives the 3 s re-renders.
  • Tiny mint chip (🧪 canary ✓ 41) in the status-chips row (round Y: beside the residency note, under the feed controls) — the canary is alive and green; the tooltip shows mode, run time and CLI version.
  • Nothing at all — older dispatcher or canary never ran: the banner degrades gracefully away (a missing report is also just "not yet").

The canary fires automatically when zcode-cli changes (the sweeper's version watch) and on demand (stream.py contract); MQTT zcode/contract/broken|ok and mute-respecting desktop notifications cover the transitions outside the browser.

Quota & cost: the fuel gauge (v1.33.0)

The 2026-10-01 quota outage killed every in-flight pinky — the deck birthed sessions (genesis) but had no view of the tank. This section is the fuel gauge, honest about what is knowable:

  • Real dual windows since v1.66.0 (round EEE). The same z.ai monitor API the cinnamon-z-usage panel applet reads (GET https://api.z.ai/api/monitor/usage/quota/limit, raw-key Authorization — no Bearer) feeds a 5 h + 7 d dual-window gauge, resolved through the applet's OWN key chain (no parallel key store): ZDO_QUOTA_API_KEY (rig/override) → ZAI_API_KEY → the applet key file ~/.config/cinnamon-z-usage/api-key → the ZCode app's cached provider key. Since v1.68.0 (round KKK) that last step mirrors the applet's _zcode_providers resolution byte-for-byte: a set ZCODE_HOME contributes <ZCODE_HOME>/config.json (no /v2 segment — the applet's spelling) first, and the home default ~/.zcode/v2/config.json always follows as fallback — the shared-source-of-truth claim holds with ZCODE_HOME set and unset. Since v1.69.0 (round LLL) the mirror is EXACT on the first-valid rule too: the FIRST candidate that parses with a valid provider dict WINS the key lookup outright — a keyless-but-valid candidate (empty provider dict, or configured for other providers) yields '', never the next candidate's key; only a candidate that fails to parse or carries no dict provider entry falls through. The key never appears in any log, payload or error — only the chain step name (keySource) crosses the API surface. A background watcher refreshes every ZDO_QUOTA_API_REFRESH_S (default 300 s, deliberately at/above the applet's cadence band); the poll path NEVER calls the API (it serves the cache, and a fresh fetch flips the poll-cache source key). Since v1.85.0 (round BBBB) the cadence is ADAPTIVE in the deep-amber band: while any window sits critical/exhausted or the refusal stamp below is fresh, the watcher fetches every ZDO_QUOTA_BAND_REFRESH_S (default 60 s) instead. Window units map per the applet's table (unit 3 = hours, 6 = weeks); each window's state uses the existing time-to-empty ladder (warn < 4 h, critical < 1 h) with the covered-hours floor and the honest refill cap — a window whose projected emptying lands after its own reset reads ok. With no key the panel renders the honest UNARMED state ("no real source; manual totals only") and the manual env model below keeps working; never a silent default window masquerading as armed.
  • The refusal snap — death flips the panel red (v1.85.0 / BBBB-1). A provider-refusal death in the dispatch ledger (the 1308/ratelimited family, or the opaque "delta modelErrors" shape) is ground truth the monitor may only echo minutes later: every sessions build sweeps the newest terminal deaths and a NEWER refusal moves a forward-only stamp that lives ZDO_QUOTA_REFUSAL_TTL_S (default 900 s). While the stamp is fresh, /api/quota carries realQuota.refusedAtMs + refusedSource, every quota surface (chip, panel, slim line) renders refused — exhausted regardless of the monitor's cached shares, and one background FORCED monitor refresh fires so the monitor's own post-refusal truth lands within seconds. The stamp expires with the TTL (a reset never keeps a stale refusal alive) and only ever moves forward.
  • The real windows display REMAINING, applet-exact (v1.72.0 / B-65). The monitor API's percentage field is PERCENT USED; every real-window surface on this deck — the ⛺ chip, the slim-line stats, the dual-window rows — displays remaining = 100 − used, mirroring the cinnamon-z-usage applet's arithmetic exactly so the two never disagree on the owner's screen: when a window carries credit figures (currentValue/usage, or the API's own remaining), the exact fraction replaces the integer percentage (the applet's _exact_used_percent, 2.0 %-pt divergence guard, mirrored in quota_window_exact_used) — owner-verified live: API 32 % (5 h) + credit-exact 32.63 → the applet's "67 %", and 44 % (7 d) + exact 44.9 → "55 %"; plain 100 − 32 would read 68. The display rounding is the applet's formatPercent too (mirrored as quotaPctText): ≥ 10 → integer, below ten → two decimals (a thin balance must not hide behind a rounded label). REASONING stays on used semantics — window states, the time-to-empty ladder and the throttle notices all read the used share and say so; the manual env model's pct (consumed share of the ceiling, labeled used) was never inverted and is untouched.
  • Self-throttle (advisory, v1.66.0). When a REAL window goes amber, every ACTIVE brain-class session receives ONE gentle in-band steer per window-episode ("finish your current round, then idle until the reset" — the 5 h wording; "stop consciously before 0 %" — the 7 d wording); a CRIT escalation earns one stronger notice; the episode closes when the window recovers. ZDO_THROTTLE=0 disables. The dispatcher's hard refusals remain the ONLY enforcement.
  • 🔥 FULL SPEED — the do-not-slow-down override (v1.73.0, round PPP; the owner's ruling: "the system must not brake when the pilot steers"). While the owner manually banks a quota reset, the deck stops braking: a header toggle (the expanded control band + the collapsed slim line — one implementation, both surfaces) suppresses the SOFT self-throttle emissions at their one seam — no amber advisory notices, no wrap-up-and-idle park recommendations, no self-slow steers to brain-class sessions are SENT. The scope line is exact: SOFT surfaces only — the dispatcher's HARD refusals (exit-7 budget admission) are code-level in zcode-dispatch; the observatory never had the power or the right to suppress them and gains none (the boundary audit in the suite pins that the override is read by exactly the throttle seam, the poll payload and the POST route — never a budget surface). The honesty line is exact too: emissions are suppressed, not truth — the quota windows, chip and panel keep rendering the REAL numbers while ON (they derive from the quota cache, never from the throttle state), and episode bookkeeping (open + close) rides on under suppression, so flipping OFF mid-episode resumes the discipline with every still-owed escalation firing then. OFF is the default on fresh state; the flag lives in observatory-state.json (the house store — restart-proof) and both directions are a manual owner action (POST /api/fullspeed {"on": bool}; the toggle is unmissable while ON — ember-ignited button + an alert-tier slim stat).
  • The ceiling is CONFIG, not observation. Usage data alone cannot see the plan ceiling, so it rides two env knobs: ZDO_QUOTA_TOTAL_TOKENS (0/unset = unconfigured → the chip and every forecast surface hide honestly, burn stats keep working) and ZDO_QUOTA_WINDOW_H (default 24). When a real quota API lands on the zcode-dispatch side (joint lane), it swaps in behind the ONE scan seam (quota_burn_scan in observatory.py) — everything downstream stays.
  • The burn is measured, station-wide. Every model_usage request inside the window counts, regardless of which session or app made it (interactive desktop chats burn the same plan): the metric is input + output + reasoning tokens (= the request's computed_total_tokens), every status included — a running row carries 0 until it lands, an errored attempt that billed is still spent fuel.
  • The forecast is a rolling-average approximation, labeled as such. Rate/h = burned over the covered hours of the window (hours since the first in-window request, floored at 1 h, capped at the window); the projection is simply now + remaining / rate. Zero burn: no forecast ("∞"). A burst-only window divides by the hours since the burst — the gauge never pretends a stale burst is still happening, and never dilutes a fresh start with empty pre-history.

Where it surfaces:

  • The ⛽ quota chip joins the read-only status chips in the header's control band. With the REAL dual windows armed it reads the remaining fuel per window, applet-style: ⛽ 5h 67 % · 7d 55 % (B-65), classed by the worst window's state. With only the manual env ceiling: ⛽ 62 % · ends ~HH:MM (amber under 4 h, red under 1 h or exhausted, mint otherwise, one no-wrap line; the manual pct is the consumed share, labeled used). Hidden entirely while nothing is configured. Click jumps to the section.
  • The Quota & cost section (open by default, above the usage graph): scope-1 headline burn numbers (labeled usage window (N h) since v1.83.0 — all sessions incl. interactive; N follows the table's time-window selector, "all" caps at 7 d, while the guard line below stays anchored to ZDO_QUOTA_WINDOW_H), the burn-down bar (share of the ceiling consumed, colored by forecast state), an hourly sparkline of the window, and scope 2 — the Lanes / trees table over the dispatcher's usage ledger: one row per dispatched tree (root session short id + 🧠 when a brain was active + resolved title; the tree's cwd rides the tooltip), used tokens per the contract's formula, soft/hard limits and an ok/⚠️ soft/⛔ hard state chip when budgets are configured, share bars against the busiest tree, and a ⟲ badge counting the lane's --budget-override bypasses (the audit trail in the tooltip). Unconfigured: no ceiling/bar/forecast, burn stats and lanes stay; no ledger at all (dispatcher < v1.0.16 or accounting off): the table says so instead of pretending.
  • Genesis review shows the forecast before birth — an amber warning appears when the forecast window is tight (< 4 h) or the remaining headroom is thinner than ZDO_QUOTA_HEADROOM_PCT percent (default 15). Advisory only: spawning is never blocked — the owner always can.
  • Per-round cost in the response modal: a per-round token strip above the messages — the last rounds with in / out / reasoning / total each (turn_usage rows; multi-request turns sum their requests, which is exactly the cost semantics).
# The API behind the gauge (read-only, cached like the poll bodies)
curl -s http://127.0.0.1:8767/api/quota
# => {"generatedAt": …, "configured": true, "totalTokens": 120000000,
#     "windowHours": 24, "fromMs": …, "toMs": …,
#     "burned": 733662445, "ratePerHour": 63997141.3,
#     "coveredHours": 11.5, "remaining": …, "pct": 611.4,
#     "endsAtMs": null, "state": "exhausted", "requests": 3035,
#     "buckets": [{"fromMs": …, "tokens": …}, …],   # hourly, window-length
#     "budgets": {"windowS": 86400, "accounting": true,
#                 "ledgerPresent": true, "configured": true,
#                 "global": {"used": 317159, "soft": 1500000,
#                            "hard": 2000000, "state": "ok"}},
#     "lanes": [{"lane": "sess_root_brain", "short": "sess_root_bra…",
#                "title": "Mission: deck", "cwd": "/w/deck",
#                "role": "brain", "used": 197000, "entries": 3,
#                "share": 62.1, "soft": 150000, "hard": 250000,
#                "state": "soft", "pct": 78.8,
#                "overrides": [{"ts": …, "used": 205000, "hard": 200000,
#                               "scope": "lane"}],
#                "overrideCount": 1}, …]}
# state: quiet | unbounded | ok | warn (< 4 h) | critical (< 1 h)
#        | exhausted — configured=false while the ceiling is unset;
#        lane/global budget state: ok | soft | hard | null
#        (null = no limit configured — accounting only).

The honest limit: this is an approximation over measured burn, not a window into the provider's accounting — the plan dashboard remains the source of truth for the ceiling and any hard cutoffs.

The honest math (v1.78.0, sharpened v1.85.0). Pace projections derive from a RECENT rolling burn window (ZDO_QUOTA_PACE_WINDOW_MIN, default 30 min), never the window's idle-diluted average — every surface self-labels its basis ("at pace (30 min)" vs "window avg"), and on a meaningful divergence both currents render, labeled. Reset clocks are window-scale-aware: same local day keeps HH:MM, a later day carries the weekday, six-plus days add the date — never a bare clock implying same-day. And a provider REFUSAL is ground truth the quota monitor may only echo minutes later: a refusal death in the dispatch ledger snaps all three quota surfaces (chip, panel, slim line) to refused — exhausted and forces a monitor refresh (QUOTA_REFUSAL_TTL_S, default 15 min).

Budget lanes: consuming the dispatcher's contract (v1.35.0)

zcode-dispatch v1.0.16+ ships a token-budget contract (docs/BUDGETS.md) — the observatory is its first consumer, docs-first exactly like EVENTS.md was for the push round:

  • The usage ledger (~/.config/zcode-dispatch/usage-ledger.json, via ZCODE_DISPATCH_CONFIG_DIR like every dispatcher ledger): one usage record per dispatched terminal state, keyed by lane (the root session id of the notify chain — the TREE identity, one-hop resolution, no configuration), plus override records for every --budget-override bypass. The panel recomputes independently with exactly the contract's formula (tokensIn + tokensOut + tokensReasoning — entry counts, models and overrides never enter a budget) over the rolling window (ZDO_BUDGET_WINDOW_S, default 24 h; 0 = accounting off — the panel says so and shows no state chips, mirroring the inert guardrails).
  • Limits come read-only from the dispatcher config's budgets section (global + per-lane soft/hard): absent = no limit (accounting-only display), exceeded means used >= limit, the most specific scope wins (lane over global) — every derivation mirrors the guardrail's own parse so the panel can never disagree with a refusal. Env overrides (ZDO_BUDGET_SOFT/HARD/LANE) are per dispatch and invisible to the panel — file limits display.
  • Two scopes, never merged: the usage window (scope 1, model_usage; the station-wide burn, display-windowed since v1.83.0) counts EVERY session including interactive desktop work; the lanes table (scope 2, the ledger) counts DISPATCHED trees only — that is exactly the set the budget guardrails act on. No double counting: different sets, honestly labeled.
  • Freshness, both disciplines: the ledger and config files are fingerprinted in the round-W poll-cache source key (a second writer's atomic rewrite lands on the next poll cycle), AND the push feed maps the bridge's budget topics — usage.station-day, the per-lane usage.lane.<lane-key> rollups (registered dynamically as lanes appear), and infra.budget-warning — to a coalesced quota refresh, so a running tab updates within the 200 ms coalescing window of every recorded usage. A fresh tab first-paints from the ledger file directly (no empty flash): reconnects that present a Last-Event-ID get the bridge's replay ring, but a FRESH consumer receives bridge.hello only — retained-rollup topics do not re-fire for it, so the ledger/API read is what paints the first quota view, never the ring.

Budget guardrails: exit 7 rendered honestly (v1.35.0)

A hard-limit refusal (exit 7, status: "budget-refused", result JSON budget: {scope, lane, used, limit, windowS}) spends nothing and creates nothing — so the observatory renders it from its own surfaces, always with the refusal's numbers, never a generic failure:

  • Pending strip: a red ⛔ budget ceiling (lane|global) — override available chip with used / hard and the lane, full admission detail in the tooltip, result path copyable, stays until dismissed.
  • Genesis status: budget-refused is a TERMINAL phase (red chip, the poll stops) with the budget detail line; a soft-limit budgetWarning and an override budgetOverride that rode the result render as excerpt lines on any phase.
  • Steer status line (drawer, row detail): exit 7 maps to its own meaning line with the budget detail; budgetWarning/budgetOverride render as chips beside the outcome; the optimistic echo settles to an honest ⛔ budget ceiling error state (a refused steer never delivers).
  • Soft warnings, push discipline: an infra.budget-warning SSE event flashes the ⛽ chip once and toasts the lane's numbers (one toast per burst, hidden tabs skip the toast — the state, not a stale "proceeding", is what matters on wake).

The owner stays in command: raising a limit or re-running with --budget-override is a dispatcher action (audited in the ledger — the ⟲ badges in the lanes table are exactly that audit trail).

Usage over time: Pinky tokens by Brain

animation: the pointer sweeps down a lineage family — every member row glows in the family's color

Watch for: the hover glow riding the family hue across rows that share one root — the color IS the family identity, no legend needed.

Usage over time — Pinky token lines in lineage colors over the glowing active-Pinky count area, brain groups hue-sorted

The usage graph alone (fixture data, captured at 1177 px inner width): token lines in their lineage colors over the softly glowing active-Pinky count area, brain groups hue-sorted into the rainbow flow, the running gate's breathing live dot, and the dashed time-axis markers of the fired-but-sessionless dispatches (amber ⏳, red 💀 for the presumed-dead class) — never fabricated data.

The last section of the page (below the tables, open by default — a showcase piece) is a usage graph in the observatory's own handwriting: every session's per-turn token usage over time, drawn by hand-rolled SVG on zero dependencies (the repo hand-rolls everything; no chart library joined). Two metrics harmonize in one plot: the token lines in their LINEAGE color, and the count of Pinkies active at each moment as a softly glowing mint area behind them.

Design decisions, documented:

  • One plot, not per-brain mini-panels. With 10–30 series the thin alpha-packed lines read as a coherent color wash once the brain groups sort by hue — and only a single shared plot lets the global Pinky-count area sit BEHIND the very lines it summarizes. Small multiples would have orphaned that area into its own chart.
  • Brain groups sorted by COLOR (the rainbow flow). The brain groups — and the series within them — order by a deterministic hue sort of the root-family colors, so legend, line order and highlight read as one spectrum flowing from one end of the palette to the other instead of random color noise.
  • Tokens per turn on a LOG axis. The honest spread reaches from a quiet 2 k turn to a 25 M context monster; anything linear flattens the interesting half entirely. Ticks are pure decades (fmtTok labels).
  • The count area is a NORMALIZED overlay. The alternative — a true secondary axis with its own grid — would have added a second scale to parse; the area instead reads as backdrop texture (subtle fill + soft glow), with exact numbers one hover away and faint max/half labels on the right edge. A per-family cumulative second view was considered and skipped: per-turn plus active-count already tell the story, and a third mode would dilute the section.
  • Honesty first. Points are COMPLETED turns only — turn_usage rows carry their token sums only at turn end, so a RUNNING session shows a breathing live dot instead (at its last point, or down at the time axis for a first turn still in flight), and a fired dispatch without a session appears as a dashed time-axis marker (amber ⏳, red 💀 for the presumed-dead class) — never fabricated data.
  • Interaction. Hover: nearest turn point wins (session title, brain, time, in/out/reasoning/total); away from the lines the area answers ("🐺 N Pinkies active · HH:MM"). Legend chips mirror the table's family highlighting: hover washes, click PINS one family (everything else dims to a whisper, the family glows), second click clears. Lonely brains glow strongest (v1.20.0): a brain of one gets the heaviest stroke of the highlight family and a doubled drop-shadow when pinned or hovered — a single thin line is close to invisible against multi-line brains otherwise, and its solo legend chip's swatch shines wider at rest. The window follows the ONE header selector (v1.26.0 — the graph's local 24 h / 3 d / 7 d segmented control is gone; "all" maps to 7 d here) and refetches ?hours=-scoped; refresh rides the normal 3 s poll cycle, and the chart re-renders only when data or the bucket grid actually changed — hover tooltips survive polling.
  • Graph and tables agree on which sessions exist (v1.20.0). The payload plots exactly the tables' universe: sessions with table membership (dispatch evidence) minus archived and hidden — so archiving or hiding a session grooms the graph too, and sessions the Sessions table's window rightly drops never surface as phantom graph brains. Forensics that motivated it: the 24 h graph once showed brains titled "Reply with exactly: TURN1-OK" that no table listed — six bare e2e scratch sessions (first-input titles, no dispatcher evidence, quiet for hours; they fell out of the tables' lookback prefilter) whose turn_usage rows the unfiltered graph happily plotted. The same membership filter now applies to series, legend and the Pinky-count buckets alike.

The data is one capped query over what is already there: turn_usage (started_at + computed_total_tokens with the input/output/reasoning split), bucketed activity spans for the count area (a session counts as active where it has a turn in progress — running turns extend to "now"), and the same lineage machinery as the tables (root/parent ids + titles, deterministic family color; a standalone session is its own brain of one). Caps keep the payload bounded on busy stations: the newest 200 turns per session inside the window, and beyond 60 series the quietest terminal series drop first — a RUNNING series never drops. Buckets size to the window (5 / 15 / 30 min for 24 h / 3 d / 7 d).

# The API behind the graph (read-only, like every observatory endpoint)
curl -s http://127.0.0.1:8767/api/usage?hours=168
# => {"generatedAt": …, "windowHours": 168, "fromMs": …, "toMs": …,
#     "series": [{"id": "sess_…", "title": …, "rootId": …, "rootTitle": …,
#                 "parentId": …, "parentTitle": …, "color": "#a78bfa",
#                 "role": "pinky", "running": false,
#                 "turns": [{"t": 1790250966867, "total": 24819606,
#                            "in": 24694922, "out": 124684, "reason": 0}]}],
#     "buckets": {"bucketMs": 1800000, "fromMs": …, "counts": [0, 1, 3, …]},
#     "pending": [ {"id": …, "state": "pending", "cwd": …, "model": …,
#                   "startedMs": …} ]}
# hours: 1..720 (default 168 = 7 days); the dashboard sends 24 / 72 / 168.

Fleet vitals: lane health at a glance (v1.82.0)

Stillness made legible — the deck distinguishes HEALTHY IDLE from actual standstill, so a resting fleet never reads as dead. A lane is a family root (a singleton is its own lane), and every lane derives exactly one state, worst-first, with a live last-alive age:

  • STUCK — expected-busy-but-silent: the dead bodies unexplained (a member's fired-but-silent fire, a bell stuck at its door, a presumed-dead turn, an unrecovered dispatch failure);
  • WORKING — in-flight work: a live turn, a steer riding a busy turn, a bell or fire still inside the delivery window, a commission whose session has not materialized yet;
  • RESTING — with the why — steers queued, an open commission awaiting its completion bell, no open commission at all (the healthy idle), or the honest unknown (never a guess).

The evidence table is ONE shared vocabulary (server payload, dashboard panel and tests all derive through it). The fleet chip in the status band (fleet: N working · M resting · K stuck) renders only its NONZERO parts — since v1.106.0 (#61, the display law) a zero count never renders and an all-zero fleet hides the chip entirely (the unread-badge law; the vitals panel stays reachable from the slim line's fleet stat); K > 0 goes LOUD (red chip, red slim-line alert, red panel lines), and the chip is a census control — click opens the lane-vitals panel, one health line per lane, worst first, each a jump into the lane's focus session. The browser tab title pulses the counts ((2▶ · 3🔔) …) as a passive background signal, clearing at zeros. Since v1.94.0 the ▶ count is liveness-honest (owner finding #57, the lying title): it derives from the server's classification verdict, never the raw running flags — a row flagged presumed-dead (stale turn marker over a quiet, evidence-free corpse) stops counting the moment the verdict lands, so the cheapest indicator cannot claim work that is not believably in flight; waiting-alive rows (the ⏳ class below) count. Semantics: N▶ = rows whose in-flight turn the observatory believes. The same round made the title wake with you: visibilitychange and window focus each trigger one immediate poll + title refresh (coalesced), so no stale count survives a tab switch or window return.

The windowed census (v1.99.0, finding #58 — "ein Blick, eine Wahrheit"): the stuck-lanes numbers follow the header time window. The census used to show the all-time stock while the tables listed only the selected window (24 h/3 d/7 d) — the header contradicted the tables for the same glance. Now every census surface (the fleet chip, the slim-line fleet alert, the lane-vitals panel) describes the SAME universe the tables list: a lane counts iff the tables would list at least one of its member rows, decided by the tables' OWN window test (windowKeeps — one authority, cited by both surfaces, never two definitions that can drift). The straddle reading matches the tables exactly: a presumed-dead row reads running and running rows never leave the tables, so a stuck interval that began before the window edge still counts at every window; a stuck state riding only on rows the window aged out (a bell stuck at a long-idle door) drops out of the count the moment its rows drop out of the tables. The surfaces label their window (· ◷ 24 h on the chip, "counted inside the ◷ 24 h window" in the alert's tooltip, "N lanes in ◷ 24 h" in the panel head) and name the fold ("K lanes outside the window — ◷ all shows the full stock") so the negative space is visible, never silently vanished. all preserves the all-time stock verbatim (no label, the full lane list), the bells tally stays live bell-ledger machinery, and the verdict alarms (starvation, idle fleet, master door) are server-side watches that stay window-free — the server census itself is untouched (the window is a display fold, never a redefinition of stuck-ness). Rig: make rig-pppp-window (15 legs, tests/rigs/pppp_window_cdp.py).

The starvation alarm (v1.89.0) outranks everything: lanes RESTING with an OPEN backlog (queued steers, pre-blessed commissions waiting) while the master is silent past the honest threshold (ZDO_STARVATION_MASTER_SILENCE_MS, default 20 min; escalating at 3×) renders as the deck's LOUDEST class — a dedicated starvation line FIRST in the status band (ahead of the master door itself), the slim line's top alert, the fleet chip's starving count in its critical state, red per-lane health lines restating their resting why as awaiting delegation and sorting first — and a BELL to the master session: the gen-N master line when the fleet has one (v1.89.1), once per episode, deduped, wiped the moment the condition clears. The signal WATCHES the master and never acts on him — no kill, no forced dispatch; the bell IS the intervention (ZDO_STARVATION=0 disables the watch).

The idle-fleet alarm (v1.95.0, finding #56) is starvation's INVERSE TWIN and the deck's loudest class: the whole fleet idle — working==0 per liveness evidence, every lane's queues, bells, deliveries and dispatches empty — inside an ACTIVE window (master intent: the 🔥 FULL-SPEED override or the pace regime; owner activity ≤ 2 h; or quota above the dormant floor). Nothing waits anywhere, yet nothing moves — the 18:05 twilight finding ("das darf nicht passieren"). The evaluation is the frozen §0 shared predicate — since v1.96.0 quoted BYTE-EXACT in the source as A⁵'s frozen German Koord-Schluss form (the same words A⁵'s sweep-side IDLEWATCH will cite; the English explanation lives beside, never inside, the citation, and a two-copy test pin makes citation drift unable to ship silently), composed from the existing machinery: the vitals census for (a), the per-lane queue detail (now carrying summed in-flight steers), bell/expectation/ledger stores for (b), and the window disjunction for (c) — quota unarmed, stale or at/below the floor reads FALSE (legitimate dormancy is never an alarm). The §0 debounce — ≥3 consecutive positive evaluations AND ≥60 s stable, evaluated per fresh sessions body — keeps every surface silent until it completes: during the warm-up the lane-vitals panel carries a dim ⏳ idle-window evaluation in progress line (the honest not-yet-red), then the alarm renders RED — a dedicated chip FIRST in the status band, the slim line's top alert, the panel head explaining itself (which lanes idle, the window basis, idle-since, what would clear it) — plus a DESKTOP POPUP once per episode: a dismissible top-center banner (the OS notification rides best-effort beside it), never re-shown inside the same episode, re-armed by a new one. The deck NEVER bells the master — A⁵'s IDLEWATCH owns that notification; this layer watches, it never acts. Rig seams: ZDO_IDLE_FLEET_N / ZDO_IDLE_FLEET_STABLE_MS (defaults frozen at §0's 3 / 60 s).

Quota guardian vitals: the pace controller's own view (v1.91.0)

The dispatcher's quota guardian — A⁴'s reserve floor (S1) and pace controller (S2, product since zcode-dispatch v1.0.60) — becomes a vitals instrument on the deck. The 🛡 quota-guardian chip in the status band (the vitals family, beside the quota chip; a twin line on the slim header) renders ONE state word, and a click opens the guardian vitals popover with the controller's own view:

  • ON-PACE — the quiet state (normal cadence, dim); an amber mode keeps the word and adds the advisory to the reason line;
  • PARKED-BY-GUARDIAN — the controller is holding work: the mode ladder at deep-amber/red, lanes in the revival parking lot (quota-death auto-parks and voluntary brain parks — which lane, why, the resume horizon: window reset, interim cadence or the wake steer), and pace-deferred admissions from the pending queue with their ETAs;
  • OWNER-RESERVE — the 🔥 FULL-SPEED override regime active (gates open, the reserve floor yielded) — distinct from both quiet states and from the deck's own soft ❄/🔥 toggle, which renders beside it;
  • STALE / NOT ARMED — honest absence: a pace state past its grace bound cannot throttle (the floor's own rules keep applying), and no state at all means the controller has not spoken.

Per window (5 h and 7 d) the popover carries the controller's own figures — burn rate (EWMA), the burst-bounded effective rate, the sustainable allowed-burn to the ceiling, the reset projection, the on-pace verdict — plus two derivations that self-label their math (the UUU lineage): the runway to the floor (remaining credits above the effective floor at the effective rate; "resets first" when the window refills before the floor binds; unknowable without a trusted rate — never a guess) and the reserve line (the discipline reserve vs the effective floor, per-window exists/spent and the margin in points and credits). The S3 predictive park renders its honest placeholder — not yet armed, awaiting A⁴'s S3; the reactive machinery above is everything that exists today.

The sources are read-only and documented (the dispatcher's docs-first B2 contract): pace-state.json (schema zcode/pace/v1), stream-revivals.json and the pending queue's pace stamps in the dispatcher config dir; the FULL-SPEED ladder is re-derived from the same readable truth the machinery resolves live (env ZDO_THROTTLE

config pace.full_speed > off, with the ZDO_FULLSPEED_FLOOR_PCT floor yield). The payload rides /api/quota's guardian object.

Round IIII folded the HHHH validator's two wording warts: an absent window rate now says "no published rate for this window" (the old "fewer than two samples" guessed a cause the machinery never states — a window right after rollover omits rates while its EWMA exists), and the margin line's credits phrase counts above-floor credits (remaining − floor, self-labeled "cr above it") instead of the raw remaining figure that over-claimed under that name.

The self-healing view + one door + the completed census (v1.92.0)

The drawer congress — three owner findings shipped together:

  • The view heals itself (#52). After a new observatory version installs, an open tab used to keep running the OLD deck until a manual reload. Since v1.92.0 the sessions poll's version field (served since round W) is a handshake: the page's first poll latches its loaded truth, every later poll compares. On mismatch the page auto-reloads when demonstrably idle — no open panel, drawer, dialog or popover, nothing being typed — or shows a subtle persistent "deck updated — click to refresh" pill (bottom-left). It never yanks mid-interaction: the busy verdict always wins, the pill's ✕ declines just that version (it returns on the next change), and a served version auto-reloads at most once per session chain, so a stale cache replay degrades to the pill — never a reload loop.
  • One door to ZCode, everywhere (#53, extending #49). The ONLY path from the deck into the ZCode desktop app is the interface's own ↗ ZCode buttons (the response modal's and the transcript drawer's top-right — one button per interface view, both behind a confirm whose Cancel genuinely aborts). Every notification-shaped surface — desktop-notification click landings (?session= deep links open the deck's own response modal), cards, rows, popovers, the dock, every keyboard shortcut — stays inside the deck, pure reading. A standing suite audit (tests/test_round_iiii.py) pins the inventory: exactly the two sanctioned callers, no template string ever reaching the launch path, the server's only open_session.sh spawn under /api/open. The dispatcher's own desktop notification still carries its "Open in ZCode" action button — that surface lives in the zcode-dispatch repo (notify_click.sh), is tracked for its lane, and is the one known remainder outside this tree.
  • The census completes (#54). The no-turn-state class — interactive-renamed rows, brand-new rows before their first turn ends, receiptless-idle stuck shapes — is a FULL census citizen: the ◦ residue chip counts, and filters like every other state family (state buckets OR, lanes AND — the finding #13 algebra; states + residue = total, always). The #54 annex made the chip render even at zero; v1.106.0 (#61, the display law) superseded that — zero hides everywhere, the residue chip included (see the display law).

The same round rewrote the README's 20 dead in-page anchors to the real forge-slug form (version dots → dashes, apostrophes → separators, em-dashes drop — the class behind #the-mobile-deck… jumping into the void) and added the standing class-killer: tests/test_readme_anchors.py carries a test-time slugifier mirroring the forge's rules (verified 83/83 against the live rendered heading ids) and asserts every in-page href resolves — future rounds included.

One message, one time: the anchor-timestamp alignment (v1.93.0)

Owner finding #31 (the observatory half; the dispatcher half is zcode-dispatch v1.0.54): a delivered steer showed TWO times — the chat row's timestamp (the transcript row's write moment) beside a hovertext reading delivered 13:47:56 over a row timestamped 13:47:58. Since v1.93.0 the row's displayed timestamp normalizes to the anchor's delivered-at when the anchor carries one, so the row and the #33 hovertext agree to the second:

  • The source is the anchor's own delivered token — the in-band [steer: sent HH:MM:SS±ZZZZ · delivered HH:MM:SS±ZZZZ …] record that travels with the message itself. No steer-ledger correlation, no cross-source races: the record the hovertext shows IS the record the row shows.
  • Exact by construction under the dispatcher's revised semantics (delivered := the transcript-write moment, stamped post-landing): the row's own stamp and the anchor's agree, so the normalization is invisible. Under an older dispatcher the row previously showed the write moment while the anchor showed the delivery moment — now both show the anchor's truth.
  • Honest fallback, both directions. No anchor → the row's own time, unchanged. An anchor whose delivered token is unreadable → the row's own time, unchanged (the record still hides and hovers).
  • The token is station-local HH:MM:SS±ZZZZ with no date — it combines with the calendar day nearest the row's own stamp, so a delivery crossing midnight resolves to the correct day, never a ±24 h lie.
  • The same round completes the #33 split for the live anchor shape: the dispatcher's position N of M · turn-state at send: … tail segments after the delivered token belong to the same record line and now hide with it (they rode visibly in the message text before — the split only recognized the minimal sent+delivered form).

The census — one table, every state, full control

The Sessions table and everything that shapes it: classification, the ONE window, unread, grooming (hide / archive / pin / favorites), the reading surfaces, and the lineage surfaces that make the fleet a family.

One table: Running + History merged (v1.65.0)

The owner's structural ruling after the long-bounce annoyance: a PINNED session used to live in "its" section — the moment its turn completed it hopped from Running to History, and everything the owner had anchored to that row's position bounced with it. Since v1.65.0 there is ONE table:

  • Status is a column, never a section. The merged Sessions table lists every active lane — running rows and terminal rows side by side, the badge in the Status column telling them apart. The old Running and History sections are gone as sections (Hidden and Archived keep their own collapsed rest states below).
  • Running pops — the ONE glowing blue badge on a pink-whispered row. The badge is THE signal, and since v1.77.0 (round TTT, superseding the v1.72.0 / S-47 pink — the static pink read as "red" beside the blue unread glow that then rode some running badges) it wears ONE style: the glowing blue (.badge.running, pinned by round OOO's suite leg). The pink branding lives on in the ROW instead: the two-row group carries a whisper of the same tint (tr.run td) plus the v1.73.0 workers-burn-bright glow below, so a running lane still reads at a glance against completed (cyan) and failed/error (red) — the badge stays THE signal, the row tint deliberately subtler than every badge fill — and the mint returns to meaning healthy/idle (background-active hint, canary, unread), no longer competing with a live turn.
  • The workers burn bright ❤️‍🔥 (v1.73.0 / S-47 glow, round PPP). A running row group GLOWS: a subtle animated pink/ember border radiance on the group's left edge — "this row is ALIVE and working" (the unread glow's activity-cousin; owner quote: "pinky-glowen ❤️‍🔥💎✨️ border"). Pure CSS (@keyframes liveglow, box-shadow only — no layout shift) on a STABLE class (tbody.tgrp.live, derived from row state at build time, never toggled after mount), and the table renderer is keyed and churn-stable (round PPP): a refresh only takes the DELTA — unchanged rows keep their DOM nodes, so the animation never restarts across the ~3 s re-render churn (the header/pager reconcile the same way; the morph falls back to one full rebuild when the structure is absent, producing the identical DOM). prefers-reduced-motion freezes the radiance at its calm frame (visible, never animating). Semantically distinct from the unread glow: that is the STATUS BADGE's blue reading-material pulse — this is the ROW FRAME's pink activity pulse; a running row with unread carries both, each on its own surface, and the running badge (the v1.77.0 glowing blue) composes underneath without wash-out.
  • The natural order floats running rows to the top. The default newest-first sort is a three-tier float: pinned rows first (in pin order), then running rows, then the rest — each tier keeps the selected sort. Any explicit column sort is honest (only the pinned float survives it); the Status column still sorts.
  • Filter chips replace the second table. ▶ running / ● unread / 🧠 brains — one click narrows the VIEW to that focus; clicking the active chip (or another one) changes it; clearing restores everything. Chips never mutate state — pins, favorites, read markers, archive flags are all untouched by a chip. Chip state rides the deep-link discipline: ?filter=running in the URL wins on load (a shared link lands on the focused view), chip clicks persist per browser and rewrite the address bar in place.
  • The dock is an anchor, not a filterable section (v1.73.0, round PPP). Filtering the table NEVER hides the favorites dock: it derives from the chip-UNFILTERED universe (the same full-table truth the header breakdown counts), so with a filter active every favorited session stays docked with its CURRENT status — items the filter excludes render dimmed with an explicit one-word marker: ⊘ <primary filter> plus a +N count when more filters join it (⊘ running, ⊘ running +3 — one resolved value in the deck's counts idiom since v1.106.0, never the glued fragment list #61 cured), matching items render undimmed, and clearing the chip restores normal rendering. The dock is a navigation anchor; it never participates in any filter.
  • The unread filter is lane-gated (v1.73.0, round PPP — the S-43 topology leg, owner finding 2). Filtering by "unread" lists only owner-conversation-lane sessions: worker pinkies and sub-brain children never qualify — their reports reach the owner through parent summaries (the dance model), even when he once steered them directly through the console. ONE predicate decides: the server's unread computation zeroes worker-shaped rows (role pinky; brain children with a parent) while masters (any session with children) and interactive dialogues keep their evidence-decided counts — and the header count, the launcher panel and the filter chip all read that same gated field (single source of truth, extended, never forked; the transcript halos apply the same predicate).
  • Pinning is table-global and position-stable. A pinned row holds its position across renders, refreshes and running↔terminal transitions: the pinned block is ordered by pin time, never by the active sort — a completing turn bumps updated, and the pin block does not care. Pin, watch the row survive a transition without moving.
  • Pagination is the one table's (page size 10/20/50/100/all, pinned rows always on page 1), and the slim-header quick stats stay coherent with the merged view: the ▶ running count, the ● unread launcher and the table's own count badge all derive from the same payload the table renders.
  • The header carries the status breakdown (v1.70.0 / S-45). Beside Sessions (N) the glance line renders ▶ running · ✓ completed · 🔔 unread · ✖ errored — compact chips; since v1.106.0 (#61, the display law) a counter renders only when its count is nonzero (zero hides, exactly like the unread badge; tooltips carry the exact definitions). One source of truth, pinned semantics: the running/unread numbers ARE the filter chips' own predicates, and the counts describe the FULL table in the active window — the chip filter does NOT narrow them (chips narrow the view; the glance stays the table's own truth, so a focused view and the header never disagree about the fleet). 🔔 unread rides the ONE owner-unread computation (owner-conversation lane only, S-43); ✖ errored counts the honest terminal failures (failed/error/timeout turn outcomes — a recovered rumble fire-episode, below, never counts).
  • The recycle-gradient stage badge rides the spanning line (v1.70.0). The 🧠 prep-successor / transition-recommended doctrine marker used to render inside the 62 px fixed-layout ctx cell, where its ~150 px nowrap box painted straight through the Models column (the owner's overlap finding). It now renders as a flex: none chip on the row's spanning line — the cwd text ellipsizes to make room, so the badge never overlaps a sibling cell at any width, the main row stays one line, and nothing clips. The steer-target line and the panel info strip keep the inline form (flow layout, safe).

The /api/sessions payload still ships the four arrays (running/history/hidden/archived) — the merge is a VIEW concern, so the lineage graph, the unread launcher, webhooks and every API consumer keep their raw arrays.

The census IS the filter surface (v1.77.0). Every counter chip (▶ ✓ ⊘ ✖ ◦ 🔔 🧠) is a CLICKABLE toggle; the (N) count clears all filters. Multi-click combines with an honest algebra — disjoint state buckets OR, orthogonal lanes (unread / brains) AND — documented in every tooltip, and the selection rides the URL (filter=). The buckets account for EVERY row (running + completed + errored + cancelled ⊘ + ◦ residue = total, always — cancelled renders in its own dim-slate visual, paired with the errored filter); since v1.92.0 (#54) the residue chip is itself a full citizen — counting and FILTERING the no-turn-state class like every other state family (v1.77.0's count-only span is gone; its just-when-nonzero RENDER condition is back, generalized — see the display law below). Running is ONE style — the glowing blue badge with the role-aware row neon — red stays reserved for genuine failed/error/timeout, and a stale running turn row on a not-running session renders its honest derived state, never running.

The display law: zero hides, one true word (v1.106.0)

Owner finding #61 (the 2026-10-17 regressions audit's R1 + R2, both cured here). The verdict, verbatim: "Chips bei 0 NICHT sichtbar — so wie die Ungelesen-Blase, so wie es sein soll." Zero-state counters HIDE, exactly like the unread badge — the house law for every glance surface that carries a count:

  • Zero hides, everywhere. Every census chip (▶ ✓ ⊘ ✖ ◦ 🔔 🧠) renders only when its count is nonzero — the quiet fleet no longer shows a permanent ▶ 0 ⊘ 0 ✖ 0 ◦ 0 strip. Counting and filtering are untouched (states + residue = total, always; the #54 census citizenship survives — a chip at 0 still filters when active, the (N) count click and the dock's ⊘ marker keep the selection named and clearable while its chip rests). The same law governs the fleet chip (only nonzero parts render; 0 working, 0 stuck and bells healthy — the bells' zero state — never show; an all-zero fleet hides the chip entirely) and the slim line's twins (▶ 0 running is zero-noise). This supersedes the MMM-era "all classes always present" and the YYY "all three numbers always" inheritance, and the #54 always-render annex: a stable shape does not beat a breathing line when the stable shape is four zeros.
  • One true word. The dock's not-in-filter marker (born v1.73.0 as ⊘ <one label>) had degraded under the v1.77.0 multi-select into one glued fragment string (⊘ running,cancelled,… — the owner's literal symptom). It resolves ONE value again: the PRIMARY active counter's word (canonical chip order — a set has no order, the display resolves one) plus a +N count when more join it (⊘ running +3) — the deck's own multi-select idiom, counts, the unread badge's voice. Marker text, tooltip and title all carry the same one resolved value; a glued state-word pair can never ship (the structural tripwire below).
  • The structural tripwire. One data-census value per element, and no status-line text, title or aria-label may ever match /[a-z]+[.,][a-z]+/ with two state words — pinned in the suite (tests/test_round_tttt.py: rendered-surface scan, every-selection label scan) and live in the CDP rig (make rig-tttt-chips, 12 legs over rich → quiet → empty fixture stages).

The takeover-discipline law (the class root, now written law). Both regressions shipped silently because the superseding round carried its own tests: the old test either migrated in the same commit (R1 — it never went red), was counter-pinned by a new contrary test (R2 — the flip was pinned FOR), or had never discriminated at all (the positive-half-only pin). From v1.106.0 on: when a round supersedes a contract, the OLD test must be proven RED under the new code (a tmp-revert of the flip) BEFORE it is migrated or counter-pinned — the migration must be forced into consciousness, never slipped through. The TTTT round applied the law to its own migrations (the IIII always-render pin, the YYY label pins and the slim-line zero pins were all proven red first; the transcripts live in the round dossier).

Honest classification + activity-anchored windows (v1.42.0)

The standing "renamed interactive lands in Running and then presumed- dead" bug is fixed at the root, and every time window now keys on real activity instead of the administrative time_updated clock (steer #28).

  • Dispatched classification demands corroboration. A pinned title (title_source='custom') alone no longer marks a session dispatcher-driven — the desktop's own RENAME writes the same title_source (live evidence: an interactive session renamed "oss-oo"), and such rows were adopted by the dispatched lifecycle: the rename bumps time_updated, the freshness grace flips them "running", and with no dispatcher ever driving them they read presumed-dead minutes later. Classification now requires at least ONE witness beyond the pin:
    • ledger membership — the sid is the SUBJECT of a dispatch record: stream-dispatches session_id (the created child), stream-bells child (spawn record), or usage-ledger session (billed through the dispatcher). Bell/notify parenthood alone is deliberately not evidence — a desktop session that once parented a manual dispatch stays interactive.
    • the dispatcher's own static-title stamp survived on the title (dispatcher_title_shape): the [🧠] delegation prefix, the <cwd-basename> · <text> pin (the dispatcher stamps the dispatch cwd's basename before the middot), a trailing (flash)/(glm) model tag (the pin's own tail and the manual dispatch routine's naming convention), or a #<seq> marker together with such a tag. Conservative by design: freeform renames ("oss-oo", "tablet-flash", even "oss-oo" in a checkout whose basename is oss-oo) match none of the shapes; turn rows are NOT a witness (interactive sessions have them too).
    • driving signals — a live dispatcher lock on the session's cwd (the driven flag) or the running child's own dispatcher linkage (live spawn / child-role digests), so a mid-flight dispatch is corroborated even before any ledger record exists.
  • INTERACTIVE-RENAMED rows stay visible, outside the lifecycle. A custom-titled session with zero witnesses keeps its place in the tables (the owner likes the overview) but never enters the running/ presumed-dead machinery: no tracker adoption (so no grace window, no completion webhooks), no running flag, no presumed-dead hint — a rename is an administrative write, not activity. The classification is honestly labeled: a quiet ✏️ renamed hint on the row (tooltip explains) and an ✏️ INTERACTIVE-RENAMED badge leading the drawer/ modal info. Interactive-renamed sessions are not dispatched-style nodes and do not enter the lineage graph (a subtree they parented roots itself, the same honest orphaning a window filter causes). A session renamed AFTER a real dispatch keeps its ledger witness and stays dispatched; an old dispatch whose ledger records pruned away keeps its classification through the title stamp, and even a hand-renamed one degrades only cosmetically (terminal rows sit in History either way — no lifecycle misclassification).
  • Time windows key on the activity anchor. The tables' and the lineage graph's day windows (24 h / 3 d / 7 d) now admit a session when its ACTIVITY anchor — the last message/part/turn_usage timestamp, derived by the same memoized sessions_activity_ms seam the busy signal uses (v1.27; extended to union turn_usage, no new scan) — falls inside the window. session.time_updated is an administrative clock: a rename, title pin or archive toggle bumps it and can no longer re-admit a stale session to a 24 h view. Rows without any recorded anchor fall back to created (a brand-new session belongs in the window), never to updated; running, pinned, favorited, docked and open-panel sessions still never drop out from under the owner, and the graph's render signature carries the anchor so anchor-only changes re-render. Window consumers audited: the tables' windowKeeps (History/Hidden/Archived + deep-link rescue) and the graph's lgWindowKeeps share the anchor semantics — and since v1.103.0 (UUUU) they share the whole keep-set through ONE core (windowKeepsCore; the graph's pre-UUUU twin exempted only running+favorite, so a pinned aged-out row stayed in the tables but dropped from the graph); the usage graph and quota gauge were already keyed on real usage rows, not time_updated, and are unchanged. (The lineage REPLAY view keeps its own as-of evidence model — its bounds note that titles reflect today's state; the live view is where the steer pointed.)

Dual-stamp-shape tolerance: the ledger is the backbone (v1.59.0)

ZCode 3.14.5 introduced a new stamp shape: during a dispatched session's first turn, the app-server overwrites the pinned custom title with a first_input raw-prompt title (the prompt's first line, cut at 60 chars with a trailing ...) — even with the dispatcher's titleGenerationEnabled=False. The dispatcher's finish re-pin (or its sweep's dead-dispatcher reconciliation over the ledger's static_title) restores the old-grammar stamp afterwards, but a dispatch that dies inside the window keeps the raw-prompt title forever. Mid-window the stamp, its [🧠] prefix and its (glm) tail are all GONE — the trip that fired the contract canary's stamp.overwrite-window check (dispatcher v1.0.33).

  • The classification does not blink. Every stamp-shape consumer was audited and now provably tolerates all three grammar classes — OLD (<project> · <text> (<model>), [🧠] prefix), NEW (the window shape above) and NEITHER (any freeform title): the static-title shape witness, the VV-3/UU title derivations, the YY family album, the delegation-role glyphs and the worktree-prefix strip (tests/test_dual_stamp.py).
  • The ledger/evidence backbone carries the window. The window shape carries no structural marker, so the shape witness deliberately rejects it (matching raw prompt text would swallow every interactive first_input session). Window-shape sessions classify through ledger SUBJECT membership — which every dispatcher-created session holds from creation — plus driving signals and the album: never INVISIBLE, never PHANTOM-RUNNING. A witnessed-but-idle stuck window session honestly reads idle.
  • Both grammars stay valid forever — old sessions keep old stamps, and the old-grammar stamp remains a first-class classification witness (the insurance for sessions every capped ledger already forgot).
  • Hermetic steer spawns (the AAA validation's F1 fold): an unobstructed steer's dispatcher child now always rides its own DEFAULT busy grace — an inherited station ZDO_RUNNING_GRACE_MS (the observatory's own tuning surface) used to leak into every spawned child and silently retune its self-queue horizon.

Authoritative dismissal: no zombies across both queues (v1.51.0)

A live owner report (steer #47, 2026-10-08): a steer whose delivery had failed was dismissed and retried — and DELIVERED anyway. The round-V dismissal covered the observatory's own queue representations, but a steer can hold SEVERAL pending copies at once: the failed original's still-queued sweeper entry, retry-created copies (same words, fresh send timestamp, a NEW synthetic obs- child id), and late resurrected copies (the dispatcher's exit-3 self-queue re-uses the child id). Dismissing one representation left the others alive — the zombie. v1.51.0 makes dismissal authoritative across ALL of them:

  • The fingerprint sweep — POST /api/steer/dismiss (body: id, optional text, parent, keep) removes the exact synthetic child AND every queued observatory steer of the same parent session whose prompt carries the same CONTENT FINGERPRINT (a whitespace-normalized SHA-256 of the steer text — robust against timestamp drift by design, so retry copies die with the original; two deliberately identical sends share a fingerprint and die together, the honest reading of "dismiss these words"). The prompt text comes from the request body (the UI's preserved words) or the steer's own prompt file under the steer dir.
  • The locked rewrite — the sweep mutates stream-pending.json under the dispatcher family's own ledger discipline: flock(LOCK_EX) on the .lock SIDECAR (the file itself is swapped by os.replace), a salvage read (a torn tail falls back to the valid raw_decode prefix; non-dict entries drop — the file heals on the write), a caller mutation, then a UNIQUE tmp (pid + ns) + fsync + atomic replace. Live dispatchers serialize on the same sidecar lock, so a concurrent dispatcher write during the sweep loses nothing (race-tested with a ledger-disciplined writer under repeated sweeps).
  • Dead letters stay truthful — every removed copy is recorded in stream-history.json (dropped: true, dismissed: true), never a copy that had already delivered. Both dismissals in the dismiss-retry-dismiss flow land as their own records.
  • The dismissal guard — removed (and honestly: in-flight or traceless) children stay guarded for a TTL window (ZDO_DISMISS_GUARD_TTL_MS, default 15 min). Each watcher pass re-sweeps the queue for entries under a GUARDED child: the exit-3 self-queue after a mid-delivery dismissal and a racing sweeper's resurrected entry die BEFORE any lane can deliver them. The guard is keyed by child id, never by fingerprint — a retry's FRESH child is structurally out of scope.
  • Retry re-queues exactly ONE fresh representation — the UI's Retry fires the same authoritative sweep for the OLD steer id with keep naming the fresh child: replace, not duplicate.
  • The mid-delivery boundary, honestly — a steer already being delivered (a live dispatcher holds it) cannot be recalled: the API answers inFlight: true with a "cannot recall" note, nothing is killed mid-flight, and the chip STAYS as a 🚚 delivering — cannot recall chip with the ✎ still armed; when the delivery ends busy and re-queues, the guard kills that copy and the chip settles to the not-delivered state with Retry available.

Genesis-born forever: the family album (v1.52.0)

A live owner report (steer #48, 2026-10-08): the PDrive brain showed while running (lock-driven), then VANISHED from every bucket once idle. Root cause: genesis-born sessions carried the dispatch-side stamp bug (title_source='first_input' + task_type='interactive'), so the v1.42.0 classification found neither the stamp-shape witness nor a title_source match — the session fell between every stool. v1.52.0 makes the principle structural, independent of the stamp fix (dispatch-side, A3-5): a genesis birth is a first-class fleet member — visible FOR LIFE. Nobody disappears from the family album.

  • The permanent lineage witness — classification gains a sid/cwd MEMBERSHIP set: a session whose id or cwd is named by the genesis redirect journal (every dedicated worktree it ever created — the created HOME, never the requested interactive checkout), a genesis result file (sessionId + cwd), or the dispatch/bell/usage ledgers (the v1.42.0 evidence legs) classifies dispatched-lineage PERMANENTLY, regardless of title state. The membership passes BOTH gates that used to drop the buggy-stamp shape: the SQL prefilter (idle first_input rows were never even fetched once past the 1 h lookback) and the dispatched-evidence gate. Members list in History at minimum; running state stays driven by the real turn/lock evidence as for every member; a member the owner hand-renamed stays dispatched-classified (the interactive-renamed class remains for genuinely non-dispatched custom titles — no widening).
  • The learned album never forgets — every membership source is capped and prunes (the ledgers and the journal each keep 200 records; the live repro's sid had already fallen out of ALL of them). The observatory keeps its own learned copy (lineage-album.json beside the install state, env ZDO_LINEAGE_STATE_FILE), merged write-only-on-growth under a lock, so pruning every source cannot orphan a member — the album survives observatory restarts and journal truncation alike. (YY-F2, v1.54.0: the learned set is BOUNDED — per-kind caps with oldest-learnedMs eviction, far above every capped source, and the SQL prefilter joins in chunks so one IN() never carries the whole member list; the membership sources stay the truth, the learned set is pruning-insurance.)
  • Cheap by construction — the membership set is built once per poll-cycle rebuild; the source key fingerprints the learned file, the journal and the genesis result files (the v1.27 discipline), so a birth or a prune flips the key on the next poll and nothing else rebuilds.
  • Exact-match joins only — both sides of the join normalize a trailing slash (the dispatcher's result cwd carries one, the DB and journal do not — one home, one key); a cwd that merely RESEMBLES a journal home never matches, and the journal's requestedPath (the owner's interactive checkout) is deliberately not membership.
  • Deep links land for members — with the session permanently in the payload, every artifact mentioning its id resolves: clicking a leftover quota-lane row from its active time opens the session, and the "outside the lookback or not dispatcher-driven" toast can no longer fire for a genesis-born session (the deep-link rescue widens the view window to "all" as before).
  • XX-F02 folded (dead-letter history appends) — the fast-lane history writes now ride the same locked seam as the queue rewrite: flock on the stream-history.json.lock SIDECAR (the one live dispatchers serialize on), torn-tolerant salvage read (a torn tail heals instead of wiping the prefix), the delivered-child dedupe, and a UNIQUE tmp + fsync + atomic replace — race-tested against a real dispatcher-disciplined appender process (zero lost or duplicated records).

One window for everything (v1.26.0)

ONE selector in the page header — show 24 h / 3 d / 7 d / all — drives the History, Hidden and Archived tables AND the usage graph: one mental model, persisted per browser (localStorage, same pattern as follow-output). Default: 3 d. The choice means last activity — keyed on the honest activity anchor (the newest message, part or turn-usage stamp of the session, v1.42.0 — never the mere row touch): sessions without activity inside the window leave the tables until you widen them again. Three honest exemptions — Running always stays, sessions you actively keep (favorited, docked, or with an open panel) never vanish out from under you, and the two brain toggles below can exempt the orchestrators. "all" is full history everywhere — tables and lineage graph alike (since v1.103.0 the graph rides the very same keep-set core as the tables; the original v1.26.0 round shipped with the graph falling back to 7 d there, which turned out to be the USAGE chart's rule misread onto the graph — the 7 d cap lives on the usage sparkline only, and the header tooltips scope it by name).

Two toggles beside the selector — 🧠 brains and 🧠↳ sub-brains — are OFF by default (everything follows the day filter). When ON, a toggle EXEMPTS that class from the window filter entirely — brain-typed roots / child-role brain delegations (the v1.22.0 role data) stay listed with their full history visible regardless of the selected window, even at 24 h. The point is exemption, not filtering: an orchestrator is infrastructure, not a row that ages out.

the header window selector at 24 h — old rows left, brains exempt via the toggle

Fixture data, captured at 1177 px inner width: at 24 h the www pinky's row (last active 30 h ago) has left History, while the 🧠 brains and 🧠↳ sub-brains toggles keep the delegation nodes listed with their full history — Running never moves.

animation: switching the ONE window from 3 d to 24 h and back to all — watch the Sessions table's non-running lanes shed and regain rows while running rows never move

Watch for: the History count dropping as old rows leave at 24 h, then everything returning on "all" — one dropdown, tables and graph together.

Two more header behaviors ship with the same round: the title header is sticky (scroll down to the graph and the title, controls and window selector stay visible — glass tint + backdrop blur, never covering a panel or the dock), and the title itself wears the favicon's rainbow gradient on "zcode-" and "observatory" ("dispatch" keeps the product mint).

Hiding vs archiving (view vs ZCode)

Two different concerns share one word family; the observatory keeps them apart:

  • Archive (History/Hidden rows) writes the desktop's OWN task index — a ZCode concern. The session disappears from the ZCode sidebar's active list too, and reappears in the sidebar's archive.
  • Rows you deliberately keep close — pinned or favorited — carry Hide/Archive like EVERY row since v1.84.0 (the early keep-close exclusion was reversed by owner order): full control, no orphans, and the departure itself auto-clears BOTH keep-close flags (pin AND favorite) plus the pin block's order entry — unhide/unarchive never resurrects a stale float or dock seat.
  • Hide (Sessions-table rows) writes only observatory-state.json — a VIEW concern. The session moves to the Hidden section (between History and Archived, collapsed by default — hiding is about getting things out of sight), ZCode never learns about it, and the row keeps its full toolkit (response/transcript, family coloring, pin state) plus Unhide (returns it to its natural section by turn state) and Archive. Hidden sessions leave the favorites dock while hidden; unhide brings the row back (a favorited session re-docks on its next minimize/favorite action).

Both are grooming paths for the usage graph too (v1.20.0): the graph and its legend plot exactly the sessions the tables list, so archiving or hiding a session clears its lines from the graph and its brain from the legend — archive it away or hide it away, the graph follows.

curl -s -X POST http://127.0.0.1:8767/api/hide \
  -H 'Content-Type: application/json' -d '{"id": "sess_1a2b…"}'
# => {"ok": true, "id": "sess_1a2b…", "hidden": true}
curl -s -X POST http://127.0.0.1:8767/api/unhide \
  -H 'Content-Type: application/json' -d '{"id": "sess_1a2b…"}'
# => {"ok": true, "id": "sess_1a2b…", "hidden": false}

Archiving sessions (native ZCode archive)

History rows carry an Archive button, Archived rows an Unarchive button. Both drive the desktop's OWN archive store — the tasks table of the native task index ~/.zcode/v2/tasks-index.sqlite (ZDO_TASKS_DB to override) — which is exactly what the ZCode sidebar's archive reads and writes. The observatory's ONLY write outside its local state files is one parameterized statement, mirroring the product's semantics (verified by static analysis of the installed desktop bundle, 2026-09-24):

UPDATE tasks SET archived=1  WHERE workspace_key=? AND task_id=?  -- archive
UPDATE tasks SET archived=0  WHERE workspace_key=? AND task_id=?  -- unarchive

Semantics, as the desktop itself implements them:

  • The manual archive (updateTaskState patch {archived: true}) rewrites the row but keeps its existing updated_at — the observatory writes only the flag, so the row keeps its last-activity timestamp either way.
  • The auto-archive (archiveStaleTasks) runs the very same single-column UPDATE … SET archived = 1 shown above.
  • Archived-section membership is archived=1 AND deleted=0 — the owner's real archive, identical to the ZCode sidebar's, including desktop chats the dispatcher never drove.
  • Nothing else is touched: no group rows, no meta_json rewrite, no session-database writes (the old session.time_archived mechanism was removed — see the migration note below).

The Archive button opens a small confirm ask first (Sure to archive? — Enter confirms, Esc or a backdrop click cancels); archiving is reversible, so the ask stays lightweight. Guards: archiving a session with a running turn is refused (HTTP 409), and a locked task index surfaces as a friendly HTTP 503 toast. A session without a native task row yet no longer refuses — archiving is self-seeding (v1.83.0): the task-index row is derived from the session's own DB row (directory as workspace key, title, stamps) and seeded in the SAME transaction as the archive flip; the one honest 409 left is a session row without derivable data.

Pinning sessions

Every row — the Sessions table, Hidden, Archived — has a small 📌 toggle. Pinning is observatory-local state (observatory-state.json next to the installed script, like the state file pattern of old): it never touches the ZCode database and survives observatory restarts. Since v1.65.0 pinning is table-global and position-stable: a pinned row floats to the top of the ONE table and HOLDS its position across renders, refreshes and running↔terminal transitions — the pinned block's order is pin-assignment order in STATION state (pinOrder, v1.77.0: server-side first-sight adoption, persisted across browsers and restarts, manually reorderable by drag-drop via POST /api/pin-order), never by the active sort, so a completing turn (which bumps the row's updated) cannot reshuffle it. Within the unpinned rest, rows follow the table's currently selected sort (name / status / last activity), and pagination respects the pinned-first order — pinned rows always sit on page 1. (Pre-v1.65.0 pinning was section-local: a pinned running session hopped to the top of History the moment it completed — the long-bounce annoyance the merged table kills.) Since v1.83.0 the float itself is togglable (round ZZZ): the header band's 📌 pin effect control (station state, POST /api/pin-effect {"on": bool}) deactivates the float — pinned rows then sort NATURALLY by activity (still rendered, still marked) while the block's persisted order waits untouched for re-enabling. And departing rows clean up after themselves: hiding or archiving a pinned/favorited row auto-clears BOTH flags (pin AND favorite) plus the block's order entry.

curl -s -X POST http://127.0.0.1:8767/api/pin \
  -H 'Content-Type: application/json' -d '{"id": "sess_1a2b…"}'
# => {"ok": true, "id": "sess_1a2b…", "pinned": true}
curl -s -X POST http://127.0.0.1:8767/api/unpin \
  -H 'Content-Type: application/json' -d '{"id": "sess_1a2b…"}'
# => {"ok": true, "id": "sess_1a2b…", "pinned": false}

The /api/sessions payload exposes the flags per session ("pinned": true/false, "archived": true/false, "favorite": true/false, "hidden": true/false) plus the busy-state facts that drive the row hints ("activityMs", "steerQueue": {"inFlight", "queued"}, "bell") — see Three states.

Favorites dock: minimized chats, always at hand

Every response modal and transcript panel has a – minimize control next to its ✕: minimizing turns the panel into a compact task bar in a dock at the bottom of the page — title (truncated), live status dot, unread badge, expand, remove. Dock items toggle: clicking the item (or its ⤢) of an already-expanded session minimizes it again — one click swaps chats in either direction, the fast-hopping rhythm; a click on a docked item reopens the session's response interface (the primary work surface — the same surface a table-row click opens, and the exact state a page reload lands on, v1.21.0). The open panel's header carries a segmented Interface | Full transcript view toggle, so the other depth is always one click away — a favorite that once opened the full transcript can never get stuck in it anymore (the pre-v1.21.0 last-shown-view memory did exactly that; it is gone).

The dock rides above every panel and stays visible while a modal or the transcript drawer is open (both keep a bottom margin so nothing hides behind it): clicking another dock item switches directly — the open surface closes and the target session's panel opens in the same click, no minimize-then-open dance. Clicking ANYWHERE else in the dock bar (not on a card) minimizes the currently expanded panel — the bar itself is a minimize affordance beside the card-toggle and the backdrop click.

The core is favorites (★ toggle on every row and inside every panel): a favorited session is always present in the dock — persisted in the same observatory-local state file as pinning (observatory-state.json, key "favorites"), never touching the ZCode database — and survives page reloads and closes:

  • ✕ (or Esc, or a backdrop click) on an expanded favorite panel just minimizes it back to the dock — a favorite is never fully gone.
  • ✕ on a non-favorite panel closes it fully and removes its dock item.
  • Esc never unfavorites (round-M invariant): it does exactly one thing on any panel — minimize. Favorites go back to the dock, non-favorites close as configured; the favorite check consults the last server truth (plus the optimistic mirror), so a mid-refresh race cannot route a favorite through the plain-close branch either. Unfavoriting is the explicit star / dock-✕ toggles' job alone.
  • Unfavoriting (row ★, panel ★, or the item's ✕) removes the dock item — unless the panel is currently open, which stays open.

The star's SELECTED state is session-scoped (v1.34.0): both panel headers — the interface and the full transcript — render the same server-truth state, re-synced at every surface switch, and every favorite/pin/hide POST drops the cached sessions response so the post-toggle refresh reads post-toggle truth. Before v1.34.0 a post-toggle refresh could race the cache's source-key micro-cache: the state file itself was already fingerprinted at ns-mtime precision, so staleness never spanned the full 8 s body TTL — the race window was the key snapshot (default 250 ms; the TTL only its outer bound, wording corrected v1.34.1) — and under a healthy-but-quiet push feed (where the main poll idles) the star you just toggled could revert in-page or show its old state on the other surface.

Concept hygiene: pinned orders the table (floats a row to the top of its section); favorited docks the session (bottom-bar presence). Two independent flags — pin without favorite, favorite without pin, both, or neither; neither ever moves a session between Running/History/Archived.

Dock items are live on the existing poll cadence: the status dot tracks the turn state, and the unread badge is the session's owner-unread count (v1.61.0: the same ONE computation the header launcher sums — brain→owner words since the owner's last read; expanding the item marks the session read and clears it). The soft glow rides reading material now, not doorbells. Works identically for Running, History and Archived sessions; the bar hides itself entirely when empty and scrolls horizontally at narrow widths.

favorites dock with one expanded favorite

animation: dock rhythm — expand a favorite into the interface, minimize it back, switch to another

Watch for: one click expanding a docked chat into the full response interface, the minimize returning it to the bar, and a second click switching sessions directly — the fast chat-hopping rhythm.

The favorites dock (fixture data, captured at 1177 px inner width): one favorite — the 🧠 BRAIN-CHILD docs brain, its info strip carrying the role badge and the clickable parent reference — expanded into its response interface with the segmented Interface | Full transcript toggle riding its header, while the running gate and the question-pending deploy pinky stay docked as live cards (status dot, star) along the bottom bar.

curl -s -X POST http://127.0.0.1:8767/api/favorite \
  -H 'Content-Type: application/json' -d '{"id": "sess_1a2b…"}'
# => {"ok": true, "id": "sess_1a2b…", "favorite": true}
curl -s -X POST http://127.0.0.1:8767/api/unfavorite \
  -H 'Content-Type: application/json' -d '{"id": "sess_1a2b…"}'
# => {"ok": true, "id": "sess_1a2b…", "favorite": false}

The /api/sessions payload exposes the flag per session ("favorite": true/false) alongside "pinned".

Named sessions: spawn names + the sanctioned rename ✎ (v1.55.0)

Steer #42: sessions get explicit names, wired to the title contract zcode-dispatch v1.0.30 documented (--title on creates, --retitle SESS_ID NAME standalone). Two affordances, one law: the stamp grammar is preserved BY CONSTRUCTION — a named or retitled title is still <project> · <middle slot> (<model>) (the [🧠] prefix, project and model tag never move), so every classifier keeps matching and a renamed row never falls out of the fleet.

  • D1 — the NAME field (🌱 dialog) — an optional input between cwd and model: a filled name rides the spawn as --title verbatim (the review step shows exactly what the pinned title's middle slot will carry); an empty field applies the QQQ auto-name <repo-basename> · gen-<N> server-side (the family's next generation count — and the field PREFILLS that proposal once the cwd settles, editable; a proposal, not a cage). The rail mirrors the CLI's own shape before anything stages: one line, ≤ 55 code points (the slot cap); the name rides one argv word, hostile metacharacters inert (the suite + the G1 rig prove it). The name also wins over the default when doctrine is opted out, rides the genesis queue (titleName on the entry, threaded by the pump), and the 202 payload carries it back.
  • D2 — the ✎ rename on session rows — a quiet ✎ beside the row title opens a small modal prefilled with the title's current middle slot (Enter commits, Esc cancels, a blur that leaves the panel cancels — the buttons sit inside so clicking them never counts as a cancel). The commit runs POST /api/session/<sid>/retitle {"name": …} which drives the dispatcher's standalone dispatch.py --retitle <sid> <name> — never a raw session-DB write (the stamp-contract law): the swap goes through the canonical pin helper, and the observatory's only job is argv-list plumbing. The honest edges surface VERBATIM as 409s with the dispatcher's own reasons: a RUNNING dispatcher owns the session (retitle after it completes), and a current title that is not a COMPLETE stamp (interactive/user titles, overflowed 90-char stamps) cannot carry the swap. The modal stays open on a refusal so the name can be corrected; the retitled row reflects within the normal cadence (the commit nudges one immediate resync).

The G1 rig's live catch — the spawn-argv fix. Validating D1 end-to-end against a REAL systemd-run scope exposed a v1.54.0 regression: systemd-run (255) textually EXPANDS braced parameter forms in the command line, so VV-3's "${10}"/"${a[@]}" script forms evaluated to EMPTY strings (journal: "Invalid environment variable name evaluates to an empty string: 10, a[@]") and every post-v1.54.0 genesis spawn exec'd dispatcher "". The fixed script carries ZERO braced forms — env-file guard plus exec "$@" only — and the builder composes the flag pairs (--cwd/--model/ --child-role/--prompt-file/--out, then --notify-parent/ --reasoning/--provider/--title each omitted entirely when empty): same semantics, no shell-side conditionals, hostile values still one argv word each.

Folded from the v1.54.0 validation: WW-F1 — the interrupt's pass-2 SIGKILL now re-verifies every CHILD's cmdline hash against its own pass-1 hash (a dead child PID reused by a different process between the TERM and KILL rounds is recorded — KILL withheld — and never signaled; previously only the root was re-proven). WW-F3 — the VV-4 brain-brief pointer names THIS instance's serving port (the constants keep the default-8767 strings; --port 9000 newborns get a live :9000 URL, and a queue re-feed after a port change still finds the mission through the shared brief marker).

# Birth a NAMED pinky (the name rides --title; empty titleName =
# the auto-name <repo-basename> · gen-<N>)
curl -s http://127.0.0.1:8767/api/session/create -X POST \
  -H "Content-Type: application/json" --data-binary '{
    "cwd": "~/.zcode/tmp/oss-oo/website/worker-1/",
    "model": "GLM-5.3", "role": "pinky",
    "prompt": "Ship the release round.",
    "titleName": "Release round" }'
# => 202 {"ok": true, "titleName": "Release round", …} — the session's
#    pinned title: <project> · Release round (<model>)

# Rename a landed session through the SANCTIONED path (the ✎ under
# the hood — prefix/project/model tag stay, classification too)
curl -s http://127.0.0.1:8767/api/session/sess_…/retitle -X POST \
  -H "Content-Type: application/json" \
  --data-binary '{"name": "Hotfix round"}'
# => 200 {"ok": true, "retitle": {"sessionId": "sess_…",
#          "previousTitle": "… · Release round (glm)",
#          "title": "… · Hotfix round (glm)"}}
# 409 honest edges (verbatim from the dispatcher):
#   a RUNNING dispatcher owns the session — retitle after it completes
#   the current title is not a COMPLETE stamp — the swap needs the
#     full '(tag)' tail

Reading a session without restarting ZCode

the full-transcript drawer: honest message classes, the steering console at the bottom

The full-transcript drawer on the mission masterbrain (fixture data, captured at 1177 px inner width): owner and agent cards with reasoning collapsed behind toggles and expandable tool parts, the three delivered 🔔 doorbells calm (v1.61.0: machine traffic no longer glows), harness noise (todo reminders, system notices) collapsed to one-line chips — and the steering console at the bottom. The ZCode desktop app keeps an open conversation in memory — a 🔔 doorbell that rang into an idle-but-open session only shows there after a full app restart (which kills running turns). The observatory closes that gap by reading the shared session DB live, in two depths:

  • Response interface (the modal — the owner's primary working surface) — clicking anywhere on a session row in any section (Running / History / Hidden / Archived, wherever no explicit control or link sits) opens the response modal with the steering console (v1.23.0 owner redesign: show-response behavior on the whole row — the surface you steer, answer and watch from is one click away at row size). The modal shows the session's recent conversation — the last 15 messages (user + assistant interleaved, oldest → newest, opened at the tail) rendered exactly like the transcript panel: role labels + timestamps, user vs assistant accent cards, reasoning collapsed behind a toggle, tool parts as expandable ▸ ⚙ <name> affordances (below), and — v1.61.0, round FFF — the owner's READING MATERIAL glowing: owner-unread messages (brain words newer than his last read, the same discriminator behind every count) render with a mint halo and a ● new chip, while 🔔 stream doorbell lines stay calm (visible, quietly blue-edged). The modal is the primary work surface, so it takes the room it needs: ~88vw wide (capped at 1560 px for line readability) with slim margins to the window edges — the full transcript drawer is widened to match (1200 px cap). Its position is state-invariant by construction: the width is viewport-capped (never content-driven), so it stays exactly horizontally centered whatever the steering console below shows (tail open, collapsed, or absent), and it is bottom-anchored — flush-ish, a slim ~14 px above the favorites dock when one is open (or the window bottom otherwise), visually tied down to the dock instead of floating mid-screen with dead air underneath. Close via ✕, ESC, or a backdrop click. The modal — like the transcript drawer — takes focus on open and locks the dashboard's page scroll while shown, so the mouse wheel always scrolls the conversation, never the page behind. The footer shows the message-count context (showing the last 15 of 197 messages); the header's segmented Interface | Full transcript toggle is THE way to switch surfaces (v1.26.0 retired the footer's redundant "Open full transcript" button — one switch, one place; v1.21.0 removed the generic per-panel copy buttons the owner never used; a small 📋 icon per chat message, left of its timestamp, copies that one message's text instead). While a modal is open on a running session it re-polls every 5 s and appends new messages; a quiet session parks that poll — but quiet is a moment, not a contract: the dashboard's 3 s refresh watches the session, and any new activity (a doorbell ringing in, a turn starting, a queued or in-flight steer, a pending question) resumes the modal's poll at once, so an OPEN panel live-updates exactly like the dock badge (typed-but-unsent prompt text and scroll position are never touched by the refresh — the input lives outside the re-rendered conversation). A session with no assistant answer yet opens on a friendly "no output yet" state — the row click never dead-ends. The modal header carries a compact info strip right of the title (one row, never wrapping): the lineage role as a badge — 🧠 BRAIN (root session, accented in the family color), 🧠 BRAIN-CHILD (delegation node: a child dispatched with --child-role brain that runs its own orchestra — cyan, v1.22.0) or 🐺 PINKY (worker child) —, for children a clickable → ⟨parent title⟩ reference that jumps the modal straight to the parent's panel, the context-window utilization (the steer line's math, echoed as NN% ctx with the full numbers in its tooltip), turns + total tokens (12 T · 1.2M tok, exact in/out split in the tooltip) and the session age. The strip sheds its tail chips gracefully at narrow widths — age below 1000 px, ctx and turns/tokens below 560 px — and everything dropped survives in the tooltips. The full transcript drawer wears the same strip in its header (v1.21.0 — parent references there open the parent's own transcript), and both panels also carry the steering console and the follow-output toggle described in their own sections.
  • Transcript panel (full history) — every row carries a secondary Transcript button (plain mini styling beside Hide/Archive — the row click is the primary action now, so nothing in the button cluster outshines it; the slim 📄 icon is the space-saving variant, the whole action cluster going icon-only below 560 px and on any row whose cluster would wrap — a pending ❓ question badge steals enough width that full labels would fold the row into two lines, so the cluster degrades to icons there too, v1.21.0; labels live in the tooltips) that opens the side panel with the whole readable history, capped server-side.

Poll resilience: a failed poll (transient SQLITE_BUSY on the shared DB while a turn completes, a server hiccup) never blanks an open panel — the last good content stays and a subtle "reconnecting…" hint floats over it; only after three consecutive failures does the hint gain the error accent and a Retry button. The next successful poll recovers automatically. Server-side every shared-DB read runs behind a 5 s busy timeout with one retry-on-busy, and an open panel tracks its session across section transitions (running → history) without invalidating.

Expandable tool parts: the ⚙ <name> one-liners of a turn's tool calls are expandable exactly like the reasoning affordance — click "▸ ⚙ TodoWrite" and the call's input (and output when present) unfolds, pretty-printed, monospace, scrollable, height-capped. Long payloads truncate with an "… N more lines" notice and a copy button that carries the full text — never silent loss. The expansion state survives re-polls like reasoning's does.

The intended reading path for a desktop notification: the notify-center excerpt (~100 chars, not expandable) → the matching observatory row — one click opens the response interface at the tail, and the row's Transcript button reaches the full history — no ZCode restart anywhere in the chain.

Honest message classes (user · agent · todo update · system · doorbell)

Transcript views classify what wrote a message instead of trusting the raw role — harness-injected user messages never render as owner input. The classifier (message_kind in observatory.py) uses the messages' native semantics fields first and stable text prefixes as the documented fallback:

class renders as detection
user green "user" card semantics.origin = real_user (or no semantics and no injected shape)
agent cyan "agent" card assistant role
doorbell glowing blue bell card text starts 🔔 stream doorbell
todo muted "todo update" card semantics.kind = todo_reminder; fallback prefixes The TodoWrite tool hasn't been used recently, Here are the existing contents of your todo list
system muted "system" card semantics.origin agent_runtime/system (system reminders, background <task-notification>s, fork notices, Called the … tool echoes); fallback prefixes <system-reminder, <task-notification>, This session was forked from a previous session, Called the

Honest steer attribution (v1.101.0, finding #60; window and binding honored per the v1.102.0 audit): a steer-delivered message — which rides the same real_user shape as owner input — names its PROVEN source instead of generic user. The evidence chain never guesses: the message's steer anchor pins the delivery, the anchor's send stamp plus normalized byte equality prove WHICH steer prompt file under the steer dir carried the words (the obs- child seam the lane rows know), and the source is the session whose own tool part ran the posting call (it names the target session id AND the verbatim text, created at-or-before the send stamp — a post precedes the send it causes, so a non-posting checker's read-back after the send never qualifies). Each delivery binds the EARLIEST suitable posting act in its own pre-send window, and one act carries exactly one delivery: a byte-identical double-steer to one target consumes its acts in send order, so each delivery names its own carrier. The observatory's machine steers (context guardian, quota throttle, starvation watch, turn-retry) carry their identity by construction, recorded at spawn. user stays exclusively genuine owner input: a legacy row, a cleaned prompt file, a dead source id, or a photo-finish between two posting sessions all settle honestly as user (the capped album records the ambiguity reason) — the conversation view attributes BOTH directions (who asked — the source session; who answered — the agent).

todo update and system messages collapse to a one-line chip (⚙ todo update · 19:44) by default so the flow stays readable; a click expands the full muted card (and collapses it back), and a per-surface "hide harness noise" switch (drawer meta line, modal footer — kept in sessionStorage for the page session) hides them entirely. Every message with text carries its own small 📋 copy icon (left of the timestamp, v1.21.0) for single-message copying.

  • GET /api/session/<id>/transcript — one session's messages, oldest first: sequence, role, timestamp, joined text parts, and part-type hints (text / reasoning / tool — tool parts carry a pretty-printed, capped rendering of the call's input and output for the expandable block); step-start, step-finish, and timeline noise is skipped server-side
  • Payload cap: the last 200 messages by default (?limit=N, max 1000), newest last, so giant sessions stay fast; the modal asks for ?limit=15, the panel shows x of y messages when the transcript is truncated

Markdown code rendering in agent text (v1.39.0)

Agent responses routinely carry fenced code blocks (…) and inline code; until v1.39.0 every chat surface showed them raw. Now ONE shared renderer — a small pure module inside the dashboard, unit- pinned by the node harness (tests/test_markdown_render.py) — renders exactly that minimal subset on EVERY agent-text surface: message bodies in both panels and both views (interface + full transcript), reasoning text, and the steer response tail. A grep-level audit test keeps it that way: the raw-esc spellings of those sites are pinned OUT of the file, so no surface can silently regress to raw text.

The scope is deliberately exactly two transforms:

  • Fenced code blocks → a monospace block (theme-consistent border and tint, wrap-not-crop like the tool blocks) with a ⧉ copy button per block riding the same delegated clipboard path as every other copy in the deck — it copies the RAW block text verbatim.
  • Inline `code` → a styled inline monospace span.

Nothing else transforms. No headings, lists, emphasis or link engines — **bold** and [links](…) stay literal text; newlines ride the surfaces' existing pre-wrap (no <br> synthesis). SAFETY FIRST: every character of input is HTML-escaped BEFORE the narrow transformations — hostile content inside or outside fences/spans (<script>, attribute breakouts, entities) can never inject markup, and the only tags in any output are the renderer's own scaffolding (the unit suite asserts exactly that).

Edge behavior is test-pinned: a fence must start at column zero (anything after the backticks on the opening line is an ignored info string), a line of only backticks closes (a ```-prefixed line with other content stays block content), an unterminated fence renders as a block to end-of-text, CRLF/CR normalize, and inline spans follow CommonMark's exact-run rule (a``b keeps the double run as content; an unclosed run renders literally; span content is verbatim). Copy payloads always carry the RAW source — the 📋 message-copy button still copies the original markdown, not the rendered form.

The dashboard has understood ?session=<id> URLs since v1.25.0 (notification clicks land on the row's panel), but only the notification ever produced them — every OTHER artifact that mentioned a session showed dead text. v1.40.0 extends the principle everywhere a mention exists, all through ONE landing contract (openDeepLink): resolve the id, rescue it past the history window if needed, open the response modal and flash the family. A mention that matches nothing (or ambiguously, as a prefix) degrades to the honest toast — never a wrong session.

  • Agent text linkifies session ids. The v1.39.0 shared renderer gained exactly ONE new plain-text transform: tokens matching the session-id grammar (sess_ + [A-Za-z0-9._-]+) that ALSO satisfy the product's strict id shape become anchors (?session= deep links; a plain left click opens the panel in place, a modified click opens the real URL in a new tab). Code — fenced blocks AND inline spans — stays literal (copy fidelity), trailing prose punctuation is trimmed off the token, and the ESCAPE-FIRST discipline is unchanged: the transform runs on already-escaped text and the href is only ever the relative deep-link form (javascript:/data:/URI attempts inside sid-shaped text are structurally impossible — : and / are not in the token charset; the hostile-corpus suite pins it). Doorbell texts mention children by a 15-char prefix — the landing resolves UNIQUE prefixes to the full row, so those links work too.
  • Every mentioning artifact links: steer target lines (an untitled target renders its sid as a link), the budget-warning toast (→ the lane root's row), question toasts and question-card headers (→ the asking session's row), the genesis status chip's open button and terminal toast, quota-panel lane rows and ⟲ override badges (→ the lane root's row), and dispatch-strip rows (→ their session row when the ledger knows one, else the refusal's lane root). The quota lane label and strip jumps are ↗ buttons in the house copy-button language.
  • The panel is the landing. Both panel headers (response modal and transcript drawer) now carry the row title's ↗ ZCode affordance (open_session.sh / zcode:// deep link) — a deep-linked visitor never needs to hunt for the row to hand the session back to the desktop app.

The v1.38.1 follow-up PII-F1 is included: the lineage pan tracker's window-level pointermove now carries the literal no-buttons guard (!ev.buttons → end drag), so a release that left the page mid-drag can never re-pan the view on later hover — pinned as a pure predicate (lgMoveEndsDrag) in the lineage harness, and the pan/node-click surface re-proven under trusted CDP input.

Scroll stability & "follow output"

Both scroll surfaces (response modal and transcript drawer) are strictly position-stable: a refresh cycle that returns no new content does not touch scrollTop at all — the DOM is left untouched, so the view never drifts on its own. When new content does arrive, the follow output toggle decides what happens:

  • ON — new output auto-scrolls smoothly to the bottom (live tail).
  • OFF — the scroll position is entirely yours; new content never moves it.

The toggle sits in the meta/footer row right next to hide harness noise (drawer: below the header; modal: in the footer). Follow starts ON by default, and the choice is one shared switch for both surfaces, persisted across browser sessions (localStorage, key zdo-follow) — the two checkboxes always mirror each other. Classic terminal behavior while ON: scrolling up pauses follow and shows a ↓ jump to latest pill; scrolling back to the bottom, clicking the pill, or re-toggling resumes. Both surfaces always open at the conversation tail.

Console LAYOUT changes never steal the at-end state either (v1.23.0): the steering console sits below the conversation, so its height changes — a steer answer's response tail expanding on arrival, auto-minimizing to its chip once read, the ⤢ prompt expand — resize the chat body above. When the chat was at its bottom it STAYS at its bottom across the change (scrollTop re-pinned synchronously, compensating the browser's scroll anchoring before it can drift the view a few pixels up); a chat scrolled up to read keeps its position untouched.

Hydration prediction: when a doorbell becomes visible in ZCode

Factual basis: zcode-dispatch's docs/PROTOCOL.md, section "Open-chat refresh: the 10-minute residency cycle". An open desktop chat is pure in-memory state in the owning app-server; a message appended by another process (a 🔔 doorbell) lands in the shared DB only. The owning process deactivates an idle session after 600 s on a sweep tick of up to 60 s, and the next UI fetch cold-resumes from the DB — that is the moment the doorbell "suddenly appears". The observatory predicts that moment per session:

  • Prediction: last DB activity + 600 s … + 660 s, where last DB activity is the session-wide MAX(message.time_created, part.time_created). Rendered on the row's spanning line as ⏳ hydrates between 17:30 and 17:31 · N new since 17:18, then ⏳ hydrating window open — next sweep ≤ 60 s inside the window, then ✅ hydratable — switch chats / refocus ZCode to reveal after it.
  • Shown only for idle, non-archived sessions with unseen external content: messages newer than the session's anchor — the later of the last non-running turn_usage completion and the last user message that plausibly came from the desktop (semantics.origin = real_user with a contextSnapshot.envInfo.cwd equal to the session directory). Running sessions have residency-blocking work (no deactivation while a turn runs), archived ones are hidden from the app's lists — a prediction there would be dishonest, so there is none. Sessions without any anchor (no completed turn, no desktop user message) are skipped too.
  • It is a lower bound — said so in the tooltip. Desktop reads/leases reset the idle clock WITHOUT writing the DB, so interaction in ZCode postpones hydration invisibly to this prediction ("earliest hydration").
  • On-demand recipes (tooltip next to every prediction, verified in the forensics): once the countdown reached zero, switching chats and back, or blur+refocusing the ZCode window, or clicking a zcode-dispatch desktop notification forces the refetch — the doorbell appears immediately. Inside the 10-minute window none of these help (the resident in-memory copy answers first).
  • The header's right cluster (under the notification controls) carries the line ⏳ ZCode: 10 min residency (v1.26.0 trimmed the old "residency cycle · next sweep ≤ 60 s" wording — the sweep cadence it quoted was stale and the per-session ⏳ indicators already tell the story), served from /api/sessions (residency.idleSeconds) — it describes ZCode's memory behavior, so it lives on the ZCode-facing side, not next to the observatory title.

Root-lineage family colors, parent column

Edge sources, labeled honestly — implemented all, trusted in a declared order. A child keeps exactly ONE parent: the first source that names it; weaker, conflicting sources never override or coexist (the children map is derived from the final child→parent map, so family sizes and child counts can never double-count a conflicting edge):

  1. Native session.parent_id — app-server subagent children. Direct DB column, shown as-is. Highest precedence.
  2. Bell-ledger spawn records + live spawn edges — the dispatcher's own spawn-time linkage. stream-bells.json (dispatcher v0.3.1+) records child→parent the moment a bell is spawned, so a lineage stays visible even while the bell is still queued (never only delivered doorbells); a RUNNING child links through the live spawn edge read from its dispatcher's --notify-parent command line, covering the window before the ledger record exists (it is written at child completion). Without a ledger (older dispatchers) nothing changes.
  3. Doorbell families (delivered 🔔) — --notify-parent children parsed from user messages: doorbell user messages contain the child id by 15-char prefix (🔔 stream doorbell (auto): child sess_1341618a-0… returned: …; a brain-child's doorbell says 🧠 brain-child sess_… instead — v1.22.0); the observatory scans the newest 2000 such parts (raw-data LIKE prefilter, then anchored re-check so quoted doorbells never count), resolves each prefix against all session ids, and keeps the edge only when the prefix is unambiguous. The ringing session is the parent.

Child ROLE (v1.22.0, dispatcher v0.3.6) rides the same records: every session's payload carries role — pinky (worker, default) or brain (delegation node) — resolved with its own precedence (live spawn flag/result childRole → dispatch lifecycle ledger → bell ledger → the pinned title's [🧠] prefix), so the delegation backbone (master → sub-brain → pinkies) renders distinct from the worker swarm: 🧠 BRAIN-CHILD badges in the panel strips, the 🧠-glyphed stronger parent link in the table, and thicker series lines + tooltip markers in the usage graph.

Lineage model (v1.11.0 — replaces the v1.9.0 project coloring):

  • Roots and inheritance — each session's ROOT is the top of its parent chain, walked transitively over ALL edge sources with the declared precedence — native parentage wins; a ledger/live-spawn edge beats a delivered doorbell; one parent per child, never two). A session with no parent edge is its own root; a root plus all its transitive descendants — children, grandchildren, whole work/mission trees — form one FAMILY. The root id is hashed into the fixed 12-hue dark-theme palette (same root = same color across sections, refreshes, restarts), and every member wears it: thin (~3 px) left accent bar, a very faint row tint (running/pinned/question states keep their own wash), and the spanning line's hue. Families read as coherent color blocks at a glance. The session title also renders right-aligned on each row group's spanning line, where it is always readable — a leading <worktree-slug> · prefix is stripped there and in the panel headers for display (v1.21.0: the worktree already sits left on the same line; tooltips and the dock keep the full title).
  • Guards — the walk is cycle-guarded: a loop breaks deterministically at its lexicographically smallest member, independent of the entry point, so derived edges are safely treated as a DAG. A 16-hop emergency cap sits far above any real lineage (dispatcher trees are 2–3 deep), and the resolved prefixes are memoized — the whole build is linear in edges and stable across refreshes. A loop can never hang the build or flicker the coloring.
  • No legend, by design — the color IS the family identity; a color→name strip would only re-state what the rows already show. The v1.7.0 family dot before the name was removed with it: the colored bars carry the identity cleanly, and one clear signal beats two mushy ones.
  • Recognition aids — the parent column carries the family tooltip ("family of ⟨root title⟩ · N sessions — root/child"). Hovering any family row GLOWS in that family's color — the root-lineage hue tints the whole row group and radiates from the left accent bar (sessions without a family hover in the default cyan accent) — and washes the whole root family across sections (transient); clicking a parent name or a root's ◉ glyph pins that wash on every member — click again to clear — and the parent link also scrolls to and flashes the parent's row when it is loaded (never a dead control: the family highlight always fires).
  • Parent column — one combined column drawing on the same three edge sources as the family colors (native session.parent_id, bell-ledger spawn records with live spawn edges for running children, delivered 🔔 doorbell edges), resolved by that precedence: a native parent always wins, a ledger/live-spawn edge beats a doorbell, and a child never carries two parents. When the parent resolves, the cell shows the PARENT SESSION'S TITLE (its name, tinted in the family hue — the parent's own row carries the copyable full id in its ID column, so no copy button rides the child's cell since v1.21.0); an unresolvable parent falls back to the short id. Sortable — sorting by it groups whole root families adjacently. Family roots (the parent side) show a colored ◉ root marker in their family's hue.

Lineage graph: the fleet as one interactive forest (v1.38.0)

The tables list the fleet row by row; the Lineage graph section (between the tables and the quota gauge) draws it as what it IS — a forest of delegation trees, one node per session, edges exactly where the Parent column gets its facts (native parent_id, bell-ledger spawn records, delivered doorbells — the same three sources, the same precedence). Every node wears its family's root-lineage color; a running session pulses; a 🧠 delegation node carries its glyph.

  • Pan, zoom, reset — drag pans, the wheel zooms to the cursor (clamped to 0.25×–8× of the fit), double-click or the ⌖ fit button restores the fit. All of it mutates the SVG's viewBox attribute ONLY: no frame ever rebuilds the DOM, so panning stays smooth on a full fleet (a MutationObserver watching the stage sees nothing but the one attribute change — the validation gate asserts exactly that).
  • Filters (a bar above the stage, the header-band rhythm): role (all · 🧠 brains only · 🐺 pinkies only — a "brain" is a family root or a delegation node, the deck's orchestration layer), status (running · terminal · all) and the time window (24 h · 3 d · 7 d · all, following the header window until changed locally). Since v1.103.0 (UUUU, validator finding F1 — "ein Blick, eine Wahrheit" for the graph too) the window keep-set is the TABLES' own, verbatim: lgWindowKeeps is a thin pass-through to windowKeepsCore, the ONE definition the tables' windowKeeps also closes over — running, pinned, favorited, docked and open-panel sessions never drop, the 🧠 exemption toggles ride too, and a cross-pinning harness drives both surfaces over one fixture matrix so the agreement can never silently drift again (the pre-UUUU module-local twin exempted only running+favorite). At all the graph shows full history exactly like the tables — the honest finding of the UUUU audit was that the "graph falls back to 7 d" language was the USAGE chart's rule (a full-history sparkline would be unreadable mush) misread onto the lineage graph, which never carried a cap; the graph's readability answer is the aggregate fold, and the header tooltips now scope the 7 d fallback to the usage chart by name. Filters recompute client-side from the cached payload: a toggle costs milliseconds, not a poll — and the render signature carries a fingerprint of the exemption view (dock set, open panels, brain toggles) so a flip re-renders the forest even between polls.
  • Aggregate family nodes — a tree past 12 sessions folds its non-running members into ONE aggregate node (badge: family count plus the 🧠/🐺 role split; click expands, the root's ⊟ collapses again). Running sessions ALWAYS render individually and re-attach to their nearest visible ancestor — live work is never hidden. Counts recompute under the filters.
  • Replay ("⏱ replay" — the fleet at HH:MM) — a scrubber over the last 7 days reconstructs the graph as of a chosen past instant from what the DB and the dispatcher ledgers RETAIN: sessions that already carried dispatch evidence then, turns that were running then (turn_usage spans), lineage edges already known then (a bell record from its spawn stamp, a doorbell from its delivery time). The reconstruction is honest about its bounds — every approximation is labeled under the stage (native parent edges carry no timestamps; titles, roles and archive flags reflect today's state; supplemented edge endpoints; never-completing turn spans may be dead drivers). Exit replay returns to the live fleet with one refresh.

Node click opens the session's response interface — the same panel a table row opens. The API behind the graph is read-only like every observatory endpoint (/api/lineage/replay?at=<epoch-ms>, 7-day horizon, bounds strings included); the live graph rides the existing /api/sessions poll. The fleet plotted is the tables' visible universe (running + history; hidden and archived stay groomed away — replay deliberately includes them, dimmed, because history is its job).

Delegation topology: brain-children (v1.22.0)

Orchestration is recursive: a brain can dispatch not only 🐺 pinkies (workers) but also 🧠 brain-children — sub-brains that run their own orchestra of pinkies (master → sub-brain → pinkies). The dispatcher marks the kind at spawn (--child-role brain, v0.3.6) and the role rides every record it writes: the result JSON (childRole), the [🧠]-prefixed pinned title, the bell + lifecycle ledgers (role), and the doorbell wording (🧠 brain-child sess_… returned — a woken parent immediately knows a delegation, not a worker, answered). The observatory resolves the role per session with declared precedence — live spawn flag/result childRole → dispatch lifecycle ledger → bell ledger → the title's [🧠] prefix (the title pin survives even after both ledgers rotate past their caps) — and renders the delegation backbone everywhere lineage shows: the panel info strip badges brain-children as 🧠 BRAIN-CHILD (between 🧠 BRAIN roots and 🐺 PINKY workers), the table's parent link for a brain-child wears the 🧠 glyph and a stronger glow, and the usage graph draws a brain-child's series line thicker than its sibling worker lines with the 🧠 marker in the tooltip — the delegation backbone reads apart from the worker swarm at a glance.

Row titles wear role glyphs, not worktree prefixes (round Y): the dispatcher pins brain-child titles as [🧠] <worktree-basename> · <text> — but the row already shows the worktree left-aligned, so the dashboard strips that prefix for display and renders the delegation ROLE instead: ⚡️🧠 before a masterbrain (a session that spawned at least one brain-child — any own role; the root stays recognizable), 🍼🧠 before a sub-brain, 🧠 for plain brains, and ⚡️🍼🧠 for a middle brain (child AND parent of brains). When the visible structure is flat — no brain-child-of-brain — with exactly one masterbrain, the sub-brains de-differentiate to plain 🧠 (the differentiation would be noise; the masterbrain keeps its lightning). Pinky rows and non-dispatch rows stay untouched. The strip and glyphs are DISPLAY only, decided server-side and consumed by every dashboard surface: the pinned DB title stays intact where the worktree context helps — ZCode sidebar, desktop notifications, bells, digests, row tooltips and the favorites dock.

the delegation backbone in the Sessions table

The delegation backbone in the Sessions table (fixture data, captured at 1177 px inner width): the mission family reads as one color block — the ⚡️🧠 masterbrain root with its ◉ root marker in the family color, its de-differentiated 🧠 sub-brains (the parent column carries the 🧠-glyphed, stronger-glowing parent link), and the 🐺 pinky child sharing the same hue — beside the solo 🧠 brains in their own colors.

The ideal window: 1177 px and the fit button

The dashboard is laid out for an owner-measured ideal viewport width of 1177 px (round Y: the Hidden table line-wrapped at the previous width on the owner's real browser — his fonts and rendering differ from any headless reproduction — and 15 more px bought the tables their single-line rows back). That number lives in exactly one place, the IDEAL_W constant in dashboard.html — the live indicator's text and mint/red thresholds, the popup's width feature and every snap derive from it; the docs (here) merely quote it. The header carries the affordances in the view/filter cluster under the title (round Y):

  • a live viewport-width indicator — <current> px · ideal 1177 px — that turns mint the moment you ARE at the ideal width, and red two px below it, the live-tested point where the fixed table columns start breaking;
  • an ⧉ ideal window button that opens the dashboard in its own popup at the ideal width (the width feature sizes the OUTER window; Chrome's frame inset can shave a few pixels — the live indicator beside the button confirms the true inner width, so trust it over the feature string).

The snap is authoritative (round Y): script-opened windows accept window.resizeTo, so whatever drifts the popup's width — the live-verified Chrome quirk of a popup OPENING at the opener window's inner width (ignoring the width feature entirely), or a manual resize — lands back on exactly 1177 px inner width. The fresh popup self-corrects right after open, every re-click of ⧉ ideal window re-snaps (and focuses) it, and the button works from INSIDE the popup too: there it snaps the popup itself (a page recognizes its own popup by the window name). On phones both affordances hide entirely — a phone window cannot reach the ideal, and teasing it would be noise.

Both buttons are ONE action (v1.72.0 / S-46). The collapsed slim header carries its own compact ⧉ (round EEE / S-42) beside the expand toggle — and since S-46 it rides the exact same idealWindowAction function as the full header's button, by name, no wrapper: no ideal window exists → it OPENS one at the ideal width; one exists → it snaps it back to the ideal and focuses it; clicked inside the popup → it snaps the popup itself. One shared code path means the two affordances can never drift apart again — the slim button stopped being a snap-only shortcut the day a no-popup click did nothing instead of opening the cockpit.

Honest scope, documented: a page can never resize your main browser window — that is a browser security property, and it is exactly why browsersize.com opens a NEW tab instead of resizing yours. For the main window the live indicator is the guide: drag your window until it reads mint. A popup CAN be sized at open time (window.open with width/height features), and a script-opened popup can also be RE-sized later (window.resizeTo) — both working variants are what the button uses. If the browser demoted the popup to a plain tab, the re-click falls back to focusing it (and re-opening is the last resort). At phone widths the deck switches to its own mobile layout — the popup remains the one-click way to read the DESKTOP cockpit exactly as it was designed.

The mobile deck: the cockpit on a phone (v1.56.0)

The dashboard is usable on a real phone on the LAN (~390–430 CSS px — an honest responsive compact layout, not a PWA: no service worker this round). Under a 560 px breakpoint everything re-lays into ONE column, nothing is removed:

  • The session tables become row-cards — one card per session wearing its root family's hue: the action cluster (pin, favorite, transcript, archive/hide — icon-only) on top, then context % · state badge · activity, then the cwd + hints + title line. The whole card taps into the response panel, which is the primary working surface on narrow (bottom-anchored, near-full-viewport); the steer composer docks with it — Send, the ⤢ expand and ⤓ fold toggles intact.
  • The header collapses to its slim quick-stats line BY DEFAULT (the v1.41/KK machinery reused, driven by a matchMedia hook — the persisted wide-screen collapse choice is never clobbered; ⤒ expands as a session-local override, and crossing the breakpoint re-derives from the persisted choice). 🌱 genesis rides the slim line, so birthing a session never needs expanding first. The ◷ popover carries the refresh cadence too (v1.57.0, TT-V3): the expanded header's #rate select hides with the slim line, so the popover's refresh section — options derived from that very select — is the one rate control at narrow widths; a pick applies through the same setRate the select uses.
  • Tap targets meet the 44 px mobile convention on every core surface (row actions, composer, panel controls, question options, the slim line, dialogs, the dock).
  • Quota and usage sections stack; wide data tables (quota lanes, schedules) scroll inside their own box — the page itself never scrolls sideways. The lineage graph keeps touch-drag pan; its wheel-only zoom degrades to the ⌖ fit affordance on touch.
  • Honest desktop-only leftovers, told plainly: column sorting is a click-on-header affordance and stays desktop (the pager still pages on mobile); the lineage graph's pinch-zoom is not wired. The 🌱 genesis dialog scrolls internally at 500 px (TT-V2 — its own box scrolls, so every field stays reachable; accepted as-is).

Validation rig notes from the mobile round, kept for the next rig: the first-paint spot check can flake under station load (TT-V4 — a transient empty first paint; re-run before suspecting the product), and sub-500 px proofs ride device-metrics override LOADS per the caveat below.

One rendering caveat, verified live: desktop Chrome cannot render a window narrower than 500 CSS px (the "honest-500px reality" this roadmap lane was named for) — setWindowBounds at 430/390/360 all leave innerWidth at 500. The 390/430 proofs in the round's validation rig therefore load the page under a device-metrics override (a real narrow LOAD, not a resize-after — that avoids the state-dependence trap documented in v1.41), while every interactive gate (trusted taps: card → panel, steer send, question answer, 🌱) runs in the REAL 500 px window — the physical floor.

Remote access is a station prerequisite, not a deck feature: the observatory binds 0.0.0.0 (per the systemd unit), so LAN/phone use assumes the station's network exposure is available and appropriate for your threat model — reach it at http://<station>:8767/ from any device on the same network. Whether that LAN should be reachable from outside (VPN/Tailscale or similar) is an owner call, deliberately out of scope here.

Also fixed by the mobile round's rig (a desktop bug caught at phone widths): an expired toast kept opacity: 0 but stayed in the hit-test tree — an invisible full-width box swallowing clicks over the action rows beneath it. Toasts now retire from pointer events after their fade (visibility: hidden), and at narrow widths they lift clear of dialog footers.

The control band's collapse point (round CC): the header's one control row — view/filter cluster LEFT of the status chips, right- aligned to the header content edge — holds a single line at the ideal width and everywhere above ~1060 px; below that the band wraps as a whole into two rows (controls on the first line, chips beneath — both still right-aligned). The breakpoint is measured content, not a media query: the full band measures 1012 px with the standard chip set (canary + push + residency + quota, Xvfb-geometry-validated at exactly 1177 px inner width), so it fits a single line through 1060 and only wraps when the window cannot hold 1012 px plus the header padding. The degraded push chip is deliberately FIXED-width ("⚠ push offline", reason in the tooltip) so even the alert state never pushes the band past the ideal width.

The steer lifecycle — the owner's words, never lost

From the pending strip through queueing, delivery, failure, retry and the emergency interrupt: every state counted, every word accounted for.

Dispatch lifecycle: the pending strip

zcode-dispatch v0.3.4 ledgers every dispatch's lifecycle (~/.config/zcode-dispatch/stream-dispatches.json): fired → pending → running → done/presumed-dead. The observatory's slim strip above Running shows the phase the session tables cannot see — a dispatch that was fired but has no session yet (normally seconds while the app-server boots it; the chip then resolves by itself as the session appears in the Sessions table). Each glow means:

  • Mint, breathing dot — pending, healthy. Age ticks live; the chip disappears the moment the session exists.
  • Amber glow (⚠ slow) — still no session after ZDO_PENDING_DISPATCH_MS (120 s default): the dispatch is stuck or dying; watch it or check the dispatcher.
  • Red glow (💀 presumed dead) — the sweeper's reaper found the dispatcher process gone WITHOUT a terminal state (crash, OOM-kill, SIGKILL — the silent-death class that motivated the ledger; the firing parent got a 🔔 bell with the trail automatically). The row STAYS until you dismiss it (✕), and copy buttons hand the cwd (📁) and result-file path (📤) to whoever investigates — the ledger itself is never touched by dismissal. One honest self-heal (v1.26.0): when the dispatch's session completes a turn AFTER the presumed-death stamp, the row auto-clears — the cgroup killed only the dispatcher's bell while the orphaned app-server finished the work normally (the first oss-oo masterbrain live test); a red chip then would be a lie.
  • Red ⛔ budget ceiling (v1.35.0, zcode-dispatch ≥ v1.0.16) — the dispatcher's hard token limit REFUSED the dispatch at admission (budget-refused, exit 7): no session will ever appear (nothing was spawned, nothing spent — the session tables can never own this story, so the strip does). The chip names the scope and shows the refusal's own numbers (used 412.0 k / hard 400.0 k · lane …), the tooltip carries the full admission detail, and the row stays until dismissed. The contract's fairness door (--budget-override) is one dispatch away — see Budget guardrails: exit 7 rendered honestly.

Completed dispatch entries are not shown (avoid noise — the session tables already tell that story); with an older dispatcher (no ledger) the strip simply stays empty.

the pending dispatch strip

The pending strip (fixture data; the strip element cropped from a full-window capture at 1177 px — the crop keeps the strip's own width, not the window's): a healthy fired dispatch (mint, breathing dot), a slow one past the 120 s mark (amber ⚠ slow), and a presumed-dead one (red 💀 — stays until dismissed, copy buttons hand the cwd and result-file path to whoever investigates). The lifecycle phases the session tables cannot see, one slim row.

Three states: idle / background active / queued deliveries

A steered session sits in one of three states, and the observatory says which — because desktop invisibility ≠ idleness: sessions keep processing background turns (bell-wake work, dispatcher-driven turns) that the desktop chat renders as a quiet conversation (see the residency and background-turn notes in zcode-dispatch's docs/PROTOCOL.md).

  1. idle — no transcript activity inside the busy grace window (30 s). The steer dialog's target line says idle; a send starts driving at once.
  2. background active — message/part rows landed within the last 30 s (the same cross-process signal the dispatcher's prechecks use). The row shows the "bg active · Ns" hint led by a small breathing mint dot (a CSS pulse — the dot fades in and out, no glyph); the steer dialog's target line shows "bg active · Ns"; a send self-queues and the status explains WHY it waits and WHEN it lands: "queued · waiting for the current turn to end — background turn active — last transcript activity Ns ago · delivers ~3–5 s after the current turn ends (turn-end fast lane)".
  3. queued deliveries — steers of this session waiting in the sweeper queue (or one in flight). The row shows "📨 queued · waiting for the current turn to end"; the dialog's target line carries the live depth ("1 in flight · 2 queued"). Sends keep queueing as the next sequential turns (soft cap: a 4th waiting steer is refused with a friendly inline note — the button itself never goes dead over queue state).

The turn-end fast lane (round M): a queued steer no longer waits out the sweeper's 30 s idle grace — while the owner actively converses, that could starve a steer for the whole conversation. A terminal turn_usage row appears exactly at turn COMPLETION (PROTOCOL.md forensics: rows are written at/after completion, never while a turn streams), so the observatory runs a small watcher that polls that row (~1.5 s) for every session with a queued steer and fires the delivery ~3–5 s after the turn ends: it claims its own queue entry and spawns the same zcode-dispatch --resume the sweeper would — every precheck intact (cwd lock, running-turn row, -32010, one-driver). Only the ATTEMPT TIMING changes:

  • if the owner typed first and a fresh turn is already running, the dispatch exits 3 and self-queues exactly as before — the queue semantics are the safety net, nothing interleaves;
  • the sweeper stays the backstop (a delivery the lane cannot make still lands ~≤45 s after idle); bells never ride the lane — they keep the sweeper's 30 s grace, because they must not chase turn boundaries;
  • delivery is recorded in the sweeper's own history file (same records, same dedupe), so the dispatcher's already-delivered guards see the fast-lane delivery exactly as a swept one;
  • the spawn shortens only its own busy grace (ZDO_RUNNING_GRACE_MS is per-process env; 2000 ms via ZDO_FASTLANE_GRACE_MS) — queue delivery IS the next sequential turn by design, and the settle window already proved the previous turn over.

The turn-end trigger seam (round BBB, v1.63.0): the lane's reaction is now EVENT-DRIVEN. The dashboard's push consumer POSTs /api/fastlane/nudge the moment a turn-end SSE event lands (session.turn-completed|failed|timeout; coalesced 250 ms trailing), and the watcher leaves its poll sleep at once — the fire happens at the first instant the settle window allows (~3 s after the terminal row, deterministic) instead of paying the 1.5 s poll phase on top. The nudge bypasses nothing: eligibility, the settle window and the one-slot-per-session FIFO all still decide inside the scan, and a nudge for a session without queued steers is one cheap no-op scan. Without a push feed (or with every dashboard tab closed) the poll cadence delivers the same behavior one phase later — the v1.30 discipline: the event accelerates, the poll guarantees.

The delivery lanes and human-first priority (round BBB, v1.63.0): the owner filed it rightly — machine bells used to beat the owner's own words to a session. The lanes as they stand:

  • bells — the child's own spawn (fast): the moment a child dispatch finishes, its notify_parent spawns the bell delivery itself (~1–3 s reaction, no queue involved) — that is the bell fast lane, and it is untouched by this round. A bell that hits a BUSY parent self-queues and rides the sweeper backstop like everything else.
  • user steers — the observatory's turn-end lane (fast, priority): a queued USER steer delivers ~3 s after the target's turn ends (settle window + the nudge seam above). At a delivery boundary where a user steer and queued bell(s) are BOTH pending, the user steer delivers FIRST by construction: the lane claims only the observatory's own steer entries, while a queued bell physically cannot deliver before the sweeper's 15 s cadence AND the dispatcher's default 30 s transcript-activity grace — the machine waits, the owner's words go first, even when the bell queued earlier.
  • the sweeper — the backstop: 15 s cadence, 30 s idle grace, oldest-first by queued_at. It catches anything the lanes cannot make (observatory down, lane disabled) and remains the only lane queued bells ever ride.

The honest boundary (the interleaving the observatory does NOT own): a child completing INSIDE the settle window still races on its own immediate spawn — dispatcher-side, before any queue consultation. When such a bell seizes the boundary, the user steer does not fall behind the sweeper's 30 s grace: it re-fires at THAT turn's end ~3 s later (the send time embedded in its child id keeps it eligible across exit-3 re-queues). Worst case the owner's steer waits out exactly one bell turn, never a sweep cadence. Cross-lane note for the dispatcher repo (Brain-A, filed with this round): (1) a priority/owner field on queue entries — set by the observatory's steer sends via a notify marker — so the sweeper's oldest-first sort puts owner steers ahead of bells even when the observatory lane is down and the bell queued earlier; (2) a yield-to-human precheck in the notify spawn: a bell about to drive an idle parent self-queues instead when the target already holds a queued owner steer. Both are dispatcher-side decisions the observatory cannot make for it; everything observatory-side (speed, claim, boundary re-fire) shipped here.

Tunables: ZDO_FASTLANE=0 disables the lane (the UI wording follows the server truth), ZDO_FASTLANE_POLL_S (1.5), ZDO_FASTLANE_SETTLE_S (2.5), ZDO_FASTLANE_GRACE_MS (2000).

Presumed dead (round M; the liveness-honest split v1.94.0): a row whose turn still shows in flight while the transcript went quiet for more than 5 min and no dispatcher holds the cwd is a candidate corpse — but since v1.94.0 (owner finding #55, the nervous 💀) the class SPLITS on liveness evidence, and the server classifies first:

  • ⏳ wartet — Ziel arbeitet (teal, plain text) — the quiet row has liveness evidence: a live dispatched child working under it (the brain-orchestrating shape — the row is quiet because its turn waits on the child), an open commission (a non-terminal ledger record naming it notify-parent — the same evidence the lane vitals count as working), a live steer dispatcher riding it, or the CCCC process proof. Waiting steers and deliveries pointed here follow the turn boundary; nothing needs relief. The tooltip names the evidence.
  • 💀 presumed dead · Nm (red, a button into the 🔨 since round RRR) — reserved for PROVEN lifelessness: quiet + in-flight + no liveness evidence at all. The signature of a dispatcher that died mid-turn (the turn row never completes; the stall is otherwise silent). A hint, never a verdict: a long quiet tool phase looks the same from the DB, and the tooltip says what to do if it is real.

Tunable: ZDO_PRESUMED_DEAD_MS (default 300000). The evidence legs follow the CCCC aliveness doctrine — they only ever ADD liveness evidence, never invent it (a fresh session.time_updated with a quiet transcript was measured on the live station and REJECTED as evidence: it only ever moves with part flushes or admin bumps, never as a bare app-server heartbeat).

Refresh honesty: the desktop app keeps its task list in memory, so an observatory-made archive/unarchive reaches an ALREADY-RUNNING ZCode only after its sidebar re-queries (a ZCode restart, or switching workspaces/sidebars) — there is no event channel into the running app. The reverse direction is live: the observatory re-reads the task index on every refresh (~3 s), so archives made in ZCode show up here immediately.

Migration (v1.9.0): observatory v1.5.0–v1.8.0 archived via session.time_archived — a real app-server column, but never written by the desktop UI (verified against the installed product), so it never belonged to the owner's real archive. On startup the observatory clears any leftover values once (a one-shot statement, logged on the console); after that nothing writes the column again.

curl -s -X POST http://127.0.0.1:8767/api/archive \
  -H 'Content-Type: application/json' -d '{"id": "sess_1a2b…"}'
# => {"ok": true, "id": "sess_1a2b…", "archived": true}
curl -s -X POST http://127.0.0.1:8767/api/unarchive \
  -H 'Content-Type: application/json' -d '{"id": "sess_1a2b…"}'
# => {"ok": true, "id": "sess_1a2b…", "archived": false}

Since v1.83.0 archiving is self-seeding (round ZZZ): a session with no native task row yet gets one derived from its own DB row (the session's directory as workspace key, its title and stamps) and archived in the SAME transaction — no "open it once in ZCode" ritual. The one honest 409 left is a session row without a directory (underivable). Archiving also auto-clears both keep-close flags (pin AND favorite) with the departure — no orphan entries in either store (hiding does the same).

The truthful receipt + the turn-state display (v1.108.0)

Two display contracts shipped together (round YYYY, the Astra#2 commission): every steer surface renders ONE four-way receipt vocabulary, and every working session carries its TURN STATE in the owner's five-word grammar — so acceptance is never conflated with delivery, and healthy long work never looks like a dead session.

Y1 — the four-way receipt vocabulary

Every steer — the optimistic echo, the lifecycle cards, the steer console's status line — renders one of exactly FOUR states, each with its receipt one hover away (stable message id, target session, content digest over the annotation-stripped core, sent/accepted/delivered timestamps, the consuming turn, and a machine-readable error class when failed):

state word proof (never a guess)
accepted ✓ accepted · 202 HTTP 202 landed — the dispatcher took the job. 202 is NEVER delivery: model-visible delivery is a later, separate fact.
queued 📨 queued the sweeper queue (or the parked re-queue representation) holds it; nothing is proven delivered.
delivered 📨 AM TURN GELIEFERT the send-ack receipt, the transcript write itself, or the dispatcher's completed result — and the turn_usage row whose user_message_id IS the delivered message names the turn that consumed it.
failed ✖ failed terminal without delivery proof (failed/timeout/budget-refused/dead-letter), with error: {class, message} — the AAAA death-class vocabulary.

The reconciled class keeps its ONE story: a delivered steer whose turn died renders AM TURN GELIEFERT · turn died (<class>) — the delivery is the receipt's fact, the death is the turn's fact, never a not-delivered twin beside the delivered message. The state derivation is priority-ordered (failed > delivered > accepted > queued) and an unprovable state stays its honest name.

Y2 — the turn-state grammar and its resolution table

Running rows carry their turn state beside the Laufzeit; a parked row carries its chip on the spanning line:

word renders when evidence seam
Modell a running turn the model side holds (thinking or streaming; a quiet phase annotates · denkt) no tool part in state running; the CCCC quiet+liveness thinking seam is the annotation, never a separate state
Werkzeug · <tool> a tool provably executes the session's NEWEST part is a tool part with state.status='running' (its name rides along) — only the executing window claims the word
Freigabe a pending question holds the turn the pendingQuestion seam (an AskUserQuestion part in state running). Native permission prompts write NO DB evidence — they are honestly indistinguishable from Modell; Freigabe names only the provable class
🅿 Quota-Park · frei ab HH:MM not running, and the session's LAST life signal is a quota death the CLI logs' turn.failed with cause.code=model_rate_limited (provider 1308), newer than every transcript write; the reset stamp converts from the provider's UTC+8 clock. A session that lived after the death is NOT parked
Terminal not running with an honest terminal word the badge's own honest_turn_status words (completed / failed / error / timeout / cancelled). A died-unlogged row carries NO state — never a guess

The quota-park gate is deliberately conservative (10 s skew on the activity seam — the dying turn's own completed_at lands moments after the log event): only a session whose whole story ends at the quota wall is parked; anything later (a new turn, a delivered steer) reads as the later life it is.

Steering from the observatory

The steering console is a write-capable action of its own kind — and it still never writes any database itself: sending a prompt spawns the installed zcode-dispatch (v0.2.3+, PATH command) detached with --resume <sessionId>, exactly like the dispatcher's own notify-parent steers. The observatory only auto-detects the target (session.directory plus the last modelSelection by message sequence — modelId and reasoningLevel option, never guessed), writes the prompt to a scratch file, and watches the dispatcher's incremental result JSON.

Both surfaces — the response modal and the full transcript drawer — carry the prompt dialog: a multi-line input (Enter sends, Shift+Enter adds a newline), a Send button rendered VERTICALLY (one upright column of letters, a slim tower) that spans upward from the prompt into the free space at the right end of the target line — right of the ⤢ expand control, never into the status/response row below — and a target line showing title · model · reasoning level · context utilization · cache hit rate · cwd basename plus the state-aware busy signal (bg active · Ns / idle) and the live queue depth (1 in flight · 2 queued) — so it is always visible where the message goes, how full the session's context already is, AND whether it will drive or queue. The console's geometry is state-invariant: the send tower's height comes only from the target line + prompt (rows the tail never touches), and its one-column label keeps the width constant, so the button sits at the identical spot with the last-steer answer block open, minimized to its chip, or absent — the prompt never reflows around a collapsing tail. The word "Send" is the whole label because the target line above already names the destination; the accessible context lives on aria-label/tooltip ("Send to ⟨session title⟩ — queues as the next sequential turn"). The one-click ⤢ expand control that toggles the prompt to double the compact height (112 px → 224 px, ~16 lines — v1.20.0; it used to add only a third) rides the target line, stacked above the input row next to the send tower, so it never costs horizontal width inside the prompt area itself. The Models column shows the same reasoning level next to the model chip (· max, · low, …) — the level of the session's newest model usage, single line by design.

The target name is click-to-copy for the session id (v1.100.0, owner wish #59): clicking the steer target's NAME copies that session's id to the clipboard — confirmed by the house copy toast ("sessionId copied", same idiom and timing as every ⧉ copy button on the deck, one toast that refreshes on re-clicks instead of stacking). The copy rides the #tSid idiom (the drawer's sid text is its own copy affordance — the house retired the standalone per-panel 📋 buttons), upgraded to a real <button> so the keyboard path is first-class: the name is focusable and Enter/Space trigger the same copy + toast through the deck's one delegated [data-copy] listener. The payload is the exact id the steer addresses — st.sid verbatim, the same value Send POSTs — so the shown name, the copied id and the POST target can never drift apart. An untitled target renders its id as the sid deep link (opening the session's panel — navigation is never hijacked), and there the copy rides the ⧉ glyph immediately beside the link, the same placement grammar as the sessions table's sid cell. Both branches carry the house tooltip stating what will happen ("click to copy the full sessionId — the exact id the steer addresses" / "copy sessionId").

The ⤓ collapse toggle (v1.39.0, widened v1.41.0) sits on the target line right of the ⤢ expand control: one click folds the whole prompt area away — input row, send tower, the target line's text AND, since v1.41.0, the steer-answer strip below — returning its height to the chat body for long reads (the geometry is real: the chat area grows by the full input+strip delta). Collapsed, the toggle becomes a full-width restore bar ("⤒ restore prompt · steer status") — the one element left in its row, a proper click target, no left-hugging sliver. The folded strip keeps re-rendering hidden, and its "● new" unread state surfaces as a mint ● dot on the bar's right side (tooltip names the count) — a completed steer is never silently folded away; restoring brings the strip back in its exact pre-fold state. Sending needs one unfold click, and Enter-to-send is inherently gone with the hidden textarea. The state is one localStorage flag (zdo-prompt-collapsed, the zdo-rate house pattern) shared by both consoles, applied at boot and persisted on every toggle, and it survives the 3 s poll re-renders (the console wrap is static DOM — only its status/target spans re-render). Folding rides the same scroll-compensation as every console height change: a chat at its end stays at its new bottom, a chat scrolled up keeps its reading position exactly.

The toggle pair breathes (v1.46.0): ⤢ and ⤓ wear a FIXED EQUAL width (36 px, wider than the old glyph-hugging ~27 px — comfortable click targets) with their glyphs centered (buttons center their text; zero horizontal padding keeps the fixed width honest), so the pair reads as one evenly sized duo whatever glyph each button shows. The ⤓ toggle also keeps the console's 8 px rhythm toward the send tower (margin-right) — the pair was flush against the strip before; the grid columns themselves stay flush (the prompt's right edge still touches the tower, the documented invariant), and collapsed, the margin resets so the restore bar keeps its full width.

steer console

The steer console on a session (fixture data, captured at 1177 px inner width — no question card in frame, just the console itself): the target line reads ⟶ steer target: e2e-gate — dispatch round trip · GLM-5.3 · max · 63% ctx · cache ~93% · zd-e2e-gate-g9x20261001 · idle · 2 queued, the prompt with the slim vertical Send tower rising into the target line's free right side — and the two queued steers are the FIFO showcase: oldest first, one slot per session.

Context utilization and cache hit rate (the math)

The two target-line percentages mirror the numbers the ZCode client itself shows, from the same data it uses:

  • NN% ctx — context-window utilization. The client's used-tokens value is the latest model request's tokens.total (the step-finish parts; input there already includes cached tokens — fresh input + cache read + cache write — and total adds the request's output). The window size comes from the provider config's modelConfigRules cascade (/opt/ZCode/resources/config/provider/zcode-builtin.json; regex rules applied in order, later matches override — GLM-5.3 sizes to 1,000,000 tokens). Percentage = latest tokens.total ÷ context window. The per-turn sums in turn_usage are deliberately NOT used: they add up every request of a multi-request turn and overstate the live context.
  • cache ~NN% — the session-average cache hit rate the client shows on hover: Σ cache.read ÷ Σ input across the session's model requests (step-finish parts), with the request count in the tooltip.

Both segments hide honestly when the DB offers no basis (no model requests yet, or no window rule resolves the model). Raw numbers live in the tooltips; ZDO_PROVIDER_CONFIG points at an alternative provider config, and an embedded snapshot of the z.ai model rules serves stations where no config file is found. (Formulas verified against the installed desktop bundle, 2026-09-25.)

The steer console, observed

The send button keeps the queue promise: it stays ENABLED while earlier steers for the session are queued or in flight — each send becomes the next sequential turn (the dispatcher self-queues behind the held lock; nothing ever interleaves). The button is held only for the moment of the POST itself. A soft cap (default 3 waiting steers, ZDO_STEER_QUEUE_CAP) refuses further sends — and since v1.71.0 the refused message STAYS as a visible error chip with the cap's own numbers plus Retry (the words never leave the client: the server refuses before writing any scratch file) — protection against accidental pile-ups, never a dead button over queue state. While in flight the dialog live-polls (5 s) and walks sending → turn-running → queued → terminal. The status line explains only the steer's current state — and for the queued state the WHY and WHEN: "queued · waiting for the current turn to end — delivers ~3 s after turn end on the fast lane (default); ~≤45 s only with ZDO_FASTLANE=0 (grace + sweeper cadence)". A steer that ended COMPLETED is a success whatever carried the news — exit 0, or a restart-era result file with only a status — and renders a green ✅; red ❌ stays reserved for genuine failures. The full exit-code table sits behind a small ? affordance next to the status, in a popover explicitly labeled static reference — not the live status of this steer.

Optimistic echo — never lose your words: pressing Enter shows the message INSTANTLY in the open conversation view as a pending entry (user styling with a dashed accent and a ⏳ chip — clearly not yet confirmed). The next DB poll that sees the row reconciles by content+timestamp and the echo becomes the normal message rendering in the same render pass — no flicker, no duplicate. If delivery fails (dispatcher error, drop, dead-letter, or the POST itself never got out), the entry NEVER silently disappears: it flips to a visible error state ("⚠ not delivered") with the full text preserved plus Retry (re-queues through the steer endpoint) and copy text. Every chip — pending or failed — also carries ✎ dismiss/edit: dismissing recalls the exact words back into the prompt textarea (a non-empty draft asks before being replaced), focused and ready to edit and re-send — never retyped. Dismiss CANCELS the delivery (v1.26.0): a still-QUEUED steer is removed from the dispatcher's sweeper queue and dead-lettered (POST /api/steer/dismiss — a racing sweeper save cannot resurrect it), so dismissed means gone, not "gone from view but still coming"; the words still return to the prompt for editing. An in-flight or already delivered turn cannot be recalled — the note says so honestly. Dismissal is AUTHORITATIVE across every representation (v1.51.0): the server sweeps the sweeper queue by parent session + content fingerprint (a whitespace-normalized hash of the steer text), so retry-created copies — same words, fresh timestamps, a new synthetic child id — die with the original, and a failed chip's dismissal kills its still-queued copy too (the pre-v1.51.0 UI only called the server for pending chips; a dismissed-and-retried steer could deliver as a zombie). The sweep rides the dispatcher's own ledger discipline (flock on the .lock sidecar + salvage read + unique-tmp atomic replace), a retry re-queues exactly ONE fresh representation (the retry fires the same sweep for the OLD attempt with keep protecting the fresh child), and every removed copy is dead-lettered. A child- keyed dismissal guard (15 min TTL, ZDO_DISMISS_GUARD_TTL_MS) keeps sweeping re-queues under a dismissed child — the mid-delivery exit-3 self-queue and a racing sweeper's resurrected entry die on the next watcher pass, BEFORE any lane can deliver them. The one honest boundary: a steer already mid-delivery (dispatcher running, turn started) cannot be recalled — the ✎ click answers "cannot recall — delivering" and the chip STAYS as a 🚚 delivering chip with the dismissal still armed, so a later re-queued copy is killed on the next ✎ (or by the guard) instead of resurrecting invisibly. Pending echoes survive panel close/reopen within the page session (a small local buffer keyed by session id).

The composer never silently clears (v1.71.0, round NNN — the void dies): the live defect behind this round: three consecutive owner steers typed against a "queued position 3/3" target vanished on Enter — no echo, no pending chip, no store entry, composer cleared. The send path's 409 branch (the queue-cap refusal) read an undeclared binding, threw, and landed in the catch AFTER dropping the optimistic echo; the catch then cleared the composer — the words existed nowhere. The fix is structural, in every submit branch (fresh, busy, queued, parked, error): the textarea empties ONLY when the submitted text is durably rendered — an echo in the page-session buffer, or one already confirmed into the DB (a confirmation tombstone survives the echo's removal) — and ONLY while the owner has not typed a newer draft over it (typing during the in-flight POST is never wiped). Every submit failure — the queue cap, any other refusal, a server that cannot be reached — keeps the message visible as the "⚠ not delivered" chip with Retry and copy, and the status line names where the words are ("kept below with retry" / "still in the prompt") instead of guessing. The refusal text is the server's own message when it sent one, never an undeclared binding mid-branch.

Response tail, collapsed by default (v1.26.0): on completion the dialog surfaces the response tail (last ~500 chars) as a slim one-line summary chip (✅ completed · <last line…> ▸) from the start — new content announces itself with a mint ● new dot ON the chip and the OWNER decides when to look (the old auto-expand-then-collapse cycle read as visual noise). Clicking the chip expands the full block with its own scroll area; reading it (viewed for ~1.5 s, or collapsed manually) marks it seen and returns it to the one-line form. Reading a tail counts across both surfaces (modal and drawer).

Queue-at-the-turn-boundary: a steer sent while the target session is busy (a running turn, a held dispatcher lock, or transcript activity inside the busy grace window — the same signal the dispatcher's own notify-parent prechecks use) never races the running turn. The spawned dispatcher carries the notify markers, exits 3, and self-queues into the stream sweeper's pending file. Delivery then follows the turn boundary: the turn-end fast lane (see above) claims the entry and delivers your prompt verbatim as the next sequential turn ~3–5 s after the current turn ends; the sweeper (≈15 s cadence, 30 s idle grace) is the backstop. The row and the dialog both track the queue live ("📨 queued · waiting for the current turn to end", position among the session's steers, delivery state). Note that a message typed into the desktop app can still interleave with a running background turn — the observatory steer path is the race-free one.

FIFO by send time (v1.21.0): steers deliver strictly in the order they were SENT, per session. Two forensics fixed here: the dispatcher's exit-3 re-queue appends a raced entry at the queue file's BACK with a fresh queued_at — so an older steer that once raced a fresh turn sorted younger than a later one, and delivered after it; and a fresh send onto a momentarily-idle session that still held queued steers used to drive directly, jumping the whole line. Now the fast lane evaluates exactly ONE candidate per session — the oldest queued steer by the send time embedded in its synthetic child id (obs-<send-ms>-…, a value re-queues never touch; queued_at and file position no longer decide) — so a younger steer never delivers while an older one waits, even while the elder sits in backoff; and a send with elders waiting carries a busy-grace horizon far beyond any real activity age, making it self-queue behind them (fire A then B at a busy session, free it: A's turn runs first, then B's — both, in order, no skips). The sweeper keeps its own oldest-first sort as the backstop.

Steer API

# Send a follow-up prompt (runs zcode-dispatch detached; returns at once)
curl -s -X POST http://127.0.0.1:8767/api/session/sess_1a2b…/steer \
  -H 'Content-Type: application/json' \
  -d '{"text": "Now run the tests again and report."}'
# => 202 {"ok": true, "steer": {"id": "1764…-sess_1a2b…", "promptFile": …,
#          "resultFile": …, "logFile": …, "cwd": …, "model": …}}

# Status of the newest steer for the session (poll read-only)
curl -s http://127.0.0.1:8767/api/session/sess_1a2b…/steer/latest
# => {"target": {"cwd": …, "model": …, "reasoning": "max",
#              "contextTokens": 640120, "contextWindow": 1000000,
#              "contextPct": 64, "cacheHitPct": 96, "cacheRequests": 5,
#              "busy": false, "busyAgeMs": 4123},
#     "steer": {"status": "turn-running", "processAlive": true, "exitCode": null, …},
#     "steers": [ …every recorded steer of the session, newest first… ],
#     "queueDepth": {"inFlight": 1, "queued": 2}}

Errors: empty text → 400, unknown session → 404, missing zcode-dispatch on PATH → 503, queue cap reached (default 3 waiting) → 409 with a friendly message.

Scratch files live under ~/.config/zcode-dispatch/observatory-steers/ (override with ZDO_STEER_DIR): <ts>-<sid>.prompt (your prompt text), <ts>-<sid>.result.json (the dispatcher's incremental result), and <ts>-<sid>.log (dispatcher stdout/stderr — prompt text may appear there; it is owner content, never credentials).

Exit codes (dispatcher contract)

In the dialog this table lives behind the ? reference popover — the status line itself only ever explains the steer's current state.

Exit Meaning
0 turn completed — the response tail surfaces in the dialog
2 bad arguments, or steering with the wrong model (model-pinned sessions)
3 target busy — the steer self-queued; the sweeper delivers it as the next sequential turn when the session goes idle (the dialog tracks queued → delivered)
4 turn failed (model or protocol error)
5 timeout — the session stays intact and resumable
6 dispatcher bridge/config error

The dispatcher runs with no --timeout (long turns are legal), fully detached: it survives the observatory, and even after an observatory restart /steer/latest still reports the newest steer from its result file on disk. Steers also arm the pending-question answer channel (ZCODE_DISPATCH_ANSWERABLE=1): questions raised by the steered turn wait for an answer instead of auto-resolving empty — see the next section.

Three-state notification control (v1.49.0)

Desktop echoes have TWO independent dimensions, both surfaced in the header and on the slim line:

  • The gate mode — zcode-dispatch v1.0.27's lineage gate over every human-facing desktop echo (STARTED and the terminal event). Modes: 🔔 all (every echo — the pre-v1.0.27 behavior), 🧠 smart (the default: only sessions in the owner's direct care echo — chain-internal pinky/machine-room traffic stays silent, brain bells and steers are never gated), 🔕 muted (config-level: no echoes ever). Click the selector to cycle 🔔 all → 🧠 smart → 🔕 muted → all; both the expanded header button and the slim-line stat call the same one implementation.
  • The master mute switch (the notifications-muted marker file) — the manual master: while the file exists, NO desktop notification spawns regardless of mode (bells, steers and CC copies always ring). The 🔊 master: ON / 🔇 master: MUTED button (and the slim stat) toggle it; config-level 🔕 muted and the manual master are DIFFERENT states and are never conflated in the labels.
  • Presence (v1.83.0, round ZZZ) — the automatic dimension on the SAME marker file: while the observatory tab is focused/visible the dashboard reports it (POST /api/notifications/presence) and OS echoes are suppressed (the words render in-chat); hidden or minimized, they fire again per the gate. The reconcile is manual OR (armed AND visible) — the manual switch never clobbers across tab switches, a pre-existing file boot-adopts as manual, and env ZDO_NOTIFY_PRESENCE=0 disarms the dimension (the rig seam).

The mode's truth resolves exactly as the dispatcher resolves it per echo: env ZDO_NOTIFY_GATE > config notifications.gate > the code default smart. The read surfaces label the live source honestly: an env override renders read-only (gate: 🔔 all (env), dashed border; a click explains instead of writing — the env wins on every echo anyway), an unset config renders 🧠 smart · default (stations upgrading to v1.0.27 get smart with no config change — the owner's desktop goes quiet BY DESIGN; set all to restore the old behavior), and an invalid value falls back to smart with the raw value named in the tooltip (the dispatcher NOTEs and falls back the same way — fail-safe for readability, never silent loss).

The write path is the dispatcher's own sanctioned edit (docs/USAGE.md §4): POST /api/notifications/mode performs an atomic, key-preserving config.json edit — a surgical splice of the notifications.gate member only (every other key keeps its exact bytes; unknown keys survive verbatim), under the ledger family's flock discipline (sidecar config.json.gate.lock + tmp + fsync + os.replace), parse-verified before anything lands, refused (never clobbered) on corrupt JSON, a non-object root, a non-object notifications, or duplicate gate keys, and refused with 409 while the env override is active. The dispatcher resolves the gate per echo — a mode change takes effect on future echoes with NO restart. Pre-v1.0.27 dispatcher configs (no notifications key) read as the default and the selector still works: the write ADDS the key, which older dispatchers simply ignore.

GET /api/notifications extends additively (muted unchanged for existing consumers, plus a gate object: mode, source (env/config/default), envSet, configSet, invalid, modes). Both dimensions ride the poll cycle (and the push-feed safety resync when the SSE bridge is live): an external config edit by hand reflects on the surfaces without a reload.

Round QQ also lands the SS follow-ups: inline-handler embeds go through escjs (JS-layer backslash escaping UNDER the HTML-layer esc — the round-RR HTML-only &#39; fold was ineffective because the attribute parser decodes entities before the JS layer parses the handler; a hostile round-trip node harness proves the breakout class dead), guardian steer texts and the boot-config feed render thresholds via :g ("~70%", never "~70.0%"), and prompt templates stamp createdAt at creation (updates preserve it, legacy records without one are backfilled).

Notification clicks land in the cockpit (v1.26.0)

Desktop notifications (zcode-dispatch's notify_click.sh, round V and newer) deep-link into the OBSERVATORY instead of ZCode — clicking a bell opens http://127.0.0.1:8767/?session=<id> (with a quick port probe first and the zcode:// deep link as the fallback when the observatory is not running). The dashboard honors the parameter: after the first data render it auto-opens that session's interface modal and pins a family highlight + flash on the row — the visible "why did this open" that survives closing the modal. A session outside the selected window widens the window to "all" once for the lookup (the selector UI follows, nothing persisted); a session the payload does not know at all (outside the lookback, not dispatcher-driven) earns a friendly note instead of a silent blank. One click from bell to chat.

Since v1.92.0 (#53, extending #49's click hygiene) the deck-side door law is total: notification landings, cards, rows, popovers, the dock and every shortcut stay INSIDE the deck — the only path into the ZCode desktop app is the interface's own ↗ ZCode button pair (see The self-healing view + one door + the completed census).

Pending questions: see them, answer them

ZCode sessions regularly pause on an AskUserQuestion tool call (the "approve me"/clarify dialogs with predefined options and a free-text field). Those questions live in the shared DB the moment they are asked (a tool part with state.status='running' and the full question payload), so the observatory surfaces them for every session — desktop-driven and dispatcher-driven alike:

  • The row glows amber with a ❓ question badge (owner taste: glow over markers), the section header carries a question count, and a toast fires when a session gains a pending question.
  • The response modal and transcript drawer render the question card: question text, header, every option with its description, the multi-select hint — and the answer line once the question resolved.

pending question card in the response modal

A pending question in the response modal (fixture data, captured at 1177 px inner width — the only shot with a question card in it): the card with its three options and descriptions, the info strip above it (PINKY role badge, the clickable parent reference into the delegation backbone, context %, turns/tokens), the segmented view toggle in the header, and the dock still reachable at the bottom — switch chats without closing the question.

Answering: dispatch-driven sessions are answerable from here. The steered turn's dispatcher (v0.3.0+) parks the question and polls an answer file; the observatory's Send answer (option buttons, multi-select, custom free text, or Decline) writes it — the answer is forwarded to the app-server within ~1 s and the turn continues exactly as if the option had been clicked in ZCode. Sessions steered from the observatory arm the channel automatically (ZCODE_DISPATCH_ANSWERABLE=1 disables the app-server's 5-minute empty auto-resolution for that turn, so the question waits); dispatches from other agents keep the default — their questions are answerable from the observatory only within the 5-minute auto-resolution window, and the card shows that deadline.

# Answer the session's pending question (writes the dispatcher's answer file)
curl -s -X POST http://127.0.0.1:8767/api/session/sess_1a2b…/answer \
  -H 'Content-Type: application/json' \
  -d '{"answers": {"Deploy now?": "GO — Rollout jetzt (empfohlen)"}}'
# => 202 {"ok": true, "delivered": "~/.config/zcode-dispatch/questions/….answer.json", …}

Honest limitation — desktop-driven sessions cannot be answered from here. A pending question of a session driven by the desktop app lives inside the desktop's own app-server process (in-memory interaction registry, private stdio); there is no external channel into it (hard negative verified against the installed product, see zcode-dispatch's docs/PROTOCOL.md). Those question cards are read-only — answer them in ZCode itself. The card says which case you are in.

The emergency interrupt: the owner's hammer 🔨 (v1.54.0)

A live precedent (steer #44, 2026-10-08): a brain stuck ~20 minutes IN-TURN on its pinkies held its turn while the owner's steers queued behind it — deafness by wedge. The MasterBrain surgically killed the turn and the queue drained instantly. v1.54.0 is that kill, productized with the SAME discipline and honest guards on top:

  • The control — a distinct 🔨 button on the steer composer (both consoles), quiet and dim by default. It arms ONLY when the read-only probe (GET /api/session/<id>/interrupt, piggybacked on the steer status poll) proves a genuinely running turn behind a live VERIFIED dispatcher for the session; armed, it pulses amber and its tooltip states exactly what is queued and what will die. The confirmation dialog is verbatim-honest: "Cancels the session's running turn (in-turn work is lost; dispatched pinkies survive) and delivers immediately." The typed prompt (if any) delivers as the immediate next steer through the normal queue.
  • The mechanics — the surgical pattern, exactly — locate the session's live dispatcher lock (dispatch-<sha1(cwd)>.lock, the same seam the dispatcher's exit-3 busy message exposes); VERIFY before killing (anti-PID-reuse): the lock's PID must be alive AND its /proc/<pid>/cmdline must parse as a dispatcher driving THIS session (--cwd exact AND --resume <sid>, or the --out result JSON's sessionId); kill the dispatcher PID + its app-server child tree by EXACT PIDs from the kernel's own children lists, SIGTERM then SIGKILL escalation, every member's cmdline hash re-proven before each signal round (root and children alike — folded WW-F1). Never a pattern match anywhere in the path — no pkill, no pgrep, no name scans. A PID that died and was reused between verification and the signal is SKIPPED honestly.
  • The guards — no genuinely running turn → honest 409 "the hammer stays down; a normal steer queues and delivers as the next sequential turn"; no live lock / dead-PID lock / cmdline mismatch → 409 "the steer queues normally, nothing to kill"; one interrupt per session per rate window (default 5 min, env ZDO_INTERRUPT_RATE_LIMIT_MS) → 429; the endpoint requires an explicit confirm: true (400 otherwise — no accidental hammer).
  • The audit trail — every interrupt lands durably in observatory-interrupts.json (beside the dispatcher config, env ZDO_INTERRUPT_JOURNAL; capped at 200, atomic writes, survives restarts): session, the interrupted turn's row, the lock (file, PID, model, hold age), the cmdline-proof hash + argv summary, per-PID kill/skip records with their own hashes, the drained steer ids, the queued kill breadcrumb (its queue child id — v1.58.0), the operator identity (request client + X-Operator header) and the timestamp.
  • The kill-reason breadcrumb (v1.58.0, steer #53) — the killed brain no longer has to learn WHY its turn died from the owner's next message. Every creator-kill automatically enqueues a one-line SYSTEM breadcrumb at the FIFO HEAD of the killed session's queue: [system] turn #N was terminated by creator at owner request, HH:MM UTC — queued steers follow. It rides the EXISTING queue machinery (a steer-shaped sweeper entry whose send time and queued_at both sort one tick ahead of every already-queued steer, so both delivery lanes — the observatory's turn-boundary fast lane and the dispatcher's sweeper — deliver it first without special-casing), obeys the same authoritative-dismissal discipline as any steer, never counts toward the owner-facing steer counts, caps or waiting ages ("system": true), carries no dismiss chip anywhere, and renders in transcripts as a muted SYSTEM chip — never user green. The audit journal links its queue id to the kill entry.
  • The delivery is the normal machinery — after the kill the session is idle: the queued steers drain through the dispatcher's own sweeper (its frozen-activity probe treats the dead turn as over at once — the inflight marker's dead PID is broken on sight), and the interrupting steer (when a text rode the request) delivers as the immediate next turn. FIFO by send time, exactly as every steer — with the breadcrumb at the head.

The turn-blocking detection seam (steer #45) — the wedge never needs to be noticed by hand again. The observatory surfaces the CONJUNCTION — a session whose turn is RUNNING while ≥1 live dispatched child of it runs AND ≥1 steer queues for it — as "⚠ turn-blocking · N steers queue" on the row's cwd line (a button: ONE click from that state opens the interrupt's confirm dialog), in the row detail's info strip, and as an amber alert on the collapsed slim line (⚠ turn-blocking (N) — alert precedence, above quota). Since v1.58.0 the state also carries the owner-waiting age — the age of the OLDEST queued USER steer, in the compact human form (45s / 4m12s / 1h02m): the chip reads "⚠ turn-blocking · 2 steers queue · owner waiting 4m12s", the same age rides the detail badge and the slim-line alert, and the sessions payload exposes it as turnBlocking.oldestSteerAgeMs (the peek's data — see For agents). The join rides existing seams only (the live /proc spawn linkage, the dispatch ledger's non-terminal notify-parent records, the sweeper queue's per-parent counts) — no new scans; it updates within seconds of the wedge forming or clearing, and any missing leg means no state (a stuck turn WITHOUT a live child is presumed-dead territory; queued steers without a running turn are just the queue doing its job).

Cross-lane note (Brain-A's zcode-dispatch): kill is the v1. A graceful dispatcher-side CANCEL API (a control file the dispatcher's turn loop polls, or a --cancel <sid> sidecar mode that funnels through the dispatcher's own cleanup) would beat the kill — the app-server could flush its turn state, the lock would release through the dispatcher's own atexit, and no process-tree surgery would be needed. The observatory's interrupt path is structured so its verification+audit halves stay unchanged when that API lands; only the kill step swaps.

The detector doctrine (v1.74.0, sharpened v1.86.0 / v1.88.0)

The hammer's eyes are liveliness-first and install-independent. A genuinely running turn is proved by the honest evidence classes — a running turn_usage row (the strongest), transcript CHURN strictly newer than every terminal row, a live VERIFIED dispatcher mid-lifecycle (driven), or /proc process signals: a dispatcher driving the cwd, a --resume / --out sessionId match, a live app-server owning the cwd, or an ESTABLISHED provider socket (a generation in flight). A process-proven row stays interruptible forever while the process lives; a quiet phase past 90 s renders run <dur> · thinking on the Laufzeit badge. The desktop-driven master shape (a live turn with no dispatcher to kill) is SEEN honestly — see-only evidence; the kill itself stays confirm-gated on a proven dispatcher tree (seeing ≠ killing). The free path: a running row whose driver is gone (a dead-PID lock, an unprovable cmdline, no lock, or a terminal result) is FREED — cancelled with the app-server's own user-cancel shape — instead of refused; a running row has no natural decay, and freeing stuck rows is the feature's whole purpose. Sessions that died mid-first-turn with no turn rows at all get evidence-backed DERIVED states (the bell ledger's failed verdict, terminal dispatch-ledger rows, the master's kill-log breadcrumb, a timestamp-cluster · storm-class refinement, or the honest lost (no record)) — never a bare dash. Nothing is silent: refusals carry the detector's reason and hold the screen, the 💀 presumed-dead chip is a one-click button into the free path, and the success toasts distinguish "turn cancelled" from "stuck turn freed".

(The full round-era forensics behind the detector classes — RRR's silent-failure archaeology, CCCC's doctrine, GGGG's conductor blind spots — are archived verbatim in docs/history.md.)

The finalize reconciler: no open rows from finished work (v1.62.0)

The dispatcher stamps its own lifecycle row terminal on every known exit path — but a dispatch that dies otherwise (SIGKILL mid-turn, a kill in the exit window between the terminal result write and the ledger stamp — the "launcher-exit gap", live evidence 2026-10-09: a finished genesis/steer dispatch whose row stayed open until the reaper flagged it and the strip lied "💀 presumed dead" for ~50 min) leaves the row OPEN forever while the work verifiably completed. The sibling of the CCC false-delivery root cause — there a record said delivered when it wasn't, here an open row said dead when the work was done.

Since v1.62.0 the observatory closes that gap for the rows IT owns (proof of ownership: the row's result path lives under the observatory's own result namespaces — genesis results and observatory-steers; every other row stays entirely with the dispatcher's own lifecycle and the A-lane reaper). A reconcile pass runs at startup and every ZDO_RECONCILE_POLL_S seconds (2 s default; ZDO_RECONCILE=0 disables), with two evidence tiers — never a guess:

  • Result-terminal (the seconds-bound finalize) — the dispatch's result JSON carries a terminal status: the dispatcher's own final word, written before its ledger stamp. A still-LIVE dispatcher gets a short settle window (ZDO_RECONCILE_SETTLE_S, 4 s) to stamp its own terminal first; past that the row closes with the result's status verbatim and its mapped exit code, plus a finalized_by: observatory-ggg audit trail.
  • Provable-death backstop — the dispatcher pid is gone from /proc (or a zombie) with NO terminal result: the row closes completed only when the bell ledger carries the dispatcher's own completed-event bell for this dispatch (the anchor's exact class — work done, bell rung, row open); otherwise it closes failed with the honest note. Terminal success is never guessed. The bell witness is the dispatcher's own event: completed classification — read RAW (a missing event must not default to completed), and intent-only records count deliberately (GGG-F1, v1.63.0): dispatch.py writes bell intents at exactly one site, inside notify_parent at terminal finish with the already-classified status, before the notification spawn — so an intent with event: completed (no delivered_at) still proves the child's turn completed; requiring the delivery stamp instead would re-open the binary-swap-window rows (work done, dispatcher killed between intent and spawn) this reconciler exists to close. The bell delivery is the parent's lane — its absence heals through the sweeper's watchdog, not through this child row's status.

Never touched: terminal rows, live-pid rows (a turn can run for hours), pid-reuse rows without result-terminal evidence (a starttime mismatch is not a death license), and foreign rows. Writes mirror the dispatcher's own ledger discipline (flock sidecar, salvage reads, re-check under the lock, atomic replace, 200-row cap) and the whole pass is fail-open — a reconciler problem can never disturb the dispatch fleet. The presumed-dead machinery stays fully honest for genuinely-unknown outcomes; it just never again sees a finished observatory dispatch as an open row.

The rumble monitor: dispatch acceptance ≠ running (v1.64.0)

The owner ruling after the dead-fleet illusion (2026-10-11): the MasterBrain fired wake steers, every ok: true read as success — while the whole fleet sat silently dead on pinned-provider refusals. Nothing rumbled, everyone believed. Since v1.64.0 the observatory VERIFIES its own fires:

  • The expectation ledger (I1): every fire the observatory makes — the steer API, fastlane deliveries, genesis spawns (manual, queue pump, schedule), the interrupt breadcrumb, the rumble's own nudge bells — records an EXPECTATION in its own small durable store (fired-expectations.json beside the tool; ZDO_EXPECTATIONS_FILE to relocate; capped at 200 records like the house stores). A bypassed fire is a gate red: the suite pins every fire path.
  • The join (I2): a watcher ticks every ZDO_RUMBLE_SCAN_S (5 s) and checks each open expectation for life — a genuinely running turn, transcript churn at/after the fire, or a dispatcher PROVEN by /proc cmdline truth (--cwd exact + resume/out session match — a recycled PID never fakes alive) — and for honest terminals: delivered (the CCC send-ack receipt or a completed result), failed-WITH-REASON, dead-lettered, or a ledger row the GGG reconciler closed. A fire still queued is an honest hold (the queue machinery owns delivery — that story already renders as 📨 queued).
  • Loud silence (I3): past the window (ZDO_RUMBLE_WINDOW_S, 30 s) plus grace (ZDO_RUMBLE_GRACE_S, 15 s — 45 s total, arithmetic pinned by tests) with NO life and NO honest terminal, the episode renders ⚠ fired but silent · 47s on the session row and the collapsed header's slim line (one surface per mode, the S-40 rule; the panel mirrors the badge). The age ticks from the FIRE. One optional nudge bell rides the canonical queue channel toward the silent target's fleet master (the dispatch-ledger creator trail) — gated by the three-state notification controls (muted never rings), exactly ONE per episode, and a nudge never nudges.
  • Clearing (I4): observed life or an honest terminal resolves the episode forever — a delivered-and-processed fire never warns; an honestly-failed fire shows its failure (✖ fired → failed, with the dispatcher's own error words, e.g. the pinned-provider refusal) instead of the silence-warning, decaying after 10 minutes. Episodes expire after ZDO_RUMBLE_EXPIRE_MS (6 h) — no state that cannot go away. For spawns, the session id is adopted from the result JSON the moment the dispatcher creates the session, so a dead-on-arrival spawn (no session, no terminal) warns exactly like a vanished steer.
  • The failure badge is PER EPISODE (v1.70.0 / M1). The ✖ count is the consecutive failures since the target's last SUCCESS — a success at the same target (a fire that resolved delivered, or one whose target provably showed life) CLOSES the episode: the red ✖ clears at once and the closed episode renders a calm ✖ recovered after N failures transient that fades with the same 10-minute retention. A failure that fired AFTER the last success is new knowledge and stays loud. The closure is durable (a sticky recoveredAtMs stamp on the failure records — the success record itself ages out within a minute, but a recovered failure never flips back to live) and immediate (the row-state pass computes the closure in-flight, so the badge clears on the very next poll, not just on the next watcher tick). The owner's finding that motivated this: a master wore "✖ ×2 next to a healthy door" for the full 10-minute retention because the exit-6-era failures never cleared after the retry succeeded.

This catches the whole class the receipt machinery cannot see: the fire SUCCEEDED (spawned, accepted, exit 0) and the RUN never happened — pinned-provider refusals, auth-clobbered resumes, launcher-exit-0 handoffs into a dead runtime. /api/config carries the live timing (rumble.windowS/graceS); ZDO_RUMBLE=0 disables the watcher (fires still record; nothing joins them).

Rig note (III-F6, folded v1.65.0): a fixture rig that asserts AGE-TICKING behavior (the ⚠ chip's age advancing between polls) must set ZDO_CACHE_TTL_S=1 — the response cache otherwise freezes the whole /api/sessions body (ages included) up to its TTL, and the rig reads a stale world while the fixture moved on.

The master door: the deck watches the watcher (v1.67.0)

The owner's ruling: the master session's door is the single point of failure of the whole orchestration — every pinky and brain bells HOME to it, and if the master sleeps with bells pending, the fleet's answers pile up unheard. Since v1.67.0 the deck's TOP-PRIORITY line surfaces exactly that door's health, in BOTH header modes (the one-surface-per-mode rule): the slim line's first entry and the expanded header's first status chip render

  • 🚪 master door: N bells unconfirmed · 4m (amber; red when any bell is stuck — no delivery, past BELL_STUCK_MS (5 min) and no sweeper-queue entry left, the lost-in-flight class) — completed children rang home at the master's door without a delivery receipt. It outranks every other warning (rumble, quota amber, canary, push, presumed-dead): if the master sleeps with pending bells, one glance suffices and the owner's own message can be the kiss that wakes it. Clicking the line/chip jumps to that master's steer console — the wake path is one click from the warning.
  • 🚪 master door: healthy (dim, ever-present) — every bell at the lane master's door(s) carries a delivery receipt.
  • 🚪 master door: no lane (dim) — the honest-absence state: no family root with children exists in the lineage data, so there is no orchestration door to watch (never a masquerading healthy; the row-level bell states keep every real door visible regardless).

Identification is generic (never a hardcoded session id): the master of a lane is its ROOT session per the lineage data the observatory already holds — the session that is its own family root (native parent_id, bell-ledger edges, live spawn edges, delivered doorbells — the family_maps precedence) AND has children. Sub-brains (baby masters running their own pinkies) are not lane masters: their doors stay row-level concerns. Several lanes aggregate honestly — N is the bell total across master doors, the tooltip carries the per-door breakdown, and the jump targets the WORST door (stuck before pending, oldest first).

The count is the ledger's honest receipt state: one source, the dispatcher's bell ledger (stream-bells.json) — N = records with no delivered_at whose parent is an identified master. A receipt landing drops N on the next poll (the bells file is a response-cache source key, so the line tracks the fixture/ledger within the honest cadence); delivered bells never count; ancient undelivered records (>24 h, pre-ledger noise) carry no signal. The ledger's one receipt class is delivered — there is no acknowledged/processed stamp (today a delivered-but-unread bell reads healthy by receipt; the owner-unread machinery covers the reading side). Bells at doors that are not identified masters never inflate the line.

The context guardian: the self-driving recycle gradient (v1.48.0)

The owner's context-recycle doctrine, operationalized. The deck already computes contextPct for every session; the guardian watches it on BRAIN-class sessions only — brain-role rows plus masterbrains (parents of brain-role children, any own role) per the lineage role-class. Pinkies are single-round workers and are excluded by design. One sweep rides each scheduler tick (the v1.47.0 fleet thread; same ZDO_SCHEDULER_TICK_S cadence):

  • Prepare (≥ 70%, ZDO_GUARDIAN_PREPARE_PCT) — the brain is steered ONCE with the begin-successor-preparation notice: grow a detailed, clean living handoff over the coming turns at ~/logs/<domain>-handoff.md (or under ~/.zcode/tmp/), worded as doctrine-start, not alarm.
  • Transition (≥ 78%, ZDO_GUARDIAN_TRANSITION_PCT) — steered ONCE with the transition recommendation: pick a suitable seam (round boundary, milestone, validator green), never forced mid-great-work, with the 95%-max-stretch-only-with-handoff-complete bound stated.

Both notices go through the observatory's EXISTING steer machinery (start_steer): an in-band prompt queued for delivery at a turn boundary — the guardian never interrupts a running turn, never rings a desktop bell. A jump across both thresholds in one tick fires both stages, each exactly once, prepare first.

Exactly-once is durable: per-sid stage flags live in ~/.config/zcode-dispatch/observatory-guardian.json (ZDO_GUARDIAN_STATE_FILE) — a restart never re-fires a stage. A sid still on the deck never prunes from the state (a listing flap cannot re-fire); sids gone from the deck age out after 7 days (ZDO_GUARDIAN_PRUNE_MS), and the store stays bounded (500 entries, GUARDIAN_STATE_MAX) with oldest-first eviction — the same window discipline the dispatcher's ledgers use. A stage marks fired only when its steer actually spawned: a refused send (queue cap) honestly retries on the next tick.

Stage hints in the UI: wherever contextPct renders — the Ctx column, the response-modal/drawer info strip, the steer target line — BRAIN-class rows carry a subtle marker at each stage: 🧠 prep successor (amber) and 🧠 transition recommended (mint accent). Doctrine, never a scary red. The thresholds ride /api/config, so the hints always match the firing config.

Knobs: ZDO_GUARDIAN (0 disables the guardian entirely — default on), ZDO_GUARDIAN_PREPARE_PCT (default 70), ZDO_GUARDIAN_TRANSITION_PCT (default 78), ZDO_GUARDIAN_STATE_FILE, ZDO_GUARDIAN_PRUNE_MS (default 7 days). With ZDO_SCHEDULER=0 the guardian is idle too — no tick carries it.

How brains and owners dance (v1.58.0)

The doctrine in one image — the owner's own flavor line says it best, verbatim:

"user-und-masterbrain tanzen den gemeinsamen Tanz, während die pinky-brotherhood den Liebespogo durch die Nacht schiebt."

Two tracks, by design. The FOREGROUND track is you and your owner dancing hand in hand: short turns, his steers answered, the conversation never blocked by work — the owner is the prime channel, and a brain's own pinkies talk only with the brain, never with him. The BACKGROUND track is the pinky brotherhood and the sub-brains pogoeing through the night: every multi-step chain is dispatched as executor pinkies in their own systemd scopes, the brain ends its turn, and each 🔔 bell returns the results into the conversation. Neither track waits for the other in-turn — that separation IS the design.

Three seams connect the tracks, and the observatory products all of them:

  1. The queue peek — between steps of a long in-turn action chain, a brain cheaply peeks the user steer queue (GET /api/session/<sid>/info → pendingSteers + oldestSteerAgeMs) and yields or answers early when the owner has queued something. A long turn is not an undisturbed turn; the wedge state's "owner waiting 4m12s" is the same nudge for the owner's eyes.
  2. The creator's kill — the emergency interrupt frees a sunken brain; the automatic [system] breadcrumb explains the why in its next wake, and the queued steers follow right behind it.
  3. The bell — the dispatcher's notify machinery returns a finished pinky's results into the parent's conversation as the next sequential turn; ending the turn is not losing the thread.

Recursion — a brain may spawn SUB-BRAINS (--child-role brain again): baby masters running their own pinky orchestras, belling their parent. Spawn a sub-master when a lane outgrows pinky-per-task shape. 🪆

The proof story (2026-10-08, the incident that taught the model). An owner-stuck brain, live: the brain's turn ran ~20 minutes deep on dispatched pinkies while the owner's steers queued behind it — deafness by wedge. All three seams fired in one evening: the wedge surfaced on the observatory (the turn-blocking state), the owner queued steers (the foreground track trying to reach through), the hammer fell (the creator's kill — the turn died, the pinkies survived), the breadcrumb queued (this round's product: the why, automatically), the steers delivered (the queue drained at once), and the brain re-delegated the work within one turn. Nothing was lost — not the owner's words, not the pinkies' results, not the thread. That evening is why the newborn brain brief now opens with the dance instead of a topology list: the model is not decoration, it is the survival shape of the fleet.

The same model, in full operational depth (including the recursion rule and the context-recycle gradient), lives in the station's zcode-dispatch-usage skill — terms are kept consistent across both.

For agents / inter-session steering

This is THE canonical inter-session channel for agents (brains, pinkies, any dispatched session that needs to talk to another session). Two masters have burned tool calls hunting another session's cwd — ledger greps, DB spelunking — just to send one steer. Stop hunting: the observatory resolves everything for you.

Send a steer (a prompt delivered as the target session's next turn):

curl -sS -X POST http://127.0.0.1:8767/api/session/<sessionId>/steer \
  -H 'Content-Type: application/json' \
  -d '{"text": "status report: what landed since my last steer?"}'
# => 202 {"ok": true, "steer": {"id": "…", "cwd": "…", "model": "…",
#         "busy": false, …}}

What the endpoint does for you — the whole point:

  • Auto-detection, no hunting: the target's cwd and model are resolved from the session DB (session.directory plus the last modelSelection.modelId by message sequence — never guessed). You send only the session id and the text.
  • Busy-queue semantics: a steer to a session that is mid-turn (or already has steers waiting) self-queues FIFO by send time and delivers at the turn boundary as the next sequential turn — sending while busy is legal and correct, never interleaves, never drops.
  • The cap: the dispatcher enforces one driver per cwd (a second dispatcher on the same cwd exits 3 and self-queues — that exit is the queue mechanism, not an error), and the observatory soft-caps waiting steers per session (409 when the queue is full — let them deliver first).
  • Read the state without steering: GET /api/session/<sessionId>/info answers {"id", "cwd", "model", "role", "title", "pendingSteers", "oldestSteerAgeMs"} (read-only, the same seams the steer console trusts; 404 honest for unknown ids) — for agents that still prefer the CLI dispatch path and need the exact --cwd/--model pair.
curl -sS http://127.0.0.1:8767/api/session/sess_…/info
# => {"id": "sess_…", "cwd": "/home/…/.zcode/tmp/…", "model": "GLM-5.3",
#     "role": "brain", "title": "[🧠] … · …",
#     "pendingSteers": 2, "oldestSteerAgeMs": 252000}

The queue PEEK (v1.58.0, steer #52) — the affordance the brain brief's dance clause points at: between steps of a LONG in-turn action chain, a brain cheaply peeks whether the owner has queued something — pendingSteers counts the session's queued user steers and oldestSteerAgeMs is the age of the oldest of them (epoch-ms age, null when none queue) — and yields or answers early instead of grinding on. A long turn is not an undisturbed turn; long legitimate work stays legitimate, the peek only keeps the owner from going unheard mid-chain. One read-only GET, no steering, no side effects — the same fields the wedge state surfaces as "⚠ turn-blocking · N steers queue · owner waiting …" (see the owner's hammer).

The other door, honestly: recent zcode-dispatch versions auto-resolve cwd+model on --resume too, so the CLI path (zcode-dispatch --resume <sid> --prompt-file …) needs no hunting either — pick whichever door your runtime already has. The HTTP API above is the canonical one because it also carries the queue semantics, the status feed (GET /api/session/<id>/steer/latest) and the pending-question answer channel.

This section is where the genesis role briefs point newborn brains (v1.53.0): every 🌱-born brain arrives knowing that this API — not cwd hunting — is how sessions talk to sessions. Since v1.54.0 the brief also teaches the newborn where to find its own session id (GET /api/sessions, match your cwd — the id every steer, bell and messenger addresses).

The emergency interrupt (v1.54.0, owner-facing but documented here because agents may need to know it exists): when a target session's turn is genuinely stuck while steers queue behind it, POST /api/session/<id>/interrupt with {"confirm": true} cancels the running turn (a verified, audited kill of exactly that dispatcher tree — see the owner's hammer) and the queued steers deliver at once; an optional "text" field delivers as the immediate next steer. The endpoint refuses honestly (409/429) whenever the guards — running turn, live verified dispatcher, rate window — do not hold.

Genesis — birth, worktrees, governance

Spawning new sessions from the deck: the wizard, the plan pickers, the worktree law, and fleet governance (templates, schedules, queue, lane budgets).

Session genesis: birth sessions from the deck (v1.31.0)

The 🌱 New Session control in the header opens a modal that spawns a dispatched session END-TO-END — the owner runs the whole hierarchy from the browser, never opening ZCode. Form → review → live status:

  • project picker over the known session directories (GET /api/genesis/cwds), with free-path entry and a LIVE placement-law suggestion: typing outside the sanctioned roots shows the amber hint plus a one-click ~/.zcode/tmp/<org>/<repo>/ adoption (the mirror form, since v1.44.0), and directories hosting interactive (typed) sessions carry a ⚠ interactive-lane flag. Picking a repo path resolves itself (v1.44.0): on Review the wizard cuts a dedicated worktree and adopts its path — since v1.56.0 the cut fires at REVIEW ENTRY (the role picker is final there, so the worktree's slug names the chosen role — see Auto-redirect + the worktree law);
  • model picker off the station config's actual offer (GET /api/genesis/models — the dispatcher's config.json models and provider, read-only); since v1.43.0 a GROUPED plan picker: options grouped by plan provider (registry display names), one selection = a (provider, model) pair riding --provider (see Plan-selectable spawns); since v1.111.0 every option states its layer capability — storage-key and config-chain plans ride every layer, a keyless (OAuth) plan is 🖥 desktop-only and renders DISABLED with its reason (the server's headless rail refuses the same choice with the same words — see The start-plan family: layer capability + the switch commission), and a capability-matrix line under the picker states which layer can ride WHAT;
  • reasoning picker (v1.34.0) beside the model: the dispatcher's --reasoning is a free string whose effective default is the config's default_reasoning ("max" on this station) — the picker offers the known-good ladder (max/high/medium/low/none) with that default preselected, ZDO_GENESIS_REASONING_OPTIONS overrides the list, and the chosen value rides the spawn argv verbatim (empty = the dispatcher's own default; the UI constrains, never the spawn path);
  • role picker (a compact segmented pair since v1.44.0): 🐺 pinky (worker) or 🧠 brain (delegation node — the child carries the [🧠] pinned title, brain glyphs and lineage backbone from second one, exactly like any dispatcher-spawned brain); the selected role's explanation renders as the subtext, ←/→ moves between the buttons;
  • optional bell wiring: pick an existing session as the parent and its doorbell rings when the child lands (role-badged picker; the parent must exist — 404 otherwise);
  • prompt as text (staged durably under ~/.zcode/tmp/genesis/) or a server-side prompt file (exclusive — a contradictory pair is refused; the file must resolve under ~/.zcode/tmp/, the same sanctioned scratch root as spawn cwds — the API never reads files outside it);
  • review step: the confirm summary shows cwd (copyable, with the ✓ auto-resolved tag when the wizard cut the worktree), model, role, reasoning, bell wiring and the prompt BEFORE anything starts — one glance, no accidental births.

the genesis modal open on its review step

The genesis modal on the review step (fixture data, captured at 1177 px inner width): the confirm summary lines up cwd (with the safe-root hint), model, role — here 🧠 brain, the delegation node — the bell wiring naming the parent whose doorbell rings when the child lands, and the prompt preview. Nothing has started yet; the 🌱 Spawn button below is the single confirming click.

The spawn itself (POST /api/session/create) is the create contract zcode-dispatch already exercises — the observatory only stages and spawns: the prompt lands at ~/.zcode/tmp/genesis/<slug>-task.md, the dispatcher runs in its OWN systemd user scope (zdo-genesis-<slug>, surviving observatory restarts and turn ends), and every user value rides argv POSITIONALS of a fixed bash -c script — hostile shell metacharacters are inert by construction (the test suite proves it with hostile cwd/model/name strings; the E2E proves the happy path with real dispatchers). The interim full-env rule rides the scope's ZDO_GENESIS_ENV_FILE (the station's mb2-dispatch-full.env; an empty value skips the sourcing — the post-hotfix world once zcode-dispatch injects the env itself; v1.57.0: a leading ~ is expanded by the observatory before the spawn — bash never tilde-expands a quoted variable, so the default's ~/… form sources too). The live status (GET /api/genesis/status?slug=…, polled by the status chip) walks spawning → created (session id) → turn-running → completed/failed with capped response/error excerpts, the scope unit name, and a deep link that opens the new row — which also appears in the tables on its own (poll cycle, or the SSE push feed once the bridge is live).

Rails (the owner's live world is sacred). The server-side cwd guard enforces the placement law: under ~/.zcode/tmp/ or the managed worktree root ~/git/worktrees/ is safe; everything else requires confirmDangerousCwd: true — the audit trail, surfaced in the UI as a server-driven two-step ("⚠ Spawn anyway" appears only after the server refused once). $HOME itself and the shared main checkouts under ~/git/ (mirrors included) are HARD-refused with no override. A prompt cap (256 KiB) and a rate cap (3 genesis spawns per 10 min per observatory process, ZDO_GENESIS_RATE_MAX) stop a stuck client from birthing an army.

The worktree law (v1.44.0). One law for all BORN sessions — root/master brains included, no exemptions: everyone works from a worktree under ~/.zcode/tmp/<org>/<repo>/<slug>/ — mirroring ~/git/<org>/<repo>/ exactly, so attribution is as unambiguous as the full git path; only the owner's own interactive sessions live in the main checkouts (human trust class). Existing pre-law master sessions are grandfathered until their natural context recycle; their successors spawn under the law; already-created worktree directories need no migration. The guard's refusal text cites it — context lives in the worktree.

Refused cwd? The correct home is offered — and repo picks resolve themselves (v1.36.0 / v1.44.0)

A hard refusal is correct but was unhelpful: picking a real project under ~/git/ got a flat no. Since v1.36.0 every refusal carries a door; since v1.44.0 the worktree door auto-applies (steer #29):

  • Repo variant — the MANDATORY door for project paths, now automatic. When the picked (or typed) path has a git repo at/near it (read-only git -C <path> rev-parse --show-toplevel from the nearest existing ancestor; bare mirrors honestly miss it — never base worktrees on them), the wizard probes POST /api/genesis/check {cwd, role, auto: true} on selection/blur and the server cuts the worktree right there — returning 200 {class: "resolved", resolved: {path, branch, base, …}} — and the form's cwd becomes the resolved path. Zero extra clicks between picking the repo and the review step; the review shows the resolved target, visible and copyable. A creation failure (fetch 503, collision 409, rate cap 429) surfaces inline with a retry resolution affordance — honest and retryable, never a silent nothing. The refusal text teaches WHY: context lives in the worktree — a project session must be born inside a worktree OF the project so it loads <project>/.zcode/AGENTS.md, project skills and repo context; a bare scratch dir is context-blind and wrong for project lanes.
  • Plain variant — project-less refusals ONLY, and deliberately explicit. $HOME-personal lanes, messengers, canaries get redirect: {mode: "plain", projectName, suggestedPath} — an isolated session home under the sanctioned scratch root, honestly labeled "no repo access". This door is NEVER auto-applied (it changes cwd semantics — the session sees no repo — and deserves a deliberate Create & use click); a repo path NEVER gets this door; a dangerous project-less path stays confirm-only (it is spawnable with the audit-trail click).

[Create & use] posts POST /api/genesis/worktree {mode, repoRoot | name, slug?, role, requestedPath?} and swaps the form's cwd to the returned path — the flow continues into the SAME review step (the final cwd visible with all rails); nothing spawns automatically. Since v1.44.0 the WORKTREE door reaches this endpoint through the check's auto mode (no click — the UI's explicit button remains only on the plain door); a "keep the refusal" link dismisses the plain offer; the plain refusal text stays visible either way.

The worktree creation honors the freshness law: the origin's default branch is resolved from the origin HEAD symref (ls-remote --symref), then git fetch origin +refs/heads/<default>:refs/remotes/origin/<default> runs BEFORE anything is created — a fetch failure is an honest retryable 503 with no partial dir; the worktree lands on branch oss-oo/genesis-<slug> based on the FETCHED tip, at ~/.zcode/tmp/<org>/<repo>/<slug>/ (the v1.44.0 placement law — the segments mirror ~/git/<org>/<repo>/, with a deepest-two-segments fallback for repos that live elsewhere). A repo without an origin remote stays on the mandatory worktree door, based on the repo's LOCAL default (checked-out branch, else init.defaultBranch, else main) — the base choice rides the redirect block and the audit record. Every git invocation is argv-list form through one seam (hostile repoRoot/slug/name stay inert; the suite proves it with a fake git and never runs a real fetch). Collisions answer 409 (path or branch); home creations ride their own rate window with the same caps (3 per 10 min — separate from spawn stamps, so a create-then-spawn round never eats two slots; the AUTO resolutions ride the same caps). The audit trail stays complete for BOTH doors: each creation appends a redirect record (requested path → created home + mode + branch + base and the auto flag) to ~/.zcode/tmp/genesis/redirects.json.

# Birth a pinky worker in a sanctioned scratch home (202 + live status)
curl -s http://127.0.0.1:8767/api/session/create -X POST \
  -H "Content-Type: application/json" --data-binary '{
    "cwd": "~/.zcode/tmp/oss-oo/website/worker-1/",
    "model": "GLM-5.3", "role": "pinky",
    "prompt": "Reply GENESIS-OK. No tools.",
    "notifyParent": "sess_parent…" }'
# => 202 {"ok": true, "unit": "zdo-genesis-20261001-…-reply-genesis-ok-…",
#         "slug": "…", "phase": "spawning", "resultFile": "…", …}

# Live status (the chip's poll — read-only passthrough of the
# dispatcher's incremental result JSON; works after restarts too)
curl -s "http://127.0.0.1:8767/api/genesis/status?slug=20261001-…-…"
# => {"phase": "turn-running", "sessionId": "sess_…", "unit": "…",
#     "responseExcerpt": null, …}

# The project picker's feed: known cwds with the interactive-lane flag
curl -s http://127.0.0.1:8767/api/genesis/cwds
# => {"home": "/home/…", "roots": ["~/.zcode/tmp", "~/git/worktrees"],
#     "cwds": [{"cwd": "…", "sessions": 3, "interactiveLane": false,
#               "lastUpdatedMs": …}, …]}

# The model picker's feed: the station config's actual offer
curl -s http://127.0.0.1:8767/api/genesis/models
# => {"provider": "account:…", "models": ["GLM-5.3", "GLM-5.3-Flash"],
#     "default": "GLM-5.3"}

# A dangerous cwd is refused until explicitly confirmed (the audit trail)
curl -s http://127.0.0.1:8767/api/session/create -X POST \
  -H "Content-Type: application/json" \
  --data-binary '{"cwd": "/srv/prod", "model": "GLM-5.3",
                  "role": "pinky", "prompt": "x"}'
# => 400 {"error": "this directory is outside the sanctioned roots …
#         — send confirmDangerousCwd: true …", "dangerous": true}

# A refused ~/git checkout, probed display-only (the ladder verbatim)
curl -s http://127.0.0.1:8767/api/genesis/check -X POST \
  -H "Content-Type: application/json" \
  --data-binary '{"cwd": "~/git/xxx-org/xxx-universe", "role": "pinky"}'
# => 400 {"error": "~/git holds the shared main checkouts … — context
#         lives in the worktree: …", "class": "refused",
#         "redirect": {"mode": "worktree",
#                      "repoRoot": "/…/xxx-org/xxx-universe",
#                      "projectName": "xxx-universe",
#                      "suggestedPath": "~/.zcode/tmp/xxx-org/xxx-universe/…",
#                      "base": "origin"}}

# The same probe with auto:true RESOLVES the repo pick (v1.44.0) — the
# worktree is cut server-side and the resolved path returns
curl -s http://127.0.0.1:8767/api/genesis/check -X POST \
  -H "Content-Type: application/json" \
  --data-binary '{"cwd": "~/git/xxx-org/xxx-universe",
                  "role": "pinky", "auto": true}'
# => 200 {"class": "resolved",
#         "resolved": {"ok": true,
#                      "path": "/…/.zcode/tmp/xxx-org/xxx-universe/<slug>/",
#                      "branch": "oss-oo/genesis-<slug>",
#                      "base": "origin", "mode": "worktree", …}}
# a creation failure is honest + retryable, never a silent nothing:
# => 503 {"error": "could not fetch origin/main — …; nothing was
#         created, retry in a moment", "class": "unresolved",
#         "retry": true, "redirect": {…}}

# The explicit door (the plain home for project-less paths — or any
# manual worktree birth): POST /api/genesis/worktree (201 = ready)
curl -s http://127.0.0.1:8767/api/genesis/worktree -X POST \
  -H "Content-Type: application/json" \
  --data-binary '{"mode": "worktree",
                  "repoRoot": "/…/xxx-org/xxx-universe",
                  "slug": "20261002-…-pinky-xxx-universe-…",
                  "role": "pinky", "requestedPath": "~/git/xxx-org/xxx-universe"}'
# => 201 {"ok": true, "path": "/…/.zcode/tmp/xxx-org/xxx-universe/<slug>/",
#         "branch": "oss-oo/genesis-<slug>", "base": "origin", …}

Also fixed in this round: brain-child doorbells now produce lineage edges — the doorbell scan's pre-split LIKE never matched the "🧠 brain-child" wording the dispatcher stamps for brain children (since the child-role round), so a completed brain without a ledger/live edge stayed rootless; and the round-W doorbell memo gained mtime+size to its file identity (a deleted-and-recreated database reusing an inode with an equal max rowid could serve the dead file's cached edges).

Auto-redirect + the worktree law (v1.44.0)

One law for all BORN sessions — root/master brains included, no exemptions: everyone works from a worktree under ~/.zcode/tmp/<org>/<repo>/<slug>/ — mirroring ~/git/<org>/<repo>/ exactly, so attribution is as unambiguous as the full git path (two same-named repos under different orgs can never collide). Only the owner's own interactive sessions live in the main checkouts (human trust class). Existing pre-law master sessions are grandfathered until their natural context recycle; their successors spawn under the law. Already-created worktree directories need no migration (no collision exists); the rule applies to new births only. A repo whose path doesn't fit the ~/git/<org>/<repo> shape falls back to its deepest two meaningful segments (or its single segment at filesystem root).

The 🌱 genesis wizard now enforces the law with zero extra clicks: picking a repo path (any repo-bearing path per the v1.36.0 rules — ~/git checkouts and dangerous paths alike) resolves itself — the check endpoint cuts the worktree (fetched default base or local fallback, oss-oo/genesis-<slug> branch, rate caps, audit journal — the same rails) and the form's cwd becomes the resolved path. The review step simply shows the resolved target, visible and copyable. The "⚠ Shared checkout — Create & use" click is gone.

The cut rides the role (v1.56.0, S-39). The worktree slug is role-prefixed (…-brain-<project>-… / …-pinky-<project>-…) for attribution, and the cut fires at REVIEW ENTRY — the role picker lives in the form and is final by then. (Before, an eager blur-time cut ran under the form's DEFAULT role: the owner's first own genesis spawn — a brain for PDrive — landed in a worktree named "pinky".) A role change AFTER a cut (Back from review, or a click racing an in-flight probe) re-keys client-side: the stale home rides the next probe as supersede and the server RETIRES it refusal-laddered (journal-recorded creations only, nothing spawned from it, no queue reference, git's own dirty-tree refusal; genesis branches only) — no never-spawned worktree/branch zombie lingers in the project repo. Every retirement lands in the redirect audit journal (mode: "retired").

The asymmetry is deliberate: only the worktree door auto-applies. The plain door (project-less paths — messengers, canaries, $HOME-personal) keeps its explicit Create & use: adopting it changes cwd semantics (no repo access), which deserves a deliberate click. Paths with no repo to redirect keep the plain refusal.

Creation failures are honest and retryable: a fetch failure (503), a collision (409) or the rate cap (429) surfaces inline with a retry resolution affordance — the wizard never strands the user on a silent nothing. Every auto-resolution lands in the redirect audit journal (auto: true).

The dialog also gained two polish items (steer #31): the role picker is a compact segmented pair — two dedicated individual buttons hugging their content instead of two tiles in a wide box, with the selected role's explanation as subtext, tooltips preserved and ←/→ moving between the buttons — and the dialog closes on an outside click, behaving exactly like ✕-close (value memory on reopen preserved, no accidental spawn, an in-flight spawn unaffected in its own scope).

Plan-selectable spawns (v1.43.0)

One observatory deployment, per-spawn plan choice: the 🌱 genesis dialog's model picker became a plan picker — options grouped by plan provider, one selection = a (provider, model) pair, and the spawn rides it as zcode-dispatch v1.0.25's --provider (ride free start-plan tokens when the owner provisions them; switch back by picking the station default group). Built on the dispatcher's shipped contract, read live from the installed CLI: --provider overrides the config's provider as the EFFECTIVE one for that create; an id unknown to the registry snapshot declines with exit 2 (known ids listed); a plan without a stored credential declines with exit 6; resumes are provider-pinned and refuse the flag (genesis spawns are fresh creates, unaffected).

  • The picker groups from live registry data — never hardcoded plan names. GET /api/genesis/models gained a plans feed joined from three read-only station sources: the registry snapshot (the newest provider_registry.ready line in the CLI logs — the exact provider set the dispatcher's exit-2 check validates), the builtin provider config (display names + each plan's enabled builtin models, resolved the way the dispatcher's own run_setup resolves it), and the shared credential store's api-key presence (key-name prefix/suffix match only — values are never read). Group labels are the registry's providerNames with an id-derived fallback; a plan without a stored credential stays SELECTABLE (a real registry provider) and its group is badged 🔑 not provisioned; a model outside the station config's offer is marked ⚠ not in config offer (the dispatcher's exit-2 model check owns the final word). The default group is the station config's provider, first and pre-selected — today's behavior when nothing is chosen. Every source is fail-open: a missing/corrupt one narrows the feed, and with none the picker falls back to the flat pre-v1.43.0 offer.
  • The provider rides the spawn argv-safely. POST /api/session/create accepts an optional provider (account:… shape, bounded charset — anything else is an honest 400 client-side of the spawn, hostile metacharacters never reaching a dispatcher roundtrip). It rides the fixed systemd-run script as a NINTH argv positional consumed by [ -n "$9" ] guards — never interpolated into the shell string (the suite pins the hostility inertness); absent/empty omits --provider entirely (back-compat: the whole pre-MM genesis surface unchanged). The 202 body, the spawn record and /api/genesis/status all carry the chosen provider.
  • Declines are honest, exit-7 style. A failed spawn that rode a chosen plan gets a providerDecline classification in the status feed — credential (exit 6, amber chip: an actionable "log that plan in" matter, the dispatcher's provisioning hint verbatim in the excerpt below) or unknown-id (exit 2, with the dispatcher's known-ids listing) — a DECORATION over the verbatim error excerpt, never a replacement; an ordinary failure stays an ordinary failed phase. The status hint line shows the spawn's plan id.
  • The quota panel gained a plan split. zcode-dispatch v1.0.25 stamps every usage-ledger entry with its provider, so the panel's scope-2 lane table gains a compact "plan split — same ledger, sliced by provider" strip underneath: start-plan spend vs individual spend at a glance, shares of the sliced total (the same tokens the lane rows count — a different grouping, labeled as such; no double counting). Availability-gated on the ledger schema: ledgers without the dimension (pre-v1.0.25 dispatchers, or only unattributed entries) render nothing — and unattributed entries fold into one honestly-labeled row only when named providers also exist.

Since v1.111.0 the keyless-plan policy above is superseded: a plan without a stored api-key is a desktop-only (OAuth) plan and its options render DISABLED with the reason — the observatory's own headless rail refuses the choice with the same words instead of riding it to the dispatcher's exit 6. See The start-plan family: layer capability + the switch commission.

The start-plan family: layer capability + the switch commission (v1.111.0)

The start-plan becomes a first-class choice by making every plan option state which layer can ride it (round BBBBB, owner framing 2026-10-17 17:30: "start-plan ordentlich ins Observatorium implementieren"). The owner's layer truth, encoded end-to-end:

layer can ride why
🖥 desktop every plan the desktop's OAuth login holds the account — the free start-plan tokens included.
headless (every genesis spawn) storage-key plans only (+ the station config's own plan) the dispatcher's --provider contract resolves a chosen plan's api-key from the shared credential store (exit 6 without one); the OAuth-for-headless excavation is in flight on another lane.
  • Every plan option states its layer capability — the group badge in the picker (· storage key — every layer, · config chain — every layer for the station default, · 🖥 desktop-only (OAuth — no storage key)), the hint line, the review's plan row (the capability's own reason words), and a capability-matrix line under the picker: the layer law plus every offered plan's own truth, one line, rendered from the feed. The matrix derives LIVE off the same three read-only sources the feed joins, plus the desktop OAuth-login presence per account type (credential-store KEY NAMES only — oauth:zai:*, values never read): a keyless plan whose account type holds no desktop login either SAYS so in its reason (the structural capability and the current login state are different facts, both honest).
  • A desktop-only plan is visible truth, never hidden (mission B2): its options render disabled with the refusal reason as their title, and the server's own headless rail refuses the same choice at POST /api/session/create with an honest 400 — the observatory knows its spawns are headless births, so a KNOWN keyless plan never rides to a dead dispatcher roundtrip anymore. Two honest corners ride with it: an id UNKNOWN to every station source still rides (the dispatcher's exit-2 known-ids listing owns it — MM's contract), and the station config's OWN plan refuses only its EXPLICIT keyless choice ("leave provider empty to ride the entitlement chain") — implicit default spawns are unchanged, byte-identical.
  • The chosen plan surfaces in the spawn receipt. The spawn record carries a plan snapshot — {id, name, capability} composed at CHOICE time off the live sources — and /api/genesis/status passes it through: the status view renders plan <name> · <id> · <auth> — every layer (the YYYY truthful-receipt family composes). An unknown id rides with name: null, capability: null — never a guessed layer claim. The MM-era providerDecline classification stays as the catch for a key that vanishes between the rail and the run.
  • The plan-switch commission for EXISTING sessions (the ⇄ button on every session row): GET /api/session/<sid>/plan states the pinned plan — the LAST modelSelection.providerId in the session DB, the same seam family the steer console trusts — with its layer capability, the switch truth verbatim (sessions are provider-pinned: the dispatcher refuses --provider together with --resume; a mid-session switch is dispatcher-side and not available), and the headless-capable successor targets. The commission MUTATES nothing (there is nothing honest to mutate); what it INITIATES is the honest switch — a successor: the 🌱 Spawn successor button opens the genesis dialog prefilled with the session's cwd and the chosen plan (desktop-only targets render disabled with their reason, the same matrix), and the full spawn rails own the Spawn click.

Fleet governance: templates, schedules, queue, lane budgets (v1.47.0)

Run the fleet, not just the sessions. Roadmap lane P2 ships four governance surfaces, all built on one law: everything the governance layer births goes through the EXISTING genesis rails — the cwd guard and its redirect ladder, prompt/rate caps, the argv-safe positional script. A template is prompt text + form defaults, never rails; a schedule is a stored spawn body that re-validates at every run; the queue defers the systemd-run moment, it never relaxes a check; the lane budgets are advisory only — the dispatcher's own guardrails (its docs/BUDGETS.md contract, enforced with exit 7) remain the ONLY enforcement.

D1 — Prompt templates

Named templates live in one durable JSON store under the genesis dir (~/.zcode/tmp/genesis/templates.json, atomic writes): {name, prompt, model?, role?, reasoning?, provider?, cwd?, notes?}. CRUD rides the API (GET/POST /api/genesis/templates, DELETE /api/genesis/templates/<name>) and the 🌱 dialog carries the picker: picking fills the form (only the fields the template carries — the owner reviews everything), and ⚙ manage lists/edits/deletes (delete asks first; the confirm rail). Hostile template content is inert by construction — it is prompt TEXT, staged verbatim and delivered as the session's opening prompt; a template whose default cwd is a ~/git checkout still walks the full refusal ladder at spawn time (proven by the suite).

D2 — Scheduled births (cron-lite)

Schedules (~/.zcode/tmp/genesis/schedules.json): {name, template? | inline prompt, cwd/model/role/reasoning/provider, recurrence (daily-at HH:MM or every-N-hours, 1–168), enabled, notes, lastRunAt, nextRunAt, lastOutcome}. A daemon scheduler thread ticks once a minute (ZDO_SCHEDULER_TICK_S; ZDO_SCHEDULER=0 disables) and births due schedules through genesis_spawn — same rails, same audit journal, and the spawn's status payload carries its origin ({"kind": "schedule", "id", "name"}) so the audit trail names the schedule.

Honesty laws:

  • Missed-run policy — if the station was down at the due time, the run happens on wake only within the grace window (default 30 min, ZDO_SCHEDULE_GRACE_MS); beyond it the run is marked missed (visible in the panel's last-outcome column) and nextRunAt advances — no silent catch-up storms. every-N-hours is a sliding cadence anchored at the last attempt (a station that slept three cycles wakes to ONE due run, not three); daily-at uses naive local wall-clock arithmetic (a spring-forward gap resolves to the next valid local minute, a fall-back repeats the wall time once — cron's own semantics, documented and tested).
  • Failure honesty — every refusal shape (exit 6/7 declines, busy lanes, refused cwds) lands verbatim in the schedule's lastOutcome and renders in the panel; a failed occurrence does not retry before the next one.
  • Unattended rail — a schedule's cwd must already be safe-class at store time (the auto-resolved worktree form is the intended shape); there is no confirm click to make at 03:00.

The panel (a "Fleet governance" section below Quota & cost): list with next-run countdown, enable/disable (disabling cancels the schedule's queued entries — nothing waits in the dark), manual run now (outside the cadence; does not advance it), delete (asks first). The create affordance lives in the 🌱 dialog's review step: ◷ Schedule… stores the currently-reviewed spawn as a schedule — nothing spawns at that moment.

D3 — Concurrency caps with queueing

Each project lane (the spawn's normalized cwd) has a concurrency cap (ZDO_GENESIS_LANE_CAP, default 3 — deliberately generous). A spawn that would exceed it is queued observatory-side (~/.zcode/tmp/genesis/queue.json, durable): the 202 answer carries phase: "queued" + queueId + queuedBehind, and the dialog's status step watches the queue entry until it releases. The pump releases head-of-lane entries as capacity frees, driven three ways: SSE turn events (the dashboard nudges POST /api/genesis/queue/pump on a turn-end when anything is queued), the genesis status polls (the v1.30-discipline poll fallback) and the scheduler tick. Queue honesty: entries visible with live positions in the panel, cancelable (✕ — nothing spawns), a stale entry (its schedule disabled/deleted, or a manual cancel) leaves cleanly, release refusals (rate cap, no dispatcher) surface as lastError on the entry and retry on the next pump — no silent drops. A restart with queued items replays them from the durable file; an active spawn whose result never materializes stops blocking its lane after 6 h (ZDO_GENESIS_ACTIVE_MAX_MS).

D4 — Lane budget surfacing (advisory)

Per-lane daily token budgets (cwd-keyed, local-day window) in ~/.zcode/tmp/genesis/lane-budgets.json: the 🌱 review shows the lane's spend-today vs its configured budget — amber when the lane is already at/over — and the quota panel's lane rows carry the day ≤ column (joined by the lane row's cwd; over = amber). "Projected" is honest: a not-yet-spawned session has no spend number, so the lane's used-today is the floor the spawn adds to. The observatory never blocks on its own accounting — the spawn button stays enabled, and the dispatcher's own refusals (exit 7, the strip's red ⛔ rows) render exactly as before.

The API surface (round RR)

GET    /api/genesis/templates          list templates
POST   /api/genesis/templates          create/update by name (201/200)
DELETE /api/genesis/templates/<name>   delete (404 when absent)
GET    /api/schedules                  schedules + queue view + budgets
POST   /api/schedules                  create (201) / update by id (200)
POST   /api/schedules/<id>/run         run now (outside the cadence)
POST   /api/schedules/<id>/toggle      {enabled} — off cancels its queue
DELETE /api/schedules/<id>             delete (+ cancel its queue)
GET    /api/genesis/queue              live queue view (positions)
POST   /api/genesis/queue/pump         the turn-event release nudge
DELETE /api/genesis/queue/<id>         cancel one queued entry
POST   /api/genesis/lane-budgets       {cwd, tokens, name?} — advisory
DELETE /api/genesis/lane-budgets/<cwd> remove one advisory budget

Knobs: ZDO_SCHEDULER (0 disables the thread), ZDO_SCHEDULER_TICK_S (default 60), ZDO_SCHEDULE_GRACE_MS (default 1 800 000), ZDO_GENESIS_LANE_CAP (default 3), ZDO_GENESIS_ACTIVE_MAX_MS (default 21 600 000). All stores ride ZDO_GENESIS_DIR with the rest of the genesis scratch.

Guarantees — honesty by construction

What the observatory refuses to fake: the candid limits list and the full write-surface inventory.

Honest limits

Told plainly, because a cockpit that flatters its instruments is a liability:

  • A page cannot resize your main browser window — the ⧉ ideal-window button therefore opens a sized POPUP instead of resizing anything (see The ideal window); at phone widths the deck's own mobile layout takes over instead.
  • The desktop app keeps its task list in memory. Archives made here reach an already-running ZCode only when its sidebar re-queries (restart, workspace/sidebar switch); the reverse direction is live.
  • Desktop-driven pending questions are display-only — their interaction lives inside the desktop's private app-server (see Pending questions).
  • A doorbell that rang into an open desktop chat is invisible THERE until hydration — the ~10-minute residency cycle; the observatory predicts the window (see Hydration prediction) and shows the content here regardless.
  • Sessions idle longer than the lookback window (ZDO_LOOKBACK_MS, 1 h default) without a pinned title or archive membership leave the table — the listing is for live work, not an eternal archive (the native archive section has no such cutoff).
  • Context % and cache rate are the latest-request snapshot — the same numbers the client shows; a turn that is still streaming grows past them between polls.
  • Observatory restart forgets in-flight steer liveness (on-disk result files still report the last steer; queue depth restarts at zero) — in-memory liveness never survived a restart, same honesty as the dispatcher's own views.
  • The fast lane's shortened busy grace (2 s) trades a sliver of the 30 s safety margin for turn-boundary latency. The terminal-row gate proves the previous turn over before every fire, and a raced fresh turn still self-queues — but a desktop turn that just started AND sits in a >2 s quiet phase could, in principle, be interleaved by a fast-lane delivery. This is the owner-accepted tradeoff of "queue delivery IS the next sequential turn"; ZDO_FASTLANE=0 restores the pure sweeper path at any time.
  • "Presumed dead" is a heuristic — transcript quiet for >5 min with a turn in flight and no dispatcher lock. A long-running quiet tool call looks identical from the DB; the hint says "probably", never "certainly".
  • Every surface lags by design — bounded, and honestly labeled. The page polls ~3 s (drawer and rows), the response cache holds ages ~8 s, orphan adoption waits ~10 s, a sub-250 ms API re-read can serve the key micro-cache's pre-mutation body, and a live SSE feed idles the main poll with a once-a-minute safety resync: rigs and fast fingers see these windows as lag, never as lost words.

Scope and safety

  • Read-only on the ZCode session database (mode=ro, WAL-safe) — the observatory never writes it. The one write surface is the desktop's native task index, and only through the archive/unarchive buttons: a single parameterized UPDATE on tasks.archived exactly as the desktop's own archive writes it (plus the one-shot startup migration clearing v1.5.0-era session.time_archived leftovers) — nothing else is ever written, and no other task-index table is touched
  • Pinning is observatory-local state (observatory-state.json) and never touches the ZCode database
  • Dismissing a presumed-dead strip row is observatory-local state too (the dismissedDispatches list in observatory-state.json); the dispatcher's lifecycle ledger is only ever READ here
  • The steering console triggers dispatcher runs (it spawns the installed zcode-dispatch, the one writer) but the observatory itself still never writes the ZCode database beyond the archive exception; one steer per session at a time, and the dispatcher's own exit-3 concurrency guard always applies
  • Session genesis (v1.31.0) also triggers dispatcher runs — each in its own systemd-run --user scope that survives restarts, with every user value riding argv positionals of a fixed script (never a shell string). Its rails: the placement-law cwd guard (safe under ~/.zcode/tmp/ and ~/git/worktrees/, confirmDangerousCwd elsewhere as the audit trail, $HOME and ~/git main checkouts/mirrors hard-refused), a 256 KiB prompt cap and a rate cap (3 spawns / 10 min per process); a server-side promptFile must resolve under ~/.zcode/tmp/ (400 otherwise — its content becomes the dispatched prompt, so the API never reads files outside the sanctioned scratch root); staged prompts and result JSONs live under ~/.zcode/tmp/genesis/ (owner content, no credentials — same class as steer scratch)
  • Answering a pending question writes exactly one file — the dispatcher's ~/.config/zcode-dispatch/questions/<session>.answer.json side-channel (owner answer content, no credentials); the ZCode database is never touched, and only questions whose own dispatcher parked them can be answered
  • Binds to localhost by default; the only data leaving the machine is the event payload pushed to webhook URLs you registered yourself
  • Webhook delivery is best effort (3 attempts) and never blocks the dashboard
  • Steer scratch files (prompt/result/log) live under ~/.config/zcode-dispatch/observatory-steers/; they contain prompt text and response excerpts — owner content, no credentials — and are never sent anywhere

Feel the data flow.

MIT licensed. Built in the OSS Singularity org.

Reference — orchestration, flags, webhooks, install

The plumbing: what the observatory rides on, the knobs, the push integrations, and the canonical install.

The orchestration it rides on

The observatory is one instrument in a larger machine: central Brains dispatch detached worker Pinkies, the workers ring doorbells back on return, everything publishes turn events onto an MQTT bus, safety nets (sweeper, watchdog) catch what a bell misses — and the cockpit reads it all and reaches back in (steer, answer). Validations close the loop through PRs into releases and installs back onto the station:

flowchart TB
    subgraph BRAINS["🧠 Brains — central ZCode chats"]
        BRAIN["mission brain<br/>open desktop chat"]
    end
    subgraph PINKIES["🤖 Pinkies — dispatched workers (detached)"]
        direction LR
        P1["worker pinky<br/>repo lane"]
        P2["worker pinky<br/>gate lane"]
    end
    subgraph BELLS["🔔 Bells + MQTT — the nervous system"]
        direction LR
        SWEEPER["sweeper queue<br/>retry + rate budgets"]
        WATCHDOG["watchdog<br/>stuck-bell alerter"]
        MQTT["MQTT broker — podman mosquitto<br/>topics: zcode/session/&lt;id&gt;/turn-completed · -failed · -timeout"]
        LISTENER["stream.py listen<br/>zcode/session/+/+"]
        EXT["external consumers<br/>scripts · automations"]
    end
    subgraph COCKPIT["🔭 Observatory — the cockpit"]
        direction LR
        DB[("ZCode session DB<br/>(read-only)")]
        OBS["dashboard · lineage colors<br/>transcript · response modal"]
        STEER["steer console · question cards<br/>archive · pin · favorites dock"]
    end
    subgraph SHIP["🚢 Release-return"]
        direction LR
        PR["PR → checks green<br/>→ squash-merge"]
        REL["tag + release"]
        INSTALL["station install<br/>~/tools"]
    end

    BRAIN -->|"dispatch (detached zcode-dispatch)"| P1
    BRAIN -->|"dispatch"| P2
    P1 -->|"🔔 doorbell digest on return"| BRAIN
    P2 -->|"🔔 doorbell (parent busy → queues)"| SWEEPER
    SWEEPER -->|"delivers verbatim when idle"| BRAIN
    WATCHDOG -.->|"🔔 stuck alert (> 5 min)"| BRAIN
    P1 & P2 -->|"turn-completed / -failed / -timeout"| MQTT
    MQTT --> LISTENER
    MQTT -.->|"pub/sub, any consumer"| EXT
    BRAIN & P1 & P2 --> DB
    DB -->|"live rows · lineage · bell states"| OBS
    STEER -->|"steer / answer (spawns zcode-dispatch)"| BRAIN
    P2 -->|"push branch"| PR
    PR --> REL
    REL --> INSTALL
    INSTALL -.->|"next dispatch rides the fresh tools"| BRAIN

Read it top to bottom: brains think, pinkies build, bells and MQTT carry the news, the cockpit watches and reaches back in, and every lane's work ships back to the station that started it. Each arrow is a real, verifiable mechanism — the doorbell digest and queue semantics live in zcode-dispatch's docs/PROTOCOL.md, the topic layout in its docs/MQTT-TOPICS.md, and the observatory's write paths are exactly the two shown (spawn the dispatcher, flip the archive flag).

Flags

Flag Meaning
--db PATH ZCode session database (default ~/.zcode/cli/db/db.sqlite)
--tasks-db PATH native task index — the archive store (default ~/.zcode/v2/tasks-index.sqlite, env ZDO_TASKS_DB)
--host ADDR bind address (default 127.0.0.1 — local only)
--port PORT port (default 8767)

Env-only knobs: ZDO_STEER_QUEUE_CAP (soft cap of waiting steers per session, default 3), ZDO_RUNNING_GRACE_MS (running hysteresis AND the busy grace — 300 s / 30 s respectively by default), ZCODE_DISPATCH_CONFIG_DIR (dispatcher side-channel dir that holds stream-pending.json, stream-bells.json, questions), ZDO_STEER_DIR (steer scratch files), ZDO_PROVIDER_CONFIG (provider config used to size models' context windows — default /opt/ZCode/resources/config/provider/zcode-builtin.json, with an embedded z.ai snapshot as fallback), ZDO_CACHE (0 disables the poll response cache entirely — v1.26.0 behavior), ZDO_CACHE_TTL_S (how long a cached poll body may be served on an unchanged station before a refresh rebuild, default 8), ZDO_CACHE_KEY_MS (micro-cache window for the change key so the two endpoints of one cycle share one stats pass, default 250 ms), ZDO_SIDMEMO_FULL_S (self-heal horizon: after this long the per-session facts memo is dropped and re-seeded from batched scans, default 600 s), ZDO_EVENTS_URL (v1.30.0: the SSE events bridge the dashboard consumes — default http://127.0.0.1:8788/events; empty or off disables the push feed entirely, keeping the pure-polling behavior), ZDO_GENESIS_DIR (v1.31.0: genesis scratch root — staged prompts and result JSONs, default ~/.zcode/tmp/genesis/), ZDO_GENESIS_ENV_FILE (the full-env file sourced inside every genesis scope before exec'ing the dispatcher — the interim standalone-app-server env rule; default ~/.zcode/tmp/mb2-dispatch-full.env, empty = no sourcing. v1.57.0: a leading ~ is expanded by the observatory itself at both the env read and the spawn argv — bash never tilde-expands a quoted variable, so the scope's source line always receives the absolute path; absolute paths ride byte-identical), ZDO_GENESIS_PROMPT_CAP (staged-prompt byte cap, default 256 KiB), ZDO_GENESIS_REASONING_OPTIONS (v1.34.0: comma-separated reasoning-picker options — overrides the config's reasoning_options and the built-in max/high/medium/low/none ladder; the config's default_reasoning stays the preselected default either way), ZDO_GENESIS_RATE_MAX (genesis spawns per rate window per process, default 3), ZDO_GENESIS_RATE_WINDOW_MS (the rate window, default 10 min), ZDO_QUOTA_TOTAL_TOKENS (v1.33.0: the plan ceiling in tokens for the fuel gauge — 0/unset = unconfigured, the chip and every forecast surface hide while burn stats keep working), ZDO_QUOTA_WINDOW_H (the quota window in hours the gauge averages over, default 24), ZDO_QUOTA_HEADROOM_PCT (genesis-review amber warning when the remaining headroom is a smaller fraction of the ceiling than this percent, default 15), ZDO_SCHEDULER (v1.47.0: 0 disables the fleet-governance scheduler thread — schedules and the concurrency queue then need manual pumps), ZDO_SCHEDULER_TICK_S (the scheduler tick, default 60 s), ZDO_SCHEDULE_GRACE_MS (the missed-run grace window, default 30 min — a due schedule past it is marked missed, not caught up), ZDO_GENESIS_LANE_CAP (per-lane concurrency cap behind the queue, default 3), ZDO_GENESIS_ACTIVE_MAX_MS (how long a result-less active spawn still blocks its lane, default 6 h), ZDO_GUARDIAN (v1.48.0: 0 disables the context guardian — the recycle-gradient watcher that steers brain sessions at the 70%/78% doctrine stages; default on), ZDO_GUARDIAN_PREPARE_PCT (the prepare stage threshold, default 70), ZDO_GUARDIAN_TRANSITION_PCT (the transition stage threshold, default 78), ZDO_GUARDIAN_STATE_FILE (the durable per-sid stage flags; default ~/.config/zcode-dispatch/observatory-guardian.json), ZDO_GUARDIAN_PRUNE_MS (how long a gone-from-the-deck sid's stage flags survive before pruning, default 7 days).

Webhooks (push instead of poll)

The observatory can push session status changes to your callbacks. Whenever a session that was seen running lands on a terminal status, every registered webhook subscribed to that event receives an HTTP POST — no polling needed.

Event Fired when
session.completed a running session's turn ends with status completed
session.failed a running session's turn ends with status failed

Delivery is best effort: each webhook gets up to 3 attempts (1 s apart, 5 s timeout each); after that the event is dropped for that webhook and the give-up is logged on the observatory console. Deliveries run in background threads, so a dead callback never blocks the dashboard. The first observation of a session is baseline only and never fires.

The registry is stored in ~/.config/zcode-dispatch/webhooks.json (override with ZDO_WEBHOOKS_FILE).

Webhook API

# Register a callback (omit "events" to subscribe to all)
curl -s -X POST http://127.0.0.1:8767/api/webhooks/register \
  -H 'Content-Type: application/json' \
  -d '{"url": "http://127.0.0.1:8799/hook", "events": ["session.completed", "session.failed"]}'
# => {"ok": true, "webhook": {"id": "wh-a2abf40e", "url": "http://127.0.0.1:8799/hook", ...}}

# List registered webhooks
curl -s http://127.0.0.1:8767/api/webhooks

# Remove one by id
curl -s -X DELETE http://127.0.0.1:8767/api/webhooks/wh-a2abf40e

Registration requires an absolute http(s) URL and only accepts the known event names; anything else is rejected with HTTP 400.

Event payload

{
  "event": "session.completed",
  "session_id": "sess_1a2b3c4d",
  "status": "completed",
  "timestamp": "2026-09-24T12:24:20Z",
  "title": "Pinky: Observatory Event Webhooks (Push statt Poll)"
}

Manual verification (2 minutes)

Start a sink that prints every POST body, register it, then drive any dispatcher session to completion:

# Terminal 1: local receiver on port 8799
python3 - <<'EOF'
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
class Sink(BaseHTTPRequestHandler):
    def do_POST(self):
        print(self.rfile.read(int(self.headers.get("Content-Length", 0))).decode(), flush=True)
        self.send_response(200); self.send_header("Content-Length", "2"); self.end_headers(); self.wfile.write(b"ok")
    def log_message(self, *args): pass
ThreadingHTTPServer(("127.0.0.1", 8799), Sink).serve_forever()
EOF

# Terminal 2: register and watch
curl -s -X POST http://127.0.0.1:8767/api/webhooks/register \
  -H 'Content-Type: application/json' -d '{"url": "http://127.0.0.1:8799/hook"}'
# ...start or resume a zcode-dispatch session and let its turn finish...
# Terminal 1 prints the session.completed payload within one dashboard refresh (~3 s)

Installation

From a release tarball (see the releases page for the latest version). The asset convention (pinned v1.38.1, applied retroactively to v1.37.0): the FILENAME is versioned — zcode-dispatch-observatory-vX.Y.Z.tar.gz, unique per release — while the inner prefix is UNversioned, exactly git archive --format=tar.gz --prefix=zcode-dispatch-observatory/ <tag>.

Cutting the asset (maintainers — v1.56.0, UU-1): the pinned command lives in the REPOSITORY, not in a round's hands — three rounds hand-built assets and drifted before this was pinned. From a source checkout that carries the tag:

make release-asset TAG=vX.Y.Z        # scripts/make-release.sh — the pinned git-archive, nothing else
make check-asset TAG=vX.Y.Z FILE=path/to/the/uploaded.tar.gz   # rebuild + byte-compare what ships

check-asset exits nonzero on any byte difference, and a suite test rebuilds the script's output against a direct git archive in a rig checkout to pin the pin. No round hand-builds assets.

Version-skew guard (maintainers — v1.57.1, ZZ2): the v1.57.0 ship tagged and released while SERVER_VERSION still read v1.56.0 — the live install ran new code under the old label, and nothing tripped because the suite's hardcoded version pins rot together with the constant. make check-version (a suite test, runnable alone) ties the constant to git truth instead: when HEAD sits exactly on a vX.Y.Z tag the constant must EQUAL that tag, and between tags it must never fall below the highest one (ahead = the normal pending-release window). Tag-less checkouts — CI depth-1 clones, .git-free installs — skip with a note. A release lane runs it right after tagging, so a skew dies at the ship gate instead of shipping mislabeled.

Installing from a downloaded release:

curl -L -o zcode-dispatch-observatory-v1.43.0.tar.gz \
  https://oss-oo.io/OSS-Singularity/zcode-dispatch-observatory/releases/download/v1.43.0/zcode-dispatch-observatory-v1.43.0.tar.gz
tar xzf zcode-dispatch-observatory-v1.43.0.tar.gz
cd zcode-dispatch-observatory
./install.sh --target ~/tools/zcode-dispatch-observatory

install.sh symlinks the command as zcode-observatory into ~/.local/bin and preserves local state on updates. Python 3.10+, stdlib only — no dependencies.

One canonical install target (v1.37.0)

The install TARGET is ONE name: ~/tools/zcode-dispatch-observatory (or the default ~/.local/share/zcode-dispatch-observatory). The SHORT zcode-observatory spelling legitimately names OTHER things — the command symlink in ~/.local/bin, the systemd unit FILE (zcode-observatory.service), and the restart helper (shipped beside the dashboard as observatory-restart.sh; the station carries a wrapper copy named ~/tools/zcode-observatory-restart.sh) — every one of them a FILE name, never an install directory. Do not infer a target from any of them: a v1.36.0-era station install did exactly that (from the restart script's filename) and silently forked a second live dir while the running unit kept pointing at the fork. install.sh now guards itself:

  • ExecStart anchor — when a rendered unit already exists (~/.config/systemd/user/zcode-observatory.service), installing to a directory OTHER than the one its ExecStart references is refused (exit 3) before anything is written: a second target would fork the live install. Pass --allow-target-change for the deliberate repoint of a live install (run install.sh again right after, so the rendered unit follows the new target).
  • Short-name target warning — a target whose basename is exactly zcode-observatory earns a loud stderr warning naming the canonical target.
  • Stale-sibling warning — after a successful install, a populated zcode-observatory directory BESIDE the target is reported loudly; verify which of the two the unit's ExecStart and the command symlink reference, then remove the stale copy.

After every install (agent-driven or manual), verify the triple plus the sibling check — four facts, all cheap:

# 1. runtime files byte-identical to the release you installed
diff <(tar xzf zcode-dispatch-observatory-v1.43.0.tar.gz -O \
        zcode-dispatch-observatory/observatory.py) \
     ~/tools/zcode-dispatch-observatory/observatory.py
# 2. the command symlink points INTO the canonical target
readlink ~/.local/bin/zcode-observatory
#    => /home/<you>/tools/zcode-dispatch-observatory/observatory.py
# 3. the running unit's ExecStart references the SAME directory
systemctl --user cat zcode-observatory.service | grep ExecStart
# 4. exactly ONE zcode-*observatory* DIRECTORY under the parent —
#    the restart wrapper ~/tools/zcode-observatory-restart.sh is a FILE
#    and does not count
ls -d ~/tools/*observatory*/

Crash resilience + boot-start: the systemd unit (v1.25.0, reboot-proofed v1.28.0)

The observatory is a link in the orchestration chain (bells → broker → observatory → owner), so its death must not require a human to notice: install.sh also installs a systemd user unit (~/.config/systemd/user/zcode-observatory.service) with Restart=on-failure — systemd revives the dashboard within seconds of a crash — and enables it for boot-start (v1.28.0, owner decision 2026-09-26): install.sh runs systemctl --user enable --now, so the dashboard also comes back by itself after a reboot, no manual step.

The unit carries its own PATH (Environment= with the systemd %h home-dir specifier, no hardcoded username). That matters because after a reboot the user manager starts units with the default PATH — which lacks ~/.local/bin, where the zcode-dispatch launcher symlink lives — and steering from the observatory died exactly there (owner-hit 2026-09-26: "zcode-dispatch not found on PATH"). Note the limits of the alternatives (v1.28.0 validation FV-2): a login-imported manager PATH may exist on a station (systemctl --user import-environment and friends), but it is a boot-scope stopgap at best and never includes ~/.local/bin on its own — the unit's Environment= line stays the load-bearing fix. Belt and suspenders: the steer spawn itself resolves the launcher PATH-independently, in this order —

  1. ZDO_DISPATCH_BIN environment variable (explicit operator override; when set but not an executable file it is a loud error, never a silent fall-through),
  2. the absolute ~/.local/bin/zcode-dispatch symlink,
  3. the install dirs this repo already tracks (~/tools/zcode-dispatch, ~/.local/share/zcode-dispatch, and the sibling checkout of the observatory's own install dir), each carrying dispatch.py,
  4. the bare PATH lookup (the pre-v1.28.0 behavior, last resort).

When nothing resolves, the refusal message lists every candidate tried and the ZDO_DISPATCH_BIN escape hatch.

observatory-restart.sh (shipped beside the dashboard) prefers systemctl --user restart whenever the unit exists and falls back to the legacy kill-and-nohup pattern otherwise, logging under ~/.local/state/zcode-observatory/ (never /tmp, which the station wipes on reboot). The dispatcher's own crisis hardening (MQTT broker restart policy + watchdog, notification steers in their own systemd scopes, the canary's infra group whose failures show up in the contract banner above) ships in the zcode-dispatch repo, v1.0.1.

Pair it with zcode-dispatch (the dreamteam)

Both tools are independently useful; together each covers the other's blind spots:

  • zcode-dispatch is the muscle: dispatch and resume ZCode sessions from any external agent, with the doorbell digest, the MQTT event bus, the sweeper and watchdog safety nets. The observatory's steering console literally spawns it (--resume, detached) and the genesis modal births fresh sessions through it (scope-isolated systemd-run) — its queue semantics, its pending-question side-channel, its bell ledger and its role stamping are what the cockpit's write paths ride on.
  • The observatory is the senses and the hands: lineage visibility (whole mission trees color-coded from the bell ledger and doorbell edges), live reading surfaces without restarting ZCode, a genesis UI with placement-law rails, a steering UI with optimistic echo, question answering, native archive/pin/dock.

Birth, dispatch, then watch, steer, and answer from one page — neither half can do that alone.

The rig vault: empirical rigs as regression capital (v1.96.0)

The lane's quality arcs kept losing their rigs: round implementers remove their worktrees (their 13-leg CDP rigs vanish with them), and every validator REBUILDS a full rig from scratch — duplicated night-work over the same product. tests/rigs/ is the committed vault — the durable home for the lane's empirical rig drivers, with the binding law in tests/rigs/README.md:

  • Rounds commit their empirical rigs to the vault — the rig is part of the round's deliverable, like the suite legs.
  • Validators re-run before rebuilding — a validator facing a round with a vault specimen runs it FIRST (against the round's tree); only a gap the specimen cannot cover justifies new rig code, and that code then lands in the vault too.
  • Every committed rig obeys the inherited rig laws: hermetic env pins (a rig never touches the live station or owner apps), port distance (87xx/89xx, clear of owner-app ports), the cockpit rule (own headless profile, endpoint-ownership proof before any navigate), and CDP discipline (trusted input, elementFromPoint gating, two-shape IIFE evaluates).

The first specimen is the LLLL idle-fleet CDP rig (tests/rigs/llll_idlefleet_cdp.py, make rig-llll-idlefleet): 16 legs — firing and non-firing with equal discipline — ported from the LLLL validator's run-5 official rig. The vault deliberately stays out of suite discovery (no __init__.py, no test* filenames); the suite pins its shape (tests/test_round_mmmm.py) and runs the full specimen behind the ZDO_RIG_VAULT_E2E=1 opt-in, so the canonical suite stays green and fast everywhere while the rig capital stays runnable on demand.

The growth cure: indexes + first paint + action latency (v1.112.0)

RV's verdict frame (binding): the transcript slowness is a GROWTH problem, not a regression — the session DB's whole history ran with ZERO indexes on message/part/session, so every transcript tail and deck scan degraded with the tables (2026-10-25 census: 2,741 sessions, 144,161 messages, 523,877 parts, 4 GB). The Owner refined: the pain is GLOBAL — the whole deck sluggish until ANYTHING appears — so first paint weighs at least as much as the excerpts. Round AAAAA ships the cure in three missions:

  • A1 — the index migration (ensure_query_indexes, one startup pass after the time_archived precedent): message(session_id, sequence), part(session_id, message_id, sequence) and session(time_updated) — created only when no column-equivalent index exists (the desktop's newer schema already ships the first two; the prefix check never duplicates), idempotent per index, and DEFERRED honestly on a read-only or locked database (a missing index is slow, never wrong).
  • A2 — the query shapes: the transcript's bounded tail slice is selected BEFORE the joins (the part join seeks the exact (session_id, message_id) index prefix; the old shape full-scanned message twice and built a TRANSIENT index over part per call), and the FIRST LOAD leans on the response cache: a boot PRIMER builds the sessions body until it converges (its own state-file writes flip the source key once — the primer rebuilds past that), and stale-while-revalidate serves a TTL-expired body instantly while one background rebuild refreshes it — every content mutation keeps the synchronous fresh path (the G3 ≤ 1-cycle contract unchanged).
  • A3 — action latency + the folds: dismiss/retry/turn-retry on failed steer cards acknowledge OPTIMISTICALLY (the click settles the card; an honest refusal — in-flight delivery, 4xx, network — is the only resurrection), ghost cards die by owner choice (a card whose backend entry vanished dies with the click and stays dead), the dispatch-strip dismiss gets the same law, and the V-F1 fold sources acceptedMs from the dispatcher's own startedAtUtc (the first result-file write — the honest 202 moment) instead of the file mtime (a terminal write time rendered as acceptance lied by construction).

The proof is the number (hermetic rig at quarter station census, BEFORE = v1.108.0 base, AFTER = this round): transcript cold 15,114 ms → 24 ms, warm median 189 ms → 23.5 ms, post-TTL deck serve 3,123 ms → 28 ms, first paint (min of 3 fresh loads) 3,491 ms → 318 ms, dismiss action-to-ack (median of 5) 127 ms → 5 ms. The full measurement table, the EXPLAIN QUERY PLAN transcripts and the migration design ship in the round dossier; the rig is tests/rigs/aaaaa_growth_cdp.py (ZDO_RIG_PHASE=before + ZDO_RIG_REPO drive the base tree for the BEFORE column).

The retry-timestamp truth (v1.109.0)

Round ZZZZ, owner finding #62 (2026-10-17 16:31 via gen-4 — EIL: Diagnose-Vergiftung): the STEER-RETRY button faked the initial timestamp. The one-click retry re-fires the same promptFile, but it used to mint the fresh steer id at the retry moment — and the id's embedded send-ms is what every consumer trusts: the queue's FIFO key, the receipt's sentMs, and (through the dispatcher's child-id parse) the delivered annotation that places the words in the session's timeline. A backlogged steer read younger than it was after its retry — the master's timeline got poisoned, false causal diagnoses followed, and the chronicle/audit lied.

The cure — both times, never one over the other

  • The original sent_ms is preserved by construction: a retry mints its id as {original-send-ms}-{ordinal}-{sid} (the root send embedded in the head, a retry ordinal for uniqueness), so every id-parsing consumer on both sides of the seam — the observatory's _steer_send_ms, the dispatcher's entry_send_ms twin, the self-queue capture, the sweep re-stamp — reads the words' TRUE age. Retrying a retry keeps the ROOT send; the ordinal grows honestly (retryCount). The queue position follows the same law: the re-fired words re-enter at their original FIFO place.
  • The retry moment is its OWN field: the spawn writes a .retry.json lineage sidecar ({retryOf, sentMs, retryMs, retryCount}) beside the prompt/result files, the API payload carries it, and the superseded original's settled record gains retryMs (epoch) beside retriedAtUtc — with the file's own birth (the receipt's acceptedMs) explicitly preserved across the settle write.
  • The receipt surfaces both (sentMs stays the ORIGINAL — the YYYY truthful-receipt family; retryMs/retryOf/retryCount join it on the retry's receipt, retryMs/retriedBy on the superseded one).
  • The display renders both honestly: the receipt hover reads gesendet: HH:MM:SS (the original send — a retry never moves it) beside Zustellung: HH:MM:SS · N. Versuch — retry fired HH:MM:SS, and the retry toast says the words re-fire at their ORIGINAL send time. The timeline placement of the message body stays at the ORIGINAL send (the annotation contract — the dispatcher's ZCODE_DISPATCH_STEER_SENT_MS env seam carries it explicitly, belt-and-braces beside the child-id truth).
  • The product tripwire: if a mint ever loses the original send-ms again, start_steer refuses the re-fire BY NAME ("retry id mint lost the original send ms … owner finding #62") before any file, sidecar or dispatcher exists. The suite proves it red on an in-process reintroduction of the overwrite and green on the cure, plus the honest negative: a retry's diff on the old record is EXACTLY the settle keys, every other receipt field byte-identical.

The sweep — every re-fire path audited

Path Verdict
retry_steer (the SSS one-click retry) the class — cured here
turn_retry_steer (AAAA) not the class: composes NEW canonical words whose true send IS the retry moment; the original message is never re-sent (only the settle's retryMs bookkeeping is shared)
the fast lane's delivery not the class: delivers the queue entry's own child verbatim (id-carried ms); its .fastlane.prompt naming matches the retry-aware attribution globs
the interrupt breadcrumb / the rumble nudge not the class: new system words at their own honest moment
the orphan repair re-queue not the class: re-queues the ORIGINAL child, no re-mint
dispatcher-side (self-queue capture, sweep re-stamp, inherits_send_ms) not the class: all resolve sent-ms env → id parse → honest fallbacks; RESURRECT already inherits the OLDEST pending send for identical re-sends

The round timeline

Every shipped round, newest first — the deck accreted feature-burb by feature-burb across ~126 of them. The full congress-era prose (the round narratives behind v1.74.0 → v1.89.1) is archived verbatim in docs/history.md.

Version Round — the headline PR
v1.112.0 Pinky AAAAA: the growth cure — indexes + first paint + action latency (RV's frame: the slowness is GROWTH, the whole DB history ran with ZERO indexes; the Owner's frame: the pain is GLOBAL, first paint weighs at least as much) — A1: the startup index migration (message(session_id, sequence), part(session_id, message_id, sequence), session(time_updated); prefix-aware so the desktop's own newer indexes satisfy the need without duplicates, idempotent, deferred honestly on read-only/locked DBs), A2: the transcript LIMIT pushdown (bounded slice first, part join on the exact index prefix — the old shape built a TRANSIENT index over part per call) plus the first-paint lean on the response cache (boot primer that CONVERGES past its own state-file key flips + stale-while-revalidate on unchanged keys with the background rebuild single-flighted — content mutations keep the synchronous fresh path), A3: optimistic action acknowledgment (dismiss/retry/turn-retry settle the card at the click; an honest refusal is the only resurrection), the ghost-card law (cards die with their entry OR by owner choice), and the V-F1 fold (acceptedMs sourced from the dispatcher's startedAtUtc — the honest 202 moment — never the file mtime); measured on the hermetic rig at quarter station census: transcript cold 15,114→24 ms, warm 189→23.5 ms, post-TTL serve 3,123→28 ms, first paint 3,491→318 ms, dismiss ack 127→5 ms (parked — W20 freeze; ships after ZZZZ)
v1.110.0 Pinky BBBBB: the start-plan family — layer capability + the switch commission (owner framing 17:30, missions B1+B2) — every plan option states WHICH LAYER CAN RIDE IT: desktop = every plan (the OAuth login holds the account, free start-plan tokens included), headless spawns = storage-key plans only (the dispatcher's --provider contract demands a stored api-key, exit 6 otherwise; the OAuth-for-headless excavation is another lane), the station config's own plan the structural exception (the entitlement chain rides it, no explicit choice needed); the picker's group badges (· storage key — every layer / · config chain — every layer / · 🖥 desktop-only (OAuth — no storage key)), a capability-matrix line under the picker, the review's plan row, and the spawn receipt's plan snapshot ({id, name, capability} at choice time, unknown ids ride null — never a guessed layer) all state the same truth; B2's honesty rails: a desktop-only plan renders DISABLED with its reason (visible, never hidden) AND the server's headless rail refuses the same choice with an honest 400 (known keyless plans never ride to a dead dispatcher roundtrip; unknown ids still ride — the exit-2 listing owns them; the default plan's EXPLICIT keyless choice names the entitlement-chain corner); the ⇄ PLAN-SWITCH COMMISSION on every session row: GET /api/session/<sid>/plan states the pinned plan (the LAST modelSelection.providerId), the provider-pinned switch truth verbatim (--provider + --resume refused), and the headless-capable successor targets — mutating nothing (dispatcher-side), initiating the honest switch by PREFILLING the genesis dialog (cwd + target plan) through the full spawn rails; rigs: the picker/matrix/commission node harnesses + the refusal/server legs prove plan selection, the matrix render, and the headless refusal with the honest reason (merged — the fifth ship of the reopening night)
v1.109.0 Pinky ZZZZ: the retry-timestamp truth (owner finding #62, EIL: Diagnose-Vergiftung) — the STEER-RETRY button used to mint the fresh steer id at the RETRY moment, and the id's embedded send-ms is what every consumer trusts (queue FIFO, receipt sentMs, the delivered annotation's child-id parse): backlogged steers read YOUNGER than they were and the master's timeline got poisoned; the cure preserves the original sent_ms by construction (the retry id embeds the ORIGINAL send with an honest ordinal, {orig-ms}-{n}-{sid} — retry-of-retry keeps the ROOT; the queue position follows the same law), gives the retry moment its OWN field everywhere (.retry.json lineage sidecar {retryOf, sentMs, retryMs, retryCount} + payload + the settled record's retryMs with its file-birth acceptedMs explicitly preserved across the settle write), surfaces both times on the receipt (YYYY's sentMs stays the ORIGINAL; retryMs/retryOf/retryCount join it), renders both in the deck's voice (gesendet: X (the original send — a retry never moves it) beside Zustellung: Y · N. Versuch — retry fired Z, plus the honest toast), carries the original send through the dispatcher's ZCODE_DISPATCH_STEER_SENT_MS env seam (the annotation contract — the words land placed at their send time), refuses an overwritten mint BY NAME at the product gate (proven red on an in-process reintroduction, green on the cure, and the honest negative pins the retry's record/receipt diff to EXACTLY the settle keys), extends the attribution globs to retry-shaped prompt files (root-preferred on ties), and sweeps every sibling re-fire path (turn-retry, fastlane, breadcrumbs, nudges, orphan re-queue, dispatcher-side captures — verdicts in the README section) (merged — PR #144)
v1.108.0 Pinky YYYY: the truthful receipt + the turn-state display (the Astra#2 commission, missions Y1+Y2) — Y1: every steer surface (optimistic echo, lifecycle cards, console status line) renders ONE four-way state vocabulary with contract honesty: ✓ accepted · 202 (HTTP 202 means the dispatcher took the job — NEVER delivery), 📨 queued, 📨 AM TURN GELIEFERT (delivered at turn — receipt/transcript/completed-result proof, with the turn_usage.user_message_id join naming the consuming turn), ✖ failed (machine-readable {class, message}); each state carries its receipt one hover away (stable id, session, sha256-core digest, sent/accepted/delivered timestamps, turn, error) and the reconciled turn-died class keeps its ONE story (AM TURN GELIEFERT · turn died (<class>)); Y2: working rows carry their TURN STATE in the owner's five-word grammar — Modell (model side holds the turn; a quiet phase annotates · denkt, the CCCC liveness-proven thinking), Werkzeug · <tool> (a tool part in state running provably executes), Freigabe (a pending question holds the turn; native permission prompts write no DB evidence and stay honestly Modell), 🅿 Quota-Park · frei ab HH:MM (the CLI logs' turn.failed model_rate_limited signature as the session's LAST life signal — parked is not dead, the reset stamp converts from the provider clock), Terminal (the honest_turn_status words) — the full resolution table ships in this README and the round dossier; rig make rig-yyyy-receipt proves the vocabulary live (parked — W20 freeze; ships after XXXX)
v1.107.0 Pinky XXXX: the blindness forensics + the deaf-consumer cure — the 2026-10-16 owner experience (service lived, API fresh, deck stale) reconstructed from station evidence: the PRIMARY mechanism was the old-deck class (owner finding #52, quoted by round IIII — an open tab running a pre-handshake deck through ~10 same-day installs; the observatory never owned the SSE transport, so installs tore no streams), the ONE bridge restart that day (18:06:22) at worst caused the bounded (~45 s, fresh-guard-healed) stale-high dup-skip window, and the healthz probe's clock reset stood as the latent open-but-deaf mask — the full dossier ships as the round's forensics artifact; the CURE is the consumer-side deafness law: resync from state on EVERY hello (the old mustResync gate skipped exactly the fresh-guard heal's catch-up hello, leaving the deaf window's missed state stale), FLOWING as delivered-event liveness that healthz can never reset (the probe ran once per silence episode instead of laundering "live"), the SILENT state (🔇 push silent chip + slim-line alert — live-feed/no-feed/deaf-feed named) whose poll re-arm is the bounded live-poll floor, and the per-connection id mirror reset on every fresh connect; the BLINDNESS TRIPWIRE (its own suite leg) scripts both deaf shapes — the stale-high reconnect and the dead-open feed — against the verbatim push module and was proven red on tmp-reverted mutations of BOTH cures (the tripwire-of-the-tripwire discipline) (parked — W20 freeze; ships after TTTT)
v1.106.0 Pinky TTTT: the chips regression cure — owner finding #61, the RV audit's R1+R2, both cured under ONE display law ("Chips bei 0 NICHT sichtbar — so wie die Ungelesen-Blase, so wie es sein soll"): the dock's not-in-filter marker resolves ONE true word again (the PRIMARY active counter plus a "+N" count — ⊘ running, ⊘ running +3 — never the v1.77.0-era glued fragment list; R1), and zero hides EVERYWHERE (every census chip renders only when nonzero — the v1.77.0 residue condition restored and generalized past the v1.92.0 #54 always-render annex; the fleet chip drops its zero parts and hides at all-zero; the slim line's ▶ 0 running twin rests; counting and filtering untouched; R2) — plus the DREIMAL protections: Papa's structural tripwire (one data-census value per element; no rendered text/title/aria-label ever matches a two-state-word glue pair — node scan over every selection + the live #sessbreak/.dfil arms), discriminating pins proven red on tmp-reverted mutations, the 12-leg CDP rig specimen (make rig-tttt-chips) over rich→quiet→empty fixture stages, and the takeover-discipline law written into both READMEs (the old test must be proven RED under the new code before migration — applied to this round's own migrations: the IIII, YYY and slim-line contrary pins all went red first) #141
v1.105.0 Pinky WWWW: the RV-annex part 2 — the remaining 7 audit gaps triaged with full honesty (5 protections built, 2 cut with named reasons: the residue-at-zero chip runs as owner finding #61, and v1.0.6's right-aligned title copy is superseded with a live discriminating pin in the QQQQ round): the check.yml yamllint-compliance markers front-loaded to PR time (v1.68.0), the fixed-table geometry family (v0.1.9 table-layout + ellipsis cells), the v1.3.1 quartet's geometry members (activity-time header width, the colspan spanning project line, overscroll-contained modal pans), v1.13.0's two surviving diamond-polish contracts (proportional parent names, hover glow beats the family wash), and the v1.29.0 UI quintet (IDEAL_W one-constant + frame-compensated popup snap + vpw thresholds as a node harness over the verbatim block; header groups pinned in their round-CC shape); plus the three VVVV folds — all EIGHT _invalidate_view_flag_bodies() call sites now carry their own discriminating leg (per-site drift cannot hide behind the shared-function pin), the v1.6.0 pending-question GLOW visual half pinned (amber qglow badge + hasq wash + the question-card render shape under a node harness), and the UUUU specimen pin tightened from >=10 to the exact leg set with an allow-grow floor #140
v1.104.0 Pinky VVVV: the RV-annex test-gap protections — five discriminating pins for gaps the 2026-10-17 regressions audit found unprotected, each proven red on a tmp-reverted variant first (the takeover law): the v1.6.0 Answer-API surface (validation tree + side-channel answer file + pendingQuestion view), the v1.34.0 S-6 flag-POST sessions-body drop (pinned as the protocol race it cured), the v0.1.5 running-flap GRACE_MS hysteresis (hold/re-extend/expiry/immediate-completion), the v0.1.1 main-table pagination + sorting (node harness over the verbatim dashboard fragment), the v1.14.0 cache-hit-rate math; plus the README pink-badge drift fix (running badge truth-aligned to the v1.77.0 glowing blue) and the UUUU doc folds #139
v1.103.0 Pinky UUUU: one window truth for the lineage graph — lgWindowKeeps becomes a pass-through to windowKeepsCore, the ONE keep-set the tables' windowKeeps closes over (running/pinned/favorited/docked/open-panel + the 🧠 toggles, cross-pinned by a two-surface harness); 'all' = full history in the graph exactly like the tables (the "7 d fallback" was the usage chart's rule misread onto the graph — tooltips now scope it by name); the graph's render signature carries the exemption-view fingerprint #138
v1.102.0 Pinky SSSS: the attribution-honesty fix the RRRR audit ordered — the posting-act scan ends AT the send stamp (a read-back check after the send is not a post), each delivery binds its earliest suitable act (one act carries one delivery: a double-steer names each carrier), photo-finish ambiguity lands in the album, RRRR-era scan findings never render again; all four machine callers named everywhere #137
v1.101.0 Pinky RRRR: honest steer attribution — a steer-delivered message names its PROVEN source session (anchor → prompt-file bytes → the posting tool call; machines by construction), 'user' stays exclusively owner input, unproven rows stay 'user'; the QQQQ specimen docs fold to 13 honest legs (#60) #136
v1.100.0 Pinky QQQQ: the steer-target copy affordance — click the target name (or the ⧉ beside an untitled sid link) to copy the session id the steer addresses, in the house copy idiom (#59) #135
v1.99.0 Pinky PPPP: one glance, one truth — the stuck-lanes census follows the header time window (24 h/3 d/7 d/all) via the tables' own keep-test; the straddle reading matches the tables (#58) #134
v1.98.0 Pinky OOOO: the fixture-id hardening — collision-free row ids for every (sid-slice, timestamp) derivation; the timeline fold makes every round's PR verifiable #133
v1.97.0 Pinky NNNN: the §0 quote-structure fix — the authority's clause-(c) break restored #132
v1.96.0 Pinky MMMM: the rig vault + the §0 byte-exactness fold — empirical rigs become regression capital; the frozen German form rides every citation #131
v1.95.0 Pinky LLLL: the idle-fleet deck alarm — §0's RED + popup when the fleet idles in an active window (#56) #130
v1.94.0 Pinky KKKK: the liveness-honesty congress — the nervous 💀 split into ⏳, the honest title count (#55, #57) #129
v1.93.0 Pinky JJJJ: the anchor-timestamp alignment — one message, one time (#31) #128
v1.92.0 Pinky IIII: the drawer congress — self-healing view, one door, census completion, the anchor class #127
v1.91.0 Pinky HHHH: the quota-vitals surfaces — the guardian speaks on the deck #126
v1.90.0 Pinky EEEE: the quiet QUALITY arc — README consolidation, INFO burn-down, suite hardening #125
v1.89.1 Pinky FFFF hotfix: the starvation bell targets THE master line #124
v1.89.0 Pinky FFFF: the night's third congress — starvation alarm + six owner findings #123
v1.88.0 Pinky GGGG: the deck sees the conductor — the hammer's eyes + the master's true name #122
v1.87.0 Pinky DDDD: the VF-1 fold — the waiting list keys on receipts #121
v1.86.1 Pinky CCCC hotfix: storm cluster anchors on the bell SPAWN stamp #120
v1.86.0 Pinky CCCC: the detector-doctrine round — liveliness-first, honest derived states, receipt-chasing #119
v1.85.0 Pinky BBBB: the four quick strikes #118
v1.84.0 Pinky AAAA: the Laufzeit, the anchor's quiet, the three-card reconciliation #117
v1.83.0 Pinky ZZZ: the night's second congress — ten owner-sharpened items #116
v1.82.0 Pinky YYY: the flagship — orchestration vitals — stillness made legible #115
v1.81.0 Pinky XXX: the blessed polish round — affordance symmetry, state grammar, fleet-scope unread #114
v1.80.0 Pinky VVV fix: in-flight resume windows + usage-ledger pairing #113
v1.80.0 Pinky VVV: the audience-aware unread #112
v1.79.0 Pinky WWW: the UUU reject fix — kill-cause survives the terminal + the README line-start sweep #111
v1.78.0 docs: round UUU README section #110
v1.78.0 Pinky UUU: the three honestities — pace, chip, reset clock #109
v1.77.0 Pinky TTT: the congress II — the table-grammar & visual family #108
v1.76.0 Pinky SSS: the congress I — steer lifecycle & honest surfaces #107
v1.75.0 Pinky QQQ: genesis auto-naming · gen-N — server default, name endpoint, 🌱 form prefill #106
v1.74.0 Pinky RRR: the 🔨 must WORK — honest turn evidence, the stuck-row free path, visible feedback #105
v1.73.0 Pinky PPP: full speed #104
v1.72.0 Pinky OOO: the polish triple — S-46 button parity, S-47 running pop, B-65 remaining display #103
v1.71.0 Pinky NNN: the void dies — every composer branch keeps the owner's words #102
v1.70.0 Pinky MMM: awards-ceremony polish — episode badges, spanning-line stage slot, header breakdown #101
v1.69.0 Pinky LLL: KKK reject fix — first-valid-wins key resolution #100
v1.68.0 Pinky KKK: housekeeping — EEE-V ZCODEHOME fold + FV-6 yamllint #98
v1.67.0 Pinky JJJ: the master door — the deck's top line #97
v1.66.0 Pinky EEE: dual-window real-source quota + self-throttle #96
v1.65.0 Pinky HHH: one table to rule them — merge Running+History #95
— Pinky III: the rumble monitor — the fired-vs-running join (v1.64.0) #94
v1.63.0 Pinky BBB: user-steer fast-lane parity #93
v1.62.0 Pinky GGG: genesis finalize — the dispatch-ledger reconciler #92
v1.61.0 Pinky FFF: owner-relevant unread — one source of truth + the click-launcher #91
— Pinky CCC hotfix (v1.60.1): orphan age ceiling #90
v1.60.0 Pinky CCC: the leapfrogged steer — orphan liveness, oldest-first, receipt-honest UI #89
v1.59.0 Pinky DDD: dual-stamp-shape tolerance — the ledger backbone + hermetic steer grace #88
v1.58.0 Pinky AAA: the queue-peek doctrine + the owner-waiting age + the kill breadcrumb + the dance model #87
v1.57.1 Pinky ZZ2: the version-skew fix — SERVER_VERSION bump + the tag-tied guard #86
v1.57.0 Pinky ZZ: the tilde fix + the slim popover's refresh cadence — steer #51, TT-V3 #85
v1.56.0 Pinky TT: the mobile deck + the role-true genesis slug — S-39, UU-1, UU-2 #84
v1.55.0 Pinky UU: named sessions — spawn-name field + the sanctioned row rename #83
v1.54.0 Pinky WW: the emergency interrupt — the owner's hammer + the turn-blocking detection seam #82
v1.53.0 Pinky VV: newborn role doctrine + the first-class agent channel #81
v1.52.0 Pinky YY: genesis-born forever — the family album #80
v1.51.0 Pinky XX: authoritative dismissal — no zombies across both queues #79
v1.49.0 Pinky QQ: three-state notification control — the gate selector beside the master mute #78
v1.48.0 Pinky SS: the context guardian — the self-driving recycle gradient #77
v1.47.0 Pinky RR: fleet governance — templates, scheduled births, concurrency queue, advisory lane budgets #76
v1.46.0 Pinky PP: prompt-area button polish — fixed equal toggles + Send breathing room #75
v1.45.0 Pinky OO: the capable slim line — stat-controls, window popover, genesis from collapsed; honest API unread #74
— Pinky NN: auto-redirect + the worktree law + dialog polish (v1.44.0) #73
v1.43.0 Pinky MM: plan-selectable spawns #72
v1.42.0 Pinky LL: honest classification + activity-anchored time windows #71
v1.41.0 Pinky KK: collapsible surfaces — full-width restore bar + steer-answer fold, collapsible header with quick-stats, one continuous rainbow title #70
v1.40.0 Pinky JJ: deep links everywhere #69
v1.39.0 Pinky II: markdown code rendering + collapsible prompt area #68
v1.38.1 Pinky HH2: lineage node clicks under trusted input — capture guard, window-level pan tracker #67
v1.38.0 Pinky HH: lineage graph evolution + release-recipe truth #66
v1.37.0 Pinky GG: canonical install target — ExecStart anchor, short-name/sibling guards, README verification recipe #65
v1.36.0 Pinky FF: genesis redirect doors — shared-checkout targets get the correct home offered automatically #64
v1.35.0 Pinky EE: budget contract consumer — ledger-fed quota lanes, exit-7 honesty, budget-warning push #63
v1.34.1 Pinky DD2: steer-unread hardening — non-dict payload guard, steer-dir poll-cache key, S-6 narrative correction #62
— Pinky DD: unread watermark for steer-driven sessions, session-scoped favorites, genesis reasoning picker (v1.34.0) #61
v1.33.0 Pinky CC: quota & cost observability + header control band #60
— docs: command-deck roadmap — vision, scoreboard, and prioritized lanes after v1.32.0 #59
v1.32.0 Pinky BB: gallery of the completed deck — full 1177-px recapture, genesis review shot, VGF hardening #58
v1.31.0 Pinky AA: session genesis — birth dispatched sessions from the deck #57
v1.30.0 Pinky Z: SSE consumer — push-driven dashboard over the events bridge #56
v1.29.0 Pinky Y: UI polish quintet — 1177 constant, header groups, popup snap, brain glyphs #55
v1.28.0 Pinky X: service robustness — reboot-proof steer spawn + baked boot-start #54
v1.27.0 Pinky W: cheap poll cycles — response cache, delta memo, visibility pause #53
v1.26.0 Pinky V: one window, honest dismissal, structural roots #52
v1.25.1 Restart script: match the symlink invocation in the legacy kill pattern #51
v1.25.0 Crash resilience: systemd unit + systemd-aware restart script #50
— Hygiene from banner validation: missing-ledger guard + drift line dedup #49
v1.24.0 Contract canary banner: amber alert with breaks-feature details + green chip #48
v1.23.0 Pinky T: row-click opens the interface, transcript button, 1195 px ideal width, steer-answer scroll keeping #47
v1.22.0 Render the delegation topology: child-role everywhere lineage shows #46
v1.21.0 Pinky S: FIFO steer delivery, panel view toggle, dock grooming #45
v1.20.0 Graph grooming and 921 px diamond dust #44
v1.19.0 Usage over time: Pinky token graph by Brain + modal info strip #43
v1.18.0 Dispatch lifecycle visibility: pending strip + presumed-dead surfacing #42
— Steer-console geometry round O: upward Send tower, state-invariant console, bottom-anchored modal, family-edge precedence #41
— README: add the Checks workflow badge #40
— Running-parents via live spawn edges, honest live tokens, always-live open panels (round N) #39
— Docs: README status-line quotes synced with fast-lane wording (validation M F1/F2) #38
— Turn-end fast lane, dismiss/edit chips, presumed-dead hint, ESC contract (round M) #37
— Tell the true story: README rewrite, orchestration graphic, glamour shots, steer-line context % (round L) #36
— Diamond polish round K: twelve owner refinements + superseded-steer count fix #35
— Busy-state transparency: queue semantics, optimistic echo, Hidden section (round J) #34
— Root-lineage family colors replace project colors (round I) #33
— Steer dialog round H: unread-driven response tail, exit codes behind "?" #32
— Repo hygiene: retire the GitHub workflow originals #30
— Owner review round G: native task-index archive, project identity, 13 polish items #31
— Favorites dock: minimized chats as bottom task bars, favorites always docked #29
— Hydration prediction, project colors & families, honest transcript classes #28
— Pending questions: glow + question cards + answering; steers queue-until-idle #27
— Archived section (native time_archived), session pinning, hidden-machinery removal #26
— Steering console: send prompts to sessions from the UI + scroll-stable follow output #25
— UI bugfix round: copy payload, activity time, spanning project line, modal scroll #24
— Response modal: per-row quick view of a session's recent conversation #23
— fix: driven-detection only marks the newest session per cwd #22
— Session transcript view: no-restart doorbell visibility #20
— ci: forge-side check workflow on oss-oo-public (stops ubuntu-latest zombie runs) #21
— Add webhook event push (session.completed / session.failed) #19
— Repository icon asset + deterministic generator #18
— Title copy button right-aligned in the column #17
— Favicon center dot back to mint #16
— Rainbow favicon ring #15
— Favicon ring at maximum size #14
— Transparent favicon background #13
— Social preview — Luna's astronomer-mage artwork #12
— sessionId tooltip (full id + copy hint) #11
— Unread decay window, real fixed table layout #10
— Intense unread glow, inline title copy, single-line titles #9
— Glowing unread badges, README screenshot, robust stamp #8
— Unread marker, per-row copy buttons, favicon #7
— Running flapping, first-turn visibility, clear-button removal #6
— fix: add R0914 to module lint disables #5
— Churn-based running, refresh-rate menu, hidden-section gating #4
— Header clear, page-size all, live running detection #3
— Notification mute switch + session deep links #2
— Hidden section, pagination, sorting #1
— zcode-dispatch-observatory: live local dashboard for dispatcher sessions

Credits

  • Social preview artwork by Luna ✨ — the astronomer-mage in the dome: watching the session stars, one of them pulsing blue — freshly finished, not yet seen.