All three need the editor role; a viewer gets a
403.
Validate
Validation reads the draft and answers two questions: what would publish refuse, and what should you know anyway.- Reading
{{tooldata.x}}that nothing earlier publishes is an error —tooldatastarts empty on every run, so that read can only ever resolve to nothing. - Reading
{{userdata.x}}that this tool does not publish is a warning — it has to already be in session userdata, which means the API caller or an earlier tool has to put it there. Only you know whether that happens.
publish_fields
whose path cannot be reached, handoff targets that are not published, and
TypeScript that will not compile. What it never does is open a socket, which is
why the next step exists.
A tool with no operations cannot be validated into shape:
add at least one operation before publishing.
Test run
POST /v1/tools/{tool_id}/run runs the draft once, with arguments you
supply, and reports every step.
Nothing is saved, no webhook fires, and the run is not metered.
What you send
There is no call behind a test run, so the phone-call system variables —
human_phone_number, agent_phone_number, direction — resolve empty.
What comes back
llm_response is the field worth reading first. It applies the real rule,
including the silence derived from the tree,
so it tells you what the model would actually see rather than what the silent
box says.
The whole run is capped at 60 seconds. A tree of ten HTTP operations at the
default 20-second timeout would otherwise hold an API worker for two hundred.
Error types
When a run fails,error_type names whose problem it is.
The classification is structural, not string matching: an HTTP step that
recorded no request had a bad config, one with a request and no response never
reached you, and one with a response means you answered.
Reading the trace
Each entry insteps is one operation:
path locates the operation in the tree: the node’s index, joined to its parent
by .then. or .else.. So 2.else.1 is the second child of the else-branch of
the third operation.
status is ok, failed or simulated. detail is kind-specific — an http
step carries the resolved request and the response, a code step carries its
console output and returned value, an if step carries both resolved sides, the
comparison and the branch it took, and a simulated step carries its resolved
config. Secret values are redacted throughout.
published is what that operation wrote into tooldata or userdata, which is
usually the fastest way to find a path that was one level too deep.
In the dashboard
The tool editor’s test panel has three bands — Arguments (a field per declared parameter), User Data (the session bag to start from) and Variables ({{vars.*}}) — and a Run tool button. The clock runs on your
browser’s timezone, and the panel says which one it used.
The result appears beneath, and the flowchart beside it colours itself from the
run:
An
if shows which branch the run took, so the coloured path through the chart
is literally the path the run took through the tree.
Publishing
POST /v1/tools/{tool_id}/publish validates the draft, freezes it as the next
version number, and points the tool’s published_version at it. Versions start
at 1, are never reused, and are never rewritten.
400 listing every
error. Warnings come back on the successful response instead — they are things
to know, not things to fix first.
What is frozen is the whole definition: the name, description, json_schema,
the three behaviour flags and the operation tree, plus two derived things. Any
code operation’s TypeScript is compiled at publish and the build output is
frozen with it, and silent is stored as your flag or the silence derived from
the tree — so a tool whose every operation is silent is published silent
whatever the box said. Turn a non-silent operation on later and your own choice
comes back; the derived value never writes itself into the draft.
Versions and rollback
GET /v1/tools/{tool_id} lists the versions with their changelogs and publish
times. GET /v1/tools/{tool_id}/versions/{version} returns one version’s frozen
definition in full — the same field shape as the draft, so you can diff a version
against the draft or against another version without reshaping either.
POST /v1/tools/{tool_id}/versions/{version}/rollback puts an earlier version
back into production.
The draft you get back is intent only: rolling back strips the compiled build
output from any code operation, exactly as it strips pinned versions when an
agent version is restored. Publishing is what adds the derived parts.
The pin rule, from the tool’s side
This is the one thing that surprises everyone once.1
An unpublished tool cannot be attached
Attaching a tool with no published version to an agent is refused:
tool 'check_order' has no published version - publish it before attaching.2
Publishing an agent pins its tools
The agent version records, for each attached tool and lifecycle hook, the
tool version that was live at that moment.
3
Republishing the tool changes nothing live
Live calls keep running the pinned version. The agent has to be published
again for a new tool version to reach a caller.
Agent tasks never pin. A task resolves each attached tool to its current
published version on every run, so republishing a tool changes every task that
uses it immediately, with nothing to republish. Defensible for a batch job and
indefensible for a live call, which is exactly why the two differ. See
tools and MCPs.
Next
Agent versions
The other half of the pin rule, and version history in the dashboard.
ToolCoPilot
Building and fixing a tree by describing it.
Templating and data
Why a
tooldata read is an error and a userdata read is a warning.Patterns
Complete trees worth starting from.