Composable Termination Conditions
also known as Termination DSL, Stop-Condition Composition
Express agent stop criteria as small single-purpose conditions composed with AND/OR into one explicit termination contract instead of ad-hoc loop guards.
This pattern helps complete certain larger patterns —
- specialisesStep Budget★★— Cap the number of tool calls or loop iterations the agent is allowed within a single request.
Context
An agent or orchestrator loops over model calls, tool invocations, and message exchanges until something tells it to stop. The realistic stop criteria are heterogeneous: a max number of messages, a token budget, a phrase the model emitted, a particular tool call (e.g. submit_final), a handoff to another agent, a timeout, an external operator signal, or a user cancellation.
Problem
Inlining these stop conditions as ad-hoc `if` statements in the orchestrator loop scatters the termination logic, makes its precedence implicit, and prevents reuse across loops. Adding a new condition requires editing the loop. Combining conditions (stop on max_messages OR external signal AND a specific tool call) becomes an unreadable nest. Operators reading a trace cannot tell why a run ended without re-reading the loop code.
Forces
- Different agents need different combinations of the same primitive conditions.
- Conditions must compose with AND/OR while preserving short-circuit semantics.
- The trace must record which condition tripped, for postmortem.
- External signals (operator cancellation, kill-switch) must be expressible as a condition like any other.
Example
A research-agent loop is configured with `MaxMessages(50) | TokenBudget(200_000) | TextMention('final_answer') | ExternalSignal(cancel_token)`. Each step the orchestrator asks the composite whether to stop. When the cancel token flips, the loop ends and the trace records `terminated_by=ExternalSignal`; when the model emits 'final_answer' first, the trace records that instead.
Diagram
Solution
Therefore:
Define a small set of primitive termination conditions: MaxMessages, TokenBudget, TextMention, FunctionCall, Handoff, Timeout, ExternalSignal, Cancellation. Each implements a single method `is_terminated(state) -> bool, reason`. Define a Composite that combines conditions with `any` (OR) or `all` (AND) semantics. The orchestrator loop consults the composite once per step. The trip cause (which leaf condition fired) is logged with the termination event.
What this pattern forbids. Termination criteria must not be inlined as ad-hoc loop guards; they must be expressed as named conditions and composed with AND/OR into a single termination contract per loop.
The smaller patterns that complete this one —
- usesCost Gating★★— Block actions whose expected cost exceeds a threshold without explicit user (or operator) acknowledgement.
And the patterns that stand alongside it, or against it —
- complementsKill Switch★— Provide an out-of-band control plane to halt running agent instances without redeploy.
- complementsDegenerate-Output Detection★— Detect when the agent is about to emit a near-duplicate of its own recent output and either drop, replace, or escalate to a stronger model rather than ship the loop.
- composes-withInterruptible Agent Execution★— Treat pause, resume, and cancel as a first-class control surface on every long-running agent so users can halt expensive or off-track trajectories mid-task while state is preserved for resumption.
- alternative-toUnbounded Loop✕— Anti-pattern: run the agent loop without a step budget and let model self-termination decide.
Neighbourhood
Click any neighbour to follow the language. Scroll to zoom, drag to pan.