Skip to main content
Glama
najamjad-cop

najamjad-cop

by najikay

Pursuit League — агент-полицейский 👮

Сторона полицейского в финальном проекте на двоих по курсу Orchestration of AI Agents: распределённая игра в «кошки-мышки» между двумя агентами, играемая peer-to-peer через MCP (FastMCP/HTTP) против агентов других команд, с целостностью commit-reveal на SHA-256 и отправкой результатов через Gmail API.

Репозиторий-компаньон (агент-вор): https://github.com/najikay/pursuit-thief-agent Общий базовый пакет байт-в-байт идентичен в обоих репозиториях, что обеспечивается scripts/sync_core.py в CI (см. docs/PLAN.md, ADR-002).

ci

Команда: Naji Kayal · Amjad Abed Статус: M6 — сыграно и подано шесть засчитанных серий против шести разных соперников, порог прохождения правила 31 превышен трижды. Играет полную проверяемую серию, взаимодействует с эталонным симулятором курса и с шестью независимыми реализациями команд, а после матча проверяет игру соперника. Оставшаяся работа отслеживается в docs/TODO.md.

Турнирная таблица

#

Дата

Соперник

Результат

Мы

Они

1

2026-08-08

uoh-ay26

победа 6–0

90

30

2

2026-08-13

imreeyal

победа 6–0

90

30

3

2026-08-14

vibecode

поражение 0–6

30

90

4

2026-08-17

MOAAMOHA

победа 4–2

60

40

5

2026-08-18

nis-yar1

поражение 0–6

30

90

6

2026-08-21

ahk-yosi

ничья 3–3

75

75

Тридцать шесть из тридцати шести мини-игр получили Verified OK при проверке, в шести засчитанных сериях, с нулём технических поражений по нашей вине. Именно на это число мы бы указали в первую очередь: каждая сыгранная нами игра была такой, что обе стороны могли пересчитать хэш и согласиться с результатом, включая две, которые мы крупно проиграли.

Две серии сверены поле-в-поле с собственным поданным отчётом соперника — у vibecode по 66 полям с нулём расхождений, а позже товарищеская игра против anrbj666 совпала по всем шести под-играм, по обоим значениям mutual_agreement.sha256 и по обеим парам github_commit для каждого окна. Отчёт, совпадающий с отчётом соперника, — единственный, который нельзя аннулировать по правилам 33–35, и для нас он ценнее, чем счёт на табло.


Содержание


Related MCP server: Police MCP Server

Установка

Требования

Python

3.12+ (управляется через uv — предустанавливать не нужно)

uv

единственный используемый здесь менеджер пакетов (правила §8.4)

ОС

Linux, macOS или Windows через WSL2 — разрабатывалось на WSL2

Необязательно

cloudflared для публичного туннеля; проект Google Cloud для email-отчётов

git clone https://github.com/najikay/pursuit-cop-agent.git
cd pursuit-cop-agent
uv sync                       # installs the locked dependency set
uv run python scripts/check_all.py   # every CI gate, one PASS/FAIL verdict

Прохождение check_all.py означает, что установка корректна: линт, ограничения размера файлов, правила репозитория, проверка типов и полный набор тестов.

Секреты — скопируйте .env-example в .env и заполните реальными значениями. Никакие секреты никогда не коммитятся; .gitignore покрывает .env, secrets/, token.json и credentials.json, а CI-гейт ломает сборку, если такой файл когда-либо попадает под отслеживание (правила книги 39–40).

cp .env-example .env          # then edit: ANTHROPIC_API_KEY, DEEPSEEK_API_KEY, …

Устранение неполадок

Симптом

Причина и решение

port 8802 … already in use

Запущен другой агент. Остановите его или измените network.my_port в config/police/game.toml.

peer отвечает ~15 с

Это нормально: импорт MCP-стека. Когда агент действительно готов, он печатает Uvicorn running — запускайте его задолго до матча.

Соперник сообщает, что мы недоступны

Проверьте туннель: uv run najamjad-cop preflight. 502 означает, что агент не запущен; ошибка JSON-RPC вида "Client must accept text/event-stream" означает, что агент здоров — это браузер обращается к MCP-эндпоинту.

Failed to spawn: najamjad-cop

Запускайте из корня репозитория; консольный скрипт находится в .venv этого репозитория.

