Skip to content

Python API

Use the Python API to run a YAML experiment, supply custom information to agents, or change their accounts between periods. The examples below introduce each task, followed by the supported functions and types.

Code using await can run directly in a notebook. In a script, put it inside an async def main() function and call asyncio.run(main()) after importing asyncio.

Run an experiment

Most experiments need only two functions from the Python API. run starts an experiment from a YAML file and returns its output directory. load_config lets you inspect and validate the configuration before running it.

In a notebook, you can call run with await:

import aigora

run_dir = await aigora.run("examples/tutorials/first_experiment/experiment.yaml")
print(run_dir)

In a Python script, use asyncio.run(aigora.run(path)) to start the asynchronous call. Set display=False if you do not need the live market display.

To check a configuration without making model requests, load it first:

config = aigora.load_config(
    "examples/tutorials/parameter_sweeps/experiment.yaml",
    variables={"reasoning_effort": "medium", "seed": 43},
)
print(config.protocol.periods)
print(config.agents[0].lm.reasoning_effort)

This returns an ExperimentConfig containing the resolved settings and an AgentConfig for each agent group. The example prints 4 and medium. The variables argument works the same way in run: each key must name a declared YAML variable, and its value replaces that variable for the call.

Configuration checks and runtime checks

load_config resolves variables and validates the configuration itself without making model requests. Prompt-file loading and provider and hook construction happen when the run is prepared, so a successful configuration check does not guarantee those steps will succeed.

See YAML configuration for all fields and defaults.

run async

run(
    config_path: str | Path,
    *,
    display: bool = True,
    variables: Mapping[str, Any] | None = None,
) -> Path

Run the experiment described by a YAML file and return its output path.

Use variables to override inputs declared in the file's top-level variables section. These overrides follow the same rules as load_config and apply only to this call. Set display=False to hide the live market display, for example when running a batch.

Aigora resolves and validates the inputs before creating a run directory. The returned path contains the run's configuration, market data, and traces.

load_config

load_config(
    path: Path | str,
    *,
    variables: Mapping[str, Any] | None = None,
) -> ExperimentConfig

Read and validate a YAML configuration without running the experiment.

If the file declares a top-level variables section, references to those inputs are resolved before validation. Pass overrides through variables; each key must name a declared input and replaces that input's entire value. Without a variables section, template-like text stays literal.

The result is an ExperimentConfig that you can inspect before making model requests. Prompt files are loaded later, when the run is prepared, and their contents are never interpolated.

ExperimentConfig

The validated settings returned by load_config.

This object contains the protocol, assets, markets, and agent groups after variable substitution. Access nested settings as attributes, such as config.protocol.periods or config.agents[0].lm.model.

name instance-attribute

name: _Text

output_dir instance-attribute

output_dir: _Text

protocol instance-attribute

protocol: ProtocolConfig

assets class-attribute instance-attribute

assets: list[AssetConfig] = Field(min_length=1)

markets class-attribute instance-attribute

markets: list[MarketConfig] = Field(min_length=1)

agents class-attribute instance-attribute

agents: list[AgentConfig] = Field(min_length=1)

description class-attribute instance-attribute

description: str = ''

seed class-attribute instance-attribute

seed: _Integer = 42

AgentConfig

The shared configuration for one group of agents.

The n field determines how many participants to create. For example, id="buyer" and n=3 create buyer_1, buyer_2, and buyer_3, each with its own conversation and provider instances.

id instance-attribute

id: _Identifier

n class-attribute instance-attribute

n: _PositiveInteger = 1

lm instance-attribute

lm: LMConfig

prompt instance-attribute

prompt: PromptConfig

initial_holdings instance-attribute

initial_holdings: InitialHoldings

response class-attribute instance-attribute

response: ResponseConfig = Field(
    default_factory=ResponseConfig
)

error_handling class-attribute instance-attribute

error_handling: ErrorHandlingConfig = Field(
    default_factory=ErrorHandlingConfig
)

trading_constraints class-attribute instance-attribute

trading_constraints: TradingConstraintsConfig = Field(
    default_factory=TradingConstraintsConfig
)

Providers

A provider turns information about the experiment into text an agent can read. To write one, implement get_context(context) and return a string. You do not need to inherit from ContextProvider; the class below satisfies the interface by providing that method:

