Prompt & overrides
This page is the contract behind everything an avatar says: what it knows about itself, and who gets to change it.
The one rule
What you edit in the dashboard becomes the avatar’s default. What you pass as an API parameter is a per-session override that never persists.
Both write to the same fields — system prompt, backstory, tools, config. They differ in durability, not in power:
| Where it is written | How long it lasts | Visible in the dashboard | |
|---|---|---|---|
| Dashboard edit | A new profile row | Until you change the default again | Yes, with history |
POST …/profiles | A new profile row | Same — this is the UI’s API | Yes |
Mint parameter (context.*, tools, config) | Nowhere | One session | No |
An API caller can do either. POST …/profiles is the “make this the new default”
call; the mint parameters below are the “just for this conversation” call. Reaching for the wrong
one is the mistake this page exists to prevent.
How a prompt is assembled
Every session composes one system prompt on the GPU box, in this order:
[Core] Casola-owned safety and speech rules — always present, never overridable<persona> the behaviour prompt ← system_prompt[Backstory] who this character is ← backstory[Tools] callable tool definitions ← tools[Language] reply-language preference ← response_language[Core] is owned by the box and ships with its code: reply length, no markdown, mirror the user’s
language, safety rules. Nothing you send can weaken or replace it — which is what makes it safe to
expose prompt editing to your own end users.
The resolution ladder
For each of system_prompt, backstory and tools, independently:
1. the session parameter (context.system_prompt / context.backstory / tools)2. the profile (pinned via profile_id, else the avatar's default profile)3. the sealed version (what the avatar was created with)The first layer that has a value wins. Layers replace; they do not stack.
Resolution is per field. Overriding one field leaves the others resolving normally:
{ "avatar_id": "019…", "context": { "backstory": "A support specialist at Acme." } }→ backstory from the session, system prompt from the profile, tools from the profile. Not “the session layer wins everything”.
Worked example
Given avatar Lyra:
| Layer | system_prompt | backstory | tools |
|---|---|---|---|
| Sealed version (created by the wizard) | “You are Lyra…" | "A former archivist…” | — |
| Default profile (edited in the UI) | “You are Lyra, Acme’s guide.” | — | lookup_order |
| Session parameters | — | “Speaking with a returning customer.” | — |
Resolved for this session:
| Field | Value | source |
|---|---|---|
system_prompt | ”You are Lyra, Acme’s guide.” | profile |
backstory | ”Speaking with a returning customer.” | session |
tools | lookup_order | profile |
Note the backstory: the profile had none, so without the session parameter it would have fallen through to the version’s “A former archivist…”. Falling through is per field, and goes all the way down.
Replacing vs. adding
Two different jobs, two different parameter families. This is the second most common mistake after confusing defaults with overrides.
| Parameter | Effect | Cap | |
|---|---|---|---|
| Replace | context.system_prompt | Discards the profile/version prompt entirely | 40,960 |
context.backstory | Discards the profile/version backstory | 16,384 | |
tools | Discards stored tool config | — | |
| Add | extra_system_prompt | Appended after whatever won | 2,048 |
extra_backstory | Appended to the backstory block | 2,048 | |
extra_tools | Merged into whatever tools resolved | — |
If you want “the avatar as authored, plus one sentence about this caller”, you want the extras, not a replacement:
{ "avatar_id": "019…", "extra_system_prompt": "The caller is Dana, a Pro customer since 2024. Their last ticket was about billing.", "extra_backstory": "You and Dana have spoken twice before."}Restating the whole persona just to append a sentence is the anti-pattern extras exist to remove — it also costs first-turn latency (see Performance). Combined extras are capped at 3,072 characters because they ride inside the session token itself.
Use a replacement when the avatar genuinely plays a different role this session — the same face and voice hosting a different scripted scenario, for example.
Pinning a profile for one session
{ "avatar_id": "019…", "profile_id": "019fe4a0-…" }Runs a non-default profile without changing anything stored — A/B testing a
prompt, or a “chat with this version” affordance in your own UI. The profile must belong to the
resolved avatar (400 profile_mismatch): asking for specific text and silently getting different
text is precisely the failure profiles exist to remove.
Pinning the text with profile_id is orthogonal to pinning the assets with
avatar_version_id — see Avatars. The two compose freely.
Knowing what actually ran
Every mint response echoes the text layer it resolved:
"profile": { "id": "019fe4c1-…", "hash_short": "a2271f20509d", "source": "default" }source | Meaning |
|---|---|
param | You pinned it with profile_id |
default | The avatar’s default profile |
none | No profile — the sealed version’s own text ran |
Log hash_short alongside your own session records and “which prompt was this user talking to?”
stays answerable months later.
Credential fence
Every parameter on this page is secret-key only. A publishable, device or trusted-issuer
credential sending any of them gets 403 tools_context_not_allowed (or
403 profile_pin_not_allowed for profile_id). context.memory_id is on the same fence, and so
is erasing a memory store — whatever credential can create one can destroy it, and
nothing weaker can do either.
That is deliberate: a publishable key ships to browsers where anyone can read it, and choosing which prompt runs is a server-side decision. Set overrides in your own backend, in the same place you decide which user gets which avatar.
Limits
| Field | Cap (characters) |
|---|---|
context.system_prompt / profile system_prompt | 40,960 (~10k tokens) |
context.backstory / profile backstory | 16,384 |
extra_system_prompt | 2,048 |
extra_backstory | 2,048 |
| Combined extras | 3,072 |
context.history | 40 messages / 16,384 bytes |
context.memory_id | a UUID (see Avatar memory) |
config (serialized) | 1,024 bytes |
Caps are characters, not tokens (roughly 4 characters ≈ 1 token).
Exceeding a cap on a session parameter is a 400 — it is your input, so you should hear about
it. Exceeding it on a stored profile clamps at mint with a server-side warning rather than
failing the session, because stored data may have been authored by someone else at another time and
a live call is the wrong place to discover it.
Performance
The composed prompt is re-sent to the language model on every turn. The [Core] block and the
avatar’s own bundled persona are shared across sessions and stay warm in the model’s prefix cache;
a session-level override breaks that sharing beyond [Core] for that session, so the first turn
pays extra prefill proportional to prompt size. Later turns in the same session are cached.
- Under ~3,000 characters: no perceptible effect.
- Above that: expect the first reply to start slightly later. The dashboard’s prompt editor shows a size meter for this reason.
- Prefer
extra_system_promptover restating the persona — it is small by construction and keeps the shared prefix intact.