Commit a100712
authored
fix!: Make AgentGraph traversal topological (#199)
## Summary
Makes `AgentGraphDefinition` traversal **topological** and gives each
visitor a **dependency-scoped
execution context**, aligning the traversal behavior across the
LaunchDarkly AI SDKs.
Previously `traverse` / `reverseTraverse` were plain BFS over a single
shared, accumulating context
map. That had two problems:
1. **Ordering:** where branches of unequal length converge, the
convergence node ran on first
discovery — before all of its predecessors had run.
2. **Context leakage & mutation:** every callback saw the results of
*all* previously-visited nodes
(including unrelated parallel branches), and results were written back
into the caller's context
map.
## What changed
`lib/sdk/server-ai` — `AgentGraphDefinition.java` only (no public
API/signature changes):
- **`traverse`** now visits a node only after **all of its reachable
predecessors** have been visited
(Kahn over in-degree; the root is always released first). On cycles, the
unvisited node with the
lowest remaining in-degree is chosen next, ties broken by discovery
order.
- **`reverseTraverse`** now visits a node only after **all of its
reachable descendants** have been
visited, so the root is visited **last** (Kahn over out-degree, root
excluded from cycle-break
selection). Pure cycles now visit every node instead of being a no-op.
- **Scoped context:** each callback receives a fresh map = the initial
context plus only that node's
true dependency results (transitive ancestors forward / descendants
reverse). Dependencies are
accumulated **before** the node is marked visited in both directions, so
a self-loop node never
includes itself; unrelated branches are excluded and the caller's map is
never mutated. Cross-node
data flows only through callback return values.
- **Determinism:** discovery order is the graph's BFS encounter order
(root first, then declared edge
order), used only for tie-breaks.
```java
void traverse(BiFunction<AgentGraphNode, Map<String, Object>, Object> fn, Map<String, Object> ctx);
```
## Behavior change (breaking)
Titled `fix!` because visit order and callback-context contents change
for graphs with convergent
paths, cycles, or parallel branches. Simple linear graphs are
unaffected. Cross-SDK parity with
JS/Python/.NET/Java.
## Tests
- A data-driven test over the canonical cross-SDK vectors `G1`–`G6`
(+`G2b`) asserts **exact** visit
order **and exact** context keys in both directions.
- Convergence runs the shared node last; pure cycle visits all nodes
(root last); caller context not
mutated; unrelated branches excluded; a dedicated self-loop test
confirms a node is not in its own
context; deterministic across runs. Redundant per-vector order-only
tests were dropped in favor of
the data-driven vector test.
## Notes
- No manual `CHANGELOG.md` edit — release-please generates it from the
conventional-commit title.
- Graph tracking / wire parsing unchanged; this PR is scoped to
traversal + context.
- Minor follow-up (not required): the class-level Javadoc still says
traversal is "BFS-based" — worth
updating to "topological / dependency-order" so the docs match the new
behavior.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Overview**
> Replaces **BFS** `traverse` / `reverseTraverse` on
`AgentGraphDefinition` with **dependency-ordered** walks (Kahn-style,
cycle-safe, BFS discovery for tie-breaks). Convergence nodes run only
after all predecessors (or descendants in reverse); pure cycles now
visit every reachable node instead of effectively no-oping on reverse.
>
> **Breaking behavior:** each visitor gets a **fresh** map—initial `ctx`
plus only that node’s true dependency results (ancestors forward,
descendants reverse). The caller’s `ctx` is **not** mutated with node
outputs, and parallel-branch results no longer leak into unrelated
callbacks. Self-loops exclude the node from its own context.
>
> Tests add cross-SDK **G1–G8** vectors for exact visit order and
context keys, plus determinism, diamond graphs, and caller-context
immutability checks.
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
3c24de6. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->1 parent 58f3c25 commit a100712
2 files changed
Lines changed: 491 additions & 113 deletions
File tree
- lib/sdk/server-ai/src
- main/java/com/launchdarkly/sdk/server/ai
- test/java/com/launchdarkly/sdk/server/ai
Lines changed: 226 additions & 41 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
| |||
23 | 24 | | |
24 | 25 | | |
25 | 26 | | |
26 | | - | |
27 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
28 | 30 | | |
29 | 31 | | |
30 | 32 | | |
| |||
158 | 160 | | |
159 | 161 | | |
160 | 162 | | |
161 | | - | |
| 163 | + | |
162 | 164 | | |
163 | | - | |
164 | | - | |
165 | | - | |
| 165 | + | |
| 166 | + | |
166 | 167 | | |
167 | | - | |
| 168 | + | |
| 169 | + | |
168 | 170 | | |
169 | | - | |
170 | | - | |
| 171 | + | |
| 172 | + | |
171 | 173 | | |
172 | 174 | | |
173 | 175 | | |
| |||
176 | 178 | | |
177 | 179 | | |
178 | 180 | | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
179 | 202 | | |
180 | | - | |
181 | | - | |
182 | | - | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
183 | 210 | | |
184 | | - | |
185 | | - | |
186 | | - | |
187 | | - | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
188 | 229 | | |
189 | | - | |
190 | | - | |
191 | | - | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
192 | 235 | | |
193 | 236 | | |
194 | 237 | | |
195 | 238 | | |
196 | 239 | | |
197 | 240 | | |
198 | | - | |
199 | | - | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
200 | 245 | | |
201 | | - | |
202 | | - | |
| 246 | + | |
| 247 | + | |
203 | 248 | | |
204 | | - | |
205 | | - | |
| 249 | + | |
| 250 | + | |
206 | 251 | | |
207 | 252 | | |
208 | 253 | | |
209 | 254 | | |
210 | 255 | | |
211 | 256 | | |
212 | 257 | | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
213 | 278 | | |
214 | 279 | | |
215 | | - | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
216 | 287 | | |
217 | | - | |
218 | | - | |
219 | | - | |
220 | | - | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
221 | 313 | | |
222 | 314 | | |
223 | 315 | | |
224 | | - | |
225 | | - | |
226 | | - | |
227 | | - | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
228 | 324 | | |
229 | | - | |
230 | | - | |
231 | | - | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
232 | 343 | | |
233 | 344 | | |
234 | 345 | | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
235 | 399 | | |
236 | | - | |
237 | | - | |
238 | | - | |
239 | | - | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
240 | 424 | | |
| 425 | + | |
241 | 426 | | |
242 | 427 | | |
243 | 428 | | |
| |||
0 commit comments