roboz 0.1.2.dev3__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.
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/CHANGELOG.md +11 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/PKG-INFO +9 -8
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/README.md +8 -7
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/pyproject.toml +1 -1
- roboz-0.1.2.dev4/src/roboz/deployment.py +391 -0
- roboz-0.1.2.dev3/src/roboz/deployment.py +0 -193
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/.gitignore +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/LICENSE +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/_naming.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/_execution_context.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/_identifiers.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/_notifications.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/_prompts.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/_tool_observer.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/background_agent.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/core.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/prompt_agent_tool.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/agent/subagent.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/dependencies.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/exceptions.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/_diagnostics.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/_retry.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/_truncation.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/binding.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/calls.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/completion.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/endpoints.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/openrouter.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/llm/prompts.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/models/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/models/_schema.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/models/_serialization.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/models/_telemetry.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/models/core.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/models/truncation.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/py.typed +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/_environment.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/_external.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/_logging.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/_paths.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/events.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/io.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/observability.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/persistence/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/persistence/activity.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/persistence/schema.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/pipe.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/runtime/sinks.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/skill/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/skill/_prompts.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/skill/core.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tooling/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tooling/_prompts.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tooling/_protocols.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tooling/_typing.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tooling/context.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tooling/core.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tooling/decorators.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tools/__init__.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tools/_identifiers.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tools/control.py +0 -0
- {roboz-0.1.2.dev3 → roboz-0.1.2.dev4}/src/roboz/tools/interaction.py +0 -0
|
@@ -2,6 +2,17 @@
|
|
|
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
|
+
|
|
5
16
|
## 0.1.2.dev3 - 2026-09-11
|
|
6
17
|
|
|
7
18
|
- Breaking: remove the redundant `roboz[shed]` installation extra. Install
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: roboz
|
|
3
|
-
Version: 0.1.2.
|
|
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
|
|
@@ -170,14 +170,15 @@ catalogues; it installs core automatically. See the
|
|
|
170
170
|
|
|
171
171
|
`roboz.deployment` supplies recursive `DeployableAgent` definitions and
|
|
172
172
|
capability contracts. Put definitions directly in `subagents` or
|
|
173
|
-
`background_agents
|
|
174
|
-
|
|
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
|
|
175
176
|
orchestrator and Librarian constructors, concrete capabilities, memory tools,
|
|
176
|
-
and sandbox structure. Import the constructors from `roboshed.agents`;
|
|
177
|
-
RoboSprawl
|
|
178
|
-
|
|
179
|
-
`
|
|
180
|
-
|
|
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.
|
|
181
182
|
|
|
182
183
|
## Documentation
|
|
183
184
|
|
|
@@ -141,14 +141,15 @@ catalogues; it installs core automatically. See the
|
|
|
141
141
|
|
|
142
142
|
`roboz.deployment` supplies recursive `DeployableAgent` definitions and
|
|
143
143
|
capability contracts. Put definitions directly in `subagents` or
|
|
144
|
-
`background_agents
|
|
145
|
-
|
|
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
|
|
146
147
|
orchestrator and Librarian constructors, concrete capabilities, memory tools,
|
|
147
|
-
and sandbox structure. Import the constructors from `roboshed.agents`;
|
|
148
|
-
RoboSprawl
|
|
149
|
-
|
|
150
|
-
`
|
|
151
|
-
|
|
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.
|
|
152
153
|
|
|
153
154
|
## Documentation
|
|
154
155
|
|
|
@@ -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,193 +0,0 @@
|
|
|
1
|
-
"""Data-driven agent definitions and runtime-bound capabilities."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections.abc import Callable, 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
|
-
|
|
18
|
-
class AgentCapability(Protocol):
|
|
19
|
-
"""A configured feature that binds its own tools to an agent runtime.
|
|
20
|
-
|
|
21
|
-
Store tool-specific endpoints on the capability. The build input supplies
|
|
22
|
-
the owning agent's endpoint as a default, independently of its event pipe.
|
|
23
|
-
"""
|
|
24
|
-
|
|
25
|
-
def build(
|
|
26
|
-
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
27
|
-
) -> "Capability":
|
|
28
|
-
"""Bind configured tools, using the supplied default for unset endpoints."""
|
|
29
|
-
...
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@dataclass(frozen=True)
|
|
33
|
-
class Capability(AgentCapability):
|
|
34
|
-
"""Configured tools and skills that provide an agent feature.
|
|
35
|
-
|
|
36
|
-
Tools shared between a chain and default execution retain their identity.
|
|
37
|
-
Use this directly for already-bound inputs, or return one from a capability
|
|
38
|
-
builder for runtime-bound inputs. Supplied objects remain caller-owned.
|
|
39
|
-
"""
|
|
40
|
-
|
|
41
|
-
tools: tuple[Tool | Sequence[Tool], ...] = ()
|
|
42
|
-
default_tools: tuple[Tool, ...] = ()
|
|
43
|
-
skills: tuple[Skill, ...] = ()
|
|
44
|
-
auto_loaded_skills: tuple[Skill, ...] = ()
|
|
45
|
-
|
|
46
|
-
def build(
|
|
47
|
-
self, pipe: EventPipe, *, default_endpoint: EndpointLike | None
|
|
48
|
-
) -> "Capability":
|
|
49
|
-
"""Return these already-bound inputs without copying or starting work."""
|
|
50
|
-
return self
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@dataclass(frozen=True, kw_only=True)
|
|
54
|
-
class DeployableAgent:
|
|
55
|
-
"""One agent's configuration, including its sub-agents and background agents.
|
|
56
|
-
|
|
57
|
-
All tools and skills enter through capabilities. Already-bound inputs and
|
|
58
|
-
endpoint dependencies are supplied objects; callers own their reuse. Scripted
|
|
59
|
-
mock endpoints that consume responses must be recreated for each run.
|
|
60
|
-
Configure definitions before building; do not mutate them concurrently with
|
|
61
|
-
construction. Each capability selects its tool endpoints, using the agent
|
|
62
|
-
endpoint as the default. Endpoint objects remain independent of runtime controls.
|
|
63
|
-
Sub-agents become named delegation tools. Background agents are started
|
|
64
|
-
through default tools when their parent runs. Both slots contain the same
|
|
65
|
-
recursive definition type.
|
|
66
|
-
"""
|
|
67
|
-
|
|
68
|
-
name: str
|
|
69
|
-
agent_endpoint: EndpointLike | None
|
|
70
|
-
description: str = ""
|
|
71
|
-
system_prompt: str = ""
|
|
72
|
-
interaction_mode: Output | None = Output.CLI
|
|
73
|
-
is_agentic: bool = True
|
|
74
|
-
automatic_tool_prompt: bool = True
|
|
75
|
-
capabilities: tuple[AgentCapability, ...] = ()
|
|
76
|
-
subagents: tuple["DeployableAgent", ...] = ()
|
|
77
|
-
background_agents: tuple["DeployableAgent", ...] = ()
|
|
78
|
-
initial_messages: tuple[Path | str, ...] = ()
|
|
79
|
-
|
|
80
|
-
def agent_names(self, *, include_background: bool = True) -> frozenset[str]:
|
|
81
|
-
"""Validate all names and return identities in the selected branches.
|
|
82
|
-
|
|
83
|
-
Excluding background agents also excludes their descendants, for
|
|
84
|
-
foreground-only consumers such as conversation maintenance.
|
|
85
|
-
"""
|
|
86
|
-
if not include_background:
|
|
87
|
-
self.agent_names()
|
|
88
|
-
names = {self.name}
|
|
89
|
-
definitions = self.subagents
|
|
90
|
-
if include_background:
|
|
91
|
-
definitions += self.background_agents
|
|
92
|
-
for definition in definitions:
|
|
93
|
-
children = definition.agent_names(include_background=include_background)
|
|
94
|
-
if names & children:
|
|
95
|
-
raise ValueError("agent names must be unique")
|
|
96
|
-
names.update(children)
|
|
97
|
-
return frozenset(names)
|
|
98
|
-
|
|
99
|
-
def build(
|
|
100
|
-
self,
|
|
101
|
-
*,
|
|
102
|
-
event_sinks: Sequence[EventSink] = (),
|
|
103
|
-
event_sink_factory: Callable[[str], Sequence[EventSink]] | None = None,
|
|
104
|
-
) -> Agent:
|
|
105
|
-
"""Create fresh pipes, resolve capabilities, and construct the agent once.
|
|
106
|
-
|
|
107
|
-
Caller sinks follow synchronous children. The optional factory supplies fresh
|
|
108
|
-
agent-specific sinks by name; these are never inherited by children.
|
|
109
|
-
Without supplied sinks, construction selects no output or persistence.
|
|
110
|
-
Use build_graph() to retain background agents for host control.
|
|
111
|
-
"""
|
|
112
|
-
return self.build_graph(
|
|
113
|
-
event_sinks=event_sinks, event_sink_factory=event_sink_factory
|
|
114
|
-
)[0]
|
|
115
|
-
|
|
116
|
-
def build_graph(
|
|
117
|
-
self,
|
|
118
|
-
*,
|
|
119
|
-
event_sinks: Sequence[EventSink] = (),
|
|
120
|
-
event_sink_factory: Callable[[str], Sequence[EventSink]] | None = None,
|
|
121
|
-
) -> tuple[Agent, tuple[Agent, ...]]:
|
|
122
|
-
"""Build fresh agents and return the root and all background handles.
|
|
123
|
-
|
|
124
|
-
Validate names before constructing capabilities or sinks. Sink routing
|
|
125
|
-
follows build(): caller sinks reach foreground branches only, and the
|
|
126
|
-
factory supplies each agent's own sinks. Background handles include
|
|
127
|
-
descendants of both child slots. No agents are started by construction;
|
|
128
|
-
callers own their invocation and shutdown.
|
|
129
|
-
"""
|
|
130
|
-
self.agent_names()
|
|
131
|
-
pipe = EventPipe(
|
|
132
|
-
event_sinks=(
|
|
133
|
-
*(event_sink_factory(self.name) if event_sink_factory else ()),
|
|
134
|
-
*event_sinks,
|
|
135
|
-
)
|
|
136
|
-
)
|
|
137
|
-
contributions = []
|
|
138
|
-
for capability in self.capabilities:
|
|
139
|
-
contributions.append(
|
|
140
|
-
capability.build(pipe, default_endpoint=self.agent_endpoint)
|
|
141
|
-
)
|
|
142
|
-
|
|
143
|
-
tools = [tool for contribution in contributions for tool in contribution.tools]
|
|
144
|
-
default_tools = [tool for c in contributions for tool in c.default_tools]
|
|
145
|
-
background_agents: list[Agent] = []
|
|
146
|
-
for definition in self.subagents:
|
|
147
|
-
child, descendants = definition.build_graph(
|
|
148
|
-
event_sinks=event_sinks, event_sink_factory=event_sink_factory
|
|
149
|
-
)
|
|
150
|
-
tools.append(
|
|
151
|
-
run_subagent(Ctx(agent=child)).copy(
|
|
152
|
-
name=child.name,
|
|
153
|
-
description=child.description,
|
|
154
|
-
)
|
|
155
|
-
)
|
|
156
|
-
background_agents.extend(descendants)
|
|
157
|
-
for definition in self.background_agents:
|
|
158
|
-
child, descendants = definition.build_graph(
|
|
159
|
-
event_sink_factory=event_sink_factory
|
|
160
|
-
)
|
|
161
|
-
default_tools.append(
|
|
162
|
-
run_background_agent(Ctx(agent=child)).copy(
|
|
163
|
-
name=f"start_background_agent_{child.name}",
|
|
164
|
-
)
|
|
165
|
-
)
|
|
166
|
-
background_agents.append(child)
|
|
167
|
-
background_agents.extend(descendants)
|
|
168
|
-
|
|
169
|
-
agent = Agent(
|
|
170
|
-
name=self.name,
|
|
171
|
-
description=self.description,
|
|
172
|
-
agent_endpoint=self.agent_endpoint,
|
|
173
|
-
event_pipe=pipe,
|
|
174
|
-
interaction_mode=self.interaction_mode,
|
|
175
|
-
is_agentic=self.is_agentic,
|
|
176
|
-
automatic_tool_prompt=self.automatic_tool_prompt,
|
|
177
|
-
system_prompt=self.system_prompt,
|
|
178
|
-
tools=tools,
|
|
179
|
-
default_tools=default_tools,
|
|
180
|
-
skills=[skill for c in contributions for skill in c.skills],
|
|
181
|
-
auto_loaded_skills=[
|
|
182
|
-
skill for c in contributions for skill in c.auto_loaded_skills
|
|
183
|
-
],
|
|
184
|
-
initial_messages=self.initial_messages,
|
|
185
|
-
)
|
|
186
|
-
return agent, tuple(background_agents)
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
__all__ = [
|
|
190
|
-
"AgentCapability",
|
|
191
|
-
"Capability",
|
|
192
|
-
"DeployableAgent",
|
|
193
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|