How-to Guide

Dynamic Data Injection — keeping the brain in sync with your app

Design guidance for app builders on getting your app's state into the conversation — who the viewer is, what's currently on screen, and what just happened in your UI — so the brain reasons with fresh, correct context instead of a stale snapshot from when the session first connected.

The SDK gives you four distinct mechanisms for this, each solving a different problem. Reaching for the wrong one is the most common integration mistake: using a passive mechanism when you needed the brain to react right now, or using the active one for something that should just be quiet background context. This doc explains why each exists, when to reach for it, and how they compose into one coherent update flow.

Four ways to get information to the brain

Mechanism What it's for Does it make the avatar talk?
requestVars / session.updateRequestVars() Slow-changing personalization substituted into {{var}} prompt templates (viewer name, account tier) No — passive
session.setDynamicPrompt() A full, structured context blob the brain reads fresh on its next turn (what's on screen right now) No — passive
session.speak() Actively provoke a new turn — the brain reacts immediately, in this turn Yes — this is the only one of the four that does
session.submitStructuredDataForm() Answer a request the brain itself made for structured fields No — feeds the answer back into the conversation

The first two are passive: they update what the brain will see, whenever its next turn happens to occur. speak() is the only active one — it's the trigger that makes a turn happen right now. Getting this distinction right is the key to reliable behavior: if you need the brain to react to something the instant it happens (a modal just closed, a background action just finished), a passive update alone will sit there unread until some other turn happens to occur — sometimes much later, sometimes never in a short session.

Personalization: request_vars

Pass slow-changing facts about the viewer that your prompt substitutes directly via {{var}} templating — a name, an account tier, a language preference, anything your prompt's own template references by key. Set it once at connect time, and update it again later as you learn more:

const session = new KalturaAvatarSession({
  token, /* … */,
  requestVars: { user_name: 'Ada' },
});

// later, once you learn more about the viewer:
session.updateRequestVars({ user_name: 'Ada', account_tier: 'enterprise' });

The one rule that matters: updateRequestVars() always sends the full current map. The backend resets request_vars to exactly what you send — it does not merge with the join-time map or a previous call. If you only send { account_tier: 'enterprise' }, you have just erased user_name for the rest of the session. Keep the full map in your own app state and resend all of it on every call.

Use this for values your prompt's {{ }} placeholders reference by name, and that don't change every turn. It is not the right tool for "what's on screen right now" — for that, see the dynamic prompt below.

Per-turn context: the dynamic prompt

setDynamicPrompt() sends a full, structured JSON blob the brain reads as live context for its next turns — not a set of named template substitutions, but a whole object your prompt can reason over freely (the current slide's data, the current page section, the state of a task the viewer is in the middle of).

session.setDynamicPrompt({
  current_section: 'pricing',
  section_summary: 'Enterprise tier: $499/mo, includes SSO and priority support.',
});

This is context, not speech — calling it does not make the avatar say anything. It just updates what the brain will read the next time it takes a turn, whenever that happens to be (a user's next utterance, a tool call response, or an active nudge — see below).

Reach for this over request_vars when you have more than a handful of named values, when the shape changes turn to turn, or when your prompt needs to reason over the data rather than just substitute it into fixed template slots.

If you're using the SDK's Presenter plugin for a slide-deck-style walkthrough, it already manages per-slide dynamic-prompt injection for you — see README.md → Presenter for the deck-specific API surface. Presenter.refreshDpp() re-sends the current context outside of a navigation, which is exactly the building block the worked example below relies on.

The active nudge: speak()

speak() is the one mechanism that actually provokes a new turn — it's routed through the same pipeline as the viewer's own speech, so the brain reasons over it and responds, right then.

session.speak('Tell me about your pricing.');

You're not limited to putting the viewer's own words here. A common and effective pattern is to send a short, clearly-tagged app-generated message describing something that just happened in your UI, so the brain reacts to it immediately rather than waiting for whatever the next real user turn happens to be:

session.speak('[SECTION CHANGE] The viewer just opened the pricing section — discuss THIS section only.');

A bracketed tag like [SECTION CHANGE] is not a wire-level feature — it's a convention. If your system prompt is written to recognize a tag like this as an app-generated cue (as opposed to something the viewer said out loud), you get a clean, unambiguous signal to react to, without ever putting synthetic text in the viewer's own mouth. Design your own tag vocabulary to match whatever events your app needs the brain to react to instantly.

Pair it with a context update, in this order: call setDynamicPrompt() (or Presenter.refreshDpp()) first, then speak() immediately after. That way the nudge that provokes the turn arrives after the context it needs to reason correctly about is already in place, not racing it.

Answering a brain-initiated request

The three mechanisms above all push data from your app to the brain. There's also a path in the other direction: your agent's configuration can require the brain to ask the viewer for specific structured fields at some point in the conversation (an email, a booking date, a support ticket's category) — see STRUCTURED-DATA-FORMS.md for how to configure what it asks for. Once your UI collects the viewer's answer, hand it back with:

session.submitStructuredDataForm({ email: 'ada@example.com' });

This routes the value back into the conversation so the brain can act on it. Treat the moment your UI closes that form (submitted or explicitly declined) as a real, immediate event worth an active nudge too — see the worked example below.

How they work together — a worked example

Consider an interactive product walkthrough: viewer identity is known at connect, the UI advances through sections as the viewer explores, and partway through, the agent asks the viewer for an email address via a structured data form.

// 1. Connect — personalization the prompt substitutes via {{user_name}}.
const session = new KalturaAvatarSession({ token, /* … */, requestVars: { user_name: 'Ada' } });
await session.connect();

// 2. The viewer navigates to a new section — refresh context, then actively nudge.
function onSectionChange(section) {
  session.setDynamicPrompt({ current_section: section.id, section_summary: section.summary });
  session.speak(`[SECTION CHANGE] Now viewing "${section.title}" — discuss THIS section only.`);
}

// 3. The brain asks for an email; your UI renders the form and the viewer submits it.
function onEmailFormClosed(email) {
  if (email) session.submitStructuredDataForm({ email });
  // The form closing either way is a real, immediate event — refresh context, then
  // nudge, exactly like a section change, rather than waiting for whatever turn
  // happens next to pick it up.
  session.setDynamicPrompt({ email_form_status: email ? 'submitted' : 'declined' });
  session.speak(`[EMAIL FORM] The form just closed — the viewer ${email ? 'submitted an email' : 'declined'}.`);
}

The pattern that repeats: update context, then actively nudge, in that order, for anything that just happened and needs an immediate reaction. Use request_vars only for the slow-changing facts that rarely change after connect.

Which one do I want?

I need to… Use
Substitute the viewer's name/tier into my prompt template request_vars (updateRequestVars())
Give the brain a full snapshot of what's on screen, for whenever it next speaks setDynamicPrompt()
Make the brain react to something right now, not whenever it next happens to speak setDynamicPrompt() + speak()
Get an answer back for a field the brain itself asked for submitStructuredDataForm()

If instead you need the avatar to drive your UI — navigate, open a panel, highlight something — that's a different, silent channel (session.onToolCall()), not a data-injection mechanism. See CLIENT-COMMANDS.md.

Doc What it adds
README.md → {{var}} Jinja personalization The request_vars API reference
README.md → Experience KalturaAvatarSession, the live session this doc's worked example builds on
README.md → Presenter The deck-walkthrough plugin's refreshDpp() and full API surface
STRUCTURED-DATA-FORMS.md Configuring what the brain asks the viewer for, and how it's rendered
CLIENT-COMMANDS.md The avatar-driving-your-UI channel — the opposite direction from this doc
WIRE-PROTOCOL.md The exact socket events behind each mechanism, for anyone debugging at the wire level
Click to talk with Nova — she knows this whole SDK.