refactor(threads): strip dead v1 message writes + migrate async-research stub to v2#4037
Open
tlgimenes wants to merge 1 commit into
Open
refactor(threads): strip dead v1 message writes + migrate async-research stub to v2#4037tlgimenes wants to merge 1 commit into
tlgimenes wants to merge 1 commit into
Conversation
…earch stub to v2 Two changes toward removing v1 (`thread_messages`) writes now that v2 (`thread_message_parts`, stream-of-record) is the only live write path: A. Delete the dead v1 write path. `SqlThreadStorage.saveMessages()` and its sole (uncalled) caller `Memory.save()` were vestigial after the Phase C cutover. Removes: `Memory.save`, the `saveMessages` port method + impl + org-scoped wrapper, and the now-entirely-dead thread asset-hoisting proxy (its only real interception was `saveMessages`). Trims stale doc-comments referencing the removed `saveMessagesToThread`. No runtime behavior change. B. Migrate the one remaining live v1 writer — the deep-research polling stub in `AsyncResearchJobs.markPolling()`. It wrote a v1 stub assistant message to `thread_messages` unconditionally, but v2 threads read from `thread_message_parts`, so the stub (which seeds refresh-recovery during the long async poll) was invisible on v2 threads. Now version-gated: v2 threads get a `tool_call` part + `finish` anchor written to `thread_message_parts` (so loadWindow surfaces it); legacy v1 threads keep the `thread_messages` stub. Stub cleanup (terminal transitions + abandoned sweep) now clears both tables. Adds an integration test covering v2 seed/cleanup and the v1 path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Removes/migrates v1 (
thread_messages) message writes now that v2 (thread_message_parts, append-only stream-of-record) is the only live write path. Two parts:A — delete the dead v1 write path (no behavior change)
After the Phase C cutover,
SqlThreadStorage.saveMessages()was reachable only viaMemory.save(), which has zero callers. Removed:Memory.save()(api/routes/decopilot/memory.ts)saveMessagesfrom the storage port + impl + org-scoped wrapper (storage/ports.ts,storage/threads.ts)object-storage/asset-hoister.ts) — its only real interception wassaveMessages(kept connection/virtual-mcp hoisting)saveMessages (upsert)test block + inertsaveMessagestest mockssaveMessagesToThread(harness packages,build-stream-request.ts,context-factory.ts)v1 read paths (
listMessages,listWithLastMessage, the v1 branches inmemory.ts/list-messages.ts) are untouched — legacy v1 threads still render.B — migrate the one live v1 writer (deep-research stub)
AsyncResearchJobs.markPolling()wrote a stub assistant message tothread_messagesunconditionally so a browser refresh mid-poll can reconstruct the in-flight tool call. But v2 threads readthread_message_parts, so on v2 the stub went to a table nobody reads (refresh-recovery silently broken:tool-input-availableparts are non-final → never persisted by the projector, and v2loadWindowonly surfaces messages with afinishanchor).Now version-gated:
tool_callpart +finishanchor intothread_message_parts(run_id == threadId, deterministic ids, idempotent) soloadWindowsurfaces itthread_messagesstubdeleteStubMessageon terminal transitions +sweepAbandoned) now clears both tables (best-effort)Testing
bun run check— passes across all workspacesbun run lint— 0 errors (3 pre-existing warnings in untouched files)storage/async-research-jobs.integration.test.ts(real PG): v2markPollingseeds a folded, loadable assistant message with thetool-web_searchinput-available part;markCompletedremoves it; v1 path still writesthread_messages.Follow-ups (not in this PR)
listWithLastMessage("Suggested actions") readsthread_messagesonly — has no v2 equivalent (latent v2 gap).thread_messages → thread_message_partsbackfill.🤖 Generated with Claude Code
Summary by cubic
Removes dead v1 message writes and migrates the async-research polling stub to v2
thread_message_parts, fixing refresh recovery on v2 threads and simplifying storage.Refactors
Memory.save()andThreadStoragePort.saveMessages(and all impls/wrappers).connectionsandvirtualMcpsonly.saveMessages/inline persistence.Bug Fixes
AsyncResearchJobs.markPolling()now seeds a v2 stub intothread_message_partswith atool_callpart andfinishanchor; v1 threads unchanged.thread_messagesandthread_message_parts.Written for commit 9f6dd95. Summary will update on new commits.