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/run_coordination.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
"""Atomic run loading and publication across storage adapters."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Protocol
|
|
7
|
+
|
|
8
|
+
from ww.children import ChildTask
|
|
9
|
+
from ww.contracts import run_is_open
|
|
10
|
+
from ww.errors import StateError
|
|
11
|
+
from ww.execution_models import ExecutionState, PlanSnapshot, TaskRunAggregate
|
|
12
|
+
from ww.instructions import Instruction
|
|
13
|
+
from ww.items import WorkItem
|
|
14
|
+
from ww.storage_adapters import TaskStorageAdapter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RunLifecycle(Protocol):
|
|
18
|
+
"""The run operations the workflow service lends to its coordinators.
|
|
19
|
+
|
|
20
|
+
Coordinators (recovery, children) decide *what* a transition means; the
|
|
21
|
+
service keeps ownership of locks, commits, draining, and rendering.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def load(
|
|
25
|
+
self, task_id: str, run_id: str | None = None
|
|
26
|
+
) -> tuple[ExecutionState, PlanSnapshot]: ...
|
|
27
|
+
|
|
28
|
+
def commit(
|
|
29
|
+
self,
|
|
30
|
+
state: ExecutionState,
|
|
31
|
+
snapshot: PlanSnapshot,
|
|
32
|
+
*,
|
|
33
|
+
items: tuple[WorkItem, ...] | None = None,
|
|
34
|
+
children: tuple[ChildTask, ...] | None = None,
|
|
35
|
+
handoff: str | None = None,
|
|
36
|
+
bootstrap_request_id: str | None = None,
|
|
37
|
+
) -> None: ...
|
|
38
|
+
|
|
39
|
+
def drain(
|
|
40
|
+
self, state: ExecutionState, snapshot: PlanSnapshot
|
|
41
|
+
) -> tuple[ExecutionState, PlanSnapshot]: ...
|
|
42
|
+
|
|
43
|
+
def resume(self, state: ExecutionState, snapshot: PlanSnapshot) -> Instruction:
|
|
44
|
+
"""Commit, continue the active assignment or drain, then instruct."""
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
def render(self, state: ExecutionState, snapshot: PlanSnapshot) -> Instruction:
|
|
48
|
+
"""Build the instruction for a loaded run without changing it."""
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
def instruction_status(self, task_id: str, run_id: str | None) -> Instruction: ...
|
|
52
|
+
|
|
53
|
+
def mark_interrupted(
|
|
54
|
+
self, state: ExecutionState, snapshot: PlanSnapshot
|
|
55
|
+
) -> ExecutionState: ...
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RunCoordinator:
|
|
59
|
+
"""Coordinate complete run records without owning lifecycle decisions."""
|
|
60
|
+
|
|
61
|
+
def __init__(self, tasks: TaskStorageAdapter) -> None:
|
|
62
|
+
self.tasks = tasks
|
|
63
|
+
|
|
64
|
+
def commit_run(
|
|
65
|
+
self,
|
|
66
|
+
state: ExecutionState,
|
|
67
|
+
snapshot: PlanSnapshot,
|
|
68
|
+
*,
|
|
69
|
+
items: tuple[WorkItem, ...] | None = None,
|
|
70
|
+
children: tuple[ChildTask, ...] | None = None,
|
|
71
|
+
handoff: str | None = None,
|
|
72
|
+
bootstrap_request_id: str | None = None,
|
|
73
|
+
) -> None:
|
|
74
|
+
"""Atomically publish the complete record for one run transition."""
|
|
75
|
+
runs, existing_handoff, revision = self.tasks.read_task_record(state.task_id)
|
|
76
|
+
current = next((run for run in runs if run.run_id == state.run_id), None)
|
|
77
|
+
aggregate = TaskRunAggregate(
|
|
78
|
+
run_id=state.run_id,
|
|
79
|
+
workflow=state.workflow,
|
|
80
|
+
snapshot=snapshot,
|
|
81
|
+
state=state,
|
|
82
|
+
items=items if items is not None else (current.items if current else ()),
|
|
83
|
+
children=(
|
|
84
|
+
children
|
|
85
|
+
if children is not None
|
|
86
|
+
else (current.children if current else ())
|
|
87
|
+
),
|
|
88
|
+
bootstrap_request_id=(
|
|
89
|
+
bootstrap_request_id
|
|
90
|
+
if bootstrap_request_id is not None
|
|
91
|
+
else (current.bootstrap_request_id if current else None)
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
replaced = tuple(
|
|
95
|
+
aggregate if run.run_id == aggregate.run_id else run for run in runs
|
|
96
|
+
)
|
|
97
|
+
if current is None:
|
|
98
|
+
replaced += (aggregate,)
|
|
99
|
+
self.tasks.commit_task_aggregate(
|
|
100
|
+
state.task_id,
|
|
101
|
+
replaced,
|
|
102
|
+
handoff if handoff is not None else existing_handoff,
|
|
103
|
+
expected_revision=revision,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def commit_runs(
|
|
107
|
+
self,
|
|
108
|
+
task_id: str,
|
|
109
|
+
aggregates: tuple[TaskRunAggregate, ...],
|
|
110
|
+
*,
|
|
111
|
+
handoff: str | None = None,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Atomically publish a coordinated multi-run transition."""
|
|
114
|
+
_, existing_handoff, revision = self.tasks.read_task_record(task_id)
|
|
115
|
+
self.tasks.commit_task_aggregate(
|
|
116
|
+
task_id,
|
|
117
|
+
aggregates,
|
|
118
|
+
handoff if handoff is not None else existing_handoff,
|
|
119
|
+
expected_revision=revision,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@staticmethod
|
|
123
|
+
def select_run(
|
|
124
|
+
runs: tuple[TaskRunAggregate, ...], run_id: str | None = None
|
|
125
|
+
) -> TaskRunAggregate | None:
|
|
126
|
+
"""Return the named run, else the active run, else the latest run."""
|
|
127
|
+
resolved = run_id or next(
|
|
128
|
+
(run.run_id for run in reversed(runs) if run_is_open(run.state.status)),
|
|
129
|
+
runs[-1].run_id if runs else None,
|
|
130
|
+
)
|
|
131
|
+
return next((run for run in runs if run.run_id == resolved), None)
|
|
132
|
+
|
|
133
|
+
def load(
|
|
134
|
+
self, task_id: str, run_id: str | None = None
|
|
135
|
+
) -> tuple[ExecutionState, PlanSnapshot]:
|
|
136
|
+
"""Load one complete run and verify plan/state identity."""
|
|
137
|
+
runs, _, _ = self.tasks.read_task_record(task_id)
|
|
138
|
+
return self.resolve(task_id, runs, run_id)
|
|
139
|
+
|
|
140
|
+
def resolve(
|
|
141
|
+
self,
|
|
142
|
+
task_id: str,
|
|
143
|
+
runs: tuple[TaskRunAggregate, ...],
|
|
144
|
+
run_id: str | None = None,
|
|
145
|
+
) -> tuple[ExecutionState, PlanSnapshot]:
|
|
146
|
+
"""Select and verify one run from an already-read record."""
|
|
147
|
+
aggregate = self.select_run(runs, run_id)
|
|
148
|
+
if aggregate is None:
|
|
149
|
+
raise StateError(f"task {task_id!r} has not been started; use start")
|
|
150
|
+
state = aggregate.state
|
|
151
|
+
snapshot = aggregate.snapshot
|
|
152
|
+
if state.plan_revision != snapshot.plan_revision:
|
|
153
|
+
raise StateError(
|
|
154
|
+
"task state does not match its authoritative plan revision"
|
|
155
|
+
)
|
|
156
|
+
if state.plan_digest and state.plan_digest != snapshot.plan_digest:
|
|
157
|
+
raise StateError("task state does not match its authoritative plan digest")
|
|
158
|
+
return state, snapshot
|
ww/runtimes.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
"""Workflow-runtime definitions for agent orchestration."""
|
|
3
|
+
|
|
4
|
+
from ww.contracts import NextRole
|
|
5
|
+
from ww.errors import ConfigurationError
|
|
6
|
+
from ww.executable import ww_command
|
|
7
|
+
|
|
8
|
+
RUNTIME_INSTRUCTIONS = {
|
|
9
|
+
"single": (
|
|
10
|
+
"Use this session for both manager and worker responsibilities. The "
|
|
11
|
+
"manager dispatches an assignment; the worker completes its action and "
|
|
12
|
+
"associated hooks until ww hands control back.",
|
|
13
|
+
"Do not spawn subagents.",
|
|
14
|
+
"Configured agent, model, reasoning, profile, skill, and built-in hints "
|
|
15
|
+
"are ignored. The user-controlled session settings are authoritative.",
|
|
16
|
+
),
|
|
17
|
+
"auto": (
|
|
18
|
+
"The manager dispatches each assignment to a worker and handles recovery.",
|
|
19
|
+
"You may use one worker for every step, choose a fresh worker per "
|
|
20
|
+
"step or assignment, or perform the assignment yourself.",
|
|
21
|
+
"Launch each worker with the model and reasoning the assignment "
|
|
22
|
+
"requests; they take precedence over profile or skill preferences. "
|
|
23
|
+
"Where none are shown, launch it with its default settings and do not "
|
|
24
|
+
"choose them yourself.",
|
|
25
|
+
"If the exact request is unavailable, select the closest worker and "
|
|
26
|
+
"report the difference. Agent hints are advisory and currently unenforced.",
|
|
27
|
+
"The worker submits its own results and associated hook results with "
|
|
28
|
+
"--role worker until ww explicitly hands control back. It then "
|
|
29
|
+
'returns ww\'s "Handoff to manager" block verbatim as its final '
|
|
30
|
+
"message; the manager reads the outcome there.",
|
|
31
|
+
"A worker may delegate one bounded hook when its runtime permits, but "
|
|
32
|
+
"that worker remains responsible for submitting the result. Nested "
|
|
33
|
+
"workers must not complete the same item or run manager commands.",
|
|
34
|
+
"Use this runtime when the work needs execution settings this session "
|
|
35
|
+
"cannot change, because a worker can be launched with them.",
|
|
36
|
+
),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# One-line summaries for choosing a runtime; ``RUNTIME_INSTRUCTIONS`` holds the
|
|
40
|
+
# guidance repeated in every instruction.
|
|
41
|
+
RUNTIME_DESCRIPTIONS = {
|
|
42
|
+
"single": (
|
|
43
|
+
"One session plays both manager and worker and does every assignment "
|
|
44
|
+
"itself, without subagents."
|
|
45
|
+
),
|
|
46
|
+
"auto": (
|
|
47
|
+
"The manager delegates each assignment to a worker agent it selects, "
|
|
48
|
+
"using the requested agent, model, and reasoning."
|
|
49
|
+
),
|
|
50
|
+
}
|
|
51
|
+
DEFAULT_RUNTIME = "single"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def requested_setting(value: object) -> str | None:
|
|
55
|
+
"""A requested model or reasoning, or ``None`` when the workflow asks for none.
|
|
56
|
+
|
|
57
|
+
``auto`` is not shown to agents: an open choice invites them to make one,
|
|
58
|
+
so an unrequested setting is omitted and the worker keeps its default.
|
|
59
|
+
"""
|
|
60
|
+
return value if isinstance(value, str) and value != "auto" else None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cli_ownership_warning() -> str:
|
|
64
|
+
"""The strict reminder every runtime instruction ends with."""
|
|
65
|
+
ww = ww_command()
|
|
66
|
+
return (
|
|
67
|
+
f"Strict: only {ww} start, next, and complete operate this flow. Do not "
|
|
68
|
+
"mimic or bypass it with direct commands, or read "
|
|
69
|
+
"ww.yaml; follow only the ww execution plan."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def runtime_instruction(
|
|
74
|
+
name: str, next_role: NextRole | None = None
|
|
75
|
+
) -> tuple[str, ...]:
|
|
76
|
+
"""Return a validated instruction for a persisted workflow runtime."""
|
|
77
|
+
try:
|
|
78
|
+
instructions = RUNTIME_INSTRUCTIONS[name]
|
|
79
|
+
role_instruction: tuple[str, ...]
|
|
80
|
+
if next_role == "worker":
|
|
81
|
+
role_instruction = (
|
|
82
|
+
"Worker responsibility: perform the active assignment and use "
|
|
83
|
+
"the displayed --role worker completion command. Continue its "
|
|
84
|
+
"associated hooks until ww reports a manager handoff.",
|
|
85
|
+
)
|
|
86
|
+
elif next_role == "manager":
|
|
87
|
+
role_instruction = (
|
|
88
|
+
"Manager responsibility: dispatch the next assignment or handle "
|
|
89
|
+
"the displayed recovery action. Do not ask a worker to run a "
|
|
90
|
+
"manager command.",
|
|
91
|
+
)
|
|
92
|
+
elif next_role == "operator":
|
|
93
|
+
role_instruction = (
|
|
94
|
+
"Operator decision: ww waits for the operator, the user. Stop "
|
|
95
|
+
"and report the situation to them; run a displayed recovery "
|
|
96
|
+
"command only after they choose it.",
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
role_instruction = ()
|
|
100
|
+
return (*instructions, *role_instruction, cli_ownership_warning())
|
|
101
|
+
except KeyError as error:
|
|
102
|
+
supported = ", ".join(sorted(RUNTIME_INSTRUCTIONS))
|
|
103
|
+
raise ConfigurationError(
|
|
104
|
+
f"unsupported workflow runtime {name!r}; choose one of: {supported}"
|
|
105
|
+
) from error
|