daggerheart.gvoth.dev

Multi-Agent Simulation · Google ADK · Deterministic Engine

AI Agents That Play Daggerheart Without Breaking the Rules

A Game Master agent and four player-character agents built with Google ADK play complete Daggerheart sessions autonomously—with zero human intervention, strict fog-of-war, and a deterministic Python engine that owns every die roll and state mutation.

Read Full Autonomous Replay ("The Sablewood Toll") → Explore the ADK Visual Guide ↗

Architecture

The Determinism Boundary: Make Drift Structurally Impossible

Left to roll its own dice and track its own hit points, an LLM table drifts within three turns. tinker-daggerheart enforces a hard separation of powers: ADK decides who speaks next and runs the models, while a pure Python engine owns every roll, resource track, and state write.

Stateless LLM Oracles Above · Deterministic Rules Engine Below Player Agents ×4 1 per PC · fog-of-war view only GM Mover & Director GM moves, Fear spends, scenes GM Narrator & Scribe narrates engine outcomes only emit typed Pydantic intent only — no dice, no state mutation DETERMINISM BOUNDARY — ADK output_schema disables tool calling & direct state access Legality Gate — engine/validate.py verifies range, Hope/Stress cost, target visibility & loadouts; bounded re-prompt Deterministic Engine — combat · scene · order · dice · progress rolls Duality Dice (1d12 Hope + 1d12 Fear), applies thresholds, swings spotlight Canonical State — campaign/*.json strict Pydantic models, atomic writes Audit & Replay — session.jsonl + decisions.jsonl 1:1 narration audit · byte-for-byte replay resolved outcome

Stateless Schema Oracles

Every ADK LlmAgent runs with an output_schema (PlayerIntent, GMMoveIntent, Narration). A player agent can declare "I swing my mace at the bandit leader", never a roll or HP change.

Per-Viewer Fog of War

engine/fog.py projects the world through the eyes of whoever is acting: a PC sees only revealed map features, public rumors, and their own private backstory and memory notes.

1:1 Audited Narration & Replay

ReplayPlugin records every model decision to decisions.jsonl so a whole campaign replays offline byte for byte. tools/audit.py verifies that every engine beat is answered 1:1 by one narration with zero orphans.

Orchestration

ADK Graph Workflows & Multi-Model Roles

The table is orchestrated by three nested Google ADK graph Workflows—one-shot / campaign, scene, and combat—routing via Event.route between async function nodes and a single model seam (oracle.ask(ctx, agent, prompt, schema)).

9 Playable Table Modes

Run a scripted one-shot ("The Sablewood Toll"), an auto-balanced bestiary skirmish (--random), a multi-session campaign with level-ups (--campaign), an emergent open table where the GM improvises scenes and fronts (--open-campaign), AI party creation (--session-zero), or sit in a chair yourself with DH_HUMAN_SEAT=kael while AI agents play the rest of the party.

Mix Gemini, Claude & Local Ollama Models

Assign different models per role with DH_MODEL_GM, DH_MODEL_PLAYER, and DH_MODEL_TALK—for example, Claude Haiku 5.5 or Gemini 3.1 Flash-Lite for tactical decisions and a local ollama/gemma4:26b model for table chatter and notes, with live token & cost metering via MeterPlugin.

Evaluation

Measured Against a Real Human Table (DH_TABLE=real)

Beyond mechanical legality, the simulation measures how closely AI table dynamics match transcripts of real human tabletop sessions—tracking turn-taking, out-of-character banter, rules questions to the GM, reply chains, spotlight bidding, and per-player subjective memory recaps.

Table Metric (3-Session Campaign, 4 Scenes/Session) AI Table (DH_TABLE=real) Real Human Table Yardstick
GM Share of Talk (Distance) 0.09 – 0.10 from target Baseline human GM share
GM Register Distance 0.06 – 0.12 Baseline human GM register
Player Questions to GM 12 – 13 / hour ~25 / hour
Player-to-Player Reply Chains Up to 5.4 / hour 9.6 / hour
Out-of-Character Table Talk 18% – 21% of turns 13% of turns
Mechanical Audit (tools/audit.py) 100% (1:1 beat-to-narration, 0 orphans) Deterministic invariant