Skip to content

Defaults and limits

Every number the library uses to decide "this is too big" or "this is not enough evidence" has a default, and you can change any of them for one module. This page lists each one with its meaning, its unit, what happens when it is exceeded, and when you would change it. It assumes you have made one decision already (Your first decision); words in italics on first use are in the glossary.

Defaults are starting points, not tuned values

The four data-check settings (reconstruction_margin, operational_fk_prefactor, conformal_alpha, min_support) and the calibration set carry no error rate as shipped: they make the examples run. In production, learn them from your labelled history; the learned values replace these defaults and come with a certified error rate and a provenance record (Learn parameters from your history). On a synthetic history, the defaults acted with 7.94% of acts wrong where settings learned for a 5% target had 0.93% (Where every number comes from). The size caps below are engineering budgets and are not learned.

Every value on this page was read from the installed package with:

import dataclasses
from grounded_decision import DEFAULT_LIMITS

print(dataclasses.asdict(DEFAULT_LIMITS))

How to change a value

All size caps and check thresholds live in one frozen object, Limits. Build a new one with the fields you want to change and pass it when you build the module. Everything you do not name keeps its default.

from grounded_decision import Limits, Module, parse

strict = Limits(conformal_alpha=0.05, require_conformal_calibration=True)
module = Module.from_expr(
    "fraud", parse("test(suspicious) ; act(block)"), limits=strict
)

Pass the same object to dispatch(subgoal, limits=strict) if you use the router, so its caps match. The values a decision actually used are recorded on it: channels[].metrics contains margin, alpha and min_support, so an auditor can see which settings produced a verdict.

The Limits fields

Thresholds the checks use on every decision

Field Default Unit Meaning When to change
reconstruction_margin 0.05 evidence strength The neighbour-agreement check passes only when evidence strength (average links × agreement²) is above 1 + margin, so 1.05 by default. Learn it from history (grounded-decision learn). By hand: raise it (0.2, 0.5) when acting wrongly is expensive and you would rather hold more; lowering it toward 0 brings the bar to the theoretical line at 1. The learner's grid also tries -1.0, which leaves only a requirement of some agreement.
conformal_alpha 0.1 fraction (0 to 1) The unusual-case check passes when the conformal p-value is above α. Roughly: hold a case when fewer than 10% of recent good cases were at least as unusual. Learn it from history (grounded-decision learn; 0 switches the check off). By hand: lower it (0.05) to hold fewer cases as unusual; raise it (0.2) to hold more. See the worked example below.
require_conformal_calibration False yes/no When True, an empty calibration set fails the unusual-case check. When False (default), an empty set gives p = 1 and the check passes, so it is silently switched off. Set True in production once you supply calibration scores, so a broken feed holds decisions instead of passing them.
operational_fk_prefactor 2.0 dimensionless The constant in front of the practical knife-edge window width, \(2 \ln(1/(2\varepsilon)) / (\ln n)^2\). Larger means a wider window, so more decisions held near your cut-off. Learn it from history (grounded-decision learn; 0 switches the window off). By hand: raise it if decisions near p_threshold flip too often in replay; lower it if the window holds too much at your typical size.
friedgut_kalai_prefactor 524288.0 (\(2^{19}\)) dimensionless The constant from the published theorem. Used only to report the theorem's window width, which is vacuous (wider than 1) at every realistic size. Never changes a verdict. Never. It is there so the record shows what the theorem alone would give.
min_support 0.5 fraction (0 to 1) The support check passes when at least this share of the related cases carry the outcome the proposed action implies (EvidenceSnapshot.support). Learn it from history (grounded-decision learn; candidates 0 (off), 0.5 to 0.9). By hand: raise it when acting against a mixed neighbourhood is expensive.
require_support False yes/no When True, evidence without a support share fails the support check. When False (default), unmeasured support passes (support not measured). Set True once every decision's evidence measures support, so a broken mapping holds decisions instead of passing them.

Size caps checked when a module is built

These protect you from a policy that would take too long or too much memory to compile. Exceeding one either raises an error from Module.from_expr (so you find out at deploy time, not at decide time) or makes the progress check fail on every decision.

