Skip to content

Latest commit

 

History

6 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

pi-dispatch

Orchestrate teams of pi coding agents with DAG-aware dependency resolution, git worktree isolation, automatic retry, and structured JSON output.

Table of Contents


What Is This?

pi-dispatch is a skill for agentic coding apps — primarily Claude Code, but also compatible with any tool that supports the ~/.agents/skills/ convention.

It addresses a problem: Claude Code subagents are becoming prohibitively expensive. So it learns Claude to effectively delegate work outside Anthropic's ecosystem.

You install the skill and configure which models to use. When you give your coding agent a task, it reads the skill instructions and automatically:

  1. Decomposes your task into a dependency graph of subtasks
  2. Writes a dispatch plan (JSON) describing which agents to spawn, in what order, with what prompts
  3. Runs the orchestrator, which executes agents in parallel waves inside isolated git worktrees
  4. Merges the results back into your branch — or reverts cleanly if anything fails

You never write dispatch JSON by hand. Your agent does.

When to Use

Use pi-dispatch when:

  • A coding task can be decomposed into subtasks (e.g., research → implement → test → document)
  • Subtasks have ordering constraints (A must finish before B starts)
  • You want parallel execution of independent subtasks
  • You need isolation so agents don't interfere with each other's files
  • You want automatic rollback if anything fails

Use the native subagents instead when:

  • You're spawning Claude subagents (sonnet/opus/haiku)
  • The task is simple enough for a single agent

Installation

Prerequisites

  • Python 3.12+
  • Git (for worktree support)
  • pi CLI installed and configured
  • An OpenRouter API key (or another provider configured in config.json)

Install Script

The included install.sh detects which agentic apps you have installed and lets you choose where to install the skill:

git clone <this-repo-url>
cd pi-dispatch-skill
chmod +x install.sh
./install.sh

The script will:

  1. Detect installed apps — checks for pi, Claude Code, Codex, and the universal ~/.agents/skills/ directory
  2. Let you select targets — choose one or more apps (e.g., "1 2" or "all")
  3. Choose install method:
    • Copy — snapshot the skill into each app's skills directory; re-run to upgrade
    • Symlink — create a live link back to this repo; changes in the repo are reflected immediately (repo path must remain stable)

Install Locations

App Target Path
Universal ~/.agents/skills/pi-dispatch/
Pi ~/.pi/agent/skills/pi-dispatch/
Claude Code ~/.claude/skills/pi-dispatch/
Codex ~/.agents/skills/pi-dispatch/

Manual Install

If you prefer manual installation, simply copy the pi_dispatch/ directory to the appropriate skills path for your app:

cp -r pi_dispatch/ ~/.pi/agent/skills/pi-dispatch/

Configuration

Edit pi_dispatch/config.json to customize model routing and defaults:

{
  "provider": "openrouter",
  "default_timeout_minutes": 5,
  "task_routing": {
    "hard": { "pi_tier": "hard" },
    "moderate": { "pi_tier": "hard" },
    "easy": { "pi_tier": "easy" },
    "mechanical": { "pi_tier": "easy" },
    "verification": { "pi_tier": "easy" }
  },
  "pi_models": {
    "hard": ["xiaomi/mimo-v2.5-pro", "z-ai/glm-5.1"],
    "easy": [
      "qwen/qwen3.6-plus",
      "deepseek/deepseek-v4-pro",
      "z-ai/glm-5-turbo"
    ]
  }
}

Fields

Field Description
provider Default model provider (e.g., openrouter)
default_timeout_minutes Default timeout per agent if not specified in dispatch JSON
task_routing Maps task type names to model tiers. Keys become valid task_type values in dispatch JSON.
pi_models Model pools keyed by tier name. Keys must match the pi_tier values referenced in task_routing.

Labels Are Arbitrary

Both task_routing keys and pi_models keys are arbitrary strings — not built-in constants. You can name them whatever you want, as long as they're consistent:

  • Every key in task_routing becomes a valid task_type for your agent to use in dispatch JSON
  • Every pi_tier value in task_routing must have a matching key in pi_models

For example, this is equally valid:

{
  "task_routing": {
    "complex": { "pi_tier": "premium" },
    "simple": { "pi_tier": "budget" },
    "review": { "pi_tier": "budget" }
  },
  "pi_models": {
    "premium": ["anthropic/claude-sonnet-4", "openai/o3"],
    "budget": ["qwen/qwen3.6-plus", "deepseek/deepseek-v4-pro"]
  }
}

Example Task Types

The names below are just a suggested convention from the default config — not required:

Type Tier Use Case
hard hard Complex reasoning, architecture, refactoring
moderate hard Non-trivial implementation tasks
easy easy Straightforward tasks, research, testing
mechanical easy Repetitive tasks, documentation, formatting
verification easy Review, validation, test running

