decision-circuits
Deterministic decision gates over calibrated model answers.
A System One style model (TypeSafe's Jev, or an open model that speaks the same contract) answers typed questions about a piece of state and returns probabilities, not prose. A decision circuit is a set of questions plus a set of gates: code that combines those probabilities into decisions with thresholds, boolean logic, votes, verification, and ordinal buckets, and that says out loud when it is uncertain instead of guessing.
The model never sees the gates. That makes a circuit versionable,
testable offline, and auditable per item: every result carries its
probability, its outcome (decided, abstain, escalate, default),
and a trace of every input that fed it.
Background: Attaining LLM Certainty with AI Decision Circuits.
pip install decision-circuits # no dependencies
pip install "decision-circuits[openai]" # OpenAI-compatible logprob backend (OpenAI, Fireworks, vLLM)
pip install "decision-circuits[anthropic]" # Claude backend
pip install "decision-circuits[langchain]" # LangChain runnable (LangGraph needs nothing extra)
Example
from decision_circuits import Circuit, Q, G, argmax, order
from decision_circuits.backends import SystemOne
c = Circuit()
c.noul(
"pii",
"Does this text contain PII about a private individual?",
true="Email, phone, home address, ID, or card number",
false="No PII, or business-only details",
)
c.noul("business", "Are all identifying details about a business rather than a person?")
c.noul("angry", "Is the customer angry?")
c.choice("dept", "Which team should handle this?", {"billing": "Charges, refunds, invoices", "technical": "Bugs, outages, API", "other": "Anything else"})
c.score("urgency", "How urgent is this?", ["Low", "Medium", "High", "Critical"])
c.gate("redact", ((Q("pii") >= 0.7) & ~Q("business")) >= 0.6, on_uncertain="escalate")
c.gate("route", argmax("dept", min_confidence=0.35))
c.gate("tier", order("urgency", [1.0, 2.0, 2.6]))
c.gate("human", (Q("angry") | Q("urgency")[3]) >= 0.6, on_uncertain="escalate")
c.gate("bill_hot", (G("route")["billing"] & G("tier")[3]).at(0.5))
jev = SystemOne(api_key=TYPESAFE_API_KEY, model="jev-latest") # standard library HTTP; no extra install
out = c.run(jev, "Card charged twice, refund NOW, my card ends in 4412.")
out["gates"]["redact"]
# {"value": True, "p": 0.89, "outcome": "decided", "trace": ["pii p=0.89"]}
out["gates"]["route"]
# {"value": "billing", "p": 1.0, "confidence": 1.0, "outcome": "decided", ...}
Backends
A backend is anything with one method, answer(state, questions, *, model=None), returning the System One answers map (see
decision_circuits.types). The core package depends on nothing; each
backend imports its SDK only when you construct it.
| backend | install | how it gets probabilities |
|---|---|---|
SystemOne(url, api_key, model) |
none | Calls a System One server (TypeSafe's Jev, or s1proto). Measured, calibrated distributions; the model is built for this. |
OpenAILogprobs(model, base_url=...) |
[openai] |
Prefill scoring: options are lettered, the next-token logprobs over the letters are the distribution. Works with OpenAI, Fireworks, Together, vLLM. Up to 20 options. |
Anthropic(model, mode="stated") |
[anthropic] |
One tool-use call returning a probability per option. Fast; the numbers are stated confidence, not calibrated. |
Anthropic(model, mode="sampled", k=5) |
[anthropic] |
k tool-use calls at temperature 1, one pick each; vote frequencies with add-one smoothing. An empirical distribution at k times the cost. |
c.run(client, url=..., headers=...) also accepts a plain HTTP client
(httpx, requests, a FastAPI TestClient). It sends the compiled gates so
a server that evaluates circuits itself can; a server that rejects the
extra field or returns answers only gets one retry without gates and
the gates are evaluated here. out["gates_evaluated_by"] says which.
Calibration is the backend's, not the package's. Gates threshold whatever probabilities they are given. A System One model is trained to be calibrated; a chat model's stated confidence usually is not. Check on a labeled sample before trusting a threshold, and prefer logprob or sampled modes over stated ones when it matters.
LangGraph and LangChain
Gates become conditional edges, and the uncertain outcomes are edges like any other:
from decision_circuits.langgraph import as_node, route_on
graph.add_node("triage", as_node(c, jev, state_key="text"))
graph.add_conditional_edges(
"triage",
route_on("route"),
{
"billing": "billing_agent",
"technical": "tech_agent",
"other": "general_agent",
"abstain": "human",
},
)
graph.add_conditional_edges("triage", route_on("human"), {"True": "human", "False": "continue", "escalate": "human"})
as_node writes {"circuit": {"answers": ..., "gates": ...}} into the
graph state. as_runnable(c, backend) wraps the same function as a
RunnableLambda when langchain-core is installed.
The expression language
| form | meaning |
|---|---|
Q("noul_id") |
P(yes) for a yes/no question |
Q("choice_id")["option"] |
probability of one option |
Q("score_id")[level] |
probability of one score level |
G("gate_id"), G("gate_id")["value"] |
an earlier gate's probability, or one value of a categorical gate |
~e |
NOT: 1 - p |
a & b & c |
AND: product (independence assumed and recorded in the trace) |
a | b |
OR: 1 - prod(1 - p) |
e >= tau, e.at(tau) |
threshold: boolean at tau with an uncertainty band around it |
argmax("choice_id", min_confidence=...) |
pick the top option; abstain below the confidence floor |
majority("c1", "c2", "c3") |
vote across paraphrased questions over the same options |
verify("choice_id", check=Q("supported"), tau=...) |
a negative checker: escalate when the check does not support the pick |
order("score_id", [c1, c2, ...]) |
bucket the expected score by cutpoints |
An expression used as a gate without a threshold thresholds at 0.5.
e >= tau builds a node and does not compare; bool(Q("x") >= 0.7) is
always true, so use .at(tau) anywhere an operator would read as a
runtime comparison.
Every gate takes on_uncertain="abstain" | "escalate" | "default"
(with default=value) and band=width. A gate is uncertain when the
probability it acts on lies within band of its threshold, or when a
confidence floor fails. Uncertainty is surfaced, never silently
resolved.
Without a server
c.compile() returns the flat gates map (the wire format).
c.evaluate(answers) runs the gates against answers you already have,
in the response format of any System One server. evaluate_gates is
the underlying function if you build the map by hand.
Diagram
c.to_mermaid() renders the circuit as a Mermaid flowchart with three
columns, inputs, logic, and decisions; pass the results of a run to
color each node by outcome. Paste it into a README or render it with
mmdc.
print(c.to_mermaid(results=out["gates"], answers=out["answers"]))
Status
Alpha. Core has no dependencies; Python 3.10+. The wire format is
TypeSafe's POST /v1/systemone request with an added gates block; the reference server that evaluates gates is
s1proto. The gate semantics
(and as a product, or as noisy-or) assume independent questions;
the trace records that assumption on every result so a reviewer can
see it.
Release files for decision-circuits 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| decision_circuits-0.2.0.tar.gz | 26.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| decision_circuits-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 54.0 kB
Release files / decision_circuits-0.2.0.tar.gz
| Download URL | decision_circuits-0.2.0.tar.gz |
|---|---|
| Size | 26.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ef214913f0635978b0f8b9d12923bd4814d4f420e1b3a5fa425b90ba8bdc403a
|
|
BLAKE2b-256 checksum How to use checksums |
df59f2039b91a4fb40f305e677384a00072787cf574a07f4fd4293b9be0bc9de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / decision_circuits-0.2.0-py3-none-any.whl
| Download URL | decision_circuits-0.2.0-py3-none-any.whl |
|---|---|
| Size | 27.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2414230811293626161e44fc037646ce4c010498ea29f68e7ed878c8ff0115d6
|
|
BLAKE2b-256 checksum How to use checksums |
28fa1a071ca5279860aeddf6137c4623ec11b3377624d2c2f9d0f7ad429384a6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency log