ww-agentic-workflows 1.0.0.dev3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. ww/__init__.py +18 -0
  2. ww/_bundled_extensions/ww/git/extension.py +1728 -0
  3. ww/action_execution.py +887 -0
  4. ww/actions/__init__.py +94 -0
  5. ww/actions/command.py +444 -0
  6. ww/actions/contracts.py +699 -0
  7. ww/actions/extension.py +197 -0
  8. ww/actions/mcp.py +84 -0
  9. ww/actions/prompt.py +74 -0
  10. ww/actions/skill.py +62 -0
  11. ww/actions/slash_command.py +63 -0
  12. ww/agents.py +151 -0
  13. ww/amendments.py +54 -0
  14. ww/artifacts.py +93 -0
  15. ww/assessments.py +181 -0
  16. ww/assets/__init__.py +2 -0
  17. ww/assets/agent_instructions.md +49 -0
  18. ww/assets/docs/examples.md +879 -0
  19. ww/assets/docs/features.md +4639 -0
  20. ww/assets/docs/specification.md +1876 -0
  21. ww/assets/noww_skill.md +11 -0
  22. ww/assets/workflows/catchall.yaml +26 -0
  23. ww/assets/workflows/onboarding.yaml +586 -0
  24. ww/assets/workflows/scriptize.yaml +130 -0
  25. ww/assets/ww-automate_skill.md +23 -0
  26. ww/assets/ww-deduce-feedback_skill.md +38 -0
  27. ww/assets/ww-feedback-rules_skill.md +48 -0
  28. ww/assets/ww-learn-project_skill.md +22 -0
  29. ww/assets/ww-refresh_skill.md +26 -0
  30. ww/assets/ww-rule_skill.md +83 -0
  31. ww/assets/ww-rules-from-artifacts_skill.md +22 -0
  32. ww/assets/ww-scriptize_skill.md +33 -0
  33. ww/assets/ww-setup_skill.md +94 -0
  34. ww/assets/ww-solve_skill.md +23 -0
  35. ww/assets/ww-suggest_skill.md +32 -0
  36. ww/assets/ww-wizard_skill.md +105 -0
  37. ww/assets/ww_skill.md +59 -0
  38. ww/assignments.py +283 -0
  39. ww/bootstrap.py +405 -0
  40. ww/builtin_workflows.py +215 -0
  41. ww/changes.py +225 -0
  42. ww/child_coordination.py +482 -0
  43. ww/children.py +106 -0
  44. ww/claude_permissions.py +115 -0
  45. ww/cli/__init__.py +7 -0
  46. ww/cli/__main__.py +6 -0
  47. ww/cli/audit.py +129 -0
  48. ww/cli/catalogs.py +131 -0
  49. ww/cli/discover.py +607 -0
  50. ww/cli/initialization.py +898 -0
  51. ww/cli/lookup.py +287 -0
  52. ww/cli/main.py +1768 -0
  53. ww/cli/parser.py +1200 -0
  54. ww/cli/prompts.py +217 -0
  55. ww/cli/updates.py +117 -0
  56. ww/completion_artifacts.py +156 -0
  57. ww/completion_inputs.py +39 -0
  58. ww/config/__init__.py +582 -0
  59. ww/config/actions.py +591 -0
  60. ww/config/composition.py +571 -0
  61. ww/config/rules.py +511 -0
  62. ww/config/steps.py +1220 -0
  63. ww/config/values.py +223 -0
  64. ww/config_files.py +191 -0
  65. ww/config_writes.py +264 -0
  66. ww/contracts.py +155 -0
  67. ww/control.py +41 -0
  68. ww/defaults.py +130 -0
  69. ww/design_docs.py +32 -0
  70. ww/discovery.py +104 -0
  71. ww/documents.py +217 -0
  72. ww/errors.py +18 -0
  73. ww/executable.py +43 -0
  74. ww/execution_models/__init__.py +64 -0
  75. ww/execution_models/construction.py +148 -0
  76. ww/execution_models/decoding.py +38 -0
  77. ww/execution_models/plan_codec.py +565 -0
  78. ww/execution_models/records.py +1206 -0
  79. ww/execution_models/runs.py +266 -0
  80. ww/extensions/__init__.py +40 -0
  81. ww/extensions/api.py +559 -0
  82. ww/extensions/registry.py +864 -0
  83. ww/extensions/store.py +78 -0
  84. ww/feedback.py +342 -0
  85. ww/handler_repairs.py +57 -0
  86. ww/hooks/__init__.py +40 -0
  87. ww/hooks/agents.py +380 -0
  88. ww/hooks/install.py +168 -0
  89. ww/hooks/notices.py +206 -0
  90. ww/hooks/records.py +209 -0
  91. ww/hooks/runtime.py +266 -0
  92. ww/hooks/transcripts.py +183 -0
  93. ww/inspect.py +896 -0
  94. ww/instructions/__init__.py +17 -0
  95. ww/instructions/builder.py +1682 -0
  96. ww/instructions/commands.py +335 -0
  97. ww/instructions/handoff.py +149 -0
  98. ww/instructions/models.py +686 -0
  99. ww/instructions/policy.py +219 -0
  100. ww/instructions/text.py +168 -0
  101. ww/interactions.py +187 -0
  102. ww/interpolation.py +37 -0
  103. ww/item_passes.py +167 -0
  104. ww/items.py +99 -0
  105. ww/locking.py +207 -0
  106. ww/metadata_publication.py +230 -0
  107. ww/onboarding.py +229 -0
  108. ww/open_work.py +236 -0
  109. ww/operations.py +193 -0
  110. ww/operator_ui/__init__.py +16 -0
  111. ww/operator_ui/page.html +351 -0
  112. ww/operator_ui/server.py +215 -0
  113. ww/operator_ui/session.py +389 -0
  114. ww/operator_ui/sheet.py +104 -0
  115. ww/operator_ui/view.py +109 -0
  116. ww/output.py +339 -0
  117. ww/output_adapters/__init__.py +12 -0
  118. ww/output_adapters/base.py +25 -0
  119. ww/output_adapters/json_adapter.py +37 -0
  120. ww/output_adapters/markdown.py +2293 -0
  121. ww/output_adapters/rule_pages.py +337 -0
  122. ww/output_adapters/terminal.py +21 -0
  123. ww/package_updates.py +167 -0
  124. ww/plan/__init__.py +38 -0
  125. ww/plan/actions.py +207 -0
  126. ww/plan/compiler.py +1492 -0
  127. ww/plan/constructs.py +456 -0
  128. ww/plan/models.py +665 -0
  129. ww/project_config.py +752 -0
  130. ww/recovery.py +401 -0
  131. ww/replanning.py +367 -0
  132. ww/results.py +77 -0
  133. ww/rule_checks.py +230 -0
  134. ww/rule_conversion.py +331 -0
  135. ww/rule_disputes.py +148 -0
  136. ww/rule_store.py +456 -0
  137. ww/rule_verification.py +714 -0
  138. ww/rule_views.py +447 -0
  139. ww/rule_writes.py +920 -0
  140. ww/run_coordination.py +158 -0
  141. ww/runtimes.py +105 -0
  142. ww/service.py +4405 -0
  143. ww/setup_apply.py +428 -0
  144. ww/step_values.py +20 -0
  145. ww/storage.py +447 -0
  146. ww/storage_adapters/__init__.py +36 -0
  147. ww/storage_adapters/base.py +540 -0
  148. ww/storage_adapters/filesystem.py +370 -0
  149. ww/storage_adapters/memory.py +195 -0
  150. ww/storage_adapters/project_metadata.py +69 -0
  151. ww/storage_adapters/task_document.py +484 -0
  152. ww/task_ids.py +114 -0
  153. ww/task_references.py +124 -0
  154. ww/transitions.py +1619 -0
  155. ww/updates.py +399 -0
  156. ww/upgrade.py +95 -0
  157. ww/validation.py +168 -0
  158. ww/variables.py +275 -0
  159. ww/workflow_config.py +854 -0
  160. ww/workflow_update.py +239 -0
  161. ww/workflow_validation.py +1260 -0
  162. ww/workspace.py +50 -0
  163. ww_agentic_workflows-1.0.0.dev3.dist-info/METADATA +690 -0
  164. ww_agentic_workflows-1.0.0.dev3.dist-info/RECORD +167 -0
  165. ww_agentic_workflows-1.0.0.dev3.dist-info/WHEEL +4 -0
  166. ww_agentic_workflows-1.0.0.dev3.dist-info/entry_points.txt +2 -0
  167. ww_agentic_workflows-1.0.0.dev3.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,1876 @@
