Repository navigation
feat(server): skip DB write for empty turns via TurnSubstance predicate - #592
Conversation
Buffer the assistant message body behind TurnFinalizer instead of writing it eagerly on the provider Message event. finalize materializes exactly one assistant row only when hasRecordableActivity holds (a buffered tool call, non-empty body, narration segment, or hook); a fully empty turn leaves no hollow row and broadcasts nothing. The plan-output path eagerly materializes the row first because plans.message_id is a NOT NULL FK; materializeAssistantRow is idempotent and the materializedThreads flag keeps the predicate true after the buffered body is consumed. Also clear lastPersistedMessageIdByThread on the empty-turn and materialize-failure paths so a late hook for a turn that persisted no row is discarded rather than mis-attached to the previous turn's message. Closes #578
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis PR defers assistant message row creation from event handling to turn finalization. The ChangesDeferred Assistant-Row Materialization
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
What
Gate assistant-row persistence on a TurnSubstance predicate so empty turns leave no DB row.
Why
A turn that produces no tool call, body, narration, or hook previously still wrote a hollow assistant row and broadcast
turn.persisted. #578 makes "no recordable activity" a first-class contract: such turns persist nothing and broadcast nothing, keeping threads uncluttered.Key changes
turn-finalizer.ts—bufferAssistantBodybuffers the provider body (returning the deterministic row id synchronously) instead of writing eagerly.hasRecordableActivitydecides whether the turn earns a row.finalizematerializes exactly one row viamaterializeAssistantRowonly when the predicate holds; otherwise it clears the turn with no row and no broadcast.agent-service.ts— the providerMessagehandler now buffers the body. The plan-output path eagerly materializes the row first becauseplans.message_idis a NOT NULL FK;materializeAssistantRowis idempotent and thematerializedThreadsflag keeps the predicate true after the buffered body is consumed.narrative-store.ts— newhasBufferedNarrativefeeding the predicate (tool call / narration / hook).lastPersistedMessageIdByThreadis now cleared on the empty-turn and materialize-failure paths, so a late Stop/SessionEnd hook for a turn that persisted no row is discarded rather than mis-attached to the previous turn's message.Behavioral contract changes
Messagenow materializes an assistant row (any buffered tool call is recordable), rather than being discarded.Both are covered by updated/added tests.
Test plan
bun run verify— typecheck + lint + tests, 5/5 packages, 1221 server tests greenTurnFinalizer.hasRecordableActivitypredicate tests (per-contributor + empty + whitespace body)getLastPersistedMessageIdso a late hook can't attach to turn NCloses #578
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Summary by CodeRabbit
Tests
Refactor