The web app9 of 12
Settings
On this page
Settings is an accordion of eleven sections, plus one that appears only when it has something to say. Every change is written to the backend as you make it — there is no global Save button — and the panel then re-reads the backend and draws what it got back.
Screenshot placeholder: the Settings panel open on the left of a narrowed stage — a white header strip reading "Settings", and a stack of collapsed section cards each with a coloured square, a title and a small grey summary on the right (Personality "Kotoba", Brain "gpt-5.6-luna", Security "local").
Settings is reachable before a call as well as during one (components/UIOverlay.tsx:150-152).
How a change lands
One writer handles every request. On a refusal it puts the backend's own sentence in a red banner at
the top of the panel; on success it re-reads /api/settings, so what you end up looking at is the
normalised value the backend kept, not the value you clicked
(components/panels/SettingsPanel.tsx:182-203).
The banner clears itself after 5 seconds (SettingsPanel.tsx:268-272).
There are two kinds of write, and they differ in who rolls back:
- Provider and model are written with nothing optimistic. A refusal leaves the select reading what
is really configured (
SettingsPanel.tsx:205-215). - Every other knob is written optimistically so the control does not flicker for the length of a
round trip — and a refusal therefore re-reads, or the panel would keep showing a value the backend
never accepted (
SettingsPanel.tsx:218-227).
A background re-read cannot clobber a Personality field you are still typing in: the panel marks
those three fields dirty on the first keystroke and refuses to overwrite them until you press Save
(SettingsPanel.tsx:162-171, 229-235, 274-277).
The sections
| Section | What is in it |
|---|---|
| Personality | her name, language, ElevenLabs voice id, avatar model, voice mode, voice engine, agent id |
| Brain | provider, base URL, API key, five per-role models, reasoning effort, expressive voice, the two setup doors |
| Capabilities | one toggle per tool family |
| MCP servers | connected external tool servers, with a connect control |
| Needs connection | only shown when servers are waiting for authentication |
| Plugins | community add-ons, one toggle each |
| Skills | the knowledge guides she consults — read-only |
| Memory | what she remembers, by topic, each removable |
| Reminders | scheduled reminders, each removable |
| Saved keys | credentials stored on the server, listed by name and never shown |
| Security | sandbox, trust level, the standing command grants, Sign out |
| Advanced (work) | the four work-mode limits and the browser CDP address |
(SettingsPanel.tsx:317-500.)
A test enumerates every block the panel asks the backend for and fails if any of them is requested but
never drawn — dead weight on the wire reads as a control somebody forgot to build
(tests/settings-payload-is-drawn.test.mjs).
Personality
Name, Language and Voice ID share one Save button (SettingsPanel.tsx:318-326). The name write is
read back, because the backend silently refuses a value that does not look like a name.
The avatar select lists what is installed and applies on reload, which its own label says. With
nothing installed it does not draw an empty select — a control with no options reads as a broken
control — it names the folder to unpack a model into instead (SettingsPanel.tsx:329-338).
Below a dashed rule sit three voice controls and a guide. Voice mode is labelled "applies to the
next call" and it means it: the call shell re-reads this value whenever the phase is offline and the
panel is shut, never mid-call, because a live call has its transport latched
(SettingsPanel.tsx:340-345, CompanionExperience.tsx:186-189, tests/settings-reach.test.mjs).
How to set up ElevenLabs → opens a Markdown guide in a modal (SettingsPanel.tsx:354).
Brain
Provider and model are one decision, not two keys. Both go through the guarded setup endpoints
rather than the raw runtime endpoint, because switching companies while the old model is still set
leaves a pair that cannot work — and "Test connection" then reports a perfectly good key as a bad one
(SettingsPanel.tsx:205-215, tests/settings-brain-pair.test.mjs).
The model menus list the active provider's models only. A menu mixing both companies offered ids
the install could only fail on (SettingsPanel.tsx:620-626). All five menus also accept a typed id
the catalogue does not list.
Five roles, each inheriting when left empty (SettingsPanel.tsx:376-388):
| Role | Falls back to |
|---|---|
| Companion model (voice) | — |
| Work model (background) | companion |
| Code model | work |
| Research model | work |
| Utility model (emotion / memory / captions) | companion |
Save key refuses an empty draft rather than treating it as "erase" — sending an empty value used
to delete the stored key (SettingsPanel.tsx:238-245). Remove is the way to erase one, and it asks
for confirmation first (SettingsPanel.tsx:247-255). Test connection does a small round trip and
never echoes the key (SettingsPanel.tsx:257-265).
At the foot: Run setup again and Give her a face, both full navigations into /setup.
Security
This is the section that changes what she is allowed to do on your machine.
Sandbox has three values (SettingsPanel.tsx:474-477):
| Value | Meaning |
|---|---|
local | code and shell run on this machine, gated by approvals |
docker | they run in a container |
none | execution is disabled — the select shows a red "Execution disabled." warning |
Trust level is displayed, read-only. It comes from KOTOBA_TRUST and defaults to workspace;
there is no live setter for it (api/src/kotoba/core/settings.py:127).
Allowed commands lists every standing grant, and each row says which width it is — just this line: …, running code (Python), or any "npm" command. The bin on a row revokes it
(SettingsPanel.tsx:479-491). With none saved it says so plainly: "None — she asks before anything
that isn't a simple read."
Sign out appears only when a password is configured.
No setting in this panel asks you to confirm. Changing the sandbox from
nonetolocal— which is the difference between "she cannot execute anything" and "she executes on this host, gated by approvals" — is a single select, applied immediately, with no second question. The only two confirmations anywhere in Settings are removing a stored API key (SettingsPanel.tsx:249) and, in Files, the two-click delete. Treat the Sandbox select as the serious control it is.
Advanced (work)
Four numbers, each committed on blur or Enter (SettingsPanel.tsx:495-498): work timeout in seconds
(minimum 30), max iterations, max tool calls and a fail limit (each minimum 1). A refused value —
below its minimum — is rejected by the backend and the panel re-reads rather than keeping what you
typed.
The browser CDP address is displayed, read-only.
Where a change lands on disk
Runtime knobs are validated against an allowlist and persisted to ~/.kotoba/settings.yaml; each call
site reads its value at request time, so a change applies to the next request rather than needing a
restart (api/src/kotoba/server.py:1170-1184).
API keys are a different store: verified before they are saved, encrypted at rest, decrypted only in
memory, never logged and never returned (api/src/kotoba/server.py:1187-1199). A refused key is a
400 and nothing is written at all.
What Settings does not do
- It does not let you rename the app's own labels. Her configured name does not reach the call shell — see The main screen.
- It does not expose the login password. That is an environment variable on the server.
- It does not show a conversation history. See Sessions and history.
