Skip to content

Command line

The grounded-decision command runs the built-in examples so you can see real decisions without writing code, and prints a machine-readable map of the public interface. It does not make decisions on your data: the library is meant to be called from Python, and the command line is for exploring, demonstrating and regression-testing. This page lists every subcommand and flag and shows how to read what they print.

Every output on this page was produced by the command shown above it, run from a checkout with uv run. If you installed the package into your own environment, drop the uv run prefix.

Finding every game, agent and mode

The examples reports are fixed so they can be tested. To choose who plays, how big the board is and how it is shown, use play and audit, and let the command line tell you what exists:

You want to know Run
Which games exist, their agents, regimes and worlds grounded-decision play --list
What each agent of one game does, its regime, the game's worlds, the audit oracles, and example commands grounded-decision play GAME --list
Every option of a subcommand, with worked examples grounded-decision play --help, audit --help, examples --help
A quick-start list of the most useful commands grounded-decision --help

For example, uv run grounded-decision play snake --list prints:

Games: snake made provably safe by a gate over a greedy player

Seats, in --agents order: snake. Give one agent per seat (one name fills every seat).
To compare several agents over the same games instead: grounded-decision compare snake --agents A,B,C

Agents (* = gated):
  greedy                   -                          shortest path to the apple; dies as the snake grows (baseline)
  greedy_with_tail_check   -                          greedy only if the tail stays reachable; never dies here but usually stalls
  manhattan                -                          steps straight towards the apple (O(1) baseline for huge boards)
  cycle                    -                          follows a Hamiltonian cycle; provably always wins, slowly
  grounded               * worst-case                 greedy proposals gated by the cycle-order invariant; proven to win (retraces its trail)
  grounded_multi           worst-case                 grounded, but the body may be ordered along ANY of 4-8 cycles; either orientation; proven
  planned_shortest         worst-case                 A* to each apple, accepted only with a re-entry certificate; proven to win
  planned_proven           worst-case                 A* routes scored for the next apple too, re-entry certified; proven, much faster
  probabilistic            probabilistic              like planned_proven but scores the expected next apple; proven safe, fastest online
  planned_survival         worst-case                 A* with a tail-reach certificate; never dies but may stall (survival only)
  clairvoyant              clairvoyant (benchmark)    plans with the future apple sequence (benchmark only)
  planned_proven_multi     worst-case                 planned_proven with multi-cycle certificates; proven, usually faster
  probabilistic_multi      probabilistic              probabilistic with multi-cycle certificates; proven safe, fastest online on most boards
  clairvoyant_multi        clairvoyant (benchmark)    clairvoyant with multi-cycle certificates (benchmark only)
  you                      (a person)                 take this seat yourself

Checks the gated agent (grounded) runs on every proposal:
  legal                       The move stays on the board, misses the body, and does not
                              turn back into the neck.
  keeps_body_ordered          After the move the body still lies in the order of the
                              board's cycle, so following the cycle stays safe.
  moves_closer_on_cycle       The move shortens the distance to the apple measured along
                              the board's cycle.
  keeps_some_cycle_ordered    After the move the body still lies in order along at least
                              one cycle of the family, so following that cycle stays
                              safe.
  moves_closer_on_some_cycle  The move shortens D, the least distance to the apple along
                              the cycles the body is ordered along.
  reenters_cycle_order        Simulated exactly, the route is legal, ends by eating, and
                              leaves the body in the order of the board's cycle.
  reenters_some_cycle_order   Simulated exactly, the route is legal, ends by eating, and
                              leaves the body in order along at least one cycle of the
                              family.
  tail_reachable_after        Simulated exactly, the route is legal, ends by eating, and
                              afterwards the head can still reach the tail (survival
                              only, not progress).

More about each agent: grounded-decision play snake --list --verbose, or --explain AGENT

