AETIUS

Errors reference

All error classes below live in agentstage.errors except ToolCallUnavailableError, which lives in agentstage.replay.matcher alongside the rest of the replay matching code it belongs to. Every example on this page runs against the local fixture from examples/fixtures.py.

Python
from examples.fixtures import build_environment

env = build_environment()

OrchestrationUnavailableError

Raised by every adapter's adapt() when system.orchestration.source == "unavailable". This only happens when there is no role or edge evidence in the reconstructed trace batch at all, for example reconstructing from zero traces. There is nothing for any adapter to wire up.

Python
from agentstage.reconstruction.build import build_reconstructed_system
from agentstage.errors import OrchestrationUnavailableError
from agentstage.langgraph import adapt as langgraph_adapt
from typing import TypedDict


class State(TypedDict):
    messages: list


empty_system = build_reconstructed_system([], label="empty", project_id="demo_project", source_agent_name=None)
try:
    langgraph_adapt(empty_system, state_schema=State, node_factory=lambda role_spec, tools: (lambda s: s))
except OrchestrationUnavailableError as e:
    print(f"{type(e).__name__}: {e}")
Output
OrchestrationUnavailableError: cannot build a langgraph structure: this system's orchestration is unavailable (no reliable role signal was found in the source traces) — nothing to wire up

OrchestrationMappingError

Raised when the reconstructed graph's shape does not cleanly fit a framework's native model and the caller has not supplied enough to resolve the ambiguity explicitly. This is not one specific situation, it is every case where the honest move is to ask the developer rather than guess. Two real examples.

A role that really did branch to more than one destination in the traces, with no routers entry telling LangGraph how to choose at runtime:

Python
from agentstage.errors import OrchestrationMappingError

team_system = env.reconstruct("support-team")
try:
    langgraph_adapt(team_system, state_schema=State, node_factory=lambda role_spec, tools: (lambda s: s))
except OrchestrationMappingError as e:
    print(f"{type(e).__name__}: {e}")
Output
OrchestrationMappingError: role 'supervisor' branches to ['researcher', 'writer'] in the observed traces, but how that choice was made at runtime isn't captured — pass a path function for it via routers['supervisor'] rather than have this guess which branch to take.

A graph that did not classify into any of the three known shapes at all:

Python
multi_hub_system = env.reconstruct("multi-hub")
try:
    langgraph_adapt(multi_hub_system, state_schema=State, node_factory=lambda role_spec, tools: (lambda s: s))
except OrchestrationMappingError as e:
    print(f"{type(e).__name__}: {e}")
Output
OrchestrationMappingError: orchestration graph did not classify into a known shape (pattern='unclassified') — nodes=['hub_a', 'hub_b', 'w', 'x', 'y', 'z'], edges=[('hub_a', 'x'), ('hub_a', 'y'), ('hub_b', 'z'), ('hub_b', 'w')]. Refusing to best-effort-wire this: pass `routers` explicitly (even `{}` if no role branches) to confirm you've reviewed the graph and supply a path function for any role with more than one observed destination.

The CrewAI and Agno adapters raise the same error class for their own equivalent situations (an unclassified pattern without an explicit process, or a supervisor_delegates/hierarchical role reachable only through a deeper chain than one manager plus direct reports). See the LangGraph, CrewAI, and Agno adapter pages for the full set.

MissingRoleFieldError

Raised only by the CrewAI adapter. CrewAI's Agent requires goal, and its Task requires description and expected_output, none of which reconstruction has any trace-sourced value for. Checked in order, goal first, so the first missing field is what gets raised:

Python
from agentstage.crewai import adapt as crewai_adapt
from agentstage.errors import MissingRoleFieldError

try:
    crewai_adapt(team_system, goals={"supervisor": "lead the team"}, task_descriptions={}, expected_outputs={})
except MissingRoleFieldError as e:
    print(f"{type(e).__name__}: {e}")
    print("field:", e.field)
    print("missing_roles:", e.missing_roles)
Output
MissingRoleFieldError: crewai requires 'goal' per role, which reconstruction has no source for (traces carry observed behavior, not stated intent) — missing for role(s): ['researcher', 'writer']
field: goal
missing_roles: ['researcher', 'writer']

supervisor is not in missing_roles here because a goal was supplied for it. task_descriptions and expected_outputs were left empty too, but that check never runs, since the goal check raises first. Supplying goal for every role would surface the next missing field instead.

Tool replay misses: MatchResult or ToolCallUnavailableError

A tool call that does not clear the similarity threshold against any historical call is not really an "error" in the exception sense by default. It is a MatchResult with source="unavailable", returned normally:

Python
from agentstage.replay.matcher import ReplayConfig, replay_tool_call

invoice_system = env.reconstruct("invoice-agent")
lookup_invoice = invoice_system.tools["lookup_invoice"]
result = replay_tool_call("lookup_invoice", {"invoice_id": "does_not_exist"}, lookup_invoice.behavior)
print(result.source)
print(result)
Output
unavailable
MatchResult(tool_name='lookup_invoice', arguments={'invoice_id': 'does_not_exist'}, source='unavailable', threshold=0.5, response=None, similarity=None, closest_candidate=ObservedToolCall(arguments={'invoice_id': 'inv_1001'}, response={'status': 'paid', 'amount': 129.0}, observed_at='2026-01-01T09:00:02Z'), closest_similarity=0.09090909090909091)

Passing ReplayConfig(on_unavailable="raise") turns the same situation into a real exception, ToolCallUnavailableError, carrying the tool name and the full MatchResult that would otherwise have been returned:

Python
from agentstage.replay.matcher import ToolCallUnavailableError

try:
    replay_tool_call(
        "lookup_invoice", {"invoice_id": "does_not_exist"}, lookup_invoice.behavior, config=ReplayConfig(on_unavailable="raise")
    )
except ToolCallUnavailableError as e:
    print(f"{type(e).__name__}: {e}")
    print("tool_name:", e.tool_name)
    print("result.closest_similarity:", e.result.closest_similarity)
Output
ToolCallUnavailableError: tool call to 'lookup_invoice' has no replayable match — closest candidate scored 0.09 (threshold 0.50)
tool_name: lookup_invoice
result.closest_similarity: 0.09090909090909091

Every framework adapter's own tool entrypoints go through this same function underneath. By default they return the informative unavailable payload shown on each adapter's page rather than raise, since an exception crossing into some frameworks' own tool-execution machinery gets caught and turned into a generic tool error with no way to distinguish it from a real failure. Passing a ReplayConfig(on_unavailable="raise") through to an adapter changes that for the whole system, tool call by tool call.