Fork/Join
Split work into parallel branches, join when complete
Fork/Join
Fork transitions split work into parallel branches. Each branch runs independently through its own state sub-graph. When all branches complete, the join advances the parent to the next state.
Defining a Fork
Add a fork object to any transition event:
{
"planning": {
"on": {
"FORK": {
"fork": {
"branches": {
"lint": { "initial": "implementing", "terminal": "branch-done" },
"test": { "initial": "implementing", "terminal": "branch-done" },
"docs": { "initial": "implementing", "terminal": "branch-done" }
},
"join": "all",
"on_complete": "review",
"on_fail": "failed"
}
}
}
}
}Branch Definition
Each branch specifies:
| Field | Description |
|---|---|
initial | State the branch starts in (must exist in the workflow) |
terminal | Final state that signals branch completion |
Branches share the workflow's state definitions. The initial state's allowed_tools, model, instructions, and thinking_level all apply to the branch.
Join Strategies
| Strategy | Behavior |
|---|---|
all | Wait for every branch to complete (default) |
any | First branch to complete triggers the join |
How Agents Execute Forks
Sequential (Claude Code, Codex, OMX)
The agent works branches one at a time:
statewright_transition(event="FORK")— creates branch sessionsstatewright_load_workflow(branch="lint")— enters the lint branch- Work through the branch states
statewright_transition(event="BRANCH_DONE:lint")— signal completion- Repeat for next branch
- After all branches: gateway auto-joins to
on_complete
Parallel (Pi)
Pi's statewright_fork tool spawns parallel subprocesses:
- Model calls
statewright_forkwith branch tasks - Plugin fires the FORK transition
- One Pi subprocess per branch, each with its own gateway session
- Branches work simultaneously on local or API models
- Parent fires
BRANCH_DONE:namefor each completed branch - Gateway auto-joins to
on_complete
BRANCH_DONE Event Format
Always include the branch name after a colon:
statewright_transition(event="BRANCH_DONE:lint", data={"rationale": "lint passed"})Per-Branch Model Routing
In Pi, branches inherit the initial state's model routing:
"implementing": {
"model": "ollama-qwen/qwen3.6:35b",
"thinking_level": "off",
"allowed_tools": ["Read", "Edit", "Write", "Bash"]
}Each branch subprocess switches to this model automatically.
Complete Example
Three-branch bugfix workflow: plan with a frontier model, fix three independent bugs in parallel on local models, run integration tests:
{
"id": "parallel-bugfix",
"initial": "planning",
"meta": { "default_model": "openai-codex/gpt-5.4" },
"states": {
"planning": {
"model": "openai-codex/gpt-5.5",
"thinking_level": "xhigh",
"allowed_tools": ["Read", "Grep", "Glob", "Bash"],
"instructions": "Read all files, identify bugs, plan fixes. Then trigger FORK.",
"on": {
"FORK": {
"fork": {
"branches": {
"fix-auth": { "initial": "implementing", "terminal": "done" },
"fix-parser": { "initial": "implementing", "terminal": "done" },
"fix-tests": { "initial": "implementing", "terminal": "done" }
},
"join": "all",
"on_complete": "integration",
"on_fail": "failed"
}
}
}
},
"implementing": {
"model": "ollama-qwen/qwen3.6:35b",
"allowed_tools": ["Read", "Edit", "Write", "Bash"],
"instructions": "Fix the assigned bug. Run relevant tests.",
"on": { "DONE": "done", "FAIL": "failed" }
},
"done": { "type": "final" },
"integration": {
"model": "ollama/gemma4:31b",
"allowed_tools": ["Read", "Bash"],
"instructions": "Run full test suite. All tests must pass.",
"on": { "PASS": "completed", "FAIL": "failed" }
},
"completed": { "type": "final" },
"failed": { "type": "final" }
}
}