Skip to content

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 incomplete with content_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_failed event 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.