Skip to main content
Glama

MARL Cop & Thief — два ИИ-агента на базе MCP

Децентрализованная частично наблюдаемая игра в догонялки между двумя автономными ИИ-агентами — Cop (полицейским) и Thief (вором), — которые общаются на свободном естественном языке через MCP-серверы, выбирают ходы с помощью теоретико-игрового движка minimax + self-play RL, отображаются в реальном времени в веб-панели управления и отправляют по електронной почте согласованный обеими сторонами JSON-отчёт о матче через Gmail API.

Университет Хайфы · Orchestration of AI Agents (ex06) · д-р Йорам Сегал. Одна команда запускает всё; одна вкладка браузера проводит целый матч.


Ключевые особенности

  • Запуск одной командой — python -m cop_thief.app запускает оба MCP-сервера, публичные туннели Cloudflare и браузерную панель управления вместе (никаких осиротевших туннелей, никакой возни с портами).

  • Веб-панель управления — статус узла в реальном времени, публичные URL/токены с копированием, форма вызова оппонента, зеркальный самотест в один клик и живое табло игры 5×5 — всё по адресу http://127.0.0.1:8800.

  • Настоящая стратегия — движок minimax «Ангел–Дьявол» (антагонистическая марковская игра, альфа-бета) с блокирующей игрой Конвея (Cop = стены Дьявола, §4.3) и обучением весов через self-play RL, далеко за пределами базовой табличной Q из задания. См. docs/STRATEGY.md.

  • Децентрализованность и защита от читерства — нет судьи; обе стороны хэшируют результат (SHA-256), и любое расхождение даёт счёт 0/0. Входящий текст считается враждебным: prompt-инъекции и принуждение отсеиваются, фиксируются как улики и не могут изменить исход (действия «сдаться» не существует).

  • Единая граница SDK + API Gatekeeper — вся логика за CopThiefSDK; каждый внешний вызов (LLM, Gmail) проходит через шлюз с FIFO и обратным давлением, с фейловером DeepSeek→Anthropic.

  • Контроль качества — pytest ≥ 85 % покрытия, ruff без нарушений, ≤ 150 строк/файл, только uv.


Related MCP server: Police MCP Server

Быстрый старт

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

Затем откройте http://127.0.0.1:8800.


Проведение матча (панель управления)

  1. uv run python -m cop_thief.app → открывается панель; дождитесь зелёных индикаторов Серверы ● и Туннели ●.

  2. Карточка статуса показывает два ваших публичных URL …/mcp/ + токены по ролям (кнопки копирования). Отправьте их оппоненту вместе с docs/INTER_GROUP_TREATY_SPEC.md. Используйте формы для заполнения из match_setup/ (RULES.txt, OUR_DETAILS, OPPONENT_DETAILS).

  3. Вставьте два URL …/mcp/ оппонента (и токены, если есть) в форму вызова, затем нажмите START CHALLENGE — 6 под-игр разыгрываются между хостами на табло, а отчёт отправляется по электронной почте.

  4. Партнёра пока нет? Нажмите MIRROR SELF-TEST ⟳ — он подставляет ваши собственные localhost-эндпоинты + токены и играет вас против вас самих (идеально для тестирования стратегии).

Игра = 6 под-игр (по §4.1): мы играем Cop в 3 (домашняя нога) и Thief в 3 (гостевая нога), Thief ходит первым, ≤ 25 ходов в каждой. Система очков неизменна: поимка Cop → 20 / 5; выживание Thief → 5 / 10.


Скриншоты

Панель управления — статус узла (серверы/туннели/игра), наши доступные для передачи URL …/mcp/ + токены с кнопками копирования, форма вызова оппонента и живая игра. Здесь запущен зеркальный самотест; обратите внимание на строку [INTENT: BARRIER] (Cop ставит стену на соседней клетке и остаётся на месте, §4.3), зелёный барьер B и поимку !.

Control panel

Живая доска — сетка 5×5 с Cop C (синий) и Thief T (красный), рядом лента перехвата сообщений, показывающая передачи на естественном языке [INTENT: MOVE] и разделители HOME LEG / Sub-game.

Live board

Переход между ногами — бегун переходит в AWAY LEG (мы играем за Thief) на под-игре 4/6, при этом на доске всё ещё есть барьер B и поимка !.

Away leg and barrier

Запуск узла выводит доступные для передачи эндпоинты в терминал (один процесс: серверы + туннели + панель):

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.

Архитектура

Слой

Модуль

Обязанность

Домен

domain/

Иммутабельные DecPomdpGameState, Grid, геометрия, язык NL-ходов ([INTENT: …]).

Стратегия

domain/strategy/

minimax (альфа-бета), evaluation/features (Ангел–Дьявол), selfplay (RL), базовая Q-таблица.

SDK

sdk/

Единая точка входа CopThiefSDK; MatchCoordinator (терминальная логика, логика смерти в ловушке); экран защиты от warfare/инъекций.

Gatekeeper

infra/gatekeeper/

FIFO-шлюз для всех вызовов LLM/Gmail; фейловер DeepSeek→Anthropic; телеметрия токенов.

Серверы

servers/

FastMCP-серверы Cop и Thief; авторизация по токенам; инструмент request_move → StrategyResolver.

Транспорт

infra/network/

streamable-HTTP хост /mcp, RemoteMoveClient, коммутатор Cloudflare.

Оркестрация

orchestrator/

ChallengeRunner (кросс-хост, по ногам), reconcile (взаимное согласие / 0-0), серия.

UI

ui/

Бэкенд панели управления (server.py), NodeState, широковещательная SSE-шина, static/panel.html.

Отчётность

reporting/

Gmail OAuth-репортер (имя группы в теме и теле), append-only журнал аудита, предохранитель.

Точки входа

Команда

Что делает

python -m cop_thief.app

Панель управления: серверы + туннели + веб-UI (основная).

python -m cop_thief.challenge

Интерактивный терминальный вызов между хостами (запрашивает URL оппонента).

python -m cop_thief.serve

Только серверы + туннели (без UI).

python -m cop_thief.infra.network.dual_mcp_host

Только два MCP-сервера (:8001/:8002 /mcp).

python -m cop_thief.diagnostic_runner

Офлайн-зонд погони нулевой стоимости (мок-LLM).


Стратегия в одном абзаце

Каждый request_move обрабатывается ограниченным по глубине alpha-beta minimax над антагонистической марковской игрой (Cop максимизирует, Thief минимизирует, предположение об оптимальном противнике). Терминальные оценки, формируемые прогрессом (±WIN ∓ turns), заставляют политику стремиться к поимке/выживанию, поэтому ничьи структурно исключены. Множество действий Cop включает установку стены на соседней клетке (ход «Дьявола» Конвея, §4.3); фича containment в оценке — это залитая (flood-filled) область побега Thief, поэтому планировщик сам находит легальные линии загона в ловушку. Линейные веса оценки настраиваются через self-play TD (selfplay.train_weights). Три варианта профилей (агрессивный / сбалансированный / оборонительный) формируют требуемый состав из 3 агентов. Полное описание: docs/STRATEGY.md.


Формальная модель — Dec-POMDP

Погоня моделируется как децентрализованный частично наблюдаемый марковский процесс принятия решений — кортеж ⟨ n, S, {Aᵢ}, P, R, {Ωᵢ}, O, γ ⟩ (ex06 §11):

Символ

Значение

В этом проекте

n

агенты

2 — Cop и Thief (независимы, без общей памяти).

S

пространство состояний

DecPomdpGameState: cop_pos, thief_pos ∈ сетка 5×5, множество barriers ⊆ G (≤ 5), cop_barriers_left, turn_counter, turn_role. Совместное пространство позиций ограничено (R·C)² = 625; с барьерами достижимое пространство больше, но конечно.

Aᵢ

действия агента

Cop: 8 ходов короля (Чебышев ≤ 1) ∪ поставить барьер на соседнюю свободную клетку (остаться на месте) ∪ HOLD. Thief: 8 ходов короля ∪ HOLD. «Остаться» — вырожденный ход.

P

переход

Детерминированный конечный автомат доски (apply_action): одна мутация за ход; недопустимые ходы (за пределы доски / на барьер / не ход короля) отклоняются; ход с барьером огораживает указанную соседнюю клетку, а Cop остаётся на месте.

R

награда

Иммутабельная таблица 1: поимка → Cop +20 / Thief +5; уклонение → Cop +5 / Thief +10. Планировщик использует терминальные значения, формируемые прогрессом (±WIN ∓ turns), так что партия строго результативна (без ничьих).

Ωᵢ

пространство наблюдений

Субъективный обзор для каждого агента: точные координаты оппонента тогда и только тогда, когда он в радиусе обзора, иначе качественный сектор заслонения (например, THIEF_IN_NORTHWEST_QUADRANT).

O

функция наблюдения

get_subjective_observation(role, radius) — раскрывает оппонента, когда манхэттенское расстояние ≤ vision.radius (по умолчанию 2), иначе только квадрант. Симметрично для обеих ролей (туман войны).

γ

дисконт

rl.gamma = 0.9 для self-play TD / Q-базового уровня; слой minimax вместо этого использует терминальные оценки, формируемые прогрессом, чтобы добиваться поимки/выживания.

Частичная наблюдаемость реальна: каждый агент принимает решение на основе своего представления о доске (последнее наблюдение + разобранная проза оппонента), а не на основе глобальной истинной картины.

Проблемы оркестрации (трудная часть)

Согласно ex06 §14, ценность задания — в оркестрации, а не в победе. Трудные проблемы и способы их решения:

  • Свободный естественный язык, без предопределённого протокола. Агенты общаются прозой. Мы добавляем тонкий детерминированный контракт — каждое сообщение начинается ровно с одного маркера [INTENT: MOVE|BARRIER|HOLD], за которым следует слово-компас, — так что ход машинно разрешим, а тело остаётся свободным естественным языком. Это удерживает два независимо построенных движка в синхронизации без общей кодовой базы.

  • Языковая неоднозначность и недоверенный ввод. Входящая проза разбирается детерминированно (слово направления по самому длинному совпадению, намерение только в квадратных скобках, чтобы декоративный текст не мог его подделать) с опциональным LLM-разбором для неструктурированного текста противника; низкая уверенность → безопасный исследовательский запасной вариант (никогда не падает, никогда не подделывает взятие). Каждое поле рассматривается как враждебное — например, нечисловой variant приводится, а не принимается на веру.

  • Обеспечение взаимопонимания. Обе стороны используют общий детерминированный язык ходов (кодирование/разбор без необходимости в LLM), поэтому партия воспроизводима побайтово. В конце обе стороны хешируют каноническое значение sub_games (SHA-256, K3); любое несовпадение ⇒ 0/0 для обеих — согласованность обеспечивается принудительно, а не предполагается.

  • Живучесть в ненадёжной сети. Межхостовые ходы повторяются с переподключением; длительный сбой или зависший участник (таймаут 20 с на ход) означает поражение в этой под-игре, так что серия всегда завершает все 6 партий, а отчёт всё равно отправляется по почте — недоступный противник никогда не сможет остановить матч.

