New to FORGE? Start a free 30-day trial

Workflow TOML Schema Reference

Updated on Oct 5, 2026

Complete reference for workflow definition files in Forge. For step and check fields, see Workflow Steps and Checks. For a guided example, see Creating Your First Workflow.

File location

SourceLocation
Project workflows.forge/config/workflows/<name>.toml
Built-in workflowsCompiled into frg; copy them to the project with frg config export --workflows

Commands refer to a workflow by its name. Keep the file name and name identical.

Structure

toml
workflow_schema_version = "1.0.0"

[workflow]
name = "my-workflow"
description = "What this workflow does"
version = "1.0.0"
required_context = ["issue_id"]

[workflow.initial_state]
name = "first_state"

[workflow.terminal_states]
success = ["completed"]
failed = ["first_failed"]

[workflow.loop_detection] # optional
max_transitions = 150
on_loop_detected = "approval_gate"

[[workflow.states]]
name = "first_state"
command = { target = "issue", operation = "draft" }
on_success = "completed"
on_failure = "first_failed"

[[workflow.states]]
name = "completed"

[[workflow.states]]
name = "first_failed"

Top level

FieldTypeRequiredDescription
workflow_schema_versionStringYesSchema version. Supported: 1.0.x. Any other value fails with Unsupported workflow schema version.
workflowTableYesThe workflow definition.

[workflow]

FieldTypeRequiredDescription
nameStringYesWorkflow identifier used by frg workflow commands.
descriptionStringYesShown by frg workflow list.
versionStringYesVersion of this definition, shown by frg workflow list --verbose.
required_contextArray of stringsYesContext that must be set before the run starts: issue_id, pr_id, ticket_id. Use [] for none.
initial_stateTableYesSee [workflow.initial_state].
terminal_statesTableYesSee [workflow.terminal_states].
loop_detectionTableNoSee [workflow.loop_detection].
statesArray of tablesYesSee [[workflow.states]].

A missing required key fails with, for example, missing field description``.

context accepts only these values: issue, code, pr, security, ticket. An unrecognized value makes the operation fail workflow validation. Each recognized value (other than code/security, which are reserved for future tooltip injection) appends an XML block like <issue_context><issue_id>... after the prompt's project config, separate from the prompt text itself.

Agent Prompt Placeholders:

The prompt text itself may reference {{issue_id}}, {{pr_id}}, and {{ticket_id}} directly — these are substituted with the resolved workflow context values before the prompt is sent to the agent:

toml
operation = { id = "notify", type = "agent", prompt = "Post a status update on issue {{issue_id}}." }

If a value isn't available in the current context (e.g. {{pr_id}} when no PR is set), the placeholder is left as-is rather than silently removed. Only these three IDs are supported — project metadata and toolchain placeholders (e.g. {{issue_tracker_name}}, available to command-based prompts) are not.

Agent Step Traces:

Every agent step writes a trace file to <job_dir>/traces/ (named {timestamp}_{step_id}.trace.md), containing the prompt sent to the agent and its stdout/stderr/exit code — the same convention used by command-based states, so frg --tail/--continue and frg diagnose work for agent steps too.

Built-in Actions:

  • make_directory: Create directory (path field required)
  • set_env: Set environment variable (env_name, env_value required)
  • copy_file: Copy file (source, destination required)
  • write_file: Write content to file (path, content required)

Check Operations

Evaluate a condition and control workflow based on result:

File Exists:

toml
operation = { id = "config-exists", type = "file_exists", path = ".forge/config.toml", on_false = "fail" }

Environment Equals:

toml
operation = { id = "mode-check", type = "env_equals", env_name = "MODE", env_value = "production", on_false = "skip" }

Check Fields:

FieldTypeRequiredDescription
nameStringYesState where the run starts. Must exist in states.

[workflow.terminal_states]

TypeDescriptionRequired Fields
file_existsFile existspath
directory_existsDirectory existspath
path_existsFile or directory existspath
env_existsEnvironment variable is setenv_name
env_equalsEnvironment variable equals valueenv_name, env_value
file_containsFile contains stringpath, content
file_matchesFile matches regex patternpath, pattern
command_existsCommand is available in PATHcommand
command_successShell command exits with 0command or auto_detect
tasks_completeAll tasks in tasks.md are completepath (optional)
counter_matchesLoop counter matches expressioncounter_name, counter_expression
command_success with auto_detect

