Skip to content

feat: migrate v1 state into orchestrator v2#4400

Draft
juliusmarminge wants to merge 1 commit into
t3code/codex-turn-mappingfrom
codex/v1-v2-state-migration
Draft

feat: migrate v1 state into orchestrator v2#4400
juliusmarminge wants to merge 1 commit into
t3code/codex-turn-mappingfrom
codex/v1-v2-state-migration

Conversation

@juliusmarminge

@juliusmarminge juliusmarminge commented Jul 23, 2026

Copy link
Copy Markdown
Member

Summary

  • converge packaged server, desktop, and SSH runtime state paths back to the production userdata directory
  • preserve released migration 33 and renumber the v2-only migrations to 34–41
  • import v1 materialized thread shells synchronously, then hydrate complete transcripts lazily and in bounded background batches
  • hand imported visible history to the first v2 provider turn without trying to migrate v1 provider sessions or checkpoints

Migration behavior

  • startup work is proportional to thread shells plus at most two preview messages per thread; it does not replay the raw v1 event log
  • full transcripts are read from projection_thread_messages after command readiness and on first open/dispatch
  • deterministic IDs and a migration manifest make imports idempotent and retryable after interruption
  • migrated history supports normal viewing and continuation; v1 runs, checkpoints, forks, and tool activity are intentionally not reconstructed, while newly created v2 runs keep the full v2 feature set

Verification

  • 83 focused tests across 14 changed-path test files, plus a final 19-test service regression pass
  • targeted typechecks for server, contracts, desktop, and SSH packages
  • targeted lint, format, and diff checks
  • isolated browser verification that a seeded v1 thread is imported, rendered, and remains interactive

Stacked on #2829.

Note

Migrate v1 thread state into orchestrator v2 with on-demand transcript hydration

  • Adds LegacyV1ThreadImporter service that imports v1 threads into v2 by emitting shell events (thread.created, metadata, preview messages) during startup and full transcripts asynchronously after the server accepts commands.
  • On the first run of a v1-imported thread, Orchestrator calls ContextHandoffService.prepareLegacyImport to generate a manual_context handoff containing a bounded (32,000 char) summary of prior messages.
  • ThreadManagementService wraps getThreadProjection, getThreadSnapshot, and dispatch to call ensureLegacyTranscript before returning data; errors are logged as warnings and do not fail the operation.
  • Adds DB migration 042 (LegacyV1ImportState) creating the orchestration_v2_legacy_imports tracking table, and renumbers migrations 033–041 to make room.
  • Renames non-dev state paths from userdata-v2 to userdata (and t3code-v2t3code) across server, desktop, and SSH tunnel.
  • Risk: existing installations using userdata-v2 paths will not find prior state at the new userdata location without a manual migration or compatibility shim.
📊 Macroscope summarized 0fff2d0. 14 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted

🗂️ Filtered Issues

No issues evaluated.

Co-authored-by: codex <codex@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9a9d160b-7c3e-4a82-bdd0-9d41328c144c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/v1-v2-state-migration

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Jul 23, 2026
Comment on lines +182 to +184
const suffix =
section.length <= remaining ? section : section.slice(section.length - remaining);
selected.unshift(suffix);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium orchestration-v2/ContextHandoffService.ts:182

makeLegacyImportSummary truncates from the left when a section exceeds the remaining character budget, taking a raw suffix via section.slice(section.length - remaining). When this cut lands inside the first retained section, the User: or Assistant: label is dropped and the text can start mid-word, so the imported context begins with unattributed, fragmentary text. Consider preserving the role prefix and truncating only the message body, or omitting the partial section entirely.

Suggested change
const suffix =
section.length <= remaining ? section : section.slice(section.length - remaining);
selected.unshift(suffix);
const suffix =
section.length <= remaining
? section
: section.slice(section.length - remaining);
selected.unshift(suffix);
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ContextHandoffService.ts around lines 182-184:

