v0.5.0
.NET SDK

Test .NET workflows and agents

Use deterministic agent tests and distinguish fixtures from server and provider integration evidence.

Test business handlers independently, then test the boundary that matters: serialization, scheduling, worker delivery, stores, or external providers. A passing in-process agent scenario does not establish all those properties at once.

Run a deterministic approval test

Create a net8.0 console application, use the package feed configuration, and add:

bash
dotnet add package PipelogiqSDK.Testing --version 0.5.0

This complete Program.cs needs no running Pipelogiq server, database, broker, or LLM account. It proves that the scripted mutation waits for approval and executes after an explicit approval decision:

csharp
using PipelogiqSDK.Abstractions;
using PipelogiqSDK.Agent.Models;
using PipelogiqSDK.Agent.Services;
using PipelogiqSDK.Testing;

var tool = new ReserveInventoryFixture();
var harness = AgentTestHarness.Create(options =>
{
    options.RequireConfirmationForMutations = true;
})
.WithNativeTool(new AgentToolDefinition
{
    Name = "reserveInventory",
    Description = "Reserve one unit in this test fixture.",
    IsMutating = true
}, tool)
.WithScenario(scenario => scenario
    .ThenCallTool("reserveInventory")
    .WithSynthesizedResponse("Reserved."));

var waiting = await harness.RunAsync("Reserve one unit.");
if (!waiting.IsWaitingForApproval || tool.Reservations != 0)
    throw new InvalidOperationException("The mutation ran without approval.");

var completed = await harness.WithApprovalDecision(true).RunAsync("Reserve one unit.");
if (!completed.IsSuccess || tool.Reservations != 1 ||
    !completed.ToolCallsExecuted.SequenceEqual(new[] { "reserveInventory" }))
    throw new InvalidOperationException("The approved run did not execute the expected tool.");

Console.WriteLine("Approval test passed.");

internal sealed class ReserveInventoryFixture : IAgentToolHandler
{
    public int Reservations { get; private set; }
    public Task<AgentToolOutput> ExecuteAsync(
        IReadOnlyDictionary<string, object?> parameters,
        IStageContext? context = null,
        CancellationToken ct = default)
    {
        ct.ThrowIfCancellationRequested();
        Reservations++;
        return Task.FromResult(AgentToolOutput.Success("{\"reserved\":1}"));
    }
}

Run dotnet run. In a test project, replace the thrown assertions with your test framework's assertions.

Each RunAsync starts a new pipeline using a fresh copy of the script. The second call does not resume the first call's paused pipeline. Session store state can persist across runs; your fixture instance also persists here, which lets the counter detect an unintended early mutation.

Script decisions and failures

WithScenario supports ThenCallTool(name, parameters), ThenRequestConfirmation(...), ThenFinishWith(text), WithSynthesizedResponse(text), and WithPlan(...). The harness defaults to ReAct; use UseReActMode = false plus WithPlan for a planned sequence.

Use WithPlanner, WithCritic, WithToolPolicy, WithNotificationRouter, WithSessionStore, and WithLifecycleObserver to exercise provider errors, rejected actions, delivery failures, session behavior, and callbacks. WithApprovalDecision(false, reason) tests rejection. No approval decision means the harness pauses.

Inspect IsSuccess, IsWaitingForApproval, ErrorCode, StageResults, ToolCallsExecuted, and ContextSnapshot. Success requires a completed responder without a failed stage. A tool's domain failure can still be recovered by the planner, so also assert the business outcomes you care about.

What the harness proves

It executes the actual orchestrator, think, critic, tool, confirmation, responder, and shared stage executor. Inputs, appended stages, and context patches cross JSON boundaries. Built-in HTTP tools are disabled unless replaced with native fixtures. Real provider and Telegram clients are not created.

Its scheduler is sequential and in-process. It does not test server retry timing, multiple workers, duplicate deliveries, broker failure, lease races, process restarts, or database durability. Add tests at those boundaries when your application relies on them.

Test ordinary stage handlers

Keep domain state transitions and external idempotency logic testable without a worker. Useful cases include malformed input, terminal business rejection, transient transport failure, cancellation, and reconciliation of unknown external outcomes. Tests of context patches or execution metadata should pass through the executor or a real worker, since calling a handler directly omits runtime behavior.

For integration, create an isolated application and services. Run both HTTP and AMQP when you support both transports. Assert recorded results and database side effects, not only that the worker process started. Check a fresh DI scope per attempt, retry behavior, context deletion/redaction, and dynamic follow-up work where used.

Recorded SDK verification

The September 17, 2026 release-preparation run consumed all seven local .nupkg files. Its 18 TRX files record 1,536 successful test executions across three Debug and three Release repetitions, without failures or skips. Separate Lab scenarios recorded ten passes and two unconfigured external integrations.

Those scenarios included actual HTTP/AMQP workers, server/database round trips, retries, context patches, schedules firing by the clock, approvals, native/loopback HTTP tools, and Redis/PostgreSQL stores. Agent planning was scripted. Live LLM accounts and Telegram were not configured; model quality, billing, real delivery, production-scale throughput, and every time-zone/failover combination were not established.

The Lab consumes packages independently of SDK source project references and checks loaded assembly origins. Its integration evidence supplements the harness rather than replacing it. See the SDK verification guide for the maintained development workflow. Historical run counts describe that dated run, not a fresh validation of your application or deployment.

Download this page as MarkdownMatches the 0.5.0 source release

Start here