Instead of a literal command, a command_success check can set auto_detect to "build" or "test" to run the project's own build/test command:

toml
operation = { id = "project-builds", type = "command_success", auto_detect = "build", on_false = "fail" }

The command is read from .forge/config/project.toml's [project] table (build_command / test_command) — it is not derived by scanning the filesystem for build-system markers (no Cargo.toml/package.json detection). If project.toml is absent or the field isn't set, the check is skipped, not failed, so it's safe to leave in workflows shared across projects that don't record a build/test command. A check may set exactly one of command or auto_detect, and auto_detect is only valid on command_success checks.

toml
# .forge/config/project.toml
[project]
build_command = "cargo build --all-targets"
test_command = "cargo test"

Check Behaviors:

The on_false field controls what happens when a check evaluates to false:

ValueBehavior
"fail"State fails immediately, transitions to on_failure
"skip"Log warning, continue to next operation or transition to on_success

Special Check Types:

tasks_complete Check

Verifies that all tasks in a tasks.md file are complete by scanning for unchecked task markers (- [ ]) in task sections only.

toml
operation = {
    id = "all-tasks-done",
    type = "tasks_complete",
    on_false = "fail"
}

With explicit path:

toml
operation = {
    id = "all-tasks-done",
    type = "tasks_complete",
    path = ".forge/work/job_123/code/tasks.md",
    on_false = "fail"
}

Fields:

  • path (optional): Path to tasks.md file. If omitted, defaults to {job_dir}/code/tasks.md

Section-Aware Parsing:

The check uses intelligent section detection to avoid counting non-task checkboxes:

Section TypeDetectionCheckbox Behavior
Task sectionsHeaders without exclusion keywordsCounted
Non-task sectionsHeaders containing "acceptance", "criteria", "summary", "requirement", "validation", etc.Ignored
Code blocksContent between ``` or ~~~Ignored

Example:

markdown
# Implementation Tasks
- [ ] Create config file        ← Detected as incomplete
- [x] Update documentation      ← Ignored (complete)

## Acceptance Criteria
- [ ] Feature works as expected ← Ignored (non-task section)

## Example Code
~~~~
  • Example checkbox ← Ignored (code block)
~~~~

**Behavior**:
- Check **passes** when no unchecked tasks remain in task sections
- Check **fails** when any unchecked task marker (`- [ ]`) exists in a task section
- Works with both lite plans (no task identifiers) and full plans (with `TASK-`/`TSK-` identifiers)

**Common Pattern - Loop Until Complete**:
```toml
[[workflow.states]]
name = "create_code"
command = { target = "code", operation = "create" }
on_success = "check_tasks"

[[workflow.states]]
name = "check_tasks"
operation = { id = "tasks-done", type = "tasks_complete", on_false = "fail" }
on_success = "code_review"      # All done, exit loop
on_failure = "create_code"       # Tasks remain, continue loop
counter_matches Check

Evaluates arithmetic expressions on loop counters for conditional logic.

toml
operation = {
    id = "every-third",
    type = "counter_matches",
    counter_name = "code_iterations",
    counter_expression = "% 3 == 0",
    on_false = "skip"
}

Fields:

  • counter_name: Name of loop counter to evaluate
  • counter_expression: Arithmetic expression (e.g., "% 3 == 0", "> 5", "== 10")

Supported Operators:

  • Arithmetic: +, -, *, /, % (modulo)
  • Comparison: ==, !=, <, >, <=, >=

Behavior:

  • Evaluates expression against current counter value
  • Check passes (success) when expression is true
  • Check fails when expression is false

Common Pattern - Periodic Actions:

toml
# Clear context every 3rd iteration
[[workflow.states]]
name = "check_counter"
operation = {
    id = "mod-3",
    type = "counter_matches",
    counter_name = "iterations",
    counter_expression = "% 3 == 0",
    on_false = "skip"
}
on_success = "create_code_clear"   # Use --clear flag
on_failure = "create_code"          # Regular operation

Command Operations

