Project-agnostic auto-handoff & parallel fan-out for AI coding sessions (Claude Code / Cursor / Aider). 5-layer defense against orphan tasks, git index hijack, and stale handoffs.
Project description
handoff-fanout
Project-agnostic auto-handoff & parallel fan-out for AI coding sessions. 5-layer defense against orphan tasks, git index hijack, and stale handoffs.
The problem
You're running an AI coding agent (Claude Code / Cursor / Aider) across multiple IDE tabs, often across multiple projects, on a single workstation. Four pain points show up:
| # | Symptom | Root cause |
|---|---|---|
| 1 | A tab finishes its task; the next task never spawns. | No standard handoff protocol — every project re-invents an ad-hoc baton. |
| 2 | git commit from Tab B silently sweeps in Tab A's git add. |
.git/index is a repo-shared file. Without a lock, add → commit is not atomic. |
| 3 | A session crashes mid-task; downstream sessions inherit a half-baked baseline. | No durable "last good baseline" record + no orphan detector. |
| 4 | Want to split one task into N parallel sub-tasks but can't coordinate the merge. | No fan-out/fan-in primitives that respect file ownership. |
handoff-fanout is a small, zero-runtime-dependency Python toolkit that solves all four. It was extracted from a year-old production ERP project where these failures cost real hours, and it's been hardened by three documented commit-hijack incidents (now blocked at four independent layers).
What you get
handoff dump— atomically write a queue file describing the next task; the IDE auto-spawn helper picks it up and launches a new tab.handoff dump --open-batch— fan-out: split one task into N sub-tasks, each with strictfile_ownershipboundaries; a fan-in tab consolidates results.handoff watchdog— fail-safe scanner: triggers fan-in when the last sub-task finishes; flags orphan tabs (e.g. a tab spawned by launchd but whose batch dir gotrm-ed under it).handoff safe-commit— wrapsgit commitwith cross-processflock+HANDOFF_EXPECTED_FILESinvariant + pre-commit hook integration. No more hijack.handoff heartbeat— fan-in tab heartbeat daemon, Amdahl-speedup metrics, and runtime calibration so your next batch's split decision is data-driven.handoff git-guard— aPATH-injectedgitwrapper that physically blockscommit/push/rebase/cherry-pick/reset/revert/tag/am/format-patch/mergeinside sub-task tabs. The fan-in tab is the only committer.
5-layer defense (the headline)
┌──────────────────────────────────────────────────┐
│ Layer 1 — git-guard (PATH-injected git wrapper) │
│ Sub-task tabs literally cannot run `git commit`.│
└──────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Layer 2 — pre-commit hook (HANDOFF_EXPECTED_FILES)│
│ Rejects commits whose staged set ≠ expected set. │
└────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ Layer 3 — safe-commit wrapper (flock + invariant) │
│ Cross-process lock on ~/.handoff/git-commit.lock. │
│ Verifies `git diff --cached --name-only` ⊆ expected. │
└─────────────────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ Layer 4 — atomic file primitives (queue / batch writes) │
│ atomic_create / write_with_fsync / acquire_dir_lock. │
│ No half-written queue files. No torn batch manifests. │
└────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────┐
│ Layer 5 — watchdog (orphan / stale / heartbeat scan) │
│ Runs from launchd/cron. Re-triggers fan-in if last-one-out │
│ silently dies. Marks orphan sub-task tabs BLOCKED. │
└──────────────────────────────────────────────────────────────┘
Each layer is independent. Defeating one is not enough — for a hijack to land, all four commit-path layers (1-4) must fail simultaneously.
Quickstart
pip install handoff-fanout
# Idempotent install: symlinks bin/ → ~/.local/bin, generates ~/.handoff/config.json,
# installs git hooks, optionally installs launchd plist (macOS).
curl -L https://raw.githubusercontent.com/rssprivacy-commits/handoff-fanout/main/install/install.sh | bash
# Dump the next task from your current project repo:
cd ~/Projects/my-repo
handoff dump \
--task fix-bug-123 \
--next "Fix the off-by-one in the discount calculation." \
--status active \
--tests "tests/test_discount.py"
A new file appears at ~/.handoff/my-repo/queue/fix-bug-123.md, the launchd watcher picks it up within one second, and a fresh IDE tab opens already pointed at your repo with that handoff loaded.
How it compares
| handoff-fanout | Celery | Argo Workflows | Temporal | |
|---|---|---|---|---|
| Target environment | One workstation, multiple IDE tabs | Distributed service, many workers | Kubernetes cluster | Distributed service |
| Coordination unit | An AI coding tab | A Python function | A pod | A workflow function |
| State store | Plain files under ~/.handoff/ |
Redis / RabbitMQ broker + result backend | etcd + K8s objects | Cassandra / MySQL + Temporal server |
| External dependencies | None (zero-dep Python) | Broker + (often) Redis result backend | Full K8s cluster | Temporal server + DB |
| Failure model | Atomic file writes + file locks + watchdog | Broker durability + ack semantics | K8s controller reconciliation | Event-sourced durable execution |
| Cross-process commit safety | First-class (4 layers) | Out of scope | Out of scope | Out of scope |
| Fan-out / fan-in | Yes, with file-ownership boundaries | Yes (canvas: group/chord/chain) | Yes (DAG) | Yes (child workflows) |
| Setup time | pip install + install.sh |
Hours (broker, workers, monitoring) | Days (cluster + manifests) | Hours (server + workers) |
| Designed for | AI coding-session orchestration | Background-job queues | CI/CD & ML pipelines | Long-running business workflows |
The right way to read this: handoff-fanout is not a competitor to Celery / Argo / Temporal — it occupies the corner of the design space those tools deliberately don't enter (one workstation, IDE-tab granularity, no broker, git-aware). If you need durable distributed workflow execution, pick one of those instead.
Documentation
- docs/PROTOCOL.md — queue file format, state machine, atomicity guarantees.
- docs/ARCHITECTURE.md — full 5-layer defense walk-through with sequence diagrams.
- CONTRIBUTING.md — dev setup, test layout, PR conventions.
- CHANGELOG.md — version history & extraction roadmap.
Status
v0.1.0 — public extraction in progress. Source modules ported. Bilingual docs, installer, and CI matrix are part of the v1.0.0 milestone (this branch). The original implementation has been in daily production use since 2024-Q2 across an ERP project with 70+ DB tables and 250+ tests.
Once v1.0.0 ships, the ERP repo will migrate to a thin shim that delegates to this package.
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file handoff_fanout-1.5.1.tar.gz.
File metadata
- Download URL: handoff_fanout-1.5.1.tar.gz
- Upload date:
- Size: 982.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3885e255472dc764aef3856146f93bfe3a04b03ab9014fc2c78697f3f88f3ea2
|
|
| MD5 |
64446acda8160fc260e4d52881a1475b
|
|
| BLAKE2b-256 |
7a5c605dbc104c2fa07e235fbe3f0597b1a28ac109d5b54d6f484dc52b2e5076
|
File details
Details for the file handoff_fanout-1.5.1-py3-none-any.whl.
File metadata
- Download URL: handoff_fanout-1.5.1-py3-none-any.whl
- Upload date:
- Size: 69.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.10.9 {"installer":{"name":"uv","version":"0.10.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1fb7a67e6809501804f29e88524018f5f56ad991b2bf0fe6f63b4bc366048cb5
|
|
| MD5 |
5355ecea64e11b4edb787cf82a5fe721
|
|
| BLAKE2b-256 |
ed208df48394f2aab52b77009ded8dfcaa96fdcf76c384b2c2aa9f4db50866ab
|