Agent nodes
An agent node delegates an open-ended sub-task to a child conversation workflow that can call tools. The parent process still owns control flow: the agent is a worker, not an orchestrator. This page covers the exact contract between the engine and the agent so you can author agent nodes that actually behave.
The contract, in one sentence
Given a prompt, a result schema, and a tool set, produce a single JSON object matching the schema. The engine does the rest.
The agent does not pick the next node via tool calls, does not write context via tool calls, and does not decide its own termination. It produces a value.
What the engine provides to the agent
When the engine dispatches an agent node, it injects the following into the child conversation:
- A system/user prompt (
sys:ProcessAgentNodeinteraction by default) containing:- The process name and description.
- The node id, the node's
prompt, and the node's declared writes. - The current process context (serialized).
- The available agent-triggered transitions (if any).
- A human-readable description of the result schema.
- A derived
result_schema— a JSON Schema built fromnode.writesfiltered throughprocess.context.schema.properties. If the node has more than one agent-triggered transition, the schema also requires a_next_nodefield whose enum is the set of declared targets. - The declared tools from
node.tools, plus anylearn_*skills. Tools declared at node level are added to whatever the default tool set contains.
Agents do not receive process-control tools such as set_context, transition_to, skip_node, continue_process, retry_node, or fail_process — those belong to the higher-level supervised orchestrator and would be rejected by the result schema anyway.
What the agent must return
A single JSON object that matches the result schema. Llumiverse drivers honour result_schema via native structured output when available, and the conversation workflow returns that structured block in its final output.
For a node with writes: ["parties", "total_value"] and a single agent transition flag_clauses, the schema is roughly:
{
"type": "object",
"properties": {
"parties": { "type": "string" },
"total_value": { "type": "number" }
},
"required": ["parties", "total_value"],
"additionalProperties": false
}
And a valid final response:
{ "parties": "ACCOR SA and Vertesia SAS", "total_value": 97500 }
The engine auto-transitions to flag_clauses because the node has exactly one agent-triggered transition.
For a node with two agent-triggered transitions, the schema additionally requires _next_node:
{
"type": "object",
"properties": {
"classification": { "type": "string" },
"_next_node": { "type": "string", "enum": ["human_review", "auto_publish"] }
},
"required": ["classification", "_next_node"],
"additionalProperties": false
}
The _next_node value picks the transition.
What happens on the parent side
- The parent reads the child conversation's final output and looks for a structured block (
type: "json"from the driver, or a JSON-parseable text block as a fallback). - It splits the result into
{ _next_node, ...writes }. - It applies
writesthrough the validator — rejecting anything outsidenode.writesand anything that violates the context schema. - It picks the transition:
- If
_next_nodeis present, that target must be a declared agent transition (or the engine errors). - Otherwise, if there's exactly one agent-triggered transition, it's auto-picked.
- Otherwise, there's no target and the node fails.
- If
- It checkpoints context to Mongo and moves on.
Failure behavior
The engine fails the node rather than auto-advancing when:
- A schema was declared but the child returned no parseable structured output matching it.
- The parsed
_next_nodeis not in the declared enum. - The returned writes violate
process.context.schemaor reference fields outsidenode.writes.
A failing node leaves the run in failed status with the node history entry marked completed up to the point of exit (so you can inspect what the child conversation actually did in the Conversation tab).
Tools and skills
Declare tools the agent needs in node.tools:
"extract_terms": {
"type": "agent",
"tools": ["fetch_document"],
"writes": ["parties", "term_length"],
...
}
Skills (learn_*) are available by default so the agent can self-unlock capabilities. If you want a leaner tool set, explicit names without a +/- prefix replace the default; names prefixed with + are added to it, - removes.
Artifact isolation
Each agent-node child conversation is launched with launch_id = "<nodeId>-<attempt>". That maps to a per-node artifact namespace under agents/<processRunId>/workstreams/<launch_id>/... so sibling nodes never overwrite each other's tools.json, conversation.json, or generated artifacts.
Authoring tips
- Always fill in
human_descriptionso the observability view can tell a human reader what the node does. - Keep
node.writestight — the agent only sees and emits these fields, which both reduces hallucination surface and makes validation strict. - Prefer single-transition nodes; push branching into dedicated
conditionnodes. Multi-transition agent nodes are harder to reason about and require the agent to decide routing. - Reference context fields in
node.promptusing{{field}}— the engine expands inputs against the context before dispatch. Missing fields throw at template time, so only reference fields guaranteed to exist.
