Skip to content
Ironflow is pre-1.0: APIs can change between releases. Pin a version. What is ready →

SDK Comparison

This page records what each Ironflow SDK can do — use it to pick an SDK, or to check whether a capability you need is reachable from the one you already have. For the exhaustive per-capability view — every transport, test and documentation status, and gap classification — see the SDK Parity Matrix.

The tables record capabilities, not transport. An SDK may reach a capability over ConnectRPC or a WebSocket instead of the equivalent REST path: the Go SDK resumes a run over ConnectRPC, the Browser SDK watches config over a WebSocket topic. ✅ means you can do it, ❌ means you cannot, and N/A means it is deliberately excluded — as secrets are from the browser. The Agents table reads by a narrower rule, stated above it.

SDKPackageTierWhat it is
Gogithub.com/sahina/ironflow-go/ironflow1Worker runtime + client, for servers and workers
Node@ironflow/node1Worker runtime + client, for servers, workers and handlers
Browser@ironflow/browser—Outside the tiers: hand-written like Tier 1 (it shares @ironflow/core with Node), client-only like Tier 2, plus real-time subscriptions
Pythonironflow (distribution ironflow-py on PyPI)1Worker runtime + generated client

The dividing line between the tiers is which direction the call goes.

YOUR APPLICATIONIRONFLOW SERVERCLIENTpublish, query, read streamsCheap · Generated · Every languageWORKER RUNTIMEruns your steps, memoizes, resumes after a crashExpensive · Hand-written · Go + TS + Pythonengineyou call itit calls you

Tier 1 — worker runtime + client (Go, Node, Python)

Section titled “Tier 1 — worker runtime + client (Go, Node, Python)”

Tier-1 SDKs carry the worker runtime: the engine that runs your function code under Ironflow’s control — durable-step memoization, crash-resume replay, step.sleep / waitForEvent suspension and pull mode (long-running workers). The worker runtime is hand-written; the client surface can be generated (Python’s is, per ADR 0062). The Durable Step Primitives table below lists what each Tier-1 SDK ships.

Go, TypeScript and Python are the Tier-1 languages.

The three Tier-1 SDKs are peers. A capability added to one is incomplete until the other two have it. The implementations do not need to land in the same commit or release. If one ships first, its peer gap must be filed before the release notes are published and those notes must link it. This also applies when a Tier-2 SDK reaches a capability before either Tier-1 SDK. The release may proceed, but the missing Tier-1 work cannot remain implicit.

scripts/check-tier1-sdk-parity.py enforces this mechanically for the public fields of SubscribeOptions, the option type that exposed the first concrete drift. An unmatched field fails unless tier1-sdk-parity.json names the missing SDK and links its implementation issue. The check also rejects an exception after the field reaches all three SDKs. The real-server tests/conformance/ suite checks shared worker behavior. This source check is intentionally narrow. sdk-coverage.json records which routes SDK source calls, not which capabilities or options the SDK exposes, so it is not evidence of Tier-1 capability parity. Broader parity remains a review rule until another concrete case supports a reliable source-level check.

Published blog posts, tutorials, and how-to guides follow the same peer model. When material demonstrates a Tier-1 capability with code, it includes Go, TypeScript and Python examples (or links to all three). Browser-only and Tier-2 capabilities are exempt. This is an editorial review rule rather than a code-fence-counting gate, because a document may also contain SQL, shell, frontend, or transport-specific examples. New material follows this rule from its first publication. Existing material is being backfilled in #2386.

