weakincentives 0.9.0__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.
- weakincentives/__init__.py +67 -0
- weakincentives/adapters/__init__.py +37 -0
- weakincentives/adapters/_names.py +32 -0
- weakincentives/adapters/_provider_protocols.py +69 -0
- weakincentives/adapters/_tool_messages.py +80 -0
- weakincentives/adapters/core.py +102 -0
- weakincentives/adapters/litellm.py +254 -0
- weakincentives/adapters/openai.py +254 -0
- weakincentives/adapters/shared.py +1021 -0
- weakincentives/cli/__init__.py +23 -0
- weakincentives/cli/wink.py +58 -0
- weakincentives/dbc/__init__.py +412 -0
- weakincentives/deadlines.py +58 -0
- weakincentives/prompt/__init__.py +105 -0
- weakincentives/prompt/_generic_params_specializer.py +64 -0
- weakincentives/prompt/_normalization.py +48 -0
- weakincentives/prompt/_overrides_protocols.py +33 -0
- weakincentives/prompt/_types.py +34 -0
- weakincentives/prompt/chapter.py +146 -0
- weakincentives/prompt/composition.py +281 -0
- weakincentives/prompt/errors.py +57 -0
- weakincentives/prompt/markdown.py +108 -0
- weakincentives/prompt/overrides/__init__.py +59 -0
- weakincentives/prompt/overrides/_fs.py +164 -0
- weakincentives/prompt/overrides/inspection.py +141 -0
- weakincentives/prompt/overrides/local_store.py +275 -0
- weakincentives/prompt/overrides/validation.py +534 -0
- weakincentives/prompt/overrides/versioning.py +269 -0
- weakincentives/prompt/prompt.py +353 -0
- weakincentives/prompt/protocols.py +103 -0
- weakincentives/prompt/registry.py +375 -0
- weakincentives/prompt/rendering.py +288 -0
- weakincentives/prompt/response_format.py +60 -0
- weakincentives/prompt/section.py +166 -0
- weakincentives/prompt/structured_output.py +179 -0
- weakincentives/prompt/tool.py +397 -0
- weakincentives/prompt/tool_result.py +30 -0
- weakincentives/py.typed +0 -0
- weakincentives/runtime/__init__.py +82 -0
- weakincentives/runtime/events/__init__.py +126 -0
- weakincentives/runtime/events/_types.py +110 -0
- weakincentives/runtime/logging.py +284 -0
- weakincentives/runtime/session/__init__.py +46 -0
- weakincentives/runtime/session/_slice_types.py +24 -0
- weakincentives/runtime/session/_types.py +55 -0
- weakincentives/runtime/session/dataclasses.py +29 -0
- weakincentives/runtime/session/protocols.py +34 -0
- weakincentives/runtime/session/reducer_context.py +40 -0
- weakincentives/runtime/session/reducers.py +82 -0
- weakincentives/runtime/session/selectors.py +56 -0
- weakincentives/runtime/session/session.py +387 -0
- weakincentives/runtime/session/snapshots.py +310 -0
- weakincentives/serde/__init__.py +19 -0
- weakincentives/serde/_utils.py +240 -0
- weakincentives/serde/dataclass_serde.py +55 -0
- weakincentives/serde/dump.py +189 -0
- weakincentives/serde/parse.py +417 -0
- weakincentives/serde/schema.py +260 -0
- weakincentives/tools/__init__.py +154 -0
- weakincentives/tools/_context.py +38 -0
- weakincentives/tools/asteval.py +853 -0
- weakincentives/tools/errors.py +26 -0
- weakincentives/tools/planning.py +831 -0
- weakincentives/tools/podman.py +1655 -0
- weakincentives/tools/subagents.py +346 -0
- weakincentives/tools/vfs.py +1390 -0
- weakincentives/types/__init__.py +35 -0
- weakincentives/types/json.py +45 -0
- weakincentives-0.9.0.dist-info/METADATA +775 -0
- weakincentives-0.9.0.dist-info/RECORD +73 -0
- weakincentives-0.9.0.dist-info/WHEEL +4 -0
- weakincentives-0.9.0.dist-info/entry_points.txt +2 -0
- weakincentives-0.9.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
2
|
+
# you may not use this file except in compliance with the License.
|
|
3
|
+
# You may obtain a copy of the License at
|
|
4
|
+
#
|
|
5
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
+
#
|
|
7
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
8
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
9
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
10
|
+
# See the License for the specific language governing permissions and
|
|
11
|
+
# limitations under the License.
|
|
12
|
+
|
|
13
|
+
"""Tooling for dispatching subagents in parallel."""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
from collections.abc import Iterable
|
|
19
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
20
|
+
from dataclasses import dataclass, field, is_dataclass
|
|
21
|
+
from enum import Enum, auto
|
|
22
|
+
from typing import Any, Final, cast, override
|
|
23
|
+
|
|
24
|
+
from ..prompt import SupportsDataclass
|
|
25
|
+
from ..prompt.composition import DelegationParams, DelegationPrompt, RecapParams
|
|
26
|
+
from ..prompt.errors import PromptRenderError
|
|
27
|
+
from ..prompt.markdown import MarkdownSection
|
|
28
|
+
from ..prompt.prompt import RenderedPrompt
|
|
29
|
+
from ..prompt.protocols import PromptResponseProtocol
|
|
30
|
+
from ..prompt.tool import Tool, ToolContext
|
|
31
|
+
from ..prompt.tool_result import ToolResult
|
|
32
|
+
from ..runtime.events import InProcessEventBus
|
|
33
|
+
from ..runtime.events._types import EventBus
|
|
34
|
+
from ..runtime.session.protocols import SessionProtocol
|
|
35
|
+
from ..serde import dump
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SubagentIsolationLevel(Enum):
|
|
39
|
+
"""Isolation modes describing how children interact with parent state."""
|
|
40
|
+
|
|
41
|
+
NO_ISOLATION = auto()
|
|
42
|
+
FULL_ISOLATION = auto()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _default_max_workers() -> int:
|
|
46
|
+
with ThreadPoolExecutor() as executor:
|
|
47
|
+
return executor._max_workers
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_DEFAULT_MAX_WORKERS: Final[int] = _default_max_workers()
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass(
|
|
54
|
+
slots=True,
|
|
55
|
+
)
|
|
56
|
+
class DispatchSubagentsParams:
|
|
57
|
+
"""Parameters describing the delegations to execute."""
|
|
58
|
+
|
|
59
|
+
delegations: tuple[DelegationParams, ...] = field(
|
|
60
|
+
default_factory=tuple,
|
|
61
|
+
metadata={
|
|
62
|
+
"description": (
|
|
63
|
+
"Ordered delegations to evaluate. Each entry carries the recap "
|
|
64
|
+
"lines and expected output for a child agent."
|
|
65
|
+
)
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def __post_init__(self) -> None:
|
|
70
|
+
self.delegations = tuple(self.delegations)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(
|
|
74
|
+
slots=True,
|
|
75
|
+
)
|
|
76
|
+
class SubagentResult:
|
|
77
|
+
"""Outcome captured for an individual delegation."""
|
|
78
|
+
|
|
79
|
+
output: str = field(
|
|
80
|
+
metadata={
|
|
81
|
+
"description": (
|
|
82
|
+
"Rendered response from the delegated prompt, including parsed "
|
|
83
|
+
"output when structured modes are enabled."
|
|
84
|
+
)
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
success: bool = field(
|
|
88
|
+
metadata={
|
|
89
|
+
"description": (
|
|
90
|
+
"Flag indicating whether the delegated run completed without "
|
|
91
|
+
"adapter or rendering errors."
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
error: str | None = field(
|
|
96
|
+
default=None,
|
|
97
|
+
metadata={
|
|
98
|
+
"description": (
|
|
99
|
+
"Optional diagnostic message when the delegation fails. Null "
|
|
100
|
+
"when the run succeeds."
|
|
101
|
+
)
|
|
102
|
+
},
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _extract_output_text(response: PromptResponseProtocol[Any]) -> str:
|
|
107
|
+
if response.text:
|
|
108
|
+
return response.text
|
|
109
|
+
if response.output is not None:
|
|
110
|
+
try:
|
|
111
|
+
rendered = dump(response.output, exclude_none=True)
|
|
112
|
+
return json.dumps(rendered, ensure_ascii=False)
|
|
113
|
+
except TypeError:
|
|
114
|
+
return str(response.output)
|
|
115
|
+
return ""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _build_error(message: str) -> str:
|
|
119
|
+
cleaned = message.strip()
|
|
120
|
+
return cleaned or "Subagent execution failed"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _clone_session(
|
|
124
|
+
session: SessionProtocol,
|
|
125
|
+
*,
|
|
126
|
+
bus: EventBus,
|
|
127
|
+
) -> SessionProtocol | None:
|
|
128
|
+
clone_method = getattr(session, "clone", None)
|
|
129
|
+
if not callable(clone_method):
|
|
130
|
+
return None
|
|
131
|
+
return cast(SessionProtocol, clone_method(bus=bus))
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _prepare_child_contexts(
|
|
135
|
+
*,
|
|
136
|
+
delegations: Iterable[DelegationParams],
|
|
137
|
+
session: SessionProtocol,
|
|
138
|
+
bus: EventBus,
|
|
139
|
+
isolation_level: SubagentIsolationLevel,
|
|
140
|
+
) -> tuple[tuple[SessionProtocol, EventBus], ...] | str:
|
|
141
|
+
if isolation_level is SubagentIsolationLevel.NO_ISOLATION:
|
|
142
|
+
return tuple((session, bus) for _ in delegations)
|
|
143
|
+
|
|
144
|
+
child_pairs: list[tuple[SessionProtocol, EventBus]] = []
|
|
145
|
+
for _ in delegations:
|
|
146
|
+
child_bus = InProcessEventBus()
|
|
147
|
+
try:
|
|
148
|
+
cloned = _clone_session(session, bus=child_bus)
|
|
149
|
+
except Exception as error: # pragma: no cover - defensive
|
|
150
|
+
return _build_error(str(error))
|
|
151
|
+
if cloned is None:
|
|
152
|
+
return "Parent session does not support cloning for full isolation."
|
|
153
|
+
child_pairs.append((cloned, child_bus))
|
|
154
|
+
return tuple(child_pairs)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def build_dispatch_subagents_tool(
|
|
158
|
+
*,
|
|
159
|
+
isolation_level: SubagentIsolationLevel = SubagentIsolationLevel.NO_ISOLATION,
|
|
160
|
+
accepts_overrides: bool = False,
|
|
161
|
+
) -> Tool[DispatchSubagentsParams, tuple[SubagentResult, ...]]:
|
|
162
|
+
"""Return a configured dispatch tool bound to the desired isolation level."""
|
|
163
|
+
|
|
164
|
+
def _dispatch_subagents(
|
|
165
|
+
params: DispatchSubagentsParams,
|
|
166
|
+
*,
|
|
167
|
+
context: ToolContext,
|
|
168
|
+
) -> ToolResult[tuple[SubagentResult, ...]]:
|
|
169
|
+
rendered_parent = context.rendered_prompt
|
|
170
|
+
if rendered_parent is None:
|
|
171
|
+
return ToolResult(
|
|
172
|
+
message="dispatch_subagents requires the parent prompt to be rendered.",
|
|
173
|
+
value=None,
|
|
174
|
+
success=False,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
parent_prompt = context.prompt
|
|
178
|
+
parent_output_type = rendered_parent.output_type
|
|
179
|
+
if not isinstance(parent_output_type, type) or not is_dataclass(
|
|
180
|
+
parent_output_type
|
|
181
|
+
):
|
|
182
|
+
return ToolResult(
|
|
183
|
+
message="Parent prompt must declare a dataclass output type for delegation.",
|
|
184
|
+
value=None,
|
|
185
|
+
success=False,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
delegation_prompt_cls: type[DelegationPrompt[Any, Any]] = (
|
|
189
|
+
DelegationPrompt.__class_getitem__((parent_output_type, parent_output_type))
|
|
190
|
+
)
|
|
191
|
+
delegation_prompt = delegation_prompt_cls(
|
|
192
|
+
parent_prompt,
|
|
193
|
+
cast(RenderedPrompt[Any], rendered_parent),
|
|
194
|
+
include_response_format=rendered_parent.container is not None,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
delegations = tuple(params.delegations)
|
|
198
|
+
if not delegations:
|
|
199
|
+
empty_results = cast(tuple[SubagentResult, ...], ())
|
|
200
|
+
return ToolResult(
|
|
201
|
+
message="No delegations supplied.",
|
|
202
|
+
value=empty_results,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
contexts = _prepare_child_contexts(
|
|
206
|
+
delegations=delegations,
|
|
207
|
+
session=context.session,
|
|
208
|
+
bus=context.event_bus,
|
|
209
|
+
isolation_level=isolation_level,
|
|
210
|
+
)
|
|
211
|
+
if isinstance(contexts, str):
|
|
212
|
+
return ToolResult(
|
|
213
|
+
message=contexts,
|
|
214
|
+
value=None,
|
|
215
|
+
success=False,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
adapter = context.adapter
|
|
219
|
+
parse_output = rendered_parent.container is not None
|
|
220
|
+
parent_deadline = rendered_parent.deadline
|
|
221
|
+
|
|
222
|
+
def _run_child(
|
|
223
|
+
payload: tuple[DelegationParams, SessionProtocol, EventBus],
|
|
224
|
+
) -> SubagentResult:
|
|
225
|
+
delegation, child_session, child_bus = payload
|
|
226
|
+
recap = RecapParams(bullets=delegation.recap_lines)
|
|
227
|
+
try:
|
|
228
|
+
response = adapter.evaluate(
|
|
229
|
+
delegation_prompt.prompt,
|
|
230
|
+
delegation,
|
|
231
|
+
recap,
|
|
232
|
+
parse_output=parse_output,
|
|
233
|
+
bus=child_bus,
|
|
234
|
+
session=child_session,
|
|
235
|
+
deadline=parent_deadline,
|
|
236
|
+
)
|
|
237
|
+
except Exception as error: # pragma: no cover - defensive
|
|
238
|
+
return SubagentResult(
|
|
239
|
+
output="",
|
|
240
|
+
success=False,
|
|
241
|
+
error=_build_error(str(error)),
|
|
242
|
+
)
|
|
243
|
+
return SubagentResult(
|
|
244
|
+
output=_extract_output_text(response),
|
|
245
|
+
success=True,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
payloads = [
|
|
249
|
+
(delegation, child_session, child_bus)
|
|
250
|
+
for delegation, (child_session, child_bus) in zip(
|
|
251
|
+
delegations, contexts, strict=True
|
|
252
|
+
)
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
max_workers = min(len(payloads), _DEFAULT_MAX_WORKERS) or 1
|
|
256
|
+
results: list[SubagentResult] = []
|
|
257
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
258
|
+
futures = [executor.submit(_run_child, payload) for payload in payloads]
|
|
259
|
+
for future in futures:
|
|
260
|
+
try:
|
|
261
|
+
results.append(future.result())
|
|
262
|
+
except Exception as error: # pragma: no cover - defensive
|
|
263
|
+
results.append(
|
|
264
|
+
SubagentResult(
|
|
265
|
+
output="",
|
|
266
|
+
success=False,
|
|
267
|
+
error=_build_error(str(error)),
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
return ToolResult(
|
|
272
|
+
message=f"Dispatched {len(results)} subagents.",
|
|
273
|
+
value=tuple(results),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return Tool[DispatchSubagentsParams, tuple[SubagentResult, ...]](
|
|
277
|
+
name="dispatch_subagents",
|
|
278
|
+
description="Run delegated child prompts in parallel.",
|
|
279
|
+
handler=_dispatch_subagents,
|
|
280
|
+
accepts_overrides=accepts_overrides,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@dataclass(slots=True, frozen=True)
|
|
285
|
+
class _SubagentsSectionParams:
|
|
286
|
+
"""Placeholder params container for the subagents section."""
|
|
287
|
+
|
|
288
|
+
pass
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
_DELEGATION_BODY: Final[str] = (
|
|
292
|
+
"Use `dispatch_subagents` to offload work that can proceed in parallel.\n"
|
|
293
|
+
"Each delegation must include recap bullet points so the parent can audit\n"
|
|
294
|
+
"the child plan. Prefer dispatching concurrent tasks over running them\n"
|
|
295
|
+
"sequentially yourself."
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class SubagentsSection(MarkdownSection[_SubagentsSectionParams]):
|
|
300
|
+
"""Explain the delegation workflow and expose the dispatch tool."""
|
|
301
|
+
|
|
302
|
+
def __init__(
|
|
303
|
+
self,
|
|
304
|
+
*,
|
|
305
|
+
isolation_level: SubagentIsolationLevel | None = None,
|
|
306
|
+
accepts_overrides: bool = False,
|
|
307
|
+
) -> None:
|
|
308
|
+
effective_level = (
|
|
309
|
+
isolation_level
|
|
310
|
+
if isolation_level is not None
|
|
311
|
+
else SubagentIsolationLevel.NO_ISOLATION
|
|
312
|
+
)
|
|
313
|
+
tool = build_dispatch_subagents_tool(
|
|
314
|
+
isolation_level=effective_level,
|
|
315
|
+
accepts_overrides=accepts_overrides,
|
|
316
|
+
)
|
|
317
|
+
super().__init__(
|
|
318
|
+
title="Delegation",
|
|
319
|
+
key="subagents",
|
|
320
|
+
template=_DELEGATION_BODY,
|
|
321
|
+
default_params=_SubagentsSectionParams(),
|
|
322
|
+
tools=(tool,),
|
|
323
|
+
accepts_overrides=accepts_overrides,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
@override
|
|
327
|
+
def render(self, params: SupportsDataclass | None, depth: int) -> str:
|
|
328
|
+
if not isinstance(params, _SubagentsSectionParams):
|
|
329
|
+
raise PromptRenderError(
|
|
330
|
+
"Subagents section requires parameters.",
|
|
331
|
+
dataclass_type=_SubagentsSectionParams,
|
|
332
|
+
)
|
|
333
|
+
return super().render(params, depth)
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
dispatch_subagents = build_dispatch_subagents_tool()
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
__all__ = [
|
|
340
|
+
"DispatchSubagentsParams",
|
|
341
|
+
"SubagentIsolationLevel",
|
|
342
|
+
"SubagentResult",
|
|
343
|
+
"SubagentsSection",
|
|
344
|
+
"build_dispatch_subagents_tool",
|
|
345
|
+
"dispatch_subagents",
|
|
346
|
+
]
|