Браузер OAuth не открывается (WSL)

Это ожидаемо — в WSL нет браузера по умолчанию. Используйте uv run python scripts/authorise_gmail.py --manual и вставьте URL самостоятельно.

Всё медленно на /mnt/c

Файловые системы Windows медленные в WSL. Журнал событий держит свой дескриптор открытым именно по этой причине; если возможно, держите рабочее пространство на файловой системе Linux.


Командная строка

Один консольный скрипт на репозиторий (najamjad-cop здесь, najamjad-thief в репозитории-компаньоне). Каждый глагол — это разбор аргументов плюс один вызов SDK — в CLI нет игровой логики, и мета-тест следит за тем, чтобы так и оставалось.

uv run najamjad-cop --help                     # every verb
uv run najamjad-cop version                    # code version (book rule 53)

uv run najamjad-cop preflight                  # match-day checklist
uv run najamjad-cop peer                       # serve: MCP server + tunnel + dashboard
uv run najamjad-cop match                      # serve, then play the agreed series
uv run najamjad-cop peer --no-tunnel --no-dashboard   # local play, nothing exposed

# Re-hash every step of a log and print the verdict. Paths are literal -
# `<log>` would be read by the shell as a redirect, so use a real one:
uv run najamjad-cop replay tests/goldens/artifacts/log_segal-police-team-vs-segal-thief-team_g01.json
uv run najamjad-cop replay path/to/log.json --serve   # open the viewer instead
uv run najamjad-cop archive match.zip                 # bundle evidence (secrets excluded)

Коды выхода, потому что они используются в скриптах:

Код

Значение

Пример

0

сработало

preflight готов, журнал проверен

1

выполнилось, ответ плохой

не готов к матчу, журнал ИЗМЕНЁН

2

не смогло выполниться

файл журнала отсутствует или нечитаем

Изменённый журнал и отсутствующий файл — намеренно разные коды: результат проверки нельзя спутать с опечаткой.

Команды для разработки

uv run python scripts/check_all.py                  # all CI gates, one verdict
uv run python scripts/self_play.py --games 100      # measure our brains vs baselines
uv run python scripts/demo_dashboard.py             # dashboard over a played game
uv run python scripts/two_process_match.py          # both repos as real processes
uv run python scripts/sync_core.py ../pursuit-thief-agent   # verify the mirrored core

Запуск

Всё ниже работает из свежего клона без соперника и без ключей API. Агент играет полную проверяемую серию на одних шаблонах (книга, стр. 67) — LLM является улучшением, а не зависимостью.

1. Установите и подтвердите установку

git clone https://github.com/najikay/pursuit-cop-agent.git
cd pursuit-cop-agent
uv sync                                     # locked dependency set
uv run python scripts/check_all.py          # every CI gate, one verdict

ALL GATES PASSED означает, что линт, размеры файлов, правила репозитория, типы, набор стратегий и более 1 900 тестов зелёные.

2. Запустите peer-сервер с панелью управления

uv run najamjad-cop peer --dashboard --no-tunnel

Дождитесь Uvicorn running — именно это, а не первая строка журнала, означает готовность. Холодный старт занимает ~15 с (импорт MCP-стека), так что в день матча запускайтесь заранее.

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

Порт и хост берутся из config/setup.json (ui.port, ui.host). Панель привязана к loopback намеренно: она показывает наши представления и наши запечатанные состояния, поэтому её публикация отдала бы сопернику всё то, что commit-reveal существует, чтобы скрывать (правила 8–9).

Что вы получаете:

Панель

Показывает

Board

Тепловая карта представлений в логарифмическом масштабе — линейная сжала 47 из 48 ячеек в одну полосу

Turn banner

Чей ход, какой шаг, какая фаза

Dialogue

Все подсказки входящие и исходящие, с моделью, которая их написала

Negotiation

Предложение → контрпредложение → фиксация, и условия, ожидающие человека

Budget

Токены относительно согласованного лимита в 200k

Match day

Готовность — те же проверки, что выполняет preflight

Testing

Режим практики и отвечает ли каждый эндпоинт

Matches

Каждый поданный нами матч: счёт, вердикты проверки по играм, артефакты

Events

Сырой поток событий

