A System One call is a typed decision: a text (the state), a question with a fixed set of answers, and each answer's probability back. Nothing is generated. Three shapes cover it — choice (which of these), score (where on this ordered scale), noul (does this hold, as a probability) — and a request asks several questions of one state at once.
This page is the map of what CoreAIKit does with that on a Mac or an iPhone, with the numbers and where they came from.
Swift, one line per shape (import CoreAIOps):
let a = try await CoreAI.decide(
text,
["reply": .noul("Does the speaker want a response from the assistant?"),
"topic": .choice("What is the message about?", ["billing", "delivery", "other"]),
"urgent": .score("How urgent is it?", levels: ["can wait", "this week", "today"])])
a["reply"]?.noul // P(yes)
a["topic"]?.choice // "delivery"
a["urgent"]?.score // expected level, 0…2The same call in the hosted API's own forms — a request that arrived as JSON, a response to
hand back as JSON — is CoreAI.systemOne, the typed answers beside the wire object:
let r = try await CoreAI.systemOne(json: body) // the bytes a client sent: state, questions, model
r["topic"]?.choice // typed, by the request's own key
r.dumps() // the reply, byte for byte what `systemone serve` returnsAnything else, over HTTP, in the hosted API's own forms:
brew install john-rocky/tap/systemone && systemone serve # http://127.0.0.1:8090/v1/systemone
brew services start systemone # the same, kept running by launchd
curl -s http://127.0.0.1:8090/v1/systemone -H 'Content-Type: application/json' -d '{
"state": "Help! My payouts have been failing for 3 days.", "model": "minicpm5-2b",
"questions": {"is_urgent": {"type": "noul", "instructions": "Does this convey urgency?"},
"queue": {"type": "choice", "instructions": "Which queue?",
"criteria": {"billing": "invoices, payments, refunds", "technical": "bugs, outages", "other": "everything else"}}}}'systemone is one signed, notarized binary (21 MB); the first serve downloads MiniCPM5 2B
(2.7 GB) into ~/Library/Application Support/CoreAIKit/Models; after that brew services start
answers /health 0.7–4.6 s later (M4 Max, depending on how much of the model is still in the
file cache). From a checkout the same
server is swift run -c release systemone serve,
or decide-cli serve in Examples/Decide; decide-cli serve --backend fm is the same endpoint
over Apple's on-device foundation model (FoundationModelDecisions: guided generation, one-hot
probabilities, no calibration — the response says so in metadata).
A client written for the hosted endpoint is pointed at this one by its base URL and nothing
else changes. Measured 2026-09-23 against this server: the official TypeSafe Python SDK
(typesafe-sdk 0.7.1) with TYPESAFE_BASE_URL=http://127.0.0.1:8090 and any string as
TYPESAFE_API_KEY (or TypeSafeClient(base_url:)) got its typed answers back unchanged,
158–201 ms for three questions; the JavaScript SDK takes the same variable or baseURL; the
system-one SDK on PyPI takes HTTPConfig(base_url=…), 179 ms for three. GET /v1/models
answers in the hosted list form (models, each name / description / release_date), so
a client's model listing works too. The request and answer forms are in
Examples/Decide/README.md.
A choice lists up to the hosted API's 255 options where the model reads that many
(below).
Examples/Decide/conformance/check.py <base_url>
sends 22 requests in those forms and checks every answer's shape, against this server or any
other that speaks the route.
A request can carry one image for a model that reads images, clef-flash, d1-omni-600m and d1-3b: images is an
array holding it as a data URL ("data:image/png;base64,…"), plain base64, or a path on the
server's machine, which SystemOneServer reads only when it listens on 127.0.0.1; grid picks
clef-flash's vision tower tile, 448 (the default) or 256. Any other model answers such a request with a 422
that names it, and a request without the field reads as before
(every question at once,
pictures and text). A request for d1-omni-600m can carry one clip
instead: audio, a 16 kHz mono 16-bit WAV in the same three forms, with state optional
(text, images or audio); every other model answers it with a 422.
systemone mcp serves the same decisions as tools of a Model Context Protocol server on
stdin/stdout. Register it once and the agent's own sessions see decide and models:
claude mcp add systemone -- "$(brew --prefix)/bin/systemone" mcp # Claude Code
codex mcp add systemone -- "$(brew --prefix)/bin/systemone" mcp # Codex{"mcpServers": {"systemone": {"command": "/opt/homebrew/bin/systemone", "args": ["mcp"]}}}(Cursor: that JSON in ~/.cursor/mcp.json. From a checkout, .build/release/systemone after
swift build -c release --product systemone, or decide-cli mcp in Examples/Decide.)
decide takes the /v1/systemone request above — state, questions, an optional model —
as its arguments and returns the response as structuredContent (and as text); models lists
the catalog ids that can answer. The model loads on the first call (--preload starts it at
launch) and answers one call at a time; when the input closes, the calls in flight are
answered and the process ends, so a one-shot pipe works too (printf '…' | systemone mcp).
Asked from a Claude Code session to classify a ticket
with three questions, the tool call round-tripped in 1,064 ms including the model load, 285 ms
of it decisions (M4 Max, 2026-09-23, two model conversions running on the same Mac); from a
Codex session 4,131 ms, 3.7 s of it the load under that load. The server is
SystemOneMCPServer in the kit and the message forms SystemOneMCP, for an app that is the
MCP server itself.
| Mac (M4 Max, macOS 27.0) | iPhone 17 Pro (iOS 27.0) | |
|---|---|---|
| One decision after the state is read (MiniCPM5 2B int8) | 33–65 ms | 63–95 ms |
| Reading a 565-token contract once, then 12 answers | 316 ms + 532 ms | 508 ms + 1,134 ms |
| Twenty tickets × three columns, 60 decisions | 3,963 ms | 7,420 ms |
A /v1/systemone request, 2 questions on a 59-token state, end to end |
204 ms | — |
One decision, laya-multilingual (an encoder, 256-token window, GPU, state shared) |
11.5 ms | 47 ms |
One decision, openthai-systemone (0.8B, its own answer head; a recurrent hybrid on an S = 1 graph, the state checkpointed after its prefix) |
120 ms† | 263 ms‡ |
One decision, qwen3.5-2b-decision (2B, the same S = 1 graph and checkpoint) |
220 ms† | 1,040 ms‡ |
Measured 2026-09-22/24 on the runs in Examples/Decide/README.md, which says what each
number is and what else was running; the phone at thermal state "nominal" for the last three rows.
† decide-cli bench --repeat 6, eight questions on one state, nothing else on the GPU
(2026-09-24); with every prompt from scratch they take 696 ms and 1,297 ms.
‡ The same bench on the phone with kit 0.7.1 (2026-09-24), at thermal state "nominal":
openthai-systemone unplugged, qwen3.5-2b-decision on the charger. With every prompt from
scratch they take 1,653 ms and 6,150 ms (qwen3.5-2b-decision's on kit a88ec6a, before the
checkpoint: its 0.7.1 from-scratch runs went hot).
The default, minicpm5-2b, is the fast one: at the median, 0.14 s per question on the Mac and
0.17–0.63 s on the phone, up to 255 options, probabilities read at a fitted temperature. On a
Mac, name apus-decision-v1-4b when accuracy matters more than time and download: 0.986 on
JevBench's standard items (the hosted Jev's score), up to 16 options, 5.8 GB, about 2.6 s for
the first question on a short state and 1.2 s for each later question on it. On an iPhone, keep
minicpm5-2b: of the five models run there, it scores highest on the hard items (0.459).
Name a model with options: .model("apus-decision-v1-4b") in Swift, or
systemone serve --model apus-decision-v1-4b for the server. A question about an image goes to
decider-2b-vision through CoreAI.decide(image:…) (below), or to
clef-flash with the request's images (below); a question about an image or a
voice clip to d1-omni-600m (below); a question about pictures and a text to d1-3b,
a 3B model read at each row's last token (below); the tables here are
text only.
JevBench's 231 public items (48 easy, 72 standard, 111 hard), sent one question per request to
decide-cli serve by the benchmark's own harness, which also scored them; M4 Max, macOS 27.0,
2026-09-24:
| Model | Standard | Hard | Median per question | Slowest hard item | Download | Most options | Note |
|---|---|---|---|---|---|---|---|
apus-decision-v1-4b |
0.986 | 0.559 | 5.94 s¹ | 217.6 s¹ | 5.8 GB | 16 | Mac only; browser and workflow steps, English + Chinese |
system-one-scorer-4b |
0.861 | 0.505 | 8.96 s¹ | 72.0 s¹ | 5.1 GB | 255 | Mac only; CC BY-NC 4.0; 70 hard states cut to its 384-token rows |
decider-0.8b |
0.833 | 0.414 | 1.69 s | 90.6 s | 1.3 GB | 255 | |
openthai-systemone |
0.819 | 0.324 | 0.62 s | 39.0 s | 1.1 GB | 255 | Thai + English; an abstain probability |
qwen3.5-2b-decision |
0.778 | 0.405 | 3.24 s¹ | 138.5 s¹ | 3.0 GB | 26 | |
qwen3.5-2b |
0.750 | 0.441 | 2.74 s | 62.3 s | 3.0 GB | 26 | chat model, zero-shot, no calibration |
minicpm5-2b (default) |
0.708 | 0.459 | 0.14 s | 1.8 s | 2.7 GB | 255 | chat model, zero-shot, catalog temperature 2.93 |
laya-multilingual |
0.403 | 0.342 | 0.02 s | 0.3 s | 0.7 GB | 20 | encoder; 256-token window: 95 hard states cut |
decider-2b-vision |
— | — | — | — | 3.3 GB | 10 | an image and the state, CoreAI.decide(image:…); not behind the server, so not run here (about an image) |
¹ With two other model servers on the GPU. Re-run alone on 30 items, they gave the same
probabilities in about half the time: 2.6 s per question for apus-decision-v1-4b on the easy
and standard ones.
The GPU was shared with other work on every row, so the times are an upper bound. Six of the
eight models are Qwen3.5 recurrent hybrids that read a state one token at a time, so their
slowest hard items take 39–218 s. The hosted Jev (1.13.0) scores 0.986 on the same standard
items and 0.730 on the hard ones, on its own serving stack (JevBench's published per-item
results).
The same requests on an iPhone 17 Pro, answered in an app through TypedDecisions.systemOne(_:),
the path the server takes (iOS 27.0, kit 0.7.1), 2026-09-24; every time here is at thermal
state "nominal". The phone gave the Mac's answer on every item it ran, 882 of 882:
| Model | Standard | Hard | Median per question, standard | Median per question, hard | Download | Most options | Note |
|---|---|---|---|---|---|---|---|
decider-0.8b |
0.833 | 0.414² | 1.33 s³ | 8.04 s² | 1.3 GB | 255 | |
openthai-systemone |
0.819 | 0.324² | 0.99 s | 5.77 s² | 1.1 GB | 255 | Thai + English |
qwen3.5-2b-decision |
0.778 | 0.405² | 3.95 s | 18.09 s² | 3.0 GB | 26 | |
minicpm5-2b (default) |
0.708 | 0.459 | 0.17 s | 0.63 s | 2.7 GB | 255 | chat model |
laya-multilingual |
0.403 | 0.342 | 0.05 s | 0.07 s | 0.7 GB | 20 | encoder; 256-token window |
² The phone ran the three hybrids on the 20 hard items of median length, with the Mac's answer
on each. Their hard accuracy is the Mac's over all 111; their hard time is over those 20.
³ Its easy items. Its standard items ran while the phone was hot, at 2.26 s.
apus-decision-v1-4b and system-one-scorer-4b have no iPhone variant; qwen3.5-2b was not run
on the phone. The Mac's requests and answers are in
mlboydaisuke/coreai-decision-models-public231;
Examples/Decide/README.md
has the Mac table with the easy items, ECE and p95.
minicpm5-2b is the default because its int8 decisions track its own full-precision readout:
141/144 argmax agreement on SemIf's authored fixture, mean |Δp| 0.02, family-balanced accuracy
0.681 against 0.686 published. The 4-bit qwen3-0.6b does not (59/144) and is documented as
speed-only. decider-0.8b, a model trained for these questions, is token-identical to its
author's readout on all 44 of its fixture rows, the 255-option row included; through the hosted
request form (CoreAI.systemOne) its 13-request fixture comes back with the author's option on
all 36 answers and every probability, score, level fit and fit mass within 0.02 of the author's
fp32 assembly (max 0.016, on a five-level fit mass).
openthai-systemone, a Thai +
English decision model with a 256-way answer head of its own (up to 255 options, an abstain
probability), is token-identical and argmax-identical to its author's readout on all 50 of its
fixture rows (int8 max |Δp| 0.023; on the iPhone 17 Pro the same 50/50 at 0.021) and scores 0.725 on
SemIf's 144 English rows through the kit, at 0.35 s per fixture question on the Mac and 1.5 s per
decision on the phone (1.9 s per fixture question when the phone was hot).
apus-decision-v1-4b, a Qwen3.5-4B decision model for browser and workflow steps read at the
letters A–P under its chat template, is token-identical to its author's compiled prompts on the
40 choice and yes/no fixture rows (max |Δp| 0.0055) and scores 0.906 on the same 144 rows, at
about 2 s per decision on the Mac (1.2 s for a later question on a state it has read).
qwen3.5-2b-decision, a Qwen3.5-2B decision model with its calibration folded into the weights and
read at space-prefixed letters after a plain-text prompt, is token-identical to its author's rows and
argmax-identical to its fp32 reference on all 58 fixture rows (int8 max |Δp| 0.0079; on the iPhone 17
Pro the same 58/58 at 0.0070) and scores 0.798 on the same 144 rows, at about 1 s per decision on the
Mac (0.2 s for a later question on a state it has read) and about 6 s on the phone (11 s when the
phone is hot), where its S = 1 prefill runs at the phone's rate.
system-one-scorer-4b, a Qwen3.5-4B scoring head that reads one row per option at its author's
temperature (CC BY-NC 4.0), is token-identical to its author's 280 fixture rows and argmax-identical
on all 48 questions (int8 max |Δp| 0.011) and scores 0.844 on the same 144 rows, at about 2 s per
three-option decision on the Mac (0.7 s per decision on a state it has read).
laya-multilingual, an encoder-type decision model (convaiinnovations' laya, the multilingual
checkpoint: an mmBERT-base encoder with a typed decision head, Apache-2.0), reads the whole question
in one forward pass and answers at a mask marker in front of each option; nothing is generated or
prefilled. Its rows through the kit are token- and marker-identical to its publisher's builder on
all 201 multilingual fixture rows at both of its windows (256 and 512 tokens), and the fp16-weight
bundle is argmax-identical to the official model on all 81 choice and score rows (max |Δp| 5e-6 on
the Mac GPU, 9e-6 on the CPU, at T = 1). On the same 144 rows it answers as the official model does
(144/144 argmax; family-balanced accuracy 0.611 and accuracy 0.590, the model's own figures), at
11.5 ms per decision on the Mac GPU and 47 ms on the iPhone 17 Pro's (53–55 ms per fixture row),
read at the calibration its bundle declares; on the phone the same 201 rows pass (argmax 81/81,
max |Δp| 8e-6). The shipped
bundles run on the GPU: the Neural Engine takes only an fp16-compute graph, which misses the answer
bar, and with a Neural Engine preference the shipped graph misses it too, on the Mac and on the
phone, its answers changing from run to run.
What the probabilities are worth, on the same 144 rows (top-label ECE over 10 equal-width bins,
multi-class Brier): read raw, minicpm5-2b is over-confident — ECE 0.167, Brier 0.455 at
accuracy 0.701. So the kit reads it at the temperature its catalog entry records
(CatalogEntry.calibration): 2.93, fitted by decide-cli calibrate on SemIf's 108 perturbation
rows and reported on the 144 authored rows, where ECE goes to 0.071, Brier to 0.411 and NLL from
0.886 to 0.706. No answer changes; a temperature never moves the argmax. The two sets share no
row but do share situations — every perturbation row was made from one of 36 authored rows — so
the temperature was also checked where nothing is shared: fitted on two of the authored rows'
three task families and read on the third, it comes out 1.69, 2.79 and 2.52, and the 144
held-out rows go from ECE 0.167 to 0.067 (Brier 0.455 → 0.424). One temperature is an average:
per family the ECE goes 0.279 → 0.174 and 0.185 → 0.169, and 0.096 → 0.123 for the family that
was already close. The records were fitted on three-option choices; a yes/no and a score are
read at the same temperature until they are fitted apart. minicpm5-1b (9.974) and qwen3-0.6b
(11.882) carry records too, and both read nearly flat: the perturbations break the 1B (accuracy
0.435 on them), and the 0.6B answers at chance (0.340 on the authored rows). A model whose
author fitted or folded in a temperature keeps it and has no record — decider-0.8b at its
card's 1.03 (accuracy 0.771, balanced 0.753, ECE 0.061, Brier 0.296), the bundles that declare
one, qwen3.5-2b-decision at 1. TypedDecisions.Configuration.temperature overrides every one
of them; the tables are in Examples/Decide/README.md.
JevBench's 231 public items on eight catalog models, through decide-cli serve: the table.
The shape of the question decides more than the model. On MiniCPM5 2B, measured on the screens' samples:
- A yes/no on a short text leans yes. Ask a choice with a named "none" option, or a three-level scale. A yes/no on a long text (a contract) reads correctly.
- "How urgent is this?" on a short row answers "today" for nearly every row, in every shape tried. There is no urgency column in the sample table.
- Naming the options alone ("run it / ask / refuse") makes a permission gate ask about 9 commands of 14; naming each option with the policy's own words gives 6 / 6 / 2, and nothing the policy refuses gets through.
- "Which move?" (stay / left / right) drives a car into rocks; "which lane?" with each lane described by what lies ahead never picks a rock lane when a clear one is offered. It cannot rank two bad lanes, so the road keeps one clear.
As many as the model reads, up to the hosted API's 255. TypedDecisions.maxOptions gives the
count, and the server answers a longer list with a 422 that names it.
| Model | Options are read at | Options |
|---|---|---|
minicpm5-2b |
A–Z; past 26, the numbers 1–255 | 255 |
decider-0.8b |
its author's labels A–Z, AA, AB, … | 255 |
openthai-systemone |
its 256-way head | 255 |
system-one-scorer-4b |
one row per option | 255 |
qwen3-0.6b |
A–Z | 26 |
qwen3.5-2b-decision |
A– Z after its plain-text prompt |
26 |
apus-decision-v1-4b |
A–P | 16 |
laya-multilingual |
a mask marker before each option, in one forward pass | 20 |
decider-2b-vision |
A–J after each question's Answer: (, every question of a call in one row (about an image) |
10 |
clef-flash |
a joint head over every option of every question in one pass, 16 questions and 128 options a request (every question at once) | 128 |
kev-0.8b, kev-4b |
a pointer head at each option's closing token, one row per question (one row per question) | 255 |
d1-omni-600m |
the score at a mask marker before each option, one row per question at the smallest of seven graph lengths that holds it (text, images or audio) | 255 |
d1-3b |
the tied embedding rows of each option's code tokens at its row's last position, one row per question; the provider's own codes reach 1,639 (pictures and text) | 255 |
A chat model is read at numbers past 26 because it answers a two-letter label with one of its
letters (AZ → Z). The numbers need a tokenizer that writes 1–255 as single tokens, as
MiniCPM5's does. Qwen's stops at 9, so a Qwen chat model lists 26. On 61 synthetic rows of
17–255 options, each state naming the answer, minicpm5-2b picked the named option on:
| Options | 17–27 | 40 | 52 | 64 | 100 | 128 | 200 | 255 |
|---|---|---|---|---|---|---|---|---|
| numbers past 26 (what the kit does) | 21/21 | 6/6 | 6/6 | 6/6 | 4/5 | 6/6 | 4/5 | 5/6 |
| two-letter labels past 26 | 21/21 | 5/6 | 4/6 | 4/6 | 4/5 | 3/6 | 1/5 | 1/6 |
Measured through the kit on this branch's build (int8, M4 Max, 2026-09-23); the fp32 model picks
the same option on every row. decider-0.8b picked it on all 60 rows that fit its 4,096-token
context. A question past 26 options reads its own system line, so it prefills the state again
instead of reusing it.
The iPhone's 1,024-token limit belongs to the pipelined engine that chat generation runs on: on
iOS it caps a growing KV cache there, because the on-device compiler miscompiles that graph once
the cache reaches 2,048 positions. A decision loads a language model on the sequential engine,
which has no such cap. On an iPhone 17 Pro, minicpm5-2b answered all 111 JevBench hard items,
39 of them with prompts past 1,024 tokens (up to 3,789), with the Mac's answer on every one
(max |Δp| 0.0046; kit 0.7.1, iOS 27.0, 2026-09-24). A 255-option choice in the chat form,
3,100–4,200 tokens with short options, has not been run on a phone. A decision model takes a
long row there too: decider-0.8b's 255-option fixture row, 1,965 tokens, answered on the
iPhone 17 Pro in 70 s with the fp32 readout's argmax (2026-09-23, all 44 rows argmax-identical
there, max |Δp| 0.009), and qwen3.5-2b-decision's 1,743-token rows in 155 s — on a decision
model a wide choice is a slow call, not a Mac-only one.
decider-2b-vision (Mapika, Apache-2.0) is the decider transplanted into Qwen3.5-2B's
vision-language model and fine-tuned on game frames, The Cauldron's multiple-choice image tasks and
a replay of the text mixture. The same three shapes, about an image and a state (import CoreAIOps):
let a = try await CoreAI.decide(
image: frame, "You control the right paddle. The image shows the current game screen.",
["move": .choice("What should you do right now?", ["move paddle up", "move paddle down", "stay"]),
"ball": .noul("Is the ball moving toward your paddle?")],
grid: .g256)
a["move"]?.choice // "move paddle up"
a["move"]?.timing // imageSeconds (decode, resize, tower), decoderSeconds, and the totalEvery question of a call is one block of one prompt, and one pass reads every answer at its own
slot: each answer sees the image, the state and all the questions, but no other answer — the
author's multi-question contract (the text models above read one prompt per question). The keys are
laid out in sorted order. A choice lists up to 10 options, the letters A–J; a yes/no is the choice
no / yes; a score lists its levels as k: level and answers Σ k · p(k). KitVisionDecider is
the model-level form an app keeps warm, and decide(image: nil, …) there reads a text-only row
through the same weights.
The image is resized to a fixed square, its aspect ratio not kept:
grid |
tile | image tokens | for |
|---|---|---|---|
.g256 (default) |
256 × 256 | 64 | game frames, speed |
.g448 |
448 × 448 | 196 | photos; its 663 MB tower downloads the first time it is asked for |
On the author's own code, a photo read at g448 agrees with the processor's native grid on 0.980 of
150 Visual7W items and 0.940 of 100 VSR items, against 0.947 and 0.910 at g256 (the zoo's grid-price
table); on the 256×240 game frames the native grid is g256.
The host is the model zoo's apps/DeciderVision library, the code its gates ran: the image resized
in Pillow's pass order (a CGContext resize is a different image), the tower baked at the grid, the
author's prompt with the tower's rows as its image block, and a two-function decoder (S = 1 and
S = 16) in the bundle's chunk order, on the low-level runtime. Through decide-cli parity on the
zoo's fixture, the model downloaded from the catalog pin (M4 Max, macOS 27.0 26A428, Release,
2026-09-29):
| arm | runs | slots | ids and slots = the author's | argmax = the author's fp32 | full-vocabulary top-1 | max |Δp| | mean of run means |
|---|---|---|---|---|---|---|---|
g256 |
35 | 49 | 35/35 | 49/49 | 49/49 | 0.0103 | 0.00027 |
g448 |
35 | 49 | 35/35 | 49/49 | 49/49 | 0.0080 | 0.00026 |
| text only | 6 | 10 | 6/6 | 10/10 | 10/10 | 0.0027 | 0.00024 |
Against the zoo's own Swift run of the same files the letter logits are bit-equal on 108/108 slots,
and a second pass repeats them bit for bit. A decision, from the image file to the answers, took a
median 440 ms at g256 (143 tokens), 797 ms at g448 (275) and 346 ms text only (76), with another
job holding the GPU at 80–90 % — an upper bound; the zoo measured 322 / 584 / 284 ms on this Mac
with the GPU idle, from its AOT asset. The vision tower is 29 ms (g256) and 71 ms (g448) of it.
The iPhone has not been run through the kit. The zoo's gate app runs the same library on the same
.aimodels on an iPhone 18 Pro (device JIT, iOS 27.0): 108/108 argmax against the author, and one
game frame in 754–785 ms at g256 and 1,415–1,439 ms at g448, a text row in 725–735 ms. The app
needs com.apple.developer.kernel.increased-memory-limit — without it the decoder's first
specialization dies with std::bad_alloc — and the runtime's cache for the three graphs takes
6.7 GB on the phone; the decoder loads in 21.6 s the first time and 7.9 s from that cache.
This model is not behind /v1/systemone: systemone serve and decide-cli serve do not load it,
and systemone models does not list it. The wire's images field reaches clef-flash
(below).
clef-flash (Cloudflare, Apache-2.0, from Qwen3.5-9B) reads a whole request in one pass: the state as text or
JSON and at most one image, then every question with its options. A joint schema head reads the decoder's hidden
state at every position — each question's and each option's span, the last token, and each option's rows of the
model's 2.03 GB lm_head table — and gives each option a logit; each question is a float32 softmax over its own
options at temperature 1. Nothing is generated, and every answer sees every other question. A request takes up to
16 questions and 128 options in all. Mac only: the decoder alone is 15.9 GB. Its catalog kind is jointDecision:
systemone models lists it as that, TypedDecisions(catalog:) refuses it by name before downloading anything, and a
kit released before it (0.7.3 and earlier) decodes the kind as unknown and lists it nowhere.
let decider = try await KitClefDecider(catalog: "clef-flash") // 18.2 GB the first time
let r = try await decider.systemOne(SystemOne.Request(
state: "Our checkout started returning errors and orders are blocked.",
questions: ["department": .choice("Which team should handle the message?", ["billing", "technical"]),
"outage": .noul("Is a service down?")]))
r["department"]?.choiceOn the server it is systemone serve --model clef-flash, and an image goes in the request's images (a data URL,
base64, or a path on the server's machine) with grid 448 or 256. The host is the model zoo's apps/ClefFlash
library with every numeric path unchanged: the request rendered as the author's encode_record(), each piece
tokenized alone; the image resized by Pillow's own integer bicubic (a float resize lands one or two levels off on
some tiles) and read by a tower baked at the grid; the decoder 64 tokens per call from zeroed states; the head graph
and the table. Through decide-cli parity on the zoo's fixture, the model downloaded from the catalog pin (M4 Max,
macOS 27.0 26A428, Release, 2026-10-04):
| set | runs | ids and spans = the author's | argmax = the author's fp32 (top-2 margin > 0.02) | near-ties agreeing | max |Δp| | mean of run means |
|---|---|---|---|---|---|---|
| fixture (186 records) | 200 | 200/200 | 352/352 | 3/3 | 0.0122 | 0.00057 |
| held out (30 records) | 40 | 40/40 | 186/186 | 2/2 | 0.0082 | 0.00037 |
Against the zoo's own Swift run of the same files, the logits, the probabilities, the decoder's hidden rows and the
responses are bit-equal on all 240 runs, and the first run read again repeats them bit for bit. The fixture's 14
native runs, at the processor's own grid, are not run: no tower here has that grid. The zoo's gate fed 13 of them
the author's image rows; photo_01's 1,200 image rows are more than the decoder's 1,024-row buffer.
systemone serve --model clef-flash passes conformance/check.py, 22 of 22 requests and the three routes (a
choice past 128 options is the 422 that names the count). The fixture's receipt sent in images, as a data URL and
as a path, comes back with the zoo Swift run's probabilities to the wire's four decimals, 14 of 14 values. The
answers are in the server's form: confidence is 1 − normalised entropy, and usage counts the row once per
answer, as for every model here; metadata.row_tokens is the row, read once.
kev-0.8b and kev-4b (Jared Palmer, Apache-2.0: a rank-16 LoRA adapter and a pointer head on Qwen3.5-0.8B-Base and
Qwen3.5-4B-Base) read each question as its own row: the state, then the question and its options between the author's
delimiter tokens. The author's head compares the decoder's hidden state at the row's last token with the state at each
option's closing token. Each question is a softmax over its own options at the author's temperature, and nothing is
generated. A choice lists up to 255 options, and a row holds at most 3,968 tokens. Kev-0.8B (1.5 GB) runs on the Mac and
the iPhone, Kev-4B (8.4 GB) on the Mac. The catalog kind is rowDecision: TypedDecisions(catalog:) refuses it by name,
and a kit released before it (0.7.3 and earlier) decodes the kind as unknown and lists it nowhere.
let decider = try await KitKevDecider(catalog: "kev-0.8b") // 1.5 GB the first time
let r = try await decider.systemOne(try SystemOne.request(from: body)) // a text or JSON state
r["team"]?.choice
let prepared = try await decider.prepare(state: ticket) // the state's calls, once
let later = try await decider.decide(prepared: prepared, questionsJSON: questions)On the server it is systemone serve --model kev-0.8b; a request with images gets a 422 that names the model. The host
is the model zoo's apps/Kev library with every numeric path unchanged: the author's rendering of the state and the
options, the decoder 128 tokens per call from zeroed states, and the head on the host in float64. By default a request's
state runs once and each question continues from a copy of the decoder's states; on this graph that gives the same
hidden rows as running each row whole, bit for bit. Through decide-cli parity on the zoo's fixture, its 155 records
that carry their text, the model downloaded from the catalog pin (M4 Max, macOS 27.0 26A428, 2026-10-04):
| model | questions | row ids = the author's | argmax = the author's fp32 (top-2 margin > 0.02) | near-ties agreeing | max |Δp| | mean of row means |
|---|---|---|---|---|---|---|
kev-0.8b |
186 | 186/186 | 181/181 | 4/5 | 0.0124 | 0.00107 |
kev-4b |
186 | 186/186 | 182/182 | 3/4 | 0.0153 | 0.00081 |
Against the zoo's own Swift run of the same bundle, the hidden rows and the probabilities are bit-equal on every question
(186 of 186 for each model) and the answers on every record (155 of 155); the first record read again repeats them bit
for bit. systemone serve --model kev-0.8b passes conformance/check.py, 22 of 22 requests and the three routes (a
choice of 255 options makes a row past 3,968 tokens, the 422 that says so). The answers are in the server's form:
confidence is 1 − normalised entropy and usage counts each question's row; metadata.packed_tokens is the author's
count (the state once, then each question).
d1-omni-600m (Liquid AI, LFM Open License v1.0: an LFM2.5-Encoder-350M trunk with a typed decision head, a SigLIP2
vision tower and a FastConformer audio tower) reads each question as its own row: the state, then the question and each
option between the publisher's delimiter tokens, a <|mask|> marker before each option. One forward pass of a static
decision graph scores every position, and each option is read at its marker: on a text row divided by the publisher's
temperature for the question's type and option count, then softmaxed per question in float32. Nothing is generated. A
request can also carry images or one clip, not both: the vision graph reads each image crop, the audio graph the clip
(16 kHz mono 16-bit WAV, cut at 30 s), and their rows go in front of each question's ids. The decision graph has seven
lengths, 64 to 4,096 positions, and a row runs at the smallest one that holds it; a row longer than 4,096 positions is
refused (the publisher's own limit is 16,384). 6.5 GB, the same .aimodels on the Mac and the iPhone; each graph loads
the first time a row or a medium needs it. The catalog kind is omniDecision: TypedDecisions(catalog:) refuses it by
name, and a kit released before it (0.7.3 and earlier) decodes the kind as unknown and lists it nowhere. The weights'
licence allows commercial use below 10 million US dollars in annual revenue (its Section 5).
let decider = try await KitD1OmniDecider(catalog: "d1-omni-600m") // 6.5 GB the first time
let r = try await decider.systemOne(try SystemOne.request(from: body)) // a text or JSON state, `images` or `audio`
let heard = try await decider.systemOne(
state: nil, questions: [(id: "refund", question: .noul("Does the caller want a refund?"))], audio: .file(clip))
heard["refund"]?.noulOn the server it is systemone serve --model d1-omni-600m. A request carries one image in images, or one clip in
audio as a data URL, base64, or a path on the server's machine (read only on 127.0.0.1). With audio, state may be
left out: the model then reads the {} its audio questions were trained on, which an MCP client, whose schema asks
for a state, sends as "state": {}. For an image, "state": "" reads as no state. grid is refused: the image is
read at the publisher's own crops. The host is the model zoo's apps/D1Omni library with every numeric path unchanged:
the publisher's rows (caller text escaped so that it cannot write a delimiter, the state tokenized once per request),
an image decoded as PIL reads it (ImageIO, or libjpeg's arithmetic for a baseline JPEG whose components share one
sampling factor), vision.py's crops resized by torchvision's float32 antialias kernel, the clip's log-mel in float64,
and the publisher's readout. A JPEG with chroma subsampling or progressive coding goes to ImageIO, which the zoo found
up to 3 levels off PIL on a 4:4:4 photo and did not compare on such files.
The zoo measured one decision (the graph inputs, the calls, the readout; no tokenizing, image decoding or mel) on the same graphs:
| Mac (M4 Max, macOS 27.0) | iPhone 18 Pro (iOS 27.2) | |
|---|---|---|
| One question, 47 positions (L64) | 7.35 ms | 10.79 ms |
| One question on a 384 × 384 image (144 + 39 positions) | 38.3 ms | 57.67 ms |
| One question on 9.6 s of audio (121 + 63 positions) | 24.94 ms | 27.02 ms |
The Mac column is the Core AI Python runtime on the graphs compiled for the Mac's GPU inside a measurement window, the
iPhone column the zoo's Swift host in its gate app, the .aimodel specialized on the phone
(models/d1-omni-600m/gate-d1-omni-600m-timing-mac.json and gate-d1-omni-600m-iphone.json in the zoo). The kit was
not timed.
Through D1OmniTests (M4 Max, macOS 27.0 26A428, 2026-10-09), with the repo's files at the catalog pin: the zoo's 360
public fixture rows rebuilt by the kit with its swift-transformers 1.3.3 are the publisher's ids, markers and graph
lengths; 14 requests (20 text rows at 64 to 4,096 positions, 3 images, 3 clips) through KitD1OmniDecider(catalog:) give
the zoo's Swift host's marker logits and probabilities bit for bit on all 38 rows and the publisher's response text on
all 14 requests, an image and a clip again over the wire give the same probabilities, and only the six lengths those
rows need loaded. The iPhone has not been run through the kit; the zoo's gate app ran the same graphs on an iPhone 18
Pro (78 fixture rows within the gate), and the memory of several decision lengths loaded in one process there was not
measured.
d1-3b (Liquid AI, LFM Open License v1.0: LFM2.5-VL-3B post-trained as a decision model, a SigLIP2 vision tower and the
LFM2 hybrid decoder) reads each question as its own row in the provider's chat form: the pictures, the state (a text, or
JSON written as json.dumps(state, indent=2)), the question with its options as codes, then the assistant turn. The
decoder runs the row 64 tokens a call and returns the hidden state at every position; the host reads the last one
against the tied embedding rows of each option's tokens (a 2,134-row table that ships in the bundle), keeps each option's
highest logit and takes a softmax over the options, in float64. Nothing is generated. A choice lists up to 255 options on
the wire (the provider's own codes reach 1,639), a score up to 10 levels. A row holds at most 4,032 tokens and a
request's pictures at most 2,816 image tokens (one picture needs at most 2,810); a longer request is refused before any
graph call, in the provider's words. Two decoders ship with one contract: the Mac runs the fp16 one (5.6 GB), the iPhone
the one whose MLP linears are int8 per block of 32 (3.7 GB), and the catalog entry gives each platform its own. The
vision tower (854 MB) downloads when a request carries a picture. The catalog kind is tokenDecision:
TypedDecisions(catalog:) refuses it by name, so CoreAI.decide does too, and a kit released before it (0.7.3 and
earlier) decodes the kind as unknown and lists it nowhere. The weights' licence allows commercial use below 10 million
US dollars in annual revenue (its Section 5).
let decider = try await KitD1Decider(catalog: "d1-3b") // 5.6 GB to download on a Mac
let r = try await decider.systemOne(try SystemOne.request(from: body)) // a text or JSON state, `images`
let seen = try await decider.systemOne(
state: nil, questions: [(id: "circle", question: .noul("Is there a circle in the picture?"))],
images: [.file(picture)]) // nil: the provider's form without a state
let prepared = try await decider.prepare(state: ticket) // the state's whole calls, once
let later = try await decider.decide(prepared: prepared, questionsJSON: questions)On the server it is systemone serve --model d1-3b. A picture goes in images (a data URL, base64, or a path on the
server's machine, read only on 127.0.0.1); grid is refused, since the picture is read at the provider's own crops, and
audio gets a 422 that names the model. The wire's state is required, as for every model; the provider's form
without a state block is state: nil in Swift, and a JSON null in readout(requestJSON:). The provider's code takes
instructions, levels and descriptions as text only; on the wire one given as a JSON value reads as its JSON text, the
kit's rule for every model, while readout(requestJSON:) refuses it as the provider does. The host is the model
zoo's apps/D1 library with every numeric path unchanged: the provider's rows with the checkpoint's tokenizer
(swift-transformers, plus the three steps it does differently on this tokenizer: the added tokens cut out before the regex, the Split regex
on code points, ignore_merges), a picture decoded with ImageIO, capped at one megapixel and cut into the processor's
crops by torch's uint8 bicubic kernel written out, the tower once per crop, the decoder from zeroed states, and the
readout's products through the BLAS calls NumPy makes (CoreAIKitD1BLAS, a C target). By default a request's state
runs once and each question continues from a copy of the decoder's states; on this graph that gives the direct run's
hidden rows bit for bit. A JPEG decodes with ImageIO here and with libjpeg in the provider's Python path, and the pixels
can differ (by up to 57 levels with 4:2:0 chroma, the zoo found); a PNG gives the same pixels.
The zoo measured one decision (the picture's tower call and image rows, the decoder's calls, the readout; not building the rows or decoding the picture) on the shipped bundles:
| Mac (M4 Max, macOS 27.0) | iPhone 18 Pro (iOS 27.2) | |
|---|---|---|
| One question on the card's refund request (39 tokens) | 34.3 ms | 48.3 ms |
| Three questions on that state, shared | 102.2 ms | 147.1 ms |
| One question on a 3,470-token state | 1,888.0 ms | 2,795.9 ms |
| One question on a 384 × 384 picture (144 image tokens) | 202.3 ms | 569.3 ms |
The Mac column is the zoo's Swift host with the fp16 .aimodel specialized in its process, the kit's path; the iPhone
column is the zoo's gate app with the int8mlp .aimodel specialized on the phone, on USB power
(models/d1-3b/README.md in the zoo). The kit was not timed. A load with a cold cache specializes the decoder: 10.2 s and
10,368,248 KiB of the runtime's cache on the Mac, 31.8 s and 4,779 MB on the phone; with the cache warm a load took
0.7 s and 3.7 s. Against the provider's fp32 code the zoo's gates give max |Δp| 0.0039 for the Mac's decoder and 0.0197
for the iPhone's on 393 fixture questions; the iPhone's gives 0.0140 on 120 held-out ones, 0.0024 on 24 about pictures
and 0.0186 on the phone, with every argmax equal where the provider's top two are more than 0.02 apart.
An iPhone app needs com.apple.developer.kernel.increased-memory-limit in its entitlements: without it the decoder does
not load (the zoo saw its on-device specialization die with std::bad_alloc). The kit cannot add it; the app does.
Through D13BTests (M4 Max, macOS 27.0 26A428, 2026-10-09), with the Mac's decoder and the tower at the catalog pin (the
zoo's staged copy of the repo, checked file by file against the Hub): the zoo's 393 fixture rows rebuilt by the kit with
its swift-transformers 1.3.3 are host.py's ids, text and readout groups, and the request the provider refuses is refused
in its words; 28 questions (16 records of text, 2 pictures) through KitD1Decider(catalog:) give the Python reference's
hidden rows, logits and probabilities of the same bundle bit for bit, and the provider's response text on all 18
records; the shared prefix and a prepared state give the same bits (3 records of several questions, up to 3,424 tokens
of state); max |Δp| against the provider's fp32 oracle 0.0022, every argmax equal. The five refusals come before any
graph call: a row past 4,032 tokens and a label outside the option table in the provider's words on the wire too, a
score of eleven levels and a question without instructions in the provider's words through readout(requestJSON:) and
in the wire's own on the wire, pictures past 2,816 image tokens through the Swift API (the wire carries one picture).
systemone serve --model d1-3b passes conformance/check.py, 22 of 22 requests and the three routes, and a picture
sent in images as base64 and as a path gets the same answers. The iPhone has not run through the kit; the zoo's gate
app ran the same int8mlp bundle on an iPhone 18 Pro.
Ten whole uses, one action and the complete result, from the same sources on the Mac and the
iPhone (Examples/Decide):
| Use | What you do → what you get |
|---|---|
| Autofill | copy an email → every field of a checkout form fills at once |
| Checklist | open a contract → a list of verdicts to your questions |
| Sorter | open a folder → what needs you first, then everything filed; Move files does it |
| Search | a query and passages → ranked by one decision each |
| Speech gate | speak → only the utterances meant for the assistant pass |
| Drive | press Drive → the model drives a car, one lane choice per tick |
| Columns | open a CSV → the columns you asked for, every row; sort, save |
| Command guard | an agent's command log and a policy in plain words → run / ask / refuse (also a PreToolUse hook) |
| Context | an agent transcript's tool results → the unrelated ones dropped, tokens counted |
| Typing | type → tone, intent and an emoji at every pause |
Clips of each on the Mac and on the iPhone are in
coreai-assets kit/decide.
Sources/CoreAIKit/Decide/—TypedDecisions(the model-level API: prefill once, decide N times;systemOne(_:)answers a whole request in the hosted form),Decision(the value types),DecisionPrompt/DeciderPrompt/SlotPrompt(the three renderings: a chat model's JSON turn, a decision model's plain text, a slot-head model's control tokens),SystemOneWire+OrderedJSON(the hosted forms),SystemOneCall(SystemOne.Response: a whole request's typed answers and its wire object),SystemOneServer(the endpoint over Network.framework — the same code listens in an app,host: "0.0.0.0"for the local network),DecisionQueue(one decision at a time over a shared model), andSystemOneMCPServer+SystemOneMCP(the same decisions as Model Context Protocol tools over stdio, and the message forms).Sources/CoreAIKit/Decide/FoundationModelDecisions.swift— Apple's on-device foundation model (FoundationModels,SystemLanguageModel.default) as a backend: each question one guided generation under the answer's schema, the answer one-hot, no probabilities;DecisionBackendis what it andTypedDecisionsshare and whatSystemOneServertakes.Sources/CoreAIKit/Decide/Encoder*.swift— an encoder-type model (laya):EncoderPrompt(the publisher's row builder; recent questions' tokens are kept),EncoderReadout(its host decoder: marker logits, act features, the temperature by question type and option count) andEncoderDecider(the bundle'smainandactfunctions;decideRowgives the raw numbers).TypedDecisionsis the API over them.Sources/CoreAIKit/DeciderVision/— decider-2b-vision:KitVisionDecider(the model-level API;readoutgives the row, every slot's letter logits and each stage's time),DeciderVisionPreprocessor(the image resized in Pillow's pass order, cut into the tower's patches),DeciderVisionPromptRenderer(the author's prompt and slot rule) andDeciderVisionRuntime(the tower and the two-function decoder on the low-level runtime), ported from the model zoo'sapps/DeciderVision.Sources/CoreAIKit/ClefFlash/— clef-flash:KitClefDecider(the model-level API and aDecisionBackend;readoutgives the row, every option's logit and the hidden state's digest), and the model zoo'sapps/ClefFlashhost beside it, ported with every numeric path unchanged (ClefPromptBuilder,ClefRenderer,ClefImagePreprocess,ClefDecoder,ClefHead,ClefPipeline).Sources/CoreAIKit/Kev/— Kev:KitKevDecider(the model-level API and aDecisionBackend;prepare(state:)keeps a state for later questions;readoutgives the rows, each row's hidden digest, and every option's logit and p), and the model zoo'sapps/Kevhost beside it, ported with every numeric path unchanged (KevEncoder,KevDecoder,KevHead,KevPipeline).Sources/CoreAIKit/D1Omni/— d1-omni:KitD1OmniDecider(the model-level API and aDecisionBackendthat reads images and audio;readoutgives each row, its graph length and the marker logits, and the response in the publisher's form), and the model zoo'sapps/D1Omnihost beside it, ported with every numeric path unchanged (D1Prompt,D1Readout,D1DecisionGraph,D1MediaGraphs,D1ImagePreprocess,D1AudioPreprocess,D1OmniPipeline).Sources/CoreAIKit/D1/— d1-3B:KitD1Decider(the model-level API and aDecisionBackendthat reads pictures;prepare(state:)keeps a state for later questions;readoutgives each row's ids, hidden digest, logits and p, and the response in the provider's form), and the model zoo'sapps/D1host beside it, ported with every numeric path unchanged (D13BRequest,D13BEncoder,D13BVision,D13BImagePixels,D13BTower,D13BDecoder,D13BReadout,D13BPipeline);Sources/CoreAIKitD1BLAS— the readout's float64 products in C, through the BLAS calls NumPy makes.Sources/CoreAIOps/CoreAI+Decide.swift—CoreAI.decide, the one-call op (decide(image:…)for an image);CoreAI+SystemOne.swift—CoreAI.systemOne, the same op in the hosted request and response forms (the model fromoptions, else the request'smodel, else the default).Sources/systemone— thesystemonebinary (serve | ask | models | mcp), built, signed and notarized per tag by.github/workflows/release.yml; the Homebrew formula lives in john-rocky/homebrew-tap.Examples/Decide/CLI—decide-cli ask | bench | oracle | parity | filter | serve | mcp.- Android: the same request and answer forms over LiteRT decision encoders are in hfmodels-android (typed-decisions branch).
- Other servers that speak the same route, for a client that switches between them by base
URL: jev-rs (Rust, over
llama-server), System One Lite (MLX); the vendor-neutral system-one SDK reaches any of them.