Reference
Steps
A Step here is the payload a monitor or protocol receives, and Context is what it knows about the sample; both are unrelated to inspect_ai’s StepEvent and step().
BeforeToolCall
A tool call about to execute.
@dataclass(frozen=True)
class BeforeToolCallAttributes
conversationstr-
Id linking this agent’s steps across compactions.
messagestr-
Assistant text alongside the call.
callToolCall-
The call as it will execute.
viewToolCallView-
Rendering of the tool call and its context: custom when the tool registers a viewer, a default Python-call rendering otherwise.
inputlist[ChatMessage]-
Exactly what the model was sent for the generate that produced the call: what a monitor should judge the call against.
context.inputis the sample’s input, the assignment. historylist[ChatMessage]-
The scaffold’s full conversation, pre-compaction turns included. In-process this is the scaffold’s own list; monitors must not mutate it.
escalationstuple[Reported[Decision], ...]-
Earlier links’ escalations, when this step is inside a
sequentialcomposition.
AfterToolCall
A tool call that has executed, before the model sees its result.
@dataclass(frozen=True)
class AfterToolCallAttributes
conversationstr-
Id linking this agent’s steps across compactions.
messagestr-
Assistant text alongside the call.
callToolCall-
The call as executed, after any
modify. resultChatMessageTool-
What the model will see, after truncation.
outputToolResult-
The tool’s untruncated return value.
viewToolCallView-
Rendering of the tool call and its context: custom when the tool registers a viewer, a default Python-call rendering otherwise.
inputlist[ChatMessage]-
Exactly what the model was sent for the generate that produced the call: what a monitor should judge the call against.
context.inputis the sample’s input, the assignment. historylist[ChatMessage]-
The scaffold’s full conversation, pre-compaction turns included. In-process this is the scaffold’s own list; monitors must not mutate it.
escalationstuple[Reported[Decision], ...]-
Earlier links’ escalations, when this step is inside a
sequentialcomposition.
Step
The union, for code that handles any stage: a protocol, or a dispatcher.
Step: TypeAlias = BeforeToolCall | AfterToolCallReport types
Observation
Evidence for a protocol to calibrate and threshold. What a monitor returns.
class Observation(BaseModel)Attributes
suspicionSuspicion-
How suspicious. Required: an observation with no score says nothing.
explanationstr | None-
Why, for a reader of the transcript.
referenceslist[Reference]-
Messages and events in the transcript the report points at, which Inspect View links from cites such as
[M22]inexplanation. metadatadict[str, Any] | None-
Author-supplied structured context, recorded verbatim.
Methods
- score
-
An observation that sets
suspicionto the given score.@classmethod def score( cls, suspicion: Suspicion, explanation: str | None = None, *, references: Sequence[Reference] = (), ) -> Observation - flag
-
A detector’s observation: suspicion
1.0, for a protocol to act on or not.@classmethod def flag( cls, explanation: str | None = None, *, references: Sequence[Reference] = () ) -> Observationexplanationstr | NonereferencesSequence[Reference]
Decision
What should happen at a step. What a protocol returns; advisory to any protocol wrapping it unless passed to decide_final().
class Decision(BaseModel)Attributes
actionAction-
What should happen at this step. Required.
auditbool-
Request that oversight budget be spent on this step. Recorded for offline analysis and shown in the viewer as a flag; nothing acts on it yet.
modifiedToolCall | None-
Replacement for the step’s subject, for a
modifydecision. explanationstr | None-
Why, for the log. The agent never sees it.
referenceslist[Reference]-
Messages and events in the transcript the report points at, which Inspect View links from cites such as
[M22]inexplanation. messagestr | None-
Text the agent reads when the step is rejected. The agent sees nothing else from a decision.
metadatadict[str, Any] | None-
Author-supplied structured context, recorded verbatim.
Methods
- proceed
-
Let the step proceed unchanged: a decision with action
continue.@classmethod def proceed( cls, explanation: str | None = None, *, references: Sequence[Reference] = () ) -> Decisionexplanationstr | NonereferencesSequence[Reference]
- reject
-
Reject the step, telling the agent it was rejected: with
messageif given, else with the host’s default text.@classmethod def reject( cls, explanation: str | None = None, *, message: str | None = None, references: Sequence[Reference] = (), ) -> Decisionexplanationstr | Nonemessagestr | NonereferencesSequence[Reference]
- terminate
-
End the sample.
@classmethod def terminate( cls, explanation: str | None = None, *, references: Sequence[Reference] = () ) -> Decisionexplanationstr | NonereferencesSequence[Reference]
- escalate
-
Decline to decide and pass the step to the next decider.
@classmethod def escalate( cls, explanation: str | None = None, *, references: Sequence[Reference] = () ) -> Decisionexplanationstr | NonereferencesSequence[Reference]
Suspicion
How suspicious a step is: one finite score, or a non-empty dict scoring several dimensions independently.
Suspicion: TypeAlias = SentinelSuspicionAction
What a protocol can decide about the step it examined.
Action: TypeAlias = SentinelActionReport
What the transcript records.
Report: TypeAlias = Observation | DecisionReported
A report with the identity of the configured instance that produced it.
@dataclass(frozen=True)
class Reported(Generic[R_co])Attributes
namestr-
Instance name: the mapping key, or the registry name without its package prefix.
pathstr-
Instance path, e.g.
attempt/internet_attempt. reportR_co-
The observation or decision this instance produced.
functionstr-
The
__name__of the function that produced the report, which tells apart the functions of an instance whose factory returned several.
Monitors and protocols
Protocol is a control protocol, not typing.Protocol.
monitor
Register a monitor factory.
Use as @monitor, or as @monitor(name=..., version=...). The factory returns a function that must be async, take (context, step), annotate step with exactly one stage payload, and be annotated -> Observation, or -> Observation | None if it can abstain. These are checked when the factory is called. Each function watches one stage; to watch several, return a MonitorGroup of functions, which together are one configured instance. The factory must return fresh functions on each call; a shared function would make two configured instances indistinguishable in the log and the store. A function must let a cancellation exception propagate; one that swallows it can report after a sibling has already decided terminate.
A configured function runs only through the runners while a sentinel is running: a protocol that calls one directly from its body raises RuntimeError, and must call it through run_monitors or run_children instead. Outside a run, as in a unit test, a direct call works.
def monitor(
factory: Callable[P, Monitor | MonitorGroup] | None = None,
/,
*,
name: str | None = None,
version: int = 0,
) -> Callable[P, Monitor | MonitorGroup] | _MonitorDecoratorfactoryCallable[P, Monitor | MonitorGroup] | None-
A function returning a monitor, or a MonitorGroup.
namestr | None-
The registered name, in place of the factory’s
__name__. versionint-
The monitor’s version, recorded in its registry metadata so a calibration can say which version it measured; bump it when a change alters the scores. Defaults to 0.
protocol
Register a protocol factory.
Same contract as @monitor, with each returned function annotated -> Decision, or -> Decision | None if it can abstain, and step may also be annotated Step for a protocol that runs at every stage, such as a composition that only forwards the step to its children. To configure several functions as one instance, return a ProtocolGroup; a function among them that returns terminate or calls decide_final() ends the instance’s run, and those after it do not run and are not recorded. The factory must return fresh functions on each call; a shared function would make two configured instances indistinguishable in the log and the store. A function must let a cancellation exception propagate; one that swallows it can report after a sibling has already decided terminate.
As with @monitor, a configured function called directly from another protocol’s body during a run raises RuntimeError; call it through run_protocols or run_children.
def protocol(
factory: Callable[P, Protocol | ProtocolGroup] | None = None,
/,
*,
name: str | None = None,
version: int = 0,
) -> Callable[P, Protocol | ProtocolGroup] | _ProtocolDecoratorfactoryCallable[P, Protocol | ProtocolGroup] | None-
A function returning a protocol, or a ProtocolGroup.
namestr | None-
The registered name, in place of the factory’s
__name__. versionint-
The protocol’s version, recorded in its registry metadata; bump it when a change alters its decisions. Defaults to 0.
Monitor
A monitor: observes a step at one stage and reports a suspicion score, or abstains.
Monitor: TypeAlias = (
Callable[[Context, BeforeToolCall], Awaitable[Observation | None]]
| Callable[[Context, AfterToolCall], Awaitable[Observation | None]]
)Protocol
A control protocol, not typing.Protocol: decides what happens at a step, or abstains. Annotating Step runs it at every stage.
Protocol: TypeAlias = (
Callable[[Context, Step], Awaitable[Decision | None]]
| Callable[[Context, BeforeToolCall], Awaitable[Decision | None]]
| Callable[[Context, AfterToolCall], Awaitable[Decision | None]]
)MonitorGroup
Several monitor functions that form one monitor instance.
Return one from a @monitor factory in place of a single function. Together the functions are one configured instance, sharing its name, path and store_as namespace; each runs, in the order given, at the stage it watches, sequentially since they share one store, and each report records which function made it. A group is not callable: hand it to a protocol, which runs it through run_monitors or run_children.
@final
class MonitorGroup(Group)Attributes
functionstuple[Monitor, ...]-
The member functions, in the order they run.
Methods
- __init__
-
Group monitor functions into one instance.
def __init__(self, *functions: Monitor) -> None*functionsMonitor-
At least one monitor function, each with a distinct
__name__.
ProtocolGroup
Several protocol functions that form one protocol instance.
Return one from a @protocol factory in place of a single function. As with MonitorGroup, the functions share one name, path and store_as namespace and run in the order given at the stages they accept; a function that returns terminate or calls decide_final() ends the instance’s run, and those after it do not run and are not recorded. A group is not callable: hand it to a protocol, which runs it through run_protocols or run_children.
@final
class ProtocolGroup(Group)Attributes
functionstuple[Protocol, ...]-
The member functions, in the order they run.
Methods
- __init__
-
Group protocol functions into one instance.
def __init__(self, *functions: Protocol) -> None*functionsProtocol-
At least one protocol function, each with a distinct
__name__.
Monitors
Monitors handed to a protocol, named by mapping key or by registry name without its package prefix.
Monitors: TypeAlias = (
Mapping[str, Monitor | MonitorGroup] | Sequence[Monitor | MonitorGroup]
)Protocols
Protocols handed to a protocol, named the same way.
Protocols: TypeAlias = (
Mapping[str, Protocol | ProtocolGroup] | Sequence[Protocol | ProtocolGroup]
)Children
Monitors and protocols together, for the compositions that record either.
Children: TypeAlias = Mapping[str, Child] | Sequence[Child]Shipped protocols
observe
Record every monitor’s observation and never act.
What a bare monitor or a list of monitors resolves to, and how a benign score distribution is collected before anything is configured to act on it.
@protocol
def observe(monitors: Monitor | MonitorGroup | Monitors) -> ProtocolmonitorsMonitor | MonitorGroup | Monitors-
The monitor, or monitors, to run at every step they watch.
concurrent
Run every child at once; the strictest decision wins.
What a list containing a protocol resolves to. Monitors are recorded in the transcript; protocols vote by terminate > reject > modify > escalate > continue, the first in configuration order winning a tie, so a peer’s continue does not override an escalate and the layer escalates when that is the strongest vote. A modify when another protocol also decided anything but escalate becomes a reject naming the modifier, since the others decided about the call as it stood; votes count by instance, so another function of the modifier’s own instance contests it only by also modifying; the rejection carries the modifier’s audit and metadata, and its explanation leads with the modifier’s. When more than one protocol decided, the layer’s explanation lists each one’s decision after the winner’s own, labelling a function of a ProtocolGroup or MonitorGroup name.function. A child that calls decide_final() ends the step and no vote is taken.
@protocol
def concurrent(
children: Monitor | MonitorGroup | Protocol | ProtocolGroup | Children,
) -> ProtocolchildrenMonitor | MonitorGroup | Protocol | ProtocolGroup | Children-
A monitor, protocol or group, or several, to run together.
threshold
Act on suspicion scores: reject when any score reaches its reject threshold, terminate when any reaches its terminate threshold.
A threshold is one number for every score, or a mapping that sets one per dimension. In a mapping, "<monitor>.<dimension>" applies to one dimension of one monitor (the monitor’s instance name, as in Reported.name), a bare "<dimension>" applies to that dimension from every monitor, a monitor reporting a single number is keyed by its instance name, and "*" applies to anything unlisted. The most specific key wins: qualified, then bare, then "*". A score no key covers never triggers. A number is shorthand for {"*": number}. A dimension name containing . must be qualified.
Terminate is checked first. A reject or terminate is explained for the log: it names the highest-scoring score that crossed (the first in configuration order on a tie) and carries its monitor’s explanation and references, e.g. suspicion 0.95 from audit: posts credentials to an external host when the threshold came from "*" or the score is a single number, and sabotage 0.72 ≥ 0.60 from audit: edits the tests when it came from the dimension’s own key. Neither sets message, so the agent gets the host’s default “not approved” text and learns nothing about what the monitors saw.
The smallest protocol, and the one calibration feeds: the constants come from a benign score distribution, which is why they live here and not on a monitor. Every monitor, and every function of a MonitorGroup, must watch BeforeToolCall, since that is the only stage a reject is legal at.
@protocol
def threshold(
monitors: Monitor | MonitorGroup | Monitors,
reject_at: float | Mapping[str, float],
terminate_at: float | Mapping[str, float] | None = None,
) -> ProtocolmonitorsMonitor | MonitorGroup | Monitors-
The monitor, or monitors, whose scores are thresholded.
reject_atfloat | Mapping[str, float]-
Suspicion at or above which the call is rejected: one number, or a mapping by dimension.
terminate_atfloat | Mapping[str, float] | None-
Suspicion at or above which the sample is ended, in the same form;
Nonenever terminates. Where it andreject_atboth cover a score, it must be abovereject_at.
Runner
run_monitors
Run monitors concurrently and collect their observations in configuration order.
A typed shortcut for run_children over monitors only: its parameter rejects a protocol and its return type is the observations alone.
Derives each child’s context under this layer’s path and records every observation, including the ones the caller goes on to ignore. A monitor that abstained or does not watch this stage contributes nothing, so the result may be empty. An instance whose factory returned several functions contributes one observation per function that reported, in the order the factory returned them.
async def run_monitors(
monitors: Monitor | MonitorGroup | Monitors, context: Context, step: Step
) -> ObservationsmonitorsMonitor | MonitorGroup | Monitors-
One monitor or MonitorGroup, named by its registry name without the package prefix; a sequence of them, named the same way; or a mapping of instance names to them.
contextContext-
This layer’s context.
stepStep-
The step being examined.
run_protocols
Run protocols concurrently and collect their decisions in configuration order, cancelling the rest at their next await when one returns terminate or calls decide_final().
A typed shortcut for run_children over protocols only: its parameter rejects a monitor and its return type is the decisions alone. A decide_final() from any protocol at any depth below propagates out of this call, so the caller’s own decision logic does not run.
async def run_protocols(
protocols: Protocol | ProtocolGroup | Protocols, context: Context, step: Step
) -> DecisionsprotocolsProtocol | ProtocolGroup | Protocols-
One protocol or ProtocolGroup, a sequence of them, or a mapping of instance names to them, named as for
run_monitors. contextContext-
This layer’s context.
stepStep-
The step being examined.
run_children
Run monitors and protocols together in one task group, cancelling the rest at their next await when a protocol returns terminate or calls decide_final().
Runs any mix of children; run_monitors and run_protocols are typed shortcuts for one family. A child cancelled this way is recorded through Recorder.cancelled; one that finishes without awaiting is recorded normally.
async def run_children(
children: Child | Children, context: Context, step: Step
) -> Reportsdecide_final
End the step with this decision.
Nothing above the calling protocol runs; siblings still in flight are cancelled and recorded as such. First call wins if two race.
def decide_final(decision: Decision) -> NoReturndecisionDecision-
The outcome for this step.
Observations
What a layer’s monitors reported, in configuration order.
class Observations(_ReportSequence[Observation])Methods
- max_suspicion
-
The highest suspicion reported, taking the highest dimension of a structured score;
Noneif nobody observed.def max_suspicion(self) -> float | None
Decisions
What a layer’s protocols decided, in configuration order.
class Decisions(_ReportSequence[Decision])Methods
- strongest
-
The strongest decision by
terminate > reject > modify > escalate > continue, the first in configuration order on a tie;Noneif nobody decided.def strongest(self) -> Reported[Decision] | None
Reports
Both families of report from one layer, unpackable as observations, decisions = await run_children(...).
class Reports(NamedTuple)Attributes
observationsObservations-
From the layer’s monitors.
decisionsDecisions-
From the layer’s protocols.
Context and host
Context
What a monitor or protocol is given. Constant for a sample; nothing in it came from the agent.
@dataclass(frozen=True, kw_only=True)
class ContextAttributes
taskstr | None-
Task name, or the deployed agent’s identifier.
task_descriptionstr | None-
What agents in this setting are asked to do. None until inspect_ai has
Task(description=). sample_idstr | int | None-
Dataset sample identifier.
epochint | None-
Epoch of the sample, when the task runs several.
sample_descriptionstr | None-
What is distinctive about this instance. None until inspect_ai has
Sample(description=). inputstr | list[ChatMessage]-
The sample’s input: the assignment the agent was given. Judge a step against
step.input, which is exactly what the model was sent; use this for what the agent was asked to do. metadatadict[str, Any]-
Task metadata merged with sample metadata.
pathstr-
Instance path, e.g.
attempt/internet_attempt. Empty at the top layer. storeStore-
The whole sample store, the agent’s state included; not namespaced. Use store_as() for this instance’s own state.
hostHost-
Inference through the host’s models. See Host.
targetTarget | None-
The expected answer. None until monitors and protocols can opt in with
target=True.
Methods
- store_as
-
Typed view of this instance’s state, namespaced by
path.Renaming the instance (its mapping key) or wrapping it in another layer changes
path, so its state moves with it.def store_as(self, model_cls: type[SMT]) -> SMTmodel_clstype[SMT]-
The StoreModel subclass to read and write through.
Host
What a monitor or protocol may do to the outside world. Author-facing.
Inference is the only effect for now. Outbound HTTP through named endpoints (fetch) is planned. Meanwhile a monitor running in-process may call inspect_ai APIs directly, such as sandbox() or logging, at the cost of portability to a proxy, which a future portable=False will declare.
class Host(TypingProtocol)Methods
- generate
-
Generate with a model the host resolves.
With neither
modelnorrole, the host uses themonitorrole. If that role is not configured, it falls back to the agent’s model and logs a warning once per eval naming the role to set.async def generate( self, input: str | list[ChatMessage], *, model: str | Model | None = None, role: str | None = None, tools: list[ToolInfo] | None = None, config: GenerateConfig | None = None, ) -> ModelOutputinputstr | list[ChatMessage]-
A prompt string or a list of chat messages.
modelstr | Model | None-
A model name or instance. Not with
role. rolestr | None-
A model role, e.g.
trusted. Not withmodel. toolslist[ToolInfo] | None-
Tool definitions to offer the model.
configGenerateConfig | None-
Generation configuration.