Hand-offs and routing
Different steps in a workflow can run on different agents. This page covers how steps pass results to each other, and how each step chooses its harness (runtime) and model.
Hand-offs: declare outputs, don't pass chat history
An upstream node declares stable results in outputs. A downstream node lists them in inputs, and can reference only outputs that its direct upstream nodes declare.
{
"key": "understand",
"type": "ai",
"name": "Understand and plan",
"config": { "prompt": "Write a plan to deliverables/plan.md" },
"outputs": [
{ "key": "plan", "kind": "file", "path": "deliverables/plan.md", "label": "Plan", "required": true }
]
}
{
"key": "implement",
"type": "ai",
"name": "Implement",
"dependsOn": ["understand"],
"inputs": [
{ "key": "plan", "fromNode": "understand", "outputKey": "plan", "label": "Approved plan", "required": true }
],
"config": { "prompt": "Implement and self-test from the plan" },
"outputs": [ { "key": "change", "kind": "git", "label": "Commit", "required": true } ]
}
At run time, SoduWork builds a bounded snapshot for each hand-off. It records the input key, source node, output key, checksum and whether anything was truncated. Agents don't share terminal history, and old conversations are not passed whole to the next model.
Output kinds
kind | Contains | Checked by |
|---|---|---|
file | A file at an exact path | File name and presence |
git | A commit | For engineering work, deliver Git, not an archive |
evidence | A check result, screenshot or other proof | |
url | An address where the result can be opened | |
decision | A structured verdict | UTF-8 JSON; at most one per node |
Decision files
An ai_review node writes a decision file. Use a .json path for new plugins.
{
"schemaVersion": 1,
"outcome": "proceed",
"reasonCode": "ready",
"summary": "Checked each item against the source material",
"evidence": ["Source record, paragraph 2, matches notes section 3"],
"nextAction": ""
}
When a check fails or can't be completed, the review must return "outcome": "blocked", a reasonCode other than ready, and a concrete nextAction. A blocked review stops the run.
- An
ai_reviewof files must declare a requireddecision. - A code review also needs a fixed Git version and
code-review.json.
Neither kind of AI review replaces a later human approval.
Routing: which harness and model runs each step
A plugin can suggest a route in a node's config:
"config": {
"prompt": "Review the plan independently",
"agentKind": "local:qwen",
"model": "deepseek-chat",
"fallbackMode": "strict",
"runnerTags": ["gpu"]
}
| Field | Notes |
|---|---|
agentKind | local:<harness id> for a local harness, for example local:pi or local:qwen. Empty means the organization default |
model | The model name sent to the gateway. Empty means the harness's default |
fallbackMode | strict: use exactly this harness. best-effort: pick a compliant alternative if it's unavailable |
runnerTags | Run only on runners with these tags (up to 12) |
It's usually better to leave the concrete choice to the binding. Then the same plugin can use Codex in one company, Pi in another and OpenCode on an isolated network, without copies.
- The binding sets the default harness, model, unavailable policy and runner tags for the whole workflow.
- Each
aiandai_reviewnode can override them. - Before a real start, a node-by-node simulation shows "expected route → runner actually online".
- Every run records the expected route, the harness, model and host actually used, and any fallback reason.
Don't use the fields agent, agentId, adapterId or runtime. They are rejected.
Common patterns
- Build with one model, review with another. Give
aiandai_reviewnodes different routes so a review is never self-review. - Cheap model for bulk steps, strong model for the key step. Use a small model to extract and sort; use a strong one to judge or review.
- Keep sensitive data local. Pin the step that handles identities to a local harness and model; send only de-identified outputs to a cloud model.