Skip to content

Make conversation rollback durable and atomic #200

Description

@rynfar

Problem

thread.checkpoint.revert is currently an asynchronous best-effort sequence, not an atomic rollback. It can mutate the Git worktree/index before provider rollback fails, lose the job after a server crash, publish success for adapters whose rollback is a no-op, race a new turn after the initiating client clears its local spinner, and retry by a relative turn count against a provider branch that may already have moved.

The correct success invariant is:

The checkpoint-managed workspace, the Pylon projection, and the exact provider branch that will receive the next prompt all represent the same target Pylon turn. thread.reverted is committed last, only after the first two states are verified or durably recoverable.

Cross-system ACID is not possible. Implement a durable target-absolute saga with compensation and a terminal manual-recovery state.

P0 truth and safety gate

Before any new provider-specific work:

  • Require thread idle, no active/pending input or turn, exact source revision, and a workspace-level mutation lease.
  • Persist a durable pending revert before returning admission success. Block sends and provider input/queues while it is active.
  • Reject adapters that cannot prove absolute rollback. Never report success for a no-op or NotImplemented path.
  • Remove the turn-0 fallback to ambient HEAD; a missing immutable baseline is an explicit failure.
  • Keep all newer checkpoint refs until final commit and expose a durable pending/recovering/error state to every client.
  • Update the confirmation copy to state that the worktree, index, untracked files, Pylon history, and provider conversation are rewritten.

Durable provider-neutral saga

Persist an operation ID, source and target revisions, immutable target checkpoint, workspace pre-image, provider-neutral desired anchor and receipts, phase, attempts, errors, and compensation state.

  1. Admit with compare-and-set and acquire the project/workspace lease.
  2. Fence Pylon and provider input; capture a complete pre-image of every path/index state the restore can mutate.
  3. Restore and verify the immutable filesystem target; persist an exact receipt.
  4. Apply an absolute provider target, never “remove N turns”. If the result is uncertain, inspect the provider anchor: target means applied, source means retryable, anything else means manual recovery.
  5. Commit thread.revert.complete last with operation/source/target identities. Only this event truncates the projection and publishes success.
  6. Delete stale refs, backups, and obsolete anchors only after commit through idempotent retryable cleanup.

On failure before projection commit, restore the workspace pre-image and source provider anchor. If both cannot be proved, keep the thread fenced in manual recovery. Startup and worker reconciliation must resume every nonterminal phase without sleeps or duplicate side effects.

Prime implementation

Prime does not require a new fork primitive. In native mode, persist the private exact leafId for each settled canonical turn and use public navigateTree(desiredLeafId). After rollback or reconnect, quarantine snapshots until getState().leafId matches the desired leaf; reapply when needed. Keep leaf IDs, native session IDs, recovery handles, paths, prompts, tools, and provider receipts out of public events, logs, and client payloads.

ACP and providers without an absolute inspectable anchor must fail closed until they implement one. Provider-specific translation stays at adapter boundaries.

Acceptance coverage

  • Crash/fault injection after every persisted phase and every external side effect, with deterministic restart reconciliation.
  • Provider success, failure, timeout/unknown outcome, wrong leaf, reconnect, daemon replacement, and cleanup retry.
  • Filesystem tracked, staged, unstaged, untracked, renamed, deleted, nested worktree, branch/ref, and pre-image compensation cases.
  • Concurrent send, stop, checkpoint, Git action, second revert, same-project second thread, and multi-client races.
  • Turn 0, missing/corrupt checkpoint, partial history, restored Prime session, ACP fallback, and unsupported adapters.
  • Durable UI across refresh/reconnect on web, desktop, and mobile; local, remote/relay, and tunnel behavior.
  • Privacy canaries prove no private anchor, prompt, tool, path, credential, or pre-image content crosses public contracts/logs.
  • At least one non-Prime provider path remains unchanged or explicitly fail-closed.

Delivery order

Land separate PRs for: (1) P0 truth/safety gate, (2) durable provider-neutral saga and reconciler, (3) Prime absolute leaf rollback, and (4) server-owned UX/docs. Do not combine this with artifact delivery, restart adoption, background generation, multi-instance work, Prime #20, or Comet.

Coordinate with #114.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsize:XXL

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions