Skip to main content

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.

CI License: MIT Python 3.11+

中文文档 / Chinese README

30-second demo


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 strict file_ownership boundaries; 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 got rm-ed under it).
  • handoff safe-commit — wraps git commit with cross-process flock + HANDOFF_EXPECTED_FILES invariant + 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 — a PATH-injected git wrapper that physically blocks commit/push/rebase/cherry-pick/reset/revert/tag/am/format-patch/merge inside 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

Status

v0.1.0public 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

handoff_fanout-1.2.1.tar.gz (943.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

handoff_fanout-1.2.1-py3-none-any.whl (59.6 kB view details)

Uploaded Python 3

File details

Details for the file handoff_fanout-1.2.1.tar.gz.

File metadata

  • Download URL: handoff_fanout-1.2.1.tar.gz
  • Upload date:
  • Size: 943.5 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

Hashes for handoff_fanout-1.2.1.tar.gz
Algorithm Hash digest
SHA256 3408bb0b9e34120f065cd99b31084e793eaa69df6d3de5cecc394b67b14defd9
MD5 85dda638ec46c5608f88beee3846444f
BLAKE2b-256 abb62477915aeb466077a8aab134e72de7f0a1841c9fb7addcb4a0c7156921aa

See more details on using hashes here.

File details

Details for the file handoff_fanout-1.2.1-py3-none-any.whl.

File metadata

  • Download URL: handoff_fanout-1.2.1-py3-none-any.whl
  • Upload date:
  • Size: 59.6 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

Hashes for handoff_fanout-1.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3757ea52b9cd818ca37154d24cab76eba834635e18856449e95420773aaebb45
MD5 5959da4263919677011aa610780e870a
BLAKE2b-256 bdcf12592d31ecb2bef8154790a3d3eb62d1a5cfba01957b56fba4238c5ddfd2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page