1# Legacy orchestration migration
2
3Orchestration v2 snapshots `state.sqlite` into `statev2.sqlite` before opening writable persistence
4on its first launch. Only the copy receives v2 migrations; the original remains available to v1.
5Subsequent launches reuse the copy without refreshing it from v1. It creates v2 thread shell events first and imports
6the complete user and assistant transcript lazily when a client reads or continues the thread. The
7v1 projection tables remain the import source and provide a read-only recovery source if an import
8needs investigation.
9
10## Imported data
11
12The shell import preserves the project and thread identifiers, title, provider and model selection,
13runtime and interaction modes, branch, worktree path, creation and update times, archive and delete
14times, settlement override and timestamps, snooze timestamps, pin timestamp and order, and linked
15pull request. The metadata repair path fills snooze, pin order, `unsettledAt`, and linked pull request
16fields for threads imported before those fields were covered.
17
18Transcript import reads user and assistant rows from `projection_thread_messages`. It preserves
19message identifiers, text, supported attachments, timestamps, role, and ordering. A message that was
20still streaming becomes an interrupted turn item.
21
22The importer does not translate provider session identity, native provider runs, checkpoints and
23diffs, activities and tool calls, approvals, or proposed plans. V2 therefore must not present those
24records as migrated history.
25
26## First continuation
27
28A migrated thread has no active provider thread. Its first continuation creates a fresh provider
29session and sends a legacy handoff built only from user and assistant messages. The handoff selects
30the newest transcript suffix within a 32,000-character budget, including section labels and the
31import notice. This budget is separate from portable provider handoffs.
32
33## Client and server cutover
34
35Clients and servers must agree on `ORCHESTRATION_PROTOCOL_VERSION` (currently 2). The client
36runtime appends `orchestrationProtocol=2` to the socket URL, and the `/ws` route rejects a missing
37or mismatched version with HTTP 426 (`orchestration_protocol_incompatible`) before any RPC or auth
38work runs. The client checks the environment descriptor the same way: a missing version means the
39host predates protocol 2, and a different version means both sides need updating. Either direction
40blocks the connection as `unsupported` with a message naming the machine to update rather than
41running half-upgraded. See `packages/client-runtime/src/connection/compatibility.ts` and
42`apps/server/src/ws.ts`.
43
44## Divergent migration ids
45
46`effect_sql_migrations` records `migration_id` and `name`, but the migrator compares ids only:
47rows at or below the recorded maximum are skipped without checking names. A database that ran a
48local or fork migration under an id this build later assigns to a different migration therefore
49never receives this build's migration at that id. After repairing supported V2 preview ledgers,
50[`runMigrations`](../../apps/server/src/persistence/Migrations.ts) rejects history that would skip a
51requested migration at id 55 or later without its expected name. Earlier name clashes remain
52warnings for compatibility with site-local migrations. There is no safe id range for a fork inside
53this ledger: any id at or below a future upstream id masks it forever, so fork schema changes belong
54in a separate migration table or outside the migrator entirely.
55
56## Recovery
57
58There is no supported whole-thread export API. Recovery uses an untouched copy of the environment's
59`userdata` directory and opens that copy with SQLite's read-only mode. The user guide documents the
60queries against `projection_threads` and `projection_thread_messages`. Never start a server against
61the recovery copy because startup can run migrations and write new state.