1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
|
# wf — design reference
Shared task workflow for many coding agents (Claude Code sessions) working in parallel on the
projects under one folder (`/projects`). This file is the reference for the TASKS.md format,
`workflow.toml`, every `wf` command and the release rules. The memory/CPU ledger `wf res` has its
own document: [resource-ledger.md](resource-ledger.md). Overview: [README](../README.md).
## 1. Goal
One workflow definition and one tool, used by every agent working in a subfolder of
`/projects`, instead of task scripts and workflow text copied (and drifting) per project.
Project `CLAUDE.md` files keep project specifics only.
Success:
- An agent started in any project answers "continue" with the same steps.
- A workflow fix is made once, in one repo, under revision control.
- Task references cannot rot silently (`wf check` fails on them).
- Less always-loaded instruction text per project.
- A worker that hits a workflow problem reports it in one command and keeps working; fixes
reach all workers without breaking one that is mid-task.
- Routine task upkeep (list, find, add, reprioritise, flag, finish) is one short command
each; an agent never reads or rewrites TASKS.md whole to do it.
Non-goals:
- No runner: the task tool never builds, tests, commits or pushes.
## 2. Layout
```
/projects/
CLAUDE.md -> workflow/shared/CLAUDE.md symlink
workflow/ this repo
shared/CLAUDE.md shared loop (section 6), loaded by every project
CLAUDE.md rules for sessions changing this repo
wf.py CLI entry
wf_res.py `wf res` (files, /proc, systemd); logic in wflib/res.py
wflib/tasks.py pure text: parse, insert, remove, archive
wflib/refs.py pure text: ids, links, anchors, doc sections
wflib/config.py find project root, load workflow.toml
wflib/check.py everything `wf check` reports
wflib/ledgers.py in-flight plan ledgers for `wf next`
wflib/search.py pure text: ranked search over tasks, archive, docs
wflib/migrate.py one-time converter from a numbered task list
wflib/res.py pure logic of the resource ledger
tests/test_*.py unittest, stdlib
inbox.md reports from workers, append-only, not in git (section 7)
templates/ workflow.toml, TASKS.md, archive.md for `wf init`
CHANGES.md release notes, newest first
<group>/<project>/ any depth ≤ 3; a project = a folder with workflow.toml
```
Loading: Claude Code reads `CLAUDE.md` from the working directory and every parent, so
sessions in a project or in its `.worktrees/*` get the shared file without a pointer. If a
symlink is not loaded, a real one-line file `/projects/CLAUDE.md` containing
`@workflow/shared/CLAUDE.md` does the same. The shared file sits in `shared/` so that a session
inside `/projects/public/workflow` does not load it twice.
Each project `CLAUDE.md` starts with:
`Workflow: /projects/CLAUDE.md (shared loop, wf). Below = project specifics; this file wins on conflict.`
The folder scanned by `wf projects` is the parent of this repo, or its grandparent when the parent holds no project (`WF_ROOT` overrides); the inbox
is `inbox.md` here (`WF_INBOX` overrides). The tasks of the workflow itself can live in a
separate (private) wf project; nothing in this repo needs them.
Requirements: Linux, Python ≥ 3.11 (stdlib only). `wf res` also needs systemd user units with
cgroup v2.
## 3. TASKS.md format
```markdown
# Tasks — <project>
<free prose: commands, pointers>
## Awaiting your decision
- **a-smoke-screens**: needs you present, screen unlocked. ...
## Pending
- **t-cave-seams** [P3] (<1h): Terrain: cave entrance seams. Close the slit beside the lintel.
- Steps: ...
- Tests (red first): ...
- Done: ...
- After: [[t-terrain-cave-render]]
Ref: DESIGN.md#terrain
## Needs human
- **t-playtest-feel** [P1] (<1h): Play-test feel + tuning. ...
- [ ] ...
## Deferred
- **t-android-port** [P3] (10h): ...
```
Rules:
- Item = a line `- **<id>**` at column 0 plus every following line that is blank or indented.
A flush-left non-item line ends the item list of the section (prose after items is kept).
- Id grammar: `t-[a-z0-9-]+` for tasks (Pending, Needs human, Deferred), `a-[a-z0-9-]+` for
Awaiting items. A task keeps its id when it moves between sections.
(Changed from the chat draft, which used `h-` for Needs human: a prefix tied to a section
would force an id change on a move.)
- Ids are unique across TASKS.md and the archive, and never reused.
- Task header: `- **<id>** [P0-P3] (<effort>) [status]: Title. Goal.` (old `(<effort>, interactive)`
still reads as `Sessions: owner`; `check` warns.)
Effort is one of `<1h`, `1h`, `5h`, `10h`, `100h`. Awaiting items have no priority or effort.
- Status, optional, fixed words: `(in progress: <branch or note>)`, `(blocked: [[a-id]])`.
- `After: [[id]], [[id]]` lists dependencies. `Ref:` lists `path` or `path#anchor`, comma
separated, relative to the project root; text in parentheses after a ref is a free note.
- `Model: haiku|sonnet|opus` (none = opus): the model for the task (docs/lanes.md §0).
`Sessions: parallel|solo|owner [— why]` (none = parallel; `- Sessions:` works too):
solo = only while no other session is live in the project (lib/submodule bump, repo-wide refactor,
shared runner), and while it is in progress no other session picks anything; owner = needs the owner
present (real display, playtest, choices): `next` picks it only with `--owner`.
- Slices of a task over 1h: own items `t-<parent>-1`, `t-<parent>-2` (or any id given with
`--id`), each `After:` the previous one. The parent item stays as the summary and lists its
slices (`Slices:` line); it is done when its last slice is done, and `next` does not pick it
while a slice is open.
- Pending holds open work only, in pick order.
- Sections other than the four named ones are prose: the tool keeps them and checks only
the `[[id]]` links in them.
Archive file: newest first, one line per finished task:
`- 2026-09-29 **t-cave-seams** Terrain: cave entrance seams — <entry, ≤2 lines>`.
Older lines without an id stay as they are.
## 4. workflow.toml
In the project root. Its presence marks the project root for `wf`.
```toml
format = 1 # required: TASKS format version
tasks = "TASKS.md" # required
archive = "tasks/archive.md" # required
docs = ["DESIGN.md", "docs/"] # files/dirs whose links and anchors `check` validates
verify = ["dotnet build X.slnx", "dotnet test X.slnx"] # printed by `done`, never run
done = ["Feature changed -> docs/CATALOG.md"] # printed by `done`
ledgers = ".superpowers/sdd" # optional: in-flight plan ledgers
ctx_hint = 100000 # optional: tokens; 0 = off (see below)
worktree_setup = ["mkdir -p out && ln -sfn \"$WF_MAIN/out/data\" out/data"] # optional: `wf setup` (below)
quick_gate = ["dotnet test X.slnx --filter ContractTests"] # optional: `wf gate` (below)
cloud = true # optional: project opts in to the cloud lane (default false)
cloud_include = ["out/data/"] # optional: git-ignored paths force-added to the cloud snapshot
cloud_note = "No GUI here." # optional: appended to the cloud prompt
[anchors] # optional: index/spec anchor scheme
index = "DESIGN.md" # every task Ref anchor must be a heading here
index_section = "Subsystems"
specs = "docs/superpowers/specs" # explicit <a id> here needs an index entry
```
Only `format`, `tasks` and `archive` are required. Unknown keys are an error (catches typos).
`worktree_setup`: shell commands `wf setup` runs inside a lane worktree (cwd = the worktree's project
folder, env `WF_MAIN` = the main tree's project folder), in order, stopping at the first non-zero exit
(`wf: …`, exit 1). They provide git-ignored inputs a fresh worktree lacks (e.g. link `out/…` from the
main tree) and must be idempotent: wf-worker runs it after every worktree create/reuse.
`cloud` fit (cloud-lane spec §4.5): with `cloud = true` a task fits when runner-ready, not `Sessions: owner|solo`,
no `Cloud: no` line, effort <= `slice_above`, Model opus, and its title/body does not match the unfit regex list
in `wflib/cloud.py` (`wf` / `wf res` steps, GUI, LAN hosts, live server). Routing is per task: whoever adds it sets
`Cloud: yes|no` (`wf add/set --cloud`); `Cloud: yes` skips the regex list only (still opus only), `Cloud: no`
always excludes, no line = the rules above. `wf orch pick cloud` order (`cloud.pick_key`): `Cloud: yes` first,
then prio, then larger effort. `wf check` accepts the `Cloud:` line (yes|no, one per task).
`quick_gate`: fast regression commands (minutes, not the full slow gate) `wf gate` runs in the current
tree's project folder (worktree or main; env `WF_MAIN`), stopping at the first red (`wf: …`, exit 1);
none configured → exit 0. wf-worker runs it before `wf done`, so a break is caught by the task that made
it, not blamed on a later task by the async slow gate. The project picks the commands.
## 5. wf CLI
Run as `python3 /projects/public/workflow/wf.py <cmd>` from anywhere inside a project. The
project root is the nearest ancestor of the working directory that holds `workflow.toml`;
`--project DIR` overrides. Output is plain text, exit 0 on success.
Principle: every routine operation is one command with short output, so agents spend no
context on reading or rewriting task files. Hand edits are for body prose only.
Read (never write):
| Command | Does |
|---|---|
| `next [--brief]` | Awaiting items, Needs-human titles, in-flight ledgers, then the next task with `ctx` of its refs. `--brief`: the task only. Skips blocked items, open `After:`, parents with open slices, tasks held by another live session, `Sessions: owner` (unless `--owner`), `Sessions: solo` while another session is live. A solo task in progress by another live session → picks nothing, exit 1. Exit 1 if nothing is pickable. |
| `list [filters]` | One line per item: `id P2 1h status Title` (title cut to fit 100 columns). Default: Pending. Filters: `-s pending\|human\|awaiting\|deferred\|all`, `-p 0-3` (that priority or higher), `--ready` (pickable now), `--runner` (ready + Done line, not owner-bound, no open slices), `--blocked`, `--progress`, `--ref <path[#anchor]>`, `-n N`. Last line: counts per section. |
| `show <id>...` | The raw item(s), nothing resolved. |
| `ctx <id \| path#anchor>` | The item, its refs resolved, its dependencies and the items that depend on or link to it, then the notes of every area (`## Areas`) the item names (area name, code-map anchor or Paths entry as a whole word). For an anchor: that doc section plus the tasks that reference it. |
| `search <words>... [filters]` | Ranked hits over tasks, archive and `docs`: one line each, `id` or `path:line heading`, plus the matching line. Filters: `--tasks`, `--archive`, `--docs`, `-n N` (default 15). |
| `log [-n N] [words]` | Newest archive lines (default 10), optionally only those matching the words. |
| `projects` | Every project under the folder that holds the workflow repo (`/projects`) with a `workflow.toml` (depth ≤ 3): counts per section, next task id + title, number of `check` errors. |
| `check` | Validates; prints every problem; exit 1 on any error. |
Write (each changes only the items named, and prints the changed header lines):
| Command | Does |
|---|---|
| `add "<Title. Goal.>" -p N -e EFFORT [opts]` | New item. Id made from the title unless `--id` given or the text starts with `<id>: `; printed. Opts: `-s SECTION`, `--after ID,…`, `--ref REF,…`, `--parent ID` (slice: id `t-<parent>-N` or `--id`, `After:` the previous slice, parent's slice list updated), `--model M`, `--sessions solo|owner`, `-b` (body lines from stdin; never read unasked, a harness may hold stdin open). `add -` reads a whole item block from stdin. |
| `done <id>... [-m "<entry>"]` | Removes the task(s), puts the dated line(s) on top of the archive, prints the `verify` and `done` lists once. `-m` is required for a single task and applies to it; several ids without `-m` use each task's goal sentence. |
| `prio <id> <0-3>` | Sets the priority and moves the item to its place by the insert rule. |
| `move <id> <section>` or `move <id> --before\|--after <id>` | Moves between sections, or reorders inside one (refused if it breaks priority or `After:` order, unless `--force`). |
| `status <id> progress "<note>" \| blocked <a-id> \| clear` | Sets or clears the status words. |
| `set <id> [--title T] [--effort E] [--after IDS] [--ref REFS] [--model M] [--sessions S] [--done TEXT]` | Changes header fields and the `After:` / `Ref:` / `Model:` / `Sessions:` / `Done:` lines (`""` removes). `--interactive` = old spelling of `--sessions owner`. `--after ""` removes the line. `--title` keeps the goal, unless it has its own (`Title. Goal.`) or ends in `?`/`!`: then it replaces the whole text. |
| `rename <id> <new>` | Changes an open item's id (same kind, unused, not archived) and every `[[id]]` link in TASKS.md. |
| `note <id> "<line>"` | Appends one `- <line>` to the item body. |
| `body <id>` | Replaces the item body with stdin (header, `After:`, `Ref:` kept). |
| `tick <id> <n \| text>` | Checks the n-th (or the matching) `- [ ]` box of a Needs-human item. |
| `report "<what happened>" [--kind bug\|idea\|friction] [--cmd "<command>"]` | Appends one entry to the workflow `inbox.md` (section 7). Touches nothing in the project. |
| `batch N [--lanes L,…] [--prep] [--model M] [--for 6h] [--mem 4G] [--dry-run]` · `batch --status` | Starts an unattended batch orchestrator (`claude -p`, auto mode, no prompts, prompt `templates/batch-prompt.md`) as a `wf res run` job with `CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS` = `--for`; it writes `out/wf-batch-<stamp>.md`. `--status` = this project's newest batch job + newest summary file. `--prep` = prep batch (`templates/prep-prompt.md`): sonnet workers write Done (+Model) for up to N tasks without Done, unclear → awaiting; none → nothing started. See `docs/orchestrator.md`. |
| `init` | In a folder without `workflow.toml`: writes it, TASKS.md and the archive from the templates. |
| `migrate [--write]` | One-time converter from the numbered format. Without `--write`: prints the diff, the id map and notes, changes nothing. `--write` also raises `format` in `workflow.toml`. |
Every write command accepts `--dry-run` (prints a unified diff, writes nothing) and ends by
comparing the full check before and after: a change that adds a problem is refused and
nothing is written; problems that were there before do not block other edits.
Details:
- **Pick rule (`next`)**: first Pending item, in file order, that has no `(blocked: …)` and
whose `After:` ids are all in the archive. Items it skipped are listed in one line each
with the reason.
- **Ref resolution (`ctx`, `next`)**: `path#anchor` → the section under the heading whose
slug or explicit `<a id>` equals the anchor, up to the next heading of the same or higher
level, cut at 80 lines with a `… (N more lines: <path>:<line>)` note. `path` alone → the
file's first heading and its `**Goal:**` line if it has one. With `[anchors]` set, an
index anchor also prints the spec section that carries the same `<a id>`.
- **Context hint (`next`, `done`)**: with `CLAUDE_CODE_SESSION_ID` set, the last request's prompt size
(input + cache write + cache read) from the last 1 MB of `<CLAUDE_CONFIG_DIR|~/.claude>/projects/*/<id>.jsonl`;
over `ctx_hint` → last line `context ~Nk tokens (> Mk): ask the owner to /clear, then continue`. No id,
no transcript → nothing. Subagents share the id, so the line says it is about the main session.
- **Ledgers (`next`)**: for each `<ledgers>/*/progress.md`: plan path from its first line,
done tasks, where to resume, count of `Ruling:` lines.
- **Insert rule (`add`, `prio`, `move <section>`)**: position in the section = after the
last item of the same or higher priority, and after every item named in its `After:`.
Body is re-indented to two spaces. A block given to `add -` must carry an id and, for
tasks, a priority; a leading `N.` from the old format is refused with a message.
- **Id from title (`add`)**: `t-` (`a-` for Awaiting) + slug of the title (the text up to
the first `. `), at most 40 characters, cut at a word; if taken (TASKS.md or archive), `-2`, `-3`, … is appended.
- **Search ranking**: words are matched case-insensitively as whole words or word prefixes.
Score per hit = words matched × weight of where (id or title 5, doc heading 4, header
goal 3, `Ref:`/body/doc text 1); all words matched ranks before some. Ties: open tasks,
then docs, then archive. No index file; everything is read per call.
- **Exact ids only** in write commands; a unique id prefix is accepted by read commands.
An unknown id prints the three nearest ids.
- **`done`** refuses when the id is unknown or is a parent with open slices. On an `a-` id
it removes the item without an archive line (`-m` is optional there) and clears the
`(blocked: [[a-id]])` status of the tasks that waited on it, listing them.
- **`check`** errors:
- duplicate id; id reused from the archive; bad id grammar; id prefix not allowed in its section
- task without priority or with an effort outside the vocabulary
- `[[id]]` that is neither in TASKS.md nor in the archive
- `(blocked: [[x]])` where `x` is not an open `a-` item
- `After:` cycle; Pending item placed before an open item it is `After:`
- `Ref:` path missing; anchor missing in that file
- old-format leftovers: numbered items
- item after a flush-left prose line inside a task section; malformed item header
- with `[anchors]`: task anchor without index heading; two index headings with the same
slug; index link to a missing file or anchor; explicit spec `<a id>` without index entry
- config: missing required key, unknown key, configured path that does not exist
In `docs` only `[[t-…]]` / `[[a-…]]` links are validated (other `[[x]]` may be the
project's own wiki links).
Warnings (exit stays 0): `#N` number refs in TASKS.md; task in progress with no branch/note; Awaiting item that no task
references and that is older than 30 days by `git blame`.
- **Writes** are whole-file: read, change in memory, write to a temp file in the same
directory, rename. If the file changed on disk between read and write (mtime + size), the
command stops without writing. `done` writes the archive first, then TASKS.md; if the
second write fails it reports that the archive line must be removed.
- **Errors** go to stderr as one line each, naming file and id; no tracebacks for expected
failures.
## 6. Shared CLAUDE.md
Text: `shared/CLAUDE.md` (the file is the authority). Sections: Style (concise everywhere;
compaction summaries, specs, plans stay full and exact), Cold start, Loop, Done, TASKS.md,
Context economy, Memory / CPU, Workflow problems.
## 7. Improving the workflow without disturbing workers
Problem: `wf.py` and the shared CLAUDE.md are live for every worker the moment they change.
A worker that edits them mid-task, or a half-finished change, would hit all the others.
**Report (any worker, any time)**
- `wf report` appends to `inbox.md` with a single `O_APPEND` write, so parallel workers
never clash and nobody waits. Entry: date, project, kind, the text, the command if given,
wf version (git short hash).
`- 2026-09-29 proj-a friction: prio change needs two commands (cmd: wf prio …) @84decd1`
- The worker then works around the problem and continues its own task. It does not edit
anything under `/projects/public/workflow` and does not wait for a fix.
- `wf next` in any project prints one line when the inbox has entries, for the user's eyes:
`workflow inbox: 3 reports (triage: workflow session)`.
**Triage and fix (a workflow session, started by the owner)**
- The workflow's own tasks live in a wf project of their own (e.g. a private
`/projects/workflow`), whose CLAUDE.md holds the rules of this section. Cold start there:
each inbox entry becomes a task (`wf add`, duplicates merged) or is rejected with a line in the
archive; the entry is then deleted from `inbox.md`. `inbox.md` is git-ignored; tasks and
archive carry the record. Bookkeeping there changes nothing a worker runs.
- Changes to `wf.py`, `wflib/`, `shared/CLAUDE.md` or templates happen in a git worktree
`/projects/public/workflow/.worktrees/<topic>` on a branch, never in the main tree. Workers keep
running the main tree's `master` meanwhile.
- Release = fast-forward merge to `master` in the main tree after: tests green, and
`wf check` run with the new code against every project listed by `wf projects` gives the
same result as before the change (or differences that are the intended ones).
- The user starts workflow sessions and sees the release; no worker does it on its own.
**Compatibility rules for a release**
- Commands and options are only added. Renaming or removing one keeps the old spelling
working for one release, printing a one-line notice with the new spelling.
- A change to the TASKS.md format raises `format` and ships with a `wf migrate` step.
`wf` run in a project with an older `format` refuses write commands with
`TASKS format 1, wf needs 2: run wf migrate --write (idle project, one commit)`; read
commands keep working on the old format.
- Shared CLAUDE.md text changes reach a worker at its next session start; a rule change
must therefore not make files written under the old rule invalid.
- Every release adds a line to `CHANGES.md` (newest first): date, what changed, what a
project has to do, if anything.
## 8. Testing
- `wflib` functions are pure (text in, text out); unit tests use literal before/after texts
written by hand, never output of the code under test.
- CLI tests run `wf.py` with `--project <tempdir>` on fixture projects: pick rule, skip
reasons, insert position with priority and `After:`, `done` refusals, every `check` error
once, write-conflict stop, `list` filters, `prio`/`move` placement and refusals, `status`,
`set`, `note`, `body`, `tick`, search ranking order on a fixed corpus, `report` from two
processes at once (both entries whole), `format` gate, migrate on a fixture with the known oddities (stray `N.`,
`N/M` titles, `After:` by title, prose after items).
- Each new test is seen failing first, and again with the tested line broken by hand.
|