Orkestia
Blog
Core Concepts

Workflows

The execution model in depth. Workflow types versus runs, the event-sourced state machine, DAGs, how an assistant drives it over MCP, and compositions that compile into deterministic engine config

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), and dag (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.
Orkestia never holds your source code or your data. Workflows execute against your cloud accounts through runners, using connection credentials you granted and can revoke. The engine stores workflow state and, if you enable it, Lumen telemetry.

Types vs runs

Workflow typeWorkflow run
What it isA registered capabilityA single execution of a type
IdentityA dotted name, such as aws.s3.create_bucketA unique workflow_id
LifetimeInstalled in the catalogFrom start to a terminal state
CarriesTyped input and output schema, prerequisites, workflow_kind, featured, scopeInputs, outputs, full transition history
Discover withlist_workflow_types, get_workflow_schema, get_workflow_definition, get_workflow_dagget_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, or dag), featured (a human-runnable entry point; internal *.prepare and *.finalize sub-steps are not), and scope (organization, library, or none).

The catalog is the source of truth. Never invent a type name. Quote the exact name the engine returns.

discover a capability, as an assistant does it
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")
Every per-type detail (inputs, outputs, flags, prerequisites) lives in the live catalog at reference.orkestia.dev and through 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
Your organization scopes every run automatically. It is resolved server-side from your token and never passed by hand. End-user runs through App Enablement are scoped the same way, to the signed-in user.

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.

StateRoleNotes
PENDINGInitialRuns the work through auto_advance to execute
COMPLETEDTerminalSuccess, carries the output
FAILEDTerminalFailure, carries a structured reason

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.

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

OperationToolPurpose
Inspectget_workflow_statusCurrent state of a run
Replayget_workflow_historyFull append-only transition log (include_state_data=True for payloads)
Recoverretry_workflowRe-advance a FAILED run from its last good point
Unparkresolve_workflowAnswer a remediation gate with "remediated" or "denied"
Triagelist_stuck_workflowsFind runs that stalled before reaching a terminal state
Abandonforce_terminate_workflowAppend 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

PieceRole
LayerA stage. Layers run in order; steps within a layer can run together.
StepOne existing workflow type to invoke, placed in a layer.
Input mappingWhere 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>.

  1. Structure: layers are acyclic, step names are unique.
  2. References: every referenced type exists in the installed catalog.
  3. 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": []
}
The compilation substrate is alpha. The stable contract is the wire JSON, not the internal builder API. The catalog is read at compile time: if author and runtime environments differ, a composition can compile cleanly yet fail at dispatch with an "unknown workflow type". Pin library versions across both.

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

prompts
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

RuleDetail
Confirm the typeNever assume a type exists. Use list_workflow_types (with prefix or q) or get_workflow_schema.
Prefer featuredfeatured rows are human-runnable entry points. Ignore *.prepare and *.finalize sub-steps.
Reads are safedata kinds and names containing list, get, query, describe, status can be started directly. Confirm creates and mutations.
Prerequisites firstIf has_prerequisites is true, call get_workflow_prerequisites(type, variant) before start_workflow.
Virtual types are per orgSaved 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 properlyWorkflow type, workflow ID, final state, and the output fields that matter. See knowledge://mcp/execution.

Where this fits

DGI

AI reasoning that designs workflows and compiles them into compositions.

Runners

Where steps actually execute, in your own cloud accounts.

Lumen

Observability over every run's history.

Identity & multi-tenancy

How runs are scoped per organization and per end-user.

Next