Skip to main content
It tells you what you may put in variables without reading anyone’s prompt or calling GET /v1/copilots/{id} yourself.

Context vs. variables

Both feed data from your app into the agent’s system prompt, but they arrive there in completely different ways — and they are not interchangeable.
With a prompt block that reads:
the agent receives …on the pro plan with 12 seats. Address them as "Maya" and reply in FR. — the placeholders are gone by the time the model sees the prompt. context, by contrast, is never templated: whatever strings you pass show up as their own section, unchanged.

Nested objects and dot paths

A prompt block can read a dot path — {{user.language}}, {{account.plan.tier}} — and you can fill it either way. Pass the whole object as it already exists in your app, or a dotted key; the API expands a dotted key into the nested form, so both spellings land in the same place:
You can hand the whole object straight through — no flattening step:
The one case to avoid is a genuine conflict — the same path given as both a scalar and a namespace ({ user: 'Maya', 'user.language': 'FR' }). user can’t be a string and an object, so the first one in the payload wins and the other is dropped.

Rules for variables (enforced by the API)

  • Every dot-separated segment must be a plain identifier — letters, digits, underscores, starting with a letter or underscore.
  • Values are a string, number, boolean, null, an array of those, or an object of the same.
  • Paths go up to 3 segments deep — {{a.b.c}} yes, {{a.b.c.d}} no — however you spell them.
  • An array is kept or dropped whole and renders as JSON if interpolated directly; its real use is contains / in display conditions.
  • Up to 64 values (each leaf counts, however deeply nested), 2 KB per value, 16 KB total. A null leaf is “no value” — it’s not stored and doesn’t count, so an unfilled leaf costs you nothing.
  • Anything breaking those rules is dropped, not rejected — a display-only field must never fail a chat turn. So a typo’d key fails silently; check what your agent actually renders.
  • A placeholder with no value and no default is left visible ({{userName}}) rather than blanked, which makes a missing value obvious in the answer.
  • The now.* namespace ({{now.datetime}}, {{now.date}}, {{now.iso}}, {{now.timezone}}) is filled in server-side on every turn and cannot be overridden — a key whose first segment is now is dropped either way it’s spelled, so don’t send your own clock.
Variables also drive display conditions: a prompt block can carry rules (e.g. plan in ['pro','enterprise'], user.language exists) that decide whether the block is included at all, evaluated against the same values.

Discovering which variables to send

You don’t have to read the prompt to know what an agent expects — getSidenetVariables() returns the {{placeholders}} the copilot’s agents actually declare, in the same nested shape you send back:
Each leaf is what that path renders when you omit it (the block’s {{plan || 'free'}}'free'), or null when it has none — in which case a literal {{plan}} ends up in the prompt. The nulls are exactly what you have to fill in. Keys come back alphabetical at every level. Because it’s the same shape, the object round-trips: fill in the leaves you have and send the whole thing back, no reshaping and no need to strip what you couldn’t fill.
To list the paths instead of walking the tree, use flattenSidenetVariables():
A path is itself a valid key to send ({ 'instance.currency': 'EUR' }), per the dot-path section above.

Return types

getSidenetVariables() returns SidenetDeclaredVariables — an object whose leaves are string | null and whose branches are more of the same: flattenSidenetVariables(declared) returns SidenetVariablePath[]: Notes:
  • Served from the object cached during initSidenet(), so it’s free after boot. It fetches once only if that init fetch failed. Call refreshSidenetConfig() to pick up prompt edits made since.
  • Keys are alphabetical at every level.
  • Throws if the SDK isn’t initialized, or if you initialized with only an agentId / agentVersionId — with no copilotId there’s no network to enumerate.
  • An empty object is a real answer: the agents use no placeholders, or they still run the legacy single prompt string, which isn’t templated.
  • Sending a null leaf back is a no-op — not stored, not counted against the 64-value limit, not reported as dropped. A namespace whose leaves are all null is dropped whole, so a wholesale {{instance}} stays visible rather than rendering {}.
  • A path that a prompt reads both wholesale ({{user}}) and by sub-path ({{user.name}}) comes back only as the namespace — that combination isn’t fillable both ways, since { user: 'x', 'user.name': 'y' } is rejected as a conflict.
  • Server builtins (now.*) are excluded — you can’t supply those.
Breaking change. getSidenetVariables() previously returned SidenetVariable[][{ name, default, usedBy }]. It now returns the nested object above, and usedBy is gone from the API. Code that read variables[].name should switch to flattenSidenetVariables(declared) and read .path. The SidenetVariable type is no longer exported.

Both are updatable at runtime

Neither context nor variables is init-only. updateSidenetConfig() applies both to the next send — a response already streaming keeps the values its turn started with, and the conversation is not reset:
Each call replaces the whole value rather than merging into it — pass the full set you want the next send to use, and {} (or []) to clear.
variables are display-only and untrusted. They’re rendered into the system prompt and read by nothing else — no authorization, identity, billing, or routing decision uses them (identity comes from your session token). A value that originated with an end user lands verbatim in a system message, so have the prompt block delimit it — User's stated name: "{{userName}}" rather than dropping it mid-sentence where it reads as an instruction.