The tier determines which model pool is used. Within a pool, models are selected via round-robin across parallel agents, and the next model is tried on retry.


How Your Agent Uses It

When you give your coding agent a task, here's what happens behind the scenes:

1. Your agent reads the skill

The agent's context includes SKILL.md — the skill instructions that teach it how to use pi-dispatch. It learns to decompose tasks, write dispatch JSON, and run the orchestrator.

2. Your agent decomposes the task

The agent analyzes your request and breaks it into subtasks with dependency ordering. For example, given "Add dark mode toggle to settings page", it might plan:

Wave 0: [research]       — analyze existing code and theming system
Wave 1: [implement]      — build the toggle (depends on research)
Wave 2: [tests, docs]    — write tests and update docs (both depend on implement)

3. Your agent writes dispatch JSON

The agent writes a JSON file to .pi_agent_dispatch/<timestamp>.json:

{
  "task": "Add dark mode toggle to settings page",
  "agents": [
    {
      "id": "research",
      "task_type": "easy",
      "depends": [],
      "prompt": "Analyze the current settings page and theming system. Identify which files need changes. Output a concise summary."
    },
    {
      "id": "implement",
      "task_type": "hard",
      "depends": ["research"],
      "prompt": "Implement a dark mode toggle in the settings page. Add a toggle component, wire it to the theme system, persist the preference. Output a concise summary."
    },
    {
      "id": "tests",
      "task_type": "easy",
      "depends": ["implement"],
      "prompt": "Write tests for the dark mode toggle. Test toggle state, persistence, and theme application. Output a concise summary."
    },
    {
      "id": "docs",
      "task_type": "mechanical",
      "depends": ["implement"],
      "prompt": "Update docs to describe the dark mode feature. Output a concise summary."
    }
  ]
}

4. Your agent runs the orchestrator

python pi_dispatch/orchestrate.py .pi_agent_dispatch/20260511T120000.json

5. Your agent reads the output

The orchestrator prints structured JSON. Your agent reads it and reports back to you:

  • Success — tells you what each subtask accomplished, shows the merged diff
  • Conflict — explains the conflict and suggests a fix (e.g., adjusting dependencies)
  • Failure — explains what went wrong; your branch is reverted to its original state

What you see

You just give your agent a task. It handles the decomposition, parallel execution, and merging. You get back a summary of what happened and a clean git diff to review.


How It Works

Execution Flow

┌─────────────────────────────────────────────────────────────────┐
│  1. PARSE dispatch JSON                                         │
│     • Validate agent definitions                                │
│     • Detect dependency cycles                                  │
│     • Assign agents to waves (topological sort)                 │
├─────────────────────────────────────────────────────────────────┤
│  2. CHECKPOINT current branch                                   │
│     • Commit any uncommitted changes                            │
│     • Record HEAD SHA for potential revert                      │
├─────────────────────────────────────────────────────────────────┤
│  3. CREATE integration branch + worktree                        │
│     • Branched from checkpoint                                  │
│     • All agent work merges here first                          │
├─────────────────────────────────────────────────────────────────┤
│  4. FOR EACH WAVE:                                              │
│     ┌───────────────────────────────────────────────────────┐   │
│     │  a. Create branch + worktree per agent (from integ.)  │   │
│     │  b. Spawn pi CLI in each worktree (parallel)          │   │
│     │  c. Wait for all agents in wave                       │   │
│     │  d. Retry failed agents with next model               │   │
│     │  e. Merge successful branches → integration branch    │   │
│     │  f. On merge conflict → pause (--continue to resume)  │   │
│     │  g. On total wave failure → abort + revert            │   │
│     └───────────────────────────────────────────────────────┘   │
├─────────────────────────────────────────────────────────────────┤
│  5. MERGE integration branch → working branch                   │
│     • All agent work is now in your branch                      │
├─────────────────────────────────────────────────────────────────┤
│  6. CLEANUP                                                     │
│     • Remove all worktrees                                      │
│     • Delete all temporary branches                             │
│     • Print structured JSON output                              │
└─────────────────────────────────────────────────────────────────┘

DAG Resolution

Agents are organized into a Directed Acyclic Graph (DAG) based on their depends fields. The orchestrator performs a topological sort to determine execution order:

  • Wave 0: Agents with no dependencies (run first)
  • Wave 1: Agents that depend only on wave-0 agents
  • Wave N: Agents that depend on agents in waves 0..N-1

Agents within the same wave run in parallel — but only if they touch disjoint files. If two agents modify the same file, they must be sequenced via depends.

Wave 0:  [research]
              │
Wave 1:  [implement]
           ╱       ╲
Wave 2:  [tests]  [docs]
           ╲       ╱
Wave 3:  [review]      ← depends on both tests and docs

The orchestrator detects:

  • CyclesCycleDetectedError (e.g., A depends on B, B depends on A)
  • Unknown dependenciesUnknownDependencyError (referencing a non-existent agent id)

Worktree Isolation

Each agent runs in its own git worktree — a separate working directory linked to its own branch. This means:

  • Agents can't see or modify each other's files during execution
  • Each agent's changes are captured on a dedicated branch
  • The orchestrator controls when and how changes are merged
/tmp/pi_dispatch/<timestamp>/
├── research/           ← worktree for 'research' agent
├── implement/          ← worktree for 'implement' agent
├── implement-retry/    ← worktree for retry attempt
├── tests/              ← worktree for 'tests' agent
├── docs/               ← worktree for 'docs' agent
└── pi_integration/     ← integration worktree (merges land here)

Merge Strategy

After each wave completes:

  1. Agent worktrees are committed (git add -A && git commit)
  2. Each agent branch is merged into the integration branch (git merge --no-ff)
  3. If the merge succeeds, the agent's worktree and branch are cleaned up
  4. If the merge conflicts, the orchestrator:
    • Aborts the merge
    • Cleans up all wave worktrees
    • Preserves the integration branch
    • Prints a conflict_paused state with instructions to --continue

After all waves succeed, the integration branch is merged into your working branch.

Retry Logic

Each agent gets up to 2 attempts:

  1. First attempt: Uses the model selected by round-robin from the tier pool
  2. Retry attempt: Uses the next model in the same tier pool

Model selection:

  • Agents with task_type get a model from the corresponding tier via round-robin
  • Agents with an explicit pi_model use that exact model (no retry — deterministic choice)

Example with a 3-model pool:

Wave position 0 → qwen/qwen3.6-plus      (first attempt)
                  deepseek/deepseek-v4-pro (retry)
Wave position 1 → deepseek/deepseek-v4-pro (first attempt)
                  z-ai/glm-5-turbo         (retry)
Wave position 2 → z-ai/glm-5-turbo        (first attempt)
                  qwen/qwen3.6-plus        (retry)

Dispatch JSON Reference

Your coding agent creates this automatically. The reference below documents the format for understanding agent output and debugging.

{
  "task": "One-line description of the overall task",
  "agents": [ ... ]
}

Agent Fields

Field Required Description
id Yes Unique identifier within this dispatch file
task_type Yes, unless pi_model is set Key from config.json:task_routing (e.g., hard, easy, mechanical)
pi_model No Explicit model override; bypasses tier routing; no retry on failure
timeout_minutes No Per-agent timeout; defaults to config.default_timeout_minutes
provider No Per-agent provider override; defaults to config.provider
depends No List of agent ids that must complete before this one starts; defaults to []
prompt Yes Self-contained prompt; must end with a summary instruction

Authoring Rules

These are the rules your agent follows when creating dispatch plans:

  1. Parallel agents must touch disjoint files. If two agents modify the same file, they must be sequenced with depends.
  2. Every prompt ends with an explicit summary instruction (e.g., "Output a concise summary of changes made."). This summary becomes the output field in results.
  3. Prompts use relative paths only — pi runs inside the worktree, not the project root.
  4. Prompts are self-contained — agents don't share context. Each prompt includes all necessary information.

Tool-Generated Files and Spurious Conflicts

Parallel agents each run in their own worktree. Any tool that writes files into the repo directory — IDE indexes, language-server caches, linter caches, coverage outputs, or any cache keyed on file paths or timestamps — will produce a different file per agent. When the orchestrator merges those branches, the differing files cause conflicts that look like depends ordering errors but have nothing to do with your actual code changes.

The fix is always .gitignore, not depends. Before running a dispatch, make sure your .gitignore covers any files your tools generate automatically. Common offenders:

Tool / ecosystem Files to ignore
JetBrains IDEs .idea/
VS Code .vscode/
Python **/__pycache__/, *.pyc, .pytest_cache/
Coverage .coverage, htmlcov/
Any tool with a repo cache .pi-lens/, .mypy_cache/, .ruff_cache/

To diagnose: after a conflict_paused failure, check git status inside any surviving worktree to see which unexpected files were created.


CLI Reference

Your coding agent runs these commands. The reference below is for understanding what happens and for manual intervention.

Run a Dispatch

python pi_dispatch/orchestrate.py <dispatch-file.json>

Resume After Merge Conflict

When a merge conflict occurs, the orchestrator pauses and prints instructions. Your agent handles this automatically, but you can also resume manually:

# Resume with the same dispatch file
python pi_dispatch/orchestrate.py --continue .pi_agent_dispatch/<ts>-state.json

# Resume with a fixed dispatch file (e.g., after fixing depends)
python pi_dispatch/orchestrate.py --continue .pi_agent_dispatch/<ts>-state.json \
  --json-override .pi_agent_dispatch/<fixed>.json

Already-merged agents are skipped — only failed/pending agents re-run from the preserved integration branch.

Cleanup After Crash

If the orchestrator crashes or is interrupted:

python pi_dispatch/orchestrate.py --cleanup .pi_agent_dispatch/<ts>-state.json

This removes all worktrees, deletes agent branches, and reverts the integration branch.

Environment Variables

Variable Description Default
PI_CMD Override the pi CLI command (for testing) pi

Error Handling & Recovery

Exit Codes

Code Meaning
0 Success — all agents completed and merged
1 Dispatch error (validation, config, or unexpected failure)
2 Agent failure or merge conflict — check JSON output for details

Output Statuses

status: "complete"

All agents succeeded. Changes are merged into your working branch.

{
  "status": "complete",
  "agents": {
    "research": { "status": "merged", "output": "..." },
    "implement": { "status": "merged", "output": "..." }
  }
}

status: "conflict_paused"

A merge conflict occurred. Your working branch is untouched. The integration branch is preserved.

{
  "status": "conflict_paused",
  "failure_reason": "merge conflict: agent implement",
  "resume_with": "python pi_dispatch/orchestrate.py --continue .pi_agent_dispatch/<ts>-state.json"
}

Your agent reads this output, adjusts the dependency ordering if needed, and runs --continue.

status: "failed"

All agents in a wave failed after retries. The orchestrator reverts everything — your working branch is reset to the checkpoint.

{
  "status": "failed",
  "reverted_to": "abc123...",
  "failed_wave": 1,
  "failure_reason": "non-zero exit",
  "failed_agents": ["implement"]
}

Inspecting Diffs After Success

Your agent may show you these, or you can run them yourself:

# Everything from this dispatch
git diff <checkpoint_sha>..HEAD

# One specific agent's changes
git show <merge_sha>

# What one agent added vs. the previous merge
git diff <prev_merge_sha>..<this_merge_sha>

Architecture

pi_dispatch/
├── __init__.py            # Package marker
├── config.json            # Model routing and timeout configuration (edit this)
├── config.py              # Config loading, dispatch parsing, validation
├── dag.py                 # DAG resolution: cycle detection, topological sort
├── exceptions.py          # Domain exceptions (DispatchError hierarchy)
├── git_ops.py             # Thin wrappers around git subprocess calls
├── model_selector.py      # Round-robin and retry-next model selection
├── orchestrate.py         # CLI entry point (only file touching sys.path)
├── orchestrator.py        # Core Orchestrator class and cleanup utility
├── paths.py               # Worktree/branch path computation (single source of truth)
├── process.py             # Parallel subprocess runner for agent execution
├── SKILL.md               # Skill instructions for your coding agent
└── state.py               # State file for crash recovery and --continue

Module Responsibilities

Module Responsibility
SKILL.md Instructions injected into your agent's context; teaches it how to use pi-dispatch
config.json Your file to edit — model pools, provider, timeouts
orchestrate.py CLI entry point; adds parent dir to sys.path; delegates to orchestrator.main()
orchestrator.py Orchestrator class: wave execution, merge, retry, abort, cleanup
dag.py Topological sort, cycle detection, unknown dependency detection
git_ops.py Git operations: worktree, branch, merge, checkpoint
process.py Parallel subprocess execution with timeout support
model_selector.py Model selection: round-robin by tier, next-on-retry
config.py Config/dispatch file loading and validation
paths.py Centralized path computation for worktrees and branches
state.py State file persistence for crash recovery and --continue
exceptions.py Exception hierarchy: DispatchError → specific error types

Key Design Decisions

  1. Git worktrees for isolation — Each agent gets its own working directory and branch. No filesystem conflicts, no race conditions.

  2. Integration branch as staging area — Agent branches merge into an integration branch first. Only when all agents succeed does the integration branch merge into your working branch.

  3. Checkpoint + revert — Before starting, the orchestrator commits any uncommitted changes and records the SHA. On failure, git reset --hard restores the exact state.

  4. Automatic retry with model rotation — Failed agents retry with the next model in the tier pool. Explicit pi_model agents don't retry (deterministic choice).

  5. Conflict-aware resumption — Merge conflicts don't crash the system. The orchestrator pauses, preserves state, and lets the agent fix dependencies and --continue.


License

MIT License — see LICENSE for details.

About

Skill for agents to orchestrate work of pi coding agents.

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages