Concepts overview
Sutra is organized around a few load-bearing ideas. This page is the map; deeper chapters will expand each one.
The message is the contract
A start event binds to a channel and a message type. The engine decodes the real wire format and validates it against a schema before the process runs, so malformed input becomes a routable soft error, not an exception deep in the flow.
Codec = format × schema
A codec pairs a format (how bytes are structured) with a schema (what a valid message
looks like). Decoding and validation are one step. One schema can declare several message types,
and dispatch fans out one process per type. Six formats are built in — JSON, XML, YAML, CSV, raw
text, raw bytes; a schema-bound codec comes either from the package itself (XSDs under
schemas/<name>/, compiled at deploy time) or from an extension crate implementing the codec
SPI, which is how a message standard (an industry wire format with its own envelope grammar and
schema editions) is served.
Channels and transports
Processes are triggered by messages arriving on channels — HTTP, five brokers (Kafka,
RabbitMQ, AWS SQS, Google Pub/Sub, AMQP 1.0), and an air-gapped file transport. <q:source>
binds a start event to a channel + message type. Co-deployed processes hand off over an
in-process local:// channel.
Wait-states and correlation
Wait states (userTask, intermediate message catch) suspend → persist → rehydrate → resume.
A later message is correlated back to the parked instance by a business key you name
(<q:alias> — e.g. an EndToEndId or an order reference), not an engine id, and it's durable and
replica-coherent on PostgreSQL.
Durable state beyond one instance
Wait-state data belongs to one instance. State that outlives an instance, or is shared between
them, lives in a data store — declared in the package (datastores.yaml),
read and written from the flow through <q:store>, and transactional, with optimistic
concurrency (expect="unchanged") and pessimistic locking (forUpdate="true") where a
read-modify-write needs them. Each store owns its own connection: the engine's database is
never the module's. A store is key/value by default and holds a value of any shape; a flat
record may additionally declare its structure: and project onto real typed columns in a table
you own, so the SQL tooling you already point at that database reads it directly.
Rules — DMN, .srl, and FEEL
A businessRuleTask binds a .dmn decision table or a .srl ruleset (a Drools-inspired
rule / when / then DSL). Both compile onto one shared FEEL evaluator — no JVM, no Rete
runtime.
The q: vocabulary
Layered BPMN extensions keep diagrams standard while removing boilerplate: <q:dispatch> /
<q:case> (a routing table instead of gateway sprawl), <q:validators>, <q:alias>,
<q:reply>, <q:variables>, <q:audit>, and <q:coverage>.
Content-addressed deployment
The deploy unit is one sealed .sutra archive; runtime identity is a single opaque
deploymentId = sha256(manifest). Deploys are idempotent, hot-reload flips activation without a
restart, and secrets never live in the archive — channels reference them by scheme
(secret: / env: / vault: / aws-secrets: / …), resolved at runtime through one
vendor-neutral SPI.
Multi-tenancy and observability
Per-channel tenant binding, single-database PostgreSQL with per-deployment row-level security across PG / MySQL / MariaDB / MSSQL, OTLP traces / metrics / logs to any collector, plus KEDA autoscaling and leader election.