Skip to content
Merged
7 changes: 6 additions & 1 deletion agents/build/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

The system prompt sets the personality, goals, and guardrails that steer every reply. It is capped at **4,000 tokens**. The API rejects longer prompts with `422 Unprocessable Entity`. Keeping it under **2,000 tokens** is recommended: shorter prompts cut latency and cost, and hold the model's attention better. It can also be replaced for a single session by sending `overrides.system_prompt` when [creating the session](/agents/deploy/authenticated-sessions#overrides).

To personalize the prompt per session without replacing it, write `{{placeholders}}`: [custom variables](/agents/build/dynamic-variables#custom-variables) such as `{{customer_name}}` take the values you pass when creating the session, and [system variables](/agents/build/dynamic-variables#system-variables) such as `{{system.caller_number}}` or `{{system.today}}` are filled in by the platform.

<Tip>
Keep the prompt focused:

Expand All @@ -28,7 +30,7 @@
| **Fixed message** | The agent opens with the exact text you provide, every time. | `first_message` |
| **Prompt** | The agent generates its opening line from instructions you provide. | `first_message_prompt` |

`first_message` and `first_message_prompt` can each hold up to 10,000 characters. The active mode (`first_message_mode`: `off`, `fixed`, or `prompt`) decides what callers hear. Both fields can be replaced for a single session by sending `overrides.first_message` or `overrides.first_message_prompt` when [creating the session](/agents/deploy/authenticated-sessions#overrides).
`first_message` and `first_message_prompt` can each hold up to 10,000 characters. The active mode (`first_message_mode`: `off`, `fixed`, or `prompt`) decides what callers hear. Both fields accept the same `{{placeholders}}` as the system prompt (see [Dynamic variables](/agents/build/dynamic-variables)), and both can be replaced for a single session by sending `overrides.first_message` or `overrides.first_message_prompt` when [creating the session](/agents/deploy/authenticated-sessions#overrides).

## Voice

Expand Down Expand Up @@ -68,7 +70,7 @@

`conversation.timezone` is the default IANA timezone (like `Asia/Shanghai`) the agent uses for dates and times in conversation. Leave it empty for **automatic**: each session follows the caller's device or phone number, falling back to UTC. Set one when your agent serves a single region regardless of who calls. A per-session `timezone` on the [session request](/agents/build/time-timezone) overrides this. See [Time & timezone](/agents/build/time-timezone) for the full resolution order.

## Autosave and publishing

Check warning on line 73 in agents/build/configuration.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/build/configuration.mdx#L73

Did you really mean 'Autosave'?

There is no Save button. Each change is written to the agent's draft moments after you stop editing, and the **Saving… / Saved** indicator at the bottom-left of the page shows the current state. If a save fails, the Builder tells you and keeps your pending edits so nothing is lost.

Expand Down Expand Up @@ -153,4 +155,7 @@
<Card title="Tools" icon="wrench" href="/agents/build/tools">
Let the agent call your backend mid-conversation.
</Card>
<Card title="Dynamic variables" icon="brackets-curly" href="/agents/build/dynamic-variables">
Personalize the prompt and first message per session.
</Card>
</CardGroup>
100 changes: 87 additions & 13 deletions agents/build/dynamic-variables.mdx
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
---
title: "Dynamic Variables"
description: "Personalize each session with template variables supplied at creation time"
description: "Personalize each session with template variables: your own values, and facts the platform fills in"
icon: "brackets-curly"
---

An agent's published configuration is shared by every caller. Dynamic variables personalize it per session. Write `{{variable_name}}` placeholders in your agent's system prompt or first message and supply values when you create the session. Substitution happens once, when the published configuration is assembled for the session.
An agent's published configuration is shared by every caller. Dynamic variables personalize it per session: write `{{name}}` placeholders in the configuration and they are substituted once, when the published configuration is assembled for the session. There are two kinds:

- **[Custom variables](#custom-variables)**: values you supply when you create the session, such as the caller's name or plan.
- **[System variables](#system-variables)**: facts about the session itself, such as the caller's phone number or the date, filled in by the platform under the reserved `system.*` names.

## Where variables render

Placeholders are substituted in the agent's configured text and in webhook tool requests. Everything else (knowledge base content, tool names and descriptions, post-call analysis prompts) is left as written.

| Where | Custom variables | System variables |
|---|---|---|
| System prompt (`prompt.system_prompt`) | Yes | Yes |
| First message, fixed text or prompt (`prompt.first_message`, `prompt.first_message_prompt`) | Yes | Yes |
| Session [overrides](/agents/deploy/authenticated-sessions#overrides) of those fields (`overrides.system_prompt`, `overrides.first_message`, `overrides.first_message_prompt`) | Yes | Yes |
| Outbound voicemail message (`conversation.outbound.voicemail.message`) | Yes | Yes |
| [Webhook tool](/agents/build/webhook-tools#argument-templating) URL, header values, and body template | No | Yes |

Custom values travel with the request that starts the session: `dynamic_variables` on [`POST /v1/agent/sessions`](/agents/deploy/authenticated-sessions), `dynamicVariables` in the SDK's `AgentSession.start()` for [public agents](/agents/deploy/public-agents), `dynamic_variables` on [`POST /v1/agent/phone-calls`](/agents/telephony/outbound-calls#request-fields) for outbound calls, and the variables panel of a [preview call](/agents/test/preview-calls) in the Builder. Inbound phone calls have no such request: values come from the inbound call webhook on the agent's Webhooks tab when one is configured; the caller's number and the other session facts are always available as system variables.

## Custom variables

Write `{{variable_name}}` placeholders in your agent's system prompt or first message and supply values when you create the session:

```text System prompt
You are a support agent for {{company}}. The caller's name is {{customer_name}}
and they are on the {{plan}} plan. Greet them by name.
```

Pass values as a flat object of strings, numbers, or booleans. Variable names must match `[A-Za-z][A-Za-z0-9_]*` (no hyphens or dots), string values are capped at 1,000 characters, and a request can carry at most 50 variables. Violations reject session creation with `422`:
Pass values as a flat object of strings, numbers, or booleans. Variable names must match `[A-Za-z][A-Za-z0-9_]*` (no hyphens or dots; the dotted `system.*` names are reserved for [system variables](#system-variables)), string values are capped at 1,000 characters, and a request can carry at most 50 variables. Violations reject session creation with `422`:

Check warning on line 35 in agents/build/dynamic-variables.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/build/dynamic-variables.mdx#L35

Did you really mean 'booleans'?

<CodeGroup>
```bash API (curl)
Expand Down Expand Up @@ -43,19 +64,11 @@
Placeholders with no matching variable are **not** removed: the literal `{{variable_name}}` text stays in the prompt, visible to the model. Make sure every placeholder in your configuration has a value at session creation.
</Warning>

<Note>
Session records never store dynamic variable values.
</Note>

<Tip>
You don't need a variable for the current date or time. The agent already knows both, in the session's timezone. See [Time & timezone](/agents/build/time-timezone).
</Tip>

Variables fill placeholders in the configured text. To replace whole configuration fields for a session (the prompt itself, the opener, voice, language), use [overrides](/agents/deploy/authenticated-sessions#overrides) on the same request; `{{placeholders}}` render inside overridden text too.

## Who supplies the values
### Who supplies the values

Where variable values come from depends on the session's [access mode](/agents/deploy/overview#who-may-start-sessions):
Where custom variable values come from depends on the session's [access mode](/agents/deploy/overview#who-may-start-sessions):

| Mode | Who creates the session | Who supplies the values |
|---|---|---|
Expand All @@ -81,9 +94,70 @@
```

<Tip>
In `agentId` mode, values arrive from the end user's browser. Treat them as untrusted input, and use `sessionToken` mode when personalization must come from data only your backend knows.

Check warning on line 97 in agents/build/dynamic-variables.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/build/dynamic-variables.mdx#L97

Did you really mean 'untrusted'?
</Tip>

## System variables

The platform fills a handful of `{{system.*}}` placeholders itself, on every session. You cannot supply or override them: the names you pass in `dynamic_variables` cannot contain a dot, so the two namespaces never collide.

Check warning on line 102 in agents/build/dynamic-variables.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/build/dynamic-variables.mdx#L102

Did you really mean 'namespaces'?

| Variable | Value | Notes |
|---|---|---|
| `system.channel` | `phone_inbound`, `phone_outbound`, or `web_voice` | Web SDK, API, preview, and agent test sessions are all `web_voice`. |
| `system.timezone` | The session's resolved IANA timezone, for example `Asia/Shanghai` | `UTC` when nothing resolves. Always equals the session's `timezone` field; see [which timezone a session uses](/agents/build/time-timezone#which-timezone-a-session-uses). |
| `system.today` | Today's date in that timezone, ISO 8601 `YYYY-MM-DD` | The calendar date at session creation. There is no `system.now`; see [World context](#world-context). |
| `system.language` | The session language id: `en`, `ja`, `zh`, `ko`, `es`, `fr`, `de` | The `language` [override](/agents/deploy/authenticated-sessions#overrides) when the request carries one, otherwise the agent's [speaking language](/agents/build/voice-language#speaking-language). |
| `system.caller_number` | The human party's number, E.164 | Inbound: the caller (empty if withheld or not a valid E.164 number). Outbound: the number you dialed. Empty on non-phone sessions. |
| `system.dialed_number` | Your workspace number, E.164 | Inbound: the number that was called. Outbound: the number the call was placed from. Empty on non-phone sessions. |

### Phone numbers

The phone numbers are the session's `caller_number` and `dialed_number` fields as shown in [session history](/agents/telephony/inbound-calls#phone-sessions-in-history). Only a value that is a well-formed E.164 number reaches the agent: anything else the carrier sends as the caller id (for example an `anonymous` marker) renders as an empty string, while the history field keeps the raw value. On every session that is not a phone call (web, text, preview, agent tests) both render as an empty string, never as literal `{{system.caller_number}}` text, so one agent can serve web and phone with the same prompt.

Write the prompt so it also reads well when the number is empty:

```text System prompt
You answer the support line {{system.dialed_number}}. The caller's number is
"{{system.caller_number}}". If it is empty, ask for a callback number; otherwise
confirm the last four digits before discussing the account.
```

On an inbound call from +1 415 555 0123 to your number +1 408 555 0199 the model sees:

```text Rendered
You answer the support line +14085550199. The caller's number is
"+14155550123". If it is empty, ask for a callback number; otherwise
confirm the last four digits before discussing the account.
```

On a web session the same prompt renders `The caller's number is "".`, and the agent asks for a number. To look the caller up in your own systems, template the number into a [webhook tool](#in-webhook-tools) instead of relying on the model to repeat it.

### Date and timezone

`system.today` is always ISO formatted, whatever the session language: in the system prompt the model reads it and speaks it in the session's language, while a fixed first message containing `{{system.today}}` is spoken verbatim as `2026-08-21`. A session that runs past midnight keeps its opening date. Template it, and `system.timezone`, when a request needs them explicitly, for example a webhook tool's query string; for the agent's own sense of time no variable is needed, see [World context](#world-context).

### In webhook tools

System variables render wherever custom variables do, and also inside [webhook tool](/agents/build/webhook-tools#argument-templating) requests: the URL, header values, and body template. Custom variables do not render into tools. In the URL the value is URL-encoded, like argument values: the leading `+` of an E.164 number travels as `%2B` and the `/` in a timezone name as `%2F`, both decoding back on your server.

```text Webhook tool URL
https://crm.example.com/availability?phone={{system.caller_number}}&from={{system.today}}&tz={{system.timezone}}
```

### Unknown names

A reference to a system variable that does not exist (`{{system.foo}}`) is rejected with `422` when you save the configuration or the tool, and when you send it in session overrides. The error lists the available names.

<Note>
There is no `system.session_id`. The configuration is rendered before the session receives its id, so the id cannot be templated into it. Read it from the `POST /v1/agent/sessions` [response](/agents/deploy/authenticated-sessions), the SDK's `connect` event, or the `session` object in [webhooks](/agents/monitor/webhooks).

Check warning on line 152 in agents/build/dynamic-variables.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/build/dynamic-variables.mdx#L152

Did you really mean 'templated'?
</Note>

## World context

The agent knows the current date and time without any variable. The platform states the date and the session's timezone at the start of every session and refreshes the time on every turn, so "tomorrow morning" or "next Tuesday" resolve correctly even in a long conversation. You don't need a custom `{{today}}` or `{{now}}`, and there is no `system.now`: variables render once, when the session is created, so a templated time would be stale from the first reply onward. When you want the date or timezone as text in a template, for example in a webhook tool URL, use the [`system.today` and `system.timezone`](#system-variables) system variables.

Check warning on line 157 in agents/build/dynamic-variables.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/build/dynamic-variables.mdx#L157

Did you really mean 'templated'?

See [Time & timezone](/agents/build/time-timezone) for how the timezone is resolved and how to turn the injection off for a session (`world_context: false`).

## Going further

<CardGroup cols={2}>
Expand Down
2 changes: 2 additions & 0 deletions agents/build/time-timezone.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
system prompt's character limit.
</Note>

Both facts also exist as [system variables](/agents/build/dynamic-variables#system-variables) for the cases where you want to template them yourself, for example into a webhook tool's URL: `{{system.today}}` is the date at session creation as ISO `YYYY-MM-DD`, and `{{system.timezone}}` is the resolved IANA name (`UTC` when nothing resolves). There is no `{{system.now}}`: variables render once, when the session is created, so a templated clock would freeze at the opening. The per-turn time above is the one to rely on.

Check warning on line 25 in agents/build/time-timezone.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/build/time-timezone.mdx#L25

Did you really mean 'templated'?

## Which timezone a session uses

The timezone is resolved once, when the session is created, taking the first that applies:
Expand Down
4 changes: 2 additions & 2 deletions agents/build/webhook-tools.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

## Argument templating

Reference any declared argument with `{{name}}`: in the URL, in the body template, or both. The platform substitutes the values the agent supplies before sending the request.
Reference any declared argument with `{{name}}`: in the URL, in header values, or in the body template. The platform substitutes the values the agent supplies before sending the request. [System variables](/agents/build/dynamic-variables#system-variables) such as `{{system.caller_number}}` or `{{system.today}}` are available in the same places; they are filled when the session starts, before the agent supplies any argument.

```text Endpoint URL
https://api.example.com/orders/{{order_number}}
Expand Down Expand Up @@ -114,7 +114,7 @@

## Test your tool

The tool editor's **Test** tab fires a real request at your endpoint. Fill in the arguments as JSON (pre-filled with a sample based on your declared arguments) and send. You get back the status code, latency, response headers, and response body. A failing test never blocks saving the tool.
The tool editor's **Test** tab fires a real request at your endpoint. Fill in the arguments as JSON (pre-filled with a sample based on your declared arguments) and send. You get back the status code, latency, response headers, and response body. A failing test never blocks saving the tool. There is no live call behind a test, so `{{system.*}}` placeholders have no value: in header values and the body template they render empty unless you add the values to the arguments JSON as a nested object, for example `{"system": {"caller_number": "+15551234567"}}`. The URL is sent exactly as saved; placeholders in it are not substituted by a test request.

Response bodies are captured up to 64 KB; larger bodies are cut off and flagged with `response_truncated`.

Expand Down Expand Up @@ -166,7 +166,7 @@
{ "ticket_id": "T-1042", "expected_reply": "within 24 hours" }
```

Ticket numbers get spoken aloud: short, pronounceable IDs survive text-to-speech far better than UUIDs.

Check warning on line 169 in agents/build/webhook-tools.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/build/webhook-tools.mdx#L169

Did you really mean 'UUIDs'?

<Note>
An in-call ticket depends on the model choosing to escalate. For a safety net
Expand Down
6 changes: 6 additions & 0 deletions agents/telephony/inbound-calls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,19 @@
"metadata": {}
}
],
"has_more": false,

Check warning on line 100 in agents/telephony/inbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/inbound-calls.mdx#L100

Did you really mean 'has_more'?
"next_cursor": null

Check warning on line 101 in agents/telephony/inbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/inbound-calls.mdx#L101

Did you really mean 'next_cursor'?
}
```

You can combine `caller_number` with the other list filters (`agent_id`, `status`, `created_after`, `created_before`); see [Conversation history](/agents/monitor/conversation-history) for the full parameter list.

The agent can read both numbers too, as `{{system.caller_number}}` and `{{system.dialed_number}}` in its system prompt, first message, and webhook tool requests. No variable needs to be passed; see [System variables](/agents/build/dynamic-variables#system-variables).

```text System prompt
The caller is calling from {{system.caller_number}}. Confirm the last four digits before discussing the account.
```

## Phone sessions behave like any session

Nothing about a phone call needs special handling downstream:
Expand Down
2 changes: 1 addition & 1 deletion agents/telephony/outbound-calls.mdx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: "Outbound Calls"
description: "Place calls from your phone numbers over the API; the agent speaks when the callee answers"

Check warning on line 3 in agents/telephony/outbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/outbound-calls.mdx#L3

Did you really mean 'callee'?
icon: "phone-arrow-up-right"
---

Dial any allowed number from one of your workspace numbers and the agent takes the call the moment the callee picks up. Outbound calls are ordinary agent sessions with `direction: "outbound"`: they appear in session history, they are [stored](/agents/monitor/conversation-history#what-gets-stored) and analyzed under the same per-agent settings as any other conversation, and they trigger the same webhooks plus one extra, [`phone_call.dial_finished`](/agents/monitor/webhooks), that reports how the dial attempt ended.

Check warning on line 7 in agents/telephony/outbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/outbound-calls.mdx#L7

Did you really mean 'callee'?

<CardGroup cols={3}>
<Card
Expand All @@ -30,7 +30,7 @@

<Steps>
<Step title="Get a number that supports outbound">
Any [purchased number](/agents/telephony/phone-numbers) can place calls. An [imported BYO number](/agents/telephony/byo-sip) can too, once its termination is configured; the number object reports this as `supports_outbound`. The number you dial from is the caller ID the callee sees.

Check warning on line 33 in agents/telephony/outbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/outbound-calls.mdx#L33

Did you really mean 'callee'?
</Step>
<Step title="Publish your agent">
Outbound calls run the agent's published configuration, not the draft. [Publish](/agents/deploy/versions-publishing) before dialing.
Expand Down Expand Up @@ -70,7 +70,7 @@
| `overrides` | Optional: replace whole configuration fields for this call, subject to the agent's [override allowlist](/agents/deploy/authenticated-sessions#overrides). |
| `metadata` | Optional: your own JSON object, returned verbatim on session reads and in webhook payloads. Never interpreted. |

The session's [time and timezone context](/agents/build/time-timezone) resolves from the destination number when the agent has no fixed timezone configured, so "tomorrow morning" means the callee's morning.

Check warning on line 73 in agents/telephony/outbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/outbound-calls.mdx#L73

Did you really mean 'callee's'?

### Retry safely with an Idempotency-Key

Expand All @@ -78,7 +78,7 @@

## The dial outcome

Ringing is never billed; metering starts when the callee answers. A call that is never answered is not billed and not analyzed.

Check warning on line 81 in agents/telephony/outbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/outbound-calls.mdx#L81

Did you really mean 'callee'?

You learn how the dial ended in either of two ways:

Expand All @@ -97,11 +97,11 @@
| `dial_status` | `answered`, `busy`, `no_answer`, or `failed`. `null` while the call is still ringing, and on inbound calls. |
| `answered_by` | What answering-machine detection heard: `human`, `voicemail`, or `unknown`. `null` unless answered. |

On outbound sessions the attribution fields are person-centric: `caller_number` is the human you dialed and `dialed_number` is your workspace number. Everything else about the session (transcript, recording, [post-call analysis](/agents/monitor/post-call-analysis), `call.ended` and `call.analyzed` webhooks, hang-up via `POST /v1/agent/sessions/{session_id}/end`) works exactly as for [inbound calls](/agents/telephony/inbound-calls).
On outbound sessions the attribution fields are person-centric: `caller_number` is the human you dialed and `dialed_number` is your workspace number. The same values reach the agent as `{{system.caller_number}}` and `{{system.dialed_number}}`, so a CRM lookup tool can use `https://crm.example.com/contacts?phone={{system.caller_number}}` without a per-call variable; see [System variables](/agents/build/dynamic-variables#system-variables). Everything else about the session (transcript, recording, [post-call analysis](/agents/monitor/post-call-analysis), `call.ended` and `call.analyzed` webhooks, hang-up via `POST /v1/agent/sessions/{session_id}/end`) works exactly as for [inbound calls](/agents/telephony/inbound-calls).

## Allowed destinations

Calls from purchased numbers can reach US, Canada, and Japan numbers. Japanese destinations may keep the domestic trunk zero (`+81080...` is accepted and normalized to `+8180...`), and premium-rate segments (such as `0570` Navi Dial and `0990`) are always refused. Calls from [imported BYO numbers](/agents/telephony/byo-sip) dial out through your own trunk, so the country allowlist does not apply; the destination only has to be valid E.164.

Check warning on line 104 in agents/telephony/outbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/outbound-calls.mdx#L104

Did you really mean 'Navi'?

Check warning on line 104 in agents/telephony/outbound-calls.mdx

View check run for this annotation

Mintlify / Mintlify Validation (hanabiaiinc) - vale-spellcheck

agents/telephony/outbound-calls.mdx#L104

Did you really mean 'allowlist'?

Numbers that live on the platform can never be dialed, so an agent cannot call another agent's number.

Expand Down
Loading