Uh oh!
There was an error while loading. Please reload this page.
feat: Agent graph support - #181
Conversation
…b.com:launchdarkly/java-core into mmccarthy/AIC-2837/java-ai-sdk-agent-graph
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…l-arg logs to debug
Uh oh!
There was an error while loading. Please reload this page.
…b.com:launchdarkly/java-core into mmccarthy/AIC-2837/java-ai-sdk-agent-graph
Uh oh!
There was an error while loading. Please reload this page.
…hy/AIC-2837/java-ai-sdk-agent-graph
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| 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.
tanderson-ld
left a comment
There was a problem hiding this comment.
I am reviewing from the java perspective. I can't really review from the AI product perspective.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
…hy/AIC-2837/java-ai-sdk-agent-graph
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ 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 23f1c50. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
tanderson-ld
left a comment
There was a problem hiding this comment.
Approving for the java aspects, I am not in the loop on the AI SDK product / project requirements. This seems reasonable to me, but perhaps I am missing something obvious to someone more involved.
Uh oh!
There was an error while loading. Please reload this page.
🤖 I have created a release *beep* *boop* --- ## [0.2.0](launchdarkly-java-server-sdk-ai-0.1.0...launchdarkly-java-server-sdk-ai-0.2.0) (2026-07-13) ### Features * Add AIConfigTracker with at-most-once tracking and resumption tokens ([#179](#179)) ([9a9941b](9a9941b)) * add Runner, RunnerResult, Judge, and Evaluator ([#180](#180)) ([a32c4fa](a32c4fa)) * Add template config methods to AI SDK ([#184](#184)) ([29f31fc](29f31fc)) * Agent graph support ([#181](#181)) ([47c4f36](47c4f36)) * AgentControl data model, parsing & interpolation (AIC-2662) ([#171](#171)) ([d954ba6](d954ba6)) * AICONF config types & LDAIClient methods (AIC-2663) ([#173](#173)) ([ad2ac08](ad2ac08)) * vendor Mustache templating engine & add Interpolator (AIC-2695) ([#172](#172)) ([32732ea](32732ea)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Version and changelog/metadata updates only; no runtime behavior changes in the diff. > > **Overview** > **Release-only PR** that bumps `lib/sdk/server-ai` from **0.1.0** to **0.2.0** via Release Please. > > Updates the manifest entry, `gradle.properties`, the `AISdkInfo.VERSION` constant, and prepends a **0.2.0** section to `CHANGELOG.md` listing features already landed on main (AI config client/types, Mustache `Interpolator`, AgentControl, agent graphs, Runner/Judge/Evaluator, template config APIs, and `AIConfigTracker`). **No application logic changes** appear in this diff. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit e0ed24f. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>


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 defensive copy).AgentGraphNode— wraps a node key, its resolvedAIAgentConfig, and an unmodifiable outgoingGraphEdgelist (defensive copy).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. Non-finite durations (NaN, infinity) are rejected without consuming the at-most-once slot. Reconstruction from a resumption token is handled viaLDAIClient.createGraphTracker(token, context), which pipes the configured logger from the client (the staticfromResumptionTokenmethod is package-private).AIGraphMetricSummary— immutable snapshot of graph tracker state (success, durationMs, tokens, path, resumptionToken). All nullable except resumptionToken.Client methods
agentGraphvalidates thatgraphKeyis non-null and non-blank (throwsNullPointerException/IllegalArgumentException), then evaluates 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.createGraphTrackerreconstructs anAIGraphTrackerfrom a resumption token, preserving the original run identity while using the client's configured logger.Logging
Default logger resolution (
Loggers.defaultLogger()) is centralized ininternal/Loggers.javaand used only from theLDAIClientImplsingle-argument constructor as an entry-point fallback. All other code paths receive the logger from the top via constructor injection, consistent with the convention in other LaunchDarkly SDKs (.NET, JS).Other changes
ResumptionTokensextended withencodeGraph/decodeGraphfor graph-specific tokens (fields:runId,graphKey,variationKey,version). Madepublicfor access fromAIGraphTracker. Decode enforces non-blankrunIdandgraphKey.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 (including non-finite rejection), total tokens, 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 delegation, null/blank graphKey rejectionAgentGraphFlagValueTest— parse root/edges/meta, missing fields, disabled flag, malformed input, handoff metadata, edge with missing key skippedResumptionTokensTest— graph token encode/decode round-trips, blank field rejection