Orchestrations
DAG-based workflow definitions for chaining agents, tools, and knowledge lookups into repeatable pipelines.
Overview
Orchestrations let you describe a directed acyclic graph (DAG) of nodes where each node performs a discrete operation. Nodes in the same execution round run in parallel; edges with activation groups control fan-in convergence.
Use orchestrations when you know the exact steps in advance and want deterministic, auditable execution — not when you need an LLM to decide which steps to take. An agent node inside an orchestration can still use LLM reasoning internally, but the orchestration graph itself is deterministic.
See the Permissions Reference for the IAM action strings for this module.
Related Tutorials
- Orchestrate a Sonnet - Step 6 (Create the orchestration graph)
- Orchestrate a Sonnet - Step 7 (Start a run)
- Orchestrate a Sonnet - Step 9 (Inspect the run state)
Data Model
Orchestration
| Field | Type | Description |
|---|---|---|
id | string | Public ID (orch_ prefix) |
project_id | string | Owning project |
name | string | Human-readable name |
description | string | null | Optional description |
nodes | array | Ordered list of node definitions |
edges | array | Directed connections between nodes |
state_schema | object | Optional JSON Schema describing the run state |
input_schema | object | Optional JSON Schema describing the run input |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last-updated timestamp |
OrchestrationRun
| Field | Type | Description |
|---|---|---|
id | string | Public ID (run_ prefix) |
orchestration_id | string | Parent orchestration |
status | string | running | paused | completed | failed | cancelled |
state | object | Current mutable execution state |
active_nodes | array | Node IDs awaiting input (populated when status is paused) |
artifacts | object | Outputs keyed by node ID |
error | object | null | Error details if failed |
current_node_id | string | null | Most recently executed node ID |
required_action | object | null | Present when status is paused (see Human Nodes) |
created_at | string | ISO 8601 creation timestamp |
updated_at | string | ISO 8601 last-updated timestamp |
Key Concepts
Node Types
| Type | Description |
|---|---|
agent | Invokes a SOAT Agent with a prompt. Uses agentId and prompt. |
tool | Calls a SOAT Tool. Uses toolId and inputMapping. |
transform | Evaluates a JSON Logic rule against the current state. Uses expression. |
knowledge | Searches a knowledge source via the Knowledge module. Uses inputMapping with query and optional memoryIds. |
memory_write | Writes a Memory entry. Uses memoryId and inputMapping with content. |
condition | Evaluates a JSON Logic rule and emits a string label. Downstream edges use condition: "<label>" to select the active branch. |
human | Pauses the run and waits for external input. The run enters paused status with requiredAction. |
State and Mappings
Each node can define:
inputMapping— Maps state paths (JSONPath-like$.key) to node inputs before execution.outputMapping— Maps node outputs back to state paths after execution.
The root state is available to every node. Transforms and conditions receive the full state object.
Parallel Execution
All nodes that become active in the same round execute concurrently via Promise.all. After all complete, their outputs and state mutations are applied sequentially to avoid races. A single node with multiple outgoing edges activates all targets in parallel.
Activation Groups (Fan-In)
Edges can carry an activation_group name and an activation_condition to control when a downstream node runs:
activation_condition | Behaviour |
|---|---|
all (default) | The target node activates only after every edge in the group comes from a completed node. |
any | The target node activates as soon as any edge in the group comes from a completed node. Activated at most once per run. |
Edges without an activation_group always pass through unconditionally.
Cycle Detection
Before execution begins, the engine performs a DFS-based cycle check. If a cycle is detected the run is created, set to failed, and the error field contains code: "ORCHESTRATION_CYCLE_DETECTED".
Human Nodes
When a human node is reached, the run pauses and the GET run response includes a required_action object:
{
"required_action": {
"type": "human_input",
"node_id": "approval",
"prompt": "Please approve or reject."
}
}
Examples
Create a sequential pipeline
- CLI
- SDK
- curl
soat create-orchestration \
--project-id "$PROJECT_ID" \
--name "fetch-and-summarize" \
--nodes '[
{"id":"fetch","type":"tool","tool_id":"tool_abc","output_mapping":{"result":"state.raw"}},
{"id":"summarise","type":"agent","agent_id":"agt_xyz","input_mapping":{"prompt":"state.raw"},"output_mapping":{"content":"state.summary"}}
]' \
--edges '[{"from":"fetch","to":"summarise"}]'
import { SoatClient } from '@soat/sdk';
const soat = new SoatClient({ baseUrl: 'https://api.example.com', token: 'sk_...' });
const { data, error } = await soat.orchestrations.createOrchestration({
body: {
project_id: 'proj_ABC',
name: 'fetch-and-summarize',
nodes: [
{
id: 'fetch',
type: 'tool',
tool_id: 'tool_abc',
output_mapping: { result: 'state.raw' },
},
{
id: 'summarise',
type: 'agent',
agent_id: 'agt_xyz',
input_mapping: { prompt: 'state.raw' },
output_mapping: { content: 'state.summary' },
},
],
edges: [{ from: 'fetch', to: 'summarise' }],
},
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/orchestrations \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"project_id": "proj_ABC",
"name": "fetch-and-summarize",
"nodes": [
{
"id": "fetch",
"type": "tool",
"tool_id": "tool_abc",
"output_mapping": {"result": "state.raw"}
},
{
"id": "summarise",
"type": "agent",
"agent_id": "agt_xyz",
"input_mapping": {"prompt": "state.raw"},
"output_mapping": {"content": "state.summary"}
}
],
"edges": [{"from": "fetch", "to": "summarise"}]
}'
Start a run
- CLI
- SDK
- curl
soat create-orchestration-run \
--orchestration-id orch_01 \
--input '{"query": "summarize Q1 revenue"}'
const { data, error } = await soat.orchestrations.createOrchestrationRun({
path: { orchestration_id: 'orch_01' },
body: { input: { query: 'summarize Q1 revenue' } },
});
if (error) throw new Error(JSON.stringify(error));
curl -X POST https://api.example.com/api/v1/orchestrations/orch_01/runs \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{"input": {"query": "summarize Q1 revenue"}}'
Parallel fan-out
Both branch_a and branch_b run concurrently after start completes:
{
"nodes": [
{ "id": "start", "type": "transform", "expression": { "var": "query" } },
{ "id": "branch_a", "type": "agent", "agent_id": "agt_a", "output_mapping": { "content": "state.a" } },
{ "id": "branch_b", "type": "agent", "agent_id": "agt_b", "output_mapping": { "content": "state.b" } }
],
"edges": [
{ "from": "start", "to": "branch_a" },
{ "from": "start", "to": "branch_b" }
]
}
Fan-in with activation_condition: all
merge runs only after both branches complete:
{
"edges": [
{ "from": "branch_a", "to": "merge", "activation_group": "join", "activation_condition": "all" },
{ "from": "branch_b", "to": "merge", "activation_group": "join", "activation_condition": "all" }
]
}
Condition-based routing
{
"nodes": [
{
"id": "check",
"type": "condition",
"expression": { "if": [{ ">": [{ "var": "score" }, 0.8] }, "high", "low"] }
},
{ "id": "high_path", "type": "agent", "agent_id": "agt_high" },
{ "id": "low_path", "type": "agent", "agent_id": "agt_low" }
],
"edges": [
{ "from": "check", "to": "high_path", "condition": "high" },
{ "from": "check", "to": "low_path", "condition": "low" }
]
}