Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,20 @@ The post-merge process that creates and publishes the release tag from the defau
**Publish Pipeline**:
The tag-triggered automation that validates, packages, and publishes a release after the **Release Finalization Step**.

### Dashboard and live observation

**Session Dashboard**:
The human-facing, read-only terminal surface that lists **Sessions** and presents the **Live View** of a selected **Session**. Its purpose is observation, not control.
_Avoid_: viewer, attach UI

**Live View**:
A read-only reconstruction of a **Session**'s screen derived from its **Event Log**, updated as new events are appended. It never makes the **Session** a **Command Target** and never sends input.
_Avoid_: mirror, attach, live screen share

**Event Log Follow**:
The operation that continuously updates a **Live View** by reading **Event Log** entries as they are appended. It treats the append-only **Event Log** as the source of truth, so it works uniformly for any **Session** regardless of whether that **Session** is **Live Host Eligible** or **Offline Replay Eligible**, and never queries the live session host. Defined by what it does, not how entries are transported (file tail today; a streaming subscribe channel is a possible future transport).
_Avoid_: tailing, polling, attach

### Triage operations

**AFK Triage**:
Expand Down Expand Up @@ -189,6 +203,11 @@ _Avoid_: bare "agent", "Coder agent"
- A **Session** has one **Event Log**.
- An **Offline Replay Eligible Session** is reconstructed from its persisted **Event Log** and manifest.
- A **Snapshot Result** is derived from exactly one **Semantic Snapshot**.
- A **Session Dashboard** presents a **Live View** of exactly one selected **Session** at a time.
- A **Live View** reconstructs screen state from a **Session**'s **Event Log** and is never a **Command Target**.
- A **Live View** is produced by **Event Log Follow**, never by querying the live session host.
- **Event Log Follow** applies uniformly to **Live Host Eligible** and **Offline Replay Eligible** Sessions because it depends only on the append-only **Event Log**.
- The **Session Dashboard** never resizes a **Session**; a **Live View** reflects the **Session**'s own terminal size and is clipped, panned, or shown as a lossy overview to fit the dashboard pane, never reflowed or stretched.
- A **Snapshot Artifact** contains exactly the **Snapshot Result** emitted to the caller.
- A **Screenshot Capture** produces exactly one **Screenshot Result** and exactly one **Screenshot Artifact** for the same captured event-log sequence.
- A **Screenshot Artifact** is the persisted PNG plus its manifest entry that the **Screenshot Result** describes to the caller.
Expand Down Expand Up @@ -264,4 +283,4 @@ _Avoid_: bare "agent", "Coder agent"
- "controller" was used during design discussion for the Hero Demo, but the canonical term is **Hero Demo Generator** because the settled design generates raw VHS tapes rather than actively proxying PTY control during recording.
- "helper proof" was used during design discussion, but the canonical scenario is now **Exploratory Hero Demo**: success criteria and output paths are fixed, while the coding agent chooses the command flow inside a configurable fixed review window.
- "demo" and "proof" are not interchangeable for coding-agent recordings: a **Hero Demo** optimizes for stable presentation, while a **Recursive Dogfood Proof** optimizes for self-dogfood coverage.
- "agent" is overloaded across three referents: this project's **Triage Agent** (a Claude Code instance), Coder's **Coder workspace agent** (the SSH/exec daemon), and a generic AFK implementation agent (the actor on `ready-for-agent` issues — Phase 2 of the triage pipeline). Always qualify in code comments and docs.
- "agent" is overloaded across four referents: this project's **Triage Agent** (a Claude Code instance), Coder's **Coder workspace agent** (the SSH/exec daemon), a generic AFK implementation agent (the actor on `ready-for-agent` issues — Phase 2 of the triage pipeline), and — in **Session Dashboard** product copy only — the external client driving a **Session** (often an AI coding agent). The last sense is deliberately **not** a domain term: the **Session Dashboard** and **Live View** are defined over **Sessions**, not agents, and the **Event Log** does not record which client sent input. Do not make the dashboard agent-aware (grouping or filtering by agent identity) without first extending the domain model. Always qualify in code comments and docs.
88 changes: 88 additions & 0 deletions docs/adr/0006-session-dashboard-follows-event-log.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
---
status: accepted
---

# Session Dashboard follows the Event Log rather than the live host

## Context