Обновления приходят через WebSocket; клиент никогда не опрашивает. Сбой панели не может повлиять на игру — она лишь подписчик и ничего больше (ADR-005).

Дополнительные элементы управления. Установите features.controls в true в config/setup.json, чтобы включить запуск/остановку, одобрение переговоров и переключатель режима практики со страницы. По умолчанию выключено, и намеренно нет кнопки, которая играет засчитанный матч — он оценивается и необратим, и docs/RUNBOOK.md является интерфейсом для него.

2b. Тестирование без обращения к преподавателю

Панель Testing отвечает на два вопроса, которые иначе означают копание в журналах.

В каком я режиме? Режим практики перенаправляет каждый отчёт в ваш собственный почтовый ящик вместо ящика преподавателя и добавляет префикс [PRACTICE] к теме. Он не пропускает отправку — отправка была тем шагом, на котором проигрывались матчи в задании 6, поэтому практический прогон выполняет её по-настоящему, и вы читаете реальное письмо. Перенаправление обеспечивается дважды: адрес переписывается, а затем проверяется в точке невозврата, так что молчаливо неудачная перезапись вызывает исключение вместо доставки. См. docs/CONFIG.md §3b.

Проще всего флаг — он включает практику для одного процесса и ничего не трогает:

uv run najamjad-cop match --opponent amjad --practice --dashboard --tunnel

Или переключите его с панели (при включённом features.controls), или установите practice.enabled в config/setup.json, чтобы сделать его постоянным. Он читается заново при каждой сборке отчёта, так что переключатель действует без перезапуска, и он переопределяет email.mode на send — практический прогон, который тихо создал черновик, выглядел бы точно так же, как успешная отправка.

Кто-нибудь вообще доступен? Probe endpoints обращается к нашему MCP-URL и URL соперника и сообщает о трёх состояниях, а не двух:

Состояние

Значение

зелёный

что-то приняло TCP-соединение там

красный

настроено, но никто не слушает — это блокирует матч

серый

ещё не настроено — матч не запланирован, это не ошибка

Зелёный свет означает, что порт ответил. Это не означает, что протокол работает или что соперник согласится на наши условия — для этого существует рукопожатие, и панель намеренно не заявляет больше того, что может проверить.

3. Проверьте, что вы готовы играть

uv run najamjad-cop preflight        # exit 0 or do not play
uv run python scripts/pre_match_smoke.py     # MATCH READY in ~50 s

4. Играйте

Опишите карточку соперника один раз, затем называйте её по имени. opponents/<name>.toml несёт два факта, которые приходят от них — их MCP-эндпоинт и group_id, объявляемый их рукопожатием:

url      = "https://their-agent.example.com/mcp"
group_id = "their-group"
name     = "Their Team"
notes    = "quick tunnel - URL changes if cloudflared restarts"

Скопируйте opponents/_template.toml для начала. Затем каждый глагол принимает --opponent:

uv run najamjad-cop preflight --opponent amjad
uv run najamjad-cop match --opponent amjad --dashboard --no-tunnel

Ничего отслеживаемого редактировать не нужно, настройки последнего соперника не перезаписываются следующим, а opponent_group_id — который должен совпадать с тем, что объявляет их рукопожатие, иначе отчёт привязывается к заглушке вместо их имени — можно проверить за день до матча, а не обнаружить в момент рукопожатия.

Карточка может задавать только network.opponent_*. Игровые термины согласованы в подписанном config/game.json, и переопределение одного из них для конкретного противника — это именно то, что никогда не должно быть простым.

Или задайте network.opponent_url в config/police/game.toml вручную, затем:

uv run najamjad-cop match --dashboard --no-tunnel

Завершённый матч записывает четыре артефакта согласно Приложению F в workspace/artifacts/ — декларацию, конфиг, журнал и результат — и отправляет результат по почте. Проверьте их с помощью:

uv run python scripts/post_match.py --opponent <name>

5. Попробуйте без противника

Два способа, оба настоящие:

# our cop against our thief, two OS processes over real MCP/HTTP
uv run python scripts/two_process_match.py

# against the course reference simulator (expects ../reference-sim)
uv run python scripts/rehearsal.py --games 6

Второй — тот, что имеет значение: это единственная конфигурация, которая когда-либо выявляла наши дефекты интероперабельности, потому что это единственный противник, которого мы не писали.

6. Проверка журнала

uv run najamjad-cop replay workspace/artifacts/log_<game_id>_g01.json

Код выхода 0 — Verified OK; код 1 — TAMPERED и указывает на сбойный шаг; код 2 означает, что файл не удалось прочитать. Подделанный журнал и опечатка — намеренно разные коды: вердикт аудита никогда не должен быть спутан с ошибочно набранным путём.

Добавьте --serve, чтобы открыть просмотрщик вместо вывода вердикта.

7. Измерение

uv run python scripts/strategy_smoke.py --games 50   # win rates with intervals
uv run python scripts/sweep.py --games 24            # parameter sensitivity
uv run python scripts/measure_tokens.py              # token census

Результаты попадают в results/, и именно их отображает notebooks/analysis.ipynb.

Рабочий процесс дня матча

Полная процедура с точными командами — в docs/RUNBOOK.md. Вкратце:

  1. Разогрев — запустите агента заранее; холодный старт — ~15 с.

  2. Предварительная проверка — uv run najamjad-cop preflight; код выхода 0, иначе не играть.

  3. Обмен URL — задайте network.opponent_url в config/police/game.toml.

  4. Игра — uv run najamjad-cop match, панель на http://127.0.0.1:8000/.

  5. Аудит — автоматически для каждой мини-игры; каждая игра должна показать Verified OK.

  6. Отчёт — сверьтесь с противником, затем отправьте (правило 30: только gmail.send).

  7. Архив — uv run najamjad-cop archive match.zip (секреты исключены).


Конфигурация

Файл

Роль

config/game.json

Общие подписанные условия. Оба участника должны иметь побайтово идентичную копию; рукопожатие отказывается играть при любом расхождении. Наш — это стартовое предложение: каждое значение на уровне или выше минимума Приложения F (правило 12: повышать, никогда не понижать).

config/police/game.toml

Приватный, локальный. Наш порт, URL противника, имя туннельного хоста, выбор LLM, настройка убеждений. Никогда не покидает сеть.

config/rate_limits.json

Настройки ограничителя для каждого сервиса, проверяемые на соответствие потолкам Приложения F при загрузке.

.env

Только секреты. Никогда не коммитится.

Параметры, о которых стоит знать:

Ключ

Эффект

network.my_port

Наш порт MCP (8802 коп / 8801 вор, чтобы оба работали локально).

network.opponent_url

Единственное, что мы знаем о противнике. Предварительная проверка завершается ошибкой, пока пусто.

tunnel.hostname

Постоянное публичное имя. Именованный туннель сохраняет свой URL между перезапусками — дефект, стоивший Заданию 6 больше всего времени (ADR-004).

llm.every_n_steps

Частота подсказок. Ручка качества, а не экономии — см. docs/TOKEN_BUDGET.md.

belief.smell_trust_weight

Насколько мы доверяем запаху в сравнении с возможно лживой подсказкой.

movement_and_barriers.*

Согласованные правила. Их одностороннее изменение ломает подпись.


Панель управления

Запустите её с --dashboard и откройте http://127.0.0.1:8000/ — см. Запуск §2 о панелях и дополнительных элементах управления.

Тепловая карта убеждений, баннер хода, диалог с указанием модели для каждого сообщения, шкала времени переговоров, бюджет токенов и статус доставки отчёта — всё передаётся через WebSocket, никогда не опрашивается. Она показывает только локальную истину (правила книги 8–9): позиция противника не имеет поля в модели чтения, и мета-тест гарантирует, что UI может обращаться к агенту только через SDK.

Живая панель

Просмотрщик повторов

Каждый шаг заново хэшируется из раскрытых (payload, nonce) и сравнивается с сохранённым обязательством (правило книги 20). Ниже — собственный образец журнала лектора, воспроизведённый чисто:

Verified OK

И тот же журнал с одной записью, отредактированной задним числом, — подделка локализуется ровно на том шаге, куда она была внедрена, и правило 19 аннулирует игру:

Tampered

См. assets/README.md о том, как воспроизводится каждое изображение.


Академический отчёт

Модель: Dec-POMDP, который не видит ни одна сторона

