Plugins
Operators add in-process extensions as plugins. A plugin is one
directory, one plugin.json, and one assembly in its own load context.
It can contribute tools, a protocol client, approval classifiers, slash
commands, hooks, raw hooks, and prompt placeholders. After the host has
loaded plugins, external tools, and skills, it calls Attach with a
read-only snapshot of those catalogs, then AttachSession with the live
session and review model. A plugin that implements IPluginModelClient
also receives clients for those models. A plugin with an admitted status
field then receives AttachStatus.
The host owns catalog registration, approval, workspace fencing, output
truncation, and timeouts. Plugin code does not bypass
ToolInvocationPolicy.
What this is not
Section titled “What this is not”| Mechanism | Role |
|---|---|
PluginRegistry |
First-party in-process contributions (built-in tools and the six wire protocols). |
| External tool sets | Model-callable tools under tools/. They do not implement this contract and have no hooks. See external-tools.md. |
| Skills | Markdown instructions loaded through the skill tool. |
| MCP | Deferred. It is not a plugin runner. |
Identity
Section titled “Identity”The plugin has no JSON identity field. The plugin is its directory
under plugins/.
~/.crystal/plugins/<directory>/plugin.json<workspace>/.crystal/plugins/<directory>/plugin.jsonRuntime data lives separately under data/plugins/<directory>/ in the same
two trees; see Data directories.
Overlay key: the directory name. A project directory replaces the home
plugin of the same name as a whole. Directory names are 1–64 characters,
start with a letter, then A–Z a–z 0–9 _ . -.
IPlugin.Name is the name shown by /plugins. It must be non-empty.
Two loaded plugins cannot share it.
Discovery
Section titled “Discovery”config.json field plugins enables discovery (default true). When
true, the field is omitted from the written file. When false,
manifests are not scanned. crystal run --plugins on|off overrides that
switch for one process and is not written back.
plugin.json field enabled turns one plugin on or off (default
true). "enabled": false omits that plugin after overlay, with no
operator note. A missing, unreadable, or invalid manifest skips that
plugin and records an English note. The session still starts.
/plugins lists loaded plugins and any directory that is not loaded.
/plugins on, /plugins off, and /plugins reload persist the discovery
switch when it changes and reload catalogs. /plugins enable <directory>,
/plugins disable <directory>, and /plugins show <directory> change or
show one manifest. Prefix home or project to choose that tree. Without
a prefix, a project directory wins over Home. The command reloads catalogs
after a change. It does not create a directory. A broken manifest is left
unchanged.
crystal plugins list, show, enable, and disable do the same outside
a session. --source home|project chooses the tree. --workspace chooses
the project root. --home chooses the data directory. The default output
is a Spectre.Console table. --format text prints the same fields as
aligned plain text. These commands write enabled in plugin.json and
do not write config.json. Turning one plugin on does not turn discovery
on. crystal run --plugins remains a process-only override.
Manifest
Section titled “Manifest”plugin.json is JSON. Unknown fields are ignored.
{ "enabled": true, "assembly": "Acme.Plugin.dll", "type": "Acme.AcmePlugin"}assembly is a path inside the plugin directory. type is the public
entry type. It implements CrystalCode.Plugins.IPlugin and has a public
parameterless constructor.