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
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
|