Skip to main content
Glama

MARL Cop & Thief - Agentes de IA duales sobre MCP

Un juego de persecución descentralizado y parcialmente observable entre dos agentes de IA autónomos: el Policía y el Ladrón, que conversan en lenguaje natural libre a través de servidores MCP, deciden movimientos con un motor de minimax teórico de juegos + RL de auto-juego, se renderizan en vivo en un panel de control web y envían por correo un informe de partida JSON acordado mutuamente mediante la API de Gmail.

Universidad de Haifa · Orquestación de Agentes de IA (ex06) · Dr. Yoram Segal. Un comando lo lanza todo; una pestaña del navegador ejecuta una partida completa.


Aspectos destacados

  • Nodo de un solo comando - python -m cop_thief.app arranca ambos servidores MCP, los túneles públicos de Cloudflare y un panel de control del navegador juntos (sin túneles huérfanos, sin malabares de puertos).

  • Panel de control web - estado del nodo en vivo, URLs/tokens públicos listos para copiar, un formulario de desafío contra el oponente, una prueba de espejo de un clic y una TV de juego 5×5 en vivo, todo en http://127.0.0.1:8800.

  • Estrategia real - un motor minimax Ángel-Diablo (juego de Markov de suma cero, alfa-beta) con el juego de bloqueo Conway (Policía = muros del Diablo §4.3) y aprendizaje de pesos RL de auto-juego, mucho más allá de la Q tabular base de la asignación. Ver docs/STRATEGY.md.

  • Descentralizado y resistente a trampas - sin árbitro; ambos lados calculan el hash del resultado (SHA-256) y cualquier desacuerdo puntúa 0/0. El texto entrante se trata como hostil: la inyección de prompts / coerción se filtra, se registra como evidencia y no puede cambiar el resultado (no existe acción de rendición).

  • Límite único de SDK + Guardián de API - toda la lógica detrás de CopThiefSDK; cada llamada externa (LLM, Gmail) pasa por un guardián con retro-presión FIFO y conmutación por error DeepSeek→Anthropic.

  • Puertas de calidad - pytest ≥ 85 % de cobertura, ruff sin violaciones, ≤ 150 líneas/archivo, solo uv.


Related MCP server: Police MCP Server

Inicio rápido

uv sync                                   # install (uv is the ONLY package manager)
cp .env-example .env                      # fill in real values (see "Secrets")
uv run ruff check .                       # zero-violation lint gate
uv run pytest                             # full suite (>=85% coverage gate)
uv run python -m cop_thief.app            # launch the control panel + servers + tunnels

Luego abre http://127.0.0.1:8800.


Jugar una partida (panel de control)

  1. uv run python -m cop_thief.app → se abre el panel; espera a que Servers ● y Tunnels ● estén en verde.

  2. La tarjeta de estado muestra tus dos URLs públicas …/mcp/ + tokens por rol (botones de copiar). Envía estos a tu oponente junto con docs/INTER_GROUP_TREATY_SPEC.md. Usa los formularios de relleno en match_setup/ (RULES.txt, OUR_DETAILS, OPPONENT_DETAILS).

  3. Pega las dos URLs …/mcp/ del oponente (y tokens, si los hay) en el formulario de desafío, luego INICIAR DESAFÍO - los 6 sub-juegos se juegan entre hosts en la TV y el informe se envía por correo.

  4. ¿Aún sin compañero? Haz clic en PRUEBA DE ESPEJO ⟳ - rellena tus propios endpoints localhost + tokens y juega contra ti mismo (ideal para probar estrategias).

Una partida = 6 sub-juegos (según §4.1): jugamos Policía en 3 (partido en casa) y Ladrón en 3 (partido fuera), primero el Ladrón, ≤ 25 movimientos cada uno. La puntuación es inmutable: captura del Policía → 20 / 5; el Ladrón sobrevive → 5 / 10.


Capturas de pantalla

El panel de control - estado del nodo (Servers/Tunnels/Game), nuestras URLs …/mcp/ compartibles + tokens con botones de copiar, el formulario de desafío del oponente y el juego en vivo. Aquí se está ejecutando una prueba de espejo; observa la línea [INTENT: BARRIER] (el Policía bloqueando una celda adyacente y quedándose quieto, §4.3), la barrera verde B y la captura !.

