Decision Circuitsdecision-circuits 0.3

Don't ask the model to decide. Ask it questions. Decide in code.

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.

$pip install decision-circuits# no dependencies

A support-triage circuit, live

drag the probabilities a model might return; gates re-evaluate

What the model answered

0.91
0.10
0.35
billing
billing0.72 technical0.20 other0.08

What code decided

redact(pii & ~business) ≥ 0.6, band 0.1, on_uncertain: escalate
routeargmax(dept), min_confidence 0.35
human(angry | pii) ≥ 0.6

Nudge pii to 0.65 and watch redact stop answering. That's the point: inside the band, the circuit escalates instead of guessing.

Write one

The circuit above, in Python

# 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.

formmeaning
Q("pii"), Q("dept")["billing"], Q("urgency")[3]a question's probability, or one option's
~a, a & b, a | b, e >= tauNOT, 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
Circuit schematic: inputs on the left, logic gates in the middle, decisions on the right, colored by outcome.
c.to_mermaid() renders any circuit as a schematic: inputs, logic, decisions, colored by outcome after a run.

Numbers

The 2025 article, re-run on a model built for this

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 calls2026, Jev, one call
single question, no circuit91%98%
circuit, overall87%98%
high-confidence calls80 calls, 92.5% right93 calls, 97.8% right
latencythree round trips325 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.

Scoreboard

Every System One model we can get our hands on, same items, same labels

Jev is TypeSafe's API. Bespoke-Nimble-9B and kev-0.5b are open reproductions released September 18. circuit-1.7b is ours. Cells are accuracy / expected calibration error; lower ECE is better calibrated. Latency is per question, measured the same way for every row in a block.

cold eval, 1,200 human-labeled itemsMultiNLISMS spamtoxicityCLINC, 151 intentslatency
Jev88% / 0.0496% / 0.0582% / 0.0690% / 0.05164 ms, API
Bespoke-Nimble-9B84% / 0.0991% / 0.0686% / 0.08unsupported (26-option cap)1.8 s, laptop
kev-0.5b46% / 0.2850% / 0.3062% / 0.1662% / 0.17325 ms, laptop
circuit-1.7b81% / 0.0998% / 0.0290% / 0.1686% / 0.06175 ms, laptop
circuit-8b86% / 0.0898% / 0.0293% / 0.1495% / 0.03178 ms, A6000

Those four tasks' public train splits are in circuit-1.7b's training data and presumably not in Jev's. The two blocks below were trained on by nobody.

out of distributionJevNimble-9Bkev-0.5bcircuit-1.7bcircuit-8b
the article's 100 water-utility calls, 11 types98% / 0.0293% / 0.0580% / 0.1392% / 0.0893% / 0.05
546 production questions from a real site (agreement with Jev)0.84 / 0.050.49 / 0.110.70 / 0.050.84 / 0.04
generalization grid, 9 operations × 6 formats, labels computed by code95%85%48%97%*98%*

* The grid generator is ours, so that row is held-out items, not held-out structure. Trained with one operation and one format withheld, the same recipe scores 91% on the unseen format and 57% on the unseen operation. Layouts transfer; new kinds of judgment have to be in the data.

The finding nobody else measures. About five percent of grid items are built to be undecidable: a vague claim, two equal bars under "which is taller", a date with no year. The right answer is a flat distribution. Every model above, Jev included, answers those with mean confidence between 0.5 and 0.97. That is exactly the failure an uncertainty band around a threshold exists to catch, and why circuits escalate instead of trusting a number near the line.

circuit-1.7b: LoRA plus a pointer readout head on Qwen3-1.7B-Base, trained in 61 minutes on one RTX 4090 on code-labeled and CC-licensed data only. Weights and card: huggingface.co/jbarney/circuit-1.7b. circuit-8b: the same recipe on Qwen3-8B-Base, 75 minutes on one RTX A6000; it beats Jev on three of the four cold-eval tasks and matches Nimble-9B on the production questions. Weights: huggingface.co/jbarney/circuit-8b. Training and evaluation code: github.com/Barneyjm/circuit.

Agents

The circuit sits where the agent shouldn't be trusted

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.

Models

Built for System One models

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.

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.

The family

One recipe, one head, every modality the base has an encoder for

Each circuit model is a LoRA on the language model plus the same pointer readout head, trained on code-labeled and CC-licensed data with soft targets so calibration is learned. What changes between them is the base and what the state can carry. All of them serve the same POST /v1/systemone contract.

modelbasestatestatuswhere it stands
circuit-1.7bQwen3-1.7B-Basetext, JSON, chat threadsreleasedbeats Jev on spam and toxicity, 4 points behind on 151-way intents, best calibration on the board; 175 ms per question on a laptop
circuit-8b , the 8-ballQwen3-8B-Basetext, JSON, chat threadsreleasedbeats Jev on spam, toxicity, and 151-way intents; 0.84 agreement with Jev on the production questions, level with Nimble-9B at half the calibration error
circuit-vl-4bQwen3-VL-4Bimages, multiple images, video frames, plus textreleased98.3% / ECE 0.018 on the rendered vision grid against the raw base's 96.0% / 0.041; no option cap; seven minutes of training
circuit-audioQwen2-Audio-7Brecorded speech and sound, plus textplannedraw base classifies synthesized support calls correctly and hears anger and urgency; the negative checker is its weak spot