Визуализация и неопровержимые доказательства (§11)

  • GUI — Скриншоты выше показывают живую доску 5×5, ленту перехвата коммуникаций [INTENT: …], барьеры (B), взятия (!) и переходы между этапами.

  • Облачная MCP-коммуникация — загрузочный блок выше выводит актуальные публичные Cloudflare-URL /mcp/, а лента коммуникаций панели транслирует реальные передачи [INTENT:], которыми обмениваются с облачными серверами; каждый ход также добавляется в data/game_audit.jsonl и запечатывается в архив каждой партии с защитой от подделки (data/archive/, SHA-256 набора в отчёте).

  • Обучение — веса стратегии настраиваются методом самоигры TD (selfplay.train_weights); для сравнения сохранена базовая табличная модель Q-learning. Дизайн и кривые: docs/STRATEGY.md.

Безопасность и честная игра

  • Токены — каждый вызов инструмента MCP требует отдельного отзываемого bearer-токена для каждой роли; токены передаются внеполосно и могут ротироваться для отзыва доступа. Серверы закрыты по умолчанию (fail-closed).

  • Защита от инъекций — входящие передачи считаются недоверенными; инъекции/принуждение/имитация/подделка проверяются, помечаются hostile:true в data/game_audit.jsonl, учитываются в отчёте и не влияют на результат, определяемый движком. Для противников это зафиксировано в договоре (§F).

  • Взаимное согласие — обе стороны хешируют каноническое значение sub_games; любое несовпадение ⇒ 0/0 (both_lose).

  • Отчётность — Gmail API через OAuth2 Desktop (scope gmail.modify, без паролей); название группы указывается и в теме письма, и в JSON-теле; предохранитель по умолчанию использует одноразовый почтовый ящик.


Бюджет токенов и стоимость

Каждый внешний вызов учитывается через API Gatekeeper → TokenTracker, который в реальном времени передаёт данные об использовании в data/token_usage.json (атомарная запись; эти данные исключены из хеша соглашения K3, поэтому стоимость никогда не влияет на результат). Все цифры задаются конфигурацией (config/setup.json → token_budget / economics).

Провайдер (роль)

Вход, $/M

Выход, $/M

DeepSeek deepseek-chat (основной)

0.15

0.60

Anthropic claude-3-5-sonnet (только фолбэк)

3.00

15.00

Статья бюджета

Значение

Фактические расходы на сегодня (суммарно по всем запускам)

≈ $0.01

Бюджет жизненного цикла

200 000 входных + 50 000 выходных токенов

→ прогнозируемая стоимость жизненного цикла (основной)

~$0.06

Оценка на один ход (120 входных / 40 выходных)

~$0.00004

Жёсткий предел (предупреждение при 80 %)

$0.50 (предупреждение $0.40)

Контроль соблюдения

gatekeeper возвращает BudgetExceeded для платных LLM-вызовов при достижении предела — никогда не падает

На практике весь проект обошёлся примерно в $0.01 на LLM. Ходы генерирует локальный движок минимакса, а язык ходов — детерминированный [INTENT: …] encode/parse: для игры и отправки отчёта LLM не нужен (Gmail API, а не LLM). Крошечный бюджет + фолбэк с приоритетом DeepSeek — это предохранители для опционального LLM-ассистированного разбора естественного языка; предел установлен на $0.50 с большим запасом.

Секреты и конфигурация

  • Скопируйте .env-example → .env и заполните: DEEPSEEK_API_KEY, ANTHROPIC_API_KEY, COP_MCP_TOKEN, THIEF_MCP_TOKEN, GMAIL_CREDENTIALS_PATH. Автозагрузчик без зависимостей внедряет .env при запуске — export/source не нужны; уже существующие экспортированные переменные окружения всегда имеют приоритет.

  • Все настраиваемые параметры находятся в версионируемых config/*.json (без хардкода). Секреты (.env, credentials.json, token.json) игнорируются git и никогда не попадают в систему контроля версий.

Документация

PRD · PLAN · TODO · STRATEGY · RULES_AND_AGREEMENTS · INTER_GROUP_TREATY_SPEC

Лицензия

MIT.

Related MCP Connectors

Related MCP Servers