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.
- ww/__init__.py +18 -0
- ww/_bundled_extensions/ww/git/extension.py +1728 -0
- ww/action_execution.py +887 -0
- ww/actions/__init__.py +94 -0
- ww/actions/command.py +444 -0
- ww/actions/contracts.py +699 -0
- ww/actions/extension.py +197 -0
- ww/actions/mcp.py +84 -0
- ww/actions/prompt.py +74 -0
- ww/actions/skill.py +62 -0
- ww/actions/slash_command.py +63 -0
- ww/agents.py +151 -0
- ww/amendments.py +54 -0
- ww/artifacts.py +93 -0
- ww/assessments.py +181 -0
- ww/assets/__init__.py +2 -0
- ww/assets/agent_instructions.md +49 -0
- ww/assets/docs/examples.md +879 -0
- ww/assets/docs/features.md +4639 -0
- ww/assets/docs/specification.md +1876 -0
- ww/assets/noww_skill.md +11 -0
- ww/assets/workflows/catchall.yaml +26 -0
- ww/assets/workflows/onboarding.yaml +586 -0
- ww/assets/workflows/scriptize.yaml +130 -0
- ww/assets/ww-automate_skill.md +23 -0
- ww/assets/ww-deduce-feedback_skill.md +38 -0
- ww/assets/ww-feedback-rules_skill.md +48 -0
- ww/assets/ww-learn-project_skill.md +22 -0
- ww/assets/ww-refresh_skill.md +26 -0
- ww/assets/ww-rule_skill.md +83 -0
- ww/assets/ww-rules-from-artifacts_skill.md +22 -0
- ww/assets/ww-scriptize_skill.md +33 -0
- ww/assets/ww-setup_skill.md +94 -0
- ww/assets/ww-solve_skill.md +23 -0
- ww/assets/ww-suggest_skill.md +32 -0
- ww/assets/ww-wizard_skill.md +105 -0
- ww/assets/ww_skill.md +59 -0
- ww/assignments.py +283 -0
- ww/bootstrap.py +405 -0
- ww/builtin_workflows.py +215 -0
- ww/changes.py +225 -0
- ww/child_coordination.py +482 -0
- ww/children.py +106 -0
- ww/claude_permissions.py +115 -0
- ww/cli/__init__.py +7 -0
- ww/cli/__main__.py +6 -0
- ww/cli/audit.py +129 -0
- ww/cli/catalogs.py +131 -0
- ww/cli/discover.py +607 -0
- ww/cli/initialization.py +898 -0
- ww/cli/lookup.py +287 -0
- ww/cli/main.py +1768 -0
- ww/cli/parser.py +1200 -0
- ww/cli/prompts.py +217 -0
- ww/cli/updates.py +117 -0
- ww/completion_artifacts.py +156 -0
- ww/completion_inputs.py +39 -0
- ww/config/__init__.py +582 -0
- ww/config/actions.py +591 -0
- ww/config/composition.py +571 -0
- ww/config/rules.py +511 -0
- ww/config/steps.py +1220 -0
- ww/config/values.py +223 -0
- ww/config_files.py +191 -0
- ww/config_writes.py +264 -0
- ww/contracts.py +155 -0
- ww/control.py +41 -0
- ww/defaults.py +130 -0
- ww/design_docs.py +32 -0
- ww/discovery.py +104 -0
- ww/documents.py +217 -0
- ww/errors.py +18 -0
- ww/executable.py +43 -0
- ww/execution_models/__init__.py +64 -0
- ww/execution_models/construction.py +148 -0
- ww/execution_models/decoding.py +38 -0
- ww/execution_models/plan_codec.py +565 -0
- ww/execution_models/records.py +1206 -0
- ww/execution_models/runs.py +266 -0
- ww/extensions/__init__.py +40 -0
- ww/extensions/api.py +559 -0
- ww/extensions/registry.py +864 -0
- ww/extensions/store.py +78 -0
- ww/feedback.py +342 -0
- ww/handler_repairs.py +57 -0
- ww/hooks/__init__.py +40 -0
- ww/hooks/agents.py +380 -0
- ww/hooks/install.py +168 -0
- ww/hooks/notices.py +206 -0
- ww/hooks/records.py +209 -0
- ww/hooks/runtime.py +266 -0
- ww/hooks/transcripts.py +183 -0
- ww/inspect.py +896 -0
- ww/instructions/__init__.py +17 -0
- ww/instructions/builder.py +1682 -0
- ww/instructions/commands.py +335 -0
- ww/instructions/handoff.py +149 -0
- ww/instructions/models.py +686 -0
- ww/instructions/policy.py +219 -0
- ww/instructions/text.py +168 -0
- ww/interactions.py +187 -0
- ww/interpolation.py +37 -0
- ww/item_passes.py +167 -0
- ww/items.py +99 -0
- ww/locking.py +207 -0
- ww/metadata_publication.py +230 -0
- ww/onboarding.py +229 -0
- ww/open_work.py +236 -0
- ww/operations.py +193 -0
- ww/operator_ui/__init__.py +16 -0
- ww/operator_ui/page.html +351 -0
- ww/operator_ui/server.py +215 -0
- ww/operator_ui/session.py +389 -0
- ww/operator_ui/sheet.py +104 -0
- ww/operator_ui/view.py +109 -0
- ww/output.py +339 -0
- ww/output_adapters/__init__.py +12 -0
- ww/output_adapters/base.py +25 -0
- ww/output_adapters/json_adapter.py +37 -0
- ww/output_adapters/markdown.py +2293 -0
- ww/output_adapters/rule_pages.py +337 -0
- ww/output_adapters/terminal.py +21 -0
- ww/package_updates.py +167 -0
- ww/plan/__init__.py +38 -0
- ww/plan/actions.py +207 -0
- ww/plan/compiler.py +1492 -0
- ww/plan/constructs.py +456 -0
- ww/plan/models.py +665 -0
- ww/project_config.py +752 -0
- ww/recovery.py +401 -0
- ww/replanning.py +367 -0
- ww/results.py +77 -0
- ww/rule_checks.py +230 -0
- ww/rule_conversion.py +331 -0
- ww/rule_disputes.py +148 -0
- ww/rule_store.py +456 -0
- ww/rule_verification.py +714 -0
- ww/rule_views.py +447 -0
- ww/rule_writes.py +920 -0
- ww/run_coordination.py +158 -0
- ww/runtimes.py +105 -0
- ww/service.py +4405 -0
- ww/setup_apply.py +428 -0
- ww/step_values.py +20 -0
- ww/storage.py +447 -0
- ww/storage_adapters/__init__.py +36 -0
- ww/storage_adapters/base.py +540 -0
- ww/storage_adapters/filesystem.py +370 -0
- ww/storage_adapters/memory.py +195 -0
- ww/storage_adapters/project_metadata.py +69 -0
- ww/storage_adapters/task_document.py +484 -0
- ww/task_ids.py +114 -0
- ww/task_references.py +124 -0
- ww/transitions.py +1619 -0
- ww/updates.py +399 -0
- ww/upgrade.py +95 -0
- ww/validation.py +168 -0
- ww/variables.py +275 -0
- ww/workflow_config.py +854 -0
- ww/workflow_update.py +239 -0
- ww/workflow_validation.py +1260 -0
- ww/workspace.py +50 -0
- ww_agentic_workflows-1.0.0.dev3.dist-info/METADATA +690 -0
- ww_agentic_workflows-1.0.0.dev3.dist-info/RECORD +167 -0
- ww_agentic_workflows-1.0.0.dev3.dist-info/WHEEL +4 -0
- ww_agentic_workflows-1.0.0.dev3.dist-info/entry_points.txt +2 -0
- 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.
|