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.
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.
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}")
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:
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}")
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:
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}")
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:
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)
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:
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)
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:
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)
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.