Statewright

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

FieldTypeRequiredDescription
idstringYesUnique workflow identifier
initialstringYesName of the starting state
statesobjectYesState definitions keyed by state name
contextobjectNoInitial context values for guard evaluation
guardsobjectNoNamed guard predicates for conditional transitions
metaobjectNoMachine 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.

FieldTypeDefaultDescription
type"final"Set to "final" for terminal states. Once reached, enforcement deactivates.
allowed_toolsstring[]omitted = no enforcementTools the agent can use in this state. If omitted, all tools pass through.
instructionsstringNatural language instructions injected into agent context each turn
max_iterationsinteger (>= 1)unlimitedMaximum tool calls before forcing a transition decision
safe_nextstringFallback state for unrecognized transition events
max_edit_linesinteger (>= 1)unlimitedMaximum lines per edit operation
max_files_per_stateinteger (>= 1)unlimitedMaximum files that can be edited in this state
allowed_commandsstring[]Allowed shell command prefixes for Bash tool
blocked_envstring[]Environment variables denied in Bash commands. Alias: deny_env
env_overridesobjectEnvironment variable overrides injected as context. Alias: env
context_budget_bytesintegerunlimitedMaximum accumulated tool result bytes in this state
onobject{}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?"
    }
  }
}
FieldTypeRequiredDescription
targetstringYesDestination state
guardstringNoSingle guard name to evaluate
guardsstring[]NoMultiple guard names (all must pass)
requires_approvalbooleanNoPause for human approval before transitioning
approval_messagestringNoMessage 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:

FieldTypeRequiredDescription
targetstringYesDestination state
guardstringNoGuard name to evaluate
guardsstring[]NoMultiple 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" }
    }
  }
}
FieldTypeRequiredDescription
invokestringYesSub-machine definition to invoke
on_completestringYesState to transition to on success
on_failstringNoState to transition to on failure
inputobjectNoInput 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
    }
  }
}
FieldTypeRequiredDescription
fieldstringYesContext field to check
opstringYesComparison operator
valueanyNoValue to compare against (type depends on operator)

Guard Operators

OperatorDescriptionExample
eqEqual{"field": "status", "op": "eq", "value": "pass"}
neqNot equal{"field": "status", "op": "neq", "value": "fail"}
gtGreater than{"field": "coverage", "op": "gt", "value": 80}
gteGreater than or equal{"field": "coverage", "op": "gte", "value": 80}
ltLess than{"field": "errors", "op": "lt", "value": 5}
lteLess than or equal{"field": "errors", "op": "lte", "value": 0}
inValue is in a list{"field": "env", "op": "in", "value": ["staging", "prod"]}
containsField contains value{"field": "tags", "op": "contains", "value": "approved"}
existsField exists and is not null{"field": "review_id", "op": "exists"}
not_existsField 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": { ... }
}
FieldTypeDescription
task_typestringCategorization hint (e.g., "bugfix", "deployment", "refactor")
estimated_stepsintegerRough estimate of total tool calls for the workflow
danger_level"safe" | "moderate" | "dangerous"Risk classification for the workflow
requires_human_approvalbooleanWhether the workflow requires human sign-off at some point
capture_outputbooleanEnable 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"
    }
  }
}

On this page