# TraconWorkflowFunctionExtensions

> Tracon documentation. Tracon is a .NET package family that adds a control
> plane on Microsoft Agent Framework, published as NuGet packages that run
> inside your own ASP.NET Core application.
>
> Page: https://tracon.dev/api/tracon.traconworkflowfunctionextensions/
> Summary: TraconWorkflowFunctionExtensions: Extensions that register a code function as a workflow node.

<div class="api-provenance">Namespace Tracon · Assembly <code>Tracon.Workflows.dll</code></div>

Extensions that register a code function as a workflow node.

```csharp
public static class TraconWorkflowFunctionExtensions
```

<dl class="api-relations"><dt>Inheritance</dt><dd><a href="https://learn.microsoft.com/dotnet/api/system.object">object</a> ← <a href="/api/tracon.traconworkflowfunctionextensions/">TraconWorkflowFunctionExtensions</a></dd><dt>Inherited Members</dt><dd><a href="https://learn.microsoft.com/dotnet/api/system.object.gettype">object.GetType()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone">object.MemberwiseClone()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.tostring">object.ToString()</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object)">object.Equals(object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object)">object.Equals(object?, object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.referenceequals">object.ReferenceEquals(object?, object?)</a>, <a href="https://learn.microsoft.com/dotnet/api/system.object.gethashcode">object.GetHashCode()</a></dd></dl>

## Methods

### <a id="Tracon_TraconWorkflowFunctionExtensions_AddWorkflowFunction__2_Tracon_ITraconBuilder_System_String_System_Func_System_IServiceProvider_System_Func___0_Microsoft_Agents_AI_Workflows_IWorkflowContext_System_Threading_CancellationToken_System_Threading_Tasks_ValueTask___1____System_String_Tracon_WorkflowNodeRetryPolicy_"></a> AddWorkflowFunction<TInput, TOutput\>\(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput\>\>\>, string?, WorkflowNodeRetryPolicy?\)

Registers a function that a [WorkflowKind.Sequential](/api/tracon.workflowkind/)
workflow can use as a node, mixed in with agents from the catalog.

```csharp
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)
```

#### Parameters

`builder` [ITraconBuilder](/api/tracon.itraconbuilder/)

The Tracon configuration chain.

`name` [string](https://learn.microsoft.com/dotnet/api/system.string)

The function's unique name. A [WorkflowDefinition](/api/tracon.workflowdefinition/) node of
kind [WorkflowNodeKind.Function](/api/tracon.workflownodekind/) points to it by this name.

`factory` [Func](https://learn.microsoft.com/dotnet/api/system.func\-2)<[IServiceProvider](https://learn.microsoft.com/dotnet/api/system.iserviceprovider), [Func](https://learn.microsoft.com/dotnet/api/system.func\-4)<TInput, IWorkflowContext, [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken), [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<TOutput\>\>\>

Resolves the function's dependencies from the service provider and
returns the handler that runs on every call.

`description` [string](https://learn.microsoft.com/dotnet/api/system.string)?

A short description shown in the function catalog.

`retryPolicy` [WorkflowNodeRetryPolicy](/api/tracon.workflownoderetrypolicy/)?

Retries this node on a transient provider error instead of failing
the whole run. [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null) (the default) means no retry —
today's behavior. See [WorkflowNodeRetryPolicy](/api/tracon.workflownoderetrypolicy/).

#### Returns

 [ITraconBuilder](/api/tracon.itraconbuilder/)

The chain, for continued configuration.

#### Type Parameters

`TInput` 

The type the function accepts.

`TOutput` 

The type the function returns.

#### Remarks

**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](/api/tracon.traconworkflowsbuilderextensions/) draws
for a free-form graph.

A timeout ([WorkflowFunctionOptions.Timeout](/api/tracon.workflowfunctionoptions/#Tracon_WorkflowFunctionOptions_Timeout)) and a limit on how many calls
run at once ([WorkflowFunctionOptions.MaxConcurrency](/api/tracon.workflowfunctionoptions/#Tracon_WorkflowFunctionOptions_MaxConcurrency)) are set with the overload that
takes a [WorkflowFunctionOptions](/api/tracon.workflowfunctionoptions/) callback. A node of a function registered here can
still carry its own [WorkflowNodeReference.Timeout](/api/tracon.workflownodereference/#Tracon_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](/api/tracon.workflowkind/) 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](https://learn.microsoft.com/dotnet/api/system.object): every start and resume also sends it
the framework's turn token, and a node that takes [Object](https://learn.microsoft.com/dotnet/api/system.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.

```csharp
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())]));
```

#### Exceptions

 [ArgumentNullException](https://learn.microsoft.com/dotnet/api/system.argumentnullexception)

One of the required parameters is [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null).

 [ArgumentException](https://learn.microsoft.com/dotnet/api/system.argumentexception)

`name` is empty.

 [ArgumentOutOfRangeException](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception)

`retryPolicy` has a negative [WorkflowNodeRetryPolicy.InitialDelay](/api/tracon.workflownoderetrypolicy/#Tracon_WorkflowNodeRetryPolicy_InitialDelay) or one
longer than about 49.7 days, or a negative or non-finite
[WorkflowNodeRetryPolicy.BackoffMultiplier](/api/tracon.workflownoderetrypolicy/#Tracon_WorkflowNodeRetryPolicy_BackoffMultiplier).

### <a id="Tracon_TraconWorkflowFunctionExtensions_AddWorkflowFunction__2_Tracon_ITraconBuilder_System_String_System_Func_System_IServiceProvider_System_Func___0_Microsoft_Agents_AI_Workflows_IWorkflowContext_System_Threading_CancellationToken_System_Threading_Tasks_ValueTask___1____System_Action_Tracon_WorkflowFunctionOptions__"></a> AddWorkflowFunction<TInput, TOutput\>\(ITraconBuilder, string, Func<IServiceProvider, Func<TInput, IWorkflowContext, CancellationToken, ValueTask<TOutput\>\>\>, Action<WorkflowFunctionOptions\>\)

Registers a function that a [WorkflowKind.Sequential](/api/tracon.workflowkind/) workflow can use
as a node, with a description, a retry policy, a timeout and a concurrency limit.

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

`builder` [ITraconBuilder](/api/tracon.itraconbuilder/)

The Tracon configuration chain.

`name` [string](https://learn.microsoft.com/dotnet/api/system.string)

The function's unique name. A [WorkflowDefinition](/api/tracon.workflowdefinition/) node of
kind [WorkflowNodeKind.Function](/api/tracon.workflownodekind/) points to it by this name.

`factory` [Func](https://learn.microsoft.com/dotnet/api/system.func\-2)<[IServiceProvider](https://learn.microsoft.com/dotnet/api/system.iserviceprovider), [Func](https://learn.microsoft.com/dotnet/api/system.func\-4)<TInput, IWorkflowContext, [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken), [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<TOutput\>\>\>

Resolves the function's dependencies from the service provider and
returns the handler that runs on every call.

`configure` [Action](https://learn.microsoft.com/dotnet/api/system.action\-1)<[WorkflowFunctionOptions](/api/tracon.workflowfunctionoptions/)\>

Sets the description, the retry policy, the timeout and the concurrency limit.

#### Returns

 [ITraconBuilder](/api/tracon.itraconbuilder/)

The chain, for continued configuration.

#### Type Parameters

`TInput` 

The type the function accepts.

`TOutput` 

The type the function returns.

#### Remarks

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](/api/tracon.workflowfunctionoptions/#Tracon_WorkflowFunctionOptions_Timeout) (or the node's own
[WorkflowNodeReference.Timeout](/api/tracon.workflownodereference/#Tracon_WorkflowNodeReference_Timeout)) elapses, the token the handler was
given is cancelled and the run fails with
[TraconWorkflowFunctionTimeoutException](/api/tracon.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](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/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.

```csharp
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);
           });
```

#### Exceptions

 [ArgumentNullException](https://learn.microsoft.com/dotnet/api/system.argumentnullexception)

One of the required parameters is [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null).

 [ArgumentException](https://learn.microsoft.com/dotnet/api/system.argumentexception)

`name` is empty.

 [ArgumentOutOfRangeException](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception)

The retry policy has a negative [WorkflowNodeRetryPolicy.InitialDelay](/api/tracon.workflownoderetrypolicy/#Tracon_WorkflowNodeRetryPolicy_InitialDelay) or one
longer than about 49.7 days, or a negative or non-finite
[WorkflowNodeRetryPolicy.BackoffMultiplier](/api/tracon.workflownoderetrypolicy/#Tracon_WorkflowNodeRetryPolicy_BackoffMultiplier); or
[WorkflowFunctionOptions.Timeout](/api/tracon.workflowfunctionoptions/#Tracon_WorkflowFunctionOptions_Timeout) is outside one millisecond to about 49.7 days.

### <a id="Tracon_TraconWorkflowFunctionExtensions_AddWorkflowFunction__3_Tracon_ITraconBuilder_System_String_System_Func_System_IServiceProvider_System_Func___0___2_Microsoft_Agents_AI_Workflows_IWorkflowContext_System_Threading_CancellationToken_System_Threading_Tasks_ValueTask___1____"></a> 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.

```csharp
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
```

#### Parameters

`builder` [ITraconBuilder](/api/tracon.itraconbuilder/)

The Tracon configuration chain.

`name` [string](https://learn.microsoft.com/dotnet/api/system.string)

The function's unique name. A [WorkflowDefinition](/api/tracon.workflowdefinition/) node of
kind [WorkflowNodeKind.Function](/api/tracon.workflownodekind/) points to it by this name.

`factory` [Func](https://learn.microsoft.com/dotnet/api/system.func\-2)<[IServiceProvider](https://learn.microsoft.com/dotnet/api/system.iserviceprovider), [Func](https://learn.microsoft.com/dotnet/api/system.func\-5)<TInput, TSettings, IWorkflowContext, [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken), [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<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.

#### Returns

 [ITraconBuilder](/api/tracon.itraconbuilder/)

The chain, for continued configuration.

#### Type Parameters

`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.

#### Remarks

A description for the console and a retry policy are set with the overload
that takes a [WorkflowFunctionOptions](/api/tracon.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`](/api/tracon.traconworkflowfunctionextensions/).

**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](/api/tracon.workflowresumerequest/#Tracon_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](https://learn.microsoft.com/dotnet/api/system.string).

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

```csharp
public sealed record TtsSettings
{
    public required string Voice { get; init; }

    public double LoudnessLufs { get; init; } = -16;

    public string? ApiKeyConfigurationKey { get; init; }
}
```

```csharp
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");
```

#### Exceptions

 [ArgumentNullException](https://learn.microsoft.com/dotnet/api/system.argumentnullexception)

One of the required parameters is [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null).

 [ArgumentException](https://learn.microsoft.com/dotnet/api/system.argumentexception)

`name` is empty.

 [TraconException](/api/tracon.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.

### <a id="Tracon_TraconWorkflowFunctionExtensions_AddWorkflowFunction__3_Tracon_ITraconBuilder_System_String_System_Func_System_IServiceProvider_System_Func___0___2_Microsoft_Agents_AI_Workflows_IWorkflowContext_System_Threading_CancellationToken_System_Threading_Tasks_ValueTask___1____System_Action_Tracon_WorkflowFunctionOptions__"></a> 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.

```csharp
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
```

#### Parameters

`builder` [ITraconBuilder](/api/tracon.itraconbuilder/)

The Tracon configuration chain.

`name` [string](https://learn.microsoft.com/dotnet/api/system.string)

The function's unique name.

`factory` [Func](https://learn.microsoft.com/dotnet/api/system.func\-2)<[IServiceProvider](https://learn.microsoft.com/dotnet/api/system.iserviceprovider), [Func](https://learn.microsoft.com/dotnet/api/system.func\-5)<TInput, TSettings, IWorkflowContext, [CancellationToken](https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken), [ValueTask](https://learn.microsoft.com/dotnet/api/system.threading.tasks.valuetask\-1)<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](https://learn.microsoft.com/dotnet/api/system.action\-1)<[WorkflowFunctionOptions](/api/tracon.workflowfunctionoptions/)\>

Sets the description, the retry policy, the timeout and the concurrency limit.

#### Returns

 [ITraconBuilder](/api/tracon.itraconbuilder/)

The chain, for continued configuration.

#### Type Parameters

`TInput` 

The type the function accepts.

`TOutput` 

The type the function returns.

`TSettings` 

The type of the node's settings.

#### Remarks

See [TraconWorkflowFunctionExtensions.AddWorkflowFunction`](/api/tracon.traconworkflowfunctionextensions/)
for how the settings are checked and what they must never hold.

```csharp
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),
               };
           });
```

#### Exceptions

 [ArgumentNullException](https://learn.microsoft.com/dotnet/api/system.argumentnullexception)

One of the required parameters is [null](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/null).

 [ArgumentException](https://learn.microsoft.com/dotnet/api/system.argumentexception)

`name` is empty.

 [ArgumentOutOfRangeException](https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception)

The retry policy has a negative [WorkflowNodeRetryPolicy.InitialDelay](/api/tracon.workflownoderetrypolicy/#Tracon_WorkflowNodeRetryPolicy_InitialDelay) or one
longer than about 49.7 days, or a negative or non-finite
[WorkflowNodeRetryPolicy.BackoffMultiplier](/api/tracon.workflownoderetrypolicy/#Tracon_WorkflowNodeRetryPolicy_BackoffMultiplier); or
[WorkflowFunctionOptions.Timeout](/api/tracon.workflowfunctionoptions/#Tracon_WorkflowFunctionOptions_Timeout) is outside one millisecond to about 49.7 days.

 [TraconException](/api/tracon.traconexception/)

`TSettings` is a type no settings could be read into.
