Skip to main content

Flows Overview

Flows are reactive data processing pipelines that execute when telemetry arrives from assets. They transform inputs, perform computations, and emit outputs in real time.

Two Ways to Write Flows

You can write flows in two ways:

  • BtScript — A concise Scheme-based DSL designed for reactive dataflow. Best for transformations, aggregations, and event-driven logic.
  • C# — Direct .NET implementation with full IDE support and type safety. Best for complex business logic or .NET library integration.

Both approaches compile to .NET assemblies and run identically as FlowGrains (Orleans virtual actors). You can mix BtScript and C# flows in the same project.

Quick Comparison

BtScript — concise, reactive syntax:

(flow id: temp-conversion
(inputs (celsius "sensor.temperature"))
(trigger on-any: celsius)
(let ((fahrenheit (+ (* celsius 1.8) 32)))
(emit fahrenheit-output value: fahrenheit)))

C# — full .NET, type-safe:

[Flow("btf:temp-conversion;1.0.0")]
public class TempConversionFlow : IFlow
{
public async Task<FlowResult<object?>> Execute(
IFlowContext ctx, IFlowState state, CancellationToken ct = default)
{
var celsius = state.GetInput<double>("celsius")!;
var fahrenheit = (celsius * 9.0 / 5.0) + 32;
await ctx.EmitAsync("fahrenheit-output", fahrenheit);
return FlowResult.Ok<object?>(null);
}
// ... metadata omitted for brevity
}

Three Artifact Types

Beacon Tower defines three artifact types, each identified by a BTI (Beacon Tower Identifier) with the format prefix:name;semver:

Artifact TypeBTI PrefixDescriptionC# Interface
Flowbtf:Reactive data processing pipeline. Binds to asset telemetry and emits outputs.IFlow
Taskbtft:Reusable, stateful computation. Maintains state across invocations.IFlowTask
Functionbtff:Stateless, pure computation. No side effects.IFlowFunction

Example BTIs:

btf:pump-monitor;1.0.0       # Flow
btft:rolling-std;1.2.3 # Task
btff:clamp;0.1.0 # Function

Flows are triggered by telemetry. Tasks and functions are called by flows during execution.

Input Data

Flows receive telemetry from assets via Orleans streams. The telemetry originates as NATS messages with a flat JSON payload where each key is a signal name:

{"motor_temp": 85.5, "inlet_pressure": 101.3}

Signal names in the payload must match the asset's model definitions. The IngressProcessor splits multi-signal messages into atoms and routes each signal to the appropriate TelemetryGrain, which then forwards to subscribed FlowGrains.

Full details: See Telemetry Ingestion for the complete message format, headers, and validation rules.

How Flows Run

Source (.bts or .cs)

Flow Service (Roslyn compilation)

Artifact Store (PostgreSQL)

FlowGrain (Orleans virtual actor)

Executes on telemetry events
  1. Upload — Source code (.bts or .cs) is uploaded to the Flow Service
  2. Compile — Flow Service compiles to a .NET assembly using Roslyn
  3. Store — Assembly and metadata stored in PostgreSQL
  4. Instantiate — A FlowGrain loads the assembly and subscribes to input signals
  5. Execute — When inputs arrive, the flow runs and emits outputs

Scaffolding

Create new artifacts from templates:

# BtScript (default)
flowctl new flow my-flow
flowctl new task my-task
flowctl new function my-function

# C# scaffold
flowctl new flow my-flow --cs

Next Steps