# 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
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.
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
AgentRun
s)
Indeed, the AgentRun
data structure is recursive! That is, an AgentRun
can contain other AgentRun
s. 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:
- Consider the
AgentRun
s it has as input. - (zero, one, or more times) Invoke its
model
to generate new messages. - (zero, one, or more times) Invoke downstream agents.
- Wrap those messages (from step 2) and/or downstream
AgentRun
s (from step 3) into a newAgentRun
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,list[AgentRun], # ← Input: previous AgentRuns
prev_runs: -> 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
:
=False, from_extraction=False) recursive_extract_messages([simple_conversation], from_tools
[{'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!
list[Message] = [
messages: "role": "human", "text": "Hello!"},
{"role": "ai", "text": "Hi there! How can I help?"},
{
]
'my_agent', messages) flat_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:
- It extracts all previous messages.
- Passes those messages to the model, which generates new messages.
- Returns those new messages as an
AgentRun
.
async def my_basic_agent(
model: Model,
event_callback: EventCallback,list[AgentRun],
prev_runs: -> AgentRun:
) = recursive_extract_messages(prev_runs, from_tools=False, from_extraction=False)
messages = await model.run(event_callback, messages, tools=[])
new_messages = flat_messages('my_agent', new_messages)
this_run 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.
The AgentRun
type makes database storage easy. See Database Management for more information.
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!