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 character1 of 8

The soul file

About 6 minutes to read

On this page

soul/default.md is who she is. It is one markdown file, 293 lines in the version that ships, and it is the only file you edit to change her character. Nothing about her personality is compiled in.

The parser is api/src/kotoba/soul/loader.py. It reads exactly two things: a block of key: value lines at the top, and a set of ## Section headings below. Everything else in the file — the prose, the comments, the horizontal rules — is either content of a section or ignored.


What it looks like

markdown
# SOUL

name: Kotoba
language: auto
voice_id: JTlYtJrcTzPC71hMLOxo
avatar_model: mao_pro/runtime/mao_pro.model3.json

---

## Personality
...

## How to address the user
...

## Emotional rules
...

## Tool voice patterns
...

## Quirks
...

The --- on its own line separates the frontmatter from the body. The parser takes the FIRST line that is exactly --- as that separator. Everything above it is frontmatter; everything below is the body.


The frontmatter

Four keys are read. Anything else on those lines is silently ignored — a mood: cheerful you invent does nothing and raises nothing.

KeyWhat it doesDefault when absent
nameHer name. Goes into the prompt's first sentence: You ARE Kotoba, a voice-first AI companion.(unnamed — ask the user during onboarding) appears in the prompt
languageHer reply language, and — when it is an ISO code — the transcriber's language too. See Languageauto
voice_idThe ElevenLabs voice idnone
avatar_modelWhich Live2D model she wears, as <dir>/<entry>mao_pro/runtime/mao_pro.model3.json

A # comment at the end of a value is stripped: language: auto # detect stores auto.

The one way to get the frontmatter wrong

Do not fence the frontmatter YAML-style. Skills use --- above and below their frontmatter; the soul file does not. Fencing it puts the separator on line 1, so the whole block lands in the body and every key is lost — silently:

---
name: Aria          ← this file boots as an UNNAMED companion on the DEFAULT language
language: es
---

Measured with parse_soul_markdown: that file yields name=None, language='auto'. There is no warning. kotoba doctor will not catch it either — it checks that the file was found, not that it parsed the way you meant.


The five sections

Only five headings are recognised. The heading text is matched case-insensitively against this list; a heading you invent is not an error, its content simply never reaches her.

HeadingColumnWhere it ends up
## PersonalitypersonalitySystem prompt, under ## Your personality
## How to address the useraddress_styleSystem prompt, under ## How you address the user
## Emotional rulesemotional_rulesSystem prompt, under ## When to feel what
## Tool voice patternstool_patternsNot the prompt. Parsed into the runtime's spoken-line table — see Tool voice patterns
## QuirksquirksSystem prompt, under ## Your quirks

Four of the five go into the system prompt verbatim, byte for byte. Measured on the shipped file:

personality      3,392 chars   → in the prompt
address_style      575 chars   → in the prompt
emotional_rules  1,661 chars   → in the prompt
quirks             773 chars   → in the prompt
                 -----
                 6,401 chars of a 32,953-character system prompt (19.4%)

tool_patterns    7,854 chars   → NOT in the prompt

So about one fifth of what she is told is yours. The other four fifths are written in api/src/kotoba/soul/prompt.py — the speaking rules, the tool descriptions, the platform note, the clock — and are not editable from the soul file. See Prompt versus code.

A section you delete is not an error either. It falls back to the loader's _DEFAULTS: an empty string for four of them, and the literal word name for address_style.


Which copy is read

There are two identical copies of the file in the repository:

soul/default.md                            ← the one a clone reads
api/src/kotoba/data/soul/default.md        ← the one a wheel carries inside the package

They are byte-identical, and a test keeps them that way. api/tests/test_her_two_souls_are_the_same_soul.py compares every file under both trees and fails if one differs:

"They drift the moment somebody edits one, and nothing says so: the clone keeps working, and the wheel ships a personality that was corrected weeks ago."

If you are editing the repo, edit soul/default.md and copy it to the packaged path, or that test goes red.

Never edit the packaged copy directly. Which file a running install actually reads depends on how you installed:

From a clone

resolve_soul_path() tries, in order:

  1. <repo>/api/soul/default.md — does not exist in a normal checkout
  2. <repo>/soul/default.mdthis is the one
  3. ~/.kotoba/soul/default.md
  4. the packaged copy inside api/src/kotoba/data/soul/

Verified on this checkout:

$ kotoba doctor
  ok    soul        /path/to/kotoba-companion/soul/default.md

Edit soul/default.md in your clone.

From a wheel (pip install)

There is no repo, so REPO_ROOT and API_DIR both resolve to ~/.kotoba. The order collapses to:

  1. ~/.kotoba/soul/default.md
  2. the packaged copy inside site-packages

Before you run kotoba setup, the packaged copy is what loads. kotoba setup calls seed_home_copies() before it asks its first question, which writes:

~/.kotoba/soul/default.md
~/.kotoba/soul/templates/assistant.md
~/.kotoba/soul/templates/companion.md
~/.kotoba/soul/templates/study.md

From then on ~/.kotoba/soul/default.md is what loads. Edit that one.

Whichever install you have, kotoba doctor prints the resolved path. That is the answer, not this table.


What happens on upgrade

seed_home_copies() never overwrites a file that already exists. Measured: seed once, edit name: Kotoba to name: Yuki, seed again — the file still says Yuki.

That cuts both ways:

  • Your edits survive an upgrade. pip install -U replaces the copy inside site-packages and leaves ~/.kotoba/soul/default.md alone.
  • You never receive an improved default. If a later version rewrites the personality, you will not see it. Compare against the packaged copy yourself, or delete your home copy and re-run kotoba setup to get the new one.

Skills behave differently, and worse — see Skills.


Pointing at a different file

SOUL_PATH overrides the default. It is read once, at process start, in core/engine.py:

python
soul = await sync_from_file(db, os.getenv("SOUL_PATH", "./soul/default.md"))
bash
SOUL_PATH=/home/you/my-companion.md kotoba

The trap: a typo in a relative path loads the default instead

For any path other than the exact string ./soul/default.md, the candidate list ends with <repo>/soul/default.md "as a last resort so she still boots". Measured:

SOUL_PATH=./soul/templates/stuudy.md   →  loads soul/default.md, silently
SOUL_PATH=/tmp/does-not-exist.md       →  raises FileNotFoundError

Use an absolute path if you want a typo to be loud. Either way, kotoba doctor prints the file that actually resolved.


The three templates

soul/templates/ holds three alternative personalities, seeded into ~/.kotoba/soul/templates/ on a wheel install. Point SOUL_PATH at one to use it.

TemplateVoice settings it suggestsCharacter
companion.mdstability 0.4 · style 0.45Warmest and most expressive
assistant.mdstability 0.6 · style 0.15Steadier, more even delivery
study.mdstability 0.55 · style 0.25Clear for explaining, with some warmth

All three parse cleanly and all three ship with name: blank — onboarding asks. Two things to know before you switch:

  • Those ElevenLabs voice numbers are comments. Nothing reads them. They tell you what to set in the ElevenLabs dashboard yourself.
  • Each template defines voice patterns for only six tools (web_search, web_extract, memory_write, session_search, todo, clarify) against the default file's 27. The rest fall back to each tool module's built-in lines, which is a supported path, not a bug.

Making a change take effect

The soul file is read once, at process start. sync_from_file runs inside core.engine.start(), and every turn afterwards reads the database, not the file.

So: edit the file, then restart her. There is no hot reload, no watcher, and no /reload.

And restarting is not enough for two of the four frontmatter keys — see Changing who she is, which is the part that surprises everybody.