Skip to content
Tracon

TraconWorkflowFunctionExtensions

Namespace Tracon · Assembly Tracon.Workflows.dll

Extensions that register a code function as a workflow node.

public static class TraconWorkflowFunctionExtensions
Inheritance
object ← TraconWorkflowFunctionExtensions
Inherited Members
object.GetType(), object.MemberwiseClone(), object.ToString(), object.Equals(object?), object.Equals(object?, object?), object.ReferenceEquals(object?, object?), object.GetHashCode()

AddWorkflowFunction<TInput, TOutput>(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>, string?, WorkflowNodeRetryPolicy?)

Section titled “ AddWorkflowFunction<TInput, TOutput>(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>, string?, WorkflowNodeRetryPolicy?)”

Registers a function that a WorkflowKind.Sequential workflow can use as a node, mixed in with agents from the catalog.

public static ITraconBuilder AddWorkflowFunction<TInput, TOutput>(this ITraconBuilder builder, string name, Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>> factory, string? description = null, WorkflowNodeRetryPolicy? retryPolicy = null)

builder ITraconBuilder

The Tracon configuration chain.

name string

The function’s unique name. A WorkflowDefinition node of kind WorkflowNodeKind.Function points to it by this name.

factory Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>

Resolves the function’s dependencies from the service provider and returns the handler that runs on every call.

description string?

A short description shown in the function catalog.

retryPolicy WorkflowNodeRetryPolicy?

Retries this node on a transient provider error instead of failing the whole run. null (the default) means no retry — today’s behavior. See WorkflowNodeRetryPolicy.

ITraconBuilder

The chain, for continued configuration.

TInput

The type the function accepts.

TOutput

The type the function returns.

Security boundary. A workflow definition can only point to a function registered here, by name. The function’s code is never written from the UI or the database - the same boundary AddTool draws for tools, and the same boundary TraconWorkflowsBuilderExtensions.AddWorkflow draws for a free-form graph.

A timeout (WorkflowFunctionOptions.Timeout) and a limit on how many calls run at once (WorkflowFunctionOptions.MaxConcurrency) are set with the overload that takes a WorkflowFunctionOptions callback. A node of a function registered here can still carry its own WorkflowNodeReference.Timeout.

factory runs exactly once, when the function registry is first built - not once per workflow run. Resolve every dependency the handler needs inside factory; the handler itself receives no service provider at call time (IWorkflowContext does not carry one, the same constraint tools hit).

The handler must be thread-safe. Because factory runs once, every workflow compile - and therefore every concurrent run of this workflow, and every other workflow that references the same function name - shares the exact same handler closure. A handler that captures mutable state (a counter, a non-thread-safe client) must guard it itself.

Only WorkflowKind.Sequential supports function nodes. Microsoft Agent Framework’s ready-made builders for the other four patterns accept only agents; splicing a function into them would mean hand-writing their orchestration logic, which is out of scope.

TInput must take what the node before it passes on. An agent node takes and passes on a List<ChatMessage>, and a run starts by sending the user’s message as one, so a function that follows an agent or comes first takes a List<ChatMessage> (or a type it can be assigned to, such as IEnumerable<ChatMessage>). Saving a chain that breaks this is a 400 that names both nodes, and a run of such a chain fails before its first node: Microsoft Agent Framework drops a message no handler takes, without an error, so the chain would otherwise end with no output. A first node must not take Object: every start and resume also sends it the framework’s turn token, and a node that takes Object would run once for each.

The handler must be idempotent when the workflow enables checkpointing (the default). Measured: resuming from the run’s LATEST checkpoint after it has already completed does not call the handler again - the checkpoint already reflects the finished graph, so there is nothing left to run. But resuming from an earlier checkpoint - the shape a real crash recovery takes, naming a checkpoint written before this node’s own super-step - replays that super-step, and the handler runs again with the same input. A handler with a real side effect (a file write, an HTTP call, a database insert) must therefore tolerate being called more than once for the same logical step.

builder.AddTracon()
.UseWorkflows()
.AddWorkflowFunction<List<ChatMessage>, List<ChatMessage>>(
"normalise",
provider => (messages, context, cancellationToken) =>
ValueTask.FromResult<List<ChatMessage>>(
[new ChatMessage(ChatRole.User, messages[^1].Text.Trim().ToUpperInvariant())]));

ArgumentNullException

One of the required parameters is null.

ArgumentException

name is empty.

ArgumentOutOfRangeException

retryPolicy has a negative WorkflowNodeRetryPolicy.InitialDelay or one longer than about 49.7 days, or a negative or non-finite WorkflowNodeRetryPolicy.BackoffMultiplier.

AddWorkflowFunction<TInput, TOutput>(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>, Action<WorkflowFunctionOptions>)

Section titled “ AddWorkflowFunction<TInput, TOutput>(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>, Action<WorkflowFunctionOptions>)”

Registers a function that a WorkflowKind.Sequential workflow can use as a node, with a description, a retry policy, a timeout and a concurrency limit.

public static ITraconBuilder AddWorkflowFunction<TInput, TOutput>(this ITraconBuilder builder, string name, Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>> factory, Action<WorkflowFunctionOptions> configure)

builder ITraconBuilder

The Tracon configuration chain.

name string

The function’s unique name. A WorkflowDefinition node of kind WorkflowNodeKind.Function points to it by this name.

factory Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>

Resolves the function’s dependencies from the service provider and returns the handler that runs on every call.

configure Action<WorkflowFunctionOptions>

Sets the description, the retry policy, the timeout and the concurrency limit.

ITraconBuilder

The chain, for continued configuration.

TInput

The type the function accepts.

TOutput

The type the function returns.

Everything the overload with optional parameters says applies here: the factory runs once, the handler must be thread-safe, it must take what the node before it passes on, and it must tolerate being called again for the same step.

A timeout cancels the handler’s token. When WorkflowFunctionOptions.Timeout (or the node’s own WorkflowNodeReference.Timeout) elapses, the token the handler was given is cancelled and the run fails with TraconWorkflowFunctionTimeoutException. Pass the token to every call the handler makes: a handler that ignores it keeps running in the background, and its side effects still happen.

Passing null as the third argument is ambiguous with the overload whose third parameter is the description; name the argument (description: null) or leave it out.

builder.AddTracon()
.UseWorkflows()
.AddWorkflowFunction<List<ChatMessage>, List<ChatMessage>>(
"tidy",
provider =>
{
var client = provider.GetRequiredService<IChatClient>();
// The token goes to the call: the timeout cancels it there.
return async (messages, context, cancellationToken) =>
{
var response = await client.GetResponseAsync(messages, cancellationToken: cancellationToken);
return [.. messages, .. response.Messages];
};
},
options =>
{
options.Description = "Asks a model to tidy the script.";
options.Timeout = TimeSpan.FromMinutes(2);
});

ArgumentNullException

One of the required parameters is null.

ArgumentException

name is empty.

ArgumentOutOfRangeException

The retry policy has a negative WorkflowNodeRetryPolicy.InitialDelay or one longer than about 49.7 days, or a negative or non-finite WorkflowNodeRetryPolicy.BackoffMultiplier; or WorkflowFunctionOptions.Timeout is outside one millisecond to about 49.7 days.

AddWorkflowFunction<TInput, TOutput, TSettings>(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, TSettings, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>)

Section titled “ AddWorkflowFunction<TInput, TOutput, TSettings>(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, TSettings, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>)”

Registers a function that takes typed settings, so one function can serve several workflows with a different configuration in each.

public static ITraconBuilder AddWorkflowFunction<TInput, TOutput, TSettings>(this ITraconBuilder builder, string name, Func<IServiceProvider, Func<TInput, TSettings, IWorkflowContext, CancellationToken, ValueTask<TOutput>>> factory) where TSettings : class

builder ITraconBuilder

The Tracon configuration chain.

name string

The function’s unique name. A WorkflowDefinition node of kind WorkflowNodeKind.Function points to it by this name.

factory Func<IServiceProvider, Func<TInput, TSettings, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>

Resolves the function’s dependencies from the service provider and returns the handler that runs on every call. The handler receives the node’s settings as the second argument.

ITraconBuilder

The chain, for continued configuration.

TInput

The type the function accepts.

TOutput

The type the function returns.

TSettings

The type of the node’s settings. The function’s code declares the shape; an operator writes values for it, in the console or over HTTP.

