Skip to content

feat: Implement agent graph definitions#1282

Open
jsonbailey wants to merge 6 commits intofeat/ai-sdk-next-releasefrom
jb/aic-2202/agent-graph-definitions
Open

feat: Implement agent graph definitions#1282
jsonbailey wants to merge 6 commits intofeat/ai-sdk-next-releasefrom
jb/aic-2202/agent-graph-definitions

Conversation

@jsonbailey
Copy link
Copy Markdown
Contributor

@jsonbailey jsonbailey commented Apr 15, 2026

Summary

  • Implements the AIGRAPH spec: AgentGraphDefinition, AgentGraphNode, and graph type definitions (LDAgentGraphFlagValue, LDGraphEdge, LDGraphMetricSummary, LDGraphTrackData)
  • Implements the AIGRAPHTRACK spec: full LDGraphTracker interface and LDGraphTrackerImpl with UUID v4 runId generation, at-most-once semantics (with warnings on duplicate calls), multi-fire edge-level methods, and cross-process resumption via URL-safe Base64 token
  • Adds agentGraph() and createGraphTracker() methods to LDAIClient / LDAIClientImpl; validates graph connectivity, root presence, and that all referenced agent configs are enabled before returning a graph

Test plan

  • yarn workspace @launchdarkly/server-sdk-ai test — 175 tests pass across 8 suites
  • AgentGraphDefinition.test.ts — 28 tests covering buildNodes, collectAllKeys, traverse (BFS order + context mutation), reverseTraverse (longest-path depth), node queries, createTracker
  • LDGraphTrackerImpl.test.ts — 30 tests covering runId generation, resumption token encoding/decoding, fromResumptionToken round-trip, at-most-once warning behavior, edge-level multi-fire
  • agentGraph.test.ts — 9 tests covering disabled-when-no-root, disconnected-node detection, disabled-child-config, happy-path single- and multi-node graphs, usage event tracking, createGraphTracker round-trip

🤖 Generated with Claude Code


Note

Medium Risk
Adds new public API surface and new tracking payload/token semantics; incorrect validation or token handling could disable graphs or mis-correlate telemetry, but changes are additive and well-covered by new tests.

Overview
Introduces a new agent-graph feature set: AgentGraphDefinition/AgentGraphNode plus graph flag types, enabling traversal (forward BFS and reverse-from-terminals) and parent/child/terminal queries over a LaunchDarkly-provided graph config.

Extends the public LDAIClient API with agentGraph() (usage-tracked) and createGraphTracker(), including validation that the graph is enabled, has a root, is fully connected from the root, and that all referenced agent configs can be fetched and are enabled.

Overhauls LDGraphTrackerImpl/LDGraphTracker to add per-invocation runId, resumable tracking via a URL-safe Base64 resumptionToken + fromResumptionToken, stricter at-most-once semantics (with warnings) for graph-level metrics, and updated track payloads to include runId and optional variationKey.

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

Add AgentGraphDefinition, AgentGraphNode, LDGraphTracker, and
LDGraphTrackerImpl per the AIGRAPH and AIGRAPHTRACK specs, along with
the agentGraph() and createGraphTracker() methods on LDAIClientImpl.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 25623 bytes
Compressed size limit: 29000
Uncompressed size: 125843 bytes

@github-actions
Copy link
Copy Markdown
Contributor

@launchdarkly/browser size report
This is the brotli compressed size of the ESM build.
Compressed size: 179375 bytes
Compressed size limit: 200000
Uncompressed size: 829982 bytes

@github-actions
Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 31655 bytes
Compressed size limit: 34000
Uncompressed size: 112792 bytes

@github-actions
Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 37169 bytes
Compressed size limit: 38000
Uncompressed size: 204305 bytes

…Impl

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@jsonbailey jsonbailey marked this pull request as ready for review April 15, 2026 22:20
@jsonbailey jsonbailey requested a review from a team as a code owner April 15, 2026 22:20
@jsonbailey jsonbailey changed the title feat: implement agent graph definitions (AIGRAPH + AIGRAPHTRACK) feat: Implement agent graph definitions Apr 15, 2026
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/sdk/server-ai/src/api/graph/AgentGraphDefinition.ts Outdated
Comment thread packages/sdk/server-ai/src/api/graph/AgentGraphDefinition.ts Outdated
Copy link
Copy Markdown
Contributor

@joker23 joker23 left a comment

Choose a reason for hiding this comment

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

I think the bot comments need to be addressed before merging

…pological sort

- Add _hasCycle() DFS validation to agentGraph() — cyclic graphs now return
  { enabled: false } instead of hanging reverseTraverse indefinitely
- Replace _computeLongestPathDepths BFS with Kahn's topological sort + DP,
  which is correct for DAGs and terminates in O(V+E)
- Remove dead stores _context and _graphKey from AgentGraphDefinition constructor

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread packages/sdk/server-ai/src/LDAIClientImpl.ts
…om terminals

Cyclic graphs are valid (supported in the LD UI and by frameworks like LangGraph).
Replace depth-sorted reverseTraverse with upward BFS from terminal nodes via
getParentNodes(), matching the Python SDK implementation:

- Remove _hasCycle() and cycle rejection from agentGraph() — cyclic graphs now
  pass validation and traverse safely
- Drop _computeLongestPathDepths() entirely
- reverseTraverse: BFS upward from terminalNodes(), visited set prevents cycles
  from looping, root is always deferred and invoked last
- Fully cyclic graphs (no terminals) return early without invoking fn, same as Python

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
// Defer the root so it is always processed last
if (key === rootKey) {
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

reverseTraverse skips nodes in cycles involving root

Medium Severity

When reverseTraverse encounters the root node in the BFS queue, it adds root to visited and returns immediately — deferring fn invocation to after the loop. However, this early return also skips enqueuing root's parents. In cyclic graphs where a node points back to root (e.g., root → [a, b], a → [root], b terminal), node a is a parent of root and is only discoverable through root's parent-edge exploration. Since that exploration is skipped, a is silently never visited despite being a valid, reachable node that passed agentGraph validation. The parent-exploration logic (lines 224–228) needs to run for root before the return, while still deferring the fn call.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5191c48. Configure here.

Addresses PR feedback: when _ldMeta.enabled is false, agentGraph() was
still returning { enabled: true, graph } despite the AgentGraphDefinition
documenting that enabled graphs are always true. Now returns disabled early
if the flag is explicitly disabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@cursor cursor bot left a comment

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 and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

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 5cba2c1. Configure here.

const result = fn(root, executionContext);
executionContext[rootKey] = result;
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

reverseTraverse doesn't guarantee documented descendant-first ordering

Medium Severity

reverseTraverse documents that "each node is processed only after all of its reachable descendants have been processed," but the BFS-from-terminals algorithm doesn't guarantee this for DAGs where two nodes discovered at the same BFS level have a parent-child relationship. For example, with edges a → b, a → c, b → c, terminal c's parents a and b are enqueued together. a is processed before b, but b is a descendant of a, so b's result won't be in executionContext when fn(a) runs. A proper reverse topological sort (e.g., Kahn's algorithm) is needed.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5cba2c1. 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.

3 participants