Last updated: 2026-07-16
A dependency-aware deployment loop with Claude self-healing. Two processes run on two separate machines and coordinate entirely through git commits — no sockets, no HTTP.
[Sender — runner machine] [Receiver — Mac]
sender.py ──── git push ──────────► receiver.py
runs make targets in dep order watches sender-state.json for failures
writes sender-state.json invokes Claude to diagnose + fix
reads receiver-state.json pushes fix, sets fix_pushed=true
retries on fix ◄──── git pull ──────
File ownership is strict:
sender-state.json— written only by the senderreceiver-state.json— written only by the receiver
This eliminates merge conflicts entirely.
sender_resilient.py wraps sender.py — auto-restarts on crash, pulls latest
code before each restart. make loop-start-sender always runs sender_resilient.py.
Single machine: run make loop-start-sender and make loop-start-receiver in
separate terminals on the same machine.
- Full Python implementation — all scripts in Python, no bash
- Strict file ownership — sender writes sender-state.json only, receiver writes receiver-state.json only
- No waiting_for_fix flag — receiver infers sender needs fix from sender-state.json directly via
lib.sender_needs_fix() - Renamed state files —
loop-state.json→receiver-state.json,loop-run-state.json→sender-state.json - Extracted pure functions in receiver.py —
should_invoke_claude(),build_claude_prompt(),gather_previous_logs(),set_fix_pushed(),notify_human()— all testable without subprocess - stop/pause signals —
make loop-stopandmake loop-pausekill local tmux and push signal via git - loop-start-sender / loop-start-receiver split — separate make targets for each side
- Auto-create loop-context.md — created at startup by
sender_resilient.pyif missing - 210 passing tests, 76% coverage — 201 unit tests + 9 integration tests
| File | Coverage |
|---|---|
lib.py |
99% |
receiver.py |
99% |
trim_loop_context.py |
97% |
loop_ack.py |
97% |
loop_reset.py |
97% |
sender_resilient.py |
93% |
loop_pause.py |
95% |
loop_status.py |
95% |
loop_stop.py |
95% |
loop_targets.py |
89% |
sender.py |
89% |
The uncovered lines in loop_ack.py, loop_stop.py, loop_pause.py, and loop_reset.py are the push/retry-on-conflict paths (lines after the first successful git push). These require a live git remote to test properly.
receiver.py lines 72 and 391 — module-level constant initialization and the final time.sleep after an Claude invocation. Both trivial to skip.
| File | Purpose |
|---|---|
bin/lib.py |
Shared library — all pure logic: state I/O, git ops, dep resolution, blocker detection, signal helpers, state transitions |
bin/sender.py |
Core sender loop — runs make <target> in dep order, retries, polls receiver for fix signal |
bin/sender_resilient.py |
Crash-resilient wrapper — restarts sender.py on crash, pulls latest code first, auto-creates loop-context.md |
bin/receiver.py |
Receiver — polls sender-state.json for failures, invokes Claude to fix, sets fix_pushed in receiver-state.json |
bin/loop_ack.py |
Acknowledges human action / resumes after pause |
bin/loop_pause.py |
Sets pause signal in receiver-state.json, kills local tmux |
bin/loop_stop.py |
Sets stop signal in receiver-state.json, kills local tmux |
bin/loop_reset.py |
Resets both state files to blank, commits and pushes |
bin/loop_status.py |
Prints current status from sender-state.json |
bin/trim_loop_context.py |
Caps loop-context.md at 500 lines, archives overflow |
Makefile |
loop-start-sender, loop-start-receiver, loop-attach, loop-stop, loop-pause, loop-status, loop-reset, loop-ack, loop-test |
receiver-state.json |
Template — copy to consuming repo root, edit targets/deps/blocker_patterns |
sender-state.json |
Template — copy to consuming repo root |
tests/test_loop.py |
201 tests — unit tests |
tests/test_integration.py |
9 tests — end-to-end subprocess integration tests |
| File | Owner | Purpose |
|---|---|---|
receiver-state.json |
Receiver | Config: targets, deps, max attempts, blocker patterns, fix signals, stop/pause |
sender-state.json |
Sender | Runtime state: completed/failed targets, attempt counts, status |
loop-context.md |
You + Claude | Claude's memory — failure history and fixes. Auto-created on startup. |
runs/ |
Sender | Per-target logs, resilient wrapper log, Claude invocation logs |
docs/loop-context-archive.md |
Auto | Overflow from loop-context.md when it exceeds 500 lines |
- Push coverage on ack/stop/pause/reset retry paths — requires live git remote or test git server