Worlds (--world MODE; MODE:p mixes MODE with random, e.g. nearest:0.3; default random):
  random       a uniformly random free cell (the standard game)
  nearest      a kind world: the free cell closest to the head (around the body)
  farthest     a static adversary: the free cell farthest from the head (unreachable
               first)
  adversarial  an adaptive adversary: simulates the actual agent and picks the cell it
               takes longest to eat, or dies on (bounded search)
  The world is what the game does; an agent's regime is what it assumes. Clairvoyant
  agents need a world independent of their moves; probabilistic statements assume
  random.

Default line-up: probabilistic_multi
Audit oracles (audit snake --oracle NAME): manhattan, reentry (default manhattan)

Examples:
  grounded-decision play snake --agents probabilistic --size 20x20             # live view
  grounded-decision play snake --agents probabilistic_multi --size 20x20       # either orientation
  grounded-decision play snake --agents grounded --view history --size 8x8 --frame-every 25
  grounded-decision play snake --agents planned_proven --size 320x240 --style braille --max-steps 20000
  grounded-decision play snake --you --guarded                                 # you steer, the gate guards
  grounded-decision audit snake --agent grounded --size 10x10                  # its safety tax, move by move
  grounded-decision play snake --world adversarial --size 8x8                  # apples placed against the snake
  grounded-decision compare snake --agents grounded,cycle,greedy_with_tail_check --size 8x8 --world random,farthest,adversarial

Every agent named in these listings has a one-line description, and every example command shown by --help or --list is checked by tests/test_cli_discoverability.py against the real argument parser and agent lists, so the commands you copy always work.

Synopsis

uv run grounded-decision [--version] [--help]
uv run grounded-decision demo
uv run grounded-decision examples [ID] [--track TRACK] [--all] [--json]
uv run grounded-decision learn HISTORY.jsonl --max-risk RATE [--confidence C] [--measure M] [--procedure P] [--learn-decision-line] [--seed N] [--output FILE]
uv run grounded-decision play --list
uv run grounded-decision play GAME [--agents A,B] [--view live|history|json|step|none] [--fps N|max] [--delay S] [--frame-every N] [--seed N] [--size WxH|NAME] [--max-steps N] [--until-length N] [--style STYLE] [--color WHEN] [--zoom N|auto] [--you] [--you-play NAME] [--guarded] [--world MODE] [--board NAME] [--start NAME] [--holes CELLS] [--layout NAME] [--mines N] [--first-click safe|opening] [--connect K] [--preview N] [--pieces N] [--mode placement|realtime] [--level N] [--lock-delay TICKS] [--gravity CURVE] [--target TILE] [--stop-at-target]
uv run grounded-decision audit GAME [--agent NAME] [--opponent NAME] [--oracle NAME] [--oracle-depth N] [--seed N] [--size WxH] [--seat N] [--view history|json] [--no-regimes] [--regime-seeds N] [--max-steps N] [--world MODE]
uv run grounded-decision compare GAME --agents A,B,C [--seeds N] [--first-seed N] [--opening-plies N] [--size WxH] [--players N] [--max-steps N] [--json] [--world MODE[,MODE...]]
uv run grounded-decision record GAME [--agents A,B] [--world MODE] [--seed N] [--size WxH|NAME] [--players N] [--max-steps N] [--until-length N] [game rule options] [--keyframe-interval N] -o FILE.gdr
uv run grounded-decision replay-info FILE.gdr [--verify] [--moves] [--bytes] [--json]
uv run grounded-decision interfaces

Running grounded-decision with no subcommand is the same as demo.

Commands and flags

