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

Start6 of 7

Installing from a clone

About 4 minutes to read

On this page

Do this if you want to change Kotoba. If you only want to run her, pip install is simpler and gives you a compiled web UI you do not have to build.

What you need

  • Python 3.11 or newer
  • Node 22 or newer, for the frontend. .nvmrc pins the version, so nvm use picks it up

Two processes, two terminals

bash
git clone https://github.com/rodhnin/kotoba-companion
cd kotoba-companion

# terminal 1 — backend
python -m venv .venv && source .venv/bin/activate
pip install -e "api/[dev]"
uvicorn kotoba.server:app --reload --port 8000

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

kotoba serve works from a clone too and starts both, but two terminals give you two separate logs and two separate restarts, which is what you want while editing.

The package is a src-layout under api/: the code lives in api/src/kotoba/, the ASGI target is kotoba.server:app, and api/pyproject.toml is the manifest. pip install -e "api/[dev]" installs the package plus the test tools. Installing requirements.txt alone leaves kotoba unimportable and uvicorn fails with ModuleNotFoundError.

The backend boots with no API keys at all — the loop returns an offline reply — so you can work on the UI and the event pipeline before wiring up a model or ElevenLabs.

Keys in a clone

bash
cp api/.env.example api/.env

Two lines cover the default path:

bash
OPENAI_API_KEY=<your OpenAI key>
ELEVENLABS_API_KEY=<your ElevenLabs key>

Replace both placeholders. The values shipped in api/.env.examplesk-... and el_... — are recognised as placeholders and treated as no key at all, deliberately, so an unedited copy fails with a sentence instead of a 401 buried in a log.

Or just run kotoba setup, which stores them encrypted in the database instead. Both routes work and both are read by the same code.

You do not need .env.local. It exists for the split-origin case, where the browser has to call the backend on a different origin. Creating it has a cost worth knowing: NEXT_PUBLIC_* values are frozen into the compiled bundle, so the web build script refuses to run while that file exists rather than baking your address into everybody's copy.

Where things live in a clone

This is the one place a clone differs from an install, and it surprises people:

  • The database stays with the checkout, at api/kotoba.db, when that file exists. A relative DATABASE_URL resolves against api/ and not against whatever directory you happen to be standing in — otherwise cd would silently open a different, empty database.
  • Her personality is soul/default.md in the checkout, not the copy in your home.
  • Everything else — memory, files, models, the master key, settings, logs — is still under ~/.kotoba/.

Building the packaged web UI

bash
python scripts/build_web.py

Needs Node. It compiles the Next app, checks it, and copies the result into api/src/kotoba/web/, which is gitignored — the repository never carries a build. It refuses to run when a root .env or .env.local exists, when a Live2D model is sitting under public/, and when the finished build contains a private hostname, an IP literal, a tunnel domain, a home directory path, a source map or image metadata.

You rarely need it. kotoba serve prefers the development server whenever npm is available, because your edits have to show; the packaged copy is what an install without Node gets. But building a wheel requires it: the build backend refuses a distribution whose web UI is absent, stale, or stamped for a different version.

Before you open a pull request

The four things CI runs, in the same form:

bash
pytest api/tests -q
npx tsc --noEmit --noUnusedLocals --noUnusedParameters
npm test
python scripts/build_web.py

A few backend tests fetch and then run somebody else's package, so they are deselected by default and refuse to run even when selected. Ask for them explicitly when you touch MCP installation or the Docker sandbox:

bash
KOTOBA_ALLOW_NETWORK=1 pytest api/tests -m network

To exercise the first-run screens without touching your own install, run ./try-setup.sh. It points kotoba setup at a throwaway home, so your own database, keystore, settings, memory and personality files are never opened, and it prints the sandbox path and how to delete it.

For any backend change, confirm GET /health returns {"status": "ok"}.

Docker

bash
cp api/.env.example api/.env
docker compose up --build
# → http://localhost:3000

Compose reads api/.env and will not start without it. Both services come up on the compose network and the frontend proxies /api/* to the backend.

One caveat that is not obvious: the browser's voice socket cannot go through that proxy, so its address is baked into the frontend image at build time as ws://localhost:<backend port>. Open the stack from the Docker host and voice works. Open it from another machine on your network and the browser dials its own localhost — the page loads and voice silently does not. Text is unaffected. Ports are baked in at build time, so pass --build whenever you change KOTOBA_BACKEND_PORT or KOTOBA_WEB_PORT.

Next: Troubleshooting the first hour.