🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

srdcheck

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

srdcheck

Deterministic rules verdicts for game-running agents — cited, reproducible, honest about jurisdiction. SRD 5.2.1 is adapter #1.

pipPyPI
Version
0.9.0
Weekly downloads
480
42.86%
Maintainers
1
Weekly downloads
 

srdcheck

A shared rules rail for agents: named, cited checks with explicit boundaries, so every caller can use the same deterministic result without mistaking a partial check for global legality.

Machine verdicts over specific rules of the System Reference Document 5.2.1: cited when a rule is applied, reproducible, offline, and honest enough to refuse questions outside the shipped scope. The rules lawyer for agents — including an agent acting as the authorized DM.

Status: v0.9.0 alpha — young but real, building in the open. The kill tests that shaped the product — including the one that killed half our original idea — are in eval/RESULTS-phase0.md; the with/without-rails demo is in demo/mage-hand/; the truth scorecard below is generated by CI.

Runtime and operating-system guarantees are defined in the support matrix: Python 3.10–3.13, a full Ubuntu suite, and explicit installed-artifact smoke contracts on Windows and macOS.

Why this exists

A game increasingly runs not as one model but as several agents sharing one evolving state — a GM, player agents, a rules referee, tool-callers. The moment more than one mind adjudicates, the binding problem stops being any single agent's knowledge or attention budget and becomes consistency: five capable models will each read the SRD and rule on grapple, concentration, or a death save a little differently, and the shared game state quietly desyncs. No amount of individual intelligence guarantees they agree with each other, and every re-derivation is another chance to drift. The fix is a deterministic rail they all call — the same atoms, the same citations, the same verdict every time — so every applied-rule result carries one provenance any agent (or a human) can audit. Table rulings retain separate ruling lineage. That is where srdcheck stops being a convenience and becomes load-bearing.

Even at a table of one, the rail earns its place. A model running a game is a brilliant improviser with a finite attention budget: every mechanical micro-check it handles in-context — is this legal, is that slot spent, does the reaction refresh this round — spends tokens and attention that belong to the only work that needs a mind, the story. And the checks a model can answer, it cannot prove, cannot reproduce, and — as our own benchmark showed — will not refuse when the question is outside the rules' jurisdiction.

We tested this before building. Frontier models answered our SRD rules questions nearly perfectly — and confidently ruled on house rules, GM discretion, and content that doesn't exist in the SRD, where the only correct answer is "not my call." Small local models got 19–30% wrong with zero refusals. So srdcheck does not compete with what models know. It is a rail: structured facts in, a deterministic scoped result out, with citations whenever it applies a rule — and the same rail for every agent at the table.

What it is

srdcheck answers named, bounded questions such as: does this proposal pass the modeled action-economy check? and which modeled budget option kinds remain? See the generated capability and claim map for the canonical checked and unchecked scope of every tool.

  • Verdicts, not vibes. Exit code 0 = passes the checked scope, 1 = conflicts with the checked scope, 2 = cannot adjudicate. Every applied rule carries its SRD citation; boundary refusals do not invent citations.

  • Scope and provenance travel with the verdict. Verdict schema 4.0 keeps coverage_level, checked_scope, unchecked_scope, and assumptions on every result and separately reports asserted/consumed/derived/missing facts, the advisory rule_result, any scoped DM table_decision, and explicit state_mutation. Every legal explanation says it is legal only within that checked scope.

  • Judge, never simulate. No dice, no narration, no owned game state. State comes in with the query; a verdict goes out. The kernel is a stateless pure function — embeddable in anyone's DM product, VTT, or agent.

  • State commits fail closed. event.apply emits an idempotent transition proposal bound to the exact evaluated state; transition.commit rejects a stale/out-of-order proposal and returns the same semantic receipt on retry. The agent-DM or host still performs the atomic state write. See safe state transitions.

  • Agent-DM rulings persist without hidden state. Supply a portable table-policy manifest and request/encounter/session/campaign IDs to existing query tools. Matching rulings apply automatically, remain distinct from the SRD result, and carry explicit table-authority lineage. See table policies.

  • Deterministic and fast. No LLM call anywhere in the verdict path. Runs local and offline.

  • Observable without recording the table. Opt-in metadata-only lifecycle events carry request/verdict IDs, versions, refusal class, and timing on stderr or a caller sink. Raw prompts, state, rulings, citations, and error messages are excluded by contract; SRDCheck persists nothing.

  • For agents first. MCP + CLI, --pipe, --schema, tool.json at the repo root. Humans get plain-English why in the same payload; agents treat that prose as explanatory, not contractual.

  • Rulesets are adapters. The kernel knows no game; all rule content loads from adapter packages, each carrying its own provenance manifest — source document, hash, license, attribution — that every applied-rule result cites through. The SRD 5.2.1 adapter ships in this repo as the reference implementation. Anyone can build an adapter for another ruleset — a community, a private table, or a publisher shipping a first-party adapter for their own IP — and their content never passes through this project. The adapter catalog points; it never hosts.

