Node types
Each node in a process definition has a type. The engine's behavior for executing the node, applying writes, and picking the next transition depends on the type.
Every node also accepts two documentation fields:
description— developer-facing notes for authors of the definition.human_description— a one- or two-sentence plain-language explanation shown to users watching the run. Fill this in — undescribed nodes read badly in observability.
tool
A deterministic step. Typically used to apply a fixed context_update (e.g. setting legal_decision: "auto_approved" when bypassing human review) or to invoke a pre-registered tool.
"auto_approve": {
"type": "tool",
"config": {
"context_update": { "legal_decision": "auto_approved" }
},
"writes": ["legal_decision"],
"transitions": [ { "to": "store_output" } ]
}
- Writes: either the literal
config.context_update, or derived fromnode.writesif set. Any non-empty context update requiresnode.writes. - Transitions:
auto-triggered by default; guards supported.
interaction
Invokes a Vertesia interaction by name. Use for deterministic single-shot LLM calls (summarization, extraction, classification) where the interaction already has a prompt and is registered in the project.
"classify_invoice": {
"type": "interaction",
"interaction": "ClassifyInvoice",
"input": { "doc_id": "{{invoice_doc_id}}" },
"writes": ["gl_code", "amount"],
"transitions": [ { "to": "approve" } ]
}
The engine forwards a derived result_schema built from node.writes ∩ process.context.schema.properties to the interaction execution, so the returned JSON lines up with the node's writes contract. The interaction's own schema is overridden for the duration of the call.
- Writes: declared in
node.writes; populated from the interaction's structured JSON output. - Transitions:
auto-triggered by default.
agent
A worker agent that may use tools. The engine builds a result_schema from node.writes (plus a _next_node enum if the node declares more than one agent-triggered transition) and asks the child conversation to return exactly that JSON. The agent can use any tool declared in node.tools; learn_* skills remain available.
"extract_terms": {
"type": "agent",
"prompt": "Extract key terms from the contract at {{contract_doc_id}}.",
"tools": ["fetch_document"],
"writes": ["parties", "term_length", "governing_law", "total_value", "auto_renewal"],
"transitions": [ { "to": "flag_clauses", "trigger": "agent" } ],
"human_description": "Reads the contract and extracts key terms into process context."
}
See Agent nodes for the full contract. Key rules:
- Agent nodes do not get process-control tools such as
set_context,transition_to,skip_node,continue_process,retry_node, orfail_process. Those are top-level orchestrator tools; worker agents produce structured output instead. - If a node declares writes but the child conversation returns no parseable schema-matching output, the engine fails the node rather than silently advancing.
process
Starts another process workflow and waits for it to complete. Use this for reusable subprocesses such as invoice review, document intake, or vendor onboarding. The child run gets its own process history, artifacts, gates, and human tasks.
"review_invoice": {
"type": "process",
"process": "65f000000000000000000000",
"input": {
"invoice": "{{invoice}}"
},
"returns": {
"from": "context.decision"
},
"writes": ["invoice_decision"],
"transitions": [ { "to": "route_invoice" } ]
}
Inline definitions are also supported:
"review_invoice": {
"type": "process",
"process_definition": {
"format_version": 1,
"process": "invoice_review",
"initial": "extract",
"context": {
"schema": { "type": "object", "additionalProperties": true },
"initial": {}
},
"nodes": {
"extract": { "type": "agent", "writes": ["decision"], "transitions": [ { "to": "done" } ] },
"done": { "type": "final" }
}
},
"input": { "invoice": "{{invoice}}" },
"writes": ["invoice_review"]
}
process— stored process definition id, built-insys:*process, or installed app process such asapp:contracts:contract_review.process_version— optional published version to pin whenprocessreferences a stored process history. Omit to use the latest head.process_definition— inlineProcessDefinitionBody; useful for local reusable building blocks. Pair withprocess: "tmp:<name>"only when you need a temporary id for the inline definition.run_type—programmaticby default;supervisedis allowed for child processes that need an orchestrator mode.input— merged over the child process definition's initial context.returns.from— path to read from the completed child state, commonlycontext.some_field.returns.context— list of child context fields to return when a singlefrompath is not enough.- Writes: declared in
node.writes; populated from the selected child output.
human_task
Pauses the process until a person submits an answer via the Task Inbox. The task definition describes what the reviewer sees.
"legal_review": {
"type": "human_task",
"task": {
"title": "Legal Review Required: {{parties}}",
"description": "Please review and submit your decision.",
"assignee": "group:legal",
"fields": [
{ "name": "legal_decision", "type": "select", "required": true, "options": ["approve", "reject", "request_edits"] },
{ "name": "legal_notes", "type": "text", "required": false }
]
},
"writes": ["legal_decision", "legal_notes"],
"transitions": [
{ "to": "store_output", "guard": { "==": [{ "var": "legal_decision" }, "approve"] } },
{ "to": "rejected", "guard": { "==": [{ "var": "legal_decision" }, "reject"] } },
{ "to": "flag_clauses", "guard": { "==": [{ "var": "legal_decision" }, "request_edits"] } }
]
}
Details:
task.titleandtask.descriptionsupport{{var}}placeholders that are expanded against the process context at task creation time and stored expanded on the task. A new task (new context) will render fresh values.assigneemust be eithergroup:<name>or a concrete user id.role:<name>is not supported.- When the task is submitted, each answer field is copied into context following
node.writes; then the guarded transitions pick the next node.
condition
Pure routing. Evaluates branches in order, picks the first whose when rule passes, or falls through to the branch marked default: true. Every condition node must declare either a matching when for all possible states or a default branch, otherwise runtime fails.
"risk_route": {
"type": "condition",
"branches": [
{ "to": "legal_review",
"when": {
"or": [
{ ">": [{ "var": "total_value" }, 50000] },
{ "==": [{ "var": "has_critical_flag" }, true] }
]
}
},
{ "to": "auto_approve", "default": true }
]
}
Guards and branch rules use JSON Logic with a Vertesia-specific extension: artifact_exists: { var: "some_path" } resolves to true if an artifact with that path exists on the run.
foreach
Runs a child node body once per item in a context array. The child can be a tool, interaction, agent, or process node.
"process_each_line": {
"type": "foreach",
"foreach": "invoice_lines",
"as": "line",
"node": {
"type": "interaction",
"interaction": "ClassifyLine",
"input": { "line": "{{line}}" },
"writes": ["gl_code", "amount"]
},
"collect": "classified_lines",
"failure_policy": "collect_errors",
"writes": ["classified_lines"]
}
foreach— context path to an array (max 1000 items).as— the variable name each item is exposed as inside the child node's context.item_id— optional template for a stable per-item id, e.g.{{invoice.id}}.max_concurrency— optional positive integer cap. If unset, all items are launched together.collect— context key to accumulate per-item results into.failure_policy—fail_fast(default) orcollect_errorsto keep the survivors and return error info for the failures.
For subprocess fanout, use node.type: "process":
"review_invoices": {
"type": "foreach",
"foreach": "invoices",
"as": "invoice",
"item_id": "{{invoice.id}}",
"max_concurrency": 25,
"node": {
"type": "process",
"process": "65f000000000000000000000",
"input": { "invoice": "{{invoice}}" },
"returns": { "from": "context.decision" }
},
"collect": {
"into": "invoice_results",
"include": ["status", "index", "item_id", "output", "error", "child_run_id"]
},
"failure_policy": "collect_errors",
"writes": ["invoice_results"]
}
String collect keeps the compact form. Object collect returns an envelope for each item. The common fields are status, index, item_id, item, output, context_update, error, child_run_id, child_workflow_id, and child_workflow_run_id.
The nested node is task-like leaf work only: use tool, interaction, agent, or process, and keep routing on the parent foreach node.
branch
Use branch for structured split/join semantics:
- a fixed named set of branches
- each branch usually modeled as a child subprocess or inline child process definition
- the engine launches all branches
- the parent waits for all of them, then continues
That is the clean mapping for BPMN structured parallel split/join. It is intentionally different from collection fanout:
foreachmeans "run the same child body once per item"branchmeans "run these named branches and join"
Each branch child is also task-like leaf work only: use tool, interaction, agent, or process, and keep routing on the parent branch node.
"review_tracks": {
"type": "branch",
"join": "all",
"branches": [
{
"id": "legal",
"title": "Legal Review",
"node": {
"type": "process",
"process": "legal_review_subprocess"
}
},
{
"id": "finance",
"title": "Finance Review",
"node": {
"type": "process",
"process": "finance_review_subprocess"
}
}
],
"collect": {
"into": "review_results",
"include": ["status", "branch_id", "branch_title", "output", "error"]
},
"writes": ["review_results"],
"transitions": [{ "to": "consolidate" }]
}
final
Terminal state. Entering a final node completes the process run.
"done": { "type": "final" },
"rejected": { "type": "final" }
Use multiple final nodes when you want the run's final status to carry meaning (e.g. done vs rejected), which is visible in the run graph.
