Files
agent-framework/dotnet/src/Microsoft.Agents.AI.Workflows/ExecutorRegistration.cs
T
Jacob Alber 331c750515 .NET: [BREAKING] Enable sharing of workflow instances across concurrently executing runs (#1464)
* refactor: remove unused internals

* feat: Execution Mode for sharing a workflow among concurrent runs

* feat: Update WorkflowHostAgent to support concurrent execution

* Also update AsAgent APIs to support injecting a CheckpointManager and an IWorkflowExecutionEnvironment

* fix: Make Read logic consistent in DeclarativeWorkflowContext
2025-10-15 21:34:17 +00:00

68 lines
2.8 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading.Tasks;
using Microsoft.Shared.Diagnostics;
using ExecutorFactoryF = System.Func<string, System.Threading.Tasks.ValueTask<Microsoft.Agents.AI.Workflows.Executor>>;
namespace Microsoft.Agents.AI.Workflows;
internal sealed class ExecutorRegistration(string id, Type executorType, ExecutorFactoryF provider, object? rawData)
{
public string Id { get; } = Throw.IfNullOrEmpty(id);
public Type ExecutorType { get; } = Throw.IfNull(executorType);
private ExecutorFactoryF ProviderAsync { get; } = Throw.IfNull(provider);
public bool IsNotExecutorInstance { get; } = rawData is not Executor;
public bool IsUnresettableSharedInstance { get; } = rawData is Executor executor &&
// Cross-Run Shareable executors are "trivially" resettable, since they
// have no on-object state.
!executor.IsCrossRunShareable &&
rawData is not IResettableExecutor;
public bool SupportsConcurrent { get; } = (rawData is not Executor executor || executor.IsCrossRunShareable) &&
(rawData is not Workflow workflow || workflow.AllowConcurrent);
internal async ValueTask<bool> TryResetAsync()
{
if (this.IsUnresettableSharedInstance)
{
return false;
}
// If the executor supports concurrent use, then resetting is a no-op.
if (this.SupportsConcurrent)
{
return true;
}
// Technically we definitely know this is true, since if rawData is an Executor, if it was not resettable
// then we would have returned in the first condition, and if rawData is not an Executor, we would have
// returned in the second condition. That only leaves the possibility of rawData is Executor and also
// IResettableExecutor.
if (this.RawExecutorishData is IResettableExecutor resettableExecutor)
{
await resettableExecutor.ResetAsync().ConfigureAwait(false);
return true;
}
return false;
}
internal object? RawExecutorishData { get; } = rawData;
public override string ToString() => $"{this.ExecutorType.Name}({this.Id})";
private Executor CheckId(Executor executor)
{
if (executor.Id != this.Id)
{
throw new InvalidOperationException(
$"Executor ID mismatch: expected '{this.Id}', but got '{executor.Id}'.");
}
return executor;
}
public async ValueTask<Executor> CreateInstanceAsync(string runId) => this.CheckId(await this.ProviderAsync(runId).ConfigureAwait(false));
}