import aigora

class RoundMessage:
    def __init__(self, *, message: str):
        self.message = message

    def get_context(self, context: aigora.ProviderContext) -> str:
        return f"Round {context.experiment.period}: {self.message}"

Save the class in an importable module, such as my_providers.py in your working directory, and add it to an agent group's prompt.context_providers list:

- type: my_providers.RoundMessage
  params:
    message: You may trade one unit in this round.

Aigora passes params to the constructor as keyword arguments and creates a separate instance for each agent. Before each decision, it calls get_context with a read-only snapshot. In round 1, this provider returns Round 1: You may trade one unit in this round. The custom information example develops the same idea into a provider for private-value schedules.

ProviderContext gives the provider access to the following information:

ProviderContext field Contains
agent This agent's ID, holdings, orders, current-period actions, constraints, and custom information
assets Asset catalogue keyed by ID, with names and descriptions
markets Market snapshots keyed by ID
experiment Current period and captured elapsed timing
trades, cancellations Complete trade and cancellation histories for each market, including earlier periods
previous Previous committed observation, or None initially
trades_since_previous, cancellations_since_previous Events since that observation; empty on the first update
observation Current snapshot with per-market history positions

Cancellation records identify the order, agent, market, and positive quantity cancelled. ProviderObservation is the saved snapshot used as the baseline for a later update. It includes positions in the event histories so incremental providers can identify new trades and cancellations.

Providers read state; to change an agent's holdings or stored information, use a period hook.

ContextProvider

Supply a section of text for an agent's decision update.

Implement get_context on your own class and return the information the agent should receive. The supplied context is a read-only snapshot, so rendering an update does not change the experiment's state.

get_context

get_context(context: ProviderContext) -> str

ProviderContext dataclass

ProviderContext(
    agent: AgentState,
    markets: Mapping[str, MarketState],
    experiment: ExperimentState,
    trades: Mapping[str, tuple[Trade, ...]] = (
        lambda: _mapping()
    )(),
    previous: ProviderObservation | None = None,
    trades_since_previous: Mapping[
        str, tuple[Trade, ...]
    ] = (lambda: _mapping())(),
    cancellations: Mapping[
        str, tuple[OrderCancellation, ...]
    ] = _mapping(),
    cancellations_since_previous: Mapping[
        str, tuple[OrderCancellation, ...]
    ] = _mapping(),
    *,
    assets: Mapping[str, AssetInfo],
)

The information available to a provider for the current decision.

Read agent for this participant's state and markets for the market snapshots. The context also includes timing, asset information, event histories, and the previous committed observation when one exists. Snapshot mappings are read-only.

agent instance-attribute

agent: AgentState

markets instance-attribute

markets: Mapping[str, MarketState]

experiment instance-attribute

experiment: ExperimentState

trades class-attribute instance-attribute

trades: Mapping[str, tuple[Trade, ...]] = field(
    default_factory=lambda: _mapping()
)

previous class-attribute instance-attribute

previous: ProviderObservation | None = None

trades_since_previous class-attribute instance-attribute

trades_since_previous: Mapping[str, tuple[Trade, ...]] = (
    field(default_factory=lambda: _mapping())
)

cancellations class-attribute instance-attribute

cancellations: Mapping[
    str, tuple[OrderCancellation, ...]
] = field(default_factory=_mapping)

cancellations_since_previous class-attribute instance-attribute

cancellations_since_previous: Mapping[
    str, tuple[OrderCancellation, ...]
] = field(default_factory=_mapping)

assets class-attribute instance-attribute

assets: Mapping[str, AssetInfo] = field(kw_only=True)

observation property

observation: ProviderObservation

ProviderObservation dataclass

ProviderObservation(
    agent: AgentState,
    markets: Mapping[str, MarketState],
    experiment: ExperimentState,
    trade_positions: Mapping[str, int],
    cancellation_positions: Mapping[str, int] = _mapping(),
    *,
    assets: Mapping[str, AssetInfo],
)

A saved observation used as the baseline for a later provider update.

Alongside the agent, market, and experiment snapshots, it records how far the observation reached in each trade and cancellation history. Incremental providers use those positions to identify events the agent has not yet seen.

agent instance-attribute

