How to Build Autonomous Agents with the aisuite Agents API
You build autonomous agents in aisuite by instantiating the Agent dataclass with a model, instructions, and Python callables as tools, then executing via Runner.run_sync() or Runner.run() while optionally enforcing safety constraints through ToolPolicy protocols.
The aisuite library provides a declarative Agents API that simplifies the creation of stateful, tool-using AI systems. By combining the Agent configuration object with the Runner execution engine, you can build autonomous agents that call external functions, spawn sub-agents, and maintain conversation context across multiple turns. This guide walks through the core components located in aisuite/agents/types.py, aisuite/agents/runner.py, and aisuite/agents/tools.py.
Define an Agent with the Agent Dataclass
The foundation of any autonomous agent is the Agent dataclass, defined in aisuite/agents/types.py at lines 34-42. This class encapsulates all configuration required for the agent's behavior, including the model provider, system instructions, available tools, and metadata.
import aisuite as ai
def get_weather(city: str) -> str:
"""Return weather information for a given city."""
return f"The weather in {city} is sunny."
weather_agent = ai.Agent(
name="weather_assistant",
model="openai:gpt-4o",
instructions="Answer briefly. Use tools when they help.",
tools=[get_weather],
model_settings={"temperature": 0.2},
tags=["example", "weather"],
metadata={"app": "simple_agent_example"},
)
When you pass Python functions to the tools parameter, aisuite inspects their signatures at runtime and exposes them to the LLM as formatted tool specifications. Any callable—whether a simple function or a complex class method—can serve as a tool, provided it accepts serializable arguments and returns strings or structured data.
Execute Agent Runs with the Runner
The Runner class in aisuite/agents/runner.py orchestrates the complete LLM interaction loop, handling message construction, tool invocation, and trace generation. For synchronous execution, use Runner.run_sync (lines 74-92); for asynchronous workloads, use Runner.run.
result = ai.Runner.run_sync(
weather_agent,
"What is the weather in San Francisco?",
max_turns=3,
run_name="weather_lookup",
group_id="example_conversation_1",
metadata={"request_id": "example_request_1", "user_id": "example_user"},
)
print(result.final_output)
result.print_trace()
result.write_trace_jsonl(".aisuite/runs.jsonl")
The Runner executes the following sequence: it builds the initial message list via Runner._build_messages, sends the request to the underlying provider SDK defined in aisuite/client.py, parses any tool calls, executes the corresponding Python functions, and injects the results back into the conversation history. The max_turns parameter limits how many tool-calling loops the agent may perform before returning.
Maintain State Across Conversations
Autonomous agents often require persistent memory across multiple user interactions. The aisuite Agents API enables this through the RunResult.to_state() method, located at lines 182-196 in aisuite/agents/types.py. This method converts a completed run into a RunState object containing the full message history, metadata, and execution steps.
# Continue the conversation using the previous result
next_result = ai.Runner.continue_sync(
result,
"What about Oakland?",
)
print(next_result.final_output)
Runner.continue_sync loads the RunState, appends the new user message, and initiates a new turn while preserving trace IDs and group identifiers. This ensures observability and continuity without losing context from previous turns.
Compose Hierarchical Agents with agent_tool
Complex workflows require agents that can delegate to specialized sub-agents. The agent_tool function in aisuite/agents/tools.py (lines 16-49) wraps an Agent instance as a callable tool, enabling nested autonomy where parent agents spawn sub-agents that run their own LLM loops.
# Define a researcher sub-agent
researcher = ai.Agent(
name="researcher",
model="openai:gpt-4o-mini",
instructions="Provide a concise summary.",
tags=["research"],
)
# Expose the sub-agent as a tool to the main agent
weather_agent.tools.append(ai.agent_tool(researcher, name="research_topic"))
# The main agent can now delegate research tasks
result = ai.Runner.run_sync(
weather_agent,
"Give me a quick overview of climate change.",
max_turns=4,
)
When the parent agent invokes research_topic, the agent_tool wrapper executes the sub-agent within the same trace context, inheriting the client configuration and metadata. The sub-agent runs its complete loop, and its final output returns to the parent as the tool result.
Enforce Safety with Tool Policies
Production autonomous agents require governance mechanisms to restrict dangerous operations. The ToolPolicy protocol, defined at lines 42-45 in aisuite/agents/types.py, allows you to implement pre-execution gates that receive a ToolPolicyContext and return either a boolean or a ToolPolicyDecision.
def safe_policy(ctx: ai.ToolPolicyContext) -> ai.ToolPolicyDecision:
if ctx.tool_name == "dangerous_action":
return ai.ToolPolicyDecision(allowed=False, reason="Risk too high")
return ai.ToolPolicyDecision(allowed=True)
result = ai.Runner.run_sync(
weather_agent,
"Execute dangerous_action",
tool_policy=safe_policy,
)
The policy executes before any tool is invoked, enabling you to enforce compliance rules, require user approval for sensitive operations, or audit tool usage patterns in real-time.
Summary
- Define agents using the
Agentdataclass inaisuite/agents/types.py, specifying models, instructions, and Python functions as tools. - Execute runs via
Runner.run_syncorRunner.runinaisuite/agents/runner.py, which handles the LLM loop, tool execution, and tracing. - Persist state by converting
RunResulttoRunStatewithto_state(), then resume conversations usingRunner.continue_sync. - Build hierarchies by wrapping sub-agents with
agent_toolfromaisuite/agents/tools.pyto enable nested autonomous workflows. - Enforce safety by implementing the
ToolPolicyprotocol to gate tool execution before the Runner invokes any function.
Frequently Asked Questions
What is the difference between Runner.run_sync and Runner.run?
Runner.run_sync provides a blocking, synchronous interface for executing agents, while Runner.run returns a coroutine for asynchronous execution. Both methods use the same underlying logic in aisuite/agents/runner.py for building messages and managing tool loops, but Runner.run allows you to integrate agent execution into async event loops for high-concurrency applications.
How do I persist agent conversation state between sessions?
Call RunResult.to_state() to serialize the current run into a RunState object containing the full message history and metadata. You can store this object in a database or file system, then later pass it to Runner.continue_sync to resume the conversation exactly where it left off, preserving all previous context and trace identifiers.
Can I restrict which tools an agent is allowed to call during a specific run?
Yes, by passing a tool_policy parameter to Runner.run_sync or Runner.run. This accepts any callable implementing the ToolPolicy protocol, which receives a ToolPolicyContext containing the tool name, arguments, and agent metadata. Return False or a ToolPolicyDecision(allowed=False) to block execution, or True to permit the tool call.
How do I make one agent call another agent as a tool?
Use the agent_tool function from aisuite/agents/tools.py to wrap an existing Agent instance as a callable. Append this wrapper to the parent agent's tools list with a optional name parameter. When the parent agent invokes the tool, aisuite automatically runs the sub-agent's LLM loop and returns the result to the parent, creating a hierarchical agent architecture.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →