A decision circuit is a set of typed questions a model answers with probabilities, and a set of gates that turn those probabilities into decisions: thresholds, AND/OR/NOT, votes, verification. The model never sees the gates. Every decision carries its probability, its outcome, and a trace. When the number sits too close to a threshold to call, the gate says so and a human gets the case.
Nudge pii to 0.65 and watch redact stop answering. That's the point: inside the band, the circuit escalates instead of guessing.
# questions the model answers c = Circuit() c.noul("pii", "Does the message contain personal information about a private individual?") 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": "Money, refunds", "technical": "Bugs, outages", "other": None}) # gates code evaluates c.gate("redact", (Q("pii") & ~Q("business")) >= 0.6, band=0.1, on_uncertain="escalate") c.gate("route", argmax("dept", min_confidence=0.35)) c.gate("human", (Q("angry") | Q("pii")) >= 0.6) out = c.run(SystemOne(api_key=KEY), "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', 'gate _redact_1 p=0.98', 'and under independence -> p=0.95']}
The arithmetic is deliberately simple and is written into the trace: AND is a product, OR is 1 − ∏(1 − p), NOT is 1 − p. AND and OR assume the inputs are independent, and every trace says so, because a reviewer should see that assumption next to the number.
| form | meaning |
|---|---|
Q("pii"), Q("dept")["billing"], Q("urgency")[3] | a question's probability, or one option's |
~a, a & b, a | b, e >= tau | NOT, AND, OR, threshold with an uncertainty band |
argmax("dept", min_confidence=…) | top option; abstain below the confidence floor |
majority("q1", "q2", "q3") | vote across paraphrased questions |
verify("dept", check=Q("supported"), tau=…) | a negative checker: escalate when the check doesn't support the pick |
order("severity", [c1, c2, …]) | bucket an ordered score |
G("route")["billing"] | gates over gates |
The original write-up classified 100 water-utility customer calls into eleven types with two LLM parsers and a negative checker, combined into confidence tiers. Same calls, same circuit, one call to a System One model:
| 2025 · Claude Sonnet 3.7, three LLM calls | 2026 · Jev, one call | |
|---|---|---|
| single question, no circuit | 91% | 98% |
| circuit, overall | 87% | 98% |
| high-confidence calls | 80 calls · 92.5% right | 93 calls · 97.8% right |
| latency | three round trips | 325 ms for all three questions |
And a test that didn't exist before: nine kinds of judgment (extract, compare, count, apply a rule, check a claim against a record, …) times six ways of laying out the data, every label computed by code. Jev scores 95% on it. It also gets confident on inputs built to be undecidable, which is exactly what a threshold with a band is there to catch.
CircuitToolGuard judges each tool call: allow, block, or pause with the standard human-in-the-loop interrupt. CircuitRouter picks a model per run.
Input, output, and tool guardrails from a circuit. Tripwires carry the gate's probability and trace.
A PreToolUse hook whose allow / deny / ask maps straight onto the gate's decided / blocked / uncertain.
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, a circuit combines several questions, makes the threshold and its band explicit, and sends the uncertain cases to a person instead of silently allowing or blocking them.
Circuits need calibrated probabilities. That's what a System One model produces: typed questions in, a distribution per question out, in one pass, in about 200 ms. The SystemOne backend talks to any server speaking that contract.
The model that made this practical. Drop in an API key.
Our own S1 model, trained on code-labeled data with a pointer readout. Runs on a laptop.
A backend is one method: answer(state, questions). Wrap a cache, inject code-owned facts, chain fallbacks.
No S1 model yet? Chat models can stand in through logprobs (OpenAI-compatible) or tool use (Claude), with the caveat that a chat model's stated confidence isn't calibrated the way an S1 model's output is.