Skip to main content
Stop conditions define when agents should stop execution by inspecting run history after each step. Unlike max_steps which is a hard safety limit, stop conditions represent goal completion, budget constraints, or loop detection. Why use stop conditions:
  • Goal Completion - Stop when agents call specific tools, produce expected outputs, or achieve objectives
  • Safety Limits - Prevent runaway costs by setting token, cost, or time budgets
  • Loop Detection - Detect and stop stuck agents repeating tools or hitting errors
When to use stop conditions: Use stop conditions to define success criteria for agent runs. Use max_steps as a safety backstop. Use hooks for event-driven control requiring complex logic. This guide covers goal completion, safety limits, loop detection, combining conditions, and custom stop condition patterns.
import dreadnode as dn
from dreadnode.agent.stop import tool_use, generation_count

agent = dn.Agent(
    name="task-runner",
    model="gpt-4o",
    tools=[dn.agent.tools.fs.Filesystem(path=".", variant="read")],
    stop_conditions=[
        tool_use("finish_task"),   # Stop when task is complete
        generation_count(20),      # Or after 20 LLM calls (safety limit)
    ],
)

Stop Conditions vs Other Mechanisms

MechanismPurposeWhen to use
stop_conditionsDefine success criteriaGoal completion, budget limits
max_stepsHard limit on think-act cyclesPrevent runaway agents
Hooks returning FinishReact to specific eventsComplex conditional logic
Stop conditions check the state of the run. Hooks react to individual events. Use stop conditions for declarative goals; use hooks for event-driven control.

Categories of Stop Conditions

Goal Completion

Stop when the agent achieves its objective:
from dreadnode.agent.stop import tool_use, output, tool_output

# Agent calls a completion tool
tool_use("finish_task")

# Agent's response contains a marker
output("TASK_COMPLETE")

# A tool returns a specific result (e.g., finding a flag)
tool_output(r"flag\{[a-z0-9]+\}", regex=True)

Safety Limits

Prevent runaway costs or infinite loops:
from dreadnode.agent.stop import generation_count, token_usage, estimated_cost, elapsed_time

generation_count(50)           # Max LLM inference calls
token_usage(100_000)           # Max tokens consumed
estimated_cost(5.0)            # Max cost in USD
elapsed_time(max_seconds=600)  # Max wall-clock time

Loop Detection

Detect when an agent is stuck:
from dreadnode.agent.stop import no_new_tool_used, tool_error

# Stop if no new tools used for 3 consecutive steps
no_new_tool_used(for_steps=3)

# Stop on tool errors (gracefully handled)
tool_error()
tool_error("risky_operation")  # Specific tool only

Combining Conditions

Use | (OR) when any condition should trigger stopping:
from dreadnode.agent.stop import tool_use, generation_count, estimated_cost

# Stop on success OR safety limits
stop = (
    tool_use("finish_task") |
    tool_use("give_up") |
    generation_count(50) |
    estimated_cost(10.0)
)
Use & (AND) when multiple conditions must all be true:
from dreadnode.agent.stop import tool_output, output

# Stop when flag is found in both tool output AND agent response
stop = tool_output(r"flag\{.*\}") & output("flag")

Agent Stalling

An agent “stalls” when it produces a response with no tool calls while stop conditions exist but aren’t met. This triggers an AgentStalled event. The never() condition is specifically designed to force stalling behavior:
from dreadnode.agent.stop import never
from dreadnode.agent.events import AgentStalled
from dreadnode.agent.hooks import retry_with_feedback

agent = dn.Agent(
    name="continuous-worker",
    model="gpt-4o-mini",
    tools=[dn.agent.tools.fs.Filesystem(path=".", variant="read")],
    stop_conditions=[never()],  # Never satisfied naturally
    hooks=[
        retry_with_feedback(
            AgentStalled,
            "Continue working. Use finish_task when done."
        )
    ],
)
This pattern ensures the agent keeps working until it explicitly calls a completion tool. TaskAgent uses this pattern by default.
If an agent has no stop conditions and stops calling tools, it finishes successfully without stalling. Stalling only occurs when conditions exist but aren’t met.

Custom Stop Conditions

Create conditions for domain-specific logic:
from dreadnode.agent.stop import StopCondition
from dreadnode.agent.events import AgentEvent, ToolEnd
from collections.abc import Sequence

def files_analyzed(min_count: int) -> StopCondition:
    """Stop after analyzing a minimum number of files."""

    def check(events: Sequence[AgentEvent]) -> bool:
        analyze_calls = [
            e for e in events
            if isinstance(e, ToolEnd) and e.tool_call.name == "analyze_file"
        ]
        return len(analyze_calls) >= min_count

    return StopCondition(check, name="files_analyzed")

agent = dn.Agent(
    name="file-analyzer",
    model="gpt-4o",
    tools=[dn.agent.tools.fs.Filesystem(path=".", variant="read")],
    stop_conditions=[files_analyzed(10)]
)
The function receives the full event history, giving you access to all tool calls, generations, and timing information.

Common Patterns

TaskAgent-style completion

from dreadnode.agent.stop import tool_use

# Stop when agent explicitly finishes or gives up
stop_conditions=[
    tool_use("finish_task") | tool_use("give_up_on_task")
]

CTF/capture-the-flag

from dreadnode.agent.stop import tool_output, generation_count

# Stop when flag is found, or give up after 30 generations
stop_conditions=[
    tool_output(r"flag\{.*\}", regex=True) |
    generation_count(30)
]

Budget-constrained exploration

from dreadnode.agent.stop import estimated_cost, token_usage

# Stay within budget
stop_conditions=[
    estimated_cost(2.0) |
    token_usage(50_000)
]
See the SDK reference for complete function signatures and parameters.