Panel de control

Tablero en vivo - la cuadrícula 5×5 con el Policía C (azul) y el Ladrón T (rojo), junto al feed de intercepción de comunicaciones que muestra las transmisiones en lenguaje natural [INTENT: MOVE] y los separadores HOME LEG / Sub-game.

Tablero en vivo

Transición de partido - el corredor cruzando a la AWAY LEG (jugamos como Ladrón) en el sub-juego 4/6, con una barrera B y una captura ! aún en el tablero.

Partido fuera y barrera

Al arrancar el nodo se imprimen los endpoints compartibles en la terminal (un proceso: servidores + túneles + panel):

Control panel  >  http://127.0.0.1:8800   (open in a browser)
╔══════════════════════════════════════════════════════════════════╗
║ LIVE PUBLIC MATRIX (Team Alpha)                                   ║
╠══════════════════════════════════════════════════════════════════╣
║ COP   (:8001)  https://acting-tomorrow-yard-raid.trycloudflare.com/mcp/   ║
║ THIEF (:8002)  https://dial-mean-courses-tramadol.trycloudflare.com/mcp/  ║
╚══════════════════════════════════════════════════════════════════╝
Tunnels live and written to config/setup.json. Share these /mcp/ URLs. Ctrl+C to stop.

Arquitectura

Capa

Módulo

Responsabilidad

Dominio

domain/

DecPomdpGameState inmutable, Grid, geometría, lenguaje de movimientos en NL ([INTENT: …]).

Estrategia

domain/strategy/

minimax (alfa-beta), evaluation/features (Ángel-Diablo), selfplay (RL), línea base Q-table.

SDK

sdk/

CopThiefSDK punto de entrada único; lógica de terminal/trampa-muerte de MatchCoordinator; pantalla de guerra/inyección.

Guardián

infra/gatekeeper/

Punto de estrangulamiento FIFO para todas las llamadas LLM/Gmail; conmutación por error DeepSeek→Anthropic; telemetría de tokens.

Servidores

servers/

Servidores FastMCP de Policía y Ladrón; autenticación por token; herramienta request_moveStrategyResolver.

Transporte

infra/network/

Host HTTP transmisible /mcp, RemoteMoveClient, conmutador Cloudflare.

Orquestación

orchestrator/

ChallengeRunner (entre hosts, por partido), reconcile (acuerdo mutuo / 0-0), serie.

UI

ui/

Backend del panel de control (server.py), NodeState, bus SSE de difusión, static/panel.html.

Informes

reporting/

Informador OAuth de Gmail (nombre del grupo en asunto + cuerpo), registro de auditoría de solo añadir, guardia de seguridad.

Puntos de entrada

Comando

Qué hace

python -m cop_thief.app

Panel de control: servidores + túneles + interfaz web (el principal).

python -m cop_thief.challenge

Desafío interactivo entre hosts desde terminal (solicita URLs del oponente).

python -m cop_thief.serve

Solo servidores + túneles (sin interfaz).

python -m cop_thief.infra.network.dual_mcp_host

Solo los dos servidores MCP (:8001/:8002 /mcp).

python -m cop_thief.diagnostic_runner

Sonda de persecución sin conexión y sin costo (LLM simulado).


Estrategia en un párrafo

Cada request_move se responde con minimax alfa-beta de profundidad limitada sobre el juego de Markov de suma cero (el Policía maximiza, el Ladrón minimiza, suposición de adversario óptimo). Las puntuaciones terminales con forma de progreso (±WIN ∓ turns) hacen que la política presione por la captura/supervivencia, por lo que los empates se evitan estructuralmente. El conjunto de acciones del Policía incluye bloquear una celda adyacente (movimiento "Diablo" de Conway, §4.3); la característica de contención de la evaluación es la región de escape del Ladrón rellenada por inundación, por lo que el planificador descubre líneas legales de acorralamiento hacia la trampa por sí mismo. Los pesos de evaluación lineales son ajustables mediante TD de auto-juego (selfplay.train_weights). Tres perfiles de variantes (agresivo / equilibrado / defensivo) cubren la lista de 3 agentes requerida. Diseño completo: docs/STRATEGY.md.


Modelo formal - Dec-POMDP

La persecución se modela como un Proceso de Decisión de Markov Descentralizado y Parcialmente Observable, la tupla ⟨ n, S, {Aᵢ}, P, R, {Ωᵢ}, O, γ ⟩ (ex06 §11):

Símbolo

Significado

En este proyecto

n

agentes

2 - Policía y Ladrón (independientes, sin memoria compartida).

S

espacio de estados

DecPomdpGameState: cop_pos, thief_pos ∈ cuadrícula 5×5, el conjunto de barriers ⊆ G (≤ 5), cop_barriers_left, turn_counter, turn_role. El espacio de posiciones conjuntas está acotado por (R·C)² = 625; con barreras el espacio alcanzable es mayor pero finito.

Aᵢ

acciones por agente

Policía: 8 movimientos de Rey (Chebyshev ≤ 1) ∪ colocar una barrera en una celda libre adyacente (quedarse quieto) ∪ HOLD. Ladrón: 8 movimientos de Rey ∪ HOLD. "Quedarse" es un movimiento degenerado.

P

transición

Determinista máquina de estados del tablero (apply_action): una mutación por turno; los movimientos ilegales (fuera del tablero / sobre una barrera / no-Rey) se rechazan; un turno de barrera bloquea la celda adyacente nombrada y el Policía se queda.

R

recompensa

Tabla 1 inmutable: captura → Policía +20 / Ladrón +5; evasión → Policía +5 / Ladrón +10. El planificador usa valores terminales con forma de progreso (±WIN ∓ turns) para que el juego sea estrictamente decisivo (sin empates).

Ωᵢ

espacio de observación

Una vista subjetiva por agente: coordenadas exactas del oponente si y solo si está dentro del radio de visión, de lo contrario un sector de oclusión cualitativo (p. ej. THIEF_IN_NORTHWEST_QUADRANT).

O

función de observación

get_subjective_observation(role, radius) - revela al oponente cuando la distancia de Manhattan ≤ vision.radius (por defecto 2), de lo contrario solo el cuadrante. Simétrica para ambos roles (niebla de guerra).

γ

descuento

rl.gamma = 0.9 para el TD de auto-juego / línea base Q; la capa minimax en cambio usa puntuaciones terminales con forma de progreso para presionar por captura/supervivencia.

La observabilidad parcial es real: cada agente decide desde su creencia del tablero (su última observación + prosa del oponente parseada), nunca desde la verdad global.

Desafíos de orquestación (la parte difícil)

Según ex06 §14, el valor de la asignación es la orquestación, no la victoria. Los problemas difíciles y cómo los resolvemos:

  • Lenguaje natural libre, sin protocolo predefinido. Los agentes conversan en prosa. Superponemos un contrato determinista fino - cada mensaje comienza con exactamente un indicador [INTENT: MOVE|BARRIER|HOLD] seguido de una palabra de brújula - de modo que el movimiento sea resoluble por máquina mientras el cuerpo permanece en NL libre. Esto mantiene dos motores construidos independientemente en sincronía sin un código base compartido.

  • Ambigüedad lingüística y entrada no fiable. La prosa entrante se analiza de forma determinista (coincidencia más larga de palabra de dirección, intent solo entre corchetes para que el texto decorativo no pueda suplantarlo) con un análisis LLM opcional para texto de oponente no estructurado; baja confianza → un respaldo exploratorio seguro (nunca se bloquea, nunca se forja una captura). Cada campo se trata como hostil - p. ej., un variant no numérico se coerciona, nunca se confía.

  • Garantizar el entendimiento mutuo. Ambos pares comparten un lenguaje de movimiento determinista (codificar/analizar sin necesidad de LLM), de modo que una partida es reproducible byte a byte. Al final, ambos lados calculan el hash de los sub_games canónicos (SHA-256, K3); cualquier discrepancia ⇒ 0/0 para ambos - el acuerdo se impone, no se asume.

  • Vivacidad sobre una red no fiable. Los movimientos entre hosts se reintentan con reconexión; una interrupción prolongada o un par congelado (tiempo de espera de 20 s por movimiento) pierde ese sub-juego, de modo que la serie siempre completa los 6 y el informe aún se envía por correo - un oponente muerto nunca puede detener la partida.

Visualización y pruebas concluyentes (§11)

  • GUI - las Capturas de pantalla anteriores muestran el tablero 5×5 en vivo, el feed de intercepción de comunicaciones [INTENT: …], las barreras (B), las capturas (!) y las transiciones de tramo.

  • Comunicación MCP en la nube - el bloque de arranque anterior imprime las URL públicas en vivo de Cloudflare /mcp/, y el feed de comunicaciones del panel transmite las transmisiones reales [INTENT:] intercambiadas con los servidores en la nube; cada turno también se añade a data/game_audit.jsonl y se sella en un archivo por partida a prueba de manipulaciones (data/archive/, hash SHA-256 del paquete en el informe).

  • Aprendizaje - los pesos de estrategia se ajustan mediante TD de auto-juego (selfplay.train_weights); se conserva una línea base de Q-learning tabular para comparación. Diseño + curvas: docs/STRATEGY.md.

Seguridad y juego limpio

  • Tokens - cada llamada a herramienta MCP requiere un token portador revocable por rol; intercambiado fuera de banda, rotable para revocar. Los servidores cierran en fallo.

  • Anti-inyección - las transmisiones entrantes no son fiables; la inyección/coerción/suplantación/ falsificación se detectan, se marcan hostile:true en data/game_audit.jsonl, se cuentan en el informe y no tienen efecto en el resultado determinado por el motor. Codificado para oponentes en el tratado (§F).

  • Acuerdo mutuo - ambos lados calculan el hash de los sub_games canónicos; cualquier discrepancia ⇒ 0/0 (both_lose).

  • Informes - API de Gmail sobre OAuth2 Desktop (ámbito gmail.modify, cero contraseñas); el nombre del grupo viaja tanto en la línea de asunto como en el cuerpo JSON; un guardián de seguridad usa por defecto la bandeja de entrada desechable.


Presupuesto de tokens y coste

Cada llamada externa se mide a través del API GatekeeperTokenTracker, que transmite el uso en vivo a data/token_usage.json (escritura atómica; excluido del hash de acuerdo K3 para que el coste nunca afecte al resultado). Todas las cifras están basadas en configuración (config/setup.json → token_budget / economics).

Proveedor (rol)

Entrada $/M

Salida $/M

DeepSeek deepseek-chat (principal)

0.15

0.60

Anthropic claude-3-5-sonnet (solo respaldo)

3.00

15.00

Partida presupuestaria

Valor

Gasto real hasta la fecha (todas las ejecuciones combinadas)

≈ $0.01

Presupuesto del ciclo de vida

200.000 entrada + 50.000 salida tokens

→ Coste proyectado del ciclo de vida (principal)

~$0.06

Estimación por turno (120 entrada / 40 salida)

~$0.00004

Tope duro (avisar al 80 %)

$0.50 (aviso $0.40)

Aplicación

el gatekeeper devuelve BudgetExceeded para llamadas LLM facturables en el tope - nunca se bloquea

En la práctica, todo el proyecto ha costado ≈ $0.01 en LLM. Los movimientos provienen del motor minimax local y el lenguaje de movimiento es determinista [INTENT: …] codificar/analizar - no se necesita LLM para jugar ni para enviar el informe (API de Gmail, no un LLM). El presupuesto mínimo + el respaldo DeepSeek-primero son salvaguardas para el análisis opcional de lenguaje natural asistido por LLM; el tope se fija en $0.50 con margen de sobra.

Secretos y configuración

  • Copie .env-example.env y complete: DEEPSEEK_API_KEY, ANTHROPIC_API_KEY, COP_MCP_TOKEN, THIEF_MCP_TOKEN, GMAIL_CREDENTIALS_PATH. Un autocargador sin dependencias inyecta .env al inicio - no se necesita export/source; las exportaciones de shell existentes siempre ganan.

  • Todos los parámetros ajustables viven en config/*.json versionado (sin codificación fija). Los secretos (.env, credentials.json, token.json) están ignorados por git y nunca entran en el control de fuentes.

Documentación

PRD · PLAN · TODO · STRATEGY · RULES_AND_AGREEMENTS · INTER_GROUP_TREATY_SPEC

Licencia

MIT.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/najikay/mcp-marl-pursuit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server