Visualizing Agent Execution Graphs for Faster Debugging

A trace is a timeline. An execution graph shows you the shape of the work. How to build and display agent execution graphs that reveal what a trace alone cannot.

AgentixForce Team··10 min read

Traces vs Graphs: What Each Reveals

Distributed traces and execution graphs answer different questions. A trace is a timeline. It shows you when each operation started, how long it ran, and whether it succeeded or failed. A trace is excellent for answering questions about performance: where is time being spent, what is the latency of each component, and did an error occur in this specific request.

An execution graph is a directed acyclic graph (DAG) that shows the causal structure of the agent's work, which agents were spawned, which decisions led to which tool calls, and which data dependencies caused sequential versus parallel execution. Graphs answer structural questions: why did this request spawn five agents instead of two, why did the synthesis step wait when the research could have been done in parallel, and what is the typical shape of a successful versus failed request.

Trace Timeline vs. Execution Graph: What Each RevealsTrace Timelineplannerresearchweb_searchclassifysynthesisShows: WHEN things happened and how long they tookExecution Graphplannerresearchclassifyweb_searchsynthesisShows: HOW tasks relate and which depend on which
Side by side: trace timeline shows WHEN operations happened and their duration. Execution graph shows HOW operations relate causally and which depend on which.

When to Use Each

Use traces when you are investigating a specific request. A user reported it was slow or returned wrong output. The timeline view of a trace is the fastest way to identify which span was slow or errored. Use execution graphs when you are investigating a pattern. Why are some requests consistently more expensive than others, or why does the agent sometimes spawn more agents than expected. Graphs show structural patterns across many requests that individual traces do not surface.

Anatomy of an Agent Execution Graph

An execution graph for a multi-agent request is a directed acyclic graph where nodes represent agent invocations, LLM calls, and tool executions. Edges represent causal dependencies. An edge from node A to node B means that B was initiated as a result of A's output. Node properties capture the operational data needed for analysis and debugging.

Agent Execution DAG — Parallel Fan-Out PatternUser RequestPlanner AgentResearch AgentClassifier Agenttool: web_searchtool: fetch_docstool: llm_callSynthesis AgentResponseParallel branches (research + classify) run concurrently · Synthesis waits on both completing
Full execution DAG for a multi-agent research request: planner fans out to parallel research and classification agents, both fan in to synthesis, which produces the final output.

Node Properties

  • node_id: Unique identifier for this node in the graph. Correlates to span_id in the trace.
  • node_type: 'agent', 'llm_call', 'tool_call', 'decision', or 'output'. Determines rendering style.
  • agent_role: For agent and LLM call nodes, the role of the agent that created this node.
  • duration_ms: How long this node's operation took. Displayed as node weight in analysis views.
  • token_cost: For LLM call nodes, the combined token cost of this specific call.
  • status: 'success', 'error', 'timeout', 'cancelled'. Determines node color in the visualization.
  • depth: Distance from the root node. Useful for identifying unexpectedly deep agent recursion.

Edge Properties

  • edge_type: 'spawns' (agent creates sub-agent), 'calls' (agent invokes tool), 'uses_output' (node uses result of parent).
  • data_passed: What data flowed along this edge. Critical for debugging incorrect outputs, trace the data from root to the failing node.
  • is_conditional: Whether this edge was created based on a dynamic decision. Conditional edges are key to understanding non-deterministic execution paths.
  • wait_time_ms: Time the child node waited before the parent's output was available. Large wait times in parallel graphs indicate suboptimal sequencing.

Building the Execution Graph from Traces

If your agent system already emits proper distributed traces with parent-child span relationships, you can reconstruct the execution graph from trace data. The span hierarchy in a trace directly encodes the DAG structure: a span's parent_span_id tells you which node in the graph spawned it.

python
from dataclasses import dataclass, field
from typing import Optional

@dataclass
class GraphNode:
    node_id: str
    node_type: str  # agent, llm_call, tool_call, output
    label: str
    duration_ms: float
    status: str
    token_cost: float = 0.0
    depth: int = 0
    children: list["GraphNode"] = field(default_factory=list)

def build_execution_graph(spans: list[dict]) -> GraphNode:
    # Index spans by span_id for fast lookup
    span_map = {s["span_id"]: s for s in spans}
    children_map: dict[str, list[str]] = {}

    for span in spans:
        parent_id = span.get("parent_span_id")
        if parent_id:
            children_map.setdefault(parent_id, []).append(span["span_id"])

    # Find root span (no parent)
    root_span = next(s for s in spans if not s.get("parent_span_id"))

    def build_node(span: dict, depth: int = 0) -> GraphNode:
        node = GraphNode(
            node_id=span["span_id"],
            node_type=classify_span(span),
            label=span.get("gen_ai.tool.name") or span.get("gen_ai.request.model") or span["name"],
            duration_ms=span["duration_ms"],
            status=span["status"],
            token_cost=calculate_token_cost(span),
            depth=depth,
        )
        for child_id in children_map.get(span["span_id"], []):
            child_node = build_node(span_map[child_id], depth + 1)
            node.children.append(child_node)
        return node

    return build_node(root_span)

