An Agent is an LLM-powered system that decides its own control flow. You define the tools and the goal; the model decides what to do, in what order, and when to stop.
An Agent is a developer with a terminal. You give it a shell (tools), a task (instructions), and it figures out the rest — retrying when things fail and adapting when they don’t work.
Mental model
A Workflow’s next step is written in code. An Agent’s next step is decided by the LLM at runtime, one turn at a time. Every tool call is a durable function, so tool failures retry and process crashes resume.
Three ideas compose. Tools are the capabilities an agent can invoke. Model configuration shapes how the agent reasons. Instructions are the spec — the rules the LLM follows when deciding what to do next.
A minimal agent
from agnt5 import Agent, tool
@tool
async def search_papers(ctx, query: str) -> list[dict]:
"""Search academic papers."""
return await arxiv_search(query)
agent = Agent(
name="research_assistant",
model="openai/gpt-4o",
instructions="You research AI topics. Search first; synthesize last.",
tools=[search_papers],
)
result = await agent.run_sync("What's new in AI alignment?")
print(result.output)Invoke from a client:
from agnt5 import Client
client = Client()
result = client.run(
"research_assistant",
{"query": "What's new in AI alignment?"},
component_type="agent",
)Tools
Tools are durable functions the agent can call. The @tool decorator registers a function and turns its signature and docstring into the JSON schema the LLM consumes.
@tool
async def get_weather(ctx, city: str) -> str:
"""Get the current weather for a city."""
return f"{city}: 72°F, sunny"ctx is injected automatically. The docstring is what the LLM reads when deciding whether to call the tool — write it for another engineer who doesn’t have your codebase.
Decorator options
| Parameter | Default | Description |
|---|---|---|
name | function name | Custom tool name |
description | docstring | Description shown to the LLM |
auto_schema | True | Extract schema from type hints and docstring |
confirmation | False | Require human approval before execution |
confirmation=True pauses before execution for destructive operations (see Human in the Loop):
@tool(confirmation=True)
async def delete_record(ctx, record_id: str) -> str:
"""Permanently delete a record."""
await db.delete(record_id)
return f"Deleted {record_id}"Tool writing guidelines
- Be specific in docstrings. “Get orders for a user. Filter by status: ‘pending’, ‘shipped’, ‘delivered’, or ‘all’.” beats “Get orders.”
- Use type hints. The LLM relies on them to pick arguments.
- Return strings or JSON-friendly types. Return
f"User {id} not found"instead of raising — the model can incorporate the message into its next step.
Model configuration
Models are specified as provider/model:
| Provider | Examples |
|---|---|
| OpenAI | openai/gpt-4o, openai/gpt-4o-mini, openai/o1, openai/o3-mini |
| Anthropic | anthropic/claude-sonnet-4-20250514, anthropic/claude-opus-4-20250514 |
google/gemini-2.0-flash, google/gemini-1.5-pro |
Tune behavior with standard model parameters:
agent = Agent(
name="writer",
model="openai/gpt-4o",
instructions="Write creative stories.",
temperature=0.9,
max_tokens=2000,
top_p=0.95,
max_iterations=10,
)| Parameter | Default | Description |
|---|---|---|
temperature | 0.7 | Lower = more focused, higher = more creative |
max_tokens | None | Max tokens in response (None = model default) |
top_p | None | Nucleus sampling threshold |
max_iterations | 10 | Max tool-use loops before stopping |
max_iterations bounds the reasoning loop. If the agent is still calling tools after that many turns, the run stops — protection against runaway loops.
Running an agent
Two entry points. Use run() to stream events as the agent thinks and calls tools:
async for event in agent.run("Analyze this dataset"):
if event.event_type == "lm.content_block.delta":
print(event.content, end="", flush=True)
elif event.event_type == "agent.tool_call.started":
print(f"\n[{event.tool_name}]")Use run_sync() when you only need the final answer:
result = await agent.run_sync("What's the weather in Tokyo?")
print(result.output) # final response
print(result.tool_calls) # tools called during the run
print(result.handoff_to) # agent name if handed off, None otherwiseExecution model
sequenceDiagram
participant C as Client
participant A as Agent
participant L as LLM
participant T as Tool (Function)
C->>A: run(query)
A->>L: Reason
L-->>A: Plan: call tool
A->>T: Execute (checkpointed)
T-->>A: Result
A->>L: Reason with result
L-->>A: Plan: respond
A-->>C: Final response
Every tool call goes through the Function runtime — retries, idempotency, traces. If the process dies mid-loop, the agent resumes from the last completed tool call.
Writing good instructions
The instructions field is the spec. Be explicit about:
- Role. “You are an expert code reviewer for Python.”
- Process. “Review in this order: complexity, security, performance, style.”
- Constraints. “Prioritize: security > correctness > performance > style.”
Vague instructions produce unpredictable behavior. If you’re iterating on an agent and results drift, the instructions are usually where to look first. Version them through Prompts once the behavior matters in production.
When to use agents
Agents shine when the shape of the work is decided at runtime:
- The sequence of steps isn’t known in advance
- Decisions depend on tool output (branching, routing, escalation)
- Conversations need multi-turn context (see State & Memory)
- You want to accumulate knowledge across sessions
If the sequence of steps is fixed ahead of time, a Workflow is cheaper and more predictable. Every LLM hop adds latency and cost — only reach for an Agent when the reasoning is doing real work.
Common pitfalls
- Vague instructions. The instructions are the spec. If they don’t describe role, process, and constraints, the agent improvises.
- Agents for scripted flows. If you know the steps, a Workflow is cheaper and more predictable.
- Thin tool docstrings. The docstring is how the LLM picks a tool. Sparse descriptions cause wrong-tool calls.
- Unbounded loops. Always set
max_iterationsfor agents that can keep calling tools.
Learn more
- Choose: Workflow or Agent — when to pick which
- Workflows — developer-defined control flow
- State & Memory — persistent memory and sessions
- Human in the Loop — approvals and mid-run input
- Multi-Agent Patterns — handoffs, agent-as-tool, routing
- Prompts — version and measure instructions