1
+ # `../ww.yaml` specification
2
+
3
+ `../ww.yaml` defines what `ww` workflows do. The file is strict: unknown
4
+ keys, invalid types, and invalid references are errors.
5
+
6
+ This is the exact reference; [the features guide](features.md) says when to use
7
+ what, and [the examples](examples.md) are runnable. An installation prints
8
+ its own same-version copies with `ww docs specification`, `ww docs features`
9
+ and `ww docs examples` (`docs` is read-only and takes no project state).
10
+
11
+ ## Common types
12
+
13
+ - **Name:** a non-empty string matching `[A-Za-z_][A-Za-z0-9_.-]*`.
14
+ - **Extension reference:** a qualified name such as
15
+ `ext/ww/git/handlers:git-commit` where explicitly supported.
16
+ - **Description:** a string. It is optional unless stated otherwise.
17
+ - **Template:** `{{variable}}`, double braces everywhere. A name without
18
+ `ww.` is always a variable an earlier step handed back (`variables`) or an
19
+ automatic action returned. Every value ww provides lives under `ww.`:
20
+ `{{ww.task.id}}`, `{{ww.task.workspace_dir}}`, `{{ww.task.workflows}}`
21
+ (the configured workflow names), `{{ww.project.name}}`,
22
+ `{{ww.project.dir}}`, `{{ww.project.names}}`, `{{ww.executable}}` (how
23
+ printed commands invoke ww: `./ww` or the configured `executable`),
24
+ `{{ww.documents.<name>}}`,
25
+ `{{ww.metadata.<path>}}`, `{{ww.project_metadata.<path>}}`, on a per-item
26
+ stage the `{{ww.item.*}}` values of the stage's own item (see
27
+ [Item values](#item-values)), and on a per-child stage `{{ww.child.*}}`. A
28
+ configured extension may add values under `{{ww.<namespace>.<name>}}`:
29
+ with `ww/git` listed in the settings, `{{ww.git.branch}}` (the task's
30
+ branch), `{{ww.git.base_branch}}` (the branch it was created from) and
31
+ `{{ww.git.branch_strategy}}` (the branch format key in use). Any other
32
+ `ww.` name is an error at load, and a value its extension cannot give yet,
33
+ such as the branch before ww/git recorded one, stops the task before an
34
+ agent step reading it starts (`operator_reason: value_unavailable`;
35
+ `next --retry` checks again) and fails an automatic handler reading it.
36
+
37
+ Names must be unique within their catalog or sibling step list.
38
+
39
+ ## Root
40
+
41
+ | Key | Type | Required | Meaning |
42
+ | --- | --- | --- | --- |
43
+ | `extends` | boolean | no | `false` makes this file's level ignore the levels above it; see [Configuration levels](#configuration-levels). Defaults to `true`. |
44
+ | `imports` | list of file paths | no | Other YAML files composed into this one; see [Imports](#imports). Must come before every key but `extends`. |
45
+ | `workflows` | list of workflows | yes | At least one workflow is required in the composed configuration: in this file, an imported one, or another level. |
46
+ | `modes` | list of modes | no | Reusable agent guidance. |
47
+ | `profiles` | mapping | no | Named agent profiles. |
48
+ | `documents` | list of documents | no | Durable, free-format files that workflows read and update across runs. |
49
+ | `handlers` | list of handlers | no | Reusable actions referenced by hooks. |
50
+ | `hooks` | hooks mapping | no | Hooks applying across workflows. |
51
+ | `rules` | mapping of rule groups | no | Named groups of rule files, active where their filters allow; see [Rules](#rules). |
52
+
53
+ The generated task ID format, `task_format`, is not a key of this file: it
54
+ lives in `ww.json` (see the features guide), and a
55
+ `task_format` key in a YAML file is an unknown key.
56
+
57
+ ## Imports
58
+
59
+ A root file — `../ww.yaml` or the root file of another
60
+ [configuration level](#configuration-levels) — may split its definitions across
61
+ other YAML files by listing them under `imports`, which must come before every
62
+ other key except `extends`:
63
+
64
+ ```yaml
65
+ imports:
66
+ - workflows/handlers.yaml
67
+ - workflows/review.yaml
68
+
69
+ workflows:
70
+ - task: The standard development workflow.
71
+ steps:
72
+ - develop: Implement the change.
73
+ - code-review: ~
74
+ ```
75
+
76
+ - Each entry is a non-empty path, relative to the directory of the file that
77
+ lists it, to an existing file that contains a mapping. Only `imports` resolve
78
+ this way: every other relative path in any file, such as a document `path`,
79
+ a handler's `argv`, or the settings' `projects` and `worktree_dir`, still
80
+ resolves against the project root.
81
+ - An imported file may define every root key above except `imports`: imports do
82
+ not nest, so every imported file is listed in its level's root file. A file
83
+ may not be listed twice, may not be one of the level root files, and a root
84
+ file may not import itself.
85
+ - Definitions fold in list order, and the root file last. A later file
86
+ overrides an earlier one, so a root file overrides every import it lists:
87
+ - an entry of `workflows`, `modes`, `documents`, or `handlers`, and a
88
+ `profiles` entry, replaces the entry of the same name from an earlier file,
89
+ in that entry's original position;
90
+ - `hooks` entries carry no name, so each phase's entries from a later file
91
+ run after those from earlier files;
92
+ - any other key takes the later file's value.
93
+ - Overriding across files is not an error. `lint` prints one notice per
94
+ overridden definition, naming the file that defined it and the file that
95
+ overrode it. A name repeated within a single file is still reported as a
96
+ duplicate.
97
+ - The files are composed in memory, on every command, into one document that is
98
+ then read exactly as a single `ww.yaml`; nothing is cached on disk.
99
+ Every rule in this specification applies to that composed document.
100
+
101
+ ## Configuration levels
102
+
103
+ Workflows come from up to three levels, applied top to bottom:
104
+
105
+ | Level | Root file | Required |
106
+ | --- | --- | --- |
107
+ | user | `ww.yaml` in `$WW_USER_CONFIG_DIR`, else `$XDG_CONFIG_HOME/ww/`, else `~/.config/ww/` | no |
108
+ | repo | `../ww.yaml` | yes |
109
+ | local | `../ww.local.yaml`, next to the repo file | no |
110
+
111
+ The repo file is what makes a directory a ww project; a user file alone
112
+ never does. A level is its root file plus the files that root imports, and
113
+ each level may use `imports` as described above. `init` creates the user
114
+ directory when it is missing.
115
+
116
+ - Levels fold in order, user first, each level's imports before its root
117
+ file, with the same rules as imports: a lower level overrides the levels
118
+ above it, named entries are replaced one by one, `hooks` entries of each phase
119
+ are added after those from above, and any other key takes the lower level's
120
+ value.
121
+ - `extends` is a boolean, `true` by default. When any file of a level — its
122
+ root or one of its imports — sets `extends: false`, that level ignores every
123
+ level above it and folding starts again there. `extends: true` changes
124
+ nothing. `lint` reports each file left out as a notice.
125
+ - `lint` lists the configuration files it read, and `plan` ends with the same
126
+ list; `lint` notices name the file of the level that overrode a definition.
127
+
128
+ The `discover --json` entries in `workflows`, `builtin_workflows`, and
129
+ `catchall` include additive `source` and `source_level` fields. Configured
130
+ workflows report the winning YAML definition's physical source label and its
131
+ public level (`global`, `project`, or `local`); imported definitions keep the
132
+ level of the file that imported them. A workflow with no configured
133
+ YAML definition, such as an unconfigured built-in or the unconfigured
134
+ catch-all, reports `null` for both fields; a built-in or the catch-all that a
135
+ configuration file defines has that file as its origin. Existing workflow
136
+ fields keep their meanings. The Markdown shows each project workflow as
137
+ `name — [level: path] description`, sorted local, then project, then global,
138
+ stable within a level, with the home directory written `~` and an honest
139
+ `other` label, never `local`, for a workflow without a configured source. The
140
+ ordering is only guidance for choosing between workflows that fit, never
141
+ overriding an explicit request, and does not change configuration or execution
142
+ order. `discover` Markdown omits the rules notice (`rules_notice` stays in
143
+ JSON and in task instructions).
144
+
145
+ ```yaml
146
+ # ~/.config/ww/ww.yaml
147
+ handlers:
148
+ - name: test
149
+ argv: [pytest]
150
+ ```
151
+
152
+ ```yaml
153
+ # ww.local.yaml
154
+ modes:
155
+ - economy: Keep answers short.
156
+ ```
157
+
158
+ `ww.json` has matching user (`ww.json` in
159
+ the user directory) and `.local.json` levels, which are always deep-merged and take no `extends` key; `task_format`
160
+ is one of its keys, so a lower JSON level replaces it. See the features guide.
161
+
162
+ Projects, the directories a task may work in, are configured in
163
+ `ww.json` rather than here because their locations differ per
164
+ machine; see the features guide.
165
+
166
+ ## Setup fragments
167
+
168
+ `ww setup apply <file> --for me|team` places configuration a setup skill
169
+ proposes; see the features guide for the command. `ww setup update <name>
170
+ <file> [--level local|project|global]` replaces one workflow the
171
+ configuration already defines, in the file that defines it: the fragment holds
172
+ only `workflows` with that one entry, the edit goes to the definition that
173
+ wins (or, with `--level`, is refused when a higher-precedence definition hides
174
+ that level's), and only that list item's lines change. The file is a YAML
175
+ fragment with any of the root keys `workflows`, `modes`, `profiles`,
176
+ `documents`, `handlers`, `hooks`, and `rules`, in this notation, plus an
177
+ optional `settings` mapping of `ww.json` keys:
178
+
179
+ ```yaml
180
+ workflows:
181
+ - review: Review a change before it is merged.
182
+ steps:
183
+ - read: Read the change and report what to fix.
184
+ modes:
185
+ - gently: Suggest rather than insist.
186
+ settings:
187
+ runtime: auto
188
+ ```
189
+
190
+ Any other key, `imports` and `extends` included, is an error, and every entry
191
+ of a named catalog needs a name. The YAML part goes into a file ww owns and
192
+ rewrites whole, which the level's root file imports:
193
+
194
+ | `--for` | YAML part | Imported by | `settings` merge into |
195
+ | --- | --- | --- | --- |
196
+ | `team` | `ww-setup.yaml` | `ww.yaml` | `ww.json` |
197
+ | `me` | `ww-setup.local.yaml` | `ww.local.yaml`, created with only `imports` when missing | `ww.local.json` |
198
+
199
+ Each lives next to the repo file. An existing setup file keeps what the
200
+ fragment does not name: a workflow, mode, document, or handler of the same
201
+ name is replaced, a profile or rule group of the same name is replaced whole,
202
+ and hooks are appended phase by phase, skipping an entry identical to one
203
+ already in that phase. `settings` merge key by key, objects
204
+ recursively; a key that already holds a different value is a conflict, and
205
+ the whole apply is refused listing every one. A setup file the root file does
206
+ not import is refused, since ww would not know why it is not applied. A
207
+ fragment definition that the importing root file also defines is reported as a
208
+ warning: the root file folds after its imports, so it keeps its own.
209
+
210
+ ## Modes and profiles
211
+
212
+ A mode has these keys:
213
+
214
+ | Key | Type | Required |
215
+ | --- | --- | --- |
216
+ | `name` | name | yes |
217
+ | `description` | string or list of non-empty strings | no |
218
+ | `workflows` | `"*"` or list of workflow names | no |
219
+ | `steps` | `"*"` or list of step names or paths | no |
220
+
221
+ Modes accept the named-entry shorthand too: `- economy: Use fewer tokens.` is
222
+ equivalent to `{name: economy, description: Use fewer tokens.}`. A null value
223
+ omits the description. The filters may sit beside the shorthand:
224
+ `{economy: Use fewer tokens., steps: [develop]}`.
225
+
226
+ A mode applies to a run when it is selected: `start --mode` names it, or,
227
+ without `--mode`, the workflow lists it in its `modes`. A mode with
228
+ `workflows` or `steps` (or both) is **automatic**: it also applies, without
229
+ being selected, to every step its filters admit, matched like a hook's
230
+ filters; a workflow's heirs follow it. `--mode` replaces only the workflow's
231
+ default modes and never removes an automatic mode. See
232
+ [Workflow and step filters](#workflow-and-step-filters) for the forms.
233
+
234
+ Every agent step's page, including item stages and loop steps, lists its
235
+ modes in a **Modes** section, each with its description: the selected modes
236
+ first, then the automatic modes that admit the step, a mode both selected and
237
+ automatic once. The same pages get modes as get rules: not `init`, hooks,
238
+ verification items, or the built-in workflow summary. The modes are resolved
239
+ per step when the run starts and frozen in its plan snapshot, like rules.
240
+
241
+ Profiles use a mapping rather than a list:
242
+
243
+ ```yaml
244
+ profiles:
245
+ developer: Prefer small, well-tested changes.
246
+ reviewer: ~
247
+ ```
248
+
249
+ Each profile key is a name. Its value is a non-empty string or `null`.
250
+
251
+ ## Workflows
252
+
253
+ Each item in `workflows` accepts:
254
+
255
+ | Key | Type | Required | Meaning |
256
+ | --- | --- | --- | --- |
257
+ | `name` | name | yes | Workflow identifier. |
258
+ | `steps` | list of steps | yes, unless `inherit` is set | Ordered declared steps; may be empty. The implicit `init` step is not listed. |
259
+ | `description` | string | no | Human-readable purpose. |
260
+ | `hooks` | hooks mapping | no | Hooks for this workflow. |
261
+ | `modes` | list of names or extension references | no | Default modes; every local name must exist. |
262
+ | `agent` | non-empty string other than `auto` | no | Preferred executor; currently advisory. |
263
+ | `model` | non-empty string | no | Default model guidance; `auto` stops inheritance. |
264
+ | `reasoning` | non-empty string | no | Default reasoning guidance. |
265
+ | `profile` | profile value | no | Default agent profile. |
266
+ | `role` | `manager` or `worker` | no | The role every step inherits unless it or an enclosing step sets its own; see the step key. |
267
+ | `subagents` | boolean | no | `false`: no step's performer spawns subagents, unless a step sets `true`; see the step key. |
268
+ | `handoff` | — | — | Removed; rejected with a message. A workflow transition (`handoff_to` on the last step) makes a handoff workflow; see the step key `handoff_to`. |
269
+ | `runtime` | `single` or `auto` | no | The runtime `start` uses for this workflow when `--runtime` is omitted; it outranks the project default in `ww.json`, and the flag outranks it. |
270
+ | `explicit` | boolean | no | Default `false`; every step inherits it unless it or an enclosing step sets its own; see the step key. |
271
+ | `hooks_from` | string | no | The workflow whose global hooks this one runs with: a global hook filtered with `workflows` applies here when its filter admits this workflow's own name or the named workflow, so this workflow takes that lane's branch, worktree and commit handling. Extensions receive the lane as `ExtensionContext.lane` and key per-workflow settings by it: `ww/git` takes the lane's `branch_name_formats` and `base_branches` entries, while its records and `{{ww.task.workflow}}` keep the workflow's own name and `{{ww.task.lane}}` gives its formats and argv base-branch commands the lane. Rule groups and modes filtered with `workflows` keep matching the workflow's own name only: they are the lane's conventions, not its handling. Must name another workflow that has no `hooks_from` of its own. Set it in the workflow definition in `ww.yaml`. The plan freezes it, so a run keeps its lane. |
272
+ | `needs_hooks_from` | boolean | no | The workflow refuses to run until `hooks_from` is set: `start` (before a bootstrap request is opened), a handoff to it, and a replan of a run whose recompiled workflow lacks it (a `plan_changed` refusal). The message names `hooks_from` in the workflow definition in `ww.yaml`. Defaults to `false`. |
273
+ | `restartable` | boolean | no | A new `start` of this workflow while its previous run is unfinished abandons that run and opens a new one; the abandoned run stays in the task's history. Without it, a task with an unfinished run refuses another start. An unfinished run of a different workflow is never abandoned this way. Defaults to `false`. |
274
+ | `inherit` | workflow name | no | Copy that workflow completely: steps, workflow hooks, and every setting. The workflow's own keys other than `steps` and `hooks`, which it may not declare, replace the copied values. A global hook filtered to the inherited workflow also runs for this one. Chains are allowed; a cycle or unknown name is an error. |
275
+ | `recommended_next_workflow` | workflow name or null | no | Offered to the operator when a run completes: the page asks through the agent's choice menu and shows the `start` command for the same task, to run only on confirmation. Inherited like any setting; `null` clears an inherited one. Invalid in a handoff workflow (one with a `handoff_to` transition). |
276
+
277
+ Execution settings inherit from workflow to enclosing steps to the current
278
+ step. Hooks then apply the referenced root handler and the invocation override.
279
+ Omission inherits; explicit `model: auto` or `reasoning: auto` stops inheritance.
280
+ Model and reasoning are coupled: when a child changes the effective model and
281
+ omits reasoning, reasoning resets to `auto`. Repeating the same model preserves
282
+ the inherited reasoning. `agent` falls back to the agent passed to `plan` or
283
+ `start` and may not be `auto`.
284
+
285
+ `../ww.json` sets ww-wide project behavior separately from workflow
286
+ syntax. `enabled` is `true` (the default: agents use ww for project work),
287
+ `false` (agents do not use ww, `discover` says only that, and `start` refuses),
288
+ or `"on_request"` (ww stays available, but agents use it only when the user
289
+ explicitly asks for it; `discover` says so before its workflows and reports
290
+ `"enabled": "on_request"` in JSON); any other value is an error naming the three.
291
+ `limits` holds two positive integers, each defaulting to `3`: `rounds`, the
292
+ round limit of a step `loop` without its own `max_rounds`, and `fixes`, the
293
+ rejected completions a check allows when its rule sets no `max_fixes`; any
294
+ other key in it is an error. `agent_hooks` holds `check_unfinished`, a
295
+ boolean defaulting to `true` that decides whether the `session-start` hook
296
+ lists unfinished tasks, and `recent_days`, a positive integer defaulting to
297
+ `3`: the window of that hook's scan, of `ww interrupted`, and of the
298
+ interruption pointer in `discover` and `lookup`; any other key in it is an
299
+ error. `pages` holds `worker_requirements`, `full` (the default) or
300
+ `pointer`: the first page of every delegated worker assignment prints the task
301
+ requirements in full, or only the pointer to `ww requirements` when it is
302
+ `pointer`; the manager's pages are unaffected, and `init` writes the key only
303
+ once it is set. Any other value, or key, is an error. The file may
304
+ also override the internal requests of the implicit init action, `cheapest` /
305
+ `low`, and of the workflow-summary action, `auto` / `auto`. `workflows` switches
306
+ off [built-in workflows](#built-in-workflows) by name, such as `catchall`; each
307
+ entry is an object whose only key, `enabled`, defaults to `true`, and a name ww
308
+ does not ship is an error listing the built-in ones. A workflow of the same
309
+ name in any `ww.yaml` level replaces the built-in one instead.
310
+ `executable` names the ww binary the project runs, a command on `PATH` or a
311
+ path; every command ww prints starts with it, and the `./ww` launcher runs it.
312
+ Without it, printed commands use `./ww` and the launcher runs
313
+ `ww-agentic-workflows`; `init` writes `ww-agentic-workflows` when it is
314
+ missing. The launcher is ww-owned: `init` rewrites a `./ww` that differs from
315
+ the current template, and `lint` warns about one. `runtime` (`single` or `auto`) is the runtime `start` uses when
316
+ neither `--runtime` nor the workflow names one, `update_check: false` silences
317
+ the notice that the ww checkout is behind its remote, `task_format` is the
318
+ generated task ID format, `rules` holds [the guidance for building
319
+ checks](#guiding-checks-rulescheck_guidance), `projects` lists the
320
+ directories a task may work in, and `extensions` holds each extension's
321
+ settings. Missing fields retain their individual defaults; this is every key
322
+ with its default, as `init` writes it:
323
+
324
+ ```json
325
+ {
326
+ "enabled": true,
327
+ "runtime": "single",
328
+ "update_check": true,
329
+ "feedback_learning": true,
330
+ "executable": "ww-agentic-workflows",
331
+ "task_format": "TASK-{{uuid}}",
332
+ "limits": {"rounds": 3, "fixes": 3, "auto_retries": 0},
333
+ "agent_hooks": {"check_unfinished": true, "recent_days": 3},
334
+ "rules": {},
335
+ "builtins": {
336
+ "init": {"model": "cheapest", "reasoning": "low"},
337
+ "workflow_summary": {"model": "auto", "reasoning": "auto"}
338
+ },
339
+ "workflows": {},
340
+ "projects": [],
341
+ "extensions": {}
342
+ }
343
+ ```
344
+
345
+ ### Built-in workflows
346
+
347
+ ww ships workflows of its own as YAML files inside the package
348
+ (`ww/assets/workflows/*.yaml`), written in this notation. `catchall`, which
349
+ records a change no configured workflow covers, is one of them; ww's learning
350
+ and setup workflows are others. Together they form a built-in level below the
351
+ user level:
352
+
353
+ - A workflow, document, or mode that any configuration level defines under the
354
+ same name replaces the built-in one. A project that declares a document of a
355
+ built-in's name keeps its own, and the built-in workflow uses it.
356
+ - A built-in file may declare, besides `workflows`, the root `documents` and
357
+ `modes` that belong to them, and nothing else. They come along while any of
358
+ the file's workflows is enabled; when `workflows` in
359
+ `ww.json` switches all of them off, the file contributes
360
+ nothing.
361
+ - Built-in workflows follow the configured ones. They are added after the
362
+ levels are composed, so `extends: false` never removes them.
363
+ - `discover` lists `catchall` under its own heading. Its Markdown leaves the
364
+ other built-in workflows to the `workflows` catalog, which it points at;
365
+ `builtin_workflows` in JSON still lists them apart from the project's.
366
+ - A built-in's `recommended_next_workflow` naming a workflow that is switched
367
+ off is dropped.
368
+
369
+ The built-in workflows:
370
+
371
+ | Workflow | File | Purpose |
372
+ | --- | --- | --- |
373
+ | `catchall` | `catchall.yaml` | Records a change no configured workflow covers. |
374
+ | `ww-learn-project` | `onboarding.yaml` | Learns the repository (purpose, stack, verify commands, CI, review and release process, conventions, pitfalls) into `project`, refreshing an existing file. |
375
+ | `ww-suggest` | `onboarding.yaml` | Asks a few process questions (express setup skips them), designs a minimal setup with the operator, proposes it in full, and places it with `setup apply`. |
376
+ | `ww-solve` | `onboarding.yaml` | Proposes a change for a problem the operator describes; a workflow already defined is changed with `setup update`. |
377
+ | `ww-rules-from-artifacts` | `onboarding.yaml` | Proposes rules from past artifacts of chosen steps. |
378
+ | `ww-automate` | `onboarding.yaml` | Proposes a script and its handler for a step's mechanical work. |
379
+ | `ww-scriptize-rules` | `scriptize.yaml` | Scriptizes every rule with no check yet into checks, built and proven on a branch of its own from the required `ww/git` default base, following its worktree settings; automatic Git hooks, `restartable`. |
380
+
381
+ `onboarding.yaml` also declares the documents `project` (`scope: project`,
382
+ `path: .ww/project.md`) and
383
+ `setup_proposal` (a task document at `.ww/tasks/{{ww.task.id}}/setup-proposal.yaml`),
384
+ and the mode `ww-narrate`. See the features guide,
385
+ [Setting ww up](features.md#setting-ww-up-learning-and-suggestions).
386
+
387
+ Workflows cannot be nested. A profile value is either a name or a mapping with
388
+ `name` and/or `description`:
389
+
390
+ ```yaml
391
+ profile:
392
+ name: reviewer
393
+ description: Focus on correctness and regressions.
394
+ ```
395
+
396
+ Workflows accept the [named-entry shorthand](#named-entry-shorthand). For
397
+ example, `- task: Run the standard development workflow.` supplies both the
398
+ name and description; `steps` and other workflow keys remain siblings.
399
+
400
+ ## Documents
401
+
402
+ `documents` declares durable files by name. Their format is entirely the
403
+ workflow's business; ww knows only the name, a description, the scope, and
404
+ which step last updated the file. A task-scoped document lives in the task
405
+ directory, `.ww/tasks/<task-id>/documents/<name>.md`, and persists across every
406
+ run of that task; `scope: project` puts it in `.ww/documents/<name>.md`, shared
407
+ by all tasks; `scope: user` puts it in the user configuration directory (see
408
+ [Configuration levels](#configuration-levels)) as `<name>.md`, shared by every
409
+ project of the user, and ww creates that directory when it resolves the path:
410
+
411
+ ```yaml
412
+ documents:
413
+ - test_cases: The test cases derived from the issue, kept current across runs.
414
+ - conventions: Conventions every task follows.
415
+ scope: project
416
+ - issue_notes: Notes kept with the repository, on the task's branch.
417
+ path: documentation/issues/{{ww.task.id}}/notes.md
418
+ - me: Who the operator is and how they like to work.
419
+ scope: user
420
+ ```
421
+
422
+ `path` places a document outside `.ww`. It is relative to the project root and
423
+ must stay inside it; a task-scoped path may use `{{ww.task.id}}`. When a run has a
424
+ working directory, such as a Git worktree, a task-scoped `path` resolves inside
425
+ that directory, so a document kept in the repository lands on the task's
426
+ branch; a project-scoped `path` always resolves against the project root. A
427
+ user-scoped `path` is relative to the user configuration directory and must stay
428
+ inside it; neither a project nor a user path may use `{{ww.task.id}}`. The
429
+ update journal stays under `.ww` in every case: for a user document it records
430
+ what this project's runs did to the shared file.
431
+
432
+ A step or handler that maintains a document lists it in `saves` as
433
+ `documents.<name>`, with the text instructing the update. Its worker may create
434
+ or edit that file in place, the one exception to the rule against writing
435
+ under `.ww`, and the file must exist when the step completes.
436
+ `{{ww.documents.<name>}}` in any
437
+ description resolves to the file's absolute path on the filesystem ww runs in,
438
+ whether or not the file exists yet:
439
+
440
+ ```yaml
441
+ - derive_tests: Analyze the issue and derive test cases.
442
+ saves:
443
+ - documents.test_cases: One checklist item per case; keep items that still hold, mark superseded ones.
444
+ - report: Build the test report from {{ww.documents.test_cases}}.
445
+ ```
446
+
447
+ ## Steps
448
+
449
+ Every workflow has a built-in first step named `init`. It is omitted from
450
+ `steps`, and declaring `init` anywhere as a top-level, nested, or per-item step
451
+ is an error because the name is reserved. Its fixed agent prompt records the
452
+ passed task requirements with corrected grammar and style, without analysis or
453
+ planning. It always produces an artifact, uses low reasoning, and does not
454
+ inherit the workflow profile.
455
+
456
+ Pages print the recorded requirements in full once, on the first instruction that
457
+ asks for work, and later pages point at `requirements <task> [--json]`, which
458
+ prints them again with their amendments. `amend <task> --requirements TEXT [--role ROLE]`
459
+ appends a timestamped amendment (at most 1000 characters, recorded with the caller
460
+ role or `operator`); it never rewrites the original and is refused for a completed
461
+ task. Amendments are shown on every page, newest last. The instruction JSON keeps
462
+ `task_requirements` and adds `requirements_in_full`, `requirements_command`, and
463
+ `task_amendments`.
464
+
465
+ `complete <task> --role manager [--no-dispatch]` in the `auto` runtime also
466
+ dispatches the manager's own next step (as `next --role manager` would, with a
467
+ one-line `notices` entry) when that step is `role: manager`, nothing waits for the
468
+ operator, and no worker, assessment outcome, loop boundary, or child is to be
469
+ chosen; `--no-dispatch` returns the pending page instead.
470
+
471
+ The implicit step otherwise participates in the standard step lifecycle.
472
+ `before_start_workflow` runs once before `init`; it belongs at global or workflow
473
+ scope and does not accept a `steps` filter. Use `before_start` for
474
+ preparation local to `init` or another step. A top-level declared step may use
475
+ `artifact_from: init` to
476
+ receive the requirements artifact. Handoff targets and child-task workflow runs
477
+ each begin with their own `init` step.
478
+
479
+ A step accepts every [handler key](#handlers), plus:
480
+
481
+ | Key | Type | Meaning |
482
+ | --- | --- | --- |
483
+ | `hooks` | hooks mapping | Hooks local to this step. |
484
+ | `rules` | list of rule entries | The step's own rules and the rule groups it names; see [Step rules](#step-rules). Not allowed on a step without agent work of its own, such as a container or a command. |
485
+ | `profile` | profile value | Overrides the profile inherited from the workflow and every enclosing step. Nested steps, loop bodies, and per-item stages inherit it in turn. |
486
+ | `role` | `manager` or `worker` | Who performs the step. `manager` keeps it in the managing session in every runtime and ignores its profile, agent, model, and reasoning settings; `worker`, the default, lets an `auto` run delegate it. In `auto` the manager completes a `manager` step with `complete --role manager` (or `loop --role manager`), and `complete` or `loop` with `--role worker` on it is refused. Inherited from the workflow and every enclosing step, like `profile`; nested steps, loop bodies, and per-item stages inherit it in turn. Only agent steps take it: on a step ww runs, such as a command, it is an error. |
487
+ | `subagents` | boolean | When `false`, whoever performs the step, the manager or a worker, does all of its work alone and spawns no subagent for anything; the step's page says so. It says nothing about who performs the step (`role`) or with which model. Inherited like `profile`; a nested step may set `true` again. Defaults to `true`. |
488
+ | `explicit` | boolean | Requires the agent to describe each meaningful operation before doing it and show concrete edits or a focused diff after each changed file. Large changes may use a concrete diff artifact; changed files must remain individually named and secrets redacted. Inherited from the workflow and structural groups, loops, item stages, and child stages; a child may set `false` to opt out. Defaults to `false`. |
489
+ | `interactive` | `true`, `false`, or `page` | `true`: the step is a normal conversation with the operator, held by the session that can talk to them; it implies `role: manager`, and `role: worker` beside it is an error. The agent responds to questions and corrections and treats clear contextual completion as permission to finish, asking naturally if the intent is ambiguous. `Done for today` may mean pause and resume later. Completion is refused until the conversation was recorded with `interact` and ended; an open interaction cannot be completed. `page`: the operator answers this stage on the operator page, an answer sheet over every item that `interact --await` serves while the agent waits and applies when the wait ends; valid on one per-item stage per `items` step. Defaults to `false`. |
490
+ | `learnable` | boolean | Opts this step's completed artifact into optional feedback deduction after workflow completion. Independent of `interactive`; defaults to `false` and requires `artifact: true`. |
491
+ | `choices` | list of choices | Options the operator picks from during an interactive step, `- <label>: <description>`; the label is shown as written. The agent follows the host's actual question-tool schema, using structured options when offered and a text-only question only when required, and otherwise presents a numbered list in chat. An asynchronous answer remains pending until the operator explicitly answers; timeout, dismissal, or preselection is not an answer. The pick must be recorded before the interaction ends. Requires `interactive: true`. |
492
+ | `steps` | list of steps | Nested ordered steps. |
493
+ | `loop` | non-empty list of steps | Repeats ordinary nested steps until an authorized worker stops it. |
494
+ | `max_rounds` | positive integer | Overrides the project-wide maximum number of rounds for this loop. Valid only beside `loop`. |
495
+ | `assignment` | `per_round` or `per_step` | How the body steps are split into worker assignments in the `auto` runtime; default `per_round`. Valid only beside `loop` on a step; `items` and `children` take their own `assignment` inside their mapping. Any other value is an error listing these. |
496
+ | `break` | non-empty string | On an agent-owned loop-body step, grants permission to break its enclosing loop when this condition holds. |
497
+ | `continue` | non-empty string | On an agent-owned loop-body step, grants permission to continue from the beginning of its enclosing loop when this condition holds. |
498
+ | `artifact` | boolean | Whether agent completion requires an artifact; default `true`. |
499
+ | `artifact_from` | name | Earlier artifact-producing step whose artifact is supplied to this step: an earlier sibling, or an earlier step of an enclosing level, the nearest one first. Inside assessment outcomes the assessment itself is eligible, and inside per-item stages the `items` step, each supplying its own artifact; an enclosing loop or group is not. A plain group, or an assessment named after its outcomes, supplies the artifact of the latest step inside it (inside the chosen outcome) that saved one in its current round (inside a loop, the loop's current iteration only), and needs some step inside that can save one; when none did, an assessment supplies its own artifact if it saved one, and otherwise the step is told that no artifact is available. An assessment whose outcomes cannot save an artifact supplies its own. |
500
+ | `items` | `null`, string, or mapping | Collects work items, then runs per-item stages for each; see [Items](#items). |
501
+ | `handoff_to` | workflow name or `{{variable}}` | Makes the workflow a handoff workflow and ends it by starting that workflow as the task's next run. A workflow has at most one transition, and nothing may follow it: valid only on the last top-level step (with no completion hook applying to it), with `description`, `agent`, `model`, and `reasoning` at most; or on a hook, see [Hooks](#hooks). `workflow` only runs a child, under `children`. |
502
+ | `item_phase` | `analyze`, `resolve`, or `report` | Only on an acting step of a per-item stage, at any depth inside its loops, groups, and assessment outcomes: the standard item fields the stage fills, the analysis (`processed_item`), the solution and `resolved`, or `reported`. It is rejected on an assessment step itself (put it on the outcome steps that do the work) and on any step outside a per-item stage, including through a handler used there. |
503
+ | `children` | mapping | Collects child tasks with the step's own action, then runs every child with one workflow, or runs the parent's own stages once per child; see [Children](#children). |
504
+ | `handler` | handler name | Copies a root handler definition into this step; the step keeps its own name and any explicit step fields override the copied values. A step with no content of its own, `- fetch_requirements: ~`, and a root handler of the same name copies that handler implicitly. |
505
+
506
+ `steps`, `loop`, `items`, and `children` are alternatives. A loop wrapper cannot
507
+ also declare an action or collection; its `loop` entries are ordinary steps and
508
+ may use hooks, profiles, item collection, nested steps, and the other step
509
+ features. A pure `steps` group or loop wrapper cannot be `interactive: true`
510
+ because it does not execute its own conversation; make an executed child step
511
+ interactive instead. An item or child collector remains a real step and may be
512
+ interactive. `item_phase` is invalid together with `items`, on an assessment
513
+ step, and outside a per-item stage; validation and `lint` reject each, because
514
+ the phase would silently have no effect there.
515
+
516
+ The manager enters a loop and dispatches its body. With the default
517
+ `assignment: per_round`, one worker carries consecutive body steps of
518
+ one round while they resolve to the same agent, model, reasoning, and profile;
519
+ a body step that overrides any of them starts a new assignment, because a
520
+ running worker cannot change them. `per_step` hands every body step back to
521
+ the manager. Every body step's instruction states which round of the loop it
522
+ belongs to, so a later round concentrates on the previous rounds' work rather
523
+ than on the whole task. One or more agent-owned steps may declare `break` or
524
+ `continue`. After doing such a step, its
525
+ worker evaluates that step's break gate. If it passes, the worker runs the
526
+ displayed `ww loop <TASK-ID> --break --role worker` command (`--role manager`
527
+ on the manager's own step under `auto`) instead of `complete`;
528
+ that command records the step result and deterministically exits the enclosing
529
+ loop after the step's completion hooks. The break result is also saved as the
530
+ loop wrapper's main artifact, outside its round directories. A wrapper may
531
+ set `artifact: false` to disable only this main artifact; body steps retain
532
+ their own artifact settings.
533
+
534
+ If a worker uses `continue`, ww records the result, runs the step's completion
535
+ hooks, then resets the body and dispatches its first step. If no worker breaks
536
+ or continues the loop, reaching the end resets the body and the manager
537
+ dispatches the first step again until the effective maximum is reached. The
538
+ effective value comes from the wrapper's `max_rounds`, or from `limits.rounds`
539
+ in `../ww.json` when the wrapper omits it, and is frozen in the saved
540
+ workflow plan. At the limit, ww does not expose a continuation command: it
541
+ reports `awaiting_operator` with `operator_reason: loop_limit` and a warning
542
+ that must be escalated to the user for manual resolution, and shows the operator's exit, `next --force --reason`,
543
+ which leaves the loop and continues with the steps after it.
544
+
545
+ `max_rounds` is invalid without `loop`; use `break` for a worker-controlled
546
+ loop exit.
547
+
548
+ ```yaml
549
+ - code-review-in-a-loop: ~
550
+ max_rounds: 5
551
+ loop:
552
+ - code-review: Review the development or the previous round of fixes.
553
+ break: There are no meaningful code-review findings.
554
+ - fix: Record each code-review finding as an item.
555
+ items: ~
556
+ ```
557
+
558
+ ### Learnable artifact sources
559
+
560
+ An artifact-producing step accepts `learnable: true` or `false` (default
561
+ `false`). This is independent of `interactive`; it opts the step's completed
562
+ artifact into post-workflow negative-feedback deduction. `learnable: true`
563
+ requires `artifact: true`. No deduction step or completion gate is compiled
564
+ into the plan. The setting is inherited when referencing a reusable step and
565
+ can be explicitly overridden. Saved plans retain it so completed-source
566
+ selection does not depend on later workflow edits.
567
+
568
+ ### Children
569
+
570
+ A `children` step splits the task into child tasks and runs them. Its own
571
+ action collects them: the agent records each child with `add-child`, and the
572
+ step cannot complete without one. ww then runs every child with
573
+ `children.workflow`, one at a time, and the parent continues after the last
574
+ child completes.
575
+
576
+ ```yaml
577
+ - split-work: Split the feature into stories.
578
+ children:
579
+ description: One child per story. # optional splitting guidance
580
+ workflow: implementation
581
+ ```
582
+
583
+ | Key | Value | Meaning |
584
+ | --- | --- | --- |
585
+ | `workflow` | workflow name | The workflow every child runs. Required unless `steps` is given; the two are exclusive. |
586
+ | `steps` | list of steps | The parent's own stages, run once per child; see [Per-child stages](#per-child-stages). |
587
+ | `assignment` | `per_step` | How the per-child stages split into worker assignments; only with `steps`, and `per_step` is the only value built. `per_child` is reserved and rejected until it is built. |
588
+ | `description` | non-empty string | Splitting guidance shown to the collecting agent, as `items.description`. |
589
+
590
+ The run is compiled as a ww-owned item nested under the collecting step,
591
+ `<step>/children`, so the step's completion hooks run after every child has
592
+ finished. A workflow may contain at most one `children` step, and a children
593
+ step cannot sit inside per-item stages; the named workflow must exist and
594
+ cannot itself use `children` (child tasks are one level deep). `children`
595
+ cannot be combined with the step's own `handoff_to` transition.
596
+
597
+ `add-child <task> [--id ID] --text TEXT [--project NAME] [--field
598
+ NAME=VALUE]...` records a child during the collecting step; `--field` gives it
599
+ custom fields, as `add-item --field` does for items.
600
+ `update-child <task> <child> [--text TEXT] [--project NAME] [--field
601
+ NAME=VALUE]...` changes a child's text or project while it has not started yet
602
+ (status `pending`): during the collecting step, in a per-child stage before the
603
+ child runs, and while the parent waits for its children; a started child is
604
+ refused with its status. Its custom fields only feed the parent's per-child
605
+ stages, so `--field` may change them at any time.
606
+
607
+ `start-child <parent> <child> [--workflow NAME] [--runtime single|auto]
608
+ [--agent AGENT] [--model MODEL] [--reasoning LEVEL]` can start the child in a different session
609
+ configuration from its parent. Omitted options inherit the parent's settings (`--agent` takes
610
+ the same vocabulary as `start --agent`, is validated against the child workflow, and is shown by
611
+ the child's pages and `status`); any model string is recorded as given; changing the
612
+ model without specifying reasoning resets reasoning to `auto`, while repeating
613
+ the inherited model preserves its reasoning. These options do not change the
614
+ parent or the child workflow's configured step settings. `--workflow` selects
615
+ a different child workflow instead of the coordinator's configured target.
616
+ The selected workflow must exist and cannot contain `children`; temporary
617
+ identity requests require a first-step `task_id` variable in that target.
618
+ The selection is frozen before launch and reused on retries without repeating
619
+ the flag. A starting or started child cannot change workflows. Under `single`, the
620
+ chosen session performs all child assignments without subagents; its actual
621
+ host model and reasoning settings remain authoritative, so launch that session
622
+ with the requested settings. ww records guidance rather than switching models.
623
+
624
+ The resolved launch settings are saved before starting the child, including
625
+ when it first obtains an external ID. A retry can omit the flags and keeps the
626
+ saved settings. Conflicting overrides after launch begins are refused, and
627
+ already-started children cannot be reconfigured through `start-child`.
628
+
629
+ #### Per-child stages
630
+
631
+ With `steps`, the parent owns a loop over its children: once collection
632
+ completes, ww runs the stages for the first child, then for the next, strictly
633
+ one child at a time. Exactly one top-level stage carries `workflow:`; inside
634
+ `children` it does not hand off, it starts the current child task with that
635
+ workflow and waits for it to finish.
636
+
637
+ ```yaml
638
+ - slices: One child per slice of the plan, with the slice's full text.
639
+ children:
640
+ steps:
641
+ - refine: Adjust {{ww.child.text}} to what earlier slices actually landed.
642
+ role: manager
643
+ - implement:
644
+ workflow: task
645
+ - review: Review {{ww.child.git.branch}} against the slice.
646
+ artifact_from: implement
647
+ role: manager
648
+ - land: Merge {{ww.child.git.branch}} into {{ww.git.branch}}.
649
+ ```
650
+
651
+ - The stages run in the parent task and run, with the parent's hooks,
652
+ profiles, rules, and roles; they are compiled like per-item stages, as
653
+ templates under `<step>/{child}` that become `<step>/child-1/...`,
654
+ `<step>/child-2/...` when collection completes.
655
+ - A stage reads its child as `{{ww.child.id}}`, `{{ww.child.text}}`,
656
+ `{{ww.child.project}}` (empty in the root), `{{ww.child.field.<name>}}`, and
657
+ every extension value of the child's own task as `{{ww.child.<namespace>.<name>}}`,
658
+ for example `{{ww.child.git.branch}}` and `{{ww.child.git.base_branch}}`. The
659
+ exact names are checked when the plan is compiled, and only a per-child stage
660
+ may read them. A stage before the `workflow:` stage runs before the child
661
+ task exists, so it may read only `id`, `text`, `project`, and `field.*`; a
662
+ child extension value there is rejected at compile time. A value that is not
663
+ available yet, such as the branch of a child whose task has recorded none, or
664
+ a field the child does not carry, stops the task before the stage starts
665
+ (`operator_reason: value_unavailable`), as for `{{ww.git.*}}`; for a missing
666
+ field the error names the `update-child ... --field` command that sets it.
667
+ - The `workflow:` stage takes a name and an optional `description`, and may be
668
+ written `- implement: {workflow: task}`. It shows the manager the
669
+ `start-child` command for its own child; any other child is refused while it
670
+ waits. Its artifact is the child's workflow summary, so later stages use it
671
+ with `artifact_from: implement`.
672
+ - `start_child` beside `workflow:` makes ww start the child itself when
673
+ the stage is reached, so the manager never runs `start-child` by hand:
674
+
675
+ ```yaml
676
+ - implement:
677
+ workflow: task # the default child workflow, as above
678
+ start_child: # every key optional
679
+ workflow: "{{ww.child.field.workflow}}"
680
+ runtime: "{{ww.child.field.runtime}}"
681
+ model: "{{ww.child.field.model}}"
682
+ reasoning: "{{ww.child.field.reasoning}}"
683
+ agent: "{{ww.child.field.agent}}"
684
+ ```
685
+
686
+ Each key is a template over the child's record (`{{ww.child.id}}`,
687
+ `text`, `project`, `field.<name>`; nothing else exists before the child
688
+ starts), rendered when the stage runs, so `update-child --field model=...`
689
+ made earlier counts. An omitted key, a field the child does not carry, or a
690
+ value that renders empty inherits exactly as the same `start-child` option
691
+ omitted does. The launch is `start-child` itself: the same validation and
692
+ the same frozen record. The manager's `next` at the stage starts the child
693
+ and prints the child's page under a one-line note; a launch that cannot
694
+ proceed (an unknown workflow, an invalid runtime or agent) fails the stage
695
+ like an automatic handler, for the operator to repair the child's fields
696
+ and `next --retry`. `start_child` is valid only beside `workflow:` on a
697
+ stage directly under `children.steps`; elsewhere validation rejects it.
698
+ - Stages before it may refine the child with `update-child` (including
699
+ `--field` values that record its launch settings); the child's `init`
700
+ records the text it has when it starts as its requirements.
701
+ - In `auto`, the parent's manager also manages the child: starting it returns
702
+ the child's page, and the child's steps are ordinary worker assignments the
703
+ same manager dispatches. A completed child's page names the parent command
704
+ to continue with. The stages are assigned one per step
705
+ (`children.assignment: per_step`).
706
+ - A child that fails stops the parent (`awaiting_operator`, `child_failed`), as
707
+ in the simple form.
708
+ - `break` on a stage ends the loop over the children: the stage's completion
709
+ hooks run, every remaining per-child stage is skipped, and each child that has
710
+ not started is marked `skipped`; the parent continues after the `children`
711
+ step. A `break` inside a `loop` within a stage ends that loop only.
712
+ `continue` needs a loop of its own inside the stage.
713
+ - Validation: exactly one top-level `workflow:` stage; no `handoff_to`
714
+ transition anywhere inside `children.steps` (stages, nested steps, or
715
+ hooks); the
716
+ child workflow may not use `children`; stages may not use `items` or
717
+ `children`; `steps` and `workflow` directly under `children` are exclusive;
718
+ a step with `children.steps` may not sit inside a `loop` (the stages expand
719
+ once, when collection completes), while the simple form may.
720
+
721
+ Steps also accept the [named-entry shorthand](#named-entry-shorthand). For
722
+ example, `- develop: Implement and test the change.` is equivalent to a step
723
+ with `name: develop` and that text in `description`.
724
+
725
+ To reuse a root handler as an ordinary step, set `handler` beside the step's
726
+ name. This is a definition copy, not an additional action: the compiled plan
727
+ contains only `some_step`, which uses the copied handler action. The step's
728
+ description and explicit handler fields override the copied values. A root
729
+ handler can also define a complete step container (`steps`, `loop`, or
730
+ `items`); a referencing step inherits that tree and retains its own name as the
731
+ outer step identity.
732
+
733
+ ```yaml
734
+ handlers:
735
+ - name: handler_name
736
+ argv: [printf, ready]
737
+
738
+ workflows:
739
+ - name: task
740
+ steps:
741
+ - some_step: Run the shared check for this workflow.
742
+ handler: handler_name
743
+ ```
744
+
745
+ For example, a reusable review loop can be declared once and used by any
746
+ workflow:
747
+
748
+ ```yaml
749
+ handlers:
750
+ - code-review:
751
+ loop:
752
+ - code-review: Perform the code review.
753
+ break: There are no meaningful review remarks.
754
+ profile: code-reviewer
755
+ - fix: Fix the review findings.
756
+ profile: developer
757
+
758
+ workflows:
759
+ - name: task
760
+ steps:
761
+ - code-review: ~
762
+ handler: code-review
763
+ ```
764
+
765
+ An extension handler reference can be used directly as a step, for
766
+ example `- ext/ww/git/handlers:is-git-clean: ~`. It is resolved and validated
767
+ when the workflow plan is compiled, just like an extension handler in a hook.
768
+ Such an entry, as a step or as a hook, may carry `workdir` and `args` and no
769
+ other key; everything else about the handler is the extension's to define.
770
+ `args` is the handler's positional arguments, templates allowed, and must
771
+ match the number of arguments it declares:
772
+
773
+ ```yaml
774
+ - name: ext/ww/git/handlers:merge-branch
775
+ args: ["{{ww.child.git.branch}}", "Land slice {{ww.child.id}}"]
776
+ ```
777
+
778
+ ### Items
779
+
780
+ A step with `items` owns a whole item lifecycle. Its own action is the
781
+ collection stage: the agent performs the step's work and records each resulting
782
+ work item with `add-item`. When collection completes, ww expands the per-item
783
+ stages once for every collected item. The minimal form automates as much as
784
+ possible:
785
+
786
+ ```yaml
787
+ - review: Review the pull request.
788
+ items: ~
789
+ ```
790
+
791
+ Every item then gets one built-in `handle-item` stage that analyzes, resolves,
792
+ and reports it in a single pass.
793
+
794
+ A string is splitting guidance for the collection stage. It tells the agent how
795
+ to split the work, and it does not describe the per-item stages:
796
+
797
+ ```yaml
798
+ - review: Review the pull request.
799
+ items: Split based on the comments retrieved from the Bitbucket pull request.
800
+ ```
801
+
802
+ The mapping form accepts these keys, all optional:
803
+
804
+ | Key | Type | Meaning |
805
+ | --- | --- | --- |
806
+ | `description` | non-empty string | Splitting guidance for the collection stage; the same as the string form. |
807
+ | `analyze`, `resolve`, `report` | non-empty string | Guidance for the analyze, resolve, or report phase of the built-in `handle-item` stage. Invalid together with `steps`. |
808
+ | `variables` | list of variables | Values the built-in `handle-item` stage hands back on each item's completion. Invalid together with `steps`. |
809
+ | `saves` | list of saved values | Metadata, documents, or item fields the built-in `handle-item` stage saves on each item's completion. Invalid together with `steps`. |
810
+ | `interactive` | `true` or `page` | `true` makes the built-in `handle-item` stage a conversation with the operator, for example a manual test the operator performs and reports; `page` has the operator answer it on the operator page, and `interact --await` completes it from the answer. Invalid together with `steps`. |
811
+ | `choices` | list of choices | Options the operator picks from in the built-in `handle-item` stage. Invalid together with `steps`. |
812
+ | `steps` | list of steps | The per-item stages. Omitted, one built-in `handle-item` stage runs per item. An explicit `[]` (or `~`) collects or reconciles items only and never expands the default `handle-item` stage; `items: ~` is the full-lifecycle shorthand. |
813
+ | `assignment` | `together`, `per_item`, or `per_step` | How per-item stages are split into worker assignments in the `auto` runtime; default `together`. |
814
+ | `persistent` | boolean | The items outlive the run: every run of the task starts from the task's stored items with their outcomes cleared, and the collection step reconciles that list against the source instead of splitting again. Defaults to `false`. A collection setting: see [Several passes over one collection](#several-passes-over-one-collection). |
815
+ | `identity` | field name | The custom field every new item must carry; `add-item` refuses one without it. Implied in `unique`. A collection setting. |
816
+ | `unique` | list of field names | One pool of values across the listed fields: a value may appear once over all items, in the run and in the task's stored items. `add-item` and `update-item` refuse a duplicate and name the item that holds it. A collection setting. |
817
+ | `agent` | non-empty string other than `auto` | Agent for the per-item stages. |
818
+ | `model` | non-empty string | Model for the per-item stages. |
819
+ | `reasoning` | non-empty string | Reasoning for the per-item stages. |
820
+ | `profile` | profile value | Profile for the per-item stages. |
821
+ | `role` | `manager` or `worker` | `manager` performs the per-item stages in the managing session. |
822
+ | `subagents` | boolean | `false`: the stages' performers spawn no subagents. |
823
+
824
+ ```yaml
825
+ - review: Review the pull request.
826
+ model: opus
827
+ items:
828
+ description: Split by pull request comment.
829
+ assignment: per_item
830
+ model: sonnet
831
+ reasoning: low
832
+ steps:
833
+ - analyze: Analyze this comment.
834
+ item_phase: analyze
835
+ - fix: Resolve this comment.
836
+ item_phase: resolve
837
+ - reply: Report the outcome.
838
+ item_phase: report
839
+ ```
840
+
841
+ Worker settings cascade from the `items` step, to `items`, to each stage, and
842
+ the most specific value wins. The step's own `agent`, `model`, `reasoning`,
843
+ `profile`, `role`, and `subagents` apply to collection and are inherited by the stages;
844
+ the same keys under `items` apply only to the stages. The cascade reaches each
845
+ configured stage directly; stages nested deeper inherit like ordinary nested
846
+ steps.
847
+
848
+ `assignment` changes only where worker assignments end in the `auto`
849
+ runtime:
850
+
851
+ - `together`, the default, gives one worker every stage of every item,
852
+ followed by the `items` step's completion hooks.
853
+ - `per_item` gives one worker every stage of one item, including stage hooks;
854
+ the next item starts a new assignment.
855
+ - `per_step` dispatches every stage as its own assignment.
856
+
857
+ It has no effect in the `single` runtime, where one session already performs
858
+ every assignment. A running worker cannot change its agent, model, reasoning,
859
+ or profile, so a stage that resolves to different settings than the worker's,
860
+ or that is the manager's (`role: manager`), starts a new assignment; the span resumes
861
+ with the next stage that matches. Set shared settings on `items` to keep a
862
+ whole span with one worker.
863
+
864
+ The step's `before_start` hooks run before collection, and its completion
865
+ hooks run after the last item. Each stage keeps its own hooks. Collection does
866
+ not require an artifact, because its result is the recorded items.
867
+
868
+ Each `items` declaration is a pass with a stable identity, its logical step
869
+ path, recorded in the plan on its collection item and per-item stages; it does
870
+ not depend on descriptions or work-item IDs.
871
+
872
+ #### Several passes over one collection
873
+
874
+ A workflow has one item collection, and every `items` step is a pass over it.
875
+ Passes are sequential steps, at any level and inside loops; ordinary steps
876
+ between them, such as one batch analysis or fix for all items, run once.
877
+ A pass with `steps: []` only collects or reconciles items; `items: ~` remains
878
+ the shorthand for one pass with the whole built-in lifecycle:
879
+
880
+ ```yaml
881
+ - collect: Record one item per comment with its stable source ID.
882
+ items:
883
+ steps: []
884
+ - analyze-together: Analyze all collected comments together.
885
+ - confirm-analysis: Reuse the collected items.
886
+ items:
887
+ steps:
888
+ - analyze: Reuse the shared analysis; confirm and fill gaps.
889
+ item_phase: analyze
890
+ - fix-together: Implement and verify the fixes for all analyzed items.
891
+ - finish: Reuse the collected items.
892
+ items:
893
+ steps:
894
+ - verify-resolution: Verify the result and record actual_solution.
895
+ item_phase: resolve
896
+ - report: Report the result for this original comment.
897
+ item_phase: report
898
+ ```
899
+
900
+ - **Expansion.** A pass's stages are expanded, right after its collection
901
+ step, when that step completes, once for every item recorded by then; no
902
+ other pass is expanded or changed. `steps: []` expands nothing, and a pass
903
+ whose collection is empty finishes without stages.
904
+ - **Membership.** The collection step of each pass may add or reconcile
905
+ items; a later pass's step is told to reuse the items rather than split
906
+ the source again. An item added after a pass expanded is kept and joins
907
+ the next pass (or the next round of the same pass); a running pass never
908
+ changes its items.
909
+ - **Records.** Passes share one record per item. A pass never clears its
910
+ analysis, solution, custom fields, references, or reported state, and no
911
+ stage is skipped because an earlier pass resolved or reported its item.
912
+ - **Loops.** A pass inside a loop is expanded anew in every round, for the
913
+ items recorded by then, with fresh stage records; nested loops and
914
+ nearest-loop `break` behave as for any other step.
915
+ - **Settings.** `persistent`, `identity`, and `unique` belong to the
916
+ collection. The first `items` declaration in plan order decides them, with
917
+ the defaults for what it omits, and they apply to every pass. A later pass
918
+ may omit them or repeat the same values; one that sets a different value is
919
+ a configuration error naming both steps, for example `workflow 'review'
920
+ step 'finish' sets items.persistent to true, but the collection's first
921
+ items step 'collect' leaves it unset`. `unique` is compared as a set, with
922
+ `identity` in it.
923
+ - An `items` step inside another `items` step's per-item stages is an
924
+ error; declare later passes as sequential steps instead.
925
+
926
+ #### Pass gates
927
+
928
+ When the run leaves a pass, after its last stage and before the next item
929
+ starts, every item of the pass must hold what the pass's stages declare, for
930
+ each stage that ran:
931
+
932
+ | Stage | Requires on the item |
933
+ | --- | --- |
934
+ | `item_phase: analyze` | `processed_item` |
935
+ | `item_phase: resolve` | `actual_solution` and `resolved` |
936
+ | `item_phase: report` | `reported` |
937
+ | built-in `handle-item` | `resolved` and `reported` |
938
+
939
+ A stage with `item_phase` also requires the item fields it saves (`saves:
940
+ item.field.*`). A stage without `item_phase` keeps only its ordinary
941
+ completion contract, whatever its name. A stage that never ran, because an
942
+ assessment outcome, a `break`, or a stopped workflow skipped it, requires
943
+ nothing, and ww never marks an item resolved or reported by itself. A linked
944
+ item (`--refers-to`) shares the analysis, solution, and `resolved` state of the
945
+ item it refers to, so a duplicate comment needs no duplicate fix, but it is
946
+ reported for its own source.
947
+
948
+ An assessment outcome inside a per-item stage belongs to its pass. When an
949
+ assessment is a pass's last stage, the pass ends once its outcome is chosen:
950
+ the chosen outcome's work runs first and is gated with the rest, an outcome
951
+ that skips that work requires nothing of it, and an outcome that stops the
952
+ workflow ends the run without a gate. The chosen outcome is recorded on the
953
+ assessment, so a stop after it never asks for it again.
954
+
955
+ A pass that is not satisfied stops the run for the operator with
956
+ `operator_reason: pass_incomplete` and each item's missing values; the next
957
+ step has not started. Once the items are updated with `update-item`, `next
958
+ --retry` checks them again. The gate cannot be forced: `next --force` there is
959
+ refused, because it would skip the next step rather than the missing records.
960
+
961
+ #### Item field saves
962
+
963
+ `saves: item.field.<name>` binds to an item, so it is valid only where there
964
+ is one:
965
+
966
+ - on an `items` step itself, for every item it collects or reconciles;
967
+ - on a step inside a per-item stage, at any depth (nested steps, loops,
968
+ assessment outcomes), and on a hook or handler that runs for such a step,
969
+ for the stage's current item.
970
+
971
+ Anywhere else, such as an ordinary batch step between passes, a hook of the
972
+ collection step, or a workflow-boundary hook, it is a configuration error
973
+ that names the step and the field; such a step updates items with
974
+ `update-item`. Reusable handlers are checked where they are used: as a step's
975
+ `handler`, as a hook, or through a handler group. A catalog handler with item
976
+ saves that no step uses unbound is valid. Metadata and document saves are
977
+ valid anywhere.
978
+
979
+ A shell or argv handler in a per-item stage may declare `item.field.<name>`
980
+ saves too, and ww then records them automatically; see
981
+ [Automatic item saves](#automatic-item-saves). An automatic command on an
982
+ `items` step cannot save item fields: one output cannot be distributed among
983
+ several items, so that is a configuration error. An agent still records
984
+ collection fields with `update-item`.
985
+
986
+ An `items` step cannot also declare `steps`, `loop`, `item_phase`, or child
987
+ tasks.
988
+
989
+ ## Handlers
990
+
991
+ Each root handler, and each step through the same shared shape, accepts:
992
+
993
+ | Key | Type | Required | Meaning |
994
+ | --- | --- | --- | --- |
995
+ | `name` | name | yes* | Identifier; key omitted in shorthand or for an inline hook command. |
996
+ | `description` | string | no | Instruction or explanation. |
997
+ | `kind` | `skill`, `slash_command`, or `prompt` | no | `skill` requires a discovered agent skill with this name, `slash_command` a discovered slash command, and `prompt` selects plain agent work instead of skill or slash-command resolution. |
998
+ | `mcp` | non-empty string | no | MCP connection; `description` supplies its work instruction. |
999
+ | `handlers` | non-empty list of handler mappings | no | Ordered, fully automated actions. Members may reference catalog handlers, use inline commands, or nest automated groups. Agent-owned work and actions requiring agent input are rejected. Exclusive with a direct action or step container. |
1000
+ | `argv` | string list | no | Automatic argument-vector action run by `ww`. |
1001
+ | `shell` | string | no | Automatic shell action run by `ww`. |
1002
+ | `args` | string list | no | Positional arguments for `shell`, or, beside only `name` and `workdir`, for the extension handler that `name` references, which must declare exactly that many. Templates are allowed. |
1003
+ | `env` | string mapping | no | Environment values for `shell`. |
1004
+ | `assert` | list of conditions | no | Conditions the command output must all meet; see [Commands](#commands). |
1005
+ | `on_failure` | `fix` or `operator` | no | Automatic shell/argv handlers only. `fix` opens an agent repair assignment after a known failure; completing the repair asks ww to retry the handler. Default `operator` stops for an operator decision. |
1006
+ | `on_failure_instruction` | non-empty string | no | Optional guidance for shell/argv handlers, included in the repair assignment or a hook failure page. Templates are allowed. |
1007
+ | `idempotent` | boolean | no | Running the command action again is harmless: an interrupted run is replayed by `next` instead of waiting for an operator. Requires `argv` or `shell`. Defaults to `false`. |
1008
+ | `action` | mapping | no | The registry form, `{type: <action>, ...}`: selects a registered action by its identifier, with that action's own keys beside `type`. Extensions' actions use it; it cannot be combined with `kind`, `mcp`, `argv`, `shell`, `args`, `env`, `assert`, or `idempotent`, and the core controls (`loop`, `workflow_transition`, `child_workflow`) are refused as types. |
1009
+ | `variables` | list of variables | no | What the step hands back, read later as `{{name}}`; see [Variables](#variables). |
1010
+ | `saves` | list of saved values | no | What an action writes to metadata, documents, or its item; see [Saves](#saves). |
1011
+ | `agent` | non-empty string other than `auto` | no | Preferred executor guidance. |
1012
+ | `model` | non-empty string | no | Model guidance; `auto` stops inheritance. |
1013
+ | `reasoning` | non-empty string | no | Reasoning guidance for this action. |
1014
+ | `workdir` | `task`, `project`, or `root` | no | The directory this action works in; see [Working directory](#working-directory). Defaults to `task`. |
1015
+
1016
+ ### Automated handler groups
1017
+
1018
+ Use `handlers` to declare a reusable sequence that ww executes itself:
1019
+
1020
+ ```yaml
1021
+ handlers:
1022
+ - build-frontend: ~
1023
+ shell: npm run build
1024
+ - build-all: ~
1025
+ handlers:
1026
+ - build-frontend: ~
1027
+ - argv: [test, -d, dist]
1028
+
1029
+ workflows:
1030
+ - name: task
1031
+ steps:
1032
+ - build-all: ~
1033
+ ```
1034
+
1035
+ Unlike `steps`, which can include agent work, this list accepts only automatic
1036
+ actions requiring no agent-supplied inputs. Named references resolve against
1037
+ the handler catalog, including later declarations; recursive reference cycles
1038
+ are errors. Groups can nest. `handlers` cannot be combined with a command,
1039
+ agent action, `steps`, `loop`, `items`, or `children` on the same definition.
1040
+ Use `steps` for sequences that contain manual work or step-specific lifecycles.
1041
+
1042
+ Members run in declaration order under the enclosing step or hook's identity.
1043
+ They do not create nested workflow steps or agent completion assignments.
1044
+ The group may provide defaults for `workdir`, worker guidance used for repairs,
1045
+ `on_failure`, and `on_failure_instruction`; each member or referenced definition
1046
+ can override them. On failure the normal handler policy applies, and a retry
1047
+ preserves successful earlier members.
1048
+
1049
+ A named automated group can also be referenced by a hook. Its members keep
1050
+ that hook phase and filters; a `before_complete` hook with `on_failure: fix`
1051
+ compiles eligible members into individual completion checks. Existing hook
1052
+ `handlers` groups continue to accept their existing action types, including
1053
+ agent actions: the automatic-only requirement belongs to reusable handler
1054
+ definitions and inline workflow handler groups.
1055
+
1056
+ ### Automatic handler repairs
1057
+
1058
+ An automatic command step runs when ww reaches it, records its own success,
1059
+ and advances without an agent completion for that step. With `on_failure: fix`,
1060
+ a known failure pauses that execution and opens a repair assignment:
1061
+
1062
+ ```yaml
1063
+ - build-root-assets: ~
1064
+ shell: docker compose exec -T -w /var/www/html/frontend requesttool npm run build
1065
+ on_failure: fix
1066
+ on_failure_instruction: Fix the reported build errors.
1067
+ ```
1068
+
1069
+ The repair page includes the command, failure diagnostics, full output artifact
1070
+ references, and the optional instruction. The agent fixes the cause and calls
1071
+ the displayed `complete` command with a repair artifact. ww retries the failed
1072
+ handler; only its success completes the automated step. Completed preceding
1073
+ steps stay completed. Repair assignments are attached to the execution; they
1074
+ create no extra workflow steps and invoke no step hooks of their own.
1075
+
1076
+ In the `single` runtime the same session receives the repair immediately. In
1077
+ `auto`, ww ends the previous assignment and the manager dispatches the repair
1078
+ with `next`; successive failures of the same handler stay with that repair
1079
+ worker. The handler's agent/model/reasoning/profile guidance applies to its
1080
+ repair assignment. Repair artifacts and command output survive reloads and are
1081
+ available through `artifacts`. JSON repair pages include `handler_repair` with
1082
+ the item ID, attempt count, limit, instruction, output references and artifacts.
1083
+
1084
+ The handler stops for the operator with `operator_reason: fix_limit` after
1085
+ `limits.fixes` failures (default 3), using the same counting policy as checks.
1086
+ Before that, `limits.auto_retries` (default 0; a non-negative integer) has ww
1087
+ retry a handler that reported a failure itself that many times, recording each
1088
+ failed attempt on the step and listing them on the stop or repair page; an
1089
+ interrupted handler and one that takes agent-supplied values are never retried
1090
+ this way. An operator-authorized `next --retry` starts a fresh budget and retries the
1091
+ handler; `next --force --reason "..."` skips it. An agent that cannot repair it
1092
+ can use the displayed `fail` command to request an operator decision.
1093
+
1094
+ `on_failure: fix` authorizes retry after a known failure without requiring
1095
+ `idempotent: true`. An interruption with an unknown outcome still follows the
1096
+ usual automatic-handler recovery rules: only `idempotent: true` permits
1097
+ implicit replay. These settings apply to automatic steps as well as commands
1098
+ used in hooks. Existing `before_complete` checks return failures to their
1099
+ step's worker rather than opening a separate repair assignment.
1100
+
1101
+ ### Working directory
1102
+
1103
+ `workdir` chooses the directory an action works in:
1104
+
1105
+ - `task` (the default): the task workspace, which is the selected Git worktree
1106
+ when an extension chose one, otherwise the `--project` directory, otherwise
1107
+ the project root.
1108
+ - `project`: the `--project` directory's own checkout, never a worktree made
1109
+ from it; the project root for a task started without `--project`.
1110
+ - `root`: the project root, which holds the configuration and `.ww`.
1111
+
1112
+ On a step, the value applies to the step's instruction, whose working-directory
1113
+ `cd` names that directory, to its `argv` or `shell` action, and to
1114
+ `{{ww.task.workspace_dir}}`, which resolves to that directory for the step.
1115
+ Nested steps, loop bodies, per-item stages, and an assessment inherit it from
1116
+ the enclosing step unless they set their own. A step that copies a root
1117
+ handler with `handler` takes the handler's `workdir` unless it sets its own.
1118
+
1119
+ A hook does not inherit its step's `workdir`. A hook entry's own `workdir`
1120
+ applies, otherwise that of the root handler it names, otherwise `task`:
1121
+
1122
+ ```yaml
1123
+ handlers:
1124
+ - name: refresh-shared-config
1125
+ argv: [make, shared-config]
1126
+ workdir: root
1127
+
1128
+ workflows:
1129
+ - name: task
1130
+ steps:
1131
+ - update-shared-notes: Update the git-ignored notes in {{ww.task.workspace_dir}}.
1132
+ workdir: root
1133
+ hooks:
1134
+ after_complete:
1135
+ - refresh-shared-config: ~
1136
+ - name: lint
1137
+ argv: [make, lint]
1138
+ workdir: project
1139
+ ```
1140
+
1141
+ ww gives a directory outside the task workspace no special Git treatment:
1142
+ changes made there are not committed by the task and remain for the operator.
1143
+
1144
+ ### Named-entry shorthand
1145
+
1146
+ Workflows, root handlers, steps, and hook handlers may put the name in the first mapping
1147
+ key and its optional description in the value:
1148
+
1149
+ ```yaml
1150
+ handlers:
1151
+ - update-yaml-specification: Update specification.md.
1152
+ - no-description: ~
1153
+
1154
+ workflows:
1155
+ - task: The standard development workflow.
1156
+ steps:
1157
+ - develop: Implement and test the change.
1158
+ ```
1159
+
1160
+ The value must be a string or YAML `null` (`~` or an empty value). Null means
1161
+ that the entry has no description. This is parsed exactly like the corresponding
1162
+ long form before validation and plan construction. If `name` is present, the
1163
+ mapping always uses the existing long form and unknown keys remain errors.
1164
+
1165
+ An action uses one form: `kind` (`skill`, `slash_command`, or `prompt`),
1166
+ `mcp`, `argv`, `shell`, or the registry form `action`. These forms cannot be
1167
+ combined. `kind: prompt` explicitly selects plain agent work. With no explicit
1168
+ action, `ww` resolves the name as a project skill,
1169
+ slash command, or ordinary agent prompt, in that order. To run several
1170
+ commands, use a hook's `handlers` list, one `argv` or `shell` each.
1171
+
1172
+ ### Variables
1173
+
1174
+ `variables` lists what a step hands back, read by later steps as `{{name}}`.
1175
+ An entry `- name: description` (or `name` with an optional `description`) is a
1176
+ value the performer supplies with `complete --variable name=<value>`; on an
1177
+ automatic action it is input the agent supplies before ww runs the command. A
1178
+ bare string, `- name`, is a value an automatic action returns itself, such as
1179
+ an extension handler's result:
1180
+
1181
+ ```yaml
1182
+ variables:
1183
+ - workflow: The workflow name corresponding to one of {{ww.task.workflows}}.
1184
+ - reason: ~
1185
+ ```
1186
+
1187
+ Names must be unique in the list and may not start with `ww` as their first
1188
+ dot-separated segment, or with `__`: those are ww's own values. Dots are
1189
+ allowed in a name.
1190
+
1191
+ `{{ww.choices}}` is scoped to the effective current step after reuse and
1192
+ compilation. Its value is a JSON array of configured choice labels in order,
1193
+ or `[]` when the step has no choices. It is read-only guidance for instructions
1194
+ and provided-variable descriptions; it does not validate the value supplied.
1195
+
1196
+ Within one completion window, matching supplied-variable declarations (the
1197
+ same name and description) share one input across handlers, in first-request
1198
+ order. Conflicting declarations for a name are an error identifying that name
1199
+ and both plan items. Repeating a supplied `--variable` remains an error.
1200
+
1201
+ ### Item values
1202
+
1203
+ On a per-item stage, agent instructions and automatic `argv`/`shell` actions
1204
+ read the stage's own work item through the same mapping:
1205
+
1206
+ | Value | Meaning and representation |
1207
+ | --- | --- |
1208
+ | `{{ww.item.id}}` | The item's stable ID. |
1209
+ | `{{ww.item.text}}` | The collected item text. |
1210
+ | `{{ww.item.processed_item}}` | The recorded analysis text; empty when unset. |
1211
+ | `{{ww.item.proposed_solution}}` | The proposed solution text; empty when unset. |
1212
+ | `{{ww.item.actual_solution}}` | The recorded solution, the item's resolution text; empty when unset. There is no separate resolution-text field or alias. |
1213
+ | `{{ww.item.resolved}}` | `true` or `false`. |
1214
+ | `{{ww.item.reported}}` | `true` or `false`. |
1215
+ | `{{ww.item.reference_to_id}}` | The ID of the item this one links to; empty when it links to none. |
1216
+ | `{{ww.item.field.<name>}}` | A custom field's string value; empty when the item has not set it. |
1217
+
1218
+ Values are read from the run's current item record each time an instruction
1219
+ is rendered and immediately before each command runs, never frozen when the
1220
+ plan is compiled. They therefore show changes from earlier passes and from
1221
+ `update-item` after a pass-gate stop followed by `next --retry`, and each item
1222
+ assignment sees only its own item. Unset values render as the empty string.
1223
+
1224
+ Using `{{ww.item.*}}` in a step with no bound item (outside the stages of an
1225
+ `items` step) is a context error: an automatic handler fails naming the item
1226
+ variables and saying no work item is bound, and no other item's value is
1227
+ substituted.
1228
+
1229
+ Rendered values are data. `argv` entries and shell `args` or `env` receive
1230
+ quotes, newlines, Unicode, `$`, and backticks unchanged; shell source itself
1231
+ may not contain interpolation. A project-owned script can take values as
1232
+ arguments:
1233
+
1234
+ ```yaml
1235
+ - reply: ~
1236
+ item_phase: report
1237
+ argv:
1238
+ - python3
1239
+ - scripts/reply-to-comment.py
1240
+ - "{{ww.item.field.comment_id}}"
1241
+ - "{{ww.item.actual_solution}}"
1242
+ - "{{ww.item.field.reply_id}}"
1243
+ ```
1244
+
1245
+ `scripts/reply-to-comment.py` is the project's own script, not part of ww. A
1246
+ command in a per-item stage can save its output into an item field; see
1247
+ [Automatic item saves](#automatic-item-saves).
1248
+
1249
+ ### Assessments
1250
+
1251
+ `assess` asks the agent to choose a named outcome before work continues. Prefer
1252
+ `positive` or `negative` when the evidence supports either; reserve `mixed` for
1253
+ material uncertainty. Select the result with `next --outcome <label>`.
1254
+
1255
+ The standard branches may be written beside `question` as `positive`,
1256
+ `negative`, and `mixed`, using the same ordinary step shapes as entries under
1257
+ `outcomes`. Do not combine these direct branches with `outcomes`; use
1258
+ `outcomes` when custom labels are needed. Omitted standard outcomes retain the
1259
+ existing behavior of doing no branch work and continuing after the assessment.
1260
+
1261
+ ```yaml
1262
+ - assess:
1263
+ question: Does recent development warrant refactoring?
1264
+ outcomes:
1265
+ positive:
1266
+ handler: refactor-plan
1267
+ negative:
1268
+ steps:
1269
+ - record: No refactoring is needed now.
1270
+ mixed:
1271
+ steps:
1272
+ - investigate: Gather the missing evidence.
1273
+ ```
1274
+
1275
+ Each outcome is one ordinary step shape: a direct handler/action, `handler`,
1276
+ `handlers`, or `steps`. These work types remain mutually exclusive. After the
1277
+ chosen outcome's work, the workflow continues with the step after `assess`.
1278
+ An outcome may instead be `stop_workflow: true`, alone: choosing it completes
1279
+ the workflow, skipping everything after it, completion hooks included. At
1280
+ least one outcome must have work. The standard answers `positive`, `negative`,
1281
+ and `mixed` are always accepted: one the assessment does not declare runs
1282
+ nothing and continues after the assessment, so a gate declares only the outcome
1283
+ that has work. Any other label must be declared. The compact form, `- assess: <question>`,
1284
+ accepts `positive` or `negative`; positive continues to the next step and
1285
+ negative completes the workflow, like an outcome with `stop_workflow: true`.
1286
+
1287
+ ```yaml
1288
+ - assess:
1289
+ question: Were conflicts resolved in non-trivial code?
1290
+ outcomes:
1291
+ positive:
1292
+ steps:
1293
+ - review: Review the resolutions.
1294
+ negative:
1295
+ stop_workflow: true
1296
+ ```
1297
+
1298
+ The assessment's page lists every outcome with what it does, and the page
1299
+ after it offers one `next --outcome <label>` command per outcome. An outcome
1300
+ made of an automatic command runs only after the outcome is chosen: the run
1301
+ pauses at a pending assessment and no branch executes before then.
1302
+
1303
+ ### Saves
1304
+
1305
+ `saves` lists what an action writes: each entry is a prefixed path
1306
+ and the text saying what to put there. The prefix is the kind and scope of the
1307
+ value and the rest its storage path; no `ww.` prefix is written, since an
1308
+ entry can only name something ww manages.
1309
+
1310
+ | Entry | Saves | Passed with |
1311
+ | --- | --- | --- |
1312
+ | `metadata.<path>` | task metadata at `<path>`, read as `{{ww.metadata.<path>}}` | `complete --metadata <path>=<value>` |
1313
+ | `project_metadata.<path>` | project metadata shared by every task, read as `{{ww.project_metadata.<path>}}` | `complete --metadata project_metadata.<path>=<value>` |
1314
+ | `documents.<name>` | a root document, created or edited in place; see [Documents](#documents) | the file itself |
1315
+ | `item.field.<name>` | a custom field of the step's item; on the collection step, of every collected item. Completion is refused while any is empty. Valid only on an `items` step or within a per-item stage; see [Item field saves](#item-field-saves). | `update-item --field <name>=<value>`, several per call; a shell or argv handler in a per-item stage saves its stdout, see [Automatic item saves](#automatic-item-saves) |
1316
+
1317
+ A metadata entry may add `append: true`: the path holds a list, each
1318
+ completion may pass it once per value or omit it, values are appended to what
1319
+ is stored, and repeats are dropped. The shorthand form is the usual one:
1320
+
1321
+ ```yaml
1322
+ saves:
1323
+ - metadata.jira.issue_id: The issue key.
1324
+ - project_metadata.last_auto_refactored_at: Save the current timestamp in YYYY-MM-DD HH:mm format.
1325
+ - metadata.labels: Every label the issue carries.
1326
+ append: true
1327
+ - documents.test_cases: Keep one checklist item per case.
1328
+ - item.field.bitbucket_reply_id: The ID of the reply you posted.
1329
+ ```
1330
+
1331
+ Within one action, paths must be unique, and metadata paths in the same scope
1332
+ cannot overlap (for example `metadata.jira` and `metadata.jira.issue_id`).
1333
+ `project_metadata.ww` and every path under it are reserved for ww's own state,
1334
+ such as `ww.setup.done`, and rejected.
1335
+ Agent-owned actions supply metadata through `complete --metadata`. Shell and
1336
+ argv handlers automatically save stdout when they declare metadata in `saves`:
1337
+
1338
+ ```yaml
1339
+ handlers:
1340
+ - create-github-pr: ~
1341
+ argv: [gh, pr, create, --fill]
1342
+ saves:
1343
+ - metadata.github.pr_url: The pull request URL.
1344
+ ```
1345
+
1346
+ The saved value is complete stdout with leading and trailing whitespace
1347
+ removed; stderr is excluded. Multiline output is one value, and empty output
1348
+ saves an empty string. Each metadata entry receives the same whole output;
1349
+ `append: true` appends it as one list element with the usual deduplication.
1350
+ Metadata is saved only after successful exit and all assertions pass. The
1351
+ completion and its publication intents are committed before publication;
1352
+ interrupted publication resumes without replaying the successful command.
1353
+ `from` is not a supported save option. Documents remain agent-owned.
1354
+
1355
+ #### Automatic item saves
1356
+
1357
+ A shell or argv handler in a per-item stage (`item_phase`, or any stage of an
1358
+ `items` pass, including its hooks and handler groups) may declare
1359
+ `item.field.<name>` saves. With exactly one current item, the same whole
1360
+ trimmed stdout convention applies: no `from`, no splitting, no structured
1361
+ extraction. When several fields (or metadata entries) are declared, each
1362
+ receives the same whole output; this is not a way to extract different fields.
1363
+ Every declared field is required, so empty output fails the stage.
1364
+
1365
+ ```yaml
1366
+ - reply:
1367
+ item_phase: report
1368
+ argv:
1369
+ - python3
1370
+ - scripts/reply-to-comment.py # a project-owned script, not part of ww
1371
+ - "{{ww.item.field.comment_id}}"
1372
+ - "{{ww.item.actual_solution}}"
1373
+ - "{{ww.item.field.reply_id}}"
1374
+ saves:
1375
+ - item.field.reply_id: The confirmed reply ID printed by the command.
1376
+ ```
1377
+
1378
+ After a zero exit and passing assertions, the field values and the stage
1379
+ completion are committed together; a `report` stage marks its item `reported`
1380
+ in that same commit, but only with the last plan item of the report stage's
1381
+ lifecycle (its step, handler-group members and completion hooks), whoever owns
1382
+ that item, and only that item. A report stage in which ww runs nothing is still
1383
+ reported by its agent with `update-item --reported=true`. A nonzero exit, a failed assertion, or an empty
1384
+ required value never reports the item, and the failed stage can be retried.
1385
+ ww does not derive `processed_item` or `actual_solution` from stdout; analysis
1386
+ and resolution commands rely on `update-item` or existing records, and the
1387
+ pass gate names whatever is missing. Saved field values stay on the item
1388
+ across passes.
1389
+
1390
+ Publication of task or project metadata declared by the same handler is
1391
+ recorded as an intent in the completion and resumes after an interruption
1392
+ without rerunning the successful command. ww cannot make a remote effect
1393
+ exactly-once: if the process dies after a remote reply but before the result is
1394
+ saved locally, the next attempt runs the command again. The project's handler
1395
+ owns that reconciliation, for example by reusing the saved reply ID passed as
1396
+ an argument (`{{ww.item.field.reply_id}}`) to update the reply instead of
1397
+ creating another.
1398
+
1399
+ ## Commands
1400
+
1401
+ `argv` and `shell` are root handler keys:
1402
+
1403
+ ```yaml
1404
+ name: commit
1405
+ argv: [git, commit, -m, "{{message}}"]
1406
+ ```
1407
+
1408
+ An action uses exactly one form:
1409
+
1410
+ | Form | Allowed keys | Rules |
1411
+ | --- | --- | --- |
1412
+ | Argument vector | `argv` | Non-empty list of non-empty strings. |
1413
+ | Shell | `shell`, `args`, `env` | Non-empty shell source; optional string-list `args` and string-valued `env` mapping. |
1414
+
1415
+ Templates are allowed in `argv`, shell `args`, and shell `env` values. They are
1416
+ not allowed in shell source; pass dynamic values through `args` or `env`.
1417
+
1418
+ To assert command output, add `assert` beside the action:
1419
+
1420
+ ```yaml
1421
+ argv: [git, status, --porcelain]
1422
+ assert:
1423
+ - equals: clean
1424
+ ```
1425
+
1426
+ `assert` is a non-empty list of conditions that must all hold: `empty`, which
1427
+ requires the output to be empty or whitespace, and `{equals: <value>}`, a
1428
+ non-empty string the whole output must equal:
1429
+
1430
+ ```yaml
1431
+ shell: grep -l TODO $WW_STEP_CHANGED_FILES || true
1432
+ assert: [empty]
1433
+ ```
1434
+
1435
+ ## Hooks
1436
+
1437
+ A hooks mapping (workflow hooks; the agent's own hooks are `ww hook`, see
1438
+ agent-hooks.md) accepts these lifecycle phases, each containing a list:
1439
+
1440
+ - `before_start_workflow`
1441
+ - `before_start`
1442
+ - `before_complete`
1443
+ - `after_complete`
1444
+ - `before_complete_workflow`
1445
+
1446
+ Each hook entry accepts one handler form and optional filters:
1447
+
1448
+ | Key | Type | Availability |
1449
+ | --- | --- | --- |
1450
+ | handler keys | shared handler shape | All scopes; used directly for a singular hook. |
1451
+ | `handlers` | non-empty list of handler mappings | All scopes; used only for multiple actions. |
1452
+ | `steps` | `"*"` or list of step names or paths | Global and workflow step-lifecycle hooks only; unavailable to workflow-boundary hooks. |
1453
+ | `workflows` | `"*"` or list of workflow names | Global hooks only. |
1454
+ | `on_failure_instruction` | non-empty string | Optional failure guidance, also accepted on each member of `handlers`; a member inherits the group instruction unless it overrides it. |
1455
+ | `on_failure` | `fix` or `operator` | `before_complete` hooks only, and not on a workflow transition. `fix` makes the hook a check of the step: a failure rejects the step's completion and returns the step to its worker; see [Rules](#rules). Default `operator`: a failure stops the task for the operator. Also accepted on each member of `handlers`, which inherits the group's value. |
1456
+
1457
+ The filter forms are described in
1458
+ [Workflow and step filters](#workflow-and-step-filters).
1459
+ Filters must refer to configured workflows or effective steps; the reserved
1460
+ `init` step is always an effective step. A bare step name such as `fix` matches
1461
+ that name at any nesting level unless the same selector is also an exact logical
1462
+ path. Exact paths take precedence, so `steps: [code-review]` selects a top-level
1463
+ `code-review` wrapper rather than a nested `code-review/code-review` step. Use a
1464
+ slash-separated path such as `plan-and-fix/fix` to target one specific substep.
1465
+ For per-item stages, omit the runtime item segment and use the logical path, for
1466
+ example `review/fix`.
1467
+ `before_start_workflow` and `before_complete_workflow` are workflow boundaries:
1468
+ they run once per workflow in global then workflow order and cannot be declared
1469
+ at step scope. The completion boundary follows every step's `after_complete`
1470
+ hooks and precedes the built-in workflow summary and any handoff transition.
1471
+ The other phases run in global, workflow, then step order for each matching step.
1472
+
1473
+ For one action, put the shared [handler keys](#handlers) directly on the hook.
1474
+ A name-only handler references the root catalog; action keys define an inline
1475
+ handler. Use `handlers` to apply the same filters to multiple ordered handlers.
1476
+ A hook can reference an automated `handlers` group, expanding its actions in
1477
+ order under the same phase and filters. It cannot reference a root handler that defines
1478
+ `loop`, `steps`, or `items`; use such a handler as a workflow step instead.
1479
+ Every item has exactly the same shape:
1480
+
1481
+ ```yaml
1482
+ hooks:
1483
+ before_complete:
1484
+ - workflows: [task, bugfix]
1485
+ steps: [develop]
1486
+ handlers:
1487
+ - name: update-architecture-documentation
1488
+ - name: update-readme
1489
+ - name: publish-documentation
1490
+ mcp: github
1491
+ description: Publish the updated documentation.
1492
+ ```
1493
+
1494
+ Grouped and singular hook handlers accept the shorthand too. Hook filters are
1495
+ not considered handler names, so a singular filtered hook can be written as:
1496
+
1497
+ ```yaml
1498
+ hooks:
1499
+ before_start_workflow:
1500
+ - handlers:
1501
+ - ext/ww/git/handlers:is-git-clean: ~
1502
+ - ext/ww/git/handlers:start-task-branch: ~
1503
+ - check-environment: ~
1504
+ ```
1505
+
1506
+ A `handlers` group cannot contain handler action keys at its own level; entries
1507
+ are mappings and the list cannot be empty. A singular hook can instead declare
1508
+ a workflow transition with `handoff_to` plus optional `description`, `agent`,
1509
+ `model`, and `reasoning`. A transition hook belongs only on the
1510
+ `after_complete` hooks of a workflow's last top-level step, as their last
1511
+ entry, since nothing runs after a handoff; at global or workflow scope it is
1512
+ rejected:
1513
+
1514
+ ```yaml
1515
+ steps:
1516
+ - choose: Choose the next workflow.
1517
+ variables:
1518
+ - next_workflow: The workflow to run next.
1519
+ hooks:
1520
+ after_complete:
1521
+ - handoff_to: "{{next_workflow}}"
1522
+ ```
1523
+
1524
+ ### Workflow and step filters
1525
+
1526
+ Every construct that filters by workflow or step, hooks, rule groups and
1527
+ modes, takes the same two keys, `workflows` and `steps`, each either the
1528
+ string `"*"` (all) or a list of names. Only where a construct may carry a
1529
+ filter differs.
1530
+
1531
+ | Form | Hook | Rule group | Mode |
1532
+ | --- | --- | --- | --- |
1533
+ | omitted | every workflow or step | every workflow or step | every workflow or step if the other key is set; with neither key the mode is not automatic |
1534
+ | `"*"` | every workflow or step | every workflow or step | every workflow or step |
1535
+ | `[a, b]` | only those names | only those names | only those names |
1536
+ | `[]` | every workflow or step, like omission | none: the group applies only where a step names it | every workflow or step, as on a hook |
1537
+
1538
+ `"*"` is never redundant, even where omission already means all. `"*"` inside
1539
+ a list (`["*", task]`) is an error, and so is any other string: a bare name is
1540
+ not a list, write `[task]`.
1541
+
1542
+ `ww rules --json` renders a filter that admits all as `"*"`.
1543
+
1544
+ ## Rules
1545
+
1546
+ A rule is a sentence a step's agent follows while working. It may carry a
1547
+ command ww runs when the step completes, a **check**; a failed check rejects
1548
+ the completion and sends the step back to its worker, up to `max_fixes` times.
1549
+ Rules are delivered on the page of every agent step they apply to: not `init`,
1550
+ hooks, or the built-in workflow summary.
1551
+
1552
+ ### Rule files
1553
+
1554
+ A rule file is Markdown: optional YAML frontmatter between a first line
1555
+ `---` and the next `---`, then the rule's text, which must not be empty. The
1556
+ text's first sentence is its summary on the step page.
1557
+
1558
+ ```markdown
1559
+ ---
1560
+ paths: ["src/**/*.php"]
1561
+ check:
1562
+ shell: find $WW_STEP_CHANGED_FILES -name '*Service.php' -not -path 'src/Service/*'
1563
+ assert: [empty]
1564
+ max_fixes: 5
1565
+ model: claude-opus-5-5
1566
+ ---
1567
+ Put every `*Service.php` under `src/Service/<Domain>/`, one class per file.
1568
+
1569
+ Controllers must not instantiate services; inject them.
1570
+ ```
1571
+
1572
+ | Key | Type | Meaning |
1573
+ | --- | --- | --- |
1574
+ | `paths` | non-empty list of globs | The files the rule is about, relative to the step's directory. `*` and `?` stay within a path segment, `**` spans segments, and a glob without `/` matches a file name anywhere. A check whose globs match no changed file does not run. |
1575
+ | `check` | command | `argv`, or `shell` with `args` and `env`, and optional `assert`, as in [Commands](#commands); `command` and `idempotent` are not accepted. |
1576
+ | `max_fixes` | positive integer | Rejections this check allows; defaults to `limits.fixes` in `ww.json` (3). |
1577
+ | `agent`, `model`, `reasoning` | string | The worker that verifies the rule; see [Verifying rules without a command](#verifying-rules-without-a-command). |
1578
+
1579
+ A rule's identity by wording is the SHA-256 of its text with surrounding
1580
+ whitespace removed and runs of whitespace collapsed to one space.
1581
+
1582
+ ### Rule groups
1583
+
1584
+ The root `rules` maps group names to their items, as a list or as a mapping:
1585
+
1586
+ ```yaml
1587
+ rules:
1588
+ php-architecture: [rules/php/]
1589
+ docs-style:
1590
+ rules: [rules/docs/]
1591
+ workflows: [task, bugfix]
1592
+ steps: [develop, refactor]
1593
+ reasoning: high
1594
+ engineering: [php-architecture, docs-style]
1595
+ ```
1596
+
1597
+ | Key | Type | Meaning |
1598
+ | --- | --- | --- |
1599
+ | `rules` | non-empty list of strings | Each is a group name, else a path relative to the file that declares it: a directory contributes every `*.md` directly inside it, sorted by name; a file, that rule. |
1600
+ | `workflows` | `"*"` or list of workflow names | The workflows the group applies to; omitted or `"*"`, every workflow, and a workflow's heirs follow it. |
1601
+ | `steps` | `"*"` or list of step names or paths | The steps it applies to, matched like a hook's `steps`; omitted or `"*"`, every step. `steps: []` applies nowhere on its own: only a step naming the group gets it. See [Workflow and step filters](#workflow-and-step-filters). |
1602
+ | `agent`, `model`, `reasoning` | string | Defaults for the group's rules; a rule file's own value wins. |
1603
+
1604
+ A rule's ID is `<group>/<file stem>`. Two files with the same stem in one group
1605
+ are an error; a file listed in two groups has an ID in each. A group naming
1606
+ another includes its rules under that group's IDs; a cycle is an error, as is
1607
+ an item that names no group and no existing path. An absolute path is accepted
1608
+ and `lint` reports it as a notice. A later configuration level or import
1609
+ replaces a group of the same name as a whole.
1610
+
1611
+ A configured extension may ship rule groups; they come before the YAML groups,
1612
+ and a name declared in both is an error.
1613
+
1614
+ ### Step rules
1615
+
1616
+ A step's `rules` is a list. A string names a group, which then applies to the
1617
+ step whatever its filters say; else a rule file or directory that exists
1618
+ relative to the declaring file; else it is the literal text of a rule. A string
1619
+ shaped like a reference, letters, digits, `_`, `.`, `-` with at most one `/`
1620
+ and no trailing punctuation, that names neither a group nor a file is an error.
1621
+ A mapping defines a rule of the step's own:
1622
+
1623
+ | Key | Type | Meaning |
1624
+ | --- | --- | --- |
1625
+ | `text` | string | The rule. |
1626
+ | `argv`, `shell`, `args`, `env`, `assert` | command | Its check; a command without `text` is a pure check, summarised by the command. |
1627
+ | `max_fixes`, `agent`, `model`, `reasoning` | | As in a rule file. |
1628
+
1629
+ One of `text` or a command is required. The step's own rules have IDs
1630
+ `<step>/<position>`, counted from 1, and `<step>/<file stem>` for a file or
1631
+ directory entry; a group's rules keep their group IDs.
1632
+
1633
+ ```yaml
1634
+ steps:
1635
+ - name: develop
1636
+ description: Implement it.
1637
+ rules:
1638
+ - Keep the public CLI unchanged.
1639
+ - text: Include "foo" in every file you change.
1640
+ shell: grep -L foo $WW_STEP_CHANGED_FILES || true
1641
+ assert: [empty]
1642
+ - argv: [vendor/bin/phpstan, analyse]
1643
+ - rules/one-off/no-migrations.md
1644
+ - php-architecture
1645
+ ```
1646
+
1647
+ A step receives, in order, the root groups whose filters admit it, then its
1648
+ own entries; a rule ID reached twice counts once. Its checks are its rules'
1649
+ commands in that order, then its `before_complete` hooks with
1650
+ `on_failure: fix`, named `<step>/<handler name>`, or `<step>/<program>` for an
1651
+ inline command. Such a hook must run a command that asks the agent for no
1652
+ values; it runs in the step's directory and is not also run as a hook. On a
1653
+ step with no agent work of its own, it runs as an ordinary hook.
1654
+
1655
+ ### Running checks
1656
+
1657
+ When the step's worker runs `complete`, ww runs the step's checks before
1658
+ recording anything. Each sees `WW_STEP_CHANGED_FILES`: the files the step
1659
+ changed, newline-separated and relative to the step's directory, narrowed to
1660
+ the check's `paths`. A check fails on a non-zero exit or a failed assertion.
1661
+ In a shell check, a bare `$WW_STEP_CHANGED_FILES` splits on whitespace, so a
1662
+ path containing a space needs `printf '%s\n' "$WW_STEP_CHANGED_FILES" | xargs -d '\n'`;
1663
+ an `argv` check receives the variable in its environment only.
1664
+
1665
+ ### Verifying rules without a command
1666
+
1667
+ A rule without a check of its own is never judged by the worker who did the
1668
+ step. When that worker completes and the step's checks pass, ww holds the
1669
+ completion, records nothing yet, and inserts **verification items** right
1670
+ before the step: agent items ww generates, IDs
1671
+ `<workflow>:<step path>:verify:<n>`, one per distinct worker among the rules
1672
+ (the rule's or group's `agent`, `model`, `reasoning`, else the step's). Each is
1673
+ an assignment of its own, and asks for a verdict on each of its rules.
1674
+
1675
+ When the step begins, each rule without a command resolves once against the
1676
+ rule-automation store, and the step keeps what it resolved: `converted` when
1677
+ its wording has a `converted` check, which then checks it and asks no
1678
+ verifier (subject to its `config` files; see [Rule commands](#rule-commands)),
1679
+ else `judged`, whatever other status the store gives the wording. A verifier
1680
+ never writes the store.
1681
+
1682
+ A verification item completes with its findings as `--artifact` and one
1683
+ `--rule-result` per rule, a repeatable JSON object; `complete` refuses a
1684
+ missing, unknown, duplicate, or malformed one, and the option on any other
1685
+ step.
1686
+
1687
+ | `--rule-result` key | Meaning |
1688
+ | --- | --- |
1689
+ | `id` | The rule ID; required. |
1690
+ | `status` | `judged`; any other value is refused. |
1691
+ | `verdict`, `failures` | `pass`, or `fail` with `failures`, each `{file, line?, what}`. |
1692
+
1693
+ A failing verdict rejects the held completion as a failed check would, under
1694
+ the rule's `max_fixes`. Once every rule of the round passed, ww records the
1695
+ held completion as submitted.
1696
+
1697
+ `discover` (JSON only: `rules_notice`) and the first page of
1698
+ `start` (JSON: `rules_notice`) say how many declared rules are
1699
+ `unscriptized` and suggest the `ww-scriptize` skill, which starts
1700
+ `ww-scriptize-rules`. The notice is left out while
1701
+ `ww-scriptize-rules` is switched off and when it is the workflow started,
1702
+ and never blocks anything. `lint` warns with the IDs of those rules and
1703
+ suggests `ww-scriptize-rules` only while it is switched on.
1704
+
1705
+ #### Guiding checks: `rules.check_guidance`
1706
+
1707
+ ```json
1708
+ "rules": { "check_guidance": "<free text>" }
1709
+ ```
1710
+
1711
+ | Key | Type | Default | Meaning |
1712
+ | --- | --- | --- | --- |
1713
+ | `check_guidance` | string | none | The operator's guidance for building checks, carried as written by `rules --json` (`check_guidance`) for `ww-scriptize-rules` and ww's rule-writing skills; blank text is unset. |
1714
+
1715
+ `rules` takes no other key; another key, such as `scripting`, or
1716
+ a non-string `check_guidance` is an error. Every check is recorded by the
1717
+ operator, through `rules convert`, with `approved_by: operator`; a store
1718
+ written by an earlier ww may still hold `auto`.
1719
+
1720
+ ### The rule-automation store
1721
+
1722
+ `ww-rule-automation.json` at the project root keeps the project's converted
1723
+ checks and what is known about each rule wording. It is meant to be
1724
+ committed; ww writes it under its own lock, only for the operator's `rules`
1725
+ commands, and leaves it out of every change set. Verification never edits
1726
+ it, YAML, or rule files; only the operator's `rules` write commands do.
1727
+
1728
+ ```json
1729
+ {
1730
+ "schema_version": 1,
1731
+ "rules": {
1732
+ "9f2a…": {
1733
+ "text": "Controllers must not instantiate services; inject them.",
1734
+ "status": "converted",
1735
+ "interpretation": "No `new *Service(` in src/Controller.",
1736
+ "check": "deptrac",
1737
+ "approved_by": "operator"
1738
+ }
1739
+ },
1740
+ "checks": {
1741
+ "deptrac": {
1742
+ "argv": ["vendor/bin/deptrac", "analyse", "--no-progress"],
1743
+ "assert": null,
1744
+ "config": ["deptrac.yaml"],
1745
+ "covers": ["9f2a…"],
1746
+ "proven": true,
1747
+ "status": "converted",
1748
+ "proposed_at": "2026-09-29T11:00:00Z",
1749
+ "approved_at": "2026-09-29T11:00:00Z",
1750
+ "approved_by": "operator"
1751
+ }
1752
+ }
1753
+ }
1754
+ ```
1755
+
1756
+ `rules` is keyed by the rule's text hash; ww writes its `status` as
1757
+ `converted`, `not_convertible` or `rejected`, with `reason` for a rejected or
1758
+ unconvertible rule. `checks` is keyed by check name; ww writes its `status`
1759
+ as `converted` or `rejected`, `covers` lists rule hashes, and `reason`
1760
+ explains a rejected one. Only a `converted` check runs. `approved_by`
1761
+ (`operator` or `auto`) records who approved an entry. A store written while
1762
+ verifiers proposed checks inside tasks may also hold the rule statuses
1763
+ `approach_proposed`, `approach_approved`, `interpreted`, `proposed` and
1764
+ `ambiguous` (with `approach`, `extends` and `candidates`), the check status
1765
+ `proposed`, a check's `pending` revision, and `proposed_in`, `proposed_run`
1766
+ and `approved_in` on either map; ww reads them, judges such rules, and never
1767
+ writes them any more.
1768
+ ww reads and writes `schema_version` 1 of the store. An unknown key, status,
1769
+ or `schema_version` is an error.
1770
+
1771
+ ### Rule commands
1772
+
1773
+ | Command | Effect |
1774
+ | --- | --- |
1775
+ | `check <task> [--json]` | Runs the checks of the step in progress against its change set so far, exactly as `complete` would, and prints the failures in the fix page's shape, or `All checks pass`, plus the rules a verifier judges at completion. Records nothing: no attempt counts and no output is kept. Exits 1 when a check fails. Not written to the audit log. |
1776
+ | `dispute <task> --rule <id> --reason "<why>"` | Only while the step is in progress, and only for an ID a rejected completion of it failed: a rule, a `fix` hook, a derived check, or a judged rule. Stops the task with `operator_reason: check_disputed`; the page shows the check's text, command, and last output, and the worker's reason. |
1777
+ | `rule <task> <id> [--json]` | One rule or check of the task as its plan froze it: full text, globs, rule file (or the step's own list), command and assertion, `max_fixes`, the steps of the task that carry it, and for a rule without a command what the rule-automation store knows about its wording. |
1778
+ | `rules [--json]` | The declared root groups with their filters, verifier hints, and rules (ID, summary, globs, whether it has a check, file, times disputed), then each step's own rules and the groups it names. |
1779
+ | `rules revoke <check> [--reason "<why>"] [--yes] [--json]` | Shows a `converted` or `proposed` store check, asks, and rejects it, recording the reason, together with the rules whose entries name it, which a verifier judges from then on. Never touches YAML, rule files, or the check's config files; the output says they stay for the operator. `--yes` skips the question; without it and without a terminal, it refuses. |
1780
+ | `rules convert <check> --covers <rule-id>... [--assert empty\|equals:<value>]... [--config <path>...] [--proven] [--dry-run] [--yes] [--json] (--check-shell "<sh>" \| --check-argv <arg>... \| --check-argv -- <arg>...)` | `--check-argv -- <arg>...` goes last and takes every argument after `--` as the argv, options starting with `-` included. Shows the check, its command in full, its config files, the rules it covers with each one's current store state, and every other change, asks, and records it in the store as `converted`, approved by the operator. A new name creates the check; an existing one has its command, config, proof and coverage replaced and any pending revision dropped. A covered rule another check covered moves to this one, and that check loses any pending revision and is removed once it covers nothing more. A rule this check covered before and no longer does returns to unscriptized (its entry is removed); a rejection or decline naming the check stays. `--config` paths are relative to the project and refused when absolute or with a `..` part. Refuses an unknown or repeated rule ID and a rule with a command of its own. `--dry-run` prints the preview and records nothing. `--json` gives the check, its rules, and the `unscriptized`, `moved`, `dropped_checks` and `dropped_revisions` changes. |
1781
+ | `rules decline <rule-id>... --reason "<why>" [--dry-run] [--yes] [--json]` | Shows the rules with each one's current store state and every other change, asks, and records them as `not_convertible` with the reason, removing them from any check's coverage (a check left covering nothing is removed): a verifier judges them, and `ww-scriptize-rules` leaves them out. `--json` gives the rules and the same changes as `rules convert`. |
1782
+ | `rules prune [--yes] [--json]` | Lists the store's orphans, rule entries whose wording no declared rule has and checks that cover only such rules and that no remaining rule names, asks, and deletes them. `--yes` skips the question. |
1783
+ | `rules add <group> --text "<text>" [--paths <glob>...] [--assert empty\|equals:<value>]... [--id <stem>] [--check-shell "<sh>" \| --check-argv <arg>... \| --check-argv -- <arg>...]` | `--check-argv -- <arg>...` goes last, as for `rules convert`. Creates `<stem>.md` in the group's first directory item; the stem is the first five words of the first sentence in kebab-case unless `--id` gives one. Refuses an existing file, a group without a directory, and a group an extension ships. Reports each glob's match count among the project's files. `--assert` is repeatable, one condition each. |
1784
+ | `rules add --group <name> --dir <path> [--workflows <name>...] [--steps <name>...]` | `<path>` is relative to the project root and inside it. Adds the group `{rules: [<path>/], workflows, steps}` to `ww-rules.yaml` and, the first time, `ww-rules.yaml` to the repo file's `imports`; creates the directory. A filter option without a name writes `[]`; `'*'` alone writes `"*"`. |
1785
+ | `rules edit <id> [--text "<text>"] [--paths <glob>...]` | Replaces a rule file's body, its `paths`, or both, keeping every other byte; warns when the wording's hash changes and names the store entry and approved check that stop matching. Refuses a rule written in a step's `rules` list. |
1786
+ | `rules move <id> <group>` | Moves the rule file unchanged into the group's first directory; the rule's ID becomes `<group>/<stem>`. |
1787
+ | `rules filter <group> [--workflows <name>...] [--steps <name>...] [--all-workflows] [--all-steps]` | Sets a `ww-rules.yaml` group's filters; `--workflows '*'` / `--steps '*'` writes `"*"`, and `--all-*` removes one, which also admits all. Refuses a group declared in another file. |
1788
+ | `rules promote <check>` | Copies a `converted` store check without a pending revision into the `check` of every rule file whose wording it covers, then deletes the check and those rules' entries from the store. Refuses when a covered rule is written in a step's `rules` list or already has a check. |
1789
+
1790
+ Every write loads the configuration once the files are written; when it does
1791
+ not load, or the write would not have its effect, every file is restored and
1792
+ the command fails with the reason. `--dry-run` validates the same way and
1793
+ restores every file. Writes print the steps the rule or group reaches and
1794
+ never commit. `rules --json` gives each rule a `store_check`: the approved
1795
+ store check that runs for a rule without a command of its own; and a
1796
+ `scriptize` state: `command` (its own check), `converted`, `not_convertible`,
1797
+ `rejected`, or `unscriptized`, which a rule with no store entry, or one in an
1798
+ old store's interim status (`interpreted`, `approach_proposed`,
1799
+ `approach_approved`, `proposed`, `ambiguous`), has. The text listing names the
1800
+ same state after each rule a verifier judges.
1801
+
1802
+ A converted check applies to a step only when every path in its `config`
1803
+ exists in the directory the step's checks run in, the task's worktree when it
1804
+ has one. Otherwise its rules are judged for that step (`missing` on the step's
1805
+ rule resolution names the first absent file, and the step page and the
1806
+ verifier page say so), so a check whose configuration is still on an unmerged
1807
+ branch never runs where that configuration is absent. A config path that is
1808
+ absolute or has a `..` part, which only a hand-edited store holds, counts as
1809
+ absent.
1810
+
1811
+ `ww-rules.yaml`, next to `ww.yaml`, holds only a `rules`
1812
+ mapping written by `rules add --group` and `rules filter`; ww rewrites it
1813
+ whole and it is composed like any import. While it exists without being
1814
+ imported, the write commands refuse to use it.
1815
+
1816
+ At a `check_disputed` stop the operator answers with `next`:
1817
+
1818
+ | Option | Effect |
1819
+ | --- | --- |
1820
+ | `--retry` | The check stands: the dispute is cleared, the rejection keeps counting toward `max_fixes`, and the next `next` hands the step back to its worker. |
1821
+ | `--force --reason "<why>"` | Waives the disputed ID for this step: its check does not run, or its rule is not verified, when the worker completes again, and the artifact records the waiver. |
1822
+
1823
+ At the `fix_limit` stop `--force` waives every check and rule of the step. The
1824
+ step record keeps its waivers as `checks_waived`, a mapping of ID to reason.
1825
+ `next --replan` (take the changed workflow definition from the first changed
1826
+ item on; confirmed when it reruns finished steps) and `next --keep-plan` (carry
1827
+ on with the saved plan) answer the `plan_changed` stop; see
1828
+ [features.md](features.md#when-the-workflow-changes-mid-run).
1829
+ `next --yes` confirms `--retry`, `--force`, or a rewinding `--replan`
1830
+ without the y/N prompt, for an agent carrying out the operator's stated
1831
+ decision; the effect is still printed, and the audit record notes the
1832
+ confirmation. `--yes` without one of them is an error. ww asks only at a
1833
+ terminal: without one and without `--yes` it refuses at once, never reading
1834
+ an answer from a pipe.
1835
+
1836
+ Every dispute is also appended to `.ww/rule-disputes.json`, beside the task
1837
+ states, so `lint` can list disputed IDs without reading every task:
1838
+
1839
+ ```json
1840
+ {
1841
+ "schema_version": 1,
1842
+ "disputes": [
1843
+ {
1844
+ "check": "docs/header",
1845
+ "text_hash": "348d…",
1846
+ "task_id": "TASK-19",
1847
+ "run_id": "01-task",
1848
+ "step": "develop",
1849
+ "reason": "notes.md is a scratch file.",
1850
+ "attempt": 1,
1851
+ "disputed_at": "2026-09-30T10:00:00Z"
1852
+ }
1853
+ ]
1854
+ }
1855
+ ```
1856
+
1857
+ `text_hash` is the disputed rule's wording hash, `null` for a hook or derived
1858
+ check. An unknown key or `schema_version` is an error. The log is history: the
1859
+ rule-automation store is not touched by a dispute.
1860
+
1861
+ ## Minimal example
1862
+
1863
+ ```yaml
1864
+ workflows:
1865
+ - name: task
1866
+ steps:
1867
+ - develop: Implement the requested change.
1868
+ - verify: ~
1869
+ argv: [python, -m, pytest, -q]
1870
+ on_failure: fix
1871
+ ```
1872
+
1873
+ Use `ww-agentic-workflows lint` to validate the complete configuration; it also
1874
+ warns when `./ww` differs from the launcher this ww writes. Use
1875
+ `ww-agentic-workflows plan --workflow <name> --agent <agent>` to inspect a
1876
+ selected workflow's resulting execution plan.