The **Session Dashboard** presents a read-only **Live View** of a selected
**Session**'s screen. It needs fresh screen state for sessions that are still
`running` — which `CONTEXT.md` classifies as **Live Host Eligible Session**s,
where "callers should ask the live session host for fresh state."

There are three ways the dashboard could source that screen state:

1. **Ask the live host over RPC** (the Live-Host-Eligible path). The host owns
the PTY and an in-memory renderer. But the RPC transport is one request /
one response per connection (`src/host/rpcServer.ts` sends a single response
and closes the socket), and there is no `subscribe`/`stream` method. A live
dashboard would have to poll `snapshot` on a fresh connection per tick, which
couples the dashboard to host availability and adds connect-per-poll cost.
2. **Follow the Event Log** (path chosen). The host already appends every
`output`/`resize`/`exit` event to the append-only `events.jsonl` as it
happens. Reconstructing the screen from those events is the same mechanism
the renderer already uses for snapshots and offline replay
(`buildReplayInput` → `RendererBackend.replayTo` → `snapshot`).
3. **Add a streaming `subscribe` RPC** to the host and push events to viewers.
Lowest latency and centralised fan-out, but it requires extending the
one-shot RPC dispatch model to support server-initiated frames.

A throwaway prototype validated option 2: following `events.jsonl` and replaying
into the `libghostty-vt` backend produced a screen **byte-identical to
`agent-tty`'s own `snapshot`** (including alt-screen apps such as Vim), with
frame work at ~2.3 ms p95 against a 33 ms (30 fps) budget. Render cost is never
the bottleneck; the only added latency is the follow interval, which is tunable.

## Decision

The **Session Dashboard** sources all screen state via **Event Log Follow** —
including for **Live Host Eligible** (`running`) Sessions — and does **not**
query the live session host. This is a deliberate departure from the
Live-Host-Eligible policy, justified because the append-only **Event Log** is
the canonical source of truth and is kept current for running sessions.

- v1 transport is a **file tail** of `events.jsonl` (byte-offset incremental
reads, complete-line parsing), with **zero** changes to the RPC protocol.
- Screen reconstruction reuses the existing renderer pipeline
(`replayTo` → `snapshot({ includeCells: true })`) on the **`libghostty-vt`**
backend (pure Node/WASM, ~100 ms boot), not `ghostty-web` (which requires a
Playwright Chromium and a 2–5 s boot).
- The dashboard reads the **Event Log** as the source of truth and treats the
live host as an implementation detail it intentionally avoids touching.
- The screen-data source sits behind an interface so the transport can later
change to a streaming `subscribe` RPC (option 3) without changing the
**Session Dashboard** UI. **Event Log Follow** is defined by what it does, so
it survives that transport change.

## Consequences

- The dashboard is read-only by construction and fully decoupled from the host:
it never sends input, never participates in PTY size negotiation, and cannot
destabilise a running **Session**. Many dashboards can follow the same
**Session** concurrently because they only read a file.
- Liveness is gated by the follow interval (and the host's write cadence), not
by a push channel, so the **Live View** can lag real output by up to roughly
one interval. This is acceptable for human observation and is the main thing a
future `subscribe` transport would improve.
- The dashboard depends on the on-disk **Event Log** being current for running
sessions. If a host buffered output without appending promptly, the **Live
View** would lag; today the host appends per output chunk, so this holds.
- A reader who expects the Live-Host-Eligible policy will find the dashboard
ignoring the host on purpose; this ADR is the "why."

## Alternatives considered

- **Poll the live host's `snapshot` RPC for running sessions.** Rejected for
v1: the one-shot socket forces a connect-per-poll, couples the dashboard to
host availability, and still is not push. It also would not work uniformly for
`destroying`/terminal sessions, which are not **Live Host Eligible**.
- **Add a streaming `subscribe` RPC now.** Deferred, not rejected. It is the
natural next step if follow latency or many-session fan-out demands it, and
the data-source seam is designed so it can replace the file tail without
touching the UI. It was not justified for a usable v1 given the measured
headroom.
- **Use the `ghostty-web` renderer backend.** Rejected for the dashboard:
Playwright Chromium dependency and multi-second boot are unacceptable for an
interactive, frequently-(re)started viewer. `libghostty-vt` reconstructs the
same screen in-process.
Loading
Loading