Skip to main content

decision-circuits

Deterministic decision gates over calibrated model answers.

pip install decision-circuits          # no dependencies, Python 3.10+

The idea in one paragraph

Ask a model small, typed questions about a piece of state and get back probabilities, not prose. Then decide with code: thresholds, AND/OR/NOT, argmax with a confidence floor, majority votes, verification, ordinal buckets. That code is a decision circuit. The model never sees it, so the circuit is versionable, testable offline, and auditable per item: every result carries its probability, its outcome, and a trace. And when the probability sits too close to a threshold to call, the gate says so (abstain or escalate) instead of guessing.

Background: Attaining LLM Certainty with AI Decision Circuits.

Sixty seconds

from decision_circuits import Circuit, Q, argmax

c = Circuit()
c.noul("pii", "Does this text contain personal information about a private individual?")
c.noul("angry", "Is the writer angry?")
c.choice("dept", "Which team should handle this?", {"billing": "Money, refunds", "technical": "Bugs, outages", "other": None})

c.gate("redact", Q("pii") >= 0.7, on_uncertain="escalate")
c.gate("route", argmax("dept", min_confidence=0.3))
c.gate("human", (Q("angry") | Q("pii")) >= 0.6)

Now something has to answer the questions. Anything with one method will do:

from decision_circuits.backends import SystemOne

jev = SystemOne(api_key=TYPESAFE_API_KEY, model="jev-latest")  # TypeSafe's System One model
out = c.run(jev, "Card charged twice, refund NOW. My card ends in 4412.")

out["gates"]["redact"]
# {'value': True, 'p': 0.97, 'outcome': 'decided', 'trace': ['pii p=0.97'], ...}
out["gates"]["route"]
# {'value': 'billing', 'p': 1.0, 'confidence': 1.0, 'outcome': 'decided', ...}

No model handy? c.evaluate(answers) runs the gates on answers you already have, and examples/01_first_circuit.py does exactly that with hand-written numbers.

Where it plugs in

The point of a circuit is to sit inside an agent and make the small decisions the agent shouldn't be trusted with. Adapters exist for the three agent SDKs; each is a few lines over the same object.

framework what you get install
LangChain agents CircuitToolGuard middleware: judge each tool call, then allow, block, or pause for approval with the standard human-in-the-loop interrupt. CircuitRouter picks a model per run. [langchain]
OpenAI Agents SDK circuit_input_guardrail, circuit_output_guardrail, circuit_tool_guardrail [openai-agents]
Claude Agent SDK circuit_pre_tool_use hook returning allow / deny / ask; circuit_can_use_tool [claude]
LangGraph as_node and route_on: gates as conditional edges none
from decision_circuits.integrations.langchain import CircuitToolGuard

guard = CircuitToolGuard(c, jev, gate="block", tools=[delete_file, send_email])
agent = create_agent(model, tools=[read_file, delete_file, send_email], middleware=[guard])

Compared with a single yes/no classifier at a fixed 0.5 (what langchain-typesafe's AutoModeMiddleware does), a circuit lets you combine several questions, set the threshold and its uncertainty band explicitly, and send the uncertain cases to a human rather than silently allowing or blocking them. See docs/integrations.md.

Backends

A backend is any object with answer(state, questions, *, model=None) returning the answers map. The core package ships three and depends on none of their SDKs; each imports its own when you construct it.

backend install how it gets probabilities
SystemOne(url, api_key, model) none A System One server: TypeSafe's Jev, or s1proto on open weights. Measured, calibrated.
OpenAILogprobs(model, base_url=...) [openai] Options are lettered; the next-token logprobs over the letters are the distribution. OpenAI, Fireworks, vLLM. Up to 20 options.
Anthropic(model, mode="stated") [anthropic] One tool-use call returning a probability per option. Fast; a self-report, not calibrated.
Anthropic(model, mode="sampled", k=5) [anthropic] k calls at temperature 1, one pick each; vote frequencies.

Writing your own is a dozen lines: examples/03_your_own_backend.py.

Calibration is the backend's, not the package's. Gates threshold whatever they are handed. 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.

The expression language

form meaning
Q("noul_id") P(yes) for a yes/no question
Q("choice_id")["option"], Q("score_id")[level] probability of one option or 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 with an uncertainty band; >= builds a node, it does not compare
argmax("choice", min_confidence=...) top option; abstain below the floor
majority("c1", "c2", "c3") vote across paraphrased questions
verify("choice", check=Q("supported"), tau=...) a negative checker: escalate when the check does not support the pick
order("score", [c1, c2, ...]) bucket the expected score by cutpoints

Every gate takes on_uncertain="abstain" | "escalate" | "default" (with default=value) and band=width. c.to_mermaid() draws it.

Examples

Numbered, each self-contained, in examples/:

  1. a circuit with hand-written answers (offline)
  2. the same circuit answered by Jev
  3. writing a backend (offline)
  4. LangChain tool guard
  5. OpenAI Agents guardrails
  6. Claude Agent SDK hook
  7. integrating a framework that has no adapter (offline)
  8. a refund desk: seven questions, six gates, code-owned facts mixed with model judgments, five tickets routed to four queues (offline or Jev)
  9. the refund desk answered by Claude, stated versus sampled probabilities
  10. the refund desk answered through OpenAI-compatible logprobs (OpenAI, Fireworks, vLLM)

Docs

Status

Alpha. The wire format is TypeSafe's POST /v1/systemone request plus a gates block. MIT.

Changed in 0.3.0: Circuit.run takes a backend, not an HTTP client. Where 0.2 wrote c.run(httpx.Client(), state, url=..., headers=...), write c.run(SystemOne(url, api_key=...), state). The full server response (usage, model) is on SystemOne.last_response. The core no longer depends on pydantic.

Release files for decision-circuits 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for decision-circuits 0.3.0
File Size Uploaded
decision_circuits-0.3.0.tar.gz 39.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for decision-circuits 0.3.0
File Interpreter ABI Platform
decision_circuits-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size:78.3 kB

Release files / decision_circuits-0.3.0.tar.gz

Download URL decision_circuits-0.3.0.tar.gz
Size 39.3 kB
Tags Source
SHA-256 checksum
How to use checksums
9db465d9662cc0a83a09a07a76d19904790989952f0bb99db54e4d1534f1fc3a
BLAKE2b-256 checksum
How to use checksums
32b80e9dc6440f109ff48d3775eac2a1b72234788e39a7683e041f706f71eebf
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

Release files / decision_circuits-0.3.0-py3-none-any.whl

Download URL decision_circuits-0.3.0-py3-none-any.whl
Size 39.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3c52484101519ead58fedee96c2237490f1b967322c854b134e8f9f11a304d70
BLAKE2b-256 checksum
How to use checksums
a39a923d564969e8c5a99a7de9d2e5733d576aee7d42e058ee81bf2c32ea9df0
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

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page