Varios agentes, un repositorio

Cada agente toma una tarea que los demás ven tomada. El claim es una ref de git: el segundo agente que va a por ella recibe un rechazo con el nombre del titular, y el que trabaja al lado se entera de qué archivos comparte.

dos worktrees de un mismo repositorio
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

Qué se rompe sin él

Pon dos agentes de código sobre un mismo repositorio y los fallos son silenciosos. Nada da error; el trabajo simplemente choca.

  • La misma tarea, dos veces

    Los dos agentes leen el mismo backlog y cogen el mismo primer elemento. Ninguno sabe que el otro ya empezó.

  • Los mismos archivos, reescritos

    Dos tareas parecen independientes y tocan el mismo módulo. La segunda rama deshace la primera, y te enteras en el merge.

  • Un claim que caduca en silencio

    Una sesión muere a mitad de camino. La tarea sigue diciendo «en curso», nadie está en ella y nadie sabe qué se intentó.

Un tree y una identidad por agente

Dale a cada agente su propio working tree, un git worktree o un clon, en una rama recién sacada de la rama por defecto, y su propia identidad en ANK_AGENT. ank status indica cuánto te has desviado de la rama por defecto, porque una base desfasada pone en rojo en otro sitio un tree que estaba en verde.

Todos los worktrees de un repositorio comparten refs/ank/, así que el compare-and-swap de git resuelve la carrera entre ellos: un solo ganador. Los clones separados se arbitran a través de un remote llamado origin, que ank init configura para transportar refs/ank/*.

Sin ANK_AGENT, la identidad recurre a <user>@<hostname>, así que dos terminales en la misma máquina son un solo agente para las refs. Un segundo claim con el mismo nombre se rechaza, y el rechazo explica cómo darle a la sesión el suyo.

git worktree add ../svc-rate -b task/rate
export ANK_AGENT=codex:rate
dos sesiones, un mismo nombre
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

Un claim es una ref de git

ank claim hace tres cosas a la vez, y ninguna es un campo que se pueda editar a mano.

  1. La tarea pasa a in_progress

    Y claim fija HEAD, así que los comandos siguientes no necesitan id. Un claim a la vez por identidad.

  2. Aparece una ref en refs/ank/claims/

    Es lo que arbitra cuando dos agentes van a por la misma tarea. Todos los worktrees la ven, y el remote la lleva a cada clon.

  3. El criterio se congela por hash

    El hash va al registro del claim, fuera del alcance del editor. Rebajar el criterio para que el trabajo encaje no desbloquea nada: done compara y se niega.

Un lease que se renueva trabajando

Un claim dura 30 minutos y se renueva con ank context, con ank show sobre la tarea que tienes y con ank log cuando escribe. No hay heartbeat que recordar: anotar lo que aprendiste es lo que mantiene el bloqueo. Un claim caducado ya no está vivo, y cualquiera puede tomar la tarea.

Nunca dejes que un claim caduque en silencio

ank release --reason devuelve la tarea y deja el motivo en su log, donde el siguiente titular lo lee con ank log <id> antes de repetir lo que ya probaste. ank done no borra la ref: la convierte en un registro de finalización que nombra el commit y la rama, para que ningún otro tree tome la tarea mientras el merge está pendiente.

lo que guardan las refs
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

Un claim nombra los scopes con los que se cruza

Cada tarea lleva un scope, un conjunto de globs. Si tomas una tarea cuyo scope se cruza con un claim vivo, ank la toma igualmente, con un aviso que nombra al titular, su tarea y las rutas compartidas. Es un dato que leer, no un error que rechazar: si dos tareas pueden compartir un archivo es una decisión, y le toca a quien hace el claim.

La regla que las skills enseñan a los agentes sale de ahí: toma la tarea que no puede chocar, o no tomes ninguna. Cuando nada abierto está a la vez desbloqueado y despejado, una sesión parada sale más barata que dos agentes reescribiendo el mismo perímetro.

un scope es src/auth/**, el otro nombra 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

Ver quién tiene qué

Antes de hacer un claim, un agente lee tres cosas: qué tiene otro agente, con qué se cruzaría su propio claim y qué orden impone blocked_by.

ank status

La rama, la identidad, el claim que tienes y cuándo caduca, y cada claim que hay en otra parte.

~/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

El DAG de blocked_by, indentado bajo lo que bloquea. Una tarea sin bloqueos es una raíz, y está lista.

~/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

Un dashboard consulta ank status y ank find. No context ni show, que renuevan un claim, ni check, que poda las refs obsoletas y por tanto escribe.

El paralelismo se deduce, no se declara

  • blocked_by es el único orden

    Las tareas cuyos bloqueos están terminados quedan listas a la vez, y ank context las lista, primero las que más desbloquean. Si no hay blocked_by, el orden es una ficción.

  • Una rama por tarea

    ank done prueba la tarea en el tree donde se ejecutó. El merge es cosa de git; un cambio hecho de varias tareas tiene una tarea de integración, bloqueada por cada parte, con sus propios verificadores.

  • Git se queda con lo que git hace bien

    Ningún verbo de ank crea un worktree, nombra una rama ni la mergea. Tareas, claims y pruebas son el plano de ank; ramas e historial siguen siendo de git.

Instalar ank

Dos comandos. Linux, macOS y Windows, con git 2.34 o posterior.

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