Portable orchestration for step-based workflows (supervisor + loop runners) with configurable prompt sequencing.
This package can be used as a git submodule across multiple projects. Each project provides its own orchestration.yaml configuration file.
git submodule add <remote-url> scripts/orchestrationCreate orchestration.yaml in your project root:
# Workflow sequencing
workflow:
name: standard # standard | review_cadence
review_every_n: 0 # cadence cycles (review_cadence only)
# Prompt paths
prompts_dir: prompts
supervisor_prompt: supervisor.md
main_prompt: main.md
reviewer_prompt: reviewer.md
# State management
state_file: sync/state.json
# Doc/meta auto-commit whitelist (glob patterns)
doc_whitelist:
- input.md
- galph_memory.md
- docs/fix_plan.md
- plans/**/*.md
- prompts/**/*.md
# Tracked output globs for auto-commit
tracked_output_globs:
- tests/fixtures/**/*.npy
- tests/fixtures/**/*.npz
# Key file paths
findings_file: docs/findings.md
input_file: input.md
# Directories
logs_dir: logs
tmp_dir: tmp
# Router (optional)
router:
enabled: false
mode: router_default # router_default | router_first | router_only
prompt: router.md
review_every_n: 0
allowlist:
- supervisor.md
- main.md
- reviewer.md
# Agent dispatch (optional)
agent:
default: auto # auto | claude | codex
roles:
supervisor: claude
loop: codex
prompts:
supervisor.md: codex
main.md: claudeThe config is loaded by searching upward from CWD for orchestration.yaml. If not found, sensible defaults are used.
For bootstrapping specs from an existing implementation:
spec_bootstrap:
templates_dir: ~/Documents/project-templates
specs:
dir: specs # Canonical location; templates discovered from templates_dir/specs (fallback: templates_dir/docs/spec-shards)
implementation:
dirs:
- src/
exclude:
- "**/__pycache__/**"
- "**/tests/**"
scoring:
coverage: 80
accuracy: 85
consistency: 90
state_file: sync/spec_bootstrap_state.json
prompts:
reviewer: spec_reviewer.md
writer: spec_writer.mdNote: If the specs.dir key is omitted, it defaults to specs/. Spec shard templates are discovered from templates_dir/specs/*.md with a fallback to templates_dir/docs/spec-shards/*.md for legacy template layouts. See docs/index.md for the full documentation map.
- Two runners:
supervisor.sh→scripts/orchestration/supervisor.py(even step_index)loop.sh→scripts/orchestration/loop.py(odd step_index)
- Combined entrypoint:
orchestrator.sh→scripts/orchestration/orchestrator.py(runs both steps sequentially)
- Modes:
- Async: local, back‑to‑back steps.
- Sync via Git: strict step handoff using
sync/state.jsoncommitted and pushed between machines.
- Wrappers call Python by default; set
ORCHESTRATION_PYTHON=0to force legacy bash logic.
- Path:
sync/state.json(tracked and pushed so both machines see updates) - Fields:
workflow_name(string; default:standard)step_index(int, 0‑based)iteration(int; legacy alias forstep_index + 1)expected_step(string; selected prompt for the current step)status("idle" | "running" | "waiting-next" | "complete" | "failed")last_update,lease_expires_at(ISO8601)galph_commit,ralph_commit(short SHAs)last_prompt(string; set by router-enabled runs only)
- Always operate on the intended branch; pass
--branch <name>to both runners. - The orchestrators will abort if the current branch is not the specified one.
- Pushes use explicit refspecs:
git push origin HEAD:<branch>to avoid cross‑branch mistakes.
- Iterations: 20 (
--sync-loops Nto change) - Poll interval: 5s (
--poll-interval S) - No max wait by default (
--max-wait-sec Sto enable) - Workflow:
standard(supervisor → main). Setworkflow.name: review_cadenceandworkflow.review_every_nto insert review cycles. - Per‑iteration logs under
logs/(see Logging).
Deterministic routing selects the per-step prompt using sync/state.json plus optional review cadence.
- Deterministic routing uses
workflow_name+step_indexto select a prompt. - Review cadence: in the
review_cadenceworkflow, whenreview_every_n > 0and a cadence cycle hits, both steps in that cycle runreviewer.md.- Example (
review_every_n=2): supervisor, main, reviewer, reviewer, supervisor, main, ...
- Example (
- Allowlist enforcement: selected prompts must be in the allowlist and exist on disk.
- Optional router prompt override: if configured, a router prompt runs after deterministic selection and may override it.
- Router output must be a single, non-empty line naming a prompt file.
- Invalid output (empty, missing, or not allowlisted) aborts the run with a descriptive error.
- Router modes:
router_default: deterministic selection first; router prompt (if configured) may override.router_first: router prompt runs first when configured; otherwise deterministic selection is used.router_only: router prompt required; deterministic selection is never used.
- State annotation: when router is enabled, the final selected prompt is stored in
sync/state.jsonaslast_promptonly. - No actor gating: router overrides apply to every step (combined/sync), not just supervisor/loop.
Implementation lives in scripts/orchestration/router.py with a thin wrapper scripts/orchestration/router.sh.
You can route different CLIs per role or per prompt. Role keys are runner labels (supervisor/loop) and do not influence prompt selection. Legacy galph/ralph keys are accepted as aliases. Resolution precedence:
- CLI prompt map (
--agent-prompt) - CLI role map (
--agent-role) - YAML prompt map (
agent.prompts) - YAML role map (
agent.roles) - Default (
--agentoragent.default)
Prompt keys are normalized to .md and matched relative to prompts_dir (e.g., supervisor.md, subdir/debug.md).
CLI usage examples:
./orchestrator.sh --mode combined --agent codex \
--agent-role supervisor=claude,loop=codex \
--agent-prompt reviewer.md=claudeEnvironment variables:
- Combined:
ORCHESTRATOR_AGENT_ROLE,ORCHESTRATOR_AGENT_PROMPT - Supervisor:
SUPERVISOR_AGENT_ROLE,SUPERVISOR_AGENT_PROMPT - Loop:
LOOP_AGENT_ROLE,LOOP_AGENT_PROMPT
- The supervisor auto‑stages/commits a limited set of doc/meta paths at end of turn to keep the tree clean.
- Default whitelist includes:
input.md,galph_memory.md,docs/fix_plan.md,plans/**/*.md,prompts/**/*.md, and core Git meta files:.gitignore,.gitmodules,.gitattributes. - Rationale: allow intentional repo‑hygiene edits made by the supervisor without tripping the post‑run guard. Override via
--autocommit-whitelistif needed.
- Preconditions:
- Both machines share the same remote and branch (e.g.,
feature/spec-based-2). - Ensure
sync/state.jsonexists; setworkflow_nameandstep_index(even = supervisor starts).
- Both machines share the same remote and branch (e.g.,
- Start supervisor (even step index):
ORCHESTRATION_BRANCH=feature/spec-based-2 ./supervisor.sh --sync-via-git --branch feature/spec-based-2 --sync-loops 20 --logdir logs --verbose --heartbeat-secs 10
- Start loop (odd step index):
ORCHESTRATION_BRANCH=feature/spec-based-2 ./loop.sh --sync-via-git --branch feature/spec-based-2 --sync-loops 20 --logdir logs
- Optional wrapper (role-gated orchestrator):
./orchestrator.sh --mode role --role galph --sync-via-git --branch feature/spec-based-2 --sync-loops 20 --logdir logs ./orchestrator.sh --mode role --role ralph --sync-via-git --branch feature/spec-based-2 --sync-loops 20 --logdir logs
- Handshake:
- Supervisor writes:
status=waiting-next, incrementsstep_indexon success. - Loop writes:
status=complete, incrementsstep_indexon success. - Supervisor advances when it observes an even
step_indexthat has advanced.
- Supervisor writes:
- Run without
--sync-via-gitto execute N iterations locally (still writes per‑iteration logs):./supervisor.sh --sync-loops 5 --logdir logs ./loop.sh --sync-loops 5 --logdir logs
Run both actors sequentially in a single process:
./orchestrator.sh --mode combined --sync-loops 5 --logdir logsRouter notes for combined mode:
- Review cadence is driven by the workflow (review cycles replace both steps when enabled).
- Router overrides (router prompt output) are applied to every step.
- Combined mode can auto-commit doc/meta, reports, and tracked outputs using supervisor defaults.
- Dirty non-whitelist paths are logged as warnings only (no hard failure).
- Auto-commit messages include the role prefix (
SUPERVISOR AUTOorRALPH AUTO), the prompt name, and the iteration tag (for example,prompt=reviewer.mdanditer=00017). - Use
--commit-dry-runto log what would be committed without staging. - Use
--no-gitto skip all git operations in combined mode.
- Descriptive per‑iteration logs:
- Step logs:
logs/<branch>/steps/iter-00017_step-0_YYYYMMDD_HHMMSS.log - Next step:
logs/<branch>/steps/iter-00017_step-1_YYYYMMDD_HHMMSS.log
- Step logs:
- Configure base directory with
--logdir PATH(defaultlogs/). - If you generate markdown summaries, keep them next to the raw logs and follow
docs/logging/log_summary_conventions.mdas needed. - Supervisor console options:
--verbose: print state changes to console and log--heartbeat-secs N: periodic heartbeat lines while polling
- Prompt execution heartbeat:
ORCHESTRATION_PROMPT_HEARTBEAT_SECS(default10) emits periodic console/log lines when a prompt produces no output.- Set to
0to disable prompt heartbeats entirely.
- Prompt output buffering:
ORCHESTRATION_PYTHONUNBUFFERED(default1) exportsPYTHONUNBUFFERED=1for agent CLIs.ORCHESTRATION_USE_STDBUF(default1) wraps agent CLIs withstdbuf -oL -eLwhen available.
- Prompt streaming:
ORCHESTRATION_CLAUDE_STREAM_JSON=1runs Claude with--output-format stream-json+--include-partial-messages, usingscripts/orchestration/claude_stream_runner.pyto emit text and terminate aftermessage_stop.ORCHESTRATION_ENGINEER_SUMMARY_PATH(defaultengineer_summary.md) lets the stream runner exit once the engineer summary file is created or updated (fallback if stream-json parsing missesmessage_stop).ORCHESTRATION_CODEX_JSON=1adds--jsontocodex exec(JSONL events to stdout).ORCHESTRATION_CLAUDE_SESSION_PERSIST=1enables session persistence (default is disabled with--no-session-persistence).
- PTY selection:
ORCHESTRATION_PTY_MODE=auto(default) disables PTY for Claude (uses pipes) and keeps the default PTY behavior for other agents.ORCHESTRATION_PTY_MODE=alwaysforces PTY for all agents.ORCHESTRATION_PTY_MODE=neverdisables PTY for all agents.
logs/is ignored by Git.
Note: tail_interleave_logs currently targets the legacy galph/ralph log layout; step-based logs will need a follow-up update.
Use the helper script to interleave the last N galph/ralph logs (or markdown summaries) for a branch prefix. Entries are matched on iteration number and wrapped in an XML-like tag with CDATA. The tool annotates each log with the post-state commit that stamped the handoff and can optionally snapshot selected directories from that commit:
python -m scripts.orchestration.tail_interleave_logs feature-spec-based-2 -n 3
# Summaries instead of raw logs:
python -m scripts.orchestration.tail_interleave_logs feature-spec-based-2 -n 3 --source summariesOutput structure:
<logs prefix="feature-spec-based-2" count="3" source="logs">
<log role="galph" iter="141" path="logs/feature-spec-based-2/galph/iter-00141_....log" source="log" format="text" commit="abc1234" commit_subject="[SYNC i=141] actor=galph → next=ralph status=ok ...">
<![CDATA[
...
]]>
<ls path="docs" commit="abc1234">
<![CDATA[
docs/architecture/pytorch_design.md
...
]]>
</ls>
</log>
<log role="ralph" iter="141" path="logs/feature-spec-based-2/ralph/iter-00141_....log" source="log" format="text" commit="def5678" commit_subject="[SYNC i=142] actor=ralph → next=galph status=ok ...">
<![CDATA[
...
]]>
<!-- Optional ls-tree snapshots repeat for each requested root -->
</log>
...
</logs>Flags of note:
-n/--counttail length (0 = all matching iterations)--min-iter/--max-iternumeric bounds on iteration selection--no-lsdisables the commitls-treesnapshots--ls-paths docs,plans,reportsoverrides which repository roots are listed whenlsoutput is enabled--source {logs,summaries}switches between raw log files and markdown summaries--roles galph,ralphnarrows the interleaved output to a subset of actors (order preserved)
Use the stamper to advance sync/state.json step index and publish without executing a supervisor/loop body:
# Supervisor stamps success (advances to next step)
python -m scripts.orchestration.stamp_handoff galph ok --branch feature/spec-based-2
# Supervisor marks failure (no handoff)
python -m scripts.orchestration.stamp_handoff galph fail --branch feature/spec-based-2
# Loop stamps success (advances to next step)
python -m scripts.orchestration.stamp_handoff ralph ok --branch feature/spec-based-2
# Loop marks failure (no increment)
python -m scripts.orchestration.stamp_handoff ralph fail --branch feature/spec-based-2Flags:
--no-pullto skip pre-stamp pull;--no-pushto skip push (local-only)--allow-dirtyto bypass dirty-tree guard (not recommended)
Notes:
- Messages and step semantics match the orchestrators: supervisor stamps
waiting-nextand incrementsstep_indexon success; loop stampscompleteand incrementsstep_indexon success. - The tool updates
last_update,lease_expires_at, andgalph_commit/ralph_commitusing the current HEAD.
-
Supervisor
--sync-via-git·--sync-loops N·--poll-interval S·--max-wait-sec S--branch NAME(abort if not on this branch)--logdir PATH(per‑iteration logs)--workflow NAME·--workflow-review-every-n N(workflow sequencing + review cadence)--verbose·--heartbeat-secs N--auto-commit-docs/--no-auto-commit-docs(default: on)- When enabled, supervisor will auto‑stage+commit changes limited to a doc/meta whitelist after a successful run and before handing off:
- Whitelist (globs):
input.md,galph_memory.md,docs/fix_plan.md,plans/**/*.md,prompts/**/*.md - Files must be ≤
--max-autocommit-bytes(default 1,048,576 bytes) - Any dirty tracked changes outside the whitelist cause a clear error and the handoff is aborted (no state flip)
- Whitelist (globs):
- Configure whitelist via
--autocommit-whitelist a,b,cand size via--max-autocommit-bytes N
- When enabled, supervisor will auto‑stage+commit changes limited to a doc/meta whitelist after a successful run and before handing off:
--tolerate-doc-dirtyto log non-whitelisted dirty paths and continue the handoff (doc/meta auto-commit still runs)- Reports auto-commit (publishes supervisor evidence by file type)
--auto-commit-reports/--no-auto-commit-reports(default: on)--report-extensions ".png,.jpeg,.npy,.txt,.md,.json,.log,.py,.c,.h,.sh"— allowed file types (logs + source files/scripts)--report-path-globs "glob1,glob2"— optional glob allowlist (default allows any path); logs/tmp/are always skipped--max-report-file-bytes N(default 5 MiB) ·--max-report-total-bytes N(default 20 MiB)--force-add-reports(default: on) — force-add files even if ignored by .gitignore- Notes: stamp-first handoff ensures reports + state publish together; adjust caps/extension list / path globs as needed for your workflow.
- Tracked outputs auto-commit (publishes modified tracked artifacts like fixtures)
--auto-commit-tracked-outputs/--no-auto-commit-tracked-outputs(default: on)--tracked-output-globs "tests/fixtures/**/*.npy,tests/fixtures/**/*.npz,tests/fixtures/**/*.json,tests/fixtures/**/*.pkl"— path allowlist (glob); only tracked modifications are considered--tracked-output-extensions ".npy,.npz,.json,.pkl"— allowed extensions--max-tracked-output-file-bytes N(default 32 MiB) ·--max-tracked-output-total-bytes N(default 100 MiB)- Notes: runs before doc/meta hygiene; keeps repo clean when fixture‑like binaries are legitimately regenerated during a supervisor loop. Files exceeding caps remain dirty and will trigger the whitelist guard (handoff abort).
--prepull-auto-commit-docs/--no-prepull-auto-commit-docs(default: on)- If the initial git pull fails (e.g., due to local modified files), supervisor now follows a three-step recovery:
- Submodule scrub:
git submodule sync --recursivethengit submodule update --init --recursive --checkout --force(with manual gitlink align fallback) - Tracked outputs auto-commit: stage+commit modified fixture-like files within limits (default globs
tests/fixtures/**/*.npy,*.npz,*.json,*.pkl) - Doc/meta whitelist auto-commit: stage+commit changes to
input.md,galph_memory.md,docs/fix_plan.md,plans/**/*.md,prompts/**/*.mdwithin size caps
- Submodule scrub:
- The pull is retried after each step; if dirty paths remain outside these guards, the supervisor exits with a clear error.
- If the initial git pull fails (e.g., due to local modified files), supervisor now follows a three-step recovery:
-
Loop
--sync-via-git·--sync-loops N·--poll-interval S·--max-wait-sec S--branch NAME·--logdir PATH--workflow NAME·--workflow-review-every-n N(workflow sequencing + review cadence)--allow-dirty(default: off) to continue when git pull fails (not recommended)- Reports auto-commit (publishes loop evidence by file type)
--auto-commit-reports/--no-auto-commit-reports(default: on)--report-extensions ".png,.jpeg,.npy,.log,.txt,.md,.json,.py,.c,.h,.sh"— allowed file types (including code diffs/scripts)--report-path-globs "glob1,glob2"— optional glob allowlist (default allows any path); logs/tmp/are always skipped--max-report-file-bytes N(default 5 MiB) ·--max-report-total-bytes N(default 20 MiB)--force-add-reports(default: on) — force-add files even if ignored by .gitignore- Notes: stamp-first handoff ensures reports + state publish together; adjust caps/extension list / path globs as needed for your workflow.
-
Orchestrator (combined mode)
--no-git·--commit-dry-run--workflow NAME·--workflow-review-every-n N(workflow sequencing + review cadence)--auto-commit-docs/--no-auto-commit-docs·--autocommit-whitelist·--max-autocommit-bytes--auto-commit-reports/--no-auto-commit-reports·--report-extensions·--report-path-globs--auto-commit-tracked-outputs/--no-auto-commit-tracked-outputs·--tracked-output-globs·--tracked-output-extensions- Notes: local-only auto-commit uses supervisor defaults; best-effort warnings on non-whitelist dirt.
- No live console output: the runner uses a pseudo‑TTY by default to encourage streaming from agent CLIs. If you need the old pipe behavior, set
ORCHESTRATION_USE_PTY=0. - Pull failures: both orchestrators now fail fast on git pull errors (including untracked‑file or local‑modification collisions). Read the console/log message, resolve locally (commit/stash/move), and rerun.
- Submodule pointer drift: if
.claude/or other gitlinks appear dirty, the supervisor auto-scrubs submodules (sync + update with--checkout --force) before retries. This is idempotent and does not commit pointer bumps; it aligns worktrees to the recorded superproject commits. - Push rejected / rebase in progress: orchestrators auto‑abort in‑progress rebase before pulling. If conflicts arise, fix them locally, commit, and rerun.
- Branch mismatch: checkout the correct branch or adjust
--branch. - Missing prompt: ensure
prompts/<name>.mdexists (default ismain).
loop.sh,supervisor.sh, andinput.mdare treated as protected entrypoints elsewhere in the project. Keep wrappers; they manage env and call Python modules by default.