Tool processes
A tool process gives the agent new tools from a separate program, in any language. The daemon starts it, talks to it over its standard input and output, and keeps every call inside the same capability checks and audit log as the built-in tools. All the integrations — Gmail, Calendar, Notion, the toolkit and the rest — are tool processes.
The full contract is
docs/TOOL_SDK.md;
this page is the overview.
How it works
- The daemon starts one process per
[[tool_process]]in the config, and stops it on shutdown. - Frames are the same as the IPC protocol: a 4-byte big-endian length, then JSON. Daemon → tool on stdin, tool → daemon on stdout; stderr is yours for logging.
- The daemon sends
ToolHello { protocol_version }. - The tool answers
ToolRegisterwith oneToolDescriptorper tool:name,description(what the model reads),input_schemaandrequired_scope. - The daemon checks each declared scope can be granted by some active role; tools that can’t are logged and left out.
- For each call:
InvokeTool { call_id, tool_name, input, turn_id }→ any number ofToolEventprogress frames →ToolResultorToolError.CancelInvocation { call_id }asks the tool to stop; replyToolError { code: "cancelled" }promptly.
What you get, and what you can’t skip
- Scopes are bound at registration. A tool can only be called with the
authority it declared, and the operator can narrow it further
(
scope_overrides) — never widen it.expected_scopeslets the operator pin what a tool may declare at all. - Every call is audited before your process sees it. Tools don’t write audit entries and can’t avoid them.
- Output is treated as untrusted. Whatever a tool process returns is fenced as data before the model sees it and scanned for prompt-injection phrasing, which makes a tool that relays web pages or mail much harder to use to steer the agent.
Sandboxing
A tool process runs as the operator’s user, so sandbox anything you didn’t
write. A [tool_process.sandbox] block wraps it in bubblewrap, firejail or
any wrapper you choose; [sandbox] default_backend = "auto" (what
aivyx-pa init writes) applies a default sandbox to tool processes that
don’t declare their own. TOOL_SDK.md §9 has worked examples.
Start a new tool
aivyx-pa tool init my-tool
writes a buildable Rust project — handshake, the call loop and a
conformance test — using the aivyx-tool crate for the wire types; you
fill in handle_invocation. The crate isn’t on crates.io yet, so the
generated Cargo.toml has a path dependency to point at your checkout.
In another language, start from
examples/python-tool/
and its conformance suite
(python3 -m unittest discover examples/python-tool/tests).
Then add it to the config:
[[tool_process]]
name = "my-tool"
command = "/path/to/my-tool"
and restart the daemon. Its tools appear in the Studio’s Tools screen
and in tools.list.