Skip to content

docs: one gateway path for live viewing, durable rows for history#252

Merged
drewstone merged 1 commit into
mainfrom
docs/one-gateway-path
Jul 25, 2026
Merged

docs: one gateway path for live viewing, durable rows for history#252
drewstone merged 1 commit into
mainfrom
docs/one-gateway-path

Conversation

@drewstone

Copy link
Copy Markdown
Contributor

Records the streaming model that the live 4-arm A/B proved, and pins the one property the docs now claim.

The model

Job Mechanism Owner
Watch a turn live; reconnect within minutes orchestrator session gateway (Redis sorted set per viewer connection, WS fanout, lastEventId replay) platform
Read a turn after the hot buffer expires the durable assistant row, written incrementally (#250) agent-app /chat-routes
Stop two drivers running the same turn createDurableTurnLock (/turn-stream) agent-app — the gateway has no equivalent

No Durable Object for sandbox live-viewing. The hot buffer stays short by design: its cost is TTL x viewers x bytes-per-event — measured 814 B/event, 27.3 GiB at 10k concurrent watched sandboxes, and 198 GiB at the long-answer end because every part update re-sends the whole accumulated part (79.7% snapshot redundancy). Raising bufferTtlMs buys history at permanent linear memory cost. It is the failure mode, not the fix.

What is NOT claimed as done

agent-dev-container #4114 (run/stream lane publishes to the session bus) is merged to develop, not on main. Production sandbox.tangle.tools still delivers 0 turn events on the detach lane. The docs say merged-not-deployed.

/turn-stream is not retired

Its broadcast/replay half is superseded by the gateway for sandbox turns. createDurableTurnLock is not — the gateway's only lock is SubscriptionMutex in sse-bridge.ts, an in-process per-instance guard that goes silent across instances. gtm-agent's SessionStreamDO also uses the base for product seams (Vault endpoints, interaction rendezvous, persistence queue) that have no gateway analogue.

New guard test

tests/chat-routes/lane-portability.test.ts pins createSandboxChatProducer at 1.000x streamed and persisted across eight lane shapes. The lanes disagree on text semantics — run/stream sends deltas, the message lane re-sends the whole accumulated part — and incremental persistence writes the draft before the terminal result receipt, so the pre-receipt projection must be correct on its own rather than relying on the receipt to rescue it.

Proven capable of failing: removing the snapshot reconciliation in textDelta inflates a five-update turn to 3.133x and the snapshot probes fail, while the delta probes stay green.

DELTA+ID         streamed=30 persisted=30 true=30  ratio=1.000
SNAPSHOT+ID      streamed=94 persisted=94 true=30  ratio=3.133   <- reconciliation removed
SNAPSHOT no-id   streamed=94 persisted=94 true=30  ratio=3.133   <- reconciliation removed

Verification

check result
pnpm typecheck exit 0
pnpm test 3170 passed / 1 skipped, 195 files, exit 0 (baseline on main: 3162 / 194)
agent-docs --check docs are fresh, exit 0
merges into origin/main clean (git merge-tree)

Records the measured streaming model and pins the property the docs claim.

Invariant 6 gains a "Live viewing vs history" section: the platform session
gateway is the only live-viewing mechanism, the durable assistant row is the
only history mechanism, and the gateway's hot buffer stays short BY DESIGN
because its cost is TTL x viewers x bytes-per-event (measured 814 B/event,
27.3 GiB at 10k concurrent watched sandboxes, swinging to 198 GiB purely on
answer length). Raising bufferTtlMs is the failure mode, not the fix.

Both platform lanes now fan out to the session bus, but only on
agent-dev-container's develop -- #4114 is not on main, so production still
delivers 0 turn events on the run/stream (detach) lane. Documented as
merged-not-deployed rather than done.

/turn-stream is NOT retired. Its broadcast/replay half is superseded by the
gateway for sandbox turns, but createDurableTurnLock is single-flight mutual
exclusion the gateway does not provide -- the gateway's only lock is
SubscriptionMutex, an in-process per-instance guard that goes silent across
instances.

/stream gets its own row, correcting export names and marking the turn buffer
as the sandbox-free lane only.

tests/chat-routes/lane-portability.test.ts pins createSandboxChatProducer at
1.000x streamed and persisted across eight lane shapes. The two lanes disagree
on text semantics -- run/stream sends deltas, the message lane re-sends the
whole accumulated part -- and incremental persistence writes the draft before
the terminal receipt, so the pre-receipt projection has to be right on its own.
Proven failable: removing the reconciliation in textDelta inflates a
five-update turn to 3.133x and the probes fail.

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Auto-approved drewstone PR — ed5f6eda

This PR was opened by the trusted drewstone account.
The full PR reviewer audit still runs separately and will publish findings if it detects issues.

tangletools · auto-approval · reason: drewstone_author · 2026-07-25T02:17:55Z

@tangletools tangletools left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Value Audit — sound

Verdict sound
Concerns 0 (none)
Heuristic 0.0s
Duplication 0.0s
Interrogation 133.9s (2 bridge agents)
Total 133.9s

💰 Value — sound

Docs correct a real live-vs-history conflation and add a focused regression test pinning the pre-receipt draft projection that incremental persistence (#250) made load-bearing; no duplication or better approach found.

  • What it does: Three edits: (a) AGENTS.md invariant 6 gains a 'Live viewing vs history' section — live viewing = platform session gateway, history = durable assistant row written incrementally (#250), single-flight lock = createDurableTurnLock; documents the measured hot-buffer cost (814 B/event, 27.3 GiB at 10k) and that raising bufferTtlMs is the failure mode; flags agent-dev-container #4114 as merged-to-devel
  • Goals it achieves: 1) Stop products/fleet apps from hand-rolling a Durable Object to broadcast sandbox turns by recording that the gateway owns live viewing and the durable row owns history. 2) Pin the exact invariant the docs now claim (1.000x across both lanes, pre-receipt), so the textDelta reconciliation can't silently regress and inflate a durable row 3.133x without a failing test. 3) Correct a prior doc that c
  • Assessment: Good change, in the grain of the codebase. The docs fix a real conflation: the old ARCHITECTURE.md row ('Durable turn replay + live-viewer fanout + the single-flight turn lock') pointed everything at turn-stream, which is wrong now that the gateway and incremental persistence exist. The split into three rows is accurate and matches the code (textDelta reconciliation at sandbox-producer.ts:122, dra
  • Better / existing approach: none — this is the right approach. Checked for duplication: the existing tests/chat-routes/sandbox-producer.test.ts (797 lines) broadly tests producer behavior (tools, usage, reasoning, severance, interactions) but only touches delta-vs-snapshot once, with a receipt, for one part id. The pre-receipt draft invariant — the one the docs now claim and that #250 made critical — is not asserted anywhere
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 2
  • Bridge warning: opencode/kimi-for-coding/k2p7: bridge stream ended without value-audit content

🎯 Usefulness — sound

A focused guard test plus accurate docs that pin a real, load-bearing property of the production chat producer — lane-portable text projection across the two platform lanes.

  • Integration: The test exercises createSandboxChatProducer, which is the actual producer seam for createChatTurnRoutes (src/chat-routes/turn-routes.ts:140) and runDetachedTurn (src/chat-routes/detached-turn.ts:319), and the documented reference producer in examples/chat-app.md:84. It is deeply reached in production, not orphaned. The test's synthetic event shape ({type:'message.part.updated', data:{part, delta}
  • Fit with existing patterns: Follows the repo's established pattern of focused guard tests for documented invariants (e.g. tests/theme/tokens-contract.test.ts pins the theme contract the docs claim). It complements, not duplicates, the existing 30 KB sandbox-producer.test.ts by framing cases by platform lane (run/stream deltas vs message-lane snapshots) rather than by producer feature. The docs section it pins (AGENTS.md 'Liv
  • Real-world viability: The 8 cases cover both lane semantics (delta vs snapshot), with/without part id, with/without terminal receipt, whole-message echo, and a transcript split across two part ids — and a dedicated test asserts the pre-receipt draft projection is already correct, which is precisely the hazard #250 incremental persistence creates (the durable row is written before the terminal result). The reconciliatio
  • Model: opencode/zai-coding-plan/glm-5.2
  • Bridge attempts: 1

No concerns — sound change, no better or existing approach found. ✅


What this audit checks

It judges the change on its merits — not whether it was tasked out in an issue. Unticketed, fast-moving work is fine; the question is whether the change is good and whether a better or existing approach should be used instead.

Pass What it asks
Heuristic Vague title? Whitespace-only or cruft-bearing diff? (content signals only)
Duplication Do added function/class names already exist elsewhere in the repo?
Value Audit What does it do? What goal does it achieve? Is it good? Better architecture or already-exists?
Usefulness Audit Does it integrate and fit? Will it hold up in real use and actually get used?

Findings are concerns, not blocks — the human reviewer decides what to do with them.

value-audit · 20260725T022156Z

@drewstone
drewstone merged commit 4457f41 into main Jul 25, 2026
1 check passed
@drewstone
drewstone deleted the docs/one-gateway-path branch July 25, 2026 02:24
@drewstone

Copy link
Copy Markdown
Contributor Author

Overlap heads-up from #253 (chore/retire-turn-stream-streaming) — same topic, complementary halves, and we both edit AGENTS.md + ARCHITECTURE.md, so whichever lands second will conflict.

#253 is the code-side half you don't have: @deprecated jsdoc on the 7 exports that make up the interactive rebroadcast (broadcastTurnStreamEvent, createSegmentStore/appendSegmentEvent/replayActiveSegment, SegmentStore/TurnSegment/MAX_SEGMENT_EVENTS), with the marker in the first jsdoc sentence so it renders in docs/api/turn-stream.md and reaches the shipped .d.ts. Nothing removed. It agrees with your verdict exactly: broadcast/replay superseded for sandbox turns, createDurableTurnLock not.

One thing I took from you, verified independently: adc #4114 is merged to develop at 2026-07-25T01:54Z with base develop, not main. #253 originally wrote the detached-lane gap as permanent; it now says production-today and names the pending fix in AGENTS.md, examples/resumable-turns.md, and three jsdoc headers.

One place I'd push back: your /stream row says the turn buffer is "the SANDBOX-FREE lane only". Until #4114 ships, a detached sandbox run a browser must tail has no gateway path either, which is the second real niche (runDetachedTurn) — otherwise that row reads as forbidding the one mechanism that works today.

Merge order: yours is ahead in the CI queue. If it lands first I'll rebase #253, drop my ### The two sandbox streaming lanes (measured) section, and fold its unique content — the message-lane wiring, the frame mapping that made the old lane render blank, and the streamPrompt('', {executionId,lastEventId}) zero-loss resume numbers — into your Live viewing vs history, so there is one section, not two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants