Plugins are how you extend Koris without touching its core. There are two plugin
families — channels and tools — that share the same
shape and the same kernel, described below. plugins/ also holds
skills, but skills are a different kind of extension — plain Markdown,
synced by SkillSyncService rather than the kernel this page describes.
plugins/registry.ts holds the family-agnostic core: ExtensionPoint,
PluginRegistry, and buildRegistry. Both families build on it. At startup
core/src/app.ts builds one shared PluginRegistry from the channel plugins and the
tool plugins together.
A plugin imports only from its own family's contracts.ts and the shared
plugins/registry.ts. It never imports from core/src/, and never from the other
family's contracts.ts.
Core depends on the SDKs and injects concrete services the other way, through a
PluginContext (channels) or ToolPluginContext (tools) assembled at the composition
root, core/src/app.ts. Each plugin folder exposes a create(context) function that
receives that context.
The one documented exception is plugins/tools/create-tool/, which reaches into
scripts/scaffold-tool.ts to generate new tool folders.
The scanner loads every subdirectory of a family folder — plugins/channels/ and
plugins/tools/. Files that sit directly in those folders, such as contracts.ts, are
skipped. Adding a plugin means adding a folder; for tools, pnpm scaffold:tool <name>
creates one for you. No core changes are required. (plugins/skills/ sits alongside
these two, but isn't scanned here — see Skills for how it's synced.)
Enablement lives in the database, in the plugin_settings table: family, name, and
enabled, with a primary key of (family, name). resolvePluginEnabled reads the row
and falls back to a code-level default when no row exists yet.
Toggling is live — no restart. PluginCatalogSingleton keeps every registered
{ family, name } pair so the admin API can list plugins without rescanning disk. See
the admin dashboard for the Plugins panel.
Channel plugins still keep their secrets and the allow_unlisted_senders trust flag in
each folder's config.yml; only the enabled flag moved to the database.