Skip to content

NATS JetStream Streams

Ironflow keeps two stores side by side and they answer different questions. The database is the record: every row it holds survives for the life of the environment, and it is what an executor reconciles against after a crash. NATS is transport — five JetStream streams, each with a retention window tuned to its job, plus KV buckets that have no table behind them at all. For events emitted through the API and entity appends, the database transaction commits before the outbox publishes notifications. External NATS ingestion reverses that order: an event exists only in NATS until EventRouter consumes and stores it. Losing that message before storage loses the event unless the producer can replay it.

Ironflow DB and NATS ownership
StreamRetentionMax ageMax size (prod)Subject
EVENTSLimits7 days512 MBironflow.*.*.events.>
STEPSWorkQueue1 day512 MBironflow.*.*.steps.>
RESULTSLimits1 hour128 MBironflow.*.*.results.>
PUBSUBLimits7 days512 MBpublic.>
CANCELLimits1 hour64 MBironflow.*.*.cancel.>

The *.* wildcards match the {projectName}.{envName} segments. PUBSUB is the exception: it is keyed by the environment ID and carries no project segment (#1516).

Retention decides when a message disappears. Under LimitsPolicy a message stays until it ages out or the stream hits its size cap — reading it changes nothing, so two subscribers both see it. Under WorkQueuePolicy the message is deleted the moment it is acked, which is what makes a work queue a work queue.

STEPS is still created with the configuration above, but it no longer carries pull dispatch. Since ADR 0037 (#1206) capacity admission is the only dispatch path: runs are admitted to the dispatch_queue table and workers long-poll GET /api/v1/workers/{id}/jobs over HTTP. Nothing in the server publishes to or consumes from ironflow.*.*.steps.> today.

KV buckets are purely NATS-native (the JetStream KV API) with no corresponding DB tables. The SYS_ prefix is hidden from users; APP_ buckets are the ones that show up in the dashboard.

BucketHolds
SYS_cron_triggersCron dedup — the first kv.Create() for a slot wins
SYS_config_*Environment-scoped JSON config documents
SYS_secrets_*Encrypted environment-scoped secrets
SYS_debounce_statePending debounce arms, per function and key
SYS_policy_epochCEL policy cache epoch, per organization
SYS_agent_toolsAgent tool definitions registered by the SDK
APP_{bucketName}Arbitrary user key/value — one NATS KV bucket each

SYS_* buckets are keyed by the environment ID verbatim (SYS_secrets_env_default), unlike the ironflow.* subject family above (#2024).

events, runs, steps, functions, entity streams, consumer_groups and audit_events are all kept for the life of the environment. For API emits and entity appends, the event and its outbox rows commit together before publication. External ingestion instead calls CreateEvent after consuming from EVENTS, then creates matching runs. Cancellation also commits the terminal run state before publishing its best-effort signal.