LemonSim
August 10, 2026 · View on GitHub
LemonSim is the deterministic simulation arena in the Lemon stack. It runs LLMs inside explicit game and benchmark worlds, records what happened, and produces artifacts that can be replayed, scored, and verified.
Shape
LemonSim.Kernelowns the event-sourced core: state, events, runner flow, updater/action/projector behaviours, decision adaptation, persistence, and pubsub helpers.LemonSim.LLMowns model-facing execution:ToolLoopDecider, tool policies, provider/model setup, throttling, live-run helpers, and transcripts.LemonSim.Benchowns benchmark artifacts: manifests, scorecard behaviours, registry-driven scorecard recompute verification, hash verification, and shared run-bundle helpers.LemonSim.Examples.*owns the worlds. The examples are 92.3% of the current Elixir source underapps/lemon_sim/lib, because the interesting work is in scenario rules, tools, projections, and scoring.
ToolLoopDecider is the fairness boundary for heterogeneous models. A scenario
offers a constrained tool/action surface, the model chooses through that
surface, and the updater turns accepted actions into authoritative events.
Scenarios
There are 19 examples in this app:
TicTacToeis the smallest self-play scenario.Werewolfis the social-deduction showcase with hidden roles, transcripts, and video replay tooling. It is one of five scenarios in the persistent multi-game league system (LemonSim.Bench.League+ per-scenario adapters, alongsideSpaceStation,StockMarket,Survivor, andPoker): randomized model-to-seat match planning, per-game records, and per-model/per-role standings with Bradley-Terry ratings. Thelemon_sim_uiarenas run them around the clock.VendingBenchis the operator/physical-worker vending business benchmark, with deterministic CI, paper, V2, offline Arena, and live multi-agent Arena modes.TcgShopis a single-operator local game store benchmark with inventory, suppliers, customers, events, online orders, accounting, and scorecards.Pokeris a multi-hand no-limit hold'em session with per-seat model assignment; it is the fifth league/arena scenario (ranked mode on final chip stacks).Diplomacy,Skirmish,Survivor,Pandemic,Auction,Courtroom,SpaceStation,StockMarket,SupplyChain,DungeonCrawl,MurderMystery,Legislature,IntelNetwork, andStartupIncubatorcover other state machines and decision surfaces.
Run
Small local smoke run:
mix lemon.sim.tic_tac_toe --offline-strategy random --seed 42 --no-persist --max-turns 10
Deterministic benchmark run with artifacts, also keyless:
mix lemon.sim.vending_bench --preset ci --offline-strategy baseline --sim-id vb_ci_baseline
mix lemon.sim.verify apps/lemon_sim/priv/game_logs/vending_bench/vb_ci_baseline
mix lemon.sim.score apps/lemon_sim/priv/game_logs/vending_bench/vb_ci_baseline
TCG Shop deterministic lanes:
mix lemon.sim.tcg_shop --preset ci --offline-strategy baseline --sim-id tcg_ci_baseline
mix lemon.sim.tcg_shop --preset ci --offline-strategy pressure --sim-id tcg_ci_pressure
mix lemon.sim.tcg_shop --preset stress --offline-strategy overextended --sim-id tcg_stress_bad_operator
Live model runs require configured providers and credentials:
mix lemon.sim.tic_tac_toe --no-persist --max-turns 10
mix lemon.sim.werewolf --player-count 6 --no-persist --max-turns 50
mix lemon.sim.werewolf --player-count 5 --models anthropic:claude-sonnet-4-20250514,openai:gpt-4.1,google_gemini_cli:gemini-2.5-flash,openai-codex:gpt-5.1-codex-mini,kimi:k2p5
mix run apps/lemon_sim/priv/scripts/werewolf_5model.exs
A single mix lemon.sim.replay <scenario> <log> task renders JSONL transcripts
for every scenario. For example, a Werewolf transcript produced by
mix lemon.sim.werewolf --models ... --transcript-path path.jsonl:
mix lemon.sim.replay werewolf apps/lemon_sim/priv/game_logs/werewolf_4model.jsonl
mix lemon.sim.replay skirmish priv/game_logs/abc123.jsonl --output replay.mp4 --fps 4
A single positional argument is treated as a skirmish log for backwards
compatibility. Run mix lemon.sim.replay --help for the full scenario list.
The replay task requires rsvg-convert and ffmpeg on PATH. (VendingBench
uses mix lemon.sim.vending_bench_replay, which builds a static HTML browser.)
Artifacts
Benchmark runs write bundles under apps/lemon_sim/priv/game_logs/... unless
--artifact-dir is provided. Vending Bench and TCG Shop write rich bundles with
scenario-specific replay/transcript files. Poker, Stock Market, and Pandemic
write verified scorecard bundles with manifest.json, hashes.json,
final_world.json, events.jsonl, actions.jsonl, scorecard.json, and
usage.json.
Use:
mix lemon.sim.verify path/to/run
mix lemon.sim.score path/to/run
Determinism
The kernel (kernel/runner.ex) is a pure
reducer: a run is a fold of an event sequence through apply_event, so the same
seed produces a byte-identical run. This is verified, not just asserted —
test/lemon_sim/determinism_test.exs runs
the offline VendingBench strategy (a scripted decider, no LLM) twice with one
seed and asserts both the in-memory final world state and every artifact's
sha256 in hashes.json are identical. It is fast (~0.2s) and fully offline, so
it belongs in the CI lane. The deterministic_artifacts?: true option pins the
two knobs that are nondeterministic by design (a unique run id and the wall-clock
timestamp); everything else falls out of the reducer.
verify checks the manifest, hash schema, required benchmark files, manifest
integrity hashes, and hashed file contents. For scenarios registered in
LemonSim.Bench.Scorecard.Registry, it also recomputes scorecard.json from
final_world.json and compares canonical JSON. Registered verified scenarios:
courtroom, diplomacy, intel_network, legislature, murder_mystery,
pandemic, poker, space_station, startup_incubator, stock_market,
supply_chain, survivor, tcg_shop, vending_bench,
vending_bench_arena, and werewolf. Unregistered scenarios still get
manifest/hash verification and skip scorecard recompute. score verifies first,
then prints the scorecard.
Regenerate local demo bundles with:
scripts/generate_sim_demo_bundles.sh [out_dir]
The generator writes deterministic VendingBench baseline and pressure bundles,
a CI-scale VendingBench Arena bundle, a small VendingBench suite, and ratings
output. It runs mix lemon.sim.verify and mix lemon.sim.score over every
bundle and prints a final metric summary. The default output directory is
tmp/sim_demo_bundles.
Suites and Leaderboards
Suites run one scenario across competitors and seeds, verify each run bundle, aggregate the registered primary metric, and rank competitors with tokens and cost alongside score. Keyless deterministic suites use offline strategies:
mix lemon.sim.suite --scenario vending_bench --preset ci --seeds 11,22,33 --offline baseline,pressure --out /tmp/vending-suite
Competitor specs accept exactly one execution mode:
| Shape | Scenario support | Cost behavior |
|---|---|---|
{"id":"baseline","offline_strategy":"baseline"} | Deterministic adapters | $0.0000 |
{"id":"model","model":"provider:model-id"} | Live adapters | Provider pricing or unknown |
{"id":"my-agent","external_cmd":"python3 my_agent.py"} | vending_bench | Unknown |
Suite run adapters are currently available for vending_bench, tcg_shop, and
vending_bench_arena. Other registered scorecards can be recompute-verified from
artifact bundles but do not yet have suite runners.
The keyless CI smoke lane is:
scripts/ci_sim_bench.sh
It runs deterministic offline suites for VendingBench, VendingBench Arena, and
TCG Shop under $RUNNER_TEMP or tmp/, verifies and scores every produced run
bundle, writes cross-suite ratings, and checks VendingBench byte-reproducible
artifact output with a double-run diff -r gate.
That writes /tmp/vending-suite/suite.json, one verified bundle per
competitor/seed under /tmp/vending-suite/runs/, and
/tmp/vending-suite/leaderboard.md. The leaderboard shape is:
# LemonSim Suite Leaderboard
Scenario: `vending_bench`
Preset: `ci`
Seeds: 3
Metric: `score_modes.v1_net_worth` (maximize)
All included runs are manifest hash and scorecard verified.
| Rank | Competitor | Mean score_modes.v1_net_worth (maximize) | Per-seed values | Tokens | Cost |
|---:|---|---:|---|---:|---:|
| 1 | baseline | ... ± ... (n=3) | 11: ..., 22: ..., 33: ... | 0 | \$0.0000 |
| 2 | my-agent | ... ± ... (n=3) | 11: ..., 22: ..., 33: ... | 0 | unknown |
Each ranking entry in suite.json includes additive stats with n, mean,
sample std, min, and max for the primary metric across included seeds.
Live competitors can be added with repeatable --model MODEL_ID options when
provider credentials are configured. VendingBench external agents can be added
with repeatable --external-cmd COMMAND options. Re-render an existing suite
without rerunning scenarios:
mix lemon.sim.leaderboard /tmp/vending-suite
mix lemon.sim.leaderboard /tmp/vending-suite --recompute
--recompute re-verifies the run bundles and rebuilds rankings. Any failed or
tampered run stays visible in suite.json and the failure section of
leaderboard.md, but it is excluded from rankings.
Write A Scenario
Start with LemonSim.Examples.TicTacToe; it is the smallest complete example.
The standard anatomy is:
- top-level scenario module, such as
apps/lemon_sim/lib/lemon_sim/examples/tic_tac_toe.ex action_space.exfor legal model/user actionsupdater.exfor authoritative state transitionsevents.exfor emitted facts- optional
performance.ex,game_log.ex,frame_renderer.ex, orvideo_generator.exwhen the scenario needs scoring or replay media
Keep scenario-specific rules in LemonSim.Examples.*. Shared runner,
model-loop, artifact, and verification logic belongs in Kernel, LLM, or
Bench.
Spectator UI
apps/lemon_sim_ui is the LiveView spectator surface. It can watch registered
simulation runs, render scenario-specific boards, and serve replay-oriented
views for long or public runs.