From 7d8bc3d6382dad6620ac76a2e9053741156595fc Mon Sep 17 00:00:00 2001 From: Chris Phillipson Date: Tue, 4 Aug 2026 08:28:03 -0700 Subject: [PATCH 1/2] feat(dashboard): unify observability experience --- README.md | 7 +- docs/DASHBOARD.md | 127 +++++ docs/LIVE-SESSIONS.md | 296 ----------- docs/OBSERVABILITY.md | 443 ++++++++++++++++ docs/TRANSCRIPTS.md | 10 +- docs/TROUBLESHOOTING.md | 2 +- docs/USAGE-SCORECARD-METRICS.md | 20 +- .../0005-dashboard-in-page-routing-reveal.md | 36 +- ...ge-scorecard-local-transcript-analytics.md | 43 +- ...observability.md => 0012-observability.md} | 212 ++++++-- ...-capability-driven-integration-adapters.md | 2 +- .../adr/0021-inference-provider-provenance.md | 22 +- docs/adr/README.md | 10 +- docs/ddd/README.md | 2 +- docs/ddd/context-map.md | 29 +- .../{live-sessions.md => observability.md} | 279 ++++++++-- src/commands/x/dashboard.mjs | 2 +- src/lib/codex-state.mjs | 23 + src/lib/dashboard/client.mjs | 93 +++- src/lib/dashboard/live/client.mjs | 123 +++-- src/lib/dashboard/live/styles.mjs | 32 +- src/lib/dashboard/live/template.mjs | 114 ++-- src/lib/dashboard/page.mjs | 108 ++-- src/lib/dashboard/styles.mjs | 27 +- src/lib/live/claude-adapter.mjs | 27 + src/lib/live/codex-adapter.mjs | 6 + src/lib/live/event-schema.mjs | 82 ++- src/lib/live/git-workspace.mjs | 133 +++++ src/lib/live/index.mjs | 4 + src/lib/live/live-sessions-service.mjs | 187 ++++--- src/lib/live/native-transcript-discovery.mjs | 73 +++ src/lib/live/process-sessions.mjs | 26 +- src/lib/live/projection.mjs | 194 ++++++- src/lib/live/structured-adapter.mjs | 24 +- src/lib/live/workspace-store.mjs | 123 +++++ src/lib/paths.mjs | 2 + src/lib/usage-index.mjs | 16 +- tests/dashboard.test.cjs | 25 +- tests/kit/codex-state.test.mjs | 15 +- tests/kit/live-adapters.test.mjs | 32 +- tests/kit/live-core.test.mjs | 232 +++++++- tests/kit/live-process-sessions.test.mjs | 32 ++ tests/kit/live-qe-contract.test.mjs | 50 +- tests/kit/live-service.test.mjs | 188 ++++++- tests/kit/usage-index.test.mjs | 8 +- tests/ui/dashboard-ui.mjs | 501 ++++++++++++++++-- 46 files changed, 3267 insertions(+), 775 deletions(-) create mode 100644 docs/DASHBOARD.md delete mode 100644 docs/LIVE-SESSIONS.md create mode 100644 docs/OBSERVABILITY.md rename docs/adr/{0012-live-sessions-observability.md => 0012-observability.md} (68%) rename docs/ddd/{live-sessions.md => observability.md} (65%) create mode 100644 src/lib/live/git-workspace.mjs create mode 100644 src/lib/live/native-transcript-discovery.mjs create mode 100644 src/lib/live/workspace-store.mjs diff --git a/README.md b/README.md index 78a3a14..acf0cc6 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ command always works.) | **setup** | Installs/updates ruflo + agentic-qe + the **agentdb** CLI globally (handling npm ≥11.17's `allow-scripts` so natives build; agentdb is pinned to ruflo's bundled version so the shared learning store stays coherent), installs the **RuvNet Brain** (an offline knowledge base over the rUv stack, powering the `search_ruvnet` MCP — a ~2 GB one-time download, prompted; skip with `--no-ruvnet-brain`), deploys the token-audit skill, merges the managed guidance blocks into the machine-wide guidance files (`~/.claude/CLAUDE.md`, plus `~/.codex/AGENTS.md` on codex machines), offers one-time MCP registration (user scope, with a tool-family picker), and — inside a repo — initializes the project: sanitized `ruflo init`, absolute memory-path pin, a **verified** store→disk write, statusline footer, and a background daemon with **local-only ($0) workers** (token-spending AI workers stay opt-in behind upstream's machine-wide budget). Project scope triggers on a `.git` directory in the current folder; without one it's skipped with a note. `--project` forces it anyway (e.g. a not-yet-`git init`-ed folder), `--minimal` skips it, `--yes` accepts all prompts (non-interactive), `--no-aqe` / `--no-ruvnet-brain` / `--no-security` disable those subsystems, and `--reconfigure` re-offers MCP registration. `--codex` enables + installs the Codex host during setup (ambidextrous dual-host mode; both hosts then run at once), and `--primary-host claude\|codex` picks which host leads (codex implies `--codex`). | | **status** | Per-subsystem ✓/⚠/✗ (versions, the kit's own version, **ruvnet-brain** (present + release drift, or "not installed"), natives (agentdb copies **and** ruflo's own memory runtime — the one `npx ruflo memory` loads — load-tested for a native better-sqlite3, not just the agentdb dirs), **memory-pin** (warns when `CLAUDE_FLOW_DB_PATH` points off the live DB), security, learning, aqe/RVF, **agentdb** (CLI present + coherent with ruflo's bundled version, or a store-skew warning), MCP, **hosts** (claude/codex/opencode version + install method; the Claude/Codex **primary** marked and failed when absent), **providers** (host wiring + aqe fallback chain, or "drifted"/claude-only default), **routing** (the persisted activity host+model policy; only Claude/Codex routes project into AQE), daemons, guidance-file blocks (`~/.claude/CLAUDE.md`, project `AGENTS.md`, and `~/.codex/AGENTS.md` on codex machines), statusline), each drift row naming what `sync` would do about it — plus a **health-history** line that flags regressions since the last sync (learning shrank, native slots dropped, drift/security backslid). | | **sync** | The one convergence verb: upgrades first when a new release exists, then re-heals everything an upgrade wipes, then re-checks and reports. Included in that heal: it **installs any enabled frontier host** (claude/codex/opencode) that's entirely absent — never touching an external (mise/brew/native) install — and **re-applies provider wiring** (the `ENABLE_*` host env, OpenCode's native configuration, the aqe fallback chain, and ruflo API providers) whenever it has drifted — and, on a dual-host project, **seeds/heals the Claude/Codex default routing policy** (materializing eligible routes into agentic-qe's `agentOverrides`, e.g. after an aqe upgrade first makes it eligible). It also **installs/repins the standalone `agentdb` CLI** to ruflo's bundled version (keeping the shared cognitive store coherent) and appends a **health-history snapshot** so `status` can flag regressions across syncs. It also **re-runs the RuvNet Brain installer** to pull the latest release when the on-disk KB has drifted (or installs it if absent, when enabled). It also **self-updates the kit**: when a newer `@pacphi/agentic-kit` exists it installs it as the *last* step (the new code applies from the next `ak` run, never mid-sync). Prerelease installs (`4.0.0-alpha.*`) track the `next` npm dist-tag as well as `latest`, so alphas see their successors; stable installs only ever follow `latest`. `--no-upgrade` skips the self-update along with the package upgrades. | -| **dashboard** | Opens a read-only local web dashboard (`127.0.0.1:7431`, localhost-only, never detaches) with seven tabs: **Overview · Hosts & Routing · Providers · Runtime · Intelligence · Usage · Live**. The first five render `ak status` health and routing; Usage indexes local Claude/Codex/OpenCode transcripts on demand and can display a separately cached OpenRouter account-analytics block. That block never changes local transcript/session/host totals; the dashboard itself never fetches it. Live groups work by project, then host-branded root sessions with nested agent/worker threads and independently evidenced provider/model metadata, and pairs an interactive agent/tool execution canvas with a rich, server-masked transcript stream. Active sessions can be followed live or reviewed with synchronized play/pause/seek; completed sessions remain available for bounded playback. Live contains no chat or control plane. Ruflo and agentic-qe stores are not auto-discovered; register each trusted structured JSONL file with repeatable `--live-source 'surface=path'` (`surface` is `ruflo` or `aqe`). The page is self-contained and offline-first (no internet fetches; local files and loopback subprocesses/endpoints only). A fresh **per-session token** is minted at startup and required by every `/api/*` route (`x-dash-token` header; the launch URL carries it in the `#` fragment, same contract as `ak admin`) — this page serves full transcript text, so it is gated the same way admin already gated GitHub/npm stats. See [Live Sessions](docs/LIVE-SESSIONS.md) for coverage, syntax, and privacy limits. **Auto-opens your browser** (`--no-open` for headless/SSH); `--port N` changes the port; tabs deep-link (`#live`) and persist. Stop with Ctrl-C. (Also available as `ak x dashboard`.) | +| **dashboard** | Opens an observation-only local web dashboard (`127.0.0.1:7431`, localhost-only, never detaches) with three primary areas: **Overview · Usage · Observability**. One fixed, left-aligned secondary rail exposes Overview's Summary/Hosts & Routing/Providers/Runtime/Intelligence views, Usage's Scorecard/Limits/Findings/Sessions/Transcript views, and Observability's Live/History scopes. Canonical deep links are hierarchical (`#overview/summary`, `#usage/sessions`, `#usage/`, `#observability/live`); arrow keys plus Home/End operate focused tab rows. Usage indexes local Claude/Codex/OpenCode transcripts on demand. Observability groups eligible sessions by project and pairs an interactive agent/tool execution canvas with masked transcript or review evidence; its Session Stream can collapse without disconnecting so Agent activity gets more room. Live shows only current roots; History shows only retained non-live roots. A bounded owner-only cache retains safe last-recorded workspace context; no dashboard action mutates agents or repositories. Ruflo and agentic-qe stores are opt-in through repeatable `--live-source 'surface=path'`. The page is self-contained, offline-first, localhost-only, and protected by a per-session token. See the [Dashboard guide](docs/DASHBOARD.md) and [Observability guide](docs/OBSERVABILITY.md). **Auto-opens your browser** (`--no-open` for headless/SSH); `--port N` changes the port. Stop with Ctrl-C. (Also available as `ak x dashboard`.) | | **usage** | Reads provider-account analytics from local cache (`ak usage status`) or performs one explicit OpenRouter management-API refresh (`ak usage refresh openrouter`). Refresh requires `OPENROUTER_MANAGEMENT_KEY`, writes a credential-free mode-`0600` cache, and discards endpoint/user/key/session identifiers. `status` and dashboard reads make no network request. OpenRouter account rows have no grounded host/session/project correlation and are never merged into transcript totals. | | **admin** | Opens the **maintainer admin** (`127.0.0.1:7432`, localhost-only, foreground) — the project-telemetry sibling of `dashboard`, with the same dark/light visual theme and persisted theme preference: unique repo visitors and cloners (GitHub traffic API, needs a push-access token via `GITHUB_TOKEN`/`GH_TOKEN`/`gh auth token` — panels degrade honestly without one), contributors and watchers, npm download momentum (last 7d vs prior 7d, sparklines — shown as trend only, never an absolute reach number, since mirrors/CI inflate the raw count), latest CI run status and open Dependabot alerts, a **"since you last looked"** delta strip over a local baseline, open issues/PRs from others (oldest first), and external humans ranked by recency (bots excluded). Access is gated by a **per-session token** carried in the URL fragment and sent header-only; the page makes **zero external fetches** (the server proxies GitHub/npm; your credential never reaches the page or the payload). Where `dashboard` is offline-first, `admin` does deliberate GitHub/npm egress — that contract split is why they're siblings, not tabs. `--port N`, `--no-open`; Ctrl-C stops. (Also available as `ak x admin`.) | | **run** | **Canonical execution surface.** Executes the template vocabulary through host-neutral supervised adapters. It accepts an explicit OpenCode route (persisted or `--route`) alongside Claude/Codex; `--dry-run` prints the exact static plan (with each worker's escalation ladder); at runtime, successful dependencies pass runtime-only, sanitized handoffs capped at 2 KiB each/8 KiB fan-in, never exposed in public JSON. A handoff may cross hosts/vendors and must exclude secrets, credentials, raw logs, and transcript excerpts. `--escalate` advances a failed worker one rung of its route's ladder per attempt (bounded by the ladder; permission/consent and uncertain results are never escalated). `--timeout` is one absolute readiness→prepare→launch→observe budget per attempt, while separately bounded teardown proves whether resources terminated. An OpenCode worker runs an isolated loopback server with ephemeral basic authentication, returns only normalized observed facts, and aborts instead of approving a permission request. `ak run` does not turn OpenCode into an AQE provider or primary host. | @@ -238,6 +238,9 @@ Design record: [docs/adr/0017-opencode-host.md](docs/adr/0017-opencode-host.md). [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) — symptom → `agentic-kit` command. +[docs/DASHBOARD.md](docs/DASHBOARD.md) — dashboard navigation, deep links, keyboard behavior, and +the meaning of each primary and secondary view. + ## How tools are managed [docs/MANAGED-TOOLS.md](docs/MANAGED-TOOLS.md) — the consistency contract every @@ -248,7 +251,7 @@ with the per-tool table and the checklist for adding a new tool. ## Domain design [docs/ddd/](docs/ddd/) defines the shared language, bounded contexts, and invariants behind hosts, -inference providers, bindings, routing, orchestration, evidence, usage, and Live Sessions. ADRs +inference providers, bindings, routing, orchestration, evidence, usage, and Observability. ADRs explain why decisions were made; the domain documents define what those concepts mean across the codebase. diff --git a/docs/DASHBOARD.md b/docs/DASHBOARD.md new file mode 100644 index 0000000..6f52df3 --- /dev/null +++ b/docs/DASHBOARD.md @@ -0,0 +1,127 @@ +# Dashboard + +`ak dashboard` opens an observation-only local diagnostic workspace. It binds to loopback, requires +the per-session dashboard token for every API route, and does not mutate configuration, agents, or +repositories. + +```bash +ak dashboard + +# Do not open a browser automatically. +ak dashboard --no-open +``` + +The dashboard has exactly three stable primary areas: **Overview**, **Usage**, and +**Observability**. The row directly below them is one fixed, left-aligned secondary navigation rail. +Its position does not move when the primary area changes; only the choices inside it change. + +## Navigation and deep links + +| Primary area | Secondary view | Canonical hash | Heading | What it answers | +|--------------|----------------|----------------|---------|-----------------| +| Overview | Summary | `#overview/summary` | System overview | Overall readiness, configuration health, and items needing attention | +| Overview | Hosts & Routing | `#overview/hosts` | Hosts & routing | Enabled execution hosts, activity assignments, primary-host policy, and escalation paths | +| Overview | Providers | `#overview/providers` | Inference providers | Provider bindings, availability, provenance, and configuration health | +| Overview | Runtime | `#overview/runtime` | Runtime health | Local services, MCP connections, processes, and operational readiness | +| Overview | Intelligence | `#overview/intelligence` | Intelligence & learning | Memory, learned patterns, quality feedback, and improvement signals | +| Usage | Scorecard | `#usage/score` | Usage scorecard | Token consumption, API-equivalent cost, efficiency, and trends | +| Usage | Limits | `#usage/limits` | Provider limits | Current provider windows, reset timing, and available capacity | +| Usage | Findings | `#usage/findings` | Usage findings | Actionable anomalies, efficiency opportunities, and evidence-backed recommendations | +| Usage | Sessions | `#usage/sessions` | Session usage | Retained sessions grouped by project, category, duration, tokens, and cost | +| Usage | Transcript | `#usage/transcript` | Transcript detail | The selected session's locally retained, server-masked evidence | +| Observability | Live | `#observability/live` | Observability · Live | Projects and roots with current presence or fresh meaningful activity | +| Observability | History | `#observability/history` | Observability · History | Retained roots that are not currently Live | + +Opening a transcript replaces `transcript` with the URL-encoded session ID: +`#usage/`. These hashes select state inside the one page; they do not create separate +servers or weaken the dashboard token boundary. + +### Keyboard behavior + +The primary row and every secondary row are ARIA tab lists with one selected tab in the keyboard +order. + +- `Left Arrow` and `Right Arrow` select and focus the previous or next tab, wrapping at the ends. +- `Home` selects and focuses the first tab in that row. +- `End` selects and focuses the last tab in that row. +- `Tab` moves into or out of the active row normally. +- `Enter` or `Space` activates ordinary buttons such as expanders, chevrons, and map controls. + +Every view supplies its own heading and short description beneath the shared navigation. Badges on +Overview child views remain scoped to those views, while Summary aggregates the items that need +attention. + +## Overview + +Overview keeps status and routing in one health-first area: + +- **Summary** presents the overall verdict, attention items, and subsystem map. +- **Hosts & Routing** presents execution-host health, the primary-host policy, per-activity routes, + escalation paths, and routed host models. A configured route is assignment intent, not evidence + of which inference provider served a particular session. +- **Providers** presents inference-provider bindings and their configuration provenance. +- **Runtime** presents operational services, processes, and MCP readiness. +- **Intelligence** presents memory, learning, and quality-improvement signals. + +## Usage + +Usage loads lazily when first opened. Scorecard, Limits, Findings, Sessions, and Transcript share the +same secondary rail; the 7/14/30-day filters remain aligned to its right. + +### Reading a session row + +Projects begin collapsed. Expand a project to reveal its session rows. Each row deliberately keeps +identity axes separate: + +- the compact host badge states only the execution host: Claude Code, Codex, or OpenCode; +- the leading chevron expands an independent detail strip without opening the transcript; +- the detail strip reports execution host, inference provider, provider provenance, model or models, + classification basis, token details, tools, and flags; +- clicking the rest of the row opens the locally retained masked transcript. + +Host, inference provider, provenance, and model are independent facts. The dashboard never derives a +provider from a host name or model string. Codex `session_meta.model_provider` and equivalent +`turn_context` evidence are reported as **observed**. Native Claude transcript history does not name +the serving provider, so a historical row may honestly show **Not recorded**. That is missing +evidence, not a claim that Claude Code was served by Anthropic or by any provider inferred from its +model. + +Usage transcript masking happens on the server. Redaction is marked, there is no reveal or export +control, and the original masked value never reaches the browser. See +[ADR-0009](adr/0009-usage-scorecard-local-transcript-analytics.md) for the full evidence and pricing +contract. + +## Observability + +Observability separates navigation scope from playback state: + +- **Live** and **History** are mutually exclusive secondary views. +- **Follow Live** and **Review** describe how one selected session is presented. +- switching between Live and History clears incompatible project/session selection. + +Its workspace contains the project/session browser, the **Agent activity** execution map, and the +selected **Session stream**. Use the stream header's chevron to collapse or expand that rail. Collapse +hides the stream body but does not close the selected transcript connection or stop ingestion; it +widens Agent activity on desktop. The choice is stored locally and restored on the next dashboard +visit. The compact rail keeps the chevron available. On narrower screens the stream moves below the +browser and map; when collapsed it becomes a compact full-width restore bar instead of consuming a +full transcript-height row. + +The chevron is a real button. Its `aria-expanded`, accessible label, and tooltip describe the next +action, and activating it with Enter or Space announces whether the stream was collapsed or expanded. +This control is presentation-only; **Pause stream** separately controls visual application of live +updates. + +See [Observability](OBSERVABILITY.md) for the map legend, workspace facts, host capability coverage, +History/Review semantics, privacy limits, and troubleshooting. + +## Local state and security + +Theme, polling preference, the selected primary/Overview view, and the Session Stream collapse choice +are stored in browser-local storage. Canonical hashes make views linkable without putting the +dashboard token in the path or query string. + +The launch token initially arrives in the URL fragment and is then stored locally for authenticated +API requests. The dashboard remains localhost-only and offline-first. Usage and Observability may +show sensitive local project or transcript information; use them only where that local information +may be viewed. diff --git a/docs/LIVE-SESSIONS.md b/docs/LIVE-SESSIONS.md deleted file mode 100644 index ab2f02c..0000000 --- a/docs/LIVE-SESSIONS.md +++ /dev/null @@ -1,296 +0,0 @@ -# Live Sessions - -The **Live** tab in `ak dashboard` is a read-only execution workspace for local -Claude and Codex activity. It pairs an interactive agent/tool canvas with the -selected session's streaming transcript. It is not a chat client or control -plane: it cannot send messages, cancel work, or reroute agents. - -```bash -ak dashboard - -# Optional structured telemetry; repeat the option for multiple files. -ak dashboard \ - --live-source 'ruflo=.claude-flow/live-events.jsonl' \ - --live-source 'aqe=.agentic-qe/live-events.jsonl' -``` - -> [!NOTE] -> `--live-source` is opt-in, not required. Without it, Claude and Codex -> sessions are still fully covered — auto-discovered, no config needed. What -> you don't get: ruflo and agentic-qe activity, which are never -> auto-discovered and only appear once you register their event file -> explicitly (see [Evidence and limitations](#evidence-and-limitations)). - -Open `#live`, for example `http://127.0.0.1:7431/#live` — once the dashboard's -per-session token is already in the browser (auto-opened, or pasted once at -the token gate; ADR-0014), this deep link works on its own. Collection starts -lazily when the snapshot or event endpoint is first requested. Leaving the Live -tab closes that browser's event stream. After the last snapshot/SSE client -leaves, collectors stop after 30 seconds by default and restart on the next -request. Stopping the dashboard closes the live service and all clients. - -## Reading the operations console - -- Claude and Codex sessions discovered newest-first from their local JSONL - stores and bootstrapped from bounded, metadata-only records. -- Project-first session cards with a host glyph and name, independently - evidenced inference provider/model when reported, lifecycle, freshness, and - a concise operational summary. Provider identity is graded (ADR-0021): - observed from Codex artifacts (`model_provider`), configured/inferred from - Claude Code's documented selection surface (Bedrock, Vertex, Foundry, - `ANTHROPIC_BASE_URL` gateways such as OpenRouter), and "Provider not - established" only when no evidence of any grade exists. -- Session, agent, sub-agent, tool, skill, plugin, MCP, and gate entities when a - supported source record identifies them. -- Authoritative Codex parent/child edges from the Codex state ledger. -- Human-readable identity and current work: resolved agent or capability name, - host, provider/model, lifecycle, elapsed time, latest safe operation summary, - and evidence confidence. -- Typed relationships whose accessible titles use verbs such as **spawned**, - **delegated**, **invoked**, **returned**, **evaluated**, and **gated**. -- A semantic execution canvas with stable agent anchors and bounded tool, - skill, plugin, MCP, and gate satellites attached to their owners. -- Animated active edges that make delegation, invocation, and return flow - visible without turning every event into an equal-rank box. -- A persistent selected-session transcript rail containing rich, server-masked - conversation, plaintext reasoning, tool inputs/results/errors, commands, - patches, MCP/web activity, and subagent messages as the sources report them. -- Shared selection: choosing an agent filters the rail; choosing a tool opens - its detail; transcript evidence remains tied to its actor. -- Keyboard-selectable nodes, host/completed filters, reduced-motion support, - actor-specific geometry, and a **Pause live** control. -- Sanitized adapter health showing status and aggregate file/event/error counts. - -The overview answers which project and host are involved, which inference -provider is evidenced, whether the evidence is current, who is active, and -what operation is happening now. Selecting a -node highlights adjacent relationships, focuses its work, and synchronizes the -transcript. Search filters the current stream. Auto-follow yields when the -reader scrolls away and reports unread activity until following resumes. The -transcript is newest-first: fresh evidence appears at the top and older entries -flow downward. Follow anchors to the top; playback remains chronologically -ordered internally. - -Projects are the durable top-level grouping. Select a project first, then one -of its host-qualified root sessions; currently active sessions appear -before recent completed sessions. Selecting a root reveals its agent/worker -threads as an indented hierarchy. Those child threads remain selectable for -their own map, transcript, and playback, but do not inflate the project's -top-level session count. The project row reports nested workers separately. -Claude and Codex sessions with the same native ID remain distinct. - -Linked Git worktrees roll up to their owning repository. A branch-named -worktree is session context, not a separate project, and its filesystem path is -never sent to the browser. - -Hierarchy may arrive after a thread is first discovered. The dashboard -reconciles later Codex ledger parentage into the existing session instead of -showing a second peer row. If a declared parent is not currently retained, the -thread remains visible as an orphan root until the parent appears; malformed -cycles are bounded and cannot lock the browser. - -Bare `unknown` is not used as user-facing copy. Missing facts are described -honestly, such as **Model not reported**, **Waiting for lifecycle evidence**, -**Active; completion unobserved**, or **Task details unavailable**. - -Session state is phrased as evidence a person can act on: - -- **Working now** means fresh observed execution is still arriving and is the - only state that continuously pulses. -- **Waiting for activity** means a previously running session is quiet inside - the quiescence window. -- **No recent activity** means that quiet period crossed the expiry threshold; - it does not assert failure or completion. -- **Blocked**, **Failed**, **Cancelled**, and **Completed** require explicit - source evidence. -- **Last active** identifies retained historical evidence when no trustworthy - completion outcome was recorded. - -Color and motion are redundant cues: green motion means current work, amber -means waiting/stale/queued, red means blocked/failed, and completed history -recedes. Reduced-motion mode keeps the labels and colors while removing pulse -and flow animation. - -Selecting a session explicitly opens only that session's content stream. The -previous stream closes; other transcripts are not prefetched. Large thinking, -arguments, results, and patches are collapsed for readability and carry -visible truncation metadata when bounded. - -### Live and review modes - -An active session opens in **Live** mode and follows new topology and transcript -evidence. **Review session** loads its bounded retained history and reconstructs -both panes at one playhead. The review bar supports play, pause, seek, and -0.5×–2× playback. **Resume live** returns an active session to its current -stream; completed sessions remain review-only. - -Review is event reconstruction, not a screen recording. The server returns -chronological, masked evidence and the browser rebuilds the execution map and -transcript at each event boundary. The -`GET /api/live/playback/:host/:id?at=` endpoint supports -deterministic server-side seeking and reports its retained range, truncation, -and any history gap. - -### Navigating the graph - -- Drag empty canvas space to pan. Use a wheel or trackpad scroll gesture to zoom - around the pointer. -- Drag a node to move and pin it without changing the observed topology. -- Use the visible **Zoom in**, **Zoom out**, **Fit all**, **Focus**, and - **Reset layout** controls when pointer gestures are unavailable or imprecise. -- Select a session to show its work map. Completed high-cardinality resource - calls are summarized automatically. -- Keyboard users can traverse selectable nodes, synchronize transcript detail, - and operate every viewport control. Reduced-motion mode removes decorative - transitions without removing status or topology. - -Live updates preserve the user's viewport, node positions, and selection during -the active page. A new off-screen event does not forcibly -recenter the canvas. **Fit** changes the camera to include the current map, -**Focus** centers the selected actor, and **Reset layout** discards manual node -positions and restores the deterministic automatic layout. -**Pause live** freezes visual application while collection continues. - -The graph displays at most 80 nodes for one selected session. Completed resource -nodes are collapsed first. The server retains at most 100 sessions and 1,000 -nodes per session by default, tails at most 256 files, and retains 2,000 events -for resume. These are implementation bounds, not claims about the number of -agents the underlying tools can run. Explicit `--live-source` files receive -tailer slots first; Claude and Codex automatic discovery divide the remaining -capacity. - -## Evidence and limitations - -| Source | Current coverage | Confidence | -|--------|------------------|------------| -| Claude transcript | Session lifecycle, assistant/tool activity, and explicit sidechain markers supported by the adapter | Observed or correlated according to the record | -| Codex rollout | Session, response/tool lifecycle, model, and explicit rollout metadata | Observed | -| Codex state ledger | Parent-to-child spawn edges | Observed | -| Ruflo / agentic-qe | Conservative adapter for an explicitly supplied structured JSONL source | Observed as reported by that source | - -The default dashboard automatically discovers only Claude and Codex transcript -files and reads the Codex state ledger. It does **not** search arbitrary ruflo, -agentic-qe, plugin, or skill stores. Register a structured source -explicitly with repeatable `--live-source 'surface=path'`, where `surface` is -exactly `ruflo` or `aqe`. - -Relative paths resolve from the directory where `ak dashboard` starts; absolute -paths remain absolute. The parser rejects an unsupported/missing surface or an -empty path, but registration does not prove that the file exists, is a regular -file, is inside the current project, or is produced by the named subsystem. -Unreadable/malformed sources degrade their adapter rather than crashing the -dashboard. Only register a local file you trust the dashboard process to read. -The structured adapter still constructs allowlisted events, so arbitrary JSON -fields do not pass through to the browser. - -Historical persisted events from the pre-GA executor normalize to the read-only -`internal` surface so retained evidence is not mislabeled as native. No current -writer or `--live-source` parser accepts the retired label. - -The diagram is a bounded current-state projection, not a durable audit log. -Silence changes a session's projection lifecycle to quiescent after 30 seconds -and expired after five minutes; it does not claim successful completion. -Source timestamps are preserved, but the event cursor records ingestion order -and does not establish causality across clocks. - -Names and work summaries are evidence-graded. A source-provided role or tool -name is preferred. When the source exposes only a generic kind, the UI says -that the identity is generic or inferred; it does not invent a specialist -name. “Running” means that supported lifecycle evidence is open and fresh, not -that the dashboard has inspected an agent's private reasoning. - -## Privacy - -The topology plane is constructed from an allowlist and contains no transcript -bodies. The separately selected content plane intentionally carries rich local -evidence. It parses source records into a bounded DTO, masks every emitted -string server-side, and never emits Codex `encrypted_content`. Secret masking -is best effort, not a guarantee; only run the dashboard where its local -transcripts may be viewed. - -Transcript lookup validates both host and session ID, resolves the real file -beneath the configured host transcript root, rejects symlink escapes, and rechecks -containment after replacement. Content responses are `no-store`, same-origin, -bounded, and destroyed after their last subscriber. - -Run the dashboard only for users who may see local project and model names. -It binds to `127.0.0.1`, applies Host, Origin, and Fetch Metadata checks, and -does not support remote binding. Those checks stop a hostile *browser tab*; -they do nothing against another *local, non-browser process* that can reach -the port directly. That gap is closed by the per-session token (ADR-0014): -every `/api/*` route — including both SSE streams above — requires it, so a -process without the token gets a `401`, not transcript content. - -## Streaming and recovery - -The browser obtains a snapshot from `GET /api/live` and subscribes to -`GET /api/live/events`. The latter is a Server-Sent Events stream with: - -- named `init`, `delta`, and `reset` events; -- event IDs and `Last-Event-ID` replay; -- a 15-second heartbeat by default; -- subscription-before-snapshot initialization to close the startup race; -- a default maximum of 32 clients, clamped to 256; -- a per-client server queue of 256 frames, clamped to 4,096. - -If replay history is no longer retained, initialization carries `reset: true` -and a current snapshot. If a slow client's server queue overflows, queued -frames are discarded and replaced with a reset snapshot after the socket -drains. While the UI is paused it buffers 256 updates; overflow causes a -snapshot reload on resume. - -The browser's native `EventSource` reconnects automatically. A stale indicator -means no telemetry has arrived for 30 seconds; it does not necessarily mean the -connection failed. - -The selected transcript uses -`GET /api/live/transcripts/:host/:id/events`, a separate SSE stream with bounded -history/replay and a per-client queue. A stale or cross-session cursor produces -a bounded reset; a slow client receives an explicit gap instead of an -unbounded content snapshot. - -## Troubleshooting - -| Symptom | Explanation | -|---------|-------------| -| No sessions | No supported metadata was found within the bounded newest-first discovery set | -| Many identical host session rows | Refresh after the current snapshot reconciles ledger hierarchy; root sessions and nested worker threads are counted separately | -| Worker thread appears at top level | Its declared parent is not currently retained, so it remains navigable as an orphan rather than hiding evidence | -| Ruflo or AQE absent | Their stores are not auto-discovered; register each JSONL file with `--live-source` | -| Project name not reported | No supported metadata supplied a working directory; raw paths are never sent to the browser | -| Node disappeared | Server projection or client visibility bounds evicted/collapsed it | -| Connection interrupted | `EventSource` retries; a cursor miss or buffer overflow resets from a snapshot | -| `503 too many live telemetry clients` | The configured concurrent-client bound has been reached | -| `401` on any `/api/*` request | Missing or wrong per-session token; reopen the dashboard's launch URL, or paste the token printed at startup | -| Graph moved while streaming | Existing positions should remain stable; **Reset layout** discards manual positions | -| Transcript unavailable | The selected session cannot be resolved safely beneath its host root | -| Detail truncated | The source value exceeded a content bound; the UI reports the original/shown size | -| Task details unavailable | The topology source did not provide role/task metadata; inspect the selected transcript for available evidence | - -Implementation and rationale are documented in -[ADR-0012](adr/0012-live-sessions-observability.md) and the -[domain design](ddd/live-sessions.md). - -## Design references - -The information architecture was independently informed by Albert and Agent -Flow. Agent Flow's stable agent anchors, owned tool cards, causal movement, and -synchronized transcript informed the execution grammar; agentic-kit's -project-first, cross-host identity and isolated content plane are its own -design. No project branding or assets are copied. See the clean-room decision -in [ADR-0012](adr/0012-live-sessions-observability.md) and the -[Agent Flow source](https://github.com/patoles/agent-flow). - -The interaction model follows the established “overview first, zoom and -filter, then details on demand” guidance for network visualizations -([Shneiderman][eyes-have-it]) and direct-manipulation viewport conventions -documented by [D3 zoom][d3-zoom]. Stable incremental placement is intentional: -dynamic-graph research finds that unnecessary movement makes visual search -harder, while also cautioning that stability must serve the user's task -([Di Giacomo et al.][stable-graphs], -[Archambault and Purchase][mental-map-review]). - -[d3-zoom]: https://d3js.org/d3-zoom -[eyes-have-it]: https://www.cs.umd.edu/~ben/papers/Shneiderman1996eyes.pdf -[mental-map-review]: https://doi.org/10.1016/j.ijhcs.2013.08.004 -[stable-graphs]: https://doi.org/10.1177/1473871620972339 diff --git a/docs/OBSERVABILITY.md b/docs/OBSERVABILITY.md new file mode 100644 index 0000000..f99db9b --- /dev/null +++ b/docs/OBSERVABILITY.md @@ -0,0 +1,443 @@ +# Observability + +The **Observability** tab in `ak dashboard` is an observation-only execution workspace for local +Claude Code, Codex, and OpenCode activity. It cannot steer or mutate an agent or repository. It +does retain a small owner-only file of sanitized workspace snapshots so the last recorded branch +and tracked change counts remain available in History. It pairs an interactive agent/tool +canvas with the selected session's evidence when that host supplies it. It is +not a chat client or control plane: it cannot send messages, cancel work, or +reroute agents. + +```bash +ak dashboard + +# Optional structured telemetry; repeat the option for multiple files. +ak dashboard \ + --live-source 'ruflo=.claude-flow/live-events.jsonl' \ + --live-source 'aqe=.agentic-qe/live-events.jsonl' +``` + +> [!NOTE] +> `--live-source` is opt-in, not required. Without it, supported Claude and Codex +> transcript evidence is auto-discovered and top-level Claude Code, Codex, and +> OpenCode process presence is observed. What you don't get: ruflo and agentic-qe activity, which are never +> auto-discovered and only appear once you register their event file +> explicitly (see [Evidence and limitations](#evidence-and-limitations)). + +Open `#observability/live` or `#observability/history`, for example +`http://127.0.0.1:7431/#observability/live` — once the dashboard's per-session token is already in +the browser (auto-opened, or pasted once at the token gate; ADR-0014), these deep links work on +their own. The Observability choices occupy the same fixed, left-aligned secondary rail used by +Overview and Usage; see the [Dashboard guide](DASHBOARD.md). Collection starts +lazily when the snapshot or event endpoint is first requested. Leaving the Observability +tab closes that browser's event stream. After the last snapshot/SSE client +leaves, collectors stop after 30 seconds by default and restart on the next +request. Stopping the dashboard closes the live service and all clients. + +## Read Observability in 30 seconds + +Observability has two navigation modes: + +- **Live** is the default. It shows only projects with a root session that has an unexpired process + presence lease or fresh meaningful activity. If none qualify, it shows **0 projects**. +- **History** shows only projects with at least one retained non-live root session. It never carries + a current selection, live status, or motion across from Live. + +Switching modes clears the incompatible project/session selection and chooses only inside the new +scope. Project, session, and worker counts always describe visible rows in that scope. + +The three columns answer three different questions: + +1. **Projects and sessions:** where is work happening, on which execution host, and is it working, + merely present, quiet, completed, or unsupported? +2. **Agent activity:** who owns the work, which operation is in flight, and how are actors related? +3. **Session Stream:** what transcript or review evidence supports the selected session and actor? + +Start by choosing a project, then a session. Select any actor or operation for persistent detail. +Hover or focus it for a short description. Open **Legend / Help** whenever a shape or animation is +unclear. Drag empty space to pan; drag an actor or operation card to pin it; double-click that item +to restore its automatic position. **Reset layout** restores every item in the selected session. + +### What each unit represents + +| Unit | Meaning | What to do with it | +|------|---------|--------------------| +| Project row | Sanitized repository grouping sessions across hosts | Select it to enter its session history | +| Root session row | One top-level Claude Code, Codex, or OpenCode controller/thread | Select it to load its map and supported evidence | +| Working tree at capture | Branch, meaningful non-root relative directory, and tracked `+ / −` lines versus `HEAD` at the recorded time; the project heading supplies repository identity | Select the session for capture time, source, confidence, and completeness; never read it as agent attribution | +| Indented child session | Real independently addressable thread, currently evidenced by the Codex ledger | Select it for its own map, transcript, and playback | +| Indented worker view | Claude sidechain actor contained by the parent session, not a fabricated session | Select it to focus that actor in the parent map/evidence | +| Large ring/hex anchor | Session coordinator, agent, subagent, or quality gate | Select for detail; drag to pin; double-click to unpin | +| Work bubble | Actor's most recent meaningful allowlisted activity | Read it as evidence, not a prompt or hidden reasoning trace | +| Operation card | Tool, skill, plugin, or MCP operation owned by an actor | Select for safe metadata; drag independently when crowded | +| Solid relationship | Static containment, delegation, or planned/observed topology | Hover/focus to inspect its evidence; no movement means no flow claim | +| Moving dashed relationship | Explicitly started, unfinished operation or relationship | Follow it to see where live activity is flowing now | +| Presence halo | The controller process was observed recently | Slow breathing means present; it does not mean working | +| Status dot/pulse | Meaningful current work or explicit outcome | Use its text label; color and motion are redundant cues | +| Earlier operations chip | Count of completed operation cards collapsed for readability | Review the transcript/timeline for earlier detail | +| Session Stream rail | Masked transcript or deterministic review for the selected session | Search, follow, filter by selected actor, enter Review, or collapse the rail to give Agent activity more room | + +The **Live / History** filter is a hard boundary. A mixed project can appear in both modes, but each +appearance counts and reveals only roots from that mode. A live-only project never appears as an +empty History project. Historical rows never fill an empty Live mode. + +### Motion has one meaning at a time + +- A **slow breathing halo** means the process is present. +- A **work pulse** means meaningful current activity is evidenced. +- A **moving dashed line** means that specific operation is started and unfinished. +- A **solid line** is structural. It never starts moving merely because one endpoint is working. +- **History and Review are inert.** They show no green current-work dot, pulse, breathing presence + halo, moving dash, flow particle, live-follow control, or unread-live alert. Completed history is + neutral; an explicit historical failure may remain static red. +- **Pause** stops stream application plus CSS/SVG movement. Reduced-motion mode removes + nonessential animation while preserving labels, status, and topology. + +## Reading the operations console + +- Claude and Codex sessions discovered newest-first from their local JSONL + stores and bootstrapped from bounded, metadata-only records. +- Project-first session cards with a host glyph and name, independently + evidenced inference provider/model when reported, lifecycle, freshness, and + a concise workspace summary. The Claude Code, Codex, and OpenCode glyph identifies the execution + host; adjacent text remains authoritative and the glyph never substitutes for inference-provider + evidence. Provider identity is graded (ADR-0021): + observed from Codex artifacts (`model_provider`), configured/inferred from + Claude Code's documented selection surface (Bedrock, Vertex, Foundry, + `ANTHROPIC_BASE_URL` gateways such as OpenRouter) as soon as a transcript or + runtime lease supplies the session working directory, and "Provider not + established" only when no evidence of any grade exists. +- A compact **working tree at capture** line: a non-root repository-relative directory when it + adds context, a branch icon and branch or detached-revision name, and tracked `+ / −` line + counts when Git reports them. The selected project heading already owns the repository name, + so session rows do not repeat it or display the unhelpful `repo root` label. + These counts describe the shared checkout versus `HEAD`, not changes authored by that session. + Untracked content and binary line counts are excluded. Missing Git evidence is omitted rather + than displayed as zero. +- Session, agent, sub-agent, tool, skill, plugin, MCP, and gate entities when a + supported source record identifies them. +- Authoritative Codex parent/child edges from the Codex state ledger. +- Human-readable identity and current work: resolved agent or capability name, + host, provider/model, lifecycle, elapsed time, latest safe operation summary, + and evidence confidence. Internal sentinel models such as `` are + replaced by freshness context; containment is described as **Worker linked**. +- Typed relationships whose accessible titles use verbs such as **spawned**, + **delegated**, **invoked**, **returned**, **evaluated**, and **gated**. +- A semantic execution canvas with stable agent anchors and bounded tool, + skill, plugin, MCP, and gate satellites attached to their owners. +- Animated edges only for explicitly started, unfinished flow; presence and endpoint work never + animate every structural relationship. +- A connected, collapsible selected-session transcript rail containing rich, server-masked + conversation, plaintext reasoning, tool inputs/results/errors, commands, + patches, MCP/web activity, and subagent messages as the sources report them. +- Shared selection: choosing an agent filters the rail; choosing a tool opens + its detail; transcript evidence remains tied to its actor. +- Keyboard-selectable nodes, reduced-motion support, actor-specific geometry, a consultable + **Legend / Help**, evidence-aware tooltips, and a **Pause live** control. +- Sanitized adapter health showing status and aggregate file/event/error counts. + +The overview answers which project and host are involved, which inference +provider is evidenced, whether the evidence is current, who is active, and +what operation is happening now. Selecting a +node highlights adjacent relationships, focuses its work, and synchronizes the +transcript. Search filters the current stream. Auto-follow yields when the +reader scrolls away and reports unread activity until following resumes. The +transcript is newest-first: fresh evidence appears at the top and older entries +flow downward. Follow anchors to the top; playback remains chronologically +ordered internally. + +Projects are the durable top-level grouping. Select a project first, then one +of its host-qualified root sessions. Live and History are intentionally separate; historical rows +never fill an empty Live mode. Selecting a root reveals its agent/worker +evidence as an indented hierarchy. A Codex ledger child is a real independently +addressable session with its own supported map, transcript, and playback. A +Claude sidechain is a selectable **worker view** of an actor contained by the +parent; it does not claim a child session or content endpoint that Claude +evidence has not supplied. Neither inflates the project's top-level session +count. The project row reports real nested sessions separately. +Claude and Codex sessions with the same native ID remain distinct. + +Linked Git worktrees roll up to their owning repository. A branch-named +worktree is session context, not a separate project, and its filesystem path is +never sent to the browser. Multiple sessions may point at the same checkout, so branch and diff +counts are context—not attribution. In Live, the line is the newest safe capture; in History it is +explicitly the last recorded snapshot. History never queries today's checkout and presents it as +past state. + +Hierarchy may arrive after a thread is first discovered. The dashboard +reconciles later Codex ledger parentage into the existing session instead of +showing a second peer row. If a declared parent is not currently retained, the +thread remains visible as an orphan root until the parent appears; malformed +cycles are bounded and cannot lock the browser. + +Bare `unknown` is not used as user-facing copy. Missing facts are described +honestly, such as **Model not reported**, **Waiting for lifecycle evidence**, +**Active; completion unobserved**, or **Task details unavailable**. + +Session state is phrased as evidence a person can act on: + +- **Working now** means fresh observed execution is still arriving and is the + only state that continuously pulses. +- **Process active · quiet** means a runtime lease proves the controller exists, while no + meaningful activity is currently evidenced; only its slow presence halo breathes. +- **Waiting for activity** means a previously running session is quiet inside + the quiescence window. +- **No recent activity** means that quiet period crossed the expiry threshold; + it does not assert failure or completion. +- **Blocked**, **Failed**, **Cancelled**, and **Completed** require explicit + source evidence. +- **Last active** identifies retained historical evidence when no trustworthy + completion outcome was recorded. + +Color and motion are redundant cues: **green anywhere in the session browser or execution map means +current Live work**. Retained and reviewed activity never uses green, even if an old event was +recorded as `running`. Review uses neutral or amber styling and past-tense labels. Explicit failure +remains static red. Reduced-motion mode keeps labels and topology while removing pulse and flow. + +Selecting a session explicitly opens only that session's content stream. The +previous stream closes; other transcripts are not prefetched. Large thinking, +arguments, results, and patches are collapsed for readability and carry +visible truncation metadata when bounded. + +### Collapse or restore Session Stream + +The chevron in the **Session Stream** header collapses or expands the evidence rail. Collapsing is +presentation-only: the selected transcript stream stays connected, new evidence continues to be +ingested, and **Agent activity** receives the released horizontal space. It is not the same as +**Pause stream**, which freezes visual application of Live deltas. + +The collapse choice is stored locally and restored on later dashboard visits. In the collapsed +state, a compact rail remains visible so the chevron is always available to restore the stream. On +narrower screens the rail stacks below the project browser and Agent activity; when collapsed it +becomes a compact full-width restore bar instead of retaining transcript height. + +The chevron is keyboard-operable with Enter or Space. Its accessible label, tooltip, and +`aria-expanded` state change with the action, and a polite status message announces whether Session +Stream was collapsed or expanded. + +### History and Review are different + +**History** is a navigation mode: it decides which retained sessions are available. **Review** is a +playback mode: it reconstructs one selected session at a playhead. A currently Live session may +enter Review without moving into History. Review never reactivates old presence, pulses old work, +or animates an old relationship as current traffic. Choose **Live** or **History** from the +secondary tab row directly beneath the main Observability tab; the Left/Right, Home, and End keys +also move between these two views when the tab row has focus. Their canonical hashes are +`#observability/live` and `#observability/history`. + +### Live and Review playback + +An active session opens in **Live** mode and follows new topology and transcript +evidence. **Review session** loads its bounded retained history and reconstructs +both panes at one playhead. The review bar supports play, pause, seek, and +0.5×, 1×, 1.5×, 2×, 5×, and 10× playback. **Resume live** returns an active session to its current +stream; completed sessions remain review-only. The bar is reserved beneath the +execution map in History or Review, so it cannot cover nodes or map guidance. +During ordinary Live viewing it is hidden unless the selected active session +supports a concise **Review session** action. + +Review is event reconstruction, not a screen recording. The server returns +chronological, masked evidence and the browser rebuilds the execution map and +transcript at each event boundary. The +`GET /api/live/playback/:host/:id?at=` endpoint supports +deterministic server-side seeking and reports its retained range, truncation, +and any history gap. + +### Navigating the graph + +- Drag empty canvas space to pan. Use a wheel or trackpad scroll gesture to zoom + around the pointer. +- Drag an actor or individual operation card to move and pin it without changing the observed + topology. Its labels and connected edges move with it. +- Use the visible **Zoom in**, **Zoom out**, **Fit all**, **Focus**, and + **Reset layout** controls when pointer gestures are unavailable or imprecise. +- Select a session to show its work map. Completed high-cardinality resource + calls are summarized automatically. +- Hover or focus an actor/operation for a short description. Click, Enter, or Space opens + persistent selection detail. **Legend / Help** explains every visual cue and gesture. +- Keyboard users can traverse selectable nodes, synchronize transcript detail, + and operate every viewport control. Reduced-motion mode removes decorative + transitions without removing status or topology. + +Live updates preserve the user's viewport, node positions, and selection during +the active page. A new off-screen event does not forcibly +recenter the canvas. **Fit** changes the camera to include the current map, +**Focus** centers the selected actor, and **Reset layout** discards manual node +positions and restores the deterministic automatic layout. +**Pause live** freezes visual application while collection continues. + +The graph displays at most 80 nodes for one selected session. Completed resource +nodes are collapsed first. The server retains at most 100 sessions and 1,000 +nodes per session by default, tails at most 256 files, and retains 2,000 events +for resume. These are implementation bounds, not claims about the number of +agents the underlying tools can run. Explicit `--live-source` files receive +tailer slots first; Claude and Codex automatic discovery divide the remaining +capacity. + +## Evidence and limitations + +| Source | Current coverage | Confidence | +|--------|------------------|------------| +| Claude transcript | Session lifecycle, assistant/tool activity, and explicit sidechain markers supported by the adapter | Observed or correlated according to the record | +| Codex rollout | Session, response/tool lifecycle, model, and explicit rollout metadata | Observed | +| Codex state ledger | Parent-to-child spawn edges | Observed | +| OpenCode runtime | Top-level controller presence plus inspectable workspace context; detailed activity, hierarchy, transcript, and playback are not reported yet | Observed presence/workspace | +| Ruflo / agentic-qe | Conservative adapter for an explicitly supplied structured JSONL source; reported court leader/seat hosts remain independent | Observed as reported by that source | + +Agentic-QE court membership and native session hierarchy are separate. When AQE evidence reports a +Claude-led court with a Codex seat, or the reverse, the map may show those cross-host actors within +the court without reparenting their native sessions. OpenCode is not shown as a court member because +Agentic-QE cannot currently configure OpenCode-routed court models. The dashboard will not infer a +seat from an OpenCode process heartbeat. + +The default dashboard automatically discovers Claude and Codex transcript files, observes +supported controller processes, and reads the Codex state ledger. It does **not** search arbitrary ruflo, +agentic-qe, plugin, or skill stores. Register a structured source +explicitly with repeatable `--live-source 'surface=path'`, where `surface` is +exactly `ruflo` or `aqe`. + +Relative paths resolve from the directory where `ak dashboard` starts; absolute +paths remain absolute. The parser rejects an unsupported/missing surface or an +empty path, but registration does not prove that the file exists, is a regular +file, is inside the current project, or is produced by the named subsystem. +Unreadable/malformed sources degrade their adapter rather than crashing the +dashboard. Only register a local file you trust the dashboard process to read. +The structured adapter still constructs allowlisted events, so arbitrary JSON +fields do not pass through to the browser. + +Historical persisted events from the pre-GA executor normalize to the read-only +`internal` surface so retained evidence is not mislabeled as native. No current +writer or `--live-source` parser accepts the retired label. + +The diagram is a bounded current-state projection, not a durable audit log. +Silence changes a session's projection lifecycle to quiescent after 30 seconds +and expired after five minutes; it does not claim successful completion. +Source timestamps are preserved, but the event cursor records ingestion order +and does not establish causality across clocks. + +Names and work summaries are evidence-graded. A source-provided role or tool +name is preferred. When the source exposes only a generic kind, the UI says +that the identity is generic or inferred; it does not invent a specialist +name. “Running” means that supported lifecycle evidence is open and fresh, not +that the dashboard has inspected an agent's private reasoning. + +## Privacy + +The topology plane is constructed from an allowlist and contains no transcript +bodies. The separately selected content plane intentionally carries rich local +evidence. It parses source records into a bounded DTO, masks every emitted +string server-side, and never emits Codex `encrypted_content`. Secret masking +is best effort, not a guarantee; only run the dashboard where its local +transcripts may be viewed. + +Transcript lookup validates both host and session ID, resolves the real file +beneath the configured host transcript root, rejects symlink escapes, and rechecks +containment after replacement. Content responses are `no-store`, same-origin, +bounded, and destroyed after their last subscriber. + +Workspace context is a separate metadata-only snapshot. The collector may persist an opaque +workspace key, sanitized repository and repository-relative directory labels, a bounded and +secret-masked branch label, numeric tracked additions/deletions/file counts, capture time, source, +and confidence. It never persists an absolute working directory, filename, patch, prompt, command, +tool input, or tool result. The owner-only snapshot file is +`observability-workspaces.json` under agentic-kit's local config directory and is advisory: a write +failure degrades retention without stopping Live observation. + +Run the dashboard only for users who may see local project and model names. +It binds to `127.0.0.1`, applies Host, Origin, and Fetch Metadata checks, and +does not support remote binding. Those checks stop a hostile *browser tab*; +they do nothing against another *local, non-browser process* that can reach +the port directly. That gap is closed by the per-session token (ADR-0014): +every `/api/*` route — including both SSE streams above — requires it, so a +process without the token gets a `401`, not transcript content. + +## Streaming and recovery + +The browser obtains a snapshot from `GET /api/live` and subscribes to +`GET /api/live/events`. The latter is a Server-Sent Events stream with: + +- named `init`, `delta`, and `reset` events; +- event IDs and `Last-Event-ID` replay; +- a 15-second heartbeat by default; +- subscription-before-snapshot initialization to close the startup race; +- a default maximum of 32 clients, clamped to 256; +- a per-client server queue of 256 frames, clamped to 4,096. + +If replay history is no longer retained, initialization carries `reset: true` +and a current snapshot. If a slow client's server queue overflows, queued +frames are discarded and replaced with a reset snapshot after the socket +drains. While the UI is paused it buffers 256 updates; overflow causes a +snapshot reload on resume. + +The browser's native `EventSource` reconnects automatically. A stale indicator +means no telemetry has arrived for 30 seconds; it does not necessarily mean the +connection failed. + +The selected transcript uses +`GET /api/live/transcripts/:host/:id/events`, a separate SSE stream with bounded +history/replay and a per-client queue. A stale or cross-session cursor produces +a bounded reset; a slow client receives an explicit gap instead of an +unbounded content snapshot. + +## Troubleshooting + +| Symptom | Explanation | +|---------|-------------| +| No sessions | No supported metadata was found within the bounded newest-first discovery set | +| Live shows 0 projects | No retained root currently satisfies the Live predicate; switch to History for past sessions | +| History shows 0 projects | No retained non-live root exists; current work, if any, remains in Live | +| Green or moving content appears in History | This violates the Observability contract; refresh, then report it as a presentation defect if it remains | +| A History project opens with no sessions | This violates scope-local project filtering; refresh, then report it as a defect | +| Many identical host session rows | Refresh after the current snapshot reconciles ledger hierarchy; root sessions and nested worker threads are counted separately | +| Worker thread appears at top level | Its declared parent is not currently retained, so it remains navigable as an orphan rather than hiding evidence | +| Ruflo or AQE absent | Their stores are not auto-discovered; register each JSONL file with `--live-source` | +| Project name not reported | No supported metadata supplied a working directory; raw paths are never sent to the browser | +| Node disappeared | Server projection or client visibility bounds evicted/collapsed it | +| Connection interrupted | `EventSource` retries; a cursor miss or buffer overflow resets from a snapshot | +| `503 too many live telemetry clients` | The configured concurrent-client bound has been reached | +| `401` on any `/api/*` request | Missing or wrong per-session token; reopen the dashboard's launch URL, or paste the token printed at startup | +| Graph moved while streaming | Existing positions should remain stable; **Reset layout** discards manual positions | +| A line is solid, not moving | It is structural or completed; moving dashes require specific in-flight evidence | +| Session says “Process active · quiet” | The controller exists, but no meaningful work event is currently evidenced | +| OpenCode has no tools or transcript | Only runtime presence is available; the shared shell explains unsupported detail | +| Branch or `+ / −` counts are missing | Git was unavailable, the checkout had no usable `HEAD`, or that fact was never captured; the UI does not invent zero | +| Historical workspace looks old | History shows the last bounded snapshot recorded for that session, not the checkout's current state | +| Claude worker has no separate transcript row | It is an actor lens within the parent session, unlike a ledger-backed Codex child | +| qe-court does not show OpenCode seats | Agentic-QE cannot currently configure OpenCode-routed court models; Observability does not fabricate them | +| Transcript unavailable | The selected session cannot be resolved safely beneath its host root | +| Detail truncated | The source value exceeded a content bound; the UI reports the original/shown size | +| Task details unavailable | The topology source did not provide role/task metadata; inspect the selected transcript for available evidence | + +Implementation and rationale are documented in +[ADR-0012](adr/0012-observability.md) and the +[domain design](ddd/observability.md). + +## Design references + +Host marks identify third-party execution hosts and remain subordinate to adjacent text. The +OpenAI/Codex row uses OpenAI's official monochrome Blossom geometry, and the OpenCode row uses the +official dark square “O” asset. Those marks remain property of their owners and are used only to +identify the directly related service. See the [OpenAI design guidelines](https://openai.com/brand/) +and [OpenCode brand assets](https://opencode.ai/brand). + +The information architecture was independently informed by Albert and Agent +Flow. Agent Flow's stable agent anchors, owned tool cards, causal movement, and +synchronized transcript informed the execution grammar; agentic-kit's +project-first, cross-host identity and isolated content plane are its own +design. No Albert or Agent Flow branding or assets are copied. See the clean-room decision +in [ADR-0012](adr/0012-observability.md) and the +[Agent Flow source](https://github.com/patoles/agent-flow). + +The interaction model follows the established “overview first, zoom and +filter, then details on demand” guidance for network visualizations +([Shneiderman][eyes-have-it]) and direct-manipulation viewport conventions +documented by [D3 zoom][d3-zoom]. Stable incremental placement is intentional: +dynamic-graph research finds that unnecessary movement makes visual search +harder, while also cautioning that stability must serve the user's task +([Di Giacomo et al.][stable-graphs], +[Archambault and Purchase][mental-map-review]). + +[d3-zoom]: https://d3js.org/d3-zoom +[eyes-have-it]: https://www.cs.umd.edu/~ben/papers/Shneiderman1996eyes.pdf +[mental-map-review]: https://doi.org/10.1016/j.ijhcs.2013.08.004 +[stable-graphs]: https://doi.org/10.1177/1473871620972339 diff --git a/docs/TRANSCRIPTS.md b/docs/TRANSCRIPTS.md index 9e13fb1..0ae87c6 100644 --- a/docs/TRANSCRIPTS.md +++ b/docs/TRANSCRIPTS.md @@ -181,7 +181,7 @@ transcript content leaves the module, and every step is a gate: ### 4.1 Locate, contain, bound 1. **Id grammar before any filesystem access** — `VALID_ID` - (`/^[A-Za-z0-9._-]{1,128}$/`, `usage-index.mjs:71`) rejects traversal + (`/^[A-Za-z0-9._-]{1,128}$/`, `usage-index.mjs:83`) rejects traversal shapes with `ERR_INVALID_SESSION_ID` (`usage-index.mjs:1260`). 2. **Locate by id** across both roots (`locate`, `usage-index.mjs:1267`), consulting the scan cache when present but never requiring it — @@ -207,10 +207,10 @@ hardcoded `$0.00`; the comment at the site records why). ### 4.3 Mask, then truncate — both marked, differently -Every turn body is passed through `maskSecrets` (`usage-index.mjs:184` — the +Every turn body is passed through `maskSecrets` (`usage-index.mjs:196` — the 23 secret shapes) **server-side, before serialization**, then length-capped at `MAX_TURN_CHARS` (40,000, -`usage-index.mjs:65`) with the marker appended +`usage-index.mjs:77`) with the marker appended (`usage-index.mjs:1404-1414`). Two invariants: - **Presence is the signal.** `truncated`/`originalChars` are emitted only @@ -356,10 +356,10 @@ was wrong before, for the curious. context injections — ~93% of its "you" turns (§3.1's measured split). The turn-`kind` machinery in §3 is the fix. - **Prompt counts included harness output (SCHEMA_VERSION 5).** - `isHumanPrompt` once counted harness-output envelopes as human prompts — + `isHumanPrompt` once counted `harness-output` envelopes as human prompts — 32 claimed vs 20 real on the reference session. Cached session records carried the inflated counts, hence the wholesale `SCHEMA_VERSION` 5 cache - invalidation (`usage-index.mjs:47-51`). + invalidation (`usage-index.mjs:48-51`). - **Session expander fields shipped but unrendered.** The per-session fields §6.1's expander now renders (classification `basis` + confidence, the token split, flags) once travelled on the wire and rendered nowhere. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 2104ac7..012e1b9 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -36,7 +36,7 @@ ak sync # apply it | `status` shows a `memory-pin` warning | `CLAUDE_FLOW_DB_PATH` is pinned to a dead or foreign path, so every memory op targets the wrong DB ("Database not initialized" beside a healthy in-repo DB). The pin may be deliberate, so `sync` never touches it | repoint (or remove) the pin in `.claude/settings.local.json` `env` | | Want to run `ak sync` but Claude/Codex/OpenCode sessions are open in other terminals | Upgrade-bearing syncs stop **all** ruflo daemons machine-wide and swap the global npm trees live sessions execute hooks/statusline/MCP calls from; converged syncs touch nothing | `ak sync --dry-run` first; a `versions` row means idle the other sessions or use `ak sync --no-upgrade`; see [Running `ak sync` while sessions are live](UPGRADING.md#running-ak-sync-while-sessions-are-live) | | Suspicious token burn | Background automation vs interactive usage | ask Claude to run the **ruflo-token-audit** skill (deployed by `setup`) | -| Live dashboard is empty or has no ruflo/AQE nodes | Live tails new Claude/Codex records by default; existing files start at EOF, while ruflo/AQE stores are not auto-discovered | open Live before producing activity; register a trusted JSONL file with repeatable `--live-source 'surface=path'`; see [Live Sessions](LIVE-SESSIONS.md) | +| Observability is empty or has no ruflo/AQE nodes | Live mode tails Claude/Codex records by default, while ruflo/AQE stores are not auto-discovered | open Observability before producing activity; switch to History for retained sessions; register a trusted JSONL file with repeatable `--live-source 'surface=path'`; see [Observability](OBSERVABILITY.md) | | `status` shows `ruvnet-brain … not installed` | The RuvNet Brain (offline KB + `search_ruvnet` MCP) isn't on disk | `ak sync` (or `ak setup`) runs the installer; `npx ruvnet-brain --doctor` health-checks it | | Don't want the RuvNet Brain (the ~2 GB KB download) | It's on by default | `ak setup --no-ruvnet-brain`, or set `ruvnetBrain: false` in `~/.config/agentic-kit/kit.json` | | Don't want the security surface managed | Also on by default | `ak setup --no-security` (persists `security:false`; status shows an info row and sync stops healing it) | diff --git a/docs/USAGE-SCORECARD-METRICS.md b/docs/USAGE-SCORECARD-METRICS.md index 29f22e3..873fd32 100644 --- a/docs/USAGE-SCORECARD-METRICS.md +++ b/docs/USAGE-SCORECARD-METRICS.md @@ -57,7 +57,7 @@ Every metric section below follows the same shape: Two transcript stores, read-only, parsed at most once per file (cache keyed by `(path, mtime, size)`; `SCHEMA_VERSION` invalidates the whole cache on a -schema change — `src/lib/usage-index.mjs:51`): +schema change — `src/lib/usage-index.mjs:65`): | Transcript host | Store | Format | |---|---|---| @@ -105,12 +105,12 @@ responses = Σ over included sessions of session.responses **Source:** - Filter: a parsed record with zero assistant turns is dropped entirely — "no - assistant turn → not a session" (`usage-index.mjs:892`) — and a record whose + assistant turn → not a session" (`usage-index.mjs:904`) — and a record whose last activity falls outside the requested window is dropped too - (`usage-index.mjs:893`). + (`usage-index.mjs:905`). - `responses` accumulation: Claude increments per assistant message (`usage-index.mjs:493`); Codex increments per `agent_message` event - (`usage-index.mjs:631`). + (`usage-index.mjs:649`). - Totals: `totals.responses += s.responses` per included session (`usage-index.mjs:884`). - Render: `kpi("sessions", fmtNum(t.sessions), fmtNum(t.responses)+" assistant @@ -439,7 +439,7 @@ byDay[day].cost = Σ costOf(row) for every usage row whose day == that key **Source:** the day key is the row's own `row.day`, computed once at parse time as **local calendar day**, not UTC -(`usage-index.mjs:520`/`usage-index.mjs:649` call `localDay(at)`) — so a +(`usage-index.mjs:530`/`usage-index.mjs:667` call `localDay(at)`) — so a session that runs from 23:58 local to 00:05 local is billed to the day its *first* row landed on (test: `tests/kit/usage-index.test.mjs:634`, "a session that opens before midnight @@ -465,8 +465,8 @@ renders "no sessions in window" instead of zeroed figures (`dashboard/client.mjs`). **Formula:** identical aggregation to every other bucket -(`byProvider[s.provider]`, populated via `addTo()`, `usage-index.mjs:843-852`, - called once per session at `usage-index.mjs:966`), keyed by the literal string +(`byProvider[s.provider]`, populated via `addTo()`, `usage-index.mjs:870-879`, + called once per session at `usage-index.mjs:1000`), keyed by the literal string `"claude"` or `"codex"` assigned at parse time (`blankSession(id, 'claude')` / `blankSession(id, 'codex')`, `usage-index.mjs:291-301`, `parseClaude`/`parseCodex` entry points). @@ -506,7 +506,7 @@ punchcard[dow + "-" + hour] += 1 per assistant/agent_message response, at its **Source:** incremented once per Claude assistant turn (`usage-index.mjs:493-496`, keyed by `punchKey(at)`) and once per Codex -`agent_message` (`usage-index.mjs:630-634`), merged into the window-level +`agent_message` (`usage-index.mjs:649-652`), merged into the window-level `punchcard` object per session (`usage-index.mjs:981`). Cell intensity is linear against the single busiest cell in the window: `v = pcMax ? n/pcMax : 0` (`dashboard/client.mjs`) — this is a @@ -894,7 +894,7 @@ rollout replays the parent's entire token history (ccusage/ccusage#950 measured up to 91× inflation) — while the session record stays visible. The rollout's own `session_meta.thread_source` sniff remains as the fallback when the ledger is absent or migrated beyond recognition. Codex sessions also carry -`reasoningOutput` (`usage-index.mjs:656-659`) — reasoning tokens are a **subset** +`reasoningOutput` (`usage-index.mjs:677-678`) — reasoning tokens are a **subset** of output tokens and are annotation only, never added to any sum. ## 14. Known limitations, restated as a single checklist @@ -939,7 +939,7 @@ commit `540be18` on this branch. `parseCodex`'s single `addUsage()` call never included a `responses` field — Claude's parser passes `responses: 1` per assistant turn -(`usage-index.mjs:489`, as it existed before this fix), but Codex's call +(`usage-index.mjs:503`, the current equivalent), but Codex's call passed no such field at all. Because `byModel[model].responses` is summed directly from each usage row's `responses` field (`usage-index.mjs:921`, `m.responses += row.responses`), **every** Codex model in §10's "Models in diff --git a/docs/adr/0005-dashboard-in-page-routing-reveal.md b/docs/adr/0005-dashboard-in-page-routing-reveal.md index c832867..e817910 100644 --- a/docs/adr/0005-dashboard-in-page-routing-reveal.md +++ b/docs/adr/0005-dashboard-in-page-routing-reveal.md @@ -1,14 +1,15 @@ # ADR-0005 — Dashboard surfaces routing via in-page reveal -- **Status:** Amended by [ADR-0020](0020-ga-stable-surfaces.md) +- **Status:** Implemented - **Date:** 2026-07-23 -- **Updated:** 2026-07-30 -- **Update note:** Kept the read-only routing reveal and changed it to display only canonical GA - configuration. +- **Updated:** 2026-08-04 +- **Update note:** Kept the read-only routing reveal and canonical GA configuration while + consolidating the dashboard into three primary areas with one shared secondary navigation rail. - **Deciders:** agentic-kit maintainers > **GA amendment:** the read-only dashboard decision remains. References below to compatibility -> configuration names and projections describe the implementation at adoption time only. +> configuration names and projections describe the implementation at adoption time only; +> [ADR-0020](0020-ga-stable-surfaces.md) defines the canonical GA surface. ## Context @@ -17,10 +18,20 @@ At the time of this decision, `ak dashboard` was a single-page, poll-every-5s, grid grouped by subsystem, a `#history` strip — all fed by shelling `ak status --json` (`src/lib/dashboard-server.mjs`). It is health/status oriented. -> **Current implementation note (2026-07-27):** the read-only and loopback boundaries remain, but -> the page now has seven tabs, user-configurable status polling, lazy Usage reads, and an SSE-driven -> Live view. Page, styles, browser client, Live view, and request/session security now live under -> `src/lib/dashboard/`; `dashboard-server.mjs` is the HTTP composition root. +> **Current implementation note (2026-08-04):** the read-only and loopback boundaries remain. The +> page now has three primary areas—Overview, Usage, and Observability—and one fixed, left-aligned +> secondary rail. It also has user-configurable status polling, lazy Usage reads, and an SSE-driven +> Observability view. Page, styles, browser client, Observability, and request/session security live +> under `src/lib/dashboard/`; `dashboard-server.mjs` is the HTTP composition root. + +**2026-08-04 information-architecture amendment:** Overview absorbs the former health-oriented +peer tabs as **Summary**, **Hosts & Routing**, **Providers**, **Runtime**, and **Intelligence**. +Usage owns **Scorecard**, **Limits**, **Findings**, **Sessions**, and **Transcript**. Observability +owns **Live** and **History**. The secondary row remains in one stable location across all three +areas. Canonical hashes are `#overview/{view}`, `#usage/{view-or-session-id}`, and +`#observability/{live,history}`. Every destination has a visible heading and description. Primary +and secondary tab lists use roving focus: Left/Right activates the adjacent tab with wrapping, and +Home/End activates the first/last tab. The routing policy warrants a richer view than a flat status card (a vendor-coded activity→host/model matrix with provenance badges and escalation ladders). The question is how to add it without breaking the existing @@ -44,6 +55,10 @@ Surface routing via **in-page reveal**, not a new page or tab: - Preserves the single-page, health-first idiom; routing is an enhancement reached by an intuitive in-page link, not a replacement. +- Presents only three stable primary choices while keeping Overview's status domains one keyboard + action away in the shared secondary rail. +- Provides durable, hierarchical deep-link vocabulary without adding routes, servers, or a second + navigation component per area. - Minimal server change — the dashboard already shells `ak status --json`, so the health card flows for free; only the rich matrix needs a new data key + render function. - Read-only is preserved (the matrix visualizes; tuning stays in `ak x provider pick` / `kit.json`). @@ -52,4 +67,7 @@ Surface routing via **in-page reveal**, not a new page or tab: ## References - `src/lib/dashboard-server.mjs` (`renderPage`, `#cards`, `#history`/`renderHistory`, `PREF`, `shellOutStatus`) +- `src/lib/dashboard/page.mjs` and `src/lib/dashboard/client.mjs` (three-area shell, shared secondary + rail, canonical hashes, headings, and keyboard semantics) +- [Dashboard user guide](../DASHBOARD.md) - Mockup: ak dashboard — Routing panel; ADR-0001, ADR-0003 diff --git a/docs/adr/0009-usage-scorecard-local-transcript-analytics.md b/docs/adr/0009-usage-scorecard-local-transcript-analytics.md index 192a25d..05e050d 100644 --- a/docs/adr/0009-usage-scorecard-local-transcript-analytics.md +++ b/docs/adr/0009-usage-scorecard-local-transcript-analytics.md @@ -1,9 +1,11 @@ # ADR-0009 — Usage scorecard: local transcript analytics with graded evidence -- **Status:** Accepted +- **Status:** Implemented - **Date:** 2026-07-25 -- **Updated:** 2026-07-30 -- **Update note:** Added the explicit OpenRouter account-analytics cache boundary for issue #59. +- **Updated:** 2026-08-04 +- **Update note:** Added the explicit OpenRouter account-analytics cache boundary for issue #59, + aligned Usage with the dashboard's shared three-area navigation, and documented independent + host, inference-provider, provenance, and model facts in session rows. - **Deciders:** agentic-kit maintainers ## Context @@ -34,19 +36,34 @@ Three properties of the data force most of the design: ## Decision -### 1. A tab on `dashboard`, not a third command — the split is egress, and this has none +### 1. A dashboard area, not a third command — the split is egress, and this has none ADR-0007 split `admin` from `dashboard` along **network egress**: `dashboard` promises silence, `admin` promises reach. Usage analytics reads **local files only** and makes **zero network calls**. -It therefore sits squarely inside the dashboard's existing offline-first contract and ships as a -sixth tab (`#usage`), not a new server. - -> **Current implementation note (2026-07-27):** Usage remains the sixth tab; Live Sessions is now a -> seventh tab. Usage still loads lazily and remains separate from the live transcript tailers. - -The tab carries four in-page views — **Scorecard → Findings → Sessions → Transcript** — deep-linked -as `#usage`, `#usage/findings`, `#usage/sessions`, `#usage/`, reusing the in-page reveal -idiom ADR-0005 established rather than adding navigation concepts. +It therefore sits squarely inside the dashboard's existing offline-first contract and ships as one +of the dashboard's three primary areas, not a new server. + +> **Current implementation note (2026-08-04):** The dashboard exposes exactly three primary areas: +> Overview, Usage, and Observability. One fixed, left-aligned secondary rail provides the current +> area's destinations. Usage still loads lazily and remains separate from the live transcript +> tailers. + +Usage carries five in-page views — **Scorecard**, **Limits**, **Findings**, **Sessions**, and +**Transcript** — deep-linked as `#usage/score`, `#usage/limits`, `#usage/findings`, +`#usage/sessions`, and `#usage/transcript`. A selected retained session uses +`#usage/` and opens Transcript detail. Each destination publishes a visible heading and +plain-language description. The primary and secondary tab sets use a roving selected state: +Left/Right Arrow activates the adjacent destination with wrapping, while Home and End activate the +first and last destination. This reuses ADR-0005's in-page reveal idiom without adding navigation +concepts. + +**2026-08-04 session-identity amendment:** the compact session badge identifies the execution host +only. Its adjacent disclosure control expands evidence without navigating away from the session +list. The details report execution host, inference provider, provider evidence/provenance, and +model as independent facts. Codex `model_provider` recorded in `session_meta` or `turn_context` is +observed provider evidence. Claude's native transcript history may contain no corresponding provider +fact; in that case the interface says `Not recorded`. Host names and model identifiers are never +used to infer the provider. `ruflo-token-audit` **stays** as an independent standalone reporter. It is the offline second opinion and the fallback when the kit is not installed; the panel does not replace it and does not shell it. diff --git a/docs/adr/0012-live-sessions-observability.md b/docs/adr/0012-observability.md similarity index 68% rename from docs/adr/0012-live-sessions-observability.md rename to docs/adr/0012-observability.md index 9b464b3..94b90d8 100644 --- a/docs/adr/0012-live-sessions-observability.md +++ b/docs/adr/0012-observability.md @@ -1,12 +1,32 @@ -# ADR-0012 — Live sessions as local, evidence-graded observability +# ADR-0012 — Evidence-graded session observability -- **Status:** Accepted; compatibility-source references amended by - [ADR-0020](0020-ga-stable-surfaces.md) +- **Status:** Implemented - **Date:** 2026-07-27 -- **Updated:** 2026-08-03 -- **Update note:** Retained local evidence-graded observability, removed the compatibility source, - mapped its historical records to internal provenance, and added canonical repository identity - plus runtime leases for concurrent Claude Code, Codex, and OpenCode controllers. +- **Updated:** 2026-08-04 +- **GA surface:** Canonical naming and retired vocabulary follow + [ADR-0020](0020-ga-stable-surfaces.md). +- **Update note:** Renamed the dashboard surface from Live to Observability and made Live and + History mutually exclusive navigation scopes. Historical and reviewed evidence is visually + inert: no green current-work cues, breathing presence halo, or moving flow. Reconciled the + interaction contract so presence is distinct from meaningful + work and in-flight flow; layout is collision-aware and directly manipulable; help is available + in context; Claude sidechains use actor lenses; and provider/API differences are disclosed through + capability coverage rather than hidden behind inconsistent host-specific UI. Claude runtime-only + sessions now resolve configured/inferred provider identity without treating the host as provider + evidence or downgrading stronger transcript evidence. Internal model placeholders are suppressed + from activity callouts in favor of useful freshness context. Codex ledger history retains its + source timestamps, runtime presence binds only within the current process generation, and the + navigator separates Live-only from History-only results. Compatibility-source references remain + amended by [ADR-0020](0020-ga-stable-surfaces.md). Session rows and Selection details now defer + repository identity to the selected project heading, omit `repo root`, and use a branch glyph + with the branch name; meaningful non-root directories remain available when recorded. Playback + is contextual and docked inside the execution-map card so it cannot obscure map evidence. + Live and History use the dashboard's shared secondary segmented-tab treatment beneath the + Observability tab, including arrow-key navigation and an explicit selected state. + Session Stream may be collapsed without disconnecting its live subscription; the choice is + stored locally and a compact, keyboard-accessible restore rail remains visible across responsive + layouts. + Historical and active-session Review playback exposes 0.5× through 10× speeds. - **Deciders:** agentic-kit maintainers - **Related:** [ADR-0005](0005-dashboard-in-page-routing-reveal.md), [ADR-0007](0007-maintainer-admin-local-telemetry.md), @@ -25,6 +45,40 @@ Unresolved internal evidence is retained for later reconciliation but is never p `unknown` workspace. Public project keys hash the canonical repository root when proven and never expose the path. +**2026-08-03 interaction and capability amendment:** a runtime heartbeat proves that a process is +present; it does not prove that an actor is working. Animated flow requires an explicitly started, +unfinished operation or relationship signal. Claude sidechain actors are nested under their real +parent as selectable actor lenses because current Claude evidence does not establish independently +addressable child sessions. Codex ledger threads remain real child sessions. OpenCode currently +contributes controller presence only. Agentic-QE court membership is a separate orchestration axis: +when structured AQE evidence reports it, a Claude-hosted court may contain Codex members and vice +versa. OpenCode is not presented as a court member until Agentic-QE reports that routing capability. + +**2026-08-03 history-boundary amendment:** collector observation time must not become displayed +session freshness. Codex ledger rows retain their source thread timestamps. A runtime process may +attach presence to a retained session only when that session was updated during the current process +generation; otherwise it remains a separate presence-only session until stronger identity evidence +arrives. The default navigator is Live-only. History is an explicit mode and never appears in Live +project or session counts. + +**2026-08-03 Observability naming and scope amendment:** **Observability** is a top-level dashboard +area with two mutually exclusive navigation scopes: canonical `#observability/live` and +`#observability/history`. **Live** contains roots with current process presence or fresh meaningful +work; **History** contains the retained complement. A project appears only when it has a root in +the selected scope, and all visible counts are scope-local. History is distinct from **Review**: +History chooses retained sessions, while Review reconstructs one selected session at a playhead. +Neither History nor Review may present green current-work status, presence breathing, or moving +flow. Explicit historical failures may remain static red; all other retained state is neutral. + +**2026-08-04 workspace-context amendment:** every supported top-level Claude Code, Codex, or +OpenCode runtime session may carry the same `SessionWorkspace` value object. It contains only an +opaque workspace key, sanitized repository and repository-relative directory labels, a bounded +secret-masked branch or detached revision, numeric tracked working-tree counts versus `HEAD`, +capture time, source, and confidence. It is checkout context, never per-agent authorship. The last +safe snapshot per host-qualified session is persisted owner-only for History; History must not +inspect the current checkout and mislabel that state as historical. Absolute paths, filenames, +patches, untracked contents, and raw Git output never cross the event boundary or enter the store. + ## Context Before this decision, `ak dashboard` reported configuration health and historical usage but could @@ -33,7 +87,8 @@ delegated agents can all participate in one development task. Their evidence is distributed across append-only transcripts, Codex's state ledger, local daemon registries, workflow metadata, and quality hooks. -The existing implementation is deliberately local and read-only. `dashboard-server.mjs` binds to +The existing implementation is deliberately local and observation-only: it cannot steer agents or +mutate repositories. `dashboard-server.mjs` binds to loopback and protects requests with Host, Origin, and Fetch Metadata checks. `usage-index.mjs` incrementally indexes completed and changing transcripts for historical analytics. `codex-state.mjs` exposes authoritative `thread_spawn_edges`; `daemons.mjs` discovers local ruflo @@ -66,11 +121,11 @@ requires a way to pause moving information and a non-visual equivalent ## Decision -### 1. Add a read-only Live view; do not add chat or a control plane +### 1. Add read-only Observability; do not add chat or a control plane -The dashboard has a top-level Live view that depicts supported evidence: +The dashboard has a top-level Observability view that depicts supported evidence: -- sessions and their Claude or Codex host; +- sessions and their Claude Code, Codex, or OpenCode host, with source capabilities disclosed; - parent, delegated, and sub-agent relationships; - ruflo orchestration and workflow steps; - agentic-qe workers, evaluations, gates, and verdicts; @@ -87,6 +142,7 @@ Every source adapter emits an allowlisted envelope containing: - schema version, event ID, source timestamp, observation timestamp, and ingest sequence; - session, parent-session, trace, span, and parent-span identifiers when known; - host, surface, project, actor kind, role, provider, and model; +- optional schema-v2 `SessionWorkspace` capture with bounded labels, tracked counters, time, and evidence; - action, target, lifecycle status, and safe aggregate attributes; - source adapter and confidence: `observed`, `correlated`, `inferred`, or `planned`. @@ -128,9 +184,9 @@ fresh snapshot. Filesystem watches are hints, reinforced with stat polling and r Tailers retain byte offsets, accept only newline-terminated records, tolerate partial writes, and reset safely after rotation or truncation. -### 5. Present one coordinated execution workspace, not disconnected modes +### 5. Present one coordinated Observability workspace with distinct scopes -The Live view answers five questions in order: which project and provider is this, is the session +Observability answers five questions in order: which project and provider is this, is the session healthy and current, which agent is doing what now, how did execution reach this point, and what evidence supports that claim? @@ -139,11 +195,12 @@ text, model when reported, lifecycle, freshness, and a one-line operational summ the visualization. Host/provider, agent role, and evidence confidence are separate concepts. Opaque IDs, raw adapter vocabulary, and technical source details remain subordinate. -Projects are the primary navigation aggregate. The first level lists sanitized repository identity, -provider mix, live/recent session counts, failures, and latest activity. Selecting a project reveals -its Claude and Codex sessions, ordered live first and then by recency. A session row uses provider -mark plus text, role/task when evidenced, model, freshness, duration, and lifecycle; UUIDs remain -technical detail. +Projects are the primary navigation aggregate. **Live** lists only projects containing a current +root; **History** lists only projects containing a retained non-live root. Counts include only the +selected scope. Sessions never mix across the boundary: Live orders current evidence by freshness, +while History orders retained evidence by its last meaningful source time. A session row uses +provider mark plus text, role/task when evidenced, model, freshness, duration, and lifecycle; UUIDs +remain technical detail. The session browser is hierarchical. A root session or orchestrated run is one navigation unit; provider worker threads do not become peer rows merely because they have independently addressable @@ -158,10 +215,15 @@ projection. Session DTOs carry host-qualified parent and root keys, a hierarchy remains navigable until its parent arrives. Corrupt cycles terminate deterministically rather than hanging projection or navigation. Raw native IDs are never joined across hosts. -The selected session workspace is a persistent split: approximately two thirds execution canvas -and one third evidence rail. A live session defaults to **Follow Live**. Every retained session can -enter **Review**, which uses the same canvas and transcript with play/pause, seeking, event markers, -speed control, and an explicit **Resume Live** when the source remains active. +The selected session workspace defaults to a split of approximately two thirds execution canvas +and one third **Session Stream**. The stream body may be collapsed without disconnecting its live +subscription. The canvas expands, a compact restore rail remains, and the user's choice is stored +locally. At responsive breakpoints the stream stacks below the canvas; its collapsed restore rail +remains compact rather than consuming the expanded panel's height. Navigation scope and playback +state are orthogonal. A Live-scope session defaults to **Follow Live** and may enter **Review** +without becoming historical. A History-scope session opens retained evidence in Review. Review +uses the same canvas and transcript with play/pause, seeking, event markers, and speed control. +**Resume Live** exists only when the selected root still satisfies the Live predicate. This is an immersive operations workspace rather than a dashboard panel containing another node editor. The usable viewport is edge-to-edge below the global navigation; project/session @@ -170,10 +232,12 @@ timeline is docked inside the canvas. At 1440×900 the canvas, evidence rail, an visible without document scrolling. The canvas renders agents as stable anchors and current or recent tools as smaller owned -satellites. Parent-child edges express delegation; animated particles distinguish dispatch, -tool invocation, return, and message flow. Animation carries meaning and stops under reduced -motion or Pause. Selecting an agent focuses its subtree and filters the rail; selecting a tool -shows its bounded safe summary. Selecting evidence in the rail highlights and centers its owner. +satellites. Parent-child edges express containment or delegation and remain static unless the edge +itself carries explicit in-flight evidence. A slow breathing halo means process presence; a work +pulse means meaningful current activity; moving dashes mean one started, unfinished operation. +Animation carries meaning and stops under reduced motion or Pause. Selecting an agent focuses its +subtree and filters the rail; selecting a tool shows its bounded safe summary. Selecting evidence +in the rail highlights and centers its owner. Agent anchors are compact iconic hex/ring glyphs with role, name, model, and state—not equal-rank rectangular workflow cards. Current work appears beside the actor as an evidence-derived bubble; @@ -181,10 +245,13 @@ tool calls appear as transient tethered chips with verb, target, status, and ela operations recede into the timeline instead of permanently crowding the world. A subtle depth field supports spatial orientation; a prominent spreadsheet grid and nested panel borders do not. -The rail is always present and defaults to the selected session's live transcript. It distinguishes -human, Claude/Codex, thinking/summary, tool call, tool result, status, and error. It supports search, -role/type filters, auto-follow, an unread indicator when the user scrolls away, and collapsed -thinking and tool detail. It is evidence, not chat. +The Session Stream subscription remains present and defaults to the selected session's live +transcript. Its body is collapsible through a chevron button whose `aria-expanded`, accessible +label, and title reflect the current state. Collapse does not pause ingestion or close the SSE +connection; **Pause** remains the separate control for motion and following. The stream +distinguishes human, Claude/Codex, thinking/summary, tool call, tool result, status, and error. It +supports search, role/type filters, auto-follow, an unread indicator when the user scrolls away, +and collapsed thinking and tool detail. It is evidence, not chat. Primary labels use a source-provided display name or role; secondary labels show entity kind and host/model; the current-work line shows an allowlisted operation summary, lifecycle, and elapsed @@ -196,14 +263,18 @@ copy. Missing evidence is described precisely, for example `Model not reported`, User-facing state describes work rather than adapter fields. Fresh observed execution is `Working now`; quiescent work is `Waiting for activity`; expired open work is `No recent activity`; retained evidence without a completion signal is `Last active`. -`Blocked`, `Failed`, `Cancelled`, and `Completed` require explicit evidence. Continuous pulse and -flow animation are reserved for `Working now`; waiting/stale/queued use static amber, and -blocked/failed use static red. Text and geometry remain authoritative when color or motion is -unavailable. - -Users can drag the empty canvas to pan, zoom around the pointer by wheel/trackpad, and drag -nodes to temporary pinned positions. Visible controls provide zoom in, zoom out, fit, reset, and -separate camera/layout resets. View transforms follow the conventional scale-and-translation +`Blocked`, `Failed`, `Cancelled`, and `Completed` require explicit evidence. Continuous work pulse +is reserved for `Working now`; slow breathing is reserved for observed presence; moving flow is +reserved for an explicitly started, unfinished operation or relationship. Waiting/stale/queued use +static amber, and blocked/failed use static red. Green anywhere in the session browser or execution +map means current Live work. History and Review use neutral or amber playback styling even when an +old record said `running`; completed history is muted, and explicit historical failure may remain +static red. Text and geometry remain authoritative when color or motion is unavailable. + +Users can drag the empty canvas to pan, zoom around the pointer by wheel/trackpad, and drag actors +or individual operation cards to temporary pinned positions. Visible controls provide zoom in, +zoom out, fit, reset, and separate camera/layout resets. View transforms follow the conventional +scale-and-translation model documented by [D3 zoom][d3-zoom]. Live updates preserve viewport, selection, expansion, and user pins; status-only updates cannot trigger layout. Topology changes use a seeded layered layout with constrained collision relaxation, not continuous force motion. Fit/recenter is explicit, not @@ -213,6 +284,42 @@ Relationship titles use human verbs—spawned, delegated, invoked, returned, eva gated—rather than raw transport event names. The view batches visual changes on animation frames, bounds retained tools/messages, and preserves graph position while evidence streams. +Automatic layout treats an actor, its work bubble, history summary, and owned operation cards as +one measured bundle. Tool lanes begin outside the actor bundle and reserve vertical footprint +before the next actor is placed. This prevents overlap caused by positioning labels and tools +independently. Manual pins remain session- and host-qualified so identically named Claude and Codex +entities cannot mutate each other's layout. + +Every selectable component provides a concise hover/focus description and opens persistent detail +on click or keyboard activation. A consultable **Legend / Help** explains shapes, status, motion, +confidence, selection, pan/zoom, drag/pin, pause, and reset. The guidance line reports what the +current selection means and the next available action; it is also announced through a polite live +region. SVG is an interactive group, not one monolithic image. + +### 5.1 Separate observation axes and disclose capability coverage + +The additive event `signal` value object contains `kind`, `phase`, and an optional correlation ID. +Its kinds are `presence`, `activity`, `operation`, `relationship`, and `metadata`. A session +projection independently carries `presence` and `activity`; a heartbeat updates only presence and +must not overwrite the actor's last meaningful action, activity timestamp, or open operation. + +Each session DTO also publishes a capability-coverage map for presence, activity, actors, +resources, hierarchy, transcript, playback, and provider identity. The dashboard uses one visual +grammar for every host, then disables or explains unavailable evidence instead of fabricating +parity. Current native coverage is: + +| Source | Hierarchy representation | Detailed Live evidence | +|--------|--------------------------|------------------------| +| Codex | Ledger-backed child sessions, selectable independently | Rollout activity, tools, transcript, playback | +| Claude Code | Sidechain actors nested as selectable actor lenses under the parent session | Transcript activity, tools, parent transcript filtered by actor when attribution exists | +| OpenCode | Controller session only | Presence lease and runtime workspace snapshot; detailed activity, hierarchy, transcript, and playback unavailable | +| Agentic-QE | Court/workflow actors and relationships only when an explicit AQE source reports them | Evidence-graded structured events; Claude/Codex members may coexist in one reported court | + +Native session hierarchy and Agentic-QE court membership are orthogonal. A Codex child thread can +participate in a Claude-led court without becoming a Claude child session. Conversely, the UI must +not infer court membership from host, provider, model, timing, or visual proximity. OpenCode court +membership is unavailable until Agentic-QE can configure and report OpenCode-routed court models. + Labels, status colors, actor-specific geometry, and edge verbs provide redundant meaning. The textual inventory includes recent events, nodes, and relationships with action and confidence. Users can pause live updates, navigate nodes and viewport controls by keyboard, honor @@ -236,9 +343,12 @@ The UI must not promote a host-based assumption to an observed provider claim. Collection bootstraps stable identity before following new appends. Sources are discovered newest-first. Codex state and bounded metadata records hydrate project, provider, model, hierarchy, and lifecycle; Claude records hydrate sanitized project and safe runtime metadata. The graph plane -never receives `cwd`, transcript paths, raw agent paths, prompt-derived titles, branch names, tool -arguments, results, or message content. The separately selected content plane may receive redacted -message text and allowlisted tool summaries under §7. +never receives `cwd`, transcript paths, raw agent paths, prompt-derived titles, filenames, patches, +raw Git output, tool arguments, results, or message content. It may receive the closed +`SessionWorkspace` DTO: opaque key, sanitized repository/relative-directory label, secret-masked +branch or detached revision, tracked aggregate change counts, capture time, source, and confidence. +The separately selected content plane may receive redacted message text and allowlisted tool +summaries under §7. Loopback binding and the dashboard's existing request validation apply to both endpoints. SSE listeners and response resources are released when clients disconnect. After the last client, the @@ -382,7 +492,7 @@ before backfill remains an explicit unrecoverable gap. ## Delivery and acceptance -The implementation proceeds in six independently testable milestones: +The implementation proceeds in seven independently testable milestones: 1. **Identity bootstrap:** newest-first discovery, Codex ledger hydration, bounded Claude/Codex metadata reads, and field-level provenance. @@ -411,7 +521,12 @@ Acceptance requires: - reconnect is duplicate-safe and either replays or explicitly resets; - partial writes, rotation, child-before-parent, missing completion, and clock skew do not crash; - pausing stops visual movement while ingestion continues; +- presence, meaningful work, and in-flight flow remain independently testable and visually + distinct; heartbeats never activate all structural edges; - pan, pointer-centred zoom, node drag/pin, fit, and reset work without losing live updates; +- automatic actor bundles do not intersect owned operation cards at the reference viewport, and + actors and operation cards are independently draggable and pinnable; +- hover/focus help, click/keyboard detail, and the consultable legend explain each visible unit; - streaming preserves viewport, selection, and manually positioned nodes; - visible labels identify the semantic actor/capability and current safe operation when evidence supplies them, and visibly grade weaker identity; @@ -422,14 +537,20 @@ Acceptance requires: - default topology contains agent anchors and bounded owned tool satellites; - projects contain their sessions without collapsing same-ID sessions from different hosts; - project session counts include navigation roots only; nested worker threads remain selectable; +- every retained root belongs to exactly one navigation scope; Live never falls back to History, + History never includes a current root, and projects/counts include only the selected scope; +- an empty Live scope renders zero projects and zero sessions; +- History and Review emit no green current-work status, breathing presence, moving flow, or live + follow/unread affordance; past running state uses historical copy and static styling; - rollout-first, ledger-later evidence reparents a thread without duplicating or hiding it; - replay at a timestamp is deterministic and does not mutate or execute source activity; - an active review extends without stealing the playhead and can explicitly resume live; - a 1440×900 reference fixture with one coordinator, three subagents, tools, messages, and completion fits all actors legibly while also showing current work, populated transcript, provider/project/session identity, and the docked timeline without page scrolling; -- active sessions require observed `running` status and active lifecycle; discovery-only, - timestamp-less, unknown, quiescent, and expired sessions never claim to be live; +- active sessions require an unexpired observed presence lease or fresh meaningful activity in an + active lifecycle; discovery-only, retained `running`, timestamp-less, unknown, quiescent, and + expired sessions never claim to be live; - disconnecting clients releases SSE listeners and the bounded idle stop releases collectors; - historical usage and dashboard behavior remain compatible; - unit, integration, privacy, accessibility, and synthetic load tests pass with `pnpm run check`. @@ -440,7 +561,8 @@ Acceptance does not imply automatic knowledge of every upstream store: - normal `ak dashboard` auto-discovers Claude/Codex transcripts and observes top-level Claude Code, Codex, and OpenCode controller liveness; OpenCode transcript topology remains unavailable until a - native adapter is added; ruflo and agentic-qe require explicit, repeatable + native adapter is added, and OpenCode is not a qe-court member until Agentic-QE supports and + reports such routing; ruflo and agentic-qe require explicit, repeatable `--live-source 'surface=path'` registration; - plugin, skill, MCP, and gate nodes appear only when a supported transcript or structured record emits them; there is no independent registry adapter for those surfaces; diff --git a/docs/adr/0016-capability-driven-integration-adapters.md b/docs/adr/0016-capability-driven-integration-adapters.md index b6584e1..5b4e35d 100644 --- a/docs/adr/0016-capability-driven-integration-adapters.md +++ b/docs/adr/0016-capability-driven-integration-adapters.md @@ -16,7 +16,7 @@ [ADR-0009](0009-usage-scorecard-local-transcript-analytics.md), [ADR-0010](0010-provider-mediated-quota-reads.md), [ADR-0011](0011-local-model-provenance-zero-cost-and-transcript-fidelity.md), - [ADR-0012](0012-live-sessions-observability.md), + [ADR-0012](0012-observability.md), [ADR-0015](0015-managed-codex-native-statusline.md), [issue #59](https://github.com/pacphi/agentic-kit/issues/59), [issue #71](https://github.com/pacphi/agentic-kit/issues/71) diff --git a/docs/adr/0021-inference-provider-provenance.md b/docs/adr/0021-inference-provider-provenance.md index d022936..ebbb97b 100644 --- a/docs/adr/0021-inference-provider-provenance.md +++ b/docs/adr/0021-inference-provider-provenance.md @@ -1,15 +1,21 @@ -# ADR-0021 — Inference-provider provenance for live sessions +# ADR-0021 — Inference-provider provenance for observed sessions - **Status:** Accepted - **Date:** 2026-07-31 +- **Updated:** 2026-08-03 +- **Update note:** Claude provider resolution now covers runtime leases as well as transcript + discovery, while stronger observed identity remains authoritative; OpenCode runtime presence is + acknowledged without manufacturing provider identity. The user-facing surface is now named + Observability; Live remains one navigation scope within it. - **Deciders:** agentic-kit maintainers -- **Related:** [ADR-0012](0012-live-sessions-observability.md) +- **Related:** [ADR-0012](0012-observability.md) ## Context ADR-0012 keeps the execution-host and inference-provider axes independent and forbids promoting a host-based assumption to an observed provider claim. In practice every Claude Code session in -the Live view rendered "Provider not established", and — unnoticed — every Codex session did too. +the Observability view rendered "Provider not established", and — unnoticed — every Codex session +did too. Investigation against current upstream sources found two distinct causes: 1. **Codex records the provider; ak read the wrong field.** Codex rollout `session_meta` @@ -35,8 +41,9 @@ configured | inferred | unknown`, populated from field-level evidence. Only emit - **Codex: read `model_provider`** in the rollout adapter and the state-ledger reader, keeping the bare `provider` spelling only as legacy tolerance. The claim remains **observed** — it is in the artifact. Custom provider ids pass through untranslated. -- **Claude: resolve the provider from the host's configuration surface** at transcript-discovery - time (`src/lib/live/claude-provider.mjs`), mirroring the documented selection order: +- **Claude: resolve the provider from the host's configuration surface** whenever session evidence + supplies a canonical working directory, including transcript discovery and runtime leases + (`src/lib/live/claude-provider.mjs`), mirroring the documented selection order: Bedrock flag → Vertex flag → Foundry flag → `ANTHROPIC_BASE_URL` (classified to `anthropic`, `openrouter`, or generic `gateway` by hostname) → first-party default `anthropic`. Explicit selections are **configured**; the first-party default is **inferred**; an unknown gateway stays @@ -61,7 +68,8 @@ configured | inferred | unknown`, populated from field-level evidence. Only emit - Claude Code sessions show who serves Claude models — Anthropic, Bedrock, Vertex, Foundry, OpenRouter, or an unnamed gateway — labeled configured or inferred, answering the operator question without overclaiming. -- OpenCode is unaffected: it is not a live host (ADR-0017/0018 scope it to managed execution), - and its usage-side records already carry an observed `providerID`. +- OpenCode runtime leases can establish host presence, but provider identity remains unavailable + until OpenCode supplies direct evidence; its usage-side records already carry an observed + `providerID` on that separate historical analytics path. - If codex renames or Claude Code starts recording the serving endpoint, the adapters prefer the in-artifact (observed) value automatically. diff --git a/docs/adr/README.md b/docs/adr/README.md index 6232928..195b5eb 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -13,14 +13,14 @@ Consequences**, and cites the grounded source it rests on where relevant. | [0002](0002-activity-vocabulary-defaults-from-ruv-templates.md) | Activity vocabulary & defaults seeded from rUv's shipped templates | Amended by 0020 | | [0003](0003-auto-seed-dual-host-provenance.md) | Auto-seed on dual-host, subscription-only, per-route provenance | Amended by 0020 | | [0004](0004-escalation-per-projection.md) | Escalation is per-projection, availability stated per path | Superseded by 0019/0020 | -| [0005](0005-dashboard-in-page-routing-reveal.md) | Dashboard surfaces routing via in-page reveal | Amended by 0020 | +| [0005](0005-dashboard-in-page-routing-reveal.md) | Dashboard surfaces routing via in-page reveal | Implemented | | [0006](0006-primary-host-and-ambidextrous-mirroring.md) | Primary host & ambidextrous mirroring (which host leads) | Amended by 0020 | | [0007](0007-maintainer-admin-local-telemetry.md) | Maintainer admin: a loopback telemetry page with deliberate egress | Accepted | | [0008](0008-guidance-target-scope-split.md) | Machine-scoped guidance blocks land in machine files, not a repo's AGENTS.md | Accepted | -| [0009](0009-usage-scorecard-local-transcript-analytics.md) | Usage scorecard: local transcript analytics with graded evidence | Accepted | +| [0009](0009-usage-scorecard-local-transcript-analytics.md) | Usage scorecard: local transcript analytics with graded evidence | Implemented | | [0010](0010-provider-mediated-quota-reads.md) | Provider-mediated quota reads (the only honest denominators) | Accepted | | [0011](0011-local-model-provenance-zero-cost-and-transcript-fidelity.md) | Local models: provenance out-of-band, $0 per model, stated transcript fidelity | Proposed | -| [0012](0012-live-sessions-observability.md) | Live sessions as local, evidence-graded observability | Accepted; compatibility source amended | +| [0012](0012-observability.md) | Evidence-graded session observability | Implemented | | [0013](0013-admin-build-security-signals-and-honest-reach.md) | Admin: build/security signals, an honest Reach panel, and a pagination fix | Accepted | | [0014](0014-dashboard-auth-and-remediation.md) | Dashboard auth token, plus a security/quality remediation pass | Accepted | | [0015](0015-managed-codex-native-statusline.md) | Manage Codex's native user-wide status line without claiming rich-renderer parity | Accepted | @@ -51,7 +51,7 @@ accounting, approximate token counts, and a model id the vendor documents how to provenance is established out-of-band via a loopback catalogue read, local models are priced at an exact `$0` **per model** rather than in one bucket, unrecognised models become `unpriced` instead of silently fallback-priced, and each local session states what its provider could not report. See also -`docs/PROVIDERS.md`. **0012** adds a read-only Live Sessions workspace: host-specific evidence is +`docs/PROVIDERS.md`. **0012** adds a read-only Observability workspace: host-specific evidence is normalized into a versioned, provenance-bearing event model, reduced into an interactive agent/tool canvas, and paired with a rich selected-session transcript rail. Separate SSE planes keep content out of broad topology snapshots/replay while preserving masked local evidence and @@ -101,7 +101,7 @@ host-management namespace, and the versioned top-level routing envelope; removes adapter-specific execution bootstrap; limits old vocabulary to marked decision history and one upgrade section; and preserves OpenCode's opt-in, supervised, non-primary, non-AQE boundary. -**0021** makes the Live view answer who serves each session's models without overclaiming. Codex's +**0021** makes Observability answer who serves each session's models without overclaiming. Codex's in-artifact `model_provider` (rollouts + state ledger) is read as observed evidence; Claude Code's provider is resolved from its documented configuration surface (Bedrock/Vertex/Foundry flags, `ANTHROPIC_BASE_URL` gateways) with configured/inferred provenance, since its transcripts never diff --git a/docs/ddd/README.md b/docs/ddd/README.md index 1f5758c..f683d7c 100644 --- a/docs/ddd/README.md +++ b/docs/ddd/README.md @@ -11,7 +11,7 @@ describe the current system unless a section is explicitly marked as future work | [Context map](context-map.md) | Bounded contexts, ownership, and relationships | | [Integration management](integration-management.md) | Hosts, inference providers, bindings, capabilities, lifecycle, facts, and ownership | | [Routing and orchestration](routing-and-orchestration.md) | Activities, routes, leadership, escalation, projections, and canonical `ak run` execution | -| [Live Sessions](live-sessions.md) | Evidence acquisition, live-session aggregates, replay, and dashboard delivery | +| [Observability](observability.md) | Evidence acquisition, observed-session aggregates, replay, and dashboard delivery | ## Relationship to other documentation diff --git a/docs/ddd/context-map.md b/docs/ddd/context-map.md index 250ecf5..0039397 100644 --- a/docs/ddd/context-map.md +++ b/docs/ddd/context-map.md @@ -15,9 +15,11 @@ Native Evidence ----> Evidence Acquisition ----> Canonical Evidence +-------------------------+----------------------+ | | v v - Live Sessions Historical Usage - | | - +-----------------> Dashboard Delivery <---------+ + Observability Historical Usage + | | | + | +----> Workspace Snapshot Cache | + | | | + +-----------------------+----> Dashboard Delivery <----+ Maintainer Administration is a separate, deliberately-egressing context. ``` @@ -52,23 +54,30 @@ Owns discovery, parsing, checkpoints, and translation of host- or provider-nativ Source adapters form an anti-corruption layer: native transcript, quota, catalog, runtime, and metadata shapes do not cross into the domain unchanged. -### Live Sessions +### Observability Owns canonical live events, session identity, lifecycle, topology, evidence confidence, and live aggregates. It consumes canonical evidence and publishes read-model projections. -See [Live Sessions](live-sessions.md). +See [Observability](observability.md). ### Historical usage Owns transcript indexing, session history, token and cost aggregation, classification, and usage -findings. It may share a host-qualified session identity with Live Sessions, but its aggregate and +findings. It may share a host-qualified session identity with Observability, but its aggregate and cache are separate from the live event store. +### Workspace snapshot cache + +Owns the bounded, owner-only last safe `SessionWorkspace` value per host-qualified session. It is +an advisory read-model cache, not the append-only Evidence Archive and not a source of liveness. +Restoration supplies inert History context using the original capture time; it cannot query a +current checkout and present that state as historical. + ### Dashboard delivery Owns protected HTTP/SSE delivery, browser DTOs, filters, presentation, and interaction state. It -may combine read models from Live Sessions, Historical Usage, routing, and integration facts. It +may combine read models from Observability, Historical Usage, routing, and integration facts. It cannot manufacture or strengthen domain facts. ### Maintainer administration @@ -84,9 +93,11 @@ and credential policy is distinct from the offline-first dashboard and integrati | Integration management | Native surfaces | Configuration projections with ownership receipts | | Integration management | Routing and orchestration | Capability-qualified host and binding facts | | Native evidence | Evidence acquisition | Source-specific anti-corruption adapters | -| Evidence acquisition | Live Sessions | Versioned canonical events | +| Evidence acquisition | Observability | Versioned canonical events | | Evidence acquisition | Historical usage | Normalized transcript and provider evidence | -| Live Sessions | Dashboard delivery | Read-model snapshots, deltas, and selected evidence | +| Observability | Dashboard delivery | Read-model snapshots, deltas, and selected evidence | +| Observability | Workspace snapshot cache | Last safe metadata-only session workspace capture | +| Workspace snapshot cache | Dashboard delivery | Inert last-recorded History context after restart | | Historical usage | Dashboard delivery | Historical aggregates and findings | ## Boundary rules diff --git a/docs/ddd/live-sessions.md b/docs/ddd/observability.md similarity index 65% rename from docs/ddd/live-sessions.md rename to docs/ddd/observability.md index 4adb0bd..9cfdcfa 100644 --- a/docs/ddd/live-sessions.md +++ b/docs/ddd/observability.md @@ -1,12 +1,12 @@ -# Live Sessions Domain Design +# Observability Domain Design This document specifies the domain model behind -[ADR-0012](../adr/0012-live-sessions-observability.md). It follows SPARC's specification, +[ADR-0012](../adr/0012-observability.md). It follows SPARC's specification, architecture, refinement, and completion stages; implementation pseudocode appears at the relevant boundaries. The shared [ubiquitous language](ubiquitous-language.md) and -[context map](context-map.md) are normative. Terms defined below refine the Live Sessions context +[context map](context-map.md) are normative. Terms defined below refine the Observability context without redefining shared integration concepts. ## Ubiquitous language @@ -15,6 +15,8 @@ without redefining shared integration concepts. |------|---------| | Session | One host thread or orchestrated run whose live state is observed | | Project | Sanitized repository identity grouping sessions across hosts and time | +| Session workspace | Temporal, privacy-bounded checkout context owned by one observed session | +| Working tree at capture | Tracked checkout counts versus `HEAD`; context, never agent attribution | | Session key | Host-qualified identity (`host:sessionId`) preventing cross-host collisions | | Root session | Top-level host thread or orchestrated run used as one navigation unit | | Child session | Independently addressable agent/worker thread with an observed parent | @@ -23,6 +25,12 @@ without redefining shared integration concepts. | Entity | A session, agent, tool, skill, plugin, MCP server, gate, or other observed capability | | Actor | An entity that initiates or owns an activity | | Activity | A bounded operation performed by an actor | +| Presence lease | Observed proof that a host controller process exists; never proof of work | +| Meaningful activity | Semantic input, output, operation, or evaluation evidence attributable to an actor | +| In-flight flow | A started and unfinished operation/relationship eligible for moving edge treatment | +| Actor lens | Selectable view of an embedded actor inside its parent session; not a fabricated child session | +| Capability coverage | Per-session declaration of which evidence dimensions the source can support | +| Court membership | Agentic-QE leader/seat relationship; orthogonal to native session parentage | | Display identity | Evidence-graded human label and role; never the source ID alone | | Current work | The latest open, allowlisted operation summary for an entity | | Evidence | A source record supporting a domain fact | @@ -32,13 +40,19 @@ without redefining shared integration concepts. | Topology | Nodes and typed relationships visible for a session | | Execution canvas | Spatial view of agents, owned tools, and causal flow for one session | | Evidence rail | Selected-session transcript synchronized with canvas selection | +| Primary area | One stable dashboard domain workspace: Overview, Usage, or Observability | +| Secondary navigation rail | One fixed-position tab row whose choices belong to the active primary area | +| Live scope | Navigation roots with current presence or fresh meaningful activity | +| History scope | Retained navigation roots that do not satisfy the Live predicate | +| Scope predicate | One canonical classifier assigning every retained root to exactly one scope | | Follow Live | Playhead follows the newest retained event and advances on append | | Review | Deterministic reconstruction of one session at a selected evidence time | +| Review-time state | Past state at the playhead; never current presence or work | | Playhead | Current playback offset within retained session evidence | | Cursor | Monotonic ingest position used for delivery and replay | | Adapter health | Freshness and failure state of one evidence source | | Quiescent | No recent activity, but no authoritative completion was observed | -| Terminal | Completed, failed, cancelled, or expired according to lifecycle rules | +| Terminal | Completed, failed, or cancelled according to explicit lifecycle evidence | “Live” describes the freshness of local observation, not distributed consensus. A planned workflow step is not an active agent. A correlated edge is not an observed spawn. @@ -59,7 +73,7 @@ Source adapters are anti-corruption layers for: - agentic-qe tasks, workers, evaluations, gates, and court verdicts; - explicit skill, plugin, MCP, and tool lifecycle records. -### Live session domain +### Observability domain Owns canonical events, identity correlation, lifecycle transitions, evidence confidence, aggregates, and invariants. Host-specific record shapes do not cross this boundary. @@ -72,7 +86,9 @@ reduction. It is optimized for current state, not historical cost analytics. ### Dashboard delivery Owns HTTP/SSE representation, connection lifecycle, filters, stable SVG presentation, the -synchronized evidence rail, and textual equivalents. It cannot manufacture domain facts. +synchronized evidence rail, shared dashboard navigation, canonical hashes, local presentation +preferences, and textual equivalents. It cannot manufacture domain facts or collapse distinct host, +provider, provenance, and model facts into one identity. ### Historical usage @@ -85,13 +101,14 @@ aggregate is not the live event store. ```text Claude ─┐ Codex ──┤ +OpenCode┤ ruflo ──┤ source adapters / anti-corruption layer AQE ────┤ ak ─────┘ ↓ canonical event normalizer ↓ -LiveSession aggregate → read-model projection/replay → snapshot + SSE → dashboard +ObservedSession aggregate → read-model projection/replay → snapshot + SSE → dashboard │ └── session-id link ──→ historical usage context ``` @@ -105,18 +122,34 @@ metadata prefix and Codex ledger state so stable identity is available on first records establish discovery and identity; they do not fabricate current liveness for an old session. +Codex ledger reads retain the thread's source creation, update, and recency timestamps. Collector +observation time records when the dashboard saw the row; it is never substituted for thread +freshness. A runtime process can attach presence to a retained session only when that session was +updated during the current process generation. Otherwise it remains a separate synthetic presence +session until stronger identity evidence arrives. + ## Aggregates and entities -### `LiveSession` aggregate root +### `ObservedSession` aggregate root Identity is `(host, sessionId)`, with an optional cross-host `runId`. It owns: -- lifecycle status and last-observed time; +- lifecycle status, an independent presence lease, and last meaningful-activity time; - actor identities and typed relationships; - active and terminal activities; - evidence references and confidence; - adapter freshness relevant to this session; -- safe aggregate usage/duration counters when explicitly reported. +- safe aggregate usage/duration counters when explicitly reported; +- its latest safe `SessionWorkspace` snapshot and the capture evidence supporting it. + +Presence and activity are independent axes. A runtime heartbeat may update `presence` and the +session freshness cursor, but cannot replace the last semantic actor action, change a quiet actor +to working, or animate a structural edge. An operation becomes in-flight only from an explicit +started phase that has not reached a terminal phase. + +The scope predicate is total and exclusive: every retained navigation root is either Live or +History, never both. Observation time cannot promote History into Live. An expired unfinished +operation retains its unknown outcome but clears current activity and current-operation identity. The public `sessionKey` is host-qualified. The raw native ID remains available for provider lookup, but UI state, project membership, selection, and playback use `sessionKey`. @@ -137,25 +170,69 @@ Only the aggregate may: Project { id, label, updatedAt, sessionKeys[], - sessionCount, childSessionCount, liveCount, completedCount, + sessionCount, liveCount, historicalCount, + liveChildCount, historicalChildCount, presentCount, workingCount, completedCount, hosts, providers } ``` -It is reduced from sanitized session project labels. `sessionKeys` and `sessionCount` contain -navigation roots only; `childSessionCount` reports descendant threads without presenting them as -peer work. Sessions are ordered live first and then by most recent evidence. A project ID is opaque -and safe for DOM/routing; it never contains the raw working directory. +It is reduced from sanitized session project labels. Navigation roots are classified once by the +canonical scope predicate. Live orders current roots by current evidence; History orders retained +roots by last meaningful source time. There is no cross-scope ordering. Project and child counts +are computed per scope, so a live-only project cannot appear as an empty History project and an +empty Live scope remains empty. A project ID is opaque and safe for DOM/routing; it never contains +the raw working directory. `hosts` counts execution hosts independently. `providers` counts evidence-backed inference providers and uses an explicit `unknown` bucket when no provider evidence exists; it never substitutes the session host. +`presentCount` and `workingCount` are separate. `liveCount` is their navigation-oriented union, not +a synonym for working. This makes a quiet but present controller visible without overstating work. +Collector observation time and retained `status: running` are not scope evidence. Only a valid +presence lease, fresh meaningful activity, or a genuinely in-flight operation can classify a root +as Live. + Project identity means the owning repository, not the current branch or linked-worktree directory. For a live local worktree, the `.git` ownership pointer resolves the repository. Known nested -worktree layouts provide a privacy-safe fallback for retained paths that no longer exist. Only the -sanitized repository label crosses the event boundary; raw working directories and Git metadata -paths do not. +worktree layouts provide a privacy-safe fallback for retained paths that no longer exist. Raw +working directories and Git metadata paths do not cross the boundary. A separate temporal +`SessionWorkspace` may carry sanitized repository-relative context; it does not change project +identity. + +### `SessionWorkspace` value object + +`SessionWorkspace` belongs to `ObservedSession`, not `Project`, because two sessions in one project +may use different linked worktrees or capture the same checkout at different times: + +```text +SessionWorkspace { + opaqueKey, + repositoryLabel, + directoryLabel, + branchLabel, branchState, + changes { additions, deletions, files, binaryFiles, basis, completeness }, + capturedAt, source, confidence +} +``` + +Acquisition computes tracked counts against `HEAD` with Git invoked directly, no shell, a bounded +timeout/buffer, disabled terminal prompting, and optional locks disabled. Untracked contents and +binary line counts are excluded. A missing repository, unborn `HEAD`, timeout, or failed Git read +produces absent evidence—not fabricated zeroes. + +The value object never contains an absolute working directory, filename, patch, raw Git output, +prompt, command, tool input, or tool result. Branch and relative-directory labels are bounded, +control-stripped, path-checked, and secret-masked at the event and persistence boundaries. Its +numeric delta is titled **Working tree at capture** because multiple live sessions can share a +checkout; it is never represented as “changes made by this agent.” + +The projection merges snapshots by source confidence and capture time. A weaker metadata record +may fill a missing label but cannot erase stronger Git counts. The owner-only +`WorkspaceSnapshotStore` retains at most one last safe value per host-qualified session. On restart +it restores metadata-only History evidence using the original capture time. Restoration cannot +create presence, meaningful activity, green state, or animation, and it never re-queries the +current checkout to reconstruct past state. ### Session hierarchy projection @@ -178,6 +255,18 @@ deterministic navigation root and never recurse indefinitely. Selecting a root e threads as a nested Miller-style tier; selecting a child changes the map/transcript context without promoting that child into the project root count. +Claude sidechain evidence does not currently establish a separate session aggregate or content +endpoint. It therefore creates a `contains` relationship and a nested actor lens under the real +parent session. Codex ledger children remain full `LiveSession` aggregates. Actor lenses may filter +the parent transcript but never acquire invented IDs, timestamps, transcripts, playback, or +completion state. + +Agentic-QE court membership is another hierarchy projection, not native session parentage. A court +leader and seat may report different execution hosts inside one AQE session. This permits +Claude-led/Codex-seat and Codex-led/Claude-seat views when explicit structured evidence supplies the +relationship. OpenCode remains absent from court membership until Agentic-QE supports and reports +OpenCode court routing; runtime presence alone cannot imply a court seat. + ### `Entity` and `Actor` An entity has a stable source-scoped ID, kind, display name, role, host/surface, provider/model @@ -224,8 +313,9 @@ provider (`model_provider` in rollout `session_meta` and the state ledger), so C **observed**. Claude transcripts never name one; the domain resolves it from the host's documented configuration surface — Bedrock/Vertex/Foundry selection flags and `ANTHROPIC_BASE_URL` gateway classification across settings layers — yielding a **configured** claim, or an **inferred** one for -the first-party default. An unrecognized gateway stays `gateway` rather than a guessed vendor, and -a claim's grade is never upgraded downstream. +the first-party default. Resolution occurs whenever canonical Claude evidence supplies a working +directory, including transcript discovery and runtime leases. An unrecognized gateway stays +`gateway` rather than a guessed vendor, and a claim's grade is never upgraded downstream. ### `Activity` @@ -233,6 +323,19 @@ An activity represents a bounded invocation or evaluation. It has source-scoped action, target, timestamps, status, optional safe counters, and evidence. Repeated updates mutate the aggregate state through idempotent events; they do not create duplicate visible activities. +### `CapabilityCoverage` value object + +Every serialized session declares evidence coverage for `presence`, `activity`, `actors`, +`resources`, `hierarchy`, `transcript`, `playback`, `providerIdentity`, `workspaceIdentity`, +`gitBranch`, and `gitChanges`. Values describe the +strongest supported evidence such as `observed`, `events`, `child-sessions`, `embedded-actors`, +`lifecycle`, `session`, `presence-only`, or `unavailable`. UI affordances derive from this object; +they do not hardcode host names as capability proxies. + +The same component shell is used across Claude Code, Codex, and OpenCode. An unavailable value +disables the unsupported action and supplies a specific explanation. It never causes the renderer +to invent a child session, transcript, operation, provider, court seat, or animated edge. + ### `Source` value object Contains adapter name, opaque or hashed artifact reference, source event identity, observation time, @@ -248,7 +351,7 @@ category, checkpoint age, and retry state. ```js { - schemaVersion: 1, + schemaVersion: 2, eventId: "ak:1842", ingestSeq: 1842, observedAt: "2026-07-27T18:42:01.125Z", @@ -262,17 +365,33 @@ category, checkpoint age, and retry state. host: "codex", surface: "ruflo", project: "agentic-kit", + workspace: { + key: "workspace:opaque-hash", + repositoryLabel: "agentic-kit", + directoryLabel: "repo root", + branchLabel: "feature/observability", + changes: { additions: 12, deletions: 3, files: 2, basis: "tracked-vs-head" }, + capturedAt: "2026-07-27T18:42:00.991Z", + source: "git", + confidence: "observed" + }, actor: { id: "source-scoped-id", kind: "subagent", label: "test runner", role: "tester", + host: "codex", provider: "openai", model: "gpt-5.6-sol" }, action: "agent.spawned", target: { id: "child-id", kind: "subagent" }, status: "running", + signal: { + kind: "relationship", + phase: "observed", + correlationId: null + }, source: { adapter: "codex-state", artifact: "opaque-ref", @@ -443,18 +562,52 @@ on dashboard close: The UI receives only projection DTOs: - `sessions`: identity, sanitized project label, host/provider/model, status, freshness, safe - counters, and field provenance; + counters, independent presence/activity, capability coverage, and field provenance; - `nodes`: identity, session, kind, label, role, host/model, status, last action, confidence, and - safe metadata; + safe metadata and actor execution host; - `edges`: source, target, relationship type, human verb, confidence, and status; - `health`: sanitized adapter status and aggregate counters; - `cursor` and `schemaVersion`. +The dashboard shell has exactly three primary areas: `Overview`, `Usage`, and `Observability`. One +fixed, left-aligned secondary navigation rail remains in the same location while its contents change: + +```text +Overview → Summary | Hosts & Routing | Providers | Runtime | Intelligence +Usage → Scorecard | Limits | Findings | Sessions | Transcript +Observability → Live | History +``` + +Navigation state has canonical hierarchical hashes: + +```text +#overview/{summary,hosts,providers,runtime,intelligence} +#usage/{score,limits,findings,sessions,transcript} +#usage/{sessionId} +#observability/{live,history} +``` + +Each destination owns a visible heading and concise description. The primary and secondary controls +are ARIA tab lists with roving focus: Left/Right selects and focuses the adjacent tab with wrapping, +Home selects the first tab, and End selects the last. This navigation state is dashboard +presentation, not Observability evidence, and changing a hash cannot strengthen or mutate a domain +fact. + +Usage session rows preserve four independent identity axes. The compact badge names only the +execution host; an independently expandable strip names inference provider, provider provenance, +and reported models. Codex `model_provider` carried by `session_meta` or `turn_context` is observed +provider evidence. Native Claude transcript history has no equivalent serving-provider field, so +`Not recorded` is a valid value. Dashboard delivery never infers provider from the execution host or +model string. + The renderer separates domain projection from presentation state. A world transform `{x, y, k}` controls the viewport, while a position cache and optional pin offset are keyed by entity identity. Status-only updates cannot trigger global layout or reset the world transform. Agents are stable -anchors; bounded current/recent tool operations occupy deterministic satellite positions around -their owner. Animated particles distinguish dispatch, invocation, return, and message flow. +anchors. Each actor, work bubble, history summary, and owned operation lane forms a measured bundle; +the next bundle begins after the first bundle's full vertical footprint. Tool cards occupy a lane +outside the actor/work-label bounds, so deterministic automatic placement cannot overlap them. +Only explicitly started, unfinished flow animates. Structural relationships are static; observed +presence breathes slowly; meaningful work pulses independently. The presentation grammar treats an agent as an iconic hex/ring anchor, not a workflow card. Observed current work is a nearby transient bubble, and tool activity is a small tethered @@ -466,28 +619,50 @@ Direct manipulation follows a slippy-map interaction contract: - drag empty space to pan; - wheel or trackpad scrolling zooms about the pointer within bounded scale; -- drag a node beyond a click threshold to move and pin it; +- drag an actor or individual operation card beyond a click threshold to move and pin it; - visible controls zoom, fit, reset the camera, and reset the layout; - fit changes only the viewport; reset layout discards manual layout offsets; - stream updates preserve viewport, selection, and manually positioned nodes. +Positions and pins are keyed by `sessionKey|entityId`, not raw native ID. Hover and focus show an +ephemeral evidence-aware description. Click, Enter, or Space selects the component and opens +persistent detail. A **Legend / Help** dialog explains shapes, statuses, confidence, motion, and +interactions; a polite guidance region announces selection and next action. Pause stops CSS and SVG +movement as well as visual delta application, while `prefers-reduced-motion` removes nonessential +motion without hiding state. + The execution canvas and transcript rail share one selection model. Selecting an agent focuses its subtree and transcript; selecting a tool shows its safe summary; selecting evidence highlights and centers its owner. The initial overview shows project, host, evidence-backed provider identity, lifecycle, current work, and critical failures; metadata and evidence are details on demand ([Shneiderman][eyes-have-it]). -The right rail is persistent and subscribes only to the selected `{host, sessionId}`. It carries +The right Session Stream rail subscribes only to the selected `{host, sessionId}`. It carries the rich masked conversation, plaintext reasoning, tool inputs/results/errors, patches, MCP/web activity, and subagent messages available from that host. It supports search, type/actor filtering, collapsed large details, auto-follow, and unread state. It renders transcript text as text, never markup, and labels masking as best effort. -Project selection precedes session selection. Session selection chooses Follow Live for active -work and Review for retained work. Both modes use the same event reducer and canvas/transcript -renderers. Review owns `{playhead, playing, speed}`; appends extend duration without moving a -reviewer's playhead. Seeking resets presentation state and reapplies ordered evidence through the -selected offset. Resume Live applies the retained tail and restores automatic following. +The rail's body is collapsible presentation state, locally persisted independently of session +evidence. Collapsing it does not close its selected-session subscription or stop ingestion; it gives +the execution canvas more horizontal room and leaves a compact restore rail with a real chevron +button. The button exposes `aria-expanded`, an action-specific label, and a polite state +announcement. At narrower breakpoints the rail stacks below the browser/canvas and its collapsed +form becomes a compact full-width restore bar. Leaving Observability still closes the connection; +collapsing the rail does not. Pause remains a separate state that freezes visual delta application. + +Project selection precedes session selection. Navigation scope and playback state are separate +state machines: `Live | History` chooses eligible roots, while `Follow Live | Review` chooses how +one selected root is presented. Switching scopes clears incompatible selection and chooses only +within the destination scope. Review owns `{playhead, playing, speed}`; appends extend duration +without moving a reviewer's playhead. Seeking resets presentation state and reapplies ordered +evidence through the selected offset. Resume Live is available only when the selected root still +satisfies the Live predicate. + +Green is current-state vocabulary, never a historical outcome color. History and Review suppress +presence breathing, work pulse, flow particles, moving dashes, live following, and unread updates. +Completed history is neutral; an explicit historical failure may remain static red. A playback +event that was running is described as in progress at that playhead, not `Working now`. The graph has a textual inventory of recent events, nodes, and relationships with action and confidence. Pause freezes visual application while the client queues at most 256 deltas; overflow @@ -537,7 +712,8 @@ Tests are written against ports and fixtures before each adapter or lifecycle tr 8. **UI tests:** stable coordinates, pointer-centred zoom, pan, drag-versus-click, pin persistence, camera/layout reset, stream-stable viewport and selection, coordinated graph/transcript, pause/resume, search, auto-follow/unread, - reduced motion, keyboard navigation, textual equivalence, filters, and dark/light themes. + reduced motion, keyboard navigation, textual equivalence, filters, dark/light themes, measured + actor/tool non-intersection, independent actor/tool drag, legend/tooltips, and truthful flow. 9. **Load tests:** synthetic concurrent sessions and high-cardinality tools establish aggregation and frame-time budgets. @@ -624,15 +800,33 @@ process memory limits. - Rotation, truncation, partial writes, missing parents/completions, and clock skew do not crash. - No excluded content appears in snapshot, SSE, logs, or adapter error responses. - Pause and reduced-motion behavior work; all essential data is keyboard-accessible without SVG. +- Presence, meaningful activity, and in-flight flow are independent; a heartbeat never overwrites + semantic activity or animates structural relationships. +- Every retained root belongs to exactly one scope; scope-local project/session/worker counts match + visible rows, and empty Live renders zero projects rather than recent History. +- History and Review contain no green current-work cues, breathing presence, moving flow, live + follow state, or unread-live announcements. - Pan, bounded pointer-centred zoom, node drag/pin, fit, and reset have keyboard-accessible controls and do not interrupt ingestion. +- Automatic actor bundles and operation cards do not intersect at the reference viewport; both are + independently draggable and pinnable. +- Hover/focus descriptions, persistent selection detail, and Legend / Help explain every unit and + the available next interaction. +- Exactly three primary areas share one fixed, left-aligned secondary rail; canonical hashes, + headings, descriptions, roving Left/Right focus, and Home/End behavior match each destination. +- Collapsing Session Stream preserves its connection and local choice, expands Agent activity, + leaves a keyboard-accessible restore rail, and remains compact when the layout stacks. +- Usage session rows present host, provider, provider provenance, and model as independent facts; + Codex `model_provider` is observed and absent Claude provider evidence reads `Not recorded`. - Streaming preserves the user's viewport, selection, and manually positioned nodes. - The overview uses evidence-backed semantic labels and current-work summaries; a generic or inferred identity is visibly identified as such. - Graph and transcript selection remain coordinated; auto-follow yields to manual reading. - No client connection remains after disconnect; bounded idle or dashboard close releases collectors. -- Historical dashboard routes and usage results remain regression-compatible. +- The canonical dashboard routes are `#observability/live` and `#observability/history`; + Historical Usage remains a separate bounded + context and its results remain regression-compatible. - Clean-room provenance is documented; no Albert or Agent Flow branding/assets are copied. ## Implementation conformance @@ -654,18 +848,29 @@ The server subscribes before taking the initial snapshot and reconciles buffered closing the snapshot-to-subscribe race. A slow-client queue overflow discards queued frames and sends a reset snapshot after drain. -The default command discovers Claude/Codex transcripts and reads Codex ledger edges. Ruflo and +The implemented dashboard shell exposes three primary areas and one shared secondary rail. It emits +the canonical Overview, Usage, and Observability hashes above, gives every view a heading and +description, and implements Left/Right/Home/End tab semantics. Observability's locally persisted +Session Stream chevron changes layout without changing subscription ownership. Usage session rows +show a host-only badge and reveal provider/provenance/model facts in their own detail strip. + +The default command discovers Claude/Codex transcripts, observes Claude/Codex/OpenCode controller +presence and inspectable workspace context, and reads Codex ledger edges. Claude sidechains are +embedded actor lenses; Codex ledger children are independent sessions. OpenCode detailed activity, +transcript, playback, hierarchy, and Agentic-QE +court membership remain unavailable until their respective sources report those capabilities. +Ruflo and agentic-qe source adapters require explicit, repeatable `--live-source 'surface=path'` registration, where `surface` is `ruflo` or `aqe`. Explicit sources consume tailer capacity before Claude/Codex discovery. Paths resolve against the startup working directory and are not confined to the project, so registration is an operator authorization to read that file. Independent plugin, skill, MCP, and gate registries are not implemented. This is an explicit source -coverage limitation; ADR-0012 is Accepted because the supported adapter contract does not claim +coverage limitation; ADR-0012 is Implemented because the supported adapter contract does not claim automatic upstream discovery. ## References -- [ADR-0012](../adr/0012-live-sessions-observability.md) +- [ADR-0012](../adr/0012-observability.md) - [Albert license][albert-license] - [Reviewed Albert graph source][albert-graph] - [OpenTelemetry GenAI semantic conventions][otel-genai] diff --git a/src/commands/x/dashboard.mjs b/src/commands/x/dashboard.mjs index 7357c47..e43b20c 100644 --- a/src/commands/x/dashboard.mjs +++ b/src/commands/x/dashboard.mjs @@ -22,7 +22,7 @@ Serves a self-contained web panel that visualizes the same subsystem rows \`ak status\` reports — versions, natives, security, learning, providers, hosts, mcp, ruvnet-brain, aqe — plus version drift, a learning-history sparkline, and (on the Usage tab) full session transcripts. Bound to 127.0.0.1; health -polling defaults to 30s and the Live tab streams metadata with SSE. Read-only: +polling defaults to 30s and the Observability tab streams metadata with SSE. Read-only: it never changes state. Nothing leaves your machine — the page is fully self-contained (no external fetches, no internet). diff --git a/src/lib/codex-state.mjs b/src/lib/codex-state.mjs index ec21c9b..cc3936b 100644 --- a/src/lib/codex-state.mjs +++ b/src/lib/codex-state.mjs @@ -25,6 +25,16 @@ import path from 'node:path'; import { codexDir } from './paths.mjs'; import { withDb } from './sqlite.mjs'; import { resolveProjectIdentity } from './live/project-label.mjs'; +import { workspaceFromSource } from './live/git-workspace.mjs'; + +function ledgerTimestamp(row, milliseconds, seconds) { + const millis = Number(row[milliseconds]); + const value = Number.isFinite(millis) && millis > 0 + ? millis : Number(row[seconds]) * 1000; + if (!Number.isFinite(value) || value <= 0) return null; + const date = new Date(value); + return Number.isFinite(date.getTime()) ? date.toISOString() : null; +} /** Newest-generation state db file under ~/.codex, or null. Exported for test * via the `dir` override. */ @@ -63,10 +73,16 @@ export function readCodexState(opts = {}) { .concat([ 'tokens_used', 'source', 'model', 'git_branch', 'agent_nickname', 'agent_role', 'cwd', 'model_provider', 'provider', 'status', + 'created_at', 'updated_at', 'recency_at', + 'created_at_ms', 'updated_at_ms', 'recency_at_ms', ].filter((c) => cols.has(c))); const threads = new Map(); for (const row of db.prepare(`SELECT ${pick.join(', ')} FROM threads`).all()) { const project = typeof row.cwd === 'string' ? resolveProjectIdentity(row.cwd) : null; + const createdAt = ledgerTimestamp(row, 'created_at_ms', 'created_at'); + const updatedAt = ledgerTimestamp(row, 'updated_at_ms', 'updated_at'); + const recencyAt = ledgerTimestamp(row, 'recency_at_ms', 'recency_at'); + const sourceAt = updatedAt ?? recencyAt ?? createdAt; threads.set(String(row.id), { tokensUsed: Number(row.tokens_used) || 0, threadSource: typeof row.thread_source === 'string' ? row.thread_source : null, @@ -82,6 +98,13 @@ export function readCodexState(opts = {}) { provider: typeof row.model_provider === 'string' ? row.model_provider : (typeof row.provider === 'string' ? row.provider : null), status: typeof row.status === 'string' ? row.status : null, + createdAt, + updatedAt: sourceAt, + recencyAt, + workspace: sourceAt ? workspaceFromSource({ + cwd: row.cwd, branch: row.git_branch, project: project?.label, + capturedAt: sourceAt, source: 'codex-state', + }) : null, }); } const parents = new Map(); diff --git a/src/lib/dashboard/client.mjs b/src/lib/dashboard/client.mjs index cc9b60a..eb4de6a 100644 --- a/src/lib/dashboard/client.mjs +++ b/src/lib/dashboard/client.mjs @@ -16,7 +16,7 @@ export const JS = ` (function(){ "use strict"; var root=document.documentElement; - var LS="ak-dash-theme", LS_TAB="ak-dash-tab"; + var LS="ak-dash-theme", LS_TAB="ak-dash-tab", LS_OVERVIEW="ak-dash-overview-view"; // Dashboard-wide session token (ADR-0014). Bootstrap is idempotent and // duplicated from live-view.mjs's copy (separate