System One models
A System One model answers typed questions about some content. It returns typed answers fast. It does not write text. Each answer comes with a confidence.
Your worker asks these questions in the middle of a turn. The engine runs the call, saves it, and sends the answers back as a decision. The engine never asks on its own.
Declare a block
Declare a [system1.<id>] block, the same way you declare an LLM.
[system1.typesafe]
type = "typesafe"
model = "jev-latest"
[system1.cf]
type = "workers-ai"
model = "clef-flash"
account_id = "0123abcd…"type | Calls | Key |
|---|---|---|
typesafe | TypeSafe's POST /v1/systemone. | $TYPESAFE_API_KEY |
workers-ai | Cloudflare Workers AI, for example Clef. | $CLOUDFLARE_API_TOKEN |
http | Any endpoint that takes and returns the same shapes as TypeSafe. | none, or api_key_env |
The key stays in the engine. Your worker never holds it.
Use http for a model you host yourself, or for fixed answers in tests.
[system1.local]
type = "http"
url = "http://localhost:8080/v1/systemone"
model = "my-model"On a deployment, upload the key with subs auth system1.<id>. A workers-ai
block there names its account_id, and an http block's url must be a
public https address.
Ask a question
Return a system1.call action. Each question has a name and a type.
{
"type": "system1.call",
"id": "spam:turn_1",
"questions": {
"spam": { "type": "noul", "instructions": "Is this message spam?" }
}
}- Without
id, the engine makes one. - Without
system1, the call runs on the only block the file declares. - Without
model, the call uses the block'smodel. - Without
state, the model reads the conversation as a list of roles and text.
Send your own state to ask about something else. It is a string, an object,
or an array.
{
"type": "system1.call",
"system1": "cf",
"model": "clef",
"state": "I was charged twice. Please help.",
"questions": {
"team": {
"type": "choice",
"instructions": "Which team handles this?",
"criteria": { "billing": "Money and invoices", "technical": "Bugs and outages" }
}
}
}Get the answers
When the call ends, your worker gets a system1.finished decision.
{
"type": "system1.finished",
"id": "spam:turn_1",
"ok": true,
"model": "jev-1.13",
"answers": { "spam": { "type": "noul", "noul": 0.03 } },
"usage": { "input": 120, "output": 12, "uncached_input": 120, "cache_read": 0, "cache_write": 0, "total": 132 }
}A failed call has ok: false and an error.
The engine proposes nothing for this trigger. Your worker decides what happens
next. To go on with the turn, return an llm.call. To end it, return done.
Question types
type | Asks | Answer |
|---|---|---|
noul | Is it true? | noul: the probability of yes. |
choice | Which option? criteria maps each option to when it applies. | choice, confidence, probabilities. |
score | Where on a scale? criteria lists the levels in order. | score, confidence, legend, probabilities. |
box | Where in the image? Needs an image in assets. | box edges and confidence. |
An answer keeps every field the provider sends, including fields the engine does not name.
The engine knows what some models answer. It checks the question types and the limits before it makes the call.
| Block and model | Types | Limits |
|---|---|---|
typesafe, jev-* | all four | none |
workers-ai, clef and clef-flash | noul, choice, score | Each question needs instructions, and 2 or more options or levels. Question names are 1 to 100 letters, digits, _, . or -. At most 4 images. |
A call with more than 64 questions to Clef goes out as several requests. You
get one system1.finished with all the answers.
For any other model, the engine sends the call and the provider decides. To have the engine check a new model, list its types on the block.
[system1.cf]
type = "workers-ai"
model = "clef-2"
questions = ["noul", "choice", "score"]Images
An asset is an image from the session, or a data: URI. Its key is a
placeholder. Use the key in the state, the instructions, or the criteria.
{
"type": "system1.call",
"state": "Receipt: img-1",
"assets": { "img-1": { "type": "image", "uri": "blob://…" } },
"questions": { "total": { "type": "box", "instructions": "The total." } }
}The engine stores a data: URI as a blob, so the log holds a reference and not
the bytes.
Clef does not read placeholders. It puts the images before the state, in the order of their keys.
Guard a tool
Ask before a tool runs. The tool call waits, the same as an async tool.
if (trigger.type === "tool.execute" && trigger.name === "refund") {
pending[trigger.id] = trigger;
return {
actions: [{
type: "system1.call",
id: `guard:${trigger.id}`,
questions: { asked: { type: "noul", instructions: "The user asked for this refund." } }
}]
};
}
if (trigger.type === "system1.finished" && trigger.id.startsWith("guard:")) {
const call = pending[trigger.id.slice("guard:".length)];
if (trigger.ok && trigger.answers.asked.noul >= 0.9) {
return { actions: [{ type: "tool.result", id: call.id, result: runRefund(call) }] };
}
return { actions: [{ type: "tool.error", id: call.id, error: "The user did not ask for this." }] };
}What the engine does for you
- It saves each call and its answers in the session log. After a crash it does not run a finished call again.
- It retries rate limits and server errors. Set the policy with
[agent.<id>.retry] system1. See Retries. - It stops an open call when the turn ends, when the run is cancelled, or when the branch changes.
- An open call keeps the turn open.
- It adds the call's cost and tokens to the turn and the session.
turn_calls.system1onturn.completedshows them apart from the model's. Set the price per million input tokens on the block.
[system1.cf]
type = "workers-ai"
model = "clef-flash"
price = { input = "0.038" } # Cloudflare's price for clef-flash. clef is 0.24.