The AgentRun type

The AgentRun type is the core data structure in Lasagna AI. It represents what the agent generated. It serves as both the input and output format for agents, enabling seamless composition and layering.

# This page will use the following imports:
from lasagna import AgentRun, Model, EventCallback, Message
from lasagna import recursive_extract_messages, flat_messages
from lasagna import recursive_sum_costs

What is an AgentRun?

An AgentRun is a TypedDict that captures an agent’s execution:

  • which agent ran (agent name, model, provider)
  • what it generated (messages, structured data, or downstream AgentRuns)

Indeed, the AgentRun data structure is recursive! That is, an AgentRun can contain other AgentRuns. This recursive nature reflects the execution path of layered agents (i.e., when an agent uses another agent during its execution).

The Four Types of AgentRun

The AgentRun type is a Union of four different execution patterns:

from lasagna import (
    # These are the unioned types:
    AgentRunMessageList,
    AgentRunParallel,
    AgentRunChained,
    AgentRunExtraction,
)

1. AgentRunMessageList — Standard Conversation

This is the most common type — a simple conversation between human and AI.

simple_conversation: AgentRunMessageList = {
    "type": "messages",
    "agent": "my_chat_agent",  # ← agent name can be anything you want!
    "messages": [
        {"role": "human", "text": "Hello!"},
        {"role": "ai", "text": "Hi there! How can I help?"},
    ],
}

2. AgentRunParallel — Concurrent Execution

Used when an agent spawns multiple sub-agents to work simultaneously (in parallel).

parallel_subagents: AgentRunParallel = {
    "type": "parallel",
    "agent": "committee_agent",  # ← agent name can be anything you want!
    "runs": [
        # Multiple AgentRuns that executed in parallel:
        # {...}, {...}, {...}
    ],
}

3. AgentRunChained — Sequential Execution

Used when an agent coordinates a sequence of sub-agents (“chained subagents”).

chained_subagents: AgentRunChained = {
    "type": "chain",
    "agent": "pipeline_agent",  # ← agent name can be anything you want!
    "runs": [
        # AgentRuns that executed one after another:
        # {...}, {...}, {...}
    ],
}

4. AgentRunExtraction — Structured Output

Used when an agent extracts structured data from its input.

extracted_info: AgentRunExtraction = {
    "type": "extraction",
    "agent": "data_extractor",  # ← agent name can be anything you want!
    "messages": [
        # Model's generated messages will be here.
        # Consider this an implementation detail; see the `result` below
        # for the actual "extraction result".
    ],
    "result": {
        # This is the payload that was extracted:
        "name": "John Doe",
        "email": "john@example.com", 
        "age": 30,
    },
}

Patterns

Much of your job writing code that uses Lasagna will be “wrapping” and “unwrapping” AgentRun objects. Each agent must:

  1. Consider the AgentRuns it has as input.
  2. (zero, one, or more times) Invoke its model to generate new messages.
  3. (zero, one, or more times) Invoke downstream agents.
  4. Wrap those messages (from step 2) and/or downstream AgentRuns (from step 3) into a new AgentRun and return it.

Let’s discuss this pattern and give some helper functions along the way!

Agent Composition Pattern

The AgentRun type enables Lasagna’s core composition pattern. Recall the standard agent signature:

async def my_agent(
    model: Model,
    event_callback: EventCallback,
    prev_runs: list[AgentRun],  # ← Input: previous AgentRuns
) -> AgentRun:                  # ← Output: new AgentRun
    # Agent logic here... normally you'd do something with `prev_runs`.
    return {
        "type": "messages",
        "agent": "my_agent",
        "messages": [
            {"role": "ai", "text": "Hi there! How can I help?"},
        ],
    }

This pattern allows agents to:

  • Build on previous work by analyzing prev_runs.
  • Chain together by passing outputs as inputs to next agents.
  • Compose into larger systems through layering and delegation.

Recursive Extract Messages