Field Default Unit What happens when exceeded When to change
max_star_height 3 levels of nested * StarHeightError at build time. Almost never. A policy with repeats nested four deep is very hard for a person to review; rewrite it instead.
max_nfa_states 512 automaton states CompileError("NFA has … states > 512") at build time. Raise it for a long, flat policy (hundreds of events) that is legitimately large.
max_dfa_states 256 automaton states CompileError("DFA exceeded 256 states") at build time. As above. For scale: a policy of 30 test(e) ; act(a) alternatives compiles to 32 states.
max_game_vertices 200 states in the policy chart The progress check cannot be solved; it fails, so every decision from this module abstains. No exception is raised. Raise it with max_dfa_states if you raise that above 200.
max_parity_depth 64 recursion levels Same as above: the progress check fails on every decision. Only if you hand-build a chart with more than 64 distinct priorities.
max_karp_vertices 200 states in the policy chart The mean-payoff solver raises GameTooLarge (… vertices exceed the mean-payoff limit 200); the progress check fails closed on every decision. With the defaults, max_game_vertices is reached first. Raise it together with max_game_vertices. Karp's table grows with the square of the number of states.

Caps used by the router and the compare helpers

Field Default Unit What happens when exceeded When to change
max_wl_k 2 Weisfeiler–Leman level k dispatch returns a refusal labelled exptime_trap; refine raises WLRejected. Keep it. Level k costs roughly \(n^{k+1}\) work, so this is a cost budget, not a hardness result.
max_table_cells_exact 64 table cells sample_two_row(..., limits=) raises ValueError: N cells exceed the exact-sampling limit 64; use sample_switch. Raise it if exact sampling of wider two-row tables is fast enough for you.
max_switch_steps 10000 chain steps sample_switch(..., steps=N, limits=) raises ValueError: N switch steps exceed the limit 10000 (the default steps is 2,000). Raise it if you need longer chains and can afford the time.

Performance budgets

These two fields are targets, not timers: nothing stops a slow call. tests/test_performance_budget.py checks them on the library itself in CI, by building the fraud_review module and requiring the median of 200 decide calls to be under decide_ms_budget and the median of 20 module builds to be under compile_ms_budget. The measured costs are far lower; see Limits and performance.

Field Default Unit Meaning
decide_ms_budget 50.0 milliseconds Target for one decide call. See FAQ: What's the latency?.
compile_ms_budget 200.0 milliseconds Target for building one module.

Other defaults

These are not in Limits; they are arguments or fields with default values.

Where Default Meaning When to change
EvidenceSnapshot.n_states, snapshot_from_graph(n_states=) 2 Number of label values a case can have (q). Used when turning raw agreement into neighbour agreement: \( \lambda = (q\,p_{\mathrm{same}} - 1)/(q-1) \). Set it to the real number of labels (for example 3 for low / medium / high). For 5 or more labels the 1.05 bar is a policy choice rather than a theorem; see reconstruction theory.
EvidenceSnapshot.scores () (empty) The calibration set. Empty means the unusual-case check passes with p = 1, unless require_conformal_calibration=True. Supply it in production, from learned parameters (learned.apply(evidence, proposed_action)). With α = 0.10 you need at least 9 scores before the check can ever fail.
EvidenceSnapshot.label_score 0.0 This case's unusualness score. 0.0 is the least unusual possible if your scores are non-negative, so the check passes. Always supply it when you supply scores.
EvidenceSnapshot.support, snapshot_from_graph(supporting_label=) None The support share; None means not measured, and the support check passes unless require_support=True. Supply it (pass supporting_label) once you have a mapping from actions to the outcomes they imply.
snapshot_from_graph(agreement_baseline=) "uniform" How neighbour agreement is measured: against equal label proportions, as the Kesten–Stigum result assumes. "label_mix" when one label dominates everywhere and links may be random; not for judging a homogeneous ring. See Do the related cases agree?.
threshold_report(epsilon=) 0.1 ε in the window formula: how sure the theory wants to be that the answer has settled. Module.decide always uses 0.1. Only when calling threshold_report yourself to explore.
Module.from_expr(envelope=) Envelope.P_EXACT The hardness label recorded on every decision. Does not change decide. Leave it unless your records need a different label.
Statechart().state(priority=, payoff=, environment=) 0, 0.0, False Inputs to the progress check. A chart left at priority 0 everywhere passes the priority part whatever its shape. Text policies get priority 2 on states with an action or no way out, 1 elsewhere. Mark loops the workflow must not circle in forever with odd priorities, and states someone else controls with environment=True. See Can the workflow keep going?.
compare_outcome_laws(tau=) none (required) Tolerance for "the distribution did not move". Choose per comparison; see Compare outcomes and features.

Defaults of the learner

These are the defaults of grounded_decision.learn (Learn parameters from your history).

Where Default Meaning
RiskTarget(max_risk=) none (required) The error rate you accept. A business input; --max-risk on the command line.
RiskTarget(confidence=) 0.95 How sure the certificate must be.
RiskTarget(measure=) error_rate_among_acts Or false_act_rate (share of all decisions that act and are wrong).
ParameterGrid.reconstruction_margins -1.0, 0.0, 0.05, 0.25, 0.5, 1.0, 2.0, 4.0 Candidate margins; -1.0 asks only for some agreement.
ParameterGrid.window_prefactors 0.0, 0.5, 1.0, 2.0, 4.0 Candidate window prefactors; 0.0 switches the knife-edge window off.
ParameterGrid.decision_lines None (each case's own p_threshold) --learn-decision-line adds 5 quantiles of observed density (grid ×6).
ParameterGrid.conformal_alphas 0.0, 0.02, 0.05, 0.1, 0.2 Candidate alphas; 0.0 switches the unusual-case check off.
ParameterGrid.minimum_supports 0.0, 0.5, 0.6, 0.7, 0.8, 0.9 Candidate support minimums; 0.0 switches the support check off.
ParameterGrid().size 1200 Combinations tried (8 × 5 × 1 × 5 × 6).
learn_parameters(procedure=) "fixed_sequence" Test the combinations in an order fixed from the calibration split, each at the full significance, stopping at the first failure. "bonferroni" divides the significance by the grid size instead. --procedure on the command line.
learn_parameters(calibration_fraction=) 0.3 Share of history used to build calibration scores.
learn_parameters(holdout_fraction=) 0.2 Share kept aside for the honest report; the remaining 0.5 is the risk split.
learn_parameters(seed=) 0 Seed for the random split; recorded in the provenance.
Settings when nothing is certified margin 1e12, prefactor 2.0, alpha 0.1, support 0.5 Holds every decision (fails closed); exit code 2 on the command line.
RiskMonitor(window=) 500 Recent outcomes kept for the live check.
make learn variables HISTORY=history.jsonl, MAX_RISK=0.02, PARAMETERS=parameters.json Input file, target and output file.

The knife-edge window at different sizes

The knife-edge check holds a decision when the link density \( \hat p \) is within a half-width of your cut-off p_threshold. The half-width depends only on how many related cases there are (n). Values from grounded_decision.evidence.thresholds.operational_width(n, 0.1) with the default prefactor:

Related cases n Half-width (held if density is this close to the cut-off or closer) What it means
2 to 6 0.5 Covers every density between 0 and 1: always held unless your cut-off is near 0 or 1 and density is at the other end.
7 0.425
10 0.304 Density must be about 0.3 away from the cut-off.
16 0.209
20 0.179
40 0.118
80 0.084 The size used by most examples.
200 0.057
1000 0.034

So graphs of under about ten related cases nearly always fail this check. That is intended: with so few cases, a single extra link moves the density by a lot. If your groups are routinely that small, read Are we on a knife-edge? and Choose thresholds and calibration.

Worked example: what α = 0.10 holds

With a calibration set of 40 scores spread evenly from 0.0 to 0.975 ([i/40 for i in range(40)]), conformal_report gives:

This case's score Calibration scores at least as high p-value Verdict of the check
0.875 5 0.146 pass
0.900 4 0.122 pass
0.925 3 0.098 fail
0.950 2 0.073 fail

The p-value is \( (1 + \text{count}) / (1 + 40) \). At α = 0.10 the check fails once three or fewer of the 40 recent cases were at least as unusual. At α = 0.05 it would fail only at one or none.

What these settings do not do

  • min_support has no effect unless the evidence carries a support share. Without it, the direction comes from the event you send.
  • The timing budgets are checked by a test on the library, not in your service, and nothing interrupts a slow call.
  • Changing Limits changes the settings fingerprint (parameters_fingerprint) in the reference service, not the policy fingerprint, so saved states stay valid.

Learning the data-check settings from your own outcomes: Learn parameters from your history. What each setting does when set by hand: Choose thresholds and calibration. Capacity planning and measured costs: Limits and performance.