Skip to main content

session-branch-fork

Design a session-level fork / revert / recover / suspend mechanism over a paginated history with lineage tracking, immutable segments, global lock, ModelContext reconstruction, and bounded replay. USE WHEN: designing session persistence, building a "fork this conversation" feature, building "undo last N turns", building "suspend and resume later", implementing a paginated history with segment-level cursor, reconstructing ModelContext from disk, or any task involving "session as a git-like object graph". TRIGGER PHRASES: "session fork", "session branch", "thread fork", "thread rollback", "revert thread", "ThreadRollback", "SuspendTurnAndShutdown", "Op::RecoverTurn", "paginated history", "RolloutLineage", "ForkBoundary", "RolloutReferenceIndex", "ModelContext reconstruction", "ReverseJsonlScanner", "bounded replay", "git baseline", "writer lock", "subagent lineage". SKIP WHEN: single-session task state (use `world-state-tracking`), no need to undo, no need to fork.

Zur Installation springen

Quellinformationen

Repository
MiniMax-AI/MiniMax-Code-Plugins
Letzte Quellaktivität
10. September 2026 um 01:39
Erkannte Sprache von SKILL.md
Englisch
Sterne
11
Forks
10

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
session-branch-fork
description
Design a session-level fork / revert / recover / suspend mechanism over a paginated history with lineage tracking, immutable segments, global lock, ModelContext reconstruction, and bounded replay. USE WHEN: designing session persistence, building a "fork this conversation" feature, building "undo last N turns", building "suspend and resume later", implementing a paginated history with segment-level cursor, reconstructing ModelContext from disk, or any task involving "session as a git-like object graph". TRIGGER PHRASES: "session fork", "session branch", "thread fork", "thread rollback", "revert thread", "ThreadRollback", "SuspendTurnAndShutdown", "Op::RecoverTurn", "paginated history", "RolloutLineage", "ForkBoundary", "RolloutReferenceIndex", "ModelContext reconstruction", "ReverseJsonlScanner", "bounded replay", "git baseline", "writer lock", "subagent lineage". SKIP WHEN: single-session task state (use `world-state-tracking`), no need to undo, no need to fork.
license
Apache-2.0
compatibility
Targets MiniMax Code 0.2.4. **Conceptual reference only.** The Rust types (`ThreadHistoryMode` / `RolloutLineageSegment` / `RolloutLineage` / `ForkBoundary` / `PrepareForkParams` / `PreparedFork` / `RevertThreadParams` / `StoredModelContext` / `UpdateProjectParams` / `ReverseJsonlScanner` / `ModelContextScan`), the SQLite CAS on `rollout_path`, the `writer_lock_coordinator`, the `try_claim_global_phase2_job` / `heartbeat_global_phase2_job` lease, the `Op::SuspendTurnAndShutdown` / `Op::RecoverTurn` ops, the `JOB_LEASE_SECONDS` / `GRACEFULL_INTERRUPTION_TIMEOUT_MS` / `MAX_ROLLOUT_LINE_BYTES` constants, the `TaskKind::Regular` / `Feature::Collab` / `PermissionProfile::External` enums, and the `expected_sqlite_path` anchor are all **Codex-internal APIs** (`codex-rs/thread-store/`, `codex-rs/state/src/runtime/recovery.rs`, `codex-rs/state/migrations/0047_rollout_migration_state.sql`). They are **not** part of the mcode 0.2.4 public surface. The mcode 0.2.4 `task` tool returns a `task_id` (string); anything else (`thread_id`, `rollout_id`, sqlite path, lineage segment, ModelContext reconstruction algorithm) is **host-internal**. The Skills below present the Codex reference as conceptual pseudocode; the actual implementation must follow the host's verified APIs.
metadata
{"author":"antianqi","version":"0.1.1","inspired-by":"https://github.com/openai/codex/tree/main/codex-rs/thread-store/ (P-49/50/51/52 + P-67-77)","changes-from-v0.1.0":"v1.0.5 amendment (PR #33 round-12, hetaoBackend CHANGES_REQUESTED on head 5a4e3fc): the previous body presented the Codex `codex-rs/thread-store/` Rust types and SQLite / `writer_lock_coordinator` / `Op::SuspendTurnAndShutdown` surface as if it were an mcode 0.2.4 design. None of those types are on the mcode public surface; the mcode call shape for sub-agent lifecycle is `task(...)` returning a `task_id` (string). The `compatibility` frontmatter field now states the conceptual-reference nature of the Skill explicitly, and a banner above the Process section reiterates that every Rust block, SQLite CAS anchor, lease helper, and op name is Codex-internal pseudocode."}
# Session Branch / Fork > **Conceptual reference only.** The mcode 0.2.4 public surface has **no** > parameters for session fork / revert / suspend / paginated history / > ModelContext reconstruction. The only documented mcode 0.2.4 tool for > sub-agent lifecycle is `task(...)`, which returns a `task_id` (string); > any deeper structure (`thread_id`, `rollout_id`, sqlite path, lineage > segment, ModelContext reconstruction algorithm) is **host-internal**. > Every Rust block, SQLite CAS anchor, lease helper, and op name below is > **Codex-internal pseudocode** (`codex-rs/thread-store/`), NOT a mcode > 0.2.4 design. The actual implementation must follow the host's verified > APIs. Design a session-level fork / revert / recover / suspend system over a paginated history. Mirrors `codex-rs/thread-store/`. ## When to use Activate when designing: - A "fork this session from turn N" feature. - A "undo the last N turns" feature. - A "suspend the running session, recover it later" feature. - A paginated history with cross-segment cursors. - A model-context reconstruction algorithm that doesn't re-read the entire history. ## When NOT to use - Single-session task state → use `world-state-tracking`. - No undo, no fork, no suspend needed → standard append-only history is enough. ## Process ### 1. Pick a `ThreadHistoryMode` ```rust pub enum ThreadHistoryMode { Legacy, // entire thread in one JSONL Paginated, // immutable segments, supports fork/revert } ``` **Default to Legacy** for backward compatibility, but use Paginated for any new thread. Paginated is what makes fork / revert / lineage work. ### 2. Define the immutable segment model ```rust pub struct RolloutLineageSegment { pub rollout_id: ThreadId, pub rollout_path: PathBuf, pub start_ordinal: u64, pub end: Option<HistoryPosition>, // byte offset } pub struct RolloutLineage { pub segments: Vec<RolloutLineageSegment>, } ``` **Key invariants**: - Segments are **immutable**. A new segment is created for any change. - The lineage is a list of segments ordered from oldest to newest. - Each segment knows its start ordinal and (optionally) its end offset. ### 3. Implement fork with `ForkBoundary` ```rust pub enum ForkBoundary { Latest, // inherit source's latest durable state ThroughTurn(String), // include this turn BeforeTurn(String), // exclude this turn } pub struct PrepareForkParams { pub thread_id: ThreadId, pub boundary: ForkBoundary, } pub struct PreparedFork { pub source_thread_id: ThreadId, pub model_context: Arc<ModelContext>, } ``` Fork flow: 1. Lock the source thread's lifecycle + writer. 2. Persist any pending items in the source. 3. Resolve the source's `RolloutLineage`. 4. Materialize each ancestor segment to SQLite (if not already). 5. Load the `ModelContext` for the chosen boundary. 6. Return a `PreparedFork` ready to be turned into a new thread. ### 4. Implement revert with CAS ```rust pub struct RevertThreadParams { pub thread_id: ThreadId, pub before_turn_id: String, // first turn EXCLUDED from retained history } ``` Revert flow: 1. Lock lifecycle + writer + writer_lock_coordinator. 2. Resolve the current rollout from SQLite (`expected_sqlite_path` is the CAS anchor). 3. Read `SessionMeta` from the current rollout, verify `id == thread_id` and `history_mode == Paginated`. 4. Materialize any compressed lineage segments. 5. Create a new immutable rollout file referencing the retained prefix. 6. CAS the SQLite `rollout_path` to the new file. **Critical contract**: `revert` only rolls back in-memory context. **It does not undo filesystem changes.** The client is responsible for undoing edits on disk. ### 5. Implement suspend + recover ```rust Op::SuspendTurnAndShutdown { reply: oneshot::Sender<...> } Op::RecoverTurn { thread_settings, reply: oneshot::Sender<...> } ``` Suspend flow (9 steps): 1. Lock `active_turn` and verify `task.kind == TaskKind::Regular`. 2. Snapshot descendants (not a seal; best-effort). 3. `live_thread.flush()` — persistence first; if it fails, leave the turn running. 4. Re-lock and re-verify kind (flush can yield). 5. Take the turn and task; cancel the cancellation token; cancel git enrichment. 6. `task.handle.detach()` with a `GRACEFULL_INTERRUPTION_TIMEOUT_MS` timeout. 7. `session.input_queue.clear_pending(&turn)` — pending input is NOT persisted. 8. `shutdown_session_runtime(session)` + `live_thread.flush()` + `live_thread.shutdown()`. 9. Emit `ShutdownComplete` event **only after** the writer is closed. **Critical contract**: do NOT record a terminal turn event on suspend. This intentionally leaves the turn's ID reclaimable, so `Op::RecoverTurn` can resume it. ### 6. Use a global lock for multi-process safety When two Codexes might fork / revert the same thread, use a DB-level lease: - `try_claim_global_phase2_job(thread_id, JOB_LEASE_SECONDS)` — single-writer. - `heartbeat_global_phase2_job(ownership_token, JOB_LEASE_SECONDS)` — keep lease alive. - On agent completion, re-verify ownership BEFORE `reset_git_repository` to avoid resetting someone else's work. ### 7. Reconstruct ModelContext with bounded replay ```rust pub fn load_latest_model_context(store, params) -> StoredModelContext { let path = thread_rollout_resolver::resolve_current(...).await?; let session_meta = read_session_meta_line(path).await?; if session_meta.id != params.thread_id { return Err(InvalidRequest); } let mut scanner = ReverseJsonlScanner::new(file)? .with_max_record_bytes(MAX_ROLLOUT_LINE_BYTES); let mut scan = ModelContextScan::default(); while let Some(outcome) = scanner.scan_next::<Value>()? { if let ScanOutcome::Parsed(value) = outcome { if scan.push(line) == ModelContextScanProgress::Complete { let items = scan.finish(session_meta); items.retain(|i| !matches!(i, RolloutItem::SessionMeta(_))); return Ok(StoredModelContext { items }); } } // ScanOutcome::Rejected — skip bad line, do not abort } // No bounded cutoff found — fall back to full replay } ``` Three guarantees: - `ReverseJsonlScanner` reads from the END of the file — only the suffix is touched. - `MAX_ROLLOUT_LINE_BYTES` prevents a single bad line from OOM-ing the reader. - `ScanOutcome::Rejected` skips malformed lines; never aborts the whole read. - If no bounded cutoff is found, fall back to the full replay (read entire file). ### 8. Run a Legacy → Paginated migration on startup ```rust pub async fn migrate_rollouts_on_startup(store) { // Use a creation-ordered cursor in SQLite to check only newer rollout files // 48h lookback window catches files we skipped earlier // Fingerprint (size_bytes + modified_at_ns) skips empty/malformed rollouts // Use run marker to prevent overlapping migrations // Spawn subagent-aware bounded replay for subagent rollouts (don't copy // the parent's full history into every child) } ``` ### 9. Use a writer lock for serialization `writer_lock_coordinator.acquire(thread_id)?` — guarantees that writes to a single thread are serialized even across processes. Drop the lock when the thread is closed. ### 10. Adopt the 3-state section / project model ```rust pub struct UpdateProjectParams { pub project_id: String, pub name: Option<String>, // None = no change pub roots: Option<Vec<StoredProjectRoot>>, pub metadata: Option<BTreeMap<String, String>>, } ``` Use `Option<Option<T>>` for 3-state: `None` = no change, `Some(None)` = clear, `Some(Some(v))` = set. ## Output contract A session-management system that follows this design: - Threads have a `ThreadHistoryMode` (Legacy / Paginated). - Paginated threads have a `RolloutLineage` of immutable segments. - Fork accepts a `ForkBoundary` and returns a `PreparedFork`. - Revert uses CAS on the SQLite rollout_path; creates a new immutable segment. - Suspend + Recover are paired; suspend does NOT record a terminal event. - Reconstruct ModelContext via reverse scan with bounded replay + byte cap + Rejected skip. - Migration runs at startup with cursor in DB + fingerprint skip + 48h lookback. - Multi-process safety via DB leases and writer lock coordinator. ## Common pitfalls - **No `Paginated` history mode** → no fork / revert. New threads should default to Paginated. - **Revert undoes filesystem** → it does not. Client is responsible. - **Suspend records terminal event** → Recover can never resume. Don't. - **Pending input persisted** → replay state is wrong. `clear_pending` on suspend. - **Reverse scan reads whole file** → slow. Bounded replay + `MAX_ROLLOUT_LINE_BYTES`. - **Migration on every startup reads all rollouts** → slow. Cursor + 48h lookback. - **Two Codexes reverting simultaneously** → corruption. Lease / writer lock. - **Reset git baseline with diff present** → deleted content stays in git objects. Delete diff first. - **Single-type partial update vs. clear** → use `Option<Option<T>>` for 3-state. ## Example — fork from a turn ```text # Source thread has 50 turns fork_boundary = BeforeTurn("turn-30") prepare_fork(thread_id, boundary) → 1. Lock source lifecycle + writer 2. Persist pending items 3. Resolve lineage (5 segments) 4. Materialize segments 1..4 to SQLite 5. ModelContext = base + turns 1..29 (before turn-30) 6. PreparedFork { source_thread_id, model_context } new_thread = create_thread(forked_from_id = source.id, history_base = ModelContext.snapshot) # New thread starts at turn 30, inherits turns 1..29 from ModelContext ``` ## Verification checklist - [ ] All new threads default to `ThreadHistoryMode::Paginated`. - [ ] Fork accepts `ForkBoundary` (Latest / ThroughTurn / BeforeTurn). - [ ] Revert uses CAS on the SQLite rollout path; creates a new immutable segment. - [ ] Suspend flushes BEFORE canceling, and re-checks turn kind after flush. - [ ] Suspend does NOT record a terminal turn event. - [ ] Suspend clears pending input (input is not persisted). - [ ] Suspend emits `ShutdownComplete` ONLY after `live_thread.shutdown()` returns. - [ ] Reconstruct ModelContext via `ReverseJsonlScanner` with byte cap. - [ ] Reconstruct skips `ScanOutcome::Rejected` without aborting. - [ ] Migration uses SQLite cursor + fingerprint skip + 48h lookback. - [ ] Multi-process safety: writer lock + DB lease + ownership token. - [ ] Partial-update APIs use `Option<Option<T>>` for 3-state.
Auf GitHub ansehen