roboz 0.1.2.dev9__tar.gz → 0.1.2.dev11__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 (72) hide show
  1. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/CHANGELOG.md +12 -0
  2. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/PKG-INFO +18 -13
  3. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/README.md +17 -12
  4. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/pyproject.toml +1 -1
  5. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/core.py +16 -4
  6. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/deployment.py +6 -2
  7. roboz-0.1.2.dev11/src/roboz/examples/__init__.py +1 -0
  8. roboz-0.1.2.dev11/src/roboz/examples/complex.py +72 -0
  9. roboz-0.1.2.dev11/src/roboz/examples/simple.py +36 -0
  10. roboz-0.1.2.dev11/src/roboz/examples/simpsons_quotes.py +44 -0
  11. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/.gitignore +0 -0
  12. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/LICENSE +0 -0
  13. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/__init__.py +0 -0
  14. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/__init__.pyi +0 -0
  15. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/_naming.py +0 -0
  16. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/__init__.py +0 -0
  17. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/_execution_context.py +0 -0
  18. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/_identifiers.py +0 -0
  19. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/_notifications.py +0 -0
  20. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/_prompts.py +0 -0
  21. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/_tool_observer.py +0 -0
  22. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/background_agent.py +0 -0
  23. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/prompt_agent_tool.py +0 -0
  24. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/agent/subagent.py +0 -0
  25. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/dependencies.py +0 -0
  26. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/exceptions.py +0 -0
  27. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/__init__.py +0 -0
  28. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/_diagnostics.py +0 -0
  29. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/_retry.py +0 -0
  30. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/_truncation.py +0 -0
  31. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/binding.py +0 -0
  32. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/calls.py +0 -0
  33. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/completion.py +0 -0
  34. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/endpoints.py +0 -0
  35. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/openai_compatible.py +0 -0
  36. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/openrouter.py +0 -0
  37. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/llm/prompts.py +0 -0
  38. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/models/__init__.py +0 -0
  39. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/models/_schema.py +0 -0
  40. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/models/_serialization.py +0 -0
  41. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/models/_telemetry.py +0 -0
  42. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/models/core.py +0 -0
  43. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/models/messages.py +0 -0
  44. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/models/truncation.py +0 -0
  45. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/py.typed +0 -0
  46. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/__init__.py +0 -0
  47. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/_environment.py +0 -0
  48. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/_external.py +0 -0
  49. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/_logging.py +0 -0
  50. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/_paths.py +0 -0
  51. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/events.py +0 -0
  52. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/io.py +0 -0
  53. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/observability.py +0 -0
  54. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/persistence/__init__.py +0 -0
  55. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/persistence/activity.py +0 -0
  56. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/persistence/schema.py +0 -0
  57. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/pipe.py +0 -0
  58. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/runtime/sinks.py +0 -0
  59. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/skill/__init__.py +0 -0
  60. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/skill/_prompts.py +0 -0
  61. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/skill/core.py +0 -0
  62. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tooling/__init__.py +0 -0
  63. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tooling/_prompts.py +0 -0
  64. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tooling/_protocols.py +0 -0
  65. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tooling/_typing.py +0 -0
  66. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tooling/context.py +0 -0
  67. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tooling/core.py +0 -0
  68. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tooling/decorators.py +0 -0
  69. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tools/__init__.py +0 -0
  70. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tools/_identifiers.py +0 -0
  71. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tools/control.py +0 -0
  72. {roboz-0.1.2.dev9 → roboz-0.1.2.dev11}/src/roboz/tools/interaction.py +0 -0
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.2.dev11 - 2026-09-21
6
+
7
+ - Ship the simple and complex examples in `roboz.examples`, so installed users
8
+ can run the credential-free agent demo with `python -m roboz.examples.simple`
9
+ without cloning the repository.
10
+
11
+ ## 0.1.2.dev10 - 2026-09-21
12
+
13
+ - Define default tools as scheduler-owned chain roots: downstream tools can now
14
+ reference a default-only parent with `chained_to`, while defaults that are
15
+ themselves chained downstream are rejected during agent construction.
16
+
5
17
  ## 0.1.2.dev9 - 2026-09-20
6
18
 
7
19
  - Remove the leftover `proton-bridge-beta` installation extra. Install
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: roboz
3
- Version: 0.1.2.dev9
3
+ Version: 0.1.2.dev11
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
@@ -69,9 +69,8 @@ Chaining not only reduces the llm calls, but it also provides a useful way of in
69
69
  ```python
70
70
  from random import choice
71
71
 
72
- from simpsons_quotes import QUOTES
73
-
74
72
  from roboz import Agent, tool
73
+ from roboz.examples.simpsons_quotes import QUOTES
75
74
  from roboz.llm.endpoints import MockLLMEndpoint
76
75
  from roboz.models import Empty, Message, Stop
77
76
 
@@ -97,12 +96,12 @@ output, messages_ = agent.invoke()
97
96
  print(f'"{output.value}"')
98
97
  ```
99
98
 
100
- The above [simple example](https://github.com/Tachion-Oy/roboz/blob/main/examples/simple.py) uses the accompanying
101
- [quote file](https://github.com/Tachion-Oy/roboz/blob/main/examples/simpsons_quotes.py) in this repository. Run it from the
102
- repository root with
99
+ The above [simple example](https://github.com/Tachion-Oy/roboz/blob/main/src/roboz/examples/simple.py) uses the accompanying
100
+ [quote file](https://github.com/Tachion-Oy/roboz/blob/main/src/roboz/examples/simpsons_quotes.py). First add RoboZ to your
101
+ environment as shown in [Try it out](#try-it-out), then run it with
103
102
 
104
103
  ```bash
105
- uv run python examples/simple.py
104
+ uv run python -m roboz.examples.simple
106
105
  ```
107
106
  It creates an agent that returns a random
108
107
  Simpsons quote. **The docstring in the tool is the instruction that the agent sees**. It uses a mock endpoint, with pre-determined replies, so you can run it without API keys.
@@ -122,7 +121,7 @@ An agent is a loop that calls tools. Everything is defined as a tool: Skills, b
122
121
  **A tool can be triggered in three ways:**
123
122
  - **Invoked by an agent**: The `prompt_agent` tool asks an LLM what to do next and its `Invoke` output always calls another tool. It is constructed internally for `AgentMode.STEERABLE` and `AgentMode.AUTONOMOUS` agents, but it is still just a tool.
124
123
  - **By chaining**. After an invoked tool has fired RoboZ checks if a chained tool with a *true* chain condition exists (for more than one *true* condition for a fork you get a runtime error). If yes, the output is passed on and the process repeats until the first broken chain or all chained tools are exhausted
125
- - **As default tools**. Once the tool chain is exhausted or the chain breaks due to a *false* condition the loop returns to the freely defined `default_tool` (there actually can be many, they are all called in sequence).
124
+ - **As default tools**. Defaults are ordered chain roots owned by the scheduler. When no chained successor exists, they run in configuration order; a default may feed downstream tools, and the sequence resumes with the next default after that branch ends. A default cannot itself be downstream, so it cannot declare `chained_to`.
126
125
 
127
126
  RoboZ then collapses to the traditional agentic approach as a special case if one just has the `prompt_agent` as the default with no chaining. An `AgentMode.DETERMINISTIC` agent has no `prompt_agent`; its default tools perform tasks directly, allowing deterministic branching through chaining. This is useful for a background agent that performs periodic maintenance work. `AgentMode.STEERABLE` agents may ask the user for input, while `AgentMode.AUTONOMOUS` agents cannot.
128
127
 
@@ -136,7 +135,7 @@ The contract in RoboZ is that every action in the agentic loop is a tool call an
136
135
  ### LLM Endpoints are instances
137
136
  As a fundamental design rule in Roboz, everything that depends on an LLM call must be trivially swappable to another provider or model. This makes changing an agent endpoint trivial and furthermore multi-endpoint functionality, where inside a single agent several endpoints are implemented, quite easy.
138
137
 
139
- To see the above in practice see the [complex example](https://github.com/Tachion-Oy/roboz/blob/main/examples/complex.py) example below.
138
+ To see the above in practice see the [complex example](https://github.com/Tachion-Oy/roboz/blob/main/src/roboz/examples/complex.py) below.
140
139
 
141
140
 
142
141
 
@@ -145,7 +144,7 @@ To see the above in practice see the [complex example](https://github.com/Tachio
145
144
 
146
145
  In the code example below we illustrate some of the features that make RoboZ different from other frameworks.
147
146
 
148
- **Tool chaining** is usually introduced via the decorator argument `chained_to` which indicates the name of the tool whose output is passed as the input (the tools also possess a `.chain` method). The input/output contract must be Liskov compatible, i.e the upstream output must be a subclass of the downstream input. A possible `chain_condition` can be passed in, which by definition has access to the tool's input argument and returns a boolean. The chain condition must evaluate to at most one true condition, but it can evaluate to `false` on all links, in which case you return to the default tool(s). `AgentMode.STEERABLE` and `AgentMode.AUTONOMOUS` construct a `prompt_agent` tool backed by the agent endpoint; `AgentMode.DETERMINISTIC` uses the configured default tools.
147
+ **Tool chaining** is usually introduced via the decorator argument `chained_to`, which points from a downstream tool to the upstream tool whose output becomes its input (tools also possess a `.chain` method). The input/output contract must be Liskov compatible, i.e. the upstream output must be a subclass of the downstream input. A possible `chain_condition` can be passed in, which by definition has access to the tool's input argument and returns a boolean. The chain condition must evaluate to at most one true condition, but it can evaluate to `false` on all links, in which case scheduling resumes with the next configured default. Defaults may be referenced as upstream parents without also appearing in `tools`, but cannot declare `chained_to` themselves. `AgentMode.STEERABLE` and `AgentMode.AUTONOMOUS` construct a `prompt_agent` tool backed by the agent endpoint; `AgentMode.DETERMINISTIC` uses the configured default tools.
149
148
 
150
149
  The `escalate` is an example of a **tool factory**, which is a simple concept. It accepts a context parameter which is added to the tool's closure and calling the factory with a context argument returns a tool. A very common use case is a tool with an endpoint as a context. In RoboZ all llm **endpoints are instances**, so it is easy to have a specific endpoint for a tool, that is different from that of the agent, below we construct deterministic mock endpoints so that no API keys are required for the examples. Factories have precisely the same chaining arguments in their decorator as a tool.
151
150
 
@@ -226,23 +225,29 @@ agent.invoke()
226
225
 
227
226
  ```
228
227
 
229
- The above [complex example](https://github.com/Tachion-Oy/roboz/blob/main/examples/complex.py) can be run from the root with
228
+ The above [complex example](https://github.com/Tachion-Oy/roboz/blob/main/src/roboz/examples/complex.py) is also bundled with
229
+ RoboZ. First add the library to your environment as shown in
230
+ [Try it out](#try-it-out), then run it with
230
231
 
231
232
  ```bash
232
- uv run python examples/complex.py
233
+ uv run python -m roboz.examples.complex
233
234
  ```
234
235
 
235
236
 
236
237
  ## Try it out
237
238
 
239
+ Add RoboZ to a uv project and run the same bundled example:
240
+
238
241
  ```bash
239
242
  uv add roboz
243
+ uv run python -m roboz.examples.simple
240
244
  ```
241
245
 
242
- Or with pip:
246
+ or with pip, install RoboZ into the active environment first:
243
247
 
244
248
  ```bash
245
249
  python -m pip install roboz
250
+ python -m roboz.examples.simple
246
251
  ```
247
252
 
248
253
  ## Packages
@@ -42,9 +42,8 @@ Chaining not only reduces the llm calls, but it also provides a useful way of in
42
42
  ```python
43
43
  from random import choice
44
44
 
45
- from simpsons_quotes import QUOTES
46
-
47
45
  from roboz import Agent, tool
46
+ from roboz.examples.simpsons_quotes import QUOTES
48
47
  from roboz.llm.endpoints import MockLLMEndpoint
49
48
  from roboz.models import Empty, Message, Stop
50
49
 
@@ -70,12 +69,12 @@ output, messages_ = agent.invoke()
70
69
  print(f'"{output.value}"')
71
70
  ```
72
71
 
73
- The above [simple example](https://github.com/Tachion-Oy/roboz/blob/main/examples/simple.py) uses the accompanying
74
- [quote file](https://github.com/Tachion-Oy/roboz/blob/main/examples/simpsons_quotes.py) in this repository. Run it from the
75
- repository root with
72
+ The above [simple example](https://github.com/Tachion-Oy/roboz/blob/main/src/roboz/examples/simple.py) uses the accompanying
73
+ [quote file](https://github.com/Tachion-Oy/roboz/blob/main/src/roboz/examples/simpsons_quotes.py). First add RoboZ to your
74
+ environment as shown in [Try it out](#try-it-out), then run it with
76
75
 
77
76
  ```bash
78
- uv run python examples/simple.py
77
+ uv run python -m roboz.examples.simple
79
78
  ```
80
79
  It creates an agent that returns a random
81
80
  Simpsons quote. **The docstring in the tool is the instruction that the agent sees**. It uses a mock endpoint, with pre-determined replies, so you can run it without API keys.
@@ -95,7 +94,7 @@ An agent is a loop that calls tools. Everything is defined as a tool: Skills, b
95
94
  **A tool can be triggered in three ways:**
96
95
  - **Invoked by an agent**: The `prompt_agent` tool asks an LLM what to do next and its `Invoke` output always calls another tool. It is constructed internally for `AgentMode.STEERABLE` and `AgentMode.AUTONOMOUS` agents, but it is still just a tool.
97
96
  - **By chaining**. After an invoked tool has fired RoboZ checks if a chained tool with a *true* chain condition exists (for more than one *true* condition for a fork you get a runtime error). If yes, the output is passed on and the process repeats until the first broken chain or all chained tools are exhausted
98
- - **As default tools**. Once the tool chain is exhausted or the chain breaks due to a *false* condition the loop returns to the freely defined `default_tool` (there actually can be many, they are all called in sequence).
97
+ - **As default tools**. Defaults are ordered chain roots owned by the scheduler. When no chained successor exists, they run in configuration order; a default may feed downstream tools, and the sequence resumes with the next default after that branch ends. A default cannot itself be downstream, so it cannot declare `chained_to`.
99
98
 
100
99
  RoboZ then collapses to the traditional agentic approach as a special case if one just has the `prompt_agent` as the default with no chaining. An `AgentMode.DETERMINISTIC` agent has no `prompt_agent`; its default tools perform tasks directly, allowing deterministic branching through chaining. This is useful for a background agent that performs periodic maintenance work. `AgentMode.STEERABLE` agents may ask the user for input, while `AgentMode.AUTONOMOUS` agents cannot.
101
100
 
@@ -109,7 +108,7 @@ The contract in RoboZ is that every action in the agentic loop is a tool call an
109
108
  ### LLM Endpoints are instances
110
109
  As a fundamental design rule in Roboz, everything that depends on an LLM call must be trivially swappable to another provider or model. This makes changing an agent endpoint trivial and furthermore multi-endpoint functionality, where inside a single agent several endpoints are implemented, quite easy.
111
110
 
112
- To see the above in practice see the [complex example](https://github.com/Tachion-Oy/roboz/blob/main/examples/complex.py) example below.
111
+ To see the above in practice see the [complex example](https://github.com/Tachion-Oy/roboz/blob/main/src/roboz/examples/complex.py) below.
113
112
 
114
113
 
115
114
 
@@ -118,7 +117,7 @@ To see the above in practice see the [complex example](https://github.com/Tachio
118
117
 
119
118
  In the code example below we illustrate some of the features that make RoboZ different from other frameworks.
120
119
 
121
- **Tool chaining** is usually introduced via the decorator argument `chained_to` which indicates the name of the tool whose output is passed as the input (the tools also possess a `.chain` method). The input/output contract must be Liskov compatible, i.e the upstream output must be a subclass of the downstream input. A possible `chain_condition` can be passed in, which by definition has access to the tool's input argument and returns a boolean. The chain condition must evaluate to at most one true condition, but it can evaluate to `false` on all links, in which case you return to the default tool(s). `AgentMode.STEERABLE` and `AgentMode.AUTONOMOUS` construct a `prompt_agent` tool backed by the agent endpoint; `AgentMode.DETERMINISTIC` uses the configured default tools.
120
+ **Tool chaining** is usually introduced via the decorator argument `chained_to`, which points from a downstream tool to the upstream tool whose output becomes its input (tools also possess a `.chain` method). The input/output contract must be Liskov compatible, i.e. the upstream output must be a subclass of the downstream input. A possible `chain_condition` can be passed in, which by definition has access to the tool's input argument and returns a boolean. The chain condition must evaluate to at most one true condition, but it can evaluate to `false` on all links, in which case scheduling resumes with the next configured default. Defaults may be referenced as upstream parents without also appearing in `tools`, but cannot declare `chained_to` themselves. `AgentMode.STEERABLE` and `AgentMode.AUTONOMOUS` construct a `prompt_agent` tool backed by the agent endpoint; `AgentMode.DETERMINISTIC` uses the configured default tools.
122
121
 
123
122
  The `escalate` is an example of a **tool factory**, which is a simple concept. It accepts a context parameter which is added to the tool's closure and calling the factory with a context argument returns a tool. A very common use case is a tool with an endpoint as a context. In RoboZ all llm **endpoints are instances**, so it is easy to have a specific endpoint for a tool, that is different from that of the agent, below we construct deterministic mock endpoints so that no API keys are required for the examples. Factories have precisely the same chaining arguments in their decorator as a tool.
124
123
 
@@ -199,23 +198,29 @@ agent.invoke()
199
198
 
200
199
  ```
201
200
 
202
- The above [complex example](https://github.com/Tachion-Oy/roboz/blob/main/examples/complex.py) can be run from the root with
201
+ The above [complex example](https://github.com/Tachion-Oy/roboz/blob/main/src/roboz/examples/complex.py) is also bundled with
202
+ RoboZ. First add the library to your environment as shown in
203
+ [Try it out](#try-it-out), then run it with
203
204
 
204
205
  ```bash
205
- uv run python examples/complex.py
206
+ uv run python -m roboz.examples.complex
206
207
  ```
207
208
 
208
209
 
209
210
  ## Try it out
210
211
 
212
+ Add RoboZ to a uv project and run the same bundled example:
213
+
211
214
  ```bash
212
215
  uv add roboz
216
+ uv run python -m roboz.examples.simple
213
217
  ```
214
218
 
215
- Or with pip:
219
+ or with pip, install RoboZ into the active environment first:
216
220
 
217
221
  ```bash
218
222
  python -m pip install roboz
223
+ python -m roboz.examples.simple
219
224
  ```
220
225
 
221
226
  ## Packages
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "roboz"
3
- version = "0.1.2.dev9"
3
+ version = "0.1.2.dev11"
4
4
  description = "Typed primitives for composable agentic workflows"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Tachion Oy" }]
@@ -83,6 +83,14 @@ class Agent(HasExternalDependencies):
83
83
  list of callbacks attached to a newly created pipe. Hosts compose CLI,
84
84
  run-scoped, process-scoped, or agent-specific callbacks before creating the
85
85
  agent; Agent does not construct or distinguish those scopes.
86
+
87
+ ``default_tools`` are scheduler-owned chain roots. The scheduler runs them in
88
+ configuration order whenever the current tool has no chained successor, then
89
+ repeats that sequence in the next cycle. A downstream tool may declare
90
+ ``chained_to=default_tool``; when that branch ends, scheduling resumes with the
91
+ next configured default. A default tool cannot itself declare ``chained_to``
92
+ because defaults cannot be downstream tools. Default-only tools remain hidden
93
+ from the model-facing active and passive registries.
86
94
  """
87
95
 
88
96
  def __init__(
@@ -280,23 +288,27 @@ class Agent(HasExternalDependencies):
280
288
 
281
289
  def _init_tools(self, *, tools: Sequence[Tool], skill: Skill | None):
282
290
  for t in self.default_tools:
291
+ if t.chained_to:
292
+ raise ValueError(
293
+ f"Default tool '{t.name}' cannot declare chained_to"
294
+ )
283
295
  if any(
284
296
  issubclass(output_type, Invoke)
285
297
  for output_type in get_constituent_types(t.OutputModel)
286
298
  ):
287
299
  raise ValueError(f"Default tool '{t.name}' cannot return Invoke")
288
300
 
289
- all_tools = set(tools)
301
+ registered_tools = set(tools)
290
302
  if skill:
291
- all_tools |= set(skill.tools)
303
+ registered_tools |= set(skill.tools)
292
304
 
293
- for t in all_tools:
305
+ for t in registered_tools:
294
306
  if t.chained_to:
295
307
  self.passive_tools[t.id] = t
296
308
  continue
297
309
  self.active_tools[t.id] = t
298
310
  self._init_prompt_agent_tool()
299
- self._validate_tool_chains(all_tools)
311
+ self._validate_tool_chains(registered_tools | set(self.default_tools))
300
312
 
301
313
  def external_dependencies(
302
314
  self,
@@ -41,8 +41,12 @@ class AgentCapability(Protocol):
41
41
  class Capability(AgentCapability):
42
42
  """Already-bound tools and skills that provide an agent feature.
43
43
 
44
- Tools shared between a chain and default execution retain their identity.
45
- Supplied objects remain caller-owned and require no owner configuration.
44
+ ``default_tools`` are ordered, repeatedly scheduled chain roots. A tool in
45
+ ``tools`` may declare ``chained_to`` with one of those defaults as its parent,
46
+ and scheduling resumes with the next default when the downstream branch ends.
47
+ Defaults cannot themselves declare ``chained_to``. Tools shared between
48
+ ``tools`` and ``default_tools`` retain their identity. Supplied objects remain
49
+ caller-owned and require no owner configuration.
46
50
  """
47
51
 
48
52
  tools: tuple[Tool | Sequence[Tool], ...] = ()
@@ -0,0 +1 @@
1
+ """Runnable examples shipped with Roboz."""
@@ -0,0 +1,72 @@
1
+ """Run an interactive example of chains, factories, and truncation."""
2
+
3
+ from builtins import input as read_input
4
+
5
+ from roboz import Agent, factory, tool
6
+ from roboz.llm import EndpointLike, MockLLMEndpoint, get_completion
7
+ from roboz.models import Empty, Int, Message, Role, Stop, Str, filter_messages
8
+ from roboz.models.truncation import Severity, Truncation
9
+ from roboz.runtime.io import interact_with_user
10
+ from roboz.runtime.sinks import CliSink
11
+ from roboz.tools import stop
12
+
13
+
14
+ @tool
15
+ def ask_number(input: Empty, messages: list[Message]) -> Int:
16
+ """Ask the user for an integer, repeating until the response is valid."""
17
+ while True:
18
+ reply = read_input("Enter an integer: ")
19
+ try:
20
+ return Int(value=int(reply))
21
+ except ValueError:
22
+ print("Please enter an integer :)")
23
+
24
+
25
+ @factory(chained_to=ask_number, chain_condition=lambda x: x.value % 2 == 0)
26
+ def escalate(input: Int, messages: list[Message], ctx: EndpointLike) -> Stop | Str:
27
+ """An even number?! Need to check this with HR!"""
28
+ interact_with_user("Careful now, that is pretty spicy!", with_reply=False)
29
+ prompt = f"The user chose {input.value}. Is this too hot to handle?! (y/n)?"
30
+ verdict = get_completion(
31
+ endpoint=ctx, messages=[Message(role=Role.SYSTEM, content=prompt)]
32
+ )
33
+ is_first_escalation = (
34
+ len(filter_messages(caller="escalate", messages=messages)) == 0
35
+ )
36
+ if verdict == "y" and not is_first_escalation:
37
+ return Stop(value="Too much spiciness, need to quit!")
38
+ return Str(
39
+ value="HR gave a pass, but still, let's show this to the agent only once.",
40
+ truncation=Truncation(threshold=1, severity=Severity.REMOVE),
41
+ )
42
+
43
+
44
+ @tool(chained_to=ask_number, chain_condition=lambda x: x.value % 2 != 0)
45
+ def give_praise(input: Int, messages: list[Message]) -> Str:
46
+ """We need to give praise for such an erudite approach to the problem."""
47
+ interact_with_user(f"{input.value} a fine and bold choice!", with_reply=False)
48
+ return Str(value=f"{input.value} is good, no biggie.")
49
+
50
+
51
+ def main() -> None:
52
+ """Build and run the interactive example agent."""
53
+ agent_endpoint = MockLLMEndpoint(
54
+ responses=[
55
+ *(10 * [{"action": "ask_number", "rationale": "This is my only job"}]),
56
+ {"action": "stop", "rationale": "Enough numbers!", "value": ""},
57
+ ]
58
+ )
59
+ guard_endpoint = MockLLMEndpoint(responses=10 * ["y"])
60
+
61
+ agent = Agent(
62
+ name="demo",
63
+ system_prompt=f"Without exception, use the {ask_number.name} tool.",
64
+ event_sinks=[CliSink.default()],
65
+ agent_endpoint=agent_endpoint,
66
+ tools=[ask_number, escalate(guard_endpoint), give_praise, stop],
67
+ )
68
+ agent.invoke()
69
+
70
+
71
+ if __name__ == "__main__":
72
+ main()
@@ -0,0 +1,36 @@
1
+ """Run a credential-free agent that returns a random Simpsons quote."""
2
+
3
+ from random import choice
4
+
5
+ from roboz import Agent, tool
6
+ from roboz.llm.endpoints import MockLLMEndpoint
7
+ from roboz.models import Empty, Message, Stop
8
+
9
+ from .simpsons_quotes import QUOTES
10
+
11
+
12
+ @tool
13
+ def get_quote(input: Empty, messages: list[Message]) -> Stop:
14
+ """Return a random Simpsons quote and then stop."""
15
+ return Stop(value=choice(QUOTES))
16
+
17
+
18
+ def main() -> None:
19
+ """Build and run the credential-free example agent."""
20
+ mock = MockLLMEndpoint(
21
+ responses=[{"action": "get_quote", "rationale": "Need Simpsons quote!"}]
22
+ )
23
+
24
+ agent = Agent(
25
+ name="demo",
26
+ system_prompt="You are a Simpsons quote generator",
27
+ agent_endpoint=mock,
28
+ tools=[get_quote],
29
+ )
30
+
31
+ output, messages_ = agent.invoke()
32
+ print(f'"{output.value}"')
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
@@ -0,0 +1,44 @@
1
+ """Provide short Simpsons quotes collected from Reddit discussions.
2
+
3
+ Sources are fan recollections; punctuation and capitalization are normalized.
4
+ """
5
+
6
+ from typing import Final
7
+
8
+ QUOTES: Final[list[str]] = [
9
+ "Little bit of column A, little bit of column B.",
10
+ "At first I was just full of vinegar!",
11
+ "Haha, nobody ever says Italy.",
12
+ "Some people say evasion, I say avoision.",
13
+ "Trying is the first step toward failure.",
14
+ "Kids, you tried your best and you failed miserably. The lesson is, never try.",
15
+ 'Maybe for once, someone will call me "sir" without adding, "You\'re making a scene."',
16
+ "Please refrain from tasting the knob!",
17
+ "Money can be exchanged for goods and services.",
18
+ "Whoa, whoa, slow down, egghead!",
19
+ "It's my first day.",
20
+ "It takes two to lie. One to lie and one to listen.",
21
+ "It's a boy, and what a boy!",
22
+ "I told you, my baby beat me up!",
23
+ "We've tried nothing and we're all out of ideas.",
24
+ "You don't get rich by writing a lot of checks.",
25
+ "Good evening, sir. Would you please leave without a fuss right now?",
26
+ "Save me Jebus!",
27
+ "To alcohol, the cause of, and solution to, all of life's problems.",
28
+ "Purple's a fruit.",
29
+ "That's a paddlin'.",
30
+ "Bake 'em away, toys!",
31
+ "Meh. M-E-H. Meh.",
32
+ "My boy's a box! Damn you! A box!",
33
+ "Care to join me in a belt of scotch?",
34
+ "I'm all hopped up on goofballs.",
35
+ "D'oh!",
36
+ "Yoink!",
37
+ "Ha-ha!",
38
+ "Smell ya later!",
39
+ "Now with vitamin R!",
40
+ "Me fail English? Unpossible!",
41
+ "The goggles! They do nothing!",
42
+ "If you don't like your job, you don't go on strike; you go in every day and do it really half assed",
43
+ "I don't want to look like a weirdo, I'll just wear a muumuu.",
44
+ ]
File without changes
File without changes