Agent API¶
Mesh tool catalogue, model protocol, and the plan → tool → observe loop.
Details: Agent loop.
Agent loop over mesh tools — plan → query_once → observe.
The fabric primitives stay as they are (@channel, @handle, queues).
This package is the glue that turns a channel handler into an agent whose tools
are other services on Zenoh.
Agent
dataclass
¶
One agent in a handoff graph: a model, its tools, a system prompt, and the agents it may transfer to.
name must be usable in a tool name ([A-Za-z0-9_-]); it is sanitized
the same way key expressions are. description is surfaced to a router
model in the transfer_to_<name> tool so it knows when to hand off.
Source code in src/istos/agent/multi.py
AgentEvent
dataclass
¶
One step the loop emits for the caller to forward (e.g. over a channel).
kind is one of:
message— final assistant text for this turntool_call— model asked to run a mesh tool (name,arguments)tool_result— tool returned (content);errorwhen it raisedhandoff— active agent transferred toname(multi-agent loop)done— turn finished (no more steps)
Source code in src/istos/agent/loop.py
MeshTool
¶
One mesh endpoint the agent may call.
Built from a local @handle via :func:tools_from_handlers, or by hand
when the tool lives on another node (pass app and the remote prefix)::
MeshTool("math/add", app=app, description="Add two integers",
parameters={"type": "object", "properties": {
"a": {"type": "integer"}, "b": {"type": "integer"},
}, "required": ["a", "b"]})
Source code in src/istos/agent/tools.py
call(arguments, *, token=None, timeout_s=5.0)
async
¶
Run the tool. Mesh tools go through query_once so authorizers run.
Source code in src/istos/agent/tools.py
openai_schema()
¶
Tool definition in the OpenAI chat-completions shape.
Model
¶
Bases: Protocol
Source code in src/istos/agent/model.py
complete(messages, *, tools=None)
async
¶
Next assistant turn. tools is the OpenAI tools array, or None.
ModelError
¶
ModelReply
dataclass
¶
What the model returned for one completion turn.
model, finish_reason, and usage ({prompt_tokens,
completion_tokens, …}) are optional telemetry an adapter may fill in; the
loop puts them on its completion span. A custom model can leave them unset.
Source code in src/istos/agent/model.py
OpenAIChatModel
¶
Thin /v1/chat/completions client (non-streaming, with tool calls).
Uses aiohttp — already an Istos dependency — so no extra install for the common OpenAI-compatible local servers::
model = OpenAIChatModel(
base_url="http://127.0.0.1:1234/v1",
model="qwen/qwen3.5-9b",
)
Source code in src/istos/agent/model.py
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
ToolCall
dataclass
¶
build_registry(entry)
¶
Every agent reachable from entry through handoffs, keyed by name.
Source code in src/istos/agent/multi.py
drive_agents(session, entry, *, max_steps=8, max_messages=40, token=None, timeout_s=5.0, send_events=True)
async
¶
Channel helper: reload history, then run :func:run_multi_agent per turn.
The active agent persists across turns within a session, and is restored on
reconnect from persisted handoff frames (send_events=True); otherwise
a resumed session restarts at entry. token forwards to tool calls
under whichever agent is active. See :func:~istos.agent.loop.drive_channel
for the send_events payload shapes.
Source code in src/istos/agent/multi.py
drive_channel(session, model, tools, *, system=None, max_steps=8, max_messages=40, token=None, timeout_s=5.0, send_events=True)
async
¶
Channel helper: reload history, then run :func:run_agent per inbound turn.
By default each :class:AgentEvent is sent as a dict
{"kind", "content", …}. Set send_events=False to send only the final
message content (plain string). max_messages bounds the reused log so
a long-lived session does not grow unboundedly; pass None to disable.
Source code in src/istos/agent/loop.py
history_to_messages(history, *, system=None, include_tools=True)
¶
Map a durable channel log ([{dir, data, ts}, …]) into chat messages.
With include_tools (the default) the tool transcript is reconstructed:
each persisted tool_call frame becomes an assistant tool_calls message
followed by the tool message carrying its result, so a reconnecting agent
sees the same context it had live. A tool_call with no matching
tool_result in the log (a crash mid-tool) is dropped so the sequence stays
valid. Set include_tools=False to rebuild plain text only.
Source code in src/istos/agent/loop.py
run_agent(model, tools, messages, *, max_steps=8, max_messages=None, token=None, timeout_s=5.0)
async
¶
Run plan → tool → observe until the model returns text or max_steps.
Mutates messages in place so the caller can keep a multi-turn
conversation. Each mesh tool call forwards token on query_once. Pass
max_messages to bound the log before each completion (system prompt kept).
Source code in src/istos/agent/loop.py
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | |
run_multi_agent(active, messages, *, max_steps=8, max_messages=None, token=None, timeout_s=5.0)
async
¶
Run the loop starting at active, switching agents on handoff.
Mutates messages in place (shared history). Emits the same events as
:func:~istos.agent.loop.run_agent plus handoff when the active agent
changes; the last handoff event names the agent that should drive the
next turn. token is forwarded to every tool call, across handoffs.
Source code in src/istos/agent/multi.py
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | |
tools_from_handlers(app, *, prefixes=None)
¶
Build :class:MeshTool entries from the app's @handle registry.
Plumbing under .istos/ is skipped. Pass prefixes to whitelist
(exact key expressions). Schemas come from the same path MCP uses.
Source code in src/istos/agent/tools.py
user_text(msg)
¶
Pull a user string out of a channel message (str or common dict shapes).