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_id que 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_unmet y la lista de lo que falta.
  • Reintentar es seguro. Si el mismo agente repite la petición con el mismo execution_id mientras 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_lock antes 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

  1. Conecta tu agente por MCP con su propio token.
  2. Instala la skill de MarQ: enseña al agente a reservar antes de escribir y a liberar siempre, incluso si falla.
  3. 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.

Blog