See docs/product-truths.md for the invariants this project holds itself to, docs/anatomy-of-a-turn.md for executable-today versus target examples, and docs/compatibility-policy.md for the N/N-1 machine contract and correction policy.

Show your work. A big context window can read the SRD; srdcheck's value is the layer above — a consistent, cited set of rule atoms and a shared schema that several agents can rely on without each re-deriving the rules into context and drifting apart. docs/atom-concordance.md is the audit trail for that layer: every rule the engine applies, mapped to the exact SRD text it's grounded in (forward and inverse), generated and CI-checked-fresh, with a verbatim-citation oracle proving each quote really is on its cited page. A machine-readable atom-concordance.json is the slim index an agent can load to know what the rail covers and where it came from.

What srdcheck does not check yet

Honesty is the product (truth T8), so the boundaries are stated, not implied. The current SRD adapter covers combat-turn fundamentals; it does not yet check:

  • Feature prerequisites. turn.plan judges the turn's action economy — that you spent at most one action, one bonus action, one reaction, one spell slot. It does not verify that a feature actually grants a given action. A lone two-weapon-fighting offhand attack is "action-economy legal" even though the 2024 rules require the Attack action first; that prerequisite lives in the character's features, which this version doesn't model. The success message says so explicitly.
  • Per-spell effects. Damage/HP, death saves, and saving throws are now modeled (the reducer folds damage/heal/death-save; save.check and concentration.check resolve caller-rolled saves), but what a given spell does — Fireball's 8d6, Hold Person's paralysis on a failed save — is the long-tail swamp and stays refused. srdcheck computes the save; it does not roll the dice or apply the spell's bespoke effect.
  • Condition coverage is surface-specific. All 15 SRD conditions are registry-anchored and classified clause by clause, while each query claims only the effects it actually consumes: attack modifiers, modeled action economy and Speed, selected save/check effects, damage typing, immunity, and Reaction blocking through Incapacitated and its definitional embeds. The completeness oracle proves its named attack/turn surfaces; it does not imply that every condition clause is checked by every tool. Positional geometry, initiative order, and stealth/perception contests remain explicit boundaries.
  • Content outside the SRD 5.2.1 — anything not in the SRD returns exit 2 from jurisdiction; in adapter 0.2.1 the name registries cover spells, creatures, conditions, classes, subclasses, species, feats, and backgrounds (census-anchored against the source text in CI). Background coverage is jurisdiction-only: it recognizes the four backgrounds actually present in SRD 5.2.1 (Acolyte, Criminal, Sage, and Soldier) but does not yet expose their mechanics as structured facts.
  • Misspelled or unknown query fields refuse loudly (adapter 0.2.1): every query validates its params against the declared schema — an unrecognized field returns exit 2 naming the offender, never a silently-defaulted verdict. A drift oracle in CI keeps handlers and schemas from ever diverging again.
  • Facts are requested only while they can change the result. A granted Mage Hand use refuses with missing-fact until its relevant distance and, when it carries or manipulates an object, weight are supplied. A prohibition or an already-exceeded limit is decisive and does not trigger an irrelevant repair loop. A turn.plan move requires explicit feet; omission is never treated as a zero-foot move.

Nonsensical inputs (negative Speed, a 99th-level spell, exhaustion past 6) return exit 2 rather than a confident-looking answer. When in doubt, srdcheck refuses — a wrong verdict is the only unforgivable bug.

Try it now