agent: AgentState

markets instance-attribute

markets: Mapping[str, MarketState]

experiment instance-attribute

experiment: ExperimentState

trade_positions instance-attribute

trade_positions: Mapping[str, int]

cancellation_positions class-attribute instance-attribute

cancellation_positions: Mapping[str, int] = field(
    default_factory=_mapping
)

assets class-attribute instance-attribute

assets: Mapping[str, AssetInfo] = field(kw_only=True)

Hooks

Hooks let you change the experiment at the start or end of a period. For example, you can restore trading accounts before a round or pay a dividend after it. Implement on_period_start(context) for a start hook or on_period_end(context) for an end hook. The corresponding context contains the current period number, the live agents keyed by ID, and a shared seeded random-number generator, rng.

To change an account, assign a new Holdings value to agent.holdings. To provide information for later decisions, call agent.set_information(key, text); a provider can then read it from context.agent.information. Use the supplied rng for random assignments that should follow the experiment seed.

Start hooks run before the opening snapshot and trading clock, including in period 1. End hooks run after successful trading, before closing snapshots and order cleanup, including in the final period. Each configured list runs in order, and a failed hook stops the run without undoing changes that have already happened.

Adding multiple rounds shows a start hook that restores trading accounts. Here is an end hook that pays one cash unit for each unit of a chosen asset held by an agent:

import aigora

class Dividend:
    def __init__(self, *, asset_id: str):
        self.asset_id = asset_id

    def on_period_end(self, context: aigora.PeriodEndContext) -> None:
        for agent in context.agents.values():
            holdings = agent.holdings
            units = holdings.units.get(self.asset_id, 0)
            agent.holdings = aigora.Holdings(
                cash=holdings.cash + units,
                units=holdings.units,
            )

The loop reads each agent's holdings and replaces them with a new account containing the dividend. Asset quantities stay the same. Save this class in an importable module, such as my_hooks.py in the working directory, then add it under experiment.protocol.hooks.period_end:

- type: my_hooks.Dividend
  params:
    asset_id: asset

In this configuration, an agent holding three units of asset receives three cash units at the end of each completed period.

PeriodStartHook

Run custom code before each trading period, including the first.

Implement on_period_start to change accounts or assign information before the opening snapshots are recorded and the trading clock starts. The context contains live agents and a seeded random-number generator.

on_period_start

on_period_start(context: PeriodStartContext) -> None

PeriodStartContext dataclass

PeriodStartContext(
    period: int,
    agents: Mapping[str, _PeriodAgent],
    rng: Random,
)

The live agents and random-number generator supplied to a start hook.

period identifies the round that is about to begin. The agents mapping is read-only, but its agent objects can have their holdings or stored information changed by the hook.

period instance-attribute

period: int

agents instance-attribute

agents: Mapping[str, _PeriodAgent]

rng instance-attribute

rng: Random

PeriodEndHook

Run custom code after a period finishes successfully.

Implement on_period_end to settle accounts or store information before the closing snapshots and order cleanup. The context contains live agents and the experiment's seeded random-number generator.

on_period_end

on_period_end(context: PeriodEndContext) -> None

PeriodEndContext dataclass

PeriodEndContext(
    period: int,
    agents: Mapping[str, _PeriodAgent],
    rng: Random,
)

The live agents and random-number generator supplied to an end hook.

period identifies the round that has just completed successfully. The hook can update each agent before closing snapshots are recorded.

period instance-attribute

period: int

agents instance-attribute

agents: Mapping[str, _PeriodAgent]

rng instance-attribute

rng: Random

State and actions

These types describe the participants, market state, and actions that providers and hooks work with. Agent is the live object available to hooks, while AgentState is the read-only snapshot available to providers. The other value objects capture information at a particular point in the experiment.

Types What they represent
Holdings, TradingConstraints An agent's cash and asset quantities, and the rules governing new orders
AssetInfo, MarketState Asset names and descriptions, open orders, recent trades, and market rules
ExperimentState, ElapsedTiming The current period and the elapsed trading time when an observation was captured
Order, Trade, ActionRecord An order, a completed exchange, or a decision with its outcome
PlaceBid, PlaceAsk A request to buy or sell a quantity at a specified limit price
CancelOrder, DoNothing A request to cancel an order, or a decision to take no action
DecisionContext The provider update and captured state supplied for a decision
ParsedResponse, LLMResponse The parsed action and reason, and the model response with usage and attempt details

