world-simulator / goal.md
kikikita's picture
Refactor action handling and update UI elements
81070c7
|
Raw
History Blame Contribute Delete
32 kB

A newer version of the Gradio SDK is available: 6.20.0

Upgrade

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-контракт

Оставь контракт в таком стиле. Это единственный публичный набор базовых действий.

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

Во время выборов можно добавлять временный инструмент:

"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.

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 — короткое текстовое указание от правителя.

Примеры:

Protect the treasury, gather wood and avoid reckless war.
Steal coins from the other country and prepare to build a cannon.
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:

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

Пушка крафтится через:

use(use_type="craft", params={"recipe_id": "cannon"})

Условия:

  • инициатор — ruler;
  • населения достаточно;
  • в казне достаточно ресурсов;
  • пушка ещё не построена;
  • HP tax не должен сразу уничтожить страну.

Оператор

Ruler назначает оператора через:

use(use_type="assign_cannon_operator", params={"npc_id": "..."})

Выстрел

Оператор стреляет через:

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.

Не-админ не может.

Добавить или использовать:

ADMIN_TOKEN=dev-admin-token
MODEL_PROFILES_PATH=config/model_profiles.json

Минимальные endpoints:

GET  /admin/models
POST /admin/npcs/{npc_id}/model

Защита:

X-Admin-Token: <ADMIN_TOKEN>

В ledger должно быть видно, какой model profile использовался для NPC request.


14. Admin Model Logs Page

Сделать админ-страницу/вкладку в Gradio для просмотра общения с моделями.

Backend

Добавить endpoints:

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.

Читать именно:

logs/run_<UTC timestamp>/ledger.jsonl

Читать построчно, не грузить весь файл.

Возвращать нормализованный формат:

{
  "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

Добавить вкладку:

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 — ядро нового демо

  1. Добавить две страны и country_id.
  2. Добавить country info в perception.
  3. Добавить country badges в Gradio.
  4. Добавить выборы через env.
  5. Добавить vote только во время election window.
  6. Добавить ruler.
  7. Добавить policy и передачу policy в prompt.

P2 — экономика и конфликт

  1. Добавить физическую казну.
  2. Реализовать deposit/steal через transfer.
  3. Добавить treasury events в ledger/UI.
  4. Добавить простую пушку.
  5. Реализовать craft через use.
  6. Реализовать assign operator через use.
  7. Реализовать fire через use.

P3 — админка

  1. Добавить admin model switching.
  2. Добавить admin logs backend.
  3. Добавить Admin Logs вкладку в Gradio.
  4. Добавить live polling логов.

P4 — self-play и полировка

  1. Запустить игру.
  2. Прогнать минимум 100–200 ticks.
  3. Проанализировать ledger.
  4. Исправить баги поведения.
  5. Повторить прогон.
  6. Подготовить короткий финальный отчёт.

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.

Команды:

python -m pytest -q
python scripts/headless_sim.py
npm run typecheck

Если используется Windows venv:

.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. Финальный отчёт агента

В конце напиши:

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 -q116 passed; tests/test_country_systems.py (7 новых). Frontend: npm run typecheck → clean.

  • 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 удалены).
  • VERIFIED — Публичные действия только через MCP. api/mcp_server.py assert set(ACTION_TOOLS)==set(PLAYER_ACTIONS); всё идёт через runtime.player_actqueue_action.
  • VERIFIED — Единые tools move/speak/attack/use/transfer. mcp_server.py:51, players.py:38.
  • VERIFIED — vote только во время выборов. mcp_server VOTE_TOOL за player_vote_available; коннектор добавляет vote лишь при active_election_for_npc (openai_compatible.py:558). Тест test_vote_tool_only_during_election.
  • VERIFIED — Роли не блокируют действия. Промпт «Your role is context, not a tool restriction»; allowed=PRIMITIVE_TOOL_IDS для всех; players unrestricted_actions=True.
  • 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.
  • VERIFIED — NPC знают свою/чужую страну, правителя, policy, казну, мир. _build_survival_messages (country+world_countries), build_perception + rival lines. Тест test_survival_prompt_exposes_rival_countries.
  • 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.)
  • 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.
  • VERIFIED — Для каждого жителя видно модель. npc_request несёт model+model_profile+connector_id; admin-нормализатор отдаёт model per NPC (qwen-001→qwen3.6-27b).
  • 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 в этой сессии.
  • 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.
  • 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.
  • VERIFIED — Gradio demo без God/Overseer. api/public_ui.py: Observe / Connect your agent / Country, без chaos/god framing.
  • 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 и подтверждает поведение.