Skip to main content
When you call agent.run(), it returns an AgentResult containing the complete execution outcome. The result provides access to messages, token usage, stop reasons, execution metrics, and any errors that occurred during the run. Why inspect results:
  • Understand Completion - Check stop reasons to determine if the agent finished successfully, hit limits, stalled, or failed
  • Access Outputs - Retrieve final responses, intermediate messages, and tool call history from the conversation
  • Track Resources - Monitor token usage, estimated costs, and execution steps for optimization and budgeting
When to inspect results: Inspect results after every agent run to check success status, retrieve outputs, handle errors, and track resource consumption. This guide covers stop reasons, accessing messages, token usage and cost tracking, error handling, and execution metrics.
import dreadnode as dn

agent = dn.Agent(
    name="problem-solver",
    model="gpt-4o-mini",
    max_steps=10,
    tools=[dn.agent.tools.fs.Filesystem(path=".", variant="read")],
)

result = await agent.run("Solve this problem.")

if result.failed:
    print(f"Failed: {result.error}")
else:
    print(f"Completed in {result.steps} steps")
    print(f"Final response: {result.messages[-1].content}")

Stop Reasons

The stop_reason tells you why the agent stopped:
result = await agent.run("Task")

match result.stop_reason:
    case "finished":
        # A stop condition was met or agent completed naturally
        print("Task completed successfully")
    case "max_steps_reached":
        # Hit the max_steps limit without meeting a stop condition
        print("Consider increasing max_steps or simplifying the task")
    case "error":
        # An unhandled exception occurred
        print(f"Error: {result.error}")
    case "stalled":
        # Agent produced no tool calls while stop conditions existed but weren't met
        print("Agent got stuck - consider adding stall handling")

Accessing Messages

The conversation history is available in result.messages:
result = await agent.run("Research this topic.")

# User input is typically first
print(f"Input: {result.messages[0].content}")

# Final response is last
print(f"Output: {result.messages[-1].content}")

# Find tool calls in the history
for msg in result.messages:
    if hasattr(msg, 'tool_calls') and msg.tool_calls:
        for call in msg.tool_calls:
            print(f"Called: {call.function.name}")

Token Usage and Cost

Track resource consumption:
result = await agent.run("Analyze this file.")

print(f"Input tokens:  {result.usage.input_tokens}")
print(f"Output tokens: {result.usage.output_tokens}")
print(f"Total tokens:  {result.usage.total_tokens}")

# Cost estimate (when model pricing is available)
if result.estimated_cost:
    print(f"Estimated cost: ${result.estimated_cost:.4f}")
During streaming, access running totals on each event:
from dreadnode.agent.events import GenerationEnd

async with agent.stream("Task") as events:
    async for event in events:
        if isinstance(event, GenerationEnd):
            print(f"Running total: {event.total_usage.total_tokens} tokens")
            if event.estimated_cost:
                print(f"Running cost: ${event.estimated_cost:.4f}")

Error Handling

Check failed and stop_reason together for complete error handling:
result = await agent.run("Task")

if result.failed:
    if result.stop_reason == "error":
        # Exception during execution
        print(f"Exception: {result.error}")
    elif result.stop_reason == "stalled":
        # Agent stopped without completing
        print("Agent stalled without meeting stop conditions")
For streaming, listen for error events:
from dreadnode.agent.events import AgentError, AgentStalled

async with agent.stream("Task") as events:
    async for event in events:
        if isinstance(event, AgentError):
            print(f"Error: {event.error}")
        if isinstance(event, AgentStalled):
            print("Agent is stalling...")

Streaming Events

Use agent.stream() when you need real-time visibility into execution:
from dreadnode.agent.events import StepStart, ToolStart, ToolEnd

async with agent.stream("Analyze the codebase.") as events:
    async for event in events:
        if isinstance(event, StepStart):
            print(f"\n--- Step {event.step} ---")
        elif isinstance(event, ToolStart):
            print(f"Calling: {event.tool_call.name}")
        elif isinstance(event, ToolEnd):
            print(f"Result: {event.message.content[:100]}...")
Events provide the full history via event.events, with helpers for filtering:
# Get all events of a specific type
generations = event.get_events_by_type(GenerationEnd)

# Get the most recent event of a type
last_tool = event.get_latest_event_by_type(ToolEnd)
See Hooks for the complete list of event types and how to react to them.

Thread Snapshot

The result contains a snapshot of the thread state at completion:
from dreadnode.agent import Thread

my_thread = Thread()
result = await agent.run("Task", thread=my_thread)

# result.thread is the state when this run finished
print(f"Total tokens across run: {result.thread.total_usage.total_tokens}")

# Analyze events from this specific run
from dreadnode.agent.events import ToolEnd
tool_events = [e for e in result.thread.events if isinstance(e, ToolEnd)]
print(f"Made {len(tool_events)} tool calls")
This is useful when passing a long-lived thread—result.thread gives you the precise state after that specific run. See the SDK reference for complete AgentResult properties.