Operations read values with {{root.name}} tokens. There are six roots, and between them they are the whole vocabulary an operation has: what the model sent, what earlier operations produced, what the session knows, what the platform knows, what the workspace configured, and your credentials.

The six roots

Every root works in the URL, in the query, in the body and in headers — and in every other templated string in the tree: a say, a set_variable value, an if’s left and right, a frontend_rpc payload. Three places templates deliberately do not resolve:
  • A code operation’s TypeScript. It has no template syntax at all; the same six roots arrive as input.args, input.tooldata and so on.
  • An http operation’s method, which is a fixed choice of GET, POST, PUT, PATCH or DELETE.
  • A transfer destination, which is a literal phone number by design.
A string that is exactly one token keeps the resolved value’s type. "{{args.quantity}}" sends the number 3, not the string "3"; "{{tooldata.slots}}" sends the array. Put anything else in the string — a space, a prefix — and the token is stringified into the surrounding text. A token whose root is not one of the six is an error at save, not text. A bare {{name}}, a {{customer.number}} pasted from another platform, or a typo like {{userdate.name}} is refused when the tool is saved, because the alternative is sending those braces to your API on every call. There is no escape syntax; prose containing {{anything}} cannot be saved outside a code operation.

The two stores

tooldata and userdata are the two places an operation can write. Which one you pick is not a detail: tooldata is created empty every time the tool runs and is gone when it returns. Only later operations in the same tree can read it. It is the plumbing between two operations. userdata belongs to the session. Other tools read it, later turns read it, the agent’s prompt substitutes it, and it is persisted with the call and merged onto the contact record. Write there only what the conversation should still know afterwards. The default is tooldata. A booking reference the agent must be able to repeat ten turns later belongs in userdata; the raw API envelope you unpacked to find it does not.

Moving a value between operations

An operation’s result is not automatically visible to the next one. http, code and frontend_rpc declare publish_fields, each entry {path, key?, store}:
  • path picks a value out of the result by dotted path. A numeric segment indexes an array, so order.items.0.name is the first item’s name.
  • key names it. Left out, it is the last segment of the path — order.status publishes as status. Set it explicitly whenever the last segment is not the name you want to read back, and always when the path ends in an index.
  • store is required, and it is the difference between a value that dies with the tool and one the whole session carries.
set_variable writes one templated value directly, with the same required store and no operation to produce it:
Two failure modes to know:
A publish path that is not in the result fails the operation. If the value at path is missing — or is null — the operation fails with publish field path 'order.status' was not found in operation output, and on_error decides what happens next. This is the most common mistake in a tool: a path one level too deep, or a field your API only returns sometimes. Run the draft against a real response before publishing.
A background_execution operation never waits for a result, so it cannot publish. Setting both is a publish error.

What is an error, and what is a warning

Errors block publish; warnings do not. A key published anywhere earlier in the tree counts as available, including inside an if branch: keys published before the branch are readable in both, and keys published inside a branch are readable only later in that same branch. One thing validation cannot catch: a secret that is deleted after the tool was published. The next run fails at the start, before any operation, with secret 'X' does not exist.

system_vars

Six keys, filled by the platform and writable by nothing. So a tool can post the caller’s number to a CRM, stamp a record with {{system_vars.now}}, or branch on the direction, without the agent having to ask. The three phone keys are empty on web calls, on text conversations and in a dashboard test run. Never make one the only source of a fact the tool needs; take it as an argument as well, or read it from userdata. The clock keys read the moment the tool ran, not the moment the call started — which is what makes {{system_vars.now}} right in a created_at you send to an API forty minutes into a call. (In an agent’s prompt or greeting the same tokens are frozen at the moment the agent started, because re-resolving them every turn would throw away the model’s prompt cache.) They resolve wherever the agent has a timezone set, on every channel.

vars

{{vars.name}} is the workspace’s own configuration for this session: the agent’s declared defaults, with whatever the request that started the session supplied merged over them. Read-only, and nothing inside the session can write it. See variables for declaring them. For a tool this is what removes the copy-per-customer problem:
One published tool serves every reseller. A tool is a workspace-level object and does not know which agent will call it, so the {{vars.…}} names it reads are checked against declarations only when an agent that attaches it is published — and an undeclared name is a warning there, never an error.
vars are visible to the model, which can read them aloud or repeat them back. A credential belongs in a workspace secret, read as {{secrets.NAME}} from a tool, where the model never sees it.

Do not compare a clock string

{{system_vars.time}} is "7:26 PM" and {{system_vars.date}} is "Wednesday, 19 August 2026". Neither is a number, and gt / lt need two numbers — so an if comparing "7:26 PM" against "09:00" fails the operation rather than quietly taking the else branch. Business-hours logic belongs in a code operation over new Date(input.system_vars.now). See conditionals.

A worked example

Look up availability, keep three values with three different lifetimes, and read them back in a spoken line.
What each store buys:
  • requested_date and held_slot_id go to userdata, so the next tool — book_appointment, called on a later turn — can read {{userdata.held_slot_id}} without asking the caller again, and the agent’s prompt can refer to the date.
  • count and first_slot go to tooldata: they exist to build one sentence and have no business outliving it.
  • The header carries {{system_vars.now}} — templating works in headers exactly as it does in the URL.
  • Because the only operation that could return a result is silent, the tool is silent and the agent says the say line and nothing else.
When you test that tool, seed the state it will really run in: the test run takes userdata, vars and a timezone alongside the arguments. See testing and publishing.

Next

The HTTP operation

Every config field, auth, network rules and worked examples.

The code operation

The TypeScript sandbox, input, and its real limits.

Conditionals

Branching on a value an earlier operation published.

Secrets

Where {{secrets.NAME}} comes from.