Execute a Forge CLI command within the workflow. A command operation is identified solely by the presence of the target field — there is no type field on command operations, and adding one causes a parse error:

toml
operation = { target = "issue", operation = "draft", modifiers = ["lite"] }

With arguments:

toml
operation = { target = "code", operation = "review", args = { model = "gpt-4" } }

Command Fields:

FieldTypeRequiredDescription
targetStringYesCommand target (e.g., "issue", "code", "pr")
operationStringYesCommand operation (e.g., "draft", "review")
modifiersArrayNoList of modifiers to apply
argsObjectNoArgument overrides

[workflow.loop_detection]

FieldTypeDefaultDescription
max_state_visitsInteger3Visit limit for states without max_visits.
max_transitionsInteger100Limit on total transitions in one run. The built-in iterative workflows use 100 or 150.
on_loop_detectedString"approval_gate""approval_gate": pause for approve or reject. "fail": fail immediately.

[[workflow.states]]

FieldTypeRequiredDescription
nameStringYesUnique state name. Used by transitions, --start-from, and --resume-at.
descriptionStringNoShown by --list-states, frg workflow graph, and progress output.
is_entry_pointBooleanNoRecommends this state as a safe --start-from target. Defaults to false; it does not restrict which existing states the CLI accepts.
commandInline tableOne of fourRun a Forge command. See Command.
operationInline tableOne of fourRun one step or check. See Operation types.
operationsArray of inline tablesOne of fourRun several steps and checks in order.
approvalTableOne of fourPause at an approval gate. See Approval.
on_successStringFor non-terminal statesNext state when the state succeeds or the gate is approved.
on_failureStringFor non-terminal statesNext state when the state fails or the gate is rejected.
loop_enabledBooleanNoMarks a loop state. Requires at least one escape condition.
loop_counterStringWith loop_enabledCounter name. counter_matches checks read it through counter_name.
escape_conditionsArray of tablesWith loop_enabledSee Escape conditions.
max_visitsIntegerNoPer-state visit limit that overrides max_state_visits. Must be greater than 0.
retryTableNoSee Retry.

Every non-terminal state must set exactly one of command, operation, operations, or approval.

is_entry_point = true marks a state that the workflow author recommends as a starting point. frg workflow run --list-states labels it [recommended start]; the CLI still accepts any existing state with --start-from. Forge Desktop offers the initial state and states marked as entry points. Before starting at a later state, verify that its prerequisites and required context are already satisfied.

Command

toml
command = { target = "code", operation = "review", modifiers = ["auto-fix", "issues"] }
FieldTypeRequiredDescription
targetStringYesCommand group, for example issue, code, pr, ticket.
operationStringYesCommand within the group, for example draft, plan, create, review, merge, triage.
modifiersArray of stringsNoCommand flags without --; a flag's value follows as the next element. Built-in workflows use retry, clear, lite, and auto-fix with issues.
argsTableNoArgument overrides.

No other keys are accepted. The validator reports unknown field ..., expected one of target, operation, modifiers, args.

Operation types

An operation is a command, a step, or a check. Forge infers the kind from its fields; there is no kind field.

Operation is a…WhenFields
CommandIt has targettarget, operation, modifiers, args
Steptype is shell, builtin, or agentStep fields
Checktype is a check typeCheck fields

Check types: file_exists, directory_exists, path_exists, file_contains, file_matches, env_exists, env_equals, command_exists, command_success, tasks_complete, counter_matches.

Write the concrete type directly. type = "step", type = "check", step_type, and check_type are rejected:

text
Operation 'x' declares type = "check", but "check" is not an operation type value.
Use the concrete type directly, without a `step_type`/`check_type` field:
 { id = "x", type = "agent", prompt = "..." } # agent step
 { id = "x", type = "shell", command = "..." } # shell step
 { id = "x", type = "file_exists", path = "..." } # check

Operations in operations run in definition order.

Approval

toml
[workflow.states.approval]
gate_id = "plan_approval"
message = "Implementation plan is ready. Review & edit before approving for code generation"
timeout_seconds = 86400
FieldTypeRequiredDescription
gate_idStringYesGate identifier, recorded in approval history and approval transaction events.
messageStringYesInstructions shown to the approver.
required_codeStringNoCode the approver must pass with --code (case-sensitive).
timeout_secondsIntegerNoTime to wait for a decision. All built-in gates use 86400.
on_timeoutStringNoState to go to when the gate times out.

Approving follows on_success; rejecting follows on_failure. --resume-at overrides either.

Escape conditions

toml
[[workflow.states.escape_conditions]]
counter_value = 15
action = "approval"
approval_gate = "await_iteration_approval"
FieldTypeRequiredDescription
counter_valueIntegerYesCounter value at which the condition applies.
actionStringYescontinue, transition, approval, or abort.
target_stateStringFor transitionState to transition to.
approval_gateStringFor approvalName of the approval state to transition to.
error_messageStringFor abortError the workflow ends with.
messageStringNoMessage to display.
ActionEffectMissing field error
continueKeep looping—
transitionGo to target_stateEscape condition with action 'transition' requires 'target_state'
approvalGo to the approval state named by approval_gateEscape condition with action 'approval' requires 'approval_gate'
abortEnd the workflow (Workflow aborted by escape condition)Escape condition with action 'abort' requires 'error_message'

Retry

toml
[workflow.states.retry]
max_retries = 3
retry_delay = "30s"
timeout = "5m"

[[workflow.states.retry.retry_conditions]]
retry_count = 2
action = "approval"
approval_gate = "await_retry_approval"
FieldTypeDescription
max_retriesIntegerMaximum retry attempts. Very high values produce a validation warning.
retry_delayStringDelay between attempts, for example "30s".
timeoutStringPer-attempt timeout, for example "5m".
retry_conditionsArray of tablesBehaviour at specific retry counts.
retry_conditions fieldTypeDescription
retry_countIntegerRetry count at which the condition applies.
actionStringcontinue, transition, or approval.
timeoutStringReplacement per-attempt timeout.
target_stateStringFor transition.
approval_gateStringFor approval.
messageStringMessage to display.

Counter expressions

counter_matches checks evaluate counter_expression against the counter named in counter_name:

FormExample
{{COUNTER}} <operator> N{{COUNTER}} >= 5
{{COUNTER}} % N == M{{COUNTER}} % 3 == 0
{{COUNTER}} % N != M{{COUNTER}} % 3 != 0

An expression without {{COUNTER}} passes frg workflow validate but fails at run time with Expected format: '{{COUNTER}} <operator> N'.

Validation rules

frg workflow validate <name> and workflow loading apply these rules.

Errors (the workflow cannot run):

MessageFix
missing field `` (for example required_context, description, version)Add the key under [workflow]. Use required_context = [] for none.
Unsupported workflow schema version '<v>'. Supported: ["1.0.x"]Set workflow_schema_version = "1.0.0".
Initial state does not exist in states listFix [workflow.initial_state] name.
Invalid transition from '<state>' to '<target>': Target state does not existFix the on_success or on_failure target.
State '<name>' has no operation defined. Non-terminal states must have command, operation, operations, or approvalGive the state work, or list it in [workflow.terminal_states].
No terminal state is reachable from initial stateAdd a transition path to a terminal state.
Loop state must have at least one escape conditionAdd [[workflow.states.escape_conditions]].
Escape condition with action '<action>' requires '<field>'Add the required field.
State '<name>' has max_visits=0, which would prevent any executionSet max_visits to 1 or more.
Operation '<id>' has unknown check type '<type>'Use a listed check type.
unknown field , expected one of ...Remove or rename the key in the step, check, or command.
unknown variant , expected ...Use an allowed value, for example for on_loop_detected, action, or required_context.

Warnings (the workflow can run):

MessageMeaning
State '<name>' is unreachable from initial state '<initial>'No on_success or on_failure path leads to the state. States entered only through escape conditions also show this warning.
Loop state '<name>' has no escape that guarantees termination. Consider adding an 'abort' escape at high counter value.The loop has no abort escape.
Escape conditions in state '<name>' are not in ascending order. Counter value <n> appears after <m>.List escape conditions in ascending counter_value order.

Keys the schema does not define are ignored at the [workflow], state, approval, escape-condition, loop-detection, and retry levels, but rejected inside steps, checks, and commands. Check spelling carefully at the ignoring levels.

See Also