Command What it does Output
--version Print the installed package version (0.1.0 today). text
--help, -h Print usage. Also works after a subcommand (examples --help). text
demo (or no subcommand) Run the clinical-triage example: three patients, three decisions, explained. text
examples List every example id with its track and title. text
examples --track TRACK List only one track: product, step, adaptive, combo, live, markets or games. text
examples ID Run one example and print its full report. text
examples ID --json Run one example and print one line of JSON. JSON
examples --all Run every listed example (respecting --track) and print every report. text
examples --json Run every listed example (respecting --track), one JSON line each. JSON lines
learn HISTORY.jsonl --max-risk RATE Learn check settings from labelled history with a certified error rate; --output saves them with their provenance. Exits 2 if nothing could be certified. See Learn parameters from your history. text, JSON file
learn … --procedure P How the grid is tested: fixed_sequence (default: in an order fixed from the calibration split, each at the full significance, stopping at the first failure) or bonferroni (every combination at the significance divided by the grid size). Both certify the same guarantee; the fixed sequence usually acts more often. See Learning settings with a guarantee. text, JSON file
learn … --confidence C --measure M --seed N --learn-decision-line Certificate confidence (default 0.95); which error rate --max-risk limits (error_rate_among_acts, the default, or false_act_rate); split seed (default 0); also choose p_threshold from quantiles of the observed density.
play --list Every game (tictactoe, snake, checkers, chinese_checkers, go, tetris, hexxagon, minesweeper, 2048, connect4), its agents and its default line-up. text
play GAME Watch the default agents play: a live board redrawn in place on a terminal, the history of boards otherwise. play tictactoe alone is you against the engine, with every open square rated. animation / interactive
play GAME --agents A,B --view V Choose one agent per player (you takes a seat) and the viewer (live, history, json, step, or none: no drawing, one summary line with moves per second at the end). animation, text, JSON lines
play … --fps N / --fps max Live view: the match runs at full speed and the screen is redrawn at most N times a second (default 60), always with the latest position; max draws every move. Only the cells and panel text that changed are sent to the terminal.
play … --delay S Live view: pause S seconds after each frame (snake defaults to 0.05, board games to 0.6). Above 0, every move is drawn and --fps does not apply; --delay 0 runs at full speed. Games with a move animation (Hexxagon's infection: land, flash, flip) play it within this pause; with --delay 0 or --fps max there is none.
play … --zoom 1\|2\|3\|auto Draw each cell bigger: zoomed cells are boxes, pieces discs (with colour) or outlined tokens, highlights outlines that read without colour. auto (the default) picks the largest zoom at which the whole board fits the terminal, for boards up to 10 × 10; output that is not a terminal is drawn at zoom 1.
play hexxagon --board B --start S --holes CELLS --layout L Hexxagon variants: --board standard\|open\|donut\|rectangle, --start corners\|opposite\|ring (ring: six Pearls round an empty cell, Ruby infects all six on move 1), --holes e4,c2 (your own holes), --layout flat\|pointy (flat-topped columns as the original, default; or rows). play GAME --list shows every game's boards, starts and layouts; other games exit 2 with --board is not an option of GAME. See Hexxagon.
play GAME --you [--guarded] Take a seat; with --guarded the game's gate overrides your moves that fail its checks and says why. interactive
play chinese_checkers --stall-plies N --clear-finished End rules of Chinese checkers: stop after N plies in which nobody sets a new personal best distance to go (default players × 2 × 4 × side; 0 turns it off), and take finished players' pieces off the board. Also on compare. See When the game ends.
play connect4 --connect K Discs in a row needed to win in Connect Four (default 4; with --size WxH any board from 1 to 16 a side). Also on compare. See Connect Four.
play minesweeper --size NAME --mines N --first-click RULE Minesweeper's board: --size also takes the named levels beginner (9x9, 10 mines), intermediate (16x16, 40) and expert (30x16, 99); --mines N overrides the count; --first-click safe (default: the first cell clicked is never a mine) or opening (its neighbours are clear too, so it opens an area). Also on compare. See Minesweeper.
play tetris --preview N --pieces N Tetris: next pieces shown in advance (0-3, default 1) and the number of placed pieces that wins the game (default 500). Also on compare. See Tetris.
play tetris --mode realtime Tetris tick by tick: each tick one action (left, right, rotate_cw, rotate_ccw, soft_drop, hard_drop, wait), then gravity, then the lock delay; SRS turns with wall kicks, so tucks and T-spins (scored as in the guideline, with combos and back-to-back). --mode placement (the default) is the turn-based game. Also on compare and record. With --agents you you key one action per tick: a or ←, d or →, w, x or ↑ (turn clockwise), z (counter-clockwise), s or ↓ (soft drop), f (hard drop), . (wait), or the action names. See Tetris in real time. animation / interactive
play tetris --mode realtime --level N --lock-delay TICKS --gravity CURVE The clock: the starting level (1-30, default 1; it rises every 10 lines), the ticks a resting piece waits before it locks (0-30, default 5, half a second at 10 ticks a second), and the gravity: guideline (default: the guideline's speed curve by level), 20g (pieces fall to the stack at once) or N (one row every N ticks, 1-60). In placement mode these exit 2.
play 2048 --target TILE --stop-at-target 2048: the winning tile (a power of two, default 2048), and whether the game ends, won, when it appears (default: play on). Also on compare. See 2048.
play … RULE OPTION on another game A rule option the game does not have exits 2 with one line, for example grounded-decision play: error: --preview is not an option of connect4.
play … --world MODE Who controls the game's randomness, for games that have any (snake: where apples land): random (default, the standard game), nearest, farthest, adversarial (simulates the actual player and places the apple where it hurts most), or MODE:p to mix a mode with random. Also on audit and compare (a comma-separated list there gives one table per world). Games without randomness exit 2 with GAME has no world modes. See Who controls the randomness.
audit GAME Play one match, judge every decision of one agent (by default the game's gated agent) against a stronger oracle, and print the game's information-regime table. GAME is any of the ten games. See Seeing the future, assuming the worst, or betting on the odds. text, or JSON with --view json
audit … --agent A --opponent B --oracle O --oracle-depth N --seat N Choose the audited agent, the other seat's agent, the judge, the judge's search depth (where it has one) and the audited agent's seat (0 = first player).
audit … --seed N --size WxH --max-steps N Match seed (default 0), board size, and move budget for the match.
audit … --no-regimes --regime-seeds N Skip the regime price table, or choose the seeds it is computed over (default: the game's).
serve [--host H] [--port P] [settings] Run our own service (needs the service extra): a session protocol session per WebSocket on /ws/, one message per POST /api/, the reference decision service under /api/decisions/, and GET /healthz. Listens on 127.0.0.1:8700 by default; every setting (--database, --max-sessions, --messages-per-second, --request-timeout …) is also an environment variable (GD_SERVICE_MAX_SESSIONS), and serve --help lists them with their defaults. Exits 2 for a bad setting. See Run our own service. JSON log lines
interfaces Print the public interface map as indented JSON. JSON

Details worth knowing:

  • --all and --json without an id both run examples. With an id, --all is ignored.
  • --json takes precedence over --all: examples --all --json prints JSON lines, not reports.
  • demo has no --json. Use examples clinical_triage --json.
  • Ctrl-C prints Interrupted., restores the terminal and exits with status 130; a closed pipe (examples --all | head) exits quietly. At any play prompt, q quits and ? shows the keys; end of input (Ctrl-D) quits cleanly.
  • learn exits with status 1 and one line on standard error if the history file cannot be read.
  • audit exits with status 0 after a match, and with status 2 and one line on standard error for an unknown game (argparse usage), agent or oracle, for example grounded-decision audit: error: unknown agent 'nobody' for tictactoe; choose from random, rule_of_thumb, perfect, grounded, noisy_rule, minimax, expectimax, expectimax_rule, gambler, clairvoyant.
  • An unknown id prints unknown example '<id>'. known: … with the full list of ids and exits with status 1. An unknown subcommand prints argparse usage and exits with status 2.
  • make demo, make examples and make examples-all are shortcuts for demo, examples and examples --all; make learn, make play-tictactoe, make play-snake, make watch-snake, make history-snake and the other game targets (make games lists the games) for learn and play. The games and the viewers are described on Games.

demo

uv run grounded-decision demo

The demo is an emergency-department triage desk. A triage model labels each arriving patient low_risk (send home) or high_risk (move to a monitored bed). The policy proposes discharge or escalate accordingly; the library then checks whether the recent visits that resemble this patient (the related cases) are numerous and consistent enough to act on. The demo prints the business problem, a key, an index of the three patients, one card per patient, and a closing summary. The first two patients have strong histories and are acted on. The third is the interesting one:

tick 2/3  event=low_risk
  why    low-risk score, but only 5 loosely linked similar visits with mixed outcomes - a naive rule would discharge
  INPUT  no saved cursor — this case starts fresh
         Event low_risk  {heart_rate_bpm=88, spo2_percent=95}
         evidence  FLICKER, 5 observations
                   neighborhood d=1.6  λ=0  strength dλ²=0  (need > 1.05)  TOO THIN — below KS
                   density p̂=0.4  flicker band p*=0.15  FLICKERING (inside window — do not trust)
                   conformal  label=0.12  bag=40  p=1  vs α=0.10  in-distribution
                   support    share=0.8  vs min=0.50  supports the proposed action
                   observations {comparable_visits=5, similar_visit_links=4, adverse_outcomes_in_cohort=1}
  NAIVE  fire discharge without checking the graph
  DECISION
         hold — failed: reconstruction, threshold
         certificate      PASS  can we compile this policy (star-height ≤ 3)?
         game             PASS  can the policy keep making progress (parity win, nonnegative mean)?
         reconstruction   FAIL  do noisy neighbors recover the seed (dλ² > 1+margin)?
                            d λ² = 0.0000 vs 1+0.050 (at or below KS; abstain)
         threshold        FAIL  is density outside the operational flicker window?
                            evidence density inside operational window (theorem width=3.26e+05, vacuous=True)
         conformal        PASS  is this candidate weirder than the calibration bag?
         support          PASS  do the related cases support the proposed action?
         → ABSTAIN  because conjunction failed on reconstruction, threshold; proposed 'discharge' is blocked; naive would have fired 'discharge-or-escalate'
  OUTPUT hold_for_human
         proposed_action=discharge  (blocked, kept on the record)
         reason: d λ² = 0.0000 vs 1+0.050 (at or below KS; abstain); evidence density inside operational window (theorem width…
         extras['state']=auto  (cursor to persist)
  NEXT   this case is done; the next row is an independent decide (fresh cursor)
         naive would have fired 'discharge-or-escalate'; ledger committed hold_for_human

The closing block of the same run:

  close
    fired: discharge, escalate
    held for a human: blocked discharge
    naive policy: discharge-or-escalate — fires without the six checks
    naive over-fires on 1 tick(s); grounded holds those
    also: naive_action=discharge-or-escalate
  ward instruction for {'heart_rate_bpm': 72, 'spo2_percent': 98}: carry out: discharge
  ward instruction for {'heart_rate_bpm': 128, 'spo2_percent': 90}: carry out: escalate
  ward instruction for {'heart_rate_bpm': 88, 'spo2_percent': 95}: keep in intake, page charge nurse (score wanted: discharge)

Reading a report

Every example that makes decisions prints the same four blocks per tick (one tick = one event arriving). Here is what each line of the card above means.

Line Plain meaning
tick 2/3 event=low_risk The third of three arrivals. The event is what happened, named the way the policy names it.
why The example's one-line story for this tick. Not computed; written by the example author.
INPUT … cursor Whether a saved state was restored. "Cursor" is the demo's word for state. "Starts fresh" means the module was reset before this event.
Event low_risk {…} The event name and its payload. Only the name moves the policy; the payload is recorded but not read.
evidence FLICKER, 5 observations The example's label for this evidence (STRONG, THIN, FLICKER, …) and how many related cases there are (n).
neighborhood d=1.6 λ=0 strength dλ²=0 (need > 1.05) d is the average number of links per related case; λ is neighbour agreement; dλ² is evidence strength. It must exceed 1 + margin = 1.05. KS is the Kesten–Stigum line at 1.
density p̂=0.4 flicker band p*=0.15 p̂ is how densely the related cases are linked (0 to 1); p* is your cut-off (p_threshold). FLICKERING means p̂ is inside the knife-edge window around the cut-off. With only 5 cases that window covers every density, so this check cannot pass.
conformal label=0.12 bag=40 p=1 vs α=0.10 label is this case's unusualness score; bag is the size of the calibration set; p is the share of calibration cases at least as unusual (with a +1 correction). Passes when p > α.
support share=0.8 vs min=0.50 The share of related cases carrying the outcome the proposed action implies (here: 4 of the 5 similar visits were uneventful, as a discharge implies). Passes when the share is at least the minimum. Printed only when the evidence measures support.
NAIVE What a rule that acts on every proposal would do.
DECISION The six checks, each PASS or FAIL, with the question it asks. A failing check prints its reason underneath.
theorem width=3.26e+05, vacuous=True The width of the knife-edge window the published theorem would give at this n. Anything above 1 is vacuous (it covers every possible density), so the library uses a practical width instead and reports the theorem's only for the record.
→ ABSTAIN because … The verdict and why. Any failed check means abstain.
OUTPUT hold_for_human What the example's routing did with the verdict: execute, hold for a person, or wait for the next event.
proposed_action=discharge (blocked, kept on the record) The proposed action is always recorded, even when it is not carried out.
extras['state']=auto The state to save for this entity and send back with its next event.
NEXT What the example does before the next tick (here: nothing carried over; each patient is independent).
close … naive over-fires on 1 tick(s) Summary: how many proposals a naive rule would have carried out that the library held (blocked_overconfident_acts).

The printouts call the checks "channels" and the policy a "grammar"; the record field is channels. The words in the DECISION block (seed, KS, flicker window) are defined in the glossary.

Examples that do not call decide (router and compare examples such as facility_location or cache_admission) print the same four blocks over whatever stream they walk, and their JSON has an empty records list.

examples

uv run grounded-decision examples

Lists 68 ids in seven tracks (excerpt):

examples:
  clinical_triage                  [product ] Clinical triage
  fraud_review                     [product ] Fraud review
  ...
  hilbert_refuse                   [product ] Refuse complete rational root finding
  step_hello                       [step    ] Step: hello policy
  ...
  mkt_vol_term                     [markets ] Markets: VX vs SPX-option vol term
  games_tictactoe                  [games   ] Games: tic-tac-toe with exact advice and a gated rule-of-thumb player
  ...

run: uv run grounded-decision examples <id>
filter: uv run grounded-decision examples --track step
run all: uv run grounded-decision examples --all   (or --json)
Track Count What it teaches Page
product 18 One business problem each, naive vs grounded Example library
step 11 One feature at a time, smallest first Step by step
adaptive 8 Evidence changing over many ticks Adaptive loops
combo 6 Several modules or helpers together Combinations
live 9 Service desks with a ledger of side effects Live service desks
markets 11 Trading desks on a simulated market Market data desks
games 5 Board games with a gate over a move proposer Games

To run one:

uv run grounded-decision examples fraud_review

To run every example in a track and read every report:

uv run grounded-decision examples --track step --all

JSON output

uv run grounded-decision examples clinical_triage --json

prints one line: an object with id, title, records (each a DecisionRecord.to_dict()) and summary. The third record, trimmed to its top-level fields:

{
  "verdict": "abstain",
  "action": null,
  "proposed_action": "discharge",
  "reason": "d λ² = 0.0000 vs 1+0.050 (at or below KS; abstain); evidence density inside operational window (theorem width=3.26e+05, vacuous=True)",
  "module": "clinical_triage",
  "event": {"name": "low_risk", "payload": {"heart_rate_bpm": 88, "spo2_percent": 95}},
  "envelope": "p_exact",
  "extras": {"state": "auto"}
}

and the summary:

{"naive_acts": 3, "grounded_acts": 2, "grounded_abstains": 1, "blocked_overconfident_acts": 1, "naive_action": "discharge-or-escalate"}

Every field of a record is described on The decision record. For tests and dashboards, use the JSON (or run_example(id, as_json=True) from Python) rather than parsing the human report, whose wording may change.

From Python:

from grounded_decision.examples.catalog import list_examples, run_example

ids = [entry["id"] for entry in list_examples(track="step")]
result = run_example(
    "fraud_review", as_json=True
)  # {"id", "title", "records", "summary"}

audit

uv run grounded-decision audit tictactoe --no-regimes
== Audit: grounded [worst-case] playing O in tictactoe, seed 0 ==
Line-up: X (rule_of_thumb) vs O (grounded). Result: the board is full: a draw after 9 moves.
Oracle: the exact solver (perfect play by both sides). Regret unit: outcome classes under perfect play (win 1, draw 0, loss -1).

Decisions audited: 4
  total regret 0, mean 0 per decision
  agreed with the oracle: 4/4 (P(agrees with the oracle) ∈ [0.40, 1.00] (95%, 4 trials))
  gate overrides: 0

Per decision (regret; * = the gate overrode the proposal):
  0 0 0 0

No decision had positive regret: the agent matched the oracle.

Each decision's regret is how much worse the agent's move was than the oracle's, in the unit the second line names. Without --no-regimes the report ends with the game's information-regime table, explained on Seeing the future, assuming the worst, or betting on the odds.

Seeds and reproducibility

Every game is driven by a seed: snake's apples, checkers and Chinese checkers openings, and every agent's random choices.

  • play draws a fresh seed each run unless you pass --seed, so watching --agents random,random twice shows two different games. The seed is printed when the game ends (also after Ctrl-C), for example seed 482913 (replay this exact game with --seed 482913); pass it back to replay that game move for move.
  • compare, audit, examples and the tests use fixed seeds, because their purpose is identical conditions: the same command always prints the same numbers.

compare

play runs one match with one agent per seat: for tic-tac-toe, --agents perfect,random makes X perfect and O random for the whole game, and a single name fills every seat. It never switches strategies mid-game, and naming more agents than seats is an error that points here.

To ask which agent is better, use compare: every listed agent plays under the same conditions.

  • One-player games (snake): every agent plays every seed.
  • Two players: a round robin; each ordered pair plays every seed, so every agent meets every other from both seats.
  • More players (chinese_checkers --players N): every rotation of the line-up round the table, so each agent sits in every seat equally often.
uv run grounded-decision compare tictactoe --agents grounded,rule_of_thumb,random --seeds 3
Comparison: tictactoe, round robin, 3 seed(s); seats: X, O (assigned by the comparison); opening: 1 random ply per seed

  agent                    games  won drawn lost  cut  points    moves overrides  win rate (95% interval)
  grounded                    12    6     6    0    0     9.0      6.6         2  0.50 [0.21, 0.79]
  rule_of_thumb               12    6     6    0    0     9.0      6.6         0  0.50 [0.21, 0.79]
  random                      12    0     0   12    0     0.0      5.2         0  0.00 [0.00, 0.26]

points: win 1, draw or cut-off shares 1 among the seats, loss 0. moves: mean moves per game (all players).
Every ordered pair of different agents played every seed, so each agent met each opponent from both seats.
Openings (drawn by the seed, the same for every seating): 0: X takes square 4; 1: X takes square 6; 2: X takes square 1.

A win is 1 point, a draw or cut-off shares 1 among the seats, a loss is 0; in a one-player game, dying is a loss. Win rates carry exact Clopper–Pearson intervals, so three seeds say clearly how little they prove. Options: --seeds N, --first-seed N, --opening-plies N, --size WxH, --players N, --max-steps N, --json, every rule option of play (--stall-plies N, --clear-finished, --connect K, --mines N, --first-click, --preview N, --pieces N, --mode, --level N, --lock-delay TICKS, --gravity CURVE, --target TILE, --stop-at-target, --board, --start, --holes, --layout), and for games with a world --world MODE[,MODE...].

Openings: different games from a fixed start

Connect Four, checkers, Chinese checkers, Go, Hexxagon and tic-tac-toe start from the same position whatever the seed, so two players that choose their moves deterministically would play the same game on every seed: twenty seeds would be one game counted twenty times. compare therefore plays the first few moves of every game at random, drawn from the legal moves by the seed alone. Every seating of a seed starts from the same position, so each pairing still meets the same openings from both seats, and the openings are printed under the table (all of them, per seed, in --json's "openings"). A random move that would end the game is skipped while another exists.

Game Random opening plies (default)
tic-tac-toe 1 (every first move still draws with perfect play)
Connect Four, checkers, Chinese checkers, Go, Hexxagon 2
snake, Tetris, 2048, Minesweeper 0 (one player: the seed varies the game, or a deterministic world such as snake's nearest is the thing compared)

--opening-plies N overrides the default (0 turns the openings off). A game module sets its own default with COMPARE_OPENING_PLIES; without one, a game of two or more players whose start is the same for seeds 0 and 1 gets 2. Whatever the setting, compare counts the games that repeat an earlier seed's game move for move (same seating) and says so under the table, for example Repeated: 4 of 6 games replayed an earlier seed's game move for move, so they add no information; vary the start with --opening-plies N. ("repeated_games" in --json).

Worlds: the same agents against a different game

An agent's information regime is what it assumes about the future; a game's world is what the game does. --world changes the world and leaves the agents alone, so the same comparison can be run against a fair, a kind or a hostile game. Snake is the first game with worlds; play --list shows every game that has them. With several worlds, compare prints one table per world:

uv run grounded-decision compare snake --agents grounded,cycle --size 6x6 --seeds 3 --world farthest:0.3
Comparison: snake, solo, 3 seed(s); seats: snake (assigned by the comparison); world: farthest:0.3

  agent                    games  won drawn lost  cut  points    moves    score overrides  win rate (95% interval)
  grounded                     3    3     0    0    0     3.0    289.3     36.0       209  1.00 [0.29, 1.00]
  cycle                        3    3     0    0    0     3.0    374.0     36.0         0  1.00 [0.29, 1.00]

points: win 1, draw or cut-off shares 1 among the seats, loss 0. moves: mean moves per game (all players).

Two rules hold for every game: a clairvoyant agent is refused (exit 2) in a world whose future depends on the agent's own moves, and when a probabilistic agent plays in any world but random, the table ends with Note: assumption not met: …, because its statements assume random. The world is also named in the JSON output ("world"), in the --view none summary, and in the live panel when it is not random.

record and replay-info

record plays a match of agents to its end and saves every frame as a .gdr replay, which the web showcase plays back without Python; replay-info prints a replay's header and checks it:

uv run grounded-decision record connect4 --agents gated,search --seed 1 -o connect4.gdr
uv run grounded-decision replay-info connect4.gdr --verify

record takes play's match options and refuses what play refuses (exit 2); every seat must be an agent. replay-info --verify also records the header's spec again and checks the frames are identical; it exits 3 when a check fails. --bytes shows where the replay's bytes go: each part of the frame, the keyframes and the transitions, compressed on their own. The format, its sizes and the curated gallery (make replays) are on Replay format.

interfaces

uv run grounded-decision interfaces

Prints interface_map() as indented JSON with these top-level keys: version, decide (inputs, output, verdicts, the six checks), builders, router_subgoals, compare, examples (every id with its track and title), tracks and integrate. Excerpt:

{
  "version": "0.1.0",
  "decide": {
    "input": ["Event(name, payload)", "EvidenceSnapshot(...)"],
    "output": "DecisionRecord.to_dict()",
    "verdicts": ["act", "abstain", "degrade"],
    "channels": [
      "certificate (star-height ≤ 3, Lean 134)",
      "game (parity + mean-payoff, Lean 104; Truffet banned)",
      "reconstruction (d λ² > 1+margin, Lean 229 supercritical)",
      "threshold (Friedgut–Kalai window, Lean 186; operational prefactor)",
      "conformal (split conformal, operational)",
      "support (share of related cases agreeing with the proposed action ≥ min_support, operational)"
    ]
  },
  "router_subgoals": ["max_cut", "vertex_cover", "k_median", "wl", "rational_zeros"],
  ...
}

The numbers after "Lean" are result numbers in the companion math catalog. Use this output when another team or tool needs a description of the interface without importing the package.

What the command line will not do

  • Decide on your own data. Call Module.decide from Python, or run the reference HTTP service with make service (Build a decision service).
  • Read a policy from the shell. Use parse() in Python.
  • Change Limits. Pass limits=Limits(...) to Module in code (Defaults and limits).
  • Build the documentation (make docs) or run Lean (nothing in this package runs Lean).