Settings
What the org set for an agent, per environment, a version a row; and the org's words.
What an agent runs on — which vendors and models, how a call opens and ends, how a turn is cut,
what is remembered, which knowledge bases it reads, and its lexicon, how the voice says a brand and
what the ears must know — is the org's to set here, per environment, per scope, a version a row,
and a call's head row says which versions it ran on. The class declares the contract, the tools,
the state, the endpoints, and may declare any of this too: what the class declares wins over
these settings for its agent (the wire's AgentConfig), so a setting it declares is listed under
fixed and refused here with 409. The environment is put on the declaration at the one place
every call is built, and a knob nobody set is the runtime's default.
Two kinds of key open them. A key that opens pipeline (a developer's, an admin's) may set
everything. A key that opens words alone (a supervisor's, a manager's) may set the opening's
words, what is remembered and the agent's lexicon, and is refused the vendors, the models, the cut of
a turn, the model's deadline, the bases, the recording and the duration by name: 403 llm, turn: the pipeline's, and this key opens words alone. Its set carries those fields over untouched from what stands.
Three scopes. In the sandbox a person's key has a scope of its own: what they set is theirs
until they set it with team: true, and a colleague's next call does not hear it. team: true
writes the org's own scope, which every scope falls back to. A key that names nobody (CI's) writes
the org's own scope always. Production has one scope, the org's own, set directly by a request that
runs there (pinecall-env: production): a person with production access, or a production server's
key.
Every knob falls through on its own. A scope supplies the knobs it set, and each knob it did not set is read from the scope below it, never the whole row of the first scope that has one. Setting a voice of your own does not disconnect you from the team's model:
| set in | voice |
llm |
stt |
|---|---|---|---|
| yours | nova |
||
| the team's | anthropic/claude-sonnet-5 |
soniox |
|
| what the next call runs | nova |
anthropic/claude-sonnet-5 |
soniox |
A knob is set when it is in the row, which is what leaving it out of a PUT decides. A knob set
to a falsy value is set and wins over the scope below (turn {endpointing_ms: 0}, bases []); only
an absent knob falls through. The version a call records is the nearest scope that supplied a knob.
Versions. Every set is a new row; nothing is updated, nothing deleted. The body carries the
version it was read at, and a scope that moved on since answers 409 this corner is at v{n} now.
A rollback is a new version equal to an old one, and the history says so.
GET /v1/agents/{slug}/settings — pipeline or words#
{ "world": "sandbox",
"yours": { "holder": "m_ana", "version": 4, "author": "m_ana", "note": "flat", "set_at": 1758300000, "config": { "voice": "amelia" } },
"team": { "holder": "", "version": 11, "author": "m_bruno", "note": null, "set_at": 1758200000, "config": { "llm": "anthropic/claude-haiku-5-5", "greeting": { "say": "Buenas…" } } },
"production": null,
"fixed": ["voice"] }
fixed names the settings the class holding the agent declares itself, by the declaration's names
(language, voice, llm, stt, judge, greeting, hangup, turn, knowledge, docs, memory,
record): these run as the class says whatever the rows below hold. Empty when no app holds the
agent here, or its class declares none. Each scope's own newest, or null when that scope set nothing; never the fallthrough, because
if_version is about the scope being written. config carries only the knobs the row set: voice,
tts, tts_model, stt, llm (the three model knobs take vendor/model, a vendor alone to keep
its own default model, or a model alone on whichever vendor is in use), temperature (the
model's, in its vendor's range; unset, the vendor's default), end_of_turn (who ends the
caller's turn: stt, the ears themselves where the row says they can, livekit or smart-turn, a
model on the worker; unset, the row's choice for the vendor), llm_builds, stt_builds,
tts_builds (a class of the stage's plugin other than its default, dots reaching into a module of
it: responses.LLM) and llm_options, stt_options, tts_options (that class's keyword arguments
as the plugin names them, each replacing the providers row's of the same name; both only on the
org's own key for the vendor, 409 on a lent one; set on an agent whose row names no vendor for
the stage, these name the one in use, which then runs without the default's fallbacks), judge,
judge_builds, judge_options (the model the agent's calls are judged on, named as llm is, over
the org's choice at PUT /v1/org/judging and the platform's; on a key of the org's own, a local
model's server among them through judge_options {"base_url": …}, its evals are never billed —
Evals), language (a tag, en or
pt-BR, given to the ears and the voice; unset, each vendor's own default), greeting ({say} or
{reply}, an empty reply the model opening on its prompt alone, and allow_interruptions, false
unless set), hangup {when}, turn {min_interruption_words, endpointing_ms, eot_threshold, eager_eot_threshold, min_interruption_ms} (the last is how long the caller must speak over the
agent before it stops; unset, livekit's own half second), memory {remember, forget}, record, max_duration_s (voice calls; 0 is
no limit), llm_timeout_s (how long a turn waits for the model's first word, livekit's retries
included; past it the caller hears a short sentence asking them to say it again, in the agent's
language (Spanish, English, Portuguese; English otherwise), and the call's log says error {code: llm_timeout}; unset, 12 s), knowledge (Markdown read whole into the static block of every call) and bases [{base, mode, k, min_score}].
PUT /v1/agents/{slug}/settings — pipeline or words#
{ "config": { "voice": "amelia", "llm": "anthropic/claude-haiku-5-5" }, "if_version": 4, "note": "cleaner", "team": false }
The whole set for this scope. It is checked as a call would be built from it: the declaration
when an app holds the agent, a bare one otherwise, its lexicon in the scope, and the vendors on the
org's keys, so a vendor this build has no plugin for, a blank knob, an opening with both verbs or a turn or
voice knob the vendor takes under no name (Provider keys) is 400 in its
own sentence, and a vendor the platform does not lend this org — for a stage or for the judge —
is refused here and not on the next call. A pipeline key's set is held to the bands the ears take the thresholds in —
eot_threshold 0.5 to 0.9, eager_eot_threshold 0.3 to 0.9, outside which they refuse the
connection — and 400 eot_threshold 0.1 is outside 0.5 to 0.9 otherwise; a version kept before
this check is still read. A stage the set changes — the voice, its vendor or model, the model, the
ears — is tried once before anything is kept, on the key a call would use: a line said, one word
answered, half a second of silence heard (ears that take only a whole utterance are tried by their
first call). The vendor's no is 400 acme refused the voice this sets: no such voice, in its words;
a vendor that does not answer is 502 … nothing was kept, try again. A save that changes no stage
asks no vendor anything. 409 when the scope is not at if_version, and 409 voice set by the class of clinica-norte: … when the set changes a knob the class declares (tts, tts_model,
tts_builds and tts_options are the class's voice; temperature, llm_builds and
llm_options its llm; stt_builds, stt_options and end_of_turn its stt; judge_builds and
judge_options its judge; bases its docs), before
anything is tried or kept. A set whose plugin class or options would run on a key the platform
lends is 409 llm options and builds run on the org's own openai key, and this agent's llm would run on the platform's: …, nothing kept. Answers the GET shape.
GET …/settings/history?team=&limit= · GET …/settings/diff?against=team|production · POST …/settings/rollback {version, team}#
One scope's versions, newest first, each with who set it and why. This key's scope's newest
against the team's or production's newest, with the fields that differ by name. One version copied
forward as the next one, note: "rollback to v{n}"; 404 for a version the scope never had.
Rollback takes pipeline.
A version on a share of the calls — GET · PUT · DELETE /v1/agents/{slug}/settings/canary#
A new version need not take every call at once. PUT {version, share, note, team} puts one of
the scope's own versions (yours, or the team's with team: true) on share calls in a hundred
(0 to 100): each call is picked once, by its id, where its settings are resolved (the worker asks
for them naming the call it is about to open, ?for_call=, and the open records the same pick),
so a call asked for again, or reopened by a gateway that restarted, keeps its version. Every other
call runs what the scope would run without that version: the newest of its others. Setting it
takes pipeline, as the vendors do: a canary decides what a share of the calls runs. A share of 0
keeps the version off every call and the canary standing; DELETE ?team= clears it, and every call
runs the scope's newest version again (the canary's, when it was the newest: that is promoting it).
A version the scope never had is 404. GET ?team= (pipeline or words) and every endpoint answer
{world, holder, canary: {holder, version, share, author, note, set_at} | null}; an older worker,
which names no call, runs every call off the canary. The two versions are compared by
GET /v1/insights/drift?agent=&before=v3&after=v4 (Console API): the same
days, two versions, which judge and which stage moved.
PUT /v1/agents/recepcion/settings/canary
{"version": 4, "share": 10, "note": "shorter greeting", "team": true}
{"world": "sandbox", "holder": "",
"canary": {"holder": "", "version": 4, "share": 10, "author": "m_ana",
"note": "shorter greeting", "set_at": 1790000000.1}}
GET /v1/calls/{call}/settings — calls#
The exact tuning and lexicon the call was built on, by the two version numbers its head row kept:
{config_version, lexicon_version, config, lexicon, canary}, each row null where the scope had set
nothing; canary is true for a call a canary's share picked (its version was the canary's that
stood when it opened), false otherwise.
The lexicon — /v1/agents/{slug}/lexicon#
The agent's words: what the voice says in place of a word (says) and the words the ears must
know (hears). Each agent has a lexicon of its own, versioned per environment and scope as its
settings are, and a class that declares says or hears wins over it for that half. GET
answers {world, yours, team, production, fixed}, each scope {holder, version, author, note, set_at, lexicon: {said: [{word, spoken}], heard: [string]}} and fixed the halves the class
declares. PUT {lexicon, if_version, note, team} is the whole lexicon, with the same 409, a
409 hears set by the class of … when it changes a half the class declares, and a blank word
refused. GET /v1/agents/{slug}/lexicon/history?team=&limit=. All three open to pipeline
or words.