world-simulator / goal.md
kikikita's picture
Refactor action handling and update UI elements
81070c7
|
Raw
History Blame Contribute Delete
32 kB
# 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: <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_<UTC timestamp>/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 и подтверждает поведение.