# target.md — краткая цель для Codex / Claude Code ## 0. Режим работы агента Прочитай этот документ и реализуй задачу шаг за шагом. Не пытайся угадать архитектуру: сначала найди реальные файлы проекта, затем вноси минимальные изменения. Главный принцип: после каждого крупного блока запускай игру, смотри `logs/run_*/ledger.jsonl`, проверяй, как реально живут жители, и исправляй баги поведения. Зелёные тесты недостаточны: в демо жители должны жить, голосовать, копить ресурсы, взаимодействовать со странами и действовать через MCP. --- ## 1. Финальная цель Сделать hackathon MVP игры: - единый живой мир; - две страны/фракции; - NPC-жители внутри стран; - внешний пользователь подключает своего агента через MCP как обычного жителя; - публичных God Console, Chaos и Overseer больше нет; - все публичные действия идут только через единый MCP-контракт; - роли NPC не ограничивают доступные действия, а только влияют на контекст и стиль поведения; - каждые N тиков проходят выборы; - во время выборов появляется дополнительное действие `vote`; - правитель задаёт текстовую политику страны; - политика попадает в контекст NPC; - у страны есть физическая казна; - жители могут пополнять казну и воровать у другой страны; - страна может скрафтить пушку; - у пушки есть оператор; - оператор может стрелять по координатам; - всё работает через текущий Gradio-интерфейс; - админ может смотреть логи общения с моделями в читаемом виде. --- ## 2. Что обязательно выпилить / выключить ### 2.1. Overseer В финальном режиме Overseer не нужен. Нужно: - выключить Overseer в runtime; - убрать его из tick loop; - убрать Overseer directives из perception; - убрать панель мыслей Overseer из Gradio; - новые логи не должны содержать активные `overseer_request` / `overseer_response`. - удалить Если полное удаление файлов рискованно — не удаляй физически, а выключи через config/env. ### 2.2. God Console / Chaos Публичной консоли бога быть не должно. Нужно: - убрать God Console из Gradio; - убрать Chaos framing из текстов; - отключить публичные god/chaos endpoints или сделать их admin-only; - пользователь может только: - наблюдать за миром; - создать персонажа; - подключить своего агента через MCP; - проверить статус персонажа. --- ## 3. Главный продуктовый сценарий Пользователь открывает Gradio-интерфейс и видит: 1. **Observe the world** Наблюдать за миром двух AI-стран. 2. **Connect your agent** Создать персонажа в выбранной стране и получить MCP token/URL. 3. **Check your character** Проверить статус персонажа по токену. 4. **Admin logs / Admin panel** Только для админа по `ADMIN_TOKEN`. В мире должны быть видны: - две страны; - бейдж страны у NPC; - роль NPC; - текущий правитель страны; - текущая политика страны; - казна страны; - пушка страны, если построена; - ближайшие события. --- ## 4. MCP — единственный публичный способ действия Все NPC и внешние агенты должны использовать один и тот же набор инструментов. Нельзя делать отдельные “магические” действия, которые обходят validator или напрямую меняют world state. Инструменты доступны всем живым NPC и внешним агентам, если действие физически возможно. Роль не должна запрещать базовые действия. Исключение: в день выборов появляется дополнительный инструмент `vote`. --- ## 5. Единый MCP-контракт Оставь контракт в таком стиле. Это единственный публичный набор базовых действий. ```python ACTION_TOOLS: dict[str, dict[str, Any]] = { "move": { "description": ( "Walk toward a destination position (x and z) this tick. To flee, " "move toward coordinates away from the threat." ), "inputSchema": _obj( { "x": {"type": "number", "description": "Destination X coordinate."}, "z": {"type": "number", "description": "Destination Z coordinate."}, } ), }, "speak": { "description": ( "Say a message. With target_npc_id it is direct speech to that NPC; " "without one it is a shout heard by everyone nearby." ), "inputSchema": _obj( { "message": {"type": "string", "description": "What to say."}, "target_npc_id": {"type": "string", "description": "Who to speak to (optional)."}, }, required=["message"], ), }, "attack": { "description": ( "Attack a valid nearby threat or hostile target. Give target_entity_id " "to choose a specific target, or omit it to attack the nearest valid threat. " "The engine resolves approach, range and damage." ), "inputSchema": _obj( {"target_entity_id": {"type": "string", "description": "Id of the target to attack."}} ), }, "use": { "description": ( "Use something in the world: eat food, heal with herbs, gather from a resource node, " "build or repair a house, use treasury, craft an object, or use a cannon. " "The exact effect depends on target/use_type/params and is validated by the engine." ), "inputSchema": _obj( { "resource_type": { "type": "string", "enum": ["food", "herbs", "wood", "coins"], "description": "Optional resource type for consuming, crafting or treasury actions.", }, "resource_id": {"type": "string", "description": "Resource node id, if using/gathering a resource."}, "target_entity_id": {"type": "string", "description": "World object/entity id to use."}, "use_type": { "type": "string", "description": "Use mode, e.g. eat, heal, gather, build, repair, craft, fire.", }, "params": { "type": "object", "description": "Additional structured parameters for the selected use_type.", }, } ), }, "transfer": { "description": ( "Give a resource to a nearby NPC, treasury, storage or object. " "Set take=true to steal/take from a valid nearby target if rules allow it." ), "inputSchema": _obj( { "target_npc_id": {"type": "string", "description": "The other NPC, if target is a character."}, "target_entity_id": {"type": "string", "description": "Treasury/storage/object target id."}, "resource_type": { "type": "string", "enum": ["food", "herbs", "wood", "weapon", "coins"], }, "amount": {"type": "integer", "minimum": 1, "maximum": 20}, "take": {"type": "boolean", "description": "Steal/take instead of give."}, }, required=["resource_type", "amount"], ), }, } ``` ### 5.1. Что делать с build/gather Если в проекте уже есть отдельные `build` и `gather`, не обязательно ломать их сразу. Но публично и логически они должны быть приведены к `use`. - `gather(resource_id)` → `use(resource_id=..., use_type="gather")` - `build()` → `use(use_type="build")` - `repair()` → `use(use_type="repair")` Не добавляй отдельные новые MCP tools для каждой механики. Новые игровые механики должны проходить через `use` или `transfer`. ### 5.2. Единственное исключение — vote Во время выборов можно добавлять временный инструмент: ```python "vote": { "description": ( "Vote for a ruler candidate during an active election. " "Use candidate_id from the current election candidate list." ), "inputSchema": _obj( { "candidate_id": { "type": "string", "description": "NPC id of the candidate." } }, required=["candidate_id"], ), } ``` `vote` доступен только во время election window. В обычные тики его не должно быть в available tools. --- ## 6. Роли NPC Сейчас классовая система ограничивает выбор действий. Это нужно убрать. Роль должна быть только контекстом: - Guard чаще защищает и атакует угрозы; - Builder чаще строит и чинит; - Gatherer чаще собирает ресурсы; - Ruler чаще думает о политике страны; - Cannon operator чаще управляет пушкой. Но базовые MCP tools не должны блокироваться ролью. Validator запрещает действие только по реальным причинам: - NPC мёртв; - цель мертва; - цель далеко; - нет ресурса; - нет объекта; - не хватает казны; - не идёт голосование; - NPC не ruler для смены политики; - NPC не cannon operator для выстрела; - cooldown; - действие нарушает физику/правила мира. --- ## 7. Две страны Добавить в world state две страны: - `nemotron`; - `qwen`. У каждой страны: - id; - name; - color/badge; - citizens; - ruler_id; - policy; - treasury; - cannon, если построена; - next_election_tick. У каждого NPC: - `country_id`; - role; - optional special status: `ruler`, `cannon_operator`; - optional `model_profile_id`. Perception каждого NPC должен содержать: - свою страну; - страну видимых NPC; - текущего правителя своей страны; - текущую политику своей страны; - состояние казны; - состояние пушки; - ближайшие события. --- ## 8. Выборы Выборы должны быть настраиваемыми через `.env`. ```env ELECTION_INTERVAL_TICKS=50 ELECTION_DURATION_TICKS=10 ``` Если таких env нет — добавить. ### Как должны работать выборы 1. Каждые `ELECTION_INTERVAL_TICKS` начинается election window. 2. Для каждой страны формируется список кандидатов из живых граждан. 3. В perception добавляется: - что идут выборы; - список кандидатов; - краткое описание каждого кандидата; - что он делал; - что говорил; - значимые события. 4. В available tools временно появляется `vote`. 5. Каждый житель страны может проголосовать. 6. Если NPC не проголосовал через модель — fallback должен проголосовать сам. 7. Победитель становится ruler. 8. Новый ruler задаёт policy страны через LLM или fallback. 9. Policy сохраняется в CountryState и попадает в prompt граждан. Важно: во время выборов NPC должен понимать, что сейчас политическая фаза, а не обычный survival tick. --- ## 9. Политика страны Policy — короткое текстовое указание от правителя. Примеры: ```text Protect the treasury, gather wood and avoid reckless war. ``` ```text Steal coins from the other country and prepare to build a cannon. ``` ```text Trade peacefully, help weak citizens and attack only if threatened. ``` Policy не должна напрямую менять мир. Она должна попадать в perception/prompt и влиять на выбор действий модели. --- ## 10. Казна У каждой страны должна быть физическая казна на карте. Казна хранит: - food; - herbs; - wood; - coins. Действия: - свой гражданин может положить ресурс в казну через `transfer`; - свой гражданин может взять ресурс, если правила позволяют; - гражданин другой страны может украсть ресурс через `transfer(take=true)`, если он рядом с казной; - все treasury events должны попадать в ledger/engine events. --- ## 11. Пушка и крафт Пушка — killer feature, но реализация должна быть простой. ### Условия крафта Через `.env`: ```env CANNON_MIN_POPULATION=6 CANNON_TREASURY_COST=20 CANNON_WOOD_COST=15 CANNON_HP_TAX_PER_CITIZEN=5 CANNON_DAMAGE=35 CANNON_RADIUS=5 CANNON_COOLDOWN_TICKS=20 ``` Пушка крафтится через: ```text use(use_type="craft", params={"recipe_id": "cannon"}) ``` Условия: - инициатор — ruler; - населения достаточно; - в казне достаточно ресурсов; - пушка ещё не построена; - HP tax не должен сразу уничтожить страну. ### Оператор Ruler назначает оператора через: ```text use(use_type="assign_cannon_operator", params={"npc_id": "..."}) ``` ### Выстрел Оператор стреляет через: ```text use(use_type="fire", target_entity_id="cannon_id", params={"x": 10, "z": -5}) ``` Эффект: - проверка operator; - проверка cooldown; - area damage по живым целям в радиусе; - friendly fire допустим, чтобы у решения была цена; - событие пишется в ledger и отображается в UI. --- ## 12. Внешний MCP-персонаж Пользователь через Gradio создаёт персонажа: - name; - icon; - country. Получает: - token; - MCP URL; - character id; - список tools. Ограничения: - максимум 1 world-changing action per tick; - observe можно чаще, но с rate limit; - если персонаж умер — write actions возвращают ошибку; - invalid token — auth error; - внешний агент не может менять модель; - внешний агент не может вызывать God Console; - внешний агент действует теми же tools, что и NPC. --- ## 13. Admin model switching Админ может переключить модель любого NPC из списка в JSON config. Не-админ не может. Добавить или использовать: ```env ADMIN_TOKEN=dev-admin-token MODEL_PROFILES_PATH=config/model_profiles.json ``` Минимальные endpoints: ```text GET /admin/models POST /admin/npcs/{npc_id}/model ``` Защита: ```text X-Admin-Token: ``` В ledger должно быть видно, какой model profile использовался для NPC request. --- ## 14. Admin Model Logs Page Сделать админ-страницу/вкладку в Gradio для просмотра общения с моделями. ### Backend Добавить endpoints: ```text GET /admin/logs/runs GET /admin/logs/runs/current/records?since_line=0&limit=200 GET /admin/logs/runs/{run_id}/records?phase=&npc_id=&tick_from=&tick_to=&limit= ``` SSE optional. Если не успевает — polling. Читать именно: ```text logs/run_/ledger.jsonl ``` Читать построчно, не грузить весь файл. Возвращать нормализованный формат: ```json { "line": 123, "tick": 45, "phase": "npc_response", "npc_id": "npc_7", "model": "nemotron3-nano-4b-fp8", "summary": "NPC used transfer to deposit wood; validator accepted", "record": {} } ``` ### Frontend / Gradio Добавить вкладку: ```text Admin Logs ``` Показывать: - список runs; - фильтр phase; - фильтр npc_id; - tick range; - accepted/repaired/rejected/fallback; - live polling каждые 1–2 секунды; - prompt payload pretty JSON; - raw model output; - parsed action; - validator verdict; - fallback reason; - engine events. Если старые логи содержат Overseer — можно показывать как legacy. Новый режим не должен создавать новые Overseer records. --- ## 15. Gradio integration Всё должно работать через текущий Gradio-интерфейс. Нужно обновить: - landing text; - Observe screen; - Connect Agent; - Check Character; - Admin Logs; - Admin model switch, если успеешь; - country panels; - NPC badges; - treasury/cannon display. Не делай отдельный несовместимый UI, если основной демо-интерфейс — Gradio. --- ## 16. Приоритет реализации ### P0 — сначала 1. Найти реальные файлы tick/perception/validator/MCP/Gradio/ledger. 2. Выключить Overseer. 3. Выключить публичную God Console/Chaos. 4. Зафиксировать MCP tools. 5. Убрать role-based action blocking. 6. Проверить, что игра запускается. ### P1 — ядро нового демо 7. Добавить две страны и `country_id`. 8. Добавить country info в perception. 9. Добавить country badges в Gradio. 10. Добавить выборы через env. 11. Добавить `vote` только во время election window. 12. Добавить ruler. 13. Добавить policy и передачу policy в prompt. ### P2 — экономика и конфликт 14. Добавить физическую казну. 15. Реализовать deposit/steal через `transfer`. 16. Добавить treasury events в ledger/UI. 17. Добавить простую пушку. 18. Реализовать craft через `use`. 19. Реализовать assign operator через `use`. 20. Реализовать fire через `use`. ### P3 — админка 21. Добавить admin model switching. 22. Добавить admin logs backend. 23. Добавить Admin Logs вкладку в Gradio. 24. Добавить live polling логов. ### P4 — self-play и полировка 25. Запустить игру. 26. Прогнать минимум 100–200 ticks. 27. Проанализировать ledger. 28. Исправить баги поведения. 29. Повторить прогон. 30. Подготовить короткий финальный отчёт. --- ## 17. Обязательный self-play workflow После реализации каждого крупного блока: 1. Запусти тесты. 2. Запусти headless simulation. 3. Запусти Gradio/server. 4. Прогони 100–200 ticks. 5. Найди latest `logs/run_*/ledger.jsonl`. 6. Проверь: - NPC понимают свою страну; - NPC видят страну других; - роли не блокируют базовые tools; - во время выборов появляется `vote`; - жители голосуют; - ruler назначается; - policy попадает в prompt; - жители используют treasury; - враги могут воровать; - пушка достижима в demo config; - dead NPC не появляются как targets; - Overseer/God Console не активны; - MCP-персонаж может действовать; - accepted/repaired/rejected/fallback не деградировали. Если поведение плохое — исправь prompt/perception/validator/balance и прогони снова. --- ## 18. Тесты Добавить или обновить тесты: - existing tests pass; - Overseer disabled in final mode; - public God Console disabled; - MCP tools schema valid; - roles do not block base tools; - vote appears only during election; - country_id in NPC perception; - visible NPCs include country; - election starts by env interval; - fallback vote works; - ruler assigned; - policy appears in perception; - treasury deposit works; - treasury steal works; - cannon craft rejects too early; - cannon craft succeeds when requirements met; - cannon fire damages area; - dead MCP character cannot act; - admin token required for admin endpoints; - admin logs read ledger by line. Команды: ```bash python -m pytest -q python scripts/headless_sim.py npm run typecheck ``` Если используется Windows venv: ```bash .venv\Scripts\python.exe -m pytest -q .venv\Scripts\python.exe scripts\headless_sim.py ``` --- ## 19. Definition of Done Готово, если: - Gradio запускается; - публичного Overseer/God Console больше нет; - есть две страны; - NPC имеют country badges; - MCP tools единые; - роли не блокируют базовые действия; - выборы идут по env interval; - `vote` доступен только во время выборов; - ruler задаёт policy; - policy попадает в context NPC; - treasury физически существует и используется; - cannon можно скрафтить и выстрелить; - external MCP character работает; - admin model switching работает; - admin logs page читает ledger; - Codex сам прогнал игру и исправил найденные проблемы; - тесты зелёные. --- ## 20. Финальный отчёт агента В конце напиши: ```text Implemented: - ... Validation: - pytest: ... - headless_sim: ... - gradio/server: ... - latest ledger: ... - ticks simulated: ... Observed behavior: - countries: ... - elections: ... - policies: ... - treasury: ... - cannon: ... - MCP character: ... Known limitations: - ... ``` --- ## 21. Verification status (audit 2026-06-14, Claude) Проверено по коду / тестам / ledger. Статусы: `[x] VERIFIED` `[~] PARTIAL` `[!] BROKEN` `[ ] NOT IMPLEMENTED`. Self-play ledger: `logs/run_20260614T104917Z/ledger.jsonl` (200 тиков, routing Nemotron+Qwen, offline LLM-shaped completer гоняет тот же pipeline). Headless: `scripts/headless_sim.py` (300 тиков, deterministic). Tests: `pytest -q` → **116 passed**; `tests/test_country_systems.py` (7 новых). Frontend: `npm run typecheck` → clean. - [x] VERIFIED — Нет активных Overseer/God Console/Chaos в runtime. `api/runtime.py` tick() зовёт `plan_world_tick` без overseer; `overseer_mode="off"` (`spawning.py:81`); нет `/chaos`,`/god` роутов (`api/gradio_app.py`,`api/server.py`); фронт без Chaos/Overseer (ChaosDock/OverseerPanel удалены). - [x] VERIFIED — Публичные действия только через MCP. `api/mcp_server.py` `assert set(ACTION_TOOLS)==set(PLAYER_ACTIONS)`; всё идёт через `runtime.player_act`→`queue_action`. - [x] VERIFIED — Единые tools `move/speak/attack/use/transfer`. `mcp_server.py:51`, `players.py:38`. - [x] VERIFIED — `vote` только во время выборов. `mcp_server` VOTE_TOOL за `player_vote_available`; коннектор добавляет `vote` лишь при `active_election_for_npc` (`openai_compatible.py:558`). Тест `test_vote_tool_only_during_election`. - [x] VERIFIED — Роли не блокируют действия. Промпт «Your role is context, not a tool restriction»; `allowed=PRIMITIVE_TOOL_IDS` для всех; players `unrestricted_actions=True`. - [x] VERIFIED — Две страны, одна на Qwen. `spawning.py` Nemotron+Qwen; Qwen NPC `connector_id="qwen"`→ qwen secondary connector (`game.modal.local.json`, model `qwen3.6-27b-h100-fp8`). Ledger: модели per-country (nemotron3-nano 534 / qwen3.6-27b 466). Тест `test_demo_config_spawns_two_populated_countries`. - [x] VERIFIED — NPC знают свою/чужую страну, правителя, policy, казну, мир. `_build_survival_messages` (`country`+`world_countries`), `build_perception` + rival lines. Тест `test_survival_prompt_exposes_rival_countries`. - [x] VERIFIED — LLM-жители реально играют (action/speak/move/vote/transfer). Self-play ledger: 1914 npc_request/response; actions use 724 / move 829 / transfer 179 / speak 94 / vote 27; engine events gather/treasury_deposit/vote_cast/cannon_*. (Modal-эндпоинты настроены; live-прогон не делал из-за latency, harness гоняет идентичный pipeline routing→ledger.) - [x] VERIFIED — Ledger содержит npc_id, model/model_profile, request, raw response, parsed action, validator verdict, fallback reason, engine events. `connectors/openai_compatible.py` `_npc_request_entry`/`_npc_response_entry`; verdict реконсилится в `tick.py:_publish_plan_ledger_entries`. - [x] VERIFIED — Для каждого жителя видно модель. npc_request несёт `model`+`model_profile`+`connector_id`; admin-нормализатор отдаёт `model` per NPC (qwen-001→qwen3.6-27b). - [x] VERIFIED — Fallback явно виден. `npc_fallback` с `reason` (45 записей «model output was unusable»). Тест `test_unusable_model_output_is_logged_as_fallback`. - [~] PARTIAL — Внешняя MCP-модель играет по тем же правилам. `players.py` гонит queued action через тот же survival pipeline (`unrestricted_actions`, `vote` gated, dead→error); покрыто `tests/test_players_mcp.py`. Не гонял живого внешнего агента end-to-end в этой сессии. - [x] VERIFIED — Выборы настоящие. `_run_election_system`/`_start_election`/`_finish_election`/`_fallback_vote`/`_set_country_policy`. Self-play: election_started/completed 5, vote_cast 25, policy_updated 5. - [x] VERIFIED — Казна и пушка не декоративны. transfer меняет ресурсы (deposit/steal — тесты `test_treasury_*`), пушка craft/assign/fire меняет HP и убивает. Self-play: treasury_deposit 71, cannon_crafted 2, cannon_operator_assigned 2, cannon_fired 10. - [x] VERIFIED — Gradio demo без God/Overseer. `api/public_ui.py`: Observe / Connect your agent / Country, без chaos/god framing. - [x] VERIFIED — Admin logs читают `ledger.jsonl` построчно и показывают LLM/fallback. `runtime.admin_log_records` (line-by-line, фильтры phase/npc_id/tick); smoke: модели per-country + fallback summaries. ### Баги, найденные и исправленные в этой сессии 1. `RoutingWorldSimulator.propose_tick` терял `ledger_entries` → при Qwen-роутинге LLM-вызовы не попадали в ledger/admin. Fix: `connectors/routing.py` (мерж entries по owner). 2. `npcs.count: 0` во всех survival-конфигах → пустой demo-мир. Fix: `config/game*.json` count=6 (12 жителей). 3. В ledger не было видно модель жителя. Fix: `model_profile`/`connector_id`/`country_id` в `_npc_request_entry` + `_stamp_model_attribution`. 4. `_optional_known_entity_id` знал только beast-id → `target_entity_id` казны/пушки обнулялся, deposit/steal через LLM деградировали. Fix: распознаём treasury/cannon/house id. 5. `transfer(take=true)` к казне уходил в NPC-steal вместо казны. Fix: `_verb_for_action` — transfer к treasury всегда идёт в treasury-handler (он чтит `take`). 6. Perception без чужих стран и без своей позиции → нельзя воровать/навигировать. Fix: `world_countries` + `own_state.position` + rival lines в `build_perception`. 7. При сбое/непарсимом ответе модели verdict в `npc_response` реконсилился к «accepted» дет-фолбэка → фолбэк маскировался. Fix: `tick.py:_publish_plan_ledger_entries` реконсилит только `pending`; явный `rejected`+reason сохраняется (self-play теперь показывает rejected 48). ### Real-endpoint smoke (честно) 2-тиковый прогон на реальных Modal-эндпоинтах (`logs/run_20260614T110045Z`): tick'и прошли (status 200), но `/v1/chat/completions` не ответил в окне сессии (health=200, но генерация холодная/недоступна) → все NPC ушли в deterministic fallback, что корректно залогировано (24 `npc_fallback`). Это инфра, не код: ID моделей и routing верные, offline LLM-shaped self-play гоняет идентичный pipeline и подтверждает поведение.