diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentGraph.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentGraph.tsx
new file mode 100644
index 00000000000..5b8704259fe
--- /dev/null
+++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentGraph.tsx
@@ -0,0 +1,208 @@
+"use client";
+
+import { useMemo, useState } from "react";
+
+import { Button } from "@/components/ui/button";
+import { cn } from "@/lib/cva.config";
+
+import type { AgentNode, Span } from "./traceTypes";
+import {
+ agentsWithErrors,
+ fmtCost,
+ fmtMs,
+ GRAPH_NODE_HEIGHT,
+ invocationsOf,
+ layoutAgentGraph,
+ type GraphEdge,
+ type GraphNode,
+} from "./traceUtils";
+
+interface AgentGraphProps {
+ agents: AgentNode[];
+ spans: Span[];
+ /** Jump to the Tree view focused on this invocation span. */
+ onOpenInvocation: (spanId: string) => void;
+}
+
+const INVOCATIONS_PAGE_SIZE = 10;
+
+function Edge({ edge }: { edge: GraphEdge }) {
+ const x1 = edge.from.x + edge.from.width;
+ const y1 = edge.from.y + GRAPH_NODE_HEIGHT / 2;
+ const x2 = edge.to.x;
+ const y2 = edge.to.y + GRAPH_NODE_HEIGHT / 2;
+ const mid = (x1 + x2) / 2;
+ return (
+
+
+
+ {edge.label}
+
+
+ );
+}
+
+interface NodeProps {
+ node: GraphNode;
+ hasError: boolean;
+ selected: boolean;
+ onClick: (name: string) => void;
+}
+
+function Node({ node, hasError, selected, onClick }: NodeProps) {
+ const { agent } = node;
+ return (
+ onClick(agent.name)}
+ onKeyDown={(event) => event.key === "Enter" && onClick(agent.name)}
+ >
+
+
+ {agent.name}
+
+
+ {`${fmtCost(agent.spend)} · ${agent.llm_calls} LLM · ${agent.tool_calls} tool`}
+
+
+ );
+}
+
+function InvocationList({
+ agentName,
+ spans,
+ onOpenInvocation,
+}: {
+ agentName: string;
+ spans: Span[];
+ onOpenInvocation: (spanId: string) => void;
+}) {
+ const [page, setPage] = useState(0);
+ const invocations = useMemo(() => invocationsOf(spans, agentName), [spans, agentName]);
+ const pages = Math.max(1, Math.ceil(invocations.length / INVOCATIONS_PAGE_SIZE));
+ const visible = invocations.slice(page * INVOCATIONS_PAGE_SIZE, (page + 1) * INVOCATIONS_PAGE_SIZE);
+ return (
+
+
+
+ {agentName} · {invocations.length} invocation{invocations.length === 1 ? "" : "s"}
+
+ {pages > 1 && (
+
+
+ {page + 1} / {pages}
+
+
+ )}
+
+
+ {visible.map((span, i) => (
+ -
+
+
+ ))}
+
+
+ );
+}
+
+/** One node per distinct agent, edges parent → child labelled with invocation count. */
+export function AgentGraph({ agents, spans, onOpenInvocation }: AgentGraphProps) {
+ const [selected, setSelected] = useState(null);
+ const layout = useMemo(() => layoutAgentGraph(agents), [agents]);
+ const failing = useMemo(() => agentsWithErrors(spans), [spans]);
+
+ if (agents.length === 0) {
+ return No agent spans in this trace.
;
+ }
+
+ return (
+
+
+
+ Node width scales with spend. Click an agent to list its invocations.
+
+ {selected && (
+
+ )}
+
+ );
+}