roboz 0.1.2.dev6__tar.gz → 0.1.2.dev7__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.dev6 → roboz-0.1.2.dev7}/CHANGELOG.md +28 -0
- roboz-0.1.2.dev7/PKG-INFO +287 -0
- roboz-0.1.2.dev7/README.md +258 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/pyproject.toml +1 -1
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/__init__.py +2 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/_prompts.py +22 -3
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/core.py +112 -75
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/deployment.py +17 -19
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/exceptions.py +5 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/completion.py +89 -10
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/endpoints.py +5 -5
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/models/__init__.py +4 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/models/core.py +24 -0
- roboz-0.1.2.dev7/src/roboz/models/messages.py +53 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/io.py +22 -12
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tooling/_prompts.py +1 -1
- roboz-0.1.2.dev6/PKG-INFO +0 -270
- roboz-0.1.2.dev6/README.md +0 -241
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/.gitignore +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/LICENSE +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/__init__.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/__init__.pyi +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/_naming.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/_execution_context.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/_identifiers.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/_notifications.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/_tool_observer.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/background_agent.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/prompt_agent_tool.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/agent/subagent.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/dependencies.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/__init__.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/_diagnostics.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/_retry.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/_truncation.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/binding.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/calls.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/openai_compatible.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/openrouter.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/llm/prompts.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/models/_schema.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/models/_serialization.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/models/_telemetry.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/models/truncation.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/py.typed +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/__init__.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/_environment.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/_external.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/_logging.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/_paths.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/events.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/observability.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/persistence/__init__.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/persistence/activity.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/persistence/schema.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/pipe.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/runtime/sinks.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/skill/__init__.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/skill/_prompts.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/skill/core.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tooling/__init__.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tooling/_protocols.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tooling/_typing.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tooling/context.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tooling/core.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tooling/decorators.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tools/__init__.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tools/_identifiers.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tools/control.py +0 -0
- {roboz-0.1.2.dev6 → roboz-0.1.2.dev7}/src/roboz/tools/interaction.py +0 -0
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.1.2.dev7 - 2026-09-20
|
|
6
|
+
|
|
7
|
+
- Breaking: replace the `is_agentic` boolean with the string-valued
|
|
8
|
+
`AgentMode.DETERMINISTIC`, `.STEERABLE`, or `.AUTONOMOUS` enum (the default
|
|
9
|
+
remains steerable). Autonomous agents use model-driven scheduling without a
|
|
10
|
+
user-input tool or direct user-interaction sidecar; their tools communicate
|
|
11
|
+
through the event pipe. Remove `interaction_mode` arguments and
|
|
12
|
+
`set_interaction_mode` calls: invocation uses a bound host channel, defaulting
|
|
13
|
+
to CLI, and restores the previous binding automatically. Registering a
|
|
14
|
+
`UserIO` adapter selects `Output.API` for the binding's lifetime.
|
|
15
|
+
`AgentMode` now lives in `roboz.models`; the existing `roboz.agent` export
|
|
16
|
+
remains available for compatibility.
|
|
17
|
+
|
|
18
|
+
- Run each configured default tool exactly once per cycle for deterministic agents.
|
|
19
|
+
Model-driven agents retain their master-tool step.
|
|
20
|
+
|
|
21
|
+
- Filter conversation history with `roboz.models.filter_messages` by role,
|
|
22
|
+
requested action, or result caller while retaining message order and metadata.
|
|
23
|
+
|
|
24
|
+
- Call `get_completion(endpoint=..., messages=...)` directly without an API
|
|
25
|
+
callback. `MockLLMEndpoint` now accepts literal text responses as well as JSON
|
|
26
|
+
objects and exceptions.
|
|
27
|
+
- Breaking: completions without an output model or tool list now return raw text
|
|
28
|
+
verbatim instead of attempting action validation. Supply `LlmOutputModel=...`
|
|
29
|
+
or `active_tools=...` to retain structured dictionary results and repair retries.
|
|
30
|
+
Endpoint and callback arguments are mutually exclusive. See the
|
|
31
|
+
[completion guide](docs/tool-authoring.md#standalone-llm-backed-tools).
|
|
32
|
+
|
|
5
33
|
## 0.1.2.dev6 - 2026-09-15
|
|
6
34
|
|
|
7
35
|
- Breaking: make the `roboz` root a lazy authoring facade. It now exports only
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: roboz
|
|
3
|
+
Version: 0.1.2.dev7
|
|
4
|
+
Import-Name: roboz
|
|
5
|
+
Summary: Typed primitives for composable agentic workflows
|
|
6
|
+
Project-URL: Homepage, https://github.com/Tachion-Oy/roboz
|
|
7
|
+
Project-URL: Repository, https://github.com/Tachion-Oy/roboz
|
|
8
|
+
Project-URL: Issues, https://github.com/Tachion-Oy/roboz/issues
|
|
9
|
+
Project-URL: Documentation, https://github.com/Tachion-Oy/roboz/tree/main/docs
|
|
10
|
+
Author: Tachion Oy
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: agents,llm,tool-calling,typing,workflows
|
|
14
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.13
|
|
23
|
+
Requires-Dist: pydantic>=2.12.4
|
|
24
|
+
Requires-Dist: python-dotenv>=1.2.1
|
|
25
|
+
Requires-Dist: rich>=14.2.0
|
|
26
|
+
Provides-Extra: proton-bridge-beta
|
|
27
|
+
Requires-Dist: roboz-proton-bridge<0.2.0,>=0.1.0b1; extra == 'proton-bridge-beta'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
<div align="center">
|
|
31
|
+
<picture>
|
|
32
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/roboz-logo-dark.svg">
|
|
33
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/roboz-logo-light.svg">
|
|
34
|
+
<img alt="RoboZ" src="docs/assets/roboz-logo-dark.svg" width="560">
|
|
35
|
+
</picture>
|
|
36
|
+
|
|
37
|
+
<p><strong>Chain tools. Skip calls.</strong></p>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
RoboZ is a framework for building llm powered agents. The core ingredient is that every tool can may be chained conditionally to a subsequent tool thus allowing easy injection of deterministic flows into agentic processes.
|
|
41
|
+
|
|
42
|
+
[](https://github.com/Tachion-Oy/roboz/actions/workflows/ci.yml)
|
|
43
|
+
[](https://www.python.org/downloads/)
|
|
44
|
+
[](LICENSE)
|
|
45
|
+
|
|
46
|
+
> [!WARNING]
|
|
47
|
+
> RoboZ is pre-release software requiring Python 3.13 or newer. APIs may change
|
|
48
|
+
> before 1.0.
|
|
49
|
+
|
|
50
|
+
## Basic idea
|
|
51
|
+
|
|
52
|
+

|
|
53
|
+
|
|
54
|
+
### Problems to solve: Context bloat and too many llm calls
|
|
55
|
+
Suppose the task we want to achieve is ask our buddy Bob out to lunch and then book a table. For the sake of argument assume that our agent has access to the following MCP servers (Note: this is an example, RoboZ has native Tool primitives):
|
|
56
|
+
|
|
57
|
+
- Ask Bob what they want
|
|
58
|
+
- Find a restaurant
|
|
59
|
+
- Book a table.
|
|
60
|
+
|
|
61
|
+
In the usual approach an agent is presented each MCP server separately in the their system prompt and it must call them one-by-one to complete the task. When the agent is completing the task, at every turn it must choose the correct tool, formulate its output accordingly and absorb the reply into its context, which already must contain the specific instructions on how to use each tool. In addition, at each turn one has to wait for the llm to reply, each reply costs tokens and each reply risks a mistake from the llm.
|
|
62
|
+
|
|
63
|
+
### Deterministic chains
|
|
64
|
+
The philosophy in RoboZ is that of a a workflow is (mostly) deterministic an only on occasion does one need to call an llm. For example in RoboZ an agent would trigger the "ask Bob if they wan to have lunch" tool and all subsequent steps come by chaining: each tool can be chained to other tools upstream where their outputs are passed down the chain. Each link/edge may introduce a True/False condition, in our case for example if Bob interested in having lunch (with us). If he is not, RoboZ allows for the chain to break and returns back to the default tool, which for an agentic process is usually "ask the llm what to do next". The default mode is that chained tools are not presented to the agent, they are thus *passive* or in other words their role is strictly in forming deterministic workflows and they cannot be invoked.
|
|
65
|
+
|
|
66
|
+
Chaining not only reduces the llm calls, but it also provides a useful way of introducing a fine-grained guard layer for tool calls. This is in fact precisely how the cli tools and their access policies work in Roboz. For a cli command a chained passive tool evaluates the intent and breaks the chain if policies are violated.
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
## Start here: Agent with a tool
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from random import choice
|
|
73
|
+
|
|
74
|
+
from simpsons_quotes import QUOTES
|
|
75
|
+
|
|
76
|
+
from roboz import Agent, tool
|
|
77
|
+
from roboz.llm.endpoints import MockLLMEndpoint
|
|
78
|
+
from roboz.models import Empty, Message, Stop
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@tool
|
|
82
|
+
def get_quote(input: Empty, messages: list[Message]) -> Stop:
|
|
83
|
+
"""Return a random Simpsons quote and then stop."""
|
|
84
|
+
return Stop(value=choice(QUOTES))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
mock = MockLLMEndpoint(
|
|
88
|
+
responses=[{"action": "get_quote", "rationale": "Need Simpsons quote!"}]
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
agent = Agent(
|
|
92
|
+
name="demo",
|
|
93
|
+
system_prompt="You are a Simpsons quote generator",
|
|
94
|
+
agent_endpoint=mock,
|
|
95
|
+
tools=[get_quote],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
output, messages_ = agent.invoke()
|
|
99
|
+
print(f'"{output.value}"')
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The above [simple example](examples/simple.py) can be run from the root with
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
uv run python examples/simple.py
|
|
106
|
+
```
|
|
107
|
+
It creates an agent that returns a random
|
|
108
|
+
Simpsons quote. It uses a mock endpoint, with pre-determined replies, so you can run it without API keys.
|
|
109
|
+
The main contracts of RoboZ are already visible:
|
|
110
|
+
|
|
111
|
+
- `@tool` creates an instance of a usable tool for the agent
|
|
112
|
+
- A tool's input and output are typed. Tools also receive the entire message stack
|
|
113
|
+
- Callable endpoints are single instances, as a hard rule
|
|
114
|
+
- Different output types impact the dymanics, importantly `Stop` breaks out of the agentic loop
|
|
115
|
+
- `agent.invoke()` runs the agent and returns its output `Stop` and messages.
|
|
116
|
+
|
|
117
|
+
The above does not show the main idea of tool chaining, for that read the following sections.
|
|
118
|
+
## The central abstraction
|
|
119
|
+
|
|
120
|
+
An agent is a loop that calls tools. Everything is defined as a tool: Skills, background agents, prompting the agent, prompting the user, running nested agents, start up hooks etc. Everything.
|
|
121
|
+
|
|
122
|
+
**A tool can be trigger in three ways:**
|
|
123
|
+
- **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
|
+
- **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).
|
|
126
|
+
|
|
127
|
+
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
|
+
|
|
129
|
+
## Why is this framework useful?
|
|
130
|
+
### Tool Chaining
|
|
131
|
+
This may be used to reduce the number of llm calls, leading to a speed increase, lower cost and fewer AI errors. It also provides a useful way of introducing a guard layer for tool calls, which can be used to restrict agentic actions.
|
|
132
|
+
### Output Truncation
|
|
133
|
+
A Tools output can be hidden from the llm, also partially, and this can start to apply after the message has been shown N times.
|
|
134
|
+
### Tool outputs are typed
|
|
135
|
+
The contract in RoboZ is that all tool calls and hence in everything that happens is that outputs are typed classes. Agents and tools never exchange raw strings or even JSON, typed classes and validation are present throughout, with designated classes for tasks such as `Invoke` and `Stop`.
|
|
136
|
+
### LLm Endpoints are instances
|
|
137
|
+
As strict 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
|
+
|
|
139
|
+
The [simple example](examples/simple.py) above does not fully illustrate how these features of RoboZ work. For that see [complex example](examples/complex.py) example below.
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
## Chains, factories, truncation and many endpoints
|
|
144
|
+

|
|
145
|
+
|
|
146
|
+
In the code example below we illustrate some of the features that make RoboZ different from other frameworks.
|
|
147
|
+
|
|
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 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 via the `chain_condition` callable, which uses by definition 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.
|
|
149
|
+
|
|
150
|
+
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
|
+
|
|
152
|
+
Another useful feature demonstrated below in the `escalate` factory is **message truncation**. This parameter is present in all output types and allows the tool to decide if the output should be visible in the conversation passed on to the agent. It can truncate partially (show only n chars or just a caller stub) or completely. Importantly, we can choose to start applying the truncation only after the complete message has been shown to the agent n times. Below, we choose to show the message once and then truncate it completely, as useful pattern for example for long tracebacks etc.
|
|
153
|
+
|
|
154
|
+
All RoboZ tools by definition include the full conversation **messages** as input. They are not intended to be altered in place (although they can be and this is how e.g. *compactification* works), but can be used to alter the behavior of tools in a non-trivial way. For example, a start up hook intended to show the agent some information at the start or performing some initial maintenance can simply be one of the default tools that checks if it has already been called and if it has, does nothing.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from builtins import input as read_input
|
|
161
|
+
|
|
162
|
+
from roboz import Agent, factory, tool
|
|
163
|
+
from roboz.llm import EndpointLike, MockLLMEndpoint, get_completion
|
|
164
|
+
from roboz.models import Empty, Int, Message, Role, Stop, Str, filter_messages
|
|
165
|
+
from roboz.models.truncation import Severity, Truncation
|
|
166
|
+
from roboz.runtime.io import interact_with_user
|
|
167
|
+
from roboz.runtime.sinks import CliSink
|
|
168
|
+
from roboz.tools import stop
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@tool
|
|
172
|
+
def ask_number(input: Empty, messages: list[Message]) -> Int:
|
|
173
|
+
"""Ask the user for an integer, repeating until the response is valid."""
|
|
174
|
+
while True:
|
|
175
|
+
reply = read_input("Enter an integer: ")
|
|
176
|
+
try:
|
|
177
|
+
return Int(value=int(reply))
|
|
178
|
+
except ValueError:
|
|
179
|
+
print("Please enter an integer :)")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@factory(chained_to=ask_number, chain_condition=lambda x: x.value % 2 == 0)
|
|
183
|
+
def escalate(input: Int, messages: list[Message], ctx: EndpointLike) -> Stop | Str:
|
|
184
|
+
"""An even number?! Need to check this with HR!"""
|
|
185
|
+
interact_with_user("Careful now, that is pretty spicy!", with_reply=False)
|
|
186
|
+
prompt = f"The user chose {input.value}. Is this too hot to handle?! (y/n)?"
|
|
187
|
+
verdict = get_completion(
|
|
188
|
+
endpoint=ctx, messages=[Message(role=Role.SYSTEM, content=prompt)]
|
|
189
|
+
)
|
|
190
|
+
is_first_escalation = (
|
|
191
|
+
len(filter_messages(caller="escalate", messages=messages)) == 0
|
|
192
|
+
)
|
|
193
|
+
if verdict == "y" and not is_first_escalation:
|
|
194
|
+
return Stop(value="Too much spiciness, need to quit!")
|
|
195
|
+
return Str(
|
|
196
|
+
value="HR gave a pass, but still, let's show this to the agent only once.",
|
|
197
|
+
truncation=Truncation(threshold=1, severity=Severity.REMOVE),
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@tool(chained_to=ask_number, chain_condition=lambda x: x.value % 2 != 0)
|
|
202
|
+
def give_praise(input: Int, messages: list[Message]) -> Str:
|
|
203
|
+
"""We need to give praise for such an erudite approach to the problem."""
|
|
204
|
+
interact_with_user(f"{input.value} a fine and bold choice!", with_reply=False)
|
|
205
|
+
return Str(value=f"{input.value} is good, no biggie.")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
agent_endpoint = MockLLMEndpoint(
|
|
209
|
+
responses=[
|
|
210
|
+
*(10 * [{"action": "ask_number", "rationale": "This is my only job"}]),
|
|
211
|
+
{"action": "stop", "rationale": "Enough numbers!", "value": ""},
|
|
212
|
+
]
|
|
213
|
+
)
|
|
214
|
+
guard_endpoint = MockLLMEndpoint(responses=10 * ["y"])
|
|
215
|
+
|
|
216
|
+
agent = Agent(
|
|
217
|
+
name="demo",
|
|
218
|
+
system_prompt=f"Without exception, use the {ask_number.name} tool.",
|
|
219
|
+
event_sinks=[CliSink.default()],
|
|
220
|
+
agent_endpoint=agent_endpoint,
|
|
221
|
+
tools=[ask_number, escalate(guard_endpoint), give_praise, stop],
|
|
222
|
+
)
|
|
223
|
+
agent.invoke()
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The above [complex example](examples/complex.py) can be run from the root with
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
uv run python examples/complex.py
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
## Try it
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
uv add roboz
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Or with pip:
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
python -m pip install roboz
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Optional ecosystem
|
|
248
|
+
|
|
249
|
+
Start with core and add only the integrations the application needs.
|
|
250
|
+
|
|
251
|
+
| Distribution | Adds |
|
|
252
|
+
| --- | --- |
|
|
253
|
+
| `roboshed` | Guarded file and CLI tools, memory, compaction, reusable agents, and deployment recipes. |
|
|
254
|
+
| `roboz-endpoints` | Lazy model catalogues and SDK adapters for OpenAI-compatible providers. |
|
|
255
|
+
| `roboz-proton-bridge` | Proton Bridge email tools. |
|
|
256
|
+
|
|
257
|
+
See the [add-on guide](docs/addons.md) for installation and composition, and the
|
|
258
|
+
[endpoint guide](packages/endpoints/README.md) for model selection and provider
|
|
259
|
+
adapters.
|
|
260
|
+
|
|
261
|
+
## Documentation
|
|
262
|
+
|
|
263
|
+
| Guide | Start here for |
|
|
264
|
+
| --- | --- |
|
|
265
|
+
| [Tool authoring](docs/tool-authoring.md) | Chaining, factories, conditions, and typed handoffs |
|
|
266
|
+
| [Agent authoring](docs/agent-authoring.md) | Agent composition and prompt policy |
|
|
267
|
+
| [Reference](docs/reference.md) | Runtime and API semantics |
|
|
268
|
+
| [Complex chaining and truncation example](examples/complex.py) | Conditional chains, factory endpoints, and message visibility |
|
|
269
|
+
| [Testing practices](docs/testing-practices.md) | Deterministic workflow and contract tests |
|
|
270
|
+
|
|
271
|
+
## Development
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
uv sync --locked --dev
|
|
275
|
+
uv run pytest
|
|
276
|
+
uv run ruff check
|
|
277
|
+
uv run pyright
|
|
278
|
+
bash scripts/run_type_tests.sh
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and the
|
|
282
|
+
[build and test guide](docs/build-and-test.md) for the complete release gate.
|
|
283
|
+
Roboz is typed and ships a PEP 561 `py.typed` marker.
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
Roboz is licensed under the [Apache License 2.0](LICENSE). Copyright © 2026 Tachion Oy.
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/roboz-logo-dark.svg">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="docs/assets/roboz-logo-light.svg">
|
|
5
|
+
<img alt="RoboZ" src="docs/assets/roboz-logo-dark.svg" width="560">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
8
|
+
<p><strong>Chain tools. Skip calls.</strong></p>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
RoboZ is a framework for building llm powered agents. The core ingredient is that every tool can may be chained conditionally to a subsequent tool thus allowing easy injection of deterministic flows into agentic processes.
|
|
12
|
+
|
|
13
|
+
[](https://github.com/Tachion-Oy/roboz/actions/workflows/ci.yml)
|
|
14
|
+
[](https://www.python.org/downloads/)
|
|
15
|
+
[](LICENSE)
|
|
16
|
+
|
|
17
|
+
> [!WARNING]
|
|
18
|
+
> RoboZ is pre-release software requiring Python 3.13 or newer. APIs may change
|
|
19
|
+
> before 1.0.
|
|
20
|
+
|
|
21
|
+
## Basic idea
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
### Problems to solve: Context bloat and too many llm calls
|
|
26
|
+
Suppose the task we want to achieve is ask our buddy Bob out to lunch and then book a table. For the sake of argument assume that our agent has access to the following MCP servers (Note: this is an example, RoboZ has native Tool primitives):
|
|
27
|
+
|
|
28
|
+
- Ask Bob what they want
|
|
29
|
+
- Find a restaurant
|
|
30
|
+
- Book a table.
|
|
31
|
+
|
|
32
|
+
In the usual approach an agent is presented each MCP server separately in the their system prompt and it must call them one-by-one to complete the task. When the agent is completing the task, at every turn it must choose the correct tool, formulate its output accordingly and absorb the reply into its context, which already must contain the specific instructions on how to use each tool. In addition, at each turn one has to wait for the llm to reply, each reply costs tokens and each reply risks a mistake from the llm.
|
|
33
|
+
|
|
34
|
+
### Deterministic chains
|
|
35
|
+
The philosophy in RoboZ is that of a a workflow is (mostly) deterministic an only on occasion does one need to call an llm. For example in RoboZ an agent would trigger the "ask Bob if they wan to have lunch" tool and all subsequent steps come by chaining: each tool can be chained to other tools upstream where their outputs are passed down the chain. Each link/edge may introduce a True/False condition, in our case for example if Bob interested in having lunch (with us). If he is not, RoboZ allows for the chain to break and returns back to the default tool, which for an agentic process is usually "ask the llm what to do next". The default mode is that chained tools are not presented to the agent, they are thus *passive* or in other words their role is strictly in forming deterministic workflows and they cannot be invoked.
|
|
36
|
+
|
|
37
|
+
Chaining not only reduces the llm calls, but it also provides a useful way of introducing a fine-grained guard layer for tool calls. This is in fact precisely how the cli tools and their access policies work in Roboz. For a cli command a chained passive tool evaluates the intent and breaks the chain if policies are violated.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Start here: Agent with a tool
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from random import choice
|
|
44
|
+
|
|
45
|
+
from simpsons_quotes import QUOTES
|
|
46
|
+
|
|
47
|
+
from roboz import Agent, tool
|
|
48
|
+
from roboz.llm.endpoints import MockLLMEndpoint
|
|
49
|
+
from roboz.models import Empty, Message, Stop
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@tool
|
|
53
|
+
def get_quote(input: Empty, messages: list[Message]) -> Stop:
|
|
54
|
+
"""Return a random Simpsons quote and then stop."""
|
|
55
|
+
return Stop(value=choice(QUOTES))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
mock = MockLLMEndpoint(
|
|
59
|
+
responses=[{"action": "get_quote", "rationale": "Need Simpsons quote!"}]
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
agent = Agent(
|
|
63
|
+
name="demo",
|
|
64
|
+
system_prompt="You are a Simpsons quote generator",
|
|
65
|
+
agent_endpoint=mock,
|
|
66
|
+
tools=[get_quote],
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
output, messages_ = agent.invoke()
|
|
70
|
+
print(f'"{output.value}"')
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The above [simple example](examples/simple.py) can be run from the root with
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
uv run python examples/simple.py
|
|
77
|
+
```
|
|
78
|
+
It creates an agent that returns a random
|
|
79
|
+
Simpsons quote. It uses a mock endpoint, with pre-determined replies, so you can run it without API keys.
|
|
80
|
+
The main contracts of RoboZ are already visible:
|
|
81
|
+
|
|
82
|
+
- `@tool` creates an instance of a usable tool for the agent
|
|
83
|
+
- A tool's input and output are typed. Tools also receive the entire message stack
|
|
84
|
+
- Callable endpoints are single instances, as a hard rule
|
|
85
|
+
- Different output types impact the dymanics, importantly `Stop` breaks out of the agentic loop
|
|
86
|
+
- `agent.invoke()` runs the agent and returns its output `Stop` and messages.
|
|
87
|
+
|
|
88
|
+
The above does not show the main idea of tool chaining, for that read the following sections.
|
|
89
|
+
## The central abstraction
|
|
90
|
+
|
|
91
|
+
An agent is a loop that calls tools. Everything is defined as a tool: Skills, background agents, prompting the agent, prompting the user, running nested agents, start up hooks etc. Everything.
|
|
92
|
+
|
|
93
|
+
**A tool can be trigger in three ways:**
|
|
94
|
+
- **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.
|
|
95
|
+
- **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
|
|
96
|
+
- **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
|
+
|
|
98
|
+
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.
|
|
99
|
+
|
|
100
|
+
## Why is this framework useful?
|
|
101
|
+
### Tool Chaining
|
|
102
|
+
This may be used to reduce the number of llm calls, leading to a speed increase, lower cost and fewer AI errors. It also provides a useful way of introducing a guard layer for tool calls, which can be used to restrict agentic actions.
|
|
103
|
+
### Output Truncation
|
|
104
|
+
A Tools output can be hidden from the llm, also partially, and this can start to apply after the message has been shown N times.
|
|
105
|
+
### Tool outputs are typed
|
|
106
|
+
The contract in RoboZ is that all tool calls and hence in everything that happens is that outputs are typed classes. Agents and tools never exchange raw strings or even JSON, typed classes and validation are present throughout, with designated classes for tasks such as `Invoke` and `Stop`.
|
|
107
|
+
### LLm Endpoints are instances
|
|
108
|
+
As strict 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.
|
|
109
|
+
|
|
110
|
+
The [simple example](examples/simple.py) above does not fully illustrate how these features of RoboZ work. For that see [complex example](examples/complex.py) example below.
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
## Chains, factories, truncation and many endpoints
|
|
115
|
+

|
|
116
|
+
|
|
117
|
+
In the code example below we illustrate some of the features that make RoboZ different from other frameworks.
|
|
118
|
+
|
|
119
|
+
**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 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 via the `chain_condition` callable, which uses by definition 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
|
+
|
|
121
|
+
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.
|
|
122
|
+
|
|
123
|
+
Another useful feature demonstrated below in the `escalate` factory is **message truncation**. This parameter is present in all output types and allows the tool to decide if the output should be visible in the conversation passed on to the agent. It can truncate partially (show only n chars or just a caller stub) or completely. Importantly, we can choose to start applying the truncation only after the complete message has been shown to the agent n times. Below, we choose to show the message once and then truncate it completely, as useful pattern for example for long tracebacks etc.
|
|
124
|
+
|
|
125
|
+
All RoboZ tools by definition include the full conversation **messages** as input. They are not intended to be altered in place (although they can be and this is how e.g. *compactification* works), but can be used to alter the behavior of tools in a non-trivial way. For example, a start up hook intended to show the agent some information at the start or performing some initial maintenance can simply be one of the default tools that checks if it has already been called and if it has, does nothing.
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from builtins import input as read_input
|
|
132
|
+
|
|
133
|
+
from roboz import Agent, factory, tool
|
|
134
|
+
from roboz.llm import EndpointLike, MockLLMEndpoint, get_completion
|
|
135
|
+
from roboz.models import Empty, Int, Message, Role, Stop, Str, filter_messages
|
|
136
|
+
from roboz.models.truncation import Severity, Truncation
|
|
137
|
+
from roboz.runtime.io import interact_with_user
|
|
138
|
+
from roboz.runtime.sinks import CliSink
|
|
139
|
+
from roboz.tools import stop
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@tool
|
|
143
|
+
def ask_number(input: Empty, messages: list[Message]) -> Int:
|
|
144
|
+
"""Ask the user for an integer, repeating until the response is valid."""
|
|
145
|
+
while True:
|
|
146
|
+
reply = read_input("Enter an integer: ")
|
|
147
|
+
try:
|
|
148
|
+
return Int(value=int(reply))
|
|
149
|
+
except ValueError:
|
|
150
|
+
print("Please enter an integer :)")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@factory(chained_to=ask_number, chain_condition=lambda x: x.value % 2 == 0)
|
|
154
|
+
def escalate(input: Int, messages: list[Message], ctx: EndpointLike) -> Stop | Str:
|
|
155
|
+
"""An even number?! Need to check this with HR!"""
|
|
156
|
+
interact_with_user("Careful now, that is pretty spicy!", with_reply=False)
|
|
157
|
+
prompt = f"The user chose {input.value}. Is this too hot to handle?! (y/n)?"
|
|
158
|
+
verdict = get_completion(
|
|
159
|
+
endpoint=ctx, messages=[Message(role=Role.SYSTEM, content=prompt)]
|
|
160
|
+
)
|
|
161
|
+
is_first_escalation = (
|
|
162
|
+
len(filter_messages(caller="escalate", messages=messages)) == 0
|
|
163
|
+
)
|
|
164
|
+
if verdict == "y" and not is_first_escalation:
|
|
165
|
+
return Stop(value="Too much spiciness, need to quit!")
|
|
166
|
+
return Str(
|
|
167
|
+
value="HR gave a pass, but still, let's show this to the agent only once.",
|
|
168
|
+
truncation=Truncation(threshold=1, severity=Severity.REMOVE),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@tool(chained_to=ask_number, chain_condition=lambda x: x.value % 2 != 0)
|
|
173
|
+
def give_praise(input: Int, messages: list[Message]) -> Str:
|
|
174
|
+
"""We need to give praise for such an erudite approach to the problem."""
|
|
175
|
+
interact_with_user(f"{input.value} a fine and bold choice!", with_reply=False)
|
|
176
|
+
return Str(value=f"{input.value} is good, no biggie.")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
agent_endpoint = MockLLMEndpoint(
|
|
180
|
+
responses=[
|
|
181
|
+
*(10 * [{"action": "ask_number", "rationale": "This is my only job"}]),
|
|
182
|
+
{"action": "stop", "rationale": "Enough numbers!", "value": ""},
|
|
183
|
+
]
|
|
184
|
+
)
|
|
185
|
+
guard_endpoint = MockLLMEndpoint(responses=10 * ["y"])
|
|
186
|
+
|
|
187
|
+
agent = Agent(
|
|
188
|
+
name="demo",
|
|
189
|
+
system_prompt=f"Without exception, use the {ask_number.name} tool.",
|
|
190
|
+
event_sinks=[CliSink.default()],
|
|
191
|
+
agent_endpoint=agent_endpoint,
|
|
192
|
+
tools=[ask_number, escalate(guard_endpoint), give_praise, stop],
|
|
193
|
+
)
|
|
194
|
+
agent.invoke()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The above [complex example](examples/complex.py) can be run from the root with
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
uv run python examples/complex.py
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
## Try it
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
uv add roboz
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Or with pip:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
python -m pip install roboz
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## Optional ecosystem
|
|
219
|
+
|
|
220
|
+
Start with core and add only the integrations the application needs.
|
|
221
|
+
|
|
222
|
+
| Distribution | Adds |
|
|
223
|
+
| --- | --- |
|
|
224
|
+
| `roboshed` | Guarded file and CLI tools, memory, compaction, reusable agents, and deployment recipes. |
|
|
225
|
+
| `roboz-endpoints` | Lazy model catalogues and SDK adapters for OpenAI-compatible providers. |
|
|
226
|
+
| `roboz-proton-bridge` | Proton Bridge email tools. |
|
|
227
|
+
|
|
228
|
+
See the [add-on guide](docs/addons.md) for installation and composition, and the
|
|
229
|
+
[endpoint guide](packages/endpoints/README.md) for model selection and provider
|
|
230
|
+
adapters.
|
|
231
|
+
|
|
232
|
+
## Documentation
|
|
233
|
+
|
|
234
|
+
| Guide | Start here for |
|
|
235
|
+
| --- | --- |
|
|
236
|
+
| [Tool authoring](docs/tool-authoring.md) | Chaining, factories, conditions, and typed handoffs |
|
|
237
|
+
| [Agent authoring](docs/agent-authoring.md) | Agent composition and prompt policy |
|
|
238
|
+
| [Reference](docs/reference.md) | Runtime and API semantics |
|
|
239
|
+
| [Complex chaining and truncation example](examples/complex.py) | Conditional chains, factory endpoints, and message visibility |
|
|
240
|
+
| [Testing practices](docs/testing-practices.md) | Deterministic workflow and contract tests |
|
|
241
|
+
|
|
242
|
+
## Development
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
uv sync --locked --dev
|
|
246
|
+
uv run pytest
|
|
247
|
+
uv run ruff check
|
|
248
|
+
uv run pyright
|
|
249
|
+
bash scripts/run_type_tests.sh
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) and the
|
|
253
|
+
[build and test guide](docs/build-and-test.md) for the complete release gate.
|
|
254
|
+
Roboz is typed and ships a PEP 561 `py.typed` marker.
|
|
255
|
+
|
|
256
|
+
## License
|
|
257
|
+
|
|
258
|
+
Roboz is licensed under the [Apache License 2.0](LICENSE). Copyright © 2026 Tachion Oy.
|
|
@@ -11,9 +11,11 @@ from roboz.agent.background_agent import (
|
|
|
11
11
|
from roboz.agent.core import Agent
|
|
12
12
|
from roboz.agent.prompt_agent_tool import PromptAgentContext, prompt_agent
|
|
13
13
|
from roboz.agent.subagent import run_subagent
|
|
14
|
+
from roboz.models import AgentMode
|
|
14
15
|
|
|
15
16
|
__all__ = [
|
|
16
17
|
"Agent",
|
|
18
|
+
"AgentMode",
|
|
17
19
|
"BackgroundAgentContext",
|
|
18
20
|
"BackgroundAgentPhase",
|
|
19
21
|
"BackgroundAgentState",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""System-prompt assembly for
|
|
1
|
+
"""System-prompt assembly for model-driven agent runs."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -35,7 +35,7 @@ def _get_available_tools_and_skills_section(
|
|
|
35
35
|
return section.rstrip()
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
def get_example_agent_discussion() -> str:
|
|
38
|
+
def get_example_agent_discussion(*, allow_user_input: bool = True) -> str:
|
|
39
39
|
"""Return a multi-turn example showing how turns appear in the agent (roles and JSON shapes)."""
|
|
40
40
|
section = "## Example discussion\n\n<example>\n\n"
|
|
41
41
|
section += (
|
|
@@ -48,6 +48,24 @@ def get_example_agent_discussion() -> str:
|
|
|
48
48
|
section += (
|
|
49
49
|
"System: This is an example system message for illustrative purposes.\n\n"
|
|
50
50
|
)
|
|
51
|
+
if not allow_user_input:
|
|
52
|
+
section += "Assistant: "
|
|
53
|
+
section += (
|
|
54
|
+
json.dumps(
|
|
55
|
+
{
|
|
56
|
+
"action": "example_tool",
|
|
57
|
+
"rationale": "Calling the example tool.",
|
|
58
|
+
"value": 42,
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
+ "\n\n"
|
|
62
|
+
)
|
|
63
|
+
section += "User: "
|
|
64
|
+
section += json.dumps(
|
|
65
|
+
{"caller": "example_tool", "value": "Example: 42"}
|
|
66
|
+
)
|
|
67
|
+
section += "\n\n</example>"
|
|
68
|
+
return section
|
|
51
69
|
section += "Assistant: "
|
|
52
70
|
section += (
|
|
53
71
|
json.dumps(
|
|
@@ -138,6 +156,7 @@ def get_agentic_system_prompt(
|
|
|
138
156
|
system_prompt: str = "",
|
|
139
157
|
tools: Sequence[Tool] | None = None,
|
|
140
158
|
skills: Sequence[Skill] | None = None,
|
|
159
|
+
allow_user_input: bool = True,
|
|
141
160
|
) -> str:
|
|
142
161
|
"""Build the complete technical prompt for an agentic run.
|
|
143
162
|
|
|
@@ -149,6 +168,6 @@ def get_agentic_system_prompt(
|
|
|
149
168
|
AGENT_TOOL_USE_INSTRUCTIONS,
|
|
150
169
|
AGENT_SKILL_USE_INSTRUCTIONS,
|
|
151
170
|
_get_available_tools_and_skills_section(tools, skills),
|
|
152
|
-
get_example_agent_discussion(),
|
|
171
|
+
get_example_agent_discussion(allow_user_input=allow_user_input),
|
|
153
172
|
]
|
|
154
173
|
return "\n\n".join(p for p in parts if p)
|