A description for the console and a retry policy are set with the overload that takes a WorkflowFunctionOptions callback. Everything about the two-parameter overload applies here too: the factory runs once, the handler must be thread-safe and must tolerate being called again for the same step. See TraconWorkflowFunctionExtensions.AddWorkflowFunction`.

The settings are checked by being read. When a definition is saved, and again when a run starts, the node’s settings are read into TSettings: an unknown field, a missing required member, a value of the wrong type and a null where the member does not allow it are all refused, and the message names the node and the member. A node without settings is read as {}, so a type with a required member refuses a node that carries none - the operator hears about it when saving, not in the first run. The handler receives exactly the object that was checked.

A settings change takes effect on the next run: the graph is built again for every run. A run waiting for input resumes with the settings of the definition version it recorded, so an edit made while it waited does not change what it does; a resume that names a newer version (WorkflowResumeRequest.DefinitionVersion) reads that version’s settings. The run in progress keeps the settings it started with.

Settings are values, not expressions, and never a secret. They are stored in the workflow definition and in the audit trail, and are returned to every caller who can read the workflow. When the function needs a credential, make the setting the name of the configuration key and read the value from IConfiguration inside factory. A setting is not substituted from a run parameter either: read the run’s values with context.GetRunParameters.

The names are camelCase and matched without regard to case. The exported JSON schema (settingsSchema on GET /api/workflows/functions) lists them for the console and is for display only - the read above is the check.

The console reads a JSON number as a double, like every JavaScript client, so an integer beyond 9007199254740991 (2^53 - 1) loses its low digits when an operator opens a workflow and saves it again. Numbers are read strictly, so a string cannot fill a numeric member: declare an identifier that wide (a snowflake or a 64-bit database key) as a String.

The settings type is plain code. Settings are values, so a secret has no place in it - name the configuration key instead:

public sealed record TtsSettings
{
public required string Voice { get; init; }
public double LoudnessLufs { get; init; } = -16;
public string? ApiKeyConfigurationKey { get; init; }
}
builder.AddTracon()
.UseWorkflows()
.AddWorkflowFunction<List<ChatMessage>, string, TtsSettings>(
"tts",
provider => (messages, settings, context, cancellationToken) =>
ValueTask.FromResult($"{settings.Voice} at {settings.LoudnessLufs} LUFS: {messages[^1].Text}"),
options => options.Description = "Text to speech");

ArgumentNullException

One of the required parameters is null.

ArgumentException

name is empty.

TraconException

TSettings is a type System.Text.Json cannot create (an interface or an abstract class, for example). The function could never be given settings, so this fails here instead of at the first save.

AddWorkflowFunction<TInput, TOutput, TSettings>(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, TSettings, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>, Action<WorkflowFunctionOptions>)

Section titled “ AddWorkflowFunction<TInput, TOutput, TSettings>(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, TSettings, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>, Action<WorkflowFunctionOptions>)”

Registers a function that takes typed settings, with a description, a retry policy and a timeout.

public static ITraconBuilder AddWorkflowFunction<TInput, TOutput, TSettings>(this ITraconBuilder builder, string name, Func<IServiceProvider, Func<TInput, TSettings, IWorkflowContext, CancellationToken, ValueTask<TOutput>>> factory, Action<WorkflowFunctionOptions> configure) where TSettings : class

builder ITraconBuilder

The Tracon configuration chain.

name string

The function’s unique name.

factory Func<IServiceProvider, Func<TInput, TSettings, IWorkflowContext, CancellationToken, ValueTask<TOutput>>>

Resolves the function’s dependencies and returns the handler that runs on every call; the handler receives the node’s settings as the second argument.

configure Action<WorkflowFunctionOptions>

Sets the description, the retry policy, the timeout and the concurrency limit.

ITraconBuilder

The chain, for continued configuration.

TInput

The type the function accepts.

TOutput

The type the function returns.

TSettings

The type of the node’s settings.

See TraconWorkflowFunctionExtensions.AddWorkflowFunction` for how the settings are checked and what they must never hold.

builder.AddTracon()
.UseWorkflows()
.AddWorkflowFunction<List<ChatMessage>, string, TtsSettings>(
"tts",
provider => (messages, settings, context, cancellationToken) =>
ValueTask.FromResult($"{settings.Voice}: {messages[^1].Text}"),
options =>
{
options.Description = "Text to speech";
options.RetryPolicy = new WorkflowNodeRetryPolicy
{
MaxAttempts = 3,
InitialDelay = TimeSpan.FromSeconds(1),
};
});

ArgumentNullException

One of the required parameters is null.

ArgumentException

name is empty.

ArgumentOutOfRangeException

The retry policy has a negative WorkflowNodeRetryPolicy.InitialDelay or one longer than about 49.7 days, or a negative or non-finite WorkflowNodeRetryPolicy.BackoffMultiplier; or WorkflowFunctionOptions.Timeout is outside one millisecond to about 49.7 days.

TraconException

TSettings is a type no settings could be read into.