Files
reliquary/docs/workpad/research/2026-10-15-14-claude-code-tasks-and-pcvelz.md

202 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Claude Code native Tasks, and how pcvelz/superpowers uses them
- Model: claude-opus-5-5 (Claude Code 2.1.296 on this machine)
- Date: 2026-10-15 13:16 CEST
- Sources: Claude Code docs at code.claude.com (fetched 2026-10-15), `anthropics/claude-code` CHANGELOG.md (main, top entry 2.1.300) with npm publish dates, a local read of `~/.claude/tasks/`, and the pcvelz fork clone at `/tmp/sp-research/pcvelz` (HEAD `cb12f4f`, 2026-09-18). Cloned content was read only; nothing from it was run.
- Labels: **[fact]** is cited. **[observed]** was seen on this machine. **[inferred]** is reasoned from cited facts. **[judgement]** is my opinion.
## Summary
1. **[fact]** Claude Code's native Tasks are four tools: `TaskCreate`, `TaskGet`, `TaskList` and `TaskUpdate`. They arrived in 2.1.16 (2026-01-22) and replaced `TodoWrite`. Each task is a JSON file under `~/.claude/tasks/<list>/`. Tasks survive compaction and `--resume`. A new session gets an empty list unless `CLAUDE_CODE_TASK_LIST_ID` names a shared one.
2. **[fact]** In 2.1.233 (2026-08-14), Claude Code stopped offering the task tools on Opus 4.8, Sonnet 5, Fable 5 and newer models. In 2.1.268 (2026-09-10) it changed to an allowlist of older models. On Opus 5.5 they are **off** unless `CLAUDE_CODE_ENABLE_TODO_TOOLS=1` is set. Anthropic's stated reason: newer models track multi-step work without a written checklist, and the tools' definitions and reminders cost context.
3. **[fact]** The fork's cross-session resume is **not native**. `<plan>.md.tasks.json` is a mirror that the model writes by hand, because skill prose tells it to (Read, edit, Write after every status change). A new session rebuilds the native tasks from that file. Its hooks never read the task store. They rebuild task state by replaying the session transcript.
4. **[judgement]** The parts that make it "work better with Claude" are (a) a live checklist that the harness re-injects into context, which keeps the model on track after compaction, and (b) a structured event (`TaskCreate`/`TaskUpdate`) that hooks can gate on. Persistence is no better than a markdown file. In practice it is the same thing: a file the model keeps current.
5. **[judgement]** For Spire and grotto, a native Task list is at best a **complement**, never the handoff substrate. It is per-user and outside the repo, swept by retention, invisible to Codex and to background subagents, and it cannot hold what the run log holds (deviations, flags, expected-broken areas). The ideas worth taking are the compact task shape (Goal/Files/AC/Verify plus `blockedBy`) and "status lives in the plan file, the harness list is a view".
---
## 1. Claude Code native Tasks today
### What they are
| Claim | Source |
|---|---|
| Tools: `TaskCreate` (create), `TaskGet` (full details of one task), `TaskList` (all tasks with status), `TaskUpdate` (status, dependencies, details, delete). None of them needs permission. | **[fact]** docs `tools-reference` table, lines 54–59 |
| "Added new task management system, including new capabilities like dependency tracking". | **[fact]** CHANGELOG 2.1.16 (npm 2026-01-22) |
| `CLAUDE_CODE_ENABLE_TASKS=false` (later `0`) keeps the old `TodoWrite` system. | **[fact]** CHANGELOG 2.1.19 (2026-01-23); docs `env-vars` line 298 |
| Tasks can be deleted via `TaskUpdate`. | **[fact]** CHANGELOG 2.1.20 (2026-01-27) |
| `TaskCreated` hook event (fires on `TaskCreate`; exit 2 or `{"decision":"block"}` deletes the task and returns the message as a tool error). `TaskCompleted` hook event (fires when a task is marked completed, or when a teammate ends its turn with in-progress tasks). Neither takes a matcher. | **[fact]** CHANGELOG 2.1.84 (2026-03-25); docs `hooks` §TaskCreated / §TaskCompleted. `TaskCompleted`/`TeammateIdle` were added earlier, for agent teams. |
| `TaskList` sorts by ID (2.1.119, 2026-04-23). Malformed `TaskCreate` input is repaired automatically (2.1.169, 2026-06-08). | **[fact]** CHANGELOG |
| `Ctrl+T` toggles the task list view, which shows up to five tasks. "Tasks persist across context compactions." The expanded view is restored on `--resume`/`--continue`. | **[fact]** docs `interactive-mode` §Task list, lines 697–700 |
### Storage and lifetime
| Claim | Source |
|---|---|
| `~/.claude/tasks/` holds "Task lists written by the task tools, one directory per list". It is listed as "Task lists that a resumed session would pick up". | **[fact]** docs `claude-directory` lines 1550, 1705 |
| On this machine each list is a directory named with the **full session UUID**. It contains a `.lock` and one `N.json` per task with exactly these keys: `id, subject, description, activeForm, status, blocks, blockedBy`. There is no `metadata` or `owner` key. 19 tasks in 3 lists, all `completed`, last written 2026-09-22. Each directory name matches a transcript under `~/.claude/projects/…`. | **[observed]** 2026-10-15. The Claude Code version that wrote them is unknown. |
| Agent-team task lists live in `~/.claude/tasks/session-<first 8 chars of session id>/`. They "persist locally and [are] never uploaded, so resumed sessions keep their tasks". Retention follows `cleanupPeriodDays`. `claude purge` deletes per-session `tasks/`. | **[fact]** docs `agent-teams` lines 248–254; `claude-directory` line 1634 |
| `CLAUDE_CODE_TASK_LIST_ID=<name>` makes sessions share a named list in `~/.claude/tasks/<name>/` ("coordinate on a shared task list"). | **[fact]** docs `interactive-mode` line 701; `env-vars` line 413 |
| Across compaction the list persists. On `--resume` the list is picked up. A **new session or `/clear`** starts a new session id and so an empty list, unless `CLAUDE_CODE_TASK_LIST_ID` is set. | Compaction and resume: **[fact]**. New session and `/clear`: **[inferred]** from per-session directories. The docs do not state it for `/clear`. |
### Dependencies, subagents, teammates
| Claim | Source |
|---|---|
| Tasks have `blocks`/`blockedBy`. In agent teams, "a pending task with unresolved dependencies cannot be claimed", claims use file locking, and completing a task unblocks its dependents automatically. | **[fact]** docs `agent-teams` §Assign and claim tasks |
| In a solo session, `blockedBy` is **advisory**: nothing stops `TaskUpdate status=in_progress` on a blocked task. | **[inferred]**. The fork ships `pre-task-blockedby-enforce.sh` because "skill prose that says 'respect dependencies' is not enforcement" (header, lines 1–4). Not tested here. |
| Subagents get the task tools only when the parent session has them. In-process teammates follow the session. Split-pane teammates follow their own model. | **[fact]** docs `tools-reference` line 596; `sub-agents` line 452 |
| **Background subagents lose the Task tools.** Their built-in allowlist includes `TodoWrite` but not `TaskCreate/Get/List/Update`. Agent-team teammates keep them. | **[fact]** docs `sub-agents` lines 444, 450 |
| Foreground subagents sometimes missed the task tools until 2.1.286 (2026-09-30). | **[fact]** CHANGELOG 2.1.286 |
| A foreground subagent's `TaskCreate` writes into the session's list, not a private one. | **[inferred]** from `TaskCreated` input carrying `agent_id` "identifies the subagent … creating the task" (docs `hooks`, requires 2.1.290). Not verified. |
### Versus `TodoWrite`
- **[fact]** `TodoWrite` "manages the session task checklist". It is off by default in favour of the four Task tools and can be selected with `CLAUDE_CODE_ENABLE_TASKS=0` (docs `tools-reference` line 60). `~/.claude/todos/` is a legacy directory that current versions no longer write (docs `claude-directory` line 1558).
- **[unverified, from memory]** `TodoWrite` rewrites the whole list in one call (`content/status/activeForm`, no ids, no dependencies). The Task tools address tasks one at a time by id, have dependencies, and keep the list in per-list files on disk.
### The env flag and the version change
- **[fact]** 2.1.233 (npm 2026-08-14): "Todo/task-tracking tools (TaskCreate/Get/Update/List, TodoWrite) are no longer available on Opus 4.8, Sonnet 5, Fable 5, Mythos 5, and newer models; set `CLAUDE_CODE_ENABLE_TODO_TOOLS=1` to bring them back."
- **[fact]** 2.1.268 (2026-09-10) flips this to an allowlist: the tools are offered only on Claude 3.x, Opus 4.0–4.7, Sonnet 4.0–4.6 and Haiku 4.5. Unknown model ids (for example custom names behind a gateway) also get none. Docs: "The default set described here applies in Claude Code v2.1.268 and later" (`tools-reference` line 598).
- **[fact]** Ways to opt in: the env var (which "requires Claude Code v2.1.233 or later"), naming a task tool in `--allowedTools`, or listing it in `--tools`. Background sessions (agent view) and cloud sessions get the tools on every model regardless (`tools-reference` lines 589–594).
- **[fact]** Stated rationale: "On newer models, Claude keeps track of multi-step work without a written checklist, and the tools' definitions and reminders take up context" (`tools-reference` line 585).
- **[fact]** The fork's README claim ("2.1.233 removed the task tools by default", README:85) is correct for Opus 4.8+/Sonnet 5/Fable 5 at 2.1.233, and for every non-allowlisted model from 2.1.268 on. **On this setup (Opus 5.5, 2.1.296) the tools are off.** Spire's harness note "`TaskList` is the todo list" (`spawning-claude-code.md:33`, 2026-09-22) predates the allowlist flip.
---
## 2. How the fork uses them
### Task shape: `skills/shared/task-format-reference.md`
- Subject at most 60 characters, because "the harness re-injects every task's subject line into context during periodic reminders" (`:7`).
- Required body sections: **Goal** (one sentence, what not how), **Files** (create/modify/delete with line ranges), **Acceptance Criteria** (checkboxes), **Verify** (exact command and expected output) (`:15–24`). Optional: **Context** and **Steps**. TDD happens inside a task, not as separate tasks (`:28–32`, `:144–146`).
- Metadata goes in a ` ```json:metadata ` fence **inside the description**, because "the `metadata` parameter on TaskCreate is accepted but **not returned by TaskGet**" (`:36`; README:143). Keys (`:38–55`):
- Core: `files`, `verifyCommand`, `acceptanceCriteria` (required); `estimatedScope`.
- Gates: `userGate`, `tags`, `requiresUserSpecification`, `gateScope`, `failurePolicy`, `subagentBrief`.
- Dispatch pins: `subagentType`, `model`, `modelTier` (`mechanical|standard|frontier`), `dispatchBrief`.
- Evidence: `requireEvidenceTokens`, `requireABCompare`.
- Granularity: one task produces one commit and one reviewable unit. Setup and docs fold into the task that needs them (`:117–150`).
### Who writes what, and where
| Artifact | Writer | Mechanism |
|---|---|---|
| Native tasks | the model | `writing-plans` "REQUIRED FIRST STEP": call `TaskList`, then `TaskCreate` per plan task with the full Goal/Files/AC/Verify body verbatim plus fence (`skills/writing-plans/SKILL.md:40–51, 303–340`). After that, `TaskUpdate addBlockedBy` (`:351–359`). |
| `<plan>.md.tasks.json` | **the model, by prose instruction** | Written "at plan completion … in the same directory as the plan" as `{planPath, tasks:[{id, subject, status, blockedBy, description}], lastUpdated}` (`writing-plans/SKILL.md:377–405`). The plan author checks it with `grep -c '**Goal:**'` etc. (`:309–318`). |
| `.tasks.json` status sync | the model (controller) | "Read the tasks file, update the task's status … set lastUpdated … write back" after every change (`executing-plans/SKILL.md:37, 79`; `subagent-driven-development/SKILL.md:599–608`; `dispatching-parallel-agents/SKILL.md:208–218`: "Controller is responsible for `.tasks.json` sync"). **No hook writes it.** |
| Context hygiene | the model | On completion, shrink the task description to its Goal line plus "Complete — see ledger", because "the harness re-injects every task's full description on periodic reminders". User-gate tasks are the exception (`subagent-driven-development/SKILL.md:445–452`). |
### Resume across sessions
`executing-plans/SKILL.md:26–37` ("Step 0: Load Persisted Tasks"):
1. Call `TaskList`.
2. Look for `<plan-path>.tasks.json`, or glob for one.
3. If the file exists and the native list is empty, recreate every task with `TaskCreate` (full description and metadata) and restore `blockedBy` with `TaskUpdate`.
4. If native tasks exist, check them against the plan and resume from the first `pending`/`in_progress`.
5. If neither exists, bootstrap from the plan's `### Task N:` headers (`:53–65`).
Entry point: `/superpowers-extended-cc:executing-plans <plan-path>` (`writing-plans/SKILL.md:407–414`). The `session-start` hook (`hooks/session-start`, 146 lines) injects `using-superpowers` plus routing and workflow notices. **It does not restore tasks.** Resume therefore depends on the model running Step 0 correctly.
**[judgement]** This is the same reliability class as "read the run log and continue". The difference is a second copy (the native list) that must be kept in sync by hand.
### Hooks
Registered by the plugin (`hooks/hooks.json`):
| Hook | Event / matcher | Enforces | Reads |
|---|---|---|---|
| `pre-taskcreate-model-tier` (205 lines) | PreToolUse `TaskCreate` | Only if `docs/superpowers/model-routing.json` or `~/.claude/superpowers/model-routing.json` exists. Then: a plan-shaped task (template headers or subjects like `Task 3:`/`Phase 0.1:`) must have a fence, and the fence needs a valid `modelTier` or a `model` pin. Exit 2 with a self-teaching message. Fails open; kill switch `SUPERPOWERS_ROUTING_GUARD=0` (`:51–125, 157–205`). | `tool_input.description` |
| `pre-taskcreate-commit-strategy` (152 lines) | PreToolUse `TaskCreate` | When `workflow.json` has `commitStrategy: at-end`, blocks plan tasks that carry per-task Commit steps (header `:1–18`). | `tool_input` |
| `pre-agent-model-routing` (558 lines) | PreToolUse `Agent` | Agent `model` must match the tiers of the in-progress tasks. | transcript replay, keyed by the `"Task #N created successfully"` tool_result (`:226–246`) |
| `pre-askuser-handoff-guard` (405 lines) | PreToolUse `AskUserQuestion` | Forces the two-option execution menu after `writing-plans`. | transcript |
Opt-in examples (`hooks/examples/`, which the user pastes into settings, README:345–481):
| Hook | Event | Enforces |
|---|---|---|
| `pre-commit-check-tasks.sh` (63 lines) | PreToolUse `Bash` matching `git commit` | Blocks while any task is `in_progress`. Pending tasks pass (`:1–10, 55–60`). |
| `pre-task-blockedby-enforce.sh` (210 lines) | PreToolUse `TaskUpdate` with `status=in_progress` | Refuses while any `blockedBy` entry is not completed. |
| `post-task-complete-revalidate.sh` (478 lines) | PostToolUse `TaskUpdate` with `completed` | User-gate tasks need evidence per AC. `requireEvidenceTokens` axes must appear. |
| `stop-revalidate-user-gates.sh`, `stop-wait-guard.sh`, `stop-deflection-guard.sh` | Stop | Block "done" claims without `AC: … PROVEN BY` evidence, turns that end on "waiting…", and "context is full" excuses. |
| `pre-agent-task-dispatch-validate.sh`, `post-agent-return-validate.sh` | Pre/PostToolUse `Agent` | Dispatch pins from metadata; evidence tokens in subagent reports. |
Mechanism notes:
- **[fact]** Nearly every hook rebuilds task state by **replaying the transcript JSONL** (`TaskCreate` tool_use count, then later `TaskUpdate`s). It does not read `~/.claude/tasks/`. Example: `pre-commit-check-tasks.sh:33–54`.
- **[fact]** The only exception: `stop-wait-guard.sh` reads `~/.claude/tasks/session-<sid8>/*.json` (`:35, 91`). On this machine the solo-session lists use full-UUID directory names, so that path may miss them (**[observed]**, version-dependent, not tested).
- **[fact]** The fork found that counting creates mis-keys ids ("the old 1..N create-count mis-keyed once earlier tasks shifted the numbering", `pre-agent-model-routing:226–230`; commit `3b5f5d2`, 2026-06-17). It fixed this **only in that hook**. `pre-commit-check-tasks.sh:35`, `post-agent-return-validate.sh:46` and `pre-agent-task-dispatch-validate.sh:59` still use `next_id = 1` counting. **[inferred]** A `TaskCreate` blocked by the tier gate still leaves a tool_use in the transcript, so those hooks can mis-key ids after any blocked create.
- **[fact]** The fork uses PreToolUse matchers on `TaskCreate`/`TaskUpdate` and does not use the native `TaskCreated`/`TaskCompleted` events (grep: no hits).
- **[fact]** The fork's design never touches `CLAUDE_CODE_TASK_LIST_ID`, the one native cross-session mechanism. `/onboard` writes only `CLAUDE_CODE_ENABLE_TODO_TOOLS=1` (`commands/onboard.md:42–62`, added `becc0bd` 2026-08-16).
---
## 3. Useful vs ceremony (judgement)
### What actually helps the model
1. **[judgement]** **A checklist the harness keeps in front of the model.** Task reminders are re-injected, and the docs confirm "reminders" exist (`tools-reference` line 585). After compaction or a long tool chain, the model still sees what is next and what is open. This is the "execution on rails" effect (README:51) and most of what "works better with Claude" means. Anthropic's position for Opus 4.8+ is that the model no longer needs it and it costs context.
2. **[judgement]** **A structured, hookable event.** "Task N is now in progress / completed" is a tool call, so a hook can block it. With a markdown run log you can only gate on Bash or Edit patterns. This is real, but it only pays off if you want hard gates, and Spire's lean doctrine deliberately does not.
3. **[judgement]** **The task shape.** Goal/Files/AC/Verify is a compact, checkable unit. "Verify = exact command + expected output" is the most useful field. Keep subjects short and shrink completed tasks, because reminders re-inject them.
4. **[judgement]** **Human visibility** (`Ctrl+T`). Nice when watching a session. Irrelevant for a closed-door Athanor.
### What is ceremony
- **[judgement]** Two sources of truth (native list plus `.tasks.json`) kept in sync by the model after every status change, with `grep -c` self-checks. This is overhead to make up for native lists not surviving new sessions.
- **[judgement]** Metadata stuffed into description fences (because `TaskGet` drops `metadata`), parsed by about 3,400 lines of bash and python that replay the transcript.
- **[judgement]** User-gate tagging, evidence-token axes, the handoff-menu guard and the wait/deflection Stop hooks. These enforce the SDD loop, which Spire has already rejected.
### Spire Athanor (one phase per turn, run log, Igor resumes)
What already exists: the run log (`implementation/references/run-log.md`: header, block table, per-block handoff note with deviations, flags, left-for-later, expected-broken and commits; phase close), trunk notes, and journals. Igor resumes the Athanor with `SendMessage`.
- **[judgement]** The run log is the better handoff substrate. It is in the repo, versioned, readable by the reviewer, by Codex and by a human, and its fields carry judgement that the native schema cannot hold (`subject/description/status/blockedBy` only).
- **[judgement]** A native list could serve as a **within-turn block checklist** for the Athanor: blocks of the current phase, `blockedBy` mirroring the plan's order. That would give the reminder effect during a long phase. It would not replace any run-log content.
- What breaks or limits it:
- **[fact]** Igor and the Athanor run on Opus 5.5, so the tools are absent unless the user sets `CLAUDE_CODE_ENABLE_TODO_TOOLS=1`. That is session-wide, and subagents follow the session, so it cannot be enabled for the Athanor alone.
- **[fact]** If Igor ever dispatches or resumes the Athanor **in the background**, the Athanor has no Task tools at all (`sub-agents.md:444`).
- **[inferred]** Athanor-created tasks would land in Igor's session list and mix with Igor's own items.
- **[judgement]** The phase reviewer cannot read `~/.claude/tasks/` in a useful way, and the run log already has to exist for review.
- **[judgement]** Net: marginal. If a phase is so long that the Athanor loses its place, that is a block-size problem for the plan attack, not a tracking problem.
### grotto (plans and handoffs that must survive drift and session death, standalone and harness-neutral)
- **[judgement]** The plan file in the repo must be authoritative. Native lists are per-user, outside git, swept by `cleanupPeriodDays`, gone on `/clear` and new sessions (**[inferred]**), absent on Codex and on Opus 5.5 by default, and absent in background subagents. None of that survives "session death" better than a markdown file. The fork's own resume path is "read a JSON file the model kept current".
- **[judgement]** What grotto should take:
- The per-task fields (Goal, Files, AC, Verify, `blockedBy`) as a light, optional plan-task shape.
- A **status line per task in the plan or handoff file** (for example `- [x] T3 — done @<commit>`), updated at each completion.
- A **resume step**: read the plan's status, find the first open unblocked task, continue.
This is the fork's Step 0 without the second copy.
- **[judgement]** Native Tasks as an optional **projection**: "if `TaskCreate` is in your tool list, mirror the open tasks of this plan into it for the session; the plan file stays the record." That gives the reminder effect where it exists and degrades to nothing elsewhere.
- **Codex**: **[unverified, from memory]** Codex CLI has its own session-local plan tool (`update_plan`: steps with status, no ids or dependencies). The same "file is the record, harness list is a view" rule covers it.
### Is a native list plus a file mirror better than markdown run logs?
**[judgement]** No. It is at best a complement. Markdown is the record. A native list is a session-local view that keeps the model on track while it works, and it is worth having only when the tools are present anyway.
---
## 4. Smallest adoptions that could pay off (options, not decisions)
| Option | Cost | Payoff | Notes |
|---|---|---|---|
| **A. Format only.** grotto plan tasks may use Goal/Files/AC/Verify plus `blockedBy`. The handoff file carries one status line per task. Resume = read status, take the first open unblocked task. | One page of skill text | Most of the fork's resume value, in any harness | No hooks, no env flag. Fits Spire's lean doctrine. |
| **B. A plus optional native projection.** One paragraph: "If `TaskCreate` is available, mirror open tasks for this session. Short subjects, shrink on completion. The file stays authoritative; update it at each completion." | One paragraph | The reminder effect when tools are on; no-op otherwise | Never write `.tasks.json`. The markdown status lines are the mirror. |
| **C. Measured experiment.** Run one Athanor phase with `CLAUDE_CODE_ENABLE_TODO_TOOLS=1` in Igor's session and one without. Compare drift (skipped or reordered blocks, missing handoff notes) and tokens in the run logs. | One run each | Evidence on whether Opus 5.5 benefits, against Anthropic's claim that it does not | The flag is session-wide, so Igor gets the tools too. |
| **D. `CLAUDE_CODE_TASK_LIST_ID=<plan-slug>` for one multi-session build.** | An env var per launch | Native cross-session list without `.tasks.json` | Per machine, outside git, swept by retention. Experimental, not a grotto dependency. |
| **E. One native hook, not transcript replay.** For example a `TaskCompleted` hook that appends "task X completed `<date>`" to the run log, or a PreToolUse `git commit` check against the plan file's status lines. | Small script | A mechanical trail or gate | Only if a gate is ever wanted. Use `TaskCreated`/`TaskCompleted` (documented, carry `task_id`) rather than counting transcript creates, which the fork found unreliable. |
| **F. Do nothing.** | none | none | Fine if the run log plus phase-sized blocks already prevent drift. |
---
## Could not verify
- Whether `/clear` gives a fresh task list. Inferred from per-session directories; the docs do not say so explicitly.
- Whether a foreground subagent's `TaskCreate` writes into the parent session's list (inferred from `agent_id` in `TaskCreated` input) and whether the parent sees it in `TaskList`.
- Whether the solo-session list directory is now `session-<sid8>` (as for teams, and as `stop-wait-guard.sh` assumes) or the full UUID (as observed here on 2026-09 files). The writing version is unknown.
- That `TaskGet` drops the `metadata` parameter (fork claim, README:143). The local task files have no `metadata` key, but those tasks may never have passed one.
- That the harness re-injects full task **descriptions**, not just subjects, in periodic reminders (fork claim, `subagent-driven-development/SKILL.md:447`). The docs only mention that "reminders take up context".
- That `blockedBy` is not enforced in solo sessions (inferred from the fork needing a hook). Not tested, since the tools are off in this session.
- `TodoWrite`'s exact schema and Codex's `update_plan` behaviour (from memory).
- Whether reminder-driven tracking measurably helps Opus 5.5. No data; option C would produce some.