An agent’s prompt and greeting are templates. {{userdata.name}}, {{vars.api_domain}} and {{system_vars.date}} are substituted when the agent starts, so one stored definition addresses the person it actually reached. A token whose root is not one of ours is an error when the agent is saved, not text. Talqing does not forward {{customer.number}} to the model and hope.

The six roots

The three refusals are errors at save, with the reason in the message: tool arguments do not exist outside a tool call, tooldata exists only while the tool that wrote it is running, and a workspace secret is a credential for a tool to send rather than text for the model to read. Everything about the tool side of templating — tooldata versus userdata, publish_fields, set_variable — is on templating and data. One extra token exists and belongs to recording: {{consent.notice}}, legal in a greeting and nowhere else. See recording.

Tokens that are not ours

Talqing matches anything shaped like a token, not only the roots it knows, so these are all refused when you save: The error reads, for example:
The same check runs over a handoff destination’s description, summary_prompt and message, because all three are substituted before the model or the caller sees them.
There is no escape syntax. Prose containing {{anything}} cannot be saved on an agent. The one exemption is a code operation’s TypeScript source, where braces are the author’s and nothing substitutes them.

vars

config.vars is what this agent declares it reads as {{vars.name}}. A declaration is not a value — it is the contract, and the values arrive per session.
string
required
Matches [A-Za-z_][A-Za-z0-9_]*, and unique within the agent. A name declared twice is refused: each name gets one description and one default, and the later entry would silently win.
string
default:""
For your teammates and the CoPilot. Never substituted anywhere.
string | null
default:"null"
What {{vars.name}} resolves to when the session supplied no value. null means “empty unless supplied”.
boolean
default:"false"
Refuse the session rather than resolve this to nothing. A default satisfies it outright.
Values arrive on the request that starts the session — POST /v1/calls/token, POST /v1/calls/outbound, POST /v1/calls/batches and POST /v1/conversations — and override the declared defaults. vars values are strings only; an integer is refused, because substitution is textual and a reference number like "007" is not the integer 7. There is one bag per session, and it reaches every agent the session runs: the entry agent, every team member, and a handoff target that was never in the plan. Each of them merges that bag over its own declared defaults, so a handoff target falls back to its defaults rather than inheriting the source agent’s. Nothing inside the session can write vars — no tool, no hook, no model.
A write takes the whole config, not a patch — the example above is trimmed to the fields under discussion, and sending it as written would reset everything it omits to that field’s default. Read the current config, change it, send it back. See agent overview. Supplying values for one call:

Four things to get right

Values are visible to the model. Anything in vars is substituted into the prompt the model reads, so it can be repeated back or read aloud. A credential belongs in a workspace secret, read as {{secrets.NAME}} from a tool, where the model never sees it. required: true refuses the session. A variable that is required, has no default, and gets no value refuses the call token, the dial, the batch and the text conversation — before anything is compiled and before any provider is called:
An empty string counts as a value: {"api_domain": ""} is a caller deliberately blanking a default, and it wins over the default like any other string. A default satisfies required outright. Nothing is enforced once a session is connected — a handoff target that needs a value nobody supplied reads it as empty rather than ending the call mid-conversation. An inbound call or message has no request of ours, so only the declared defaults resolve. That makes the rule above a hard constraint for inbound voice: a voice agent that requires a variable with no default cannot answer a phone number at all. All three doors into “this agent answers this number” refuse it — assigning the number, publishing that config while the agent is already assigned to one, and rolling back to a version that predates the default:
Anything an inbound session needs has to have a default. agent_override.vars and the top-level vars are different things. agent_override.vars changes what an agent declares for one call — the declarations and their defaults. The top-level vars on the same request supplies the values. Both can appear on one request and they do not conflict: the override decides what is declared, the top-level bag what it is worth. Because the required check runs on the merged config, an override that adds a default satisfies it. See per-call configuration.

Reading a variable nobody declared

{{vars.x}} where x is not in config.vars is a warning, not an error:
The key space is open on purpose. A request may carry a value no stored agent declared, which is what lets an agent or a tool defined inline in that same request read one. Contrast system_vars, whose key space is closed — Talqing fills it, so a name outside the catalog can only be a typo.

system_vars

Six keys, filled in by Talqing. Any other name is refused when you save. GET /v1/catalog returns the list as system_vars. human_phone_number is the other party either way — the caller on an inbound call, the person you dialled on an outbound one — so a prompt never has to work out which end it is on. One agent can greet a caller and someone it called differently by branching on {{system_vars.direction}}. The three call keys are empty on web calls and on text conversations. Only a voice agent is ever dispatched onto a phone call, so writing one into a text or video agent earns a warning saying it will always be empty. Do not make one the only source of a fact the agent needs. The three clock keys work on every channel and are rendered against the agent’s timezone (an IANA name such as Asia/Kolkata). Using one with no timezone set is a save error:
{{system_vars.now}} is the machine format — send it to an API, never read it aloud. Writing it into a greeting earns a warning, because a text-to-speech voice cannot say an ISO timestamp; use {{system_vars.time}} or {{system_vars.date}} in a spoken line.

When the clock is read

The prompt is resolved once, when the agent is built: on a call that is the moment before it answers, on a handoff the moment the target takes over, on text the start of a cold conversation window. Re-resolving it per turn would rewrite the prompt prefix and throw away the provider’s prompt cache for the rest of the call. So an agent that has been talking for forty minutes still reads its own {{system_vars.time}} as the time it picked up. A tool resolves per run, so {{system_vars.now}} inside a tool is the time that tool ran — which is what you want in a created_at you send to a CRM.
If the agent has to reason about “today”, opening hours or an appointment time, set timezone and let a tool read the clock. Do not paste a date into the prompt.

Which one do I use?

Use userdata for facts about the person and vars for configuration of the session. If a value would be wrong to replay into that person’s next conversation, it is a var.

Next

Userdata

Session state about the person, and how tools write it.

Templating and data

The tool side: args, tooldata, publish_fields, set_variable.

Secrets

Where credentials go, and how {{secrets.NAME}} resolves.

Per-call configuration

agent_override, inline agents, and what a single call can change.