$ pip install git+https://github.com/chaoz23/srdcheck
$ python -m srdcheck jurisdiction "Fireball"          # exit 0 — known content
$ python -m srdcheck jurisdiction "Hexblade"          # exit 2 — not in the SRD, honestly refused
$ python -m srdcheck query mage-hand.use '{"kind": "attack"}'
{
  "verdict": "illegal",
  "exit_code": 1,
  "why": "The hand can't attack.",
  "citations": [{"section": "SRD 5.2.1 p.145 'Spells > Mage Hand'", "page": 145,
                 "quote": "The hand can't attack"}],
  "rule_ids": ["mage-hand.cant-attack"],
  "adapter": "srd-5.2.1@0.2.1",
  "coverage_level": "rule-surface-complete",
  "checked_scope": ["listed uses and prohibitions", "10-pound limit", "30-foot range for the proposal"],
  "unchecked_scope": ["fine-manipulation ambiguity", "duration or range trigger tracking across events", "narrative consequences"],
  "assumptions": ["the supplied use kind describes the proposal", "supplied weight and distance facts are accurate when relevant to the result"]
}
$ python -m srdcheck query mage-hand.use '{"kind": "attack"}' --table-evaluation
# deterministic table.evaluation/1.0; status=findings, authority=self_attested
$ python -m srdcheck query save.check '{"modifier":2,"dc":10,"d20_result":12,"save_ability":"dex","saver_conditions":[]}' \
    --table-evaluation --table-context '{"session_id":"session-7","correlation_id":"discord-42"}'
$ python -m srdcheck cite "Command"                    # verbatim SRD text + page for any registered name
$ python -m srdcheck --schema                          # I/O contract for agents
$ python -m srdcheck capabilities                      # engine, protocol, adapters, digests, tools

Deterministic, offline, and no tokens. CI gates 100-verdict p95 latency below 100 ms; lower local observations are not a compatibility promise. The query surface is young and growing slice by slice — the architecture (kernel + adapters, spec at v0.9 RC) is the point.

query and --pipe accept --table-evaluation for portfolio hosts that need the shared table.evaluation/1.0 envelope. Legal/illegal verdicts become complete checked-clean/finding results only within the named query scope; missing facts, unsupported content, ambiguity, and DM discretion remain non-clean. The subject carries a machine-readable srdcheck-query-scope:* reference; optional --table-context JSON can add caller-owned session_id, entity_refs, and a correlation_id (emitted as correlation:*). Rule and citation evidence is self-contained and keyed by every finding evidence ref. Every result is self_attested, including when the calling agent is the DM: this adapter cannot grant table, encounter-state, action, or protected-host authority.

For a live agent-DM host, use the Discord message or resolved-intent event snowflake as the privacy-safe request join key:

python -m srdcheck query mage-hand.use '{"kind":"attack"}' \
  --trace --request-id 123456789012345678

The verdict stays on stdout; canonical metadata-only NDJSON goes to stderr. See privacy-safe observability for the event schema, MCP configuration, retention boundary, and residual metadata risk.

For agents (MCP)

srdcheck is an MCP server with zero dependencies — stdlib only. After pip install, the command is srdcheck-mcp; from a clone it's:

{
  "mcpServers": {
    "srdcheck": {
      "command": "python3",
      "args": ["-m", "srdcheck.mcp"],
      "cwd": "/path/to/srdcheck"
    }
  }
}

The complete tool inventory and each tool's checked/unchecked scope are generated from runtime metadata in the capability map; CI rejects stale or incomplete inventory. Every call returns the same verdict object as the CLI as structured content. Applied-rule results carry their source citations; boundary refusals do not invent provenance. An illegal verdict is a result, not an error; cannot-adjudicate is an honest refusal, not a failure. Tool descriptions and schemas come from loaded adapters, so new adapters extend the tool list without kernel changes. See also tool.json for the CLI surface. The opt-in table_evaluation MCP tool accepts {query_type, params, context?} and emits the same shared envelope without changing native query-tool responses.

Every first-party cannot-adjudicate result carries stable recovery metadata under data: a reason code, recoverability, exact missing-input paths, and a suggested next action. That tells an agent whether to repair the request, provide facts, select an adapter, use another capability, resolve a table ruling, reconcile stale state, or stop—without parsing why. When DM authority is required, an authorized agent-DM can rule directly; another caller can escalate. See the machine-actionable refusal contract.

As a Python library

Consume a ruleset's content through a stable, versioned interface — no coupling to internal file paths:

from srdcheck import load_adapter, available_adapters

