Contributing to Kokua¶
Thanks for your interest. Kokua is a small, hackable personal assistant built on AIMU, and it exists so people can learn how agentic systems work by reading, running, and extending a real one. That goal is what most of the conventions below are protecting, so it is worth reading why Kokua exists before your first change: a contribution that makes the system harder to follow costs more than the capability it adds.
Setup¶
Kokua and AIMU are developed together, so [tool.uv.sources] points AIMU at a sibling ../aimu
checkout installed editable. Drop --no-sources if you have that checkout and want your AIMU edits
picked up live:
git clone https://github.com/saxman/aimu # sibling of kokua/
uv sync --all-extras # installs ../aimu editable
A path source is installed without being checked against the aimu>=0.28.0 specifier, so a sibling on
an older branch is the failure mode to expect. Startup's preflight (kokua.aimu_compat) catches it and
names the fix rather than failing on an import.
Without uv:
Checks¶
Run these before opening a PR:
Line length is 120 (ruff is configured in pyproject.toml). Tests are mock-only and require no model,
network, or API keys.
Build the docs locally¶
The site at saxman.info/kokua is built from docs/ by
.github/workflows/docs.yml on every push to main. To see a change before pushing:
uv sync --all-extras
uv run mkdocs serve # http://127.0.0.1:8000, rebuilds on save
uv run mkdocs build --strict # what CI runs: a broken link or an off-nav page fails the build
A new page needs an entry in mkdocs.yml's nav, or the strict build fails. That is deliberate: a
page nobody can navigate to is indistinguishable from a page that does not exist. Links out of docs/
must be absolute, in the style of the toolsets/image.py
link under Conventions below, which tests/test_docs.py enforces along with checking that every such
path still exists.
Design principles¶
Read docs/explanation/design-principles.md first. It opens with why Kokua exists, then the six principles that serve it: 1 and 2 keep Kokua readable, 3 and 4 keep it observable, 5 keeps it runnable by anyone who clones it, and 6 keeps its capability yours to bound. A change that serves none of them is probably a plugin rather than a core change, and principle 2 exists to make that an easy answer.
Where does it go?¶
src/kokua/ is grouped by subsystem, and tests/ mirrors it exactly -- a test goes where its module
does.
| Subpackage | Holds |
|---|---|
core/ |
the transport-agnostic runtime: the assistant, conversations, turns, human decisions, runtime settings, agent building |
config/ |
the settings schema, the TOML file, the writers, the write policy, and the runtime-settings table |
workflows/ |
the workflow protocol and its two tiers, the shared reviewer, and planning/, the /plan pipeline |
mcp/ |
remote MCP servers and their OAuth |
scheduling/ |
recurrence math and the durable task lifecycle over config.toml |
channels/ |
ChannelUI plus the concrete channels |
frontends/ |
cli and web, registered as plugins exactly as a third party's would be |
toolsets/ |
the registry, Kokua's six core toolsets, the AIMU wrappers, and the three plugin toolsets |
Agent tools live under toolsets/, and only there. A module defining an @aimu.tool is a toolset
module and nothing else is, so grep -rl '@tool' src/kokua/ should only ever find files in that one
directory.
cli.py, plugins.py, images.py, logging_setup.py, config.example.toml and web_static/ stay
at the package root: entry points and package-data paths point at them.
The stable public import surface is kokua.plugins, kokua.config, kokua.core,
kokua.channels.web, and kokua.images. Everything else is internal and may move.
Conventions¶
- Plain Python: dataclasses, functions, type hints. Keep the core small; push capability into plugins.
- Add a front end (a new transport) or a toolset (a named capability an agent can declare) as a
plugin, in its own package or under
src/kokua/frontends/src/kokua/toolsets, registered via thekokua.frontends/kokua.toolsetsentry-point groups. Seesrc/kokua/toolsets/image.pyfor the template: one tool, and abuildthat returns nothing when its prerequisite is missing. Neither reaches an agent until an[agents.*]table inconfig.tomlnames it. - A new runtime setting for a toolset is one
kokua.toolsets.Settingon the toolset and nothing else. For one of Kokua's own it is oneCORE_RUNTIME_SETTINGSentry inconfig/table.pyand oneAssistantConfigfield. If either takes more edits than that, fix the table rather than working around it.tests/config/test_table.pyenforces that a core entry is a real config field and is documented inconfig.example.tomlunder its own section. - Anything that writes state derives its path from
AssistantConfig, never from a new function inconfig/paths.py. - Anything touching turn concurrency updates the invariants block at the top of
core/turns.py, in the same commit, naming the failure it prevents. - Docstrings explain why, and stand on their own: no bare task or phase numbers ("Task 6", "Phase B"), which point at a design history that isn't in this repository.
- Use English punctuation (no em dashes); inclusive terminology (allowlist/blocklist, primary/replica, main branch).
- Update
CHANGELOG.mdand the README when you change behavior or the public surface. Include tests.
Pull requests¶
One concern per PR. Make sure ruff check, ruff format --check, and pytest pass.