--- title: Wargame Backend emoji: "\U0001F3AF" colorFrom: blue colorTo: indigo sdk: docker app_port: 7860 pinned: false short_description: AI war-gaming engine API (private trial) --- # AI War Gaming > Strategic war-gaming for company executives. A human CEO plays one seat; 11–13 AI agents (CFO, CMO, rival CEO, regulator, media, board chair, unions…) simulate the rest of the world. Five turns, ~15 minutes of play, and an end-game **debrief** that reveals every agent's hidden agenda, their private per-turn deliberation, and the umpire's structured verdict on every action. The differentiator isn't the play — it's the reveal. Every scenario has 11–13 *named* characters (Bettina Lange the CFO, Sir Rupert the Chair, Andrew Greenwood the activist MP, Aergis's Li Wei Zhang…), each with a hidden agenda and a hidden irritant pinned into their prompt. If reading the debrief doesn't make a playtester say *"wait, that's what **Sir Rupert** was really thinking?"* or *"that's what **Aergis** was about to do?"*, the prototype failed regardless of anything else. ## What's in the box A **headless Python engine** with three surfaces: - **CLI** — the primary interface (`uv run wargame --help`). - **Web API** — FastAPI wrapper powering the Vite + React + TS UI in [`frontend/`](frontend/). - **Debrief exporter** — markdown report, the product's signature output. ### Features | Area | What it does | |---|---| | **Matrix-game engine** | 5-phase turn loop. Actions must carry 3 anchors (`prior_analogue`, `stated_capability`, `resource_committed`). Control scores plausibility 1-5 against the anchors and emits `effect_major` / `effect_minor` / `no_effect` / `backfires`. | | **Scenario library** | 12 shipped scenarios — 4 original archetypes (`competitor_disruption`, `crisis_response`, `ma_integration`, `activist_investor`) plus 8 DACH/EU scenarios grounded in recent European events (`signa_contagion`, `russia_exit`, `energy_supply_shock`, `ev_transition`, `hormuz_energy_shock`, `defense_ramp_up`, `orf_reform`, `swiss_bank_contagion`). Each ships a world bible + 11–13 named agents with hidden agendas + hidden irritants + initial state. | | **Setup wizard** | LLM-driven interactive scenario composer. Asks 1-3 rounds of clarifying questions, synthesises a brief, commits a fully-generated scenario. | | **Document grounding** | Attach `.pdf` / `.docx` / `.md` / `.txt` documents when scaffolding a scenario — board decks, 10-Ks, competitor filings become grounding material for the generator. | | **Multi-model** | 5 registered models across 3 providers: Claude Opus 4.6 / 4.7 and Sonnet 4.6 (Anthropic via Azure AI Foundry), GPT-5.4 (Azure OpenAI Responses), Kimi K2.5 (Azure AI Inference). Model is pinned per game via `wargame new --model `. | | **Scenario Orchestrator** | Agentic-age Monte Carlo equivalent. Before turn 1, spins up K forked branches (default 3) auto-played by proxy CEOs under different strategic vectors. Synthesises a landscape the real CEO studies before committing to their opening move. | | **Mid-game tools (non-blocking)** | *Peek* — reveal one agent's past deliberation. *Challenge-the-umpire* — dispute a verdict, re-adjudicated async without pausing the game. *Fork/replay* — branch a new game from any past turn to play forward differently. | | **Prompt caching** | Stable system prompt (world bible + persona + rules) is cached across every agent call via Anthropic's top-level `cache_control`. Verified live with substantial cache-read token ratios. | ## Quick start ```bash # Install uv sync --extra dev # Configure Azure AI Foundry endpoint + key cp .env.example .env # Edit .env — set AZURE_AI_FOUNDRY_ENDPOINT and AZURE_AI_FOUNDRY_KEY # Verify install (156 tests, fully offline, ~60s) uv run pytest -q # Play uv run wargame new --scenario activist_investor uv run wargame briefing uv run wargame ask cfo "what's the real capital-return room?" uv run wargame act "Convene the board and commit publicly to a £200M buyback + NED review" \ --why "Pre-empt Greenwood's EGM window by 10 days; split L&G off from the activist coalition" \ --anchor "prior_analogue: Sainsbury's 2022 response to Czech-origin activist letter" \ --anchor "stated_capability: Board can convene by Thursday; CFO has sale-leaseback scenarios in-ready" \ --anchor "resource_committed: £200M from existing £480M revolver capacity" uv run wargame advance # ~2-5 min — deliberation → adjudication → world update uv run wargame debrief --out debrief.md ``` More commands: ```bash uv run wargame scenarios # list installed scenarios uv run wargame models # list registered LLM models uv run wargame setup my_topic --brief "..." --auto-commit # compose a scenario via the wizard uv run wargame scaffold custom_case --brief "..." --doc board_deck.pdf # one-shot scaffold with docs uv run wargame orchestrate --branches 3 --turns 1 # pre-play strategic landscape uv run wargame orchestrate-show # print the synthesis uv run wargame new --scenario activist_investor --orchestrate # one-command: game + orchestrator uv run wargame setup my_topic --brief "..." --orchestrate # full walk-up: setup → game → orchestrator uv run wargame peek cfo # reveal the CFO's most recent deliberation uv run wargame challenge 2 --actor player --argument "..." # dispute a past verdict, non-blocking uv run wargame fork --from-turn 2 # branch a new game from turn 2 uv run wargame games --all # list including sim-branch games ``` ## Run the Web API ```bash uv run uvicorn wargame.api.app:app --reload --port 8080 ``` Then `http://localhost:8080/docs` for Swagger UI, `http://localhost:8080/openapi.json` for the spec. The API is a thin wrapper — every CLI command has a corresponding endpoint. See [`frontend/endpoints.md`](frontend/endpoints.md) for the full reference. ## Frontend A Vite + React + TypeScript UI ships in [`frontend/`](frontend/), built by Claude Design against the API contract below. ```bash cd frontend npm install npm run dev # http://localhost:5173 — proxies /api/* to the FastAPI backend ``` The `frontend/` directory doubles as the API contract for any UI builder (Claude Design or equivalent): - [`frontend/README.md`](frontend/README.md) — entry point, run instructions, navigation guide. - [`frontend/openapi.json`](frontend/openapi.json) — full OpenAPI 3.1 spec (~42 KB). - [`frontend/endpoints.md`](frontend/endpoints.md) — human reference of all endpoints grouped by use case. - [`frontend/state-machine.md`](frontend/state-machine.md) — the 5-phase turn loop + which endpoints are legal when. - [`frontend/examples/`](frontend/examples/) — captured JSON responses from the live API. - [`frontend/flows/`](frontend/flows/) — UX flow narratives (first-time player, setup wizard, orchestrate, peek/challenge/fork). - [`frontend/design-notes/`](frontend/design-notes/) — matrix-game mechanics, hidden-state reveal, async patterns, visualization ideas. - [`frontend/src/`](frontend/src/) — the shipped React app (screens: `GamesList`, `NewGame`, `GameView`, `Debrief`). ## Scenarios Twelve ship out of the box. All fictional but operator-grade (named entities, named numbers, named regulators). **Original archetypes:** | Scenario | The setup | |---|---| | `competitor_disruption` | Norhard Thermal AG (German heat-pump maker, €680M) vs Aergis Climate (Chinese entrant 38% below list). Installers defecting, sell-side downgrading, FSR filing on the table. | | `crisis_response` | Perlman Cardio (US pacemaker maker, $452M) facing a 61-event MAUDE cluster from Cleveland Clinic. FDA inspection in 3 weeks, class action filed, Silverlake's 2027 IPO pressure mounting. | | `ma_integration` | Wendland Performance Chemicals AG, 9 months post the €742M Mercier Additifs acquisition. Synergy targets slipping (6.4 / 22 €M), incumbent MD building a succession, BaFin watching. | | `activist_investor` | Haverford Retail plc (£2.1B UK food retailer) vs Greenwood Strategic Partners (6.81% stake demanding £720M sale-leaseback + break-up + board refresh). 14-day board response clock. | **DACH / EU scenarios grounded in recent European events:** | Scenario | The setup | |---|---| | `signa_contagion` | Königstadt Warenhaus Holding (DACH department-store retail, 73 stores) vs Tremont Credit Partners — post-Signa-style liquidity squeeze. | | `russia_exit` | Altösterreich Bank AG (pan-European banking, CEE/Russia footprint) navigating exit pressure from regulators and a hostile Russian counterparty. | | `energy_supply_shock` | Donau Energie AG (Austrian gas trading + generation + distribution) vs Transgazprom OAO — supply-contract crisis. | | `ev_transition` | Radmann Präzisionstechnik (German Tier-1 automotive supplier) vs BYD Component Systems EU — EV-transition cliff. | | `hormuz_energy_shock` | Nordstern Energie (AT-HQ integrated oil & gas, Gulf-exposed) vs Pacific Sun Trading — Hormuz-strait disruption. | | `defense_ramp_up` | Westfalia Rüstungswerke (German ammunition / land systems, MDAX) vs an NBIM / ABP / Ethos divestment coalition coordinated by Urgewald. | | `orf_reform` | Bundesrundfunk Österreich (Austrian public broadcaster) with a hostile Stiftungsrat chair driving reform under political pressure. | | `swiss_bank_contagion` | Zürich Privatbank AG (Swiss private banking, CHF 142B AUM) vs Morgan Sterling Private Wealth — lateral-hire raid during contagion. | Run `uv run wargame scenarios` to list what's installed locally. ## Architecture (one page) ``` wargame/ cli.py Typer CLI entry point config.py Env loading + model registry + per-provider client factories (Anthropic / Azure OpenAI / Azure AI Inference) engine/ loop.py Public turn orchestrator (new_game / act / advance / etc.) phases.py Per-phase step functions (briefing / deliberation / ...) state.py Pydantic state models (WorldState, Game, Verdict, etc.) persistence.py JSON snapshot per game agents/ base.py call_structured() — every LLM call, with prompt caching control.py Umpire: briefings, adjudication, world updates, re-adjudication role.py Generic role-agent driven by YAML persona rival_ceo.py Special: maintains multi-turn RivalPlan; revised on surprise registry.py Load scenarios/{id}/agents.yaml prompts_loader.py Markdown templates with {placeholder} slots orchestrator/ models.py StrategyVector, SimBranchOutcome, ScenarioMap, … vectors.py Strategy-vector generator + diversity constraint anchor_precheck.py Control-side proxy-action gate (anti-inflation) proxy_player.py Autonomous CEO stand-in per branch runner.py Branch orchestration (sequential branches, parallel deliberations) synthesis.py Post-branch strategic-landscape synthesis persistence.py ScenarioMap storage scaffold/ Scenario authoring: setup wizard + one-shot scaffold + doc extractors scenarios/ Data only: world_bible.md + agents.yaml + initial_state.json per scenario prompts/ Markdown templates (one per LLM call type) debrief/exporter.py Markdown debrief — the killer output api/ FastAPI thin wrapper over engine agents/providers/ Provider adapters — one per wire format, all return Pydantic instances tests/ Offline test suite (fake LLM via conftest.fake_llm) frontend/ Vite + React + TS UI + API contract (OpenAPI, flows, examples) scripts/benchmark_models.py Opt-in cross-model benchmark (real API calls; writes to benchmarks/) benchmarks/ Markdown reports from benchmark runs ``` Design invariants that must survive edits (see [`CLAUDE.md`](CLAUDE.md) for the full list): 1. **Data-driven agents.** Personas live in `scenarios/{id}/agents.yaml`. Adding an agent is a YAML edit, not a Python edit. 2. **Structured outputs always.** Every LLM call goes through `call_structured` and targets a Pydantic schema. Free-form JSON parsing is banned. 3. **The action contract is enforced.** Exactly 3 anchors per action, at the Pydantic layer. Do not loosen. 4. **Hidden agenda + hidden irritant per agent.** The anti-milquetoast mechanism that makes the debrief substantive. Do not drop. 5. **World bible is pinned.** Every agent prompt includes the full world bible. Specificity kills AI slop. 6. **Control does not play.** Control writes briefings, adjudicates, and synthesises world updates. It never submits an `ActionSubmission`. ## LLM backend Everything runs through Azure, but across three different API shapes — one wrapper (`call_structured` in `wargame/agents/base.py`) hides the differences: | Provider | Transport | Models | |---|---|---| | **Anthropic via Azure AI Foundry** | native Anthropic SDK, `base_url` pointed at `{foundry}/anthropic` | `claude-opus-4-6`, `claude-opus-4-7`, `claude-sonnet-4-6` | | **Azure OpenAI Responses** | `AzureOpenAI` SDK | `gpt-5.4` (default) | | **Azure AI Inference** | OpenAI-compatible chat completions | `Kimi-K2.5` | Default model is `gpt-5.4` (override via `WARGAME_DEFAULT_MODEL` env or `--model` on `wargame new`). Every game is pinned to a single model at creation time — this is deliberate, so the reveal in the debrief is attributable to one model. Use `scripts/benchmark_models.py` for a cross-model A/B on a fixed turn. Prompt caching uses Anthropic's top-level `cache_control={"type": "ephemeral"}` when the active provider is Anthropic. ## Testing ```bash uv run pytest -q # 156 tests, fully offline, ~60s ``` Tests use a `fake_llm` fixture (in `wargame/tests/conftest.py`) that monkey-patches `call_structured` and dispatches canned responses by Pydantic schema class name. Adding a new structured-output schema requires extending the `_dispatch()` map. ## Security - `AZURE_AI_FOUNDRY_KEY` lives **only** in `.env` (gitignored). Never hardcoded, never logged. - API has **no auth** in MVP — localhost-only by design. - No PII flows through. Scenario docs uploaded for grounding are held in-memory during scaffold and not persisted. ## Deferred to v1 - Per-agent model routing (model is currently pinned per-game; every role in a game shares one model). - Multi-user / team play (single-player is the MVP's deliberate shape). - Real-data connectors (EDGAR, news wires, analyst feeds). - Export formats beyond markdown (PDF, board decks, CSV). - Authentication on the Web API. - SQLite / real DB (JSON snapshots are fine for MVP). ## License TBD — this is an early prototype, not yet licensed for distribution. ## Acknowledgements Matrix-game methodology is a well-established wargaming tradition; this project applies it to an agentic-LLM multi-agent simulation. The strategic-orchestrator pattern is novel to this codebase.