def classify_span(span: dict) -> str:
    name = span.get("name", "")
    if name.startswith("agent."):
        return "agent"
    if name.startswith("llm."):
        return "llm_call"
    if name.startswith("tool."):
        return "tool_call"
    return "other"

Visualizing Parallel and Conditional Execution

The most valuable patterns to visualize in execution graphs are parallel fan-out (multiple agents running concurrently), sequential bottlenecks (operations that should be parallel but are running sequentially), conditional branches (different execution paths based on agent decisions), and loops (agent re-invocations that indicate retry or multi-step reasoning).

Detecting Unnecessary Sequential Execution

A common performance anti-pattern is sequential execution of operations that have no data dependency on each other. If the orchestrator spawns a research agent and then waits for it to complete before spawning a classification agent, but the classification agent does not need the research agent's output, you have an unnecessary sequential bottleneck. The execution graph makes this immediately visible: nodes at the same depth with no edge between them are parallel candidates.

python
def find_parallelization_opportunities(graph: GraphNode) -> list[dict]:
    opportunities = []

    def analyze_node(node: GraphNode):
        if len(node.children) < 2:
            for child in node.children:
                analyze_node(child)
            return

        # Check pairs of sibling nodes for data independence
        for i, child_a in enumerate(node.children):
            for child_b in node.children[i+1:]:
                if not has_data_dependency(child_a, child_b):
                    total_sequential_cost = child_a.duration_ms + child_b.duration_ms
                    parallel_savings = min(child_a.duration_ms, child_b.duration_ms)
                    if parallel_savings > 500:  # More than 500ms savings
                        opportunities.append({
                            "parent": node.label,
                            "node_a": child_a.label,
                            "node_b": child_b.label,
                            "savings_ms": parallel_savings,
                        })
        for child in node.children:
            analyze_node(child)

    analyze_node(graph)
    return sorted(opportunities, key=lambda x: x["savings_ms"], reverse=True)

def has_data_dependency(node_a: GraphNode, node_b: GraphNode) -> bool:
    # Check if node_b uses any output from node_a
    # In practice, this requires tracking data flow through edge annotations
    return False  # Simplified, implement with edge data tracking

Graph-Level Metrics for Optimization

Aggregating metrics across many execution graphs reveals system-level patterns that single-trace analysis cannot surface. These graph-level metrics are the most powerful tools for identifying structural optimization opportunities.

  • Average graph depth: The average depth of the deepest leaf node across all graphs. Increasing depth over time signals agent delegation chains growing longer than designed.
  • Average fan-out at each depth: How many children each node spawns on average at each level. Sudden changes in fan-out indicate behavior drift.
  • Critical path length vs. parallelizable path length: What percentage of total latency is on the critical path (unavoidable sequential steps) versus parallelizable work. High critical path ratios indicate architectural bottlenecks.
  • Graph shape frequency: How often each distinct graph topology appears. A system with 50 different execution patterns is harder to optimize than one with 5 dominant patterns.
  • Node failure position: At what depth and in what node type do failures most commonly occur? This guides where to invest in reliability improvements.

Rendering Execution Graphs in Your UI

For production tools, rendering execution graphs requires a library that handles DAG layout algorithms. The most common choices are D3.js with a custom DAG renderer, React Flow (excellent React integration, handles large graphs), Cytoscape.js (most mature graph visualization library with extensive layout algorithms), and Mermaid (for simple embedded diagrams without interactivity).

typescript
// React Flow graph renderer for agent execution graphs
import ReactFlow, { Node, Edge, Background } from "reactflow";

interface ExecutionNode {
  node_id: string;
  label: string;
  node_type: "agent" | "llm_call" | "tool_call" | "output";
  status: "success" | "error" | "timeout";
  duration_ms: number;
}

const NODE_COLORS = {
  agent: "#6366f1",
  llm_call: "#0ea5e9",
  tool_call: "#10b981",
  output: "#f59e0b",
};

function convertToReactFlow(executionNodes: ExecutionNode[], edges: [string, string][]) {
  const nodes: Node[] = executionNodes.map((n, i) => ({
    id: n.node_id,
    position: { x: 0, y: 0 },  // Dagre layout will compute positions
    data: {
      label: (
        <div style={{ textAlign: "center" }}>
          <div style={{ fontWeight: 700, fontSize: 11 }}>{n.label}</div>
          <div style={{ fontSize: 9, color: "#64748b" }}>{n.duration_ms}ms</div>
        </div>
      ),
    },
    style: {
      background: NODE_COLORS[n.node_type],
      color: "#fff",
      border: n.status === "error" ? "2px solid #ef4444" : "none",
      borderRadius: 8,
      padding: "6px 12px",
    },
  }));

  const rfEdges: Edge[] = edges.map(([source, target], i) => ({
    id: `e-${source}-${target}`,
    source,
    target,
    type: "smoothstep",
    animated: false,
  }));

  return { nodes, edges: rfEdges };
}