Игра — это децентрализованный частично наблюдаемый марковский процесс принятия решений. Ни один агент не наблюдает истинное состояние: позиции запечатаны в обязательствах до финального аудита, поэтому каждый участник держит убеждение о том, где может быть другой, и действует на его основе.

Два канала наблюдения с противоположными свойствами доверия:

  • Запах — затухающий феромонный след, который противник испускает непроизвольно и не может подделать (книга, СТР. 22). Неподделываемый, но размытый.

    Поставляются две модели, и на каждый матч можно выбрать любую. Книжная (СТР. 43–44) — радиальная — 0.90 / 0.62 / 0.42 / 0.20 / 0.14 / 0.04 — с относительным затуханием τ ← (1-ρ)·τ; эталонный симулятор — линейный по расстоянию Чебышёва — кольца 0.90 / 0.60 / 0.30 — с абсолютным затуханием τ ← τ - ρ. Мы реализуем обе, и обе зарегистрированы в интероп-наборе: ScentModel.BOOK — это multiplicative_book_v1 (934c220d…), а ScentModel.REFERENCE — это subtractive_chebyshev_v1 (81ebee59…). Каждая воспроизводит собственные опубликованные векторы набора — включая порядок отправки по каналу, который field_walk набора фиксирует как состарить предыдущий след, слить свежее отложение без затухания, передать это: наш вор отправлял след на один шаг затухания слишком свежим до 2026-08-22, покадровый шлюз противника это измерил, и кадры теперь закреплены по ячейкам против обхода (test_wire_scent_serve_order.py). Так что подбор под противника — это один ключ — pheromones.pheromone_model в config/game.json — и не изменение четырнадцати подписанных условий, поэтому дайджест контракта a284082d… переживает переключение, и никому не нужно переподписывать. Дайджест, который мы объявляем при переговорах, берётся из сконфигурированной модели, так что не существует состояния, в котором мы передаём одну физику, а заявляем другую. Эмиссия отдельно регулируется от подсказок — --scent full|window|none и --hints/--no-hints — так что полностью тихая серия — это один флаг; в режиме тишины мы не объявляем никакой модели вообще, потому что заявление о поле, которое никто не отправляет, — это не заявление, которое стоит делать.

  • Подсказки — свободный естественный язык, которому правила явно разрешают быть ложью (правила 26–27). Точные, но ненадёжные.

Наш механизм убеждений объединяет их: диффузия для движения, обновление правдоподобия запаха и вес достоверности для каждого противника, который растёт и падает по мере того, как их подсказки согласуются или расходятся со следом. Полный вывод — в docs/PRD_belief_engine.md.

Три вывода из его построения, каждый из которых изменил дизайн:

  • Затухание запаха имело неподвижную точку. Относительное затухание, округлённое до трёх знаков, никогда не достигало нуля, поэтому мёртвые следы вечно загрязняли убеждение. Исправлено явным эпсилоном.

  • Мультипликативное слияние дважды учитывало кумулятивное поле запаха и оставляло убеждение отстающим от движущегося противника на ~5 ячеек. Заменено на устойчивое обновление смеси.

  • Плоское правдоподобие парковало убеждение в середине следа. Заострение плюс аддитивный минимум исправили это; ограничение делало слабый запах неотличимым от его отсутствия.

Дилеммы оркестрации

Один шлюз или много вызывающих? Правило книги 3 предписывает оркестратор, и мы поняли его буквально: периферийные модули никогда не вызывают друг друга. Механизм убеждений ничего не знает о транспорте, стратегия ничего не знает о криптографии, транспорт ничего не знает о правилах. Именно это делает весь цикл хода тестируемым против заглушек — шов, которого у Задания 6 никогда не было.

Насколько доверять заглушке. Наш самый острый урок. Три отдельных дефекта пережили 1500 прошедших тестов, потому что заглушки были добрее, чем канал: фиктивный транспорт, оборачивавший аудиторскую полезную нагрузку так, как настоящий не оборачивал, внутрипроцессная ссылка, которая никогда не блокировалась, и участники, которые играли только сами с собой. Интероперабельность можно проверить только против того, что вы не писали.

Куда поместить дедлайн. Участник, отвечающий вечно, не должен удерживать нас в решённой игре, а участник, замолчавший, не должен стать нашей технической потерей. Каждое ожидание ограничено, и каждое завершение объявляется, а не предполагается — см. ниже.

