Cómo evitar que dos agentes de IA se pisen la misma tarea
MarQ · 2026-11-08
Si dos agentes de IA editan la misma tarea a la vez, el último en escribir gana y el trabajo del otro desaparece sin avisar. La solución clásica en sistemas distribuidos es una reserva (lease): antes de tocar algo, el agente la pide; mientras la tiene, nadie más puede cambiarla; y si el agente se queda colgado, la reserva caduca sola. Un token de control (fencing token) añade la garantía que faltaba: aunque un agente lento reaparezca tarde, sus escrituras antiguas se reconocen como obsoletas y se rechazan.
El problema: dos agentes, una tarea
Imagina dos agentes de código, uno en Claude Code y otro en Cursor, trabajando sobre el mismo tablero. Ambos leen la tarea "Construir la API REST" en estado por hacer. Los dos empiezan, los dos escriben notas y cambian el estado. El resultado depende del orden en que lleguen las peticiones, y nadie se entera de que se han pisado.
Con personas esto se resuelve hablando. Con agentes no hay conversación: hay peticiones concurrentes. Necesitas una regla mecánica, y que se cumpla en el servidor, no en la buena fe de cada agente.
La reserva: pedir antes de escribir
En MarQ un agente reserva la tarea con la herramienta MCP pm_acquire_task antes de modificarla. Si está libre, recibe tres cosas:
- un
lease_idque identifica su reserva, - un
fencing_token, un número que sube cada vez que se concede una reserva nueva sobre esa tarea, - una fecha de caducidad (
expires_at).
Si otro agente ya la tiene, la respuesta no es un error genérico sino información útil: quién la tiene, hasta cuándo y cuántos segundos esperar antes de reintentar (retry_after_seconds). Un agente bien instruido pasa a otra tarea en vez de insistir.
Dos detalles importantes:
- Las dependencias cuentan. Si la tarea depende de otras que no están hechas, la reserva se rechaza con el motivo
dependencies_unmety la lista de lo que falta. - Reintentar es seguro. Si el mismo agente repite la petición con el mismo
execution_idmientras su reserva sigue vigente, recibe la misma reserva con el plazo ampliado, en lugar de un conflicto consigo mismo.
Reservas que caducan: nadie se queda bloqueado
Un agente puede caerse, perder la conexión o quedarse sin contexto. Si la reserva fuera eterna, la tarea quedaría bloqueada para siempre. Por eso toda reserva tiene un plazo: 15 minutos por defecto y hasta 2 horas como máximo (configurables en el servidor con LOCK_DEFAULT_TTL_SECONDS y LOCK_MAX_TTL_SECONDS).
- El agente que sigue trabajando la renueva con
pm_renew_task_lockantes de que venza. - Cuando termina, la libera con
pm_release_task. - Una reserva ya vencida no se puede renovar con efecto retroactivo: otro agente pudo haberla tomado entre medias. El agente debe volver a reservar.
Cuando una reserva caduca sin haberse liberado, MarQ lo detecta una sola vez, lo deja en el historial y avisa a las personas del proyecto, para que se sepa que un agente abandonó el trabajo a medias.
El token de control: el agente lento que vuelve tarde
Hay un caso que la caducidad sola no cubre. El agente A reserva la tarea, se queda pausado más tiempo del plazo, la reserva caduca, el agente B la toma y avanza. Cuando A despierta, cree que sigue siendo el dueño e intenta escribir.
Aquí entra el token de control. Cada reserva nueva incrementa el contador de esa tarea; la de A era la generación 4 y la de B es la 5. Si A presenta su reserva antigua, el servidor la reconoce como obsoleta (stale_lock) y rechaza la escritura. La idea viene de los sistemas de bloqueo distribuido: el recurso comprueba la generación, no la confianza.
Tres modos: de medir a exigir
No todo el mundo quiere empezar rechazando escrituras. MarQ tiene tres modos, con la variable LOCK_ENFORCEMENT:
| Modo | Qué hace con una escritura sin reserva válida |
|---|---|
off |
No comprueba nada. |
warn (por defecto) |
La permite, pero deja el aviso en el historial para que puedas ver quién se saltó la regla. |
enforce |
La rechaza con un error claro y la deja registrada. |
Los códigos de rechazo son cuatro: lock_required (no había reserva), lock_expired (la reserva venció), locked_by_other (la tiene otro) y stale_lock (generación antigua). Un agente que los entiende sabe qué hacer en cada caso: reservar, volver a reservar, esperar o cambiar de tarea.
La recomendación práctica: empieza en warn, revisa durante unos días el historial en busca de escrituras sin reserva y pasa a enforce cuando el listado esté limpio.
Y las personas
Las reservas coordinan sobre todo a los agentes. Una persona que edita desde el panel solo se encuentra con la regla cuando otro tiene la tarea reservada, y un administrador puede pasar por encima con su permiso. En ese caso el cambio queda auditado como una excepción. También puede liberar a la fuerza una reserva atascada indicando un motivo, y eso queda en el historial.
Cómo probarlo
- Conecta tu agente por MCP con su propio token.
- Instala la skill de MarQ: enseña al agente a reservar antes de escribir y a liberar siempre, incluso si falla.
- Mira cómo se ve una tarea reservada en la demo de solo lectura: la tarea "Construir la API REST" lleva una reserva de un agente.
Las reservas no sustituyen a otras capas, como trabajar cada agente en su propia rama de git: protegen el estado del tablero, es decir, quién hace qué y en qué estado está cada tarea, que es justo lo que se corrompe cuando varios agentes escriben a la vez.