Plugins1 of 7
Plugins: your own tools, without touching the repository
A plugin is a Python module that gives Kotoba a tool she did not ship with. You write it on your own machine, in your own directory or your own package. You do not fork the repository, you do not open a pull request, and you do not wait for a release.
The loader is core/plugins.py. It runs once, when the process starts, and it is the only supported
extension point for tools outside the tree.
Adding a tool inside the repository is a different job with different rules — it goes in
api/src/kotoba/tools/, gets registered by an explicit import intools/registry.py, and is reviewed like any other change. That belongs to the contributing guide. This section is for the other case: your code, your machine, no repository involved.
The two install paths
| A folder plugin | An entry-point plugin | |
|---|---|---|
| Where it lives | ~/.kotoba/plugins/<name>/ | anywhere pip can install it |
| What the user types | copy a folder in, restart | pip install <package>, restart |
| Shape | one or more .py files, no packaging | a real distribution with a pyproject.toml |
| Tools per plugin | one per .py file | exactly one |
| Multi-file code | awkward — see Installing and shipping | natural |
| Dependencies | you install them yourself | declared, and pip resolves them |
| Best for | your own hack, a single file, trying something out | anything you give to somebody else |
Both end up in the same place: a tool registered under the toolset plugin:<name>, listed in
Settings, switchable on and off.
What a plugin can do
Everything a built-in tool can do. The loader hands your module to the same registry the built-ins
use (tools/registry.py), and the agent loop calls your execute() exactly the way it calls
shell or web_search. You get:
- The tool schema the model sees, written by you.
- The working directory (
ctx.workdir), the database (ctx.db), the execution sandbox (ctx.sandbox) and the approval gate (ctx.approval). - Her spoken lines around your tool — before, during, after, and on failure.
- Her face: which expression she wears while your tool runs, and which one when it fails.
- A row in the work panel, if your tool's risk makes it an action.
What a plugin cannot do
It cannot join a conversation. This is the one limit that surprises people, so it is first.
schemas_for() offers a companion turn only the toolsets in COMPANION_TOOLSETS
(tools/registry.py), and that set is a hard-coded literal:
COMPANION_TOOLSETS = {"web", "memory", "core", "skills", "cron", "terminal", "code", "report",
"file", "discord"}
plugin:<anything> is not in it, and no setting, environment variable or TOOLSET value you
declare changes that — the loader overwrites your TOOLSET with plugin:<name> before it registers
(core/plugins.py, _register_plugin_module). So a plugin tool is work-mode only: it is offered
when she is running a background job, and never while you are simply talking to her. If she calls it
by name in a conversation anyway, the dispatcher refuses and tells her it belongs to a background
job.
In practice that means: to use your plugin, ask her for something that becomes a job. She calls
start_work, the job's loop sees your tool in its schema list, and it runs there.
It cannot replace a built-in. Plugins are registered with allow_override=False, so a plugin
declaring a tool named shell is refused and the real shell survives. See
How the loader works — there is a sharp edge attached to this.
It cannot be delegated to. delegate's toolset parameter is a fixed enum
(research, web, file, code, browser), so a subagent cannot be restricted to your plugin's
toolset.
It is not sandboxed. A plugin is Python running inside her process, as your user account. Read The security position before you install anybody else's.
Where to go next
- Your first plugin — a complete, working tool, written out in full.
- The tool module contract — every attribute the loader and the loop read, enumerated from the code.
- Installing and shipping — the folder path, the package path, and what survives an upgrade.
- How the loader works — validity, rejection, isolation, load order.
- The security position — stated plainly.
- Testing your plugin — a test suite you can run today.
