feat(chat-routes): let authorize suppress the user-row insert for a dispatched turn#207
Conversation
…ispatched turn Add an optional insertUserMessage?: boolean to the ok:true branch of ChatTurnAuthorization. When false, the engine skips the role:'user' appendMessage for a product-dispatched / synthetic turn (e.g. a plan follow-up the product raised itself) that must not surface a new user row. AND-composed with the engine's retry-dedup so it can only subtract, never resurrect a turn already deduped as a retry. Omitting the field reproduces today's behavior exactly.
tangletools
left a comment
There was a problem hiding this comment.
🟢 Value Audit — sound
| Verdict | sound |
| Concerns | 1 (1 weak-concern) |
| Heuristic | 0.0s |
| Duplication | 0.0s |
| Interrogation | 259.5s (2 bridge agents) |
| Total | 259.5s |
💰 Value — sound
Adds an optional insertUserMessage flag on authorize's ok-branch that lets a product suppress the user-row insert for a dispatched/synthetic turn; minimal, idiomatic, AND-composed with the engine's existing retry dedup — ship.
- What it does: Adds an optional
insertUserMessage?: booleanfield to theok:truebranch ofChatTurnAuthorization<TContext>(src/chat-routes/turn-routes.ts:117-131). At the user-insert site (src/chat-routes/turn-routes.ts:583-591) the engine now computeschatTurn.shouldInsertUserMessage && (auth.insertUserMessage ?? true)and skipsstore.appendMessage({role:'user', ...})when that's false. The assistant - Goals it achieves: Lets a product run a turn through the assembled chat vertical WITHOUT surfacing a new user bubble — the canonical case is a product-dispatched follow-up (e.g. the user clicks Approve on a plan and the product raises the next turn itself). Today the engine unconditionally inserts a
role:'user'row before any product seam runs (the insert sits in thetryblock beforecontextGate/beforeTurn), - Assessment: Coherent and in the grain. (1) It matches the exact precedent of the other single-consumer
@experimentalseams (turnLock/contextGate/beforeTurn/onRawEventfrom #200) — one optional typed field, no new lifecycle position, additive-only. (2) The AND-composition is the right design:authorizeruns at src/chat-routes/turn-routes.ts:464 BEFOREresolveChatTurnat :476, soauthorizegenui - Better / existing approach: none — this is the right approach. Searched: (a)
shouldInsertUserMessageis only in src/stream/turn-identity.ts:12,54,62 — the engine's own dedup signal, which this change composes with rather than replaces; (b) no existing seam can suppress the insert —beforeTurn(src/chat-routes/turn-routes.ts:605) runs AFTER the insert,contextGate(:596) also after,turnLock(:529) acquires before but - 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 minimal, backward-compatible seam on the existing authorize return that lets a product suppress the user-row insert for a dispatched/synthetic turn; AND-composes with the engine's retry-dedup so it can only subtract, never resurrect.
- Integration: Wired directly at the insert site —
src/chat-routes/turn-routes.ts:583-591— exactly one line of new logic plus the optional field onChatTurnAuthorizationatsrc/chat-routes/turn-routes.ts:122-130. Reachable today: the reference consumer docexamples/chat-app.md:58-70was updated in this same PR to demonstrate the concrete usage (body?.planFollowUp ? { insertUserMessage: false }), and t - Fit with existing patterns: Matches the established single-consumer
@experimentalseam precedent exactly (turnLock/contextGate/beforeTurn/onRawEvent, #200 #202, documented atsrc/chat-routes/turn-routes.ts:33-39): one optional field on a type products already implement, no new lifecycle position, additive-only. Placement onauthorize's ok-branch is the right spot and not a competitor to existing seams — `before - Real-world viability: Holds up under the relevant edges. AND-composition (
chatTurn.shouldInsertUserMessage && (auth.insertUserMessage ?? true)atsrc/chat-routes/turn-routes.ts:583) is the correct primitive becauseauthorizeruns beforeresolveChatTurn(src/chat-routes/turn-routes.ts:464vs:476), so it cannot distinguish retries from fresh turns — the field can only subtract. Backward compat is exact: omit - Model: opencode/zai-coding-plan/glm-5.2
- Bridge attempts: 1
💰 Value Audit
🟡 Behavior hint on the authorize seam is a mild semantic stretch [maintenance] ``
authorizeis conceptually about access (who is allowed to do this turn), and now also carries a persistence-behavior hint (insertUserMessage). In practice this is fine —authorizealready returnscontext/tenantId/userId, so it's effectively 'the product's per-turn preamble to the engine', not pure auth. But if a second behavior hint of this kind shows up later, consider grouping these as a dedicatedturnOptions?field on the ok-branch rather than letting them accumulate top-level.
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.
What
Adds an optional
insertUserMessage?: booleanto theok:truebranch ofChatTurnAuthorization. When a product'sauthorizereturnsinsertUserMessage: false, the turn engine skips therole:'user'store.appendMessagefor that turn.Why
The turn engine owns the user-row insert (before any product seam runs), guarded only by its own retry-dedup (
resolveChatTurn.shouldInsertUserMessage). A product-dispatched / synthetic turn — e.g. a plan follow-up the product raises itself when the user clicks "Approve", not something they typed — must run the turn (attach a producer, stream, persist the assistant row) without surfacing a new user bubble. There is currently no seam for that; the store adapter can't suppress it because the insert happens beforebeforeTurn.This is the minimal, idiomatic addition matching the
turnLockseam precedent (#200): one optional@experimentalfield on a type products already implement, no new lifecycle position.Design
chatTurn.shouldInsertUserMessage && (auth.insertUserMessage ?? true).authorizeruns before turn-identity resolves, so it cannot tell a retry from a fresh turn — the field may only subtract the insert, never resurrect a turn the engine already deduped. A strayinsertUserMessage: truetherefore can't force a double-insert.undefined ?? true→ collapses to today's exact behavior. No existing consumer or test is affected;turnIndex/priorMessages/produce/turnLock/contextGate/beforeTurnare untouched, andresolveChatTurnneeds no change.Changes
src/chat-routes/turn-routes.ts— the optional field + the one-line AND-composition at the insert site.tests/chat-routes/turn-routes.test.ts— two tests: (1)insertUserMessage:falsesuppresses the user row but still runsproduceand persists the assistant row; (2)insertUserMessage:truecannot resurrect a deduped retry.examples/chat-app.md—authorizenote showing the marker-driveninsertUserMessage: false.Verification
pnpm typecheck— clean.pnpm test— chat-routes turn-routes suite 23/23; full suite 2514 pass. (Pre-existingsrc/sandbox/index.test.tsfailures are unrelated — they source a local~/.bash_profilealias and fail onmaintoo.)pnpm build— emitsinsertUserMessage?: booleanintodist/chat-routes/index.d.ts.Consumer
gtm's chat vertical will return
insertUserMessage: falsefromauthorizewhen the request body carries a durableplanFollowUpmarker, letting the follow-up attach to its already-dispatched sidecar execution without a synthetic user row.