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

Contributing and reference1 of 12

A development environment

About 5 minutes to read

On this page

Kotoba is two programs in one repository: a Python package under api/, and a Next.js frontend at the root. You need Python for the first. You need Node only if you are changing the second.

Most of the commands here were run in the repository before this was written. Three were not — the backend server, the setup sandbox and the frontend build — and each says so where it appears.

What you need

VersionWhere it is written down
Python3.11 or newerapi/pyproject.toml, requires-python = ">=3.11"
Node22.nvmrc

Node is optional. An install of the wheel carries the frontend already compiled and never needs it. A clone needs it only to run the dev server or to rebuild the packaged UI.

Clone and install

bash
git clone https://github.com/rodhnin/kotoba-companion
cd kotoba-companion
python -m venv .venv && source .venv/bin/activate
pip install -e "api/[dev]"

pip install -e "api/[dev]" is the whole install: the package plus every optional extra plus the test tools. A --dry-run of exactly that command resolved cleanly and reported it would install kotoba-companion-0.10.0.

Installing api/requirements.txt instead is not equivalent. That file says so at the top: it is a convenience mirror for tooling that wants a flat list, it carries the backend set only (no cli, no dev), and a package installed from this file alone is not importable as kotoba at all — so uvicorn kotoba.server:app stops with ModuleNotFoundError.

It cannot silently drift: api/tests/test_requirements_mirror_pyproject.py fails if any line disagrees with pyproject.toml. Change a floor there and copy it down; never edit one only in the mirror. Measured: 3 passed.

The extras, read out of api/pyproject.toml

ExtraWhat it pulls inWhy
serverfastapi, pydantic, uvicorn[standard]only kotoba.server imports these, so the terminal installs without them
voicewebsocketsthe local voice socket
cliprompt_toolkit, rich, pillowthe interactive terminal; without pillow it falls back to kaomoji
webreadability-lxml, lxmlthe local page extractor
mcpmcp >=1.27,<2MCP servers; the ceiling is deliberate, 2.0 renames Tool.inputSchema
discorddiscord.py[voice], numpythe Discord bot
devall six above, plus pytest and pytest-timeoutwhat a contributor installs

There is no lint extra and no linter in CI. Ruff is not a gate here.

The README's clone instructions now say pip install -e "api/[dev]"; the short set survives in docs/self-hosting.md. That is the runtime set without discord and without pytest, so the tests will not run under it. Use [dev] if you intend to contribute.

The two processes

The backend and the frontend run separately, in two terminals.

bash
# terminal 1 — the backend
uvicorn kotoba.server:app --reload --port 8000

# terminal 2 — the frontend
npm install
npm run dev          # http://localhost:3000

Port 8000 is not arbitrary. next.config.ts proxies /api/* to KOTOBA_BACKEND_URL, which defaults to http://127.0.0.1:8000. Point the frontend somewhere else by setting that variable before npm run dev. The voice WebSocket does not go through that proxy — Next does not forward upgrades — so it dials the backend directly.

/ redirects to /app; that is where the companion lives. Measured on a running dev server: /app answers 200, / answers 307. /setup is the first-run wizard, and with no keys configured / sends you there instead.

The dev server used to write this page was started on a spare port to avoid a collision, not on 3000. 3000 is next dev's own default and what the scripts and the README use.

The backend starts with no API keys at all — the loop answers with a canned offline reply (core/loop.py, _offline_reply) — so the UI, the event pipeline and the avatar can be worked on before any provider is wired up. Real keys go in api/.env, copied from api/.env.example.

For a backend change, GET /health must answer {"status": "ok"}. That is what the route returns in api/src/kotoba/server.py, and several tests call it; it is the one route that is never behind the gate. The server itself was not started while writing this page, to avoid opening the database sitting in the tree.

The packaged UI, and how it relates to the dev server

The Python package can carry the compiled frontend inside itself, at api/src/kotoba/web/. That directory is gitignored, so a fresh clone has none of it, and nothing about a clone depends on it.

bash
python scripts/build_web.py     # needs Node; writes api/src/kotoba/web/

Not run while writing this page — it rewrites that directory. Its individual checks were exercised against the build already present; see the build pipeline.

Which one you get:

  • A clone with Node keeps the dev server. Your edits have to show, and a compiled bundle would not show them.
  • An install from a wheel gets the packaged build, and kotoba serve serves it from the backend.

The build script refuses to run while a .env, .env.local, .env.production, .env.development or either .local variant sits at the repository root, because Next would inline those values into the bundle that then ships to everyone. It also refuses if a Live2D model is sitting under public/. See the build pipeline.

Trying the first-run screens without touching your own install

bash
./try-setup.sh

It runs the real kotoba setup against a throwaway directory under /tmp: KOTOBA_HOME and a dozen sibling variables are redirected, and OPENAI_API_KEY, XAI_API_KEY and ELEVENLABS_API_KEY are blanked, so the wizard asks from the top the way a stranger sees it. It prints the sandbox path and, on every exit including Ctrl+C, the command to delete it.

Not exercised while writing this page — it launches an interactive wizard. The script itself was read line by line.

Line endings

.gitattributes stores and checks out everything as LF, including on Windows. A CRLF checkout breaks the shell scripts and makes every file look modified.