複数のエージェント、ひとつのリポジトリ

各エージェントが取ったタスクは、他のエージェントから取得済みとして見えます。claim は git の ref なので、同じタスクに手を伸ばした 2 番目のエージェントは保持者の名前つきで拒否され、隣で作業するエージェントはどのファイルを共有しているかを知らされます。

同じリポジトリの 2 つの worktree
ANK_AGENT=claude:auth ank claim 0e7d
claimed TASK-0e7de9c55b80 migrate-auth-to-opaque-sessions -> HEAD

cd ../svc-rate
ANK_AGENT=codex:rate ank claim 0e7d
error[4]: TASK-0e7de9c55b80 held by claude:auth (expires in 30m)
  -> ank claim TASK-20e993387d9d   (another ready task in this scope)
ANK_AGENT=codex:rate ank claim 20e9
warning: claude:auth holds TASK-0e7de9c55b80, overlapping on src/auth/login.ts
claimed TASK-20e993387d9d rate-limit-the-login-route -> HEAD

それがないと何が起きるか

ひとつのリポジトリで 2 つのコーディングエージェントを走らせると、失敗は静かに起きます。エラーは出ません。ただ作業がぶつかるだけです。

  • 同じタスクを二度

    両方のエージェントが同じ backlog を読み、同じ先頭の項目を取ります。どちらも相手が始めたことを知りません。

  • 同じファイルを書き換える

    独立に見える 2 つのタスクが同じモジュールに触れます。後のブランチが前の変更を打ち消し、merge の時点で初めて気づきます。

  • 黙って切れる claim

    セッションが途中で落ちます。タスクは「作業中」のまま、誰も手をつけておらず、何が試されたのかも誰も知りません。

エージェントごとに、ツリーとアイデンティティをひとつ

各エージェントに専用の作業ツリー(git worktree か clone)を与え、デフォルトブランチから切ったばかりのブランチに置き、ANK_AGENT で専用のアイデンティティを与えます。ank status はデフォルトブランチとのずれを示します。古いベースは、ここで緑のツリーを別の場所で赤にするからです。