`makeLegacyImportSummary` truncates from the left when a section exceeds the remaining character budget, taking a raw suffix via `section.slice(section.length - remaining)`. When this cut lands inside the first retained section, the `User:` or `Assistant:` label is dropped and the text can start mid-word, so the imported context begins with unattributed, fragmentary text. Consider preserving the role prefix and truncating only the message body, or omitting the partial section entirely.

const getThreadSnapshot: ThreadManagementServiceShape["getThreadSnapshot"] = (threadId) =>
ensureLegacyTranscript(threadId).pipe(Effect.andThen(orchestrator.getThreadSnapshot(threadId)));

const dispatch: ThreadManagementServiceShape["dispatch"] = (command) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High orchestration-v2/ThreadManagementService.ts:261

The dispatch wrapper only calls ensureLegacyTranscript for message.dispatch, thread.fork, and thread.merge_back. All other commands targeting an existing migrated thread — such as thread.metadata.update, thread.archive, or thread.delete — bypass hydration and dispatch directly. When a later projection read triggers the importer, its transcript marker emits a thread.metadata-updated event from the stale v1 row, overwriting the newer title/archive/deletion metadata. Hydrate every command that operates on an existing thread before dispatching, not just those three types.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ThreadManagementService.ts around line 261:

The `dispatch` wrapper only calls `ensureLegacyTranscript` for `message.dispatch`, `thread.fork`, and `thread.merge_back`. All other commands targeting an existing migrated thread — such as `thread.metadata.update`, `thread.archive`, or `thread.delete` — bypass hydration and dispatch directly. When a later projection read triggers the importer, its transcript marker emits a `thread.metadata-updated` event from the stale v1 row, overwriting the newer title/archive/deletion metadata. Hydrate every command that operates on an existing thread before dispatching, not just those three types.

Comment on lines +242 to +251
const ensureLegacyTranscript = (threadId: ThreadId) =>
legacyImporter.ensureTranscript(threadId).pipe(
Effect.tapError((cause) =>
Effect.logWarning("Unable to hydrate migrated v1 thread transcript", {
threadId,
cause,
}),
),
Effect.ignore,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium orchestration-v2/ThreadManagementService.ts:242

ensureLegacyTranscript swallows all hydration failures with Effect.ignore, so every call site (dispatch, getThreadProjection, getThreadSnapshot) proceeds as if the legacy transcript was loaded even when it wasn't. For message.dispatch this means the provider turn is built from only the shell preview instead of the full legacy conversation — context omitted from that turn cannot be restored by a later retry. Consider letting hydration failures propagate (or retrying) so dispatch does not silently continue with an incomplete transcript.

-  const ensureLegacyTranscript = (threadId: ThreadId) =>
-    legacyImporter.ensureTranscript(threadId).pipe(
-      Effect.tapError((cause) =>
-        Effect.logWarning("Unable to hydrate migrated v1 thread transcript", {
-          threadId,
-          cause,
-        }),
-      ),
-      Effect.ignore,
-    );
+  const ensureLegacyTranscript = (threadId: ThreadId) =>
+    legacyImporter.ensureTranscript(threadId).pipe(
+      Effect.tapError((cause) =>
+        Effect.logWarning("Unable to hydrate migrated v1 thread transcript", {
+          threadId,
+          cause,
+        }),
+      ),
+    );
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @apps/server/src/orchestration-v2/ThreadManagementService.ts around lines 242-251:

`ensureLegacyTranscript` swallows all hydration failures with `Effect.ignore`, so every call site (`dispatch`, `getThreadProjection`, `getThreadSnapshot`) proceeds as if the legacy transcript was loaded even when it wasn't. For `message.dispatch` this means the provider turn is built from only the shell preview instead of the full legacy conversation — context omitted from that turn cannot be restored by a later retry. Consider letting hydration failures propagate (or retrying) so dispatch does not silently continue with an incomplete transcript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant