roboz 0.1.2.dev2__tar.gz → 0.1.2.dev4__tar.gz

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. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/CHANGELOG.md +28 -1
  2. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/PKG-INFO +12 -9
  3. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/README.md +11 -6
  4. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/pyproject.toml +1 -2
  5. roboz-0.1.2.dev4/src/roboz/deployment.py +391 -0
  6. roboz-0.1.2.dev2/src/roboz/deployment.py +0 -151
  7. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/.gitignore +0 -0
  8. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/LICENSE +0 -0
  9. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/__init__.py +0 -0
  10. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/_naming.py +0 -0
  11. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/__init__.py +0 -0
  12. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/_execution_context.py +0 -0
  13. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/_identifiers.py +0 -0
  14. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/_notifications.py +0 -0
  15. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/_prompts.py +0 -0
  16. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/_tool_observer.py +0 -0
  17. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/background_agent.py +0 -0
  18. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/core.py +0 -0
  19. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/prompt_agent_tool.py +0 -0
  20. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/agent/subagent.py +0 -0
  21. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/dependencies.py +0 -0
  22. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/exceptions.py +0 -0
  23. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/__init__.py +0 -0
  24. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/_diagnostics.py +0 -0
  25. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/_retry.py +0 -0
  26. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/_truncation.py +0 -0
  27. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/binding.py +0 -0
  28. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/calls.py +0 -0
  29. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/completion.py +0 -0
  30. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/endpoints.py +0 -0
  31. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/openrouter.py +0 -0
  32. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/llm/prompts.py +0 -0
  33. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/models/__init__.py +0 -0
  34. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/models/_schema.py +0 -0
  35. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/models/_serialization.py +0 -0
  36. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/models/_telemetry.py +0 -0
  37. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/models/core.py +0 -0
  38. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/models/truncation.py +0 -0
  39. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/py.typed +0 -0
  40. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/__init__.py +0 -0
  41. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/_environment.py +0 -0
  42. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/_external.py +0 -0
  43. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/_logging.py +0 -0
  44. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/_paths.py +0 -0
  45. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/events.py +0 -0
  46. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/io.py +0 -0
  47. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/observability.py +0 -0
  48. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/persistence/__init__.py +0 -0
  49. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/persistence/activity.py +0 -0
  50. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/persistence/schema.py +0 -0
  51. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/pipe.py +0 -0
  52. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/runtime/sinks.py +0 -0
  53. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/skill/__init__.py +0 -0
  54. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/skill/_prompts.py +0 -0
  55. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/skill/core.py +0 -0
  56. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tooling/__init__.py +0 -0
  57. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tooling/_prompts.py +0 -0
  58. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tooling/_protocols.py +0 -0
  59. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tooling/_typing.py +0 -0
  60. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tooling/context.py +0 -0
  61. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tooling/core.py +0 -0
  62. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tooling/decorators.py +0 -0
  63. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tools/__init__.py +0 -0
  64. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tools/_identifiers.py +0 -0
  65. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tools/control.py +0 -0
  66. {roboz-0.1.2.dev2 → roboz-0.1.2.dev4}/src/roboz/tools/interaction.py +0 -0
@@ -2,6 +2,33 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.2.dev4 - 2026-09-12
6
+
7
+ - Breaking: make `DeployableAgent` an explicitly configured class. Constructor
8
+ capabilities are protected defaults; append capabilities and child agents with
9
+ the add methods, configure endpoints and runtime values separately, and unpack
10
+ `(agent, background_agents)` from `build()`. `build_graph()` is removed.
11
+ - Breaking: capability builders now declare concrete `required_attributes` and
12
+ receive `build(agent, pipe)`. Builds validate the complete graph before
13
+ constructing sinks, pipes, or tools and aggregate missing, `None`, and
14
+ incorrectly typed owner attributes.
15
+
16
+ ## 0.1.2.dev3 - 2026-09-11
17
+
18
+ - Breaking: remove the redundant `roboz[shed]` installation extra. Install
19
+ `roboshed` directly; it installs its compatible core Roboz dependency.
20
+
21
+ - Add `DeployableAgent.build_graph()` to return the root agent and all background
22
+ handles for host lifecycle control. `build()` still returns the root alone.
23
+
24
+ - Breaking: replace `AgentDefinition` and `SubAgentSpec` with recursive
25
+ `DeployableAgent` definitions. Put child definitions directly in `subagents`
26
+ or `background_agents`; delegation tools use the child's name and description,
27
+ and background agents start through default tools. Construct generic
28
+ definitions directly with `DeployableAgent(...)`.
29
+ Sandbox-aware `Deployment` lives in `roboshed.deployments`, not core.
30
+ See `docs/agent-factories.md` for migration.
31
+
5
32
  ## 0.1.2.dev2 - 2026-09-09
