.NET: [BREAKING] Workflows API Review Naming Changes (Part 1?) (#4090)

* refactor: Normalize Run/RunStreaming with AIAgent

* refactor: Clarify Session vs. Run -level concepts

* Rename RunId to SessionId to better match Run/Session terminology in AIAgent
* [BREAKING]: Will break existing checkpointed sessions in CosmosDb due to field rename

* refactor: Rename and simplify interface around getting typed data out of ExternalRequest/Response

* Also adds hints around using value types in PortableValue

* refactor: Rename AddFanInEdge to AddFanInBarrierEdge

This will prevent a breaking change later when we introduce a programmable FanIn edge, analogous to the FanOut edge's EdgeSelector.

The goal, in the long run is to support a number of different FanIn scenarios, with naive FanIn (no barrier) by default, similar to FanOut.

* refactor: AsAgent(this Workflow, ...) => AsAIAgent(...)

* misc - part1: SwitchBuilder internal

---------

Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
This commit is contained in:
Jacob Alber
2026-02-20 02:05:18 +00:00
committed by GitHub
co-authored by Dmytro Struk
parent 5fd260e11d
commit 0086d38f58
93 changed files with 397 additions and 458 deletions
@@ -28,8 +28,7 @@ internal static class Step1EntryPoint
public static async ValueTask RunAsync(TextWriter writer, IWorkflowExecutionEnvironment environment)
{
// TODO: Potentially normalize terminology viz Agent.RunStreamingAsync
StreamingRun run = await environment.StreamAsync(WorkflowInstance, input: "Hello, World!").ConfigureAwait(false);
StreamingRun run = await environment.RunStreamingAsync(WorkflowInstance, input: "Hello, World!").ConfigureAwait(false);
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
@@ -33,7 +33,7 @@ internal static class Step2EntryPoint
public static async ValueTask<string> RunAsync(TextWriter writer, IWorkflowExecutionEnvironment environment, string input = "This is a spam message.")
{
StreamingRun handle = await environment.StreamAsync(WorkflowInstance, input: input).ConfigureAwait(false);
StreamingRun handle = await environment.RunStreamingAsync(WorkflowInstance, input: input).ConfigureAwait(false);
await foreach (WorkflowEvent evt in handle.WatchStreamAsync().ConfigureAwait(false))
{
switch (evt)
@@ -28,7 +28,7 @@ internal static class Step3EntryPoint
public static async ValueTask<string> RunAsync(TextWriter writer, IWorkflowExecutionEnvironment environment)
{
StreamingRun run = await environment.StreamAsync(WorkflowInstance, NumberSignal.Init).ConfigureAwait(false);
StreamingRun run = await environment.RunStreamingAsync(WorkflowInstance, NumberSignal.Init).ConfigureAwait(false);
await foreach (WorkflowEvent evt in run.WatchStreamAsync().ConfigureAwait(false))
{
@@ -37,7 +37,7 @@ internal static class Step4EntryPoint
string? prompt = UpdatePrompt(null, signal);
Workflow workflow = WorkflowInstance;
StreamingRun handle = await environment.StreamAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
StreamingRun handle = await environment.RunStreamingAsync(workflow, NumberSignal.Init).ConfigureAwait(false);
List<ExternalRequest> requests = [];
await foreach (WorkflowEvent evt in handle.WatchStreamAsync().ConfigureAwait(false))
@@ -25,7 +25,7 @@ internal static class Step5EntryPoint
StreamingRun handle =
await environment.WithCheckpointing(checkpointManager)
.StreamAsync(workflow, NumberSignal.Init)
.RunStreamingAsync(workflow, NumberSignal.Init)
.ConfigureAwait(false);
List<CheckpointInfo> checkpoints = [];
@@ -38,13 +38,13 @@ internal static class Step5EntryPoint
CheckpointInfo targetCheckpoint = checkpoints[2];
Console.WriteLine($"Restoring to checkpoint {targetCheckpoint} from run {targetCheckpoint.RunId}");
Console.WriteLine($"Restoring to checkpoint {targetCheckpoint} from session {targetCheckpoint.SessionId}");
if (rehydrateToRestore)
{
await handle.DisposeAsync().ConfigureAwait(false);
handle = await environment.WithCheckpointing(checkpointManager)
.ResumeStreamAsync(workflow, targetCheckpoint, CancellationToken.None)
.ResumeStreamingAsync(workflow, targetCheckpoint, CancellationToken.None)
.ConfigureAwait(false);
}
else
@@ -29,7 +29,7 @@ internal static class Step6EntryPoint
{
Workflow workflow = CreateWorkflow(maxSteps);
StreamingRun run = await environment.StreamAsync(workflow, Array.Empty<ChatMessage>())
StreamingRun run = await environment.RunStreamingAsync(workflow, Array.Empty<ChatMessage>())
.ConfigureAwait(false);
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
@@ -15,7 +15,7 @@ internal static class Step7EntryPoint
{
Workflow workflow = Step6EntryPoint.CreateWorkflow(maxSteps);
AIAgent agent = workflow.AsAgent("group-chat-agent", "Group Chat Agent");
AIAgent agent = workflow.AsAIAgent("group-chat-agent", "Group Chat Agent");
for (int i = 0; i < numIterations; i++)
{
@@ -69,10 +69,10 @@ internal static class Step9EntryPoint
var requestPort = RequestPort.Create<TRequest, TResponse>(id);
return builder.ForwardMessage<ExternalRequest>(source, targets: [filter], condition: message => message.DataIs<TRequest>())
.ForwardMessage<ExternalRequest>(filter, targets: [requestPort], condition: message => message.DataIs<TRequest>())
.ForwardMessage<ExternalResponse>(requestPort, targets: [filter], condition: message => message.DataIs<TResponse>())
.ForwardMessage<ExternalResponse>(filter, targets: [source], condition: message => message.DataIs<TResponse>());
return builder.ForwardMessage<ExternalRequest>(source, targets: [filter], condition: message => message.IsDataOfType<TRequest>())
.ForwardMessage<ExternalRequest>(filter, targets: [requestPort], condition: message => message.IsDataOfType<TRequest>())
.ForwardMessage<ExternalResponse>(requestPort, targets: [filter], condition: message => message.IsDataOfType<TResponse>())
.ForwardMessage<ExternalResponse>(filter, targets: [source], condition: message => message.IsDataOfType<TResponse>());
}
public static WorkflowBuilder AddExternalRequest<TRequest, TResponse>(this WorkflowBuilder builder, ExecutorBinding source, string? id = null)
@@ -207,11 +207,11 @@ internal static class Step9EntryPoint
}
else if (evt is RequestInfoEvent requestInfoEvent)
{
if (requestInfoEvent.Request.DataIs<ResourceRequest>())
if (requestInfoEvent.Request.IsDataOfType<ResourceRequest>())
{
resourceRequests.Add(requestInfoEvent.Request);
}
else if (requestInfoEvent.Request.DataIs<PolicyCheckRequest>())
else if (requestInfoEvent.Request.IsDataOfType<PolicyCheckRequest>())
{
policyRequests.Add(requestInfoEvent.Request);
}
@@ -237,14 +237,14 @@ internal static class Step9EntryPoint
foreach (ExternalRequest request in resourceRequests)
{
ResourceRequest resourceRequest = request.DataAs<ResourceRequest>()!;
ResourceRequest resourceRequest = request.Data.As<ResourceRequest>()!;
resourceRequest.Id.Should().BeOneOf(ResourceMissIds);
responses.Add(request.CreateResponse(Part2FinishedResponses[resourceRequest.Id].ResourceResponse!));
}
foreach (ExternalRequest request in policyRequests)
{
PolicyCheckRequest policyRequest = request.DataAs<PolicyCheckRequest>()!;
PolicyCheckRequest policyRequest = request.Data.As<PolicyCheckRequest>()!;
policyRequest.Id.Should().BeOneOf(PolicyMissIds);
responses.Add(request.CreateResponse(Part2FinishedResponses[policyRequest.Id].PolicyResponse!));
}
@@ -372,7 +372,7 @@ internal sealed class ResourceCache()
private async ValueTask UnwrapAndHandleRequestAsync(ExternalRequest request, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (request.DataIs(out ResourceRequest? resourceRequest))
if (request.TryGetDataAs(out ResourceRequest? resourceRequest))
{
ResourceResponse? response = await this.TryHandleResourceRequestAsync(resourceRequest, context, cancellationToken)
.ConfigureAwait(false);
@@ -421,7 +421,7 @@ internal sealed class ResourceCache()
private ValueTask CollectResultAsync(ExternalResponse response, IWorkflowContext context)
{
if (response.DataIs<ResourceResponse>())
if (response.IsDataOfType<ResourceResponse>())
{
// Normally we'd update the cache according to whatever logic we want here.
return context.SendMessageAsync(response);
@@ -459,7 +459,7 @@ internal sealed class QuotaPolicyEngine()
private async ValueTask UnwrapAndHandleRequestAsync(ExternalRequest request, IWorkflowContext context)
{
if (request.DataIs(out PolicyCheckRequest? policyRquest))
if (request.TryGetDataAs(out PolicyCheckRequest? policyRquest))
{
PolicyResponse? response = await this.TryHandlePolicyCheckRequestAsync(policyRquest, context)
.ConfigureAwait(false);
@@ -507,7 +507,7 @@ internal sealed class QuotaPolicyEngine()
}
private ValueTask CollectAndForwardAsync(ExternalResponse response, IWorkflowContext context)
{
if (response.DataIs<PolicyResponse>())
if (response.IsDataOfType<PolicyResponse>())
{
return context.SendMessageAsync(response);
}
@@ -569,7 +569,7 @@ internal sealed class Coordinator() : Executor(nameof(Coordinator), declareCross
internal async ValueTask RunWorkflowHandleEventsAsync<TInput>(Workflow workflow, TInput input) where TInput : notnull
{
StreamingRun run = await InProcessExecution.StreamAsync(workflow, input);
StreamingRun run = await InProcessExecution.RunStreamingAsync(workflow, input);
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
switch (evt)
@@ -19,7 +19,7 @@ internal static class Step10EntryPoint
public static async ValueTask RunAsync(TextWriter writer, IWorkflowExecutionEnvironment executionEnvironment, IEnumerable<string> inputs)
{
AIAgent hostAgent = WorkflowInstance.AsAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment);
AIAgent hostAgent = WorkflowInstance.AsAIAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment);
AgentSession session = await hostAgent.CreateSessionAsync();
foreach (string input in inputs)
@@ -31,7 +31,7 @@ internal static class Step11EntryPoint
public static async ValueTask RunAsync(TextWriter writer, IWorkflowExecutionEnvironment executionEnvironment, IEnumerable<string> inputs)
{
AIAgent hostAgent = WorkflowInstance.AsAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment);
AIAgent hostAgent = WorkflowInstance.AsAIAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment);
AgentSession session = await hostAgent.CreateSessionAsync();
foreach (string input in inputs)
@@ -67,7 +67,7 @@ internal static class Step12EntryPoint
public static async ValueTask RunAsync(TextWriter writer, IWorkflowExecutionEnvironment executionEnvironment, IEnumerable<string> inputs)
{
AIAgent hostAgent = WorkflowInstance.AsAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment);
AIAgent hostAgent = WorkflowInstance.AsAIAgent("echo-workflow", "EchoW", executionEnvironment: executionEnvironment);
AgentSession session = await hostAgent.CreateSessionAsync();
foreach (string input in inputs)
@@ -30,7 +30,7 @@ internal static class Step13EntryPoint
public static async ValueTask<AgentSession> RunAsAgentAsync(TextWriter writer, string input, IWorkflowExecutionEnvironment environment, AgentSession? session)
{
AIAgent hostAgent = WorkflowInstance.AsAgent("echo-workflow", "EchoW", executionEnvironment: environment, includeWorkflowOutputsInResponse: true);
AIAgent hostAgent = WorkflowInstance.AsAIAgent("echo-workflow", "EchoW", executionEnvironment: environment, includeWorkflowOutputsInResponse: true);
session ??= await hostAgent.CreateSessionAsync();
AgentResponse response;
@@ -83,10 +83,10 @@ internal static class Step13EntryPoint
{
if (resumeFrom == null)
{
return await environment.StreamAsync(WorkflowInstance, input);
return await environment.RunStreamingAsync(WorkflowInstance, input);
}
StreamingRun run = await environment.ResumeStreamAsync(WorkflowInstance, resumeFrom);
StreamingRun run = await environment.ResumeStreamingAsync(WorkflowInstance, resumeFrom);
await run.TrySendMessageAsync(input);
return run;
}