Repository navigation
5.5.2.1.7. Output Pin
An output pin declares one type of object token leaving an Activity scope. Root-level pins publish each arriving token as a raw notification. Pins inside structured, iterative, or parallel Activities forward tokens to the containing node's outgoing object flows instead. An output pin does not terminate the Activity.
Connect a producer to OutputPin<TEvent> and register AddOutputPin<TEvent>() at the Activity root:
using Stateflows.Activities;
using Stateflows.Common;
using Stateflows.Entities.Enums;
.AddActivity("Totals", activity => activity
.AddInputPin<decimal>(pin => pin.AddFlow("Calculate"))
.AddAction("Calculate", context =>
{
context.Output(context.GetTokensOfType<decimal>().Sum());
return Task.CompletedTask;
}, node => node.AddFlow<decimal, OutputPin<decimal>>())
.AddOutputPin<decimal>(pin => pin
.SetPublishScope(PublishScope.Self)
.SetTimeToLive(300)
.SetRetain(true)
.SetImmediatePublish(false)))Root pins use Publish Event Action and OutputPinNode<TEvent> internally. That node implements IPublishEventActionNode<TEvent> and ITokenConsumer<TEvent>, and publishes every arriving token. Independent incoming flows do not implicitly join at the pin.
By default, an output pin is named OutputPin<TToken>.Name, so its typed target
marker works unchanged. Supply an optional globally unique name to distinguish
pins of the same type across scopes; target a custom-named pin with the string
flow overload instead of OutputPin<TToken>:
.AddAction("Calculate", async c => c.Output(125m),
node => node.AddFlow<decimal>("nested-total"))
.AddOutputPin<decimal>("nested-total")| Setting | Default | Meaning |
|---|---|---|
SetPublishScope |
PublishScope.Self |
Publish on behalf of self, parent, owner, or a combination of these flags. None disables publication; absent parent/owner scopes are skipped. |
SetTimeToLive |
0 |
No history or subscription retries unless retained; positive values bound notification lifetime in seconds. |
SetRetain |
false |
Keep only the latest retained notification of the declared type for requests and retries. |
SetImmediatePublish |
false |
Publish during execution rather than waiting for the transactional call to commit. Streamed/mixed firings already publish immediately. |
Publication uses the pin's declared event type. It does not synthesize separate notifications for payload subtypes, base classes, or interfaces. Notification attributes also apply. See notification retention for the complete TTL/retention policy.
await using var watcher = await activity.WatchOutputAsync<decimal>(
total => Console.WriteLine(total));
// Latest unexpired retained value; empty when retention is not enabled.
var latest = await activity.GetOutputAsync<decimal>();
// Available history plus the latest retained value.
var history = await activity.GetOutputAsync<decimal>(since);
// Named overloads expose EventHolder envelopes.
var envelopes = await activity.GetOutputAsync(
new[] { Event<decimal>.Name }, since);Each typed watch callback receives one raw payload, not a token batch. Register watches before sending input. Root pins do not maintain an output-token archive or contribute notification payloads to SendInputAsync response tokens. GetOutputAsync delegates to notification retrieval; its results follow notification retention and expiry, not Activity completion.
Default pins are live-only (Retain = false, TTL 0). Retained output can remain requestable after Activity finalization, subject to its TTL and notification store lifetime. A client watch is volatile and does not make output durable.
flowchart LR
subgraph nested["Structured Activity"]
calculate(Calculate total) -->|decimal| output["Output pin"]
end
output -->|decimal| parent(Record total)
Mermaid approximation: this diagram shows the model's routing, not a complete UML Activity notation. Rounded boxes represent actions; edge labels identify token types or guards. The labeled box represents a Stateflows boundary pin, not a UML final node. Mermaid does not model pins, activity parameters, or expansion regions natively.
Connect the inner producer to OutputPin<TToken> when the nested pin has its
default name, or use its custom string name. Declare
AddOutputPin<TToken>(name) in the nested definition, then connect the
containing structured node to a parent consumer:
.AddInitial(initial => initial.AddControlFlow("CalculateTotal"))
.AddStructuredActivity("CalculateTotal", nested => nested
.AddInitial(initial => initial.AddControlFlow("Calculate"))
.AddAction(
"Calculate",
async c => c.Output(125m),
node => node.AddFlow<decimal, OutputPin<decimal>>())
.AddOutputPin<decimal>()
.AddFlow<decimal>("Record"))
.AddAction("Record", async c =>
await ledger.RecordAsync(
c.GetTokensOfType<decimal>().Single(), c.CancellationToken))Declare a separate output pin for each supported output type. All nested output pins are collected, including results from iterative and parallel partitions. Exported boundary tokens are drained from the node buffer, so later inputs do not export them again. A declared typed pin may remain unconnected, but a connected flow must have a destination type assignable to the pin; transformations are validated using their result type. A typed-only structured scope's outgoing flows must also match its declared output pins.
Output pins are boundary connectors, not general-purpose nodes needed for flows within one Activity level. They preserve transactional/streamed provenance and work with checkpoint recovery. They do not introduce a new completion or durability guarantee. See Input Pin, Structured Activity Node, and Data Flow.
The Minimal APIs extension maps each root output pin to GET /stateflows/activities/{name}/{instance}/output/{tokenType}. It waits for the next raw notification by default, or watches over SSE with stream=true; it does not query stored output. SSE envelope payloads contain the raw token at payload. See Activity input and output HTTP endpoints.
Observers can inspect ICurrentNodeContext.OutputPins for a containing scope. The collection uses IPinContext with the declared TokenType, name, parent, and connected flows. Registration visitors receive OutputPinAddedAsync<TToken>. Existing node lifecycle callbacks continue to observe pin execution.
Root publishing pins execute as NodeType.PublishEventAction; nested output pins execute as NodeType.OutputPin. The publication builder callback is available only on root-level AddOutputPin<TEvent> declarations.
Home page Support Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. © by Mikołaj Milewski, 2026