Repository navigation
Stream clock for Prism: exact-second worker liveness from every token, plus prism_cancel #55
Copy link
Copy link
Closed
Description
Outcome
Prism knows the exact moment a worker thread's model stops streaming, from every token T3 receives (text, reasoning or thinking, tool output). Prism can also cancel a job. Model Router uses both to detect stale turns with near certainty and never kill a working model (toolboxmd/model-router#132).
Why (evidence, 2026-09-27)
- T3 receives every provider token delta in memory (
OpenCodeAdaptermessage.part.deltabecomescontent.delta; Claudestream_eventdeltas andsystem/thinking_tokens; Codexitem/*Delta). But it exposes them only as ~100-char reasoning or assistant message segments.EventNdjsonLoggerdeliberately drops deltas. - Router therefore measures silence from segment timestamps with a fixed 60 s window. On job
ao-27-t3-attribution-2, the healthy Muse worker streamed reasoning segments every 5-15 s (max gap ~18 s). The dead turn's last segment was at 18:01:22 and nothing followed, yet Router only acted at 18:03:07. An 87-char segment that never closed is invisible at segment granularity. - Prism has no cancel tool. Router's CLI cancel cannot reach T3 without Prism's token, so cancelled T3 jobs stay
cancelling(model-router#132, bug 3).
Elon record
- Wanted result: exact-second staleness for every provider, measured where the tokens already arrive, plus a working cancel.
- Cut: a separate watchdog process, Router reading provider databases, fixed timeouts, logging every delta to disk.
- Smallest surviving solution: an in-memory per-thread stream clock in the fork (new files), exposed through Prism, plus per-turn gap statistics that make thresholds measurable.
Acceptance criteria
- Per thread, the server keeps
lastStreamAt(the timestamp of the latest provider event of any kind, including every token delta, reasoning delta, Claudethinking_tokens, and tool output delta) andopenTool(a tool call in progress, with its start time). Both are in memory only. No per-delta persistence. - Prism exposes them per thread (for example
GET /api/prism/liveness?threadId=, or in the existing Prism snapshot) with the server'snow, so the caller computes silence without clock skew. - At each turn end, the server records one activity with the turn's stream statistics: time to first token, max inter-event gap while no tool was open, event count, provider, model and outcome (completed, aborted, error). This is the measurement source for thresholds.
- A
prism_cancelMCP tool runs Routercanceland thenrecoverwith Prism's token for a job this thread owns, and returns the final state. - New files first. Any upstream edit is allowlisted and mapped in
docs/fork-features.md. - Handler tests with fake provider streams, and a live check on a Chromeria build:
lastStreamAtadvances during a long reasoning turn and stops when the turn is aborted.
Non-goals
Stall decisions in T3 (Router owns policy and recovery), and changing upstream usage or logging.
Blockers
None. Installing the new Chromeria build on this Mac is the user's step.
Direction
This serves the current Objective, "bring Model Router into it". Reliable T3 jobs are part of Router jobs running as T3 threads.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels
Activity
docs(fork): map the Prism stream clock feature to its PR (#55)
fix(server): build the tapped event loggers on the upstream layer; dr…
lukemaj commentedon Sep 27, 2026
Implementation is in PR #56. It is not merged.
feat/55-stream-clock89dce174bded3109b97f0ff84dcf0fff88da920cDesign (Elon record applied). No adapter edits and no per-delta persistence. Every adapter already writes each native event (every token, reasoning and tool output delta, Claude
thinking_tokens, Codexitem/*Delta, OpenCodemessage.part.delta) to the shared native event logger. A new in-memoryStreamClockwraps that logger and the canonical logger through one layer swap inserver.ts.GET /api/prism/liveness?threadId=returnsnow,lastStreamAt,silenceMs,openTooland the running turn. Each turn end records one hiddenprism.stream-statsactivity.prism_cancelrefuses a job whoseplanner_t3_threadis not the calling thread, then runs Routercancelandrecoverwith a Prism token and returns the final status. Upstream edits: a one-line work-log filter each in websession-logic.tsand mobilethreadActivity.ts(allowlisted and mapped), plus wiring in the already-forkedserver.tsandMcpHttpServer.ts.Proof
src/prism,src/mcp,server.test.ts,ProviderService.test.tsandentrypoint.test.tson the server; websession-logic; mobilethreadActivity. Fake provider streams instreamClock.test.tscover native-only deltas, tool silence left out of the max gap, abort and error outcomes, and logging off.handlers.test.tscovers the cancel sequence and the refusal for another thread's job.vpr typecheck(all 15 packages),vp check(0 errors),knip:check: pass locally.shikiReviewHighlightertest failed once, passed on rerun, and passes locally. It is unrelated to this PR.scripts/fork-check.shfails only on the stack size:mainalready has 20 commits on upstream, and 20 is the limit. With--max-commits 21it printsfork-features: OK (17 features)andfork-check: OK. Any further fork PR hits this limit, so the limit needs raising or the stack needs consolidating. That is a fork-maintenance decision for you.Independent review. A reviewer child thread (Codex, gpt-6-luna) reviewed
89dce17and returned APPROVE with no blockers or majors and one minor: the clock's per-thread map never evicts entries. I left that as is, because each thread's entry is a few numbers, and evicting it would lose the last known state that Router reads. On acceptance, the reviewer marked everything met except the tests-plus-live-check line, which is partial because the live check is pending.feat(server): Prism stream clock for worker liveness, plus prism_cancel
docs(fork): map the Prism stream clock feature to its PR (#55)
fix(server): build the tapped event loggers on the upstream layer; dr…
feat(server): Prism stream clock for worker liveness, plus prism_canc…