Skip to main content
The Engine provides two complementary mechanisms for controlling the run lifecycle:
  • Critics (step-level validators) validate each step after reduce() runs. They can allow the run to continue, force a stop, or trigger a retry of the current step.
  • Stop criteria (loop-termination rules) are checked after critics pass. They evaluate state and runtime metrics to decide whether to end the loop.

Critics

A critic receives the current state, the decision (the model’s structured output for a step) taken, and the action (the tool call dispatched based on the decision) results, then returns a structured verdict dict.

The Critic contract

Supported critic actions

Any action value other than "stop" or "retry" is treated as "continue".

Adding critics to a run

Pass a list of Critic instances to agent.run():
Or pass them when constructing the Engine directly:

Writing a custom critic

The critic outputs are recorded in the trace (the structured run log) as step.critic_outputs and visible in the qita board Critic section for each step.

Stop criteria

Stop criteria are evaluated after every step’s critic pass. Each criterion receives the current state, the step count, and a runtime info dict (with elapsed_seconds).

The StopCriteria contract

Built-in criteria

QitOS ships four built-in criteria in qitos.engine.stop_criteria: FinalResultCriteria (default) Stops when state.final_result is set to a non-empty string. It is the only criterion used when you do not pass stop_criteria to the Engine.
MaxStepsCriteria Stops when the step count reaches max_steps. The Engine applies this automatically from the RuntimeBudget — you rarely need to instantiate it manually.
MaxRuntimeCriteria Stops when wall-clock time exceeds a threshold.
StagnationCriteria Stops when state has not changed for max_stagnant_steps consecutive steps. Uses a signature function to detect change (defaults to checking final_result and phase).
MaxTokensCriteria Stops when cumulative token usage exceeds a budget. The Engine passes total_tokens in runtime_info on each step, so this criterion tracks real token consumption across the entire run.
Use this when you need a hard token budget independent of step count or wall-clock time. Pair it with gold preset advisory values:

Passing criteria to a run

When you pass stop_criteria, you replace the default FinalResultCriteria. If you still want the run to stop when final_result is set, include FinalResultCriteria() in your list.

Writing a custom criterion


Budget-based stopping with TaskBudget

For structured task definitions, use TaskBudget to express all three budget dimensions together:
The Engine applies TaskBudget values to its internal RuntimeBudget before the run starts, overriding any constructor defaults.

StopReason values

StopReason is a string enum in qitos.core.errors. The value is written to state.stop_reason at the end of every run. Check result.state.stop_reason after a run to distinguish a successful completion from a budget exhaustion: