Creating Your First Workflow
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/.
mkdir -p .forge/config/workflows
Step 2: Write the workflow file
Create .forge/config/workflows/hello-workflow.toml with this content:
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 writeshello-workflow.txt.await_review: an approval gate. Approving goes toverify_greeting; rejecting goes toreview_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
frg workflow validate hello-workflow
Output:
✓ 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
frg workflow run hello-workflow --list-states
This lists the states without running anything:
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:
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:
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:
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
- Open
hello-workflow.txtand confirm that it contains the greeting. - Show the pending approval:
frg workflow approve
- Approve it with the approval code and your email address, which Forge records for the audit trail:
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
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:
frg workflow reject --code <CODE> --email you@example.com --reason "Wrong greeting"
The run ends in review_rejected.
Next steps
- Replace
write_greetingwith an AI command, for examplecommand = { target = "issue", operation = "draft" }. Then setrequired_context = ["issue_id"]and runfrg 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:
- Add
required_context = []under[workflow]. - 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:
- Confirm the file is at
.forge/config/workflows/hello-workflow.tomland thatname = "hello-workflow". - Run
frg workflow listand 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:
- Run
frg workflow statusto see the current state. - If the run was interrupted, start it again with
frg workflow run hello-workflow --start-from await_review.