Appearance
An ADW Script is a deterministic Python shell around one non-deterministic Claude Code call. The script makes the agent's work repeatable, composable, and observable.
Primitive
ADW Script
The core shape
Deterministic wrapper. Non-deterministic core.
Every ADW Script has the same structure: deterministic Python on both sides of one subprocess call to Claude.
1.load_dotenv() · parse args
2.ADWState.load() or create() — resume or start fresh
3.check_env_vars() · validate before invoking
4.make_issue_comment("🚀 Starting phase")
🤖
subprocess.run(["claude", "-p", "/command {args}"])
One call. The only unpredictable line in the file.
5.check returncode · gate on success
6.state.save() · persist progress for next phase
7.make_issue_comment("✅ Phase complete")
The agent is the only source of variance. Everything around it is a contract — validated inputs, checked exit codes, persisted state, observable events.
Memory across phases
ADWState is how phases hand off to each other.
Each phase reads the prior state, does its work, writes an updated state. One JSON file per workflow.
adw_plan.py
+ branch_name+ plan_file
→
adw_build.py
+ worktree_path+ commit_sha
→
adw_test.py
+ test_results+ pr_url
💾
agents/{adw_id}/adw_state.json
Single source of truth for the whole pipeline run.
issue_number · branch_name · plan_file · worktree_path · adw_ids[]
Build phase fails halfway? Resume from state. Re-running doesn't re-plan. State is what makes the pipeline restartable from any checkpoint.
Composition
Phases compose at the shell level — not in the prompt.
Combined scripts are sequencing wrappers. Each phase stays single-purpose.
uv run adw_plan.py 123
plan
uv run adw_plan_build.py 123
plan→build
uv run adw_plan_build_test.py 123
plan→build→test
Composed scripts call the phase functions, not the individual scripts.
Each phase remains independently invokable — the composition is mechanical sequencing, not a merged prompt.
Observability
The GitHub issue is the console.
Async, out-of-loop execution needs an async, out-of-loop reporting channel. make_issue_comment is that channel.
Issue #123 — "Add session token validation"
adw-a7f3_ops:🚀 Starting plan
adw-a7f3_ops:✅ plan complete
adw-a7f3_ops:🚀 Starting build
adw-a7f3_ops:📦 worktree created at /tmp/wt-a7f3
adw-a7f3_ops:✅ build complete
adw-a7f3_ops:🚀 Starting test
adw-a7f3_ops:❌ test failed — 2 assertions
Every comment prefixed with adw_id so parallel pipelines don't create an unreadable thread.
Triggers, crons, and humans all watch the same stream.
The catalog
The scripts in the Code Factory today.
adw_plan.py
issue number
spec in specs/
adw_build.py
adw_id + plan_file
code changes committed
adw_test.py
adw_id
test results on issue
adw_plan_build.py
issue number
plan + implementation
adw_plan_build_test.py
issue number
plan + impl + tests
Pink rows are composed scripts — they sequence the purple single-phase scripts.
What breaks if done wrong
Each failure mode is a missing contract in the shell.
✗Missing check_env_vars
Script runs against missing API keys. Cryptic subprocess errors, no meaningful failure message.
✗No state persistence
Build fails halfway → must re-run the plan phase. State is what lets you restart from the checkpoint.
✗Blocking subprocess
Long builds block the trigger loop. Use subprocess.Popen() for detached execution.
✗No returncode check
Script proceeds to next phase with bad output. Always gate phase transitions on exit code.
The discipline
Wrap every non-deterministic call in a deterministic shell.
The agent is one subprocess line. Everything else — state, validation, observability, error handling — is Python you can trust. That's how you get a pipeline instead of a pile of prompts.