Skip to content

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_context is one bundle for the current page. It can include the URL, title, locale, browser locale, and page content when the host supplies it.
  • visitor_identity represents identity context when the host provides it.
  • visitor_request bundles 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 string or json.
  • Trust tier is unverified or signed.
  • Sensitivity is normal or sensitive.
  • 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.

  • unverified is suitable for page state and host-supplied hints. Do not use it to unlock account-specific answers.
  • signed is 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:

text
GET /api/v1/agents/{agentId}/context-variables/signing-key

The 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:

json
{
  "customerId": "cus_123",
  "sessionId": "public-session-id",
  "origin": "https://example.com",
  "issuedAt": 1782398400000,
  "nonce": "unique-random-value",
  "attributes": {
    "plan": "pro"
  }
}

Create the token as:

text
payload = base64url(JSON.stringify(payloadObject))
signature = base64url(HMAC_SHA256(signingKeyBytes, payload))
token = payload + "." + signature

Pass the token to the launcher with a provider function, or with a string:

javascript
window.Radioso.identify(async ({ sessionId, origin }) => {
  const response = await fetch(`/identity?session=${sessionId}`, {
    credentials: 'same-origin',
  })
  const { token } = await response.json()
  return token
})
javascript
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
  • issuedAt must be within the acceptance window, five minutes
  • sessionId must match the established public chat session
  • origin must match the approved embed origin
  • nonce must 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:

text
session -> customer -> agent -> workspace

It 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’s CF-IPCountry.
  • language — the primary tag of the browser’s Accept-Language header, so de-DE,de;q=0.9 resolves to de.
  • 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.

  • always includes the value whenever it resolves.
  • on_reference keeps the value available for turn logic and includes it only when the turn needs it.
  • operator_only stores 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 — an account_tier string 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:

text
PUT /api/v1/context-variables/{id}/values

The body includes a scope and data:

json
{
  "scope": { "type": "session", "id": "public-session-id" },
  "data": {
    "items": [
      { "sku": "course-101", "quantity": 1 }
    ]
  }
}

Radioso resolves values from most specific to least specific:

  1. session
  2. customer
  3. agent
  4. workspace

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.

  • 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.