For example, a PlaceBid contains a market ID, a price, and a quantity. It describes the requested action; the market still checks whether the agent has enough cash and whether the order meets its trading constraints.

Agent

Agent(
    agent_id: str,
    holdings: Holdings,
    *,
    policy: _AgentPolicy,
    trading_constraints: TradingConstraints | None = None,
)

A live participant whose account can be changed by a period hook.

Providers receive an AgentState snapshot instead. To change the live account, assign a new Holdings value to holdings. Use set_information to store text for subsequent provider updates.

Attributes:

Name Type Description
agent_id str

Unique identifier for this agent instance.

holdings Holdings

Current cash and unit balances.

trading_constraints TradingConstraints

Limits checked when the agent submits an order.

policy _AgentPolicy

Decision policy supplied at construction.

outstanding_orders dict[str, Order]

Outstanding orders keyed by order ID.

action_history list[ActionRecord]

Actions taken during the current period.

Initialize the agent with starting holdings.

Parameters:

Name Type Description Default
agent_id str

Unique agent identifier.

required
holdings Holdings

Initial holdings. Cash and unit mappings are copied.

required
policy _AgentPolicy

Decision policy for this Agent.

required
trading_constraints TradingConstraints | None

Optional institutional role, position, and outstanding-order limits.

None

available_cash property

available_cash: Decimal

Return settled cash not reserved by outstanding bids.

state

state() -> AgentState

Return a read-only snapshot of this agent's current state.

The snapshot includes holdings, open orders, current-period actions, trading constraints, and stored information.

set_information

set_information(key: str, value: str) -> None

Store text that providers can read in subsequent observations.

Both the key and value must be nonblank strings. A provider accesses the saved value through context.agent.information[key].

available_units

available_units(market_id: str) -> int

Return unreserved sell capacity, including a configured short allowance.

AgentState dataclass

AgentState(
    agent_id: str,
    holdings: Holdings,
    outstanding_orders: tuple[Order, ...] = (),
    action_history: tuple[ActionRecord, ...] = (),
    information: Mapping[str, str] = _mapping(),
    trading_constraints: TradingConstraints = TradingConstraints(),
)

A read-only snapshot of one agent at the time of an observation.

It includes holdings, open orders, actions from the current period, trading constraints, and custom information saved through Agent.set_information. Providers read this snapshot rather than the live agent object.

agent_id instance-attribute

agent_id: str

holdings instance-attribute

holdings: Holdings

outstanding_orders class-attribute instance-attribute

outstanding_orders: tuple[Order, ...] = ()

action_history class-attribute instance-attribute

action_history: tuple[ActionRecord, ...] = ()

information class-attribute instance-attribute

information: Mapping[str, str] = field(
    default_factory=_mapping
)

trading_constraints class-attribute instance-attribute

trading_constraints: TradingConstraints = field(
    default_factory=TradingConstraints
)

AssetInfo dataclass

AssetInfo(asset_id: str, name: str, description: str = '')

An asset's configured ID, display name, and description.

Built-in providers use the name when describing the asset to agents. The description is available to custom providers but is not automatically included in an agent's update.

asset_id instance-attribute

asset_id: str

name instance-attribute

name: str

description class-attribute instance-attribute

description: str = ''

Holdings dataclass

Holdings(
    cash: Decimal = Decimal(0),
    units: Mapping[str, int] = (lambda: _mapping())(),
)

An account's settled cash and asset quantities.

units maps asset IDs to integer positions. To change an account in a hook, create a new Holdings value and assign it to agent.holdings. The stored mapping is read-only.

cash class-attribute instance-attribute

cash: Decimal = Decimal(0)

units class-attribute instance-attribute

units: Mapping[str, int] = field(
    default_factory=lambda: _mapping()
)

TradingConstraints dataclass

TradingConstraints(
    allowed_sides: frozenset[Literal["bid", "ask"]]
    | None = None,
    max_units: Mapping[str, int] = (lambda: _mapping())(),
    max_outstanding_orders: int | None = None,
    min_units: Mapping[str, int] = (lambda: _mapping())(),
    max_order_quantity: int | None = None,
    min_order_quantity: int = 1,
)

Limits checked when an agent submits an order.

These settings restrict order sides, positions, individual quantities, and the number of open orders. The market also checks available cash, inventory, and whether short selling is allowed.

allowed_sides class-attribute instance-attribute

allowed_sides: frozenset[Literal["bid", "ask"]] | None = (
    None
)

max_units class-attribute instance-attribute

max_units: Mapping[str, int] = field(
    default_factory=lambda: _mapping()
)

max_outstanding_orders class-attribute instance-attribute

max_outstanding_orders: int | None = None

min_units class-attribute instance-attribute

min_units: Mapping[str, int] = field(
    default_factory=lambda: _mapping()
)

max_order_quantity class-attribute instance-attribute

max_order_quantity: int | None = None

min_order_quantity class-attribute instance-attribute

min_order_quantity: int = 1

MarketState dataclass

MarketState(
    market_id: str,
    bids: tuple[Order, ...] = (),
    asks: tuple[Order, ...] = (),
    recent_trades: tuple[Trade, ...] = (),
    short_selling_allowed: bool | None = None,
)

A snapshot of one market's open orders, recent trades, and rules.

Providers can inspect bids and asks to describe the order book. When constructing a snapshot yourself, set short_selling_allowed so holdings providers can determine how much an agent may sell.

market_id instance-attribute

market_id: str

bids class-attribute instance-attribute

bids: tuple[Order, ...] = ()

asks class-attribute instance-attribute

asks: tuple[Order, ...] = ()

recent_trades class-attribute instance-attribute

recent_trades: tuple[Trade, ...] = ()

short_selling_allowed class-attribute instance-attribute

short_selling_allowed: bool | None = None

ExperimentState dataclass

ExperimentState(
    experiment_id: str,
    period: int,
    periods_total: int,
    periods_remaining: int,
    timing: ElapsedTiming,
)

The experiment's identity, current period, and captured timing.

Period numbers start at 1. A provider can use period to select information for the current round, such as a private value that changes halfway through the experiment.

experiment_id instance-attribute

experiment_id: str

period instance-attribute

period: int

periods_total instance-attribute

periods_total: int

periods_remaining instance-attribute

periods_remaining: int

timing instance-attribute

timing: ElapsedTiming

ElapsedTiming dataclass

ElapsedTiming(
    elapsed_seconds: float, period_duration_seconds: float
)

The trading time captured with an observation.

elapsed_seconds measures time since the period's trading clock started, and period_duration_seconds gives the configured length of the period.

elapsed_seconds instance-attribute

elapsed_seconds: float

period_duration_seconds instance-attribute

period_duration_seconds: float

Order dataclass

Order(
    order_id: str,
    agent_id: str,
    market_id: str,
    side: Literal["bid", "ask"],
    price: Decimal,
    quantity: int,
    period: int,
    arrival_sequence: int | None = None,
)

An order in a market, with its owner, price, and remaining quantity.

The side is bid for a buy order or ask for a sell order. order_id identifies the order when requesting a cancellation or matching it to a recorded trade.

order_id instance-attribute

order_id: str

agent_id instance-attribute

agent_id: str

market_id instance-attribute

market_id: str

side instance-attribute

side: Literal['bid', 'ask']

price instance-attribute

price: Decimal

quantity instance-attribute

quantity: int

period instance-attribute

period: int

arrival_sequence class-attribute instance-attribute

arrival_sequence: int | None = None

Trade dataclass

Trade(
    trade_id: str,
    market_id: str,
    buyer_id: str,
    seller_id: str,
    price: Decimal,
    quantity: int,
    bid_order_id: str,
    ask_order_id: str,
)

A completed exchange between a buyer and a seller.

The record gives the execution price and quantity, along with both agent IDs and the IDs of the bid and ask that matched.

trade_id instance-attribute

trade_id: str

market_id instance-attribute

market_id: str

buyer_id instance-attribute

buyer_id: str

seller_id instance-attribute

seller_id: str

price instance-attribute

price: Decimal

quantity instance-attribute

quantity: int

bid_order_id instance-attribute

bid_order_id: str

ask_order_id instance-attribute

ask_order_id: str

