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

Configuration and operations4 of 9

Where everything lives

About 6 minutes to read

On this page

Kotoba keeps its state in one directory — ~/.kotoba — and the code resolves that directory in one place, on purpose. Five modules once counted parents[N] by hand, which is the failure a package move makes silent: a wrong depth still names a directory that exists, so the database is empty and the skills are missing with nothing raised.

KOTOBA_HOME moves the whole directory. An empty value is not "the current directory" — that bug once made a command run inside someone's project take that project for her home.

The layout

PathWhat it isIts own override
settings.yamlYour saved settings and disabled toolsetsKOTOBA_SETTINGS
.keystore_keyThe 32-byte master key, mode 0600KOTOBA_KEYSTORE_KEY_FILE
kotoba.db (+ -wal, -shm)Conversations, saved keys, approvals, reminders, the audit trailDATABASE_URL
files/The files library — and, by default, her working directoryKOTOBA_FILES_DIR
files/.index.jsonWhich files are new or edited. Never served, never deletable
tmp/Scratch. Exposed to her shell and code as $TMPDIR; entries older than 24 h are swept every two minutesKOTOBA_TMP_DIR
memory/USER.md, memory/topics/*.mdWhat she remembers about you, as editable markdownKOTOBA_MEMORY_DIR
visual-memory/index.json, visual-memory/images/Durable image keepsakesKOTOBA_VISUAL_MEMORY_DIR
models/Installed Live2D models, one folder each. The installer unpacks here and marks the defaultKOTOBA_MODELS_DIR
plugins/Community tool pluginsKOTOBA_PLUGINS_PATH
mcp.yamlConnected MCP servers. Written when you save one; absent means none configuredKOTOBA_MCP_CONFIG
pending_mcp.yamlServers found but not finished. Never holds a secretKOTOBA_PENDING_MCP
browser-profile/The profile of the browser she launches herselfKOTOBA_BROWSER_PROFILE
playwright-output/Browser artefacts (screenshots)KOTOBA_BROWSER_OUTPUT_DIR
cli.logThe terminal's log, mode 0600, restarted past 2 MiBKOTOBA_CLI_LOG
cli_historyTerminal input historyKOTOBA_CLI_HISTORY
soul/default.md, soul/templates/Seeded by kotoba setup and never overwritten afterwards. On a wheel this copy is the one that loadsSOUL_PATH

Nothing here is created until something needs it, so a fresh install has a much shorter directory than this table.

The one rule that surprises people

A database sitting beside the package wins over the one in your home.

The resolution is exactly this:

  • Installed from a wheel: always your home.
  • Installed from a clone, and api/kotoba.db exists: that file, where it lies.
  • Installed from a clone with nothing there yet: your home.

So a fresh clone puts the database in ~/.kotoba/kotoba.db, and an older clone that already has one in api/ keeps using it. Measured on a clone that has one:

db_dir()                       → <repo>/api
sqlite:///./kotoba.db resolves → <repo>/api/kotoba.db

Two things about why it is this way round.

Why the home is the new default. A database inside the checkout is lost the moment you unpack a new version into a new folder — keys, memory and every conversation — and the folder is precisely what an upgrade replaces. That was measured on a real reinstall which came up with no saved keys.

Why an existing one is never moved for you. Another process may hold it open, and a move that half succeeds costs the history it was meant to protect. Moving to the home is something you do on purpose, by moving the file, with everything stopped.

Why "beside the package" wins rather than "whichever exists". Preferring the home whenever a file happened to be there was measured switching a live install onto a stray two-turn database while 561 conversations and two saved keys went invisible — and doctor called it ok. A stray in the home is easy to acquire: a wheel in another virtualenv, a second account, a test that escaped its isolation.

kotoba doctor prints the path it actually opened, and adds "Beside the package, not in your home — move it there to keep it across upgrades" only for a database that is genuinely in the old place.

DATABASE_URL in detail

A relative path resolves against that database directory, never against the current working directory. The terminal starts in your own project folder, so without this rule it would open — and create — a different, empty database with nothing on screen to say why.

A driver-qualified scheme is the same URL: sqlite:///, sqlite+aiosqlite:/// and sqlite+pysqlite:/// all land on the same file. Three slashes means the path is relative; the fourth slash belongs to the path, so sqlite:////abs/a.db is /abs/a.db. sqlite:// and sqlite:///:memory: mean in-memory.

The personality file

SOUL_PATH defaults to ./soul/default.md, and the default is resolved through a candidate list so it can never follow your current directory: api/soul/default.md, then the repository's soul/default.md, then <home>/soul/default.md, then the copy packaged inside the wheel. A wheel install has no repository, and the packaged copy is byte-identical to the repository's, so it can never shadow an edit.

A path you set is honoured, including relative to where you ran the command — that is the only way to point at your own file. One caveat worth knowing: a relative SOUL_PATH that does not exist falls through the candidate list and quietly loads the shipped default. Only an absolute SOUL_PATH that does not exist raises, listing everything it tried.

File permissions

Kotoba only ever moves toward the more restrictive mode, and never refuses to run over it.

  • The database is touched and chmoded to 0600 on every connect, so a mode someone widened goes back each boot. SQLite would otherwise create it at 0644 minus umask — every turn she has ever been told, readable by any local account.
  • The -wal, -shm and -journal sidecars are restricted twice: once at connect and again after the migrations, because on a fresh install the migrations are what create them.
  • .keystore_key is created with 0600 at creation, via O_EXCL, so two processes booting together cannot each generate a key and orphan the other's secrets.
  • Every atomic write — settings.yaml, the library index, the memory files — is written to a unique temporary at 0600 and renamed over the target.
  • cli.log is 0600.

kotoba doctor has a permissions row that answers by doing it rather than by reading the platform, because the helper that applies the grant logs its failures and raises nothing. It never fails the run: Kotoba works either way, and a filesystem with no ACLs would report a problem for ever.

Two directories that are not in your home

Skills are read from the clone's soul/skills, or from the copy inside the wheel. KOTOBA_SKILLS_DIR overrides. Nothing writes there.

The packaged web UI lives inside the installed package, at <package>/web. KOTOBA_FRONTEND_DIR points elsewhere — and it wins over whatever the install carries, so a typo in it disables the UI rather than falling back. Both kotoba serve and kotoba doctor say so by name when the directory it names holds no app.html.