Workflow Schema Reference
Every field in the Statewright workflow definition schema
Workflow Schema Reference
Workflow definitions are JSON documents conforming to the schema at https://statewright.ai/workflow-schema.json.
Validate your definitions with $schema:
{
"$schema": "https://statewright.ai/workflow-schema.json",
"id": "my-workflow",
"initial": "planning",
"states": { ... }
}Top-Level Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique workflow identifier |
initial | string | Yes | Name of the starting state |
states | object | Yes | State definitions keyed by state name |
context | object | No | Initial context values for guard evaluation |
guards | object | No | Named guard predicates for conditional transitions |
meta | object | No | Machine metadata for the agent layer |
Minimal Example
{
"id": "review",
"initial": "reading",
"states": {
"reading": {
"allowed_tools": ["Read", "Grep"],
"on": { "DONE": "complete" }
},
"complete": { "type": "final" }
}
}Full Example with Context and Guards
{
"id": "deploy-pipeline",
"initial": "testing",
"context": {
"test_result": null,
"coverage": 0
},
"states": {
"testing": {
"allowed_tools": ["Read", "Bash"],
"allowed_commands": ["pytest", "npm test"],
"on": {
"DEPLOY": {
"target": "deploying",
"guard": "tests_passed"
},
"FAIL": "failed"
}
},
"deploying": {
"allowed_tools": ["Bash"],
"on": { "DONE": "complete" }
},
"complete": { "type": "final" },
"failed": { "type": "final" }
},
"guards": {
"tests_passed": {
"field": "test_result",
"op": "eq",
"value": "pass"
}
}
}State Definition Fields
Each key in states maps to a state definition object.
| Field | Type | Default | Description |
|---|---|---|---|
type | "final" | — | Set to "final" for terminal states. Once reached, enforcement deactivates. |
allowed_tools | string[] | omitted = no enforcement | Tools the agent can use in this state. If omitted, all tools pass through. |
instructions | string | — | Natural language instructions injected into agent context each turn |
max_iterations | integer (>= 1) | unlimited | Maximum tool calls before forcing a transition decision |
safe_next | string | — | Fallback state for unrecognized transition events |
max_edit_lines | integer (>= 1) | unlimited | Maximum lines per edit operation |
max_files_per_state | integer (>= 1) | unlimited | Maximum files that can be edited in this state |
allowed_commands | string[] | — | Allowed shell command prefixes for Bash tool |
blocked_env | string[] | — | Environment variables denied in Bash commands. Alias: deny_env |
env_overrides | object | — | Environment variable overrides injected as context. Alias: env |
context_budget_bytes | integer | unlimited | Maximum accumulated tool result bytes in this state |
on | object | {} | Transition events keyed by event name |
allowed_tools
Controls which MCP tools the agent can call. Tool names must match exactly as the agent knows them (e.g., Read, Edit, Bash, Grep, Glob, Write).
{
"planning": {
"allowed_tools": ["Read", "Grep", "Glob"],
"on": { "READY": "implementing" }
}
}If allowed_tools is omitted, all tools pass through — no enforcement for that state. Statewright's own MCP tools (transition, get_state, etc.) are always available regardless.
instructions
Injected into the agent's context on every turn while in this state. Use for phase-specific guidance the agent should follow.
{
"implementing": {
"allowed_tools": ["Read", "Edit", "Write"],
"instructions": "Apply the minimal fix. Do not refactor unrelated code. Keep edits under 20 lines.",
"on": { "DONE": "testing" }
}
}max_iterations
Counts tool calls in the current state. When the limit is reached, the agent is forced to make a transition decision rather than continuing to loop.
{
"planning": {
"allowed_tools": ["Read", "Grep"],
"max_iterations": 10,
"on": { "READY": "implementing" }
}
}safe_next
Fallback when the agent emits an event that has no matching transition. Only fires on truly unknown events — explicitly defined transitions (including FAIL) are unaffected.
{
"planning": {
"allowed_tools": ["Read"],
"safe_next": "implementing",
"on": {
"READY": "implementing",
"FAIL": "failed"
}
}
}If the agent calls statewright_transition(event='GO') and GO has no definition, the machine transitions to implementing. Without safe_next, the transition is rejected.
allowed_commands
Restricts which shell commands the agent can run via the Bash tool. Values are prefix-matched.
{
"testing": {
"allowed_tools": ["Read", "Bash"],
"allowed_commands": ["pytest", "npm test", "cargo test"],
"on": { "PASS": "complete", "FAIL": "debugging" }
}
}The agent can run pytest -v tests/ but not rm -rf / or git push.
blocked_env and env_overrides
Control environment variable access within Bash commands.
{
"staging": {
"allowed_tools": ["Bash"],
"blocked_env": ["PROD_DB_URL", "AWS_SECRET_ACCESS_KEY"],
"env_overrides": {
"NODE_ENV": "staging",
"DATABASE_URL": "postgres://localhost/staging"
},
"on": { "DONE": "complete" }
}
}blocked_env (alias deny_env) prevents the agent from reading specified variables. env_overrides (alias env) injects overrides into the agent's context.
max_edit_lines and max_files_per_state
Scope constraints on file modifications.
{
"implementing": {
"allowed_tools": ["Read", "Edit", "Write"],
"max_edit_lines": 20,
"max_files_per_state": 3,
"on": { "DONE": "testing" }
}
}The agent can edit at most 3 files, with each edit operation limited to 20 lines. This prevents sprawling changes.
context_budget_bytes
Caps the total bytes of tool results the agent can accumulate in a state. Prevents runaway context from large file reads.
{
"analysis": {
"allowed_tools": ["Read", "Grep"],
"context_budget_bytes": 50000,
"on": { "READY": "implementing" }
}
}Transition Patterns
Transitions are defined in a state's on object. Each key is an event name, and the value is one of four patterns.
Pattern 1: Simple
Target state as a plain string. No conditions, no approval.
{
"on": {
"READY": "implementing",
"FAIL": "failed"
}
}Pattern 2: Guarded
Object with target and one or more guards. The transition only fires if all guards pass.
{
"on": {
"DEPLOY": {
"target": "deploying",
"guard": "tests_passed"
}
}
}With multiple guards (all must pass):
{
"on": {
"DEPLOY": {
"target": "deploying",
"guards": ["tests_passed", "coverage_adequate"],
"requires_approval": true,
"approval_message": "Deploy to production?"
}
}
}| Field | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Destination state |
guard | string | No | Single guard name to evaluate |
guards | string[] | No | Multiple guard names (all must pass) |
requires_approval | boolean | No | Pause for human approval before transitioning |
approval_message | string | No | Message shown to the human reviewer |
Pattern 3: Branched (XState Pattern)
Array of guarded transitions. The engine evaluates guards in order and takes the first match.
{
"on": {
"EVALUATE": [
{ "target": "deploying", "guard": "coverage_high" },
{ "target": "improving", "guard": "coverage_low" },
{ "target": "failed" }
]
}
}The last entry with no guard acts as the default branch. If no guard matches and there is no default, the transition is rejected.
Each branch is an object:
| Field | Type | Required | Description |
|---|---|---|---|
target | string | Yes | Destination state |
guard | string | No | Guard name to evaluate |
guards | string[] | No | Multiple guard names (all must pass) |
Pattern 4: Invoke
Delegate to a sub-machine, then resume at on_complete.
{
"on": {
"RUN_TESTS": {
"invoke": "test-suite",
"on_complete": "deploying",
"on_fail": "debugging",
"input": { "suite": "integration" }
}
}
}| Field | Type | Required | Description |
|---|---|---|---|
invoke | string | Yes | Sub-machine definition to invoke |
on_complete | string | Yes | State to transition to on success |
on_fail | string | No | State to transition to on failure |
input | object | No | Input data for the sub-machine's initial context |
Guard Definitions
Guards live at the top level of the workflow under guards. Each guard checks a value in the state machine's context.
{
"guards": {
"tests_passed": {
"field": "test_result",
"op": "eq",
"value": "pass"
},
"coverage_adequate": {
"field": "coverage",
"op": "gte",
"value": 80
}
}
}| Field | Type | Required | Description |
|---|---|---|---|
field | string | Yes | Context field to check |
op | string | Yes | Comparison operator |
value | any | No | Value to compare against (type depends on operator) |
Guard Operators
| Operator | Description | Example |
|---|---|---|
eq | Equal | {"field": "status", "op": "eq", "value": "pass"} |
neq | Not equal | {"field": "status", "op": "neq", "value": "fail"} |
gt | Greater than | {"field": "coverage", "op": "gt", "value": 80} |
gte | Greater than or equal | {"field": "coverage", "op": "gte", "value": 80} |
lt | Less than | {"field": "errors", "op": "lt", "value": 5} |
lte | Less than or equal | {"field": "errors", "op": "lte", "value": 0} |
in | Value is in a list | {"field": "env", "op": "in", "value": ["staging", "prod"]} |
contains | Field contains value | {"field": "tags", "op": "contains", "value": "approved"} |
exists | Field exists and is not null | {"field": "review_id", "op": "exists"} |
not_exists | Field is missing or null | {"field": "error", "op": "not_exists"} |
Guards read from context. The agent writes to context via the data parameter of statewright_transition:
statewright_transition(event='DEPLOY', data={test_result: 'pass', coverage: 92})Meta Fields
Optional metadata at the top level under meta. Used by the agent layer and observability tooling, not by the state machine engine itself.
{
"id": "deploy-pipeline",
"initial": "testing",
"meta": {
"task_type": "deployment",
"estimated_steps": 15,
"danger_level": "dangerous",
"requires_human_approval": true,
"capture_output": true
},
"states": { ... }
}| Field | Type | Description |
|---|---|---|
task_type | string | Categorization hint (e.g., "bugfix", "deployment", "refactor") |
estimated_steps | integer | Rough estimate of total tool calls for the workflow |
danger_level | "safe" | "moderate" | "dangerous" | Risk classification for the workflow |
requires_human_approval | boolean | Whether the workflow requires human sign-off at some point |
capture_output | boolean | Enable output capture to the run history log |
Additional arbitrary fields are allowed under meta for forward compatibility.
Complete Reference Example
A full workflow definition using every feature:
{
"$schema": "https://statewright.ai/workflow-schema.json",
"id": "deploy-pipeline",
"initial": "planning",
"context": {
"test_result": null,
"coverage": 0,
"approved": false
},
"meta": {
"task_type": "deployment",
"estimated_steps": 25,
"danger_level": "dangerous",
"requires_human_approval": true,
"capture_output": true
},
"states": {
"planning": {
"allowed_tools": ["Read", "Grep", "Glob"],
"instructions": "Understand the change. Read tests and deployment config.",
"max_iterations": 10,
"context_budget_bytes": 50000,
"safe_next": "testing",
"on": {
"READY": "testing",
"FAIL": "failed"
}
},
"testing": {
"allowed_tools": ["Read", "Bash"],
"allowed_commands": ["pytest", "npm test", "cargo test"],
"blocked_env": ["PROD_DB_URL"],
"max_iterations": 15,
"on": {
"EVALUATE": [
{ "target": "deploying", "guard": "deploy_ready" },
{ "target": "fixing", "guard": "tests_failed" },
{ "target": "failed" }
]
}
},
"fixing": {
"allowed_tools": ["Read", "Edit"],
"max_edit_lines": 20,
"max_files_per_state": 3,
"instructions": "Fix only the failing tests. Minimal changes.",
"on": {
"DONE": "testing",
"FAIL": "failed"
}
},
"deploying": {
"allowed_tools": ["Bash"],
"allowed_commands": ["kubectl", "helm"],
"env_overrides": { "KUBECONFIG": "/etc/kube/staging.yaml" },
"on": {
"DONE": {
"target": "complete",
"requires_approval": true,
"approval_message": "Deployment finished. Approve to mark complete?"
},
"FAIL": "failed"
}
},
"complete": { "type": "final" },
"failed": { "type": "final" }
},
"guards": {
"deploy_ready": {
"field": "test_result",
"op": "eq",
"value": "pass"
},
"tests_failed": {
"field": "test_result",
"op": "eq",
"value": "fail"
}
}
}