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,4639 @@
|
|
|
1
|
+
# ww feature reference
|
|
2
|
+
|
|
3
|
+
This document is the design guide and feature reference for
|
|
4
|
+
`ww-agentic-workflows`. It is one of three authorities for designing workflows:
|
|
5
|
+
the [specification](specification.md) says exactly what the syntax, defaults and
|
|
6
|
+
failure semantics are, this guide says when to use what and why, and the
|
|
7
|
+
[examples](examples.md) are runnable. Start with [Designing a
|
|
8
|
+
workflow](#designing-a-workflow). For a short introduction, see
|
|
9
|
+
[README.md](../README.md); for internal design, component boundaries, and
|
|
10
|
+
persistence invariants, see [architecture.md](architecture.md).
|
|
11
|
+
|
|
12
|
+
An installation carries the same-version copies of all three: read them with
|
|
13
|
+
`ww docs specification`, `ww docs features` and `ww docs examples`.
|
|
14
|
+
|
|
15
|
+
## Feature overview
|
|
16
|
+
|
|
17
|
+
- Read-only validation of `ww.yaml`, plus agent-specific workflow
|
|
18
|
+
planning in Markdown or JSON.
|
|
19
|
+
- A `ww.yaml` split across imported files, composed in memory.
|
|
20
|
+
- User, repo, and local configuration levels, resolved automatically, above
|
|
21
|
+
the workflows ww ships itself.
|
|
22
|
+
- A read-only profile of the checkout (`inspect`): branches, activity, fix
|
|
23
|
+
signals, hot paths, manifests and verify commands, and conventions.
|
|
24
|
+
- Onboarding state, and setup fragments that ww validates and places in the
|
|
25
|
+
shared or local configuration after asking.
|
|
26
|
+
- Built-in learning and setup workflows, started by the `ww-setup` skills:
|
|
27
|
+
ww learns about the operator, team, company and project, designs a setup
|
|
28
|
+
with the operator, and proposes it in full.
|
|
29
|
+
- An implicit, reserved `init` step that preserves task requirements.
|
|
30
|
+
- Resumable task execution from immutable plan snapshots.
|
|
31
|
+
- Agent-owned prompts, skills, slash commands, profiles, and MCP calls.
|
|
32
|
+
- ww-owned CLI handlers, output assertions, and lifecycle transitions.
|
|
33
|
+
- Global, workflow, and step hooks with filters and conditional prompts.
|
|
34
|
+
- Rules delivered on each step's page, and checks ww runs on the files a step
|
|
35
|
+
changed, which send the step back to its worker until they pass.
|
|
36
|
+
- Variables, durable task and project metadata, artifacts, and artifact
|
|
37
|
+
dependencies.
|
|
38
|
+
- Nested steps, dynamic per-item work, and parent/child task workflows.
|
|
39
|
+
- Sequential workflow runs and terminal handoffs between workflows.
|
|
40
|
+
- Configurable runtimes, models, reasoning levels, and modes.
|
|
41
|
+
- Per-step and per-handler working directories: the task workspace, the
|
|
42
|
+
project checkout, or the project root.
|
|
43
|
+
- Explicit manager/worker assignment handoff with durable continuation state.
|
|
44
|
+
- Qualified extensions, including bundled Git branch, worktree, and commit
|
|
45
|
+
automation.
|
|
46
|
+
- Atomic persistence, task-level concurrency control, interruption recovery,
|
|
47
|
+
execution logs, and lock cleanup.
|
|
48
|
+
|
|
49
|
+
## Designing a workflow
|
|
50
|
+
|
|
51
|
+
Start with the smallest workflow that does the job and add structure only for a
|
|
52
|
+
concrete reason. This section is the guidance; the
|
|
53
|
+
[specification](specification.md) has the exact syntax and the
|
|
54
|
+
[examples](examples.md) have runnable versions of everything named here.
|
|
55
|
+
|
|
56
|
+
### Start linear
|
|
57
|
+
|
|
58
|
+
A workflow is a list of steps in order. Write the work as steps, give them the
|
|
59
|
+
defaults, and stop there until something real asks for more. Items, several
|
|
60
|
+
item passes, loops, assessments, persistence, modes and reusable groups each
|
|
61
|
+
cost the reader something, so each needs a concrete reason: the work really
|
|
62
|
+
splits into independent pieces, a judgment really routes what follows, the
|
|
63
|
+
same list really returns every round, a preference really varies per task.
|
|
64
|
+
When `items: ~` is enough, do not write item stages; when one pass is enough,
|
|
65
|
+
do not add a second. [Example 1](examples.md#1-a-linear-workflow-with-an-automatic-check)
|
|
66
|
+
is a complete workflow.
|
|
67
|
+
|
|
68
|
+
### Steps, handlers and hooks
|
|
69
|
+
|
|
70
|
+
- An **ordinary step** is the place for anything the workflow visibly does,
|
|
71
|
+
including a command ww runs itself (`argv` or `shell`). It appears in the
|
|
72
|
+
plan, in `status` and in the artifacts, it can fail and be repaired
|
|
73
|
+
(`on_failure: fix`), and readers find it where the work happens. Verifying a
|
|
74
|
+
change is such an operation: write it as a step, after the step that
|
|
75
|
+
changes the code.
|
|
76
|
+
- A **handler** is a named definition worth reusing: the same command in
|
|
77
|
+
several workflows, a reusable group of automatic commands, a loop used
|
|
78
|
+
by several workflows. Define one when the second use appears, not before. A step uses it
|
|
79
|
+
with `handler: <name>`.
|
|
80
|
+
- A **hook** attaches work to a lifecycle point of steps or workflows
|
|
81
|
+
without appearing as a step: it exists for invariants, things that must
|
|
82
|
+
hold around every matching step or workflow whatever the steps say, such as a
|
|
83
|
+
clean tree before any task starts or a commit when a workflow completes, or
|
|
84
|
+
a check that must pass whenever a code-changing step completes
|
|
85
|
+
(`before_complete` with `on_failure: fix`). A command being automatic does
|
|
86
|
+
not make it a hook, and putting visible operations in hooks hides the
|
|
87
|
+
workflow from the people who read it.
|
|
88
|
+
|
|
89
|
+
### Conversation or assessment
|
|
90
|
+
|
|
91
|
+
Both involve a judgment but they belong to different people. An
|
|
92
|
+
**interactive step** is a conversation with the operator: discussing a design,
|
|
93
|
+
reviewing a change, performing a manual test. The operator talks and decides;
|
|
94
|
+
the step ends when their intent is clear, and `choices` only lists the answers
|
|
95
|
+
worth offering (`{{ww.choices}}` shows their labels in the instruction; it
|
|
96
|
+
guides, it never validates). An **assessment** (`assess`) is the agent's own
|
|
97
|
+
evaluation: it looks at the evidence, picks `positive`, `negative` or `mixed`
|
|
98
|
+
(or a label you declare) and the workflow routes on that. Use an assessment
|
|
99
|
+
to gate or branch on something the agent can decide; use an interactive step
|
|
100
|
+
where the operator's say matters. Prefer the compact and standard-branch
|
|
101
|
+
forms, and custom labels in `outcomes` only when positive and negative do not
|
|
102
|
+
say it.
|
|
103
|
+
|
|
104
|
+
### Items: independent pieces of work
|
|
105
|
+
|
|
106
|
+
Use `items` when the work splits into pieces that are independently
|
|
107
|
+
completed, checked or reported: review comments, test cases, files to migrate.
|
|
108
|
+
`items: ~` gives every item one stage that analyzes, resolves and reports it.
|
|
109
|
+
Choose per-item stages (`items.steps`) only when the stages differ. When
|
|
110
|
+
several pieces are cheaper analyzed or fixed together but each still has to be
|
|
111
|
+
reported on its own, use a shared analysis or fix with one item per
|
|
112
|
+
independently reportable source: the workflow has one collection and several
|
|
113
|
+
passes over it, and ordinary steps between the passes run once for everyone
|
|
114
|
+
([Several passes over the same items](#several-passes-over-the-same-items),
|
|
115
|
+
[example 4](examples.md#4-one-analysis-one-fix-one-report-per-comment)).
|
|
116
|
+
Make the item the unit that is reported: one item per source comment, even
|
|
117
|
+
when a hundred comments get one fix.
|
|
118
|
+
|
|
119
|
+
### External items: IDs, reconciliation and restarts
|
|
120
|
+
|
|
121
|
+
When items mirror something outside ww, such as review comments on a pull
|
|
122
|
+
request, design for being run again:
|
|
123
|
+
|
|
124
|
+
- Give each item the source's own stable ID, as the item ID or in a field
|
|
125
|
+
that `identity` requires and `unique` keeps single. A rerun then recognizes
|
|
126
|
+
its comments instead of creating copies.
|
|
127
|
+
- Declare `persistent: true` when the same list returns every round. The
|
|
128
|
+
collection step then reconciles the stored items against the source
|
|
129
|
+
(`add-item`, `update-item --text`, `remove-item`) instead of splitting
|
|
130
|
+
again. Identity, text, references and custom fields carry over to the next
|
|
131
|
+
run; analysis, solution, `resolved` and `reported` start clear.
|
|
132
|
+
- Keep remote results in item fields, such as the reply ID. Pass the saved
|
|
133
|
+
value to the command that posts, `"{{ww.item.field.reply_id}}"`, so a
|
|
134
|
+
project-owned script can update the reply it already made instead of
|
|
135
|
+
creating another. A per-item command stage that declares
|
|
136
|
+
`saves: item.field.reply_id` stores the command's whole trimmed output
|
|
137
|
+
there, and the last report-stage item is marked `reported` in the same
|
|
138
|
+
commit, only after a zero exit.
|
|
139
|
+
- ww makes no exactly-once promise about remote effects. If the process dies
|
|
140
|
+
after a remote reply but before the result is saved, the next attempt runs
|
|
141
|
+
the command again. The handler or script owns idempotency, which is why it
|
|
142
|
+
takes the saved ID.
|
|
143
|
+
- `next --retry` after a failed stage, or `start --fresh-items` to forget the
|
|
144
|
+
stored list, are the restart tools; see
|
|
145
|
+
[Items that persist across runs](#items-that-persist-across-runs).
|
|
146
|
+
|
|
147
|
+
### What needs the agent and what ww does itself
|
|
148
|
+
|
|
149
|
+
A shell or argv step runs without agent work. Values ww owns, such as
|
|
150
|
+
`{{ww.task.id}}`, `{{ww.git.branch}}` and `{{ww.item.field.<name>}}`, are
|
|
151
|
+
read by ww when the command runs, so using them never asks the agent for
|
|
152
|
+
anything. Only a required agent variable, a `variables` entry of the
|
|
153
|
+
`name: description` form, means input is needed: the agent supplies it with
|
|
154
|
+
`complete --variable`, and then ww runs the command. Dynamic values go to
|
|
155
|
+
commands as `args`, `env` or `argv` entries, never into shell source.
|
|
156
|
+
|
|
157
|
+
### Modes and rules
|
|
158
|
+
|
|
159
|
+
Add a mode only for a preference that changes how an agent works, such as
|
|
160
|
+
brief updates or asking first, and a rule only for a convention no command
|
|
161
|
+
checks, on the steps it governs, with a one-line check where one exists. A
|
|
162
|
+
rule never restates its step, a preference or a command, and a step that
|
|
163
|
+
needs none gets none. Never redefine a workflow ww ships (`catchall`, `ww-*`).
|
|
164
|
+
|
|
165
|
+
### Where to put workflows
|
|
166
|
+
|
|
167
|
+
Personal defaults belong in your global file, the team's in the project's
|
|
168
|
+
`ww.yaml`, and your own variation of a project workflow in `ww.local.yaml`; a
|
|
169
|
+
lower level replaces a same-named definition. `discover` names each
|
|
170
|
+
workflow's source, and where several fit, prefer local over project over
|
|
171
|
+
global unless the operator named one
|
|
172
|
+
([example 6](examples.md#6-global-project-and-local-variants)).
|
|
173
|
+
|
|
174
|
+
## Development package snapshots
|
|
175
|
+
|
|
176
|
+
Pushes to this repository's `dev` branch publish `1.0.0.devN` snapshots to PyPI
|
|
177
|
+
after the release gates pass and the final distribution files are validated.
|
|
178
|
+
Snapshots identify unreleased work; they do not create Git tags or GitHub
|
|
179
|
+
releases. Install an exact build, for example:
|
|
180
|
+
|
|
181
|
+
```console
|
|
182
|
+
pip install ww-agentic-workflows==1.0.0.dev42
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Publishing requires the one-time Trusted Publisher setup described in
|
|
186
|
+
[development releases](development-releases.md). That guide also covers the
|
|
187
|
+
run-number sequence, retry behavior and opting into the latest snapshot.
|
|
188
|
+
|
|
189
|
+
## Initialize a project
|
|
190
|
+
|
|
191
|
+
`ww-agentic-workflows init` is additive and safe to run after a partial checkout
|
|
192
|
+
or branch switch. It creates only missing files and root configuration keys,
|
|
193
|
+
preserves existing content, restores `.ww/tasks`, and reports created and
|
|
194
|
+
preserved parts. `--json` and `--no-input` use deterministic defaults for
|
|
195
|
+
automation.
|
|
196
|
+
|
|
197
|
+
The interactive wizard chooses UUID, numeric, or timestamp task IDs, written as
|
|
198
|
+
`task_format: "TASK-{{uuid}}"`, `"TASK-{{digit}}"`, or `"TASK-{{timestamp}}"`. When the
|
|
199
|
+
project contains `.git/`, it enables `ww/git`, prefers an existing `master`
|
|
200
|
+
branch and then `main`, enables separate task branches, and starts with
|
|
201
|
+
`feature/{{ww.task.id}}`. It asks for optional workflow-specific branch formats and
|
|
202
|
+
whether worktrees should be used. Enabled worktrees default to
|
|
203
|
+
`./git-worktrees/{{ww.task.id}}`, and the directory is created immediately.
|
|
204
|
+
|
|
205
|
+
The settings file init writes holds every root-level setting with its value,
|
|
206
|
+
so each option can be found and changed in place; the answers replace the
|
|
207
|
+
defaults. Without Git, and with the uuid format:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"enabled": true,
|
|
212
|
+
"runtime": "single",
|
|
213
|
+
"update_check": true,
|
|
214
|
+
"executable": "ww-agentic-workflows",
|
|
215
|
+
"task_format": "TASK-{{uuid}}",
|
|
216
|
+
"limits": {
|
|
217
|
+
"rounds": 3,
|
|
218
|
+
"fixes": 3
|
|
219
|
+
},
|
|
220
|
+
"agent_hooks": {
|
|
221
|
+
"check_unfinished": true,
|
|
222
|
+
"recent_days": 3
|
|
223
|
+
},
|
|
224
|
+
"rules": {},
|
|
225
|
+
"builtins": {
|
|
226
|
+
"init": {
|
|
227
|
+
"model": "cheapest",
|
|
228
|
+
"reasoning": "low"
|
|
229
|
+
},
|
|
230
|
+
"workflow_summary": {
|
|
231
|
+
"model": "auto",
|
|
232
|
+
"reasoning": "auto"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
"workflows": {},
|
|
236
|
+
"projects": [],
|
|
237
|
+
"extensions": {}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
In an existing file init adds the keys that are missing, nested ones
|
|
242
|
+
included, with these defaults and keeps every value already there. A key
|
|
243
|
+
init does not ask about (`runtime`, `update_check`, `limits`, `agent_hooks`,
|
|
244
|
+
`rules`, `builtins`, `workflows`, `projects`) that the user or local settings file
|
|
245
|
+
already sets is not written, so a default in the repo file never hides it.
|
|
246
|
+
|
|
247
|
+
The wizard offers to keep `.ww` out of Git; without consent it only reports
|
|
248
|
+
that action. With consent it appends these lines to `.gitignore`:
|
|
249
|
+
|
|
250
|
+
```gitignore
|
|
251
|
+
.ww/*
|
|
252
|
+
!.ww/project.md
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Everything under `.ww` is one checkout's state, except `project.md`, in which
|
|
256
|
+
ww records what it learned about the project: it is meant to be committed and
|
|
257
|
+
shared. Git cannot re-include a file inside
|
|
258
|
+
an ignored directory, which is why the directory's contents are ignored rather
|
|
259
|
+
than the directory. A line that ignores the directory whole, as `.ww/`, `.ww`,
|
|
260
|
+
`/.ww` or `/.ww/`, would keep the
|
|
261
|
+
shared files out too, so every such line is replaced by these lines, written
|
|
262
|
+
once where the first one stood. A `.ww/*` line gains the re-inclusions it
|
|
263
|
+
lacks, right after it; a re-inclusion only counts after the last `.ww/*` line,
|
|
264
|
+
since a later one ignores the file again. Every other line is left alone, and
|
|
265
|
+
the file keeps its line endings (CRLF stays CRLF).
|
|
266
|
+
The patterns `*ww.local.yaml`,
|
|
267
|
+
`*ww.local.json` and `ww-setup.local.yaml` are added without
|
|
268
|
+
asking, since [local configuration](#user-repo-and-local-configuration) belongs
|
|
269
|
+
to one checkout:
|
|
270
|
+
they are appended to an existing `.gitignore` once, never duplicated, and a
|
|
271
|
+
missing `.gitignore` is created for them only inside a Git repository. It also reports missing `@WW_AGENT_INSTRUCTIONS.md`
|
|
272
|
+
references in `AGENTS.md` and an existing `CLAUDE.md`, and reminds the user to
|
|
273
|
+
define workflows when the initialized `ww.yaml` is empty. Equivalent
|
|
274
|
+
non-interactive choices are available through `--task-format`, `--worktrees`,
|
|
275
|
+
`--worktree-dir`, repeated `--branch-format WORKFLOW=FORMAT`,
|
|
276
|
+
`--update-gitignore`, and `--skills`.
|
|
277
|
+
|
|
278
|
+
For every agent directory it finds, such as `.claude/` or `.codex/`, the wizard
|
|
279
|
+
offers to install the shipped skills at `<directory>/skills/<name>/SKILL.md`.
|
|
280
|
+
The `ww` skill lets a user ask explicitly to work through ww: it tells the
|
|
281
|
+
agent to run `discover` and follow ww from there. The `noww` skill is the way
|
|
282
|
+
out: invoked as `/noww`, it tells the agent not to use ww for the rest of the
|
|
283
|
+
conversation, `catchall` included. The `ww-rule` skill writes rules for ww's
|
|
284
|
+
steps from the operator's words; see [Writing rules with the ww-rule
|
|
285
|
+
skill](#writing-rules-with-the-ww-rule-skill). `--skills` installs them all
|
|
286
|
+
everywhere without asking and `--no-skills` skips them; an existing skill file is never
|
|
287
|
+
overwritten, and a skill a later ww version bundles is offered once on its
|
|
288
|
+
own, as described in [Installing the ww skills during
|
|
289
|
+
init](#installing-the-ww-skills-during-init).
|
|
290
|
+
|
|
291
|
+
For every hook-capable agent whose directory exists (Claude Code, Codex,
|
|
292
|
+
Cursor, Antigravity), init also offers ww's [agent hooks](#agent-hooks), once
|
|
293
|
+
per agent, and remembers the answer in `.ww/init-choices.json`. `--hooks`
|
|
294
|
+
installs them for all of those agents without asking and `--no-hooks` skips
|
|
295
|
+
them; without either flag or a saved answer, a non-interactive init leaves
|
|
296
|
+
hooks alone. A hook installation that fails, for example because the agent's
|
|
297
|
+
hooks file is not valid JSON, never fails init: the summary names the file and
|
|
298
|
+
prints the snippet to add by hand.
|
|
299
|
+
|
|
300
|
+
When Claude Code is set up (a `.claude` directory exists), init also asks, as an
|
|
301
|
+
opt-in question whose default is no, whether to write Bash allow rules for
|
|
302
|
+
ww's role commands into `.claude/settings.local.json`: the project wrapper's
|
|
303
|
+
absolute path (never a bare `ww`) followed by `instruction *`, `next *`,
|
|
304
|
+
`complete *`, `fail *`, `dispute *`, `check *`, `status *`, `artifacts *`,
|
|
305
|
+
`items *`, `item *`, `add-item *`, `update-item *`, `interact *`, `loop *`,
|
|
306
|
+
`lookup *`, `discover*` and `requirements *`. The file is created or merged
|
|
307
|
+
(every other key and rule stays as it was), `.gitignore` gets a line for it
|
|
308
|
+
unless Git already ignores it, and an unreadable file is left untouched with the
|
|
309
|
+
rules printed to add by hand. `--permissions` answers yes without asking and
|
|
310
|
+
`--no-permissions` no; a saved answer is reused, and `--force` asks again.
|
|
311
|
+
Without a terminal or a flag, nothing is written.
|
|
312
|
+
|
|
313
|
+
`init` also creates the [user configuration
|
|
314
|
+
directory](#user-repo-and-local-configuration) when it is missing, and lists it
|
|
315
|
+
under "Created or restored".
|
|
316
|
+
|
|
317
|
+
`init --force` runs every question again, ignoring the answers remembered in
|
|
318
|
+
`.ww/init-choices.json`, so agents, skills and hooks can be chosen anew and
|
|
319
|
+
more added. It only adds: skills, instruction references, hooks, `.gitignore`
|
|
320
|
+
lines and settings keys already in place are kept, never removed and never
|
|
321
|
+
written twice, and the new answers replace the remembered ones. Values the
|
|
322
|
+
settings files already hold, such as `enabled` or `task_format`, are
|
|
323
|
+
configuration rather than remembered answers, so they are not asked again.
|
|
324
|
+
`--force` reopens a question only when it can ask it: with `--no-input`, or
|
|
325
|
+
without a terminal, every remembered answer stands, a remembered "no"
|
|
326
|
+
included, and init only adds what those answers leave missing. A flag such as
|
|
327
|
+
`--update-gitignore` still decides without asking.
|
|
328
|
+
|
|
329
|
+
`enabled` is written to `ww.json` only from the repo level's
|
|
330
|
+
own answer: when your user or local settings file already sets it, init
|
|
331
|
+
neither asks nor commits that choice for the team.
|
|
332
|
+
|
|
333
|
+
Unless it was shown before, the summary ends with what to allow so your agents
|
|
334
|
+
run ww without asking for confirmation. For each agent set up in the project
|
|
335
|
+
whose permission format ww knows, it names the file and the exact entries,
|
|
336
|
+
covering the configured `executable`, `./ww` and the `ww` shortcut. For Claude
|
|
337
|
+
Code:
|
|
338
|
+
|
|
339
|
+
```text
|
|
340
|
+
claudecode: merge these entries into .claude/settings.json
|
|
341
|
+
|
|
342
|
+
{
|
|
343
|
+
"permissions": {
|
|
344
|
+
"allow": [
|
|
345
|
+
"Bash(ww-agentic-workflows *)",
|
|
346
|
+
"Bash(./ww *)",
|
|
347
|
+
"Bash(ww *)"
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Other agents get the command prefixes to allow in their own permission
|
|
354
|
+
settings. The notice also says what you trust by doing so: your
|
|
355
|
+
`ww.yaml` with its user and local levels. `--force` shows it
|
|
356
|
+
again.
|
|
357
|
+
|
|
358
|
+
Every `init` ends with the next step: run the `ww-setup` skill, which sets ww
|
|
359
|
+
up for you, your team and this project (in Claude Code, `/ww-setup`).
|
|
360
|
+
`--json` output lists it under `next_steps`.
|
|
361
|
+
|
|
362
|
+
### Configuration file names
|
|
363
|
+
|
|
364
|
+
ww reads its configuration from two files at the project root, both named
|
|
365
|
+
after the tool: `ww.yaml` for the workflows and
|
|
366
|
+
`ww.json` for the project settings.
|
|
367
|
+
|
|
368
|
+
## Discover how to start a task
|
|
369
|
+
|
|
370
|
+
`discover` is the entry point for agents. The embeddable agent instructions stay
|
|
371
|
+
short and send agents here, so every choice comes from the project's current
|
|
372
|
+
configuration:
|
|
373
|
+
|
|
374
|
+
```console
|
|
375
|
+
./ww discover
|
|
376
|
+
./ww discover --json
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
The Markdown is deliberately short. It lists the project's workflows with
|
|
380
|
+
their descriptions and default modes, each labeled with the configuration
|
|
381
|
+
level and source it came from and sorted local, then project, then global
|
|
382
|
+
(stable within a level), under this instruction: choose a workflow matching
|
|
383
|
+
the request; when several fit, prefer local over project over global; honor an
|
|
384
|
+
explicitly requested workflow; if the choice is still unclear, ask the
|
|
385
|
+
operator. The preference is guidance, not an automatic selector:
|
|
386
|
+
|
|
387
|
+
```markdown
|
|
388
|
+
- review — [local: ww.local.yaml] Review incoming PR feedback.
|
|
389
|
+
- develop — [project: ww.yaml] Implement and verify a change.
|
|
390
|
+
- generic-fix — [global: ~/.config/ww/ww.yaml] General bug-fix workflow.
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
After the workflows come the changes no workflow covers (the `catchall`,
|
|
394
|
+
started through `lookup`), the configured projects when there are any, the
|
|
395
|
+
modes (an automatic mode with where it is always on), and a multiline start
|
|
396
|
+
synopsis in which square brackets denote optional arguments. The task ID is
|
|
397
|
+
`<task-id>` where the project requires one and `[<task-id>]` otherwise, with
|
|
398
|
+
the external-ticket guidance; `--project` and `--branch-strategy` appear only
|
|
399
|
+
when projects or branch strategies are configured, with their values. A few
|
|
400
|
+
lines explain what is not obvious: explicit modes replace the defaults,
|
|
401
|
+
automatic modes apply themselves, `auto` honours per-step worker requests while
|
|
402
|
+
`single` records them, and `--model` and `--reasoning` describe your own
|
|
403
|
+
session. It ends with the resume and status commands and one optional plan
|
|
404
|
+
preview. ww's own workflows are not listed there: it points at `ww workflows`,
|
|
405
|
+
the catalog of every workflow. Every unfinished task is listed under
|
|
406
|
+
"Unfinished tasks", newest first, in the `session-start` hook's format with any
|
|
407
|
+
interruption notice, and `unfinished_tasks` in the JSON carries each one's
|
|
408
|
+
`task_id`, `workflow`, `agent`, `step`, `item_status`, `workspace`,
|
|
409
|
+
`updated_at`, `resume` command and whether it is `interrupted`. The Rules
|
|
410
|
+
suggestion is not part of the Markdown; `rules_notice` in the JSON and the
|
|
411
|
+
first page of `start` carry it, see
|
|
412
|
+
[How a rule becomes a check](#how-a-rule-becomes-a-check). The JSON keeps every
|
|
413
|
+
field, including the built-in catalogs, the roles and the guidance texts, which
|
|
414
|
+
are the same concise texts the Markdown shows; `explicit_task_id` states
|
|
415
|
+
whether the project requires a task ID. `discover` is read-only and leaves no audit record.
|
|
416
|
+
|
|
417
|
+
In JSON, each workflow entry also carries `source` and `source_level`, naming
|
|
418
|
+
the winning YAML definition and whether it came from the global user config,
|
|
419
|
+
project config, or local config. Imported files keep the level of the config
|
|
420
|
+
that imported them. `null` for both fields means a contribution without a
|
|
421
|
+
configured definition, such as a built-in or the catch-all that no
|
|
422
|
+
configuration file defines; one that a file does define reports that file.
|
|
423
|
+
|
|
424
|
+
A task whose state ww cannot read, such as one written by a build with another
|
|
425
|
+
state schema, does not break `discover`. It is listed
|
|
426
|
+
under "Unreadable tasks" with the error, and `unreadable_tasks` in the JSON
|
|
427
|
+
carries each `task_id` and `reason`. Other tasks and new work are unaffected;
|
|
428
|
+
commands addressing that task keep failing with the same error, and whether to
|
|
429
|
+
repair, reset, or delete its directory is the operator's decision.
|
|
430
|
+
|
|
431
|
+
```markdown
|
|
432
|
+
## Unreadable tasks
|
|
433
|
+
|
|
434
|
+
- `TASK-20` — invalid task state .ww/tasks/TASK-20/state.json: unsupported plan snapshot schema: 3
|
|
435
|
+
|
|
436
|
+
Other tasks and new work are unaffected. Commands addressing these tasks fail with the error shown; ask the operator, whose choice it is to repair, reset, or delete each task directory.
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
Set `"runtime": "auto"` in `ww.json` to make `auto` the runtime
|
|
440
|
+
`start` uses when `--runtime` is omitted; `discover` then marks it as the
|
|
441
|
+
default. The setting lives in the JSON file because whether delegation is
|
|
442
|
+
available depends on the environment ww runs in, not on the workflows. A
|
|
443
|
+
workflow that only makes sense one way, such as a manual-testing workflow
|
|
444
|
+
whose every step is a conversation with the operator, may declare
|
|
445
|
+
`runtime: single` itself; that outranks the project default, and the flag on
|
|
446
|
+
the command line still wins over both.
|
|
447
|
+
|
|
448
|
+
Set `"enabled": false` in `ww.json` to switch ww off for a
|
|
449
|
+
project. `discover` then says only that ww is disabled and that the agent must
|
|
450
|
+
not use it, and `start` refuses to create a task.
|
|
451
|
+
|
|
452
|
+
Set `"enabled": "on_request"` to keep ww available but out of the way: an
|
|
453
|
+
agent uses it only when the user explicitly asks for it (says to use ww, names
|
|
454
|
+
a ww task, or invokes the `ww` skill), and otherwise works without ww and
|
|
455
|
+
without asking. `discover` opens with that rule, then lists the workflows so
|
|
456
|
+
an explicit request can proceed, and its JSON carries `"enabled":
|
|
457
|
+
"on_request"`. `start` and `lookup` work as usual, `lookup` reminding the agent
|
|
458
|
+
to go on only for an explicit request, and the `session-start` hook says that
|
|
459
|
+
ww is used here on request only. The static agent instructions and the `ww`
|
|
460
|
+
skill defer to `discover` for this choice. `init` asks which of the three
|
|
461
|
+
values to write, explaining each; without a terminal it writes `true`.
|
|
462
|
+
|
|
463
|
+
```json
|
|
464
|
+
{"enabled": false, "extensions": {}}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Onboarding state
|
|
468
|
+
|
|
469
|
+
ww keeps a little state about how far it is set up, each part where it
|
|
470
|
+
belongs:
|
|
471
|
+
|
|
472
|
+
| Key | Where | Means |
|
|
473
|
+
| --- | --- | --- |
|
|
474
|
+
| `explain` | `state.json` in the user configuration directory | whether the operator wants the agent to narrate what ww does while it learns; optional, absent until they say so |
|
|
475
|
+
| `setup.done` | `.ww/metadata.json`, as `ww.setup.done` | whether ww was set up in this project |
|
|
476
|
+
| `learned.project` | `.ww/metadata.json`, under `ww.learned` | when ww last learned about the project |
|
|
477
|
+
|
|
478
|
+
The project keys live in ww's own `ww.` namespace of project metadata, which
|
|
479
|
+
no workflow can save into, so they never collide with a workflow's values.
|
|
480
|
+
|
|
481
|
+
```console
|
|
482
|
+
./ww onboarding
|
|
483
|
+
./ww onboarding --json
|
|
484
|
+
./ww onboarding --set explain=true --set learned.project=now
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
`--set` is repeatable and takes a known key: `explain=true|false`,
|
|
488
|
+
`setup.done=true|false`, `learned.project=now` or an ISO
|
|
489
|
+
timestamp. An unknown key or a malformed value is an error, and nothing is
|
|
490
|
+
written unless every assignment is valid. Setting records the operator's stated
|
|
491
|
+
preference, so it asks for no confirmation; it appears in the audit log, while
|
|
492
|
+
showing does not.
|
|
493
|
+
|
|
494
|
+
While `setup.done` is not recorded, `discover` adds a short "Onboarding"
|
|
495
|
+
notice: ww has not been set up in this project, setup is optional and never
|
|
496
|
+
blocks ordinary work, and the agent mentions the `ww-setup` skill only when the
|
|
497
|
+
operator asks to set ww up or what ww can do. It asks no question and starts
|
|
498
|
+
nothing. Its JSON carries `onboarding` with `setup_done`, `explain` and the
|
|
499
|
+
`guidance` lines. Under `"enabled": "on_request"` the notice only informs.
|
|
500
|
+
|
|
501
|
+
## Apply a proposed setup
|
|
502
|
+
|
|
503
|
+
The setup skills propose configuration from what ww learned: workflows, modes,
|
|
504
|
+
handlers, hooks, rules, and settings. They never edit ww's configuration files
|
|
505
|
+
themselves; they write the proposal to a scratch file and hand it to ww:
|
|
506
|
+
|
|
507
|
+
```console
|
|
508
|
+
./ww setup apply proposal.yaml --for me --dry-run
|
|
509
|
+
./ww setup apply proposal.yaml --for me --yes
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
The file is a [setup fragment](specification.md#setup-fragments). `--for me`
|
|
513
|
+
places it in your local files, kept out of version control
|
|
514
|
+
(`ww-setup.local.yaml`, imported by `ww.local.yaml`, and
|
|
515
|
+
`ww.local.json`), so you can try it first; `--for team`
|
|
516
|
+
places it in the shared files (`ww-setup.yaml`, imported by
|
|
517
|
+
`ww.yaml`, and `ww.json`), committed with
|
|
518
|
+
the repository. Running it again refines the same setup file: definitions of
|
|
519
|
+
the same name are replaced, the rest kept, and a hook identical to one already
|
|
520
|
+
in its phase is skipped (the summary says so), so applying a fragment twice
|
|
521
|
+
adds nothing. The file is written as readable YAML, as a person would write
|
|
522
|
+
it: block style, a bare `- run-tests:` for a handler shorthand, long text
|
|
523
|
+
folded with `>-`, short lists such as `argv` inline, and a blank line between
|
|
524
|
+
sections and between workflows, so it can be reviewed and edited by hand.
|
|
525
|
+
|
|
526
|
+
ww first validates the fragment in memory: it loads the configuration as any
|
|
527
|
+
command would, reading the planned contents in place of the files they
|
|
528
|
+
change, so validating never touches the project and it only ever shows a
|
|
529
|
+
change that works. It then prints what it will write, file by file:
|
|
530
|
+
|
|
531
|
+
```text
|
|
532
|
+
`ww setup apply --for team` writes for the team: files shared through the repository:
|
|
533
|
+
- ww-setup.yaml (new): adds workflow `review`, mode `gently`
|
|
534
|
+
- ww.yaml: adds ww-setup.yaml to imports
|
|
535
|
+
- ww.json: sets runtime
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
and asks `Apply it? [y/N]` at a terminal. Without one, as in an agent's shell,
|
|
539
|
+
it needs `--yes`, given once the operator agreed; `--dry-run` prints the same
|
|
540
|
+
and writes nothing, and `--json` reports the files and changes for a program.
|
|
541
|
+
A setting that already holds a different value refuses the whole apply,
|
|
542
|
+
listing each conflict: ww never overwrites one. The files are written once,
|
|
543
|
+
after confirmation; a file that is a symbolic link is written through to its
|
|
544
|
+
target and keeps its permissions. When a write fails, ww puts back every file
|
|
545
|
+
it already wrote, removes its temporary file, and reports the error. Nothing
|
|
546
|
+
is committed.
|
|
547
|
+
|
|
548
|
+
`--dry-run --inspect <workflow> --agent <agent>` also compiles that workflow as
|
|
549
|
+
the change would leave it and prints its execution plan, so a draft can be
|
|
550
|
+
checked, in memory, before anything is asked or written: that automation is
|
|
551
|
+
ww-owned, that item scopes are valid, and that the operator meets only the
|
|
552
|
+
conversations intended.
|
|
553
|
+
|
|
554
|
+
### Change a workflow that is already defined
|
|
555
|
+
|
|
556
|
+
`setup apply` adds definitions to ww's own setup files. To change a workflow
|
|
557
|
+
the configuration already defines, in its root `ww.yaml`, in a file it
|
|
558
|
+
imports, or at another level, write the complete changed workflow alone in a
|
|
559
|
+
fragment (`workflows` holding that one entry, named like the workflow) and
|
|
560
|
+
update it where it is written:
|
|
561
|
+
|
|
562
|
+
```console
|
|
563
|
+
./ww setup update review proposal.yaml --dry-run --inspect review --agent codex
|
|
564
|
+
./ww setup update review proposal.yaml --level project --yes
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
The definition that wins in the composed configuration is the one edited, and
|
|
568
|
+
the preview names its file and level. `--level local|project|global` selects
|
|
569
|
+
that level's definition instead, and ww refuses when a higher-precedence
|
|
570
|
+
definition hides it, because an edit there would report success and change
|
|
571
|
+
nothing; it tells which definition is in force. Only that list item's lines
|
|
572
|
+
are replaced, in ww's YAML style: the rest of the file, other definitions and
|
|
573
|
+
comments included, stays byte for byte. Comments inside the replaced entry
|
|
574
|
+
are lost, and the preview says so. ww checks that the file reloads as the old
|
|
575
|
+
data with exactly that entry swapped, validates the whole configuration in
|
|
576
|
+
memory, and checks that the workflow now comes from the file it wrote. The
|
|
577
|
+
preview is a diff; `--yes`, `--dry-run` and `--json` work as for `apply`. A
|
|
578
|
+
workflow only ww ships, or one defined nowhere, is refused: define or
|
|
579
|
+
override it with `setup apply`. Nothing is committed.
|
|
580
|
+
|
|
581
|
+
## Inspect the project
|
|
582
|
+
|
|
583
|
+
`ww inspect` prints a read-only profile of the checkout: facts about how the
|
|
584
|
+
work is organised, each with the command or file it came from, or "not
|
|
585
|
+
found". It reads the working tree and the local Git history and nothing
|
|
586
|
+
else: no fetch, no network, nothing outside the checkout, and it writes no
|
|
587
|
+
file. It needs no `init`, so the setup workflows can run it on a fresh
|
|
588
|
+
project. `--json` prints the same profile as data, and `--commits N` reads
|
|
589
|
+
the last `N` commits instead of 300.
|
|
590
|
+
|
|
591
|
+
```console
|
|
592
|
+
$ ./ww inspect
|
|
593
|
+
# Profile of shop
|
|
594
|
+
|
|
595
|
+
Read-only facts about this checkout; each names where it came from.
|
|
596
|
+
|
|
597
|
+
## Repository
|
|
598
|
+
|
|
599
|
+
- Default branch: main (git symbolic-ref origin/HEAD)
|
|
600
|
+
- Integration branch: dev (merges on refs/remotes/origin/dev, git log --first-parent --merges)
|
|
601
|
+
- Branch patterns: feature/ 41, hotfix/ 6, release/ 3 (git branch -r, merge subjects)
|
|
602
|
+
- Merge commits: 22% (66 of 300 commits, git log -n 300)
|
|
603
|
+
...
|
|
604
|
+
|
|
605
|
+
## Fixes
|
|
606
|
+
|
|
607
|
+
- Fix share: 18% (42 of 234 non-merge commits, subject starts with fix, fixes, fixed, hotfix or revert, or names a regression, git log -n 300)
|
|
608
|
+
- Paths fixes touch: src/cart/totals.ts 9, src/cart/tax.ts 5, ... (paths the fix commits touch, git log -n 300 --numstat)
|
|
609
|
+
...
|
|
610
|
+
|
|
611
|
+
## Layout
|
|
612
|
+
|
|
613
|
+
- Manifests: package.json [12 scripts], apps/web/package.json [6 scripts] (root and two levels down, git ls-files)
|
|
614
|
+
- Verify commands: test: `pnpm run test` from package.json; lint: `pnpm run lint` from package.json (scripts and targets named test/lint/typecheck/check/format/build in the manifests)
|
|
615
|
+
...
|
|
616
|
+
|
|
617
|
+
## Conventions
|
|
618
|
+
|
|
619
|
+
- Ticket prefixes: SHOP 188, OPS 12 (git log -n 300; git branch -r, merge subjects)
|
|
620
|
+
- task_format candidate: `SHOP-{{digit}}` (the most frequent tracker key prefix)
|
|
621
|
+
- commit_format candidate: `{{ww.task.id}}: {{commit_message}}` (task ID first when half the subjects start with a key)
|
|
622
|
+
...
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
| Section | Facts |
|
|
626
|
+
| --- | --- |
|
|
627
|
+
| Repository | Default and integration branch, branch lanes (`feature/`, `hotfix/`, `release/`, …) with counts, remotes, shallow clone, share of merge commits and the merge style, pull request signals (template, `CODEOWNERS`, "Merge pull request" subjects), tags and the median days between the last ten. |
|
|
628
|
+
| Activity | Commits read, contributors and those active in 90 days, commits per week over the weeks the history spans (one to twelve, so a young history is not read as sparse), median files and lines per commit, median branch lifetime. Team shape: solo (one active contributor), small (2–5) or team (6+); cadence: daily (a median of five commits a week or more), weekly (one or more) or sparse. |
|
|
629
|
+
| Fixes | Share of non-merge commits whose subject starts with fix, fixes, fixed, hotfix or revert (after an optional tracker key, so `fix:` and `fix(scope):` count) or names a regression — "Add the fix loop" is not a fix; the five paths they touch most; the five most recent such subjects. |
|
|
630
|
+
| Hot paths | The ten most-changed files. |
|
|
631
|
+
| Layout | Manifests at the root and two levels down (`package.json`, `pyproject.toml`, `Makefile`, `composer.json`, `go.mod`, `Cargo.toml`, `Gemfile`, `pom.xml`, `build.gradle`), CI files, the verify commands as exact argv where a manifest names them (`package.json` and `composer.json` scripts, `Makefile` targets, `[tool.pytest]`, `[tool.ruff]` and `[tool.mypy]` in `pyproject.toml`), monorepo signals and the candidate `projects` they name. Sibling repositories a README links to are named, never read. |
|
|
632
|
+
| Conventions | Tracker key prefixes in subjects (upper case) and branch names (any case, so `hotfix/task-3` counts as `TASK`), with the `task_format` candidate; the share of subjects led by a key and of conventional-commit subjects, with the `commit_format` candidate; agent instruction files present. |
|
|
633
|
+
|
|
634
|
+
Outside a Git repository only the layout and the agent instruction files are
|
|
635
|
+
reported, under a line saying the Git facts are unavailable. An empty
|
|
636
|
+
repository, a shallow clone or one without remotes is reported as it is; a Git
|
|
637
|
+
call that fails or takes too long leaves only its own fact not found.
|
|
638
|
+
|
|
639
|
+
## Setting ww up: learning and suggestions
|
|
640
|
+
|
|
641
|
+
ww can learn how the project works, and design a setup for the project with the
|
|
642
|
+
operator from that; it learns only the project, never who uses it. The
|
|
643
|
+
`ww-setup` skill guides the operator through it. `discover` mentions the skill
|
|
644
|
+
only when the operator asks to set ww up or what ww can do here, while
|
|
645
|
+
`setup.done` is not recorded (see [Onboarding state](#onboarding-state)). The
|
|
646
|
+
work itself is done by ww's own [built-in workflows](specification.md#built-in-workflows),
|
|
647
|
+
shipped as YAML and started like any workflow; each skill is a thin starter
|
|
648
|
+
for one of them.
|
|
649
|
+
|
|
650
|
+
| Skill | Workflow | Does |
|
|
651
|
+
| --- | --- | --- |
|
|
652
|
+
| `ww-setup` | — | The guide. Asks once, in its opening message, which path to take: Express (learn-project, then suggest told to derive defaults) or Guided (learn-project, then suggest, which asks a few process questions), and records `setup.done` at the end, also when everything is declined. Narration (`explain`) is never asked; it is recorded only when the operator says they want it. Once set up, it offers the ones below instead. |
|
|
653
|
+
| `ww-learn-project` | `ww-learn-project` | Learns the repository: what it is for, and how its work is organised. It reads an existing `project.md` first, so a rerun refreshes it. It starts from [`ww inspect`](#inspect-the-project)'s profile and reads only what the profile cannot see, such as what `AGENTS.md` allows and what the pull request template demands. First the setup facts, each with its evidence or "not found": the default and integration branches and the branch patterns in use, merge or rebase, required pull requests, the test, lint, type check, format and build commands as exact argument lists, how and where those commands run (on the host or through a container exec, virtual environment or task runner, given as an exact argument list, and whether each worktree has its own environment), the tracker's key format as a `task_format` candidate, the commit convention as a `commit_format` candidate, CI gates and releases, the workflows and conventions already in use, and what agents may already do. Then agent tooling, infrastructure and stack, other conventions, and recurring pitfalls, starting from the profile's fix commits and adding review comments where `gh`, `glab` or a tracker is signed in, each as a candidate rule with its evidence and, where a command could verify it, a check. `project.md` keeps the trimmed profile as its "Profile" section, above "Setup facts". Changes no project file but `project.md`. |
|
|
654
|
+
| `ww-suggest` | `ww-suggest` | Gathers the setup facts from `project.md`, running `ww inspect` when it has no profile, and reads the [specification](specification.md), this guide and the [examples](examples.md) for what a setup can be made of. In the guided path it asks a few process questions: what is painful, what outcome would help, where the operator wants to be involved and what may run automatically, skipping what the project already answers. Then it designs a minimal setup with the operator in one set of questions, proposes it in full, with the step features each kind of work calls for (an `items` step for cases, interactive steps and the operator page for what the operator performs, a document or item fields for a template such as a test case, a loop for work repeated until a condition holds), shows it with `setup apply --dry-run`'s list of changes, walks through a realistic task, and places it on confirmation; see below. It follows one method per proposed workflow: trigger, result and operator involvement; the smallest structure; concise YAML with a walkthrough and a failure path; validation with the compiled plan inspected; then apply. Commands come from repository evidence, never guessed. |
|
|
655
|
+
| `ww-refresh` | `ww-learn-project` | Runs the project learning again; see below. |
|
|
656
|
+
| `ww-wizard` | — | Shapes the setup with the operator: asks which of four branches (create a workflow, change an existing workflow, create or improve rules, choose an approach) unless the request says, adapts the depth of its questions, reads the `ww docs` sections, challenges needless complexity with a simpler alternative, drafts, validates with `--dry-run --inspect`, and places the change with `setup apply` or, for an existing workflow, `setup update` at the level `discover` reports. Rule work goes to the rules skills. |
|
|
657
|
+
| `ww-solve` | `ww-solve` | Listens to a problem, proposes the smallest change that addresses it, using the step features that fit the kind of work, and applies it for the operator or the team on confirmation. |
|
|
658
|
+
| `ww-rules-from-artifacts` | `ww-rules-from-artifacts` | Reads the artifacts of chosen steps across recent tasks and proposes rules from the lessons that recur, added with `rules add` on confirmation. |
|
|
659
|
+
| `ww-scriptize` | `ww-scriptize-rules` | Turns every rule with no check yet into checks for the whole project: collects the `unscriptized` rules and groups them into the fewest checks, agrees them with the operator in one conversation, builds and proves them (a deliberate violation, then a sample of real files, with real violations reported and a baseline offered), previews each `rules convert` and `rules decline` with `--dry-run` in a second conversation, and records the approved ones in a step of its own after it. It automatically creates a branch from `extensions.ww/git.base_branches.default`, which must be configured, and follows ww/git worktree settings, so its tool installs and configuration land on a branch of their own. The store it records into, `ww-rule-automation.json`, is in the main checkout: commit it there with, or right after, merging the run's branch; until then `is-git-clean` refuses the next task. |
|
|
660
|
+
| `ww-automate` | `ww-automate` | Looks at a step's instruction and past results for mechanical work a script could do, and proposes the script and a hook (or, for a workflow the configuration defines, the workflow with the step turned into a command step, placed with `setup update`); applies on confirmation. |
|
|
661
|
+
|
|
662
|
+
ww never interviews the operator about who they are, their role, their team
|
|
663
|
+
or their company. What a setup needs of the operator is a few questions about
|
|
664
|
+
the process, asked in `ww-suggest`: they are interactive steps, which open with
|
|
665
|
+
the questions in one numbered message, then converse until the operator's
|
|
666
|
+
intent to finish is clear, asking naturally if it is ambiguous. A pick between
|
|
667
|
+
a few answers goes through the host's native question tool when available.
|
|
668
|
+
The answers are ordinary conversation: they shape the proposal (modes, operator
|
|
669
|
+
stops, review, automation) and are not written to a file of their own. Every
|
|
670
|
+
question can be skipped, and setup never blocks ordinary work. `project.md` and
|
|
671
|
+
the setup are shown before they are written, and the shared file stays
|
|
672
|
+
uncommitted until the operator commits it. These workflows declare `runtime:
|
|
673
|
+
single` and give every step to the session that talks to the operator (`role:
|
|
674
|
+
manager`), so they work in agents without subagents. When `explain` is `true`,
|
|
675
|
+
the skills start them with the built-in `ww-narrate` mode, whose steps tell
|
|
676
|
+
the operator what each one does and why.
|
|
677
|
+
|
|
678
|
+
**Express or Guided.** The opening question offers two paths. Guided runs
|
|
679
|
+
`ww-learn-project`, then `ww-suggest`, which asks the process questions; it
|
|
680
|
+
takes about five replies: the opening question, the project review, the
|
|
681
|
+
process answers, the design and "apply". Express runs `ww-learn-project`, then
|
|
682
|
+
`ww-suggest` started with the requirement "Express setup": it asks no process
|
|
683
|
+
question, states the defaults the project supports, and asks only a
|
|
684
|
+
consequential choice the project leaves open. Both keep project learning: an
|
|
685
|
+
existing `project.md` is read first and refreshed, never silently replaced by a
|
|
686
|
+
generic template.
|
|
687
|
+
|
|
688
|
+
**What `ww-suggest` proposes.** Its `design` step asks, in one message with
|
|
689
|
+
a default for each answer taken from the profile, what the setup turns on,
|
|
690
|
+
and states as decided what the profile already answers: the integration
|
|
691
|
+
branch and the lanes, from the branch patterns present (no `hotfix` lane
|
|
692
|
+
without hotfix-like branches, unless the operator asks), the commands that
|
|
693
|
+
verify a change, their order and how they run, whether agents commit (following the
|
|
694
|
+
project's convention) and push (never), worktrees (on by default when several
|
|
695
|
+
contributors are active and the operator works on parallel tasks), the task ID
|
|
696
|
+
format, who reviews (for a solo project an agent self-review step rather than
|
|
697
|
+
an interactive review; for a team the operator keeps the review), the step
|
|
698
|
+
features a workflow whose work is not a plain code change uses, which of
|
|
699
|
+
the process answers become modes or operator stops, `projects` when the
|
|
700
|
+
layout found candidates (it asks for the sibling repositories' paths and never
|
|
701
|
+
scans them), a rule for a fix-prone path only when the fixes show a repeated
|
|
702
|
+
cause, and whether the setup is for the operator alone or the team. The proposal then
|
|
703
|
+
follows the project, within what this guide and the [specification](specification.md)
|
|
704
|
+
describe: `ww/git` settings for the branching and commit format, and one workflow
|
|
705
|
+
per lane, with `inherit` where lanes differ only in their base branch. The
|
|
706
|
+
verify commands become visible command steps of the workflow by default, as
|
|
707
|
+
[Designing a workflow](#designing-a-workflow) says; a check on every completion
|
|
708
|
+
of a step is proposed only where the project treats it as an invariant. Modes
|
|
709
|
+
are for preferences, and rules only for conventions no command can check. The
|
|
710
|
+
step features a workflow needs follow its kind of work and are added only for a
|
|
711
|
+
concrete reason: a manual-testing workflow, for example, collects the test cases
|
|
712
|
+
as an `items` step, puts each case before the operator on the operator page
|
|
713
|
+
(`interactive: page` with `pass` and `fail` choices), and keeps the test case
|
|
714
|
+
template as a document the steps save, with per-case values as item fields.
|
|
715
|
+
Every command the proposal carries, in a handler, a step, a hook, a rule's
|
|
716
|
+
check or a script, is written for the directory ww runs it from, the task's
|
|
717
|
+
worktree when worktrees are on, through the wrapper `project.md` records for the
|
|
718
|
+
project's commands, and never names the main checkout. `ww-solve`,
|
|
719
|
+
`ww-automate`, `ww-rules-from-artifacts` and the `ww-rule` skill follow the
|
|
720
|
+
same convention. A small project gets one lane and a handler or two. [Example
|
|
721
|
+
21](examples.md#21-what-ww-suggest-proposes-for-a-node-project-with-devmain-and-a-jira-like-tracker)
|
|
722
|
+
shows one realistic shape. It is presented section by section, changed as the
|
|
723
|
+
operator asks for up to three rounds, and placed only on "apply".
|
|
724
|
+
|
|
725
|
+
Every piece of the proposal carries its evidence in one clause, so the
|
|
726
|
+
operator can see why it is there: "`hotfix` lane: 14 `hotfix/*` branches
|
|
727
|
+
merged this year", "`run-tests` runs `npm test`: `package.json` scripts".
|
|
728
|
+
Before asking to apply, `ww-suggest` walks through the main lane in ten lines
|
|
729
|
+
or fewer, its steps in order with what an agent does at each; after applying,
|
|
730
|
+
it shows the first page of `ww plan --workflow <main lane>` as what an agent
|
|
731
|
+
gets on the first task. When
|
|
732
|
+
`project.md` is missing, `ww-suggest` says the proposal will be weaker and
|
|
733
|
+
offers to learn first, and a later `ww-setup` run recommends learning the
|
|
734
|
+
project before anything else.
|
|
735
|
+
|
|
736
|
+
What ww learns goes into one file it keeps for its own use, `project.md` in
|
|
737
|
+
`.ww/` at the project root, written by `ww-learn-project` and shared once
|
|
738
|
+
committed. It records evidence and operational facts about the repository;
|
|
739
|
+
preferences about workflows belong in the resulting proposal and configuration,
|
|
740
|
+
and ww keeps no replacement dossier about the operator. It is the built-in
|
|
741
|
+
document `project`, so `{{ww.documents.project}}` names it in any workflow, and
|
|
742
|
+
it resolves against the project root even for a task working in a Git
|
|
743
|
+
worktree. It starts with this remark, which tells any other agent
|
|
744
|
+
to leave it alone:
|
|
745
|
+
|
|
746
|
+
```markdown
|
|
747
|
+
<!-- This file is maintained by ww for ww's own use. Do not use it for anything else. If you are an agent that is not doing ww work, ignore this file. -->
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
`init --update-gitignore` keeps `.ww/` out of Git except `project.md`. ww never
|
|
751
|
+
commits it: the last step of `ww-learn-project` names the file it left for the
|
|
752
|
+
operator to review and commit, and records when ww learned with `ww onboarding
|
|
753
|
+
--set learned.project=now`. `ww-suggest`, `ww-solve`, `ww-rules-from-artifacts`
|
|
754
|
+
and `ww-automate` read `project.md` where it exists and keep their proposals
|
|
755
|
+
within the project's conventions.
|
|
756
|
+
|
|
757
|
+
The proposals never touch ww's configuration files through the agent. A
|
|
758
|
+
workflow writes its fragment to the task's `setup_proposal` document
|
|
759
|
+
(`.ww/tasks/<task-id>/setup-proposal.yaml`) and places it with
|
|
760
|
+
[`ww setup apply`](#apply-a-proposed-setup): `--for me` into the local files,
|
|
761
|
+
for trying a setup alone, `--for team` into the shared ones. Running
|
|
762
|
+
`ww-suggest` again later and choosing to share offers the same setup to the
|
|
763
|
+
team. A fragment applied with `setup apply` only adds definitions; to change a
|
|
764
|
+
workflow the configuration already defines, wherever it is written, the
|
|
765
|
+
workflow proposes the complete changed workflow and places it with
|
|
766
|
+
`setup update` (see [Change a workflow that is already
|
|
767
|
+
defined](#change-a-workflow-that-is-already-defined)). Rules proposed from past artifacts are written with
|
|
768
|
+
`rules add`, like the `ww-rule` skill's.
|
|
769
|
+
|
|
770
|
+
**Refreshing.** Every learning step reads the existing file first, asks only
|
|
771
|
+
what is missing or may have changed, keeps what still holds, updates what
|
|
772
|
+
changed, and marks what no longer holds as superseded with the date. So
|
|
773
|
+
refreshing is running `ww-learn-project` again, which is what the
|
|
774
|
+
`ww-refresh` skill does after showing when ww last learned the project.
|
|
775
|
+
|
|
776
|
+
**Git hooks.** The learning and setup workflows create no branch or worktree
|
|
777
|
+
and commit nothing themselves. `ww-scriptize-rules` has its own Git hooks: it
|
|
778
|
+
requires `extensions.ww/git.base_branches.default`, always creates a branch
|
|
779
|
+
even when `separate_branch` is false, follows the worktree settings, commits
|
|
780
|
+
its changes and returns to the base when worktrees are off.
|
|
781
|
+
A project's global hooks filtered with `workflows:` to its
|
|
782
|
+
own workflows, as the `ww/git` start hooks usually are, do not reach them; a
|
|
783
|
+
global hook without a `workflows` filter does, so list your workflows in it
|
|
784
|
+
if it creates branches or worktrees.
|
|
785
|
+
|
|
786
|
+
**Switching them off.** Each is a built-in workflow, switched off by name in
|
|
787
|
+
`ww.json`; the documents and the mode stay while any of them
|
|
788
|
+
is enabled, and a recommendation of a switched-off one (`ww-learn-project` recommends
|
|
789
|
+
`ww-suggest`) is dropped:
|
|
790
|
+
|
|
791
|
+
```json
|
|
792
|
+
{"workflows": {"ww-solve": {"enabled": false}, "ww-automate": {"enabled": false}}}
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
A workflow of the same name in any `ww.yaml` level replaces the shipped one.
|
|
796
|
+
The `ww.json` workflow settings only switch built-ins on or off; scriptizing
|
|
797
|
+
needs no lane setting.
|
|
798
|
+
|
|
799
|
+
## The catch-all workflow
|
|
800
|
+
|
|
801
|
+
Unless `enabled` is `"on_request"`, every change to files goes through ww,
|
|
802
|
+
including the small ones that fit no workflow: renaming a helper, fixing a typo, adjusting a setting. For those, ww
|
|
803
|
+
provides `catchall` to every project. It has one step, `work`, whose page tells
|
|
804
|
+
the agent that the workflow only records the request: it carries the work out
|
|
805
|
+
exactly as it would on a plain prompt, with the same judgement, tools,
|
|
806
|
+
subagents, skills, and project conventions, and completes the step with what it
|
|
807
|
+
changed.
|
|
808
|
+
|
|
809
|
+
`discover` lists it apart from the configured workflows, under "Changes no
|
|
810
|
+
workflow covers", together with the rules for using it, which the agent
|
|
811
|
+
instructions repeat. It is only for a change to files: questions,
|
|
812
|
+
explanations, reviews, investigations, status checks and other read-only work
|
|
813
|
+
never go through ww at all. The agent answers them without the ww skill and
|
|
814
|
+
without a task, and a conversation that begins as a question turns to ww only
|
|
815
|
+
once it reaches a change. It never replaces a matching workflow.
|
|
816
|
+
|
|
817
|
+
The agent does not start it directly. It first runs `lookup` with the task the
|
|
818
|
+
conversation works on, or with what the operator called the task, as they
|
|
819
|
+
wrote it, and without one when there is none:
|
|
820
|
+
|
|
821
|
+
```console
|
|
822
|
+
./ww lookup 12345 --agent claudecode
|
|
823
|
+
```
|
|
824
|
+
|
|
825
|
+
`lookup` is read-only. It maps the reference onto the project's task IDs:
|
|
826
|
+
the exact ID in any letter case, the ID `task_format` builds from it, so
|
|
827
|
+
`12345` and `foobar-12345` both mean `FOOBAR-12345` under `FOOBAR-{{digit}}`, and,
|
|
828
|
+
failing both, the existing tasks whose ID ends in it after a separator, so
|
|
829
|
+
with tracker keys `12345` finds `FOOBAR-12345`. Then it answers with one next
|
|
830
|
+
step:
|
|
831
|
+
|
|
832
|
+
| Found | Next step |
|
|
833
|
+
|---|---|
|
|
834
|
+
| One task, with an unfinished run of another workflow | Continue that run: `./ww instruction FOOBAR-12345 --role manager`. The change belongs to it. |
|
|
835
|
+
| One task, otherwise | Start `catchall` on it; the printed `start` command is ready to run. |
|
|
836
|
+
| Several tasks | Ask the operator which one, then continue or start on it. |
|
|
837
|
+
| No task | Ask the operator to confirm creating the ID the reference names, `FOOBAR-99` for `99`. |
|
|
838
|
+
| No reference | Ask the operator whether to create a new task; under `"task_format": "explicit"` they give its ID. |
|
|
839
|
+
|
|
840
|
+
Asking goes through the agent's own choice menu, the same mechanism as an
|
|
841
|
+
interactive step's [choices](#interactive-steps), and every menu also offers
|
|
842
|
+
"Work without ww". Each choice comes with the command to run once it is
|
|
843
|
+
picked, and the page says to run nothing before then, so a task ww has never
|
|
844
|
+
seen is only created when the operator says so. A `catchall` start on a task
|
|
845
|
+
with an unfinished run of another workflow is refused, and the error names
|
|
846
|
+
the `instruction` command that continues it.
|
|
847
|
+
|
|
848
|
+
The workflow declares `runtime: auto` and its step `role: manager`: the
|
|
849
|
+
session that received the prompt does the work, and whether it uses subagents
|
|
850
|
+
along the way is its own choice, as without ww. It is `restartable`, so a new
|
|
851
|
+
request on the same task replaces one that was never finished, and it is not
|
|
852
|
+
interactive: a follow-up that changes more starts another `catchall` run on the
|
|
853
|
+
same task. Every run ends with the usual workflow summary.
|
|
854
|
+
|
|
855
|
+
The catch-all is one of ww's [built-in workflows](specification.md#built-in-workflows),
|
|
856
|
+
shipped as YAML with ww. A project replaces it by defining its own workflow
|
|
857
|
+
named `catchall` in `ww.yaml`, or switches it off in
|
|
858
|
+
`ww.json`:
|
|
859
|
+
|
|
860
|
+
```json
|
|
861
|
+
{"workflows": {"catchall": {"enabled": false}}}
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
## Validate configuration and plan a workflow
|
|
865
|
+
|
|
866
|
+
`ww-agentic-workflows` turns `ww.yaml` into an explicit, inspectable
|
|
867
|
+
execution plan. Skills and slash commands are discovered only from the project's
|
|
868
|
+
shared `.agents/` directory and the selected agent's directory.
|
|
869
|
+
|
|
870
|
+
```console
|
|
871
|
+
ww-agentic-workflows lint
|
|
872
|
+
ww-agentic-workflows plan --workflow task --agent codex
|
|
873
|
+
ww-agentic-workflows plan --workflow task --agent codex --task-id TASK-123 --json
|
|
874
|
+
```
|
|
875
|
+
|
|
876
|
+
The workflow and agent options have `-w` and `-a` short forms. On `start`,
|
|
877
|
+
runtime also accepts `-r`.
|
|
878
|
+
|
|
879
|
+
`lint` validates the complete `ww.yaml` configuration without needing a
|
|
880
|
+
workflow or agent. It and `plan` are read-only: neither creates task state,
|
|
881
|
+
artifacts, commands, or execution log records. Markdown is for people; `--json`
|
|
882
|
+
returns a stable representation for tools. `--agent` is required because
|
|
883
|
+
automatic resolution depends on the agent’s project-local skills and slash
|
|
884
|
+
commands. `--task-id` is optional; when
|
|
885
|
+
omitted, `{{ww.task.id}}` remains visible as an unresolved plan dependency.
|
|
886
|
+
|
|
887
|
+
## Split ww.yaml into several files
|
|
888
|
+
|
|
889
|
+
A large configuration can be split across files. `ww.yaml` stays the
|
|
890
|
+
required root file and lists the others under `imports`, its first key (only
|
|
891
|
+
`extends` may come before it). Paths are relative to the directory of the file
|
|
892
|
+
that lists them; every other relative path in the configuration still resolves
|
|
893
|
+
against the project root:
|
|
894
|
+
|
|
895
|
+
```yaml
|
|
896
|
+
# ww.yaml
|
|
897
|
+
imports:
|
|
898
|
+
- workflows/shared.yaml
|
|
899
|
+
- workflows/local.yaml
|
|
900
|
+
|
|
901
|
+
handlers:
|
|
902
|
+
- name: test
|
|
903
|
+
argv: [python, -m, pytest, -q]
|
|
904
|
+
|
|
905
|
+
workflows:
|
|
906
|
+
- task: The standard development workflow.
|
|
907
|
+
steps:
|
|
908
|
+
- develop: Implement the change.
|
|
909
|
+
- test: ~
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
```yaml
|
|
913
|
+
# workflows/shared.yaml
|
|
914
|
+
handlers:
|
|
915
|
+
- name: test
|
|
916
|
+
argv: [pytest]
|
|
917
|
+
workflows:
|
|
918
|
+
- hotfix: Fix a production bug.
|
|
919
|
+
steps:
|
|
920
|
+
- fix: Fix it.
|
|
921
|
+
```
|
|
922
|
+
|
|
923
|
+
```yaml
|
|
924
|
+
# workflows/local.yaml
|
|
925
|
+
workflows:
|
|
926
|
+
- hotfix: Fix a production bug, verifying it first.
|
|
927
|
+
steps:
|
|
928
|
+
- reproduce: Reproduce it.
|
|
929
|
+
- fix: Fix it.
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
An imported file can define anything `ww.yaml` can, except further
|
|
933
|
+
imports, so every file is listed in one place. Files apply in order, the root
|
|
934
|
+
file last, and a later definition overrides an earlier one of the same name:
|
|
935
|
+
here `ww.yaml`'s `test` handler replaces the shared one, and
|
|
936
|
+
`local.yaml`'s `hotfix` workflow replaces `shared.yaml`'s. Named entries of
|
|
937
|
+
`workflows`, `modes`, `documents`, `handlers`, and `profiles` are replaced one
|
|
938
|
+
by one, other entries from every file are kept, and `hooks` from every file are
|
|
939
|
+
combined, later files' entries running after earlier ones.
|
|
940
|
+
|
|
941
|
+
Overriding is never an error. `lint` reports each override as a notice:
|
|
942
|
+
|
|
943
|
+
```console
|
|
944
|
+
$ ww-agentic-workflows lint
|
|
945
|
+
ww.yaml is valid.
|
|
946
|
+
Configuration files: workflows/shared.yaml, workflows/local.yaml, ww.yaml
|
|
947
|
+
Notice: workflow 'hotfix' from workflows/shared.yaml is overridden by workflows/local.yaml.
|
|
948
|
+
Notice: handler 'test' from workflows/shared.yaml is overridden by ww.yaml.
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
ww composes the files in memory on every command into one document and reads
|
|
952
|
+
it exactly as a single `ww.yaml`, so every other rule applies unchanged
|
|
953
|
+
and there is no cache to refresh. `init` sees keys and workflows defined in
|
|
954
|
+
imported files too, and does not add them to `ww.yaml` again. The
|
|
955
|
+
[specification](specification.md#imports) has the exact rules.
|
|
956
|
+
|
|
957
|
+
## User, repo, and local configuration
|
|
958
|
+
|
|
959
|
+
Both configuration files come in three levels, which ww finds and applies on
|
|
960
|
+
every command, top to bottom:
|
|
961
|
+
|
|
962
|
+
1. user: `ww.yaml` and `ww.json` in
|
|
963
|
+
`~/.config/ww/` (under `$XDG_CONFIG_HOME` when that is
|
|
964
|
+
set), yours alone and shared by every one of your projects;
|
|
965
|
+
2. repo: `ww.yaml` and `ww.json` in the
|
|
966
|
+
project root, checked in;
|
|
967
|
+
3. local: `ww.local.yaml` and
|
|
968
|
+
`ww.local.json` in the project root, for one checkout and
|
|
969
|
+
kept out of version control.
|
|
970
|
+
|
|
971
|
+
The repo `ww.yaml` stays required: a user file alone never
|
|
972
|
+
makes a directory a ww project. `WW_USER_CONFIG_DIR` names another user
|
|
973
|
+
directory; the test suite points it at an empty one so a developer's own
|
|
974
|
+
configuration never leaks into tests. `init` creates the user directory when
|
|
975
|
+
it is missing and lists it under "Created or restored".
|
|
976
|
+
|
|
977
|
+
A lower level extends the levels above it with the same rules as
|
|
978
|
+
[imports](#split-wwyaml-into-several-files), and wins: named
|
|
979
|
+
workflows, modes, documents, handlers, and profiles are replaced one by one,
|
|
980
|
+
hooks are added per phase, and other keys take the lower value. Each level can
|
|
981
|
+
use `imports` of its own, resolved next to the file that lists them.
|
|
982
|
+
|
|
983
|
+
```yaml
|
|
984
|
+
# ~/.config/ww/ww.yaml
|
|
985
|
+
handlers:
|
|
986
|
+
- name: test
|
|
987
|
+
argv: [pytest]
|
|
988
|
+
workflows:
|
|
989
|
+
- review: Review a change.
|
|
990
|
+
steps:
|
|
991
|
+
- review: Review it.
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
```json
|
|
995
|
+
// ww.local.json
|
|
996
|
+
{"task_format": "DEV-{{digit}}"}
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
With the repo file defining its own `test` handler and its
|
|
1000
|
+
`ww.json` a `task_format`, the project gets the user's
|
|
1001
|
+
`review` workflow, the repo's `test` handler, and the local task format. `lint`
|
|
1002
|
+
lists the files it read, user to local, the YAML files first and the JSON
|
|
1003
|
+
ones after, and names the file behind each YAML override; `plan` ends with the
|
|
1004
|
+
same list:
|
|
1005
|
+
|
|
1006
|
+
```console
|
|
1007
|
+
$ ww-agentic-workflows lint
|
|
1008
|
+
ww.yaml is valid.
|
|
1009
|
+
Configuration files: ~/.config/ww/ww.yaml, ww.yaml, ww.json, ww.local.json
|
|
1010
|
+
Notice: handler 'test' from ~/.config/ww/ww.yaml is overridden by ww.yaml.
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
A configured project's own `ww.json` and
|
|
1014
|
+
`ww.local.json` add one more level for tasks working there,
|
|
1015
|
+
limited to the `extensions` section and `task_format`; see
|
|
1016
|
+
[A project's own extension settings](#a-projects-own-extension-settings).
|
|
1017
|
+
|
|
1018
|
+
A level that should not build on the ones above sets `extends: false` in its
|
|
1019
|
+
root file or any of its imports; that level then starts afresh, and `lint`
|
|
1020
|
+
reports each file it leaves out. `extends: true` is allowed and changes
|
|
1021
|
+
nothing.
|
|
1022
|
+
|
|
1023
|
+
```yaml
|
|
1024
|
+
# ww.local.yaml
|
|
1025
|
+
extends: false
|
|
1026
|
+
workflows:
|
|
1027
|
+
- task: My own way of working.
|
|
1028
|
+
steps:
|
|
1029
|
+
- develop: Do it.
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
```console
|
|
1033
|
+
$ ww-agentic-workflows lint
|
|
1034
|
+
ww.yaml is valid.
|
|
1035
|
+
Configuration files: ww.local.yaml, ww.json
|
|
1036
|
+
Notice: ~/.config/ww/ww.yaml is not applied: a lower level sets extends: false.
|
|
1037
|
+
Notice: ww.yaml is not applied: a lower level sets extends: false.
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
The JSON settings are always deep-merged and take no `extends` key: nested
|
|
1041
|
+
objects merge key by key, while strings, numbers, booleans, lists, and `null`
|
|
1042
|
+
from a lower level replace the value above. A user file can, for example,
|
|
1043
|
+
set `{"runtime": "auto"}` for every project while one checkout's
|
|
1044
|
+
`ww.local.json` sets
|
|
1045
|
+
`{"extensions": {"ww/git": {"worktrees": false}}}` without repeating the rest of
|
|
1046
|
+
the repo's `ww/git` settings.
|
|
1047
|
+
|
|
1048
|
+
`init` writes only the repo-level files, and decides what to add from the
|
|
1049
|
+
composed result: it adds no default workflow to the YAML, and no `task_format`
|
|
1050
|
+
to the JSON, when another level already provides them.
|
|
1051
|
+
|
|
1052
|
+
## Configuration
|
|
1053
|
+
|
|
1054
|
+
Every mode is a mapping with a required `name` and an optional description.
|
|
1055
|
+
Workflows, handlers, and steps support that long form plus a shorthand whose
|
|
1056
|
+
first key is the name and whose string or null value is the description.
|
|
1057
|
+
`handlers` is the reusable global catalog. A step is also a handler, with
|
|
1058
|
+
optional workflow hooks.
|
|
1059
|
+
|
|
1060
|
+
```yaml
|
|
1061
|
+
handlers:
|
|
1062
|
+
- update-yaml-specification: Update specification.md.
|
|
1063
|
+
- no-description: ~
|
|
1064
|
+
|
|
1065
|
+
workflows:
|
|
1066
|
+
- task: The standard development workflow.
|
|
1067
|
+
steps:
|
|
1068
|
+
- develop: Implement and test the change.
|
|
1069
|
+
```
|
|
1070
|
+
|
|
1071
|
+
A step can copy a root handler definition with `handler: <name>`, and a step
|
|
1072
|
+
that has no content of its own, `- fetch_requirements: ~`, copies the root
|
|
1073
|
+
handler of the same name without saying so, exactly as a bare hook entry does;
|
|
1074
|
+
settings such as `profile` or `model` on that step still override the copy.
|
|
1075
|
+
This is a hard link at configuration time, not another execution phase: the
|
|
1076
|
+
plan contains one ordinary step under the step's own name. Its description and
|
|
1077
|
+
explicitly declared handler fields override the copied definition. Root handlers may also
|
|
1078
|
+
hold a complete step container, so a loop or nested step sequence can be named
|
|
1079
|
+
once and reused by referencing steps.
|
|
1080
|
+
|
|
1081
|
+
```yaml
|
|
1082
|
+
handlers:
|
|
1083
|
+
- name: shared-check
|
|
1084
|
+
argv: [pytest, -q]
|
|
1085
|
+
|
|
1086
|
+
workflows:
|
|
1087
|
+
- name: task
|
|
1088
|
+
steps:
|
|
1089
|
+
- verify: Run the project verification.
|
|
1090
|
+
handler: shared-check
|
|
1091
|
+
```
|
|
1092
|
+
|
|
1093
|
+
A reusable handler can contain the whole step structure. For example, define a
|
|
1094
|
+
review loop once and use it as a workflow step:
|
|
1095
|
+
|
|
1096
|
+
```yaml
|
|
1097
|
+
handlers:
|
|
1098
|
+
- code-review:
|
|
1099
|
+
loop:
|
|
1100
|
+
- code-review: Perform the code review.
|
|
1101
|
+
break: There are no meaningful review remarks.
|
|
1102
|
+
profile: code-reviewer
|
|
1103
|
+
- fix: Fix the review findings.
|
|
1104
|
+
profile: developer
|
|
1105
|
+
|
|
1106
|
+
workflows:
|
|
1107
|
+
- name: task
|
|
1108
|
+
steps:
|
|
1109
|
+
- code-review: ~
|
|
1110
|
+
handler: code-review
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
The same shorthand works for singular hook actions and entries inside a hook's
|
|
1114
|
+
ordered `handlers` list. If a mapping contains `name`, ww preserves the existing
|
|
1115
|
+
long-form interpretation.
|
|
1116
|
+
|
|
1117
|
+
### Profiles
|
|
1118
|
+
|
|
1119
|
+
Profiles tailor the agent's approach without implying that another agent will
|
|
1120
|
+
be spawned. Define them at the root and apply one to a workflow or one of its
|
|
1121
|
+
steps. A profile is inherited along the same chain as agent, model, and
|
|
1122
|
+
reasoning: from the workflow, through every enclosing step, to the step itself,
|
|
1123
|
+
so a loop wrapper or a parent step sets it for its whole body and any nested
|
|
1124
|
+
step may override it. Hooks, modes, and handlers cannot declare profiles.
|
|
1125
|
+
|
|
1126
|
+
```yaml
|
|
1127
|
+
profiles:
|
|
1128
|
+
developer: >-
|
|
1129
|
+
You are a senior Symfony developer. Prefer small, well-tested changes.
|
|
1130
|
+
code-reviewer: ~
|
|
1131
|
+
|
|
1132
|
+
handlers:
|
|
1133
|
+
- name: git-stage
|
|
1134
|
+
argv: [git, add, .]
|
|
1135
|
+
- name: git-commit
|
|
1136
|
+
variables:
|
|
1137
|
+
- name: commit_message
|
|
1138
|
+
description: A concise commit message.
|
|
1139
|
+
argv: [git, commit, -m, "{{ww.task.id}}: {{commit_message}}"]
|
|
1140
|
+
|
|
1141
|
+
hooks:
|
|
1142
|
+
before_complete:
|
|
1143
|
+
- steps: [develop]
|
|
1144
|
+
handlers:
|
|
1145
|
+
- name: git-stage
|
|
1146
|
+
- name: git-commit
|
|
1147
|
+
|
|
1148
|
+
workflows:
|
|
1149
|
+
- name: task
|
|
1150
|
+
steps:
|
|
1151
|
+
- name: develop
|
|
1152
|
+
description: Implement and verify the requested change.
|
|
1153
|
+
profile: developer
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
### Implicit requirements step
|
|
1157
|
+
|
|
1158
|
+
Every workflow begins with an artifact-producing `init` step supplied by ww.
|
|
1159
|
+
Do not declare it in `steps`: `init` is a reserved step name, including inside
|
|
1160
|
+
nested and per-item steps. Its fixed prompt asks the agent only to preserve the
|
|
1161
|
+
passed requirements, correcting grammar and style without adding analysis,
|
|
1162
|
+
reasoning, or a work plan. It uses low reasoning and does not inherit a workflow
|
|
1163
|
+
profile, keeping the saved requirements independent of later execution choices.
|
|
1164
|
+
|
|
1165
|
+
`init` otherwise has the same lifecycle and persistence behavior as a declared
|
|
1166
|
+
step. The workflow-wide `before_start_workflow` boundary runs once before it and
|
|
1167
|
+
does not accept a step filter:
|
|
1168
|
+
|
|
1169
|
+
```yaml
|
|
1170
|
+
hooks:
|
|
1171
|
+
before_start_workflow:
|
|
1172
|
+
- name: check-clean
|
|
1173
|
+
|
|
1174
|
+
workflows:
|
|
1175
|
+
- name: task
|
|
1176
|
+
steps:
|
|
1177
|
+
- name: develop
|
|
1178
|
+
artifact_from: init
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
Each workflow run gets its own `init`, including handoff targets and child-task
|
|
1182
|
+
workflows. `ww plan` includes it in the exact execution order. `start` requires
|
|
1183
|
+
`--requirements` and stores that normalized requirements text immediately in
|
|
1184
|
+
the built-in artifact before returning the first declared step; it never assigns
|
|
1185
|
+
`init` to a worker or requires a subsequent `next`/`complete` cycle.
|
|
1186
|
+
|
|
1187
|
+
### Commands
|
|
1188
|
+
|
|
1189
|
+
Command handlers canonically put a structured argument vector at their root,
|
|
1190
|
+
for example `argv: [git, status, --porcelain]`. Each argument is interpolated
|
|
1191
|
+
independently and execution never invokes a shell.
|
|
1192
|
+
|
|
1193
|
+
When shell behavior is intentional, declare it explicitly. Shell source is not
|
|
1194
|
+
interpolated; dynamic workflow values must enter through `env` or `args`:
|
|
1195
|
+
|
|
1196
|
+
```yaml
|
|
1197
|
+
shell: printf '%s' "$MESSAGE" > message.txt
|
|
1198
|
+
env:
|
|
1199
|
+
MESSAGE: "{{commit_message}}"
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
`assert` adds a list of output conditions to the root command action, all of
|
|
1203
|
+
which must hold: `assert: [empty]`, or `assert: [{equals: clean}]`. A handler
|
|
1204
|
+
is one action; use a hook's ordered `handlers` list for multiple commands.
|
|
1205
|
+
|
|
1206
|
+
`idempotent: true` declares that running the handler again is harmless. It
|
|
1207
|
+
changes one thing: when ww is interrupted while the handler runs, the next
|
|
1208
|
+
locked `next` replays the interrupted and unrun commands under the same
|
|
1209
|
+
operation identity and carries on, instead of stopping at the recovery
|
|
1210
|
+
boundary for an operator decision. The default is `false`, which keeps the
|
|
1211
|
+
unknown outcome until `next --retry` or a checker settles it; see [Interrupted automatic handlers](#interrupted-automatic-handlers).
|
|
1212
|
+
Declare it on test runs, linters, and checks that only read; leave it off
|
|
1213
|
+
anything that publishes, commits, or sends. `lint` rejects it without `argv`
|
|
1214
|
+
or `shell`, the saved plan carries it, and `plan` shows it under
|
|
1215
|
+
**Recovery**:
|
|
1216
|
+
|
|
1217
|
+
```yaml
|
|
1218
|
+
handlers:
|
|
1219
|
+
- name: tests
|
|
1220
|
+
argv: [python, -m, pytest, -q]
|
|
1221
|
+
idempotent: true
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
### Handler types and ownership
|
|
1225
|
+
|
|
1226
|
+
Set `kind: skill`, `kind: slash_command`, `mcp: <connection>`, `argv`, `shell`,
|
|
1227
|
+
or `kind: prompt` to select a handler kind explicitly; the registry form
|
|
1228
|
+
`action: {type: <action>, ...}` selects any registered action by its
|
|
1229
|
+
identifier. The handler description is the instruction for prompt and MCP
|
|
1230
|
+
work. Use an `assess` step when a decision must control workflow routing.
|
|
1231
|
+
Without an explicit action, resolution is deterministic:
|
|
1232
|
+
|
|
1233
|
+
1. A matching project-local skill is an agent skill handler.
|
|
1234
|
+
2. A matching project-local slash command is an agent slash-command handler.
|
|
1235
|
+
3. Otherwise the name/description becomes an agent prompt.
|
|
1236
|
+
|
|
1237
|
+
### Automated handler groups
|
|
1238
|
+
|
|
1239
|
+
A reusable handler can contain `handlers`, an ordered sequence of actions
|
|
1240
|
+
that ww executes itself. Use `steps` for a sequence that includes agent work
|
|
1241
|
+
or needs separate step lifecycles.
|
|
1242
|
+
|
|
1243
|
+
```yaml
|
|
1244
|
+
handlers:
|
|
1245
|
+
- build: ~
|
|
1246
|
+
shell: npm run build
|
|
1247
|
+
- verify-build: ~
|
|
1248
|
+
on_failure: fix
|
|
1249
|
+
on_failure_instruction: Fix the reported build or output errors.
|
|
1250
|
+
handlers:
|
|
1251
|
+
- build: ~
|
|
1252
|
+
- argv: [test, -d, dist]
|
|
1253
|
+
|
|
1254
|
+
workflows:
|
|
1255
|
+
- name: task
|
|
1256
|
+
steps:
|
|
1257
|
+
- verify-build: ~
|
|
1258
|
+
```
|
|
1259
|
+
|
|
1260
|
+
Members run in order under the enclosing step or hook's lifecycle. They may
|
|
1261
|
+
be inline automatic actions, catalog references (including later declarations),
|
|
1262
|
+
or nested automated groups. Empty groups, reference cycles, agent-owned
|
|
1263
|
+
actions, and actions requiring agent input are rejected. A group cannot also
|
|
1264
|
+
declare a direct action or a step container.
|
|
1265
|
+
|
|
1266
|
+
Groups supply defaults for the working directory, repair worker guidance,
|
|
1267
|
+
failure policy, and failure instruction; members can override them. Each
|
|
1268
|
+
member uses normal ww execution and recovery. If a command with
|
|
1269
|
+
`on_failure: fix` fails, its agent repairs the cause and ww retries that member,
|
|
1270
|
+
preserving successful preceding members.
|
|
1271
|
+
|
|
1272
|
+
Hooks can reference these groups and keep their existing phase and filters.
|
|
1273
|
+
Eligible `before_complete` members with `on_failure: fix` become completion
|
|
1274
|
+
checks. Existing hook `handlers` lists remain compatible, including their
|
|
1275
|
+
support for agent actions; reusable automated groups require every member to
|
|
1276
|
+
be fully automatic.
|
|
1277
|
+
|
|
1278
|
+
### Outcome-based assessments
|
|
1279
|
+
|
|
1280
|
+
Use `assess` when an agent's judgment should route the remainder of a workflow.
|
|
1281
|
+
The agent should choose `positive` or `negative` whenever possible, reserving
|
|
1282
|
+
`mixed` for material uncertainty. After completing the assessment, select the
|
|
1283
|
+
declared route with `ww next <task-id> --outcome <label> --role manager`.
|
|
1284
|
+
|
|
1285
|
+
```yaml
|
|
1286
|
+
- assess:
|
|
1287
|
+
question: Does recent development warrant refactoring?
|
|
1288
|
+
outcomes:
|
|
1289
|
+
positive:
|
|
1290
|
+
handler: refactor-plan
|
|
1291
|
+
negative:
|
|
1292
|
+
steps:
|
|
1293
|
+
- record: No refactoring is needed now.
|
|
1294
|
+
mixed:
|
|
1295
|
+
steps:
|
|
1296
|
+
- investigate: Gather the missing evidence.
|
|
1297
|
+
```
|
|
1298
|
+
|
|
1299
|
+
Every outcome uses one ordinary step shape: a global `handler`, an inline
|
|
1300
|
+
action, `handlers`, or nested `steps`. Outcome work types are mutually
|
|
1301
|
+
exclusive. After the chosen outcome's work, the workflow continues with the
|
|
1302
|
+
step after `assess`. An outcome that should end the run instead is
|
|
1303
|
+
`stop_workflow: true` on its own:
|
|
1304
|
+
|
|
1305
|
+
```yaml
|
|
1306
|
+
- assess:
|
|
1307
|
+
question: Were conflicts resolved in non-trivial code?
|
|
1308
|
+
outcomes:
|
|
1309
|
+
positive:
|
|
1310
|
+
steps:
|
|
1311
|
+
- review: Review the resolutions.
|
|
1312
|
+
negative:
|
|
1313
|
+
stop_workflow: true
|
|
1314
|
+
```
|
|
1315
|
+
|
|
1316
|
+
Choosing it completes the workflow, skipping every later step and hook. An
|
|
1317
|
+
assessment needs at least one outcome with work; one that only stops is the
|
|
1318
|
+
compact form.
|
|
1319
|
+
|
|
1320
|
+
A gate declares only the outcome that has work. `positive`, `negative`, and
|
|
1321
|
+
`mixed` are accepted whether declared or not, and an undeclared one runs nothing
|
|
1322
|
+
and continues after the assessment:
|
|
1323
|
+
|
|
1324
|
+
```yaml
|
|
1325
|
+
- assess:
|
|
1326
|
+
question: Were conflicts resolved in non-trivial code?
|
|
1327
|
+
outcomes:
|
|
1328
|
+
positive:
|
|
1329
|
+
steps:
|
|
1330
|
+
- review: Review the resolutions.
|
|
1331
|
+
- verify: Run the tests.
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
Here `negative` and `mixed` go straight to `verify`. A label of your own, such
|
|
1335
|
+
as `partial`, is accepted only when declared. For a simple gate, `- assess: <question>` accepts `positive` or
|
|
1336
|
+
`negative`; positive continues normally and negative completes the workflow.
|
|
1337
|
+
|
|
1338
|
+
The standard branches can also sit directly beside `question`:
|
|
1339
|
+
|
|
1340
|
+
```yaml
|
|
1341
|
+
- assess:
|
|
1342
|
+
question: Does recent development warrant refactoring?
|
|
1343
|
+
positive:
|
|
1344
|
+
handler: refactor-plan
|
|
1345
|
+
negative:
|
|
1346
|
+
steps:
|
|
1347
|
+
- record: No refactoring is needed now.
|
|
1348
|
+
```
|
|
1349
|
+
|
|
1350
|
+
Direct branches and `outcomes` cannot be combined; use `outcomes` for custom
|
|
1351
|
+
labels.
|
|
1352
|
+
|
|
1353
|
+
The agent sees the choice before it answers: the assessment's page lists each
|
|
1354
|
+
outcome and what it does, for example "`negative` — ends the workflow here".
|
|
1355
|
+
Once the assessment is complete, the next page asks for the outcome and shows
|
|
1356
|
+
one `next --outcome <label>` command per outcome, never a plain `next`, which
|
|
1357
|
+
ww would refuse. An outcome made of an automatic command runs only after its
|
|
1358
|
+
outcome is chosen; ww pauses at a pending assessment rather than running any
|
|
1359
|
+
branch. A delegating manager chooses it itself; no worker preview is
|
|
1360
|
+
shown until the outcome decides which work comes next.
|
|
1361
|
+
|
|
1362
|
+
`profile` may be a name or a mapping containing `name` and/or `description`.
|
|
1363
|
+
The mapping form supplies an inline description. ww resolves a named profile by
|
|
1364
|
+
first checking the selected agent directory's `agents/` folder (for example,
|
|
1365
|
+
`.codex/agents/developer.md`), then using the inline or root `profiles` text,
|
|
1366
|
+
and finally instructing the agent to use the profile by name when it has no
|
|
1367
|
+
description. A discovered file is always named explicitly in the instruction.
|
|
1368
|
+
|
|
1369
|
+
CLI handlers and workflow transitions are owned by `ww`; skills, slash commands,
|
|
1370
|
+
and prompts are owned by the agent. The plan exposes this ownership for every
|
|
1371
|
+
item. It also exposes an explicit execution mode:
|
|
1372
|
+
|
|
1373
|
+
- A CLI handler is `automatic`: ww runs it. It is shown in the plan for
|
|
1374
|
+
information and an agent must not run its command directly.
|
|
1375
|
+
- A CLI handler with `variables` entries of the `name: description` form is
|
|
1376
|
+
still automatic, but first has
|
|
1377
|
+
`requires_agent_input: true`. The preceding agent completion instruction
|
|
1378
|
+
collects those values and then ww runs the command itself.
|
|
1379
|
+
- Skills, slash commands, prompts, and MCP prompts are `agent_instruction`
|
|
1380
|
+
items.
|
|
1381
|
+
- A workflow transition is a `workflow_transition`, owned by the coordinator but
|
|
1382
|
+
not an automatically run CLI command.
|
|
1383
|
+
|
|
1384
|
+
If an agent-owned item cannot be completed, its worker uses
|
|
1385
|
+
`ww fail <TASK-ID> --role worker --error "<functional error>"`. The task enters
|
|
1386
|
+
a failed state. The worker reports it to the manager, and the manager reports it
|
|
1387
|
+
to the ww operator for manual intervention.
|
|
1388
|
+
|
|
1389
|
+
An artifact-enabled workflow step must be completed with a non-empty
|
|
1390
|
+
`--artifact`. Set `artifact: false` on a step that has no useful durable result.
|
|
1391
|
+
This prevents a successful-looking completion from silently losing the agent's
|
|
1392
|
+
work.
|
|
1393
|
+
|
|
1394
|
+
## Modes and execution settings
|
|
1395
|
+
|
|
1396
|
+
Modes add reusable guidance to a workflow. Define defaults on the workflow and
|
|
1397
|
+
add more when starting a task:
|
|
1398
|
+
|
|
1399
|
+
```yaml
|
|
1400
|
+
modes:
|
|
1401
|
+
- name: economy
|
|
1402
|
+
description:
|
|
1403
|
+
- Keep responses concise.
|
|
1404
|
+
- Prefer lighter reasoning where appropriate.
|
|
1405
|
+
|
|
1406
|
+
workflows:
|
|
1407
|
+
- name: task
|
|
1408
|
+
modes: [economy]
|
|
1409
|
+
model: gpt-5
|
|
1410
|
+
reasoning: high
|
|
1411
|
+
steps:
|
|
1412
|
+
- name: implement
|
|
1413
|
+
model: gpt-5-mini
|
|
1414
|
+
```
|
|
1415
|
+
|
|
1416
|
+
```console
|
|
1417
|
+
ww-agentic-workflows start TASK-123 --workflow task --agent codex --role manager \
|
|
1418
|
+
--requirements="Implement the requested change." \
|
|
1419
|
+
--mode economy --runtime auto --model gpt-5 --reasoning high
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
Each agent step's page lists the modes it works in under **Modes**, with
|
|
1423
|
+
their descriptions, so the guidance reaches whoever performs the step. The
|
|
1424
|
+
same pages get modes as get rules: not `init`, hooks, verifiers, or the
|
|
1425
|
+
workflow summary.
|
|
1426
|
+
|
|
1427
|
+
A mode may also apply by itself. Give it `workflows`, `steps`, or both, in the
|
|
1428
|
+
same shape as a hook's filters (`"*"` or a list of names), and it applies
|
|
1429
|
+
automatically to every step they admit, in addition to the selected modes:
|
|
1430
|
+
|
|
1431
|
+
```yaml
|
|
1432
|
+
modes:
|
|
1433
|
+
- name: tdd
|
|
1434
|
+
description: Write the failing test first.
|
|
1435
|
+
workflows: [task, bugfix]
|
|
1436
|
+
steps: [develop]
|
|
1437
|
+
```
|
|
1438
|
+
|
|
1439
|
+
Here every `develop` step of `task` and `bugfix` (and of workflows inheriting
|
|
1440
|
+
them) works in `tdd`, whatever `--mode` says: `--mode` replaces only the
|
|
1441
|
+
workflow's default modes. A mode without either key applies only when
|
|
1442
|
+
selected. `[]` on a mode means all, as on a hook. `discover` marks automatic
|
|
1443
|
+
modes with where they are always on. The modes of each step are fixed when
|
|
1444
|
+
the run starts; a later change to the configuration does not alter a running
|
|
1445
|
+
task's pages.
|
|
1446
|
+
|
|
1447
|
+
`single` uses one session for manager and worker responsibilities. `auto`
|
|
1448
|
+
lets a manager dispatch assignments to separate workers; it does not make `ww`
|
|
1449
|
+
launch models. Both runtimes use the same command exchange and persisted state.
|
|
1450
|
+
Workflow and step `model` and `reasoning` values are recorded with the work, and
|
|
1451
|
+
step values override workflow values. Use `modes`, `runtimes`, and `agents` to
|
|
1452
|
+
inspect the available choices.
|
|
1453
|
+
|
|
1454
|
+
### Steps the manager performs
|
|
1455
|
+
|
|
1456
|
+
`role` says who performs a step: `worker`, the default, is delegated in the
|
|
1457
|
+
`auto` runtime; `manager` keeps the step in the managing session in every
|
|
1458
|
+
runtime, such as a review whose judgement the manager keeps for itself. The
|
|
1459
|
+
step's profile, agent, model, and reasoning settings are deliberately ignored
|
|
1460
|
+
for a manager's step, whether set on it or inherited, and `lint` notes them.
|
|
1461
|
+
The manager's pages say so: the dispatch page states that no worker is
|
|
1462
|
+
selected and shows a plain `next`, and the page after it tells the manager to
|
|
1463
|
+
perform the step itself, with no worker bootstrap. An interactive step is
|
|
1464
|
+
always the manager's, since only its session can talk to the operator.
|
|
1465
|
+
|
|
1466
|
+
`role` is inherited like `profile`: set on a workflow, a group of steps, or a
|
|
1467
|
+
loop wrapper, it applies to everything inside, and a nested step overrides it.
|
|
1468
|
+
A step ww runs itself, such as a command, has no role; setting one on it is an
|
|
1469
|
+
error, while an inherited role passes over it.
|
|
1470
|
+
|
|
1471
|
+
```yaml
|
|
1472
|
+
workflows:
|
|
1473
|
+
- name: task
|
|
1474
|
+
model: gpt-5
|
|
1475
|
+
steps:
|
|
1476
|
+
- develop: Implement the change.
|
|
1477
|
+
- name: review-loop
|
|
1478
|
+
role: manager
|
|
1479
|
+
loop:
|
|
1480
|
+
- review: Review the diff and list the findings.
|
|
1481
|
+
break: There are no findings.
|
|
1482
|
+
- fix: Apply the findings.
|
|
1483
|
+
role: worker
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
### Steps without subagents
|
|
1487
|
+
|
|
1488
|
+
`subagents: false` says that whoever performs a step does all of its work
|
|
1489
|
+
alone: no subagent for research, tests, review, or anything else. It is
|
|
1490
|
+
independent of `role` and of the model: a delegated step with its own model can
|
|
1491
|
+
forbid helpers, and so can a step the manager performs. The step's page states
|
|
1492
|
+
the rule in its work instruction. It is inherited like `profile`, so on a group
|
|
1493
|
+
it covers every step inside, and a nested step may set `subagents: true` again.
|
|
1494
|
+
|
|
1495
|
+
```yaml
|
|
1496
|
+
- name: develop
|
|
1497
|
+
model: opus
|
|
1498
|
+
subagents: false # nobody working on develop's steps spawns subagents
|
|
1499
|
+
steps:
|
|
1500
|
+
- research: Find what the change touches.
|
|
1501
|
+
- implement: Implement it.
|
|
1502
|
+
model: sonnet # its own model, still no subagents
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
## Manager and worker assignments
|
|
1506
|
+
|
|
1507
|
+
Caller role describes responsibility for one command. It is explicit workflow
|
|
1508
|
+
coordination rather than authentication. The manager owns `start`, `next`, and
|
|
1509
|
+
recovery. A worker may inspect status and complete or fail active work. Scripts and
|
|
1510
|
+
direct service callers may omit the role; every execution command ww prints
|
|
1511
|
+
includes one.
|
|
1512
|
+
|
|
1513
|
+
One manager `next` dispatches a structural assignment. For a leaf step, that
|
|
1514
|
+
assignment contains its preparation hooks, main action, and completion hooks.
|
|
1515
|
+
Each worker completion records one agent result, runs eligible automatic work,
|
|
1516
|
+
and activates the next agent-owned workflow hook in the same assignment. The response exposes
|
|
1517
|
+
`continue_worker`, `handoff_manager`, `blocked`, or `awaiting_operator`
|
|
1518
|
+
together with `next_role`. `blocked` means ww is waiting on its own work, such
|
|
1519
|
+
as a child workflow, a loop boundary, or a running automatic handler, and the
|
|
1520
|
+
manager continues. `awaiting_operator` means a human must decide; see
|
|
1521
|
+
[Awaiting the operator](#awaiting-the-operator).
|
|
1522
|
+
At handoff only the manager runs the displayed `next --role manager` command.
|
|
1523
|
+
In the `auto` runtime the manager's delegate page and the requested worker
|
|
1524
|
+
describe the step that drives selection, not whichever hook the cursor is on,
|
|
1525
|
+
and both the manager and the worker are told every item the assignment covers.
|
|
1526
|
+
A worker moving to a later item of the same assignment is told so explicitly,
|
|
1527
|
+
and the end of an assignment says to stop and return to the manager.
|
|
1528
|
+
|
|
1529
|
+
An assignment that holds no agent step, only an automatic handler waiting for
|
|
1530
|
+
values, such as a commit hook that needs its message after a loop boundary, is
|
|
1531
|
+
not delegated. The manager has just read the outcome it would summarize, so
|
|
1532
|
+
the `awaiting_input` instruction is addressed to the manager, its command
|
|
1533
|
+
carries `--role manager`, and the preview before it says that no worker is
|
|
1534
|
+
selected. Every pending-input page also lists, under "Work these values
|
|
1535
|
+
describe", the handovers of the steps completed since that handler last ran
|
|
1536
|
+
in the run, so a commit message names this round's work rather than repeating
|
|
1537
|
+
an earlier one. The first page of each session shows the requirements saved by
|
|
1538
|
+
`init` in full under "Task requirements", so the user's wording reaches the worker
|
|
1539
|
+
without the manager adding commentary: the manager's first page that asks for
|
|
1540
|
+
work, the first page of every delegated worker assignment (each is a fresh
|
|
1541
|
+
session; `pages.worker_requirements` in `ww.json` set to `pointer` swaps it for
|
|
1542
|
+
the pointer), or, in the `single` runtime, the first agent step. Later pages of
|
|
1543
|
+
the same session, such as the following stages of one assignment, carry a
|
|
1544
|
+
one-line pointer to
|
|
1545
|
+
`ww requirements <task>`, which prints them again (JSON pages keep
|
|
1546
|
+
`task_requirements` and add `requirements_in_full` and `requirements_command`).
|
|
1547
|
+
A paragraph the work instruction already quotes verbatim is shown there only.
|
|
1548
|
+
`ww amend <task> --requirements "<text>" [--role ROLE]` appends a short
|
|
1549
|
+
(at most 1000 characters), timestamped amendment recording who made it (the
|
|
1550
|
+
caller role, or `operator`) and never rewrites the original; every page lists the
|
|
1551
|
+
amendments, newest last, under "Task requirements" (JSON: `task_amendments`), and
|
|
1552
|
+
`ww requirements` prints them after the original. A completed task refuses an
|
|
1553
|
+
amendment. Each page also states that ww writes the artifact
|
|
1554
|
+
from the completion command, never the worker under `.ww`. It then shows, under
|
|
1555
|
+
"Previous step result", the handover of the step completed most recently
|
|
1556
|
+
before this one. Completing an ordinary step requires
|
|
1557
|
+
`--summary`, one or two sentences on what was done and what the
|
|
1558
|
+
next step must know, at most 500 characters (a longer one is refused); ww stores it on the step record and shows it to the next
|
|
1559
|
+
step together with the artifact's path, so the full result stays in the
|
|
1560
|
+
artifact and is read only when the summary is not enough. Hooks, `init`,
|
|
1561
|
+
and the built-in summary do not take one. Only ordinary steps count: hook results,
|
|
1562
|
+
the built-in summary, and `init` are never chosen, and the history of earlier
|
|
1563
|
+
loop rounds is included, so the first step of a later round sees the previous
|
|
1564
|
+
round's last step. `artifact_from` remains the way to point a step at a specific
|
|
1565
|
+
earlier artifact when the immediately previous one is not the right input.
|
|
1566
|
+
|
|
1567
|
+
An active step's Markdown instruction also names its later sibling steps. This
|
|
1568
|
+
gives the worker a lightweight scope boundary without repeating those steps'
|
|
1569
|
+
descriptions or prescribing a strict prohibition:
|
|
1570
|
+
|
|
1571
|
+
```markdown
|
|
1572
|
+
### Next steps
|
|
1573
|
+
|
|
1574
|
+
Leave to them the work they cover:
|
|
1575
|
+
|
|
1576
|
+
- run-tests
|
|
1577
|
+
- check-code-quality
|
|
1578
|
+
```
|
|
1579
|
+
|
|
1580
|
+
The JSON instruction exposes the same ordered names in `next_steps`. Hooks do
|
|
1581
|
+
not receive the list, and a step with no later siblings omits the section.
|
|
1582
|
+
|
|
1583
|
+
Parent-only preparation hooks form their own dispatch before descendants.
|
|
1584
|
+
Completion hooks on a parent trail the last descendant, and the built-in final
|
|
1585
|
+
summary trails the final assignment. A newly materialized item, child-workflow
|
|
1586
|
+
coordinator, workflow transition, or successor execution instance is always a
|
|
1587
|
+
manager boundary.
|
|
1588
|
+
|
|
1589
|
+
On a failure or an interruption, the response reports `awaiting_operator`
|
|
1590
|
+
and states whether a preceding worker result was already saved.
|
|
1591
|
+
`instruction --role worker` reconstructs the same continuation or handoff from
|
|
1592
|
+
persisted state after a restart. Caller roles do not change the concurrency
|
|
1593
|
+
guarantees; a stale worker is stopped by its [assignment
|
|
1594
|
+
token](#assignment-tokens).
|
|
1595
|
+
|
|
1596
|
+
### Assignment tokens
|
|
1597
|
+
|
|
1598
|
+
In the `auto` runtime every assignment the manager hands out carries a short
|
|
1599
|
+
token. The bootstrap command gives it to the worker, and every command a
|
|
1600
|
+
worker page prints repeats it:
|
|
1601
|
+
|
|
1602
|
+
```console
|
|
1603
|
+
./ww instruction TASK-7 --run 01-task --role worker --assignment 3f9a1c07
|
|
1604
|
+
./ww complete TASK-7 --role worker --assignment 3f9a1c07 --artifact="..." --summary="..."
|
|
1605
|
+
```
|
|
1606
|
+
|
|
1607
|
+
`instruction`, `complete`, `loop`, `fail`, `interact`, and `dispute` with
|
|
1608
|
+
`--role worker` accept a call only when its token is the open assignment's,
|
|
1609
|
+
so a worker whose assignment ended, or that holds another assignment's token,
|
|
1610
|
+
cannot act. When the assignment ends, at a handoff to the manager, a return
|
|
1611
|
+
from a loop, or a failure, ww closes the token. A worker command after that is
|
|
1612
|
+
refused with "your assignment has ended", and one without a token is sent back
|
|
1613
|
+
to the manager. While no assignment is open, a worker's `instruction` still
|
|
1614
|
+
answers with the page that only sends it back.
|
|
1615
|
+
|
|
1616
|
+
The manager needs no token, and it never has to remember one. After a context
|
|
1617
|
+
compaction, `instruction <task> --role manager` shows the open assignment and
|
|
1618
|
+
its bootstrap command with the same token, so a worker still holding it
|
|
1619
|
+
carries on. `next <task> --role manager --reassign` issues a new token for the
|
|
1620
|
+
open assignment and closes the old one, for a worker that was lost or must be
|
|
1621
|
+
replaced. A step the manager performs itself, `role: manager` or interactive,
|
|
1622
|
+
is an assignment of its own with its own token, which no worker page ever
|
|
1623
|
+
shows. The worker's first page of an assignment says it is addressed to the
|
|
1624
|
+
worker, that running the commands it displays is expected even where they name
|
|
1625
|
+
the parent task, and that the worker changes only its own branch and worktree. Its page gives a manager completion command, `complete <task> --role
|
|
1626
|
+
manager`, and ww refuses `complete` or `loop` with `--role worker` on it ("this
|
|
1627
|
+
step is the manager's"), even with the step's token. The manager keeps every
|
|
1628
|
+
override: it may still complete or recover any other step.
|
|
1629
|
+
|
|
1630
|
+
When the step after a manager's `complete --role manager` is also the manager's
|
|
1631
|
+
own (`role: manager`, so no worker is selected), `complete` performs the dispatch
|
|
1632
|
+
that `next --role manager` would and prints that step's work page under a
|
|
1633
|
+
one-line note, so the separate `next` is unnecessary. It never does so when a
|
|
1634
|
+
worker must be selected, when the task stops or waits for the operator, for an
|
|
1635
|
+
assessment's outcome choice, at a loop boundary or a child coordinator, or for
|
|
1636
|
+
`--role worker`; `complete --no-dispatch` keeps the pending page.
|
|
1637
|
+
|
|
1638
|
+
Tokens guard against a confused agent, not a hostile one: a worker that runs
|
|
1639
|
+
the manager's commands is still not stopped. The `single` runtime, where one
|
|
1640
|
+
session does every step, uses no tokens.
|
|
1641
|
+
|
|
1642
|
+
A handler that failed inside an assignment keeps the assignment open. When
|
|
1643
|
+
`next --retry` asks again for the values the handler takes, the completion
|
|
1644
|
+
command on that page carries the open token, so the worker supplies them with
|
|
1645
|
+
the assignment it holds.
|
|
1646
|
+
|
|
1647
|
+
### Handoff to manager
|
|
1648
|
+
|
|
1649
|
+
In the `auto` runtime the manager does not take a worker's word for what
|
|
1650
|
+
happened. When a worker's command ends its assignment (its last `complete` or
|
|
1651
|
+
`loop`, a `fail`, or a `dispute`), the page ends with a "Handoff to manager"
|
|
1652
|
+
block that ww writes from the saved state:
|
|
1653
|
+
|
|
1654
|
+
```text
|
|
1655
|
+
Handoff to manager · assignment 3f9a1c07
|
|
1656
|
+
|
|
1657
|
+
Steps:
|
|
1658
|
+
- review: completed
|
|
1659
|
+
artifact: /repo/.ww/tasks/TASK-7/runs/01-task/steps/03-review.md
|
|
1660
|
+
- fix: completed
|
|
1661
|
+
artifact: /repo/.ww/tasks/TASK-7/runs/01-task/steps/04-fix.md
|
|
1662
|
+
checks: develop/sh passed
|
|
1663
|
+
fix rounds: 1
|
|
1664
|
+
|
|
1665
|
+
Files changed:
|
|
1666
|
+
- src/app.py
|
|
1667
|
+
|
|
1668
|
+
Worker summary: Both findings fixed; the parser test covers the second.
|
|
1669
|
+
|
|
1670
|
+
Manager: continue with `./ww next TASK-7 --role manager`
|
|
1671
|
+
```
|
|
1672
|
+
|
|
1673
|
+
It lists every agent item the worker performed in the assignment with its
|
|
1674
|
+
outcome (`completed`, `loop break`, `loop continue`, `held for verification`,
|
|
1675
|
+
`failed` with the error, or `not completed`), each artifact's path, the checks
|
|
1676
|
+
of the last attempt with their status, the checks the operator waived, and the
|
|
1677
|
+
number of fix rounds. "Files changed" is the change set since the first step of
|
|
1678
|
+
the assignment with rules or checks began; without such a step, or without git,
|
|
1679
|
+
it reads "not tracked". A failed handler's error is shown too. The worker's
|
|
1680
|
+
own judgment reaches the manager only through its `--summary`.
|
|
1681
|
+
The worker page tells the worker to return the block verbatim as its final
|
|
1682
|
+
message and nothing else, and prints no further command for it. The manager's
|
|
1683
|
+
bootstrap page names the block by the assignment's token, so the manager knows
|
|
1684
|
+
which block answers which assignment. The JSON instruction carries the same
|
|
1685
|
+
facts in `handoff_block`. The `single` runtime has no block.
|
|
1686
|
+
|
|
1687
|
+
### Confirmations
|
|
1688
|
+
|
|
1689
|
+
`next --retry`, `next --force`, a `next --replan` that reruns finished steps,
|
|
1690
|
+
`rules prune`, `rules revoke`, `rules convert` and `rules decline` ask the
|
|
1691
|
+
operator to confirm, because they can repeat an external effect, skip work,
|
|
1692
|
+
record a command that will run from then on, or change the shared
|
|
1693
|
+
rule-automation store. ww asks only at a terminal. An
|
|
1694
|
+
agent's shell has none, so there ww refuses at once and names `--yes`, and it
|
|
1695
|
+
never reads an answer from a pipe. The pages that show these choices print
|
|
1696
|
+
them with `--yes`, since the agent runs one only after the operator chose it;
|
|
1697
|
+
the effect is still printed, and the task's audit record notes whether the
|
|
1698
|
+
operator confirmed at a terminal or an agent did with `--yes`.
|
|
1699
|
+
|
|
1700
|
+
### Awaiting the operator
|
|
1701
|
+
|
|
1702
|
+
The operator is the human running the agent. When only they can decide how a
|
|
1703
|
+
task goes on, the response says so in a machine-readable way instead of in
|
|
1704
|
+
prose: `control` is `awaiting_operator`, `next_role` is `operator`, and
|
|
1705
|
+
`operator_reason` says why.
|
|
1706
|
+
|
|
1707
|
+
| `operator_reason` | When |
|
|
1708
|
+
| --- | --- |
|
|
1709
|
+
| `handler_failed` | An automatic handler failed. |
|
|
1710
|
+
| `work_failed` | An agent step was recorded with `fail`, or the bootstrap failed. |
|
|
1711
|
+
| `child_failed` | A child task failed. |
|
|
1712
|
+
| `handler_interrupted` | An automatic handler was interrupted and its outcome is unknown. |
|
|
1713
|
+
| `loop_limit` | A loop reached its round limit: its `max_rounds`, else `limits.rounds`. |
|
|
1714
|
+
| `fix_limit` | A step's check failed as many times as its rule's `max_fixes`, else `limits.fixes`, allows; see [Rules and checks](#rules-and-checks). |
|
|
1715
|
+
| `check_disputed` | A step's worker disputed a check that rejected its completion; see [Checking early and disputing a check](#checking-early-and-disputing-a-check). |
|
|
1716
|
+
| `value_unavailable` | An agent step reads a `{{ww.<namespace>.<name>}}` value its extension cannot give for the task yet, such as `{{ww.git.branch}}` before the task has a branch; the step has not started. `next --retry` checks again, `next --force` skips it. |
|
|
1717
|
+
| `pass_incomplete` | An `items` pass finished its stages, but some items lack what those stages declare, such as an analysis or `reported`; see [Several passes over the same items](#several-passes-over-the-same-items). The next step has not started. Record the missing values with `update-item`, then `next --retry` checks again; the gate cannot be forced. |
|
|
1718
|
+
| `plan_changed` | The workflow's definition changed since the run's plan was saved; see [When the workflow changes mid-run](#when-the-workflow-changes-mid-run). |
|
|
1719
|
+
|
|
1720
|
+
An interrupted handler declared `idempotent: true` is not a reason: `next`
|
|
1721
|
+
replays it without asking anyone, so the task stays `blocked` for the manager.
|
|
1722
|
+
`operator_reason` is `null` whenever `control` is anything else. The same three
|
|
1723
|
+
fields appear in `instruction` and `status` JSON:
|
|
1724
|
+
|
|
1725
|
+
```json
|
|
1726
|
+
{
|
|
1727
|
+
"control": "awaiting_operator",
|
|
1728
|
+
"next_role": "operator",
|
|
1729
|
+
"operator_reason": "handler_failed"
|
|
1730
|
+
}
|
|
1731
|
+
```
|
|
1732
|
+
|
|
1733
|
+
Markdown heads the page with the decision, for example
|
|
1734
|
+
`## Operator decision: the automatic handler failed`, and tells the agent to
|
|
1735
|
+
stop and ask the operator. The recovery commands, `next --retry` and
|
|
1736
|
+
`next --force --reason`, are still shown, as the operator's choices; the
|
|
1737
|
+
agent runs one only after the operator picks it. A delegated worker is not told
|
|
1738
|
+
to ask anyone: it returns to its manager, and the manager asks.
|
|
1739
|
+
|
|
1740
|
+
The operator is someone ww waits for, not a caller: `--role` still accepts only
|
|
1741
|
+
`manager` and `worker`, and `--role operator` is rejected.
|
|
1742
|
+
|
|
1743
|
+
### When the workflow changes mid-run
|
|
1744
|
+
|
|
1745
|
+
A run works from the plan it compiled when it started. When the workflow's
|
|
1746
|
+
definition changes afterwards, say a hook's command was wrong and stopped the
|
|
1747
|
+
task, and the operator fixed it in `ww.yaml`, the manager's next `next` (and
|
|
1748
|
+
its `instruction` page) compiles the workflow again and compares it with the
|
|
1749
|
+
saved plan, step by step and hook by hook. If anything differs, ww stops
|
|
1750
|
+
before doing anything else, with `operator_reason: plan_changed`, and lists
|
|
1751
|
+
each changed, added or removed step or hook, with the fields that changed,
|
|
1752
|
+
before and after. This comes ahead of `--retry` too, so the fixed command is
|
|
1753
|
+
offered instead of the old one being run again. The operator picks one of two
|
|
1754
|
+
answers:
|
|
1755
|
+
|
|
1756
|
+
```console
|
|
1757
|
+
./ww next TASK-1 --replan --role manager
|
|
1758
|
+
./ww next TASK-1 --keep-plan --role manager
|
|
1759
|
+
```
|
|
1760
|
+
|
|
1761
|
+
- `--replan` takes the new definition from the first changed item on. Items
|
|
1762
|
+
before it keep what they did; the first changed item and everything after
|
|
1763
|
+
it are the new ones, with fresh records, and `next` goes on from there.
|
|
1764
|
+
When the first change is in a step or hook that already finished, the run
|
|
1765
|
+
rewinds to it and runs it, and everything after it, again. The page names
|
|
1766
|
+
those steps, and `next --replan` asks the operator to confirm, or takes
|
|
1767
|
+
`--yes` once they have agreed. Earlier attempts move to the run's history,
|
|
1768
|
+
so their artifacts and output stay readable.
|
|
1769
|
+
- `--keep-plan` carries on with the saved plan and is not asked again for
|
|
1770
|
+
the same configuration.
|
|
1771
|
+
|
|
1772
|
+
A change that leaves the run's own workflow as it is, such as a new workflow
|
|
1773
|
+
beside it, is taken silently. A configuration that does not load stops
|
|
1774
|
+
nothing: the run goes on with its saved plan, and `ww lint` shows the error.
|
|
1775
|
+
Two changes cannot be applied to a running task, and the page says why,
|
|
1776
|
+
offering only `--keep-plan`: a change to per-item or per-child stages that
|
|
1777
|
+
the run has already expanded for its items or children, and a rewind past a
|
|
1778
|
+
children step whose child tasks already exist. For those, reset the task and
|
|
1779
|
+
start it again. A worker's pages never stop for a changed plan; the manager
|
|
1780
|
+
decides, between assignments.
|
|
1781
|
+
|
|
1782
|
+
## Lock cleanup and command attempts
|
|
1783
|
+
|
|
1784
|
+
ww keeps lock sidecar paths in `.ww/locks`; a file there is not evidence of a
|
|
1785
|
+
currently held lock. Remove inactive sidecars with:
|
|
1786
|
+
|
|
1787
|
+
```console
|
|
1788
|
+
ww-agentic-workflows cleanup
|
|
1789
|
+
```
|
|
1790
|
+
|
|
1791
|
+
Cleanup waits until active and waiting ww operations have left their lock gate,
|
|
1792
|
+
then removes the old sidecars safely. The execution log records `started`
|
|
1793
|
+
before a command runs and records its final `ok` or `error` result afterwards,
|
|
1794
|
+
so an interrupted operation is visible as a `started` entry without a terminal
|
|
1795
|
+
record.
|
|
1796
|
+
|
|
1797
|
+
## Projects: one ww instance over several repositories
|
|
1798
|
+
|
|
1799
|
+
Projects are optional. Without them a task works in the project root, where
|
|
1800
|
+
`ww.yaml` and `.ww` live. With a `projects` list in
|
|
1801
|
+
`ww.json`, that root can be a workspace directory above
|
|
1802
|
+
several repositories, and each task or child chooses the repository it works
|
|
1803
|
+
in. Projects live in the JSON settings rather than in `ww.yaml` because
|
|
1804
|
+
their locations are machine-specific, while the workflows are shared:
|
|
1805
|
+
|
|
1806
|
+
```json
|
|
1807
|
+
{
|
|
1808
|
+
"projects": [
|
|
1809
|
+
{"name": "backend", "path": "./backend", "description": "Python API service."},
|
|
1810
|
+
{"name": "frontend", "path": "./frontend"}
|
|
1811
|
+
]
|
|
1812
|
+
}
|
|
1813
|
+
```
|
|
1814
|
+
|
|
1815
|
+
| Key | Required | Meaning |
|
|
1816
|
+
| --- | --- | --- |
|
|
1817
|
+
| `name` | yes | Unique normalized name; the value of `--project`. |
|
|
1818
|
+
| `path` | yes | The directory, resolved against the root when relative. It must exist when a task starts there. |
|
|
1819
|
+
| `description` | no | Shown by `discover` and in the children collection step. |
|
|
1820
|
+
|
|
1821
|
+
A checkout laid out differently can list its own projects in
|
|
1822
|
+
`ww.local.json`; the list replaces the repo's whole, and its
|
|
1823
|
+
relative paths still resolve against the root.
|
|
1824
|
+
|
|
1825
|
+
```console
|
|
1826
|
+
ww-agentic-workflows start PROJ-123 --workflow feature --project backend ...
|
|
1827
|
+
ww-agentic-workflows add-child EPIC-1 --text "Web part" --project frontend
|
|
1828
|
+
```
|
|
1829
|
+
|
|
1830
|
+
The chosen project's directory becomes the task's working directory: commands
|
|
1831
|
+
and hooks run there, `{{ww.task.workspace_dir}}` points at it, `{{ww.project.name}}`
|
|
1832
|
+
holds the name, `{{ww.project.dir}}` the directory, and `{{ww.project.names}}` lists
|
|
1833
|
+
every configured project name, joined by commas. `ww-agentic-workflows projects` prints the list as JSON.
|
|
1834
|
+
Configuration, state, and artifacts stay in the root, so one
|
|
1835
|
+
task's requirements, plan, and reviews are kept together even when its children
|
|
1836
|
+
touch several repositories. `discover` lists the projects, and the children
|
|
1837
|
+
collection step lists them so the agent can pass `--project` per child. A handoff
|
|
1838
|
+
successor run keeps its project. A task without `--project` works in the
|
|
1839
|
+
root.
|
|
1840
|
+
|
|
1841
|
+
The `ww/git` extension follows the working directory: branches, worktrees, and
|
|
1842
|
+
commits act on the repository the task works in, and a task in a worktree still
|
|
1843
|
+
resolves to that repository's primary checkout. A repository whose conventions
|
|
1844
|
+
differ from the root's states them in its own settings file, described next.
|
|
1845
|
+
|
|
1846
|
+
**Which configuration is in force.** Every command, child launches included,
|
|
1847
|
+
reads `ww.yaml`, `ww.json`, and their imports from the primary checkout, never
|
|
1848
|
+
from a task's worktree: ww's `.ww` state lives there too. Editing a worktree's
|
|
1849
|
+
copy changes nothing, so when a task's worktree holds a `ww.yaml` whose content
|
|
1850
|
+
differs from the primary's, the task's instruction pages (the `notices` field in
|
|
1851
|
+
JSON) say in one line that the primary checkout's file is the one in force.
|
|
1852
|
+
|
|
1853
|
+
### A project's own extension settings
|
|
1854
|
+
|
|
1855
|
+
A configured project may carry `ww.json` and
|
|
1856
|
+
`ww.local.json` in its own directory. Of those files ww reads
|
|
1857
|
+
exactly two keys, repo file then local file: the `extensions` section, applied
|
|
1858
|
+
over the root's effective section for the same extension with the same rule as
|
|
1859
|
+
between configuration levels (nested objects merge key by key, any other value
|
|
1860
|
+
replaces the root's), and `task_format`, which replaces the root's for tasks
|
|
1861
|
+
started in that project. A project therefore states only what differs:
|
|
1862
|
+
|
|
1863
|
+
```json
|
|
1864
|
+
{
|
|
1865
|
+
"task_format": "WEB-{{digit}}",
|
|
1866
|
+
"extensions": {
|
|
1867
|
+
"ww/git": {
|
|
1868
|
+
"base_branches": {"default": "master"},
|
|
1869
|
+
"commit_format": "[{{ww.task.id}}] {{commit_message}}",
|
|
1870
|
+
"worktrees": true,
|
|
1871
|
+
"worktree_dir": "../frontend-worktrees"
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
```
|
|
1876
|
+
|
|
1877
|
+
With that file, `start --workflow feature --project frontend` without a task
|
|
1878
|
+
ID generates `WEB-1`, `WEB-2`, and so on, and `add-child ... --project
|
|
1879
|
+
frontend` without `--id` names the child the same way under its parent, while
|
|
1880
|
+
tasks started without `--project`, or in a project that sets no format, keep
|
|
1881
|
+
the root's. A project may set `"task_format": "explicit"` to require an ID for
|
|
1882
|
+
its tasks while the root generates them, and the other way round. `lookup`
|
|
1883
|
+
resolves references with the root's format. `discover` names a project's
|
|
1884
|
+
format after its entry when it has one of its own.
|
|
1885
|
+
|
|
1886
|
+
Every other key of a project's file (`enabled`, `runtime`, `executable`,
|
|
1887
|
+
`projects`, `workflows`, and anything else) is ignored here: those describe
|
|
1888
|
+
the project as a ww root of its own, which it may also be when used on its
|
|
1889
|
+
own, and the workspace root owns them. A project file never marks a ww root,
|
|
1890
|
+
and a project without such files, or a task started without `--project`, gets
|
|
1891
|
+
the root's settings unchanged. The root stays the only place that decides
|
|
1892
|
+
which extensions are configured: a project section naming an extension that is
|
|
1893
|
+
not installed is an error that names the project's file, for example
|
|
1894
|
+
`frontend/ww.json (project 'frontend') configures unknown
|
|
1895
|
+
extension 'acme/notes'`.
|
|
1896
|
+
|
|
1897
|
+
The settings follow the directory a step or hook acts on, not the task as a
|
|
1898
|
+
whole, and are frozen into the plan when the task starts like every extension
|
|
1899
|
+
setting. An item working in the task workspace or the project directory
|
|
1900
|
+
(`workdir` `task` or `project`) gets the project's settings; one working in the
|
|
1901
|
+
root (`workdir: root`) gets the root's. Relative paths inside a project's
|
|
1902
|
+
section, such as `worktree_dir`, resolve against that project's repository,
|
|
1903
|
+
never the workspace root. Generated task IDs also reserve a project's worktree
|
|
1904
|
+
paths under the project's settings when the task starts with `--project`.
|
|
1905
|
+
|
|
1906
|
+
`lint` validates every project's sections and, like `plan`, lists the project
|
|
1907
|
+
files it read after the root's:
|
|
1908
|
+
|
|
1909
|
+
```console
|
|
1910
|
+
$ ww-agentic-workflows lint
|
|
1911
|
+
ww.yaml is valid.
|
|
1912
|
+
Configuration files: ww.yaml, ww.json
|
|
1913
|
+
Project frontend extension settings: frontend/ww.json, frontend/ww.local.json
|
|
1914
|
+
```
|
|
1915
|
+
|
|
1916
|
+
`plan --project <name>` compiles a workflow as a task in that project would
|
|
1917
|
+
get it, `extension ww/git settings --project <name>` prints the settings such
|
|
1918
|
+
a task's handlers receive, and `discover` names each project's branch
|
|
1919
|
+
strategies when they differ from the root's, and its task ID format when it
|
|
1920
|
+
has one.
|
|
1921
|
+
|
|
1922
|
+
One consequence of per-project worktrees: run ww through the root launcher
|
|
1923
|
+
`./ww` or with `--root`. Invoked from inside a project's checkout or worktree
|
|
1924
|
+
without either, ww resolves the root through the Git common directory to that
|
|
1925
|
+
project's own checkout, not to the workspace.
|
|
1926
|
+
|
|
1927
|
+
### Choosing where a step works
|
|
1928
|
+
|
|
1929
|
+
By default every step and hook works in the task workspace: the Git worktree
|
|
1930
|
+
when one was selected, otherwise the `--project` directory, otherwise the
|
|
1931
|
+
project root. Some work belongs elsewhere, such as updating shared or
|
|
1932
|
+
git-ignored files in the root checkout rather than in a task worktree. Set
|
|
1933
|
+
`workdir` on a step or on a handler to choose its directory:
|
|
1934
|
+
|
|
1935
|
+
| Value | Directory |
|
|
1936
|
+
| --- | --- |
|
|
1937
|
+
| `task` | The task workspace; the default, unchanged when `workdir` is omitted. |
|
|
1938
|
+
| `project` | The `--project` directory's own checkout, never the worktree made from it; the project root for a task without `--project`. |
|
|
1939
|
+
| `root` | The project root, where the configuration and `.ww` live. |
|
|
1940
|
+
|
|
1941
|
+
```yaml
|
|
1942
|
+
handlers:
|
|
1943
|
+
- name: refresh-shared-config
|
|
1944
|
+
argv: [make, shared-config]
|
|
1945
|
+
workdir: root
|
|
1946
|
+
|
|
1947
|
+
workflows:
|
|
1948
|
+
- name: feature
|
|
1949
|
+
steps:
|
|
1950
|
+
- develop: Implement the change.
|
|
1951
|
+
- update-local-notes: Record the decisions in {{ww.task.workspace_dir}}/notes/.
|
|
1952
|
+
workdir: root
|
|
1953
|
+
hooks:
|
|
1954
|
+
after_complete:
|
|
1955
|
+
- refresh-shared-config: ~
|
|
1956
|
+
- name: lint
|
|
1957
|
+
argv: [make, lint]
|
|
1958
|
+
workdir: project
|
|
1959
|
+
```
|
|
1960
|
+
|
|
1961
|
+
An extension handler entry, such as `- ext/ww/git/handlers:git-commit: ~`,
|
|
1962
|
+
may carry `workdir` as well, and nothing else: the extension defines the rest.
|
|
1963
|
+
For `update-local-notes`, the instruction's working-directory `cd` names the
|
|
1964
|
+
root and `{{ww.task.workspace_dir}}` resolves to it; an `argv` or `shell` step
|
|
1965
|
+
runs its command there. Nested steps, loop bodies, and per-item stages inherit
|
|
1966
|
+
the value from their enclosing step and may set their own. A hook does not
|
|
1967
|
+
inherit its step's directory: it uses its own `workdir`, then that of the root
|
|
1968
|
+
handler it names, and otherwise the task workspace, so above
|
|
1969
|
+
`refresh-shared-config` runs in the root and `lint` in the `--project` checkout.
|
|
1970
|
+
|
|
1971
|
+
ww does nothing special with Git for such a step: its changes are not part of
|
|
1972
|
+
the task's commits and are left for the operator.
|
|
1973
|
+
|
|
1974
|
+
## External task IDs
|
|
1975
|
+
|
|
1976
|
+
An MCP-backed first declared step can establish the task identity without a new
|
|
1977
|
+
Jira-specific setting. When `start` is called without a task ID and that step
|
|
1978
|
+
declares exactly the variable `task_id`, ww starts it as a short bootstrap request before the
|
|
1979
|
+
implicit `init`. Complete it with the ID returned by the tracker; only then does
|
|
1980
|
+
ww create `.ww/tasks/<external-id>/`, run normal start hooks and `init`, and continue
|
|
1981
|
+
the rest of the workflow.
|
|
1982
|
+
|
|
1983
|
+
```yaml
|
|
1984
|
+
workflows:
|
|
1985
|
+
- name: jira-task
|
|
1986
|
+
steps:
|
|
1987
|
+
- name: create-jira
|
|
1988
|
+
mcp: jira
|
|
1989
|
+
description: Create the Jira issue and return its key.
|
|
1990
|
+
variables:
|
|
1991
|
+
- name: task_id
|
|
1992
|
+
- name: develop
|
|
1993
|
+
description: Implement {{task_id}}.
|
|
1994
|
+
```
|
|
1995
|
+
|
|
1996
|
+
The bootstrap step must be the first declared flat workflow step and cannot have
|
|
1997
|
+
hooks, nested work, or other variables. Its artifact is retained with the
|
|
1998
|
+
task's run artifacts. If an ID is passed explicitly to `start`, it is
|
|
1999
|
+
authoritative: ww supplies it as `{{task_id}}` and never accepts a replacement
|
|
2000
|
+
from an agent.
|
|
2001
|
+
|
|
2002
|
+
### Children that bind their own IDs
|
|
2003
|
+
|
|
2004
|
+
The same step lets each child of a parent task obtain its own external ID, for
|
|
2005
|
+
example one Jira story per child of an epic. When the child workflow's first
|
|
2006
|
+
step declares the variable `task_id`, the parent's collection step tells the agent to record
|
|
2007
|
+
children without `--id`; ww names each one by a temporary request ID until it
|
|
2008
|
+
starts:
|
|
2009
|
+
|
|
2010
|
+
```console
|
|
2011
|
+
ww-agentic-workflows add-child EPIC-1 --text "Story one" --project backend
|
|
2012
|
+
ww-agentic-workflows start-child EPIC-1 REQUEST-20260923101500123456
|
|
2013
|
+
ww-agentic-workflows next REQUEST-20260923101500123456 --role manager
|
|
2014
|
+
ww-agentic-workflows complete REQUEST-20260923101500123456 --role worker \
|
|
2015
|
+
--variable task_id=PROJ-456 --artifact "Created PROJ-456."
|
|
2016
|
+
```
|
|
2017
|
+
|
|
2018
|
+
`start-child` opens the identity request instead of a run, and the parent's
|
|
2019
|
+
instruction points at it while it is in progress. Completing the request with
|
|
2020
|
+
the tracker's key creates the child as `EPIC-1/PROJ-456` in its project
|
|
2021
|
+
directory, with the identity step already done, and renames the parent's child
|
|
2022
|
+
record. Each story is therefore created by its own step with its own retry and
|
|
2023
|
+
failure handling: a crash halfway through the split cannot leave stories
|
|
2024
|
+
without children, and a retried request that already bound its child simply
|
|
2025
|
+
reports the bound task. A child added with an explicit `--id` skips the request,
|
|
2026
|
+
as an explicit ID does for `start`.
|
|
2027
|
+
|
|
2028
|
+
## Inheriting a workflow
|
|
2029
|
+
|
|
2030
|
+
A workflow that should do exactly what another does, but branch or merge
|
|
2031
|
+
differently, inherits it instead of repeating it:
|
|
2032
|
+
|
|
2033
|
+
```yaml
|
|
2034
|
+
workflows:
|
|
2035
|
+
- hotfix: Fix a bug on main.
|
|
2036
|
+
steps:
|
|
2037
|
+
- investigate: Find the cause.
|
|
2038
|
+
- fix: Fix it.
|
|
2039
|
+
- bugfix: Fix a bug on dev.
|
|
2040
|
+
inherit: hotfix
|
|
2041
|
+
```
|
|
2042
|
+
|
|
2043
|
+
`bugfix` gets `hotfix`'s steps, workflow hooks, modes, runtime, and every other
|
|
2044
|
+
setting. Its own keys replace the copied ones, so it can change its
|
|
2045
|
+
description, runtime, or recommendation; it cannot declare `steps` or `hooks`,
|
|
2046
|
+
because a workflow with other steps is a workflow of its own. A global hook
|
|
2047
|
+
filtered to `workflows: [hotfix]` also runs for `bugfix`, and for anything that
|
|
2048
|
+
inherits `bugfix` in turn. Only the name differs, and that is the point:
|
|
2049
|
+
`ww/git` reads `branch_name_formats.bugfix` and `base_branches.bugfix`, so the
|
|
2050
|
+
copy branches from and names its branches after its own entries. `discover`
|
|
2051
|
+
marks the copy with "Same steps as `hotfix`."
|
|
2052
|
+
|
|
2053
|
+
## Recommending the next workflow
|
|
2054
|
+
|
|
2055
|
+
A workflow that is usually followed by another names it:
|
|
2056
|
+
|
|
2057
|
+
```yaml
|
|
2058
|
+
- hotfix: Fix a bug on main.
|
|
2059
|
+
recommended_next_workflow: merge-to-dev
|
|
2060
|
+
steps:
|
|
2061
|
+
- fix: Fix it.
|
|
2062
|
+
```
|
|
2063
|
+
|
|
2064
|
+
When a `hotfix` run completes, its page tells the agent not to start anything
|
|
2065
|
+
on its own but to ask the operator, through its choice menu, whether to start
|
|
2066
|
+
`merge-to-dev` on the same task, and shows the `start` command to run if they
|
|
2067
|
+
agree. Nothing starts without that answer. An inheriting workflow keeps the
|
|
2068
|
+
recommendation unless it sets its own, or `recommended_next_workflow: ~` to
|
|
2069
|
+
clear it. A handoff workflow (one with a workflow transition) already starts
|
|
2070
|
+
its successor and cannot recommend one.
|
|
2071
|
+
|
|
2072
|
+
## Rules and checks
|
|
2073
|
+
|
|
2074
|
+
A **rule** is a sentence a step's agent must follow, such as "Keep the public
|
|
2075
|
+
CLI unchanged." A **check** is evidence ww collects itself: a command it runs
|
|
2076
|
+
when the step completes. A rule may carry its own check, and a
|
|
2077
|
+
`before_complete` hook with `on_failure: fix` is one too. The agent that did
|
|
2078
|
+
the work never grades it: a claim that can be a command is run by ww.
|
|
2079
|
+
|
|
2080
|
+
Rules live in Markdown files grouped under the root `rules`, or inline in a
|
|
2081
|
+
step's `rules` list; the [specification](specification.md#rules) has the
|
|
2082
|
+
format. A group applies where its `workflows` and `steps` filters allow (each
|
|
2083
|
+
`"*"` or a list of names, as on hooks), and a step may name a group to get it
|
|
2084
|
+
regardless.
|
|
2085
|
+
|
|
2086
|
+
```yaml
|
|
2087
|
+
rules:
|
|
2088
|
+
python: [rules/python/]
|
|
2089
|
+
workflows:
|
|
2090
|
+
- name: task
|
|
2091
|
+
steps:
|
|
2092
|
+
- name: develop
|
|
2093
|
+
description: Implement it.
|
|
2094
|
+
rules:
|
|
2095
|
+
- Keep the public CLI unchanged.
|
|
2096
|
+
hooks:
|
|
2097
|
+
before_complete:
|
|
2098
|
+
- argv: [pytest, -q]
|
|
2099
|
+
on_failure: fix
|
|
2100
|
+
```
|
|
2101
|
+
|
|
2102
|
+
### On the step page
|
|
2103
|
+
|
|
2104
|
+
Every agent step lists its rules after the work instruction, each with its ID,
|
|
2105
|
+
its globs, and its first sentence; the IDs of rules with a check are collected
|
|
2106
|
+
on one line, "Checked automatically when you complete". The section names
|
|
2107
|
+
`ww check <task>`, to see the checks' result at any time without completing,
|
|
2108
|
+
and `ww rule <task> <id>`, to read a rule in full. The page asks the
|
|
2109
|
+
worker to say in its artifact, under a **Rules** heading, which rules it
|
|
2110
|
+
applied and any deviation. `init`, hooks, and the workflow summary get no
|
|
2111
|
+
rules. In the `auto` runtime the worker's page carries the section. JSON
|
|
2112
|
+
output lists them as `rules`, each with `id`, `summary`, `paths`,
|
|
2113
|
+
`has_command`, `hook`, `check`, `interpretation`, and `missing`.
|
|
2114
|
+
|
|
2115
|
+
A rule without a check is judged after completion by a verifier, never by the
|
|
2116
|
+
worker; the page says so. A rule whose wording already has a converted
|
|
2117
|
+
derived check is listed with the checked ones, and a judged rule shows the
|
|
2118
|
+
store's interpretation under it.
|
|
2119
|
+
|
|
2120
|
+
### The fix loop
|
|
2121
|
+
|
|
2122
|
+
`complete` runs the step's checks before recording anything. When one fails,
|
|
2123
|
+
the completion is rejected: nothing is saved, the artifact is kept only as a
|
|
2124
|
+
draft, the step stays in progress with its worker, and `complete` exits
|
|
2125
|
+
non-zero. The response is the fix page, `## Fix required: 2 of 5 checks failed
|
|
2126
|
+
(attempt 1 of 3)`, with each failed check's rule text, command, and the last 40
|
|
2127
|
+
lines it printed, then the same completion command; `fix_required` in JSON.
|
|
2128
|
+
The worker fixes the causes and completes again with a revised artifact.
|
|
2129
|
+
|
|
2130
|
+
A failed check always goes back to the worker, never to the operator, until
|
|
2131
|
+
it has failed `max_fixes` times: the rule's own value, else `limits.fixes` in
|
|
2132
|
+
`ww.json`, default 3. Then the task stops with
|
|
2133
|
+
`operator_reason: fix_limit` and the last failures on the page. The operator
|
|
2134
|
+
chooses:
|
|
2135
|
+
|
|
2136
|
+
- `next --retry` gives the worker another round: the count starts again, and
|
|
2137
|
+
the next `next` hands the step back.
|
|
2138
|
+
- `next --force --reason "<why>"` waives the checks: the worker completes
|
|
2139
|
+
the step once more without them, and its artifact records the waiver.
|
|
2140
|
+
|
|
2141
|
+
Both ask for confirmation; `next --yes` confirms for an agent that carries out
|
|
2142
|
+
what the operator said.
|
|
2143
|
+
|
|
2144
|
+
A hook without `on_failure: fix` fails like any handler, stopping the task with
|
|
2145
|
+
`operator_reason: handler_failed`.
|
|
2146
|
+
|
|
2147
|
+
### The change set
|
|
2148
|
+
|
|
2149
|
+
A check sees the files the step changed in `WW_STEP_CHANGED_FILES`,
|
|
2150
|
+
newline-separated and relative to the step's directory, narrowed to its
|
|
2151
|
+
`paths`; a check whose globs match none of them is not applicable and does not
|
|
2152
|
+
run. ww measures the change set with git: when the step begins it records the
|
|
2153
|
+
tree of everything in the working directory, tracked or not, using a temporary
|
|
2154
|
+
index, so the real index, the stash, and the files are untouched; at
|
|
2155
|
+
completion it takes a second tree and compares. Work that was uncommitted
|
|
2156
|
+
before the step cancels out, a commit made during it still counts, and
|
|
2157
|
+
deleted files, ww's own `.ww` state, and `ww-rule-automation.json` are left
|
|
2158
|
+
out. Without git there is no
|
|
2159
|
+
change set: the globs select every file in the directory, `.git` and `.ww`
|
|
2160
|
+
excepted.
|
|
2161
|
+
|
|
2162
|
+
In a shell check a bare `$WW_STEP_CHANGED_FILES` splits on whitespace, which
|
|
2163
|
+
suits `grep -L foo $WW_STEP_CHANGED_FILES` and `xargs`; paths with spaces need
|
|
2164
|
+
`printf '%s\n' "$WW_STEP_CHANGED_FILES" | xargs -d '\n' …`. A check's full
|
|
2165
|
+
output is a command-output artifact: `ww artifacts` lists it under the step
|
|
2166
|
+
with a `check` field naming the check.
|
|
2167
|
+
|
|
2168
|
+
### In the artifact
|
|
2169
|
+
|
|
2170
|
+
The step's artifact gains a `## Rules` section after `## Result`: one line per
|
|
2171
|
+
rule with its status, `passed`, `not applicable`, `failed`, which only a
|
|
2172
|
+
waiver lets through, `verified pass (by <verification item>)` for a rule a
|
|
2173
|
+
verifier judged, or `passed (check <name>)` for one a derived check covers;
|
|
2174
|
+
hook checks carry `(hook)`. A rule is `self-declared` only when the operator
|
|
2175
|
+
waived it, which skips its verification too. The section ends with the
|
|
2176
|
+
waived IDs and the operator's reason for each, if any, and how many
|
|
2177
|
+
completions ww rejected before this one.
|
|
2178
|
+
|
|
2179
|
+
### How a rule becomes a check
|
|
2180
|
+
|
|
2181
|
+
A rule without a command is judged by another agent, a verifier, unless the
|
|
2182
|
+
rule-automation store has a converted check for its wording: then ww runs
|
|
2183
|
+
that check, for every step and task that has the rule. Verifiers only judge.
|
|
2184
|
+
Building checks is the job of `ww-scriptize-rules`, a project task of its
|
|
2185
|
+
own; see [Recording checks outside a task](#recording-checks-outside-a-task).
|
|
2186
|
+
|
|
2187
|
+
What ww knows lives in `ww-rule-automation.json` at the project root, a file
|
|
2188
|
+
to commit, keyed by each rule's text hash; `checks` there are named, and one
|
|
2189
|
+
check may cover many rules, the usual case for an ecosystem tool such as
|
|
2190
|
+
deptrac, PHPStan, import-linter, ruff, or eslint, whose one configuration
|
|
2191
|
+
expresses several rules. When a step begins, each of its rules without a
|
|
2192
|
+
command is settled once: `converted`, checked by its store check where the
|
|
2193
|
+
check's configuration files exist, or `judged`. A store written by an earlier
|
|
2194
|
+
ww may still hold the interim entries verifiers once wrote inside tasks, such
|
|
2195
|
+
as an approach or a proposed check; ww reads them, and their rules are judged.
|
|
2196
|
+
|
|
2197
|
+
When a step's worker completes and its checks pass, ww holds the completion:
|
|
2198
|
+
nothing is recorded, the artifact is kept as a draft, and **verification
|
|
2199
|
+
items** are inserted before the step, one per distinct worker the rules ask
|
|
2200
|
+
for through `agent`, `model`, and `reasoning` (else the step's). Each is its
|
|
2201
|
+
own assignment: under `auto` the manager hands it to a new worker; under
|
|
2202
|
+
`single` the same session performs it, and its page says to read the change
|
|
2203
|
+
as a reviewer would. The verification page lists each rule, the step's
|
|
2204
|
+
changed files and the `git diff` that shows them, and the path of the held
|
|
2205
|
+
artifact. The verifier gives each rule a verdict, `pass` or `fail` with
|
|
2206
|
+
`file:line — what` evidence, as one `--rule-result` per rule,
|
|
2207
|
+
`{"id": "<rule>", "status": "judged", "verdict": "pass"}`, and writes
|
|
2208
|
+
nothing to the store. A failing verdict is a rejected completion: the step
|
|
2209
|
+
goes back to its worker with the fix page, and it counts toward the rule's
|
|
2210
|
+
`max_fixes` like a failed check. Once every rule passes, ww records the held
|
|
2211
|
+
completion as submitted.
|
|
2212
|
+
|
|
2213
|
+
`discover --json` (`rules_notice`) and the first page of `start` say how many
|
|
2214
|
+
declared rules have no check yet and suggest the `ww-scriptize` skill, which starts
|
|
2215
|
+
`ww-scriptize-rules`. The notice never blocks a task; it is left out
|
|
2216
|
+
while `ww-scriptize-rules` is switched off, and on the pages of
|
|
2217
|
+
`ww-scriptize-rules` itself. `ww lint` warns with the IDs of those rules,
|
|
2218
|
+
suggesting `ww-scriptize-rules` only while it is switched on, and
|
|
2219
|
+
lists store entries whose wording no rule has any more; only
|
|
2220
|
+
`ww rules prune`, after listing them and asking the operator, deletes the
|
|
2221
|
+
orphans.
|
|
2222
|
+
|
|
2223
|
+
### Guiding the checks: `rules.check_guidance`
|
|
2224
|
+
|
|
2225
|
+
`rules.check_guidance` is free text, in the operator's own words, for whoever
|
|
2226
|
+
builds a check, such as how the project runs its tools:
|
|
2227
|
+
|
|
2228
|
+
```json
|
|
2229
|
+
"rules": {
|
|
2230
|
+
"check_guidance": "Development and quality checks run inside the docker container, on the worktree. Write every new check to run inside the container and act on the worktree, never on the host. Run a check on the host only when it uses nothing but the standard Linux tools, or when the host's tool versions, such as PHP, match the container's."
|
|
2231
|
+
}
|
|
2232
|
+
```
|
|
2233
|
+
|
|
2234
|
+
`ww rules --json` carries it as written, as `check_guidance`, and
|
|
2235
|
+
`ww-scriptize-rules` follows it for every check it builds; it wins over ww's
|
|
2236
|
+
defaults there. Like any setting, it can live in `ww.local.json` for the
|
|
2237
|
+
operator alone. The `ww-rule` skill and ww's setup workflows honour it for
|
|
2238
|
+
the checks they write, and `ww-suggest` proposes it when `project.md` records
|
|
2239
|
+
a wrapper, such as a container, that checks must go through.
|
|
2240
|
+
|
|
2241
|
+
A project that wants no checks built leaves `ww-scriptize-rules` unused, or
|
|
2242
|
+
switches it off with `"workflows": {"ww-scriptize-rules": {"enabled":
|
|
2243
|
+
false}}`, which also silences the notice; its rules without a command are
|
|
2244
|
+
judged on every completion. A check the team finds wrong can be undone at
|
|
2245
|
+
any time:
|
|
2246
|
+
|
|
2247
|
+
```console
|
|
2248
|
+
./ww rules revoke deptrac --reason "too slow for every step"
|
|
2249
|
+
```
|
|
2250
|
+
|
|
2251
|
+
`rules revoke` shows the check, asks (`--yes` skips the question, and
|
|
2252
|
+
without a terminal it refuses), and rejects the check and the rules it
|
|
2253
|
+
covers, so they are judged by a verifier from then on. It changes only the
|
|
2254
|
+
store: the YAML, the rule files, and the check's configuration files, such
|
|
2255
|
+
as `deptrac.yaml` and an installed dev dependency, stay for you to keep or
|
|
2256
|
+
remove.
|
|
2257
|
+
|
|
2258
|
+
### Checking early and disputing a check
|
|
2259
|
+
|
|
2260
|
+
A worker need not complete to learn what the checks say: `ww check <task>`
|
|
2261
|
+
runs the step's checks against what it changed so far and prints the
|
|
2262
|
+
failures as the fix page would, or `All checks pass`, plus the rules a
|
|
2263
|
+
verifier will judge once it completes. Nothing is recorded: no attempt
|
|
2264
|
+
counts, and the output is not kept. It exits 1 when a check fails.
|
|
2265
|
+
|
|
2266
|
+
A check can be wrong for a change. After a rejection, instead of bending its
|
|
2267
|
+
work around the check, the worker may dispute it with
|
|
2268
|
+
`ww dispute <task> --rule <id> --reason "<why>"`, naming the ID the fix page
|
|
2269
|
+
shows. The task stops with `operator_reason: check_disputed`; the page shows
|
|
2270
|
+
the check, its last output, and the worker's argument. The operator decides:
|
|
2271
|
+
|
|
2272
|
+
- `next --retry`: the check stands. The rejection still counts toward
|
|
2273
|
+
`max_fixes`, and the step goes back to its worker with the fix page.
|
|
2274
|
+
- `next --force --reason "<why>"`: the check is waived for this step
|
|
2275
|
+
only; the worker completes again without it, and the artifact records the
|
|
2276
|
+
waiver.
|
|
2277
|
+
|
|
2278
|
+
A dispute changes nothing in the rule-automation store. Every dispute is also
|
|
2279
|
+
kept in `.ww/rule-disputes.json`, and `ww lint` lists each disputed ID with
|
|
2280
|
+
how often and where it was last disputed, since a check disputed again and
|
|
2281
|
+
again deserves a look at its wording or command.
|
|
2282
|
+
|
|
2283
|
+
### Recording checks outside a task
|
|
2284
|
+
|
|
2285
|
+
A check is built once for the project and recorded outside any task's
|
|
2286
|
+
verification, by `ww-scriptize-rules` or by hand:
|
|
2287
|
+
|
|
2288
|
+
```console
|
|
2289
|
+
./ww rules convert phpstan --covers php/no-new-services php/typed-returns \
|
|
2290
|
+
--config phpstan.neon --proven \
|
|
2291
|
+
--check-argv -- vendor/bin/phpstan analyse --configuration phpstan.neon
|
|
2292
|
+
./ww rules decline docs/tone --reason "A matter of review."
|
|
2293
|
+
```
|
|
2294
|
+
|
|
2295
|
+
`--check-argv -- <arg>...` goes last: everything after `--` is the check's
|
|
2296
|
+
argv, so the tool's own options, such as `--configuration` or `--select E`,
|
|
2297
|
+
are not taken for ww's. Without `--`, `--check-argv` takes the arguments up
|
|
2298
|
+
to the next option.
|
|
2299
|
+
|
|
2300
|
+
`ww-scriptize-rules` (the `ww-scriptize` skill) does this for every rule that
|
|
2301
|
+
needs it, on a branch of its own. `rules convert` shows the check with its command in full, its config files,
|
|
2302
|
+
the rules it covers with where each stands now, and anything else it
|
|
2303
|
+
changes, and asks; `--yes` stands for the operator's answer,
|
|
2304
|
+
and without a terminal it refuses. It creates the check, or replaces an
|
|
2305
|
+
existing one's command, configuration and coverage; a rule it no longer
|
|
2306
|
+
covers goes back to not scriptized. `rules decline` records rules as not
|
|
2307
|
+
convertible: a verifier judges them, and `ww-scriptize-rules` leaves them out
|
|
2308
|
+
from then on. Both
|
|
2309
|
+
take `--dry-run`. `rules --json` gives each rule's `scriptize` state:
|
|
2310
|
+
`command`, `converted`, `not_convertible`, `rejected` or `unscriptized`.
|
|
2311
|
+
|
|
2312
|
+
A converted check runs in a step only when all of its `config` files exist in
|
|
2313
|
+
the directory the step's checks run in. A check built on a branch that is not
|
|
2314
|
+
merged yet therefore does not run in another task's worktree: its rules are
|
|
2315
|
+
judged there, and the step page, like the verifier page, says why, naming
|
|
2316
|
+
the missing file.
|
|
2317
|
+
|
|
2318
|
+
### Reading the rules
|
|
2319
|
+
|
|
2320
|
+
`ww rule <task> <id>` prints one rule or check of a task as the task's plan
|
|
2321
|
+
froze it: its full text, globs, rule file, command, and the steps that carry
|
|
2322
|
+
it, and for a rule without a command what the store knows about its wording.
|
|
2323
|
+
`ww rules` lists the project's declared groups, with their filters and each
|
|
2324
|
+
rule's ID and first sentence, and each step's own rules; `--json` gives the
|
|
2325
|
+
same for a program. `ww rules prune` deletes store entries no declared rule
|
|
2326
|
+
needs any more, after listing them and asking; `--yes` skips the question.
|
|
2327
|
+
`ww rules revoke <check>` rejects a converted or proposed check and its rules
|
|
2328
|
+
the same way.
|
|
2329
|
+
|
|
2330
|
+
### Writing rules with the ww-rule skill
|
|
2331
|
+
|
|
2332
|
+
Rules are easiest to add in conversation. Invoked as `/ww-rule`, or when you
|
|
2333
|
+
ask an agent to add or change a rule, the `ww-rule` skill carries the
|
|
2334
|
+
judgment: it reads the groups (`ww rules --json`) and the real workflow and
|
|
2335
|
+
step names (`ww discover`), splits what you said into atomic obligations,
|
|
2336
|
+
tells a new rule from an amendment of an existing one or a change of where a
|
|
2337
|
+
group applies, gives a rule globs only when it names a kind of file and
|
|
2338
|
+
counts what each matches, places it in a group whose filters fit, and
|
|
2339
|
+
rewrites it as one imperative sentence with the rationale below. It shows
|
|
2340
|
+
you one confirmation block, with each rule's ID, group, filters, globs and
|
|
2341
|
+
match counts, sentence, and whether it is new or replaces an existing one,
|
|
2342
|
+
and writes nothing before you answer. `/ww-rule split <file>` does the same
|
|
2343
|
+
for every item of a prose document, in one batch; `/ww-rule from-review`
|
|
2344
|
+
turns the lasting conventions in a task's last review or fix page into
|
|
2345
|
+
rules.
|
|
2346
|
+
|
|
2347
|
+
The skill writes only through `ww rules` commands, which validate every
|
|
2348
|
+
write: each loads the configuration as ww would once the write is made, and
|
|
2349
|
+
when that fails, or the write would not have its effect, every file is put
|
|
2350
|
+
back and the command reports why. `--dry-run` runs the same validation and
|
|
2351
|
+
writes nothing. Nothing is committed.
|
|
2352
|
+
|
|
2353
|
+
```console
|
|
2354
|
+
$ ww rules add --group php --dir rules/php --workflows task --steps develop
|
|
2355
|
+
Added rule group `php` (rules/php/) to ww-rules.yaml.
|
|
2356
|
+
Added ww-rules.yaml to imports in ww.yaml.
|
|
2357
|
+
Warning: rules/php holds no rule yet, and git does not keep an empty directory: add one with `rules add php --text ...` before committing.
|
|
2358
|
+
Reaches these steps (an agent step's page shows it):
|
|
2359
|
+
- task: develop
|
|
2360
|
+
$ ww rules add php --text "Put every \`*Service.php\` under \`src/Service/\`." --paths "src/**/*.php"
|
|
2361
|
+
Created rules/php/put-every-service-php-under.md: rule `php/put-every-service-php-under`.
|
|
2362
|
+
`src/**/*.php` matches 14 file(s) now.
|
|
2363
|
+
Reaches these steps (an agent step's page shows it):
|
|
2364
|
+
- task: develop
|
|
2365
|
+
```
|
|
2366
|
+
|
|
2367
|
+
- `rules add <group> --text "<sentence and body>"` creates a rule file in the
|
|
2368
|
+
group's first directory, named after the first five words of its first
|
|
2369
|
+
sentence unless `--id` names it, with `--paths` globs and a check from
|
|
2370
|
+
`--check-shell` or `--check-argv` and `--assert empty|eq:<value>`. It never
|
|
2371
|
+
overwrites a file.
|
|
2372
|
+
- `rules add --group <name> --dir <path>` adds a root group, with optional
|
|
2373
|
+
`--workflows` and `--steps` filters. ww never rewrites
|
|
2374
|
+
`ww.yaml`: the group goes into `ww-rules.yaml` next to
|
|
2375
|
+
it, a file ww owns and rewrites whole, and the repo file gains one entry
|
|
2376
|
+
under `imports` the first time, checked to change nothing else.
|
|
2377
|
+
- `rules edit <id> --text ... --paths ...` replaces a rule file's body or
|
|
2378
|
+
globs and keeps every other line. A new wording has a new hash, so the
|
|
2379
|
+
command says which rule-automation store entry stops matching and, when
|
|
2380
|
+
the old wording had an approved check, that `rules promote` keeps it.
|
|
2381
|
+
- `rules move <id> <group>` moves the file, unchanged, into another group's
|
|
2382
|
+
directory; its wording, and so what the store knows about it, stays.
|
|
2383
|
+
- `rules filter <group> --workflows ... --steps ...` changes where a group of
|
|
2384
|
+
`ww-rules.yaml` applies (`--workflows '*'` writes `"*"`; `--all-workflows`
|
|
2385
|
+
and `--all-steps` remove a filter, which also means all); a group declared elsewhere is yours, and the command says what to
|
|
2386
|
+
write there.
|
|
2387
|
+
- `rules promote <check>` copies an approved store check into the `check`
|
|
2388
|
+
frontmatter of every rule file it covers and removes the check and those
|
|
2389
|
+
rules' entries from the store, since a rule with its own command is never
|
|
2390
|
+
looked up there. It refuses a check that is not converted, or that has a
|
|
2391
|
+
pending revision an earlier ww left, and a rule written in a step's own
|
|
2392
|
+
`rules` list.
|
|
2393
|
+
|
|
2394
|
+
Each command ends with the steps the rule or group now reaches. `ww rules`
|
|
2395
|
+
names the approved store check of a rule without a command of its own
|
|
2396
|
+
(`store_check` in `--json`), which is what a promotion would copy.
|
|
2397
|
+
|
|
2398
|
+
In the `auto` runtime a worker that keeps asking for its page after its
|
|
2399
|
+
assignment ended is not given the manager's own work: its assignment token no
|
|
2400
|
+
longer opens anything (see [Assignment tokens](#assignment-tokens)), and for a
|
|
2401
|
+
`role: manager` or interactive step `instruction --role worker` names the step
|
|
2402
|
+
as the manager's and offers no completion command.
|
|
2403
|
+
|
|
2404
|
+
### Rules from extensions
|
|
2405
|
+
|
|
2406
|
+
An extension may ship rule groups through its `rules`, each a
|
|
2407
|
+
`RuleGroupContribution` naming the group, its absolute paths or other group
|
|
2408
|
+
names, and optional filters. A project gets them by listing the extension in
|
|
2409
|
+
the root `ww.json` `extensions`, even with an empty section;
|
|
2410
|
+
a group name also declared in the YAML is an error.
|
|
2411
|
+
|
|
2412
|
+
`ww lint` ends with `Rules: N groups, M rules` when the configuration declares
|
|
2413
|
+
any, after a notice for each absolute rule path.
|
|
2414
|
+
|
|
2415
|
+
## Workflow hooks, variables, and transitions
|
|
2416
|
+
|
|
2417
|
+
### Workflow hook scopes and ordering
|
|
2418
|
+
|
|
2419
|
+
These are workflow hooks, the lifecycle phases of `ww.yaml`;
|
|
2420
|
+
the agent's own hooks, installed with `ww hook`, are
|
|
2421
|
+
[agent hooks](#agent-hooks). Workflow hooks share the same handler syntax. Global hooks can filter with `workflows`
|
|
2422
|
+
and, for step lifecycle phases, `steps`; workflow hooks can filter step lifecycle
|
|
2423
|
+
phases with `steps`; step hooks cannot use either filter. The workflow boundary
|
|
2424
|
+
positions are `before_start_workflow` and `before_complete_workflow`. They run
|
|
2425
|
+
once in global → workflow order, cannot use `steps`, and cannot be declared at
|
|
2426
|
+
step scope. Step lifecycle positions are `before_start`,
|
|
2427
|
+
`before_complete`, and `after_complete`, and run in global → workflow → step
|
|
2428
|
+
order for every matching step.
|
|
2429
|
+
Step filters accept bare names at any nesting level or slash-separated logical
|
|
2430
|
+
paths such as `plan-and-fix/fix` when only one substep should match.
|
|
2431
|
+
Hooks, rule groups and automatic modes take one filter shape: `workflows` and `steps` are each
|
|
2432
|
+
`"*"` for all or a list of names (`workflows: [task, bugfix]`); a bare name is
|
|
2433
|
+
not a list, and `"*"` cannot be mixed with names. One difference: `[]` on a
|
|
2434
|
+
hook means all, like omission, while on a rule group it means the group applies
|
|
2435
|
+
only where a step names it. The
|
|
2436
|
+
[specification](specification.md#workflow-and-step-filters) has the table.
|
|
2437
|
+
|
|
2438
|
+
A singular hook is written directly as the normal handler shape; there is no
|
|
2439
|
+
`handler` wrapper. A name-only mapping references a catalog handler, while
|
|
2440
|
+
action keys define an inline handler. Use `handlers` only to run multiple
|
|
2441
|
+
ordered handlers under shared `workflows` and `steps` filters. Each grouped
|
|
2442
|
+
entry uses that same handler shape, including the named-entry shorthand, and may
|
|
2443
|
+
carry its own action selection. Put `handoff_to: "{{workflow}}"` directly on a
|
|
2444
|
+
hook to declare a transition.
|
|
2445
|
+
|
|
2446
|
+
### Variables and metadata
|
|
2447
|
+
|
|
2448
|
+
Interpolations use `{{name}}`, with double braces everywhere. Every value ww
|
|
2449
|
+
provides lives under `ww.`, so a name without it is always a variable a step
|
|
2450
|
+
handed back: `{{ww.task.id}}` is the task's ID and `{{ww.task.workflows}}` the
|
|
2451
|
+
ordered workflow-name list, joined by commas.
|
|
2452
|
+
The core `{{ww.task.workspace_dir}}` variable is always the canonical directory for
|
|
2453
|
+
the task: the project root by default, the configured project directory when a
|
|
2454
|
+
task was started with `--project`, or the selected task checkout when an
|
|
2455
|
+
extension such as `ww/git` supplies one. A step or handler with a
|
|
2456
|
+
[`workdir`](#choosing-where-a-step-works) other than `task` reads the directory
|
|
2457
|
+
it chose instead. `{{ww.project.name}}` is that project's name
|
|
2458
|
+
and `{{ww.project.dir}}` its directory, both empty for a task in the root;
|
|
2459
|
+
`{{ww.project.dir}}` keeps pointing at the project even after a worktree moves
|
|
2460
|
+
the task workspace. `{{ww.project.names}}` lists every configured project name, joined
|
|
2461
|
+
by commas. `{{ww.executable}}` is how the commands ww prints invoke ww, `./ww`
|
|
2462
|
+
or the configured [`executable`](#choosing-the-ww-binary), so a step's text can
|
|
2463
|
+
name a ww command as ww's own pages do: ``Run `{{ww.executable}} onboarding` ``.
|
|
2464
|
+
Values under `{{ww.<namespace>.*}}` come from a configured extension, such
|
|
2465
|
+
as [`ww/git`'s branch](#template-values-from-wwgit); a variable may not be
|
|
2466
|
+
named `ww` or start with `ww.` (or `__`).
|
|
2467
|
+
A handler's `variables` list declares what the step hands back, read later as
|
|
2468
|
+
`{{name}}`. Each entry supports either `name` plus an optional `description`,
|
|
2469
|
+
or the same compact `name: description` shorthand as handlers and steps, and
|
|
2470
|
+
the performer passes it with `complete --variable name=<value>`. A bare string,
|
|
2471
|
+
`- name`, is a value an automatic action returns itself. A step's values are
|
|
2472
|
+
available to its completion hooks and later plan items.
|
|
2473
|
+
|
|
2474
|
+
When several automatic completion hooks request the same variable with the
|
|
2475
|
+
same description, ww asks for it once and gives that value to each hook.
|
|
2476
|
+
For example, project and workflow commit hooks share one `commit_message`.
|
|
2477
|
+
Different descriptions for the same name are a conflict: the error names the
|
|
2478
|
+
variable and both requesting plan items. A supplied `--variable` still appears
|
|
2479
|
+
only once in the completion command.
|
|
2480
|
+
|
|
2481
|
+
```yaml
|
|
2482
|
+
variables:
|
|
2483
|
+
- workflow: The workflow name corresponding to one of {{ww.task.workflows}}.
|
|
2484
|
+
```
|
|
2485
|
+
|
|
2486
|
+
Use `saves` on an agent-owned handler or step to retain values. Each entry is a
|
|
2487
|
+
prefixed path with an instruction: `metadata.<path>` keeps a value across
|
|
2488
|
+
workflow runs of one task, `project_metadata.<path>` shares it with every task
|
|
2489
|
+
in the project, `documents.<name>` updates a [document](#documents), and
|
|
2490
|
+
`item.field.<name>` sets a [custom item field](#custom-item-fields). The path
|
|
2491
|
+
after the prefix is the storage path, and the prefix is the scope:
|
|
2492
|
+
|
|
2493
|
+
```yaml
|
|
2494
|
+
steps:
|
|
2495
|
+
- name: create-jira
|
|
2496
|
+
mcp: jira
|
|
2497
|
+
description: Create the issue and retain its ID.
|
|
2498
|
+
saves:
|
|
2499
|
+
- metadata.integrations.jira.issue_id: The ID of the created Jira issue.
|
|
2500
|
+
- name: inspect-jira
|
|
2501
|
+
description: Inspect {{ww.metadata.integrations.jira.issue_id}}.
|
|
2502
|
+
```
|
|
2503
|
+
|
|
2504
|
+
The completion instruction includes every required value as a named argument,
|
|
2505
|
+
its path written as in `saves` without the `metadata.` prefix:
|
|
2506
|
+
|
|
2507
|
+
```console
|
|
2508
|
+
ww-agentic-workflows complete TASK-123 --role worker \
|
|
2509
|
+
--metadata integrations.jira.issue_id="PROJ-456" --artifact="<result>"
|
|
2510
|
+
```
|
|
2511
|
+
|
|
2512
|
+
Shell and argv handlers automatically retain stdout when they declare
|
|
2513
|
+
metadata entries in `saves`. For example, this handler finds an existing PR or
|
|
2514
|
+
creates one, then stores its URL for later steps:
|
|
2515
|
+
|
|
2516
|
+
```yaml
|
|
2517
|
+
handlers:
|
|
2518
|
+
- create-github-pr: ~
|
|
2519
|
+
shell: |-
|
|
2520
|
+
set -eu
|
|
2521
|
+
branch=$1
|
|
2522
|
+
base=$2
|
|
2523
|
+
repo=$(gh repo view --json nameWithOwner --jq .nameWithOwner)
|
|
2524
|
+
url=$(gh pr list --repo "$repo" --head "$branch" --base "$base" \
|
|
2525
|
+
--state open --json url --jq '.[0].url // empty')
|
|
2526
|
+
if [ -z "$url" ]; then
|
|
2527
|
+
url=$(gh pr create --repo "$repo" --head "$branch" --base "$base" --fill)
|
|
2528
|
+
fi
|
|
2529
|
+
printf '%s\n' "$url"
|
|
2530
|
+
args: ["{{ww.git.branch}}", "{{ww.git.base_branch}}"]
|
|
2531
|
+
saves:
|
|
2532
|
+
- metadata.github.pr_url: The pull request URL.
|
|
2533
|
+
```
|
|
2534
|
+
|
|
2535
|
+
Later steps use `{{ww.metadata.github.pr_url}}`. ww saves the full stdout with
|
|
2536
|
+
outer whitespace removed only after successful execution and assertions.
|
|
2537
|
+
Send diagnostic messages to stderr to keep them out of the saved value.
|
|
2538
|
+
`project_metadata.<path>` and `append: true` also work: an append entry receives
|
|
2539
|
+
the whole output as one list value. Interrupted publication resumes from the
|
|
2540
|
+
committed result without rerunning the command.
|
|
2541
|
+
|
|
2542
|
+
Metadata is task-scoped rather than workflow-scoped. `ww` stores it as a nested
|
|
2543
|
+
object under `metadata` in `.ww/tasks/<task-id>/metadata.json`; a later run can use the
|
|
2544
|
+
same `{{ww.metadata.<path>}}` reference. Metadata leaves are strings. A new value
|
|
2545
|
+
may replace the same path, while a leaf/object path collision is rejected.
|
|
2546
|
+
Inspect the complete object as JSON with `ww-agentic-workflows metadata TASK-123`.
|
|
2547
|
+
|
|
2548
|
+
A leaf declared with `append: true` is a list that grows across completions and
|
|
2549
|
+
runs. Each completion passes the path once per value, or not at all, and ww
|
|
2550
|
+
appends the values to what is stored, dropping repeats, without touching other
|
|
2551
|
+
keys. The leaf interpolates as a comma-separated list, and as an empty string
|
|
2552
|
+
before anything was saved, so a prompt never shows a raw placeholder. This is
|
|
2553
|
+
how one task remembers the pull request threads it already handled across
|
|
2554
|
+
several review passes:
|
|
2555
|
+
|
|
2556
|
+
```yaml
|
|
2557
|
+
- process_pull_request: Create one work item per unresolved reviewer thread.
|
|
2558
|
+
items:
|
|
2559
|
+
report: Reply in the thread and resolve it.
|
|
2560
|
+
saves:
|
|
2561
|
+
- metadata.pull_request.handled_comments: The root comment id of the thread you just resolved.
|
|
2562
|
+
append: true
|
|
2563
|
+
- get_pull_request_comments: >-
|
|
2564
|
+
Threads whose root comment id is in {{ww.metadata.pull_request.handled_comments}}
|
|
2565
|
+
are already handled; list them as needing no work.
|
|
2566
|
+
```
|
|
2567
|
+
|
|
2568
|
+
```console
|
|
2569
|
+
ww-agentic-workflows complete TASK-123 --role worker \
|
|
2570
|
+
--metadata pull_request.handled_comments=4711 \
|
|
2571
|
+
--metadata pull_request.handled_comments=4718 \
|
|
2572
|
+
--artifact="<result>" --summary="<handover>"
|
|
2573
|
+
```
|
|
2574
|
+
|
|
2575
|
+
Under `items`, `saves` belongs to the built-in `handle-item` stage; with
|
|
2576
|
+
configured stages, declare it on the stage that produces the value.
|
|
2577
|
+
|
|
2578
|
+
Use a `project_metadata.<path>` entry for values shared by every task in the
|
|
2579
|
+
project. Project metadata has an explicit interpolation namespace so the
|
|
2580
|
+
ownership of a value is visible where it is consumed:
|
|
2581
|
+
|
|
2582
|
+
```yaml
|
|
2583
|
+
steps:
|
|
2584
|
+
- name: discover-environment
|
|
2585
|
+
description: Determine the shared staging URL.
|
|
2586
|
+
saves:
|
|
2587
|
+
- project_metadata.environments.staging.url: The staging environment URL.
|
|
2588
|
+
- name: deploy
|
|
2589
|
+
description: Deploy to {{ww.project_metadata.environments.staging.url}}.
|
|
2590
|
+
```
|
|
2591
|
+
|
|
2592
|
+
The completion command still uses `--metadata`, with the path written as in
|
|
2593
|
+
`saves`: `--metadata project_metadata.environments.staging.url=<value>`. Project metadata is stored as nested JSON in
|
|
2594
|
+
`.ww/metadata.json` and can be inspected with
|
|
2595
|
+
`ww-agentic-workflows metadata --project`. Project metadata is resolved live;
|
|
2596
|
+
copy a value into task metadata when a task needs a stable snapshot. The `ww.`
|
|
2597
|
+
namespace of project metadata is ww's own, holding its [onboarding
|
|
2598
|
+
state](#onboarding-state); a `saves` entry under `project_metadata.ww.` is a
|
|
2599
|
+
configuration error. Metadata
|
|
2600
|
+
is plain runtime state and should not be used for secrets.
|
|
2601
|
+
|
|
2602
|
+
Every workflow also receives ww's built-in `update-workflow-summary` handler as
|
|
2603
|
+
its final `before_complete_workflow` action. It asks the agent for a concise
|
|
2604
|
+
goal/result summary, and ww writes that value to the task run ledger when the
|
|
2605
|
+
run completes. No `ww.yaml` configuration is needed. Its instruction
|
|
2606
|
+
lists every ordinary step's handover of this run in order, each with its
|
|
2607
|
+
artifact, and tells the agent to build the summary from those alone, so the
|
|
2608
|
+
summary cannot borrow counts or statuses from other runs or stale material. It
|
|
2609
|
+
requests the run's ordinary worker selection (`auto`), unlike `init`, which
|
|
2610
|
+
requests `cheapest` / `low`; `builtins.workflow_summary` in
|
|
2611
|
+
`ww.json` overrides that.
|
|
2612
|
+
|
|
2613
|
+
## Interactive steps
|
|
2614
|
+
|
|
2615
|
+
Some work is a conversation with the person operating ww: agreeing an
|
|
2616
|
+
architecture, or performing a manual test case and reporting what happened.
|
|
2617
|
+
Mark such a step `interactive: true`:
|
|
2618
|
+
|
|
2619
|
+
```yaml
|
|
2620
|
+
- think_about_architecture: >-
|
|
2621
|
+
Set up the architecture. Discuss it with the operator: listen, ask
|
|
2622
|
+
questions, clarify edge cases.
|
|
2623
|
+
interactive: true
|
|
2624
|
+
```
|
|
2625
|
+
|
|
2626
|
+
The conversation comes first and is held in the session that can talk to the
|
|
2627
|
+
operator. The agent presents, asks, listens, responds to questions and
|
|
2628
|
+
corrections, and records nothing while they talk. Clear contextual completion,
|
|
2629
|
+
such as "done", "I'm done", "looks good, continue", or an appropriate final
|
|
2630
|
+
choice, lets the agent finish; if intent is ambiguous, it asks naturally
|
|
2631
|
+
whether to continue or finish. "Done for today" can mean pause, leaving the
|
|
2632
|
+
interaction open to resume later. One command records both sides, verbatim,
|
|
2633
|
+
and ends the interaction:
|
|
2634
|
+
|
|
2635
|
+
```console
|
|
2636
|
+
ww-agentic-workflows interact TASK-123 --role manager --transcript - --end <<'EOF'
|
|
2637
|
+
Agent: Proposed: a listener dispatches a queued job per upload.
|
|
2638
|
+
Operator: Resize through the queue; upload never waits.
|
|
2639
|
+
Agent: Agreed: the upload returns before any image work starts.
|
|
2640
|
+
EOF
|
|
2641
|
+
```
|
|
2642
|
+
|
|
2643
|
+
`--transcript` reads a file, or stdin with `-`. A line starting with `Agent:`
|
|
2644
|
+
or `Operator:` (also bold, as `**Operator:**`, in any case) starts an entry,
|
|
2645
|
+
and the lines up to the next marker belong to it; text before the first
|
|
2646
|
+
marker is refused. The entries keep their order and carry one recording time.
|
|
2647
|
+
`--operator-said` and `--agent-said` record a single entry, and `--end` alone
|
|
2648
|
+
ends an interaction whose entries are already recorded.
|
|
2649
|
+
|
|
2650
|
+
Every entry is appended to one file per task,
|
|
2651
|
+
`.ww/tasks/<task-id>/interactions.md`, never rewritten, each headed by the time,
|
|
2652
|
+
run, step, the item when the step is a per-item stage, and speaker, so the file
|
|
2653
|
+
reads as the task's whole history of operator involvement;
|
|
2654
|
+
`ww-agentic-workflows interactions TASK-123` prints it. The step's page shows
|
|
2655
|
+
the conversation recorded for it so far.
|
|
2656
|
+
Completing an interactive step is refused until at least one entry was recorded
|
|
2657
|
+
and the interaction was ended. The step's artifact and handover are written as
|
|
2658
|
+
usual; what goes into them is the agent's judgement.
|
|
2659
|
+
|
|
2660
|
+
For work where the operator wants to follow each action and edit, set
|
|
2661
|
+
`explicit: true` on a workflow or step. The agent describes each meaningful
|
|
2662
|
+
operation before it begins and shows the concrete edits for every changed file
|
|
2663
|
+
afterward. Large edits can use a focused diff artifact; the agent still names
|
|
2664
|
+
each changed file and redacts secrets. Structural groups, loops, item stages,
|
|
2665
|
+
and child stages inherit the setting, and a nested step can turn it off with
|
|
2666
|
+
`explicit: false`. The compiled plan preserves the resolved value across
|
|
2667
|
+
resumption. WW-owned automatic handlers continue to show their command and
|
|
2668
|
+
result through ww's existing output.
|
|
2669
|
+
|
|
2670
|
+
When the operator's answer is one of a few outcomes, declare them as `choices`
|
|
2671
|
+
and ww turns them into a real pick rather than free text:
|
|
2672
|
+
|
|
2673
|
+
```yaml
|
|
2674
|
+
items:
|
|
2675
|
+
analyze: Show the test case to the operator.
|
|
2676
|
+
interactive: true
|
|
2677
|
+
choices:
|
|
2678
|
+
- pass: The test case passed.
|
|
2679
|
+
- fail: The test case failed; no comment.
|
|
2680
|
+
- fail and give comment: The test case failed; the operator explains why.
|
|
2681
|
+
- skip: Skip this test case.
|
|
2682
|
+
```
|
|
2683
|
+
|
|
2684
|
+
The page lists choices in order and tells the agent to use the host's native
|
|
2685
|
+
choice tool when available, following its actual schema, and otherwise show a
|
|
2686
|
+
numbered list in chat. For Codex, inspect the available question-tool schema
|
|
2687
|
+
and use its supported structured options when offered; use a text-only
|
|
2688
|
+
question only when required by that tool. Keep the pick pending until the
|
|
2689
|
+
operator explicitly answers. A timeout, dismissal, or preselected value is not
|
|
2690
|
+
an answer. The named tools for other
|
|
2691
|
+
integrations include `AskUserQuestion` in Claude Code, `ask_user` in Gemini
|
|
2692
|
+
CLI, `AskQuestion` in Cursor, `ask_question` in Antigravity, and
|
|
2693
|
+
`ask_user_question` in Grok CLI. Kimi, DeepSeek, and custom agents get the
|
|
2694
|
+
numbered list. The tool names come from the
|
|
2695
|
+
[askmux](https://github.com/iShaldam/askmux) question-tool matrix (MIT,
|
|
2696
|
+
Copyright (c) 2026 iShaldam) and the Gemini CLI documentation. The pick goes in the
|
|
2697
|
+
same recording call, `interact --transcript - --choice "<label or number>"
|
|
2698
|
+
--end`, a comment the operator adds is part of the transcript, and ending the
|
|
2699
|
+
interaction is refused until a choice was recorded. The chosen label is kept on the step record and in the interactions
|
|
2700
|
+
file.
|
|
2701
|
+
|
|
2702
|
+
The limitation to know: a delegated worker is a subagent and cannot talk to the
|
|
2703
|
+
operator. An interactive step is therefore always performed by the session that
|
|
2704
|
+
holds the conversation, the manager in the `auto` runtime: it is a
|
|
2705
|
+
`role: manager` step, and the step's profile, agent, model, and reasoning
|
|
2706
|
+
are ignored. For a manual-testing workflow, put `interactive: true` on the
|
|
2707
|
+
per-item stage: the manager presents each test case, waits for the operator's
|
|
2708
|
+
result, records it, ends the interaction, and completes the item.
|
|
2709
|
+
|
|
2710
|
+
When a session ends in the middle of a conversation, the `interrupt` hook
|
|
2711
|
+
recovers what it can (see [Agent hooks](agent-hooks.md)). For Claude Code and
|
|
2712
|
+
Codex it reads the session's own transcript file, keeps the operator's typed
|
|
2713
|
+
messages and the agent's text replies since the step's attempt started, and
|
|
2714
|
+
appends them to the interactions file under the speakers `operator (recovered)`
|
|
2715
|
+
and `agent (recovered)`. The next session's notice says how many entries were
|
|
2716
|
+
recovered, and the step's page shows them, so the agent continues from the
|
|
2717
|
+
last unanswered point. The transcript formats are the agents' internal ones,
|
|
2718
|
+
so recovery is best effort; for other agents the conversation is not recorded
|
|
2719
|
+
and the notice says to ask the operator where they were.
|
|
2720
|
+
|
|
2721
|
+
### The operator page
|
|
2722
|
+
|
|
2723
|
+
A per-item stage declared with `interactive: page` is answered in the
|
|
2724
|
+
browser, on an answer sheet over every item of the run, and ww applies the
|
|
2725
|
+
answers itself. It is valid on one per-item stage per `items` step, because the page is shaped for items: other structures would
|
|
2726
|
+
need a page of their own, and none exists yet.
|
|
2727
|
+
|
|
2728
|
+
```yaml
|
|
2729
|
+
items:
|
|
2730
|
+
analyze: Show the test case to the operator.
|
|
2731
|
+
interactive: page
|
|
2732
|
+
choices:
|
|
2733
|
+
- pass: The test case passed.
|
|
2734
|
+
- fail: The test case failed; the operator explains why.
|
|
2735
|
+
```
|
|
2736
|
+
|
|
2737
|
+
The page is an extra on top of the engine. The core knows it by
|
|
2738
|
+
`interactive: page` alone: the stage's page tells the agent to run one command, and
|
|
2739
|
+
everything else lives in the `operator_ui` package, which drives the task
|
|
2740
|
+
only through the public commands an agent uses.
|
|
2741
|
+
|
|
2742
|
+
```console
|
|
2743
|
+
ww-agentic-workflows interact TASK-123 --role manager --await
|
|
2744
|
+
```
|
|
2745
|
+
|
|
2746
|
+
The command applies any answers left over from an earlier wait, serves the
|
|
2747
|
+
page on `127.0.0.1` for as long as it runs, opens the browser unless a tab is
|
|
2748
|
+
already polling, waits, and then applies what was answered. It returns when
|
|
2749
|
+
every item is answered, when the operator presses "I'm done for now", when
|
|
2750
|
+
the operator closes the page, or after `WW_OPERATOR_WAIT` seconds with
|
|
2751
|
+
nothing new. The port is derived from the task ID, so the tab survives
|
|
2752
|
+
between waits and reconnects by itself; `WW_OPERATOR_PORT` fixes it. There
|
|
2753
|
+
is no daemon. After the step's page, the command prints how the wait ended,
|
|
2754
|
+
which items it applied, how many are answered, and whether to wait again or
|
|
2755
|
+
stop.
|
|
2756
|
+
|
|
2757
|
+
How the agent runs the wait depends on what its shell can do, and the step's
|
|
2758
|
+
page says which, from the same kind of per-agent table as the choice
|
|
2759
|
+
mechanisms. Claude Code has a background shell, so the page tells it to run
|
|
2760
|
+
the command with `run_in_background` and go on with the conversation: the
|
|
2761
|
+
operator can talk to the agent in the session while the page is open, and
|
|
2762
|
+
the command's output reaches the agent when the wait ends. Such a wait may
|
|
2763
|
+
be long, so the page puts `WW_OPERATOR_WAIT=1800` in front of the command.
|
|
2764
|
+
While it runs, the agent must not run commands that change the task, because
|
|
2765
|
+
the wait applies the answers the moment it ends; `status` and `instruction`
|
|
2766
|
+
are fine. Any other agent blocks on the command, so the default wait is `90`
|
|
2767
|
+
seconds, under its shell timeout, and the agent runs it again while items
|
|
2768
|
+
remain.
|
|
2769
|
+
|
|
2770
|
+
The page lists every item with its text and analysis. The operator answers
|
|
2771
|
+
the items in any order, each with the stage's choices when it declares them
|
|
2772
|
+
and a comment, and can revise an answer until ww has applied it. An answer
|
|
2773
|
+
is recorded the moment it is given, as one atomic replacement of the answer
|
|
2774
|
+
sheet, `.ww/operator-ui/<task-id>.json`, under the task lock and before the
|
|
2775
|
+
browser is told it went through. It does not touch the task's state. A
|
|
2776
|
+
failed submission comes back to the page as an error next to the item and
|
|
2777
|
+
the draft stays; an answer given while no agent is waiting is kept in the
|
|
2778
|
+
tab and sent when a wait is back, and is lost if the tab is closed first.
|
|
2779
|
+
Drafts survive a reload. The sheet remembers when its task was created, so a
|
|
2780
|
+
sheet left behind by a reset task is discarded rather than applied to the
|
|
2781
|
+
task that reuses the ID.
|
|
2782
|
+
|
|
2783
|
+
Applying walks the plan in order from the current stage. For every
|
|
2784
|
+
`interactive: page` stage whose item has an answer on the sheet, ww records the pick and the
|
|
2785
|
+
comment and ends the interaction with `interact`, writes the answer onto
|
|
2786
|
+
the item with `update-item` as its `actual_solution` (the pick, then the
|
|
2787
|
+
comment after a colon) and marks it resolved for a `handle-item` or
|
|
2788
|
+
`item_phase: resolve` stage and reported for a `handle-item` or
|
|
2789
|
+
`item_phase: report` stage, completes the stage with `complete` and an artifact written from the
|
|
2790
|
+
answer, and only then takes the answer off the sheet, so a wait that is
|
|
2791
|
+
killed mid-way leaves at most a stale entry that the next wait drops. A
|
|
2792
|
+
document the stage promised to update is the one thing ww cannot write: the
|
|
2793
|
+
printed result names such documents and tells the agent to record the
|
|
2794
|
+
answers of the applied items in them first. When the operator closes the
|
|
2795
|
+
page, the result tells the agent not to open it again on its own. The walk stops at the first stage whose item has no answer, at any
|
|
2796
|
+
stage that is not an `interactive: page` stage, and wherever ww needs the agent. Items are
|
|
2797
|
+
therefore completed one by one in plan order whatever order the operator
|
|
2798
|
+
answered in; an answer for a later item waits on the sheet. The applied
|
|
2799
|
+
answer is what an agent would have recorded: the stage's chosen label, the
|
|
2800
|
+
comment in the interactions file, and the artifact. The agent's loop is:
|
|
2801
|
+
wait, read the printed result, wait again while items remain.
|
|
2802
|
+
|
|
2803
|
+
A pause is the operator saying they are done for now, from the page or
|
|
2804
|
+
recorded by the agent with `interact --pause`. It is kept on the task, and
|
|
2805
|
+
the page's pause is recorded after the answers given before it were applied.
|
|
2806
|
+
The step's page then tells the agent to stop, without waiting again, and to
|
|
2807
|
+
show the step with `instruction` when the operator returns. Only the
|
|
2808
|
+
operator's own words lift a pause: an answer on the page or an
|
|
2809
|
+
operator entry, whether from a transcript, `--operator-said` or `--choice`, not the
|
|
2810
|
+
agent's words and not the ending of a step.
|
|
2811
|
+
|
|
2812
|
+
## Documents
|
|
2813
|
+
|
|
2814
|
+
Metadata holds small values that steer a workflow. A document is the durable,
|
|
2815
|
+
free-format counterpart: a file a workflow builds up and returns to across
|
|
2816
|
+
runs, such as the test cases derived from an issue that a second run refines
|
|
2817
|
+
after the issue changed and a `build-test-report` workflow reads later.
|
|
2818
|
+
Declare documents once at the root; a task-scoped document lives under the
|
|
2819
|
+
task directory, a `scope: project` one under `.ww/documents`, and a `scope:
|
|
2820
|
+
user` one in the user configuration directory, shared by every project of the
|
|
2821
|
+
user, unless `path` places it elsewhere in the
|
|
2822
|
+
project (for a user document, elsewhere in the user directory), for example
|
|
2823
|
+
`documentation/issues/{{ww.task.id}}/notes.md`, which then resolves inside the
|
|
2824
|
+
task's worktree when the run has one:
|
|
2825
|
+
|
|
2826
|
+
```yaml
|
|
2827
|
+
documents:
|
|
2828
|
+
- test_cases: The test cases derived from the issue, kept current across runs.
|
|
2829
|
+
workflows:
|
|
2830
|
+
- name: derive-tests
|
|
2831
|
+
steps:
|
|
2832
|
+
- derive: Analyze the issue and derive test cases.
|
|
2833
|
+
saves:
|
|
2834
|
+
- documents.test_cases: One checklist item per case; keep items that still hold.
|
|
2835
|
+
- name: build-test-report
|
|
2836
|
+
steps:
|
|
2837
|
+
- report: Build the report from {{ww.documents.test_cases}}.
|
|
2838
|
+
```
|
|
2839
|
+
|
|
2840
|
+
A step whose `saves` names a `documents.<name>` entry sees a "Documents to update" section
|
|
2841
|
+
naming each document, its absolute path, whether it exists yet, and the
|
|
2842
|
+
instruction. Its worker edits the file in place, the one exception to the rule
|
|
2843
|
+
against writing under `.ww`. On completion ww checks that each promised file
|
|
2844
|
+
exists and journals who updated it, run, step, time, and content hash, in the
|
|
2845
|
+
task's `documents.json` or the project's. `{{ww.documents.<name>}}` resolves to the
|
|
2846
|
+
absolute path for the current filesystem, like the worktree path does, so a
|
|
2847
|
+
task started on the host reads correctly inside a container. `reset` removes a
|
|
2848
|
+
task's journal and the documents kept under its `.ww` directory, as it removes
|
|
2849
|
+
the task's metadata; a document declared with a `path` is the workflow's own
|
|
2850
|
+
file and stays in place.
|
|
2851
|
+
|
|
2852
|
+
`ww-agentic-workflows documents TASK-123` lists every declared document with
|
|
2853
|
+
its scope, path, whether it exists, and its last update; without a task ID it
|
|
2854
|
+
lists the project-scoped ones.
|
|
2855
|
+
|
|
2856
|
+
## Artifacts and dependencies
|
|
2857
|
+
|
|
2858
|
+
Agent-owned steps save their full Markdown result as an artifact by default.
|
|
2859
|
+
Use `artifact: false` for work that has no durable output. A loop wrapper also
|
|
2860
|
+
saves the final result supplied by its successful stop command as its main
|
|
2861
|
+
artifact; `artifact: false` on the wrapper disables that file independently of
|
|
2862
|
+
its body-step artifacts. `artifact_from` names an earlier artifact-producing step
|
|
2863
|
+
and adds that artifact to the later step's instruction; execution still
|
|
2864
|
+
follows the written step order.
|
|
2865
|
+
|
|
2866
|
+
A nested step, in a group, a loop body, an assessment outcome, or a per-item
|
|
2867
|
+
stage, may also name an earlier step of any enclosing level. The nearest match
|
|
2868
|
+
wins: an earlier sibling first, then an earlier step of the parent's level,
|
|
2869
|
+
and so on up to the workflow's top-level steps and `init`. An outcome may name
|
|
2870
|
+
its assessment and a per-item stage its `items` step, whose work has finished
|
|
2871
|
+
by then, and gets that step's own artifact; a loop body cannot name its own
|
|
2872
|
+
running loop. The instruction names the dependency by its step path, such as
|
|
2873
|
+
`review/check`, which is how `ww artifacts` lists it.
|
|
2874
|
+
|
|
2875
|
+
`artifact_from` may also name a plain group, which saves no artifact of its
|
|
2876
|
+
own, or an assessment from a step after it. The later step then gets the
|
|
2877
|
+
artifact of the latest step inside it that saved one in its current round:
|
|
2878
|
+
for an assessment, a step of the outcome that was chosen. Loop rounds,
|
|
2879
|
+
per-item stages, and per-child stages inside count, so the latest round's
|
|
2880
|
+
artifact wins, and a loop wrapper that saves its own result counts when it
|
|
2881
|
+
completes. A container that is itself inside a loop counts only the current
|
|
2882
|
+
iteration of that loop: an artifact an earlier round saved is never handed
|
|
2883
|
+
over. The instruction names that step and gives the artifact's file path.
|
|
2884
|
+
When the chosen outcome saved nothing, for example an outcome with no steps of
|
|
2885
|
+
its own, an assessment supplies its own artifact if it saved one. Otherwise
|
|
2886
|
+
the instruction
|
|
2887
|
+
says that no artifact is available and the step goes on without one.
|
|
2888
|
+
Validation accepts such a dependency only when some path inside it can save an
|
|
2889
|
+
artifact; an assessment whose outcomes cannot supplies its own answer.
|
|
2890
|
+
|
|
2891
|
+
```yaml
|
|
2892
|
+
handlers:
|
|
2893
|
+
- name: analyse
|
|
2894
|
+
steps:
|
|
2895
|
+
- assess:
|
|
2896
|
+
question: Is the cause clear?
|
|
2897
|
+
outcomes:
|
|
2898
|
+
positive: {handler: investigate}
|
|
2899
|
+
negative: {handler: research}
|
|
2900
|
+
workflows:
|
|
2901
|
+
- name: medium-task
|
|
2902
|
+
steps:
|
|
2903
|
+
- name: analysis
|
|
2904
|
+
handler: analyse
|
|
2905
|
+
- name: develop
|
|
2906
|
+
description: Implement the change.
|
|
2907
|
+
artifact_from: analysis # investigate's or research's artifact
|
|
2908
|
+
```
|
|
2909
|
+
|
|
2910
|
+
Filesystem artifact names begin with each step's one-based declaration ordinal
|
|
2911
|
+
among its siblings. Nested containers reset the ordinal for their children, so
|
|
2912
|
+
a workflow with `init`, `investigate`, and a `plan-and-fix` group is stored as:
|
|
2913
|
+
|
|
2914
|
+
```text
|
|
2915
|
+
steps/
|
|
2916
|
+
01-init.md
|
|
2917
|
+
02-investigate.md
|
|
2918
|
+
03-plan-and-fix/
|
|
2919
|
+
01-plan.md
|
|
2920
|
+
02-fix.md
|
|
2921
|
+
```
|
|
2922
|
+
|
|
2923
|
+
Loop bodies add an `iteration-NN` directory beneath each loop wrapper. Every
|
|
2924
|
+
round therefore keeps its own step and hook artifacts instead of overwriting
|
|
2925
|
+
the files from an earlier round:
|
|
2926
|
+
|
|
2927
|
+
```text
|
|
2928
|
+
steps/
|
|
2929
|
+
02-review-and-fix.md
|
|
2930
|
+
02-review-and-fix/
|
|
2931
|
+
iteration-01/
|
|
2932
|
+
01-review.md
|
|
2933
|
+
02-fix.md
|
|
2934
|
+
iteration-02/
|
|
2935
|
+
01-review.md
|
|
2936
|
+
```
|
|
2937
|
+
|
|
2938
|
+
These prefixes describe the workflow hierarchy, not the flat lifecycle-plan
|
|
2939
|
+
position. Hook artifacts use their plan position within the target step's
|
|
2940
|
+
`.hooks/<phase>/` directory.
|
|
2941
|
+
|
|
2942
|
+
```yaml
|
|
2943
|
+
workflows:
|
|
2944
|
+
- name: task
|
|
2945
|
+
steps:
|
|
2946
|
+
- name: research
|
|
2947
|
+
description: Investigate the change and record the findings.
|
|
2948
|
+
- name: implement
|
|
2949
|
+
description: Implement the change.
|
|
2950
|
+
artifact_from: research
|
|
2951
|
+
- name: review-and-fix
|
|
2952
|
+
loop:
|
|
2953
|
+
- name: fix
|
|
2954
|
+
description: Fix what the review found.
|
|
2955
|
+
artifact_from: research
|
|
2956
|
+
break: Nothing is left to fix.
|
|
2957
|
+
- name: notify
|
|
2958
|
+
description: Report that implementation is complete.
|
|
2959
|
+
artifact: false
|
|
2960
|
+
```
|
|
2961
|
+
|
|
2962
|
+
## Dynamic per-item workflows
|
|
2963
|
+
|
|
2964
|
+
One `items` step collects a runtime list of work items and then runs a fresh
|
|
2965
|
+
lifecycle for every item. The step's own work is the collection: the agent
|
|
2966
|
+
splits it into items with `add-item`. In the minimal form, every item then gets
|
|
2967
|
+
one built-in stage that analyzes, resolves, and reports it:
|
|
2968
|
+
|
|
2969
|
+
```yaml
|
|
2970
|
+
workflows:
|
|
2971
|
+
- name: handle-feedback
|
|
2972
|
+
steps:
|
|
2973
|
+
- review: Review the pull request.
|
|
2974
|
+
items: Split based on the comments retrieved from the Bitbucket pull request.
|
|
2975
|
+
```
|
|
2976
|
+
|
|
2977
|
+
The string is splitting guidance shown to the collecting agent. Use `items: ~`
|
|
2978
|
+
when no guidance is needed.
|
|
2979
|
+
|
|
2980
|
+
The built-in stage can take guidance for each of its phases without declaring
|
|
2981
|
+
stages. Under `items`, `analyze`, `resolve`, and `report` are
|
|
2982
|
+
strings appended to the `handle-item` prompt as "When analyzing it", "When
|
|
2983
|
+
resolving it", and "When reporting the outcome"; the item is still handled in
|
|
2984
|
+
one pass with one artifact:
|
|
2985
|
+
|
|
2986
|
+
```yaml
|
|
2987
|
+
- process_pull_request: Split the work by comment and sub-comment.
|
|
2988
|
+
items:
|
|
2989
|
+
assignment: together
|
|
2990
|
+
report: Reply in the same Bitbucket comment thread, then resolve the thread.
|
|
2991
|
+
```
|
|
2992
|
+
|
|
2993
|
+
To control the stages, list them under `items.steps`. Mark stages with
|
|
2994
|
+
`item_phase: analyze`, `item_phase: resolve`, or `item_phase: report` when they
|
|
2995
|
+
update those standard item fields. `item_phase` is valid only on an acting
|
|
2996
|
+
step inside a per-item stage (nested loops, groups, and assessment outcomes
|
|
2997
|
+
count). On an `assess` step itself, or on a step outside any per-item stage, it
|
|
2998
|
+
would do nothing, so validation and `lint` reject it: put it on the outcome
|
|
2999
|
+
steps that do the work. `steps: []` collects items without processing them, for
|
|
3000
|
+
example when a later step reads them.
|
|
3001
|
+
|
|
3002
|
+
```yaml
|
|
3003
|
+
workflows:
|
|
3004
|
+
- name: handle-feedback
|
|
3005
|
+
steps:
|
|
3006
|
+
- review: Review the pull request.
|
|
3007
|
+
items:
|
|
3008
|
+
description: Split by pull request comment.
|
|
3009
|
+
steps:
|
|
3010
|
+
- analyze: Analyze this comment.
|
|
3011
|
+
item_phase: analyze
|
|
3012
|
+
- fix: Resolve this comment.
|
|
3013
|
+
item_phase: resolve
|
|
3014
|
+
model: opus
|
|
3015
|
+
- reply: Report the outcome.
|
|
3016
|
+
item_phase: report
|
|
3017
|
+
```
|
|
3018
|
+
|
|
3019
|
+
Worker settings cascade from the `items` step, to `items`, to each stage. The
|
|
3020
|
+
step's `agent`, `model`, `reasoning`, `profile`, and `role` apply to
|
|
3021
|
+
collection and are inherited by the stages; the same keys under `items` apply
|
|
3022
|
+
only to the stages, and a stage's own value wins.
|
|
3023
|
+
|
|
3024
|
+
### One worker per item or for all items
|
|
3025
|
+
|
|
3026
|
+
In the `auto` runtime, `assignment` decides how many manager-dispatched
|
|
3027
|
+
assignments the per-item stages become. By default one worker keeps going
|
|
3028
|
+
through every stage of every item:
|
|
3029
|
+
|
|
3030
|
+
```yaml
|
|
3031
|
+
- review: Review the change and record each finding as an item.
|
|
3032
|
+
items:
|
|
3033
|
+
assignment: per_item # or together
|
|
3034
|
+
model: sonnet
|
|
3035
|
+
steps:
|
|
3036
|
+
- analyze: Analyze this finding.
|
|
3037
|
+
item_phase: analyze
|
|
3038
|
+
- fix: Resolve this finding.
|
|
3039
|
+
item_phase: resolve
|
|
3040
|
+
- reply: Report the outcome.
|
|
3041
|
+
item_phase: report
|
|
3042
|
+
```
|
|
3043
|
+
|
|
3044
|
+
- `together`, the default, keeps one worker for every stage of every item.
|
|
3045
|
+
- `per_item` keeps one worker for all stages of one item; the next item is a
|
|
3046
|
+
new assignment.
|
|
3047
|
+
- `per_step` hands every stage back to the manager.
|
|
3048
|
+
|
|
3049
|
+
The manager's preview names the scope before dispatching. The first stage of
|
|
3050
|
+
the assignment tells the worker which stages it covers and to do them one at a
|
|
3051
|
+
time. Each later stage arrives as a short instruction with only the new stage's
|
|
3052
|
+
work and its completion command, because the worker already has the role,
|
|
3053
|
+
workspace, profile, and item context. Every stage is still completed, saved,
|
|
3054
|
+
and recoverable on its own, so `next`, `status`, reloads, and interrupted-hook
|
|
3055
|
+
recovery behave exactly as with separate assignments. Because one worker
|
|
3056
|
+
performs the shared stages, a stage that requests a different agent, model,
|
|
3057
|
+
reasoning, or profile, or is the manager's (`role: manager`), starts a new assignment,
|
|
3058
|
+
exactly as a loop body step does. The setting has no effect in
|
|
3059
|
+
the `single` runtime.
|
|
3060
|
+
|
|
3061
|
+
### Several passes over the same items
|
|
3062
|
+
|
|
3063
|
+
A workflow has one item collection, and each `items` step is a pass over it.
|
|
3064
|
+
Analysis and fixes that are cheaper together can run once between passes,
|
|
3065
|
+
while every source comment is still checked and reported on its own:
|
|
3066
|
+
|
|
3067
|
+
```yaml
|
|
3068
|
+
- collect: Record one item per comment with its stable source ID.
|
|
3069
|
+
items:
|
|
3070
|
+
steps: []
|
|
3071
|
+
- analyze-together: Analyze all collected comments together.
|
|
3072
|
+
- confirm-analysis: Reuse the collected items.
|
|
3073
|
+
items:
|
|
3074
|
+
steps:
|
|
3075
|
+
- analyze: Reuse the shared analysis; confirm and fill gaps.
|
|
3076
|
+
item_phase: analyze
|
|
3077
|
+
- fix-together: Implement and verify the fixes for all analyzed items.
|
|
3078
|
+
- finish: Reuse the collected items.
|
|
3079
|
+
items:
|
|
3080
|
+
steps:
|
|
3081
|
+
- verify-resolution: Verify the result and record actual_solution.
|
|
3082
|
+
item_phase: resolve
|
|
3083
|
+
- report: Report the result for this original comment.
|
|
3084
|
+
item_phase: report
|
|
3085
|
+
```
|
|
3086
|
+
|
|
3087
|
+
Each pass expands its own stages when its collection step completes, for the
|
|
3088
|
+
items recorded by then; an item added later joins the next pass. Passes share
|
|
3089
|
+
the items' records, so the analysis written by the batch step is there for the
|
|
3090
|
+
quick analyze checkpoints, and nothing an earlier pass recorded is cleared.
|
|
3091
|
+
Inside a loop a pass is expanded again every round.
|
|
3092
|
+
|
|
3093
|
+
A pass with `steps: []` only collects or reconciles; `items: ~` stays the
|
|
3094
|
+
shorthand for a single pass with the whole built-in lifecycle.
|
|
3095
|
+
|
|
3096
|
+
Leaving a pass requires only what its stages declare: an analyze stage needs
|
|
3097
|
+
the item's analysis, a resolve stage its actual solution and `resolved`, a
|
|
3098
|
+
report stage `reported`, and the built-in `handle-item` stage both `resolved`
|
|
3099
|
+
and `reported`. So an analysis-only pass can lead into a batch fix, and a
|
|
3100
|
+
workflow that only analyzes can end there. A linked comment reuses its
|
|
3101
|
+
canonical item's analysis and fix but is reported itself. When a pass ends
|
|
3102
|
+
with an `assess` stage, the check waits for the answer: the chosen outcome's
|
|
3103
|
+
work is part of the pass, and an outcome that stops the workflow ends the run.
|
|
3104
|
+
A pass whose items still lack something stops for the operator with
|
|
3105
|
+
`operator_reason: pass_incomplete`, naming each item and what it lacks; record
|
|
3106
|
+
it with `update-item` and run `next --retry`. `next --force` is refused at a
|
|
3107
|
+
pass gate; the missing values must be recorded. An `items` step nested inside
|
|
3108
|
+
another's per-item stages is rejected.
|
|
3109
|
+
|
|
3110
|
+
Automatic saves into item fields belong to per-item stages. A command on the
|
|
3111
|
+
collecting `items` step itself cannot save item fields (one output cannot be
|
|
3112
|
+
distributed among several items), and that is rejected; every field a stage
|
|
3113
|
+
declares receives the same whole trimmed output. A report stage in which ww runs
|
|
3114
|
+
nothing, one done by the agent alone, still marks its item reported with
|
|
3115
|
+
`update-item --reported=true`.
|
|
3116
|
+
|
|
3117
|
+
`persistent`, `identity`, and `unique` describe the one collection, so the
|
|
3118
|
+
first `items` step decides them. Later passes leave them out or repeat the
|
|
3119
|
+
same values; a later pass that sets a different value fails `lint` and names
|
|
3120
|
+
both steps.
|
|
3121
|
+
|
|
3122
|
+
### Items that persist across runs
|
|
3123
|
+
|
|
3124
|
+
Some item lists are the task's, not one run's: the test cases of a manual
|
|
3125
|
+
test plan are the same in round one and round two. Declare the flow
|
|
3126
|
+
`persistent` and the items outlive the run:
|
|
3127
|
+
|
|
3128
|
+
```yaml
|
|
3129
|
+
- collect: >-
|
|
3130
|
+
Read the test cases in docs/test-cases.md. Compare them with the stored
|
|
3131
|
+
items and make the items match: add missing cases, remove cases that
|
|
3132
|
+
are gone, reword cases that changed. Keep the item ID equal to the
|
|
3133
|
+
case's heading.
|
|
3134
|
+
items:
|
|
3135
|
+
persistent: true
|
|
3136
|
+
interactive: page
|
|
3137
|
+
choices:
|
|
3138
|
+
- pass: The test case passed.
|
|
3139
|
+
- fail: The test case failed; the operator explains why.
|
|
3140
|
+
```
|
|
3141
|
+
|
|
3142
|
+
ww keeps the task's canonical list in `.ww/tasks/<task-id>/items.json` and
|
|
3143
|
+
refreshes it whenever a run adds, changes, or resolves an item, so it always
|
|
3144
|
+
holds the latest outcome of every item. Each run still keeps its own copy in
|
|
3145
|
+
its record, so round one's results stay readable after round two. A new run,
|
|
3146
|
+
whether the task is started again or a handoff enters the workflow, starts
|
|
3147
|
+
from the stored items with their outcome fields cleared: every round is a
|
|
3148
|
+
fresh round over the same cases.
|
|
3149
|
+
|
|
3150
|
+
The collection step then reconciles instead of splitting. Its page lists
|
|
3151
|
+
the stored items and carries the commands to make the list match the
|
|
3152
|
+
source: `add-item` for new cases, `remove-item` for cases that are gone,
|
|
3153
|
+
and `update-item --text` to reword one. Removing and rewording are allowed only while the
|
|
3154
|
+
collection step is in progress, and an item that other items refer to
|
|
3155
|
+
cannot be removed. Stable IDs matter: an item that changed is reworded under
|
|
3156
|
+
its ID, not replaced, so its history lines up across rounds. When nothing
|
|
3157
|
+
changed, the agent completes the step as it is. With nothing stored yet, the
|
|
3158
|
+
same page reads as a plain split.
|
|
3159
|
+
|
|
3160
|
+
```console
|
|
3161
|
+
ww-agentic-workflows remove-item TASK-123 --id case-7
|
|
3162
|
+
ww-agentic-workflows update-item TASK-123 --id case-3 --text "Upload a 25 MB image."
|
|
3163
|
+
```
|
|
3164
|
+
|
|
3165
|
+
To start over from the source, `start --fresh-items` forgets the stored
|
|
3166
|
+
items before the run begins, and the collection step splits anew. `reset`
|
|
3167
|
+
removes the store with the task.
|
|
3168
|
+
|
|
3169
|
+
### Custom item fields
|
|
3170
|
+
|
|
3171
|
+
An item carries the standard analysis and outcome fields, and any custom
|
|
3172
|
+
fields a workflow needs: the ID of the source comment, the ID of the reply
|
|
3173
|
+
posted for it, a flag. Values are strings. They are set with the item
|
|
3174
|
+
commands, several in one call, and read back with `item` or `items`:
|
|
3175
|
+
|
|
3176
|
+
```console
|
|
3177
|
+
ww-agentic-workflows add-item TASK-123 --id c1 --text "Rename it." --field bitbucket_comment_id=100
|
|
3178
|
+
ww-agentic-workflows update-item TASK-123 --id c1 --field bitbucket_reply_id=200 --field bitbucket_thread_resolved=true
|
|
3179
|
+
ww-agentic-workflows item TASK-123 --by bitbucket_reply_id=200
|
|
3180
|
+
```
|
|
3181
|
+
|
|
3182
|
+
A step declares the fields it sets as `item.field.<name>` entries of `saves`,
|
|
3183
|
+
beside its metadata and document entries, and ww refuses to complete the step
|
|
3184
|
+
while any of them is empty on the step's item, or on every item when the
|
|
3185
|
+
step is the collection. Such a save needs an item: it belongs on an `items`
|
|
3186
|
+
step or on a step, hook, or reused handler that runs within a per-item stage.
|
|
3187
|
+
`lint` rejects one on an ordinary step, such as a batch fix between passes,
|
|
3188
|
+
which updates items with `update-item` instead:
|
|
3189
|
+
|
|
3190
|
+
```yaml
|
|
3191
|
+
- reply: Reply in the Bitbucket thread, then resolve it.
|
|
3192
|
+
item_phase: report
|
|
3193
|
+
saves:
|
|
3194
|
+
- item.field.bitbucket_reply_id: The ID of the reply you posted.
|
|
3195
|
+
- item.field.bitbucket_thread_resolved: Set to true once the thread is resolved.
|
|
3196
|
+
```
|
|
3197
|
+
|
|
3198
|
+
Per-item stage prompts can read the stage's own item: `{{ww.item.id}}`,
|
|
3199
|
+
`{{ww.item.text}}`, `{{ww.item.field.<name>}}`, and the lifecycle values
|
|
3200
|
+
`{{ww.item.processed_item}}`, `{{ww.item.proposed_solution}}`,
|
|
3201
|
+
`{{ww.item.actual_solution}}`, `{{ww.item.resolved}}`, `{{ww.item.reported}}`,
|
|
3202
|
+
and `{{ww.item.reference_to_id}}`. `resolved` and `reported` render as `true`
|
|
3203
|
+
or `false`; every other value renders as the empty string while unset (a
|
|
3204
|
+
`reference_to_id` is empty for an item that links to none). The specification's
|
|
3205
|
+
[Item values](specification.md#item-values) is authoritative. The effective current
|
|
3206
|
+
step's `{{ww.choices}}` value is a JSON array of configured choice labels in
|
|
3207
|
+
order, or `[]` when there are none. Use it as guidance in prompts or provided-
|
|
3208
|
+
variable descriptions; ww does not validate a supplied value against labels.
|
|
3209
|
+
|
|
3210
|
+
Two flow-level rules make deduplication a refusal rather than a hope:
|
|
3211
|
+
|
|
3212
|
+
```yaml
|
|
3213
|
+
items:
|
|
3214
|
+
persistent: true
|
|
3215
|
+
identity: bitbucket_comment_id
|
|
3216
|
+
unique: [bitbucket_comment_id, bitbucket_reply_id]
|
|
3217
|
+
```
|
|
3218
|
+
|
|
3219
|
+
`identity` names the field every new item must carry. `unique` is one pool
|
|
3220
|
+
of values across the listed fields: a value may appear once over all items,
|
|
3221
|
+
in the run and, when the flow is persistent, in the task's stored items, so a
|
|
3222
|
+
reply posted in round one cannot become an item in round two, and no two
|
|
3223
|
+
items can claim the same comment. `add-item` and `update-item` refuse a
|
|
3224
|
+
duplicate and name the item that holds it. The collection page states both
|
|
3225
|
+
rules and the stored list shows each item's fields, so reconciling is a
|
|
3226
|
+
diff on real IDs.
|
|
3227
|
+
|
|
3228
|
+
During collection and processing, use the item commands to maintain the
|
|
3229
|
+
structured records:
|
|
3230
|
+
|
|
3231
|
+
```console
|
|
3232
|
+
ww-agentic-workflows add-item TASK-123 --id comment-1 \
|
|
3233
|
+
--text="The error path is not tested."
|
|
3234
|
+
ww-agentic-workflows items TASK-123
|
|
3235
|
+
ww-agentic-workflows item TASK-123 --id comment-1
|
|
3236
|
+
ww-agentic-workflows update-item TASK-123 --id comment-1 \
|
|
3237
|
+
--processed-item="Missing failure-path coverage" \
|
|
3238
|
+
--proposed-solution="Add an integration test"
|
|
3239
|
+
ww-agentic-workflows update-item TASK-123 --id comment-1 \
|
|
3240
|
+
--actual-solution="Added the integration test" --resolved=true --reported=true
|
|
3241
|
+
```
|
|
3242
|
+
|
|
3243
|
+
`update-item` confirms the saved item and returns the current worker completion
|
|
3244
|
+
command.
|
|
3245
|
+
|
|
3246
|
+
## Worker-controlled loop control
|
|
3247
|
+
|
|
3248
|
+
A step can wrap an ordered `loop` body. Individual agent-owned body steps may
|
|
3249
|
+
declare natural-language `break` or `continue` conditions, so review and remediation stay
|
|
3250
|
+
visible as separate assignments and more than one worker may be authorized to
|
|
3251
|
+
end the loop. Body steps retain hooks, profiles, artifacts, nesting, and item
|
|
3252
|
+
collection.
|
|
3253
|
+
|
|
3254
|
+
```yaml
|
|
3255
|
+
workflows:
|
|
3256
|
+
- task: ~
|
|
3257
|
+
steps:
|
|
3258
|
+
- review-and-fix: ~
|
|
3259
|
+
max_rounds: 5
|
|
3260
|
+
loop:
|
|
3261
|
+
- review: Review the implementation and report meaningful findings.
|
|
3262
|
+
break: The review has no meaningful findings.
|
|
3263
|
+
- fix: Fix the reported findings.
|
|
3264
|
+
```
|
|
3265
|
+
|
|
3266
|
+
After doing a break-enabled step, the worker evaluates its break gate. When the
|
|
3267
|
+
condition holds, it uses the displayed command instead of ordinary completion;
|
|
3268
|
+
the command accepts the same artifact, variable, metadata, and execution fields
|
|
3269
|
+
as `complete`:
|
|
3270
|
+
|
|
3271
|
+
```console
|
|
3272
|
+
ww-agentic-workflows loop TASK-123 --break --role worker \
|
|
3273
|
+
--artifact="<whole result in Markdown>"
|
|
3274
|
+
```
|
|
3275
|
+
|
|
3276
|
+
The breaking step's completion hooks still run before ww skips the remaining
|
|
3277
|
+
body. A step may instead declare `continue`; its worker command runs completion
|
|
3278
|
+
hooks and then skips the rest of the current body, restarting from the first
|
|
3279
|
+
body step:
|
|
3280
|
+
|
|
3281
|
+
```console
|
|
3282
|
+
ww-agentic-workflows loop TASK-123 --continue --role worker \
|
|
3283
|
+
--artifact="<whole result in Markdown>"
|
|
3284
|
+
```
|
|
3285
|
+
|
|
3286
|
+
If no worker breaks or continues, the body repeats automatically. Each repetition gives
|
|
3287
|
+
automatic actions fresh operation IDs, while retries within one round
|
|
3288
|
+
retain their existing idempotency identity.
|
|
3289
|
+
|
|
3290
|
+
### One worker per loop round
|
|
3291
|
+
|
|
3292
|
+
In the `auto` runtime, `assignment` decides how the body of one round is
|
|
3293
|
+
split into worker assignments, like `assignment` does for per-item
|
|
3294
|
+
stages:
|
|
3295
|
+
|
|
3296
|
+
- `per_round`, the default, keeps consecutive body steps in one assignment
|
|
3297
|
+
while they resolve to the same agent, model, reasoning, and profile. The
|
|
3298
|
+
worker completes each step with its own command and receives the next step
|
|
3299
|
+
straight away. The repeat boundary always ends the assignment, so the
|
|
3300
|
+
manager still sees every round and still receives the limit escalation.
|
|
3301
|
+
- `per_step` hands every body step back to the manager.
|
|
3302
|
+
|
|
3303
|
+
Body steps keep their own `profile`, `agent`, `model`, and `reasoning`
|
|
3304
|
+
overrides under `per_round`; a step whose settings differ from the
|
|
3305
|
+
worker's simply starts a new assignment. That is deliberate: a `code-reviewer`
|
|
3306
|
+
review followed by a `developer` fix stays two workers, so the reviewer never
|
|
3307
|
+
fixes its own findings, while a fix-until-green loop under one profile runs as
|
|
3308
|
+
one worker per round.
|
|
3309
|
+
|
|
3310
|
+
```yaml
|
|
3311
|
+
- run-tests: ~
|
|
3312
|
+
assignment: per_round
|
|
3313
|
+
profile: quick-developer
|
|
3314
|
+
loop:
|
|
3315
|
+
- test: Run the test suite.
|
|
3316
|
+
break: No failures found.
|
|
3317
|
+
- fix-tests: Fix the failures.
|
|
3318
|
+
```
|
|
3319
|
+
|
|
3320
|
+
Every body step's work instruction names the loop and the round. The first
|
|
3321
|
+
round is described as building on the work of the steps before the loop; a
|
|
3322
|
+
later round tells the worker to concentrate on the previous rounds of this
|
|
3323
|
+
loop, not on the whole task, and shows the `artifacts` command that lists the
|
|
3324
|
+
earlier iteration directories. The setting has no effect in the `single`
|
|
3325
|
+
runtime.
|
|
3326
|
+
|
|
3327
|
+
ww limits loops to three rounds by default. Set a different project-wide
|
|
3328
|
+
positive integer as `limits.rounds` in `ww.json`, or
|
|
3329
|
+
override one wrapper with `max_rounds` in `ww.yaml`:
|
|
3330
|
+
|
|
3331
|
+
```json
|
|
3332
|
+
{"limits": {"rounds": 4}, "extensions": {}}
|
|
3333
|
+
```
|
|
3334
|
+
|
|
3335
|
+
```yaml
|
|
3336
|
+
- review-and-fix: ~
|
|
3337
|
+
max_rounds: 7
|
|
3338
|
+
loop:
|
|
3339
|
+
- review: Review the implementation.
|
|
3340
|
+
- fix: Fix the findings.
|
|
3341
|
+
```
|
|
3342
|
+
|
|
3343
|
+
The effective limit is saved in the compiled plan. When the completed round
|
|
3344
|
+
count reaches it, ww does not begin another round or provide a continuation
|
|
3345
|
+
command. The response reports `awaiting_operator` with `operator_reason:
|
|
3346
|
+
loop_limit` and explicitly tells the manager to escalate the saved results and warning to the user for manual
|
|
3347
|
+
resolution. It also shows the operator's one way past the limit: once the user
|
|
3348
|
+
has resolved or accepted the remaining findings, `next --force --reason`
|
|
3349
|
+
leaves the loop, records the reason on the repeat boundary, and continues with
|
|
3350
|
+
the steps after the loop wrapper. Nothing else starts another round; a
|
|
3351
|
+
further round needs a higher `max_rounds`.
|
|
3352
|
+
|
|
3353
|
+
## Parent and child tasks
|
|
3354
|
+
|
|
3355
|
+
A step with `children` splits a parent task into child tasks, then runs every
|
|
3356
|
+
child with one workflow; the parent continues after the last child completes.
|
|
3357
|
+
Child tasks live under the parent task directory and are intentionally limited
|
|
3358
|
+
to one level for now.
|
|
3359
|
+
|
|
3360
|
+
```yaml
|
|
3361
|
+
workflows:
|
|
3362
|
+
- name: feature
|
|
3363
|
+
steps:
|
|
3364
|
+
- name: split-work
|
|
3365
|
+
description: Split the feature into stories.
|
|
3366
|
+
children:
|
|
3367
|
+
description: One child per story. # optional splitting guidance
|
|
3368
|
+
workflow: implementation
|
|
3369
|
+
|
|
3370
|
+
- name: implementation
|
|
3371
|
+
steps:
|
|
3372
|
+
- name: implement
|
|
3373
|
+
description: Implement this child task.
|
|
3374
|
+
```
|
|
3375
|
+
|
|
3376
|
+
While the step collects, record each child:
|
|
3377
|
+
|
|
3378
|
+
```console
|
|
3379
|
+
ww-agentic-workflows add-child TASK-123 --text "Implement the API"
|
|
3380
|
+
```
|
|
3381
|
+
|
|
3382
|
+
Without `--id`, ww uses the configured `task_format`, the child's project's
|
|
3383
|
+
own when `--project` names one that sets it, else the root's, or the usual
|
|
3384
|
+
generated `TASK-<timestamp>` ID when no format is configured. `{{timestamp}}`,
|
|
3385
|
+
`{{digit}}`, and `{{uuid}}` are the supported placeholders. Supply `--id TASK-123.1` when you
|
|
3386
|
+
want a stable, human-chosen child label instead. When the child workflow's first
|
|
3387
|
+
step declares the variable `task_id`, omit `--id` and the child obtains its own ID from that
|
|
3388
|
+
step; see [children that bind their own IDs](#children-that-bind-their-own-ids).
|
|
3389
|
+
`--project <name>` runs the child in a configured project directory.
|
|
3390
|
+
|
|
3391
|
+
Once the step completes, the parent waits at `split-work/children`, the
|
|
3392
|
+
item that runs the children; start a chosen child:
|
|
3393
|
+
|
|
3394
|
+
```console
|
|
3395
|
+
ww-agentic-workflows start-child TASK-123 TASK-123.1
|
|
3396
|
+
```
|
|
3397
|
+
|
|
3398
|
+
A child that has not started yet can still change its text or project (and,
|
|
3399
|
+
at any time, its custom `--field` values):
|
|
3400
|
+
|
|
3401
|
+
```console
|
|
3402
|
+
ww-agentic-workflows update-child TASK-123 TASK-123.1 --text "Implement the API and its client"
|
|
3403
|
+
```
|
|
3404
|
+
|
|
3405
|
+
`update-child` works while the child is `pending`, during the collecting step
|
|
3406
|
+
and while the parent waits, and refuses a child that has started, naming its
|
|
3407
|
+
status. The child's first step records the text it has when it starts as its
|
|
3408
|
+
requirements.
|
|
3409
|
+
|
|
3410
|
+
The child runs as `TASK-123/TASK-123.1`, with its own hooks, items, and run
|
|
3411
|
+
history. The parent waits while a child is active. Completing the final child
|
|
3412
|
+
automatically resumes and completes the parent's normal lifecycle; the
|
|
3413
|
+
collecting step's completion hooks run after its children, and the parent's
|
|
3414
|
+
later steps follow.
|
|
3415
|
+
|
|
3416
|
+
### Per-child parent stages
|
|
3417
|
+
|
|
3418
|
+
When the parent has its own work to do around each child, such as adjusting the
|
|
3419
|
+
next slice to what earlier ones landed, reviewing the child's branch, and
|
|
3420
|
+
merging it, give `children` a list of `steps` instead of a `workflow`. The
|
|
3421
|
+
parent then runs those stages once per child, strictly one child at a time.
|
|
3422
|
+
Exactly one stage carries `workflow:`; inside `children` that stage starts the
|
|
3423
|
+
current child with that workflow and waits for it, it does not hand off (a
|
|
3424
|
+
handoff is `handoff_to`, which cannot run inside `children.steps`). The stages
|
|
3425
|
+
are assigned one per step (`children.assignment: per_step`, the only value
|
|
3426
|
+
built; `per_child` is reserved).
|
|
3427
|
+
|
|
3428
|
+
```yaml
|
|
3429
|
+
workflows:
|
|
3430
|
+
- name: roadmap
|
|
3431
|
+
steps:
|
|
3432
|
+
- read-plan: Add one child per slice of the plan, with the slice's full text.
|
|
3433
|
+
children:
|
|
3434
|
+
steps:
|
|
3435
|
+
- refine: Adjust {{ww.child.text}} to what earlier slices landed.
|
|
3436
|
+
role: manager
|
|
3437
|
+
- implement:
|
|
3438
|
+
workflow: task
|
|
3439
|
+
- review: Review {{ww.child.git.branch}} against the slice.
|
|
3440
|
+
artifact_from: implement
|
|
3441
|
+
role: manager
|
|
3442
|
+
break: The roadmap is done; nothing else is worth building.
|
|
3443
|
+
- land: Merge {{ww.child.git.branch}} into {{ww.git.branch}}.
|
|
3444
|
+
- close-plan: Record what the roadmap delivered.
|
|
3445
|
+
|
|
3446
|
+
- name: task
|
|
3447
|
+
steps:
|
|
3448
|
+
- develop: Implement {{ww.task.id}}.
|
|
3449
|
+
```
|
|
3450
|
+
|
|
3451
|
+
A stage that only runs a command can be automatic: `land: ~` with
|
|
3452
|
+
`argv: [git, merge, --no-ff, "{{ww.child.git.branch}}"]` and `on_failure: fix`
|
|
3453
|
+
runs in the task workspace, and a conflict hands the agent a repair assignment
|
|
3454
|
+
carrying the command's output (see `on_failure` above), so the agent resolves the
|
|
3455
|
+
merge instead of performing every landing.
|
|
3456
|
+
|
|
3457
|
+
With two children `A` and `B`, the parent runs `refine`, `implement` (child `A`
|
|
3458
|
+
runs its `task` workflow), `review`, and `land` for `A`, then the same four for
|
|
3459
|
+
`B`, then `close-plan`. `ww plan --workflow roadmap` shows the stages under
|
|
3460
|
+
`read-plan/{child}`; once the children are collected they become
|
|
3461
|
+
`read-plan/child-1/refine`, `read-plan/child-2/refine`, and so on.
|
|
3462
|
+
|
|
3463
|
+
- A stage reads its child as `{{ww.child.id}}`, `{{ww.child.text}}`,
|
|
3464
|
+
`{{ww.child.project}}`, `{{ww.child.field.<name>}}`, and the child task's own
|
|
3465
|
+
extension values, such as `{{ww.child.git.branch}}` and
|
|
3466
|
+
`{{ww.child.git.base_branch}}`. Its page also names the current child and,
|
|
3467
|
+
while it has not started, how to change it. Stages before `implement` run
|
|
3468
|
+
before the child task exists, so they may read only its ID, text, project,
|
|
3469
|
+
and fields; reading `{{ww.child.git.branch}}` there is a configuration error.
|
|
3470
|
+
A value that is not available yet, such as a field the child does not carry,
|
|
3471
|
+
stops the task before the stage starts, for the operator to retry or skip;
|
|
3472
|
+
the error names the `update-child` command that sets a missing field.
|
|
3473
|
+
- `refine` runs before the child starts, so it may rewrite it with
|
|
3474
|
+
`update-child TASK-123 A --text "..."`; the child's `init` records that text
|
|
3475
|
+
as its requirements.
|
|
3476
|
+
- Children carry custom fields like items: `add-child ... --field area=parser`,
|
|
3477
|
+
and `update-child ... --field area=lexer` at any time.
|
|
3478
|
+
- Add `start_child` beside `workflow:` to let ww start the child itself:
|
|
3479
|
+
`implement: {workflow: task, start_child: {model: "{{ww.child.field.model}}"}}`.
|
|
3480
|
+
It takes `workflow`, `runtime`, `model`, `reasoning` and `agent`, each a template
|
|
3481
|
+
over the child's record, read when the stage runs; an omitted or empty value
|
|
3482
|
+
inherits as `start-child` does. Record the settings with
|
|
3483
|
+
`update-child ... --field model=...` (or `add-child --field`) in the stages
|
|
3484
|
+
before. The manager's `next` starts the child and shows its page; a failed
|
|
3485
|
+
launch stops the stage for the operator, and `next --retry` starts it again.
|
|
3486
|
+
- Without `start_child`, the `implement` stage shows `start-child TASK-123 A` for its own child only;
|
|
3487
|
+
its artifact is the child's workflow summary, which `review` reads through
|
|
3488
|
+
`artifact_from: implement`.
|
|
3489
|
+
- In `auto`, the parent's manager also manages the child: starting it returns
|
|
3490
|
+
the child's page, the child's steps are ordinary worker assignments the same
|
|
3491
|
+
manager dispatches, and the completed child's page names the parent command
|
|
3492
|
+
to continue with.
|
|
3493
|
+
- A child that fails stops the parent for the operator, as in the simple form.
|
|
3494
|
+
- `break` on a stage ends the loop over the children: every remaining stage is
|
|
3495
|
+
skipped and each child that has not started is marked `skipped`. A `break`
|
|
3496
|
+
inside a `loop` within a stage ends that loop only; a `loop` inside the
|
|
3497
|
+
stages runs its own rounds for each child.
|
|
3498
|
+
- A step with `children.steps` cannot sit inside a `loop`; the simple
|
|
3499
|
+
`children: {workflow: ...}` form can.
|
|
3500
|
+
|
|
3501
|
+
Steps may contain recursive `steps` without a depth limit. The plan remains
|
|
3502
|
+
flat: each leaf action uses a hierarchical ID such as `parent/child`, with its
|
|
3503
|
+
immediate `parent` recorded in JSON and shown in Markdown. Parent steps are
|
|
3504
|
+
stateful grouping boundaries: they become in-progress with their first child and
|
|
3505
|
+
complete when every child and parent completion hook completes.
|
|
3506
|
+
|
|
3507
|
+
A workflow such as `decide-on-workflow` hands off when it ends with a workflow
|
|
3508
|
+
transition that starts a successor and never returns. The transition itself
|
|
3509
|
+
makes it a handoff workflow; there is no flag to set. The transition is a step
|
|
3510
|
+
with `handoff_to` beside its name, usually interpolating a variable an earlier
|
|
3511
|
+
step handed back; the same key as the last `after_complete` hook of the last
|
|
3512
|
+
step is equivalent. `workflow:` on a step is rejected naming `handoff_to`:
|
|
3513
|
+
`workflow` only ever means "run a child with this workflow", under
|
|
3514
|
+
`children`:
|
|
3515
|
+
|
|
3516
|
+
```yaml
|
|
3517
|
+
- name: decide-on-workflow
|
|
3518
|
+
steps:
|
|
3519
|
+
- classify: Decide which workflow fits this request.
|
|
3520
|
+
artifact: false
|
|
3521
|
+
variables:
|
|
3522
|
+
- workflow: One of {{ww.task.workflows}}, other than decide-on-workflow.
|
|
3523
|
+
- route: ~
|
|
3524
|
+
handoff_to: "{{workflow}}"
|
|
3525
|
+
```
|
|
3526
|
+
|
|
3527
|
+
Loading the configuration rejects more than one transition and a transition
|
|
3528
|
+
anywhere but last: a transition step that is not the last top-level step, or
|
|
3529
|
+
that a completion hook would follow; a transition hook on another step, in
|
|
3530
|
+
another phase, or followed by another `after_complete` hook; and a transition
|
|
3531
|
+
hook at global or workflow scope. The plan marks the workflow as a handoff. Execution
|
|
3532
|
+
completes the selection workflow's run, records
|
|
3533
|
+
`<original-workflow>=<next-workflow>` in authoritative task state, and starts the
|
|
3534
|
+
successor as the task's next numbered run — so `ww instruction <task>` lists both,
|
|
3535
|
+
each with its own plan, state and artifacts.
|
|
3536
|
+
|
|
3537
|
+
Handoffs are deliberately limited, and the limits are accepted for now:
|
|
3538
|
+
|
|
3539
|
+
- A workflow has exactly one transition, and it is the last thing the workflow
|
|
3540
|
+
does; there is no routing to different workflows from different steps. Choose
|
|
3541
|
+
the target dynamically with `{{workflow}}` instead, or branch earlier with an
|
|
3542
|
+
assessment.
|
|
3543
|
+
- A task hands off once. The successor cannot hand off again; a second
|
|
3544
|
+
transition fails with "already has a handoff marker".
|
|
3545
|
+
- A workflow cannot call another workflow and continue afterwards. Use child
|
|
3546
|
+
tasks to run another workflow per piece of work, or a reusable `handler` step
|
|
3547
|
+
tree to share a phase between workflows.
|
|
3548
|
+
|
|
3549
|
+
General nested workflow definitions remain deferred and are rejected when
|
|
3550
|
+
`ww.yaml` is loaded.
|
|
3551
|
+
|
|
3552
|
+
## Extensions
|
|
3553
|
+
|
|
3554
|
+
An extension adds handlers, modes, and commands to ww. It is identified as
|
|
3555
|
+
`vendor/name` and is always referenced by its full address, never by a bare
|
|
3556
|
+
name:
|
|
3557
|
+
|
|
3558
|
+
```yaml
|
|
3559
|
+
workflows:
|
|
3560
|
+
- name: task
|
|
3561
|
+
modes:
|
|
3562
|
+
- ext/ww/git/modes:conventional-commits
|
|
3563
|
+
steps:
|
|
3564
|
+
- name: work
|
|
3565
|
+
kind: prompt
|
|
3566
|
+
hooks:
|
|
3567
|
+
before_start:
|
|
3568
|
+
- name: ext/ww/git/handlers:is-git-clean
|
|
3569
|
+
after_complete:
|
|
3570
|
+
- name: ext/ww/git/handlers:git-commit
|
|
3571
|
+
```
|
|
3572
|
+
|
|
3573
|
+
Because every reference is qualified, installing an extension can never change
|
|
3574
|
+
what a name already means in your project: nothing it provides takes effect
|
|
3575
|
+
until you name it. Extensions do not provide hooks — a hook decides *when* work
|
|
3576
|
+
runs against a particular workflow and step, which is your configuration's
|
|
3577
|
+
business, not the extension's.
|
|
3578
|
+
|
|
3579
|
+
An extension handler runs inside ww rather than as a shell command, so it can
|
|
3580
|
+
remember what it did. `ww/git`'s `git-commit` commits and then records the
|
|
3581
|
+
commit, and `commits` reads that back. It stages the task workspace first; when
|
|
3582
|
+
nothing is staged, for example after a review round that needed no fix, it
|
|
3583
|
+
succeeds without a commit and without invoking project pre-commit hooks such as
|
|
3584
|
+
Husky or lint-staged:
|
|
3585
|
+
|
|
3586
|
+
```console
|
|
3587
|
+
ww-agentic-workflows extensions # what is installed, and what it provides
|
|
3588
|
+
ww-agentic-workflows extension ww/git commits # every commit ww made
|
|
3589
|
+
ww-agentic-workflows extension ww/git commits TASK-123 # just this task's
|
|
3590
|
+
```
|
|
3591
|
+
|
|
3592
|
+
With Git worktrees enabled, branch creation and worktree selection are separate
|
|
3593
|
+
handlers. The default project workflow runs them together, but you may move
|
|
3594
|
+
`create-worktree` to a later hook when you want to use the primary checkout
|
|
3595
|
+
instead:
|
|
3596
|
+
|
|
3597
|
+
```yaml
|
|
3598
|
+
hooks:
|
|
3599
|
+
before_start_workflow:
|
|
3600
|
+
- name: ext/ww/git/handlers:start-task-branch
|
|
3601
|
+
- name: ext/ww/git/handlers:create-worktree
|
|
3602
|
+
```
|
|
3603
|
+
|
|
3604
|
+
`start-task-branch` only creates or adopts the branch. `create-worktree` is a
|
|
3605
|
+
no-op when worktrees are disabled. If the primary checkout is already on the
|
|
3606
|
+
exact task branch rendered from the workflow's configured branch name format,
|
|
3607
|
+
either handler selects it as the task workspace rather than creating a separate
|
|
3608
|
+
worktree. No conventional branch names such as a base or development branch
|
|
3609
|
+
participate in that decision.
|
|
3610
|
+
|
|
3611
|
+
Each extension owns `.ww/ext/<vendor>/<name>/`, reached only through the store
|
|
3612
|
+
object it is handed, and every write goes through ww's lock layer. Use
|
|
3613
|
+
`store.update_text(name, callback)` when new content depends on existing content;
|
|
3614
|
+
it holds one lock across the complete read–modify–write sequence.
|
|
3615
|
+
|
|
3616
|
+
### Configuring one
|
|
3617
|
+
|
|
3618
|
+
Extension settings live in `ww.json`, ww's project config file —
|
|
3619
|
+
separate from `ww.yaml`, which describes what a workflow *does*:
|
|
3620
|
+
|
|
3621
|
+
```json
|
|
3622
|
+
{
|
|
3623
|
+
"extensions": {
|
|
3624
|
+
"ww/git": {
|
|
3625
|
+
"commit_format": "{{ww.task.id}}: {{commit_message}}",
|
|
3626
|
+
"base_branches": {
|
|
3627
|
+
"default": "main",
|
|
3628
|
+
"bugfix": "develop",
|
|
3629
|
+
"task": {"argv": ["./scripts/base-branch", "{{ww.task.lane}}"]}
|
|
3630
|
+
},
|
|
3631
|
+
"separate_branch": true,
|
|
3632
|
+
"branch_name_formats": {
|
|
3633
|
+
"default": "feature/{{ww.task.id}}",
|
|
3634
|
+
"bugfix": "hotfix/{{ww.task.id}}"
|
|
3635
|
+
},
|
|
3636
|
+
"worktrees": false
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
```
|
|
3641
|
+
|
|
3642
|
+
`"ww/git"` is the canonical key; a bare `"git"` also works when only one
|
|
3643
|
+
installed extension has that name. An extension receives its own section and
|
|
3644
|
+
nothing else from the file, and validates it itself — ww cannot know a third
|
|
3645
|
+
party's schema. A section naming no installed extension is an error rather than
|
|
3646
|
+
ignored, because a block that silently applies to nothing looks configured and
|
|
3647
|
+
is not.
|
|
3648
|
+
|
|
3649
|
+
The formats read `{{ww.task.id}}`, `{{ww.task.workflow}}`, `{{ww.task.lane}}`,
|
|
3650
|
+
`{{ww.task.run}}`, and, in `commit_format`, `{{commit_message}}`.
|
|
3651
|
+
`{{ww.task.lane}}` is the workflow whose branch handling the task takes: the
|
|
3652
|
+
workflow's `hooks_from` when it has one, else the workflow itself, the name
|
|
3653
|
+
`branch_name_formats` and `base_branches` are looked up by.
|
|
3654
|
+
|
|
3655
|
+
For `ww/git`, `ww-agentic-workflows extension ww/git settings` prints what actually resolved,
|
|
3656
|
+
which is the first thing to run after editing the file; `--project <name>`
|
|
3657
|
+
prints what a task in that configured project receives.
|
|
3658
|
+
|
|
3659
|
+
When `worktrees` is enabled, generated task IDs reserve any existing path
|
|
3660
|
+
rendered by `worktree_dir` and `worktree_name_format`. For example, an existing
|
|
3661
|
+
`worktrees/TASK-1` makes the next `{{digit}}`-formatted task use `TASK-2`, rather
|
|
3662
|
+
than adopting that checkout for a new task. A generated ID also skips one
|
|
3663
|
+
`ww/git` still holds: a branch record for it, or an existing branch its branch
|
|
3664
|
+
name formats render for it, so a cleaned-up `.ww/tasks` does not hand an old
|
|
3665
|
+
task's ID, and with it that task's history, to a new one.
|
|
3666
|
+
|
|
3667
|
+
`base_branches` maps exact workflow names to base branches, and its `default`
|
|
3668
|
+
entry covers every other workflow, the same shape as `branch_name_formats`. A
|
|
3669
|
+
`ww-scriptize-rules` task always uses the required `default` entry, even when
|
|
3670
|
+
an entry names that workflow explicitly. A
|
|
3671
|
+
repository under a configured project with a base branch of its own sets
|
|
3672
|
+
`base_branches` in [its own settings file](#a-projects-own-extension-settings).
|
|
3673
|
+
Each value may be either a literal branch name or an
|
|
3674
|
+
object with a non-empty `argv` array. An argv command runs directly without a shell in
|
|
3675
|
+
the project root; its single non-empty stdout line becomes the base branch.
|
|
3676
|
+
Arguments may interpolate `{{ww.task.id}}`, `{{ww.task.workflow}}`, `{{ww.task.lane}}`, and `{{ww.task.run}}`;
|
|
3677
|
+
a script choosing the base by workflow reads `{{ww.task.lane}}`, so a workflow with `hooks_from` gets its lane's base.
|
|
3678
|
+
The resolved base is recorded with the task branch so retries, worktree creation,
|
|
3679
|
+
and return-to-base use one stable value. The record is trusted only while it
|
|
3680
|
+
names the branch being resolved and that branch exists; a record of another
|
|
3681
|
+
branch, or of a deleted one, is left behind by an earlier task under the same
|
|
3682
|
+
ID, and the configured base is resolved instead. A child task always uses its
|
|
3683
|
+
recorded parent task branch instead. `reset` drops the task's branch and commit
|
|
3684
|
+
records, and its children's.
|
|
3685
|
+
|
|
3686
|
+
`branch_name_formats` names the available branch naming strategies. Without an
|
|
3687
|
+
override, `start-task-branch` first looks for a strategy matching the workflow
|
|
3688
|
+
name and then falls back to `default`. Select another configured strategy for a
|
|
3689
|
+
single run by name:
|
|
3690
|
+
|
|
3691
|
+
```console
|
|
3692
|
+
ww-agentic-workflows start TASK-123 --workflow task --agent codex \
|
|
3693
|
+
--requirements="Fix the requested bug." --branch-strategy bugfix
|
|
3694
|
+
```
|
|
3695
|
+
|
|
3696
|
+
The selection is persisted with the run, so later or retried branch and
|
|
3697
|
+
worktree handlers use the same format. An unknown explicit strategy fails
|
|
3698
|
+
instead of silently falling back.
|
|
3699
|
+
|
|
3700
|
+
`on_signing_failure` says what `git-commit` and `merge-branch` do when git
|
|
3701
|
+
cannot sign a commit, for example because the signing agent is locked while the run goes on
|
|
3702
|
+
unattended. `operator`, the default, stops for the operator as for any failed
|
|
3703
|
+
handler. `unsigned` commits once more with `commit.gpgsign=false`, records
|
|
3704
|
+
`signed: false` for the commit, and says so in the handler's result; any
|
|
3705
|
+
other commit error still stops. Set it where the unattended run happens, such
|
|
3706
|
+
as the user or local settings file:
|
|
3707
|
+
|
|
3708
|
+
```json
|
|
3709
|
+
{ "extensions": { "ww/git": { "on_signing_failure": "unsigned" } } }
|
|
3710
|
+
```
|
|
3711
|
+
|
|
3712
|
+
#### What `ww/git` does with those settings
|
|
3713
|
+
|
|
3714
|
+
| Handler | Settings it acts on |
|
|
3715
|
+
| --- | --- |
|
|
3716
|
+
| `git-commit` | `commit_format`, `on_signing_failure` |
|
|
3717
|
+
| `merge-branch` | `commit_format`, `on_signing_failure` |
|
|
3718
|
+
| `start-task-branch` | `base_branches`, `separate_branch`, `branch_name_formats`, `worktrees`, `worktree_dir`, `worktree_name_format` |
|
|
3719
|
+
| `return-to-base-branch` | `base_branches`, `separate_branch` |
|
|
3720
|
+
| `remove-task-worktree` | `worktrees` |
|
|
3721
|
+
| `is-git-clean` | — |
|
|
3722
|
+
|
|
3723
|
+
A suggested wiring, with `is-git-clean` before `start-task-branch` so a branch is
|
|
3724
|
+
never cut from a dirty tree:
|
|
3725
|
+
|
|
3726
|
+
```yaml
|
|
3727
|
+
hooks:
|
|
3728
|
+
before_start_workflow:
|
|
3729
|
+
- name: ext/ww/git/handlers:is-git-clean
|
|
3730
|
+
- name: ext/ww/git/handlers:start-task-branch
|
|
3731
|
+
before_complete_workflow:
|
|
3732
|
+
- handlers:
|
|
3733
|
+
- ext/ww/git/handlers:git-commit: ~
|
|
3734
|
+
- ext/ww/git/handlers:return-to-base-branch: ~
|
|
3735
|
+
```
|
|
3736
|
+
|
|
3737
|
+
`remove-task-worktree` is left out on purpose: a worktree is where the work
|
|
3738
|
+
happened, so deleting it the moment a workflow ends should be your choice, not a
|
|
3739
|
+
default. `ww-agentic-workflows extension ww/git branches [TASK-ID]` shows what was opened, and
|
|
3740
|
+
`commits` what was committed.
|
|
3741
|
+
|
|
3742
|
+
#### Landing a branch with `merge-branch`
|
|
3743
|
+
|
|
3744
|
+
`merge-branch` merges a branch into the task's current branch, in the task
|
|
3745
|
+
workspace, with `git merge --no-ff`, so a "land" stage is ww's work instead of
|
|
3746
|
+
an agent's. It takes two `args`, the branch to merge and the merge message;
|
|
3747
|
+
both may use templates, and the message goes through `commit_format` like a
|
|
3748
|
+
`git-commit` subject:
|
|
3749
|
+
|
|
3750
|
+
```yaml
|
|
3751
|
+
children:
|
|
3752
|
+
steps:
|
|
3753
|
+
- implement:
|
|
3754
|
+
workflow: task
|
|
3755
|
+
- name: ext/ww/git/handlers:merge-branch
|
|
3756
|
+
args: ["{{ww.child.git.branch}}", "Land slice {{ww.child.id}}"]
|
|
3757
|
+
```
|
|
3758
|
+
|
|
3759
|
+
It refuses a workspace with uncommitted changes, a branch that does not
|
|
3760
|
+
exist, a detached `HEAD` (the merge would land on no branch), and a workspace
|
|
3761
|
+
where a rebase, `git am`, cherry-pick or revert is in progress. On a conflict it runs `git merge --abort` and fails naming the
|
|
3762
|
+
conflicting files, so the task stops for the operator with the workspace as
|
|
3763
|
+
it was. When git cannot sign the merge commit it follows `on_signing_failure`
|
|
3764
|
+
exactly as `git-commit` does: it stops, or, with `unsigned`, aborts the
|
|
3765
|
+
half-made merge, merges once more without a signature, and records
|
|
3766
|
+
`signed: false`. It reports success only when no merge is left in progress
|
|
3767
|
+
(no `MERGE_HEAD`) and `HEAD` is a merge commit of the branch. The merge commit's
|
|
3768
|
+
sha is its output, `{{merge_commit}}`, and the commit is recorded with the
|
|
3769
|
+
merged branch; `commits` lists it. A branch already contained in the current
|
|
3770
|
+
one merges nothing and succeeds with an empty `merge_commit`.
|
|
3771
|
+
|
|
3772
|
+
Like `git-commit`, it puts ww's operation ID in a `WW-Operation` trailer, so a
|
|
3773
|
+
retry after an interruption finds the merge commit the earlier attempt made
|
|
3774
|
+
instead of merging again. A merge that attempt left half-done (its own
|
|
3775
|
+
trailer in `MERGE_MSG`) is aborted and redone only while it is untouched:
|
|
3776
|
+
every conflicted file still carries its conflict markers, and nothing is
|
|
3777
|
+
staged or changed beyond what git's merge left (the merged branch's version
|
|
3778
|
+
of a file only it changed, or the clean three-way merge of one both sides
|
|
3779
|
+
changed). Once the operator has worked on it, by resolving or staging a file,
|
|
3780
|
+
the handler changes nothing and fails asking them to conclude the merge with
|
|
3781
|
+
`git commit` (a retry then finds that commit by its trailer) or discard it
|
|
3782
|
+
with `git merge --abort`. Any other merge in progress is refused.
|
|
3783
|
+
|
|
3784
|
+
`create-worktree` reuses a worktree that already exists at the configured
|
|
3785
|
+
path. When none does but git reports the task branch checked out in another
|
|
3786
|
+
worktree, left by an earlier round, moved by hand, or created before the
|
|
3787
|
+
settings changed, the handler adopts that worktree and records its location
|
|
3788
|
+
rather than failing on git's one-worktree-per-branch rule.
|
|
3789
|
+
|
|
3790
|
+
When `create-worktree` selects a checkout, ww persists its path for the run.
|
|
3791
|
+
Later shell and extension handlers — including `git-commit` — execute there,
|
|
3792
|
+
and Markdown agent instructions include a `cd` command so manual work uses the
|
|
3793
|
+
same checkout. JSON instructions expose the path as `working_directory`. ww
|
|
3794
|
+
persists the path relative to the project root and prints it absolute for the
|
|
3795
|
+
filesystem it runs in, so a task started on the host continues correctly inside
|
|
3796
|
+
a container that mounts the checkout elsewhere; project-local profile files are
|
|
3797
|
+
recorded and printed the same way.
|
|
3798
|
+
`ww-agentic-workflows extension ww/git branches <TASK-ID>` also reports it.
|
|
3799
|
+
|
|
3800
|
+
#### Template values from `ww/git`
|
|
3801
|
+
|
|
3802
|
+
With `ww/git` in the settings (a section, even an empty one), every template
|
|
3803
|
+
of every workflow may read the task's branch:
|
|
3804
|
+
|
|
3805
|
+
| Value | What it is |
|
|
3806
|
+
| --- | --- |
|
|
3807
|
+
| `{{ww.git.branch}}` | The task's branch. A child task's is `<parent branch>-<child ID>`. |
|
|
3808
|
+
| `{{ww.git.base_branch}}` | The branch it was created from; a child's is its parent's branch. |
|
|
3809
|
+
| `{{ww.git.branch_strategy}}` | The branch format key in use: `start --branch-strategy`, else the workflow's own `branch_name_formats` entry, else `default`. |
|
|
3810
|
+
|
|
3811
|
+
```yaml
|
|
3812
|
+
- land: Merge {{ww.git.branch}} into {{ww.git.base_branch}} and push.
|
|
3813
|
+
```
|
|
3814
|
+
|
|
3815
|
+
The branch values come from what `start-task-branch` or `create-worktree`
|
|
3816
|
+
recorded for the task, never from a live `git rev-parse`: the primary
|
|
3817
|
+
checkout and a task's worktree can be on different branches. Before the task
|
|
3818
|
+
has a branch, an agent step that reads one does not start: the task stops
|
|
3819
|
+
for the operator with `operator_reason: value_unavailable` and an error naming
|
|
3820
|
+
the variable and its extension; `next --retry` checks again once the branch
|
|
3821
|
+
exists, and `next --force` skips the step. An automatic handler that reads one
|
|
3822
|
+
fails as any handler does (`handler_failed`). Wire `start-task-branch` before
|
|
3823
|
+
the first step that uses them. An unknown `ww.`
|
|
3824
|
+
name, or `{{ww.git.*}}` without `ww/git` in the settings, is an error when
|
|
3825
|
+
the workflow is loaded.
|
|
3826
|
+
|
|
3827
|
+
### Writing one
|
|
3828
|
+
|
|
3829
|
+
Create `<project>/ext/<vendor>/<name>/extension.py` exposing `EXTENSION`, or
|
|
3830
|
+
publish a package advertising an `ww.extensions` entry point:
|
|
3831
|
+
|
|
3832
|
+
```python
|
|
3833
|
+
from ww.extensions.api import Extension, ExtensionHandler, ExtensionResult
|
|
3834
|
+
|
|
3835
|
+
|
|
3836
|
+
def _greet(context):
|
|
3837
|
+
return ExtensionResult(
|
|
3838
|
+
True,
|
|
3839
|
+
f"hello from {context.root}",
|
|
3840
|
+
values={"greeting": "hello"},
|
|
3841
|
+
)
|
|
3842
|
+
|
|
3843
|
+
|
|
3844
|
+
EXTENSION = Extension(
|
|
3845
|
+
vendor="acme",
|
|
3846
|
+
name="hello",
|
|
3847
|
+
version="1.0.0",
|
|
3848
|
+
description="A minimal example.",
|
|
3849
|
+
handlers=(ExtensionHandler("greet", _greet, "Say hello.", outputs=("greeting",)),),
|
|
3850
|
+
)
|
|
3851
|
+
```
|
|
3852
|
+
|
|
3853
|
+
For a packaged extension, name the `ww.extensions` entry point `acme.hello`;
|
|
3854
|
+
the dot maps to the `acme/hello` identifier without importing the package.
|
|
3855
|
+
Discovery is lazy, so unrelated workflows and saved-state commands do not load
|
|
3856
|
+
the extension. An extension is trusted in-process Python once referenced:
|
|
3857
|
+
qualified references isolate names, not side effects or process access.
|
|
3858
|
+
|
|
3859
|
+
The context identifies the current plan item, work item, attempt, and stable
|
|
3860
|
+
operation. Human-readable `output` is kept on the execution record; structured
|
|
3861
|
+
`values` must exactly match the `outputs` the handler declares (in YAML, a
|
|
3862
|
+
handler returning values lists them as bare `variables` entries) and become
|
|
3863
|
+
available to later workflow actions as `{{greeting}}`. Invalid return types,
|
|
3864
|
+
undeclared values, and missing declared values fail the handler consistently.
|
|
3865
|
+
|
|
3866
|
+
A handler that needs settings per use declares `arguments`, the names of its
|
|
3867
|
+
positional arguments in order. A reference passes them as `args`, a list of
|
|
3868
|
+
strings in which templates are allowed; ww checks the count and the template
|
|
3869
|
+
names when it compiles the workflow, renders the templates when the handler
|
|
3870
|
+
runs, and hands the result over as `context.arguments`. A reference to a
|
|
3871
|
+
handler that declares none may not pass `args`.
|
|
3872
|
+
|
|
3873
|
+
```python
|
|
3874
|
+
ExtensionHandler("merge-branch", _merge, arguments=("branch", "message"))
|
|
3875
|
+
```
|
|
3876
|
+
|
|
3877
|
+
A handler that declares `provide` (its agent-supplied inputs, the Python
|
|
3878
|
+
counterpart of `variables`) may also declare `validate`, a callable that
|
|
3879
|
+
receives the handler's own declared values as a mapping and returns an error
|
|
3880
|
+
message to refuse them or `None` to accept. ww calls it when the agent
|
|
3881
|
+
supplies the values, before the completion is saved, so a refused value comes
|
|
3882
|
+
back to the agent as a failed `complete` with that message instead of a
|
|
3883
|
+
handler failure the operator has to resolve. It runs with no store, no
|
|
3884
|
+
workspace, and no effects, and it does not replace the check the handler makes
|
|
3885
|
+
when it runs: `ww/git` declares one for `commit_message` and still checks the
|
|
3886
|
+
same rule in `git-commit`.
|
|
3887
|
+
|
|
3888
|
+
```python
|
|
3889
|
+
def _subject_error(values):
|
|
3890
|
+
if "\n" in values.get("commit_message", ""):
|
|
3891
|
+
return "commit_message must be a single line"
|
|
3892
|
+
return None
|
|
3893
|
+
|
|
3894
|
+
|
|
3895
|
+
ExtensionHandler(
|
|
3896
|
+
"git-commit",
|
|
3897
|
+
_commit,
|
|
3898
|
+
provide=(ProvidedVariable("commit_message"),),
|
|
3899
|
+
validate=_subject_error,
|
|
3900
|
+
)
|
|
3901
|
+
```
|
|
3902
|
+
|
|
3903
|
+
An extension that keeps records per task, as `ww/git` keeps branch and commit
|
|
3904
|
+
records, may declare two optional hooks so a reused task ID inherits nothing.
|
|
3905
|
+
`claims_task` receives a context with `task_id` (and `workflow` and the
|
|
3906
|
+
project's `config`) and returns `True` while the extension still holds
|
|
3907
|
+
anything for that ID; a generated task ID skips such an ID, as it skips one
|
|
3908
|
+
whose `reserved_paths` exist. `forget_task` receives the same context and drops
|
|
3909
|
+
the records of the task and its children; `reset` calls it. ww asks the
|
|
3910
|
+
extensions the root or the task's project lists, even with an empty section,
|
|
3911
|
+
and any that has a store.
|
|
3912
|
+
|
|
3913
|
+
```python
|
|
3914
|
+
def _claims(context):
|
|
3915
|
+
return context.store.read_text(f"{context.task_id}.json") is not None
|
|
3916
|
+
|
|
3917
|
+
|
|
3918
|
+
EXTENSION = Extension(
|
|
3919
|
+
vendor="acme",
|
|
3920
|
+
name="tickets",
|
|
3921
|
+
claims_task=_claims,
|
|
3922
|
+
forget_task=_forget,
|
|
3923
|
+
)
|
|
3924
|
+
```
|
|
3925
|
+
|
|
3926
|
+
The compiled plan also records the extension API/version, provider source,
|
|
3927
|
+
source fingerprint, and resolved settings. A resumed run uses those saved
|
|
3928
|
+
settings and refuses to dispatch if the extension's version, API version, or
|
|
3929
|
+
provider source has changed, preventing an upgrade from silently changing an
|
|
3930
|
+
in-flight run. Fixing an extension in place under the same version is allowed,
|
|
3931
|
+
as it is for ww itself; the fingerprint is recorded for audit only.
|
|
3932
|
+
|
|
3933
|
+
An extension may declare `ExtensionVariable` entries to override variables
|
|
3934
|
+
owned by ww core. It cannot introduce arbitrary global variables or replace
|
|
3935
|
+
values declared by workflow steps. Only extensions already referenced by the
|
|
3936
|
+
saved plan participate, preserving lazy discovery. Conflicting overrides are
|
|
3937
|
+
configuration errors. The Git extension uses this contract to resolve
|
|
3938
|
+
`{{ww.task.workspace_dir}}` from the primary checkout or its recorded worktree.
|
|
3939
|
+
|
|
3940
|
+
New values go in the extension's one `namespace`, an `ExtensionNamespace`
|
|
3941
|
+
whose `ExtensionVariable` entries templates read as
|
|
3942
|
+
`{{ww.<namespace>.<name>}}`. A namespace is available whenever the root
|
|
3943
|
+
settings list the extension, even with an empty section, and not only when
|
|
3944
|
+
one of its handlers is referenced. Each value is resolved for the task each
|
|
3945
|
+
time ww renders the task's templates; a resolver returning `None` means "not
|
|
3946
|
+
available yet": an agent step that reads it stops the task for the operator
|
|
3947
|
+
(`value_unavailable`) before it starts, and an automatic handler fails. The
|
|
3948
|
+
namespace `ww` and the names ww keeps for its own values (`task`, `project`,
|
|
3949
|
+
`documents`, `metadata`, `project_metadata`, `item`, `child`) cannot be
|
|
3950
|
+
claimed, and two listed extensions claiming one namespace are an error.
|
|
3951
|
+
`ww/git` declares `git`:
|
|
3952
|
+
|
|
3953
|
+
```python
|
|
3954
|
+
namespace = (
|
|
3955
|
+
ExtensionNamespace(
|
|
3956
|
+
"git",
|
|
3957
|
+
(
|
|
3958
|
+
ExtensionVariable("branch", _branch_variable),
|
|
3959
|
+
ExtensionVariable("base_branch", _base_branch_variable),
|
|
3960
|
+
),
|
|
3961
|
+
),
|
|
3962
|
+
)
|
|
3963
|
+
```
|
|
3964
|
+
|
|
3965
|
+
`ww/git` is bundled with the installed `ww-agentic-workflows` package, so it is
|
|
3966
|
+
available in every project, including a `pipx --editable` installation whose
|
|
3967
|
+
source checkout lives elsewhere. A third-party extension remains project-local.
|
|
3968
|
+
A project cannot provide a second `ww/git`; duplicate extension IDs are an
|
|
3969
|
+
error. A handler is one unit of work: a retry re-runs the whole thing, so write
|
|
3970
|
+
handlers that tolerate that.
|
|
3971
|
+
|
|
3972
|
+
## Execute a workflow
|
|
3973
|
+
|
|
3974
|
+
```console
|
|
3975
|
+
ww-agentic-workflows start TASK-123 --workflow task --agent codex --runtime single \
|
|
3976
|
+
--requirements="Implement the requested change." \
|
|
3977
|
+
--model gpt-5 --reasoning high --role manager
|
|
3978
|
+
ww-agentic-workflows next TASK-123 --model gpt-5 --reasoning high --role manager
|
|
3979
|
+
# perform the displayed prompt, skill, or slash command
|
|
3980
|
+
ww-agentic-workflows complete TASK-123 --role worker \
|
|
3981
|
+
--artifact "<whole result in Markdown>" \
|
|
3982
|
+
--summary "<one or two sentences for the next step>"
|
|
3983
|
+
ww-agentic-workflows instruction TASK-123 --role worker
|
|
3984
|
+
ww-agentic-workflows metadata TASK-123
|
|
3985
|
+
ww-agentic-workflows metadata --project
|
|
3986
|
+
```
|
|
3987
|
+
|
|
3988
|
+
A task has at most one open run. Starting a workflow on a task whose last
|
|
3989
|
+
run is unfinished, failed included, is refused until that run finishes or
|
|
3990
|
+
the task is reset, unless the workflow is declared `restartable`: then the
|
|
3991
|
+
new start abandons the unfinished run of the same workflow and opens a new
|
|
3992
|
+
one, and the abandoned run stays readable in the task's history with its
|
|
3993
|
+
items, artifacts, and interactions. This is the natural setting for a
|
|
3994
|
+
workflow that is run round after round, such as manual testing over shared
|
|
3995
|
+
items. An unfinished run of a different workflow is never abandoned this
|
|
3996
|
+
way.
|
|
3997
|
+
|
|
3998
|
+
In the `single` runtime one session does every step, so a completion opens
|
|
3999
|
+
the next agent step itself and prints that step's page: the `next` it would
|
|
4000
|
+
have run is run for it, preparation hooks included. `next` on a step that is
|
|
4001
|
+
already open then simply shows it again. When opening the next step needs
|
|
4002
|
+
something only the agent can give, such as the outcome of an assessment,
|
|
4003
|
+
the completion prints the pending page that says so. The `auto` runtime
|
|
4004
|
+
keeps the manager's `next`, because that is where a worker is chosen.
|
|
4005
|
+
|
|
4006
|
+
When an outside-of-ww issue has been resolved by an operator, a failed item
|
|
4007
|
+
can be skipped with `next --force --reason "<reason>"`. This is an exceptional operator command:
|
|
4008
|
+
ww asks for an interactive confirmation and explains that agents must obtain
|
|
4009
|
+
permission before using it. Answer `no` (or provide no answer) to leave the
|
|
4010
|
+
failed item in place; answer `yes` only after the operator has approved the
|
|
4011
|
+
forceful transition.
|
|
4012
|
+
|
|
4013
|
+
```console
|
|
4014
|
+
ww-agentic-workflows next TASK-123 --force --reason "Resolved manually" --role manager
|
|
4015
|
+
# confirmation: Proceed with force? [y/N]
|
|
4016
|
+
```
|
|
4017
|
+
|
|
4018
|
+
Runtime and execution metadata are retained in task instructions. Under
|
|
4019
|
+
`auto`, compiled requests and manager-selected agent/model/reasoning are
|
|
4020
|
+
shown and persisted separately; ww itself does not launch agents. Under `single`,
|
|
4021
|
+
configured workflow and built-in hints are ignored in favor of the current
|
|
4022
|
+
session. Completion flags can report a bounded delegate for one item without
|
|
4023
|
+
changing the rest of the assignment.
|
|
4024
|
+
|
|
4025
|
+
Human-facing instructions identify themselves as generated by `ww` and name the
|
|
4026
|
+
reader's current role. In `auto`, the manager receives only a worker
|
|
4027
|
+
bootstrap command and passes it to the selected worker without task details or
|
|
4028
|
+
commentary. The worker runs `ww instruction` with `--role worker` to retrieve its
|
|
4029
|
+
complete, role-specific assignment, then receives instructions for when and
|
|
4030
|
+
what to hand back. `single` identifies the session as both manager and worker and
|
|
4031
|
+
states which role to perform without spawning. A short explanation of how `ww`
|
|
4032
|
+
manages the saved workflow appears only after `start` and an explicit `instruction`;
|
|
4033
|
+
normal `next` and `complete` responses stay focused on the immediate action.
|
|
4034
|
+
|
|
4035
|
+
Successful commands return exit code `0`. Handled `ww` errors and rendered
|
|
4036
|
+
failed or interrupted workflow states return `1`. Invalid command-line syntax
|
|
4037
|
+
is reported by `argparse` with exit code `2`. Abbreviated flags are not
|
|
4038
|
+
accepted: every flag is spelled in full.
|
|
4039
|
+
|
|
4040
|
+
The task ID may be omitted from `start`. `task_format` in
|
|
4041
|
+
`ww.json` then controls generation with `{{timestamp}}`,
|
|
4042
|
+
`{{digit}}`, and/or `{{uuid}}`; without it, ww uses `TASK-{{timestamp}}`. It is a
|
|
4043
|
+
setting of the checkout and of the tracker a repository uses, not of what a
|
|
4044
|
+
workflow does, so it lives in the JSON settings, at any of their
|
|
4045
|
+
[levels](#user-repo-and-local-configuration), and a configured project may
|
|
4046
|
+
carry [its own](#a-projects-own-extension-settings). A `task_format` key in
|
|
4047
|
+
any YAML file is an error that names the file and points here.
|
|
4048
|
+
|
|
4049
|
+
```json
|
|
4050
|
+
{"task_format": "TASK-{{digit}}"}
|
|
4051
|
+
```
|
|
4052
|
+
|
|
4053
|
+
Prefer an explicit ID whenever the request names an external ticket, so the
|
|
4054
|
+
task matches the issue it works on; `discover` and the embedded agent
|
|
4055
|
+
instructions say so. Avoid a generated format that imitates your tracker's keys,
|
|
4056
|
+
such as `FOOBAR-{{digit}}` next to Jira's `FOOBAR-10859`. To rule generated IDs out,
|
|
4057
|
+
set `"task_format": "explicit"`: `start` and `add-child` then require an ID, and the
|
|
4058
|
+
only exception is a workflow that obtains its own ID in its first step.
|
|
4059
|
+
|
|
4060
|
+
When a task has more than one run, `instruction TASK-123` shows every run and its
|
|
4061
|
+
summary. Use
|
|
4062
|
+
`ww-agentic-workflows instruction TASK-123 --run 02-code-review --role manager` for
|
|
4063
|
+
the detailed instruction and status of one run.
|
|
4064
|
+
|
|
4065
|
+
After a run completes, start another workflow with the same task ID to append a
|
|
4066
|
+
sequential run. Each run retains its own plan, state, artifacts, execution
|
|
4067
|
+
settings, and summary.
|
|
4068
|
+
|
|
4069
|
+
Every non-terminal Markdown instruction shows one role-specific continuation
|
|
4070
|
+
command. A completion screen may request values for upcoming automatic CLI
|
|
4071
|
+
handlers in the same assignment; pass each with `--variable name=value`. Before
|
|
4072
|
+
anything is saved, ww hands each value to the handler that will consume it:
|
|
4073
|
+
a handler that declares a validator, such as `ww/git`'s `git-commit` for
|
|
4074
|
+
`commit_message`, refuses a value it would fail on, and the completion fails
|
|
4075
|
+
with the handler's own message and records nothing, so the agent corrects the
|
|
4076
|
+
value and completes again. `ww` then
|
|
4077
|
+
executes those handlers itself before it activates the next worker item or
|
|
4078
|
+
returns control to the manager. If an automatic command fails, the worker stops
|
|
4079
|
+
and reports the failure to the manager. The response reports
|
|
4080
|
+
`awaiting_operator` with `operator_reason: handler_failed`, so the manager
|
|
4081
|
+
reports it to the ww operator for manual intervention; its instruction lists
|
|
4082
|
+
the two operator
|
|
4083
|
+
options, `next --retry` to run the handler again once the cause is fixed and
|
|
4084
|
+
`next --force --reason` to skip it, so the agent can run the one the
|
|
4085
|
+
operator chooses without guessing. A retried handler that takes provided
|
|
4086
|
+
values does not replay the values it failed with: it asks for them again
|
|
4087
|
+
through the ordinary input request, which shows what it was given last time,
|
|
4088
|
+
so a wrong value is corrected and a right one repeated. `next --force` checks the task state before
|
|
4089
|
+
it asks for confirmation, and its prompt states what the force will do; a task
|
|
4090
|
+
that is neither failed, interrupted, nor stopped at a loop limit is refused
|
|
4091
|
+
without a prompt.
|
|
4092
|
+
|
|
4093
|
+
`status TASK-123` is a quick current-state check: it reports only the task ID,
|
|
4094
|
+
workflow, current step and step state, runtime, and agent/model/reasoning.
|
|
4095
|
+
Use `instruction` when an agent needs the detailed role-specific guidance.
|
|
4096
|
+
|
|
4097
|
+
Markdown and `--json` output are rendered directly from the normalized
|
|
4098
|
+
instruction record. Project-local `.ww/templates` files are not a supported
|
|
4099
|
+
customization mechanism.
|
|
4100
|
+
|
|
4101
|
+
### Catalogs, output, and project selection
|
|
4102
|
+
|
|
4103
|
+
Catalog commands expose configured and discovered capabilities as JSON; for a
|
|
4104
|
+
single agent-facing overview use `discover`:
|
|
4105
|
+
|
|
4106
|
+
```console
|
|
4107
|
+
ww-agentic-workflows workflows
|
|
4108
|
+
ww-agentic-workflows modes
|
|
4109
|
+
ww-agentic-workflows runtimes
|
|
4110
|
+
ww-agentic-workflows agents
|
|
4111
|
+
ww-agentic-workflows projects
|
|
4112
|
+
ww-agentic-workflows extensions
|
|
4113
|
+
ww-agentic-workflows artifacts TASK-123
|
|
4114
|
+
ww-agentic-workflows interrupted
|
|
4115
|
+
```
|
|
4116
|
+
|
|
4117
|
+
`artifacts` returns JSON references for completed step and hook artifacts. Hook
|
|
4118
|
+
records include their parent step, hook name, and hook phase. Each entry's
|
|
4119
|
+
`artifact` (or `command_output`) is the stable project-relative reference, and
|
|
4120
|
+
`path` is its absolute location, which a worker running in a linked worktree
|
|
4121
|
+
needs because `.ww` lives under the primary checkout.
|
|
4122
|
+
|
|
4123
|
+
Lifecycle commands support `--json` when machine-readable output is needed.
|
|
4124
|
+
By default, `ww` detects the primary Git checkout so linked worktrees share task
|
|
4125
|
+
state. Pass global `--root /path/to/project` to select a project explicitly.
|
|
4126
|
+
|
|
4127
|
+
### Interrupted automatic handlers
|
|
4128
|
+
|
|
4129
|
+
If ww is interrupted after it records an automatic command or extension as
|
|
4130
|
+
started, the operation is shown as `interrupted` because its external outcome
|
|
4131
|
+
is unknown. `next` only reports that recovery boundary; it never replays the
|
|
4132
|
+
operation implicitly. Two cases are settled without asking. A command that
|
|
4133
|
+
had already exited non-zero when ww died is a known failure, not an unknown
|
|
4134
|
+
outcome: every finished command is recorded the moment it exits, so `next`
|
|
4135
|
+
reports it as `failed` with the exit code and what it printed, and the
|
|
4136
|
+
ordinary `next --retry` applies. A command handler declared
|
|
4137
|
+
`idempotent: true` is replayed by `next` itself, because its author has said
|
|
4138
|
+
a second run cannot do damage. For everything else, inspect the current state
|
|
4139
|
+
with:
|
|
4140
|
+
|
|
4141
|
+
```console
|
|
4142
|
+
ww-agentic-workflows status TASK-123 --role manager
|
|
4143
|
+
```
|
|
4144
|
+
|
|
4145
|
+
After checking the external system, use
|
|
4146
|
+
`ww-agentic-workflows next TASK-123 --role manager --retry` to replay the
|
|
4147
|
+
unfinished operation. It requires operator confirmation because the external
|
|
4148
|
+
effect may have already occurred. To advance without replaying it, use `next
|
|
4149
|
+
--force` with a specific `--reason`; this also requires confirmation and
|
|
4150
|
+
retains the reason with the item record. Agents must ask an operator before
|
|
4151
|
+
using either exceptional option. Completed command segments remain recorded and
|
|
4152
|
+
are not replayed.
|
|
4153
|
+
|
|
4154
|
+
## Concurrent ww processes
|
|
4155
|
+
|
|
4156
|
+
Several `ww` invocations can run against one project at the same time. Each
|
|
4157
|
+
mutating command — `start`, `next`, `complete`, `reset` — holds an
|
|
4158
|
+
exclusive lock on its task for its whole duration, so a second process waits and
|
|
4159
|
+
then acts on the first one's committed state instead of overwriting it. Waiting
|
|
4160
|
+
is automatic: the blocked process parks until the holder releases, printing one
|
|
4161
|
+
notice to stderr if the wait lasts longer than a moment.
|
|
4162
|
+
|
|
4163
|
+
Reads are not locked. `instruction` and compact `status` answer immediately even while another process is
|
|
4164
|
+
mid-command, and writes are atomic, so it never sees a partial file.
|
|
4165
|
+
|
|
4166
|
+
The order in which waiting processes are served is **not** guaranteed — the
|
|
4167
|
+
operating system may grant the lock to any waiter. Waiting is bounded by
|
|
4168
|
+
`WW_LOCK_TIMEOUT` (seconds, default `30`); set it to `0` to wait indefinitely.
|
|
4169
|
+
Lock files live in `.ww/locks` and the kernel releases them when a process
|
|
4170
|
+
exits, so a killed run never leaves one behind. Locks are advisory between `ww`
|
|
4171
|
+
processes; editing `.ww/tasks/<id>/state.json` by hand is still unsupported.
|
|
4172
|
+
|
|
4173
|
+
For a real side-effect smoke test with placeholder agent output, use:
|
|
4174
|
+
|
|
4175
|
+
```console
|
|
4176
|
+
.venv/bin/python scripts/run_workflow_dummy.py TASK-123 --workflow task
|
|
4177
|
+
```
|
|
4178
|
+
|
|
4179
|
+
The runner executes every ww-owned CLI handler for real, including commits. Run
|
|
4180
|
+
it on a disposable branch or a test repository.
|
|
4181
|
+
|
|
4182
|
+
## Initialize and maintain a project
|
|
4183
|
+
|
|
4184
|
+
`init` creates a normalized default configuration and project-local agent
|
|
4185
|
+
instructions. `cleanup` removes inactive lock sidecar files. `reset` deletes one
|
|
4186
|
+
task's saved state and artifacts, and the records extensions keep for it, such
|
|
4187
|
+
as `ww/git`'s branch and commit records, and therefore requires explicit
|
|
4188
|
+
confirmation.
|
|
4189
|
+
|
|
4190
|
+
```console
|
|
4191
|
+
ww-agentic-workflows init
|
|
4192
|
+
ww-agentic-workflows cleanup
|
|
4193
|
+
ww-agentic-workflows reset TASK-123 --yes
|
|
4194
|
+
```
|
|
4195
|
+
|
|
4196
|
+
## When an automatic handler fails
|
|
4197
|
+
|
|
4198
|
+
By default, ww stops the task and hands the decision to the operator. An
|
|
4199
|
+
automatic shell/argv handler can instead declare `on_failure: fix` to request
|
|
4200
|
+
an agent repair before ww retries it:
|
|
4201
|
+
|
|
4202
|
+
```yaml
|
|
4203
|
+
- build: ~
|
|
4204
|
+
shell: npm run build
|
|
4205
|
+
on_failure: fix
|
|
4206
|
+
on_failure_instruction: Fix the build errors reported by the command.
|
|
4207
|
+
```
|
|
4208
|
+
|
|
4209
|
+
ww runs the handler until it fails, then gives an agent a repair assignment
|
|
4210
|
+
containing the command, diagnostics, full output references, and optional
|
|
4211
|
+
failure instruction. The agent fixes the cause and submits `complete` with an
|
|
4212
|
+
artifact; ww retries the handler and advances only on success. Completed
|
|
4213
|
+
preceding handlers stay completed. The repair belongs to the failed execution
|
|
4214
|
+
and creates no workflow step or hooks.
|
|
4215
|
+
|
|
4216
|
+
In `auto`, the manager dispatches the repair and repeated failures stay with
|
|
4217
|
+
that worker; in `single`, the session receives it directly. Repair artifacts
|
|
4218
|
+
survive reloads and are available through `artifacts`. Repeated failures reach
|
|
4219
|
+
the existing `limits.fixes` operator stop (default 3). An operator retry renews
|
|
4220
|
+
the budget, and an operator force skips the handler. Known failure retries
|
|
4221
|
+
need no `idempotent: true`; unknown outcomes after interruption still use the
|
|
4222
|
+
existing recovery rules.
|
|
4223
|
+
|
|
4224
|
+
`limits.auto_retries` in `ww.json` (default 0, never negative) makes ww retry a
|
|
4225
|
+
failed automatic step that many times itself before any of this applies: an
|
|
4226
|
+
interrupted step (unknown outcome) and a step that needs agent-supplied values
|
|
4227
|
+
are never retried this way. Each failed attempt is kept on the step's record, and a
|
|
4228
|
+
page that stops for the operator, or hands a repair to the agent, lists them
|
|
4229
|
+
(`ww retried this step N time(s) itself`). An operator retry starts the count over.
|
|
4230
|
+
|
|
4231
|
+
Optional `on_failure_instruction` also adds guidance to hook failures.
|
|
4232
|
+
`before_complete` hooks with `on_failure: fix` keep the step's existing check
|
|
4233
|
+
loop with its worker; see [The fix loop](#the-fix-loop).
|
|
4234
|
+
|
|
4235
|
+
For the default `on_failure: operator` policy, the failure page hands the
|
|
4236
|
+
decision to the operator as follows.
|
|
4237
|
+
|
|
4238
|
+
The page the agent receives names the command and shows what it printed:
|
|
4239
|
+
|
|
4240
|
+
```markdown
|
|
4241
|
+
### Error
|
|
4242
|
+
|
|
4243
|
+
automatic handler failed (1) running: python -m pytest -q
|
|
4244
|
+
|
|
4245
|
+
2 failed, 1 passed
|
|
4246
|
+
```
|
|
4247
|
+
|
|
4248
|
+
Either stream is reported — stderr when it has something, otherwise stdout,
|
|
4249
|
+
which is where pytest, ruff, and mypy actually write. Output longer than forty
|
|
4250
|
+
lines is tailed, and the complete text stays available through
|
|
4251
|
+
`ww artifacts <task-id>`.
|
|
4252
|
+
|
|
4253
|
+
Under "Operator recovery" the agent is told to hand over: report what failed
|
|
4254
|
+
and quote the output, say that completed work is saved and that nothing after
|
|
4255
|
+
the step has run, ask for a decision without making it, and state what happens
|
|
4256
|
+
next either way. Then stop and wait.
|
|
4257
|
+
|
|
4258
|
+
Two routes lead out, and the agent runs whichever the operator picks:
|
|
4259
|
+
|
|
4260
|
+
```console
|
|
4261
|
+
./ww next <task-id> --retry --role manager
|
|
4262
|
+
./ww next <task-id> --force --reason "<reason>" --role manager
|
|
4263
|
+
```
|
|
4264
|
+
|
|
4265
|
+
`--retry` runs the same handler again, for when the cause has been fixed.
|
|
4266
|
+
`--force` skips it and records the operator's reason in the task, so a skipped
|
|
4267
|
+
check is visible afterwards rather than forgotten. A loop that hits its
|
|
4268
|
+
round limit escalates the same way, and the force there leaves the loop.
|
|
4269
|
+
|
|
4270
|
+
## Installing the ww skills during init
|
|
4271
|
+
|
|
4272
|
+
`init` offers its bundled skills to every agent integration it knows about:
|
|
4273
|
+
`ww`, `noww`, `ww-rule`, and `ww-setup` with the skills it guides through
|
|
4274
|
+
(`ww-learn-project`, `ww-suggest`, `ww-refresh`, `ww-solve`,
|
|
4275
|
+
`ww-rules-from-artifacts`, `ww-feedback-rules`, `ww-deduce-feedback`,
|
|
4276
|
+
`ww-automate`, `ww-scriptize`, `ww-wizard`; see
|
|
4277
|
+
[Setting ww up](#setting-ww-up-learning-and-suggestions)). In a terminal it
|
|
4278
|
+
can redraw, that is one checklist rather than one question per agent:
|
|
4279
|
+
|
|
4280
|
+
```text
|
|
4281
|
+
Install the ww skills (ww, noww, ww-rule, ww-setup, ww-learn-project, …) into which agent directories?
|
|
4282
|
+
↑↓ move · space toggles · a all · enter confirms
|
|
4283
|
+
|
|
4284
|
+
> [ ] .agents
|
|
4285
|
+
[ ] .codex
|
|
4286
|
+
[x] .claude already present
|
|
4287
|
+
[ ] .gemini
|
|
4288
|
+
```
|
|
4289
|
+
|
|
4290
|
+
Directories that already exist start ticked, because having one is good
|
|
4291
|
+
evidence you use that agent. Nothing is written until you press enter, and
|
|
4292
|
+
the answers are remembered in `.ww/init-choices.json`, so a later `init` only
|
|
4293
|
+
asks about agents you have not decided on; `init --force` asks about every
|
|
4294
|
+
agent whose `ww` skill is not installed yet.
|
|
4295
|
+
|
|
4296
|
+
Where the terminal cannot be driven that way — a pipe, `TERM=dumb`, a captured
|
|
4297
|
+
stdin in a test — ww falls back to plain questions: one for each directory that
|
|
4298
|
+
already exists, then a single comma-separated question for the agents without
|
|
4299
|
+
one. `--skills` and `--no-skills` skip the interaction entirely, and
|
|
4300
|
+
`--no-input` takes the defaults.
|
|
4301
|
+
|
|
4302
|
+
`init-choices.json` also remembers which bundled skills you accepted. When a
|
|
4303
|
+
later ww version bundles a new one, the next `init` asks about that skill
|
|
4304
|
+
alone, into the directories you already chose, without the agent questions:
|
|
4305
|
+
|
|
4306
|
+
```text
|
|
4307
|
+
ww now ships the `ww-rule` skill. Install into .claude? [Y/n]:
|
|
4308
|
+
```
|
|
4309
|
+
|
|
4310
|
+
Either answer is remembered, so it is asked once; a skill you declined is not
|
|
4311
|
+
installed later on its own. Without a recorded answer, a skill already
|
|
4312
|
+
present in a chosen directory counts as accepted.
|
|
4313
|
+
|
|
4314
|
+
The rest of the summary adapts to repeat runs too. The box saying what to allow
|
|
4315
|
+
in your agents' permissions is shown the first time only (and again under
|
|
4316
|
+
`--force`), and the steps for getting started only while
|
|
4317
|
+
`ww.yaml` defines no workflow. The documentation links and
|
|
4318
|
+
the closing next step, the `ww-setup` skill, are always shown.
|
|
4319
|
+
|
|
4320
|
+
## Agent hooks
|
|
4321
|
+
|
|
4322
|
+
Agent hooks are the agent's own hooks (session-start, stop, interrupt),
|
|
4323
|
+
installed with `ww hook` and separate from the workflow hooks above. They
|
|
4324
|
+
carry ww's task state into a session — which task is unfinished, whether a
|
|
4325
|
+
step was left mid-way — without blocking the agent. `agent_hooks` in
|
|
4326
|
+
`ww.json` sets how many days back `session-start` looks for unfinished
|
|
4327
|
+
tasks (`recent_days`, 3) or switches that scan off (`check_unfinished:
|
|
4328
|
+
false`). See
|
|
4329
|
+
[documentation/agent-hooks.md](agent-hooks.md) for the events, the
|
|
4330
|
+
per-agent table, install/uninstall/show, failure behaviour, and interrupted
|
|
4331
|
+
tasks.
|
|
4332
|
+
|
|
4333
|
+
## Choosing a runtime
|
|
4334
|
+
|
|
4335
|
+
A parent and its children can use different runtimes and session models. For
|
|
4336
|
+
example, a Sol manager can coordinate an `auto` roadmap while each Luna session
|
|
4337
|
+
handles a whole child in `single`:
|
|
4338
|
+
|
|
4339
|
+
```console
|
|
4340
|
+
ww-agentic-workflows start-child TASK-123 TASK-123.1 \
|
|
4341
|
+
--workflow express --runtime single --model gpt-6-luna --reasoning high
|
|
4342
|
+
```
|
|
4343
|
+
|
|
4344
|
+
Launch the child session with those actual host settings and give it the child's
|
|
4345
|
+
manager instruction command; ww does not switch a running session's model.
|
|
4346
|
+
The parent retains its own runtime/model/reasoning and resumes its review stages
|
|
4347
|
+
after the child completes. Without flags, children inherit parent settings.
|
|
4348
|
+
Changing only the model resets reasoning to `auto`; specify both for an exact
|
|
4349
|
+
request. `--agent` starts the child for another agent and defaults to the parent's. Launch settings are fixed once starting begins and survive retries,
|
|
4350
|
+
including external-ID bootstrap. This command cannot change an already-started
|
|
4351
|
+
child's runtime or model. `--workflow` also overrides the child workflow named
|
|
4352
|
+
by the parent coordinator, without changing the parent plan or earlier children.
|
|
4353
|
+
The target must exist, cannot contain `children`, and must provide its own
|
|
4354
|
+
first-step `task_id` variable when starting an external-ID request. The chosen
|
|
4355
|
+
workflow survives interrupted starts and identity binding; it cannot change
|
|
4356
|
+
after launch begins. Omit the flag to use the configured target.
|
|
4357
|
+
|
|
4358
|
+
`single` is the default, so an agent told little more than that would omit
|
|
4359
|
+
`--runtime` and get `single` every time, including for workflows written to
|
|
4360
|
+
delegate. `discover` therefore makes the choice explicit. Any workflow declaring an `agent`,
|
|
4361
|
+
`model`, `reasoning`, or `profile` is listed with the steps that declare one
|
|
4362
|
+
and a note to start it under `auto`:
|
|
4363
|
+
|
|
4364
|
+
```markdown
|
|
4365
|
+
- `reviewed` — Cheap triage, strong review. Requests a specific worker on:
|
|
4366
|
+
`triage`, `review` — start it with `--runtime auto` so those requests apply.
|
|
4367
|
+
```
|
|
4368
|
+
|
|
4369
|
+
The search covers the whole workflow, not just its top level: nested steps,
|
|
4370
|
+
loop bodies, per-item stages, and assessment branches all count, as does a
|
|
4371
|
+
setting on the workflow itself. A workflow that requests nothing is listed
|
|
4372
|
+
without a note, so the marker means something.
|
|
4373
|
+
|
|
4374
|
+
`--json` reports the same thing as `delegation_requests`, the list of step
|
|
4375
|
+
names carrying a request, empty when there are none.
|
|
4376
|
+
|
|
4377
|
+
This is advice, not enforcement. `single` remains right when nothing is
|
|
4378
|
+
requested, when delegation is unavailable or not permitted, or when the
|
|
4379
|
+
operator asked the session to do the work itself — and a workflow that always
|
|
4380
|
+
wants delegation should declare `runtime: auto` rather than rely on the reader.
|
|
4381
|
+
|
|
4382
|
+
## Choosing the ww binary
|
|
4383
|
+
|
|
4384
|
+
A project names the ww it runs in `ww.json`:
|
|
4385
|
+
|
|
4386
|
+
```json
|
|
4387
|
+
{"executable": "ww-agentic-workflows-dev"}
|
|
4388
|
+
```
|
|
4389
|
+
|
|
4390
|
+
The value is a command on `PATH` or a path. Every command ww prints for that
|
|
4391
|
+
project starts with it — `ww-agentic-workflows-dev next TASK-1 --role
|
|
4392
|
+
manager` — and the `./ww` launcher runs it, reading the key each time, so a
|
|
4393
|
+
project switches installs by editing that one line. Like every setting, the key
|
|
4394
|
+
may also come from the user or local settings file, the local one winning,
|
|
4395
|
+
so one checkout can use a development install without changing the shared
|
|
4396
|
+
file. Without the key, printed
|
|
4397
|
+
commands use `./ww` and the launcher runs `ww-agentic-workflows`. `init` writes
|
|
4398
|
+
`"executable": "ww-agentic-workflows"` when the key is missing. The launcher
|
|
4399
|
+
itself is ww-owned: `init` rewrites a `./ww` that differs from the current one
|
|
4400
|
+
and reports it as updated, because a launcher an older ww wrote can read old
|
|
4401
|
+
configuration file names and run the wrong binary, and `lint` warns about such
|
|
4402
|
+
a launcher, naming `init` as the fix. Choose the binary with `executable`, not
|
|
4403
|
+
by editing `./ww`.
|
|
4404
|
+
|
|
4405
|
+
This is what lets two installs live side by side, for example a source
|
|
4406
|
+
checkout for developing ww itself, switched between branches often, and the
|
|
4407
|
+
PyPI development snapshots for use in other projects. pipx gives the second a
|
|
4408
|
+
different global name:
|
|
4409
|
+
|
|
4410
|
+
```console
|
|
4411
|
+
pipx install --editable ~/tools/agentic-workflows
|
|
4412
|
+
pipx install --suffix=-dev --pip-args=--pre ww-agentic-workflows
|
|
4413
|
+
```
|
|
4414
|
+
|
|
4415
|
+
Two checkouts work the same way, with
|
|
4416
|
+
`pipx install --editable --suffix=-dev ~/tools/agentic-workflows-dev`. A
|
|
4417
|
+
project that should use the second install then sets
|
|
4418
|
+
`"executable": "ww-agentic-workflows-dev"`. The update notice, `--version`,
|
|
4419
|
+
and the audit log keep naming the package, `ww-agentic-workflows`.
|
|
4420
|
+
|
|
4421
|
+
Either install also runs inside the other checkout, for example the
|
|
4422
|
+
development install used for all work in the `dev` checkout. ww recognises a
|
|
4423
|
+
checkout of its own source by `src/ww/extensions/registry.py` and then uses the
|
|
4424
|
+
running install's bundled `ww/git`, ignoring the checkout's own `ext/ww/*`
|
|
4425
|
+
copy. In any other project, an `ext/ww/<name>` of its own is refused as
|
|
4426
|
+
a duplicate of the bundled extension.
|
|
4427
|
+
|
|
4428
|
+
## Update notices and upgrading ww
|
|
4429
|
+
|
|
4430
|
+
Before normal commands, ww checks for an update at most once a day and shows
|
|
4431
|
+
an available update once. Editable Git installs compare their checkout with
|
|
4432
|
+
its tracking branch and summarise changelog entries or commit subjects.
|
|
4433
|
+
Package installs query PyPI for newer compatible, non-yanked versions. Stable
|
|
4434
|
+
installs select stable releases; dev, beta and RC installs also allow
|
|
4435
|
+
prereleases. A final release supersedes its earlier prereleases.
|
|
4436
|
+
|
|
4437
|
+
A package notice names both versions and the upgrade command, using the
|
|
4438
|
+
project's configured executable, for example:
|
|
4439
|
+
|
|
4440
|
+
```text
|
|
4441
|
+
## A newer ww is available
|
|
4442
|
+
|
|
4443
|
+
1.0.0.dev42 → 1.0.0.dev47
|
|
4444
|
+
Run: `ww-agentic-workflows upgrade`
|
|
4445
|
+
```
|
|
4446
|
+
|
|
4447
|
+
Checks have short network timeouts and remain silent when offline. Notices go
|
|
4448
|
+
to stderr for JSON and other machine-readable commands. They do not prevent
|
|
4449
|
+
the requested command from running. PyPI checks send only an ordinary package
|
|
4450
|
+
metadata request, with no project or task information.
|
|
4451
|
+
|
|
4452
|
+
```console
|
|
4453
|
+
ww-agentic-workflows updates # repeat the cached notice
|
|
4454
|
+
ww-agentic-workflows updates --now # force a fresh check
|
|
4455
|
+
ww-agentic-workflows updates --json # machine-readable version information
|
|
4456
|
+
ww-agentic-workflows upgrade # preserve the installed release preference
|
|
4457
|
+
ww-agentic-workflows upgrade --pre # allow prereleases from a stable installation
|
|
4458
|
+
```
|
|
4459
|
+
|
|
4460
|
+
`upgrade` uses pip in the running Python environment or pipx for the owning
|
|
4461
|
+
pipx environment, including suffixed installs and a custom `PIPX_HOME`. A pipx
|
|
4462
|
+
installation requires pipx on PATH. An installation injected into another
|
|
4463
|
+
pipx environment must be upgraded through pipx directly. Installer errors are
|
|
4464
|
+
reported without overriding environment protections.
|
|
4465
|
+
|
|
4466
|
+
For editable Git installs, `upgrade` performs `git pull --ff-only` on the
|
|
4467
|
+
checkout's actual tracking branch. It refuses local changes, detached HEAD,
|
|
4468
|
+
missing upstream and divergent history; local work is not stashed or reset.
|
|
4469
|
+
An editable installation without a Git checkout cannot use this command.
|
|
4470
|
+
|
|
4471
|
+
`upgrade` does not check for open tasks. A release that changes the task state
|
|
4472
|
+
format can leave tasks an older build started unreadable, in this project and in
|
|
4473
|
+
every other project that uses the same installation, so read the changelog and
|
|
4474
|
+
finish such tasks first when it says so.
|
|
4475
|
+
|
|
4476
|
+
The per-user cache is under `~/.config/ww`, or `$XDG_CONFIG_HOME/ww`; package
|
|
4477
|
+
checks have separate cache files for each Python environment and installed
|
|
4478
|
+
version. `WW_STATE_HOME` overrides the cache directory. `update_check: false`
|
|
4479
|
+
in `ww.json` disables notices for a project; `WW_UPDATE_CHECK=0` disables them
|
|
4480
|
+
globally. `WW_UPDATE_CHECK_INTERVAL` sets the seconds between checks.
|
|
4481
|
+
|
|
4482
|
+
## A newer ww is available
|
|
4483
|
+
|
|
4484
|
+
This checkout is 23 commits behind `origin/main`.
|
|
4485
|
+
|
|
4486
|
+
What changed:
|
|
4487
|
+
|
|
4488
|
+
- `interact --pause` records that the operator is done for now.
|
|
4489
|
+
- Items carry custom string fields, set with `--field NAME=VALUE`.
|
|
4490
|
+
|
|
4491
|
+
**Tell the person you are working for about this before you continue**, and let them decide whether to update. To update:
|
|
4492
|
+
|
|
4493
|
+
```console
|
|
4494
|
+
git -C ~/tools/agentic-workflows pull
|
|
4495
|
+
```
|
|
4496
|
+
|
|
4497
|
+
This notice is shown once. `ww updates` prints it again.
|
|
4498
|
+
````
|
|
4499
|
+
|
|
4500
|
+
The notice is announced, never enforced: it is written above the command's
|
|
4501
|
+
own output, which then runs exactly as it would have. An agent relaying that
|
|
4502
|
+
output shows the update to the operator first, and the decision to pull is
|
|
4503
|
+
theirs. Nothing is reported anywhere — the only network call is a `git fetch`
|
|
4504
|
+
against the remote the user cloned from, made at most once a day, and every
|
|
4505
|
+
failure in it, including no network at all, leaves the command untouched.
|
|
4506
|
+
|
|
4507
|
+
What the notice lists comes from the bullets added to `CHANGELOG.md` between
|
|
4508
|
+
the two commits, falling back to commit subjects when the changelog did not
|
|
4509
|
+
change. The branch compared against is whatever the checkout tracks, so
|
|
4510
|
+
someone following `dev` is told about `dev`.
|
|
4511
|
+
|
|
4512
|
+
Each notice is shown once. The record of what was already announced is per
|
|
4513
|
+
user, in `~/.config/ww/updates.json` (under `$XDG_CONFIG_HOME` when that is
|
|
4514
|
+
set), because the
|
|
4515
|
+
installation is shared by every project on the machine — acknowledging an
|
|
4516
|
+
update in one project does not raise it again in the next.
|
|
4517
|
+
|
|
4518
|
+
```console
|
|
4519
|
+
ww-agentic-workflows updates # print the last notice again
|
|
4520
|
+
ww-agentic-workflows updates --now # look now, before the next check is due
|
|
4521
|
+
```
|
|
4522
|
+
|
|
4523
|
+
For a command whose output is consumed by a program — the JSON catalogs,
|
|
4524
|
+
`artifacts`, `items`, anything with `--json` — the notice goes to standard
|
|
4525
|
+
error instead, so standard output stays parseable.
|
|
4526
|
+
|
|
4527
|
+
To switch the check off for a project, set `"update_check": false` in
|
|
4528
|
+
`ww.json`. `WW_UPDATE_CHECK=0` switches it off everywhere,
|
|
4529
|
+
and `WW_UPDATE_CHECK_INTERVAL` sets the seconds between checks.
|
|
4530
|
+
|
|
4531
|
+
## Learning from operator feedback
|
|
4532
|
+
|
|
4533
|
+
Feedback deduction is optional follow-up work after a workflow completes. It
|
|
4534
|
+
adds no assignments, gates, handlers or steps to the workflow. Explicitly mark
|
|
4535
|
+
artifact-producing steps that can contain useful negative feedback:
|
|
4536
|
+
|
|
4537
|
+
```yaml
|
|
4538
|
+
workflows:
|
|
4539
|
+
- name: task
|
|
4540
|
+
steps:
|
|
4541
|
+
- review: Review the changes and record any corrections.
|
|
4542
|
+
learnable: true
|
|
4543
|
+
- confirm: Confirm acceptance with the operator.
|
|
4544
|
+
interactive: true
|
|
4545
|
+
learnable: true
|
|
4546
|
+
```
|
|
4547
|
+
|
|
4548
|
+
`learnable` is a boolean, default `false`, independent of `interactive`.
|
|
4549
|
+
Noninteractive reviews can be learnable; ordinary conversations are not
|
|
4550
|
+
learnable unless explicitly marked. It requires `artifact: true`. The saved
|
|
4551
|
+
plan retains this source metadata without inserting learning work. When an
|
|
4552
|
+
eligible artifact exists and `feedback_learning` in `ww.json` is enabled
|
|
4553
|
+
(default `true`), the completed-workflow page suggests the `ww-deduce-feedback`
|
|
4554
|
+
skill. It does not run deduction automatically or postpone completion.
|
|
4555
|
+
|
|
4556
|
+
The skill reads negative feedback from eligible completed-step artifacts,
|
|
4557
|
+
reasons about possible recurrence and matches existing generalizations by
|
|
4558
|
+
meaning. It records whether enforcement could be scripted or needs reasoning,
|
|
4559
|
+
including a concrete approach and its limits. A candidate is an observation,
|
|
4560
|
+
not an obligation or an installed rule. A single encounter can be useful;
|
|
4561
|
+
frequency is evidence rather than a required threshold or prediction.
|
|
4562
|
+
|
|
4563
|
+
The internal commands expose sources and stable point IDs:
|
|
4564
|
+
|
|
4565
|
+
```console
|
|
4566
|
+
./ww feedback sources TASK-42 --run 01-task --json
|
|
4567
|
+
./ww feedback --json
|
|
4568
|
+
./ww feedback get feedback-a1b2c3d4e5f6 --json
|
|
4569
|
+
```
|
|
4570
|
+
|
|
4571
|
+
`sources` returns artifact source IDs, completion timestamps and content through
|
|
4572
|
+
ww's storage adapter. Only completed runs and explicitly learnable artifacts
|
|
4573
|
+
are eligible, including retained loop-round results. `show` is an alias for
|
|
4574
|
+
`sources`. Do not use arbitrary files or interactive transcripts as deduction
|
|
4575
|
+
sources. Generalize feedback in the artifacts, not artifact headings or
|
|
4576
|
+
instructions. The agent decides meaning; ww validates supporting quotes.
|
|
4577
|
+
|
|
4578
|
+
Write an analysis array such as this, substituting a source ID ww returned:
|
|
4579
|
+
|
|
4580
|
+
```json
|
|
4581
|
+
[
|
|
4582
|
+
{
|
|
4583
|
+
"summary": "Use English variable names.",
|
|
4584
|
+
"reason": "Unspecified naming language can recur in new code.",
|
|
4585
|
+
"enforcement": "reasoning",
|
|
4586
|
+
"approach": "Review identifiers; dictionaries have false positives.",
|
|
4587
|
+
"evidence": [
|
|
4588
|
+
{"source": "artifact-a1b2c3d4e5f60000", "quote": "Use English names."}
|
|
4589
|
+
]
|
|
4590
|
+
}
|
|
4591
|
+
]
|
|
4592
|
+
```
|
|
4593
|
+
|
|
4594
|
+
Then record the deductions after completion:
|
|
4595
|
+
|
|
4596
|
+
```console
|
|
4597
|
+
./ww feedback record TASK-42 --run 01-task --analysis analysis.json --role manager
|
|
4598
|
+
```
|
|
4599
|
+
|
|
4600
|
+
New points omit `id`. For another encounter of an existing point, supply its
|
|
4601
|
+
exact `id` from `list` or `get`; ww rejects a same-wording update with new
|
|
4602
|
+
evidence if the ID is omitted. Semantic matching of paraphrases belongs to the
|
|
4603
|
+
agent. Repeating the same point and artifact quote does not add an occurrence,
|
|
4604
|
+
including retries of new-point creation. New supporting quotes increment the
|
|
4605
|
+
count; several quotes in one task still count as one distinct encountered task.
|
|
4606
|
+
Record `[]` when no negative feedback generalizes. Recording deductions does
|
|
4607
|
+
not change the completed run's state, plan, progress or assignment.
|
|
4608
|
+
|
|
4609
|
+
The locked, atomically written `.ww/feedback.json` store retains provenance,
|
|
4610
|
+
occurrences, distinct encountered tasks, and `last_encountered_at`: the latest
|
|
4611
|
+
supporting artifact completion time, not deduction time. This timestamp is
|
|
4612
|
+
stored for later use; it does not affect pruning. `task_ratio` measures the
|
|
4613
|
+
fraction of tracked tasks with encounters; `occurrence_ratio` divides supporting
|
|
4614
|
+
quote encounters by tracked tasks and can exceed one. `completed_task_ratio`
|
|
4615
|
+
uses completed tasks on both sides. Tracking counts each completed task once
|
|
4616
|
+
and also includes explicitly analysed historical tasks; it never automatically
|
|
4617
|
+
backfills old tasks. Earlier transcript-based points remain readable with their
|
|
4618
|
+
IDs and counts; their timestamp is derived from the recorded interaction time.
|
|
4619
|
+
|
|
4620
|
+
Completion and deduction never delete points. Run `/ww-feedback-rules` for a
|
|
4621
|
+
separate review of every candidate and a batch of concrete rule proposals.
|
|
4622
|
+
The skill asks for explicit approval before writing rules through validated
|
|
4623
|
+
`ww rules` commands. It also maintains the store using a separate command:
|
|
4624
|
+
|
|
4625
|
+
```console
|
|
4626
|
+
./ww feedback prune --dry-run --json
|
|
4627
|
+
./ww feedback prune --keep feedback-a1b2c3d4e5f6 --json
|
|
4628
|
+
```
|
|
4629
|
+
|
|
4630
|
+
Pruning deletes candidates absent for five subsequently completed tasks. The
|
|
4631
|
+
review skill examines all candidates first and retains stale points still
|
|
4632
|
+
useful for proposals or deferred decisions with repeatable `--keep` IDs. A
|
|
4633
|
+
pruning preview does not write anything. Original artifacts are never deleted.
|
|
4634
|
+
No elapsed-time policy is implied by `last_encountered_at`.
|
|
4635
|
+
|
|
4636
|
+
Set `"feedback_learning": false` in `ww.json` to disable completion suggestions,
|
|
4637
|
+
deduction recording and task-exposure tracking. Existing candidates and
|
|
4638
|
+
artifacts remain readable. Pruning remains an explicit maintenance operation,
|
|
4639
|
+
independent of that suggestion setting.
|