Skip to content

Commit 463fad6

Browse files
🤖 fix: prevent queued message amnesia by flushing partial before tool-call-end (#807)
## Problem When a queued message is sent after `tool-call-end`, the model doesn't see the complete assistant response including tool results. This causes "amnesia" where the model starts tasks from scratch. ### Root Cause Race condition in partial write timing: ``` Tool completes → emit(tool-call-end) → schedulePartialWrite (fire-and-forget) ↓ sendQueuedMessages() → commitToHistory() → reads STALE partial.json ``` The `schedulePartialWrite` was called with `void` (fire-and-forget). When `sendQueuedMessages` triggers `commitToHistory`, the partial file may not contain the latest tool results. ## Solution Await the partial flush **before** emitting `tool-call-end`: ``` Tool completes → flushPartialWrite (await) → emit(tool-call-end) ↓ sendQueuedMessages() → commitToHistory() → reads COMPLETE partial ``` ## Changes - Make `completeToolCall` async - Await `flushPartialWrite` before emitting `tool-call-end` - Update callers to await the async method --- _Generated with `mux`_
1 parent 96732fd commit 463fad6

File tree

1 file changed

+16
-11
lines changed

1 file changed

+16
-11
lines changed

‎src/node/services/streamManager.ts‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -566,9 +566,11 @@ export class StreamManager extends EventEmitter {
566566
}
567567

568568
/**
569-
* Complete a tool call by updating its part and emitting tool-call-end event
569+
* Complete a tool call by updating its part and emitting tool-call-end event.
570+
* CRITICAL: Flushes partial to disk BEFORE emitting event to prevent race conditions
571+
* where listeners (e.g., sendQueuedMessages) read stale partial data.
570572
*/
571-
private completeToolCall(
573+
private async completeToolCall(
572574
workspaceId: WorkspaceId,
573575
streamInfo: WorkspaceStreamInfo,
574576
toolCalls: Map<
@@ -578,7 +580,7 @@ export class StreamManager extends EventEmitter {
578580
toolCallId: string,
579581
toolName: string,
580582
output: unknown
581-
): void {
583+
): Promise<void> {
582584
// Find and update the existing tool part
583585
const existingPartIndex = streamInfo.parts.findIndex(
584586
(p) => p.type === "dynamic-tool" && p.toolCallId === toolCallId
@@ -609,7 +611,13 @@ export class StreamManager extends EventEmitter {
609611
}
610612
}
611613

612-
// Emit tool-call-end event
614+
// CRITICAL: Flush partial to disk BEFORE emitting event
615+
// This ensures listeners (like sendQueuedMessages) see the tool result when they
616+
// read partial.json via commitToHistory. Without this await, there's a race condition
617+
// where the partial is read before the tool result is written, causing "amnesia".
618+
await this.flushPartialWrite(workspaceId, streamInfo);
619+
620+
// Emit tool-call-end event (listeners can now safely read partial)
613621
this.emit("tool-call-end", {
614622
type: "tool-call-end",
615623
workspaceId: workspaceId as string,
@@ -618,9 +626,6 @@ export class StreamManager extends EventEmitter {
618626
toolName,
619627
result: output,
620628
} as ToolCallEndEvent);
621-
622-
// Schedule partial write
623-
void this.schedulePartialWrite(workspaceId, streamInfo);
624629
}
625630

626631
/**
@@ -762,8 +767,8 @@ export class StreamManager extends EventEmitter {
762767
const strippedOutput = stripEncryptedContent(part.output);
763768
toolCall.output = strippedOutput;
764769

765-
// Use shared completion logic
766-
this.completeToolCall(
770+
// Use shared completion logic (await to ensure partial is flushed before event)
771+
await this.completeToolCall(
767772
workspaceId,
768773
streamInfo,
769774
toolCalls,
@@ -799,8 +804,8 @@ export class StreamManager extends EventEmitter {
799804
: JSON.stringify(toolErrorPart.error),
800805
};
801806

802-
// Use shared completion logic
803-
this.completeToolCall(
807+
// Use shared completion logic (await to ensure partial is flushed before event)
808+
await this.completeToolCall(
804809
workspaceId,
805810
streamInfo,
806811
toolCalls,

0 commit comments

Comments
 (0)