-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Persist messages incrementally to prevent context loss on cancellation #7827
Description
Please explain the motivation behind the feature request.
Goose persists session messages to SQLite via add_message, a per-message INSERT. The agent loop in reply_internal collects messages into a messages_to_add vector across 8 push sites (assistant response, thinking, tool requests, tool responses, summaries).
Today, these are flushed at the end of each iteration so that on the next turn the model sees the full conversation history.
The problem is mid-iteration cancellation, such as Ctrl+C in the CLI, or Stop in the UI, resulting in CancellationToken that breaks the loop.
Concretely, say the model responds with 3 tool calls that all execute successfully, but the user presses Stop during the third. All three tool request + response pairs are in messages_to_add but never reach add_message. On resume, the session shows only the user's prompt with no evidence the tools ran.
Describe the solution you'd like
Persist each message as it completes rather than batching at end-of-iteration. This eliminates the window between collection and persistence so no completed message can be lost. The cost is negligible: add_message is a single SQLite INSERT, and the current flush already loops per-message rather than doing a bulk insert.
Describe alternatives you've considered
A blunt replace_conversation() on every cancellation/disconnect path. Problems:
replace_conversationdoes a full DELETE + INSERT of all messages, too destructive for a transient event- In the server, the local
all_messagestracker can be stale relative to what the agent already persisted viaupdate_message_metadata(tool-pair summarization invisibility flags), so overwriting can clobber metadata - In the CLI,
handle_interrupted_messagesadds synthetic tool error responses toself.messagesbefore any persistence; areplace_conversationafter that writes those synthetic messages to the DB as if the model produced them - It fires on client disconnect (transient network events), not just intentional cancellation
replace_conversation is correctly reserved for intentional operations: compaction, /clear, and import_session.
Additional context
-
Codex takes this approach: each response item is persisted via
record_completed_response_itemas it completes. On interrupt, it appends a singleturn_abortedmarker and callsflush_rolloutbefore emitting the abort event. -
Cline also does incremental persistence: each message operation immediately calls
saveClineMessagesAndUpdateHistory(). -
I have verified this does not duplicate an existing feature request