From 519b79999d49156bbfaecc91a2d882fba2475fef Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Mon, 22 Jun 2026 13:52:42 +0900 Subject: feat: incremental seq assignment during generation (CR-6) The backend now persists chunks at step boundaries during generation, not only at turn-seal. This enables the FE to syncTail mid-turn and pick up committed, seq'd chunks (eliminating the provisional state). Changes: - RunTurnInput: add onStepComplete callback (kernel contract) - runTurn: call onStepComplete after each step's messages are finalized - Orchestrator: persist userMsg at turn start + each step's messages via onStepComplete. Falls back to batch persist if callback isn't called (backward compatible with test fakes). The user message gets seq numbers before the first step generates. Each step's assistant + tool messages get seq numbers as they complete. The FE's existing syncTail (?sinceSeq=N) picks them up during generation. Also adds backend-to-fe-handoff.md with CR-6 response + full endpoint list. --- packages/kernel/src/runtime/run-turn.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'packages/kernel/src/runtime') diff --git a/packages/kernel/src/runtime/run-turn.ts b/packages/kernel/src/runtime/run-turn.ts index 228ef8a..4a12e28 100644 --- a/packages/kernel/src/runtime/run-turn.ts +++ b/packages/kernel/src/runtime/run-turn.ts @@ -526,6 +526,23 @@ export async function runTurn(input: RunTurnInput): Promise { resultMessages.push(msg); } + // Incremental persistence: notify the caller that this step's + // messages are finalized. The caller can persist them immediately + // (assigning seq numbers during generation). The messages are the + // SAME objects in resultMessages — the caller must NOT double-persist. + if (input.onStepComplete !== undefined) { + const stepMessages: ChatMessage[] = []; + if (stepResult.assistantMessage !== undefined) { + stepMessages.push(stepResult.assistantMessage); + } + for (const msg of stepResult.toolMessages) { + stepMessages.push(msg); + } + if (stepMessages.length > 0) { + await input.onStepComplete(stepMessages); + } + } + if (signal.aborted) { finishReason = "aborted"; break; -- cgit v1.2.3