# How to Build Autonomous Agents with the aisuite Agents API

> Learn to build autonomous agents using the aisuite Agents API. Instantiating the Agent dataclass with models, instructions, and tools enables powerful agent creation.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-06-16

---

**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`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py), [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py), and [`aisuite/agents/tools.py`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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.

```python
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`](https://github.com/andrewyng/aisuite/blob/main/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`.

```python
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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py). This method converts a completed run into a `RunState` object containing the full message history, metadata, and execution steps.

```python

# 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`](https://github.com/andrewyng/aisuite/blob/main/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.

```python

# 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`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py), allows you to implement pre-execution gates that receive a `ToolPolicyContext` and return either a boolean or a `ToolPolicyDecision`.

```python
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 `Agent` dataclass in [`aisuite/agents/types.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/types.py), specifying models, instructions, and Python functions as tools.
- **Execute runs** via `Runner.run_sync` or `Runner.run` in [`aisuite/agents/runner.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/runner.py), which handles the LLM loop, tool execution, and tracing.
- **Persist state** by converting `RunResult` to `RunState` with `to_state()`, then resume conversations using `Runner.continue_sync`.
- **Build hierarchies** by wrapping sub-agents with `agent_tool` from [`aisuite/agents/tools.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/agents/tools.py) to enable nested autonomous workflows.
- **Enforce safety** by implementing the `ToolPolicy` protocol 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`](https://github.com/andrewyng/aisuite/blob/main/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`](https://github.com/andrewyng/aisuite/blob/main/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.