Skip to content

feat(agentic): Phase 1 — typed durable graph runtime (epic #1544)#1548

Merged
ooples merged 8 commits into
masterfrom
feature/agentic-phase1-graph-runtime
Jun 11, 2026
Merged

feat(agentic): Phase 1 — typed durable graph runtime (epic #1544)#1548
ooples merged 8 commits into
masterfrom
feature/agentic-phase1-graph-runtime

Conversation

@ooples

@ooples ooples commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Phase 1 (graph runtime) — slice 1 of the agentic-orchestration epic

Part of #1544. Phase 0 is in #1545; this PR begins Phase 1, the typed graph runtime that is the core differentiator vs LangGraph (typed state, compile-time wiring, deterministic execution — durability/HITL to follow).

What's in this slice (AiDotNet.Agentic.Graph)

  • StateGraph<TState> — fluent builder: AddNode (sync/async), AddEdge, AddConditionalEdges, SetEntryPoint, Compile(). Fully typed state (no stringly-typed state dicts). Compile() validates the graph (entry exists; edges resolve to a node or End; a node can't have both a fixed edge and conditional edges).
  • CompiledStateGraph<TState>InvokeAsync (returns final state) and StreamAsync (yields a GraphStepUpdate<TState> after each node). Cycles are allowed and bounded by GraphRunOptions.MaxSteps (default 25) → GraphRecursionException.
  • GraphSpecialNodes.End sentinel; conditional routers return a node name or End.

Not in this slice (subsequent Phase 1 PRs)

Durable checkpointing (memory/SQLite/Postgres/Redis), resume, time-travel + deterministic record/replay, human-in-the-loop interrupts, channels/reducers + dynamic fan-out (Send/map-reduce), subgraphs, reward-gated edges.

Verification

  • Core builds clean on net10.0 + net471.
  • UnitTests/Agentic/Graph/StateGraphTests.cs: 7/7 pass on net10.0 and net471 (linear, conditional cycle, streaming, recursion-limit, async node, compile validation, duplicate/reserved names).

Conventions: generic <T>, Guard.*, enums/sentinels (no magic strings), "For Beginners" XML docs, and no null-forgiving operators.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added graph state checkpointing with support for in-memory, JSON file, and SQLite persistence
    • Added human-in-the-loop execution with interrupt points and state resumption
    • Added streaming execution progress tracking and replay of recorded runs
    • Added fan-out/parallel node execution with configurable concurrency limits
    • Added step budget limits to prevent infinite loops
    • Added time-travel resumption from specific checkpoints

Introduces the typed graph runtime (AiDotNet.Agentic.Graph), the LangGraph-surpassing core:

- StateGraph<TState> builder: AddNode (sync/async), AddEdge, AddConditionalEdges, SetEntryPoint, Compile() with validation (entry exists, edges resolve, a node can't have both a fixed edge and conditional edges).

- CompiledStateGraph<TState>: InvokeAsync (final state) + StreamAsync (per-node GraphStepUpdate); fully typed state threading; cycles bounded by GraphRunOptions.MaxSteps (default 25) -> GraphRecursionException.

- GraphSpecialNodes.End sentinel; conditional routers return a node name or End (validated at runtime).

- 7 tests (linear, conditional cycle, streaming, recursion-limit, async node, compile validation, dup/reserved names) green on net10.0 + net471. No null-forgiving. Checkpointing/HITL/fan-out/subgraphs are the next slices.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
aidotnet_website Ignored Ignored Preview Jun 9, 2026 12:08pm
aidotnet-playground-api Ignored Ignored Preview Jun 9, 2026 12:08pm

@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ooples, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 55 minutes and 17 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ac4d68cf-10e9-4219-8410-918034777049

📥 Commits

Reviewing files that changed from the base of the PR and between f53e21f and 256f62c.

📒 Files selected for processing (6)
  • src/Agentic/Graph/Checkpointing/IGraphCheckpointer.cs
  • src/Agentic/Graph/Checkpointing/InMemoryGraphCheckpointer.cs
  • src/Agentic/Graph/Checkpointing/JsonFileGraphCheckpointer.cs
  • src/Agentic/Graph/GraphRunOptions.cs
  • src/AiDotNet.Storage.Sqlite/SqliteGraphCheckpointer.cs
  • tests/AiDotNet.Tests/UnitTests/Agentic/Graph/StateGraphTests.cs

Walkthrough

This PR introduces a comprehensive agentic state graph execution engine with multi-backend checkpointing support, enabling pause/resume, replay, and human-in-the-loop workflows. It includes a fluent graph builder, execution engine supporting multiple modes (stateless invoke, checkpointed, interrupted, streamed, replayed), in-memory and durable (JSON/SQLite) checkpoint backends, and extensive unit tests.

Changes

Agentic State Graph with Checkpointing

Layer / File(s) Summary
Data Models and Core Contracts
src/Agentic/Graph/GraphSpecialNodes.cs, src/Agentic/Graph/GraphRunOptions.cs, src/Agentic/Graph/GraphRecursionException.cs, src/Agentic/Graph/GraphStepUpdate.cs, src/Agentic/Graph/GraphRunResult.cs, src/Agentic/Graph/Checkpointing/GraphCheckpoint.cs
Introduces immutable DTOs and typed exception for graph execution: GraphCheckpoint<TState> captures step snapshots with thread/checkpoint identity and completion state; GraphStepUpdate<TState> and GraphRunResult<TState> represent streaming updates and run outcomes; GraphRecursionException enforces step budgets; GraphRunOptions configures per-run limits; GraphSpecialNodes.End marks terminal node.
Checkpointer Interface and In-Memory Implementation
src/Agentic/Graph/Checkpointing/IGraphCheckpointer.cs, src/Agentic/Graph/Checkpointing/InMemoryGraphCheckpointer.cs
Defines IGraphCheckpointer<TState> contract for async persistence/retrieval of checkpoints (save, latest, by-id, history); implements thread-safe in-memory storage using per-thread checkpoint lists protected by instance lock.
File-Based and SQLite Checkpointing
src/Agentic/Graph/Checkpointing/JsonFileGraphCheckpointer.cs, src/AiDotNet.Storage.Sqlite/SqliteGraphCheckpointer.cs
Provides durable checkpoint backends: JsonFileGraphCheckpointer<TState> persists per-thread checkpoint lists in a single JSON file with in-process locking; SqliteGraphCheckpointer<TState> uses SQLite database with schema initialization and row-to-object mapping for cross-process persistence.
State Graph Builder API
src/Agentic/Graph/StateGraph.cs
Fluent builder for typed state graphs supporting async/sync nodes, fixed/conditional routing, subgraph composition, reward-gated branching, parallel fan-out with concurrency throttling, human-in-the-loop interrupts, and compile-time validation ensuring entry point registration, edge targets validity, and edge-type exclusivity.
Graph Execution Engine
src/Agentic/Graph/CompiledStateGraph.cs
Sealed validated executable graph with multiple modes: InvokeAsync for stateless/checkpointed runs, ResumeFromAsync for time-travel replay, RunAsync for interruptible human-in-the-loop execution with optional state transforms, StreamAsync for per-node update streaming, ReplayAsync for deterministic trajectory replay. Core RunCoreAsync loop manages step budget, interrupt pausing, node execution, next-node routing validation, and per-step checkpoint persistence.
Behavioral and Checkpointing Tests
tests/AiDotNet.Tests/UnitTests/Agentic/Graph/StateGraphTests.cs, tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphCheckpointingTests.cs, tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphCompositionTests.cs
Validates StateGraph<T> construction and execution: linear node order and state threading, conditional cycles, streaming updates, step-limit enforcement, async node delegation, compile-time validation, node-name validation. Tests checkpointed execution: history recording, resume from latest, reinvocation of completed threads, time-travel resumption, and error cases. Tests composition: subgraphs as single steps, reward-gated looping, trajectory replay.
Durable Checkpoint and Advanced Feature Tests
tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphDurableCheckpointerTests.cs, tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphFanOutTests.cs, tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphInterruptTests.cs
Validates cross-instance persistence: JSON-file and SQLite checkpointers persist and retrieve history across separate reader instances. Tests parallel fan-out: item mapping, async branching, reduction, parallelism caps, empty sets, and composition. Tests human-in-the-loop interrupts: pausing before configured nodes, resuming with optional state mutations, entry-node interrupts, and InvokeAsync bypass.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Reasoning: This PR is a substantial multi-file feature introduction with dense logic across graph execution, checkpointing backends, and builder patterns. Key complexity drivers include: (1) CompiledStateGraph.RunCoreAsync implements intricate state threading with interrupt pausing, step budgeting, and per-step checkpoint persistence; (2) StateGraph.AddFanOutNode manages parallel task scheduling with semaphore throttling and ordered result reduction; (3) multiple checkpointing implementations handle file I/O locking and JSON/SQL serialization; (4) pervasive use of generics, async patterns, and state transformations across 11 layers. While test coverage is comprehensive, the heterogeneous nature of routing logic validation, concurrency primitives, and storage backends warrants scrutiny across multiple execution paths.

Suggested labels

feature

Poem

🔄 Graphs spin with purpose, nodes march in line,
Checkpoints guard the journey, state preserved in time.
Pause, resume, replay—a dance through stages three,
From builder's fluent whisper to engines wild and free! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main contribution: introducing a typed, durable graph runtime framework as Phase 1 of the agentic orchestration epic.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/agentic-phase1-graph-runtime

Comment @coderabbitai help to get the list of available commands and usage tips.

…1544)

Adds durable execution to the graph runtime (AiDotNet.Agentic.Graph.Checkpointing):

- GraphCheckpoint<TState> (threadId, checkpointId, step, nextNode, state, IsComplete); IGraphCheckpointer<TState> (Save/GetLatest/Get/GetHistory); InMemoryGraphCheckpointer<TState> (thread-safe).

- CompiledStateGraph: checkpointed InvokeAsync(state, checkpointer, threadId) auto-resumes from the latest checkpoint (saves after every step; cumulative step budget); ResumeFromAsync(threadId, checkpointId) replays from any past checkpoint (time-travel). Shared RunCoreAsync powers plain + checkpointed runs.

- 5 tests (fresh+history, resume-skips-earlier, re-invoke-completed, time-travel replay, unknown-checkpoint throws); 12 graph tests total green on net10.0 + net471. No null-forgiving. NEXT: HITL interrupts, channels/reducers+fan-out, DB-backed checkpointers.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown

Deployment failed with the following error:

Resource is limited - try again in 24 hours (more than 100, code: "api-deployments-free-per-day").

Learn More: https://vercel.com/franklins-projects-02a0b5a0?upgradeToPro=build-rate-limit

franklinic and others added 4 commits June 8, 2026 23:24
Adds interrupt-before HITL to the graph runtime, built on checkpointing:

- StateGraph.AddInterruptBefore(node) marks pause points (validated in Compile).

- GraphRunResult<TState> (IsComplete/IsInterrupted/State/InterruptedBefore).

- CompiledStateGraph.RunAsync(state, checkpointer, threadId, applyOnResume?): fresh run pauses just before the first interrupt node and returns Interrupted; re-invoking the same thread resumes past the pause (optionally applying the human's state edit) until the next interrupt or completion. RunCoreAsync now returns GraphRunResult and honors interrupts; plain/checkpointed InvokeAsync run straight through (no pause).

- 4 HITL tests (pause+resume, human edit on resume, interrupt-before-entry, InvokeAsync-ignores-interrupts); 16 graph tests total green on net10.0 + net471. No null-forgiving. NEXT: channels/reducers + dynamic fan-out, DB-backed checkpointers, subgraphs.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… replay (#1544)

- StateGraph.AddSubgraph(name, compiledGraph): run a whole graph as one step (composition).

- StateGraph.AddRewardGatedEdges(from, reward, threshold, ifMeets, ifBelow): route by a critic/reward score (differentiator); sugar over conditional edges.

- CompiledStateGraph.ReplayAsync + GetRecordedStateAsync: deterministically reproduce a recorded run's (node,state) trajectory from checkpoint history without executing nodes.

- 3 tests; 19 graph tests total green on net10.0 + net471. No null-forgiving.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
StateGraph.AddFanOutNode<TItem,TResult>(map, branch, reduce, maxDegreeOfParallelism?): derives items from state, runs branches in parallel (optional SemaphoreSlim throttle), and reduces results back into the typed state — the typed equivalent of LangGraph Send/map-reduce, as a single composable node. 4 tests (map-reduce, throttled, empty, composed); 23 graph tests total green on net10.0 + net471. No null-forgiving.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…1544)

- JsonFileGraphCheckpointer<TState> (core, zero new deps): persists all threads' checkpoints to a JSON file for single-process durability across restarts.

- SqliteGraphCheckpointer<TState> (opt-in AiDotNet.Storage.Sqlite): durable DB-backed checkpoints (table + JSON state column) for cross-process/cross-machine durability; keeps the SQLite dep out of core. Uses explicit arg checks (core's Guard is internal).

- Durability tests prove a fresh checkpointer instance over the same store reads history + resumes. JSON test runs on both TFMs; SQLite test is net10-gated because the e_sqlite3 native lib isn't deployed to the net471 shadow-copy test host (pre-existing, repo-wide; also affects SqliteSqlSyntaxValidatorTests). 25 graph tests green net10.0 / 24 net471. No null-forgiving.

Postgres/Redis checkpointers are interface-ready follow-ups (need live servers; not unit-testable in CI).

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ooples ooples marked this pull request as ready for review June 11, 2026 00:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Agentic/Graph/Checkpointing/IGraphCheckpointer.cs`:
- Around line 40-45: The XML comment for GetHistoryAsync on
IGraphCheckpointer<TState> is misleading: it currently says "ordered by step
ascending" which allows backend divergence when step values repeat or branching
occurs; update the contract to require history be returned in
append/chronological order (e.g., by persistent sequence id such as Seq ASC) so
replay/time-travel semantics are consistent across backends, and mention
GraphCheckpoint<TState> and threadId in the doc to clarify callers should expect
chronological/append ordering rather than logical step ordering; ensure
implementing classes (SQLite backend using Seq ASC) adhere to and are documented
to follow this chronological sequence.

In `@src/Agentic/Graph/Checkpointing/InMemoryGraphCheckpointer.cs`:
- Around line 66-71: GetAsync in InMemoryGraphCheckpointer currently returns the
first (oldest) checkpoint with a matching checkpointId; change it to return the
newest (latest) match to align with durable backends. In the GetAsync method of
class InMemoryGraphCheckpointer (where it iterates "foreach (var cp in list)"),
iterate the list in reverse or use a Last/LastOrDefault-style lookup to return
the last GraphCheckpoint<TState> whose CheckpointId == checkpointId so the most
recently saved checkpoint is returned.

In `@src/Agentic/Graph/Checkpointing/JsonFileGraphCheckpointer.cs`:
- Around line 117-132: The Load method currently calls
JsonConvert.DeserializeObject on the file at _path without catching
JsonException; wrap the deserialization in a try-catch that catches
JsonException (and optionally IOException) around the
JsonConvert.DeserializeObject call used to produce Dictionary<string,
List<GraphCheckpoint<TState>>> and handle corruption by either logging a warning
and returning a new empty Dictionary<string,
List<GraphCheckpoint<TState>>>(StringComparer.Ordinal) or rethrowing a new,
descriptive exception that includes _path; ensure the method still returns a
valid dictionary on error to avoid propagating an unhandled exception to
callers.
- Around line 134-137: Persist currently uses File.WriteAllText which can leave
the checkpoint file partially written; change JsonFileGraphCheckpointer.Persist
to perform an atomic write: serialize to a temporary file in the same directory
(use Path.Combine(Path.GetDirectoryName(_path), ...) with a unique suffix),
write the bytes using a FileStream and flush/flush-to-disk, then atomically
replace the target file (use File.Replace when available or File.Move/Move with
overwrite=true on .NET 6+, falling back to Delete+Move for older runtimes) and
ensure proper try/finally cleanup of the temp file on error; this preserves
durability and avoids corruption of the checkpoint file.

In `@src/Agentic/Graph/GraphRunOptions.cs`:
- Line 18: GraphRunOptions.MaxSteps is documented as "must be positive" but
currently allows zero/negative values; modify the MaxSteps property setter (or
add a private backing field) to validate the value and throw an
ArgumentOutOfRangeException (with a clear message) when assigned a value <= 0,
ensuring callers get an immediate, descriptive error instead of later runtime
failures.

In `@src/AiDotNet.Storage.Sqlite/SqliteGraphCheckpointer.cs`:
- Around line 31-39: Replace the manual null/whitespace checks in
SqliteGraphCheckpointer<TState> with the project's Guard helpers: in the
constructor SqliteGraphCheckpointer(string connectionString) call
Guard.NotNullOrWhiteSpace(connectionString, nameof(connectionString)); and in
the methods SaveAsync, GetLatestAsync, GetAsync, and GetHistoryAsync replace any
"if (param is null)" style checks with Guard.NotNull(param, nameof(param)) (or
Guard.NotNullOrWhiteSpace where appropriate for strings) to match the rest of
the Graph namespace and maintain consistent validation patterns.
- Around line 138-146: ReadRow deserializes stateJson with
JsonConvert.DeserializeObject<TState> which can yield null; update ReadRow to
guard against null by validating the deserialized state before constructing
GraphCheckpoint<TState> (either throw a descriptive
InvalidDataException/ArgumentException referencing checkpointId/threadId when
state is null, or use the null-forgiving operator with a clear comment asserting
non-null because serialization is controlled elsewhere). Reference ReadRow,
stateJson, JsonConvert.DeserializeObject<TState>, and GraphCheckpoint<TState>
when making the change so callers will get a clear error or documented assertion
if deserialization returns null.

In `@tests/AiDotNet.Tests/UnitTests/Agentic/Graph/StateGraphTests.cs`:
- Around line 113-119: In the test method
AddNode_RejectsDuplicateAndReservedNames remove the no-op await by deleting the
trailing "await Task.CompletedTask;" statement; keep the method signature async
Task AddNode_RejectsDuplicateAndReservedNames() as-is (xUnit may need the async
signature) and leave the existing assertions using StateGraph<int>.AddNode and
StateGraph<int>.End unchanged.
- Around line 90-110: In the
Compile_Validates_EntryPointEdgesAndExclusiveRouting test method remove the
no-op statement `await Task.CompletedTask;` at the end; leave the method
signature as `async Task Compile_Validates_EntryPointEdgesAndExclusiveRouting()`
(so xUnit timeout semantics remain) and simply delete the trailing await
statement to avoid the unnecessary no-op await.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ba632407-27d0-4cfd-94b0-d4ecd8d3f38e

📥 Commits

Reviewing files that changed from the base of the PR and between a517196 and f53e21f.

📒 Files selected for processing (18)
  • src/Agentic/Graph/Checkpointing/GraphCheckpoint.cs
  • src/Agentic/Graph/Checkpointing/IGraphCheckpointer.cs
  • src/Agentic/Graph/Checkpointing/InMemoryGraphCheckpointer.cs
  • src/Agentic/Graph/Checkpointing/JsonFileGraphCheckpointer.cs
  • src/Agentic/Graph/CompiledStateGraph.cs
  • src/Agentic/Graph/GraphRecursionException.cs
  • src/Agentic/Graph/GraphRunOptions.cs
  • src/Agentic/Graph/GraphRunResult.cs
  • src/Agentic/Graph/GraphSpecialNodes.cs
  • src/Agentic/Graph/GraphStepUpdate.cs
  • src/Agentic/Graph/StateGraph.cs
  • src/AiDotNet.Storage.Sqlite/SqliteGraphCheckpointer.cs
  • tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphCheckpointingTests.cs
  • tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphCompositionTests.cs
  • tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphDurableCheckpointerTests.cs
  • tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphFanOutTests.cs
  • tests/AiDotNet.Tests/UnitTests/Agentic/Graph/GraphInterruptTests.cs
  • tests/AiDotNet.Tests/UnitTests/Agentic/Graph/StateGraphTests.cs

Comment thread src/Agentic/Graph/Checkpointing/IGraphCheckpointer.cs Outdated
Comment thread src/Agentic/Graph/Checkpointing/InMemoryGraphCheckpointer.cs Outdated
Comment thread src/Agentic/Graph/Checkpointing/JsonFileGraphCheckpointer.cs
Comment thread src/Agentic/Graph/Checkpointing/JsonFileGraphCheckpointer.cs
Comment thread src/Agentic/Graph/GraphRunOptions.cs Outdated
Comment thread src/AiDotNet.Storage.Sqlite/SqliteGraphCheckpointer.cs
Comment thread src/AiDotNet.Storage.Sqlite/SqliteGraphCheckpointer.cs
Comment thread tests/AiDotNet.Tests/UnitTests/Agentic/Graph/StateGraphTests.cs Outdated
Comment thread tests/AiDotNet.Tests/UnitTests/Agentic/Graph/StateGraphTests.cs Outdated
…ty/corruption

- IGraphCheckpointer.GetHistoryAsync: clarify the contract to chronological/append order
  (persistent sequence, not logical step) so replay/time-travel is consistent across backends.
- InMemoryGraphCheckpointer.GetAsync: return the NEWEST matching checkpoint (LastOrDefault),
  aligning with the durable backends.
- JsonFileGraphCheckpointer.Load: throw a descriptive InvalidOperationException on a corrupt
  file (with the path) instead of silently returning empty (which would let Save overwrite
  and lose recoverable data).
- JsonFileGraphCheckpointer.Persist: atomic write — temp file + flush-to-disk + atomic replace
  (File.Move overwrite on net5+, File.Replace/Move fallback on net471), with temp cleanup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ull-state, test cleanup

- GraphRunOptions.MaxSteps: validate > 0 in the setter (ArgumentOutOfRangeException) instead
  of allowing zero/negative.
- SqliteGraphCheckpointer.ReadRow: throw a descriptive InvalidOperationException (with
  checkpointId/threadId) when the persisted state deserializes to null, rather than passing
  null into GraphCheckpoint. (Constructor null-checks kept as-is: Guard is internal to core
  and not accessible from the opt-in AiDotNet.Storage.Sqlite project.)
- StateGraphTests: drop the no-op `await Task.CompletedTask;` from two synchronous tests by
  making them non-async Task methods returning Task.CompletedTask (preserves [Fact(Timeout)],
  avoids the CS1998 that deleting the await alone would introduce).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@ooples ooples merged commit 2a39306 into master Jun 11, 2026
37 of 63 checks passed
@ooples ooples deleted the feature/agentic-phase1-graph-runtime branch June 11, 2026 12:14
ooples added a commit that referenced this pull request Jun 12, 2026
)

* feat(agentic): Phase 1 — typed StateGraph runtime core (#1544)

Introduces the typed graph runtime (AiDotNet.Agentic.Graph), the LangGraph-surpassing core:

- StateGraph<TState> builder: AddNode (sync/async), AddEdge, AddConditionalEdges, SetEntryPoint, Compile() with validation (entry exists, edges resolve, a node can't have both a fixed edge and conditional edges).

- CompiledStateGraph<TState>: InvokeAsync (final state) + StreamAsync (per-node GraphStepUpdate); fully typed state threading; cycles bounded by GraphRunOptions.MaxSteps (default 25) -> GraphRecursionException.

- GraphSpecialNodes.End sentinel; conditional routers return a node name or End (validated at runtime).

- 7 tests (linear, conditional cycle, streaming, recursion-limit, async node, compile validation, dup/reserved names) green on net10.0 + net471. No null-forgiving. Checkpointing/HITL/fan-out/subgraphs are the next slices.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(agentic): Phase 1 — durable checkpointing, resume & time-travel (#1544)

Adds durable execution to the graph runtime (AiDotNet.Agentic.Graph.Checkpointing):

- GraphCheckpoint<TState> (threadId, checkpointId, step, nextNode, state, IsComplete); IGraphCheckpointer<TState> (Save/GetLatest/Get/GetHistory); InMemoryGraphCheckpointer<TState> (thread-safe).

- CompiledStateGraph: checkpointed InvokeAsync(state, checkpointer, threadId) auto-resumes from the latest checkpoint (saves after every step; cumulative step budget); ResumeFromAsync(threadId, checkpointId) replays from any past checkpoint (time-travel). Shared RunCoreAsync powers plain + checkpointed runs.

- 5 tests (fresh+history, resume-skips-earlier, re-invoke-completed, time-travel replay, unknown-checkpoint throws); 12 graph tests total green on net10.0 + net471. No null-forgiving. NEXT: HITL interrupts, channels/reducers+fan-out, DB-backed checkpointers.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(agentic): Phase 1 — human-in-the-loop interrupts (#1544)

Adds interrupt-before HITL to the graph runtime, built on checkpointing:

- StateGraph.AddInterruptBefore(node) marks pause points (validated in Compile).

- GraphRunResult<TState> (IsComplete/IsInterrupted/State/InterruptedBefore).

- CompiledStateGraph.RunAsync(state, checkpointer, threadId, applyOnResume?): fresh run pauses just before the first interrupt node and returns Interrupted; re-invoking the same thread resumes past the pause (optionally applying the human's state edit) until the next interrupt or completion. RunCoreAsync now returns GraphRunResult and honors interrupts; plain/checkpointed InvokeAsync run straight through (no pause).

- 4 HITL tests (pause+resume, human edit on resume, interrupt-before-entry, InvokeAsync-ignores-interrupts); 16 graph tests total green on net10.0 + net471. No null-forgiving. NEXT: channels/reducers + dynamic fan-out, DB-backed checkpointers, subgraphs.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(agentic): Phase 1 — subgraphs, reward-gated edges, deterministic replay (#1544)

- StateGraph.AddSubgraph(name, compiledGraph): run a whole graph as one step (composition).

- StateGraph.AddRewardGatedEdges(from, reward, threshold, ifMeets, ifBelow): route by a critic/reward score (differentiator); sugar over conditional edges.

- CompiledStateGraph.ReplayAsync + GetRecordedStateAsync: deterministically reproduce a recorded run's (node,state) trajectory from checkpoint history without executing nodes.

- 3 tests; 19 graph tests total green on net10.0 + net471. No null-forgiving.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(agentic): Phase 1 — dynamic fan-out / map-reduce nodes (#1544)

StateGraph.AddFanOutNode<TItem,TResult>(map, branch, reduce, maxDegreeOfParallelism?): derives items from state, runs branches in parallel (optional SemaphoreSlim throttle), and reduces results back into the typed state — the typed equivalent of LangGraph Send/map-reduce, as a single composable node. 4 tests (map-reduce, throttled, empty, composed); 23 graph tests total green on net10.0 + net471. No null-forgiving.

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(agentic): Phase 1 — durable checkpointers (JSON-file + SQLite) (#1544)

- JsonFileGraphCheckpointer<TState> (core, zero new deps): persists all threads' checkpoints to a JSON file for single-process durability across restarts.

- SqliteGraphCheckpointer<TState> (opt-in AiDotNet.Storage.Sqlite): durable DB-backed checkpoints (table + JSON state column) for cross-process/cross-machine durability; keeps the SQLite dep out of core. Uses explicit arg checks (core's Guard is internal).

- Durability tests prove a fresh checkpointer instance over the same store reads history + resumes. JSON test runs on both TFMs; SQLite test is net10-gated because the e_sqlite3 native lib isn't deployed to the net471 shadow-copy test host (pre-existing, repo-wide; also affects SqliteSqlSyntaxValidatorTests). 25 graph tests green net10.0 / 24 net471. No null-forgiving.

Postgres/Redis checkpointers are interface-ready follow-ups (need live servers; not unit-testable in CI).

Part of epic #1544 (Phase 1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(agentic): address PR #1548 review — checkpointer ordering/atomicity/corruption

- IGraphCheckpointer.GetHistoryAsync: clarify the contract to chronological/append order
  (persistent sequence, not logical step) so replay/time-travel is consistent across backends.
- InMemoryGraphCheckpointer.GetAsync: return the NEWEST matching checkpoint (LastOrDefault),
  aligning with the durable backends.
- JsonFileGraphCheckpointer.Load: throw a descriptive InvalidOperationException on a corrupt
  file (with the path) instead of silently returning empty (which would let Save overwrite
  and lose recoverable data).
- JsonFileGraphCheckpointer.Persist: atomic write — temp file + flush-to-disk + atomic replace
  (File.Move overwrite on net5+, File.Replace/Move fallback on net471), with temp cleanup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(agentic): address PR #1548 review — MaxSteps validation, Sqlite null-state, test cleanup

- GraphRunOptions.MaxSteps: validate > 0 in the setter (ArgumentOutOfRangeException) instead
  of allowing zero/negative.
- SqliteGraphCheckpointer.ReadRow: throw a descriptive InvalidOperationException (with
  checkpointId/threadId) when the persisted state deserializes to null, rather than passing
  null into GraphCheckpoint. (Constructor null-checks kept as-is: Guard is internal to core
  and not accessible from the opt-in AiDotNet.Storage.Sqlite project.)
- StateGraphTests: drop the no-op `await Task.CompletedTask;` from two synchronous tests by
  making them non-async Task methods returning Task.CompletedTask (preserves [Fact(Timeout)],
  avoids the CS1998 that deleting the await alone would introduce).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: franklinic <franklin@ivorycloud.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

2 participants