roboshed 0.1.0a3__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.
- roboshed/__init__.py +1 -0
- roboshed/agents/__init__.py +6 -0
- roboshed/agents/librarian.py +39 -0
- roboshed/agents/orchestrator.py +49 -0
- roboshed/capabilities.py +297 -0
- roboshed/dependency_health.py +384 -0
- roboshed/deployments/__init__.py +1 -0
- roboshed/deployments/robosprawl/__init__.py +301 -0
- roboshed/identifiers.py +27 -0
- roboshed/models.py +242 -0
- roboshed/py.typed +1 -0
- roboshed/sandbox.py +206 -0
- roboshed/skills/__init__.py +8 -0
- roboshed/skills/cli_tools/__init__.py +10 -0
- roboshed/skills/cli_tools/prompts.py +195 -0
- roboshed/skills/email_tools/__init__.py +10 -0
- roboshed/skills/email_tools/prompts.py +125 -0
- roboshed/skills/file_editing/__init__.py +27 -0
- roboshed/skills/file_editing/prompts.py +149 -0
- roboshed/skills/robosprawl.py +141 -0
- roboshed/tools/__init__.py +21 -0
- roboshed/tools/_snapshot_metadata.py +90 -0
- roboshed/tools/apply_patch.py +206 -0
- roboshed/tools/cli_commands/__init__.py +23 -0
- roboshed/tools/cli_commands/run_file_command/__init__.py +12 -0
- roboshed/tools/cli_commands/run_file_command/command.py +155 -0
- roboshed/tools/cli_commands/run_file_command/resolve.py +404 -0
- roboshed/tools/cli_commands/run_file_command/specs.py +131 -0
- roboshed/tools/cli_commands/utilities/__init__.py +80 -0
- roboshed/tools/cli_commands/utilities/cmd_spec.py +48 -0
- roboshed/tools/cli_commands/utilities/constants.py +36 -0
- roboshed/tools/cli_commands/utilities/formatting.py +252 -0
- roboshed/tools/cli_commands/utilities/path_extractors.py +179 -0
- roboshed/tools/compactification/__init__.py +31 -0
- roboshed/tools/compactification/compactify_messages.py +276 -0
- roboshed/tools/compactification/prompts.py +107 -0
- roboshed/tools/compactification/summarize.py +279 -0
- roboshed/tools/compactification/summary_prompts.py +61 -0
- roboshed/tools/consolidate_memory.py +306 -0
- roboshed/tools/consolidate_memory_example.py +110 -0
- roboshed/tools/consolidate_memory_prompts.py +110 -0
- roboshed/tools/email/__init__.py +51 -0
- roboshed/tools/email/contracts.py +224 -0
- roboshed/tools/email/drafts.py +425 -0
- roboshed/tools/email/factory.py +267 -0
- roboshed/tools/email/inputs.py +66 -0
- roboshed/tools/email/messages.py +253 -0
- roboshed/tools/email/runtime.py +40 -0
- roboshed/tools/guard.py +170 -0
- roboshed/tools/guard_formatting.py +58 -0
- roboshed/tools/librarian_errors.py +8 -0
- roboshed/tools/memory_files.py +92 -0
- roboshed/tools/purge_files.py +101 -0
- roboshed/tools/runner.py +258 -0
- roboshed/tools/sleep_between_runs.py +67 -0
- roboshed/tools/snapshot_conversation_example.py +38 -0
- roboshed/tools/snapshot_conversation_prompts.py +89 -0
- roboshed/tools/snapshot_conversations.py +339 -0
- roboshed/tools/snapshot_normalize.py +61 -0
- roboshed/tools/truncation.py +20 -0
- roboshed/tools/types.py +30 -0
- roboshed/tools/utils.py +252 -0
- roboshed-0.1.0a3.dist-info/METADATA +141 -0
- roboshed-0.1.0a3.dist-info/RECORD +66 -0
- roboshed-0.1.0a3.dist-info/WHEEL +4 -0
- roboshed-0.1.0a3.dist-info/licenses/LICENSE +201 -0
roboshed/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Reusable tools and compositions built on Roboz."""
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Deterministic Librarian preset with caller-selected maintenance capabilities."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Final
|
|
5
|
+
|
|
6
|
+
from roboshed.identifiers import LIBRARIAN_AGENT_NAME
|
|
7
|
+
from roboz.deployment import AgentCapability, AgentDefinition
|
|
8
|
+
from roboz.llm import EndpointLike
|
|
9
|
+
|
|
10
|
+
LIBRARIAN_AGENT_DESCRIPTION: Final[str] = (
|
|
11
|
+
"Runs deterministic maintenance cycles using the configured capabilities."
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def librarian(
|
|
16
|
+
*,
|
|
17
|
+
capabilities: Sequence[AgentCapability],
|
|
18
|
+
agent_endpoint: EndpointLike | None = None,
|
|
19
|
+
name: str = LIBRARIAN_AGENT_NAME,
|
|
20
|
+
) -> AgentDefinition:
|
|
21
|
+
"""Configure a deterministic background agent with ordered capabilities.
|
|
22
|
+
|
|
23
|
+
Supply automatic maintenance work and a stopping policy, such as
|
|
24
|
+
MaintenanceCadence. Each cycle runs the contributed default tools in order.
|
|
25
|
+
Capabilities own their settings and may use agent_endpoint as a model default.
|
|
26
|
+
The definition selects no project, persistence, or thread lifecycle.
|
|
27
|
+
"""
|
|
28
|
+
return AgentDefinition(
|
|
29
|
+
name=name,
|
|
30
|
+
description=LIBRARIAN_AGENT_DESCRIPTION,
|
|
31
|
+
interaction_mode=None,
|
|
32
|
+
agent_endpoint=agent_endpoint,
|
|
33
|
+
is_agentic=False,
|
|
34
|
+
automatic_tool_prompt=False,
|
|
35
|
+
capabilities=tuple(capabilities),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
__all__ = ["LIBRARIAN_AGENT_DESCRIPTION", "librarian"]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Opinionated persistent collaboration, independent of providers and hosts."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from roboz.deployment import AgentCapability, AgentDefinition, Capability, SubAgentSpec
|
|
7
|
+
from roboz.llm import EndpointLike
|
|
8
|
+
from roboz.runtime import Output
|
|
9
|
+
from roboz.tools import stop
|
|
10
|
+
|
|
11
|
+
ORCHESTRATOR_PROMPT = """You are the user's persistent collaborator. Help them reach their
|
|
12
|
+
goals using the available capabilities, and delegate to suitable specialists.
|
|
13
|
+
Read supplied memory and apply the user's recorded preferences. Ask for missing
|
|
14
|
+
information when you cannot obtain it with the available tools. After completing
|
|
15
|
+
work, report the result and ask what the user wants to do next. Remain available
|
|
16
|
+
across tasks: do not end the session merely because a task is finished. Call stop
|
|
17
|
+
only when the user asks to end the session, including a standing instruction they
|
|
18
|
+
have supplied. Do not claim capabilities that are not available."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def orchestrator(
|
|
22
|
+
*,
|
|
23
|
+
agent_endpoint: EndpointLike,
|
|
24
|
+
name: str = "orchestrator",
|
|
25
|
+
subagents: Sequence[SubAgentSpec] = (),
|
|
26
|
+
capabilities: Sequence[AgentCapability] = (),
|
|
27
|
+
instructions: str = "",
|
|
28
|
+
initial_messages: Sequence[Path | str] = (),
|
|
29
|
+
interaction_mode: Output | None = Output.CLI,
|
|
30
|
+
) -> AgentDefinition:
|
|
31
|
+
"""Configure a persistent orchestrator with caller-selected capabilities.
|
|
32
|
+
|
|
33
|
+
Capabilities are the only extension input. The preset always supplies the
|
|
34
|
+
role prompt and stop tool; the runtime supplies user interaction. It selects
|
|
35
|
+
no provider, tool integration, filesystem permission, or memory location.
|
|
36
|
+
Interaction defaults to CLI; hosts select their channel explicitly.
|
|
37
|
+
Additional instructions describe the deployment without changing core prompts.
|
|
38
|
+
"""
|
|
39
|
+
return AgentDefinition(
|
|
40
|
+
name=name,
|
|
41
|
+
agent_endpoint=agent_endpoint,
|
|
42
|
+
description="Coordinates ongoing user goals and specialist agents.",
|
|
43
|
+
system_prompt=ORCHESTRATOR_PROMPT
|
|
44
|
+
+ ("\n\n" + instructions if instructions else ""),
|
|
45
|
+
capabilities=(Capability(tools=(stop,)), *capabilities),
|
|
46
|
+
subagents=tuple(subagents),
|
|
47
|
+
initial_messages=tuple(initial_messages),
|
|
48
|
+
interaction_mode=interaction_mode,
|
|
49
|
+
)
|
roboshed/capabilities.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Configured tool and skill capabilities for Shed agent definitions."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from collections.abc import Collection
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from roboshed.identifiers import (
|
|
8
|
+
CONSOLIDATE_MEMORY_TOOL_NAME,
|
|
9
|
+
PURGE_LOGS_TOOL_NAME,
|
|
10
|
+
PURGE_MEMORY_TOOL_NAME,
|
|
11
|
+
PURGE_SNAPSHOTS_TOOL_NAME,
|
|
12
|
+
SLEEP_BETWEEN_RUNS_TOOL_NAME,
|
|
13
|
+
SNAPSHOT_CONVERSATIONS_TOOL_NAME,
|
|
14
|
+
)
|
|
15
|
+
from roboshed.skills import cli_skill, file_editing
|
|
16
|
+
from roboshed.tools import (
|
|
17
|
+
get_apply_patch,
|
|
18
|
+
get_compactify_messages_when_needed_tool,
|
|
19
|
+
get_run_file_command,
|
|
20
|
+
)
|
|
21
|
+
from roboshed.tools.cli_commands.run_file_command import FILE_COMMANDS_READ
|
|
22
|
+
from roboshed.tools.compactification import (
|
|
23
|
+
DEFAULT_MAX_CHARS_TOLERANCE_PERCENT,
|
|
24
|
+
DEFAULT_THRESHOLD_PERCENT,
|
|
25
|
+
)
|
|
26
|
+
from roboshed.tools.consolidate_memory import consolidate_memory
|
|
27
|
+
from roboshed.tools.purge_files import purge_files
|
|
28
|
+
from roboshed.tools.sleep_between_runs import sleep_between_runs
|
|
29
|
+
from roboshed.tools.snapshot_conversations import snapshot_conversations
|
|
30
|
+
from roboshed.sandbox import PermissionPolicy, Sandbox
|
|
31
|
+
from roboz.deployment import AgentCapability, Capability
|
|
32
|
+
from roboz.llm import EndpointLike
|
|
33
|
+
from roboz.runtime import EventPipe
|
|
34
|
+
from roboz.tooling.context import Ctx
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True)
|
|
38
|
+
class FileCommands(AgentCapability):
|
|
39
|
+
"""Guarded read commands, optionally accompanied by their orientation skill."""
|
|
40
|
+
|
|
41
|
+
permissions: PermissionPolicy
|
|
42
|
+
auto_load_skill: bool = True
|
|
43
|
+
|
|
44
|
+
def build(
|
|
45
|
+
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
46
|
+
) -> Capability:
|
|
47
|
+
"""Build a read-tool chain using this agent's pipe and selected policy."""
|
|
48
|
+
return Capability(
|
|
49
|
+
tools=(
|
|
50
|
+
get_run_file_command(
|
|
51
|
+
**self.permissions.tool_options(pipe),
|
|
52
|
+
command_specs=FILE_COMMANDS_READ,
|
|
53
|
+
),
|
|
54
|
+
),
|
|
55
|
+
auto_loaded_skills=(cli_skill,) if self.auto_load_skill else (),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass(frozen=True)
|
|
60
|
+
class FileEditing(AgentCapability):
|
|
61
|
+
"""Literal patch editing with caller-selected file permissions."""
|
|
62
|
+
|
|
63
|
+
permissions: PermissionPolicy
|
|
64
|
+
auto_load_skill: bool = True
|
|
65
|
+
|
|
66
|
+
def build(
|
|
67
|
+
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
68
|
+
) -> Capability:
|
|
69
|
+
"""Build patch editing and optional orientation against the owning pipe."""
|
|
70
|
+
return Capability(
|
|
71
|
+
tools=(get_apply_patch(**self.permissions.tool_options(pipe)),),
|
|
72
|
+
auto_loaded_skills=(file_editing,) if self.auto_load_skill else (),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(frozen=True)
|
|
77
|
+
class Compactification(AgentCapability):
|
|
78
|
+
"""Automatic context compaction, bound to the selected or owning endpoint."""
|
|
79
|
+
|
|
80
|
+
endpoint: EndpointLike | None = None
|
|
81
|
+
threshold_percent: float = DEFAULT_THRESHOLD_PERCENT
|
|
82
|
+
timeout_s: float | None = None
|
|
83
|
+
|
|
84
|
+
def build(
|
|
85
|
+
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
86
|
+
) -> Capability:
|
|
87
|
+
"""Use the configured endpoint, falling back to the agent's endpoint."""
|
|
88
|
+
endpoint = self.endpoint if self.endpoint is not None else default_endpoint
|
|
89
|
+
if endpoint is None:
|
|
90
|
+
raise ValueError("compaction requires an endpoint")
|
|
91
|
+
return Capability(
|
|
92
|
+
default_tools=(
|
|
93
|
+
get_compactify_messages_when_needed_tool(
|
|
94
|
+
endpoint=endpoint,
|
|
95
|
+
threshold_percent=self.threshold_percent,
|
|
96
|
+
timeout_s=self.timeout_s,
|
|
97
|
+
pipe=pipe,
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass(frozen=True)
|
|
104
|
+
class ConversationSnapshots(AgentCapability):
|
|
105
|
+
"""Automatic bounded snapshots of the selected agents' conversations.
|
|
106
|
+
|
|
107
|
+
The endpoint defaults to the owning agent's model. Timeout is in seconds;
|
|
108
|
+
summary tolerance is a finite, non-negative percentage above max_chars.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
sandbox: Sandbox
|
|
112
|
+
project_slug: str
|
|
113
|
+
agent_names: Collection[str]
|
|
114
|
+
endpoint: EndpointLike | None = None
|
|
115
|
+
token_growth_threshold: int = 20_000
|
|
116
|
+
max_chars: int = 8_000
|
|
117
|
+
max_chars_tolerance_percent: float = DEFAULT_MAX_CHARS_TOLERANCE_PERCENT
|
|
118
|
+
timeout_s: float = 300.0
|
|
119
|
+
|
|
120
|
+
def __post_init__(self) -> None:
|
|
121
|
+
"""Validate summary bounds before constructing tools."""
|
|
122
|
+
if self.timeout_s <= 0:
|
|
123
|
+
raise ValueError("timeout_s must be greater than zero")
|
|
124
|
+
if (
|
|
125
|
+
not math.isfinite(self.max_chars_tolerance_percent)
|
|
126
|
+
or self.max_chars_tolerance_percent < 0
|
|
127
|
+
):
|
|
128
|
+
raise ValueError(
|
|
129
|
+
"max_chars_tolerance_percent must be finite and non-negative"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
def build(
|
|
133
|
+
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
134
|
+
) -> Capability:
|
|
135
|
+
"""Bind snapshotting to its chosen model and the owning agent's pipe."""
|
|
136
|
+
endpoint = self.endpoint if self.endpoint is not None else default_endpoint
|
|
137
|
+
if endpoint is None:
|
|
138
|
+
raise ValueError("conversation snapshots require an endpoint")
|
|
139
|
+
return Capability(
|
|
140
|
+
default_tools=(
|
|
141
|
+
snapshot_conversations(
|
|
142
|
+
Ctx(
|
|
143
|
+
endpoint=endpoint,
|
|
144
|
+
conversation_root=self.sandbox.project_logs_dir(
|
|
145
|
+
self.project_slug
|
|
146
|
+
),
|
|
147
|
+
snapshot_root=self.sandbox.project_snapshots_dir(
|
|
148
|
+
self.project_slug
|
|
149
|
+
),
|
|
150
|
+
memory_root=self.sandbox.project_memory_dir(self.project_slug),
|
|
151
|
+
agent_names=set(self.agent_names),
|
|
152
|
+
token_growth_threshold=self.token_growth_threshold,
|
|
153
|
+
max_chars=self.max_chars,
|
|
154
|
+
max_chars_tolerance_percent=self.max_chars_tolerance_percent,
|
|
155
|
+
timeout_s=self.timeout_s,
|
|
156
|
+
pipe=pipe,
|
|
157
|
+
)
|
|
158
|
+
).copy(name=SNAPSHOT_CONVERSATIONS_TOOL_NAME),
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
@dataclass(frozen=True)
|
|
164
|
+
class MemoryConsolidation(AgentCapability):
|
|
165
|
+
"""Automatic consolidation of pending snapshots into durable memory.
|
|
166
|
+
|
|
167
|
+
The endpoint defaults to the owning agent's model. Age and timeout are in
|
|
168
|
+
seconds; summary tolerance is a finite, non-negative percentage above max_chars.
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
sandbox: Sandbox
|
|
172
|
+
project_slug: str
|
|
173
|
+
agent_names: Collection[str]
|
|
174
|
+
endpoint: EndpointLike | None = None
|
|
175
|
+
min_pending_snapshots: int = 3
|
|
176
|
+
max_pending_age_seconds: float = 86_400.0
|
|
177
|
+
max_chars: int = 12_000
|
|
178
|
+
max_chars_tolerance_percent: float = DEFAULT_MAX_CHARS_TOLERANCE_PERCENT
|
|
179
|
+
timeout_s: float = 300.0
|
|
180
|
+
|
|
181
|
+
def __post_init__(self) -> None:
|
|
182
|
+
"""Validate summary bounds before constructing tools."""
|
|
183
|
+
if self.timeout_s <= 0:
|
|
184
|
+
raise ValueError("timeout_s must be greater than zero")
|
|
185
|
+
if (
|
|
186
|
+
not math.isfinite(self.max_chars_tolerance_percent)
|
|
187
|
+
or self.max_chars_tolerance_percent < 0
|
|
188
|
+
):
|
|
189
|
+
raise ValueError(
|
|
190
|
+
"max_chars_tolerance_percent must be finite and non-negative"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def build(
|
|
194
|
+
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
195
|
+
) -> Capability:
|
|
196
|
+
"""Bind consolidation to its chosen model and the owning agent's pipe."""
|
|
197
|
+
endpoint = self.endpoint if self.endpoint is not None else default_endpoint
|
|
198
|
+
if endpoint is None:
|
|
199
|
+
raise ValueError("memory consolidation requires an endpoint")
|
|
200
|
+
return Capability(
|
|
201
|
+
default_tools=(
|
|
202
|
+
consolidate_memory(
|
|
203
|
+
Ctx(
|
|
204
|
+
endpoint=endpoint,
|
|
205
|
+
snapshot_root=self.sandbox.project_snapshots_dir(
|
|
206
|
+
self.project_slug
|
|
207
|
+
),
|
|
208
|
+
memory_root=self.sandbox.project_memory_dir(self.project_slug),
|
|
209
|
+
conversation_root=self.sandbox.project_logs_dir(
|
|
210
|
+
self.project_slug
|
|
211
|
+
),
|
|
212
|
+
agent_names=set(self.agent_names),
|
|
213
|
+
min_pending_snapshots=self.min_pending_snapshots,
|
|
214
|
+
max_pending_age_seconds=self.max_pending_age_seconds,
|
|
215
|
+
max_chars=self.max_chars,
|
|
216
|
+
max_chars_tolerance_percent=self.max_chars_tolerance_percent,
|
|
217
|
+
timeout_s=self.timeout_s,
|
|
218
|
+
pipe=pipe,
|
|
219
|
+
)
|
|
220
|
+
).copy(name=CONSOLIDATE_MEMORY_TOOL_NAME),
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
@dataclass(frozen=True)
|
|
226
|
+
class ArtifactRetention(AgentCapability):
|
|
227
|
+
"""Automatic retention limits for project logs, snapshots, and memory."""
|
|
228
|
+
|
|
229
|
+
sandbox: Sandbox
|
|
230
|
+
project_slug: str
|
|
231
|
+
max_log_files: int = 500
|
|
232
|
+
max_snapshot_files: int = 100
|
|
233
|
+
max_memory_files: int = 10
|
|
234
|
+
|
|
235
|
+
def build(
|
|
236
|
+
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
237
|
+
) -> Capability:
|
|
238
|
+
"""Bind retention to the selected project without requiring a model."""
|
|
239
|
+
return Capability(
|
|
240
|
+
default_tools=(
|
|
241
|
+
purge_files(
|
|
242
|
+
Ctx(
|
|
243
|
+
folders=[self.sandbox.project_logs_dir(self.project_slug)],
|
|
244
|
+
pattern="*.json",
|
|
245
|
+
max_files=self.max_log_files,
|
|
246
|
+
)
|
|
247
|
+
).copy(name=PURGE_LOGS_TOOL_NAME),
|
|
248
|
+
purge_files(
|
|
249
|
+
Ctx(
|
|
250
|
+
folders=[self.sandbox.project_snapshots_dir(self.project_slug)],
|
|
251
|
+
pattern="*.md",
|
|
252
|
+
max_files=self.max_snapshot_files,
|
|
253
|
+
prune_empty_directories=True,
|
|
254
|
+
)
|
|
255
|
+
).copy(name=PURGE_SNAPSHOTS_TOOL_NAME),
|
|
256
|
+
purge_files(
|
|
257
|
+
Ctx(
|
|
258
|
+
folders=[self.sandbox.project_memory_dir(self.project_slug)],
|
|
259
|
+
pattern="*.md",
|
|
260
|
+
max_files=self.max_memory_files,
|
|
261
|
+
)
|
|
262
|
+
).copy(name=PURGE_MEMORY_TOOL_NAME),
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@dataclass(frozen=True)
|
|
268
|
+
class MaintenanceCadence(AgentCapability):
|
|
269
|
+
"""Wait between cycles while watched conversations are active; stop when idle.
|
|
270
|
+
|
|
271
|
+
Place this after maintenance capabilities so their work runs before waiting.
|
|
272
|
+
Waiting observes the owning agent's cancellation independently of any model.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
sandbox: Sandbox
|
|
276
|
+
project_slug: str
|
|
277
|
+
agent_names: Collection[str]
|
|
278
|
+
seconds: float = 120.0
|
|
279
|
+
|
|
280
|
+
def build(
|
|
281
|
+
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
282
|
+
) -> Capability:
|
|
283
|
+
"""Bind cadence and idle stopping to this agent's cancellation state."""
|
|
284
|
+
return Capability(
|
|
285
|
+
default_tools=(
|
|
286
|
+
sleep_between_runs(
|
|
287
|
+
Ctx(
|
|
288
|
+
seconds=self.seconds,
|
|
289
|
+
is_cancelled=lambda: pipe.cancelled,
|
|
290
|
+
conversation_root=self.sandbox.project_logs_dir(
|
|
291
|
+
self.project_slug
|
|
292
|
+
),
|
|
293
|
+
agent_names=set(self.agent_names),
|
|
294
|
+
)
|
|
295
|
+
).copy(name=SLEEP_BETWEEN_RUNS_TOOL_NAME),
|
|
296
|
+
)
|
|
297
|
+
)
|