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.
Files changed (66) hide show
  1. roboshed/__init__.py +1 -0
  2. roboshed/agents/__init__.py +6 -0
  3. roboshed/agents/librarian.py +39 -0
  4. roboshed/agents/orchestrator.py +49 -0
  5. roboshed/capabilities.py +297 -0
  6. roboshed/dependency_health.py +384 -0
  7. roboshed/deployments/__init__.py +1 -0
  8. roboshed/deployments/robosprawl/__init__.py +301 -0
  9. roboshed/identifiers.py +27 -0
  10. roboshed/models.py +242 -0
  11. roboshed/py.typed +1 -0
  12. roboshed/sandbox.py +206 -0
  13. roboshed/skills/__init__.py +8 -0
  14. roboshed/skills/cli_tools/__init__.py +10 -0
  15. roboshed/skills/cli_tools/prompts.py +195 -0
  16. roboshed/skills/email_tools/__init__.py +10 -0
  17. roboshed/skills/email_tools/prompts.py +125 -0
  18. roboshed/skills/file_editing/__init__.py +27 -0
  19. roboshed/skills/file_editing/prompts.py +149 -0
  20. roboshed/skills/robosprawl.py +141 -0
  21. roboshed/tools/__init__.py +21 -0
  22. roboshed/tools/_snapshot_metadata.py +90 -0
  23. roboshed/tools/apply_patch.py +206 -0
  24. roboshed/tools/cli_commands/__init__.py +23 -0
  25. roboshed/tools/cli_commands/run_file_command/__init__.py +12 -0
  26. roboshed/tools/cli_commands/run_file_command/command.py +155 -0
  27. roboshed/tools/cli_commands/run_file_command/resolve.py +404 -0
  28. roboshed/tools/cli_commands/run_file_command/specs.py +131 -0
  29. roboshed/tools/cli_commands/utilities/__init__.py +80 -0
  30. roboshed/tools/cli_commands/utilities/cmd_spec.py +48 -0
  31. roboshed/tools/cli_commands/utilities/constants.py +36 -0
  32. roboshed/tools/cli_commands/utilities/formatting.py +252 -0
  33. roboshed/tools/cli_commands/utilities/path_extractors.py +179 -0
  34. roboshed/tools/compactification/__init__.py +31 -0
  35. roboshed/tools/compactification/compactify_messages.py +276 -0
  36. roboshed/tools/compactification/prompts.py +107 -0
  37. roboshed/tools/compactification/summarize.py +279 -0
  38. roboshed/tools/compactification/summary_prompts.py +61 -0
  39. roboshed/tools/consolidate_memory.py +306 -0
  40. roboshed/tools/consolidate_memory_example.py +110 -0
  41. roboshed/tools/consolidate_memory_prompts.py +110 -0
  42. roboshed/tools/email/__init__.py +51 -0
  43. roboshed/tools/email/contracts.py +224 -0
  44. roboshed/tools/email/drafts.py +425 -0
  45. roboshed/tools/email/factory.py +267 -0
  46. roboshed/tools/email/inputs.py +66 -0
  47. roboshed/tools/email/messages.py +253 -0
  48. roboshed/tools/email/runtime.py +40 -0
  49. roboshed/tools/guard.py +170 -0
  50. roboshed/tools/guard_formatting.py +58 -0
  51. roboshed/tools/librarian_errors.py +8 -0
  52. roboshed/tools/memory_files.py +92 -0
  53. roboshed/tools/purge_files.py +101 -0
  54. roboshed/tools/runner.py +258 -0
  55. roboshed/tools/sleep_between_runs.py +67 -0
  56. roboshed/tools/snapshot_conversation_example.py +38 -0
  57. roboshed/tools/snapshot_conversation_prompts.py +89 -0
  58. roboshed/tools/snapshot_conversations.py +339 -0
  59. roboshed/tools/snapshot_normalize.py +61 -0
  60. roboshed/tools/truncation.py +20 -0
  61. roboshed/tools/types.py +30 -0
  62. roboshed/tools/utils.py +252 -0
  63. roboshed-0.1.0a3.dist-info/METADATA +141 -0
  64. roboshed-0.1.0a3.dist-info/RECORD +66 -0
  65. roboshed-0.1.0a3.dist-info/WHEEL +4 -0
  66. 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,6 @@
1
+ """Reusable agent presets built from Roboz primitives."""
2
+
3
+ from .librarian import librarian
4
+ from .orchestrator import orchestrator
5
+
6
+ __all__ = ["librarian", "orchestrator"]
@@ -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
+ )
@@ -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
+ )