Skip to content

Stability and versioning

If you call this library from a service, you need to know which names you can import safely, what the stored decision record promises, and which upgrades might break you. This page is that contract. It lists the public names grouped by task, the record format, and the rules for what counts as a breaking change. It assumes you have read Your first decision.

Current version: 0.1.0 (grounded-decision --version), Apache-2.0, Python 3.12 or newer. The project follows semantic versioning; every change is in the changelog.

The web showcase's session protocol (messages between a front end and the engine) has its own semantic version and compatibility rules, checked by a test: see Session protocol.

What is public

Public means: the names in grounded_decision.__all__, the four functions in grounded_decision.examples.catalog, the grounded-decision command, and the record format below. Everything else can change in any release.

Names you can import from grounded_decision

This is the complete __all__ (42 names), grouped by what you use them for.

Task Names
Write a policy parse, evt, act, seq, star, union, Statechart
Check a policy compile_expr, star_height, StarHeightError
Build and run a decision Module, Event, EvidenceSnapshot, DecisionRecord, Verdict, Envelope
Build evidence snapshot_from_graph, reconstruction_report, threshold_report
Combine policies Pipeline, compose_parent
Serve, replay and simulate handle_request, DecisionSession, EvidencePrior, Volatility, draw_snapshot
Learn settings from history learn_parameters, RiskTarget, HistoricalCase, LearnedParameters, RiskMonitor
Change caps and defaults Limits, DEFAULT_LIMITS
Hard side questions (router) Subgoal, dispatch
Side analyses (compare) compare_outcome_laws, sample_two_row, best_coordinate, single_item_one_sample, single_item_max_of_samples
Published constants gw_alpha, k_median_threshold

Also available: grounded_decision.__version__ (read from the installed package metadata; 0.0.0+unknown when running from a source tree that was never installed) and grounded_decision.main (the command-line entry point).

Signatures and docstrings for each: API reference.

Other supported imports

  • grounded_decision.examples.catalog: list_examples, run_example, run_all, interface_map.
  • Types that appear inside public return values, imported from their submodule to read or construct them: ChannelReport, Health, CatalogRef, ChannelName, Trust (grounded_decision.types), RouterResult (grounded_decision.router.dispatch). Their fields may grow in a minor release.
  • ParseError and CompileError from grounded_decision.grammar, so you can catch them.
  • Everything in grounded_decision.learn.__all__ (grid, risk measures, monitor status, exact binomial statistics, case_arrays and predicted_acts).

Not public

  • Any name starting with _, including Module._state (use Module.state and Module.restore).
  • grounded_decision.examples.common and the individual example modules, including reference_service.py. Copy them; do not import them from production code.
  • Submodule functions not listed above. They are documented in the API pages for contributors and can move.

The decision record format

DecisionRecord.to_dict() is the format to store and send between services; DecisionRecord.from_dict() reads it back. Event, EvidenceSnapshot, Health, ChannelReport and CatalogRef have the same pair. handle_request(module, body) accepts {"state": ..., "event": {...}, "evidence": {...}} and returns the same dictionary.

A real record, the held third patient from uv run grounded-decision examples clinical_triage --json, with each check trimmed to four fields and health shown in full:

{
  "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}},
  "channels": [
    {"name": "certificate", "passed": true, "trust": "lean", "summary": "star-height 0 ≤ 3; 4 states ≤ 256"},
    {"name": "game", "passed": true, "trust": "lean", "summary": "parity_win=True nonnegative_mean=True"},
    {"name": "reconstruction", "passed": false, "trust": "paper", "summary": "d λ² = 0.0000 vs 1+0.050 (at or below KS; abstain)"},
    {"name": "threshold", "passed": false, "trust": "operational", "summary": "evidence density inside operational window (theorem width=3.26e+05, vacuous=True)"},
    {"name": "conformal", "passed": true, "trust": "operational", "summary": "conformal p=1.000 vs α=0.100 (cover)"},
    {"name": "support", "passed": true, "trust": "operational", "summary": "support 0.80 vs minimum 0.50 (supports the proposed action)"}
  ],
  "health": {
    "d_lambda_sq": 0.0,
    "reconstruction_margin": 0.05,
    "p_hat": 0.4,
    "p_threshold": 0.15,
    "threshold_width_theorem": 325758.4501703898,
    "threshold_width_operational": 1.2426698691192237,
    "in_theorem_window": false,
    "in_operational_window": true,
    "theorem_window_vacuous": true,
    "conformal_p": 1.0,
    "star_height": 0,
    "game_solved": true,
    "mixer_status": "idle",
    "support": 0.8
  },
  "envelope": "p_exact",
  "extras": {"state": "auto"}
}

What the format promises in 0.x:

  • The top-level keys above are always present.
  • channels always has six entries, in this order: certificate, game, reconstruction, threshold, conformal, support.
  • from_dict reads records and evidence written before the support check existed (five channels, no support key in health or the evidence) without error; the missing values read as None.
  • verdict is one of act, abstain, degrade; trust is one of lean, paper, operational; envelope is one of p_exact, fpras, ugc_opt, undec, exptime_trap.
  • extras["state"] is the state to save. Treat any other extras keys as opaque.
  • The wording of reason and summary may change in any release. Do not parse them; use passed, metrics and health.
  • metrics and health may gain keys. Do not assume mixer_status stays "idle".

Field meanings: The decision record.

Versioning policy

The project follows semantic versioning:

  • Before 1.0 (now), a minor release (0.1 → 0.2) may change behaviour, including the breaking changes in the table below. Every such change is listed in the changelog under "Changed" or "Removed", with migration notes saying what to do. A patch release (0.1.0 → 0.1.1) only fixes bugs.
  • From 1.0, a breaking change bumps the major version.
  • Renamed public names keep the old name as an alias for one minor release when that is cheap to do, and the changelog says so. When it is not (for example a changed record format), the migration note says how to convert.

What counts as a breaking change

Change Breaking?
Removing or renaming a name in __all__ or examples.catalog Yes
Adding, removing or reordering a check in channels Yes
Rejecting a policy that used to compile (for example an ambiguous one) Yes
Removing or renaming a Verdict, Envelope, ChannelName or Trust value Yes
Removing a top-level key from to_dict() Yes
Making a default in Limits stricter (lower cap, higher margin, higher α) Yes: decisions that used to act may now hold
A policy text that used to parse now failing, or parsing to a different policy Yes
Renumbering the states a given policy text compiles to Yes: saved states would change meaning
Adding a name to __all__, a key to metrics, health or to_dict() No
Making a default in Limits looser No
Adding an example id, or a router subgoal No
Changing the wording of reason, summary or printed reports No
Rejecting input that was previously accepted by mistake (a bug fix) No, but listed under "Fixed"

Upgrading safely

  1. Read the changelog entries between your version and the new one.
  2. Replay recorded events through the new version and compare verdicts with the old one before switching traffic (Roll out safely).
  3. If you store states, compare policy fingerprints after the upgrade; the reference service refuses mismatched states with HTTP 409 (State and policy versions).

Machine-readable surface

uv run grounded-decision interfaces prints interface_map(): the decide inputs, verdicts, the six checks, builders, router subgoals, compare helpers and every example id. Use it when another tool needs the surface without importing the package. Output and key list: Command line.

Typing

The package ships py.typed, so type checkers use its annotations. The types are frozen dataclasses; there is no runtime validation layer, so check inputs at your service boundary.