Ограничение скорости нашего собственного протокола. Привратник существует, чтобы быть хорошим гражданином по отношению к Anthropic и Gmail. Применение его к противнику едва не стоило нам игр: наш собственный ограничитель мог задержать ответ за их 30-секундный дедлайн.

Стратегии и почему не RL

Коп — скриптованная печать-половинка (strategy/seal_cop.py), а не покадровая оценка: запечатать среднюю колонку, идя по соседней полосе, взять ворота, отрезать половину, где находится вор, и вор оказывается в кармане 3×3 с нами. Оттуда domain/endgame.py решает карман точно — размещение барьера — это ход в его дереве игры — и оценивает каждую победу в совместное расположение, потому что заявление о наступлении — это единственный захват, который чтит каждая реализация; вор, запечатанный там, куда мы никогда не можем дойти, оценивается как remote seal нашим собственным стендом и отклоняется как план. Блокировка одним барьером (strategy/lock.py: наше тело плюс одна стена, требуется смежность) завершает прижатого вора рано. Стенд (tests/regression/) гоняет её против реагирующих противников — включая вора, который сидит на собственной линии стены скрипта, — и она превращает всех них в пределах 35 ходов, обычным заявляемым захватом (test_seal_converts_every_reacting_thief.py). Преследование, управляемое убеждением (cop_brain.py), остаётся запасным вариантом для доски без локализации.

Вор — уклонение с горизонтом выживания как стопка жёстких минимумов, а не жадная максимизация дистанции: никогда не заканчивать ход в пределах одного шага от копа, отказываться от ячеек, которые полупостроенный забор уже сделал дешёвыми для запечатывания, держать достижимый квадрат 4×4, оставаться на стороне копа от формирующегося разреза (запечатывающий не может завершить стену, отделяющую его от нас), и стоять в зазорах строящегося забора — предпочтение, которое выполняется после минимумов безопасности, потому что до них оно однажды завело нас в угол, который противник затем запечатал (test_ahk_yosi_corner_hunt.py воспроизводит эту линию убийства ход за ходом). Оно переживает каждую архивную линию противника в matches/ и все 24 варианта тай-брейка реагирующего копа-охотника за углами.

Политика подсказок — блеф — это стратегический ресурс с ценой: заявление раскрывает ячейку заявляющего, поэтому ложное заявление о захвате отдаёт вору нашу точную позицию задаром.

Никакого обучения с подкреплением, намеренно:

  • По всей лиге доступно всего ~60 реальных игр — этого далеко не достаточно, чтобы обучить политику на пространстве состояний такого размера.

  • Поведение противников нестационарно; каждая команда использует что-то своё.

  • Недетерминизм сломал бы воспроизводимость, а воспроизводимость — оцениваемый результат.

  • Эвристики уже решительно превосходят базовый уровень (ниже), так что RL — это риск без измеренной выгоды.

Вместо этого мы занимаемся онлайн-моделированием противника, рассчитанным на ~210 наблюдений, которые реально даёт серия, — достоверность подсказок, тенденции ходов, реакция на барьеры.

Измерено с помощью реального механизма матчей на отложенных играх — сид 11, никогда не использовавшийся при настройке, 60 игр на каждое противостояние:

противостояние

захваты

доля

95 % ДИ Уилсона

наш коп против жадного вора

60/60

100 %

94–100 %

жадный коп против нашего вора

0/60

0 % (100 % выживание)

0–6 %

жадный против жадного (эталон)

4/60

6.7 %

2.6–16 %

Ноль разногласий между пирами и ноль сбоев аудита во всех 180 играх.

наша стратегия против жадного базового уровня

Один настраиваемый параметр решает исход матча, и это не тот, который мы ожидали:

какой параметр решает игру

barrier_threshold меняет долю захватов с 4 % до 100 % во всём диапазоне. Барьер непроходим для обеих сторон, поэтому коп, который ставит стену на слабых основаниях, отгораживается от вора, которого преследует, — мы две недели поставляли 0.15, что стоило нам примерно трети игр. lookahead — настоящий нулевой результат: глубины 1–4 дают побайтово идентичные игры, потому что изотропное диффузионное ядро сохраняет ранжирование кандидатных ходов, которое оно должно разделять.