Tier 2 — generated client only (Rust and Java later; C# not planned)

Section titled “Tier 2 — generated client only (Rust and Java later; C# not planned)”

Tier-2 SDKs call the Ironflow REST API and its ConnectRPC surface, and contain no worker runtime. Each is built in two parts: a generated surface, machine-generated from the server’s REST API so the client tracks it as the API changes, and a small hand-written transport core supplying retry, backoff, auth, and the error taxonomy.

A Tier-2 SDK is a smaller SDK, not a lesser one. Applications in Tier-2 languages still get their functions executed by Ironflow via push mode: the server POSTs to an HTTP endpoint you own, which needs no SDK runtime at all. What Tier 2 does not get is pull mode and durable steps — workflows that need crash-resume, long sleeps, or sagas are written in a Tier-1 language.

The same REST surface is published as an OpenAPI 3.1 spec at GET /api/v1/openapi.json, so a language with no SDK starts from a machine-readable contract rather than from scratch.

The server speaks two protocols to clients:

  • REST — plain HTTP paths under /api/v1, published with a machine-readable description and schemas for most of them.
  • ConnectRPC — a protobuf protocol. Paths look like POST /ironflow.v1.WebhookService/CreateWebhookSource. These sit outside the REST contract and carry no schema in it.

The REST client generator reads the REST description only, so the ConnectRPC surface needs a second pipeline (below). Tier-1 worker runtimes are hand-written and call whichever protocol fits. Run resume and step patch now use ConnectRPC in all SDKs. Their duplicate REST routes have been removed (#1972).

Ironflow serverREST APImachine-readable descriptionwith schemasConnectRPC methodsprotobuf definitionsare the schemaREST generatorfrom the route registryBuf + connect-pyfrom the protobufsIronflowClientgenerated REST methodsIronflowRPC102 capability methodsTwo pipelines, two clients. Each carries what its protocol serves.

The generated REST client covers the remaining REST routes, including environment CRUD, event reads, secrets, and projection deletion and partition listing.

The migrated capabilities are delivered by IronflowRPC — 102 capability methods generated from the protobuf definitions (98 unary, 4 server streams), covering webhook management, agent tools, time travel, pub/sub consumer groups, function versioning, executable deployments, raw SQL, and the environment compatibility RPCs. The two clients are not supersets of each other; each carries what its protocol serves.

One gap remains:

Still out of reachWhy
PubSubService/SubscribeBidirectionalGo and Node join consumer groups over it. Python has no client yet (#2973). The browser cannot stream both ways

IronflowRPC retries a unary call only when the protobuf method is annotated idempotency_level = NO_SIDE_EFFECTS. It reconnects Subscribe when the caller positions it with start_after_sequence, and no other stream — the rest are positioned by the server on a durable consumer, so re-issuing the call resumes them (#1848). Which of the 112 served RPCs the Python SDK exposes, and why, is recorded row by row in sdk/python/rpc-capabilities.yaml.

A language with no SDK is not locked out. It reaches almost everything in this document over plain HTTP — the REST API under /api/v1, plus the ironflow CLI and the MCP server (ironflow mcp) as no-codegen alternatives. But “generate a client from the OpenAPI spec and you are done” is not true, for the same reason the generated Python client needs two pipelines: the spec describes REST only.

Some ✅ rows below have no REST route at all. They are ConnectRPC, so a client generated from api/openapi.json will not contain them:

CapabilityConnectRPC methodWhat REST gives you
Register functionsIronflowService/RegisterFunctionNo REST sibling
Create projectionProjectionService/RegisterProjection, CreateSQLProjectionDeletion and partition listing, but no create
TriggerSync (blocking)IronflowService/TriggerSyncNo REST sibling
Invoke one function and blockIronflowService/InvokeFunctionSyncNo REST sibling. IronflowService/InvokeFunction returns IDs immediately; InvokeFunctionSync waits for the result (ADR 0067)
Webhook management (11 RPCs)WebhookService/*The redacted GET /api/v1/flow/webhooks view
Join a consumer groupPubSubService/JoinConsumerGroupNothing

The unary ones cost you a second path shape, not a second toolchain: a Connect unary call is an ordinary POST /ironflow.v1.Service/Method carrying proto3 JSON, which any HTTP client can send. What you give up is the schema — these sit outside the REST contract, and the field names are camelCase where REST is snake_case. Use Ironflow from any language walks the registration call end to end.

Registration being RPC-only has a knock-on effect: an OpenAPI-only client has nothing to invoke until something else registers a function, and no scheduled work either — a cron is a field on the function, so api/openapi.json contains no cron at all.

Three carve-outs, not two. Durable Step Primitives and agent authoring run inside a worker and have no equivalent outside one. The third is receiving pushed events, and it is not a worker feature: PubSubService/Subscribe and JoinConsumerGroup are server streams, SubscribeBidirectional is a bidirectional stream, and the OpenAPI artifact carries no streaming media type — no text/event-stream, no WebSocket upgrade. The GET /ws and KV/config /watch upgrades exist on the server and are documented in the REST API reference, but they are absent from the generated spec, so codegen never sees them. Inbound from generated code means push mode: an HTTP endpoint you own, which the engine calls.


CapabilityGo SDKNode SDKBrowser SDKPython SDK
Emit events✅✅✅✅
Trigger workflows✅✅✅✅
TriggerSync (blocking)✅✅✅✅
Trigger a batch of events✅✅✅✅
List stored events✅✅✅✅
Get a stored event✅✅✅✅
List event names✅✅✅✅
Offline write queue (durable outbox)N/AN/A✅N/A

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Define functions✅✅N/A✅
Register functions✅✅N/A✅
List functions✅✅✅✅
Get function detail✅✅✅✅
Enable or disable function✅✅✅✅
Delete function✅✅✅✅
List function version history✅✅✅✅
Get function at version✅✅✅✅
Roll back function version✅✅✅✅
Invoke function directly✅✅✅✅
Register executable deployment✅✅N/A✅
Read deployments and execution routing✅✅N/A✅

“Invoke function directly” is the client-level call, keyed by function id: InvokeSync in Go, invoke() in Node and the browser, rpc.functions.invoke or rpc.runs.invoke_function_sync in Python. All four block for a result except Python’s rpc.functions.invoke, which answers with the run and event IDs immediately — see ADR 0067. It is a different thing from step.invoke / step.invokeAsync, which invoke a function from inside a workflow and memoize — see Durable Step Primitives below.


These run inside a function handler, under Ironflow’s control. They are the worker runtime, so only Tier-1 SDKs have them.

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Memoized step (step.run)✅✅N/A✅
Sleep / sleep-until✅✅N/A✅
Durable wait for event✅✅N/A✅
Parallel branches✅✅N/A✅
Map over items✅✅N/A✅
Saga compensation✅✅N/A✅
Branch-scoped steps (nested fan-out, invoke, publish inside a branch)✅✅N/A✅
Invoke another function (sync / async)✅✅N/A✅
Publish to a topic from a step✅✅N/A✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Get run details✅✅✅✅
List runs✅✅✅✅
Get run steps✅✅✅✅
Get entity streams touched by a run✅✅✅✅
Cancel run✅✅✅✅
Resume run✅✅✅✅
Get audit trail (run events)✅✅✅✅
Delete run (one terminal run, or terminal runs by filter)✅✅❌✅
Redact a run, step or event payload✅✅❌✅

Scoped Injection (Hot Patching & History Editing)

Section titled “Scoped Injection (Hot Patching & History Editing)”
CapabilityGo SDKNode SDKBrowser SDKPython SDK
Pause run✅✅✅✅
Get paused state✅✅✅✅
Inject step output✅✅✅✅
Patch step (modify outputs)✅✅✅✅

The Python entry is rpc.runs.patch_step(PatchStepRequest(...)).


CapabilityGo SDKNode SDKBrowser SDKPython SDK
Get run state at timestamp✅✅✅✅
Get run timeline✅✅✅✅
Get step output at timestamp✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Append event to stream✅✅✅✅
Read stream events✅✅✅✅
Get stream info✅✅✅✅
Create snapshot✅✅✅✅
Get snapshot✅✅✅✅
List all streams✅✅✅✅
Get entity event history✅✅✅✅
Delete stream (tombstone, optional purge)✅✅❌✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
List projections✅✅✅✅
Get projection✅✅✅✅
Get projection status✅✅✅✅
List projection partitions✅✅N/A✅
Create projection✅✅N/A✅
Rebuild projection✅✅N/A✅
Get rebuild job status✅✅N/A✅
Create SQL projectionN/A✅N/A✅
Query SQL projectionN/A✅✅✅
Execute raw SQL✅N/A✅✅
List and read projection definition history✅✅✅✅
Pause projection✅✅N/A✅
Resume projection✅✅N/A✅
Cancel rebuild✅✅N/A✅
Delete projection✅✅N/A✅
Wait for catch-up (read-your-writes)✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Publish to topic✅✅✅✅
List topics✅✅N/A✅
Get topic statistics✅✅N/A✅
Join a consumer group (subscribe with acks)✅✅✅✅
Manage consumer groups (create/get/list/update/delete)✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Create bucket✅✅✅✅
List buckets✅✅✅✅
Get bucket✅✅✅✅
Delete bucket✅✅✅✅
Get key✅✅✅✅
Put key✅✅✅✅
Create key (fails if present)✅✅✅✅
Update key (compare-and-set)✅✅✅✅
Delete key✅✅✅✅
Purge key (drop history)✅✅✅✅
List keys✅✅✅✅
Watch keys✅✅✅✅

Exclusive create and compare-and-set both ride on conditional request headers. The generated Python kv_update_buckets_keys() method exposes them as if_none_match and if_match keyword arguments.


CapabilityGo SDKNode SDKBrowser SDKPython SDK
Create, list, get, update and delete buckets✅✅✅✅
Put, get, inspect, list and delete files✅✅✅✅
Move or copy a file, move a directory✅✅✅✅
Create signed upload and download URLs✅✅✅✅

See the Files guide.


CapabilityGo SDKNode SDKBrowser SDKPython SDK
Get config (read)✅✅✅✅
List configs✅✅✅✅
Set config (full replacement)✅✅N/A✅
Patch config (shallow merge)✅✅N/A✅
Delete config✅✅N/A✅
Watch config (real-time)✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Get secret✅✅N/A✅
Set secret✅✅N/A✅
Replace secret value✅✅N/A✅
Patch secret name or description✅✅N/A✅
Delete secret✅✅N/A✅
List secrets✅✅N/A✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Register schema✅✅✅✅
List schemas✅✅✅✅
Get schema (latest)✅✅✅✅
Get schema version✅✅✅✅
Delete schema version✅✅✅✅
Test upcast✅✅✅✅
List and read schema definition history✅✅✅✅

Authoring an agent is worker-runtime work, so it is Tier-1 only. The Browser SDK carries the client half — start a run, follow it, read its memory.

This table is the one place that records named SDK helpers rather than raw reachability. An agent’s memory is an ordinary entity stream behind an ordinary projection, and an agent run starts from an ordinary event, so any client that can append an event, read a projection, or emit can do those three things by hand. For the last two rows, ❌ means the SDK ships no method named for the agent use of them — not that the data is out of reach. The note under the table says the same thing about Go and Node.

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Define an agent handler✅✅N/A✅
Define a tool✅✅N/A✅
Human approval gate✅✅N/A✅
LLM call wrapper✅✅N/A✅
Spawn a sub-agent✅✅N/A✅
Expose tools over MCP✅✅N/A✅
Write agent memory✅✅❌✅
Read agent memory✅✅✅✅
Invoke an agent from a client❌❌✅❌
Subscribe to agent run events❌❌✅❌
List visible agent tools✅✅N/A✅

The last two rows are a client convenience the Browser SDK has and the others do not. From Go, Node, or Python you start an agent by emitting its trigger event, and follow it with an ordinary run subscription.


CapabilityGo SDKNode SDKBrowser SDKPython SDK
Create webhook source✅✅✅✅
Get webhook source✅✅✅✅
List webhook sources✅✅✅✅
Update webhook source✅✅✅✅
Delete webhook source✅✅✅✅
Rotate verify secret✅✅✅✅
Expire previous secret✅✅✅✅
Disable signature verification✅✅✅✅
Rotate ingest token✅✅✅✅
Test verify config❌❌❌✅
List webhook deliveries✅✅✅✅

Webhook mutation is ConnectRPC-only. Python reaches all of it through rpc.webhooks — create_source, get_source, rotate_secret and the rest — which is the capability IronflowRPC was added for. IronflowClient still sees only the redacted flow-map view (names, prefixes, delivery counts).

Test verify config is a Dashboard-facing diagnostic. Python wraps it because the generated facade covers every WebhookService method; Go, Node and Browser have no wrapper for it.


CapabilityGo SDKNode SDKBrowser SDKPython SDK
Create API key✅✅✅✅
List API keys✅✅✅✅
Get API key✅✅✅✅
Delete API key✅✅✅✅
Rotate API key✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Create org✅✅✅✅
List orgs✅✅✅✅
Get org✅✅✅✅
Update org✅✅✅✅
Delete org✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Create role✅✅✅✅
List roles✅✅✅✅
Get role✅✅✅✅
Update role✅✅✅✅
Delete role✅✅✅✅
Get the Rego policy set✅✅✅✅
Replace the policy set (with If-Match)✅✅✅✅
Validate a policy set✅✅✅✅
Dry-run a policy set✅✅✅✅
List policy-set versions✅✅✅✅
Get one policy-set version✅✅✅✅
Roll back the policy set✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Manage projects✅✅N/A✅
Manage environments✅✅N/A✅
Manage users✅✅N/A✅
Change own password✅✅N/A✅
List workers✅✅✅✅
List tenants✅✅✅✅
Provision tenant✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
List / requeue / discard outbox dead lettersN/AN/AN/A✅
List circuit breakers, reset a breakerN/AN/AN/A✅
List / clear debounce entries✅✅N/A✅
Inspect capacity (lanes, queue, leases, credits, sessions)✅✅N/A✅
Rotate the cluster join tokenN/AN/AN/A✅
Read the org-wide audit log✅✅✅✅

The generated Python client remains widest here because its methods come from the whole REST surface. Go and Node also expose debounce entries and the capacity views. Go, Node, and Browser expose the environment-wide audit log. For the other operational routes, use the ironflow CLI or call REST directly.

Platform-tier administration — platform keys, tenant provisioning, impersonation — is a separate surface; see the Platform API reference.


CapabilityGo SDKNode SDKBrowser SDKPython SDK
Check liveness✅✅✅✅
Check readiness✅✅✅✅
Discover server capabilities✅✅✅✅

CapabilityGo SDKNode SDKBrowser SDKPython SDK
Replace an API key’s rolesN/AN/AN/A✅
Read authentication and authorization auditN/AN/AN/A✅

These are intentionally outside the curated Go, Node, and Browser clients. They are not license-gated: the generated Python client reaches them because it covers the full REST and mounted ConnectRPC admin surface. Operators can also use the dashboard, CLI where available, or the underlying endpoint.


FeatureGo SDKNode SDKBrowser SDKPython SDK
Push mode (serverless functions)✅✅N/A✅
Pull mode (long-running workers)✅✅N/A✅
Subscriptions (real-time events)✅✅✅✅
WebSocket support✅✅✅❌
ConnectRPC support✅✅✅✅
HTTP/REST support✅✅✅✅

Push mode ✅ for Python is ironflow.serve: serve() returns an ASGI app, handle() is the framework-free core, and register() registers functions as push. See Push mode.

Python workers support both polling (Worker) and ConnectRPC streaming (StreamingWorker).

Python real-time subscriptions use the tested IronflowRPC.pubsub.subscribe() ConnectRPC server stream. Python has no WebSocket subscription client. Its only WebSocket use is the KV and config watch (watch_kv, watch_config). It also has no agents.subscribe() convenience for following an agent run. Those are the narrower gaps recorded in those rows.

Go, Node and Python are peers, but four differences between them are intended. ADR 0065 records each one.

  • Subscribe transport. Go has WebSocket and ConnectRPC clients. Node has a WebSocket client. Python has the generated ConnectRPC stream. All three can subscribe.
  • Python client surface. Python reaches entity streams, subscriptions, consumer groups, SQL projections and webhook management through the generated IronflowRPC methods. It has no hand-written helpers for them. Go and Node have a subscribeEntityStream helper that builds the entity:{type}.{id}.> pattern; Python callers build that pattern themselves.
  • SQL. Go runs raw SQL and cannot create or query a SQL projection. Node creates and queries SQL projections and cannot run raw SQL. The browser SDK does both. Python does both.
  • Worker error hook. Only the Go worker has one (WorkerConfig.OnError).

  • Building high-performance backend services
  • Implementing complex state machines or sagas
  • Integrating with existing Go applications
  • Need the broadest coverage of the workflow, event-sourcing and pub/sub surface
  • Building serverless functions (AWS Lambda, Vercel)
  • Using JavaScript/TypeScript throughout your stack
  • Need full control over step execution and error handling
  • Prefer working in TypeScript for type safety
  • Building real-time frontends with live event subscriptions
  • Implementing interactive dashboards or monitoring tools
  • Need lightweight client-side integrations
  • Want to display projection data and KV store values in UI
  • Running durable AI agents and long-lived pull workers
  • Building data pipelines or scripts that interact with Ironflow
  • Integrating Ironflow with Python-based AI/ML workflows
  • Running operational tasks — it is the only SDK that reaches the outbox DLQ and circuit breakers. Go, Node and Python all reach the capacity views and debounce entries
  • Want to manage Ironflow resources from Jupyter notebooks or CLI tools

Python also runs pull workers with durable steps. See the Python SDK reference.


Features are implemented incrementally, and these tables reflect the current state of each SDK.

The Python SDK in this codebase is Tier 1, with a hand-written polling worker and a push handler (ironflow.serve), plus generated client methods. The ironflow-py distribution (import name ironflow) first appeared on PyPI at v0.33.1; that release was client-only. See the Python SDK reference for the current surface.