Parallel task management and orchestration for AI coding agents.
A lead agent dispatches work to parallel workers in isolated git worktrees, with context preservation and progress tracking.
Without Subtask, a user managing multiple AI coding sessions must:
- Open multiple terminal tabs, each running Claude Code or Codex
- Mentally track what each session is working on
- Juggle progress, blockers, and dependencies in their head
- Re-explain context when sessions crash or expire
- Be the project manager while also trying to get work done
This cognitive load is exhausting. The user becomes a human orchestrator for AI workers.
Subtask shifts the cognitive burden from user to lead agent.
Three roles:
- User: Gives direction, makes decisions, approves work
- Lead (e.g., Claude Code): Orchestrates everything in between
- Workers: Execute tasks in parallel, isolated in git worktrees
The user provides direction. The lead handles everything else: understanding requirements, breaking work into tasks, dispatching to workers, tracking progress, reviewing output, iterating until it's right, merging when ready.
The lead is not a task dispatcher. The lead is a technical lead / project manager. We're building tools that enable this.
Every feature should ask:
- Does this reduce cognitive load on the user?
- Is this simple for the lead to use and understand?
If a feature adds complexity for the user, it's probably wrong. If it confuses the lead, it's also wrong. Internal complexity is fine if the interface stays simple.
- Task-centric — Task name is the primary identifier. Everything flows from it.
- Git-native — Branches for isolation, worktrees for parallelism, standard merge workflow.
- File-based collaboration — Task folder shared between lead and worker. PLAN.md for plans, PROGRESS.json for tracking. Files persist; sessions don't.
- Workspace opacity — Lead never picks workspaces. Subtask assigns them.
- Context preservation — Task folders are the portable, syncable unit. history.jsonl and
--follow-upensure nothing is lost when sessions crash. Copy anywhere, full context. Internal state and caches are local and rebuildable. - Progress visibility — Tool counts, timing, and PROGRESS.json let lead track workers without interrupting them.
- Guardrails over escape hatches — Prevent footguns. Errors guide the lead on what to do next, not just fail. Dangerous operations require explicit intent.
- Local-first — Operations use local git state. Lead controls when to sync with remote. No stale remote ref surprises.
- Event sourcing —
history.jsonlis the append-only source of truth. SQLite index is a derived projection for fast queries. If they diverge, history wins.
Lead (e.g., Claude Code, Codex, OpenCode):
- Runs
subtaskCLI commands - Sees CLI output (
list,show, errors) - Drafts tasks, sends prompts, reviews work
- Reads task folders and history.jsonl
Worker (e.g., Codex, Claude):
- Receives prompts via harness
- Sees the repository and task folder
- Updates PROGRESS.json
- Never sees CLI output or other tasks
A named unit of work: fix/epoch-boundary
- Folder at
.subtask/tasks/<name>/(with/escaped as--) - Contains TASK.md (description), optional PLAN.md, PROGRESS.json
- Symlinked into workspace for lead/worker collaboration
Isolated git worktree where tasks execute. Created on-demand from a configured pool. Lead never picks workspaces—subtask assigns them.
Worker backend that executes prompts. Supports codex and claude. Configured in .subtask/config.json.
Default: doing → review → ready
Planning workflows add a plan stage: plan → implement → review → ready
--workflow you-plan: Lead drafts PLAN.md, worker reviews--workflow they-plan: Worker drafts PLAN.md, lead reviews
Task Status (organizational, durable):
| Status | Meaning |
|---|---|
open |
Task is active |
merged |
Work merged into base branch |
closed |
Closed without merging |
Transitions:
open→merged(viamerge)open→closed(viaclose)merged→open(viasend—revives to fix issues)closed→open(viasend—revives with new workspace)
Worker Status (ephemeral, within an open task):
| Status | Meaning |
|---|---|
idle |
No worker activity yet |
running |
Worker currently executing |
replied |
Worker finished, awaiting follow-up |
error |
Last run failed |
Transitions:
idle→running(viasend)running→replied(worker finishes) orerror(failure)replied→running(viasend)error→running(viasend)
Task status is what users care about. Worker status is operational detail. Workspace stays with task until closed/merged.
| Command | Description |
|---|---|
subtask install |
One-time global install + configuration wizard |
subtask config |
Edit user defaults or project overrides |
subtask draft <task> |
Create a task without running it |
subtask send <task> |
Send a message (starts or resumes task) |
subtask stage <task> <stage> |
Advance workflow stage |
subtask list |
Show all tasks and workspaces |
subtask show <task> |
Task details, progress, diff stats |
subtask merge <task> -m "..." |
Squash-merge into base branch, close |
subtask close <task> |
Close without merging (--abandon discards changes) |
subtask workspace <task> |
Print workspace path |
subtask ask "..." |
Quick question (no task, runs in cwd) |
subtask interrupt <task> |
Gracefully stop a running worker |
subtask log <task> |
Show conversation and lifecycle events |
subtask trace <task> |
Debug session logs (tool calls, timing) |
~/.subtask/
├── config.json # global defaults (from install/config)
├── workspaces/<escaped-git-root>--<id>/ # worktrees (created on demand)
└── projects/<escaped-git-root>/ # per-project runtime state (machine-local)
├── internal/ # session IDs, workspace assignments, locks
└── index.db # SQLite cache (rebuildable)
<repo>/.subtask/
├── config.json # optional per-project overrides
└── tasks/<name>/ # task folder (portable, syncable)
├── TASK.md # description + schema version in frontmatter
├── PLAN.md # optional plan
├── PROGRESS.json # worker progress tracking
└── history.jsonl # source of truth: messages + lifecycle events
Task folder (.subtask/tasks/<name>/) is portable and syncable:
- Copy to another machine, sync via git/syncthing, back up
- Contains everything needed to understand and resume the task
history.jsonlis the source of truth for task status, stage, messages
Internal folder (.subtask/internal/<name>/) is runtime-only:
- Workspace paths, session IDs—machine-specific
- Rebuilt automatically when needed
- Never sync or back up
SQLite index is a local cache:
- Fast queries for
listand TUI - Rebuilt from task folders if missing
- Never sync—it's a projection, not source data
.
├── cmd/subtask/ # CLI commands and main.go entry point
├── task/ # Task/State structs, paths, locking, progress
│ ├── gather/ # Shared data layer (used by CLI and TUI)
│ ├── history/ # history.jsonl: append, read, tail
│ ├── index/ # SQLite index for fast list/TUI queries
│ ├── migrate/ # Schema migrations (legacy → current)
│ └── ops/ # Task operations (merge, close)
├── tui/ # Interactive TUI (Bubble Tea)
├── workspace/ # Workspace pool allocation
├── harness/ # Worker backends (Codex, Claude) and prompt building
├── git/ # Git operations (branches, merge, diff, worktrees)
├── render/ # CLI output formatting (TTY detection, colors, tables)
├── workflow/ # Workflow stage templates (YAML, embedded)
├── logs/ # Session log parsing and formatting
├── testutil/ # Test helpers (isolated environments)
├── e2e/ # Integration tests
├── skill/
│ └── SKILL.md # Embedded skill source (synced at runtime)
└── plugin/
├── .claude-plugin/
└── setup.md
go build ./cmd/subtask
go test ./... -short # fast
go test ./... # includes e2e- Create
cmd/subtask/<cmd>.go - Define struct with kong tags
- Implement
Run() error - Add to CLI struct in
main.go
- Create
harness/<name>.go - Implement
Harnessinterface - Register in
harness.New()