Skip to main content
Agentic systems chain dozens of operations — LLM calls, tool invocations, sub-agent handoffs — and it’s hard to understand the flow by looking at individual spans. Agent Trajectory visualizes the execution as an interactive graph and path diagram so you can see which agents call which, where loops happen, and which paths lead to failures.
Agent Trajectory view showing execution flow across agents in Arize AX
This requires span attributes that identify agents and define transitions between them. These are plain OpenTelemetry span attributes, so the same pattern works in any language.

Frameworks with Built-In Support

These frameworks set the required attributes automatically through their auto-instrumentors — no additional setup needed:
FrameworkWhat’s tracked
LangGraphAgent nodes and graph transitions from LangGraph’s native node metadata
AutoGengraph.node.id, graph.node.parent_id, agent handoffs
CrewAIAgent roles and task relationships
OpenAI AgentsAgent metadata via OpenInferenceTracingProcessor, handoffs
AgnoAgent names and team relationships
If your framework isn’t listed above, you can set the attributes manually:

Custom Implementation

Add these attributes to your agent spans:
AttributeRequiredDescription
graph.node.idYesUnique name for the agent/node
graph.node.parent_idRecommendedID of the parent node. If omitted, Arize infers from span hierarchy
You only need to annotate the components you want in the graph — not every span.
# Basic pattern: add graph attributes to your agent spans
with tracer.start_as_current_span("my_agent") as span:
    span.set_attribute("graph.node.id", "research_agent")
    span.set_attribute("graph.node.parent_id", "orchestrator")
    # agent logic here...

Multi-Level Hierarchy

For agents that call sub-agents, nest the spans and set graph.node.parent_id to point to the parent. Arize uses these relationships to build the graph:
# Root level (no parent)
with tracer.start_as_current_span("main_workflow") as main_span:
    main_span.set_attribute("graph.node.id", "orchestrator")

    # Child level
    with tracer.start_as_current_span("parse_input") as parse_span:
        parse_span.set_attribute("graph.node.id", "parser")
        parse_span.set_attribute("graph.node.parent_id", "orchestrator")

        # Grandchild level
        with tracer.start_as_current_span("validate") as validate_span:
            validate_span.set_attribute("graph.node.id", "validator")
            validate_span.set_attribute("graph.node.parent_id", "parser")
This creates the graph structure:
orchestrator
├── parser
│   └── validator
├── research_agent
└── writer_agent

Next step

See what each request is costing you:

Next: Track Costs