Skip to content

feat: Agent graph support#181

Open
mattrmc1 wants to merge 12 commits into
mmccarthy/AIC-2664/ai-config-tracker-overhaulfrom
mmccarthy/AIC-2837/java-ai-sdk-agent-graph
Open

feat: Agent graph support#181
mattrmc1 wants to merge 12 commits into
mmccarthy/AIC-2664/ai-config-tracker-overhaulfrom
mmccarthy/AIC-2837/java-ai-sdk-agent-graph

Conversation

@mattrmc1

@mattrmc1 mattrmc1 commented Jun 23, 2026

Copy link
Copy Markdown

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) through AIGraphTracker.

New types

GraphEdge — immutable edge holding target key and optional handoff metadata map (unmodifiable).

AgentGraphNode — wraps a node key, its resolved AIAgentConfig, and outgoing GraphEdge list. isTerminal() returns true when edges are empty.

AgentGraphFlagValue (package-private) — parses the graph flag JSON protocol: root, edges adjacency map, and _ldMeta (enabled, variationKey, version). Defensively handles malformed input without throwing.

AgentGraphDefinition — the resolved graph:

boolean isEnabled();
AgentGraphNode rootNode();
AgentGraphNode getNode(String nodeKey);
List<AgentGraphNode> getChildNodes(String nodeKey);
List<AgentGraphNode> getParentNodes(String nodeKey);
List<AgentGraphNode> terminalNodes();
AIGraphTracker createTracker();

void traverse(BiFunction<AgentGraphNode, Map<String, Object>, Object> fn, Map<String, Object> ctx);
void reverseTraverse(BiFunction<AgentGraphNode, Map<String, Object>, Object> fn, Map<String, Object> ctx);

traverse is BFS root-to-leaves; reverseTraverse is 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:

// At-most-once (invocation success/failure share one guard):
void trackInvocationSuccess();
void trackInvocationFailure();
void trackDuration(double durationMs);
void trackTotalTokens(TokenUsage tokens);
void trackPath(List<String> path);

// Multi-fire:
void trackRedirect(String sourceKey, String redirectedTarget);
void trackHandoffSuccess(String sourceKey, String targetKey);
void trackHandoffFailure(String sourceKey, String targetKey);

AIGraphMetricSummary getSummary();
String getResumptionToken();
static AIGraphTracker fromResumptionToken(String token, LDClientInterface client, LDContext context);

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

AgentGraphDefinition agentGraph(String graphKey, LDContext context, Map<String, Object> variables);
AgentGraphDefinition agentGraph(String graphKey, LDContext context);
AIGraphTracker createGraphTracker(String resumptionToken, LDContext context);

agentGraph evaluates the graph flag, validates (enabled → root present → all nodes reachable from root → all child configs enabled), fetches each node's AIAgentConfig passing graphKey for tracker correlation. Returns disabled definition on any validation failure. Emits $ld:ai:usage:agent-graph usage event.

Other changes

  • ResumptionTokens extended with encodeGraph/decodeGraph for graph-specific tokens (fields: runId, graphKey, variationKey, version). Made public for access from AIGraphTracker.
  • agentConfigs() reordered to emit usage count before fetching configs.
  • Config evaluation methods gain graphKey parameter so child node trackers include graph identity in their track data.

Test plan

  • ./gradlew :lib:sdk:server-ai:test passes
  • AIGraphTrackerTest — 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, createTracker
  • LDAIClientImplTest — agentGraph usage event, enabled/disabled graph, unreachable node validation, non-enabled child config validation, graphKey threading to child trackers, createGraphTracker delegation
  • AgentGraphFlagValueTest — parse root/edges/meta, missing fields, disabled flag, malformed input, handoff metadata, edge with missing key skipped
  • ResumptionTokensTest — graph token encode/decode round-trips

Note

Medium Risk
New public API and telemetry paths affect how multi-agent graphs are evaluated and tracked; graph fetches perform N flag evaluations per graph without duplicate usage events, so behavior changes for integrators wiring graphs.

Overview
Adds agent graph support to the server-side AI SDK: callers resolve a graph via LDAIClient#agentGraph, walk nodes with BFS helpers, and emit graph- and edge-level metrics through AIGraphTracker.

agentGraph evaluates the graph flag, parses root / edges / _ldMeta (AgentGraphFlagValue), validates enabled state, non-empty root, reachability from root, and that every referenced node’s agent config is enabled. On failure it returns a disabled AgentGraphDefinition with an empty node map; on success it builds AgentGraphNode instances (config + outgoing GraphEdge with optional handoff). It records $ld:ai:usage:agent-graph and loads node configs without per-node $ld:ai:usage:agent-config events. Child LDAIConfigTracker factories now receive graphKey for correlation.

AgentGraphDefinition exposes topology (rootNode, children/parents, terminals) plus cycle-safe traverse (root → leaves) and reverseTraverse (terminals → root last), storing visitor results in a shared context map. createTracker() issues a new graph run when enabled.

AIGraphTracker sends $ld:ai:graph:* metrics (invocation, duration, tokens, path at-most-once; redirect/handoff multi-fire), exposes AIGraphMetricSummary, and supports graph resumption tokens via new ResumptionTokens.encodeGraph / decodeGraph and createGraphTracker. Config-token validation now rejects whitespace-only required fields.

agentConfigs counts non-null requests for the usage metric before building the result map.

Reviewed by Cursor Bugbot for commit 80ef017. Bugbot is set up for automated code reviews on this repo. Configure here.

@mattrmc1 mattrmc1 marked this pull request as ready for review June 24, 2026 21:26
@mattrmc1 mattrmc1 requested a review from a team as a code owner June 24, 2026 21:26

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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 35d8b02. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant