- Core Concepts
- Workflows
Workflows
Ironflow builds Continuous History systems — record every event, durably execute workflows, derive projections, and time-travel through execution history. Workflows survive crashes, retries, and restarts automatically, and every step is permanently recorded.
Key Concepts
| Concept | Description |
|---|---|
| Function | A workflow definition that responds to events. Contains one or more steps. |
| Step | A recorded fact within a function. Steps are memoized—if a workflow restarts, completed steps aren’t re-executed. |
| Event | A permanent recorded fact that triggers a workflow (e.g., order.placed, user.signup). |
| Run | A single execution of a function, triggered by an event. |
How it works:
- You define functions with steps using the SDK
- Register functions with the Ironflow server (push or pull mode)
- Trigger events from your application
- Ironflow executes functions, recording each step result as a permanent fact — enabling replay, debugging, and history navigation
Real-time events: For subscribing to workflow events in real-time, see the Events & Pub/Sub guide.
Installation
The @ironflow/* npm packages are public and install without authentication. For building from source, see the Local Development guide.
npm install @ironflow/node# orpnpm add @ironflow/nodeThe Go SDK is publicly available via GitHub:
go get github.com/sahina/ironflow-go/ironflow@latestpip install ironflow-py, not ironflow
The bare name ironflow on PyPI belongs to an unrelated third-party project.
The distribution is ironflow-py; the import name stays ironflow. Available
from v0.33.0.
The Python SDK is client-only — it can trigger workflows and read their results, but it ships no worker runtime, so it cannot define the workflow this page walks through. Use the TypeScript or Go tab for that, and see the Python SDK reference.
Local Development
# Build first. `embed` matters: a plain `make build` produces a binary whose# `serve` exits "embedded dashboard missing (static/index.html not found)".make embed build
# Start Ironflow server./build/ironflow serve
# Dashboard: http://localhost:9123# API: http://localhost:9123/api/v1Register your function endpoint in the dashboard or via API, then trigger events to test.
Quick Start
Here’s a minimal workflow to get started:
import { ironflow } from "@ironflow/node";
export const helloWorld = ironflow.createFunction( { id: "hello-world", triggers: [{ event: "hello.triggered" }], }, async ({ event, step }) => { const message = await step.run("create-message", async () => { return `Hello, ${event.data.name}!`; });
return { message }; },);import "github.com/sahina/ironflow-go/ironflow"
var HelloWorld = ironflow.CreateFunction(ironflow.FunctionConfig{ ID: "hello-world", Triggers: []ironflow.Trigger{{Event: "hello.triggered"}},}, func(ctx ironflow.Context) (any, error) { var data struct { Name string `json:"name"` } if err := ctx.Event.Data(&data); err != nil { return nil, err }
message, err := ironflow.Run(ctx, "create-message", func() (string, error) { return fmt.Sprintf("Hello, %s!", data.Name), nil }) if err != nil { return nil, err }
return map[string]string{"message": message}, nil})Triggering Workflows
Workflows run when a matching event is sent. You can trigger events from your application code, the CLI, or the REST API.
import { createClient } from "@ironflow/node";
const client = createClient({ serverUrl: "http://localhost:9123" });
// Fire-and-forgetawait client.emit("hello.triggered", { name: "Alice" });
// Wait for every run the event triggersconst results = await client.emitSync("hello.triggered", { name: "Alice" });
// Or target one function by ID and wait for its single resultconst result = await client.invoke("hello-world", { data: { name: "Alice" } });client := ironflow.NewClient(ironflow.ClientConfig{ ServerURL: "http://localhost:9123",})
// Fire-and-forgetclient.Emit(ctx, "hello.triggered", map[string]any{"name": "Alice"})
// Wait for every run the event triggersresults, err := client.EmitSync(ctx, "hello.triggered", map[string]any{"name": "Alice"}, 30*time.Second)
// Or target one function by ID and wait for its single resultresult, err := client.InvokeSync(ctx, "hello-world", map[string]any{"name": "Alice"}, 30*time.Second)from protobuf.wkt import Structfrom ironflow import IronflowClient
client = IronflowClient(server_url="http://localhost:9123")
# Fire-and-forgetwith IronflowRPC(server_url=client.server_url, api_key=client.api_key) as rpc: res = rpc.events.emit(v1.TriggerRequest(event='hello.triggered', data=Struct.from_python({'name': 'Alice'})))print(res.event_id, res.run_ids)
# No emitSync on the REST client — TriggerSync is ConnectRPC-only. Poll instead:from ironflow import IronflowRPCfrom ironflow.rpc import v1
rpc = IronflowRPC(server_url=client.server_url, api_key=client.api_key)run = rpc.runs.get(v1.GetRunRequest(id=res.run_ids[0]))print(run.status)Python also exposes the asynchronous invocation RPC, which skips event matching but returns immediately with a run id to poll:
from ironflow import IronflowRPCfrom ironflow.rpc.v1 import InvokeFunctionRequestfrom protobuf.wkt import Struct
rpc = IronflowRPC()rpc.functions.invoke(InvokeFunctionRequest(function_id="hello-world", data=Struct.from_python({"name": "Alice"})))The blocking form is on the generated ConnectRPC client rather than the REST
one — IronflowRPC.runs.invoke_function_sync — matching Go’s InvokeSync and
the JavaScript invoke().
# Fire-and-forgetironflow emit hello.triggered --data '{"name": "Alice"}'
# Wait for the run to completeironflow emit hello.triggered --data '{"name": "Alice"}' --waitcurl -X POST http://localhost:9123/ironflow.v1.IronflowService/Emit \ -H "Content-Type: application/json" \ -d '{"event": "hello.triggered", "data": {"event": "Alice"}}'You can also invoke a function directly by ID, bypassing event matching. Every SDK has a blocking form — InvokeSync in Go, invoke() in Node and the browser, invoke_function_sync in Python — over the InvokeFunctionSync RPC; it returns exactly one result and raises on a failed run. For the non-blocking form see the REST API reference for POST /functions/{id}/invoke. The split between the two is recorded in ADR 0067.
The underlying gRPC IronflowService exposes the event-ingestion RPCs SDKs and tooling use directly: Trigger and Emit (fire-and-forget event publish), TriggerSync (publish and wait for every matched run to complete), InvokeFunctionSync (run one function by ID and wait for its single result), TriggerBatch (atomic batch publish), and PatchStep (scoped injection — overwrite a completed step’s output and resume). See the generated api/ironflow/v1 package on the public Go mirror for the full service definition.
For more details on event types, namespaces, and pattern matching, see the Events guide. SDK-specific options (idempotency keys, metadata, timeouts) are covered in the Node.js SDK, Browser SDK, and Go SDK references.
What’s Next?
- Defining Functions — Learn about function configuration and triggers
- Step Primitives — Understand run, sleep, sleepUntil, waitForEvent, parallel, map, compensate, invoke, invokeAsync, and publish
- Execution Modes — Push mode vs. Pull mode
- Error Handling — NonRetryableError and signature verification
- Sagas & Compensation — Automatically undo completed steps on failure
- Debugging — Hot patching, scoped injection, time-travel debugging, TUI debugger, VS Code DAP
- API Reference — REST API, Events API, WebSocket