Get started Agents

Agents

LLM-powered systems that reason, select tools, and loop until done

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 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 otherwise

Execution 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_iterations for agents that can keep calling tools.

Learn more