Оговорка, которую мы обязаны сделать. Наш вор выживает против каждого копа, с которым мы когда-либо сталкивались или которого архивировали, — и всё же проигрывает, примерно на шаге 30, нашему собственному запечатывающему копу, чей план половинного деления не показал ни один противник. Оба направления зафиксированы, а не сглажены (test_thief_beats_sealing_cops.py фиксирует поражение и его цену; наборы corner-hunt и archive фиксируют выживания), потому что вор, оценённый только против копов, которых он побеждает, оценён против самого себя.

Полные выводы, доверительные интервалы, таблица стоимости токенов и ссылки находятся в notebooks/analysis.ipynb. Воспроизвести с помощью:

uv run python scripts/baselines.py --games 60 --seed 11   # held-out comparison
uv run python scripts/sweep.py --games 24 --seed 7        # sensitivity sweeps
uv run python scripts/measure_tokens.py                   # token census

Что нам дало тестирование против незнакомца

Мы клонировали эталонный симулятор курса и направили его на себя. Ничего не работало ни в одну, ни в другую сторону. Эталон называет аргумент инструмента MCP message для трёх инструментов и payload для одного; мы отправляли payload во все четыре и принимали только payload. Каждый ход и каждое предложение отклонялись при связывании аргументов до того, как выполнялся хотя бы один байт игровой логики — против любого агента, построенного на эталоне, а таких большинство в классе.

За этим последовали ещё четыре несовместимости: обязательный timestamp, который мы никогда не отправляли, поле claimed_cell, которое их парсер отвергает наотрез, и три поля claim, типы которых различались. Затем выяснилось, что аудит-раскрытие отправляется как простой список, тогда как схема объявляет конверт, поэтому оба пира записали TAMPERED для игр, в которых никто не жульничал.

Каждая из этих проблем проходила наши собственные тесты. Ядро commit-reveal, напротив, пережило контакт без изменений: наш commit_of воспроизводит сигнатуру эталона байт в байт.

Урок, который мы вынесли для любого распределённого проекта: зелёный набор тестов доказывает, что ваш код согласуется с вашими предположениями, а не то, что ваши предположения верны.


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

Документ

Назначение

docs/PRD.md

Требования к продукту (идентификаторы FR-*, KPI, вехи)

docs/PLAN.md

Архитектура: диаграммы C4 + FSM, ADR-001..021, карта модулей

docs/TODO.md

План сборки из 688 задач с трассируемостью и прогрессом

docs/HANDOFF-2026-08-14.md

Текущее состояние, открытые вопросы и каждое измеренное исправление

docs/PROTOCOL.md

Что пересекает провод, а что никогда

docs/SECURITY.md

Модель угроз, защита от prompt-инъекций, обработка секретов

docs/UX.md

Эвристики Нильсена, сопоставленные с решениями дашборда; доступность

docs/EXTENDING.md

Четыре точки расширения с рабочим плагином

docs/CONFIG.md

Каждый ключ конфигурации, его файл и его согласуемость по Приложению F

docs/CI.md

Что проверяет каждый шлюз и как воспроизвести сбой

CONTRIBUTING.md

Соглашения: синхронизация ядра, коммиты, тесты, заморозка в день матча

docs/ISO25010.md

Характеристики качества ISO/IEC 25010, сопоставленные с доказательствами

docs/edge-cases.md

Каждое обработанное граничное условие, каждое со ссылкой на свой тест

docs/TOKEN_BUDGET.md

Измеренное потребление токенов и модель затрат

docs/OPEN_ITEMS.md

Что известно как неполное, с доказательствами

notebooks/analysis.ipynb

Исследования чувствительности, базовые уровни, таблица затрат, ссылки

docs/PRD_belief_engine.md · PRD_commit_reveal.md · PRD_llm_router.md

Проектирование механизмов

docs/runbook-network.md

Процедуры туннеля и подключения

docs/research/

Дайджесты источников (книга, руководства, эталонный симулятор, ретроспектива A6)


Аудит противника

Commit-reveal доказывает, что пир не переписывал историю. Он ничего не доказывает о том, играли ли они по правилам, и это разные гарантии — мы смешивали их на протяжении всего этапа лиги и не могли сказать после проигранной серии, была ли игра легальной.

