New to FORGE? Start a free 30-day trial

Creating Your First Workflow

Updated on Oct 5, 2026

In this tutorial, you build a three-state workflow from an empty file. It writes a file, pauses for your approval, and then verifies the file. Along the way you validate, run, monitor, and approve a workflow. The workflow uses only built-in steps and checks, so no AI agent call or issue context is needed.

Prerequisites

  • Forge installed (frg --version).
  • A Git repository initialized for Forge (frg config init).
  • Two terminals open in the repository root. The run waits at the approval gate in one terminal, and you approve it from the other.

Step 1: Create the workflows directory

Forge discovers project workflows in .forge/config/workflows/.

bash
mkdir -p .forge/config/workflows

Step 2: Write the workflow file

Create .forge/config/workflows/hello-workflow.toml with this content:

toml
workflow_schema_version = "1.0.0"

[workflow]
name = "hello-workflow"
description = "Write a file, wait for approval, then confirm the file exists"
version = "1.0.0"
required_context = []

[workflow.initial_state]
name = "write_greeting"

[[workflow.states]]
name = "write_greeting"
description = "Write a greeting file"
operation = { id = "write-greeting", type = "builtin", action = "write_file", path = "hello-workflow.txt", content = "Hello from a Forge workflow" }
on_success = "await_review"
on_failure = "write_failed"

[[workflow.states]]
name = "await_review"
description = "Review hello-workflow.txt before continuing"
on_success = "verify_greeting"
on_failure = "review_rejected"

[workflow.states.approval]
gate_id = "greeting_review"
message = "Open hello-workflow.txt. Approve to continue or reject to stop."
timeout_seconds = 3600

[[workflow.states]]
name = "verify_greeting"
description = "Confirm the greeting file exists"
operation = { id = "greeting-exists", type = "file_exists", path = "hello-workflow.txt", on_false = "fail" }
on_success = "completed"
on_failure = "verify_failed"

[workflow.terminal_states]
success = ["completed"]
failed = ["write_failed", "verify_failed", "review_rejected"]

[[workflow.states]]
name = "completed"
description = "Greeting written, reviewed, and verified"

[[workflow.states]]
name = "write_failed"
description = "Could not write the greeting file"

[[workflow.states]]
name = "verify_failed"
description = "Greeting file not found"

[[workflow.states]]
name = "review_rejected"
description = "Greeting was rejected at review"

The file defines:

  • required_context = []: this workflow needs no issue, PR, or ticket. The field itself is required.
  • write_greeting: a built-in step that writes hello-workflow.txt.
  • await_review: an approval gate. Approving goes to verify_greeting; rejecting goes to review_rejected.
  • verify_greeting: a check that fails the state if the file is missing.
  • [workflow.terminal_states]: the states where the run ends.

Step 3: Validate the workflow

bash
frg workflow validate hello-workflow

Output:

text
✓ Workflow 'hello-workflow' is valid

 Version: 1.0.0
 States: 7
 Terminal: completed (success), write_failed, verify_failed, review_rejected (failed)
 Source: .forge/config/workflows/hello-workflow.toml

Validation checks passed:
 ✓ Schema structure valid
 ✓ All states reachable from 'write_greeting'
 ✓ Terminal states reachable
 ✓ No direct self-loops detected
 ✓ Escape conditions valid

If you see an error instead, compare your file with Step 2. See also Troubleshooting.

Step 4: Inspect the states

bash
frg workflow run hello-workflow --list-states

This lists the states without running anything:

text
Workflow States: hello-workflow
════════════════════════════════════════

 Name Type Description
 ──── ──── ───────────
 write_greeting command* Write a greeting file
 await_review approval Review hello-workflow.txt before continuing
 verify_greeting check Confirm the greeting file exists
 completed success Greeting written, reviewed, and verified
 write_failed failed Could not write the greeting file
 verify_failed failed Greeting file not found
 review_rejected failed Greeting was rejected at review

Total: 7 states

The * marks the initial state. The state flow looks like this:

text
write_greeting ──success──▶ await_review ──approve──▶ verify_greeting ──success──▶ completed
 │ │ │
 failure reject failure
 ▼ ▼ ▼
 write_failed review_rejected verify_failed

Step 5: Run the workflow

In the first terminal, run:

bash
frg workflow run hello-workflow

Forge writes hello-workflow.txt and stops at await_review, waiting for approval. Leave this terminal running.

Step 6: Check the status

In the second terminal, run:

bash
frg workflow status hello-workflow

The status shows the workflow's current state and its work artifacts. Add --verbose for file sizes and timestamps.

Step 7: Approve the gate

  1. Open hello-workflow.txt and confirm that it contains the greeting.
  2. Show the pending approval:
bash
frg workflow approve
  1. Approve it with the approval code and your email address, which Forge records for the audit trail:
bash
frg workflow approve --code <CODE> --email you@example.com

The first terminal continues to verify_greeting and ends in completed.

Step 8: Confirm the result

bash
frg workflow status hello-workflow
frg workflow approve --list-all

--list-all shows the approval you just gave. You have now run a complete workflow: a step, an approval gate, a check, and a terminal state.

To see the rejection path, run the workflow again and reject the gate this time:

bash
frg workflow reject --code <CODE> --email you@example.com --reason "Wrong greeting"

The run ends in review_rejected.

Next steps

  • Replace write_greeting with an AI command, for example command = { target = "issue", operation = "draft" }. Then set required_context = ["issue_id"] and run frg config set-issue <ID> before you run the workflow.
  • Add a loop or a retry: Handling Failures with Loops and Retries.
  • Add agent steps, branching, and recovery: Advanced Workflow Patterns.

Troubleshooting

missing field 'required_context'

Cause: The [workflow] table has no required_context key. The key is mandatory, even when the workflow needs no context.

Solution:

  1. Add required_context = [] under [workflow].
  2. Run frg workflow validate hello-workflow.

Unknown workflow: 'hello-workflow'

Cause: Forge cannot find the workflow, or the file failed to parse. frg workflow list prints a ✗ line with the file path for files that fail to load.

Solution:

  1. Confirm the file is at .forge/config/workflows/hello-workflow.toml and that name = "hello-workflow".
  2. Run frg workflow list and fix any file marked ✗.

Non-terminal states must have command, operation, operations, or approval

Cause: A state has no work and is not listed in [workflow.terminal_states].

Solution: Add the state name to success, failed, or cancelled under [workflow.terminal_states], or give the state an operation.

No workflow is currently pending approval.

Cause: No run is waiting at a gate, for example because the run has not reached await_review yet or has already ended.

Solution:

  1. Run frg workflow status to see the current state.
  2. If the run was interrupted, start it again with frg workflow run hello-workflow --start-from await_review.

See Also