Debugging Workflows Using Execution Graphs

The most common debugging workflows using execution graphs fall into three categories: root cause analysis for unexpected outputs, performance debugging for slow requests, and anomaly detection for requests that have an unusual structure compared to normal.

Root Cause Analysis: Tracing Wrong Outputs to Source

When an agent produces incorrect output, the execution graph lets you walk backwards from the output node through its parent chain to find where incorrect data entered the pipeline. Start at the output node, check its inputs, trace each input to its source node, and compare the data at each step against expected values. This data flow tracing is much faster through a graph visualization than through raw log records.

Performance Debugging: Finding the Bottleneck

Color-code execution graph nodes by duration. Red for nodes that took longer than their P90 baseline, yellow for nodes between P50 and P90, green for fast nodes. This heat map view immediately reveals which node in the graph is the latency bottleneck without requiring you to mentally parse a flat list of span durations.

Storing and Querying Historical Graphs

Storing execution graphs as structured data rather than just as visual snapshots enables powerful historical queries: find all requests that followed the five-agent topology, compare graph metrics before and after a deployment, or compute the most common failure paths across a week of production traffic.

python
# Store execution graph as serializable JSON
import json
from dataclasses import asdict

def serialize_graph(root: GraphNode, request_id: str, session_id: str) -> dict:
    def node_to_dict(node: GraphNode) -> dict:
        return {
            "node_id": node.node_id,
            "node_type": node.node_type,
            "label": node.label,
            "duration_ms": node.duration_ms,
            "status": node.status,
            "token_cost": node.token_cost,
            "depth": node.depth,
            "children": [node_to_dict(c) for c in node.children],
        }

    return {
        "request_id": request_id,
        "session_id": session_id,
        "graph": node_to_dict(root),
        "summary": {
            "total_nodes": count_nodes(root),
            "max_depth": max_depth(root),
            "total_duration_ms": root.duration_ms,
            "total_token_cost": sum_token_cost(root),
            "had_errors": any_errors(root),
        }
    }

# Store in ClickHouse for analytical queries
INSERT_QUERY = """
INSERT INTO agent_execution_graphs
(request_id, session_id, graph_json, total_nodes, max_depth,
 total_duration_ms, total_token_cost, had_errors, created_at)
VALUES (%(request_id)s, %(session_id)s, %(graph_json)s, ...)
"""

Frequently Asked Questions

Build secure agentic systems with AgentixForce

We help companies design, build, and secure production-grade AI agent systems. From architecture reviews to full implementation, our team brings deep expertise to every engagement.

More from AI Observability

All articles
TRACE: 7f4a-bc92 • total: 2.4sorchestrator.run2400msllm.invoke (claude)1840msmemory.retrieve210mscontext.build95mstool.web_search520mshttp.get480mstool.write_file38msoutput.validate12msservices: 4spans: 8llm_tokens: 4821cost: $0.042status: SUCCESSDISTRIBUTED TRACE WATERFALL
AI Observability

Distributed Tracing Across Agent Chains: A Practical Implementation Guide

When an agent call spans five services, four models, and three external APIs, you need trace context that follows the work all the way through. Here is how to instrument it.

13 min read
May 14, 2026
12:04:01.221INFO tool.call name=web_search query="agent security 2026"12:04:01.224DEBUGtool.input tokens=48 model=claude-sonnet-412:04:02.847INFO tool.result name=web_search results=5 latency=1623ms12:04:02.851INFO tool.call name=read_file path=/docs/arch.md12:04:02.855WARN tool.result name=read_file size=42kb exceeds=threshold12:04:03.201INFO llm.response tokens_out=812 finish=end_turn12:04:03.205INFO agent.step_complete step=3 total_cost=$0.01412:04:03.210DEBUGmemory.write key=session_ctx bytes=1204STRUCTURED AGENT LOG STREAM
AI Observability

Logging Tool Calls, Inputs, and Outputs for Debuggable Agents

The difference between agents that are debuggable and ones that are black boxes is almost entirely in how you log. What to capture, how to structure it, and where to send it.

9 min read
May 11, 2026
LATENCY842msp95: 1.2sCOST / CALL$0.014daily: $8.40SUCCESS RATE98.4%errors: 16/1000AGENT PERFORMANCE DASHBOARD
AI Observability

Agent Metrics That Actually Matter: Latency, Cost, and Success Rate

What metrics to track for each agent in your system, how to define success in a way that is measurable, and the dashboards worth building from day one.

10 min read
May 9, 2026
threshold⚠ DRIFT DETECTEDsemantic shifthallucinationrate risingALERT: hallucination_rate exceeded 12% — page on-callHALLUCINATION DRIFT MONITOR
AI Observability

Alerting on Agent Drift and Hallucination Patterns in Production

Agents that worked perfectly in staging can start behaving strangely weeks after deployment. How to detect semantic drift and hallucination before users do.

11 min read
May 5, 2026
Visualizing Agent Execution Graphs for Faster Debugging