Decision Circuits
Yesp 0.95

tap, or shake your phone

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

The family

Four models, one contract

Each is a small open model tuned to do one thing: answer a typed question with a probability you can trust. They differ in what they take in, text, pictures, or sound. They all speak the same API, so a circuit written for one runs on any of them, and the weights are yours to download.

circuit-1.7b

Qwen3-1.7B-Base · text, JSON, chat threads

SMS spam
98% / 0.02
151-way intents
86% / 0.06
per question
175 ms

Reads text, JSON, and chat threads. A yes/no, a pick from any number of options, or a score, in one pass and 175 ms on a laptop. Runs anywhere, no GPU needed.

Try nowWeights on Hugging Face

circuit-8b

Qwen3-8B-Base · text, JSON, chat threads

151-way intents
95% / 0.03
agreement with Jev
0.84
per question
178 ms

The 8-ball. The strongest text model in the family: 151-way intents, long threads, real production questions. Probabilities steady enough to put a threshold on.

Try nowWeights on Hugging Face

circuit-vl-4b

Qwen3-VL-4B · images, video frames, plus text

vision grid
96.4% / 0.036
raw base
92.6% / 0.079
real photos
89.5% / 0.105

Reads receipts, charts, tables, and forms, and looks at photographs. Ask what a document says, whether a claim matches it, how many rows are flagged, whether there is a dog in the picture. No cap on options.

Try nowWeights on Hugging Face

circuit-audio-7b

Qwen2-Audio-7B · speech and sound, plus text

audio grid
94.5% / 0.039
raw base
73.2% / 0.199
per clip
1.2 s

Listens to calls and recordings. What kind of call is this, what amount was said, did they say the word, which sentence was read. Hears the clip; no transcript needed.

Try nowWeights on Hugging Face

Think you're better than the model?

Eight questions straight from the benchmark, on the clock.

Real items from the text, vision, and audio grids and the article's water-utility calls, labels assigned by the code that built them. Chat-model times were measured here through the Claude CLI; audio went through a local Whisper first, because a chat model has to read a transcript.

8 questions from the benchmark, one at a time, on the clock.

The clock starts when a question appears and stops when you pick. Each answer shows what the circuit model said and how long it took, against a chat model on the same question. The last card puts every answer and every time side by side.

API

Call the models without running anything

A hosted endpoint serves all four models at api.decisioncircuits.com: the same request format as TypeSafe's POST /v1/systemone, and for circuit-vl-4b and circuit-audio-7b the state is an image or a clip. Keys are free: 1,000 questions a day per key, no card, no account. The models scale to zero between calls, so the first call after a quiet spell takes about a minute while a GPU spins up; warm calls answer in under a second.

No account. An email is optional, and only so your key has somewhere to reach you.

curl

curl https://api.decisioncircuits.com/v1/systemone \
  -H "Authorization: Bearer dc-..." \
  -H "Content-Type: application/json" \
  -d '{
    "model": "circuit-8b",
    "state": "My name is Dana Whitfield and my account number is 88421.",
    "questions": {
      "pii": {
        "type": "noul",
        "instructions": "Does this message contain personally identifiable information such as a name, address, phone number, or account number?"
      }
    }
  }'

{"answers": {"pii": {"type": "noul", "noul": 0.814}}}

Python

pip install decision-circuits

from decision_circuits import Circuit, Q
from decision_circuits.backends import SystemOne

api = SystemOne("https://api.decisioncircuits.com/v1/systemone", api_key="dc-...")

c = Circuit()
c.noul("pii", "Does this message contain personally identifiable information such as a name, address, phone number, or account number?")
c.gate("redact", Q("pii") >= 0.6, band=0.1, on_uncertain="escalate")

out = c.run(api, "My name is Dana Whitfield and my account number is 88421.", model="circuit-8b")
print(out["gates"]["redact"])

