Kotoba

Where to start

What Kotoba isWho she is and where she runs, in one page
InstallingOne package, two commands
First runA key, a model, her first words
The approval gateHow she asks before she acts
The two voice modesLocal voice, or the agent tunnel
The soul fileChange who she is
RoadmapWhat grows next, and what was cut on purpose

Or a section

↑↓ move openesc close124 pages
All pages

Her character2 of 8

Changing who she is — a worked example

About 6 minutes to read

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:

sql
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 fileEffect after a restart
## Personality, ## How to address the user, ## Emotional rules, ## Tool voice patterns, ## QuirksTakes 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 installkotoba doctor tells you which) and rewrite ## Personality.

Replace the shipped opening:

markdown
## 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:

markdown
## 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 terminalkotoba 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 reads You 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:

InstallPath
Clone, with a kotoba.db already beside the packageapi/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:

bash
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.