| 1 | # Session Recovery and Parallel Work |
| 2 | |
| 3 | Reasonix keeps transcript persistence and workspace mutation as separate safety |
| 4 | boundaries. Read-only work and non-overlapping file claims can run concurrently; |
| 5 | opaque writers such as unrestricted shell or unknown MCP mutations retain the |
| 6 | workspace write lease. Git worktrees provide an isolated checkout when a task |
| 7 | needs an independent workspace. |
| 8 | |
| 9 | ## Session versions |
| 10 | |
| 11 | A format-2 session log holds every version of one conversation as a head of |
| 12 | its append-only DAG (see [`SESSION_OWNERSHIP.md`](SESSION_OWNERSHIP.md)). |
| 13 | Heads have kinds: |
| 14 | |
| 15 | - `main` is the line the log started with. |
| 16 | - `fork` and `rewind` come from user actions: fork-from-here, `/branch`, and |
| 17 | conversation rewind. |
| 18 | - `concurrent` is created by a save that found another writer on the same |
| 19 | chain. |
| 20 | |
| 21 | One head is *selected*; opening the session opens it. Heads are listed, |
| 22 | switched, renamed, and retired inside the log. Nothing creates a second |
| 23 | session file, and a head that was retired keeps its bytes until a single writer |
| 24 | rotates the log. |
| 25 | |
| 26 | Format-1 transcripts (saved before Reasonix 1.39.0 and not yet upgraded) keep |
| 27 | their version identity in branch metadata: |
| 28 | |
| 29 | - `normal` is an ordinary conversation transcript. |
| 30 | - `recovery` preserves local content after a transcript save conflict, file-lock |
| 31 | timeout, or external removal. |
| 32 | - `subagent` is reserved for a session-backed child run. |
| 33 | |
| 34 | Older sidecars remain readable. A sidecar with `Recovered=true` is interpreted as |
| 35 | a recovery version when the explicit version field is absent. Recovery metadata |
| 36 | records the parent conversation/version and the base and disk revisions |
| 37 | observed at the conflict; recovery copies stay in the same logical conversation |
| 38 | lineage and are not treated as ordinary conversations or subagents. |
| 39 | |
| 40 | ## Recovery lifecycle |
| 41 | |
| 42 | A format-2 save never conflicts. Another writer's appends are followed when |
| 43 | this session added nothing; when both added content the save forks a |
| 44 | `concurrent` head, and both sides receive a notice. The versions dialog shows |
| 45 | both heads and the user picks. There is no `pending` state and no lease |
| 46 | handoff to retry: the lease decides only who writes the derived transcript, |
| 47 | the indexes, and the turn ledger. |
| 48 | |
| 49 | For a format-1 session an append-compatible snapshot is adopted from disk |
| 50 | without creating another version. A divergent snapshot is preserved as a |
| 51 | recovery version using the existing CAS and digest checks. A failed lease |
| 52 | handoff marks that recovery version as `pending`; the desktop client can retry |
| 53 | activation after the writer is released. Recovery lineage reconciliation is |
| 54 | idempotent. Covered copies may be moved to recoverable session trash, while |
| 55 | divergent content remains available for an explicit version choice. |
| 56 | |
| 57 | The desktop bridge exposes `GetRecoveryLineage` and `GetSessionVersionState`, |
| 58 | which list a format-2 log's heads (state `heads`; every member shares the log's |
| 59 | path and carries `headId`, `headKind`, `headName`, and `selected`); |
| 60 | `ChooseRecoveryBranch` and `SetActiveSessionVersion`, which take `headId` and |
| 61 | make a head current (an open tab switches in place, a closed session gets a |
| 62 | `select` marker); `CleanRecoveryLineage`, which retires covered heads and |
| 63 | reports heads active within the last minute as busy; `RenameSessionHead`; |
| 64 | and the format-1 `RetrySessionRecovery` and `ReconcileRecoveryVersions`. A |
| 65 | family whose format-1 root was upgraded after recovery copies had been made |
| 66 | shows the log's heads and the copies together. Worktree status and merge |
| 67 | preparation use the same backend inspection and identity checks as the |
| 68 | existing merge flow. |
| 69 | |
| 70 | ## Legacy recovery copies |
| 71 | |
| 72 | `-recovery-` files made before the upgrade are not imported into the log. They |
| 73 | remain format-1 sessions of the same lineage: listed under *View versions*, |
| 74 | selectable, and covered copies are still moved to recoverable trash by the |
| 75 | existing sweep and by `reasonix sessions cleanup`. A format-2 log never forms |
| 76 | a recovery group, so cleanup reports zero candidates for it, and |
| 77 | `reasonix sessions diagnose` counts session logs, heads, covered heads, and |
| 78 | retired heads next to the recovery-copy numbers. |
| 79 | |
| 80 | ## Compatibility |
| 81 | |
| 82 | | Field or format | Old-data behavior | New-reader behavior | Previous-reader behavior | Conclusion | |
| 83 | | --- | --- | --- | --- | --- | |
| 84 | | `.events.jsonl` format 1 | unchanged | replayed; message ids derived deterministically | unchanged | compatible | |
| 85 | | `.events.jsonl` format 2 | n/a | native | refused; file left untouched | explicit boundary (>= 1.39.0) | |
| 86 | | `.jsonl` transcript | unchanged format | derived from the selected head | readable, not authoritative | compatible | |
| 87 | | `.jsonl.meta` head fields | absent → treated as format 1 | used | ignored | compatible | |
| 88 | | `.event-index.json` schema 2 | schema-1 index rejected → replay | native | rejected → slow path | compatible | |
| 89 | | `-recovery-<hex>.jsonl` | format-1 lineage | not imported; listed as before | unchanged | compatible | |
| 90 | | session catalog `v8.sqlite` | v7 file isolated | native | separate generation files | compatible | |
| 91 | |
| 92 | Format 2 opens in Reasonix 1.39.0 and newer. Mixed installations should |
| 93 | upgrade the older side before sharing a session directory; the older binary |
| 94 | reports the newer format and does not modify the file. |
| 95 |