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
ww/plan/constructs.py ADDED
@@ -0,0 +1,456 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Typed planning contracts for composite workflow constructs.
3
+
4
+ The normalized workflow definition deliberately remains the source of truth for
5
+ YAML parsing and validation. This module makes the compiler's second-stage
6
+ choice explicit: a definition is normalized into one construct input, then a
7
+ registered planner requests ordinary leaf items, boundaries, or nested regions.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, replace
13
+ from typing import Protocol, TypeVar, runtime_checkable
14
+
15
+ from ww.actions import DefinedAction
16
+ from ww.contracts import (
17
+ DEFAULT_LOOP_ASSIGNMENT,
18
+ ChildOperation,
19
+ ItemAssignment,
20
+ ItemOperation,
21
+ LoopAssignment,
22
+ LoopOperation,
23
+ )
24
+ from ww.errors import ConfigurationError
25
+ from ww.operations import ChildWorkflowRun
26
+ from ww.workflow_config import (
27
+ ChildFlow,
28
+ HandlerDefinition,
29
+ ItemFlow,
30
+ StepDefinition,
31
+ WorkflowDefinition,
32
+ )
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class ItemAnnotations:
37
+ """Construct-selected annotations for one emitted plan item."""
38
+
39
+ item_operation: ItemOperation | None = None
40
+ child_operation: ChildOperation | None = None
41
+ assessment_question: str | None = None
42
+ assessment_outcomes: tuple[str, ...] = ()
43
+ assessment_stops: tuple[str, ...] = ()
44
+ assessment_parent: str | None = None
45
+ assessment_outcome: str | None = None
46
+ # Carried by every per-item stage and hook of an ``items`` step.
47
+ item_assignment: ItemAssignment | None = None
48
+ # Carried by the collection item and every per-item stage and hook: the
49
+ # stable identity (logical step path) of their ``items`` declaration.
50
+ item_pass: str | None = None
51
+ # Carried only by the collection item of an explicit ``steps: []`` pass.
52
+ item_collect_only: bool = False
53
+ # Carried by every body step and hook of the nearest enclosing ``loop``.
54
+ loop_id: str | None = None
55
+ loop_assignment: LoopAssignment | None = None
56
+ # Carried by every per-child stage and hook: the ``children`` step path.
57
+ child_stage: str | None = None
58
+ # Carried with ``child_stage``: the per-child stages that run before the
59
+ # child task exists, so they cannot read its extension values.
60
+ child_before_run: tuple[str, ...] = ()
61
+ # Carried only by the collection item itself.
62
+ split_instruction: str | None = None
63
+ shared_items: bool = False
64
+ item_identity: str | None = None
65
+ item_unique: tuple[str, ...] = ()
66
+
67
+
68
+ EMPTY_ITEM_ANNOTATIONS = ItemAnnotations()
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class PlanningScope:
73
+ """Immutable logical scope shared by a construct planner and core."""
74
+
75
+ workflow: WorkflowDefinition
76
+ step: StepDefinition
77
+ path: str
78
+ parent: str | None
79
+ ancestors: tuple[str, ...]
80
+ available_values: tuple[str, ...]
81
+ item_template: bool
82
+ annotations: ItemAnnotations = EMPTY_ITEM_ANNOTATIONS
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class ExpansionResult:
87
+ """Explicit variable result from a construct expansion.
88
+
89
+ ``available_values`` replaces the enclosing available scope for containers
90
+ whose bodies intentionally propagate values (sequences and loops). Leaf,
91
+ assessment, and item-template planners instead return only direct outputs.
92
+ """
93
+
94
+ outputs: tuple[str, ...] = ()
95
+ available_values: tuple[str, ...] | None = None
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class LeafRequest:
100
+ reference: HandlerDefinition
101
+ action: DefinedAction | None = None
102
+ annotations: ItemAnnotations = EMPTY_ITEM_ANNOTATIONS
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class LoopBoundaryRequest:
107
+ operation: LoopOperation
108
+ max_times: int
109
+ artifact: bool
110
+
111
+
112
+ class PlanningContext(Protocol):
113
+ """Small set of compiler-owned primitives exposed to construct planners."""
114
+
115
+ scope: PlanningScope
116
+ default_max_rounds: int
117
+
118
+ def derive_scope(
119
+ self,
120
+ *,
121
+ parent: str,
122
+ parent_ancestors: tuple[str, ...],
123
+ item_template: bool | None = None,
124
+ ) -> PlanningScope: ...
125
+
126
+ def compile_steps(
127
+ self, steps: tuple[StepDefinition, ...], scope: PlanningScope
128
+ ) -> tuple[str, ...]: ...
129
+
130
+ def compile_scoped_region(
131
+ self,
132
+ steps: tuple[StepDefinition, ...],
133
+ scope: PlanningScope,
134
+ annotations: ItemAnnotations,
135
+ ) -> tuple[str, ...]: ...
136
+
137
+ def emit_leaf(self, request: LeafRequest) -> tuple[str, ...]: ...
138
+
139
+ def emit_loop_boundary(self, request: LoopBoundaryRequest) -> None: ...
140
+
141
+
142
+ DefinitionT = TypeVar("DefinitionT", contravariant=True)
143
+
144
+
145
+ @runtime_checkable
146
+ class ConstructPlanner(Protocol[DefinitionT]):
147
+ def expand(
148
+ self, definition: DefinitionT, context: PlanningContext
149
+ ) -> ExpansionResult: ...
150
+
151
+
152
+ class ConstructPlannerRegistry:
153
+ """Checked, internal dispatch table for normalized construct inputs."""
154
+
155
+ def __init__(self) -> None:
156
+ self._planners: dict[type[object], ConstructPlanner[object]] = {}
157
+
158
+ def register(
159
+ self, definition_type: type[DefinitionT], planner: ConstructPlanner[DefinitionT]
160
+ ) -> None:
161
+ if not isinstance(planner, ConstructPlanner):
162
+ raise TypeError("construct planner must implement expand")
163
+ if definition_type in self._planners:
164
+ raise ValueError(f"construct planner already registered: {definition_type}")
165
+ self._planners[definition_type] = planner # type: ignore[assignment]
166
+
167
+ def expand(self, definition: object, context: PlanningContext) -> ExpansionResult:
168
+ try:
169
+ planner = self._planners[type(definition)]
170
+ except KeyError as error:
171
+ raise ConfigurationError(
172
+ f"no construct planner registered for {type(definition).__name__}"
173
+ ) from error
174
+ return planner.expand(definition, context)
175
+
176
+
177
+ @dataclass(frozen=True)
178
+ class LeafDefinition:
179
+ step: StepDefinition
180
+ annotations: ItemAnnotations
181
+
182
+
183
+ @dataclass(frozen=True)
184
+ class SequenceDefinition:
185
+ step: StepDefinition
186
+ body: tuple[StepDefinition, ...]
187
+
188
+
189
+ @dataclass(frozen=True)
190
+ class AssessmentDefinition:
191
+ step: StepDefinition
192
+ question: str
193
+ outcomes: tuple[StepDefinition, ...]
194
+
195
+
196
+ @dataclass(frozen=True)
197
+ class ItemFlowDefinition:
198
+ step: StepDefinition
199
+ flow: ItemFlow
200
+
201
+
202
+ @dataclass(frozen=True)
203
+ class LoopDefinition:
204
+ step: StepDefinition
205
+ body: tuple[StepDefinition, ...]
206
+ max_times: int | None
207
+ assignment: LoopAssignment | None = None
208
+
209
+
210
+ @dataclass(frozen=True)
211
+ class ChildFlowDefinition:
212
+ step: StepDefinition
213
+ flow: ChildFlow
214
+
215
+
216
+ # The leaf under a ``children`` step that runs the collected children.
217
+ CHILDREN_RUN_STEP_NAME = "children"
218
+
219
+
220
+ class LeafPlanner(ConstructPlanner[LeafDefinition]):
221
+ def expand(
222
+ self, definition: LeafDefinition, context: PlanningContext
223
+ ) -> ExpansionResult:
224
+ return ExpansionResult(
225
+ outputs=context.emit_leaf(
226
+ LeafRequest(definition.step, annotations=definition.annotations)
227
+ )
228
+ )
229
+
230
+
231
+ class SequencePlanner(ConstructPlanner[SequenceDefinition]):
232
+ def expand(
233
+ self, definition: SequenceDefinition, context: PlanningContext
234
+ ) -> ExpansionResult:
235
+ scope = context.derive_scope(
236
+ parent=context.scope.path,
237
+ parent_ancestors=context.scope.ancestors,
238
+ )
239
+ return ExpansionResult(
240
+ available_values=context.compile_steps(definition.body, scope)
241
+ )
242
+
243
+
244
+ class AssessmentPlanner(ConstructPlanner[AssessmentDefinition]):
245
+ def expand(
246
+ self, definition: AssessmentDefinition, context: PlanningContext
247
+ ) -> ExpansionResult:
248
+ context.emit_leaf(
249
+ LeafRequest(
250
+ definition.step,
251
+ annotations=ItemAnnotations(
252
+ assessment_question=definition.question,
253
+ assessment_outcomes=tuple(
254
+ outcome.name for outcome in definition.outcomes
255
+ ),
256
+ assessment_stops=tuple(
257
+ outcome.name
258
+ for outcome in definition.outcomes
259
+ if outcome.stop_workflow
260
+ ),
261
+ ),
262
+ )
263
+ )
264
+ scope = context.derive_scope(
265
+ parent=context.scope.path,
266
+ parent_ancestors=context.scope.ancestors,
267
+ )
268
+ for outcome in definition.outcomes:
269
+ if outcome.stop_workflow:
270
+ # Selecting it completes the workflow; there is nothing to run.
271
+ continue
272
+ context.compile_scoped_region(
273
+ (outcome,),
274
+ scope,
275
+ ItemAnnotations(
276
+ assessment_parent=context.scope.path,
277
+ assessment_outcome=outcome.name,
278
+ ),
279
+ )
280
+ return ExpansionResult()
281
+
282
+
283
+ class ItemFlowPlanner(ConstructPlanner[ItemFlowDefinition]):
284
+ """Collect items with the step's own action, then template each stage.
285
+
286
+ The templates are replaced by one concrete lifecycle per collected item
287
+ when collection completes.
288
+ """
289
+
290
+ def expand(
291
+ self, definition: ItemFlowDefinition, context: PlanningContext
292
+ ) -> ExpansionResult:
293
+ flow = definition.flow
294
+ outputs = context.emit_leaf(
295
+ LeafRequest(
296
+ definition.step,
297
+ annotations=ItemAnnotations(
298
+ item_operation="collect",
299
+ item_pass=context.scope.path,
300
+ item_collect_only=flow.collect_only,
301
+ split_instruction=flow.description,
302
+ shared_items=bool(flow.persistent),
303
+ item_identity=flow.identity,
304
+ item_unique=flow.effective_unique,
305
+ ),
306
+ )
307
+ )
308
+ if flow.steps:
309
+ scope = context.derive_scope(
310
+ parent=f"{context.scope.path}/{{item}}",
311
+ parent_ancestors=(*context.scope.ancestors, context.scope.path),
312
+ item_template=True,
313
+ )
314
+ scope = replace(
315
+ scope,
316
+ annotations=replace(
317
+ scope.annotations,
318
+ item_assignment=flow.assignment,
319
+ item_pass=context.scope.path,
320
+ ),
321
+ )
322
+ context.compile_steps(flow.steps, scope)
323
+ return ExpansionResult(outputs=outputs)
324
+
325
+
326
+ class LoopPlanner(ConstructPlanner[LoopDefinition]):
327
+ def expand(
328
+ self, definition: LoopDefinition, context: PlanningContext
329
+ ) -> ExpansionResult:
330
+ max_times = (
331
+ definition.max_times
332
+ if definition.max_times is not None
333
+ else context.default_max_rounds
334
+ )
335
+ context.emit_loop_boundary(
336
+ LoopBoundaryRequest("enter", max_times, definition.step.artifact)
337
+ )
338
+ scope = context.derive_scope(
339
+ parent=context.scope.path,
340
+ parent_ancestors=context.scope.ancestors,
341
+ )
342
+ scope = replace(
343
+ scope,
344
+ annotations=replace(
345
+ scope.annotations,
346
+ loop_id=context.scope.path,
347
+ loop_assignment=definition.assignment or DEFAULT_LOOP_ASSIGNMENT,
348
+ ),
349
+ )
350
+ available = context.compile_steps(definition.body, scope)
351
+ context.emit_loop_boundary(LoopBoundaryRequest("repeat", max_times, False))
352
+ return ExpansionResult(available_values=available)
353
+
354
+
355
+ class ChildFlowPlanner(ConstructPlanner[ChildFlowDefinition]):
356
+ """Collect child tasks with the step's own action, then run them.
357
+
358
+ The run is a ww-owned leaf nested under the collecting step, so one step
359
+ owns the whole child lifecycle; the step's completion hooks follow it.
360
+ With ``children.steps`` the parent's stages are templated per child,
361
+ exactly as per-item stages are, and replaced by one concrete lifecycle
362
+ per collected child when collection completes; the stage that runs the
363
+ child is then a ``ChildWorkflowRun`` of that one child.
364
+ """
365
+
366
+ def expand(
367
+ self, definition: ChildFlowDefinition, context: PlanningContext
368
+ ) -> ExpansionResult:
369
+ flow = definition.flow
370
+ outputs = context.emit_leaf(
371
+ LeafRequest(
372
+ definition.step,
373
+ annotations=ItemAnnotations(
374
+ child_operation="collect", split_instruction=flow.description
375
+ ),
376
+ )
377
+ )
378
+ if flow.steps:
379
+ scope = context.derive_scope(
380
+ parent=f"{context.scope.path}/{{child}}",
381
+ parent_ancestors=(*context.scope.ancestors, context.scope.path),
382
+ item_template=True,
383
+ )
384
+ run_index = next(
385
+ index
386
+ for index, stage in enumerate(flow.steps)
387
+ if isinstance(stage.operation, ChildWorkflowRun)
388
+ )
389
+ scope = replace(
390
+ scope,
391
+ annotations=replace(
392
+ scope.annotations,
393
+ child_stage=context.scope.path,
394
+ child_before_run=tuple(
395
+ stage.name for stage in flow.steps[:run_index]
396
+ ),
397
+ ),
398
+ )
399
+ context.compile_steps(flow.steps, scope)
400
+ return ExpansionResult(outputs=outputs)
401
+ run = StepDefinition(
402
+ name=CHILDREN_RUN_STEP_NAME,
403
+ description=(
404
+ f"Run every child task with the `{flow.workflow}` workflow, "
405
+ "one at a time."
406
+ ),
407
+ operation=ChildWorkflowRun(flow.workflow),
408
+ )
409
+ context.compile_steps(
410
+ (run,),
411
+ context.derive_scope(
412
+ parent=context.scope.path,
413
+ parent_ancestors=context.scope.ancestors,
414
+ ),
415
+ )
416
+ return ExpansionResult(outputs=outputs)
417
+
418
+
419
+ def step_annotations(step: StepDefinition) -> ItemAnnotations:
420
+ """Return the operation annotations inherited by a step and its hooks.
421
+
422
+ Child collection is not among them: only the collect leaf of a
423
+ ``children`` step collects, while its hooks and the workflow's completion
424
+ items around it do not.
425
+ """
426
+ return ItemAnnotations(item_operation=step.item_operation)
427
+
428
+
429
+ def normalize_construct(step: StepDefinition) -> object:
430
+ """Translate existing normalized steps at one deliberate dispatch boundary."""
431
+ if step.assessment_question is not None:
432
+ return AssessmentDefinition(
433
+ step, step.assessment_question, step.assessment_outcomes
434
+ )
435
+ if step.items is not None:
436
+ return ItemFlowDefinition(step, step.items)
437
+ if step.loop_steps:
438
+ return LoopDefinition(
439
+ step, step.loop_steps, step.max_rounds, step.loop_assignment
440
+ )
441
+ if step.child_steps:
442
+ return SequenceDefinition(step, step.child_steps)
443
+ if step.children is not None:
444
+ return ChildFlowDefinition(step, step.children)
445
+ return LeafDefinition(step, step_annotations(step))
446
+
447
+
448
+ def builtin_construct_planners() -> ConstructPlannerRegistry:
449
+ registry = ConstructPlannerRegistry()
450
+ registry.register(LeafDefinition, LeafPlanner())
451
+ registry.register(SequenceDefinition, SequencePlanner())
452
+ registry.register(AssessmentDefinition, AssessmentPlanner())
453
+ registry.register(ItemFlowDefinition, ItemFlowPlanner())
454
+ registry.register(LoopDefinition, LoopPlanner())
455
+ registry.register(ChildFlowDefinition, ChildFlowPlanner())
456
+ return registry