diff options
| author | Adam Malczewski <[email protected]> | 2026-06-10 10:06:27 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-10 10:06:27 +0900 |
| commit | f8bf715abc8a89ec0c6370b40403c509b1ce2870 (patch) | |
| tree | 915600a766e042a8491ac57423542cde1dda1eb6 /backend-handoff.md | |
| parent | ccfd2f4157c1cbbb3d8aeceee94d9e963a82ab03 (diff) | |
| download | dispatch-web-f8bf715abc8a89ec0c6370b40403c509b1ce2870.tar.gz dispatch-web-f8bf715abc8a89ec0c6370b40403c509b1ce2870.zip | |
feat(metrics): per-turn + per-step token/timing metrics bubbles
Consume [email protected] / [email protected] metrics: usage.stepId,
step-complete (ttft/decode/genTotal), done.durationMs/usage, and the
durable GET /conversations/:id/metrics endpoint.
- core/metrics: pure live-fold + durable-merge reducer; decode-rate TPS;
head-aligned, stable placement; progressive per-step rows (each shown as
its step ends) with the turn-total row gated on the done event.
- features/chat: store folds metric events + hydrates durable TurnMetrics;
ChatView renders inline step bubbles + a turn-total bubble.
- app: MetricsSync HTTP effect (tolerates 404) injected into chat stores.
- scripts/live-probe: drives the metrics path; live-verified 17/17 vs bin/up.
- docs: regenerate .dispatch wire/transport mirrors to 0.4.0; glossary terms
(turn/step metrics, TTFT, decode time, TPS, metrics bubble); trim handoff.
Diffstat (limited to 'backend-handoff.md')
| -rw-r--r-- | backend-handoff.md | 92 |
1 files changed, 16 insertions, 76 deletions
diff --git a/backend-handoff.md b/backend-handoff.md index b0b0494..df6a618 100644 --- a/backend-handoff.md +++ b/backend-handoff.md @@ -5,93 +5,33 @@ > **From:** dispatch-web orchestrator · **To:** arch-rewrite orchestrator · **Courier:** the user. > `lsp` does NOT span the repos (ORCHESTRATOR §5) — every cross-repo ask flows through here. -_Last updated: 2026-06-06 — Slice 3 (tabs + model selector + DaisyUI) FE-complete; no new backend asks._ +_Last updated: 2026-06-10 — metrics display slice FE-complete + live-verified (probe 17/17). No open backend asks._ --- -## 1. Current FE status +## 1. Pinned backend contracts (consumed by the FE) -| Slice | State | -|---|---| -| **Slice 1** — surface system + WS + composition root | ✅ DONE, committed, green. | -| **Slice 2** — conversation transcript: cache + delta streaming (design §6) | ✅ DONE + **LIVE-VERIFIED** — live e2e probe **9/9** against `bin/up` (see §6). | -| **Slice 3** — tabs (multi-conversation) + model selector + DaisyUI/dracula | ✅ FE-COMPLETE — svelte-check 0/0, **281 vitest**, biome clean, build ok. Per-tab chat stores, one WS routed by `conversationId`, local-forget on tab close, tabs persisted to localStorage. No backend change needed. | - -**Slice 2 units built** (all pure-core / injected-shell, single-owner): `core/chunks` (the one -transcript reducer) · `core/wire` (contract-conformance drift guard) · `adapters/ws` (now multiplexes -`chat.send`/`chat.delta` on the one socket) · `features/conversation-cache` (pure reconcile/evict + -`ConversationChunkStore` port) · `adapters/idb` (IndexedDB impl) · `features/chat` (runes view-model -+ `ChatView`/`Composer`) · `app` (one socket for surface+chat, host-relative HTTP `:24203` history -sync, IndexedDB cache, renders the chat). Consumes ONLY the pinned `@0.1.0` contracts — no backend -change was needed. - -## 2. Pinned backend contracts (consumed by the FE) - -All three pinned as `file:` deps at **`@0.1.0`** and live-verified consumable (import smoke-test passes): | Package | Used for | |---|---| -| `@dispatch/ui-contract` | surfaces + surface WS protocol (Slice 1) | -| `@dispatch/wire` | chat wire types: `Chunk`/`StoredChunk`(+`seq`)/`ChatMessage`/`AgentEvent`/`TurnSealedEvent`/`Usage` | -| `@dispatch/transport-contract` | HTTP endpoints + `ChatRequest`/`ModelsResponse`/`ConversationHistoryResponse` + WS chat ops + unified `WsClientMessage`/`WsServerMessage` | - -Backend endpoints in use (port **24203** HTTP, **24205** WS, CORS wildcard `*` — all confirmed live): -`POST /chat` (NDJSON), `GET /models`, `GET /conversations/:id?sinceSeq=<n>`, WS `chat.send`→`chat.delta`. -Confirmed invariants C1–C4 (raw seq-ordered history slice · one path-agnostic WS multiplexing surface+chat · `turn-sealed` fires post-persist = cache-commit · live deltas carry no `seq`). - -Mirrored in-repo for headless agents: `.dispatch/ui-contract.reference.md`, `.dispatch/wire.reference.md`, -`.dispatch/transport-contract.reference.md` (regenerated on any contract bump). - -## 3. Open items FOR THE BACKEND +| `@dispatch/ui-contract` | surfaces + surface WS protocol | +| `@dispatch/wire` | `Chunk`/`StoredChunk`(+`seq`)/`ChatMessage`/`AgentEvent`/`TurnSealedEvent`/`Usage`/`StepId` + metrics: `StepMetrics`/`TurnMetrics`, `usage.stepId`, `step-complete`, `done.durationMs`/`done.usage`, `tool-result.durationMs` | +| `@dispatch/transport-contract` | `ChatRequest`/`ModelsResponse`/`ConversationHistoryResponse`/`ConversationMetricsResponse` + WS chat ops + `WsClientMessage`/`WsServerMessage` | -### 3.1 Resolved / answered -- ✅ Wire-types split, per-chunk `seq`, history endpoint, WS chat multiplexing, CORS — all delivered - (backend commit `812621c`). +Endpoints in use (HTTP **24203**, WS **24205**, CORS `*`): +`POST /chat` (NDJSON) · `GET /models` · `GET /conversations/:id?sinceSeq=<n>` · +`GET /conversations/:id/metrics` · WS `chat.send`→`chat.delta`. -### 3.2a Finding — model is NOT persisted/exposed per conversation (FE handled it) -`model` is a per-turn `ChatRequest` field only; `session-orchestrator` resolves it per `handleMessage` -and never stores it, and `conversation-store`/`ConversationHistoryResponse` carry no model. So the FE -**persists the selected model per tab** (localStorage). No action needed. OPTIONAL future nicety: if -you ever persist + expose a per-conversation "last model" (e.g. on the `GET /conversations` list when -it lands), the FE could seed a reopened tab's model from the server instead of localStorage. +Mirrored in-repo for headless agents: `.dispatch/{ui-contract,wire,transport-contract}.reference.md` +(regenerate on any contract bump). -### 3.2 FYI — non-blocking gotcha (no action required unless you publish externally) -- **`workspace:*` breaks external `file:` consumption under bun.** `transport-contract`'s deps are - `@dispatch/ui-contract`/`@dispatch/wire` at `workspace:*`; `bun install` from dispatch-web could not - resolve them ("Workspace dependency not found"). **Worked around FE-side** with a `package.json` - `overrides` block mapping both to their `file:` paths — no backend change needed now. If you ever - publish these to a registry, prefer real semver ranges over `workspace:*` for out-of-monorepo - consumers. +## 2. Open asks FOR THE BACKEND -### 3.3 Pending asks / roadblocks -- _(none open)_ — Slice 2 needed no backend change. One coordination item below (§6). +- _(none open)_ -## 6. LIVE end-to-end probe — DONE ✅ (9/9, against `bin/up`) +## 3. Likely NEXT backend asks (heads-up, not yet requested) -Ran `bun scripts/live-probe.ts` (drives the FE's REAL network-facing stack — `adapters/ws` socket, -`core/chunks` reducer, `conversation-cache` + `adapters/idb`, and the HTTP history endpoint — against -the running backend). **All 9 checks passed:** -- one WS (`:24205`) delivered the surface `catalog` AND the chat stream; -- `chat.send` → ~33 `chat.delta` events (incl. `text-delta`) → folded to the expected assistant text - → `turn-sealed`; -- post-seal `GET :24203/conversations/:id?sinceSeq=0` → 3 seq-monotonic `StoredChunk`s - (`latestSeq=3`); `applyHistory` superseded the provisional turn (`sealedTurnId` cleared); -- IndexedDB cache persisted the sealed turn; committed transcript shows the assistant text. - -**No backend mismatch found — every confirmed invariant (C1–C4) held live.** One FE-internal note -(not a backend matter): the idb adapter relies on the global `IDBKeyRange` (fine in a browser; the -probe needed `fake-indexeddb/auto` to supply it under Bun). - -Also caught + fixed during browser bring-up (FE-only bug, not backend): a BLANK page on plain-HTTP -non-localhost origins (`http://arch-razer:24204`) because `crypto.randomUUID()` is secure-context-only -— now replaced with a `getRandomValues`-based fallback. - -## 4. (history) Slice 2 unit map — delivered, see §1. - -## 5. Likely NEXT backend asks (heads-up, not yet requested) - -These belong to **later** FE slices (design §7 "later slice") — flagged early so they're on your radar: -- `GET /conversations` — conversation list / sidebar (FE history explorer / conversation switcher). +- `GET /conversations` — conversation list / sidebar (history explorer / switcher); could also expose a + per-conversation "last model" so a reopened tab seeds its model from the server instead of localStorage. - `POST /conversations/:id/cancel` — "stop generating". - -When the FE reaches those slices, the concrete request will be filed here in §3.3. |
