Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
a8e2903
feat(adapter): adopt shared Vuoro adapter kit
Aug 13, 2026
4efcc83
refactor(sprintctl): extract export and import commands
Aug 14, 2026
8d9e70b
refactor(sprintctl): extract sprint and item commands
Aug 14, 2026
4f834d3
refactor(sprintctl): extract event and rollout commands
Aug 14, 2026
e04aff0
refactor(sprintctl): extract lifecycle commands
Aug 14, 2026
3f8e549
refactor(sprintctl): extract remaining CLI commands
Aug 14, 2026
2492995
fix(sprintctl): harden local identity resolution
Aug 14, 2026
48bac91
refactor(sprintctl): split application services
Aug 14, 2026
aec388f
refactor(sprintctl): make cli a composition root
Aug 14, 2026
7a76578
refactor(sprintctl): separate handoff contract models
Aug 14, 2026
425ff79
refactor(sprintctl): centralize served doctor probe routes
Aug 14, 2026
a0df383
refactor(sprintctl): add immutable served operation specs
Aug 14, 2026
3a9767d
refactor(sprintctl): route served guards through registry
Aug 14, 2026
e7c4c70
test(sprintctl): validate served registry catalog parity
Aug 14, 2026
8e73d7a
refactor(sprintctl): make repository sync reusable
Aug 14, 2026
00edcfd
feat(sprintctl): define normal repository sync paths
Aug 14, 2026
1e656dc
feat(sprintctl): add normal sync command
Aug 14, 2026
8cba74d
feat(sprintctl): move observations to normal sync state
Aug 14, 2026
e13df86
docs(sprintctl): document normal synchronization
Aug 14, 2026
d5b67e4
feat(sprintctl): retire pilot command surface
Aug 14, 2026
6c357a1
refactor(sprintctl): remove retired cutover catalog contract
Aug 14, 2026
e9e87bd
refactor(sprintctl): decouple normal sync from rollout helpers
Aug 14, 2026
6183d6b
refactor(sprintctl): delete rollout-era sync modules
Aug 14, 2026
e437039
docs(sprintctl): describe projection reads through normal sync
Aug 14, 2026
2b11cf5
docs(sprintctl): retire pilot recovery instructions
Aug 14, 2026
bc7b992
feat(sprintctl): add advisory reservation ledger
Aug 14, 2026
819ec92
feat(sprintctl): add postgres reservation ledger
Aug 14, 2026
430f238
feat: add advisory reservation core and cli
Aug 14, 2026
aeace4d
feat: publish reservation and project dispatch contracts
Aug 14, 2026
1a06d1e
feat: retire claim command and served catalog surface
Aug 14, 2026
e44cf40
feat: migrate context and handoff aggregates to reservations
Aug 14, 2026
c9bf3db
feat: derive next-work explanation from reservations
Aug 14, 2026
06925c0
refactor: remove retired served claim facade
Aug 14, 2026
ce53c20
feat: archive legacy claims in sqlite migration
Aug 14, 2026
0fbfb45
feat: archive legacy claims in postgres migration
Aug 14, 2026
662242a
test: migrate claim contracts to reservations
Aug 14, 2026
7423822
fix: advance reservation schema migrations correctly
Aug 14, 2026
8d225f7
test: replace served claim routes with reservations
Aug 14, 2026
355ae2c
feat: migrate context and session guidance to reservations
Aug 14, 2026
8092a96
feat: derive next-work explanations from reservations
Aug 14, 2026
a9b1e37
test: retire claim help and performance coverage
Aug 14, 2026
881c906
test: align application catalog with reservations
Aug 14, 2026
482a90c
test: replace application claim reads with reservations
Aug 14, 2026
6726400
test: expect next-work reservation contract v2
Aug 14, 2026
277d69d
refactor: remove retired claim registration seam
Aug 14, 2026
d85ad16
fix: publish reservation release metadata
Aug 14, 2026
403c9e0
refactor: publish reservation v2 aggregate contracts
Aug 14, 2026
00c6bf1
refactor: retire done-from-claim command surface
Aug 14, 2026
49f575c
test: remove retired completion command from usage contract
Aug 14, 2026
2700be9
refactor: remove claim arbitration application seam
Aug 14, 2026
ee32053
fix: archive historic claims idempotently
Aug 14, 2026
0968591
feat: preserve reservations in sprint transfer
Aug 14, 2026
7371643
refactor: stop exporting retired claim helpers
Aug 14, 2026
9a4db5e
docs: remove retired claim instructions from usage
Aug 14, 2026
16f5817
refactor: make item status proof-free
Aug 14, 2026
47d95c0
docs: publish reservation usage guidance
Aug 14, 2026
bce62f7
fix: derive served next-work guidance from reservations
Aug 14, 2026
f8765ba
refactor: remove done-from-claim command path
Aug 14, 2026
6174301
refactor: remove dead claim application methods
Aug 14, 2026
0d2f64d
refactor: remove retired completion registration shim
Aug 14, 2026
2640c0d
refactor: make item transitions claim-proof-free
Aug 14, 2026
64d010c
refactor: remove legacy claim cli implementation
Aug 14, 2026
8e6f0b6
refactor: retire claim authority submit surface
Aug 14, 2026
8d96693
refactor: retire claim authority record contracts
Aug 14, 2026
f1c561e
refactor: remove retired claim authority handlers
Aug 14, 2026
859c2dd
refactor: remove residual claim proof helpers
Aug 14, 2026
f4f17b0
refactor: detach authority from claim terminal recovery
Aug 14, 2026
85b4819
refactor: remove claim terminal recovery subsystem
Aug 14, 2026
97f0781
refactor: remove served claim facade
Aug 14, 2026
6985fd4
refactor: remove claim token recovery runtime
Aug 14, 2026
0122eed
refactor: migrate maintenance from claims to reservations
Aug 14, 2026
d482145
test: replace maintenance claim assertions
Aug 14, 2026
5a6c25d
feat: retain reservations in transfer archives
Aug 14, 2026
b23c715
feat: preserve reservation archives in recovery
Aug 14, 2026
add8c77
refactor: gate maintenance on reservations
Aug 14, 2026
4903031
refactor: remove retired claim authority helpers
Aug 14, 2026
83e82b2
refactor: replace claim eligibility context field
Aug 14, 2026
a01b3db
test: align postgres maintenance diagnostics
Aug 14, 2026
fd44095
refactor: remove residual claim command seams
Aug 14, 2026
a07f26c
test: retire served claim lifecycle contracts
Aug 14, 2026
2aa9755
test: remove retired claim backend modules
Aug 14, 2026
625cde5
docs: define claim archive compatibility boundary
Aug 14, 2026
6b7ada2
test: remove claim sync fixtures
Aug 14, 2026
0888a9a
test: treat claims as archive-only schema
Aug 14, 2026
b5b6106
refactor: remove the claim-core runtime
Aug 14, 2026
ffb8f4c
fix: restore mutual exclusion between reservations and maintenance
Aug 14, 2026
07dda4e
refactor: remove the transient claim-proof credential path
Aug 14, 2026
0948e0e
refactor: remove claim-era record validators
Aug 15, 2026
0521132
refactor: remove the pending authority-proof sidecar system
Aug 15, 2026
c54761d
refactor: drop dead claim guards from the command runtime and served …
Aug 15, 2026
469142b
test: derive PostgreSQL migration expectations from the ledger
Aug 15, 2026
5161e83
fix: align PostgreSQL touch_reservation errors with SQLite
Aug 15, 2026
41b6c25
test: convert the remaining legacy claim test groups to reservations
Aug 15, 2026
96495be
docs: rewrite reservation model protocol and reference contracts
Aug 15, 2026
b4348c8
docs: rewrite operator guides for the reservation model
Aug 15, 2026
cc5e409
docs: rewrite examples and templates for the reservation model
Aug 15, 2026
0e0ab77
test: retarget doc contracts at the reservation model
Aug 15, 2026
b6e7e87
chore(dispatch): adopt native instruction catalog
Aug 15, 2026
ed140d5
chore(dispatch): pin instruction source revisions
Aug 15, 2026
9e99625
feat: drop the live claim relation (SQLite 20, PostgreSQL 10)
Aug 15, 2026
05de1f9
fix(project): accept descriptive role presets
Aug 15, 2026
21b6984
refactor: remove the orphaned pilot command surface
Aug 15, 2026
c2a36d2
feat: add the repo-level recovery record (V3-7 / #1234)
Aug 15, 2026
4a9444a
test: pin the served operation surface where it always runs
Aug 15, 2026
73a28b5
fix(reservation): report overlap instead of enforcing exclusivity
Aug 15, 2026
2d36e74
docs: retire the exclusivity claim from the reservation model
Aug 15, 2026
c2f0a21
Merge origin/main into codex/p23-vuoro-adapter-kit
Aug 15, 2026
a8d45e8
fix(reservation): make implicit activity actually fire, and PG audit it
Aug 15, 2026
80b35cb
release: 0.3.0
Aug 15, 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
177 changes: 107 additions & 70 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Primary language: Python. Use `pytest` for testing. Markdown for documentation.

| Variable | Purpose |
|---|---|
| `SPRINTCTL_INSTANCE_ID` | Stable per-process UUID — set once and reuse across every claim call |
| `SPRINTCTL_INSTANCE_ID` | Optional session metadata; never a credential |
| `SPRINTCTL_RUNTIME_SESSION_ID` | Runtime session ID (auto-detected from `CODEX_THREAD_ID`) |
| `SPRINTCTL_DB` | Override the database path (default: `~/.sprintctl/sprintctl.db`) |

Expand All @@ -40,8 +40,8 @@ If tests fail after a change, diagnose the root cause, fix, and re-run — up to
---

sprintctl is a local sprint coordination CLI backed by a SQLite database.
It uses a **claim system** to give agents exclusive, time-limited ownership of
work items. Read this file before touching any sprint item.
It uses an **advisory reservation system** to make coordination visible among
agent sessions. Read this file before touching any sprint item.

---

Expand Down Expand Up @@ -71,118 +71,154 @@ uv tool upgrade sprintctl kctl

---

## Claim lifecycle (summary)
## Reservation lifecycle (summary)

### 1. Startup — claim the item
A reservation is a visible coordination signal, not a capability. Multiple
sessions may hold active reservations on the same item; conflicts are
operator-visible rather than enforced.

### 1. Startup — reserve the item

```bash
sprintctl claim start \
sprintctl reservation reserve \
--item-id <id> --actor <your-name> \
--ttl 600 \
--instance-id "$SPRINTCTL_INSTANCE_ID" \
--role execution \
--session-id "$SPRINTCTL_RUNTIME_SESSION_ID" \
--json
```

Save **both** `claim_id` and `claim_token` from the response.
`claim_token` is a secret — store it for the entire session.
sprintctl also writes a local recovery token file next to the active database
so `claim recover` can restore the secret after context loss.
The claim response also carries the item's refs. Read every governing doc ref
before editing files, and pin the executed revision as described in
Save `reservation_id` from the response. There is no token, no secret, and no
recovery file.

The reservation response also carries the item's refs. Read every governing doc
ref before editing files, and pin the executed revision as described in
`docs/reference/doc-refs.md`.

**Coordinators** (orchestrators spawning sub-agents): claim with `--type coordinate`.
Sub-agents then call `claim create` with `--coordinate-claim-id` and `--coordinate-claim-token`
to acquire their own `execute` claim without triggering a conflict.
The role is the relationship to the work: `execution` (doing it),
`verification` (reviewing or testing it), `observation` (watching it). That is
what makes an overlap readable — two `execution` reservations are worth
coordinating over, `execution` beside `verification` is ordinary.

If somebody else already holds a reservation, yours is still created. The
response carries `conflict`, `conflicting_reservations`, and
`conflict_severity`; read it and coordinate rather than assuming you are alone.
Nothing refuses you, because refusing you would only remove you from the
ledger, not from the work.

### 2. Heartbeat — keep claim alive
To deliberately displace an execution reservation — a stalled session, a
takeover you have agreed — add `--interrupt-existing`. It interrupts the
item's active execution reservations, records `interrupted by <actor>
(<session>)`, and emits a durable audit event. Verification and observation
reservations are left alone.

**Coordinators** (orchestrators spawning sub-agents): reserve with
`--role observation`. Orchestration is session and project context, not a
relationship to the item, so a coordinator observes the work it coordinates.
Sub-agents reserve with `--role execution` on the same item.

### 2. Activity — touch when useful

```bash
sprintctl claim heartbeat \
--id <claim_id> --claim-token <token> \
--ttl 600 --actor <your-name>
sprintctl reservation touch \
--id <reservation_id> \
--session-id "$SPRINTCTL_RUNTIME_SESSION_ID"
```

Heartbeat every ~half-TTL. The response includes `expires_at` and a warning
if the TTL is within the expiry-warn window.
`last_activity_at` also advances on its own whenever your session
successfully mutates the item (status, edit, note, ref, dep, item-scoped
events), so `touch` is for work that happens outside sprintctl — long external
or git-only stretches. Attribution is by session id, never by actor name, and
it works the same in served mode: the client attaches its session to the
invocation, since the server cannot see it.

Touch bumps `last_activity_at`. There is no lease, no TTL, and no heartbeat
contract to violate. Staleness is display-only.

### 3. Transition item status (done/blocked, or active when using `claim create`)
### 3. Transition item status

```bash
sprintctl item status \
--id <item_id> --status active|done|blocked \
--actor <your-name> \
--claim-id <claim_id> --claim-token <token>
--expected-revision <revision>
```

Status transitions are **blocked** unless you provide valid claim proof.
`claim start` already performs the `pending -> active` transition.
Status transitions are protected by expected-revision compare-and-swap, not by
reservation proof. Read the current `status_revision` from `item show --json`
before mutating.

### 4. Handoff — required before session end if work continues
### 4. Handoff — reassign when work continues

```bash
# Transfer claim ownership to next session (token rotates)
sprintctl claim handoff \
--id <claim_id> --claim-token <token> \
--actor <next-agent-name> --mode rotate \
--runtime-session-id <next-session-id> \
# Reassign the advisory reservation to the incoming session
sprintctl reservation reassign \
--id <reservation_id> \
--actor <next-agent-name> \
--session-id <next-session-id> \
--json

# Produce a sprint handoff bundle for the incoming session
sprintctl handoff [--sprint-id N] [--output path] [--format json|text]
```

The claim handoff response contains the new `claim_token` for the incoming agent.
The old token is immediately invalidated.
`reservation reassign` changes the reserving actor/session. `sprintctl handoff`
produces a working-memory bundle; it does not carry ownership proof because
there is none.

`--format text` produces a human-readable bundle (status groups, active claims,
shutdown protocol). `--format json` (default) produces the machine-parseable
bundle for agent session resumption.
`--format text` produces a human-readable bundle (status groups, active
reservations, shutdown protocol). `--format json` (default) produces the
machine-parseable bundle for agent session resumption.

### 5. Release — when work is done

```bash
sprintctl claim release \
--id <claim_id> --claim-token <token> --actor <your-name>
sprintctl reservation release \
--id <reservation_id> --actor <your-name>
```

---

## Session resumption (context loss recovery)

If you restart and no longer have the `claim_token`:
If you restart, there is no token to recover. List reservations and reassign or
reserve as appropriate:

```bash
# Find your claims by identity
sprintctl claim resume --instance-id "$SPRINTCTL_INSTANCE_ID" --json

# Recover the locally persisted token that sprintctl wrote when the claim was created
sprintctl claim recover --id <claim_id> --json

# If no local recovery file exists and the token is gone, adopt the claim (mints a fresh proof)
sprintctl claim handoff \
--id <claim_id> --actor <your-name> --mode rotate --allow-legacy-adopt --json
# Find reservations by item or list all active reservations
sprintctl reservation list --item-id <id> --json
sprintctl reservation list --all --json

# Reassign an existing reservation to the current session, or release and
# create a new one if the old session is gone.
sprintctl reservation reassign \
--id <reservation_id> \
--actor <your-name> \
--session-id <current-session-id> \
--json
```

Reservations contain no recoverable credential.

---

## Shutdown checklist

Before terminating:

1. For each owned claim: **handoff** to the next agent _or_ **release** it.
1. For each active reservation: **reassign** to the next session _or_ **release** it.
2. Run `sprintctl handoff` to write a bundle for the incoming session.
3. The bundle's `agent_shutdown_protocol` field repeats these instructions.

---

## Ownership model

- Proof = `claim_id` **+** `claim_token` (both required)
- sprintctl can restore the locally persisted token via `claim recover`, but the recovered secret is still the proof used by claim operations
- `instance_id`, `hostname`, `pid`, `actor` name are advisory metadata only — never proof
- Default TTL: 300 s. Use `--ttl` to increase for long-running tasks
- `coordinate` claims allow sub-agent `execute` claims; all other exclusive claim types block each other
- There is no ownership proof. `reservation_id` is a handle, not a secret.
- `instance_id`, `hostname`, `pid`, `actor` name, branch, worktree, and commit SHA
are advisory metadata only.
- The reservation model is advisory: conflicting reservations are detected and
surfaced, not prevented.
- Status transitions are gated by expected-revision CAS (`item:<uuid>@status:<status>`).

---

Expand All @@ -194,8 +230,9 @@ Before picking up work, read the current state in one call:
sprintctl usage --context [--sprint-id N] [--json]
```

This emits: sprint summary, active claims (who owns what), stale/blocked items,
ready-to-start items (no unresolved deps), and recent knowledge candidates.
This emits: sprint summary, active reservations (who is working on what),
stale/blocked items, ready-to-start items (no unresolved deps), and recent
knowledge candidates.

Use `--json` for machine-readable output — compact enough to paste into a prompt
without summarisation.
Expand Down Expand Up @@ -252,7 +289,7 @@ Items with unresolved blockers are excluded from `next-work` output.

---

## Recording git context on notes and claims
## Recording git context on notes

`item note` accepts git provenance fields so knowledge candidates carry their origin:

Expand All @@ -264,15 +301,13 @@ sprintctl item note --id <item-id> --type decision \
--actor <your-name>
```

`claim create` and `claim heartbeat` accept `--branch`, `--commit-sha`,
`--worktree`, and `--pr-ref` to keep the claim record current as work progresses.

---

## Capability receipt at sprint close

For an intentional sprint close, first run the close gate, then close explicitly
with `sprintctl sprint status --id <id> --status closed --actor <actor> --json`.
For an intentional sprint close, first run the close gate, read the current
`sprint show --json` `status_revision`, then close explicitly with
`sprintctl sprint status --id <id> --status closed --actor <actor> --expected-revision <revision> --json`.
The status change and one local `sprint-close-boundary` event commit atomically;
the JSON response returns `boundary_event_id` and its database-local
`boundary_revision` (`event:<id>`). That reference depends on preserving the
Expand All @@ -294,10 +329,12 @@ append-only procedural assertion rather than authenticated identity. An

Routing and hooks are declared in `sprintctl.dispatch.json`; closed subjects
and escalation rules live in `.agents/overlays/sprintctl.state-protocols.md`.
Use `verify-state-protocols` for claims, proof rotation, retries, projections,
or SQLite/PostgreSQL parity. `survey` and `reconcile` are read-only; product
repair requires separate authorization. Run concurrent histories only against
temporary SQLite databases and disposable PostgreSQL repository scopes.
Use `verify-state-protocols` for reservations, retries, idempotency,
reconciliation, append-only histories, canonical projections, crash recovery,
dual writes, concurrent workers, or SQLite/PostgreSQL parity. `survey` and
`reconcile` are read-only; product repair requires separate authorization. Run
concurrent histories only against temporary SQLite databases and disposable
PostgreSQL repository scopes.

## Hybrid dispatch

Expand All @@ -307,15 +344,15 @@ modify, and explicit registered gates that fail for each relevant incorrect
behaviour. One rejected attempt returns to the coordinator.

Parity fixtures, test-oracle construction, tests as the primary deliverable,
SQLite/PostgreSQL behavioural proof, and claim, authority, compatibility,
SQLite/PostgreSQL behavioural proof, and reservation, authority, compatibility,
migration, recovery, or credential semantics are coordinator-only regardless
of diff size.

## Environment variables

| Variable | Purpose |
|---|---|
| `SPRINTCTL_INSTANCE_ID` | Stable per-process UUID — set once and reuse across every claim call |
| `SPRINTCTL_INSTANCE_ID` | Optional session metadata only; never a credential |
| `SPRINTCTL_RUNTIME_SESSION_ID` | Runtime session ID (auto-detected from `CODEX_THREAD_ID`) |
| `SPRINTCTL_DB` | Override the database path |

Expand Down
58 changes: 7 additions & 51 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,53 +1,9 @@
# CLAUDE.md
# Claude Code guidance

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Read `AGENTS.md` first. It is the canonical Sprintctl instruction source for
ownership, reservation semantics, development workflow, and verification.

## Agent integration guide

**Read `AGENTS.md` first.** It is the primary reference for this repo: claim lifecycle, environment variables, session resumption, shutdown checklist, and quick-reference commands. The parent `/workspace/dev/AGENTS.md` covers devbox vs workstation context, tool install rules, PATH, direnv, and session cost logging.

## Development

```sh
# Set up local venv
python -m venv .venv
.venv/bin/pip install -e .

# Run all tests
PYTHONPATH=. .venv/bin/python -m pytest tests/ -v

# Run a single test file
PYTHONPATH=. .venv/bin/python -m pytest tests/test_claims.py -v

# Invoke CLI from source (use instead of global binary when developing)
.venv/bin/python -m sprintctl --help
```

Refresh stale global installs with `pipx upgrade sprintctl && pipx upgrade kctl` (or `uv tool upgrade sprintctl kctl`).

## Commit discipline

- Run `pytest` before every commit; report pass/fail count.
- **Never commit with failing tests.**
- **One sprint item = one commit.** Commit when the item is done, not at session end.
- Behavior changes must include updated or new tests in the same commit.
- If tests fail, self-heal (diagnose, fix, re-run) up to 5 cycles before escalating.

## Source layout

| Module | Role |
|--------|------|
| `sprintctl/cli.py` | Click CLI — all commands defined here |
| `sprintctl/backend.py` | Business logic, state transitions, claim operations |
| `sprintctl/db.py` | SQLite schema, migrations, low-level queries |
| `sprintctl/contracts.py` | Typed data models for JSON/text output surfaces |
| `sprintctl/calc.py` | Staleness thresholds, derived state calculations |
| `sprintctl/maintain.py` | `maintain check` health rules |
| `sprintctl/render.py` | Text renderer for sprint snapshot documents |
| `sprintctl/pg.py` | Optional PostgreSQL backend (`remote` extra) |

The `contracts.py` models are the boundary between internal DB state and what `--json` surfaces emit. Keep JSON and text output describing the same state in the same order.

## Environment

`SPRINTCTL_DB` must point at the project-scoped database, not `~/`. The `envrc.example` template sets this; copy it to `.envrc` and run `direnv allow`.
Claude-specific invocation preference: use the repository environment and run
`.venv/bin/python -m sprintctl` when the globally installed CLI is stale. This
adapter grants no additional Sprintctl, Git, publication, or deployment
authority.
Loading
Loading