Case study — autonomous overnight orchestrator
Autonomous Overnight Orchestrator
An engineering associate that works the backlog while I sleep. I tag tasks during the day; overnight a loop on my own machine spawns a fresh Claude Code session per task, each one working in an isolated clone and opening a draft PR. I wake up to a review queue, not a to-do list. And it can't merge, push to main, or lie about whether the tests passed.
in practice
What it's actually like to use
During the day, when something is well-specified enough to hand off, I tag it !lead, with a short spec: the context, the repo and paths, acceptance criteria, how to test it, and any surfaces it's forbidden to touch. Then I stop thinking about it.
Overnight, a loop runs on my own machine. Each pass spawns a fresh agent that reads the backlog, picks a single tagged task, works it in a throwaway clone of the repo, and opens a draft PR with the actual test output pasted in. Then it exits and the loop spawns the next one. By morning I have a queue of reviewable PRs instead of an empty backlog. The well-scoped work is done, each change is isolated to its own branch, and I'm the one who decides what merges.
The hard problem here isn't getting an agent to write code. It's letting one run unattended, against real repos, without it doing something irreversible or quietly shipping work that doesn't actually pass. The entire system is built around that one constraint: it's an associate, not a hero, and every path to production runs through me.
The practical outcome: a well-specified task costs me a morning review instead of an evening of work.
architecture
How it's put together
one night, one loop
by day — you
Tag a task !lead with a rich spec — context, repo, acceptance, tests, and forbidden surfaces.
overnight — the loop
repeats until a brake fires ↺
- 01A fresh session spawns — No memory of the last one — state lives only in git, the journal, and the task list.
- 02Picks ONE tagged task — Reads the backlog, open PRs, and playbooks; selects a single !lead task.
- 03Works it in an isolated clone — Operates from a throwaway clone — never the real working tree — following the task-type playbook.
- 04Opens a draft PR — Branch + draft PR only, with actual command output pasted in. It cannot merge.
- 05Journals, then exits — Appends an audit entry; the wrapper re-spawns the next iteration.
brakes on the loop
by morning — you
A queue of reviewable draft PRs — each verified and journaled. You review and merge; nothing shipped itself.
The loop: a fresh agent every pass
A wrapper script drives the Claude Code CLI in a loop (the "Ralph" pattern). Each iteration spawns a brand-new, stateless session. Its only memory is git history, a running journal, a playbook library, and the shared task list. Statelessness is the point: no context rot across a long night.
One task, one draft PR
Per iteration the agent reads the backlog, picks a single !lead-tagged task, matches it to a playbook (code change, feature port, content draft, report…), and works it to completion or blocks cleanly, always from an isolated clone, always ending in a draft PR. Never a merge, never a push to main.
Six independent brakes
The loop halts on any of: a kill-switch file, a nightly budget cap, a per-task cost cap, a max-iteration count, a stuck-fingerprint match, or a drained queue. A task that fails twice tags itself so future passes skip it. Nothing runs away.
Defense in depth: 7 layers
Prompt rules, filesystem scoping (throwaway clones only), a 115-pattern harness deny-list, two PreToolUse hooks, GitHub branch protection, the wrapper brakes, and a human review gate. No single layer is load-bearing; a failure of one is caught by the next.
Honesty gates it can't skip
After the agent exits, the wrapper independently re-runs the build/lint/ type-check in the clone. Pass clears the PR; fail locks the PR titleand posts the output. A dishonest "all green" can't survive, which makes the honest path the cheap one.
Observability by default
Every iteration writes a journal entry (task, approach, outcome, cost) and a per-night run log; the morning review is one gh pr list. If a session crashes without journaling, the wrapper backfills a stub. No silent failures.
engineering highlights
The parts I'm proud of
It cannot lie about whether the tests passed
An agent's own claim that "tests pass" is worthless if it skipped them when the budget ran low. So verification is a wrapper-level gate that runs after the session exits, independent of anything the agent said. It re-runs the real chain in the clone; a pass clears the PR, a failure locks the title at [BLOCKED-LEAD: verify-failed] and posts the output for review.
# runs AFTER the agent exits — it can't work around this
verify_summary=$(verify-repo.sh "$repo" "$branch" "$workdir") # tsc + lint + build
case "$verify_exit" in
0) # PASS → clear any "verification-skipped" tag, comment "verified ✓"
gh pr edit "$pr" --title "${title/'[BLOCKED-LEAD: verification-skipped]'/}" ;;
1) # FAIL → LOCK the PR so a dishonest "all green" can't slip through
gh pr edit "$pr" --title "[BLOCKED-LEAD: verify-failed] $title" ;;
esac
# "The honest path is always cheaper." A skipped-and-hidden test loses.Stateless by design, with a stuck-detector
Each iteration starts fresh, so nothing degrades over a long run. To catch the opposite failure (burning budget while making no progress), the wrapper fingerprints the world each pass (latest commit + last journal entry + open PRs). Three identical fingerprints in a row means no real work is happening, and the loop halts.
FINGERPRINT_INPUT="${LAST_COMMIT}|${JOURNAL_TAIL}|${OPEN_PRS}"
FINGERPRINT=$(echo -n "$FINGERPRINT_INPUT" | shasum -a 256 | cut -c1-16)
# same fingerprint 3 iterations running → "stuck", halt the loopDangerous commands die at the harness, not on trust
Prompt rules are the soft layer. Underneath, a PreToolUse hook inspects every shell command before it runs and hard-blocks the irreversible ones: merges, force-pushes, main-branch pushes, git add -A, amends, catastrophic deletes. It's cwd-aware, so the same command is fine in a throwaway clone and blocked in a real working tree.
if echo "$COMMAND" | grep -qE 'gh +pr +merge'; then
echo "BLOCKED: Lead opens drafts only; merging is a human action." >&2
exit 2 # non-zero → harness refuses the tool call
fi
# also blocked: git push --force, push origin main (client repos),
# git add -A / --all, git commit --amend, rm -rf on ~ or client treesIt spends real money, so it has a budget
stack
Built with
A Bash wrapper (the loop, brakes, and post-exit gates) · the Claude Code CLI run headless (--print --output-format json --max-budget-usd) · a scoped settings deny-list + two PreToolUse hooks · gh, jq, git · playbooks for each task type · a cloud agent fleet (via MCP) and local executor sub-agents it can call when a task needs them.
Client repo names and task specifics are abstracted here; the loop, brakes, safety model, and verification gates are shown as built.