Context variables
Context variables let an agent use structured visitor context during a turn. They are meant for facts such as the current page, visitor identity, cart, order status, account tier, or another value supplied by the host system.
Operators configure declarations and agent wiring. Runtime values come from the host backend or a resolver.
Built-in context
Radioso provides built-in context for the website experience.
page_contextis one bundle for the current page. It can include the URL, title, locale, browser locale, and page content when the host supplies it.visitor_identityrepresents identity context when the host provides it.visitor_requestbundles the country, region, city, browser language, referrer, and entry page Radioso observed when the conversation opened. See Visitor request below.
Built-ins are not listed in the host-defined catalog and cannot be edited there. They are code-owned platform context, available on every agent.
Page context is relevance-gated
The embed captures page context with each message, but the agent does not see it
on every turn. Each turn is classified first. Page data — including the
URL, title, and locale — reaches the model only when the turn actually asks
about the current page, or when an active routine reads the page. A routine
reads the page when a skill input is bound to page_context or a step
instruction references it (the Current page chip in the routine editor, or
{{context.page_context}} in the stored instruction). A step reference renders
the page’s URL, title, and locale into the step; the page excerpt stays out.
Unrelated turns contribute no page data to model input or stored message
metadata.
In practice this means answers to general questions are not influenced by whatever page the visitor happens to be on, and page content cannot inject instructions into unrelated turns.
Page capability resolution works in two modes. When a message omits
clientContextCapabilities, Radioso infers the capability from whichever
pageContext fields are populated — full page content resolves to mode
content, a URL, title, or locale alone resolves to mode metadata. When a
message includes clientContextCapabilities["page.read"] with an explicit
available flag and mode, Radioso treats it as an advertised claim and
negotiates it against the pageContext actually supplied, narrowing to
metadata unless both agree on content, and withholding the capability
entirely when the advertised claim says available: false. Both modes go
through the same relevance gate.
Host-defined variables
Use host-defined variables for data Radioso cannot know by itself. Common
examples are cart, order_status, subscription_plan, or account_region.
Each declaration belongs to the workspace catalog:
- Name is the stable variable key.
- Description tells operators what the value means.
- Value type is
stringorjson. - Trust tier is
unverifiedorsigned. - Sensitivity is
normalorsensitive. - Default surfacing is the default prompt visibility policy.
Then each agent enables the variables it should use. In the agent Context section, choose the source and surfacing policy for that agent.
The selection belongs to the agent’s private draft. Save it, then use Test Chat to try the candidate with sample values for the enabled variables. Sample values are private test inputs: publishing the candidate never writes them as visitor or production values. Changing the selected candidate or sample values starts a fresh test conversation, so the conversation’s context and routine state always match the version you selected.
When the behavior is ready, review the Context changes with the other draft changes and publish the candidate. The shared variable declarations and runtime values remain workspace and host concerns; the agent revision records which variables the agent uses and how it resolves them.
The dashboard supports one source:
- Pushed API — the host backend writes values through the REST API.
Resolver-backed variables exist in the API model. The dashboard does not configure them.
Trust tiers
Trust tier describes how much the system should trust the value.
unverifiedis suitable for page state and host-supplied hints. Do not use it to unlock account-specific answers.signedis for values the host can prove came from a trusted backend or a signed identity flow.
In practice, identity-sensitive behavior should depend on signed or backend pushed values.
Signed visitor identity
Signed visitor identity lets a website backend prove which customer is using an embedded chat session. This is the only browser path that unlocks customer-scoped context values.
First, reveal the per-agent signing key from the signed-in dashboard as a workspace administrator. This is a session-only settings operation; API credentials cannot reveal signing keys:
GET /api/v1/agents/{agentId}/context-variables/signing-keyThe response contains a hex HMAC key derived for that workspace and agent. It is not stored as a separate database secret.
Your backend signs this payload:
{
"customerId": "cus_123",
"sessionId": "public-session-id",
"origin": "https://example.com",
"issuedAt": 1782398400000,
"nonce": "unique-random-value",
"attributes": {
"plan": "pro"
}
}Create the token as:
payload = base64url(JSON.stringify(payloadObject))
signature = base64url(HMAC_SHA256(signingKeyBytes, payload))
token = payload + "." + signaturePass the token to the launcher with a provider function, or with a string:
window.Radioso.identify(async ({ sessionId, origin }) => {
const response = await fetch(`/identity?session=${sessionId}`, {
credentials: 'same-origin',
})
const { token } = await response.json()
return token
})window.Radioso.identify(token)The string form goes out unchanged with every message, and each message’s
nonce is single-use, so a static token verifies exactly one message — the
first one that carries it. Radioso records the customer on the conversation
the moment a turn verifies, so later turns keep the customer scope even
though the built-in visitor_identity value and its attributes show up only
on the turn that verified. Use the provider form above for any conversation
with more than one turn.
A provider is the form a website backend should use. The token has to carry
the public chat sessionId, and only the embed knows it — the session begins
when the visitor opens the widget, not when the page loads. Verification also
checks a single-use nonce and a five-minute issuedAt window on every
message, so the token that always verifies is the one minted at the moment of
use. Minting per message keeps the built-in visitor_identity context value,
with the token’s attributes, present on every turn. A same-origin endpoint
such as /identity?session=<sessionId>, called with credentials: 'same-origin', is the typical provider: it reads the host’s session cookie,
checks the logged-in visitor, and signs the payload above.
When the provider runs
Radioso calls the provider right before it sends each visitor message,
passing the chat’s current sessionId and the host page’s origin
(window.location.origin). The provider returns the token as a string, or as
a Promise that resolves to one.
If the provider returns anything else, throws, or takes longer than five seconds, that message goes out without identity and the turn continues as anonymous.
Radioso verifies the token on each message. Verification is strict:
- the signature must match the current or previous per-agent signing key
issuedAtmust be within the acceptance window, five minutessessionIdmust match the established public chat sessionoriginmust match the approved embed originnoncemust not have been used before inside the acceptance window
If any check fails, the identity is treated as absent. The visitor turn still continues as anonymous. Radioso does not expose token, payload, or secret values in logs.
When verification succeeds, Radioso adds the customer scope to context
resolution:
session -> customer -> agent -> workspaceIt also exposes a sensitive built-in visitor_identity context value with
verified trust. Unsigned browser identity never gets verified trust and never
unlocks customer-scoped data.
Token attributes are a per-turn hint — good for something true only in this
session, such as the plan the visitor is currently comparing. For a durable
customer fact such as a name, an email address, or order history, push a
customer-scoped context variable from the host backend instead: it persists
for that customer across sessions, where attributes do not.
Visitor request
visitor_request gives a directive or a prompt six facts about the request
that opened the conversation:
country,region,city— read from the geo header a load balancer or CDN stamps on the request, such as Cloudflare’sCF-IPCountry.language— the primary tag of the browser’sAccept-Languageheader, sode-DE,de;q=0.9resolves tode.referrer— the page that sent the visitor, reported by the embed launcher.entryPageUrl— the page the chat opened on, reported by the embed launcher.
The IP address and user agent that accompanied the request are not part of
it — visitor_request carries only the six fields above, so a directive that
reads it never sees an address or a browser fingerprint. Values are
unverified: they describe what the request looked like, not a proven
identity.
entryPageUrl shares its sensitivity with page_context, so it follows the
same page-read gate: it reaches visitor_request only on a turn where that
turn’s own page-content check is open, and drops out otherwise, even though
the conversation opened on that page throughout. The other five fields carry
no page-content sensitivity and reach every turn that has them, regardless of
that turn’s page-read decision.
visitor_request is available on every agent, the same as page_context and
visitor_identity — there is no per-agent setting to turn it on. It reaches a
turn whenever the conversation carried at least one of the six facts; a
directive condition such as “when the visitor is in Germany, mention that
shipping to the EU takes 3-5 days” reads visitor_request.country the same
way it would read any other context variable.
Surfacing
Surfacing controls whether a resolved value is shown to the model as part of the prompt context.
alwaysincludes the value whenever it resolves.on_referencekeeps the value available for turn logic and includes it only when the turn needs it.operator_onlystores it for operator inspection and logic, but does not surface it into the answer prompt; a routine step that references it reads nothing.
Sensitive variables should use the narrowest surfacing that works. Redacted values may still appear in the Inbox so operators can debug what happened without exposing raw sensitive data.
Conditions that read context
Directive conditions are judged against the turn’s visitor context, so a
condition can describe visitor state rather than only what the visitor said.
“The visitor’s cart is worth more than 100” or “the visitor is on a product
page” are conditions the matcher can decide, because cart and page_context
are part of the signals it sees.
Alongside your own variables, the turn carries one fact Radioso establishes
itself: radioso_caller_kind is agent when the other side of the conversation
is another AI agent rather than a person, and human otherwise. Radioso reads it
from the channel the conversation arrived on — the MCP converse door and the REST
agent channel carry agents; the embed, a shared link, Slack, and the dashboard
carry people — so a caller cannot claim to be something it is not. Write
“the caller is another AI agent” to scope a directive to them, which is how you
give agent callers a terser answer or a different escalation path than a person
gets. The key is present on every turn, so a condition about it always has
something to read.
Test Chat and the workbench run as human, because an operator driving a test is
a person. A condition scoped to agent callers is therefore one you confirm
against real traffic from the MCP converse door or the REST agent channel rather
than in a test run.
Every resolved variable takes part, whatever its surfacing: operator_only and
on_reference values steer behavior without being written into the answer
prompt. Two limits are worth knowing when you write a condition:
- Sensitive values arrive as
[redacted], so a condition that depends on reading one cannot hold. Condition on a non-sensitive companion value instead — anaccount_tierstring rather than the account number. - Long values are shortened and a turn’s context is capped, so conditions that hinge on something deep inside a large JSON blob are unreliable. Push the fact the condition needs as its own variable.
Page context reaches conditions as the URL, title, and locale. The visible page excerpt is not part of it.
Pushing values
A host backend pushes a value to:
PUT /api/v1/context-variables/{id}/valuesThe body includes a scope and data:
{
"scope": { "type": "session", "id": "public-session-id" },
"data": {
"items": [
{ "sku": "course-101", "quantity": 1 }
]
}
}Radioso resolves values from most specific to least specific:
sessioncustomeragentworkspace
Use session scope for short-lived visitor state, customer scope for account data, agent scope for agent-specific defaults, and workspace scope for shared defaults.
Inbox
Open a conversation in the Inbox and inspect the assistant turn. When context variables were resolved for that turn, the debug panel shows a read-only Visitor context block. Sensitive values are already redacted by the backend before they reach the dashboard.
Read next
- Deployment — where
WORKSPACE_TOKEN_SECRET, which derives the signing key above, is set as a required secret. - Visitor data — every field behind
visitor_request, plus the fields that never reach a prompt.