tasks
Guides

Working with agents

The loop an agent should follow, the skill that teaches it, and the rules that bite.

The CLI is usable by a person, but it exists because agents need somewhere to put state that outlives a context window. This page is what you would tell an agent — and, via the bundled skill, what it gets told automatically.

The skill

tasks init installs an agent skill into .claude/skills/tasks/. It is what makes an agent reach for this CLI on its own rather than inventing a TODO.md: it documents when persistent tasks beat a session checklist, the claim-before-working loop, the invariants, and the JSON contract.

tasks skill install [--force]

Add it to a project that predates it, or refresh it after upgrading the CLI, with the command above. An existing install is left alone unless --force, so local edits survive an accidental reinstall.

Commit .claude/skills/tasks/ so every agent working in the repo gets it.

Skip it if you want

tasks init --no-skill scaffolds .tasks/ without touching .claude/.

The loop

tasks ready --json                  # unblocked and available right now
tasks claim <id>                    # take it — records who and when
# ...do the actual work...
tasks status <id> DONE              # complete it

Two things about this that are easy to get wrong:

Claim before working, not after. The claim is what stops a second agent starting the same task, and it is only useful if it happens first. Claiming on the way out records history but prevents nothing.

Update status as you go, not in a batch at the end. A session that dies mid-task leaves a claimed task, which is recoverable — you can see who had it and when. A session that dies leaving everything PENDING loses the work record entirely.

Before anything else

tasks list --json

Exit 3 with No .tasks/ directory found means the project is not set up. Ask before running tasks init — creating it is a project-level decision, not a side effect of a question about work.

If tasks is not on PATH, use npx @speekl/tasks in place of tasks everywhere.

Rules that will bite

Creating tasks well

tasks new <phase> <name...> [--id <id>]
tasks dep add <id> <ref>...

The phase directory is created on first use. The id defaults to a kebab-case slug of the name and must be unique across the whole tree, including .archive/.

Write the body after creating. tasks new scaffolds ## Description, ## Acceptance criteria, and ## Notes; fill them in with a normal file edit at the path returned in the JSON. A task whose body is empty is not a task, it is a reminder. Put enough in it that a different agent arriving cold in a later session can do the work without asking.

Prefer several small dependent tasks over one large one. depends_on is what makes tasks ready meaningful; a single task with a ten-item checklist in its body tells the next agent nothing about what is unblocked.

Parsing output

Use --json whenever you parse. The plain tables are for humans and their layout is not stable. See the JSON contract for shapes and codes.

id=$(tasks ready --json | jq -r '.tasks[0].id // empty')
[ -n "$id" ] && tasks claim "$id"

Without -p / -a, list and ready scope to active_phase from .tasks/config.yml when it is set. If a task you expect is missing, retry with --all before concluding it does not exist.

When something looks wrong

Run tasks validate --json before hand-repairing anything. It reports id/filename mismatches, phase drift, unknown statuses, broken or cyclic dependencies, and a stale _index.md. --fix repairs the mechanically safe subset and leaves the rest for a human.

Do not fix drift by editing YAML. See troubleshooting.

On this page