available_adapters()               # ['srd-5.1', 'srd-5.2.1', 'toy-tictactoe']
a = load_adapter("srd-5.2.1")      # a versioned identifier
a.categories()                     # the content categories this adapter carries
a.names("creature")                # every creature name (326, complete from the stat blocks)
a.record("creature", "Ghast")      # {"name": "Ghast", "cr": "2", "xp": 450, "citation": "SRD 5.2.1 p.287"}
a.query("encounter.xp-budget", {"level": 3, "difficulty": "moderate"})  # a verdict dict

Cross-version edition-trap detection — catch a name that was valid in an older edition but renamed/removed in the current one (the classic LLM failure, e.g. "Goblin" in 2014 → "Goblin Warrior" in 2024):

from srdcheck import edition_check
edition_check("Goblin", "creature")          # exit 1 — a srd-5.1 name, not in 5.2.1;
                                             #   data.candidates_in_current suggests Goblin Warrior/Minion
edition_check("Aboleth", "creature")         # exit 0 — valid in both
# or from the CLI: srdcheck edition-check "Goblin" --category creature

Adapter identifiers carry their version, so load_adapter("srd-5.2.1") and load_adapter("srd-5.1") coexist without a breaking change; edition_check is caller-parameterized (current / priors), so it generalizes to any versions and any category. The handle is content-neutral: it knows about categories and records, never about a specific ruleset's vocabulary.

The benchmark

bench/ is the rules-fidelity referee: versioned question sets with SRD-cited gold verdicts, a harness that scores any model or agent (gemini:, ollama:, or cmd:your-agent on stdin/stdout), and a generated scorecard that reports wrong-rate, refusal-rate, and false-confidence separately, per category, with no aggregate number — ever. A generated leaderboard ranks subjects by wrong-count alone (the one unforgivable failure), and any third party can submit a tamper-checked result — you can't grade your own homework, because the golds are the set's. Published findings: frontier models ace codified rules and fail by false confidence exactly where the rules end — and, on a per-horizon drift lane whose gold verdicts are derived by the engine itself, they don't drift even at 30-round horizons, while a local 8B model's errors are mistakes of rule-knowledge, not memory. Benchmark your own DM product with one command.

Truth scorecard

Every tagged release publishes a scorecard against the product truths — generated by CI, never hand-edited, no aggregate score.

Generated by scripts/truth_scorecard.py — regenerated and diff-checked in CI, never hand-edited. Statuses are honest: structural and held in review mean exactly that.

truthclaimstatusevidence
T1wrong verdictsenforced in CI709 tests including gold suites ported from the Phase 0 eval; any wrong verdict fails the build
T2no citation, no ruleenforced in CI90/90 rule atoms carry verbatim source quotes; applied-rule paths cite, while boundary refusals do not invent provenance
T3advise, never overrulestructuralthe API has no blocking or veto interface to wire; verdicts are advisory by construction
T4one payload, two audiencesenforced in CIevery verdict carries machine fields plus a templated plain-English why; schema-tested
T5enumeration is the directionproven for shipped slicesconsistency sweeps (50 turn states + toy boards) verify enumerate/validate agreement in both directions on every push; creature-complete enumeration remains a target
T6judge, never simulateenforced in CIdeterminism test plus a purity lint: no randomness anywhere in the kernel, no network or subprocess in the verdict path
T7mechanism never knows the gameenforced in CIkernel lint scans every kernel module for game vocabulary (it caught a real violation during development)
T8honest boundariesenforced in CIrefusal goldens: unknown content, unmodeled conditions, and genuinely ambiguous rules text all return exit 2; ambiguity cites its source, while registry and input boundaries may have no citation
T9never a single numberenforced in CIbench scorecard freshness test; per-category tables, no aggregate score exists anywhere in this repository — even the leaderboard ranks by wrong-count alone (T1) and shows the other failure modes unblended
T10stranger-agent bootstrapenforced in CIcold-start conformance test reaches a first verdict from tool.json/--schema/MCP alone (24 tools); live probe: a frontier model given only tool.json produced a correct first verdict in 1 attempt(s), 3.9s (2026-07-16)
T11table speedenforced in CIp95 latency budget test: 100 verdicts must stay under 100 ms at p95; lower local observations are not a compatibility promise
T12never sell what the model hasheld in reviewa strategy invariant: features pitched on knowledge parity are cut in review — enforced by humans and admitted as such
T13the benchmark is a productshippedbench/ publishes 5 sets across 5 subjects with cited gold verdicts, incl. a per-horizon drift lane whose golds are engine-derived; a generated LEADERBOARD ranks any subject; cmd: driver + validate command let a third party submit a tamper-checked result
T14every derived state has a lineageenforced in CIevent.apply stamps every successful derived transition (predecessor hash, causing event, rule ids, rule-vs-ruling kind); tests cover replay verification, tamper detection, the schema minimality ratchet, and reducer/validator agreement; demo replays 15 rounds hash-for-hash

