Skip to content

Agents

Agents are the core runtime loop in the Python SDK. An Agent is a Pydantic model that coordinates generations, tool calls, and lifecycle events. Every run is captured in a Trajectory, which you can inspect for messages, events, and usage.

FieldTypeDefaultDescription
namestr(required)Display name for logs and tracing.
descriptionstr""Brief description of the agent.
modelstr | Generator | NoneNoneModel identifier (e.g. "gpt-4o-mini") or a Generator instance.
instructionsstr | NoneNoneSystem prompt injected into each run. Automatically dedented.
toolslist[Tool | Toolset][]Tools the agent can use. Accepts callables (auto-converted) and nested lists (auto-flattened).
tool_modeToolMode"auto"How tool calls are parsed. See Tool modes.
hookslist[Hook][]Hooks applied during execution. See Hooks and reactions.
stop_conditionslist[StopCondition][]Legacy conditions evaluated against trajectory.steps. Prefer stopping hooks.
judgeJudge | NoneNoneOptional judge for scoring trajectories. Available for external evaluation workflows.
max_stepsint10Maximum generation/tool steps before the agent stops.
generation_timeoutint | NoneNoneTimeout in seconds for each LLM generation call. None means no timeout.
generate_params_extradict{}Extra parameters merged into every generation request.
working_dirPath | NoneNoneWorking directory for tool output offloading and IO. Falls back to Path.cwd().
cacheCacheMode | NoneNoneCache behavior for generated messages.
tagslist[str][]Tags for filtering and identification. "agent" is auto-inserted.
labelstr | NoneNoneOptional label for UI and tracing.
agent_idUUIDautoUnique identifier for the agent instance.

The two primary execution methods are:

  • await agent.run(goal) — returns a Trajectory.
  • async with agent.stream(goal) as events — yields structured events.
from dreadnode.agents import Agent, tool
@tool
def lookup(question: str) -> str:
"""Fetch an answer from a local map."""
return f"Local answer for: {question}"
async def main() -> None:
agent = Agent(
name="support-agent",
model="gpt-4o-mini",
instructions="Answer concisely and cite tools when used.",
tools=[lookup],
max_steps=25,
)
trajectory = await agent.run("What is Dreadnode?")
print(trajectory.messages[-1].content)
print(trajectory.usage)

Use stream() when you want to observe lifecycle events as they happen.

from dreadnode.agents import Agent
async def main() -> None:
agent = Agent(name="streamer", model="gpt-4o-mini")
async with agent.stream("Summarize the platform.") as events:
async for event in events:
print(type(event).__name__, event.status)

Pass trajectory= to share state between agents or reset=False to continue a conversation without clearing history.

from dreadnode.agents import Agent
from dreadnode.agents.trajectory import Trajectory
shared = Trajectory()
async with agent_a.stream("Research the topic", trajectory=shared) as s:
async for _ in s:
pass
# agent_b picks up where agent_a left off
async with agent_b.stream("Summarize the findings", trajectory=shared) as s:
async for _ in s:
pass

tool_mode controls how tool calls are parsed from model output.

ModeDescription
"auto"Default. Uses "api" if the generator supports function calling, otherwise falls back to "json-in-xml".
"api"Delegates to the provider’s native function calling API.
"xml"Tool calls parsed in nested XML format. Adds a tool stop token.
"json"Tool calls parsed as raw name/argument JSON in assistant content.
"json-in-xml"JSON arguments inside an XML envelope.
"json-with-tag"JSON structures inside an XML tag.
"pythonic"Tool calls parsed as Python function call syntax (e.g. tool_name(arg=value)).

Use "api" with mainstream providers (OpenAI, Anthropic). Use "xml" or "json-in-xml" for open-source models that do not support native function calling.

Use generate_params_extra to enable extended thinking for models that support it.

agent = Agent(
name="deep-thinker",
model="claude-sonnet-4-20250514",
generate_params_extra={
"thinking": {"type": "enabled", "budget_tokens": 8000},
},
)

Use agent.task() to convert an agent into a Task for use with Evaluation or Study. The task takes a goal: str and returns a Trajectory.

from dreadnode.agents import Agent
agent = Agent(name="eval-target", model="gpt-4o-mini")
# Use in an evaluation
@dn.evaluation(dataset=[{"goal": "Explain TLS"}])
async def eval_agent(goal: str) -> Trajectory:
return await agent.run(goal)
# Or convert directly
task = agent.task(name="agent-task")

