feat: Agent graph support#181
Conversation
…b.com:launchdarkly/java-core into mmccarthy/AIC-2837/java-ai-sdk-agent-graph
…l-arg logs to debug
…b.com:launchdarkly/java-core into mmccarthy/AIC-2837/java-ai-sdk-agent-graph
…hy/AIC-2837/java-ai-sdk-agent-graph
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 96a810e. Configure here.
| if (visited.add(root.getKey())) { | ||
| Object result = fn.apply(root, ctx); | ||
| ctx.put(root.getKey(), result); | ||
| } |
There was a problem hiding this comment.
Reverse traverse skips cycle nodes
Medium Severity
When a validated graph has no terminal nodes (for example a directed cycle), reverseTraverse seeds an empty queue and only runs the final root block. Non-root nodes on the cycle never receive the visitor, despite the API stating each node is visited exactly once.
Reviewed by Cursor Bugbot for commit 96a810e. Configure here.
There was a problem hiding this comment.
See spec AIGRAPH 1.4
The spec says reverse_traverse starts from terminal nodes — no terminals means no starting point, so a no-op is correct.


Summary
Adds agent graph support — flag evaluation, graph validation, BFS traversal, graph-level tracking, and resumption tokens. Callers fetch a graph definition via
agentGraph(graphKey, context, variables), inspect or traverse the node topology, and track graph-level metrics (invocation success/failure, duration, tokens, path) plus edge-level events (redirect, handoff) throughAIGraphTracker.New types
GraphEdge— immutable edge holding targetkeyand optionalhandoffmetadata map (unmodifiable).AgentGraphNode— wraps a node key, its resolvedAIAgentConfig, and outgoingGraphEdgelist.isTerminal()returns true when edges are empty.AgentGraphFlagValue(package-private) — parses the graph flag JSON protocol:root,edgesadjacency map, and_ldMeta(enabled, variationKey, version). Defensively handles malformed input without throwing.AgentGraphDefinition— the resolved graph:traverseis BFS root-to-leaves;reverseTraverseis BFS terminals-to-root (root always processed last). Both are cycle-safe — each node visited at most once. Visitor results stored in the context map under the node's key.AIGraphTracker— graph-level tracking:Uses
AtomicReference.compareAndSet(null, value)for at-most-once. Empty token usage doesn't burn the slot. Version clamped to minimum 1 on resumption decode.AIGraphMetricSummary— immutable snapshot of graph tracker state (success, durationMs, tokens, path, resumptionToken). All nullable except resumptionToken.Client methods
agentGraphevaluates the graph flag, validates (enabled → root present → all nodes reachable from root → all child configs enabled), fetches each node'sAIAgentConfigpassinggraphKeyfor tracker correlation. Returns disabled definition on any validation failure. Emits$ld:ai:usage:agent-graphusage event.Other changes
ResumptionTokensextended withencodeGraph/decodeGraphfor graph-specific tokens (fields:runId,graphKey,variationKey,version). Madepublicfor access fromAIGraphTracker.agentConfigs()reordered to emit usage count before fetching configs.graphKeyparameter so child node trackers include graph identity in their track data.Test plan
./gradlew :lib:sdk:server-ai:testpassesAIGraphTrackerTest— invocation success/failure + shared guard, duration, total tokens (including zero-usage skip), path, redirect/handoff multi-fire, base data correctness, variationKey omission, getSummary, resumption token round-trip, concurrency (20-thread contention for invocation and duration)AgentGraphDefinitionTest— buildNodes, collectAllKeys, traverse/reverseTraverse (including cycles, single-node, diamond graphs), rootNode/getNode/getChildNodes/getParentNodes/terminalNodes, disabled graph behavior, createTrackerLDAIClientImplTest— agentGraph usage event, enabled/disabled graph, unreachable node validation, non-enabled child config validation, graphKey threading to child trackers, createGraphTracker delegationAgentGraphFlagValueTest— parse root/edges/meta, missing fields, disabled flag, malformed input, handoff metadata, edge with missing key skippedResumptionTokensTest— graph token encode/decode round-tripsNote
Medium Risk
New public SDK surface and flag-evaluation path that fans out to many agent configs; resumption tokens still carry variation metadata and should stay server-side.
Overview
Adds agent graph support to the server-side AI SDK so callers can resolve multi-agent workflows from a single graph flag.
LDAIClientgainsagentGraph(...)andcreateGraphTracker(...).agentGraphevaluates the graph flag, validates structure (enabled, non-empty root, all nodes reachable from root, every node’s agent config enabled), loads each node’sAIAgentConfigwithout per-node usage events, emits$ld:ai:usage:agent-graph, and returns anAgentGraphDefinitionwithAgentGraphNode/GraphEdgetopology plus optional edge handoff data. Failed validation yields a disabled definition with empty nodes.AgentGraphDefinitionexposes navigation helpers and cycle-safetraverse/reverseTraverse(BFS), andcreateTracker()for a new graph run.AIGraphTrackerreports graph metrics ($ld:ai:graph:*: invocation, duration, tokens, path; multi-fire redirect/handoff) with thread-safe at-most-once guards andAIGraphMetricSummary. Graph runs useResumptionTokens.encodeGraph/decodeGraph(class made public for graph tokens; config decode tightens emptyrunId/configKey).LDAIClientImplthreadsgraphKeyinto nodeLDAIConfigTrackerfactories when configs are fetched via a graph;agentConfigsnow records the batch usage count before evaluating requests.Reviewed by Cursor Bugbot for commit 30d937c. Bugbot is set up for automated code reviews on this repo. Configure here.