6
33
 
7
34
  - Breaking: remove the `roboz[openai]` extra. Install
@@ -20,7 +47,7 @@
20
47
  references retain identity and deferred resolution. See
21
48
  `docs/agent-factories.md` for builder migration.
22
49
 
23
- - Breaking: move the Librarian and its memory/summarization tools out of core into `roboshed.agents` and `roboshed.tools`. Use `librarian(capabilities=..., agent_endpoint=...)` instead of `LibrarianConstructor` and its path record; see `docs/agent-factories.md`. Core retains control, interaction, and generic construction primitives.
50
+ - Breaking: move the Librarian and its memory/summarization tools out of core into `roboshed.agents` and `roboshed.tools`. Use `librarian(sandbox, agent_names, agent_endpoint=...)` instead of `LibrarianConstructor` and its path record; see `docs/agent-factories.md`. Core retains control, interaction, and generic construction primitives.
24
51
 
25
52
  - Add generic `AgentDefinition`, `AgentCapability`, `Capability`, and `SubAgentSpec` in `roboz.deployment`. Configure all tools and skills through capabilities, with fresh agent pipes and explicit sink configuration; definitions select no project, memory, or persistence conventions. See `docs/agent-factories.md`.
26
53
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: roboz
3
- Version: 0.1.2.dev2
3
+ Version: 0.1.2.dev4
4
4
  Import-Name: roboz
5
5
  Summary: Typed primitives for composable agentic workflows
6
6
  Project-URL: Homepage, https://github.com/Tachion-Oy/roboz
@@ -25,8 +25,6 @@ Requires-Dist: python-dotenv>=1.2.1
25
25
  Requires-Dist: rich>=14.2.0
26
26
  Provides-Extra: proton-bridge-beta
27
27
  Requires-Dist: roboz-proton-bridge<0.2.0,>=0.1.0b1; extra == 'proton-bridge-beta'
28
- Provides-Extra: shed
29
- Requires-Dist: roboshed<0.2.0,>=0.1.0a2; extra == 'shed'
30
28
  Description-Content-Type: text/markdown
31
29
 
32
30
  # Roboz
@@ -170,12 +168,17 @@ Install `roboz-endpoints[openai]` for the initial OpenRouter, Cerebras, and Groq
170
168
  catalogues; it installs core automatically. See the
171
169
  [endpoint guide](packages/endpoints/README.md) for model selection and SDK adapters.
172
170
 
173
- `roboz.deployment` supplies generic agent definitions, capability contracts,
174
- and specialist composition. Capabilities accept tool-specific endpoints and pass
175
- them directly to their tools, using the owning agent's endpoint as the default. `roboshed` supplies orchestrator/Librarian
176
- factories, concrete capabilities, memory tools, and workspace structure. These are described in [agent factories](docs/agent-factories.md). Configured file and compaction capabilities live in `roboshed.capabilities`,
177
- alongside `roboshed.tools` and `roboshed.skills`. The project deployment lives in
178
- `roboshed.deployments.robosprawl`; agent presets live in `roboshed.agents`.
171
+ `roboz.deployment` supplies recursive `DeployableAgent` definitions and
172
+ capability contracts. Put definitions directly in `subagents` or
173
+ `background_agents`, append extensions with the explicit add methods, and unpack
174
+ `agent, background_agents = definition.build()`. Capabilities declare required
175
+ owner attributes and bind with `build(agent, pipe)`. `roboshed` supplies reusable
176
+ orchestrator and Librarian constructors, concrete capabilities, memory tools,
177
+ and sandbox structure. Import the constructors from `roboshed.agents`; the fixed
178
+ lazy RoboSprawl recipe is `roboshed.deployments.robosprawl.RoboSprawl`. Construct
179
+ it without inputs, configure it through setters, then call argument-free
180
+ `build()` with all required inputs supplied. The recipe owns project context
181
+ and persistence; the caller owns invocation.
179
182
 
180
183
  ## Documentation
181
184
 
@@ -139,12 +139,17 @@ Install `roboz-endpoints[openai]` for the initial OpenRouter, Cerebras, and Groq
139
139
  catalogues; it installs core automatically. See the
140
140
  [endpoint guide](packages/endpoints/README.md) for model selection and SDK adapters.
141
141
 
142
- `roboz.deployment` supplies generic agent definitions, capability contracts,
143
- and specialist composition. Capabilities accept tool-specific endpoints and pass
144
- them directly to their tools, using the owning agent's endpoint as the default. `roboshed` supplies orchestrator/Librarian
145
- factories, concrete capabilities, memory tools, and workspace structure. These are described in [agent factories](docs/agent-factories.md). Configured file and compaction capabilities live in `roboshed.capabilities`,
146
- alongside `roboshed.tools` and `roboshed.skills`. The project deployment lives in
147
- `roboshed.deployments.robosprawl`; agent presets live in `roboshed.agents`.
142
+ `roboz.deployment` supplies recursive `DeployableAgent` definitions and
143
+ capability contracts. Put definitions directly in `subagents` or
144
+ `background_agents`, append extensions with the explicit add methods, and unpack
145
+ `agent, background_agents = definition.build()`. Capabilities declare required
146
+ owner attributes and bind with `build(agent, pipe)`. `roboshed` supplies reusable
147
+ orchestrator and Librarian constructors, concrete capabilities, memory tools,
148
+ and sandbox structure. Import the constructors from `roboshed.agents`; the fixed
149
+ lazy RoboSprawl recipe is `roboshed.deployments.robosprawl.RoboSprawl`. Construct
150
+ it without inputs, configure it through setters, then call argument-free
151
+ `build()` with all required inputs supplied. The recipe owns project context
152
+ and persistence; the caller owns invocation.
148
153
 
149
154
  ## Documentation
150
155
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "roboz"
3
- version = "0.1.2.dev2"
3
+ version = "0.1.2.dev4"
4
4
  description = "Typed primitives for composable agentic workflows"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Tachion Oy" }]
@@ -30,7 +30,6 @@ requires = ["hatchling>=1.32"]
30
30
  build-backend = "hatchling.build"
31
31
 
32
32
  [project.optional-dependencies]
33
- shed = ["roboshed>=0.1.0a2,<0.2.0"]
34
33
  proton-bridge-beta = ["roboz-proton-bridge>=0.1.0b1,<0.2.0"]
35
34
 
36
35
  [tool.uv.workspace]