{'value': True, 'p': 0.81, 'outcome': 'decided', 'trace': ['pii p=0.81']}

Two ways to send the same question. The output under each is what these exact calls return — 0.81 clears the gate's 0.6 threshold by more than its band, so the circuit decides rather than escalating. Shorten that message and the probability slides toward the band; that is the model being honest, not broken.

What happens to what you send: your inputs answer your request and are discarded, never stored and never trained on. The key record holds a hash of the key, the date, and an email if you give one. Terms and data use.

Reading this as an agent? decisioncircuits.com/skill.md is written for you: when a calibrated judgment beats a guess, how to get a key without a human, and how to turn the probability into a decision you can defend.

A clip instead of a sentence

Nothing about the call changes when the state is sound or a picture: the state becomes {"audio": "<url or data URI>"}, or Audio(...) and Image(...) in Python. This example runs as written — the clip is a synthesized water-utility call hosted on this site, and the model hears it without a transcript.

curl

curl https://api.decisioncircuits.com/v1/systemone \
  -H "Authorization: Bearer dc-..." \
  -H "Content-Type: application/json" \
  -d '{"model": "circuit-audio-7b",
       "state": {"audio": "https://decisioncircuits.com/bench/call.wav"},
       "questions": {"kind": {"type": "choice",
         "instructions": "Which kind of water-utility call is this?",
         "criteria": {"billing": null, "metering": null, "taste and odor": null, "outage": null, "leak": null}}}}'

{"kind": {"choice": "leak", "confidence": 0.99998, ...}}

Python

from decision_circuits import Audio, Circuit

c = Circuit()
c.choice("kind", "Which kind of water-utility call is this?",
         {"billing": None, "metering": None, "taste and odor": None, "outage": None, "leak": None})
c.noul("urgent", "Does the caller need someone today?")

out = c.run(api, Audio("https://decisioncircuits.com/bench/call.wav"), model="circuit-audio-7b")

kind   leak, p 1.00
urgent 0.99

That clip is one of the quiz's audio questions, so you can listen to it yourself above. A first call to a model that has been idle waits about a minute for a GPU; after that it answers in around a second. Pass Image("receipt.png") with circuit-vl-4b for documents and photographs.

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 callsJev, one callcircuit-8b, one callcircuit-1.7b, one call
single question, no circuit91%98%93%91%
circuit, overall87%98%93%91%
high-confidence calls80 calls, 92.5% right93 calls, 97.8% right90 calls, 94.4% right88 calls, 90.9% right
sent to a human (the article's rule)2%0%0%0%
latencythree round trips325 ms for all three questions579 ms, 88 ms of it on the GPU720 ms, 175 ms of it on the GPU

Jev is the commercial model; the circuit columns are the open weights on this page, run through the same circuit on the same 100 calls (examples/11_water_utility_calls.py). The line worth noticing is the last column: circuit-1.7b matches the article's three Sonnet calls — 91% single, 91% circuit — from a 1.7B model you can download and run on a laptop. circuit-8b adds two points and a cleaner high-confidence tier. Latency is wall time per call from a laptop, all three questions in one request, most of it network.

That 0% is the rule going quiet, not the model being certain. The article escalates only when the negative checker says the call isn't categorizable and the two parsers disagree — and two sampled chat calls disagree often enough for that to fire. One calibrated model asked the same question two ways agrees with itself on 97 of 100 calls, so the two conditions never coincide and nothing reaches a person. Seven calls that circuit-8b itself scored below 0.5 on "is there enough information here" were answered anyway.

Disagreement was a stand-in for a number nobody had. Tier on the probability instead — the same three answers, no extra calls — and the escalation comes back: circuit-8b sends 8% to a human and is right on 95.5% of the 88 it keeps; circuit-1.7b sends 15% and is right on 100% of the 69 it keeps. That is the trade the article was reaching for, priced in a quantity the model actually reports. The example prints both readings.

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.