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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. ww/__init__.py +18 -0
  2. ww/_bundled_extensions/ww/git/extension.py +1728 -0
  3. ww/action_execution.py +887 -0
  4. ww/actions/__init__.py +94 -0
  5. ww/actions/command.py +444 -0
  6. ww/actions/contracts.py +699 -0
  7. ww/actions/extension.py +197 -0
  8. ww/actions/mcp.py +84 -0
  9. ww/actions/prompt.py +74 -0
  10. ww/actions/skill.py +62 -0
  11. ww/actions/slash_command.py +63 -0
  12. ww/agents.py +151 -0
  13. ww/amendments.py +54 -0
  14. ww/artifacts.py +93 -0
  15. ww/assessments.py +181 -0
  16. ww/assets/__init__.py +2 -0
  17. ww/assets/agent_instructions.md +49 -0
  18. ww/assets/docs/examples.md +879 -0
  19. ww/assets/docs/features.md +4639 -0
  20. ww/assets/docs/specification.md +1876 -0
  21. ww/assets/noww_skill.md +11 -0
  22. ww/assets/workflows/catchall.yaml +26 -0
  23. ww/assets/workflows/onboarding.yaml +586 -0
  24. ww/assets/workflows/scriptize.yaml +130 -0
  25. ww/assets/ww-automate_skill.md +23 -0
  26. ww/assets/ww-deduce-feedback_skill.md +38 -0
  27. ww/assets/ww-feedback-rules_skill.md +48 -0
  28. ww/assets/ww-learn-project_skill.md +22 -0
  29. ww/assets/ww-refresh_skill.md +26 -0
  30. ww/assets/ww-rule_skill.md +83 -0
  31. ww/assets/ww-rules-from-artifacts_skill.md +22 -0
  32. ww/assets/ww-scriptize_skill.md +33 -0
  33. ww/assets/ww-setup_skill.md +94 -0
  34. ww/assets/ww-solve_skill.md +23 -0
  35. ww/assets/ww-suggest_skill.md +32 -0
  36. ww/assets/ww-wizard_skill.md +105 -0
  37. ww/assets/ww_skill.md +59 -0
  38. ww/assignments.py +283 -0
  39. ww/bootstrap.py +405 -0
  40. ww/builtin_workflows.py +215 -0
  41. ww/changes.py +225 -0
  42. ww/child_coordination.py +482 -0
  43. ww/children.py +106 -0
  44. ww/claude_permissions.py +115 -0
  45. ww/cli/__init__.py +7 -0
  46. ww/cli/__main__.py +6 -0
  47. ww/cli/audit.py +129 -0
  48. ww/cli/catalogs.py +131 -0
  49. ww/cli/discover.py +607 -0
  50. ww/cli/initialization.py +898 -0
  51. ww/cli/lookup.py +287 -0
  52. ww/cli/main.py +1768 -0
  53. ww/cli/parser.py +1200 -0
  54. ww/cli/prompts.py +217 -0
  55. ww/cli/updates.py +117 -0
  56. ww/completion_artifacts.py +156 -0
  57. ww/completion_inputs.py +39 -0
  58. ww/config/__init__.py +582 -0
  59. ww/config/actions.py +591 -0
  60. ww/config/composition.py +571 -0
  61. ww/config/rules.py +511 -0
  62. ww/config/steps.py +1220 -0
  63. ww/config/values.py +223 -0
  64. ww/config_files.py +191 -0
  65. ww/config_writes.py +264 -0
  66. ww/contracts.py +155 -0
  67. ww/control.py +41 -0
  68. ww/defaults.py +130 -0
  69. ww/design_docs.py +32 -0
  70. ww/discovery.py +104 -0
  71. ww/documents.py +217 -0
  72. ww/errors.py +18 -0
  73. ww/executable.py +43 -0
  74. ww/execution_models/__init__.py +64 -0
  75. ww/execution_models/construction.py +148 -0
  76. ww/execution_models/decoding.py +38 -0
  77. ww/execution_models/plan_codec.py +565 -0
  78. ww/execution_models/records.py +1206 -0
  79. ww/execution_models/runs.py +266 -0
  80. ww/extensions/__init__.py +40 -0
  81. ww/extensions/api.py +559 -0
  82. ww/extensions/registry.py +864 -0
  83. ww/extensions/store.py +78 -0
  84. ww/feedback.py +342 -0
  85. ww/handler_repairs.py +57 -0
  86. ww/hooks/__init__.py +40 -0
  87. ww/hooks/agents.py +380 -0
  88. ww/hooks/install.py +168 -0
  89. ww/hooks/notices.py +206 -0
  90. ww/hooks/records.py +209 -0
  91. ww/hooks/runtime.py +266 -0
  92. ww/hooks/transcripts.py +183 -0
  93. ww/inspect.py +896 -0
  94. ww/instructions/__init__.py +17 -0
  95. ww/instructions/builder.py +1682 -0
  96. ww/instructions/commands.py +335 -0
  97. ww/instructions/handoff.py +149 -0
  98. ww/instructions/models.py +686 -0
  99. ww/instructions/policy.py +219 -0
  100. ww/instructions/text.py +168 -0
  101. ww/interactions.py +187 -0
  102. ww/interpolation.py +37 -0
  103. ww/item_passes.py +167 -0
  104. ww/items.py +99 -0
  105. ww/locking.py +207 -0
  106. ww/metadata_publication.py +230 -0
  107. ww/onboarding.py +229 -0
  108. ww/open_work.py +236 -0
  109. ww/operations.py +193 -0
  110. ww/operator_ui/__init__.py +16 -0
  111. ww/operator_ui/page.html +351 -0
  112. ww/operator_ui/server.py +215 -0
  113. ww/operator_ui/session.py +389 -0
  114. ww/operator_ui/sheet.py +104 -0
  115. ww/operator_ui/view.py +109 -0
  116. ww/output.py +339 -0
  117. ww/output_adapters/__init__.py +12 -0
  118. ww/output_adapters/base.py +25 -0
  119. ww/output_adapters/json_adapter.py +37 -0
  120. ww/output_adapters/markdown.py +2293 -0
  121. ww/output_adapters/rule_pages.py +337 -0
  122. ww/output_adapters/terminal.py +21 -0
  123. ww/package_updates.py +167 -0
  124. ww/plan/__init__.py +38 -0
  125. ww/plan/actions.py +207 -0
  126. ww/plan/compiler.py +1492 -0
  127. ww/plan/constructs.py +456 -0
  128. ww/plan/models.py +665 -0
  129. ww/project_config.py +752 -0
  130. ww/recovery.py +401 -0
  131. ww/replanning.py +367 -0
  132. ww/results.py +77 -0
  133. ww/rule_checks.py +230 -0
  134. ww/rule_conversion.py +331 -0
  135. ww/rule_disputes.py +148 -0
  136. ww/rule_store.py +456 -0
  137. ww/rule_verification.py +714 -0
  138. ww/rule_views.py +447 -0
  139. ww/rule_writes.py +920 -0
  140. ww/run_coordination.py +158 -0
  141. ww/runtimes.py +105 -0
  142. ww/service.py +4405 -0
  143. ww/setup_apply.py +428 -0
  144. ww/step_values.py +20 -0
  145. ww/storage.py +447 -0
  146. ww/storage_adapters/__init__.py +36 -0
  147. ww/storage_adapters/base.py +540 -0
  148. ww/storage_adapters/filesystem.py +370 -0
  149. ww/storage_adapters/memory.py +195 -0
  150. ww/storage_adapters/project_metadata.py +69 -0
  151. ww/storage_adapters/task_document.py +484 -0
  152. ww/task_ids.py +114 -0
  153. ww/task_references.py +124 -0
  154. ww/transitions.py +1619 -0
  155. ww/updates.py +399 -0
  156. ww/upgrade.py +95 -0
  157. ww/validation.py +168 -0
  158. ww/variables.py +275 -0
  159. ww/workflow_config.py +854 -0
  160. ww/workflow_update.py +239 -0
  161. ww/workflow_validation.py +1260 -0
  162. ww/workspace.py +50 -0
  163. ww_agentic_workflows-1.0.0.dev3.dist-info/METADATA +690 -0
  164. ww_agentic_workflows-1.0.0.dev3.dist-info/RECORD +167 -0
  165. ww_agentic_workflows-1.0.0.dev3.dist-info/WHEEL +4 -0
  166. ww_agentic_workflows-1.0.0.dev3.dist-info/entry_points.txt +2 -0
  167. ww_agentic_workflows-1.0.0.dev3.dist-info/licenses/LICENSE +674 -0
