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 Type | BTI Prefix | Description | C# Interface |
|---|---|---|---|
| Flow | btf: | Reactive data processing pipeline. Binds to asset telemetry and emits outputs. | IFlow |
| Task | btft: | Reusable, stateful computation. Maintains state across invocations. | IFlowTask |
| Function | btff: | 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
- Upload — Source code (
.btsor.cs) is uploaded to the Flow Service - Compile — Flow Service compiles to a .NET assembly using Roslyn
- Store — Assembly and metadata stored in PostgreSQL
- Instantiate — A FlowGrain loads the assembly and subscribes to input signals
- 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
- BtScript Overview — Learn the BtScript DSL for writing flows
- BtScript Language Reference — Full syntax and built-in functions
- Writing C# Flows — Write flows, tasks, and functions in C#
- Compilation & Runtime — Pipeline architecture details
- Persistence & State — State management strategies
- Local Flow Development — Set up your dev environment