Аудит по замыслу проводится после матча: правила 33–35 аннулируют матч при противоречивых отчётах, поэтому агент, действующий на основании собственного обвинения, превращает подозрение во взаимный ноль. Всё ниже фиксирует доказательства и ничего не меняет в том, как мы играем (PLAN ADR-019).

# replay their revealed records through the fair-play rules: movement legality,
# the Barrier Law, the budget, step order, hint length - and say what it could NOT check
uv run python scripts/audit_opponent.py --team vibecode

# are we disclosing scent on the same terms they are?
uv run python scripts/scent_parity.py --since 2026-08-14T16:00   # UTC

# 323 of 323 sealed capture claims name the claimer's own revealed cell
uv run python scripts/claim_evidence.py

# both repos must declare the same counted-match count (rules 37-38)
uv run python scripts/reconcile_counted.py ../pursuit-thief-agent --apply

Что может установить раскрытие и что может только провод: запечатанная запись пира содержит то, что этот пир решил запечатать. Движение и барьеры проверяемы по одному архиву; smell_grid, capture_claim, hint и время ответа проверяемы только по тому, что пришло по проводу, поэтому FrameLog хранит их так, как они были отправлены. Аудит сообщает о них как о непроверяемых, а не включает их в чистый вердикт — «мы посмотрели и согласились» и «смотреть было не на что» никогда не должны читаться одинаково.

Три результата, которые ограничивают любую стратегию

Все они были установлены измерениями во время этапа лиги, и все они имеют решающее значение.

Барьер может уменьшить доску, но никогда не может взять вора. В книге даны три условия захвата (правила 46–47); эталон курса реализует ровно одно. В его rules.py есть thief_result и is_captured, и нигде нет проверки захвата барьером или обездвиживания. Каждый противник, которого мы встречали, происходит из эталона, поэтому окружение даёт мини-игру, которую мы засчитываем, а они — нет — противоречие правил 33–35. Каждый захват должен быть заявлением, которое вор подтверждает (PLAN ADR-020).

Один коп не может сойтись на открытой доске. Сетка 7×7 — это декартово произведение двух путей, поэтому её число копов равно 2 (Maamoun & Meyniel, 1987), и исчерпывающая неподвижная точка по всем состояниям 49×49 не находит ни одного состояния, из которого коп, использующий только движение, может вынудить захват при одновременных ходах. Наш коп, преследующий вора до дистанции 2 и удерживающий её 28 шагов, — это теорема, а не дефект. Барьеры — единственный ресурс, который меняет ответ (PLAN ADR-021).

А с планом барьеры действительно меняют это. Запечатывание половинным делением превращает любого реагирующего вора, которого может построить стенд, — доска делится пополам, половина делится пополам, 3×3 решается точно, заканчиваясь заявлением о совместном нахождении в пределах 35 ходов. Два указанных выше ограничения по-прежнему управляют формой этой победы: она должна заканчиваться заявлением, которое вор подтверждает, и её нельзя получить только движением. То, что долго фиксировалось здесь как наш самый большой конкурентный риск — коп, который преследует до дистанции 2 и удерживает, — закрыто; остающийся риск — это противник, чей собственный коп играет по плану, столь же полному, как наш, и именно против этого оцениваются нижние границы вора.

tests/regression/cop_duel.py — это эталон со стороны копа, с помощью которого проверяются эти утверждения: наш коп против адаптивного вора, с убеждением, которое реальный путь входа строит по запаху. Записанная линия противника не реагирует и не может измерить сближение.


Лицензия и атрибуция

MIT (см. LICENSE). Формы протокола и схемы артефактов совместимы с эталонным симулятором курса rmisegal/Game-P2P-Cop-Chase (образовательная лицензия); в случае конфликта между книгой и кодом главенствует книга.

Создано с помощью FastMCP, FastAPI, pydantic и uv.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    B
    maintenance
    Implements a distributed cops-and-robbers game agent as a FastMCP server, enabling peer-to-peer play with no central server. It manages turn-based moves, belief tracking, strategy selection, and secure protocol via SHA-256 commit-reveal.
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Runs a decentralized thief agent for a peer-to-peer cops-and-robbers game, using FastMCP to exchange moves and messages with a police agent while employing Bayesian belief and credibility-based bluffing strategies.
    -