ActionRecord dataclass

ActionRecord(
    period: int,
    parsed_response: ParsedResponse,
    status: str = "unknown",
    rejection_reason: str | None = None,
    order_id: str | None = None,
)

A decision and its outcome within a trading period.

The record pairs the parsed response with a status, an optional rejection reason, and the order ID assigned to an accepted order.

period instance-attribute

period: int

parsed_response instance-attribute

parsed_response: ParsedResponse

status class-attribute instance-attribute

status: str = 'unknown'

rejection_reason class-attribute instance-attribute

rejection_reason: str | None = None

order_id class-attribute instance-attribute

order_id: str | None = None

PlaceBid dataclass

PlaceBid(market_id: str, price: Decimal, quantity: int)

Request to buy a quantity in a market at the given limit price.

The market checks the request against the agent's available cash and trading constraints before accepting it.

market_id instance-attribute

market_id: str

price instance-attribute

price: Decimal

quantity instance-attribute

quantity: int

PlaceAsk dataclass

PlaceAsk(market_id: str, price: Decimal, quantity: int)

Request to sell a quantity in a market at the given limit price.

The market checks the agent's available units, position limits, and short-selling permission before accepting it.

market_id instance-attribute

market_id: str

price instance-attribute

price: Decimal

quantity instance-attribute

quantity: int

CancelOrder dataclass

CancelOrder(market_id: str, order_id: str)

Request to cancel an agent's open order.

Supply both its market ID and full order ID. Cancelling removes any remaining quantity; it does not undo trades that already took place.

market_id instance-attribute

market_id: str

order_id instance-attribute

order_id: str

DoNothing dataclass

DoNothing()

Take no market action for this decision.

An agent can deliberately choose to wait. Aigora also uses this action to record a failed decision when the failure policy is do_nothing.

DecisionContext dataclass

DecisionContext(prompt: str, state: ProviderContext)

The update text and captured state supplied to a decision policy.

prompt contains the combined provider output for this decision, while state contains the structured ProviderContext behind that update. The prompt alone is not the agent's full conversation history.

prompt instance-attribute

prompt: str

state instance-attribute

state: ProviderContext

ParsedResponse dataclass

ParsedResponse(
    reason: str,
    action: Action,
    raw_text: str,
    retry_attempts: int = 0,
    parse_error: str | None = None,
    llm_response: LLMResponse | None = None,
)

A decision's action and reason, together with its response metadata.

raw_text preserves the original response. Retry and parse-error fields describe any failed attempts, while llm_response carries model usage when available. The action still needs to pass the market's trading rules.

reason instance-attribute

reason: str

action instance-attribute

action: Action

raw_text instance-attribute

raw_text: str

retry_attempts class-attribute instance-attribute

retry_attempts: int = 0

parse_error class-attribute instance-attribute

parse_error: str | None = None

llm_response class-attribute instance-attribute

llm_response: LLMResponse | None = None

LLMResponse dataclass

LLMResponse(
    text: str,
    tokens_in: int | None,
    tokens_out: int | None,
    latency_ms: int,
    error: str | None = None,
    cached_tokens_in: int | None = None,
    cache_creation_tokens_in: int | None = None,
    reasoning_tokens: int | None = None,
    provider: str | None = None,
    model: str | None = None,
    finish_reason: FinishReason | None = None,
    failure_kind: LLMFailureKind | None = None,
    attempts: tuple[LLMAttempt, ...] = (),
    conversation: ConversationTurn | None = None,
)

The model response and usage information for a decision.

This value combines the final response text with usage totals, latency, failure information, and the individual request attempts. Usage categories that the model provider did not report are None.

text instance-attribute

text: str

tokens_in instance-attribute

tokens_in: int | None

tokens_out instance-attribute

tokens_out: int | None

latency_ms instance-attribute

latency_ms: int

error class-attribute instance-attribute

error: str | None = None

cached_tokens_in class-attribute instance-attribute

cached_tokens_in: int | None = None

cache_creation_tokens_in class-attribute instance-attribute

cache_creation_tokens_in: int | None = None

reasoning_tokens class-attribute instance-attribute

reasoning_tokens: int | None = None

provider class-attribute instance-attribute

provider: str | None = None

model class-attribute instance-attribute

model: str | None = None

finish_reason class-attribute instance-attribute

finish_reason: FinishReason | None = None

failure_kind class-attribute instance-attribute

failure_kind: LLMFailureKind | None = None

attempts class-attribute instance-attribute

attempts: tuple[LLMAttempt, ...] = ()

conversation class-attribute instance-attribute

conversation: ConversationTurn | None = None

Errors

When an experiment fails after its output directory has been reserved, run raises ExperimentRunError. The exception gives you the partial output path in run_dir and the original execution error in cause. This lets a batch script record a failed experiment without losing track of its files:

try:
    run_dir = await aigora.run("experiment.yaml", display=False)
except aigora.ExperimentRunError as error:
    print("Partial output:", error.run_dir)
    print("Run failed:", error.cause)

Configuration errors happen before an output directory is reserved and are raised directly, rather than wrapped in ExperimentRunError.

AgentDecisionError describes a decision that could not be completed within the agent's retry policy. Its parsed attribute retains the response, retry count, and failure details for the trace. The default failure policy, abort, stops the experiment. With do_nothing, aigora records the failure and continues without adding the failed exchange to the agent's conversation.

ExperimentRunError

ExperimentRunError(run_dir: Path, cause: Exception)

Report an execution failure after the run directory was created.

run_dir points to the partial output, and cause holds the original exception. Configuration errors raised before reserving output are passed through directly instead of being wrapped in this error.

AgentDecisionError

AgentDecisionError(message: str, parsed: ParsedResponse)

Report a decision that cannot be completed within the retry policy.

A policy raises this error when it has exhausted its retries or encounters a failure that cannot be retried. An intermediate failed attempt should be handled within the policy if another attempt will follow.

The parsed response preserves the final raw text, parse error, number of retries, and aggregate model usage when available. Aigora uses those details to record the failed decision before applying its failure policy.

The default policy, abort, stops the run. With do_nothing, aigora records a failed no-op and continues without committing the failed exchange to the conversation.

Initialise the error with a human-readable message and trace payload.

message is forwarded to Exception.__init__ as usual and is therefore available via str(exc) and exc.args[0]. parsed is stored as self.parsed and is accessed directly by callers — typically the trace logger inside AsyncPeriodRunner — to record token usage, retry counts, and the last error before the configured failure policy is applied.

Parameters:

Name Type Description Default
message str

A human-readable description of the failure, typically including the final error string from the last attempt.

required
parsed ParsedResponse

A fully populated ParsedResponse carrying retry_attempts, parse_error, raw_text, and llm_response so the trace logger can write a complete record. See class docstring for the population contract.

required

Read traces

Each agent's trace contains both decision outcomes and conversation events. Use iter_decisions to read only the completed decisions, for example after the first tutorial's call has returned run_dir:

from aigora.traces import iter_decisions, iter_trace_records

trace = run_dir / "traces" / "buyer_1.jsonl"
for decision in iter_decisions(trace):
    print(decision["period"], decision["parsed_action"], decision["action_status"])

To inspect the complete event history and reconstruct model requests, use iter_trace_records with request expansion enabled:

for record in iter_trace_records(trace, expand_requests=True):
    for attempt in record.get("attempts", []):
        if "request_messages" in attempt:
            print(attempt["request_messages"])

Read from the beginning before filtering

The reader reconstructs requests from earlier records in the same file. Pass the complete trace to iter_trace_records and filter the records it yields, so request expansion still has the earlier messages it needs.

See Output files for the field definitions and ordering rules.

aigora.traces.iter_decisions

iter_decisions(
    path: str | Path,
) -> Iterator[dict[str, Any]]

Read decision outcomes from an agent's trace, one record at a time.

This selects decision_resolved events and leaves out conversation events. Use it to inspect actions, supplied reasons, and decision usage without counting the same decision's conversation event again. Model request deltas are not expanded by this reader.

aigora.traces.iter_trace_records

iter_trace_records(
    path: str | Path, *, expand_requests: bool = False
) -> Iterator[dict[str, Any]]

Read every event from one agent's trace in its original order.

Set expand_requests=True to reconstruct complete model requests from the recorded deltas. Read from the beginning before filtering events, because later requests can reuse messages from earlier ones. Expansion restores only messages present in the trace.