Agent Plugins (Experimental)
An Agent Plugin is a bundle of agent extensions in the open Agent Plugins format. Scion supports version 1.0.0 of the format. A bundle is a directory with:
plugin.json: the manifest (required). It holds the plugin’sname,versionanddescription.skills/: one or more Agent Skills folders, each with aSKILL.md.mcp.json: MCP servers for the plugin (optional).- Any other files the skills or MCP servers need, such as executables under
bin/.
You declare plugins in a template. When Scion provisions an agent from that template, it validates and copies each bundle into the agent. The harness then installs it with its own plugin mechanism (for example codex plugin add or copilot plugin install).
How Agent Plugins differ from Scion skills
Section titled “How Agent Plugins differ from Scion skills”| Scion skills | Agent Plugins | |
|---|---|---|
| Unit | One skill folder | A whole bundle: skills, MCP servers and support files |
| Delivery | Copied into the harness’s skills directory | Installed through the harness’s own plugin mechanism |
| Naming | The skill folder name | The harness namespaces plugin skills, for example scion-smoke:smoke-hello in claude or plugin:scion-smoke:smoke-hello in muse-code |
| Harnesses | Every harness with a skills directory | codex, copilot, muse-code and claude (see the support matrix) |
| Sources | A template’s skills/ directory, or the Skill Bank |
Template-relative paths only |
Turning the experiment on
Section titled “Turning the experiment on”Agents are started through a Hub (this includes the workstation combo server), and only the Hub decides. An admin turns on Agent Plugins (native install) (hub.agent_plugins) under Admin → Server Config → Experiments; see Experiments. The Hub sends the value to the Runtime Broker with every create and start. A Runtime Broker never reads its own settings.yaml for this, so a Runtime Broker operator cannot turn the feature on for a Hub that has it off. The local_experiments setting of the removed local-only mode has no effect (see Local-only mode removal).
Turning it off. Scion checks the experiment when it provisions an agent and at every start. If it is off at a start, Scion stages no plugins for that start, and the harness removes the plugins that Scion installed earlier. The start prints:
agent_plugins: ignored: experiment hub.agent_plugins is off; plugins Scion installed earlier, if any, are removedBundles staged while the experiment was on stay in the agent’s control-plane record, so turning the experiment back on reinstalls them at the next start, without re-provisioning. An agent that was provisioned while the experiment was off has nothing staged: it gets plugins only when it is provisioned again (see When changes take effect).
Declaring plugins in a template
Section titled “Declaring plugins in a template”Put each bundle inside the template directory, and list it under agent_plugins in the template’s scion-agent.yaml:
my-template/├── scion-agent.yaml└── plugins/ └── scion-smoke/ ├── plugin.json ├── mcp.json ├── bin/ │ └── stdio-mcp └── skills/ └── smoke-hello/ └── SKILL.mdagent_plugins: - path: plugins/scion-smoke - path: plugins/extra-tools optional: trueplugin.json:
{ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "scion-smoke", "version": "0.1.0", "description": "Scion smoke fixture"}mcp.json:
{ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json", "mcpServers": { "smoke-stdio": { "type": "stdio", "command": "./bin/stdio-mcp", "args": ["--data", "${PLUGIN_DATA}/x"] }, "smoke-http": { "type": "streamable-http", "url": "http://127.0.0.1:8765/mcp" } }}The fields of an agent_plugins entry:
| Field | Meaning |
|---|---|
path |
Required. The bundle directory, relative to the directory of the template that declares the entry. A leading ./ is allowed. Absolute paths, .. components and symlinked directories are rejected. |
optional |
When true, a plugin that cannot be loaded is skipped with a warning. When false (the default), it fails provisioning, as a required skill does. |
scope |
Set by Scion when it merges templates (template). It is informational only. |
Rules to know
Section titled “Rules to know”- The installed name is the manifest
name, not the directory name. Names are 1–64 characters of lowercase letters, digits,.and-; they start and end with a letter or digit and do not contain--or... - Each entry resolves against its own template. In a template chain, a child template’s entry resolves inside the child template’s directory, never in the parent’s.
- Same name in two templates: the later template wins. If a parent and a child template both provide a plugin named
acme, the child’s is installed, with a warning. - No fallback to the parent. If the child’s plugin replaces the parent’s and then fails to stage (for example, it is marked
optionaland exceeds a size limit), the plugin is skipped. Scion does not fall back to the parent template’s plugin of the same name. (A child bundle whoseplugin.jsoncannot be read has no known name, so it does not replace anything.) - Template entries only. Phase 1 installs only plugins declared in templates. An
agent_pluginsentry that comes from somewhere else, such as an inline agent config, is ignored with a warning. - The persisted list is informational. An agent’s stored config (
scion-agent.json) contains a mergedagent_pluginslist. It is not the effective set of plugins: a persisted entry keeps its old fields after a template edits them, and entries that a template dropped can stay in it. Scion always re-reads each template’s ownagent_pluginswhen it provisions an agent. To see what is actually installed, read the status file.
When changes take effect
Section titled “When changes take effect”| Change | Takes effect |
|---|---|
Edit a bundle, or add or remove an agent_plugins entry |
When the agent is provisioned again: delete the agent and create it again. On a Hub you can also use scion reincarnate, except for agents in worktree-per-agent workspaces. A plain restart or resume reuses the bundles staged at provisioning. |
| Turn the experiment off or on | At the next start of the agent. |
Switch harness-config at start (--harness-config) |
At that start. Bundles are staged even for a harness that cannot install them, so the new harness installs them from the record. The old harness’s install stays in the old harness’s directories. |
| Restart with no change | No plugin work: the harness checks a few files and makes no plugin CLI call. |
A plugin that a user removes by hand inside the agent is reinstalled at the next start. A plugin that a user disables in copilot or muse-code is re-enabled at the next start. Plugins a user installs themselves are never touched: Scion only removes plugins that it recorded as installed.
Harness support
Section titled “Harness support”| Harness | Support | Minimum CLI | Tested with¹ | Notes |
|---|---|---|---|---|
codex |
partial | 0.147.0 | 0.162.1 | Installed through a Scion-owned local marketplace. Codex skips sse MCP servers. |
copilot |
yes | 1.0.74 | 1.0.97 | Installed with copilot plugin install <dir>. See the copilot notes. |
muse-code |
partial | 1.4.6 | 1.4.6, 1.4.7; 1.4.9 (live check) | Skills install natively with muse plugins install. muse 1.4.9 and earlier keeps Agent Plugins MCP servers inactive: they are listed but never started. |
claude |
partial | 2.1.157 (approximate) | 2.1.296 | Installed as a skills-dir plugin with a generated manifest. See the claude notes. |
gemini-cli, grok-build, antigravity, hermes, opencode |
no | — | — | Bundles are staged but not installed. The start prints a warning. |
¹ CLI-level smoke tests: the real harness CLI driven by Scion’s installer with staged inputs. None of the four harnesses has yet been verified end to end in a Scion-provisioned agent; for claude in particular, the path through scion start is not yet verified.
If the harness CLI in the image is older than the minimum, the harness skips plugin install with a warning and changes nothing. The claude minimum is approximate: only 2.1.296 has been tested.
Each harness-config declares its support level in an agent-plugins.yaml file next to its config.yaml, for example:
support: "partial"reason: "Native install via local marketplace; Codex skips sse MCP servers"The file belongs to the harness-config’s provisioner: scion harness-config reset and scion harness-config upgrade refresh it together with provision.py. A harness-config without it (and without the deprecated key below) is reported as predating Agent Plugins support. If the file cannot be read, does not parse or has a support level this Scion version does not know, Scion logs a warning and treats the harness as not supporting Agent Plugins, with the error as the reason; the harness itself stays usable. Harness-configs from the first Agent Plugins release declared the level as capabilities.agent_plugins in config.yaml; Scion still reads that key when there is no agent-plugins.yaml, but it is deprecated, because hubs, brokers and CLIs built before Agent Plugins reject a config.yaml that contains it.
- Scion keeps a local marketplace named
scion-agent-pluginsin~/.scion/agent-plugins/and runscodex plugin marketplace add,codex plugin add <name>@scion-agent-pluginsandcodex plugin remove. The local marketplace path does not clone or download the bundle; a fully offline install is not yet verified end to end. sseMCP servers are skipped, with the warningagent plugin <name>: codex skips sse server(s): <list>inagent.log.- The
codexharness-config’sprovisioner.timeoutis 60s to leave room for plugin installs.
copilot
Section titled “copilot”- Scion runs
copilot plugin install ~/.scion/agent-plugins/bundles/<name>and removes withcopilot plugin uninstall <name>@(only the direct install, never a marketplace plugin of the same name). - Direct local-path installs are deprecated by copilot. copilot 1.0.97 prints
Direct plugin installs (repos, URLs, local paths) are deprecatedon every install. The route works today. If a later copilot release drops it, the install fails with a warning and the agent starts without the plugin. The fallback is a local marketplace, as for codex. - Agents that sign in with an auth file reinstall every plugin on every start. On those agents, provisioning rewrites copilot’s
config.jsonfrom the staged credential at each start, which drops copilot’s plugin registration. Each reinstall takes about 1 second of CLI time. Plugin work has a 20-second budget per start, so with about 20 or more plugins, the same plugins at the end of the list are deferred at every start (agent plugins: time budget reached; N plugin(s) deferred to next start). Agents that use other sign-in methods take the no-CLI fast path after the first start. - The
copilotharness-config’sprovisioner.timeoutis 60s.
muse-code
Section titled “muse-code”- Scion runs
muse plugins install <dir> --scope user --json, andmuse plugins remove <name> --jsonto remove. It runsmuse plugins approveormuse plugins enableonly when the install result asks for it. - muse’s own diagnostics are copied to
agent.log. On muse 1.4.9 and earlier, every MCP server of a plugin producesAgent Plugins MCP server "<s>" is not supported in this release; it stays visible and inactive. - muse refuses a bundle that has no usable skill, such as an MCP-only bundle.
- The installer reads muse’s registry under
$XDG_DATA_HOME/muse/plugins/(default~/.local/share/muse/plugins/). A templateenvthat setsXDG_DATA_HOMEis not visible to the installer, so plugins would be installed in the default location while muse looks in the other one. Do not setXDG_DATA_HOMEfor muse-code agents that use plugins. - The
muse-codeharness-config’sprovisioner.timeoutis 60s.
claude
Section titled “claude”Claude has no plugin install command for local bundles, so Scion writes the plugin as a skills-dir plugin:
-
The bundle is copied to
~/.claude/skills/<name>/(the harness-config’sskills_dir), and Scion writes a generated.claude-plugin/plugin.jsonthere. Claude loads it as<name>@skills-dir, and its skills appear as<name>:<skill>. -
MCP servers are written into the generated manifest, translated from
mcp.json:- stdio servers run through a small
/bin/shwrapper that changes to the server’scwd(the plugin root by default, as the spec requires) before it runs the command. Claude has nocwdfield of its own. ${PLUGIN_ROOT}becomes Claude’s${CLAUDE_PLUGIN_ROOT}.${PLUGIN_DATA}becomes~/.scion/agent-plugins/data/<name>/, which Scion creates (mode 0700) and keeps when the plugin is removed.streamable-httpservers become Claudehttpservers;sseservers staysse.- Skip rule: Claude expands
${VAR}text in MCP config, but the spec requires unknown placeholder text to stay literal. A server whoseargs,envorcwdcontains${other than an exact${PLUGIN_ROOT}or${PLUGIN_DATA}, or whosecommand,urlor header values contain any${, is skipped withmcp server <s> skipped for claude: …. The plugin’s other servers still load.
- stdio servers run through a small
-
Claude-specific files in a bundle are not installed. Claude loads these from a plugin root by default, and they would bypass Scion’s validation. Each one a bundle ships is removed from the installed copy, with a warning (
ignored claude-specific <path> in bundle):.mcp.json,.lsp.json,settings.json;hooks/,commands/,agents/,monitors/,output-styles/,workflows/,themes/;.claude-plugin/(replaced by the generated manifest).
A root
SKILL.mdis not installed either, with its own warning:agent plugin <name>: ignored root SKILL.md in bundle (Agent Plugins skills live under skills/). Agent Plugins skills live underskills/.An MCP server whose command, cwd, args or env point into a removed path is skipped (
mcp server <s> skipped for claude: it uses <path>, which is not installed for claude). -
Name conflicts with skills. If a template, platform or Skill Bank skill has the same name as a plugin, the plugin is not installed for claude (
plugin <name> not installed for claude: a skill named <name> is already installed in the skills dir). A directory in~/.claude/skills/that Scion did not install is never replaced or removed (… exists and was not installed by Scion). -
Scion removes only what it wrote. Scion records every file it installs, with its checksum, in
~/.scion/agent-plugins/state-claude.json. On removal or update it deletes only recorded files whose content is unchanged. -
Presence check. At each start, Scion checks that the generated
.claude-plugin/plugin.jsonand the bundle’s rootplugin.jsonare still in place and unchanged (by checksum), and that~/.scion/agent-plugins/data/<name>/exists.- If either manifest or the data dir is missing, the plugin is installed again.
- If the generated manifest was changed, Scion gives up the directory (see the next item).
- If the root
plugin.jsonwas edited, the reinstall carries the edited file over like runtime output, so it is blocked at that path (see below) and the old copy stays.
-
When Scion gives up a directory. Only signs that something else now lives in the plugin directory make Scion hand it back: a skill with the same name (see above), a root
SKILL.md, or a.claude-plugin/that is a symlink or whoseplugin.jsonis not the one Scion generated. Scion then removes only its own files, drops the plugin from its state, and warns (… removed only the files Scion installed). -
Runtime output is kept. Other files that appear in the plugin directory, such as
__pycache__/or logs that an MCP server writes, and installed files that were edited, are treated as runtime output and never deleted:- On an update they are carried into the new copy. If one sits at a path the new version also uses, the update is blocked and the old version stays installed (
plugin <name> not updated for claude: … move them away to update). - On removal they stay behind in a directory that no longer has a plugin manifest, which Claude does not load. Delete
~/.claude/skills/<name>/by hand if you want it gone.
- On an update they are carried into the new copy. If one sits at a path the new version also uses, the update is blocked and the old version stays installed (
-
The
claudeharness-config’sprovisioner.timeoutstays at 30s: the install is a file write with no extra CLI call.
Where plugins end up
Section titled “Where plugins end up”Inside the agent container:
| Path | Contents |
|---|---|
~/.scion/harness/inputs/plugins/<name>/, ~/.scion/harness/inputs/plugins.json |
The bundles Scion staged for this start, and their index. Restored from the agent’s control-plane record at every start. |
~/.scion/agent-plugins/bundles/<name>/ |
Scion’s copy of each bundle, the source codex, copilot and muse-code install from (claude copies from the staged inputs). |
~/.scion/agent-plugins/state-<harness>.json |
The plugins Scion installed for that harness. Scion only ever removes plugins listed here. |
~/.scion/agent-plugins/status-<harness>.json |
The result of the last start: installed, stale (an older version kept after a failed upgrade, or a removal deferred to the next start), skipped (each with a reason), removed, cli_version and updated_at. |
~/.scion/agent-plugins/.agents/plugins/marketplace.json |
codex only: the scion-agent-plugins local marketplace. |
~/.scion/agent-plugins/data/<name>/ |
claude only: the plugin’s ${PLUGIN_DATA} directory. |
~/.scion/agent-plugins/pending-claude.json |
claude only: an intent record written just before a plugin directory is swapped in. If provisioning stops before the state file is saved, the next start uses it to adopt the new copy or keep the old one. See Claude recovery for the case it does not cover. |
The native install, per harness:
| Harness | Location |
|---|---|
codex |
~/.codex/plugins/cache/scion-agent-plugins/<name>/<version>/, enabled in ~/.codex/config.toml |
copilot |
~/.copilot/installed-plugins/_direct/<name>/, registered in ~/.copilot/config.json (installedPlugins) |
muse-code |
~/.local/share/muse/plugins/cache/local/<name>/…, registered in ~/.local/share/muse/plugins/installed.json |
claude |
~/.claude/skills/<name>/, with the generated .claude-plugin/plugin.json |
Troubleshooting
Section titled “Troubleshooting”Plugin problems show up in two places.
Start warnings
Section titled “Start warnings”Scion’s own checks run when it provisions or starts the agent. Their messages start with agent_plugins: and appear in the scion create / scion start output, and with the agent’s warnings on a Hub.
| Message | Meaning |
|---|---|
ignored: experiment hub.agent_plugins is off |
Plugins are declared (by a template or inline), but the experiment is off. Nothing is staged. |
ignored: experiment hub.agent_plugins is off; plugins Scion installed earlier, if any, are removed |
Same, at a start of an agent that has staged plugins. |
ignored: harness "<h>" is not a container-script harness |
The harness-config does not use a container-script provisioner, so it cannot install plugins. |
entry "<path>" ignored: only template-relative paths are supported |
The entry does not come from a template. |
template <t>: path "<p>" must be relative to the template directory |
The path is absolute, has .., or leaves the template directory. |
template <t>: plugin dir "<p>" not found or is a symlink |
The directory is missing, is not a directory, or is (or sits under) a symlink. |
template <t>: plugin dir "<p>": <error> |
Any other error opening the plugin directory, such as an unreadable template directory. |
plugin at "<p>": plugin.json … |
plugin.json is missing, not a regular file, not valid JSON, has a field of the wrong type, or has an invalid name. |
plugin "<n>": unsupported Agent Plugins version "<x>"; Scion supports 1.0.0 |
The manifest’s $schema is not the 1.0.0 schema. |
plugin "<n>": bundle exceeds <limit> |
The bundle is over 10,000 entries (files, directories and skipped symlinks), 100 MiB, or a directory depth of 32. |
plugin "<n>": skipped <k> symlink(s) or special file(s) (not followed): … |
Those entries were not copied. |
plugin "<n>": unknown manifest field "<f>" ignored |
Unknown plugin.json fields are ignored. |
plugin "<n>": manifest field "extensions" is not an object; ignored |
extensions must be a JSON object; anything else is ignored. |
plugin "<n>": MCP disabled: <reason> |
mcp.json is invalid as a whole, so none of the plugin’s MCP servers is installed. Its skills still are. |
plugin "<n>": mcp server "<s>" skipped: <reason> |
One server is invalid; the others are kept. |
plugin "<n>": skills ignored: <reason> |
skills/ is not a usable directory. |
plugin "<n>": from template <t2> replaces the one from <t1> |
Two templates provide the same plugin name; the later one wins. |
harness "<h>" does not support Agent Plugins; plugins staged but not installed (<reason>) |
The harness has no plugin support (see the matrix). |
harness-config "<h>" predates Agent Plugins support; plugins staged but not installed (run scion harness-config reset <h> for the bundled defaults, or scion harness-config upgrade <h> to keep local edits) |
The installed harness-config is older than plugin support. See Old harness-configs. |
The messages in the first column are shown without their agent_plugins: prefix. The path, manifest, version and size errors fail provisioning for a plugin that is not optional (the error starts with agent plugins:). For an optional plugin they are warnings that end in (optional plugin skipped).
Harness messages in agent.log
Section titled “Harness messages in agent.log”The harness’s provisioner installs the plugins inside the container just before the harness starts. An agent never fails to start because of a plugin. Problems are written to agent.log as [provision] lines such as:
[provision] codex provision: warning: agent plugin scion-smoke: install failed (…); will retry next start| Message | Meaning |
|---|---|
agent plugins skipped: <cli> <version> is older than <min> (needs Agent Plugins support) |
The image’s harness CLI is too old. Nothing is installed or removed. |
agent plugin <n>: install failed (…); will retry next start |
The harness CLI failed. The CLI output is included. |
agent plugin <n>: removal failed (…); will retry next start |
A plugin that was dropped could not be removed. |
agent plugins: time budget reached; <k> plugin(s) deferred to next start, or …; <k> removal(s) deferred to next start |
Plugin work has a 20-second budget per start. |
agent plugin <n>: codex skips sse server(s): … |
codex only. |
agent plugin <n>: muse: Agent Plugins MCP server "<s>" is not supported in this release; … |
muse-code only. |
agent plugin <n>: mcp server <s> skipped for claude: … |
claude only; see the skip rule. |
These lines are logged at INFO level, so SCION_LOG_LEVEL=warn hides them; see Observability. For the full result of the last start, read ~/.scion/agent-plugins/status-<harness>.json in the agent.
Old harness-configs
Section titled “Old harness-configs”Harness-configs installed before plugin support have no plugin code in their provisioner and do not declare Agent Plugins support. Scion stages the bundles but does not install them, and the start prints:
agent_plugins: harness-config "codex" predates Agent Plugins support; plugins staged but not installed (run scion harness-config reset codex for the bundled defaults, or scion harness-config upgrade codex to keep local edits)To fix this:
- For a bundled harness-config in
~/.scion/harness-configs/<name>, runscion harness-config reset <name>, which restores the embedded version (it overwrites local edits).scion harness-config upgrade <name>is the additive alternative: it replaces the provisioner scripts andagent-plugins.yaml(with a backup), but it keeps your existingprovisioner.timeout. For codex, copilot and muse-code, raise that to60sby hand. - Harness-configs stored on a Hub must be updated there: install an updated source over it with
scion harness-config install --force <source>, or upload a fixed local copy withscion harness-config sync <name>. - Copies of a harness-config bundled inside templates must be updated too.
Claude recovery
Section titled “Claude recovery”- Lost Scion state. If
~/.scion/agent-plugins/state-claude.jsonis missing or corrupt, Scion no longer knows which~/.claude/skills/directories it installed. It treats them as user content and does not replace or remove them, so the plugin is not updated (… exists and was not installed by Scion). To recover, delete~/.claude/skills/<name>/in the agent; the next start installs the plugin again. - Interrupted update. If the provisioner is killed in a narrow window during an update of a plugin directory, carried runtime output can be lost, or the old copy can be left behind without Scion managing it.
pending-claude.jsoncovers most interruptions, but not this one. To recover, delete~/.claude/skills/<name>/; the next start installs the plugin again. - Changing
skills_dir. If you change the claude harness-config’sskills_dir, the plugin is installed in the new directory, and the copy in the old directory stays behind. Delete<old skills_dir>/<plugin name>/by hand.
Security notes
Section titled “Security notes”- Bundles run code. A stdio MCP server is a program from the bundle, started by the harness with the agent’s permissions. Only use bundles you trust, as you would for template files.
- Template-relative sources only. Scion does not fetch plugins from URLs or registries in this release. A plugin path must stay inside its template directory, and the plugin directory and its parents inside the template must be real directories, not symlinks.
- Symlinks are never followed. Symlinks and special files inside a bundle are skipped and reported. Copied files get mode 0644, or 0755 if any execute bit was set; setuid, setgid and sticky bits are dropped.
- Size limits. Each bundle is limited to 10,000 entries (files, directories and skipped symlinks), 100 MiB and a directory depth of 32.
plugin.jsonandmcp.jsonare limited to 1 MiB each. - MCP values are visible data. Header and
envvalues inmcp.jsonare stored in the template and copied into the agent home and the harness’s config. Do not put secrets in them. - Remote MCP URLs must use https, except for
localhostand loopback addresses. URLs with user information or a fragment are rejected. - claude:
bin/is on the model’s shell PATH. Claude appends each plugin’sbin/directory to the PATH of its Bash tool. It is appended, so it cannot shadow existing commands, but every executable in the bundle’sbin/can be run from the model’s shell. - claude: Claude-specific root files are not installed.
.mcp.json,.lsp.json,settings.json,hooks/,commands/,agents/,monitors/,output-styles/,workflows/,themes/,.claude-plugin/and a rootSKILL.mdare removed from the installed copy, so a bundle cannot add hooks, commands, agents or unvalidated MCP servers to Claude. See the claude notes.
Limitations
Section titled “Limitations”- Only Agent Plugins 1.0.0 is supported.
- Sources are template-relative paths only: no GitHub or registry sources, no CLI flag and no Hub API to add plugins to an agent.
- gemini-cli, grok-build, antigravity, hermes and opencode do not install plugins yet.
- Plugin MCP servers are not added to Scion’s own MCP configuration (
mcp_servers); each harness reads them from the plugin. - Install results inside the container (
status-<harness>.json) are not reported back to the Hub.