Live checks¶
A live check holds every real turn of an agent to a rule while the turn happens. It reads the question a person actually asked, or the answer the model actually gave, and decides on the spot whether that turn may go on. Three examples:
- "the answer never contains a bank account number" — a pattern on the answer;
- "the question is about our products" — a judge model on the question;
- "the answer stays polite" — a judge model on the answer, scored afterwards on a sample of turns.
You write live checks on an agent version, next to its model and instructions. Every call that names the agent is held to them, whoever calls it.
Live checks and evaluations¶
An evaluation and a live check use the same kinds of check, so a rule means the same thing in both. They differ in when the rule is applied and in what happens when it does not hold.
| Evaluation | Live check | |
|---|---|---|
| What it reads | conversations you wrote yourself | the conversations your callers really have |
| When it runs | when you start a run, or on a schedule | on every turn of the agent |
| What a failure does | lowers the run's score | stops the turn, or is recorded and counted |
| What it answers | "is this version good enough?" | "did this turn keep to the rule?" |
Use an evaluation to decide whether to publish a version. Use a live check to enforce what must hold on every single turn, and to see how often a rule is broken in practice.
Input or output¶
A check looks at one of two places, its stage:
input— what the caller wrote in this request, before any model is called: the user and developer messages, the transcript of a recording, and the values the request fills into the agent's prompt. Attached files, images and tool output you send back are not read. On a thread, only the new request is read; the messages already stored are not checked again.output— the answer the model gives, when a round ends without asking for a tool. Two kinds, "does not contain" and a pattern, also read the text a model writes beside a tool call, so that forbidden text never reaches a tool either.
Block or background¶
A check also has a mode: what happens when it fails.
- Block. The check decides on the turn itself. A failed input check answers the caller with the agent's standard reply and no model is called. A failed output check withholds the answer. Rule checks take microseconds; a judged check adds one model call to the turn.
- Background. The check never touches the turn. After the turn is stored, a judge model scores the answer on a sample of turns — one in ten unless you choose otherwise — and the result is recorded. Use it for a quality you want to watch rather than enforce, such as tone. Only a judged check on the output can run in the background, and only a turn stored on a thread is judged.
A check that cannot decide — the judge model did not answer in time, say — is errored. A blocking
check that errors blocks the turn, as a failed one does, unless you set on_error: "pass" on that
judged check: then the turn goes on as if the check had passed, and the result is still recorded as
errored and counts toward its alert. That trades safety for availability, so it suits a check such
as tone, never a check that guards confidential data. Left unset, a check never lets a turn through
because it could not be done.
What the caller sees¶
The answer keeps the shape of the vendor API you call, so a client that already handles a model's
refusal or a cut-off answer handles a live check too. The X-Core-Check response header says which
check stopped the turn, with a fixed code, and never the pattern, the expected text or a judge's
reasoning.
| The turn | The response | X-Core-Check |
|---|---|---|
| passed every check | the answer, as always | absent |
| an input check refused it | completed, with one message whose only part is a refusal holding the standard reply |
code=input_check_failed |
| an output check withheld the answer | incomplete, with incomplete_details.reason: "content_filter" |
code=output_check_failed |
| the model was asked again and its answer passed | the new answer, as an ordinary answer | absent |
| the model was asked again and its last answer still failed | incomplete, content_filter |
code=recovery_exhausted |
| a blocking check could not decide | refused or withheld as above | code=check_errored |
| the model itself declined | completed, a refusal in the model's own words |
absent |
On POST /messages both a refused question and a withheld answer are stop_reason: "refusal".
The check's name is part of the header, so a name never holds anything secret.
Streaming¶
A blocking output check has to read the answer before the caller does, so it changes how a streamed answer arrives:
- Only pattern checks. The answer streams as usual, a few characters behind the model: Core
holds back as many characters as the longest match a pattern allows (at most 200). On a match the
answer stops where the match starts. What came before has been sent, the match never is, and the
turn ends
incompletewithcontent_filter. - Any other blocking output check. The answer arrives in one piece once every check has passed. The stream still carries the reasoning summaries and the tool calls as they happen, unless a check asks for the answer again: then nothing of a round, its reasoning included, is shown until the answer passed.
- Input checks run before the stream starts; a judged one delays the first token by one judge call.
Asking the model again¶
A blocking output check can ask for recovery: when the answer fails it, Core asks the model again, at most the agent version's number of attempts (two unless you choose one to three), and the caller receives the answer that passed. For example: a person asks the agent to swear, the answer contains a forbidden word, and the person only ever sees "I'm not allowed to do that."
- The model never sees the rejected answer. Core tells it the rule the answer broke — the check's description, or its expected text, or its name — and that it must not mention a correction.
- A retry may not call tools, so a retry never repeats an action.
- Every attempt is a model call that counts on the key that ran the turn.
- Recovery works where the caller has not seen any of the answer yet: a turn that is not streamed, a background run, and a streamed turn whose answer is held in one piece. When an answer was already streaming, a failed check cuts it instead.
- An answer is asked for again only when every blocking check it failed asks for recovery. A check
without recovery, or one that could not decide, withholds the answer at once; one that could not
decide with
on_error: "pass"withholds nothing and asks for nothing.
The judge¶
A judged check — correct format, polite tone, no made-up facts, or your own rule — is decided by a model, the same judge an evaluation uses. On the input it reads the caller's text and your rule; on the output it reads the last user message, the answer and your rule. It never reads the agent's instructions, its tools or the rest of the conversation.
The judge is the check's own judge model, or else the model the turn ran on. An agent whose model
is a router has to name a judge model on every judged check, because a router picks a different
model per turn. The same holds for an agent without a model of its own that a caller calls with a
router as the model: a judged check without a judge model then cannot decide, and a blocking one
stops the turn unless it says on_error: "pass". The judge's tokens count on the key that ran the turn, like the turn itself. A key
held to certain regions only reaches a judge model in those regions; outside them the check is
errored, and its on_error decides as for any other error.
What is recorded¶
Every check decides one result per turn: passed, failed or errored, with a score for a judged
check and a reason. A rule's reason is a fixed sentence; a judge's is its own sentence. The results
are stored with the turn, expire with it under your retention window, and are erased with the
person the turn belongs to.
- Per turn. The results of one turn can be read back through the API and on the conversation's page in the console.
- Pass rates. Per check, how many results passed, failed or could not decide over a period.
- Alerts. A check can raise a notification when too many of its latest results did not pass: at most one per check per day, by mail or Slack, to the organisation's administrators.
- Events. Every result that did not pass is a
response.check_failedevent on your event feed and webhooks, with the check's name and never any text.
Where checks apply¶
Live checks belong to the agent version. A change to them is a new version, which you can save as a draft, measure with an evaluation and then publish, like any other change to the agent.
- Every turn that runs the agent applies its checks: a call that names it, an agent called as a tool by another agent, a background run, and a step of an evaluation run. An evaluation run applies the blocking checks, recovery included, so a suite measures what a caller receives; it skips the background checks.
- A turn that Core starts itself, where nobody waits for a reply — a workflow step, a task started in the background — is not answered with the standard reply when an input check fails: the step ends refused and the task is not started.
- Prompts and model routers carry no checks of their own.
Read on¶
| API | Console | |
|---|---|---|
| Live checks | Live checks through the API | Live checks in the console |
| Agents | Agents through the API | Agents in the console |
| Evaluations | Evaluations through the API | Evaluations in the console |
The notification a failing check raises is set on Notifications.