同じリポジトリの worktree はすべて refs/ank/ を共有するので、その間の競合は git の compare-and-swap が決着をつけます。勝者はひとり。別々の clone は origin という名前の remote を通じて調停され、ank init がそれを refs/ank/* を運ぶように設定します。

ANK_AGENT がなければ、アイデンティティは <user>@<hostname> にフォールバックします。つまり同じマシンの 2 つのターミナルは、ref から見れば同じエージェントです。同じ名前での 2 つ目の claim は拒否され、その拒否メッセージがセッションに自分の名前を与える方法を示します。

git worktree add ../svc-rate -b task/rate
export ANK_AGENT=codex:rate
2 つのセッション、ひとつの名前
ank claim 51c2
error[7]: human:marie holds a live claim on TASK-820d259af6a7 (expires in 30m)
  -> ank release --reason "<why>"   (a second session on this machine sets its own ANK_AGENT)

ANK_AGENT=human:marie-2 ank claim 51c2
claimed TASK-51c2a0f6d418 say-in-the-readme-what-a-session-is-now -> HEAD

claim は git の ref

ank claim は 3 つのことを同時に行います。どれも手で書き換えられるフィールドではありません。

  1. タスクが in_progress になる

    さらに claim は HEAD を設定するので、続くコマンドに id は要りません。claim はアイデンティティごとに一度にひとつ。

  2. refs/ank/claims/ に ref ができる

    2 つのエージェントが同じタスクを狙ったとき、これが調停します。どの worktree からも見え、remote がそれをすべての clone に届けます。

  3. 完了条件がハッシュで凍結される

    ハッシュは claim の記録に入り、エディタの手は届きません。作業に合わせて条件を緩めても何も解決しません。done が比較して拒否します。

作業することで更新されるリース

claim の有効期限は 30 分で、ank context、自分が持つタスクへの ank show、書き込むときの ank log で更新されます。覚えておくべき heartbeat はありません。学んだことを書き留めることが、そのままロックを保つことになります。期限切れの claim はもう有効ではなく、誰でもそのタスクを取れます。

claim を黙って切らさない

ank release --reason はタスクを返し、その理由をタスクの log に残します。次の保持者は、あなたが試したことを繰り返す前に ank log <id> でそれを読みます。ank done は ref を消しません。commit とブランチを記した完了記録に変えるので、merge を待つ間に他のツリーがタスクを取ることはありません。

ref に入っているもの
git for-each-ref refs/ank/
fc6c72c07eaee7d0e1d31f38316d8d5304382d44 blob	refs/ank/claims/TASK-0e7de9c55b80
e671470149b55366650f6da572866a25f33315c5 blob	refs/ank/claims/TASK-20e993387d9d

ank release --reason "the criterion names a test that does not exist yet"
released TASK-820d259af6a7 -> open

claim は重なる scope を名指しする

どのタスクも scope、つまり glob の集合を持ちます。有効な claim と scope が重なるタスクを claim すると、ank はそれでも取らせたうえで、保持者、そのタスク、共有するパスを示す警告を出します。拒否のためのエラーではなく、読むための事実です。2 つのタスクがファイルを共有してよいかは判断であり、その判断は claim する側に委ねられます。

skills がエージェントに教えるルールはここから来ています。衝突しえないタスクを取るか、何も取らないか。開いているタスクに、ブロックされておらず衝突もしないものがないなら、アイドルのセッションのほうが、2 つのエージェントが同じ範囲を書き換えるより安くつきます。

一方の scope は src/auth/**、もう一方は src/auth/login.ts を含む
ANK_AGENT=codex:rate ank claim 20e9
warning: claude:auth holds TASK-0e7de9c55b80, overlapping on src/auth/login.ts
claimed TASK-20e993387d9d rate-limit-the-login-route -> HEAD

誰が何を持っているかを見る

claim の前に、エージェントは 3 つを読みます。他のエージェントが何を持っているか、自分の claim が何と重なるか、そして blocked_by がどんな順序を課しているか。

ank status

ブランチ、アイデンティティ、保持中の claim とその期限、そして他で保持されているすべての claim。

~/svc-rate
ank status
branch task/rate (default main)
drift 1 entity file(s) differ from main (git merge main)
identity codex:rate (ANK_AGENT)
claim TASK-20e993387d9d Rate-limit the login route
  expires 2026-10-02T08:59:19Z
elsewhere 1 claim(s) by other agents
  TASK-0e7de9c55b80 Migrate auth to opaque sessions (claude:auth until 2026-10-02T08:59:18Z)
perimeter the scope of TASK-20e993387d9d, 0 constraint(s)
queue 0 proposal(s), 0 finished elsewhere
corpus 0 fault(s), 4 signal(s)

ank graph

blocked_by の DAG を、ブロックしているタスクの下にインデントして表示します。ブロッカーのないタスクはルートで、すぐに着手できます。

~/svc
ank graph
.

TASK-0e7d  [claimed:claude:auth] Migrate auth to opaque sessions
└── TASK-357b  [open] Say in the README what a session is now
TASK-20e9  [claimed:codex:rate] Rate-limit the login route

3 task(s), 2 root(s) — indented under what blocks them

ダッシュボードがポーリングするのは ank status と ank find です。claim を更新してしまう context や show ではなく、古い ref を刈り込む、つまり書き込む check でもありません。

並列性は宣言せず、導き出す

  • 順序は blocked_by だけ

    ブロッカーが完了したタスクは一斉に着手可能になり、ank context がそれを、多くをアンブロックするものから順に並べます。blocked_by がなければ、その順序は作り話です。

  • タスクごとにブランチひとつ

    ank done は、タスクが走ったツリーの中でそれを証明します。merge は git の仕事です。複数のタスクからなる変更には、各部分にブロックされ、独自の検証を持つ統合タスクを用意します。

  • git が得意なことは git に

    worktree を作る、ブランチに名前をつける、merge する。そうした ank の動詞はありません。タスク、claim、証明は ank の領域で、ブランチと履歴は git のものです。

ank をインストール

コマンドは二つ。Linux、macOS、Windows に対応し、git 2.34 以降が必要です。

npm install -g @haksolot/ank
npx skills add haksolot/ank