Since an AgentRun can be a recursive data structure, yet models require a flattened list of messages, there’s a convenience function to recursively extract all the messages found in an AgentRun:

recursive_extract_messages([simple_conversation], from_tools=False, from_extraction=False)
[{'role': 'human', 'text': 'Hello!'},
 {'role': 'ai', 'text': 'Hi there! How can I help?'}]

Wrap Messages into an AgentRun

It is common that you have a list of messages that you want to return as an AgentRun, so there’s a simple convenience function for that as well!

messages: list[Message] = [
    {"role": "human", "text": "Hello!"},
    {"role": "ai", "text": "Hi there! How can I help?"},
]

flat_messages('my_agent', messages)
{'agent': 'my_agent',
 'type': 'messages',
 'messages': [{'role': 'human', 'text': 'Hello!'},
  {'role': 'ai', 'text': 'Hi there! How can I help?'}]}

Put it all together!

Using the patterns above, we’ve derived the “most basic agent” from the Quickstart. Recall, this agent is very simple:

  1. It extracts all previous messages.
  2. Passes those messages to the model, which generates new messages.
  3. Returns those new messages as an AgentRun.
async def my_basic_agent(
    model: Model,
    event_callback: EventCallback,
    prev_runs: list[AgentRun],
) -> AgentRun:
    messages = recursive_extract_messages(prev_runs, from_tools=False, from_extraction=False)
    new_messages = await model.run(event_callback, messages, tools=[])
    this_run = flat_messages('my_agent', new_messages)
    return this_run

Other Benefits of the AgentRun type

Canonized Message Representations 😇

The AgentRun type provides a standardized format for representing any agent execution, regardless of which agent or which underlying model was invoked. Whether your agent is a simple chatbot or a complex multi-agent system, it all gets represented in the same consistent format.

This canonization means:

  • Type safety: Static analysis catches mismatches at development time! See Intro to Type Hints if you’re new to static type checking.
  • Model swapping: You can swap providers or models at any point; data types will stay the same! See Model-swap Example.
  • Layering: You can develop an agent as the root, then later on use it as a subagent. See Layering.

Token Count Preservation

Unlike other AI frameworks, Lasagna AI meticulously tracks token usage throughout your entire agent system. Token counts are preserved inside every AgentRun, no matter how many layers of agents you stack.

Each Message within an AgentRun can include cost information:

this_run: AgentRun = {
    "type": "messages",
    "agent": "my_agent",
    "messages": [
        {
            "role": "ai",
            "text": "Hi there! How can I help?",
            "cost": {
                "input_tokens": 150,
                "output_tokens": 75, 
                "total_tokens": 225,
            },
        },
    ],
}

Why this matters:

  • Accurate billing: Know exactly what each agent execution costs.
  • Performance optimization: Identify expensive operations in complex systems.
  • Budget management: Set limits and track usage across layered agents.
  • No surprises: Token counts don’t get lost in multi-agent workflows.

Here is a convenient helper function to recursively sum the cost across an entire AgentRun:

recursive_sum_costs(this_run)
{'input_tokens': 150, 'output_tokens': 75, 'total_tokens': 225}

Immutable by Design

AgentRun follows functional-programming principles — once created, it never changes. This provides:

  • Thread and coroutine safety: No race conditions in concurrent environments.
  • Predictable behavior: No surprise modifications to debug.

JSON Serializable

Since AgentRun is a TypedDict, it’s just a Python dict at runtime:

  • Database storage: Store directly in JSON columns or document stores.
  • API communication: Send over HTTP/WebSocket without complicated serialization.
  • Caching: Easy to cache and retrieve from Redis, memcached, etc.
  • Logging: Human-readable, and can be pretty printed into logs.
Database Storage

The AgentRun type makes database storage easy. See Database Management for more information.

Best of two worlds!

You can have both streaming and easy database storage. See Streaming & Events for how to stream, which is an independent feature, so you can have both!