How the Runner Class Executes Agent Workflows in aisuite: A Complete Technical Guide

The Runner class orchestrates end-to-end agent workflows by normalizing inputs, executing async or sync model calls, handling tool interactions, emitting detailed trace events, and persisting conversation state.

The Runner class in the andrewyng/aisuite repository serves as the central orchestrator that drives agent execution from prompt preparation through final output delivery. It abstracts away the complexity of managing LLM provider interactions, tool calling loops, and telemetry collection into a unified API. Understanding how the Runner class executes agent workflows reveals the architectural patterns that enable robust, observable, and stateful AI agent systems.

Execution Pipeline: From Input to Result

The execution flow in aisuite/agents/runner.py proceeds through twelve distinct stages that handle everything from input validation to final state persistence.

Initialization and Input Processing

Execution begins when users invoke Runner.run() (async) or Runner.run_sync() (sync). Both entry points delegate to the private _run_impl method, which serves as the unified execution engine.

Input normalization distinguishes between raw inputs (str or list) and pre-built RunState objects. The method _build_messages constructs the initial message list and injects system instructions according to the agent's configuration. Any stored artifacts—such as file references from previous turns—are rehydrated into the message payload via hydrate_messages to restore the full conversation context.

Request Configuration and Telemetry Setup

Before invoking the model, the runner merges configuration hierarchies. It combines agent.model_settings with user-provided kwargs, attaches available tools if defined, and sets max_turns to limit conversation length. An optional tool_policy can be injected to control tool execution behavior.

Tracing initialization generates a unique trace ID unless telemetry is disabled. The runner emits a run.started event and, for providers that don't emit native model events, a model.send event to all configured TraceSink instances. The set_active_run_context method then stores the current run context—including the client, trace ID, and agent name—enabling downstream utilities and tool wrappers to access execution metadata globally.

Model Execution and Response Handling

Depending on the use_async_client flag, the runner invokes either client.chat.completions.acreate (async) or .create (sync) with the prepared message payload. The implementation in _run_impl wraps these calls in comprehensive error handling to emit model.error and run.failed events when exceptions occur.

Upon successful completion, the response undergoes normalization. The runner constructs RunStep objects representing both the agent's output and the model's response, transforming any embedded tool events into distinct steps. This structured approach enables detailed inspection of the execution trajectory.

Finalization and State Management

The runner assembles a RunResult object containing the final output, raw provider responses, the updated message list, complete step history, and trace metadata. It emits model.response (if not already provided by the underlying provider) and iterates through tool events to emit tool.allowed, tool.completed, and related lifecycle events before finally signaling run.completed.

If a StateStore is supplied, the result undergoes dehydration—stripping artifact references to create a serializable snapshot—and persists under the specified thread_id. The cleanup phase ensures reset_active_run_context runs in a finally block, guaranteeing context isolation even when errors occur.

Key Architectural Components

Async and Sync Execution Modes

The Runner provides dual APIs to accommodate different runtime environments. run_sync() wraps the async implementation using asyncio.run(), making it suitable for scripts and notebooks, while run() supports native async/await patterns for high-concurrency applications.

Tracing and Observability

Every significant lifecycle event generates structured telemetry. The runner emits events for run start/completion, model requests/responses, tool executions, and error conditions. These events flow to configurable sinks defined in aisuite/tracing/sinks.py, enabling integration with monitoring systems and debugging tools.

State Persistence Architecture

The StateStore abstraction in aisuite/mcp/state_store.py enables conversation continuity across process boundaries. By dehydrating large artifacts (managed in aisuite/agents/artifact_store.py) and storing only references, the runner optimizes storage while maintaining the ability to reconstruct full conversation state.

Practical Code Examples

Basic Async Execution

from aisuite.agents.runner import Runner

async def execute_agent():
    result = await Runner.run(
        agent=my_agent,
        input="Analyze the quarterly sales data",
        max_turns=5,
    )
    print(result.final_output)

Synchronous Execution for Scripts

from aisuite.agents.runner import Runner

def quick_summary():
    result = Runner.run_sync(
        agent=my_agent,
        input="Summarize this article: ...",
        tracing_disabled=True,
    )
    return result.final_output

Continuing a Persisted Conversation

from aisuite.agents.runner import Runner
from aisuite.mcp.state_store import InMemoryStateStore

store = InMemoryStateStore()
thread_id = "user-session-123"

# Initial execution

first_result = Runner.run_sync(
    agent=my_agent,
    input="Initialize the analysis",
    state_store=store,
    thread_id=thread_id
)

# Resume later

continuation = Runner.run_sync(
    agent=my_agent,
    input="Provide more details on the second point",
    state_store=store,
    thread_id=thread_id
)

Core Files in the Execution Pipeline

File Role
aisuite/agents/runner.py Main orchestrator containing Runner class and _run_impl logic
aisuite/agents/types.py Defines Agent, RunResult, RunState, and RunStep dataclasses
aisuite/client.py LLM provider wrapper; instantiated by Runner when no client is provided
aisuite/tracing/sinks.py Implements TraceSink interface for telemetry emission
aisuite/mcp/state_store.py Abstract persistence layer for conversation continuity
aisuite/agents/artifact_store.py Manages serialization of large objects referenced in messages

Summary

  • The Runner class provides unified async (run) and sync (run_sync) entry points that delegate to _run_impl for execution orchestration.
  • Input processing involves _build_messages for system prompt injection and artifact hydration to restore conversation context.
  • Model invocation supports both async and sync client modes with comprehensive error handling and automatic trace event emission.
  • The execution flow generates detailed RunStep objects and RunResult instances containing full conversation history and metadata.
  • State persistence via StateStore enables conversation dehydration/rehydration, while the context management system ensures proper isolation across concurrent runs.

Frequently Asked Questions

What is the difference between run() and run_sync() in the aisuite Runner?

The run() method is the native async implementation that returns a coroutine and must be awaited, making it ideal for applications handling multiple concurrent agent workflows. The run_sync() method provides a synchronous wrapper that internally calls asyncio.run() on the async implementation, designed for convenience in scripts, notebooks, or legacy synchronous codebases.

How does the Runner handle tool calling during agent execution?

When an agent defines tools, the Runner attaches them to the model request and sets max_turns to enable multi-turn conversations. Tool calls embedded in model responses are transformed into RunStep objects with specific event types (tool.allowed, tool.completed), allowing the execution loop to continue until all tool interactions resolve or the turn limit is reached.

Can I persist and resume agent conversations across different processes?

Yes, by supplying a StateStore implementation (such as InMemoryStateStore or a custom database-backed store) and a consistent thread_id, the Runner automatically dehydrates the conversation state—stripping large artifacts to references—and persists it. Subsequent calls with the same thread_id and store instance resume the conversation from the previous state.

How does tracing work in the Runner class?

The Runner initializes a trace ID at execution start and emits structured events including run.started, model.send, model.response, tool.completed, and run.completed to all configured TraceSink instances. This occurs throughout the pipeline in aisuite/agents/runner.py, enabling comprehensive observability without requiring manual instrumentation in agent definitions.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →