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

What is enforced, and what is only asked

About 7 minutes to read

On this page

Her character is a prompt. A prompt is a pile of instructions, and an instruction is not a guarantee.

This page separates the two, because the difference decides what you can rely on. If a rule lives only as a sentence in soul/default.md or prompt.py, a model that ignores it produces nothing that fails, nothing that logs, and nothing that a test would catch.


How big each half is

Measured by building a real system prompt from the shipped soul file, with the three shipped skills listed, a one-line profile and one memory fact:

the whole system prompt           32,953 characters
of which yours (the soul file)     6,401 characters   (19.4%)

The total moves with your memory, your skills and your toolset. The soul half does not.

The 6,401 are ## Personality, ## How to address the user, ## Emotional rules and ## Quirks, inserted verbatim. The remaining ~26,500 are written in api/src/kotoba/soul/prompt.py: the speaking rules, the tool descriptions, the platform note, the clock, the emotion block. You cannot edit those from the soul file.

## Tool voice patterns — another 7,854 characters — is not in the prompt at all. It is a runtime table the loop speaks from directly.


Enforced in code

Each of these happens whether or not the model cooperates.

Her output

RuleWhere
Only the 19 audio tags survive into speech; anything else is strippedAudioTagFilter + ALLOWED_AUDIO_TAGS
Emoji are removed from the spoken streamForbiddenPhraseFilter / _EMOJI_RE
Markdown (**bold**, ## headings, - bullets) is removed from speechsame
Typed shorthand (lol, omg, btw, idk) is removed; real laughter (haha) is kept_SLANG_RE
Bare URLs are removed before speechUrlFilter
Fenced code and inline backticks are removed from speechCodeFenceFilter
A false "I can't reach the web" claim is rewritten into an in-character line (or dropped when it arrived in another language)ForbiddenPhraseFilter
Leaked tool-call syntax is strippedToolCallLeakFilter

On the written surfaces (terminal, Discord) the chain differs — it keeps markdown, which is the whole point of writing, and adds a CitationFilter the spoken chain does not use.

Measured through the real chain:

'That is great <party emoji> lol!'          -> 'That is great!'
'Check https://example.com/x for details.'  -> 'Check for details.'
'Here: ```python\nprint(1)\n``` done.'      -> 'Here: done.'
'## My skills\n- **Frontend design**'       -> 'My skills\nFrontend design'
'Let me run that real quick~'               -> 'Let me run that real quick'

So "never use emoji" and "never speak a URL" are real. "Sound warm" is not.

Her loop

RuleValue
Tool calls per turn8 companion, 40 work
Failed calls in a turn before tools are withdrawn2 companion, 6 work — a running total for the turn, not a consecutive streak
An exact repeat — same tool, same arguments — is not executedthe call is replaced with an instruction to continue from the earlier result
Calls to one tool per turn3, except delegate 3, todo/cronjob 10, browser__* 40 in work mode
A tool exception never reaches the model as a tracebackthe loop returns "The tool errored." / "The tool timed out."
Heartbeat cadencefirst at 9 s, then every 21 s
Canned narration is suppressed for a reasoning model, for MCP tools, and for a written register_heartbeat_lines + ctx.narrate_tools

That third row is worth noticing: # General rules: NEVER call the same tool twice with the same input is in the prompt and backed by code.

Her identity and memory

RuleWhere
The soul file's prose overwrites the database on every start; name and language never doSOUL_CONFIG_SYNC
A name that does not look like a name is refused and the current one keptupdate_soul_config
A junk language is coerced to autosame
The transcriber is pinned to an ISO code, or left on autostt_language_for
A compound memory fact is refusedmemory_write
A memory fact that looks Spanish is refused_looks_spanish
A memory fact is one line, max 400 characterswrite_fact
A skill name cannot escape the skills directoryview_skill jail
The profile and the memory facts are withheld entirely when the speaker is not youload_context(personal=…)

Her face

RuleWhere
Fourteen emotions, and no fifteenthVALID_EMOTIONS, mirrored in lib/expressions.ts
A tag maps to a face by table lookupTAG_TO_FACE
Only focus and fail faces are emitted; done is declared and never read_expr_for
An unmapped emotion falls back down a chain that always ends at neutrallib/avatar-config.ts:FALLBACK

Asked of the model, and nothing more

Everything below is a sentence somebody wrote. It works as well as the model you point at it.

All of ## Personality. Warm, curious, catlike, opinionated, holds an opinion under pressure, adapts without dissolving, does not hedge, does not disclaim, finishes what it starts. None of it is checked. There is no "did she hedge?" filter.

All of ## Quirks. "Say Mmm... when you are genuinely thinking, not as filler" is advice to a language model.

All of ## How to address the user. Including the senpai rule, which is stated at length in the file:

"Never call them 'senpai' either: it is off by default and stays off. The ONLY thing that turns it on is them, in this conversation."

The word senpai appears nowhere in the code. There is no filter, no counter, no check. It is a paragraph, and that is all.

All of ## Emotional rules. Fourteen trigger-to-emotion lines. They shape which tag she writes, and the tag is what does the work. A turn where she writes no tag falls back to a second model call guessing one word from her text.

That she writes a tag at all, and where she puts it. A tag at the end of a sentence gets read aloud as a word; nothing prevents it.

That she reply in the pinned language. One line: Language: es (auto = match the user). There is no detector and no rewrite. The transcriber pin is enforced; the reply is not.

That she says one short line before a tool. On the shipped reasoning model the canned line is suppressed precisely because she is supposed to say her own — and if she does not, the turn is silent.

That she loads a skill. The list is accurate and the tool works; choosing to open one is hers.

That she does not narrate her own machinery in writing, does not read file paths aloud, does not speak maths as notation. These are long, emphatic prompt blocks — three of them open with a no-entry sign and the words NEVER in capitals. Emphasis is not enforcement.

That she never surfaces a raw technical error. Half-true: the loop hands her a sanitised string instead of a traceback, so there is usually no raw error available to leak. What she says about a failure is still hers.


The trap this page exists for

The soul file reads like configuration. It has key: value pairs at the top and structured blocks below, and four of those blocks are copied into a prompt verbatim.

That structure makes it tempting to read When you finish a task successfully → emotion: happy as a rule the system applies. It is not. Nothing in the code reads that line. It is text handed to a model that may or may not act on it — and in this specific case, the code deliberately does the opposite: no face is emitted on success, because the face is meant to come from the tag she writes next. See Emotion and the face.

The same shape appears in the tool voice patterns. They look like configuration, they parse like configuration, and on the default model most of them never play.


How to tell, for a rule this page does not cover

  1. Grep the code for a distinctive word from the rule. senpai returns nothing outside the soul file, so it is prompt-only. ALLOWED_AUDIO_TAGS returns a filter, so tags are enforced.
  2. Look for a test. api/tests/ names its files after the property they pin. A rule with a test asserting it is usually code; a rule with no test is usually prose.
  3. Change it and watch. Set a personality that contradicts a prompt rule and see which one wins. If the prompt rule is enforced, your soul file loses. If it is not, you get whatever the model decides — which is the honest answer to "how much of her is configurable".