@@ -0,0 +1,391 @@
1
+ """Configurable agent definitions and runtime-bound capabilities."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Iterator, Mapping, Sequence
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Protocol
9
+
10
+ from roboz.agent import Agent, run_background_agent, run_subagent
11
+ from roboz.llm import EndpointLike
12
+ from roboz.runtime import EventPipe, EventSink, Output
13
+ from roboz.skill import Skill
14
+ from roboz.tooling import Tool
15
+ from roboz.tooling.context import Ctx
16
+
17
+ type RequiredAttributeType = type[object] | tuple[type[object], ...]
18
+ type RequiredAttributes = Mapping[str, RequiredAttributeType]
19
+
20
+
21
+ class AgentCapability(Protocol):
22
+ """A configured feature that binds its tools to one agent runtime."""
23
+
24
+ @property
25
+ def required_attributes(self) -> RequiredAttributes:
26
+ """Declare configuration values required from the owning agent."""
27
+ ...
28
+
29
+ def build(self, agent: "DeployableAgent", pipe: EventPipe) -> "Capability":
30
+ """Bind configured tools using the owning agent and its fresh pipe."""
31
+ ...
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class Capability(AgentCapability):
36
+ """Already-bound tools and skills that provide an agent feature.
37
+
38
+ Tools shared between a chain and default execution retain their identity.
39
+ Supplied objects remain caller-owned and require no owner configuration.
40
+ """
41
+
42
+ tools: tuple[Tool | Sequence[Tool], ...] = ()
43
+ default_tools: tuple[Tool, ...] = ()
44
+ skills: tuple[Skill, ...] = ()
45
+ auto_loaded_skills: tuple[Skill, ...] = ()
46
+
47
+ @property
48
+ def required_attributes(self) -> RequiredAttributes:
49
+ """Declare that already-bound objects need no owner configuration."""
50
+ return {}
51
+
52
+ def build(self, agent: "DeployableAgent", pipe: EventPipe) -> "Capability":
53
+ """Return these already-bound inputs without copying or starting work."""
54
+ return self
55
+
56
+
57
+ class DeployableAgent:
58
+ """Mutable configuration for one agent and its attached child graph.
59
+
60
+ Constructor capabilities are fixed defaults. Later capabilities and child
61
+ definitions can only be appended. Runtime-specific values can be supplied
62
+ after declaration with set_attributes(); they belong only to this node.
63
+ Each build validates the complete graph and creates fresh runtime agents,
64
+ pipes, and capability bindings without retaining execution state here.
65
+ """
66
+
67
+ def __init__(
68
+ self,
69
+ *,
70
+ name: str,
71
+ description: str = "",
72
+ system_prompt: str = "",
73
+ is_agentic: bool = True,
74
+ automatic_tool_prompt: bool = True,
75
+ default_capabilities: Sequence[AgentCapability] = (),
76
+ subagents: Sequence["DeployableAgent"] = (),
77
+ background_agents: Sequence["DeployableAgent"] = (),
78
+ ) -> None:
79
+ """Configure identity, behavior, fixed capabilities, and initial children."""
80
+ self._name = name
81
+ self._description = description
82
+ self._system_prompt = system_prompt
83
+ self._is_agentic = is_agentic
84
+ self._automatic_tool_prompt = automatic_tool_prompt
85
+ self._default_capabilities = tuple(default_capabilities)
86
+ self._additional_capabilities: list[AgentCapability] = []
87
+ self._subagents: list[DeployableAgent] = []
88
+ self._background_agents: list[DeployableAgent] = []
89
+ self._agent_endpoint: EndpointLike | None = None
90
+ self._interaction_mode: Output | None = Output.CLI
91
+ self._initial_messages: tuple[Path | str, ...] = ()
92
+ self._attributes: dict[str, object] = {}
93
+ self.add_subagents(*subagents)
94
+ self.add_background_agents(*background_agents)
95
+
96
+ @property
97
+ def name(self) -> str:
98
+ """Return this agent's runtime name."""
99
+ return self._name
100
+
101
+ @property
102
+ def description(self) -> str:
103
+ """Return this agent's delegation description."""
104
+ return self._description
105
+
106
+ @property
107
+ def system_prompt(self) -> str:
108
+ """Return this agent's system prompt."""
109
+ return self._system_prompt
110
+
111
+ @property
112
+ def is_agentic(self) -> bool:
113
+ """Return whether the runtime agent performs model-driven turns."""
114
+ return self._is_agentic
115
+
116
+ @property
117
+ def automatic_tool_prompt(self) -> bool:
118
+ """Return whether tool instructions are added automatically."""
119
+ return self._automatic_tool_prompt
120
+
121
+ @property
122
+ def default_capabilities(self) -> tuple[AgentCapability, ...]:
123
+ """Return the fixed capabilities supplied at construction."""
124
+ return self._default_capabilities
125
+
126
+ @property
127
+ def additional_capabilities(self) -> tuple[AgentCapability, ...]:
128
+ """Return capabilities appended after the fixed defaults."""
129
+ return tuple(self._additional_capabilities)
130
+
131
+ @property
132
+ def capabilities(self) -> tuple[AgentCapability, ...]:
133
+ """Return fixed and additional capabilities in build order."""
134
+ return (*self._default_capabilities, *self._additional_capabilities)
135
+
136
+ @property
137
+ def subagents(self) -> tuple["DeployableAgent", ...]:
138
+ """Return attached synchronous child definitions."""
139
+ return tuple(self._subagents)
140
+
141
+ @property
142
+ def background_agents(self) -> tuple["DeployableAgent", ...]:
143
+ """Return attached background child definitions."""
144
+ return tuple(self._background_agents)
145
+
146
+ @property
147
+ def agent_endpoint(self) -> EndpointLike | None:
148
+ """Return the endpoint configured for this node."""
149
+ return self._agent_endpoint
150
+
151
+ @property
152
+ def interaction_mode(self) -> Output | None:
153
+ """Return this node's selected interaction mode."""
154
+ return self._interaction_mode
155
+
156
+ @property
157
+ def initial_messages(self) -> tuple[Path | str, ...]:
158
+ """Return configured initial message sources."""
159
+ return self._initial_messages
160
+
161
+ def __getattr__(self, name: str) -> object:
162
+ """Expose explicitly supplied capability attributes for owner reads."""
163
+ try:
164
+ attributes: dict[str, object] = object.__getattribute__(self, "_attributes")
165
+ return attributes[name]
166
+ except (AttributeError, KeyError):
167
+ raise AttributeError(name) from None
168
+
169
+ def set_agent_endpoint(self, endpoint: EndpointLike | None) -> None:
170
+ """Set or defer this node's model endpoint."""
171
+ self._agent_endpoint = endpoint
172
+
173
+ def set_interaction_mode(self, interaction_mode: Output | None) -> None:
174
+ """Select this node's runtime interaction mode."""
175
+ self._interaction_mode = interaction_mode
176
+
177
+ def set_initial_messages(self, messages: Sequence[Path | str]) -> None:
178
+ """Replace this node's initial message sources before a build."""
179
+ self._initial_messages = tuple(messages)
180
+
181
+ def set_attributes(self, **values: object) -> None:
182
+ """Set capability-specific values without altering agent structure.
183
+
184
+ Existing capability attributes may be updated for a later build. Public
185
+ structural attributes, methods, and all private names are protected.
186
+ """
187
+ for name in values:
188
+ if name.startswith("_") or any(
189
+ name in cls.__dict__ for cls in type(self).__mro__
190
+ ):
191
+ raise ValueError(
192
+ f"capability attribute cannot overwrite agent structure: {name!r}"
193
+ )
194
+ self._attributes.update(values)
195
+
196
+ def add_capabilities(self, *capabilities: AgentCapability) -> None:
197
+ """Append capabilities after this node's fixed defaults."""
198
+ self._additional_capabilities.extend(capabilities)
199
+
200
+ def add_subagents(self, *subagents: "DeployableAgent") -> None:
201
+ """Append synchronous child definitions."""
202
+ self._subagents.extend(self._checked_agents(subagents))
203
+
204
+ def add_background_agents(self, *agents: "DeployableAgent") -> None:
205
+ """Append background child definitions."""
206
+ self._background_agents.extend(self._checked_agents(agents))
207
+
208
+ @staticmethod
209
+ def _checked_agents(
210
+ agents: Sequence["DeployableAgent"],
211
+ ) -> tuple["DeployableAgent", ...]:
212
+ checked = tuple(agents)
213
+ if any(not isinstance(agent, DeployableAgent) for agent in checked):
214
+ raise TypeError("child definitions must be DeployableAgent instances")
215
+ return checked
216
+
217
+ def agent_names(self, *, include_background: bool = True) -> frozenset[str]:
218
+ """Validate all names and return identities in the selected branches.
219
+
220
+ Excluding background agents also excludes their descendants, for
221
+ foreground-only consumers such as conversation maintenance.
222
+ """
223
+ if not include_background:
224
+ self.agent_names()
225
+ return frozenset(self._collect_names(include_background, frozenset()))
226
+
227
+ def _collect_names(
228
+ self, include_background: bool, ancestors: frozenset[int]
229
+ ) -> set[str]:
230
+ identity = id(self)
231
+ if identity in ancestors:
232
+ raise ValueError("agent graph must not contain cycles")
233
+ ancestors = ancestors | {identity}
234
+ names = {self.name}
235
+ definitions = self.subagents
236
+ if include_background:
237
+ definitions += self.background_agents
238
+ for definition in definitions:
239
+ children = definition._collect_names(include_background, ancestors)
240
+ if names & children:
241
+ raise ValueError("agent names must be unique")
242
+ names.update(children)
243
+ return names
244
+
245
+ def validate(self) -> None:
246
+ """Validate names and every capability requirement in the full graph."""
247
+ self.agent_names()
248
+ errors = list(self._configuration_errors())
249
+ if not errors:
250
+ return
251
+ details = "\n".join(f"- {error}" for error in errors)
252
+ raise ValueError(f"invalid agent configuration:\n{details}")
253
+
254
+ def _configuration_errors(self) -> Iterator[str]:
255
+ """Yield capability requirement failures in stable graph order."""
256
+ for agent in self._walk():
257
+ yield from _capability_errors(agent)
258
+
259
+ def _walk(self) -> tuple["DeployableAgent", ...]:
260
+ definitions: list[DeployableAgent] = [self]
261
+ walked: list[DeployableAgent] = []
262
+ while definitions:
263
+ agent = definitions.pop(0)
264
+ walked.append(agent)
265
+ definitions.extend((*agent.subagents, *agent.background_agents))
266
+ return tuple(walked)
267
+
268
+ def build(
269
+ self,
270
+ *,
271
+ event_sinks: Sequence[EventSink] = (),
272
+ event_sink_factory: Callable[[str], Sequence[EventSink]] | None = None,
273
+ ) -> tuple[Agent, tuple[Agent, ...]]:
274
+ """Validate and build a fresh root with every background handle.
275
+
276
+ Caller sinks reach foreground branches only. The optional factory gives
277
+ each named agent its own fresh sinks. Construction starts no agents.
278
+ """
279
+ self.validate()
280
+ return self._build(
281
+ event_sinks=event_sinks, event_sink_factory=event_sink_factory
282
+ )
283
+
284
+ def _build(
285
+ self,
286
+ *,
287
+ event_sinks: Sequence[EventSink] = (),
288
+ event_sink_factory: Callable[[str], Sequence[EventSink]] | None = None,
289
+ ) -> tuple[Agent, tuple[Agent, ...]]:
290
+ pipe = EventPipe(
291
+ event_sinks=(
292
+ *(event_sink_factory(self.name) if event_sink_factory else ()),
293
+ *event_sinks,
294
+ )
295
+ )
296
+ contributions = [
297
+ capability.build(self, pipe) for capability in self.capabilities
298
+ ]
299
+
300
+ tools = [tool for contribution in contributions for tool in contribution.tools]
301
+ default_tools = [tool for c in contributions for tool in c.default_tools]
302
+ background_agents: list[Agent] = []
303
+ for definition in self.subagents:
304
+ child, descendants = definition._build(
305
+ event_sinks=event_sinks, event_sink_factory=event_sink_factory
306
+ )
307
+ tools.append(
308
+ run_subagent(Ctx(agent=child)).copy(
309
+ name=child.name,
310
+ description=child.description,
311
+ )
312
+ )
313
+ background_agents.extend(descendants)
314
+ for definition in self.background_agents:
315
+ child, descendants = definition._build(
316
+ event_sink_factory=event_sink_factory
317
+ )
318
+ default_tools.append(
319
+ run_background_agent(Ctx(agent=child)).copy(
320
+ name=f"start_background_agent_{child.name}",
321
+ )
322
+ )
323
+ background_agents.append(child)
324
+ background_agents.extend(descendants)
325
+
326
+ agent = Agent(
327
+ name=self.name,
328
+ description=self.description,
329
+ agent_endpoint=self.agent_endpoint,
330
+ event_pipe=pipe,
331
+ interaction_mode=self.interaction_mode,
332
+ is_agentic=self.is_agentic,
333
+ automatic_tool_prompt=self.automatic_tool_prompt,
334
+ system_prompt=self.system_prompt,
335
+ tools=tools,
336
+ default_tools=default_tools,
337
+ skills=[skill for c in contributions for skill in c.skills],
338
+ auto_loaded_skills=[
339
+ skill for c in contributions for skill in c.auto_loaded_skills
340
+ ],
341
+ initial_messages=self.initial_messages,
342
+ )
343
+ return agent, tuple(background_agents)
344
+
345
+
346
+ _MISSING = object()
347
+
348
+
349
+ def _type_name(expected: RequiredAttributeType) -> str:
350
+ types = expected if isinstance(expected, tuple) else (expected,)
351
+ return " or ".join(item.__name__ for item in types)
352
+
353
+
354
+ def _capability_errors(agent: DeployableAgent) -> Iterator[str]:
355
+ """Yield unmet attribute requirements for one configuration node."""
356
+ for capability in agent.capabilities:
357
+ for name, expected in capability.required_attributes.items():
358
+ error = _required_attribute_error(agent, capability, name, expected)
359
+ if error is not None:
360
+ yield error
361
+
362
+
363
+ def _required_attribute_error(
364
+ agent: DeployableAgent,
365
+ capability: AgentCapability,
366
+ name: str,
367
+ expected: RequiredAttributeType,
368
+ ) -> str | None:
369
+ """Describe one unmet requirement, or return None when it is satisfied."""
370
+ value = getattr(agent, name, _MISSING)
371
+ if value is not _MISSING and value is not None and isinstance(value, expected):
372
+ return None
373
+
374
+ owner = f"agent {agent.name!r}, capability {type(capability).__name__}"
375
+ requirement = f"attribute {name!r} must be {_type_name(expected)}"
376
+ if value is _MISSING:
377
+ actual = "it is missing"
378
+ elif value is None:
379
+ actual = "it is None"
380
+ else:
381
+ actual = f"got {type(value).__name__}"
382
+ return f"{owner}: {requirement}; {actual}"
383
+
384
+
385
+ __all__ = [
386
+ "AgentCapability",
387
+ "Capability",
388
+ "DeployableAgent",
389
+ "RequiredAttributeType",
390
+ "RequiredAttributes",
391
+ ]
@@ -1,151 +0,0 @@
1
- """Data-driven agent definitions and runtime-bound capabilities."""
2
-
3
- from collections.abc import Callable, Sequence
4
- from dataclasses import dataclass
5
- from pathlib import Path
6
- from typing import Protocol
7
-
8
- from roboz.agent import Agent, run_subagent
9
- from roboz.llm import EndpointLike
10
- from roboz.runtime import EventPipe, EventSink, Output
11
- from roboz.skill import Skill
12
- from roboz.tooling import Tool
13
- from roboz.tooling.context import Ctx
14
-
15
-
16
- class AgentCapability(Protocol):
17
- """A configured feature that binds its own tools to an agent runtime.
18
-
19
- Store tool-specific endpoints on the capability. The build input supplies
20
- the owning agent's endpoint as a default, independently of its event pipe.
21
- """
22
-
23
- def build(
24
- self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
25
- ) -> "Capability":
26
- """Bind configured tools, using the supplied default for unset endpoints."""
27
- ...
28
-
29
-
30
- @dataclass(frozen=True)
31
- class Capability(AgentCapability):
32
- """Configured tools and skills that provide an agent feature.
33
-
34
- Tools shared between a chain and default execution retain their identity.
35
- Use this directly for already-bound inputs, or return one from a capability
36
- builder for runtime-bound inputs. Supplied objects remain caller-owned.
37
- """
38
-
39
- tools: tuple[Tool | Sequence[Tool], ...] = ()
40
- default_tools: tuple[Tool, ...] = ()
41
- skills: tuple[Skill, ...] = ()
42
- auto_loaded_skills: tuple[Skill, ...] = ()
43
-
44
- def build(
45
- self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
46
- ) -> "Capability":
47
- """Return these already-bound inputs without copying or starting work."""
48
- return self
49
-
50
-
51
- @dataclass(frozen=True)
52
- class SubAgentSpec:
53
- """A specialist definition and its parent-facing tool identity."""
54
-
55
- definition: "AgentDefinition"
56
- tool_name: str
57
- tool_description: str
58
-
59
-
60
- @dataclass(frozen=True, kw_only=True)
61
- class AgentDefinition:
62
- """Configured inputs for independently constructing an agent runtime.
63
-
64
- All tools and skills enter through capabilities. Already-bound inputs and
65
- endpoint dependencies are supplied objects; callers own their reuse. Scripted
66
- mock endpoints that consume responses must be recreated for each run.
67
- Each capability selects its tool endpoints, using the agent endpoint as
68
- the default. Endpoint objects remain independent of runtime controls.
69
- """
70
-
71
- name: str
72
- agent_endpoint: EndpointLike | None
73
- description: str = ""
74
- system_prompt: str = ""
75
- interaction_mode: Output | None = Output.CLI
76
- is_agentic: bool = True
77
- automatic_tool_prompt: bool = True
78
- capabilities: tuple[AgentCapability, ...] = ()
79
- subagents: tuple[SubAgentSpec, ...] = ()
80
- initial_messages: tuple[Path | str, ...] = ()
81
-
82
- def agent_names(self) -> frozenset[str]:
83
- """Return recursive agent identities, rejecting ambiguous names."""
84
- names = {self.name}
85
- for spec in self.subagents:
86
- children = spec.definition.agent_names()
87
- if names & children:
88
- raise ValueError("agent names must be unique")
89
- names.update(children)
90
- return frozenset(names)
91
-
92
- def build(
93
- self,
94
- *,
95
- event_sinks: Sequence[EventSink] = (),
96
- event_sink_factory: Callable[[str], Sequence[EventSink]] | None = None,
97
- ) -> Agent:
98
- """Create fresh pipes, resolve capabilities, and construct the agent once.
99
-
100
- Caller sinks follow specialists. The optional factory supplies fresh
101
- agent-specific sinks by name; these are never inherited by children.
102
- Without supplied sinks, construction selects no output or persistence.
103
- """
104
- self.agent_names()
105
- pipe = EventPipe(
106
- event_sinks=(
107
- *(event_sink_factory(self.name) if event_sink_factory else ()),
108
- *event_sinks,
109
- )
110
- )
111
- contributions = []
112
- for capability in self.capabilities:
113
- contributions.append(
114
- capability.build(pipe, default_endpoint=self.agent_endpoint)
115
- )
116
-
117
- tools = [tool for contribution in contributions for tool in contribution.tools]
118
- for spec in self.subagents:
119
- child = spec.definition.build(
120
- event_sinks=event_sinks, event_sink_factory=event_sink_factory
121
- )
122
- delegation = run_subagent(Ctx(agent=child)).copy(
123
- name=spec.tool_name, description=spec.tool_description
124
- )
125
- tools.append(delegation)
126
-
127
- return Agent(
128
- name=self.name,
129
- description=self.description,
130
- agent_endpoint=self.agent_endpoint,
131
- event_pipe=pipe,
132
- interaction_mode=self.interaction_mode,
133
- is_agentic=self.is_agentic,
134
- automatic_tool_prompt=self.automatic_tool_prompt,
135
- system_prompt=self.system_prompt,
136
- tools=tools,
137
- default_tools=[tool for c in contributions for tool in c.default_tools],
138
- skills=[skill for c in contributions for skill in c.skills],
139
- auto_loaded_skills=[
140
- skill for c in contributions for skill in c.auto_loaded_skills
141
- ],
142
- initial_messages=self.initial_messages,
143
- )
144
-
145
-
146
- __all__ = [
147
- "AgentCapability",
148
- "AgentDefinition",
149
- "Capability",
150
- "SubAgentSpec",
151
- ]
File without changes
File without changes