An Agent uses a Large Language Model (LLM) to orchestrate a cycle of reasoning and action. At the core agents run a “think-act-observe” loop: it thinks about what to do, acts by using tools, and observes the results to inform its next thought.
Agent Basics
The Agent class is the heart of the system. You’ll find it doesn’t contain specific logic, or require overloading to change, but rather orchestrates the components you provide. When you create an Agent, you define its core identity and capabilities.
import dreadnode as dn
agent = dn.Agent(
name="File Explorer",
model="gpt-4-turbo",
tools=[
dn.agent.tools.fs.Filesystem(variant="read")
],
)
# run until done
result = await agent.run("Summarize the README.")
# stream events
async with agent.stream("Summarize the README.") as events:
async for event in events:
print(event)
# print to console
await agent.console("Summarize the README.")
You have 3 primary methods for executing an agent in code:
run(): Use this when you just need a final answer. It executes the entire think-act-observe loop until completion and returns a single AgentResult object.
stream(): Use this when you need to see the process unfold in real-time. It’s an async context manager that yields AgentEvent objects as they happen, which is perfect for building responsive UIs or detailed logging systems.
console(): Use this for a quick, human-friendly view of the entire run. It streams the agent’s messages and events directly to your console with rich formatting.
Agents in the CLI
In addition to running agents programmatically, Dreadnode includes a powerful command-line interface (CLI) that is essential for testing, debugging, and interacting with your agents. All CLI functionality is available under the dn agent command.
Discovering and Listing Agents
The CLI will automatically search for agents in common files like agent.py and main.py.
$ dn agent ls
Agents in agent.py:
╭───────┬───────────────────┬─────────────┬──────────────────────────────╮
│ Name │ Description │ Model │ Tools │
├───────┼───────────────────┼─────────────┼──────────────────────────────┤
│ basic │ A basic agent ... │ gpt-4o-mini │ Filesystem, finish_task, ... │
╰───────┴───────────────────┴─────────────┴──────────────────────────────╯
To see a more detailed view, including all hooks, stop conditions, and other configurations, use the --verbose or -v flag.
$ dn agent ls -v
Agents in agent.py:
╭─ basic ───────────────────────────────────────────────────────────────────╮
│ ╷ │
│ Description │ A basic agent that can handle simple tasks. │
│ Model │ gpt-4o-mini │
│ Tools │ Filesystem, finish_task, give_up_on_task, update_todo │
│ Hooks │ retry_with_feedback, summarize_when_long │
│ Stops │ stop_on_generation_count, stop_never │
│ ╵ │
╰───────────────────────────────────────────────────────────────────────────╯
Running and Configuring Agents
The run command takes the agent’s name and the user input as arguments. The agent’s name can be specified as just the name (basic), a file (agent.py), or a combination (agent.py:basic).
The most powerful feature of the CLI is its ability to automatically generate command-line arguments from your agent’s configuration. To see all available options for a specific agent, use the help command.
$ dn agent run basic help
Usage: basic [ARGS] [OPTIONS]
Run the 'basic' agent.
╭─ Parameters ────────────────────────────────────────────────────────────────╮
│ * INPUT --input Input to the agent [required] │
╰─────────────────────────────────────────────────────────────────────────────╯
╭─ Agent Config ──────────────────────────────────────────────────────────────╮
│ --tools.filesystem.path Base path to work from. [default: /path/to/sdk] │
│ --model Inference model... [default: gpt-4o-mini] │
│ --max-steps The maximum number of steps... [default: 10] │
│ ... │
╰─────────────────────────────────────────────────────────────────────────────╯
You can then use these generated flags to override any part of the agent’s configuration for that specific run.
# Run the agent with a standard prompt
$ dn agent run basic "Summarize the README.md file"
# Override the model and the filesystem path for this run
$ dn agent run basic "Summarize the file in /tmp" \
--model "gpt-4-turbo" \
--tools.filesystem.path "/tmp"
This automatic configuration exposure works for any Pydantic Config fields on your Agent class itself, or on any Toolset or Hook that it uses. This is a powerful way to make your custom components easily configurable from the command line with no extra work.
Lifecycle
Every agent run follows a predictable sequence of events. Understanding this lifecycle is the key to knowing when and how you can influence its behavior. For every step (up to max_steps), the agent proceeds as follows:
[ Start Run ]
↓
┌──── Step 1 ───┐
│ ↓ │
│ [ Think ] │ → GenerationEnd Event
│ ↓ │
│ [ Act ] │ → ToolStart / ToolEnd Events
│ ↓ │
└───( Stop? )───┘
↓
[ End Run ]
This diagram shows how all the pieces fit together:
┌──────────────────┐
│ Thread │ (Memory: Messages & Events)
└────────┬─────────┘
│
┌────────────────────┼────────────────────┐
│ Agent │ │
│ (Orchestrator) ↓ │
│ │
│ ┌───────────────────────────────┐ │
│ │ Lifecycle Loop │ │
│ │ (Start → Think → Act → Stop?) │ │
│ └───────────────┬───────────────┘ │
│ │ │
│ ┌──────────────────┴──────────────────┐ │
│ │ Your Components │ │
│ │ │ │
│ │ ∙ Tools (For Acting) │ │
│ │ ∙ Hooks (For Reacting) │ │
│ │ ∙ StopCond (For Finishing) │ │
│ └─────────────────────────────────────┘ │
└─────────────────────────────────────────┘
TaskAgent
Dreadnode includes the TaskAgent, a subclass of Agent that is pre-configured for goal-oriented tasks. It’s a powerful demonstration of how the core components can be assembled into a robust, ready-to-use pattern. It comes with:
- Default Tools: Includes
update_todo for planning, and finish_task or give_up_on_task for explicit completion.
- Resilient Behavior: It includes a default hook to prevent it from stalling and a
never step condition, forcing it to work until it explicitly finishes its task.
The implementation is a great example of building specializations on top of the Agent base class:
class TaskAgent(Agent):
def model_post_init(self, _: t.Any) -> None:
self.tools.extend([finish_task, give_up_on_task, update_todo])
self.stop_conditions.append(never())
self.hooks.insert(0,
retry_with_feedback(
event_type=AgentStalled,
feedback="Continue the task if possible, use the 'finish_task' tool to complete it, or 'give_up_on_task' if it cannot be completed.",
),
)
Advanced Concepts
As you build more complex agents, you’ll find these deeper mechanics useful.
Agent Stalling
An agent is considered “stalled” only under specific circumstances. The AgentStalled event will fire if, and only if:
- The agent produces a response with no tool calls.
- The agent has
StopConditions defined, and none of them are met.
If an agent stops using tools and has no stop conditions, it simply finishes its run successfully. This event is your tool for handling cases where the agent gets “stuck” and doesn’t know how to proceed toward its goal.