Workflows
TL;DR
- A type is a capability; a run is one execution. Types have dotted names and typed schemas. Runs have a
workflow_id, a state, and a history. - Three kinds of type:
state_machine(atomic,PENDING → COMPLETED | FAILED),data(read-only lookups, safe to start), anddag(multi-step, layers of steps). - The engine is event-sourced. A run is an append-only log of transitions, so history is complete and recovery is deterministic.
- Compositions are your logic. Chain existing types with explicit input mapping, validate against the live catalog, compile to a normal DAG. No code.
- The same loop everywhere. Discover, schema, prerequisites, start, watch, recover, whether from the console, an SDK, or an assistant over MCP.
Types vs runs
| Workflow type | Workflow run | |
|---|---|---|
| What it is | A registered capability | A single execution of a type |
| Identity | A dotted name, such as aws.s3.create_bucket | A unique workflow_id |
| Lifetime | Installed in the catalog | From start to a terminal state |
| Carries | Typed input and output schema, prerequisites, workflow_kind, featured, scope | Inputs, outputs, full transition history |
| Discover with | list_workflow_types, get_workflow_schema, get_workflow_definition, get_workflow_dag | get_workflow_status, watch_workflow, get_workflow_history, list_workflows |
Types: registered capabilities
A type is a named, typed capability the platform knows how to run. Atomic types are named {provider}.{service}.{operation} and grouped into namespaces you can browse. Each type declares:
- A typed input and output schema, validated before a run starts and before a result is accepted.
- Prerequisites: what must exist first, most often a cloud connection. When a schema reports
has_prerequisites: true, fetch the setup guide before starting. - Metadata an assistant uses to choose well:
workflow_kind(state_machine,data, ordag),featured(a human-runnable entry point; internal*.prepareand*.finalizesub-steps are not), andscope(organization,library, ornone).
The catalog is the source of truth. Never invent a type name. Quote the exact name the engine returns.
list_workflow_namespaces()
list_workflow_types(prefix="aws.s3.")
get_workflow_schema("aws.s3.create_bucket") → fields, read_only, has_prerequisites
get_workflow_prerequisites("aws.s3.create_bucket", variant="aws")
get_workflow_schema. This page describes capabilities at the model level.Runs: executions with a workflow_id
A run is one execution of a type. You start it with inputs, get back a workflow_id, then watch it to a terminal state, inspect its history, or retry it if it failed.
start_workflow("aws.s3.create_bucket", { "bucket": "my-bucket", "connection_uuid": "…" })
→ { "workflow_id": "wf_8f3c…", "status": "PENDING" }
watch_workflow("wf_8f3c…") → blocks until COMPLETED or FAILED
get_workflow_history("wf_8f3c…") → full append-only transition log
The event-sourced state machine
Every run, atomic or composed, executes on one event-sourced state machine. A run is not a row mutated in place. It is the replay of an append-only sequence of transitions. Current state is derived from history. Two properties follow:
Full history
Every state change, input, and output is preserved. You can reconstruct exactly how a run reached its outcome.
Deterministic recovery
State is rebuilt from events, so a run resumes or retries from a known point instead of restarting blindly.
The 3-state pattern
Atomic workflows follow one predictable shape. A run enters PENDING, auto-advances to execute its single operation, and lands in exactly one terminal state.
| State | Role | Notes |
|---|---|---|
PENDING | Initial | Runs the work through auto_advance to execute |
COMPLETED | Terminal | Success, carries the output |
FAILED | Terminal | Failure, carries a structured reason |
- [*]
- PENDING
- COMPLETED
- FAILED
- [*]
- [*]→PENDING
- PENDING→ execute succeeds →COMPLETED
- PENDING→ execute raises →FAILED
- COMPLETED→[*]
- FAILED→[*]
This uniformity is the point. Because every primitive obeys the same contract, the console, SDKs, and AI assistants treat every capability identically: discover it, start it, watch it to one of two terminal states.
DAGs: multi-step workflows
Real processes are many steps with data threaded between them. A DAG workflow arranges atomic steps in layers: layers run in order; steps within a layer can run together. Each step still obeys the 3-state contract, so a DAG is a composition of primitives, not a different runtime. Ask get_workflow_dag(type) for the layer and step structure.
- aws.s3.create_bucket
- aws.s3.put_bucket_versioning
- aws.s3.put_bucket_policy
- aws.s3.create_bucket→aws.s3.put_bucket_versioning
- aws.s3.create_bucket→aws.s3.put_bucket_policy
A DAG step that fails on a fixable precondition can park in remediation_pending instead of compensating. The run's state_data.remediation envelope names the fix. Apply it, then call resolve_workflow(workflow_id, "remediated") and the engine re-runs only the failed step. Resolve "denied" to compensate and fail.
Middleware, plugins, and RBAC
Every transition runs through a middleware pipeline with a plugin system, so validation, authorization, and observability live in one place. RBAC is enforced in the engine through declared capability metadata. Permissions are checked the same way for every run, whether it started from the console, an SDK, the API, or an assistant over MCP. There is no back door.
Concurrency
To keep a single run's transitions serialized, the engine takes a per-run lock. No two workers can advance the same run into conflicting states. Concurrency safety is by construction.
Async
Work that takes time never blocks your call. The engine publishes a workflow.transition message; a dedicated consumer picks it up and advances the run out of band. You start a run and watch it. There is no queue to provision.
Retries, recovery, and stuck runs
| Operation | Tool | Purpose |
|---|---|---|
| Inspect | get_workflow_status | Current state of a run |
| Replay | get_workflow_history | Full append-only transition log (include_state_data=True for payloads) |
| Recover | retry_workflow | Re-advance a FAILED run from its last good point |
| Unpark | resolve_workflow | Answer a remediation gate with "remediated" or "denied" |
| Triage | list_stuck_workflows | Find runs that stalled before reaching a terminal state |
| Abandon | force_terminate_workflow | Append a failed terminal state to a confirmed-stale run, with a reason and guards |
The MCP prompt template diagnose_workflow_run walks exactly this sequence for a given workflow_id.
Compositions: virtual workflows
The platform ships thousands of capabilities, but your business logic is the order you run them in and the data you thread between them. Compositions (virtual workflows) express that by chaining existing workflows, with no code.
You declare which workflows run, in what order, with which inputs. The platform validates that against the live catalog and compiles it into a regular engine DAG. From then on a composition runs, is watched, and is retried like any other run.
The three building blocks
| Piece | Role |
|---|---|
| Layer | A stage. Layers run in order; steps within a layer can run together. |
| Step | One existing workflow type to invoke, placed in a layer. |
| Input mapping | Where each step argument's value comes from. |
Input mapping
Every step input is explicit about its source. There is no implicit global scope and no runtime string templating. Three sources:
input
The composition's own input.
step
A prior step's output field.
static
A literal fixed at authoring time.
Because mappings are explicit, the whole composition is type-checked before it runs. Errors are precise (which step, which reason) rather than runtime surprises.
Validate, compile, stamp, run
Authoring is a deterministic pipeline driven by the composition.save workflow: validate the definition, compile it to a DAG, stamp the compiled cache, and the type becomes runnable as virtual.<uuid>@<version>.
- Definition: layers + steps + mappings
- Validate: structure
- Validate: references
- Validate: compatibility
- Compile to DAG
- Stamp: content_hash + format version
- Run on the engine
- Definition: layers + steps + mappings→Validate: structure
- Validate: structure→Validate: references
- Validate: references→Validate: compatibility
- Validate: compatibility→Compile to DAG
- Compile to DAG→Stamp: content_hash + format version
- Stamp: content_hash + format version→Run on the engine
- Validate: structure→ errors[] →Definition: layers + steps + mappings
- Validate: references→ errors[] →Definition: layers + steps + mappings
- Structure: layers are acyclic, step names are unique.
- References: every referenced type exists in the installed catalog.
- Compatibility: every input mapping resolves to a compatible source field and type.
Authoring shape
You author the definition. Each step points at a real workflow type and declares an input_mapping from each parameter to its source (input, step, or static):
{
"name": "vw_demo",
"version": "1.0",
"layers": [
{ "name": "layer_1", "steps": [
{ "name": "s1", "workflow_type": "aws.s3.create_bucket",
"input_mapping": { "bucket": { "source": "input", "field": "bucket_name" } } }
] },
{ "name": "layer_2", "steps": [
{ "name": "s2", "workflow_type": "aws.s3.put_bucket_tagging",
"input_mapping": {
"bucket": { "source": "step", "step": "s1", "field": "bucket_name" },
"tagging": { "source": "static", "value": { "TagSet": [{ "Key": "env", "Value": "demo" }] } }
} }
] }
]
}
The compiled form is a server-owned cache: the per-step input_mapping becomes input, a step source becomes state_data, the workflow is marked type: "dag", and a metadata block with content_hash and compiled_format_version is stamped on. The definition stays the durable source of truth; the engine can always recompile from it.
Validation returns two lists of plain strings, precise enough to feed straight back into an authoring loop:
{
"errors": [
"step 's2': unknown workflow type 'aws.s3.put_bucket_taging'; did you mean 'aws.s3.put_bucket_tagging'?"
],
"warnings": []
}
Why this is how apps express logic
A composition is just another workflow type, which means it can be exposed to your app's end-users through App Enablement. A signed-in user invokes it; Orkestia runs it scoped to that user. Your app's business logic lives on the platform with no backend of your own and no database credentials in your code.
Ask your AI assistant
List the workflow types under "aws.s3." with their workflow_kind, and tell me which ones are featured entry points.
Show me the DAG structure of <dag_workflow_type> as layers and steps.
Read concept://dag, then design a composition that creates a bucket, enables versioning, and applies a tag. Validate it but do not save it.
Workflow <workflow_id> failed. Read its history with state data and tell me the failing step and the reason.
For AI agents
| Rule | Detail |
|---|---|
| Confirm the type | Never assume a type exists. Use list_workflow_types (with prefix or q) or get_workflow_schema. |
| Prefer featured | featured rows are human-runnable entry points. Ignore *.prepare and *.finalize sub-steps. |
| Reads are safe | data kinds and names containing list, get, query, describe, status can be started directly. Confirm creates and mutations. |
| Prerequisites first | If has_prerequisites is true, call get_workflow_prerequisites(type, variant) before start_workflow. |
| Virtual types are per org | Saved compositions run as virtual.<uuid>@<version> and do not appear in list_workflow_types. An empty prefix="virtual." browse does not mean the user has none. Use audit.workflow-run.query to see what ran. |
| Report properly | Workflow type, workflow ID, final state, and the output fields that matter. See knowledge://mcp/execution. |
Where this fits
Next
- Build a composition step by step: Virtual workflows guide.
- Let AI design one: Building with DGI.
- Drive capabilities from an assistant: Connect an AI assistant and MCP integration.
- The complete catalog: reference.orkestia.dev and the workflow types registry.
Core Concepts
The mental model behind Orkestia, workflows, MCP, DGI, Staff, agents, Agent Exchange, runners, Lumen, identity, billing, App Data, App Host, Engram, and DevKit, with a reading order and a map from each concept to its MCP namespaces and tools
DGI — Dialog Generative Interface
How Orkestia turns a natural-language goal into a typed, executable workflow plan, how that plan compiles into a reusable deterministic composition, and how DGI relates to assistants over MCP
