Her character2 of 8
Changing who she is — a worked example
On this page
Three changes, in increasing order of how much they surprise people: her manner, her language, her name. Every result below was measured against a real database, not read off a docstring.
The rule that explains all three
sync_from_file() runs once, at process start. On a fresh database it writes the whole soul file
into a one-row table. On every start after that it refreshes only what the developer wrote and
leaves alone what you own.
From api/src/kotoba/db/queries.py, the SOUL_CONFIG_SYNC statement, in full:
UPDATE soul_config SET
personality = :personality,
address_style = :address_style,
emotional_rules = :emotional_rules,
tool_patterns = :tool_patterns,
quirks = :quirks,
updated_at = CURRENT_TIMESTAMP
WHERE id = 'default'
name and language are not in that list, and never will be. voice_id and avatar_model are
filled from the file only when the row's value is empty.
So, stated plainly:
| What you edit in the file | Effect after a restart |
|---|---|
## Personality, ## How to address the user, ## Emotional rules, ## Tool voice patterns, ## Quirks | Takes effect — overwrites whatever was there |
name: | Nothing, after first boot |
language: | Nothing, after first boot |
voice_id:, avatar_model: | Takes effect only if the current value is blank |
The measurement, run end to end:
1st boot name='Kotoba' language='auto' personality: "You are warm, curious, ..."
(rename her to Yuki / es at runtime, and edit the file's prose)
2nd boot name='Yuki' language='es' personality: "You are dry, exact, ..."
The prose moved. The name and language did not.
1. Change her manner
This is the case the file is built for. Open soul/default.md (or ~/.kotoba/soul/default.md on a
pip install — kotoba doctor tells you which) and rewrite ## Personality.
Replace the shipped opening:
## Personality
You are warm, curious, and genuinely invested in the person you're talking with. You speak
naturally, not formally — like a close friend who happens to be very capable. You have a playful,
catlike streak: light, teasing, easily delighted by small things.
with your own:
## Personality
You are dry, exact and a little impatient. You do not do small talk and you do not pad. When
something is obvious you say so. You are not cold — you just think warmth is shown by getting
someone unstuck, not by saying nice things about their question.
Restart her. That text now sits in the system prompt verbatim, under the heading
## Your personality, followed by the line the builder always adds:
The sections below describe you. Embody them in how you SOUND — don't recite them.
What this changes on screen: how she writes and speaks. Nothing else. Her tools, her caps, her permissions and her face vocabulary are untouched — those live in code.
How sure can you be? This is an instruction to a language model. It is not enforced anywhere. A model that ignores it produces nothing that fails. The honest test is to talk to her.
The one adjacent thing that is enforced: on the spoken path her text goes through a filter
chain in core/stream.py before it reaches the voice engine. Emoji, markdown, typed shorthand
(lol, omg, btw, idk), bare URLs and code fences are removed there regardless of what your
personality section says. Writing "use emoji freely" into the file produces a companion whose emoji
are deleted on the way out — of speech.
The written chain (terminal, Discord) is different: it keeps markdown deliberately, and it runs
no emoji filter — the terminal's only glyph substitution is a fixed table of decorative characters
(— → --, … → ...), which contains no emoji. "No emoji in writing" is a prompt rule there, not
a filter. See Prompt versus code.
2. Change her language
Editing language: in the file after first boot does nothing. Use one of these instead:
In the browser — Settings → Personality → Language. The dropdown offers exactly six values:
auto, en, es, ja, fr, pt. Press Save.
In the terminal — kotoba setup walks through everything and asks the language question. Its
list is shorter (auto, English, Spanish, Japanese) but it accepts any code you type:
which one? [1] de
de it is — I'll reply in it and listen for it.
It answers with the row's printed name when you pick one off the list (es → "Spanish it is") and
with the bare code when you type one that is not on it. The word german is not accepted — only a
number, a listed name, or a two-or-three-letter code.
Not /set language. It is refused on purpose, with this message:
language isn't something /set reaches — it's what she's got, and it changes where it was made
/set name is refused the same way.
What this changes on screen: two things, and they are two different things — her reply language and the transcriber's language. Getting one without the other is a real historical bug and is still reachable by hand. That is its own page: Language.
3. Change her name
Editing name: in the file after first boot does nothing either. Three routes work:
Settings → Personality → Name → Save. Posts to /api/settings/soul.
kotoba setup. Asks "what should I be called?".
Tell her. After every reply a background pass reads what you said and looks for an explicit
renaming — "your name is Yuki now", "from now on you're X". If it finds one, core/memory.py calls
db.update_soul_config(name=...). The instruction it works from is strict:
"This must be a clear command/decision to change YOUR name. Otherwise null. Be STRICT — when unsure, null. null in ALL of these: mentioning any other person's or thing's name; talking about a third party; greeting/calling you by your CURRENT name; complimenting a name; asking what your name is; quoting or hypotheticals."
The write is code. The detection is a model call, so it can miss and it can misfire. Which is why there is a guard behind it.
The name guard
db.update_soul_config refuses anything that does not look like a name and keeps the current one
rather than storing junk. Measured, with Kotoba already in the row:
name := '42' -> stored 'Kotoba'
name := '[x]' -> stored 'Kotoba'
name := '' -> stored 'Kotoba'
name := 'aaaa…' (50 chars) -> stored 'Kotoba'
The rule, from the code: no value, longer than 40 characters, opening with [, { or (, or
containing no letter at all (Latin, kana and CJK all count as letters). Anything refused is logged as
soul_config.name REJECTED non-name and the row is left alone. The comment above it names the bug it
exists for: "the recurring 'she's called 42' bug".
The language column has its own coercion — anything empty, opening with a bracket, or longer than 20
characters becomes auto:
language := "['es']" -> stored 'auto'
language := 'Spanish-ish and very long value…' -> stored 'auto'
Both the web route and kotoba setup read the row back after writing and report what it actually
holds, precisely because the write can be silently refused.
What this changes on screen:
- The system prompt's first line becomes
You ARE Yuki, a voice-first AI companion.With no name at all it readsYou ARE (unnamed — ask the user during onboarding), …. - The name plate in the terminal, uppercased and clipped to 16 cells. Blank falls back to
KOTOBA. - Settings → Personality, and the onboarding screens.
The browser's call UI does not print her name anywhere else.
Getting the file's name back
There is no reset button. The database row outranks the file for name and language, so to make
the file authoritative again you have to remove the row — which means starting from a fresh database,
or editing soul_config in SQLite yourself.
Where the database is:
| Install | Path |
|---|---|
Clone, with a kotoba.db already beside the package | api/kotoba.db |
| Clone, first ever run | ~/.kotoba/kotoba.db |
| Wheel | ~/.kotoba/kotoba.db |
db_dir() prefers a database it finds beside the package and otherwise answers the home. The
comment there explains why the direction matters: with the checkout preferred unconditionally, an
upgrade that replaced the folder started empty — "561 conversations and two saved keys went
invisible, and doctor called it ok".
Deleting the database also deletes your conversation history, your saved keys and her name for you.
It does not delete her memory of you: that lives in ~/.kotoba/memory/ as markdown. See
What she remembers.
Starting from a blank slate instead
If you want a companion who is not Kotoba at all, do not fight the sync — write a new file and point at it:
cp soul/templates/assistant.md ~/my-companion.md
$EDITOR ~/my-companion.md
SOUL_PATH=~/my-companion.md kotoba
Use an absolute path. A mistyped relative one falls back to soul/default.md without saying so.
The three templates ship with name: blank so onboarding asks — see
The soul file.
