Iterion’s plugin ecosystem lets you extend the runtime with declarative,
out-of-process packages. A plugin never injects Go code (iterion ships static
CGO_ENABLED=0 binaries that are bind-mounted into sandbox containers — Go’s
plugin package is a non-starter). Instead a plugin.yaml manifest declares
what to contribute, and the runtime wires it into iterion’s existing seams.
Plugins are installable-by-default, uninstallable, replaceable, and composable —
rtk (the command-output compressor) ships as a plugin enabled by default; the
knowledge-graph explorers graphify and repo-falcon ship disabled.
A manifest’s contributes: block lists one or more typed extension points:
| kind | what it adds | wired into |
|---|---|---|
rewriters |
command-output compressors (the rtk generalization) | the rewrite chain on all three shell surfaces (claude_code Bash hook, claw bash builtin, tool nodes) |
mcp_servers |
MCP servers (e.g. a knowledge-graph explorer) | the workflow MCP catalog — ambient, workflow-wide, like a project .mcp.json entry |
skills |
markdown skills | mirrored into <workspace>/.claude/skills/ at run start |
commands |
markdown slash commands | mirrored into <workspace>/.claude/commands/ (claude_code discovers via --setting-sources project) |
agents |
markdown subagents | mirrored into <workspace>/.claude/agents/ (claude_code discovers via --setting-sources project) |
hooks |
JSON settings fragments ({"hooks": {...}}) |
idempotently merged into <workspace>/.claude/settings.json (claude_code fires them via --setting-sources project) |
lifecycle |
index / refresh shell commands |
iterion plugin run <name> index|refresh (+ optional auto_index) |
skills / commands / agents share one mirror mechanism + the bundle
collision policy (copy / no-op / refresh / shadow) — a same-named
bundle/workspace file shadows the plugin’s. Each is a []string of paths
relative to the plugin root.
hooks is a []string of JSON settings-fragment paths. At run start iterion
idempotently merges every enabled plugin’s hooks into
<workspace>/.claude/settings.json under .hooks: a sidecar
(.claude/.iterion-managed/plugin-hooks.json) records the last injection, so a
re-run/resume removes the prior set before re-adding the current one — user
hooks already in settings.json are preserved, and disabling a plugin removes
its hooks. A fragment is either a full settings shape ({"hooks": {...}}) or a
bare {<Event>: [...]} map. Security: a command-type hook runs arbitrary
shell on tool events — installed plugins are opt-in (disabled by default), so
enabling one with hooks is the operator’s deliberate choice, like installing any
tool.
A single plugin may contribute several kinds (repo-falcon ships mcp_servers +
lifecycle + skills).
pkg/plugin/builtin/<name>/
(rtk, graphify, repo-falcon).~/.iterion/plugins/<name>/ (a directory
with a plugin.yaml). iterion plugin install <path|git-url> puts them there.~/.iterion/plugins.yaml; the
default for a plugin with no recorded preference is its manifest
default_enabled. ($ITERION_HOME overrides the home dir.)plugin.yaml)name: rtk # unique id (kebab-case) = install dir + enable key
version: 1.0.0
description: … # shown in `plugin list`
author: …
schema_version: 1 # default 1; a newer version than the binary supports is rejected
default_enabled: true # enable state when the operator has expressed none
auto_index: false # run lifecycle.index before a run if enabled
contributes:
rewriters:
- id: rtk
locate: # env → PATH (bin) → conventional paths (~ expanded)
env: ITERION_RTK_BIN
bin: rtk
paths: [~/.local/bin/rtk, /usr/local/bin/rtk, /usr/bin/rtk]
invoke:
argv: ["rewrite", ""] # exactly one arg holds (the full shell line)
env: { RTK_TELEMETRY_DISABLED: "1" }
timeout_ms: 5000
apply_exit_codes: [0, 3] # exit codes whose stdout is taken as the rewrite (default [0])
modes:
on: {}
ultra: { inject_flag: "--ultra-compact" } # inserted after the binary name
sandbox_mount: /usr/local/bin/rtk # bind-mount the host binary here in sandboxed runs
mcp_servers:
- { name: falcon, transport: stdio, command: falcon,
args: ["mcp","serve","--snapshot","/.falcon/artifacts","--repo",""] }
skills:
- skills/code-knowledge-graph.md
lifecycle:
index: "falcon index --repo "
refresh: "falcon refresh --repo "
Activation-time placeholders in mcp_servers and lifecycle:
,,
(`~/.iterion/plugins/<name>/cache`), and for any declared
config field (see below). The rewriter placeholder is substituted
at rewrite time with the full shell command line; a rewriter's `invoke.env` and
`invoke.argv` also resolve.
config:)A plugin can declare user-configurable settings — like a Firefox add-on’s
preferences. The operator sets values in the studio (the Configure pane on
the Plugins view) or via the CLI; the values are stored in ~/.iterion/plugins.yaml
(0600, alongside enable state) and substituted into the plugin’s
mcp_servers/rewriters/lifecycle commands through ``.
config:
- key: max_depth # referenced as
label: Max depth # studio form label (defaults to key)
type: int # string | bool | int | float | enum | secret
default: "3" # used until the operator sets a value
description: How deep to traverse.
- key: mode
type: enum
options: [on, ultra] # required for type: enum
default: on
- key: api_key
type: secret # password field; value never sent back to the studio
required: true # advisory; surfaced in the UI/CLI
contributes:
lifecycle:
index: "graphify --depth "
mcp_servers:
- { name: g, command: graphify, args: ["mcp"], env: { GRAPH_TOKEN: "" } }
Values are stored and substituted as strings (a bool is "true"/"false",
an int is "30"). A secret is write-only over the API: the list/info
responses report only whether it is set, never its value, and a blank
submission keeps the prior value (the studio shows “leave blank to keep”).
Secrets are stored in cleartext in the 0600 plugins.yaml, so this is for
instance-level configuration, not a substitute for tenant secret bindings.
Manage config from the CLI (parity with the studio):
iterion plugin config <name> # show schema + values
iterion plugin config <name> --set max_depth=5 --set mode=ultra # set values
iterion plugin info <name> # includes the config block
Endpoints: the plugin list/info DTO carries config_schema + config_values
(+ config_secret_set); PUT /api/v1/plugins/{name}/config (super-admin)
persists values.
Compression is the rewriter kind. The DSL field is compress:
(on|ultra|off) on the workflow block and on agent/judge/tool nodes;
the CLI flag is --compress; the env default is ITERION_COMPRESS.
Precedence: CLI --compress → node compress: → workflow compress: →
ITERION_COMPRESS → default.
off at any level
wins: per-run via --compress off / the studio toggle, or globally via
iterion plugin disable rtk (chain empty → off) or ITERION_COMPRESS=off.git diff stays full-fidelity unless
the node sets compress:.The active rewriter chain is every enabled rewriter plugin, applied in stable name order — so you can replace rtk (disable it, enable another) or complement it (enable several; each rewrites the previous one’s output). rtk ships installed + enabled, so it is used on runs by default (agent/judge), disableable per-run and globally as above.
Diagnostic C102 flags an invalid compress: value.
Two are shipped as disabled builtins; enable either to give agents code-graph context:
falcon MCP server into every run’s catalog (so agent/judge nodes get
falcon_* tools: symbol lookup, file context, architecture, call paths) and
mirrors a skill telling agents to query the graph before reading code. Its
lifecycle builds/refreshes the .falcon/artifacts snapshot.graphify-out/)
spanning code and docs; the skill guides agents to graphify query / read the
report. Its lifecycle builds/updates the graph.iterion plugin enable repo-falcon
iterion plugin run repo-falcon index # build the snapshot for this workspace
# now any bot run exposes falcon_* tools to its agents
iterion plugin list # all plugins + enable state + kinds
iterion plugin info <name> # manifest details (+ config schema/values)
iterion plugin config <name> [--set k=v] # show or set a plugin's configuration
iterion plugin enable <name> # turn on (persists to plugins.yaml)
iterion plugin disable <name> # turn off (use this for builtins; they can't be uninstalled)
iterion plugin run <name> index|refresh # run a lifecycle command in the cwd
iterion plugin install <path|git-url> # install a third-party plugin under ~/.iterion/plugins/
iterion plugin uninstall <name> # remove an installed plugin
Marketplace entries carry a kind (bot | plugin, defaulting to bot for
legacy entries) so bots and plugins share one hosted registry. iterion
marketplace list can filter by kind; installing a plugin entry resolves its
repo coordinates and runs the same plugin install path.
The contributes: design is deliberately open so iterion can grow to configure
every Claude Code plugin type from the UI and the marketplace. Planned kinds
map onto Claude Code’s plugin model:
| Claude plugin type | iterion kind | parity note |
|---|---|---|
| skills | skills ✅ shipped |
claude_code native lookup + claw skill tool both read .claude/skills/ |
| MCP servers | mcp_servers ✅ shipped |
both backends consume the MCP catalog |
| slash commands | commands ✅ shipped (claude_code) |
mirrored to .claude/commands/; claude_code discovers via --setting-sources project. claw reads commands only from CLAUDE.md today → a .claude/commands/ loader is staged in .works/claw-code-go (internal/commands/), lands on the next claw release + go.mod bump |
| subagents | agents ✅ shipped (claude_code) |
mirrored to .claude/agents/; claude_code discovers via --setting-sources project. claw has the agent tool + SubagentRunner but no named-agent file loader → claw-side follow-on |
| hooks | hooks ✅ shipped (claude_code) |
plugin hooks idempotently merged into .claude/settings.json; claude_code fires them via --setting-sources project. claw has shell + Go hook runners but no settings discovery → claw-side follow-on |
The principle: where claude_code has a native surface and claw does not (or they
diverge), the gap is closed in .works/claw-code-go (the vendored claw
source) so a plugin behaves identically on either backend, rather than papered
over with a claude_code-only adapter. Adaptation bridges are acceptable as an
interim only when native parity is impractical.
The commands/agents/hooks kinds are tracked as a follow-on; the manifest
schema and registry already accommodate new kinds without a breaking change.
iterion plugin install <path|git-url> installs any repo carrying a
plugin.yaml. When the source has no plugin.yaml but ships bare skills,
install synthesizes a skills-only manifest, so a popular community skill
pack becomes a first-class, enable/disable-able iterion plugin with no
authoring step:
iterion plugin install https://github.com/acme/awesome-claude-skills
iterion plugin enable awesome-claude-skills # disabled by default (opt-in)
Skill discovery: every *.md under skills/ (recursively); if there is no
skills/ directory, top-level *.md (excluding README.md). The plugin name
is derived from the repo/dir basename (kebab-cased). The synthesized
plugin.yaml is written into ~/.iterion/plugins/<name>/, and the skills
mirror into <workspace>/.claude/skills/ once enabled — same path as any other
skill-contributing plugin.
Skill pack (this) vs. skill library. A skills-only plugin is a shared, versioned, enable/disable-able unit — install a whole pack from git, toggle it on/off. The complementary skill library is your editable, per-skill store (
~/.iterion/skills/,iterion skill add|rm) that workflows reference by name via the DSLskills:field.iterion skill import <git-url>installs a pack through this plugin path — the bridge between the two halves of the hybride model (ADR-059).
installRewriteHook), claw bash builtin
(RewriteCommandFieldCtx), tool node (executor_tool.go).addRewriterMounts in
pkg/runtime/sandbox_mounts.go.