@@ -0,0 +1,686 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Instruction contract and serialization."""
3
+
4
+ from __future__ import annotations
5
+
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING
8
+
9
+ from ww.amendments import Amendment
10
+ from ww.assessments import AssessmentOutcome
11
+ from ww.children import ChildTask
12
+ from ww.contracts import (
13
+ CallerRole,
14
+ Control,
15
+ InstructionStatus,
16
+ ItemStatus,
17
+ NextRole,
18
+ OperatorReason,
19
+ PlanItemKind,
20
+ RecoveryAction,
21
+ StepRole,
22
+ )
23
+ from ww.execution_models import WorkflowRunSummary
24
+ from ww.items import WorkItem
25
+ from ww.plan import PlannedMode
26
+
27
+ if TYPE_CHECKING:
28
+ from ww.replanning import PlanChange
29
+ from ww.workflow_config import (
30
+ ChoiceDefinition,
31
+ ItemFieldUpdate,
32
+ ProvidedVariable,
33
+ SavedMetadata,
34
+ )
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class RecoveryCommand:
39
+ """One operator choice for an interrupted automatic item."""
40
+
41
+ action: RecoveryAction
42
+ command: str
43
+
44
+ def to_dict(self) -> dict[str, str]:
45
+ return {"action": self.action, "command": self.command}
46
+
47
+
48
+ @dataclass(frozen=True)
49
+ class StepHandover:
50
+ """One completed step's handover, an input to the workflow summary."""
51
+
52
+ step: str
53
+ artifact: str
54
+ summary: str | None
55
+
56
+ def to_dict(self) -> dict[str, str | None]:
57
+ return {"step": self.step, "artifact": self.artifact, "summary": self.summary}
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class DocumentTask:
62
+ """A document the active step must create or update, with its file."""
63
+
64
+ name: str
65
+ instruction: str
66
+ path: str
67
+ exists: bool
68
+
69
+ def to_dict(self) -> dict[str, object]:
70
+ return {
71
+ "name": self.name,
72
+ "instruction": self.instruction,
73
+ "path": self.path,
74
+ "exists": self.exists,
75
+ }
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class InteractCommands:
80
+ """The commands of an interactive step, spelled for the role that holds it."""
81
+
82
+ # Record the whole conversation and end it: a heredoc transcript, with
83
+ # the pick in the same call on a step with choices.
84
+ transcript: str
85
+ operator: str
86
+ agent: str
87
+ choice: str
88
+ end: str
89
+ # Serve the operator page and wait on it; a ``ui`` stage only.
90
+ wait: str
91
+ # Show the step again after the operator paused and returned.
92
+ resume: str
93
+
94
+ def to_dict(self) -> dict[str, str]:
95
+ return {
96
+ "transcript": self.transcript,
97
+ "operator": self.operator,
98
+ "agent": self.agent,
99
+ "choice": self.choice,
100
+ "end": self.end,
101
+ "wait": self.wait,
102
+ "resume": self.resume,
103
+ }
104
+
105
+
106
+ @dataclass(frozen=True)
107
+ class ConversationEntry:
108
+ """One recorded entry of the current step's conversation."""
109
+
110
+ at: str
111
+ speaker: str
112
+ text: str
113
+
114
+ def to_dict(self) -> dict[str, str]:
115
+ return {"at": self.at, "speaker": self.speaker, "text": self.text}
116
+
117
+
118
+ @dataclass(frozen=True)
119
+ class RuleLine:
120
+ """One rule the active step is given, as its page lists it.
121
+
122
+ ``has_command`` rules are checked by ww when the step completes; the
123
+ others the worker follows and reports on in its artifact.
124
+ """
125
+
126
+ id: str
127
+ summary: str
128
+ paths: tuple[str, ...] = ()
129
+ has_command: bool = False
130
+ hook: bool = False
131
+ interpretation: str | None = None
132
+ # The converted derived check that checks a rule without a command, or,
133
+ # with ``missing``, the converted check that does not apply here.
134
+ check: str | None = None
135
+ # A verifier judges the rule because its converted ``check`` does not
136
+ # apply here: this configuration file is missing in the step's directory.
137
+ missing: str | None = None
138
+
139
+ def to_dict(self) -> dict[str, object]:
140
+ return {
141
+ "id": self.id,
142
+ "summary": self.summary,
143
+ "paths": list(self.paths),
144
+ "has_command": self.has_command,
145
+ "hook": self.hook,
146
+ "interpretation": self.interpretation,
147
+ "check": self.check,
148
+ "missing": self.missing,
149
+ }
150
+
151
+
152
+ @dataclass(frozen=True)
153
+ class FixFailure:
154
+ """One check that failed when the worker last completed the step."""
155
+
156
+ id: str
157
+ hook: bool
158
+ text: str | None
159
+ command: str
160
+ output: str
161
+ # A verifier's verdict rather than a command: ``output`` is its evidence.
162
+ judged: bool = False
163
+ # For a derived check: the rules it covers, whose texts are ``text``.
164
+ covers: tuple[str, ...] = ()
165
+
166
+ def to_dict(self) -> dict[str, object]:
167
+ return {
168
+ "id": self.id,
169
+ "hook": self.hook,
170
+ "text": self.text,
171
+ "command": self.command,
172
+ "output": self.output,
173
+ "judged": self.judged,
174
+ "covers": list(self.covers),
175
+ }
176
+
177
+
178
+ @dataclass(frozen=True)
179
+ class DisputeView:
180
+ """A worker's dispute of a check, as the operator decides it.
181
+
182
+ ``failure`` is the disputed check as it last failed, ``reason`` the
183
+ worker's argument, ``attempt`` the rejected completion it answers.
184
+ """
185
+
186
+ failure: FixFailure
187
+ reason: str
188
+ attempt: int
189
+
190
+ def to_dict(self) -> dict[str, object]:
191
+ return {
192
+ "check": self.failure.to_dict(),
193
+ "reason": self.reason,
194
+ "attempt": self.attempt,
195
+ }
196
+
197
+
198
+ @dataclass(frozen=True)
199
+ class CheckPreview:
200
+ """What ``ww check`` found: the step's checks run now, nothing recorded.
201
+
202
+ ``failures`` are shown like a fix page; ``passed`` and ``not_applicable``
203
+ name the other checks; ``judged`` are the rules a verifier judges only
204
+ when the step completes; ``waived`` the checks the operator waived.
205
+ """
206
+
207
+ task_id: str
208
+ step: str
209
+ checks: int
210
+ failures: tuple[FixFailure, ...] = ()
211
+ passed: tuple[str, ...] = ()
212
+ not_applicable: tuple[str, ...] = ()
213
+ judged: tuple[str, ...] = ()
214
+ waived: tuple[tuple[str, str], ...] = ()
215
+ all_files: bool = False
216
+
217
+ def to_dict(self) -> dict[str, object]:
218
+ return {
219
+ "task_id": self.task_id,
220
+ "step": self.step,
221
+ "checks": self.checks,
222
+ "passed": not self.failures,
223
+ "failures": [failure.to_dict() for failure in self.failures],
224
+ "passed_checks": list(self.passed),
225
+ "not_applicable": list(self.not_applicable),
226
+ "judged_at_completion": list(self.judged),
227
+ "waived": dict(self.waived),
228
+ "all_files": self.all_files,
229
+ }
230
+
231
+
232
+ @dataclass(frozen=True)
233
+ class VerificationRuleLine:
234
+ """One rule on a verification page, which the verifier judges."""
235
+
236
+ id: str
237
+ text: str
238
+ interpretation: str | None = None
239
+ # Its converted ``check`` does not apply here: this configuration file is
240
+ # missing in the step's directory.
241
+ check: str | None = None
242
+ missing: str | None = None
243
+
244
+ def to_dict(self) -> dict[str, object]:
245
+ return {
246
+ "id": self.id,
247
+ "text": self.text,
248
+ "interpretation": self.interpretation,
249
+ "check": self.check,
250
+ "missing": self.missing,
251
+ }
252
+
253
+
254
+ @dataclass(frozen=True)
255
+ class VerificationPage:
256
+ """What a verification item's worker needs: the rules and the evidence.
257
+
258
+ ``files`` is the verified step's change set, or every file when
259
+ ``all_files`` (no git); ``diff_command`` shows the change itself;
260
+ ``draft_artifact`` is the path of the step worker's held artifact.
261
+ """
262
+
263
+ step: str
264
+ rules: tuple[VerificationRuleLine, ...]
265
+ files: tuple[str, ...] = ()
266
+ all_files: bool = False
267
+ diff_command: str | None = None
268
+ draft_artifact: str | None = None
269
+
270
+ def to_dict(self) -> dict[str, object]:
271
+ return {
272
+ "step": self.step,
273
+ "rules": [rule.to_dict() for rule in self.rules],
274
+ "files": list(self.files),
275
+ "all_files": self.all_files,
276
+ "diff_command": self.diff_command,
277
+ "draft_artifact": self.draft_artifact,
278
+ }
279
+
280
+
281
+ @dataclass(frozen=True)
282
+ class FixRequired:
283
+ """ww refused the step's completion: the failed checks and the count.
284
+
285
+ ``attempt`` is the number of rejected completions so far and
286
+ ``max_fixes`` the most any of the step's checks allows.
287
+ """
288
+
289
+ attempt: int
290
+ max_fixes: int
291
+ checks: int
292
+ failures: tuple[FixFailure, ...]
293
+ draft_artifact: str | None = None
294
+
295
+ def to_dict(self) -> dict[str, object]:
296
+ return {
297
+ "attempt": self.attempt,
298
+ "max_fixes": self.max_fixes,
299
+ "checks": self.checks,
300
+ "failures": [failure.to_dict() for failure in self.failures],
301
+ "draft_artifact": self.draft_artifact,
302
+ }
303
+
304
+
305
+ @dataclass(frozen=True)
306
+ class HandoffStep:
307
+ """One item a worker performed in an assignment, as the handoff reports it.
308
+
309
+ ``outcome`` is ``completed``, ``loop break``, ``loop continue``,
310
+ ``held for verification``, ``failed``, or ``not completed``; ``checks``
311
+ pairs each check of the last attempt with its status, and ``fix_rounds``
312
+ counts the completions ww rejected.
313
+ """
314
+
315
+ name: str
316
+ outcome: str
317
+ artifact: str | None = None
318
+ checks: tuple[tuple[str, str], ...] = ()
319
+ checks_waived: tuple[str, ...] = ()
320
+ fix_rounds: int = 0
321
+ error: str | None = None
322
+
323
+ def to_dict(self) -> dict[str, object]:
324
+ return {
325
+ "name": self.name,
326
+ "outcome": self.outcome,
327
+ "artifact": self.artifact,
328
+ "checks": dict(self.checks),
329
+ "checks_waived": list(self.checks_waived),
330
+ "fix_rounds": self.fix_rounds,
331
+ "error": self.error,
332
+ }
333
+
334
+
335
+ @dataclass(frozen=True)
336
+ class HandoffBlock:
337
+ """What ww tells the manager when a worker's assignment ends.
338
+
339
+ Built from the saved state, so the manager never depends on free text
340
+ from the worker; the worker's own words are only its short ``summary``.
341
+ ``files`` is ``None`` when no change set was taken: no step of the
342
+ assignment has rules or checks, or the directory has no git.
343
+ """
344
+
345
+ task_id: str
346
+ token: str
347
+ steps: tuple[HandoffStep, ...] = ()
348
+ files: tuple[str, ...] | None = None
349
+ summary: str | None = None
350
+ error: str | None = None
351
+
352
+ def to_dict(self) -> dict[str, object]:
353
+ return {
354
+ "task_id": self.task_id,
355
+ "assignment": self.token,
356
+ "steps": [step.to_dict() for step in self.steps],
357
+ "files": None if self.files is None else list(self.files),
358
+ "summary": self.summary,
359
+ "error": self.error,
360
+ }
361
+
362
+
363
+ @dataclass(frozen=True)
364
+ class Instruction:
365
+ """A presentation-ready view derived only from persisted execution state."""
366
+
367
+ task_id: str
368
+ workflow: str
369
+ status: InstructionStatus
370
+ item_id: str | None
371
+ item_name: str | None
372
+ stage: str | None
373
+ step: str | None
374
+ parent: str | None
375
+ item_status: ItemStatus | None
376
+ action_kind: PlanItemKind | None
377
+ action_text: str | None
378
+ loop_break_prompt: str | None = None
379
+ loop_break_command: str | None = None
380
+ # The break ends the per-child stages, skipping the remaining children.
381
+ breaks_children: bool = False
382
+ loop_continue_prompt: str | None = None
383
+ loop_continue_command: str | None = None
384
+ loop_iteration: int | None = None
385
+ max_rounds: int | None = None
386
+ loop_limit_reached: bool = False
387
+ # The enclosing loop of a body step, so the round can be described.
388
+ loop_name: str | None = None
389
+ # The task requirements saved by ``init``, repeated to every worker.
390
+ task_requirements: str | None = None
391
+ # The full requirements are printed on the task's first work page; later
392
+ # pages point at ``requirements_command`` instead.
393
+ requirements_in_full: bool = True
394
+ requirements_command: str | None = None
395
+ # Amendments appended to the requirements, oldest first; every page lists
396
+ # them in full.
397
+ task_amendments: tuple[Amendment, ...] = ()
398
+ # The most recently completed step's handover and artifact reference, so a
399
+ # worker knows what was just done. Hooks and the summary never qualify.
400
+ previous_step: str | None = None
401
+ previous_step_artifact: str | None = None
402
+ previous_step_summary: str | None = None
403
+ # The completion of this step must include a summary for the next step.
404
+ summary_required: bool = False
405
+ # For the built-in workflow summary: every step's handover in this run.
406
+ run_handovers: tuple[StepHandover, ...] = ()
407
+ # For pending input: the steps completed since the waiting handler last
408
+ # ran in this run, so the values describe current work.
409
+ input_context: tuple[StepHandover, ...] = ()
410
+ # Documents the active step promised to create or update in place.
411
+ documents: tuple[DocumentTask, ...] = ()
412
+ # An interactive step: its conversation state and the recording commands.
413
+ interactive: bool = False
414
+ # This agent-owned step requests concrete pre/post operation visibility.
415
+ explicit: bool = False
416
+ # Optional suggestion after completion; never a plan item or assignment.
417
+ feedback_deduction_command: str | None = None
418
+ interaction_entries: int = 0
419
+ interaction_ended: bool = False
420
+ interact_commands: InteractCommands | None = None
421
+ # Operator choices of an interactive step, the agent's way to offer them,
422
+ # and what was chosen so far.
423
+ choices: tuple[ChoiceDefinition, ...] = ()
424
+ choice_mechanism: str | None = None
425
+ chosen: str | None = None
426
+ # The operator said they are done for now; the agent stops until they
427
+ # return.
428
+ operator_paused: bool = False
429
+ # The conversation of this step so far, as recorded.
430
+ conversation: tuple[ConversationEntry, ...] = ()
431
+ # A per-item stage answered on the operator page rather than in a
432
+ # conversation; the page is an extra the core knows only by this flag.
433
+ ui: bool = False
434
+ # A collection step of a shared item flow: the items it reconciles.
435
+ shared_items: bool = False
436
+ stored_items: tuple[WorkItem, ...] = ()
437
+ # Custom item fields this step must set, and the flow's identity and
438
+ # unique fields when this step is the collection.
439
+ required_item_fields: tuple[ItemFieldUpdate, ...] = ()
440
+ collects_items: bool = False
441
+ item_identity: str | None = None
442
+ item_unique: tuple[str, ...] = ()
443
+ # Pending input on an input-only assignment: the manager supplies it.
444
+ manager_input: bool = False
445
+ required_values: tuple[ProvidedVariable, ...] = ()
446
+ # What a retried handler was given last time, per requested value.
447
+ previous_values: tuple[tuple[str, str], ...] = ()
448
+ required_metadata: tuple[SavedMetadata, ...] = ()
449
+ automatic_context: tuple[str, ...] = ()
450
+ has_previous_artifacts: bool = False
451
+ next_steps: tuple[str, ...] = ()
452
+ artifact: str | None = None
453
+ continuation_command: str | None = None
454
+ error: str | None = None
455
+ handoff: str | None = None
456
+ # The workflow a completed run offers the operator next.
457
+ recommended_workflow: str | None = None
458
+ # A completed child task's parent, which the manager continues next.
459
+ parent_task_id: str | None = None
460
+ # An assessment's answers and what each does: on the assessment's own
461
+ # page, and on the page that asks ``next`` for the chosen one.
462
+ assessment_outcomes: tuple[AssessmentOutcome, ...] = ()
463
+ # This page asks for the outcome of the named, completed assessment.
464
+ choosing_outcome_of: str | None = None
465
+ profile_instruction: str | None = None
466
+ run_id: str | None = None
467
+ task_runs: tuple[WorkflowRunSummary, ...] = ()
468
+ working_directory: str | None = None
469
+ child_tasks: tuple[ChildTask, ...] = ()
470
+ operation_id: str | None = None
471
+ recovery_commands: tuple[RecoveryCommand, ...] = ()
472
+ workflow_runtime: str = "single"
473
+ # The agent the task was started for, as ``start --agent`` named it.
474
+ agent: str = ""
475
+ workflow_runtime_instruction: tuple[str, ...] = ()
476
+ model: str = "auto"
477
+ reasoning: str = "auto"
478
+ requested_agent: str | None = None
479
+ requested_model: str | None = None
480
+ requested_reasoning: str | None = None
481
+ requested_profile: str | None = None
482
+ role: StepRole = "worker"
483
+ # ``False``: whoever performs the item spawns no subagents.
484
+ subagents: bool = True
485
+ selected_agent: str | None = None
486
+ selected_model: str | None = None
487
+ selected_reasoning: str | None = None
488
+ assignment_preview: dict[str, object] | None = None
489
+ # The first stage of a multi-stage item assignment describes its scope;
490
+ # later stages of the same assignment are rendered compactly.
491
+ assignment_scope: dict[str, object] | None = None
492
+ # Agent-owned explicit steps covered by this worker assignment.
493
+ assignment_explicit_steps: tuple[str, ...] = ()
494
+ continues_assignment: bool = False
495
+ # The assignment the current item belongs to, in the ``auto`` runtime: the
496
+ # step that drives worker selection, every agent or input item it covers,
497
+ # and whether the current item is a later one the worker already holds.
498
+ assignment_step: str | None = None
499
+ assignment_items: tuple[str, ...] = ()
500
+ assignment_continues: bool = False
501
+ manager_intro: bool = False
502
+ # On the first page of ``start``: the declared rules no check covers yet.
503
+ rules_notice: str | None = None
504
+ # One-line notes ww shows under the page header: what it did on the
505
+ # caller's behalf (started a child, dispatched the next step).
506
+ notices: tuple[str, ...] = ()
507
+ completion_registered: bool = False
508
+ # The completion was accepted but held: verifiers judge its rules first.
509
+ completion_held: bool = False
510
+ caller_role: CallerRole | None = None
511
+ next_role: NextRole | None = None
512
+ control: Control | None = None
513
+ # Set exactly when ``control`` is ``awaiting_operator``.
514
+ operator_reason: OperatorReason | None = None
515
+ result_saved: bool | None = None
516
+ # The rules the active step is given, and, after a rejected completion,
517
+ # what failed; ``checks_waived`` are the checks the operator waived for
518
+ # the step, each with the reason; ``dispute`` is the worker's objection
519
+ # to one at a ``check_disputed`` stop.
520
+ rules: tuple[RuleLine, ...] = ()
521
+ # The modes the active step works in: the run's selected modes, then the
522
+ # automatic ones whose filters admit the step.
523
+ modes: tuple[PlannedMode, ...] = ()
524
+ fix_required: FixRequired | None = None
525
+ handler_repair: dict[str, object] | None = None
526
+ checks_waived: tuple[tuple[str, str], ...] = ()
527
+ dispute: DisputeView | None = None
528
+ # A worker asked for an item the manager performs itself (``role:
529
+ # manager`` or ``interactive`` in the ``auto`` runtime): the page names it
530
+ # and offers no completion command.
531
+ manager_only: bool = False
532
+ # The open assignment's token in the ``auto`` runtime, carried by every
533
+ # worker command the page prints.
534
+ assignment_token: str | None = None
535
+ # The worker's assignment ended with this command: ww's report of it,
536
+ # which the worker returns to the manager verbatim.
537
+ handoff_block: HandoffBlock | None = None
538
+ # A verification item's rules and evidence.
539
+ verification: VerificationPage | None = None
540
+ # At a ``plan_changed`` stop: how the workflow's current definition
541
+ # differs from the run's saved plan, which the operator takes or declines.
542
+ plan_change: PlanChange | None = None
543
+ # Internal capability markers let presentation and service refresh paths
544
+ # avoid rediscovering a saved plan or matching built-in action names.
545
+ is_child_workflow_control: bool = False
546
+ is_loop_control: bool = False
547
+
548
+ def to_dict(self) -> dict[str, object]:
549
+ return {
550
+ "task_id": self.task_id,
551
+ "workflow": self.workflow,
552
+ "status": self.status,
553
+ "item_id": self.item_id,
554
+ "item_name": self.item_name,
555
+ "stage": self.stage,
556
+ "step": self.step,
557
+ "parent": self.parent,
558
+ "item_status": self.item_status,
559
+ "action_kind": self.action_kind,
560
+ "action_text": self.action_text,
561
+ "loop_break_prompt": self.loop_break_prompt,
562
+ "loop_break_command": self.loop_break_command,
563
+ "breaks_children": self.breaks_children,
564
+ "loop_continue_prompt": self.loop_continue_prompt,
565
+ "loop_continue_command": self.loop_continue_command,
566
+ "loop_iteration": self.loop_iteration,
567
+ "max_rounds": self.max_rounds,
568
+ "loop_limit_reached": self.loop_limit_reached,
569
+ "loop_name": self.loop_name,
570
+ "task_requirements": self.task_requirements,
571
+ "requirements_in_full": self.requirements_in_full,
572
+ "requirements_command": self.requirements_command,
573
+ "task_amendments": [entry.to_dict() for entry in self.task_amendments],
574
+ "previous_step": self.previous_step,
575
+ "previous_step_artifact": self.previous_step_artifact,
576
+ "previous_step_summary": self.previous_step_summary,
577
+ "summary_required": self.summary_required,
578
+ "run_handovers": [item.to_dict() for item in self.run_handovers],
579
+ "input_context": [item.to_dict() for item in self.input_context],
580
+ "documents": [item.to_dict() for item in self.documents],
581
+ "interactive": self.interactive,
582
+ "explicit": self.explicit,
583
+ "feedback_deduction_command": self.feedback_deduction_command,
584
+ "interaction_entries": self.interaction_entries,
585
+ "interaction_ended": self.interaction_ended,
586
+ "interact_commands": (
587
+ self.interact_commands.to_dict() if self.interact_commands else None
588
+ ),
589
+ "choices": [choice.to_dict() for choice in self.choices],
590
+ "choice_mechanism": self.choice_mechanism,
591
+ "chosen": self.chosen,
592
+ "operator_paused": self.operator_paused,
593
+ "conversation": [entry.to_dict() for entry in self.conversation],
594
+ "ui": self.ui,
595
+ "shared_items": self.shared_items,
596
+ "stored_items": [item.to_dict() for item in self.stored_items],
597
+ "required_item_fields": [
598
+ field.to_dict() for field in self.required_item_fields
599
+ ],
600
+ "collects_items": self.collects_items,
601
+ "item_identity": self.item_identity,
602
+ "item_unique": list(self.item_unique),
603
+ "manager_input": self.manager_input,
604
+ "required_values": [value.to_dict() for value in self.required_values],
605
+ "previous_values": dict(self.previous_values),
606
+ "required_metadata": [value.to_dict() for value in self.required_metadata],
607
+ "automatic_context": list(self.automatic_context),
608
+ "has_previous_artifacts": self.has_previous_artifacts,
609
+ "next_steps": list(self.next_steps),
610
+ "artifact": self.artifact,
611
+ "continuation_command": self.continuation_command,
612
+ "error": self.error,
613
+ "handoff": self.handoff,
614
+ "recommended_workflow": self.recommended_workflow,
615
+ "parent_task_id": self.parent_task_id,
616
+ "assessment_outcomes": [
617
+ {
618
+ "label": outcome.label,
619
+ "stops": outcome.stops,
620
+ "first_step": outcome.first_step,
621
+ }
622
+ for outcome in self.assessment_outcomes
623
+ ],
624
+ "choosing_outcome_of": self.choosing_outcome_of,
625
+ "profile_instruction": self.profile_instruction,
626
+ "run_id": self.run_id,
627
+ "task_runs": [item.to_dict() for item in self.task_runs],
628
+ "working_directory": self.working_directory,
629
+ "child_tasks": [child.to_dict() for child in self.child_tasks],
630
+ "operation_id": self.operation_id,
631
+ "recovery_commands": [item.to_dict() for item in self.recovery_commands],
632
+ "workflow_runtime": self.workflow_runtime,
633
+ "agent": self.agent,
634
+ "workflow_runtime_instruction": list(self.workflow_runtime_instruction),
635
+ "model": self.model,
636
+ "reasoning": self.reasoning,
637
+ "requested_agent": self.requested_agent,
638
+ "requested_model": self.requested_model,
639
+ "requested_reasoning": self.requested_reasoning,
640
+ "requested_profile": self.requested_profile,
641
+ "role": self.role,
642
+ "subagents": self.subagents,
643
+ "selected_agent": self.selected_agent,
644
+ "selected_model": self.selected_model,
645
+ "selected_reasoning": self.selected_reasoning,
646
+ "assignment_preview": self.assignment_preview,
647
+ "assignment_scope": self.assignment_scope,
648
+ "continues_assignment": self.continues_assignment,
649
+ "assignment_step": self.assignment_step,
650
+ "assignment_items": list(self.assignment_items),
651
+ "assignment_explicit_steps": list(self.assignment_explicit_steps),
652
+ "assignment_continues": self.assignment_continues,
653
+ "manager_intro": self.manager_intro,
654
+ "rules_notice": self.rules_notice,
655
+ "notices": list(self.notices),
656
+ "completion_registered": self.completion_registered,
657
+ "completion_held": self.completion_held,
658
+ "caller_role": self.caller_role,
659
+ "next_role": self.next_role,
660
+ "control": self.control,
661
+ "operator_reason": self.operator_reason,
662
+ "result_saved": self.result_saved,
663
+ "rules": [rule.to_dict() for rule in self.rules],
664
+ "modes": [mode.to_dict() for mode in self.modes],
665
+ **(
666
+ {"handler_repair": self.handler_repair}
667
+ if self.handler_repair is not None
668
+ else {}
669
+ ),
670
+ "fix_required": (
671
+ self.fix_required.to_dict() if self.fix_required else None
672
+ ),
673
+ "checks_waived": dict(self.checks_waived),
674
+ "dispute": self.dispute.to_dict() if self.dispute else None,
675
+ "manager_only": self.manager_only,
676
+ "assignment_token": self.assignment_token,
677
+ "handoff_block": (
678
+ self.handoff_block.to_dict() if self.handoff_block else None
679
+ ),
680
+ "verification": (
681
+ self.verification.to_dict() if self.verification else None
682
+ ),
683
+ "plan_change": (
684
+ self.plan_change.to_dict() if self.plan_change is not None else None
685
+ ),
686
+ }