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.3.0.tar.gz (956.9 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.3.0-py3-none-any.whl (60.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: handoff_fanout-1.3.0.tar.gz
  • Upload date:
  • Size: 956.9 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.3.0.tar.gz
Algorithm Hash digest
SHA256 4186064dfb7ccb044330737b5bc915707b31cbb32f8ccb17ee1c29b4b8def898
MD5 d7a6bf3ef9c2f381831dae18920a536c
BLAKE2b-256 23fbde1ea6006d4db2b722c13c2bb0fff6beea40af06aa2053c68fe688ec6cfa

See more details on using hashes here.

File details

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

File metadata

  • Download URL: handoff_fanout-1.3.0-py3-none-any.whl
  • Upload date:
  • Size: 60.7 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.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c9a00d06ec337284f7682bf359df39fa1aada9bd5f6cff5da5f617abe64a4e6a
MD5 78b1b8dc98b74c60d7cd901ddbd59a99
BLAKE2b-256 205ac2d46c1e548d987829b762c3d00b958b12bf91c180e2e0e2bfea308be947

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