Skip to content
Proud to collaborate with Microsoft for Startups

Compositions ​

A composition (also called a virtual workflow) is a workflow you assemble from catalog workflows — saved, versioned, and runnable like any other type, without writing or deploying code.

You describe what runs, in which order, and where each input comes from. Orkestia validates the definition against the live catalog, compiles it, and registers it as:

virtual.<composition-uuid>@<version>

From that point you can invoke it (API / MCP / SDK), share it with your app's end-users, and they use it with their Sign in with Orkestia JWT.

Not to be confused with the composition.* workflows in the catalog — those manage compositions (composition.save, composition.validate, composition.activate, …). The composition itself is data: a JSON definition, not Python.

How-to for app builders: docs.orkestia.dev — Compositions. Catalog ops: Composition domain.

The definition format ​

A composition is a JSON document with ordered layers. Layers run in sequence. Steps inside one layer can run together. Each step names a catalog workflow_type and an input_mapping object — keys are the step's input parameter names.

The live serializer dialect (composition.validate / composition.save) is:

input_mapping: { <param>: { source: "input"|"step"|"static", field_name?, step?, value? } }
json
{
  "name": "provision-and-notify",
  "description": "Create a bucket, then tell the team",
  "layers": [
    {
      "name": "provision",
      "steps": [
        {
          "name": "create-bucket",
          "workflow_type": "aws.s3.create_bucket",
          "input_mapping": {
            "bucket_name": { "source": "input", "field_name": "bucket_name" },
            "region": { "source": "static", "value": "us-east-1" }
          }
        }
      ]
    },
    {
      "name": "notify",
      "steps": [
        {
          "name": "announce",
          "workflow_type": "slack.message.send",
          "input_mapping": {
            "connection_uuid": { "source": "input", "field_name": "slack_connection_uuid" },
            "channel": { "source": "static", "value": "#deploys" },
            "text": { "source": "step", "step": "create-bucket", "field_name": "bucket_name" }
          }
        }
      ]
    }
  ]
}

Do not use the legacy input_mappings array with a target field. composition.validate accepts input_mapping and only warns on leftover per-step inputs.

Input mapping sources ​

sourceMeaningExtra fields
inputRead from the composition's own top-level inputfield_name (required)
stepRead from a previous step's outputstep (step name) + field_name
staticA hardcoded value baked into the definitionvalue

step mappings may only read steps in earlier layers. static values are invisible to whoever invokes the composition — that is how you hide connections, table names, and tenant columns when you share the composition with an app user.

Validate, save, invoke ​

Do not pass organization_uuid. The server fills it from your token.

text
composition.validate  →  composition.save  →  start virtual.<uuid>@<version>
WorkflowWhat it does
composition.validateThree static phases, no persist. Input: { definition }
composition.saveValidate + compile + persist. Input: { name, definition }. Returns composition_uuid, workflow_type (virtual.<uuid>@N), version
composition.versionAppend a new immutable version on the same lineage
composition.activateRe-validate against the live registry and set active
composition.planRead-only diff of desired definitions vs saved state
composition.archive / composition.rename / composition.delete-archivedLifecycle

Invoke as an org member (API) ​

bash
# 1. Save
curl -X POST https://workflow-api.orkestia.dev/api/workflows/start \
  -H "Authorization: Bearer $ORKESTIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow_type": "composition.save",
    "initial_data": {
      "name": "provision-and-notify",
      "definition": { }
    }
  }'

# 2. Run the compiled type from the save output
curl -X POST https://workflow-api.orkestia.dev/api/workflows/start \
  -H "Authorization: Bearer $ORKESTIA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "workflow_type": "virtual.6f2c9a41-0000-0000-0000-000000000000@1",
    "initial_data": { "bucket_name": "my-new-bucket" }
  }'

Invoke over MCP ​

text
whoami()
start_workflow("composition.save", { "name": "…", "definition": { … } })
watch_workflow(workflow_id)
# from terminal output: composition_uuid, workflow_type, version
start_workflow("virtual.<composition_uuid>@1", { /* free inputs */ })

MCP clients: MCP client setup.

Invoke with the Node SDK ​

ts
import { LtIntegWorkflowsClient } from "@ltinteg/workflows-sdk"

const client = new LtIntegWorkflowsClient({
  baseUrl: "https://workflow-api.orkestia.dev",
  token: process.env.ORKESTIA_TOKEN,
})

const saved = await client.start("composition.save", {
  name: "provision-and-notify",
  definition: { /* JSON above */ },
})
const out = await saved.wait()
// out.state_data.workflow_type === "virtual.<uuid>@1"

const run = await client.start(out.state_data.workflow_type, {
  bucket_name: "my-new-bucket",
})

Every save creates a new immutable version — virtual.<uuid>@1, @2, …. The uuid is the lineage id and stays stable. Runs always pin an explicit version.

Share with an app user ​

End-users of your app cannot start raw catalog workflows. They can start only virtual types you expose.

  1. Provision the app (identity.app.provision) and wire @orkestia/auth — see App Enablement.
  2. Author the composition so every step is end_user_eligible. Lock secrets as static mappings. Leave only free inputs as source: "input".
  3. Expose that exact version:
text
identity.app.expose-virtual-workflow({
  identity_app_uuid: "<from provision>",
  composition_uuid: "<from composition.save>",
  version: 1
})

Pass only those three fields. Do not pass organization_uuid or actor.

  1. The signed-in user starts virtual.<uuid>@1 with their JWT. Orkestia injects the end-user principal immutably.
ts
import { createOrkestiaAuth } from "@orkestia/auth"
import { LtIntegWorkflowsClient } from "@ltinteg/workflows-sdk"

const auth = createOrkestiaAuth({ clientKey: "orkestia_…" })
const session = auth.getSession()
const client = new LtIntegWorkflowsClient({
  baseUrl: "https://workflow-api.orkestia.dev",
  token: session.token,
})
const run = await client.start("virtual.<composition_uuid>@1", {
  /* free inputs only */
})

Revoke with identity.app.unexpose-virtual-workflow (identity_app_uuid, composition_uuid, optional version).

By default every authorized end-user of the app can call an exposed composition. To restrict it, grant vw:<composition-uuid>:invoke to end-user access groups. Grants use the version-agnostic uuid, so they survive re-publishes.

If expose fails, read ineligible_steps — every step must be end_user_eligible in the live catalog.

Enable a user to use it ​

Give your app user this path (or paste the agent prompt on docs):

  1. They sign in with Orkestia (@orkestia/auth).
  2. Your UI calls virtual.<uuid>@<version> with their session token.
  3. They supply only free inputs. They never see the connection, the table, or another user's rows.
App user  →  Sign in with Orkestia  →  JWT
     →  start virtual.<uuid>@N  (Bearer JWT)
     →  engine injects end-user  →  only their data

Ways to author ​

You rarely write the JSON by hand:

  • Visual builder — Orkestia console DAG builder.
  • DGI — describe the outcome in chat; review and save.
  • TypeScript SDK — defineComposition / .vw.ts.
  • DevKit — ltinteg-devkit vw validate|plan|import.
  • API / MCP — composition.save with the JSON on this page.

Composition patterns ​

  • Chaining — one step per layer; each reads the previous step (source: "step").
  • Fan-out — several steps in one layer, same input, different static/input values.
  • Fan-in — a later layer with one step reading several earlier steps.

Prefer several focused compositions over one sprawling DAG — each stays independently versioned, testable with composition.plan, and separately exposable to end-user groups.