agent-claim is a small installable CLI that gives coding agents one
append-only, locked GitHub issue ledger per repository. It is provider-neutral:
Codex, Claude, Grok, people, and future agents use the same contract.
uv tool install git+https://github.com/FlexOr2/agent-claim.git@v0.10.0
# or: pipx install git+https://github.com/FlexOr2/agent-claim.git@v0.10.0
uv tool upgrade agent-claim
uv tool uninstall agent-claimTo roll back, force-install the previous tag with uv tool install --force git+https://github.com/FlexOr2/agent-claim.git@v0.8.0.
A claim comment's field set is part of the append-only ledger contract, not just
one release's schema. A reader refuses a comment outright only when a field it
requires is missing -- that is a corrupt record. A comment carrying a field an
older reader's schema does not know is not a corrupt ledger: the reader fences
that one claim as unreadable (status names it, with its unknown field names)
and still answers board, next, who, and pr-check for every other claim; a
claim or rescope that could overlap the unreadable claim is refused, since the
reader cannot tell. A new field therefore ships only in a release whose notes
name it and the consumers pinned to an older tag, so each can bump its pin in its
own lane instead of discovering the mismatch as a broken ledger read. For
example, the rescope marker's whole_clear field is written only by the
automatic revert after a lost rescope race and is readable from 0.11.0 onward;
a 0.10.1 reader sees it as an unknown field and, since the fence itself ships
only in 0.11.0, still treats it as a whole-ledger refusal -- so a consumer must
bump its pin to 0.11.0 before that repair path can appear on its ledger.
agent-claim bootstrap
agent-claim status
agent-claim claim 42 --agent "Ada" --scope src/widget.py
agent-claim release 42 --merged 57
agent-claim reconcileOmitted --base/--branch bind the current checkout; explicit values must match it.
Omitted --agent on claim and release is filled from non-empty
AGENT_CLAIM_AGENT, else non-empty GROK_SESSION_ID as Grok {session}, else
non-empty CLAUDE_SESSION_ID as Claude {session}. GROK_AGENT is not a name.
Missing or present-invalid identity fails closed before GitHub work. Omitted
--role on claim is builder; an explicit --role wins. Repeating an
interrupted claim for the same active item, agent, role, branch, and scope
returns that active claim's existing ID without posting a second claim. A
different live claim still fails; a released claim ID remains terminal.
Omitted --claim-id on release selects the unique active claim on that issue
or lane whose agent is this session and whose branch is the current checkout;
otherwise it fails closed.
Omitted --role on release uses that selected claim's role; an explicit
--role must still match unless --coordinator-override, which still requires
--role coordinator. release takes exactly one outcome, never a free-form
reason: --merged <pull request> or --abandoned "<reason>". --merged is
verified against GitHub before anything is posted — the pull request must be
merged into the default branch, its Work-Item: line must name this claim's
item (or it must carry No-Item: for an issue-less lane), and that item must be
closed; otherwise the release is refused, naming what is missing. The ledger
records merged #<n> or abandoned: <reason>. supersede still requires
--agent and --role. A --claim-id already present on the ledger, active or
released, is refused before anything is posted; release the old claim and pass a
fresh --claim-id instead.
rescope <issue> --add <path> [--drop <path>] changes a live claim's scope
without releasing it: the claim id and base stay, added paths are advisory
like claim, and a resulting wide scope uses the same --whole rule as
claim. There is no release window. It does not require HEAD to match
base or a clean tree. A rescope ledger event is a new v2 action; older
helpers fail loud on the whole ledger until they upgrade.
Run commands in the repository being coordinated, or pass --repo OWNER/REPOSITORY. A claim must begin from a clean linked worktree and binds its
base commit, branch, issue, and repository-relative scope. --scope a,b is
the same as --scope a --scope b; each path is stored and compared
separately, including when an older ledger comment still has one comma-joined
string. A scope is wide when it declares more than three paths, any directory,
or, once the repository has at least twelve versioned files, more than a
quarter of them; a single named path in a smaller repository is never wide on
share. Named new paths count; children of containers are never exempt. The
refusal names the one condition that tripped, with its numbers, instead of
restating the whole rule: scope is wide: 4 paths exceeds three; pass --whole REASON, scope is wide: 1 directory in scope (docs); pass --whole REASON,
or scope is wide: 4 paths of 12 versioned files (33 %) exceeds a quarter; pass --whole REASON. Wide
scopes need --whole "<one sentence why it does not split>"; the sentence
lands in the claim record and status/who show it. --allow-directory is
removed: pass --whole instead. Live claims
are advisory: they say who works where and do not refuse path overlap. Two
lanes may claim the same
directory or the same file; claim and status print the overlap as a note.
The same issue or the same docs//fix/ lane branch still holds at most one
live claim. claim --resource <name> posts a name-only intent; the live integer is the next
positive value not occupied by an earlier first-occurrence request for that name. An explicit
posted value occupies that integer even after release; a released auto still occupies the
integer it would have been assigned. A second live hold of the same name and value is
refused: only the earliest live claim of that pair is the holder. Sequential allocations
stay unique even after a release. claim prints
how many versioned files the scope covers and which open claims it overlaps.
who <path> prints every live claim that holds a path.
Agents should read --json from status, claim, release, rescope, and who.
status prints each live claim's age from its claim comment as Xh Ym, and
marks it old after more than one hour.
bootstrap does two onboarding jobs in one command, in order, until the
state-ref cut (issue #164) retires the first of them for good. It first
adopts the exact <!-- agent-claim-ledger:v1 --> issue marker, ensures it is
locked and labelled, and safely converges concurrent first starts to the
earliest ledger, visibly closing later duplicates; it refuses to compete when
another machine-readable claim/ledger contract exists. It then creates or
reports this repository's claim-state ref, refs/aco/state, on its canonical
remote (origin) -- a git ref, invisible in the GitHub UI, never checked out
into a working tree. A present ref is a pure read: it prints the ref's commit
id and makes no write. An absent ref (proven by git ls-remote --exit-code,
never inferred from a fetch failure) gets one commit holding an empty state
tree (schema.toml, version = 1) pushed as a plain fast-forward. An
unreachable remote (auth or transport failure) fails loud instead of either
printing or writing. The ledger half disappears once the state-ref cut lands:
claim/release/rescope/status read and write the issue ledger only
until then. A claimed issue gets one reusable minimal projection comment
and a generation-scoped label.
Use release --coordinator-override only for an explicit coordinator action.
Ledger rollover (supersede) requires a coordinator whose named claim is the
only active claim and owns the ledger issue; the successor is a higher-numbered
open empty collaborator-locked issue, and the freeze is atomic.
reconcile also repairs a duplicated claim id it finds on the ledger, keeping the
newest occurrence and printing one REPAIRED claim '<id>': superseded <comments> -> survivor #<comment> line per id it fixes, where <comments> lists every superseded
comment it neutralized (the older CLAIM plus each terminal comment that honored its
release — there can be more than one, e.g. a release retry) as #id, #id, ....
An older occurrence only auto-repairs when it is already released, or when it
shares the survivor's agent and role (a same-agent re-claim, kept newest because
that reflects the agent's latest intent — this is not scoped to one identity, so
a same-agent duplicate spanning two issues, two lanes, or an issue and a lane
still only keeps the newer identity's workstream and silently ends the older
one).
A duplicate still active under two different agents is a real ownership
conflict; reconcile reports it and leaves the whole ledger untouched — for
every duplicated id, not just the conflicting one — instead of picking a winner.
agent-claim pr-check --pr <n> reads one pull request of the current
checkout's repository (or --repo OWNER/REPOSITORY) and answers one question:
which item does this landing close? It prints PR #<n> by <author> declares <classification> and exits 0, or prints one REFUSED: pull request #<n> ...
line and exits 1. Run it as a required check on every pull request that targets
the default branch. It needs a working tree of the repository (a shallow
checkout is enough) to read the repository's body_contract pin from
.agent-claim/board.toml, and refuses outright without one.
A pull request body carries exactly one classification line:
Work-Item: OWNER/REPO#n(orWork-Item: #nfor this repository) together with a closing reference for that same item —Closes #n, or any other keyword GitHub itself closes on, optionally qualified asOWNER/REPO#n; orNo-Item: docsorNo-Item: fixfor a lane that owns no issue.
pr-check refuses a body with no classification line, with more than one, or
naming two work items (split the pull request); a work item that is the claim
ledger issue or lives in another repository; a work item with no active claim
on the pull request's head branch; a closing reference naming anything but the
work item; a No-Item pull request without an active issue-less lane claim on
that head branch, or carrying any closing reference at all; a pull request
whose head branch lives in another repository; and a pull request that does not
target the default branch. A classification line inside a fenced code block is
documentation, never a declaration. Advances #n is read nowhere: a dispatched
slice is its own item, and its pull request closes it.
Parentage is GitHub's own sub-issue relation, not a line in a body. pr-check
reads the work item's recorded parent and that parent's open sub-issues. The
parent must be kind container (its own native issue type); any other kind is
refused by name, since only a container holds children. Closing the parent's
last open child permits closing the parent in the same landing but
requires it only when the parent's own Next line names no further work
(keiner/keine/nichts/none/-, case-insensitively, all count as none);
with further Next work the container keeps dispatching slices and the
landing may pass without closing it. A parent that keeps other open children
must stay open and carry a Next line in its body. A parent recorded in
another repository is refused by name, never skipped silently. claim warns
when a slice-shaped title such as Schema (#79 Scheibe 21) names a parent
that GitHub does not record as one, and refuses outright when the target
itself is a container (claim a child).
agent-claim board reads the open issues, open PRs, PRs merged since the
oldest open issue was filed, and the claim ledger, then prints a ranked
projection with READY NOW and STALE sections. A pull request that
advances an issue without closing it — an epic's dispatched slice, typically
— credits that issue when the pull request names it a second time outside a
dedicated Refs #N/Part of #N line; that is a syntactic marker, not a
verified relation, so an unrelated pull request naming the same issue twice
by coincidence would still credit it.
The table exposes which exact contract headings were found, an
EXPECT cell (-, OPEN/TOTAL, or ruled N / ruled N old), a concise Next, and a CLAIM
cell with - or the agent, role, claim age, and old when the claim comment
is older than one hour; JSON includes the complete derived contract state and
the same open/total expectation progress. An Erwartung, Erwartungen, or
Erwartungsliste heading makes the following block an expectation list: a line
with *(Default: yes|no|later)* is proposed. A block is ruled only when every
expectation line carries a *(geregelt: ja)* or *(geregelt: NEIN ...)*
marker; absent or malformed markers remain proposed. A ruled block also shows
how many default-branch first-parent landings (git log --first-parent
committer times) happened after its heading date (DD.MM.YYYY, preferring
GEREGELT: Operator …); ten or more mark it old. Missing or proposed
expectations have neither fresh nor old. If a ruled block has no readable date
or git cannot name the default branch, that is an error, never silently fresh.
It never writes GitHub.
The target defaults to the repository of the current checkout;
for another GitHub repository run agent-claim --repo FlexOr2/atelier-2 board.
The current checkout may set priority_labels as an ordered non-empty list in
.agent-claim/board.toml; absent configuration uses security, data, ci,
product, ux, then cleanup. board_rank orders every item on five fields:
category, then score, then the critical label's index, then container, then
issue number. Category is critical (the first three configured labels or a
Bug, competing by score among each other — see the KIND paragraph below)
first, then a blocker, then a container's completing last child, then the
remaining configured labels, then unlabelled; the label index only ever
tie-breaks inside the critical category, so every other category still
degenerates to plain number order at equal score. The same file may set one
idea_label; an item carrying that label with no Now/Next/Blocked by/Done when
projection ranks normally, and next tells the head Problem neu prüfen und Item verfeinern. Once it has a complete contract, its own Next takes over;
without the configured label, a projectionless item remains body incomplete.
The same file's body_contract key pins how a work-item body itself is read
(prose, the default, or the typed block below); priority_labels and
idea_label mean the same thing in either mode.
The board table's FREED column shows YYYY-MM-DD (N d) when every listed
issue blocker has closed, using the latest such UTC closing date and whole days
since then; it otherwise shows -. Every item in board --json carries the
same values as freed_on (YYYY-MM-DD or null) and freed_days (a
nonnegative integer or null).
An item's KIND column (task, bug, feature, container, or - when the
forge reports no native issue type) comes from GitHub's issue type, never a
label; a Bug counts as critical exactly like the first three configured
labels, per the ranking paragraph above. A container-kinded issue shows its
sub-issue progress — board's KIND cell (container 2/3) and a trailing
CONTAINERS section (#122 2/3 closed; open: #112 (blocked by #136));
board --json carries the same figures under each item's container
(closed, total, open_children) and its parent under container_parent.
A container is never itself actionable — its board/next reason reads
container; claim a child — and its own last open child (once at least one
sibling has closed) ranks above ordinary work, though never above a critical
item or a real blocker.
board also shows an UNCUT section naming, per item, the slice-table
rows (#79's grammar) that are not yet linked to a dispatched child — an
undispatched (—) row, or one whose item cell is neither the marker nor a
well-formed #n link — by row index, as #<item>: rows N, N, … uncut; a row
linking any issue (open or closed) is landed, never uncut. A malformed row
(the wrong column count, or a non-integer # cell) is named by its # cell
and reason instead, e.g. row "B": index must be a positive integer,
appended to the same line. board --json's uncut contract changed: the
top-level uncut list still carries item, but rows is now a list of
{"index", "title"} objects (was a list of bare name strings) and a new
malformed list carries each malformed row's line, id_cell, and
reason. This is a finding, never a status column: a landed row simply
leaves the list.
board prints a RECOVERY (close or re-project) section after STALE,
followed by the CONTAINERS and UNCUT sections above; next names recovery
items first with that step: open issues that a merged pull request already
declared as its Work-Item: — the landing happened, the bookkeeping did not. It
is keyed on that typed line, never on an issue's update time, and never names
the ledger issue. next --json carries the same items under recovery.
board ends its text output with a requests: N line, counting every read
the command made through the forge port; board --json carries the same
count as a top-level "requests" field.
agent-claim rulings lists only open board items with open expectation lines
as #NUMBER OPEN/TOTAL: TITLE; rulings --json returns the same number,
title, open, and total values. It is read-only and uses the board's
priority category and score first, then fewer open expectation lines and the
issue number. An empty list succeeds.
Use agent-claim next (or agent-claim next --json) to name the board's
top-ranked qualifying row — the same board_rank order board shows.
next --json always carries an action field, naming one of three shapes
or null when nothing qualifies. work_item: the row is open, free,
unblocked, not frozen, and has a complete Now/Next/Blocked by/Done when
contract, or is a configured projectionless idea; its text form also prints
Run: agent-claim claim <n> --scope <paths> (the literal placeholder
<paths>, since the scope cannot be derived) and a line pointing at the
item body for the real paths — the --json form is unchanged beyond the
always-present action field. cut_slice: a container with no open child
still names work in its own Next line ({"action": "cut_slice", "number", "title", "slice", "cut_title"}); slice is the container's own human step,
cut_title is the exact title cut accepts (#177: the first uncut
[[slice]] entry's title when one exists, else slice) — the head cuts
that slice (agent-claim cut <number> --title "<cut_title>") and dispatches
it. close_container: a container with no
open child and no further Next work ({"action": "close_container", "number", "closed", "total"}); the head closes it. A container is never
itself the work_item target. Pulling is not dispatching, so unruled
expectations never withhold a work_item; the pulled item carries
Erwartungen ungeregelt, beim Ziehen zuerst refinen instead, and an item
ruled long ago carries vor N Landungen geregelt, beim Ziehen neu refinen
(both as the JSON ruling_hint). Items that genuinely cannot be worked —
claimed, blocked by an open issue, frozen, or without a complete contract
when they are not a configured projectionless idea — are named with that
reason under SKIPPED (also in the JSON skipped list; a container chosen
as the next action is never also listed there). next exits 3 when
nothing qualifies, but still prints at least No actionable item. (plus any
SKIPPED/RECOVERY sections) in text, and --json still emits an object —
{"action": null, "recovery": [...], "skipped": [...]} — never nothing.
claim refuses work out of order when a higher-priority actionable item — the
same order board and next use — is free. It also refuses a blocked item,
one sentence per repository pin, then the same shared override in either mode:
- Prose:
claimrefuses an item whoseBlocked bystill names at least one open issue (a pull request, or a closed or missing issue, does not count — those stay their own refusals below). - Block (
body_contract = "block", below):claimrefuses an item that has at least one open GitHub blocked-by dependency, including a foreignowner/repo#n; a pull request or a closed same-repository dependency does not count. - Shared: the message is
#5 is blocked by #3 (open); pass --out-of-order REASON to claim it anyway(a foreign entry renders asowner/repo#n), naming every open blocker. Pass--out-of-order REASONto proceed deliberately in either case; it remains visible as a warning and preserves the reason in the claim comment.
Before it writes a claim, claim also reads the pulled issue's live contract:
Now, Next, Blocked by, and Done when each appear at most once outside
fenced code examples. Blocked by is exactly nichts or a comma-separated
#N list such as #62, #75; every listed issue must be open. claim also
refuses with #<n> body incomplete: <missing sections> (body order, e.g.
#150 body incomplete: Now, Done when) when any of the four sections is
empty, unless the issue is a configured projectionless idea (above) — the
same rule board already applies to actionable (whose own short
body incomplete form is unchanged), so a freshly cut child (its
board.CHILD_SKELETON body has every section present but empty) is refused
until the head fills it in. The check does not limit body size or inspect
references in Next, and release stays available even when the body's
contract has since become invalid.
A body line Eingefroren bis: <trigger in one sentence> (Operator, DD.MM.YYYY)
freezes an issue: it drops out of next and the higher-priority refusal check
even though its score keeps showing on board, and deleting the line thaws it
again. The tool only checks the line's form, never who wrote it — that
authority is the coordination contract's. It reads the body the way GitHub
renders it: a marker inside a fenced code block (``` or ~~~, including
one left unclosed to the end of the body) is documentation, never a live
marker — examples belong in a fence. A blockquoted > Eingefroren bis: …
still freezes; this repo already quotes operator rulings, so a quoted freeze
line reads as the freeze itself.
Everything above describes the default, body_contract = "prose": the four
regex-read ## Now/## Next/## Blocked by/## Done when sections. A
repository may instead set, in .agent-claim/board.toml:
body_contract = "block"Under that pin, board, next, issue-mode claim, cut, rulings, and the
parent-body part of pr-check read a work item's Now/Next/Done when,
freeze, expectations, and undispatched slices from one typed agent-claim
fenced TOML block instead — no regex, no German markers, no slice table. The
claim ledger's own issue (protocol.LEDGER_ISSUE) is exempt and always read
as prose: its body belongs to ledger discovery, never the work-item grammar.
A fresh, unfilled item looks like this — the same four lines cut writes
automatically for a dispatched child, and what a human pastes by hand into a
gh issue create / operator-opened item:
```agent-claim
version = 1
now = ""
next = ""
done_when = ""
```
The full schema:
```agent-claim
version = 1
now = "Current fact"
next = "One concrete next action"
done_when = "Observable terminal condition"
frozen_until = { trigger = "named trigger", ruled_on = 2026-09-06 }
[[expectation]]
text = "An operator sentence"
default = "later"
[[expectation]]
text = "A ruled operator sentence"
ruling = "yes"
ruled_on = 2026-09-06
[[slice]]
index = 4
title = "Block contract in issue bodies"
```
version, now, next, and done_when are required; now/next/done_when
may be the empty string (an unfilled skeleton — incomplete, but still a valid
block). frozen_until, expectation, and slice are optional; an explicit
slice = [] is a table intentionally left present but empty (it still counts
as "has a table" for cut --row). Each [[expectation]] is either proposed
(default = "yes" | "no" | "later") or ruled (ruling = "yes" | "no" with a
TOML date ruled_on) — never both, never neither. Per-slice files, done-when,
and dependencies stay in the human prose beside the block; only a slice's
index and title are typed. Schema and version tokens, and an expectation's
default/ruling values, are protocol — always this exact English spelling;
every other value (now/next/done_when, frozen_until.trigger,
expectation text, slice title) is the operator's prose and is never
parsed, exactly like prose mode. next's own retained non-parsed vocabulary
(keiner | keine | nichts | none | - for "no further work", plus tbd | todo | unknown for "not yet concrete") still applies to a block's next value.
An item with no recognized agent-claim fence at all is body legacy; one
with a recognized fence that is unclosed, duplicated, invalid TOML, or a
schema violation is body malformed: <path>: <reason> (e.g. body malformed: version: version must be exactly 1). Both fail loud, by name, on
board, next (SKIPPED), and claim (body-legacy / body-contract
checks) — never a guess through the missing or broken block, and a container
in either state is never proposed as cut_slice or close_container.
Blockers come from GitHub's own issue-dependency relations, not a body
line — Blocked by: prose beside the block is documentation only and changes
nothing. An open same-repository dependency blocks exactly like a local
Blocked by blocker; a foreign owner/repo#n blocks the same way and is
named the same way (blocked by owner/repo#n, or #3, owner/repo#n mixed
with a local one). A same-repository closed dependency does not block and
lets board's FREED column and claim proceed; a closed foreign
dependency does not free an item on its own (foreign relations can only
block, never free). Unlike prose, a same-repository pull-request dependency
blocks or frees exactly like any other dependency — blocker-is-a-PR is a
prose-only check. Parentage stays on sub-issues in both modes; it never
passes through the body.
cut writes and reads the block the same way it writes and reads the
prose slice table: without --row it links the first [[slice]] entry when
one exists and otherwise creates an untied child (table or not); --row N
selects entry N and requires --title to equal that entry's own title
exactly, refusing before any write on a mismatch. cut removes only the
selected entry (slice = [] after removing the last one) and preserves every
other byte of the body, including CRLF line endings, exactly. The two refusal
strings are shared with prose/#151: #N has no slice table; --row needs one to select a row from, and #N has no cuttable slice row; 0 malformed rows need a hand fix (block mode cannot itself produce malformed rows; the
string is kept so both modes read the same way).
Migration is a hand edit, not a command. There is no migration command,
module, or receipt: one reviewed AI session per repository transcribes each
open item's current prose into a block (refusing, by name, anything it
cannot derive rather than inventing it) and keeps the existing prose in place
— GitHub's own edit history is the undo. Forge dependencies are added to
reproduce existing Blocked by relations before the pin lands; parentage
needs no migration since it already lives on sub-issues. Upgrade every
active agent-claim installation to a release containing this contract
before a repository sets body_contract = "block" — an older client either
does not know the key (and keeps reading prose blindly) or, once every open
item carries a block, would otherwise see a repository it cannot coordinate
on correctly. From the pin onward, every hand-created issue (gh issue create, an operator-opened item) must carry a valid block — the four-line
skeleton above — or it is body legacy; only cut writes that skeleton
automatically.
agent-claim cut <container> --title "…" dispatches a container's next slice
as a fresh child issue in one step: it creates the issue (native type Task),
records it as the container's sub-issue, and, when there is a slice-table row
to link, rewrites the container's slice table so that row now links
#<child> instead of the undispatched — marker. The fresh child's body is
board.CHILD_SKELETON — every contract section present, Now/Next/Done when empty and Blocked by: nichts — so it is body incomplete (invisible
to next, refused by claim) until the head fills it in.
cut without --row links into the first still-cuttable row when one exists
and otherwise creates an untied child, table or not (#151): a container with
no slice table at all — only a numbered Next line, as #122 carried on
06.09.2026 — and one whose table is fully linked but whose own Next line
still names further work both cut this way, the container's body left exactly
as it was. next never prints --row, so a command it prints for a
container is always one cut accepts. --row N requires a table containing
an uncut row N and refuses by name otherwise: no slice table at all, N
already linked (#122 row 4 is already cut (#150); cuttable rows: 5, 6, 7),
no row left uncut anywhere in the table (#122 has no uncut row; rows 4-7 are cut), or no row N left cuttable for any other reason, in which case
any malformed rows are named by their # cell and reason instead of only
counted (#79 has no cuttable slice row; row "B": index must be a positive integer).
Every refusal precedes every write. cut refuses when the forge cannot
create a child issue or update an item body (capability() answers anything
but read_write for either); when the target is not an open container, or is
itself a child of another issue (nested containers are not supported); and,
for --row N, when the table it names does not exist, or has no row N left
cuttable — a row already linked to any issue, or one whose item cell is
malformed, is never a target. None of the three writes are atomic with each
other — nor is the child issue's creation atomic with its own sub-issue
relation write inside create_child — so a failure at any point after the
child issue exists names the created child and the step that failed, and
instructs a hand fix rather than a re-run, which would create a second child.
docs/- and fix/-prefixed branches land within one session without a GitHub
issue. Omit the positional issue number on claim/release for this lane mode,
derived from the current checkout branch — no separate --lane flag. Lane mode
is refused with the offending branch name and both remedies (pass an issue
number, or check out a docs//fix/ branch) when the branch does not follow
that convention, so a builder who simply forgot the issue number never gets a
silent, unlabeled claim:
git worktree add ../repo-worktrees/docs-tidy-readme -b docs/tidy-readme
cd ../repo-worktrees/docs-tidy-readme
agent-claim claim --agent "Ada" --scope README.md
agent-claim release --merged 58Like an issue claim, a lane claim must begin from a clean linked worktree
checked out on that branch — claim fails outside one.
A lane claim shares the same identity exclusivity, advisory overlap notes, and
release path as an issue claim: two lane claims collide on the same branch;
overlapping scope with another lane or issue is a visible note, not a refusal.
status and protect show and authorize it the same way. A lane owns no
GitHub issue, so it gets no projection comment or label, and reconcile never
touches it.
There is no flag to name a lane explicitly on release: a lane's only name is
the checkout branch it was claimed from, so releasing it — including a
coordinator override — always runs from a checkout of that same lane branch.
If the original worktree is gone or held by another session, re-create a
worktree on that branch (git worktree add <path> <lane-branch>) and run
agent-claim release --claim-id <id> --coordinator-override --role coordinator --abandoned "..." from inside it, where <id> comes from agent-claim status
(omitting --claim-id still filters by the releasing agent, coordinator
override or not, so a foreign stuck claim needs the id).
The lane-claim marker extends the same agent-claim:v2 event, but with a
different key set than an issue claim. A pre-issue-38 agent-claim cannot
parse it: it fails loud on the whole ledger, not just the lane claim, until it
upgrades — deliberate, since an agent that cannot read the live locks must not
build blindly. Upgrade every agent-claim installation together with (or
before) the first lane claim posted to a shared ledger.
Run agent-claim policy --print and append the block once into the file the
provider actually loads. Skip the append when <!-- agent-claim-policy:v1 -->
is already present. Never overwrite an existing loader. The CLI does not write
~/.claude, ~/.codex, or ~/.grok.
Copy this hook once into the file the provider actually loads. Skip when
Write|Edit|MultiEdit|write|search_replace is already present. Never overwrite
an existing hook file. The CLI does not write ~/.grok.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write|Edit|MultiEdit|write|search_replace",
"hooks": [
{
"type": "command",
"command": "agent-claim protect",
"timeout": 60
}
]
}
]
}
}GitHub via the gh CLI is supported today. Invocations set NO_COLOR=1
and GH_NO_UPDATE_NOTIFIER=1, strip ANSI from output, and parse pretty or
compact JSON, so a wrapping gh shim is not required. The tool does not
automatically allocate work, merge code, or operate a lease server. Omitted --agent follows
the documented else-chain; it does not invent an identity. It intentionally
leaves policy-file generation and non-GitHub adapters for a later release.