Hooks are callables that receive an AgentEvent and can return a reaction to steer execution. Use the @hook decorator to filter by event type.

When a hook returns a reaction, it controls what happens next. If multiple hooks react to the same event, priority determines which wins.

ReactionPriorityEffect
Finish(reason?)HighestStop execution successfully.
Fail(error)HighStop execution with an error.
Retry(messages?)MediumRetry the step, optionally replacing messages.
RetryWithFeedback(feedback)MediumRetry with a feedback message injected.
Continue(messages?, feedback?)LowContinue execution, optionally injecting messages or feedback.
EventWhen emitted
AgentStartAgent execution begins.
AgentEndAgent execution ends (has stop_reason, error).
AgentStalledNo tool calls and no stop conditions met.
AgentErrorUnrecoverable error occurred.
GenerationStartBefore an LLM call.
GenerationEndAfter an LLM call completes.
GenerationStepFull generation step for trajectory.
GenerationErrorError during generation.
ToolStartBefore a tool call executes.
ToolEndAfter a tool call completes (has result, output_file).
ToolStepTool result for trajectory.
ToolErrorError during tool execution.
ReactStepA hook returned a reaction.
UserInputRequiredAgent needs human input.
HeartbeatKeepalive during long operations.
from dreadnode.agents import Agent, Finish, RetryWithFeedback
from dreadnode.agents.events import GenerationStep, ToolError
from dreadnode.core.hook import hook
@hook(GenerationStep)
def stop_if_short(event: GenerationStep) -> Finish | None:
if event.messages and len(event.messages[-1].content or "") < 20:
return Finish("Response too short")
return None
@hook(ToolError)
def retry_on_tool_error(event: ToolError) -> RetryWithFeedback:
return RetryWithFeedback(f"Tool failed: {event.error}. Try a different approach.")
agent = Agent(
name="quality-agent",
model="gpt-4o-mini",
hooks=[stop_if_short, retry_on_tool_error],
)
HookDescription
backoff_on_ratelimitExponential backoff on rate limit errors (max 8 retries, 300s). Requires litellm.
tool_metrics(detailed=False)Logs metrics about tool usage, execution time, and success rates. detailed=True logs per-tool metrics.
summarize_when_longAuto-summarizes conversation when context exceeds 100K tokens or a context-length error occurs.

default_hooks() returns [tool_metrics(), summarize_when_long, step_count(50)] plus backoff_on_ratelimit if available. These are applied automatically when agents run via the server.

Stopping hooks are hook factories that return Finish reactions when conditions are met. These are the preferred way to control when an agent stops.

from dreadnode.agents import Agent
from dreadnode.agents.stopping import (
step_count,
token_usage,
elapsed_time,
tool_use,
output,
)
agent = Agent(
name="bounded-agent",
model="gpt-4o-mini",
hooks=[
step_count(20),
token_usage(50_000),
elapsed_time(120),
],
)
FactoryListens toDescription
step_count(max_steps)GenerationStepStop after N steps.
generation_count(max)AgentEventStop after N LLM inference calls (counts retries).
tool_use(tool_name, count=1)AgentEventStop after a specific tool is used N times.
any_tool_use(count=1)AgentEventStop after any tool is used N total times.
output(pattern, ...)GenerationEndStop if pattern found in generated text. Supports case_sensitive, exact, regex.
tool_output(pattern, tool_name?)ToolEndStop if pattern found in tool output.
tool_error(tool_name?)ToolErrorStop on tool error.
no_new_tool_used(for_steps)AgentEventStop if no previously-unseen tool is used for N steps.
no_tool_calls(for_steps=1)AgentEventStop if no tool calls for N consecutive steps.
token_usage(limit, mode="total")GenerationEndStop if token usage exceeds limit. Mode: "total", "in", "out".
elapsed_time(max_seconds)AgentEventStop if wall-clock time exceeds limit.
estimated_cost(limit)GenerationEndStop if estimated LLM cost exceeds USD limit.
consecutive_errors(count)AgentEventStop after N consecutive tool errors.

Trajectory collects every event and message in order. Key properties:

  • trajectory.events — raw events list.
  • trajectory.messages — reconstructed conversation history.
  • trajectory.usage — token usage totals.
  • trajectory.steps — step events for stop condition evaluation.
  • agent.reset() — clears internal state and returns the previous trajectory.
  • stream(reset=False) — continues the conversation without clearing history.
  • stream(trajectory=shared) — uses an external trajectory object.