Policy Explanation¶
Ralph Workflow is a free and open-source AI agent orchestrator built around a simple core loop inspired by the original Ralph loop. That simple core composes into a stronger workflow system for serious repo work, and the default workflow is already strong enough to start with before you customize anything.
Ralph Workflow can render a human-readable explanation of the active policy configuration with a single command. Use it when you want to sanity-check your pipeline before a run or document the workflow your team is relying on.
The command¶
ralph --explain-policy
This reads the active pipeline.toml — project-local .agent/pipeline.toml when present, otherwise the bundled defaults — and prints a structured summary to stdout.
To inspect a custom policy directory instead:
ralph --explain-policy --explain-policy-dir /path/to/policy/dir
Fast validation without explanation¶
If you only want a pass/fail validation without the full explanation output, use:
ralph --check-policy
This validates the same policy source as --explain-policy and prints a brief summary of both the authored block model and the compiled runtime phases:
Policy OK: /path/to/.agent
entry block: developer_iteration
blocks: 10
lifecycle completion phases: 1
phases: 10
drains: 11
artifact contracts: 6
loop counters: 3
budget counters: 1
workflow fallbacks: 0
terminal failure phase: failed_terminal
Exit codes: 0 = valid, 2 = PolicyValidationError, 1 = other error.
--check-policy is useful in CI scripts or pre-flight hooks where you want to catch
invalid policy before starting a run. Accepts --explain-policy-dir for a custom
directory.
What the output shows¶
The explanation covers all policy-declared elements:
Section |
What it contains |
|---|---|
WORKFLOW DIAGRAM |
ASCII diagram showing phases, routing edges, decision branches, loopbacks, and terminal markers |
Entry block |
The authored block where the workflow starts |
Entry phase |
The compiled runtime phase where every run starts |
Terminal phase |
The compiled phase that marks successful completion |
Authored blocks |
The user-authored block names preserved by the policy loader |
Lifecycle completion |
Which compiled phase completes each lifecycle block and advances budget |
Phases |
Each compiled runtime phase with its role, drain, and key routing |
Loop counters |
Iteration counters with their names and caps |
Budget counters |
Outer-progress counters with names and budget-tracking flag |
Terminal outcomes |
All phases declared as terminal with their outcome type |
Parallel execution |
Whether parallel fan-out is configured and its source |
Recovery |
Cycle cap and where terminal failures route |
Workflow diagram¶
The ASCII diagram is the first visual output from --explain-policy. It shows:
Boxed phase nodes — each phase rendered as a box with its name and role
Entry marker —
=ENTRY=>marks the starting phaseHappy-path arrows —
|andvconnect phases on the success pathDecision branches —
+--[decision_name]--> targetshows routing for specific decisionsLoopback arrows —
<<==[loopback]== returns to 'target'marks phases that loop back;>> RE-ENTRY at targetshows the re-entry point so the direction is unambiguousTerminal markers —
==SUCCESS==>or==FAILURE==>marks terminal outcomesFanout annotations —
>>> FAN_OUT (max_workers=N, max_units=M, post_fanout_verify=yes/no) >>>before phases with parallelizationLoop annotations —
[loop: counter=NAME, max=N]on phases with bounded iteration
=ENTRY=>
+----------------+
| planning |
| role=execution |
+----------------+
|
v
>>> FAN_OUT (max_workers=8, max_units=50, post_fanout_verify=no) >>>
+----------------+
| development |
| role=execution |
+----------------+
<<==[loopback]== returns to 'development'
>> RE-ENTRY at development
<<< REJOIN
|
v
[loop: counter=development_analysis_iteration, max=10]
+----------------------+
| development_analysis |
| role=analysis |
+----------------------+
+--[failed]--> development
+--[request_changes]--> development
<<==[loopback]== returns to 'development'
[LOOPBACK: counter=development_analysis_iteration, max=10]
>> RE-ENTRY at development
|
v
+--------------------+
| development_commit |
| role=commit |
+--------------------+
|
v
...
==SUCCESS==>
+-----------------+
| failed_terminal |
| role=terminal |
+-----------------+
==FAILURE==>
Legend:
=ENTRY=> pipeline entry point
==SUCCESS==> terminal success outcome
==FAILURE==> terminal failure outcome
+--[decision]--> analysis decision branch
<<==[loopback]== loopback to earlier phase
+--[workflow_fallback]--> fallback on chain exhaustion
>>> FAN_OUT ... parallel worker fan-out
<<< REJOIN workers rejoin after fan-out
Reading the diagram¶
Glyph |
Meaning |
|---|---|
|
Phase box with name and role |
|
Entry phase — where every run starts |
|
Terminal success — marks a phase declared with |
|
Terminal failure — marks a phase declared with |
|
Loopback edge — routes BACK to phase X on loopback signal |
|
The phase where control re-enters after a loopback |
|
Loopback consumes loop counter N; present when loopback increments a loop counter |
|
Decision branch — routes to Y when decision is emitted |
|
Fan-out — phase fans out to parallel workers |
|
Workers rejoin after fan-out completes |
|
Loop annotation — phase has bounded iteration |
Explanation sentences¶
The structural breakdown appends explanation sentences per phase for every routing surface. Four sentence forms are generated:
Form |
Example |
|---|---|
Decision route |
|
Terminal outcome |
|
Bypass route |
|
Loopback cap |
|
These sentences make it possible to answer “why did Ralph Workflow route here?” from the explanation output alone, without reading pipeline.toml or the runtime code.
Structural breakdown¶
The second section provides the full structured breakdown:
RALPH WORKFLOW — ACTIVE POLICY EXPLANATION
======================================================================
Entry block : developer_iteration
Entry phase : planning
Terminal phase: complete
----------------------------------------------------------------------
AUTHORED BLOCKS
----------------------------------------------------------------------
developer_iteration
planning
planning_analysis
development
development_commit_cleanup
development_commit
development_analysis
development_final_commit_cleanup
development_final_commit
complete
failed_terminal
----------------------------------------------------------------------
LIFECYCLE COMPLETION
----------------------------------------------------------------------
developer_iteration: completion block 'development_final_commit' compiles to phase 'development_final_commit' and increments iteration
before_complete: development_commit_cleanup, development_commit, development_final_commit_cleanup
Terminal outcomes:
success → complete
----------------------------------------------------------------------
PHASES
----------------------------------------------------------------------
Phase: planning [ENTRY]
Role : execution (agent runs code)
Drain : planning
Chain : planning → agents: [claude]
Retry : up to 3 retries per agent, then fail
On success → development
On failure → pipeline fails (no on_failure route)
Phase: development
Role : execution (agent runs code)
Drain : development
Chain : development → agents: [claude, opencode]
Retry : up to 3 retries per agent, then fall back to next agent
On success → development_commit_cleanup
On failure → pipeline fails (no on_failure route)
On loopback → development
Phase: development_commit_cleanup
Role : commit_cleanup (normalizes diff before commit)
Drain : commit
Retry : up to 3 retries per agent, then fail
On success → development_commit
On failure → failed_terminal
On loopback → development_commit_cleanup
Phase: development_commit
Role : commit (agent commits the current development pass)
Drain : development_commit
Chain : development_commit → agents: [claude]
Retry : up to 3 retries per agent, then fail
On success → complete
On failure → failed_terminal
Commit : increments 'iteration'
resets loop counters: ['development_analysis_iteration']
requires artifact: yes
Explanation: after commit phase 'development_commit' with budget_state 'remaining' → routes to 'development_analysis' because the workflow policy declares this post_commit_route
Explanation: after commit phase 'development_commit' with budget_state 'exhausted' → routes to 'development_analysis' because the workflow policy declares this post_commit_route
Explanation: after commit phase 'development_commit' with budget_state 'no_review' → routes to 'development_analysis' because the workflow policy declares this post_commit_route
Phase: development_analysis
Role : analysis (agent reviews the committed development pass)
Drain : development_analysis
Chain : development_analysis → agents: [claude]
Retry : up to 3 retries per agent, then fail
On success → development_final_commit_cleanup
On failure → pipeline fails (no on_failure route)
On loopback → development
Decisions:
completed → development_final_commit_cleanup
request_changes → development
failed → development
Loop : counter='development_analysis_iteration', max=10
Explanation: phase 'development_analysis' routes to 'development_final_commit_cleanup' because the configured decision was 'completed'.
Explanation: phase 'development_analysis' routes to 'development' because the configured decision was 'request_changes'.
Explanation: phase 'development_analysis' routes to 'development' because the configured decision was 'failed'.
Explanation: phase 'development_analysis' loops back to 'development' until 10 attempts are exhausted, after which the run terminates.
Phase: development_final_commit_cleanup
Role : commit_cleanup (final pass before planning resumes)
Drain : commit
Retry : up to 3 retries per agent, then fail
On success → development_final_commit
On failure → failed_terminal
On loopback → development_final_commit_cleanup
Phase: development_final_commit
Role : commit (final post-analysis commit before planning resumes)
Drain : development_commit
Chain : development_commit → agents: [claude]
Retry : up to 3 retries per agent, then fail
On success → complete
On failure → failed_terminal
Commit : increments 'iteration' only when it actually commits
resets loop counters: ['development_analysis_iteration']
requires artifact: yes
Explanation: after commit phase 'development_final_commit' with budget_state 'remaining' → routes to 'planning' because the workflow policy declares this post_commit_route
Explanation: after commit phase 'development_final_commit' with budget_state 'exhausted' → routes to 'complete' because the workflow policy declares this post_commit_route
Explanation: after commit phase 'development_final_commit' with budget_state 'no_review' → routes to 'complete' because the workflow policy declares this post_commit_route
Phase: complete [TERMINAL]
Role : terminal (pipeline ends here)
Drain : complete
Terminal outcome: success
On loopback → complete
Explanation: when reached, the run terminates because the workflow policy declares phase 'complete' as a terminal 'success' outcome.
----------------------------------------------------------------------
LOOP COUNTERS
----------------------------------------------------------------------
development_analysis_iteration: max=10 — Development analysis loop iteration counter
planning_analysis_iteration: max=3 — Planning analysis loop iteration counter
----------------------------------------------------------------------
BUDGET COUNTERS
----------------------------------------------------------------------
iteration: tracked (exhaustion matters) — Development iteration counter (developer cycles)
----------------------------------------------------------------------
PARALLEL EXECUTION
----------------------------------------------------------------------
Fanout phase : development
Max workers : 8
Max work units: 50
Require allowed_directories: yes
When is parallel execution allowed? When the planning artifact declares multiple work_units (up to 50) for phase 'development'.
----------------------------------------------------------------------
RECOVERY POLICY
----------------------------------------------------------------------
Max recovery cycles : 200
Terminal failure route: failed_terminal
Session preserved on: agent
======================================================================
Why this routed here¶
Every routing decision the pipeline makes traces back to a single declared field in
pipeline.toml. The explanation output makes that trace explicit.
Runtime event |
Explanation sentence source |
|---|---|
Analysis decision → phase |
|
Terminal pipeline outcome |
|
Review bypass |
|
Loop cap exhausted |
|
Verification failure |
|
Parallel execution rejected |
Absence of |
Post-commit route |
|
When a run routes somewhere unexpected, run ralph --explain-policy and find the
corresponding Explanation: sentence. The sentence names the exact policy field that
produced the route. If the field is wrong, update pipeline.toml; if the field is
correct but the runtime ignores it, that is a bug.
To confirm which specific decision produced a route, check the run transcript for the
phase’s artifact decision or review outcome, then cross-reference against the matching
Explanation: sentence in the explanation output.
Why this is useful¶
Reading the explanation output answers “what will Ralph Workflow do?” without reading TOML files. It is the machine-enforced statement of how the active policy routes work.
The workflow diagram provides a quick visual overview of the pipeline shape, while the structural breakdown provides complete details for deep inspection.
The explanation output is deterministic: for the same pipeline.toml the output is always
the same. Pin it in a review artifact, CI log, or runbook to record the exact workflow
a run used.
Note: the ASCII example in this document is illustrative. Regenerate it with
ralph --explain-policy when the renderer changes.
How the explanation is generated¶
The command calls ralph.policy.explain.explain_policy() which traverses the validated
PipelinePolicy in memory and produces a PolicyExplanation dataclass. That dataclass
is rendered by ralph.policy.render.render_explanation_ascii() into the ASCII diagram
and by ralph.policy.render.render_explanation_text() into the structured format shown above.
Because it runs against the already-validated policy, an explanation can only be
produced if the policy is complete. If pipeline.toml is invalid, the command exits 1
and prints a PolicyValidationError to stderr instead of partial output.
Q&A¶
Q: How do I prove policy is the source of truth?
A: tests/test_custom_policy_workflow.py constructs a fully renamed policy (phases design/build/audit/sign_off/done, counter cycles, loop audit_round) and exercises the reducer to confirm no built-in name is secretly meaningful.