Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
dcee028
WIP (openRepoTools#91): a lane that dies mid-handoff can say so — the…
brettheap Sep 15, 2026
d2cff42
openRepoTools#91: a resume can now say WHERE the last session stopped…
brettheap Sep 15, 2026
ac58d3c
openRepoTools#91: the two deviations a reader would otherwise have to…
brettheap Sep 15, 2026
612ba5c
openRepoTools#91: a report that called the lane's own checkout an unm…
brettheap Sep 15, 2026
7c60c8e
Merge remote-tracking branch 'origin/main' into feat/crash-consistent…
brettheap Sep 15, 2026
6391819
openRepoTools#91: the six fences this capability exists for, each of …
brettheap Sep 16, 2026
3840c87
openRepoTools#91: the two safety holes of the sixth round — a snapsho…
brettheap Sep 16, 2026
64dfd97
Merge remote-tracking branch 'origin/main' into feat/crash-consistent…
brettheap Sep 16, 2026
682f855
Merge origin/main into feat/crash-consistent-lane-worktree-recovery
brettheap Oct 4, 2026
b5aedae
Move lane-state's unreadable-snapshot exit from 9 to 10
brettheap Oct 4, 2026
64bcf5b
Close a swept lane's lifecycle snapshot in the Amendment 19 sweep
brettheap Oct 4, 2026
8f69e96
Stop claiming #97 keeps the log at five lane-kind verbs
brettheap Oct 4, 2026
28ed7c7
Never pronounce a crash from outside a lane's binding
brettheap Oct 4, 2026
e9cd956
Carry a renamed lane's lifecycle snapshot to its new name
brettheap Oct 4, 2026
39434ba
Refuse every legacy lane act on a lane the managed ledger owns
brettheap Oct 4, 2026
673a81a
Move a lane's lifecycle only on a legacy verdict and an unchanged pre…
brettheap Oct 4, 2026
e959c3f
Document the managed-owner seam and the four merge fixes
brettheap Oct 4, 2026
a0cf37b
Prove the managed-owner seam on every fixture class and every act
brettheap Oct 4, 2026
d869dcc
Assert a lifecycle snapshot never reaches the listing's binding column
brettheap Oct 4, 2026
a98b0d8
Tick task 8.11: the seam is proved on every fixture class
brettheap Oct 4, 2026
6e64660
Let lane-end's own pair refusal speak before the seam
brettheap Oct 4, 2026
ec9847e
Say where lane-end's seam actually runs
brettheap Oct 4, 2026
7915745
Close the three findings Copilot left open on 64dfd97
brettheap Oct 4, 2026
7773267
Take Codex's four inventory findings on ec9847e
brettheap Oct 4, 2026
d2d2eec
Say that the reconciliation reads the published register first
brettheap Oct 4, 2026
19df774
Refuse a managed or unknown row in the state-cell migration
brettheap Oct 4, 2026
eada5ff
Defer the live-swap takeover to #157 and list the migration's seam
brettheap Oct 4, 2026
07fb3ad
Close the seam on every lane-kind line, request-handoff and archive-rows
brettheap Oct 4, 2026
8c5958f
Name every arm the seam guards in the spec, the design and the manual
brettheap Oct 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
278 changes: 277 additions & 1 deletion docs/README-lanes.md

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions ideation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# openRepoTools Ideation

This directory holds non-normative brainstorm material. Documents under
`brainstorm/` describe possibilities for later specification and implementation;
they do not amend the lane collision protocol or the shipped command contracts.

## Brainstorm packets

- [Crash-Consistent Lane Worktree Recovery](brainstorm/lane-worktree-recovery-state-overview.md) — a proposed canonical lane worktree layout, two-phase swap state, and resume-time reconciliation model.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Canonical Lane-Owned Worktree Layout — Brainstorm

Status: brainstorm
Kind: architecture
Summary: Give each lane a derivable worktree root beneath its home repository while keeping the coordinator in the canonical base checkout.
Topics: lane-worktree-recovery-state, canonical-worktree-layout, lane-directory, worktree-ownership
Repository context: openRepoTools lane orchestration, with openRepoShape and Speckit worktree-layout compatibility as a boundary
Captured: 2026-09-15

## Possible feats

- **Derivable lane root** — Resolve every lane-owned worktree without relying on a historical absolute path.
- **Coordinator-base invariant** — Require the lane coordinator to launch from its home repository's canonical base checkout.

## Focus

This document isolates where a lane and its worktrees live. Today `dir` is the exact directory from which a coordinator was launched; it can be a base checkout or a feature worktree, and legacy lanes may not record it at all. That makes recovery depend on historical paths and leaves the relationship between one coordinator and several writer worktrees implicit.

## Proposed model

Resolve a lane's stable repository identity from `home owner/repo` and `estate`. On each workstation, resolve that identity to the canonical base checkout. The coordinator always launches there. Lane-owned writer worktrees occupy a deterministic root derived from that checkout, conceptually:

```text
<home-base-parent>/worktrees/<canonical-lane>/
├── lane-state.yaml
├── tree-state/
│ └── <tree>.yaml
└── trees/
└── <tree>/
```

The exact path must remain compatible with the estate's `project.yaml`, `SPECKIT_GIT_WORKTREE_ROOT`, and openRepoShape's feature-first worktree contract. A naive `..` calculation is insufficient for a three-leg estate. The invariant is derivability from repository identity and shape, not this illustrative spelling.

State files live beside the Git worktrees rather than inside them. That avoids making a checkout dirty, accidentally committing orchestration metadata, and losing the only record when a worktree directory disappears.

One lane may own many worktrees, but its coordinator has one launch directory. Each worktree receives a stable tree identifier and records its own repository, role, branch, HEAD, upstream, writer, and lifecycle state.

## Interfaces and boundaries

The layout consumes canonical lane name, `home`, `estate`, and shape configuration. It emits derivable paths and sidecar locations. It does not decide whether work is safe, whether a writer is live, or whether a missing worktree can be rebuilt.

Existing governed Speckit feature paths remain authoritative. Lane ownership may need to be an index over those paths rather than a second physical layout when moving them would violate the shape contract.

## Alternatives and tensions

- Keeping the coordinator in whichever worktree it last used preserves current flexibility but makes the lane's identity depend on mutable feature state.
- Putting `.lane-state` inside each worktree makes discovery local but dirties or disappears with the checkout unless special exclusion rules are reliable everywhere.
- Recording absolute paths is easy on one workstation but is not portable between host, container, and another workstation.
- Organizing physical worktrees by lane makes discovery simple; organizing them by feature preserves the existing Speckit contract. A sidecar lane index may reconcile both.

## Open questions

- Is the coordinator base the estate root, the lane's `home` repository checkout, or a declared coordination leg?
- Should lane-owned scratch worktrees and governed Speckit feature worktrees use different physical roots?
- What stable identifier names a writer tree when the branch is renamed?
- Is the sidecar local-only, workspace-replicated, or split between both?

## Relationships

The lifecycle written beneath this layout is defined in [Crash-Consistent Lane Lifecycle](lane-worktree-recovery-state-crash-consistent-lifecycle.md). Resume reconciles the layout against Git and processes as described in [Resume-Time Worktree Reconciliation](lane-worktree-recovery-state-resume-reconciliation.md).
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Crash-Consistent Lane Lifecycle — Brainstorm

Status: brainstorm
Kind: architecture
Summary: Persist RUNNING, SWAPPING, and SWAPPED as guarded transitions so token exhaustion before or during a handoff is distinguishable and recoverable.
Topics: lane-worktree-recovery-state, crash-consistent-lifecycle, swap, session-binding
Repository context: openRepoTools `/swap`, `lane-handoff`, `lane-start`, and SessionStart behavior
Captured: 2026-09-15

## Possible feats

- **Two-phase swap** — Mark a swap in progress before preservation work and complete it only after every required handoff write succeeds.
- **Generation-fenced transition** — Prevent a delayed old process from overwriting the state of a newer resumed session.

## Focus

This document isolates lane-level lifecycle state and transition ordering. A session may exhaust tokens before `/swap`, during `/swap`, after a clean swap, or while a replacement is starting. Those cases must not collapse into one ambiguous `PAUSED` observation.

## Proposed model

Use an atomically replaced current-state sidecar plus the existing append-only lane event history:

```text
RUNNING --/swap begins--> SWAPPING --handoff complete--> SWAPPED
^ |
+------- confirmed replacement SessionStart ----------+
```

`/swap` first performs a compare-and-swap from `RUNNING` to `SWAPPING`. It then inventories worktrees, polls writers, records dirty and unpushed state, refreshes the handoff, and lands the protocol's pause writes. Only after every required step succeeds may it compare-and-swap the same operation from `SWAPPING` to `SWAPPED`.

Each transition carries at least:

```yaml
state: swapping
generation: 42
operation_id: 73989e7c-0000-0000-0000-000000000000
session_id: <transcript-uuid>
agent: claude
profile: team-01l
updated_at: <UTC>
```

The finishing write must match both `generation` and `operation_id`. A stale `/swap` process therefore cannot mark a lane `SWAPPED` after another process has recovered or resumed it.

The replacement must not write `RUNNING` merely because a launcher was invoked. `RUNNING` is written only after SessionStart confirms the new process, transcript, canonical lane, directory, and binding. An optional `RESUMING` state can make the launch interval explicit; without it, state remains `SWAPPED` until confirmation.

## Interfaces and boundaries

The lifecycle owns lane intent and transition authority. It consumes a verified session/binding identity and emits state changes. Liveness remains an observation from process/session records and must not be inferred from the state word alone.

This lifecycle does not claim that work is durable. `SWAPPED` means the required swap procedure completed; durability assertions come from the worktree reconciliation checks.

## Alternatives and tensions

- Reusing only append-only `PAUSED` and `RESUMED` events preserves one source but makes an interrupted multi-step swap harder to distinguish without an explicit `SWAPPING` event.
- A mutable sidecar gives a cheap current-state read but needs atomic replacement, locking, and an append-only audit event to explain every change.
- A heartbeat can identify abandoned `RUNNING` and `SWAPPING` states sooner, but process identity and namespace boundaries remain the stronger liveness evidence.

## Open questions

- Is `RESUMING` worth a fourth persisted state, or should `SWAPPED` remain until SessionStart?
- What timeout, if any, changes an indeterminate holder into a recovery candidate?
- Which swap steps are mandatory before `SWAPPED`, and which may finish asynchronously?
- Is recovery allowed to complete an interrupted operation ID, or must it always create a new generation?

## Relationships

The state refers to worktrees placed by [Canonical Lane-Owned Worktree Layout](lane-worktree-recovery-state-canonical-layout.md). Its crash outcomes are interpreted by [Resume-Time Worktree Reconciliation](lane-worktree-recovery-state-resume-reconciliation.md).
98 changes: 98 additions & 0 deletions ideation/brainstorm/lane-worktree-recovery-state-overview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Crash-Consistent Lane Worktree Recovery Overview — Brainstorm

Status: brainstorm
Kind: reference
Summary: Make a multi-worktree lane resumable by deriving its worktree inventory, journaling swap transitions, and reconciling persisted intent with live Git and process evidence before relaunch.
Topics: lane-worktree-recovery-state, lane-management, worktree-recovery, swap, resume
Repository context: openRepoTools, with integration boundaries at the lane collision protocol, openRepoShape, Speckit, and estate park/resume
Captured: 2026-09-15

## Possible feats

- **Structured lane recovery** — Deliver canonical lane worktree roots, crash-consistent swap state, and a guarded resume report as one capability.

## Motivation

A lane can coordinate several worktrees containing dirty files or unpushed commits. Today the lane launch record identifies one coordinator directory, while writer worktrees live primarily in the handoff and process context. Token exhaustion can happen before `/swap` or after `/swap` begins but before it completes, leaving the next session unable to distinguish a clean handoff from an interrupted one.

Legacy lanes add another ambiguity: their human-readable handoffs may name a directory while their machine-readable event history does not. Asking the operator for `--dir` recovers the coordinator but does not discover or verify every worktree the lane owned.

## Goals

- Derive the complete expected worktree inventory for a lane on a workstation.
- Distinguish normal running, an in-progress swap, and a completed swap across process death.
- Detect token exhaustion before and during `/swap` without claiming a clean handoff.
- Prevent duplicate coordinators and writers through locked, generation-fenced transitions.
- Find and preserve dirty or unpushed worktrees before resuming.
- Delegate reconstructable feature worktrees to the existing estate `resume` mechanism.

## Non-goals

- Reconstruct missing uncommitted file contents from metadata.
- Delete unknown worktrees or stale paths automatically.
- Replace the estate parked record or hand-roll its Git operations.
- Treat a profile, absolute host path, or branch name as the lane's stable identity.
- Ratify the coordinator-base invariant merely by recording this brainstorm.

## What the system delivers

The coordinator starts from a canonical base checkout resolved from stable repository identity. Writer worktrees have deterministic or indexed locations and sidecar manifests. `/swap` uses a two-phase `RUNNING -> SWAPPING -> SWAPPED` transition. Resume compares those records with live session holders, Git worktree registrations, actual directories, branches, commits, dirty files, and upstream state before allowing a new SessionStart to return the lane to `RUNNING`.

The result is an explainable outcome: attach to the existing owner, complete or recover an interrupted swap, resume a cleanly swapped lane, rebuild durable parked trees, or refuse with precise evidence of possible loss.

## System model

```text
/swap
confirmed session ----------------> transition journal
| RUNNING
| |
v v
canonical base ----> lane tree index --> SWAPPING
| |
v v
Git worktrees ------> SWAPPED
| |
+---- reconcile <--+
|
attach / recover / refuse / resume
|
confirmed SessionStart
|
RUNNING
```

## Cluster map

- [Resumable Lane Ownership](lane-worktree-recovery-state-synthesis-resumable-lanes.md) — joins physical discovery, transition authority, and evidence-based recovery.

## How it fits

The proposal extends rather than replaces existing roles. The lane event log remains the append-only audit history. The sidecar provides atomic current state and a bounded local inventory. The handoff remains the human and agent resume narrative. `git worktree list` and Git status remain filesystem truth. Estate `park`, `status`, and `resume` remain the only mechanism for durably recording and reconstructing governed feature worktrees.

Implementation must account for the current openRepoShape worktree convention. If lane-first physical paths conflict with feature-first paths, the canonical lane root should index existing paths rather than move them.

The governed change is [add-crash-consistent-lane-worktree-recovery](../../openspec/changes/add-crash-consistent-lane-worktree-recovery/proposal.md), tracked in [opensoft/openRepoTools#91](https://github.com/opensoft/openRepoTools/issues/91). The brainstorm remains non-normative; the OpenSpec proposal, capability specification, and design define the implementation boundary.

## Key decisions and open questions

- Proposed: `SWAPPING` is entered before any handoff work; `SWAPPED` is committed only after every mandatory step succeeds.
- Proposed: `RUNNING` is written only by a confirmed SessionStart.
- Proposed: every transition is locked and fenced by generation plus operation ID.
- Proposed: the coordinator launches from a canonical base checkout and feature changes live in writer worktrees.
- Open: whether `RESUMING` is a separate persisted state.
- Open: how the lane index coexists with Speckit's feature-first worktree paths.
- Open: which recovery discrepancies block only a writer versus the whole coordinator.
- Open: which metadata is local and which is replicated for another workstation.

## Document map

### Synthesis

- [Resumable Lane Ownership](lane-worktree-recovery-state-synthesis-resumable-lanes.md)

### Atomic concepts

- [Canonical Lane-Owned Worktree Layout](lane-worktree-recovery-state-canonical-layout.md)
- [Crash-Consistent Lane Lifecycle](lane-worktree-recovery-state-crash-consistent-lifecycle.md)
- [Resume-Time Worktree Reconciliation](lane-worktree-recovery-state-resume-reconciliation.md)
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Resume-Time Worktree Reconciliation — Brainstorm

Status: brainstorm
Kind: process
Summary: Resume a lane only after comparing its persisted intent with live holders, Git worktree registrations, filesystem state, branches, commits, and unpublished work.
Topics: lane-worktree-recovery-state, resume-reconciliation, git-worktree, loss-prevention
Repository context: openRepoTools lane restart behavior and the separate estate `park`, `resume`, and `status` mechanisms
Captured: 2026-09-15

## Possible feats

- **Lane recovery report** — Classify every expected and discovered worktree before launching a replacement writer.
- **Safe rebuild delegation** — Recreate only worktrees whose commits and branch state are durably represented by the estate parked record or origin.

## Focus

This document isolates what resume must prove. A sidecar state records intent, but only live-process evidence, `git worktree list --porcelain`, filesystem inspection, and Git status reveal what survived a crash.

## Proposed model

Resume acquires the lane lock, resolves the canonical lane root, and reconciles three inventories:

1. expected trees from the lane sidecars and latest handoff;
2. registered trees from each repository's `git worktree list --porcelain`;
3. directories actually present beneath the canonical lane root.

For every tree it recalculates repository identity, branch or detached HEAD, current commit, upstream, dirty count, untracked files, and unpushed commits. It also checks the recorded writer/session against live holders. Stored `dirty`, `unpushed`, branch, and HEAD values are comparison points, never current truth.

Suggested outcomes:

| Persisted state | Observed holder | Outcome |
|---|---|---|
| RUNNING | live and matching | Attach or refuse a duplicate launch |
| RUNNING | absent | Ungraceful stop; preserve and inspect every tree |
| SWAPPING | live and matching | Swap still executing; do not compete |
| SWAPPING | absent | Interrupted swap; recovery required |
| SWAPPED | absent | Validate trees, then permit resume |
| SWAPPED | live | Inconsistent state; refuse until reconciled |
| CLOSED | any dirty or unpushed tree | Closure inconsistency; refuse cleanup |

A missing worktree may be rebuilt only when its branch and commit are durably available and the existing estate `resume` contract authorizes reconstruction. A path that may have held uncommitted work is reported as possible loss; metadata cannot reconstruct missing file contents.

Unknown worktrees beneath the lane root are never deleted. They are reported as orphan or unmanaged candidates and require adoption or an explicit operator cleanup act.

## Interfaces and boundaries

This process reads lane sidecars, lane event history, handoffs, session holders, Git registrations, and filesystem state. It may delegate reconstruction to the estate's `resume` command. It does not hand-roll WIP commits, force-add worktrees, reset branches, or delete unknown directories.

The lane mechanism and estate mechanism remain distinct: lane resume restores orchestration identity; estate resume reconstructs previously parked feature worktrees from durable branch and commit records.

## Alternatives and tensions

- Automatically rebuilding every missing path is convenient but cannot distinguish a clean removed worktree from lost uncommitted work.
- Refusing on every discrepancy is safe but may make ordinary stale registrations expensive; classifications should include precise operator remedies.
- A lane-first physical directory makes enumeration cheap, while an index over shape-governed feature paths avoids changing the current worktree contract.

## Open questions

- Which discrepancies are warnings, and which must block the coordinator launch?
- Can the coordinator start while individual trees remain recovery-required?
- How are cross-repository writer worktrees represented under one lane root?
- Should a clean, pushed, unknown worktree be adoptable automatically or only on a person's word?

## Relationships

The expected inventory comes from [Canonical Lane-Owned Worktree Layout](lane-worktree-recovery-state-canonical-layout.md). The meaning of stale state comes from [Crash-Consistent Lane Lifecycle](lane-worktree-recovery-state-crash-consistent-lifecycle.md).
Loading
Loading