Turn goals into tracked work, kept aligned through inspectable repository state.
Taskrail is a deterministic execution harness for humans and AI agents. It turns goals into structured tasks, keeps lifecycle transitions aligned across task files and a generated current-state projection, and advances work through validation, verification, and explicit follow-up.
It is built on durable primitives — Git for history and review, plain Markdown with YAML frontmatter for specs, tasks, and state. No database. No hidden automation. No opaque dashboards. Your repo stays inspectable, and the same taskrail commands work whether a person or an agent is at the keyboard.
taskrail init # adopt Taskrail in an existing repo, non-destructively
taskrail validate # confirm the layout and state are consistent
taskrail status # see the active spec, task counts, and what's nextFrom there, the daily loop is next → start → complete → verify (see Commands).
- Why Taskrail
- What It Is Not
- Install
- Commands
- Quickstart
- Layout Upgrades
- What a Verification Leaves Behind
- State Contract
- Repository Layout
- Development
- Status
- License
- Read Next
- Deterministic: the workflow is
validate → next → start → complete → verify, and next-task selection follows status, dependencies, priority, and stable tie-breaking — same repo, same answer, every time. - State-first: task files are the durable work ledger and
planning/STATE.mdis the generated continuity and control projection for the current run. - Repo-native: work is tracked as Markdown task files with an explicit, machine-checkable schema — specs under
specs/, tracked work underplanning/. No database, no hidden automation. - Verification is first-class: completing implementation and verifying it are distinct steps; verification records pass/fail outcomes, writes inspectable artifacts, and opens follow-up tasks as needed.
- Retrofit-friendly:
taskrail init(orretrofit) drops the contract into an existing repository with no rewrite. - Agent-ready: reporting and most agent handoffs have structured output today; the active v0.5 spec completes lifecycle JSON parity and standardizes one machine-result envelope.
- Not a built-in LLM provider integration — Taskrail is provider-agnostic and manual-first. (
importstructures notes; it never calls a model.) - Not a sandbox, container, or worktree orchestrator.
- Not a background daemon, distributed worker pool, or multi-lane scheduler.
- Not a built-in semantic spec-to-task generator or reviewer — the binary provides mechanical reports and reviewed write boundaries, while optional skills let an external agent supply judgement.
Homebrew (macOS and Linux):
brew install tessariq/tap/taskrail
taskrail --versionThis pulls the release binary from the tessariq/homebrew-tap tap.
Windows (WinGet):
winget install Tessariq.Taskrail
taskrail --versionBuild from source (needs Go 1.26):
git clone https://github.com/tessariq/taskrail.git
cd taskrail
go install ./cmd/taskrail
taskrail versionPlain go build/go install produce a development build that reports version
0.0.0-dev. To produce a release build that reports a real version, inject it at
build time:
VERSION=vX.Y.Z
go build -ldflags "-X main.version=${VERSION}" -o taskrail ./cmd/taskrail
# or, via Taskfile:
VERSION="${VERSION}" task release
./taskrail version # -> vX.Y.ZTagged v* releases are built and published automatically with
GoReleaser — Linux/macOS/Windows binaries for
amd64/arm64, with archives, checksums, and notes from CHANGELOG.md. See
docs/workflow/releasing.md for the release checklist.
The core loop is five commands — the ones you run every day:
taskrail validate # check the repo is consistent
taskrail next --json # pick the next eligible active-spec task
taskrail start T-001 # mark it active
taskrail complete T-001 --note "implemented" # mark implementation done
taskrail verify T-001 --result pass --summary "acceptance met"Every --json command — including the start, complete, and block
lifecycle writers — emits one versioned envelope: schema_version, the canonical
command, warnings, and exactly one of result or error. This is the one-time
v0.5 break from pre-v0.5 bare result objects; consumers must reject unsupported
versions rather than decode an inherited shape optimistically. Text output stays
human-oriented and unchanged. See the
envelope contract.
Idle next selection is anchored to the active spec: it considers only todo
tasks whose spec_ref points at the active spec. When only older-spec work is
runnable, next reports no eligible task and lists the skipped tasks under
warnings; an already-active off-spec task is still returned so you can continue
or resolve it. Recover older work explicitly with start <id> or
next --include-off-spec, or move it onto the active spec with
task repoint.
Details: docs/commands.md.
Beyond the core loop
- Adopt an existing repo —
initandretrofitscaffoldspecs/+planning/non-destructively;importturns rough notes into spec/task drafts without an LLM;repairreconciles mechanicalSTATE.mddrift. - See where work stands —
status,stats, andcoveragereport a live snapshot, aggregate metrics, and advisory spec-linkage, all read-only.statusalso breaks down open work (todo/in_progress/blocked) by how much targets the active spec versus points away from it, listing the away tasks and theirspec_ref; the away set matches the active-spec filternextuses for idle selection. - Author and steer specs — the
specfamily (list,show,add,activate,diff) inspects and evolves versioned specs;spec diffpreviews the mechanical area-set delta before activation. - Draft missing work — the optional
taskrail-decomposeandtaskrail-gapskills turn uncovered areas and structural gap signals into reviewable proposals; only an explicittask neworimport --applywrites tracked tasks. - Handle the messy parts —
block/unblockpark and resume work,task newscaffolds a task,task renamere-slugs it,task repointmoves itsspec_ref, andtask dependency add|removechanges one reviewed dependency edge. When a crashed writer leaves the repository mutation lock behind,lock statusinspects it read-only andlock clearremoves exactly the observed stale lock — never automatically, and never while its owner is provably alive on this host. When a crashed durable transaction leaves a recovery fence behind,recover <transaction-id>previews and — with--apply— performs the one mechanically safe action (restore originals, accept the validated candidate, or clear the fence).
Run taskrail --help, or taskrail <command> --help, for the full command list and every flag.
Taskrail commands intentionally use different write conventions based on risk:
| Class | Current examples | Effect |
|---|---|---|
| Read-only | validate, status, stats, coverage, spec list/show/diff, lock status |
Inspect only; never rewrite tracked planning state. |
| Mode-dependent initialization | init |
Fresh, unmarked-standard, and current-layout adoption/repair paths may write immediately; detected migration or retrofit paths preview unless --apply is supplied. In v0.4, --with-skills may also install skills after any successful init result. A repository at layout 1 reports the read-only layout 2 upgrade preview instead: the flagless invocation writes nothing, and --apply requires --confirm-quiescent plus the note and skill decisions the preview names. |
| Preview by default | retrofit, repair |
Report a candidate; --apply is the write opt-in. |
| Apply with preview option | task rename, task repoint, task dependency add/remove |
Write by default; --dry-run validates the candidate first. |
| Lifecycle/state writers | next, start, complete, block, unblock, verify, spec activate, task new |
Rewrite STATE.md and sometimes task files; inspect git status afterward. |
| Operator lock recovery | lock clear <lock-id> --expect-sha256 <digest> |
Removes only the unchanged mutation lock observed via lock status; refuses a provably live same-host owner and never touches retained transaction data. Never rewrites tracked planning state. |
| Operator transaction recovery | recover <transaction-id> [--apply] |
Previews the single safe action a retained durable transaction derives (restore-original, accept-candidate, clear-fence); --apply performs exactly that action. Requires a free mutation lock; any holder refuses. |
| Reviewed import writer | import --apply <draft> |
Validates an external draft and writes its bounded task/spec/state set. |
next is not a read-only selection probe: it persists next_action and
updated_at. Use status when you need the same next-task computation without a
tracked write.
next, start, complete, block, unblock, and verify take the repository
mutation lock and publish their exact state/task write set (plus verification
artifacts and any --create-followup task) transactionally. So do the task
mutation writers — task new, task rename, task repoint, and
task dependency add/remove: rename publishes its coupled move by filesystem
operations (no git mv staging), and each writer publishes only the task and
state files it declares. A concurrent writer refuses with lock_held; unselected
task bytes are never re-encoded.
All semantic command classes share one recovery admission fence: retained or
malformed transaction state beneath the canonical repository runtime root makes
readers and writers fail with recovery_pending, and writers do not begin.
recover is the one command the fence admits. See
docs/commands.md.
coverage answers "is this spec area linked to any task?";
coverage --gaps answers "does a covered area lack a verification/companion task, have
a dependency-graph anomaly, or look under-decomposed?" Both are read-only and
advisory by default — they never write STATE.md or task files and never make
validate fail; --gaps opts into gating only through --fail-on <category>.
The hard limit: --gaps is mechanical only — its signals are candidates, not violations: false positives are expected, and each is something to inspect
and promote into a real task, never a semantic "this needs a test" rule. For the
semantic half use the taskrail-gap skill. Details:
docs/commands.md.
Taskrail keeps mechanically testable state (validate, coverage) separate from
agent or human semantic review (verify records evidence against one task).
Semantic findings never become validate violations automatically; humans adopt
accepted changes through the bounded task/spec/import commands. The active v0.5
roadmap adds distinct advisory review stages — see
docs/commands.md.
Taskrail ships shell completion via Cobra. Load it for your shell (or add the line to your shell profile):
source <(taskrail completion bash) # bash
taskrail completion zsh > "${fpath[1]}/_taskrail" # zsh
taskrail completion fish | source # fishRun taskrail completion --help for per-shell install steps. Completion is
read-only: it never writes STATE.md or task files. Beyond every command and
flag, it completes spec versions, real <path>#<anchor> values for
--spec-ref, and the active spec's bare anchors for --area flags — exactly
the anchors validate accepts.
Initialize Taskrail inside an existing repository, then confirm it is sane:
taskrail init --apply
taskrail validateTasks live under planning/tasks/ as Markdown with YAML frontmatter:
---
id: T-001
title: Bootstrap repository structure
status: todo
priority: high
spec_ref: specs/v0.1.0.md#summary
dependencies: []
updated_at: "2026-08-05T00:00:00Z"
---
# T-001 Bootstrap repository structure
## Description
Create the initial Taskrail structure, specs, and planning area.
## Acceptance
- `planning/STATE.md` exists.
- `taskrail validate` passes.
## Verification Notes
- Run `taskrail validate` and record the successful observation.
## Implementation NotesTasks may also carry paired loop_policy: allow|hold and loop_reason: <reason>
frontmatter. Omitting both means an implicit hold with reason
implicit hold: loop policy is not set; taskrail validate rejects incomplete
or malformed pairs. Lifecycle and task writers preserve explicit policy metadata,
and STATE.md does not duplicate it.
Let Taskrail pick the next eligible task, start it, and advance it:
taskrail next --json
taskrail start T-001
taskrail complete T-001 --note "implementation landed"
taskrail verify T-001 --result pass --summary "validate passes; acceptance met"When verification reveals more work, spawn a follow-up task in the same step:
taskrail verify T-001 \
--result fail \
--summary "missing dependency check" \
--create-followup \
--followup-title "Add dependency validation" \
--followup-priority highAuthor a task against the active spec without copying the spec path by hand —
--area <anchor> is shorthand for --spec-ref <active-spec-path>#<anchor>:
taskrail task new --title "Add machine envelope" --area uniform-agent-machine-results
taskrail spec show v0.5.0 --anchors # list the active spec's valid anchors--area and --spec-ref are mutually exclusive; an unknown anchor fails before
anything is written and points you at spec show <active-version> --anchors.
Before advancing the active spec, inspect what changed between two versions with a read-only, mechanical anchor-set delta:
taskrail spec diff v0.3.0 v0.4.0 # added / removed / candidate-rename areasAdded areas are the ones a migration must decompose into tasks; removed areas
are the ones whose existing tasks become orphaned drift; rename candidates are
best-effort, labeled for you to verify, never asserted as fact. It is
side-effect-free, and --json mirrors the output with structured
added/removed/renamed lists.
A task's id and its filename are two encodings of one identifier: validate
enforces filename == "<id>.md", so a slugged filename requires a slugged id.
task new produces that pairing directly — --title "X" derives a slug and
writes T-<n>-x-slug with a matching T-<n>-x-slug.md, --slug overrides the
slug source, and passing neither keeps the bare T-<n> / T-<n>.md form. Every
case passes validate with no follow-up edit. Because id and filename move
together, you cannot rename a file for readability on its own — a bare
git mv T-<n>.md T-<n>-add-slug.md leaves the frontmatter id: bare, and the
next validate fails with task <id> filename must be <id>.md. The fix is
task rename, which re-slugs atomically (id, filename, heading, inbound
dependency refs, STATE.md):
taskrail task rename T-<n> --slug add-slug # or --title "Add slug"; --dry-run previewstask rename re-encodes the identifier only — it never rewrites the title:
frontmatter field, and there is no task retitle command in this version, so
rename --title "New Title" derives a new slug and leaves the title unchanged;
to retitle, edit the title: field directly. Edge cases — accented
transliteration, the ~50-character slug cap, symmetric slug stripping — are
covered in
docs/commands.md.
After spec activate, open tasks still pointing at the previous spec are off-spec:
next skips them, status lists them under the active-spec drift breakdown, and
next --include-off-spec recovers one to run where it is. To move an open task
onto the active spec instead, task repoint rewrites its spec_ref — the one
edit that would otherwise mean hand-editing frontmatter:
taskrail task repoint T-<n> --area status-active-spec-drift-breakdown # active-spec anchor
taskrail task repoint T-<n> --spec-ref specs/v0.2.0.md#some-area # explicit, cross-spec--area resolves the anchor against the active spec exactly as task new --area
does, so an unknown anchor fails before any write; --dry-run previews the state
the repoint would leave behind. Repoint never touches id, slug, filename, title,
status, or dependencies; completed and cancelled tasks are rejected. Because it
re-projects planning/STATE.md, run git status afterwards. Details:
docs/commands.md.
Use exact full persisted task IDs to apply one accepted dependency-review change:
taskrail task dependency add T-010-api T-009-model --dry-run
taskrail task dependency add T-010-api T-009-model
taskrail task dependency remove T-010-api T-009-modelAdd appends without reordering and rejects missing, self, duplicate, cancelled,
or cyclic edges; remove rejects an absent edge. Both preserve all other task
bytes, transactionally publish the task with a reprojected STATE.md, and
support the common --json envelope.
Bootstrap drafts from rough notes without any LLM — preview first, then apply:
taskrail import notes.md --to tasks # preview the structural task drafts
taskrail import notes.md --to tasks --emit-prompt # print an agent prompt for a richer draft
taskrail import --apply draft.json # validate an agent draft and write real filesAn apply that fails during writing exits non-zero and still reports what it wrote or may have touched. Review those paths before retrying — a failed spec write may leave an empty or truncated file, and re-applying the same draft creates any already-written tasks a second time under new ids. Details: docs/commands.md.
Typical flow:
- Write a goal as a Markdown task inside
planning/tasks/. validatethe repository.nextto select deterministically, thenstart.completethe implementation.verifyto record the outcome and leave artifacts — opening follow-up tasks as needed.
A repository whose layout marker sits at layout 1 has a read-only layout 2
upgrade preview: plain taskrail init reports every operator decision the
upgrade resolves before anything can apply — the complete candidate paths
(marker, schema-2 state, preserved task files, notes sidecar), committed
storage, the default broad review-round maximum, decoded continuation notes
with their applicable extract/drop choices, and each installed skill's
classification (parity mirrors stay marker-free; stamped copies normalize
through a forced refresh). The preview writes nothing, and a blocking state —
an AUTONOMY.tsv legacy entry at the configured planning path, an unsafe
notes destination, or a divergent or conflicting skill copy — refuses with
actionable guidance instead.
Applying the upgrade is gated and durable: taskrail init --apply requires
--confirm-quiescent (your assertion that every older Taskrail process able
to touch this repository or its linked-worktree storage has stopped), exactly
one of --extract-continuation-notes or --drop-continuation-notes when
decoded notes exist and neither when they do not, and the combined
--with-skills --force whenever stamped skill copies require normalization.
A fully gated apply publishes the exact previewed candidate through one
recoverable transaction: the marker is fenced as layout 2 with a
migration_fence transaction id before any task, state, note, or skill byte
changes, the complete candidate publishes and post-validates, and the strict
final marker replaces the fence as the transaction's last operation. A handled
failure rolls every candidate-written byte back before the original marker;
an interruption leaves the fence plus the retained transaction, every other
command refuses (recovery_pending with the transaction, or
migration_in_progress when only the fenced marker remains), and
taskrail recover <transaction-id> derives the single safe restore, accept,
or clear action. Older binaries refuse layout 2 through the command-wide
compatibility guard, and downgrade is complete Git reversion of the upgrade —
never hand-editing the marker. An explicit
init --with-skills request on a layout 1 repository is served by the current
layout, so skill installation keeps working independently of the upgrade.
Every verification writes repo-local evidence under planning/artifacts/verify/<task-id>/<timestamp>/:
planning/
STATE.md # generated current execution projection
NOTES.md # optional human-owned repository context
tasks/
T-001.md # task with frontmatter schema
artifacts/
verify/
T-001/
20260619T113646Z/
plan.md # verification plan
report.json # machine-readable outcome
report.md # human-readable outcome
These are plain files — no proprietary formats, no database required. The
planning/artifacts/ tree is gitignored, reproducible local output: verify
creates it on demand, taskrail init never pre-creates it, and neither committed
state nor validate depends on it surviving a Git round-trip. No .gitkeep
placeholder is required or tracked.
planning/STATE.md is the authoritative current execution projection. It carries the active spec, current task, status summary, blockers, the next action, and the last verification result, plus pointers to relevant artifacts. It is not a per-task or per-session log: keep durable task context in task ## Implementation Notes, blocker reasons, portable verification summaries/reports, or follow-up tasks. Repository-wide human context lives in planning/NOTES.md, a human-owned sidecar init and retrofit --apply create as a short commented template when that path is absent and never rewrite afterwards; agents may read it but edit it only when explicitly asked. Do not hand-edit machine-managed state fields or append continuation prose; let the taskrail transitions update the file.
.
├── AGENTS.md # guidance for coding agents
├── CHANGELOG.md
├── README.md
├── cmd/taskrail/ # CLI entry point
├── internal/ # core packages
├── lefthook.yml # opt-in local git hooks (mirror CI)
├── mise.toml # optional pinned developer toolchain (mise)
├── planning/ # task ledger, generated STATE.md, optional human NOTES.md
├── scripts/
└── specs/ # versioned, normative product specs
The packaged skill set lives in internal/taskrail/skills/ (embedded; installed
by taskrail init --with-skills). This repository adopts it: committed copies in
.agents/skills/ and .claude/skills/ are kept byte-identical to the package by
task check:skills. Installed skills record the Taskrail version that wrote them
in metadata.taskrail_version, so version skew is detectable and advisory —
the details, including why you must not run init --with-skills --force in this
repository, live in
docs/workflow/skills-productization.md.
mise provisions the pinned toolchain (Go, task,
lefthook) from mise.toml — optional; direct go commands and the
Taskfile.yml targets work without it:
mise run setup # provision, build taskrail onto PATH, wire the opt-in git hooks
go build ./cmd/taskrail && go test ./...CI (.github/workflows/ci.yml) is the authoritative gate: it provisions the
same toolchain via jdx/mise-action and
runs the build/test matrix over Linux, Windows, and macOS. Optional
lefthook git hooks mirror CI locally
(task hooks:install, or go install github.com/evilmartians/lefthook@v1.13.6; pre-commit runs gofmt/go vet/
validate plus the skill-parity and binary-freshness guards). Do not bypass
them with --no-verify.
The binary-freshness guards and the mise/PATH wiring they rely on matter when hacking on Taskrail itself — see AGENTS.md → Toolchain And Environment for the full contract. See CONTRIBUTING.md for the PR checklist, the AI-assisted contribution policy, and tracked-work rules.
Taskrail is an in-progress open-source project. The current release is v0.4.0;
the active development specification is v0.5.0.
v0.1.0established the repository contract: deterministic task progression, the authoritativeSTATE.md, and verification as a first-class concept.v0.2.0makes adoption in existing repositories easy — guidedretrofit, LLM-freeimportof rough notes into spec/task drafts, opt-in shippable agent skills, a version-aware non-destructiveinit, and conservativeSTATE.mdrepair — while keeping the core CLI provider- and tooling-independent.v0.3.0adds read-only insight into tracked work —status,stats, andcoverage— plus thespeccommand family for inspecting and authoring specs,unblockto release blocked tasks, and Windows install via WinGet.v0.4.0adds active-spec task selection and authoring, slugged task creation and atomic rename/repoint operations, mechanical spec/gap review, and version-skew detection across the binary, repository layout, and installed skills.v0.5.0is active development: uniform agent results, lifecycle-complete skills, human-owned repository notes, configurable prompt/task/spec review, prompt-bound safe review publication, a bounded external-process loop, and maintainer-run skill evaluations.v0.6.0plans durable task identity, cancellation preview, dependency editing, all-or-none legacy imports, and immutable archival over one ledger.v0.7.0plans reviewed OpenSpec/Spec Kit handoff, immutable planning-source receipts, and profile/receipt inventory.- Later work is tracked under
specs/README.md.
This repository also dogfoods the Taskrail workflow style — using planning/, docs/workflow/, and the packaged skill set it adopts like any adopter — until the product itself fully replaces that scaffolding.
Apache-2.0. See LICENSE.
specs/v0.4.0.md— current release scopedocs/commands.md— command deep-dive reference (envelope, gaps, slugs, repoint, import)specs/README.md— spec reading order and versioningplanning/STATE.md— live execution stateAGENTS.md— guidance for coding agentsCHANGELOG.md
The versioned specs in specs/ remain the normative source of truth for release scope and behavior.