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.
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 items | MultiNLI | SMS spam | toxicity | CLINC, 151 intents | latency |
|---|---|---|---|---|---|
| Jev | 88% / 0.04 | 96% / 0.05 | 82% / 0.06 | 90% / 0.05 | 164 ms, API |
| Bespoke-Nimble-9B | 84% / 0.09 | 91% / 0.06 | 86% / 0.08 | unsupported (26-option cap) | 1.8 s, laptop |
| kev-0.5b | 46% / 0.28 | 50% / 0.30 | 62% / 0.16 | 62% / 0.17 | 325 ms, laptop |
| circuit-1.7b | 81% / 0.09 | 98% / 0.02 | 90% / 0.16 | 86% / 0.06 | 175 ms, laptop |
| circuit-8b | 86% / 0.08 | 98% / 0.02 | 93% / 0.14 | 95% / 0.03 | 178 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 distribution | Jev | Nimble-9B | kev-0.5b | circuit-1.7b | circuit-8b |
|---|---|---|---|---|---|
| the article's 100 water-utility calls, 11 types | 98% / 0.02 | 93% / 0.05 | 80% / 0.13 | 92% / 0.08 | 93% / 0.05 |
| 546 production questions from a real site (agreement with Jev) | — | 0.84 / 0.05 | 0.49 / 0.11 | 0.70 / 0.05 | 0.84 / 0.04 |
| generalization grid, 9 operations × 6 formats, labels computed by code | 95% | 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.
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 models: pointer readout, code-labeled and CC-licensed data only. The small one runs at 175 ms per question on a laptop; the large one leads the scoreboard.
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.
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.
| model | base | state | status | where it stands |
|---|---|---|---|---|
| circuit-1.7b | Qwen3-1.7B-Base | text, JSON, chat threads | released | beats 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-ball | Qwen3-8B-Base | text, JSON, chat threads | released | beats 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-4b | Qwen3-VL-4B | images, multiple images, video frames, plus text | released | 98.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-audio | Qwen2-Audio-7B | recorded speech and sound, plus text | planned | raw base classifies synthesized support calls correctly and hears anger and urgency; the negative checker is its weak spot |
A query from the decide token, a key from each option's closing delimiter, softmax. Order-invariant, no cap on options, and it doesn't know or care what encoder produced the hidden state.
A grid of judgment operations times state formats, labels computed at generation time. For images that's rendered receipts and charts; for audio it's synthesized calls with known content.
Hold out a whole operation or a whole format and measure transfer. So far: layouts transfer, new kinds of judgment have to be in the data, and nobody is calibrated on undecidable inputs yet.