Licensing

  • Code: MIT.
  • data/: includes material derived from the System Reference Document 5.2.1 under CC-BY-4.0 — see srdcheck/adapters/srd-5.2.1/sources/README.md for provenance and the required attribution.
  • srdcheck is unofficial and is not affiliated with or endorsed by Wizards of the Coast.

vs alternatives

  • Just asking the LLM — frontier models know these rules nearly perfectly (we measured it and killed half our own idea). What they can't do by construction: prove a verdict, reproduce it, or refuse when the question is outside the rules' jurisdiction — they claimed rules authority in the discretion zone in 5 of 6 demo runs. srdcheck sells proof, refusal, determinism, and economy — never knowledge.
  • Lookup APIs and reference MCP servers (Open5e, 5e-srd-api and their wrappers) — they answer "what does the book say," not "is this legal given this state." Complementary, not competing; srdcheck cites the same text they serve.
  • Closed engines inside AI-DM products — the strongest products in this vertical run deterministic rules layers, each rebuilt privately, uncited, unbenchmarked, and locked to their platform. srdcheck is that layer open, cited, benchmarked, and embeddable — including in theirs.
  • Simulator libraries (combat engines, character libraries) — they run games and own state. srdcheck judges and owns nothing (state travels with the query, stamped with lineage), which is exactly what makes it embeddable anywhere.

Prior art

srdcheck stands on lessons from Temple of Elemental Evil / Temple+ (dispatcher architecture), PCGen (prerequisite predicates), the FoundryVTT PF2e system (rules as data), Datasworn (official rules-as-JSON precedent), and FIREBALL (structured play state). Patterns were studied; no code was taken from any of them.

mcp-name: io.github.chaoz23/srdcheck

Formula blanks (v0.3)

If a rule is a formula, the verdict is the formula — a number appears only when the caller supplied every blank. Once the required size/free-hand prerequisite facts are supplied, grapple.initiate without str_modifier + proficiency_bonus returns dc_formula and no dc; passive.perception without perception_modifier returns score_formula and no score. Origin: a live table where the base-8 rendered as "DC 8" when the true DC was 13. srdcheck stays lookup-pure: the formula is the rule text, filling caller-supplied blanks is arithmetic — deriving the blanks from a sheet is charactercheck's lane, never this one.

Spell & feature facts (v0.4)

srdcheck query spell.facts '{"name": "Protection from Evil and Good", "cast_at": 1000}'
srdcheck query feature.uses '{"feature": "divine-sense", "charisma_modifier": 4}'

Casting time, range, components, duration, concentration — the facts a DM misremembers at 2am — as cited verdicts, census-anchored over all 336 SRD spells (the build fails if any spell parses incompletely). cast_at adds expires_at as pure arithmetic on the caller's own clock: srdcheck holds no state and no clock. feature.uses covers the SRD's use-count formulas under the formula-blanks discipline. Origin: a 10-minute ward narrated as active for seventy minutes, caught by the human player.

Writing your own adapter (v0.5)

The kernel knows no game — and now the kit proves it. Three bundled adapters span the tiers (registry-only srd-5.1, full srd-5.2.1, tutorial toy-tictactoe):

srdcheck new-adapter my-ruleset     # conformant skeleton
srdcheck conformance <adapter-id>   # the bar every adapter must clear

Conformance is the entry ticket: provenance manifest with a hash-pinned source, schema-declared inputs that refuse unknown keys, crash-free honest refusal, census-anchored registries. Golden-verdict pinning (scripts/build_golden.py --check, in CI) guarantees kit refactors are byte-invisible to existing consumers. Guide: docs/ADAPTER-GUIDE.md.

Keywords

srd

FAQs

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts