Configure Stop Policies
Use this when a run must terminate on explicit loop, budget, timeout, or domain-specific conditions.
What is auto-wired
AgentDefinition.max_roundsis lowered intoStopConditionSpec::MaxRoundsduring agent wiring unless you already declared an explicitmax_roundsstop spec.AgentOsBuilderwires the internalstop_policybehavior automatically when stop specs or stop-condition ids are present.stop_policyis a reserved behavior id. Register stop policies through builder APIs instead of attaching a behavior id manually.
Prerequisites
- You know whether the stop rule is declarative (
StopConditionSpec) or custom (StopPolicytrait). - You have a way to observe terminal run status through events or the Run API.
Steps
- Add declarative stop specs on the agent definition.
use tirea::composition::{AgentDefinition, AgentDefinitionSpec, StopConditionSpec};
let agent = AgentDefinition::new("deepseek-chat")
.with_stop_condition_specs(vec![
StopConditionSpec::Timeout { seconds: 30 },
StopConditionSpec::LoopDetection { window: 4 },
StopConditionSpec::StopOnTool {
tool_name: "finish".to_string(),
},
]);
- Keep
max_roundsaligned with your stop strategy.
max_roundsstill acts as the default loop-depth guard.- If you already added
StopConditionSpec::MaxRounds, do not expectmax_roundsto stack on top of it.
- Register reusable custom stop policies when declarative specs are not enough.
use std::sync::Arc;
use tirea::composition::AgentOsBuilder;
use tirea::runtime::{StopPolicy, StopPolicyInput};
use tirea::contracts::StoppedReason;
struct AlwaysStop;
impl StopPolicy for AlwaysStop {
fn id(&self) -> &str {
"always"
}
fn evaluate(&self, _input: &StopPolicyInput<'_>) -> Option<StoppedReason> {
Some(StoppedReason::new("always_stop"))
}
}
let os = AgentOsBuilder::new()
.with_stop_policy("always", Arc::new(AlwaysStop))
.with_agent_spec(AgentDefinitionSpec::local_with_id(
"assistant",
AgentDefinition::new("deepseek-chat").with_stop_condition_id("always"),
))
.build()?;
- Observe the terminal reason from events or run records.
AgentEvent::RunFinish { termination, .. }GET /v1/runs/:id(termination_code,termination_detail)
Verify
- The run terminates with the expected stopped reason instead of timing out implicitly elsewhere.
GET /v1/runs/:idreflects the same terminal reason that you observed in the event stream.- A new run starts with fresh stop-policy runtime state rather than inheriting counters from the previous run.
Common Errors
- Trying to register
stop_policyas a normal behavior id. - Expecting
max_roundsandStopConditionSpec::MaxRoundsto both apply independently. - Using stop policies for tool authorization. Authorization belongs in tool/policy behaviors, not termination logic.
- Forgetting that stop-policy runtime bookkeeping is run-scoped and will be reset on the next run.
Related Example
examples/src/starter_backend/mod.rsdefines astopperagent that terminates onStopConditionSpec::StopOnTool { tool_name: "finish" }
Key Files
crates/tirea-agentos/src/runtime/plugin/stop_policy.rscrates/tirea-agentos/src/composition/wiring.rscrates/tirea-agentos/src/composition/agent_definition.rscrates/tirea-agentos/src/runtime/tests.rs