Repository navigation
5.5.2.1.6. Input Pin
An input pin exposes one declared token type arriving at an Activity boundary to nodes inside that graph. At the top level, tokens arrive through token-input sends/requests, including IActivityBehavior.SendInputAsync and Orleans StreamInputAsync; inside a structured node, they arrive from the containing graph. It has no incoming edge in its own scope. Declare each supported type with AddInputPin<TToken>(pin => pin.AddFlow(target)); the flow inherits its token type from the pin.
flowchart LR
parent(Parent Activity) -->|Order| input["Input pin"]
subgraph nested["Structured Activity"]
input --> calculate(Calculate total)
end
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 UML activity-parameter notation. Mermaid does not model pins, activity parameters, or expansion regions natively.
Streaming does not bypass this boundary or inject tokens into arbitrary actions. StreamInputAsync returns after volatile admission and then routes tokens through the same declared flows as ordinary input. While streaming is active, token-input requests (SendInputAsync/RequestAsync) are rejected; use sends and output watches/queries. A standalone ActivityCheckpoint contains no input tokens and does not activate input pins. See Activity Streaming.
.AddInitial(initial => initial.AddControlFlow("LoadOrder"))
.AddAction(
"LoadOrder",
async c => c.Output(await orders.LoadAsync(
orderId, c.CancellationToken)),
node => node.AddFlow<Order>("CalculateTotal"))
.AddStructuredActivity("CalculateTotal", nested => nested
.AddInputPin<Order>(pin => pin.AddFlow("Calculate"))
.AddAction("Calculate", async c =>
{
var order = c.GetTokensOfType<Order>().Single();
c.Output(order.Lines.Sum(line => line.Price * line.Quantity));
}, node => node.AddFlow<decimal, OutputPin<decimal>>())
.AddOutputPin<decimal>())The nested input declaration does not create an application token; it forwards matching parent inputs along its object flows. Use AddInitial separately when the nested graph also needs independent initial control work. For nested results, connect an inner producer to OutputPin<TToken> and declare AddOutputPin<TToken>(). See Structured Activity Node, Output Pin, and Data Flow.
Declare distinct pins for distinct supported types:
.AddInputPin<Order>(pin => pin.AddFlow("Calculate"))
.AddInputPin<PriceUpdate>(pin => pin.AddFlow("UpdatePrices"))Typed input pin names are generated as
{parentName}.{InputPin<TToken>.Name} (the Activity name at the root and the
containing node name in a nested scope). Thus the same token type can be
declared at different scopes without a name collision.
Each compatible pin receives the matching part of the input batch. A pin with no matching input does not execute. Compatibility uses the actual payload type, including base classes and interfaces; a typed null uses its holder's declared type. Overlapping pin declarations deliberately fan out to every compatible pin, rather than selecting one most-specific pin.
Every nonempty token-input batch must match declared input pins. The entire batch is rejected if any token has no compatible pin, even if other tokens match; a graph with no input pins rejects every nonempty batch. Top-level ordinary input returns Rejected before implicit initialization. Orleans streamed input returns Rejected before admission or cold initialization, not Accepted followed by silent token loss.
Structural Activity boundaries are typed contracts: incoming object flows require matching input pins, and outgoing object flows require matching output pins. Effect Activities invoked with AddEffectActivity receive the transition event as an input token, so declare a matching AddInputPin<TEvent> even if an Initial node starts the work and the pin has no outgoing flow. For example, .AddInputPin<StepSucceeded>(_ => { }) declares the required boundary type without routing it. These are supported-type contracts, not a guarantee that every admitted token eventually produces output: guards, joins, and flow weights still apply.
Pin flows support the usual guards, transformations, and typed flow implementations through IInputPinBuilder<TToken> and the shared typed decision-flow API. A transformation can change the destination token type without changing the pin's declared input type.
The Minimal APIs extension maps each root input pin to POST /stateflows/activities/{name}/{instance}/input/{tokenType} with a {"tokens":[...]} payload batch. sendAsStream=true opts into Orleans streaming admission; ordinary input remains the default. See Activity input and output HTTP endpoints.
ICurrentNodeContext.InputPins and .OutputPins describe the typed boundaries inside that node's scope, not every pin in the Activity. Each IPinContext exposes TokenType, Name, NodeType, parent lookup, and connected flows. Ordinary leaf actions and boundary nodes have no nested pins. Inspect execution contexts while their invocation is active.
Visitors receive InputPinAddedAsync<TToken> and OutputPinAddedAsync<TToken>, including the containing node name (null at the root). Observers use the existing node lifecycle callbacks. Input pins are NodeType.InputPin; nested output pins are NodeType.OutputPin, while root output pins are NodeType.PublishEventAction.
Declare one AddInputPin<TToken> per supported type to enable input validation; multiple pins of the same declared type and direction are rejected.
Home page Support Code licensed under an MIT-style License. Documentation licensed under CC BY 4.0. © by Mikołaj Milewski, 2026