Agents
LLM-powered systems with autonomous reasoning, tool selection, and memory
An Agent is an LLM-powered system that reasons about tasks, dynamically selects tools, and maintains memory across conversations.
Think of an Agent like a developer with a terminal — it reasons about what to do, picks the right tools, and adapts when things don’t work.
Why It Exists
When you need to analyze research papers, decide which sources to consult, retrieve context from previous work, and synthesize everything into insights — you can’t script every step. You need reasoning. You need tool selection. You need autonomous decision-making.
Agents solve this. You define capabilities (tools) and goals (instructions). The agent figures out how to accomplish them. If a tool fails, it adapts. If it needs more information, it asks.
When to Use
- Tasks requiring dynamic decision-making (not scriptable in advance)
- Tool orchestration where you don’t know which tools are needed upfront
- Conversational interactions with multi-turn context
- Knowledge accumulation across sessions
Example
from agnt5 import Agent, tool
@tool.function(auto_schema=True)
def search_papers(query: str, year_from: int = 2020) -> list[dict]:
"""Search academic papers by query and year."""
return arxiv_search(query, year_from)
@tool.function(auto_schema=True)
def extract_key_findings(paper_text: str) -> list[str]:
"""Extract key findings from a research paper."""
return llm_extract(paper_text)
agent = Agent(
name="research_assistant",
model="openai/gpt-4o",
instructions="""You are a research assistant specializing in AI.
1. Search for relevant recent papers
2. Extract key findings from each
3. Synthesize into a comprehensive summary""",
tools=[search_papers, extract_key_findings]
)
result = await agent.run("What are the latest developments in AI alignment?")Call it via the client:
from agnt5 import Client
client = Client()
result = client.run("research_assistant", {
"query": "What are the latest developments in AI alignment?"
}, component_type="agent")The agent reads the query, decides to search papers, extracts findings from the most relevant ones, identifies themes, and synthesizes a summary. You didn’t script this flow — the LLM orchestrated it.
How It Works
Agents provide three guarantees:
- Autonomous Reasoning — The LLM decides what to do based on instructions and context
- Durable Tool Execution — Every tool call is a checkpointed function with retries
- Persistent Memory — Knowledge and conversation history survive crashes
sequenceDiagram
participant C as Client
participant A as Agent
participant L as LLM
participant T as Tools
participant S as State Store
C->>A: run(query)
A->>L: Reason about task
L-->>A: Plan: call search_papers
A->>T: Execute tool (checkpointed)
T-->>A: Results
A->>L: Reason with results
L-->>A: Plan: call extract_findings
A->>T: Execute tool (checkpointed)
T-->>A: Findings
A->>L: Synthesize response
L-->>A: Final response
A->>S: Save session state
A-->>C: Response
Configuration
Instructions
Instructions shape how the agent reasons. Be specific about role, process, and constraints:
agent = Agent(
name="code_reviewer",
model="openai/gpt-4o",
instructions="""You are an expert code reviewer for Python.
Review process:
1. Analyze code for complexity, duplication, and style
2. Check for security vulnerabilities
3. Suggest improvements with code examples
Prioritize: security > correctness > performance > style"""
)Tools
Tools are functions the agent can call. Agents select tools based on reasoning:
@tool.function(auto_schema=True)
def search_docs(query: str) -> list[dict]:
"""Search programming documentation."""
return doc_search(query)
@tool.function(auto_schema=True)
def execute_code(code: str) -> dict:
"""Execute code and return output."""
return code_runner.run(code)
agent = Agent(
name="coding_assistant",
tools=[search_docs, execute_code]
)Tools are implemented as AGNT5 functions — durable, retryable, observable.
Memory
Agents can maintain long-term knowledge across conversations:
from agnt5 import Agent, Memory
memory = Memory(service=VectorMemoryService())
await memory.store("user_expertise", "Expert in distributed systems")
agent = Agent(
name="assistant",
memory=memory # Agent recalls stored knowledge automatically
)Memory is backed by entities, so knowledge persists across crashes.
Sessions
Sessions provide conversation context across multiple turns:
from agnt5 import Agent, Session
session = Session(id="research-001", user_id="user-123")
agent = Agent(name="research_assistant", session=session)
# Turn 1
await agent.run("Find papers on AI alignment from 2023")
# Turn 2 — agent has context from turn 1
await agent.run("Now analyze the top 3 for common themes")Sessions use entities for state management, providing single-writer consistency.
Multi-Agent Coordination
Agents as tools — Coordinator synthesizes results from specialists:
data_analyst = Agent(name="data_analyst", tools=[calculate_stats])
researcher = Agent(name="researcher", tools=[search_papers])
coordinator = Agent(
name="coordinator",
tools=[data_analyst, researcher] # Agents as tools
)
result = await coordinator.run("Analyze our data and compare to research")Explicit handoffs — Route to specialist and delegate full responsibility:
technical_support = Agent(name="technical_support", tools=[run_diagnostics])
billing_support = Agent(name="billing_support", tools=[process_refund])
triage = Agent(
name="triage",
handoffs=[technical_support, billing_support]
)
# Routes to billing_support based on content
await triage.run("I was charged twice for my subscription")Human-in-the-Loop
Agents can pause for user input within workflows:
from agnt5 import Agent, RequestApprovalTool, workflow, WorkflowContext
@workflow(chat=True)
async def deployment_workflow(ctx: WorkflowContext, changes: dict) -> dict:
deploy_agent = Agent(
name="deployment_assistant",
tools=[RequestApprovalTool(ctx)] # Enables pause for approval
)
result = await deploy_agent.run(f"Review and deploy: {changes}", context=ctx)
return {"response": result.output}The agent autonomously decides when to ask for input based on its instructions.
Streaming
Stream agent execution in real-time:
async for event in agent.stream("Analyze this dataset"):
match event.type:
case "thinking":
print(f"Reasoning: {event.content}")
case "tool_call":
print(f"Calling {event.tool_name}")
case "response":
print(f"Response: {event.content}")Guidelines
Common Patterns
Agent → Tool (Function) → Result (autonomous tool selection)
Agent → Memory (Entity) → Persistent knowledge
Agent → Session (Entity) → Conversation history
Workflow → Agent → Agent (hybrid orchestration)
Coordinator Agent → Specialist Agents (multi-agent as tools)
Triage Agent → handoff → Specialist Agent (delegation)Common Pitfalls
-
❌ Don’t use agents for scripted flows — If you know the exact steps, use Workflows. Agents add LLM overhead.
-
❌ Don’t forget HITL tools require workflows —
AskUserToolandRequestApprovalToolonly work inside@workflow(chat=True). -
❌ Don’t skip instructions — Vague instructions produce unpredictable behavior. Be specific about role, process, and constraints.
What Agents Don’t Do
- Not for deterministic operations — Use Functions for stateless computation
- Not for scripted orchestration — Use Workflows when steps are known in advance
- Not for persistent state alone — Use Entities for state without reasoning
API Reference
Agent(name, model, instructions, tools, memory, session, handoffs)— Define an agentagent.run(query)— Execute and wait for resultagent.stream(query)— Stream execution eventsagent.plan(query)— Preview execution plan@tool.function()— Define a tool from a functionMemory— Long-term knowledge storeSession— Conversation contextAskUserTool,RequestApprovalTool— Human-in-the-loop tools