Plugins
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, repo-falcon and codeindex and the web toolkit firecrawl (Firecrawl search/scrape/crawl MCP — see web-search.md) ship disabled.
Contribution kinds (v1)
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, in BOTH the directory form <name>/SKILL.md (what claude_code's Skill tool discovers — Agent Skills spec) and the flat alias <name>.md (what prompt Reads by path resolve) |
commands | markdown slash commands | mirrored into <workspace>/.claude/commands/<name>.md; a prompt opening with /<name> resolves to that file's body on claude_code and claw (capability matrix) |
agents | markdown subagents | mirrored into <workspace>/.claude/agents/<name>.md (claude_code discovers via --setting-sources project; claw resolves the same files in-process — pkg/backend/model/workspace_agents.go, see the parity table) |
hooks | JSON settings fragments ({"hooks": {...}}) | idempotently merged into <workspace>/.claude/settings.json (claude_code fires them via --setting-sources project; claw fires a subset — see the parity table) |
lifecycle | index / refresh shell commands | `iterion plugin run <name> 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).
Where plugins live
- Builtins are embedded in the binary under
pkg/plugin/builtin/<name>/(rtk,graphify,repo-falcon,codeindex,firecrawl). - Installed plugins live under
~/.iterion/plugins/<name>/(a directory with aplugin.yaml).iterion plugin install <path|git-url>puts them there. - Enable/disable state is persisted in
~/.iterion/plugins.yaml; the default for a plugin with no recorded preference is its manifestdefault_enabled. ($ITERION_HOMEoverrides the home dir.)
Env-based enablement & config (cloud / headless)
~/.iterion/plugins.yaml is per-machine and ephemeral in a cloud runner pod, so enablement can also be driven by immutable env — set once on the runtime (e.g. a Helm chart's config.extraEnv), no persistent file needed. Env wins over both stored state and default_enabled:
| Env var | Effect |
|---|---|
ITERION_PLUGINS_ENABLE=a,b | force-enable these plugins (comma/space list) |
ITERION_PLUGINS_DISABLE=c | force-disable (wins over enable for the same name) |
ITERION_PLUGIN_<NAME>_<KEY>=v | set config value <key> for <name> (highest precedence over defaults + stored values) |
<NAME>/<KEY> are upper-cased with - → _. Example — enable the firecrawl MCP plugin and point it at a self-hosted instance, entirely from env:
ITERION_PLUGINS_ENABLE=firecrawl
ITERION_PLUGIN_FIRECRAWL_API_URL=http://iterion-firecrawl:3002Markdown contributions reach cloud runs (ADR-079)
Env-based enablement above is what a runner pod needs for process-local kinds (rewriters, mcp_servers, lifecycle): those run in the pod, so the pod's own registry must know about them.
The markdown kinds (skills, commands, agents) work differently, and used not to work on cloud at all. A runner pod's iterion home is ephemeral and empty, so an installed plugin — one living in the studio/server instance's ~/.iterion/plugins/, which no env var can conjure onto a pod — mirrored nothing there, silently (mirroring is best-effort so no error surfaced).
Since ADR-079 the launching instance resolves the enabled plugins' markdown files and ships them on the queue message (RunMessage.Contributions, schema v5); the runner mirrors that payload with the same collision policy and precedence. So an org-private plugin installed + enabled on a cloud studio now reaches its runs. Two consequences worth knowing:
hooksare not carried yet — they merge into.claude/settings.jsonrather than mirroring markdown, so they still need the pod's own registry (env-based enablement).- Enablement of installed plugins stays global per instance. For a team-scoped, private plugin see the next section.
The mirror prunes orphans in run-owned worktrees. A skill (or command or agent) whose source is renamed, removed or disabled upstream leaves its copy behind unless the mirror actively drops it. At the end of every mirror pass — launch AND resume — pruneWorkspaceMirror walks .claude/{skills,commands,agents}/.iterion-managed/ and removes every marker whose .tier sidecar was not refreshed this pass AND whose destination file still hashes to the marker AND which carries iterion's .iterion-wrote provenance sidecar. That predicate is exactly reconcileSkillFile's "refresh-vs-shadow" test plus a provenance stamp: a matching hash with the sidecar means iterion wrote the file and the operator did not edit it, so pruning it takes nobody's work. Anything else — a file with no marker, a file the operator edited (hash diverged), a file that already vanished, or a file mirrored by an iterion OLDER than the provenance sidecar (see below) — is left alone. The sweep runs by default only when the workspace is a run-owned worktree (worktree: auto, where a stale mirror file has no operator interpretation). A worktree: none / direct-mode run against the operator's own checkout skips pruning — an orphan there costs one unused file; a false-positive prune costs an operator's edit. The escape hatch is greppable and opt-in: ITERION_PRUNE_MIRROR_IN_CHECKOUT=1.
A pass that could not verify its declaration never prunes. The sweep runs only after BOTH mirrors report the pass complete — every declared entry actually produced this pass (a library skills: ref missing from a resume's contributions payload, or a plugin registry that could not be read, aborts the blessing). On the cloud path a dispatch that arrives WITHOUT the contributions payload is treated the same way: a runner pod's iterion home is empty by design, so its local "0 plugins enabled" proves nothing about the launching instance's set — the pass mirrors nothing, warns, and leaves the pruner unarmed. The publisher ships the payload on every launch and resume (possibly empty, which IS a statement: nothing enabled), so a missing field is an anomaly, never silently read as "the operator disabled everything".
Pre-upgrade leftovers are exempt — delete by hand. The .iterion-wrote sidecar only exists on files this version wrote. A workspace whose mirrors predate it carries none, and an orphan (its source is gone) can never be re-mirrored into acquiring one — so those stale copies stay un-pruned by design. Backfilling the sidecar for every hash-matching pre-upgrade file would also stamp provenance onto an operator's byte-identical copy of a skill and make THAT prunable, which is the delete the sidecar exists to prevent. In a persistent workspace, delete a pre-upgrade leftover by hand; any file whose upstream content changes gets refreshed into full provenance and prunes normally from then on.
Same-name collisions across enabled plugins are loud. Two plugins contributing one file name land on one destination — the mirror keeps whoever wrote last (refusing the overwrite would break a workspace that relies on the incumbent), and the overwrite is logged with both plugin names so an operator can rename one. Two enabled team-scoped git-hosted sources shipping the same file are dedup'd at the publisher (later source wins, on the same rationale) and the shadowing is logged against the run. A locally installed plugin sharing a name with a team source shadows it deterministically — the local install is the explicit act.
Org-private plugins from a git repo (ADR-080)
Installing a plugin into a cloud pod's iterion home is not durable — the pod is ephemeral, so the plugin silently disappears on the next restart and runs proceed without it. And a single plugins.yaml per instance cannot give one team a private plugin.
A plugin source fixes both: a team-scoped record naming the git repository that holds the plugin, persisted in Mongo (the durable cloud substrate). The checkout is only a re-derivable cache, so a restart rebuilds it. The fetch is built in a staging directory and moved into place with a single rename, so a launch is never handed a half-populated tree; and concurrent cold launches on the same source share one clone (a per-key lock serialises them) rather than racing N fetches against the same ref.
# team admin/owner
POST /api/teams/{id}/plugin-sources
{ "name": "deploy-k8s-acme", "git_url": "https://github.com/acme/iterion-deploy.git",
"ref": "v1.0.0", "secret_id": "<generic secret: PAT or deploy key>", "enabled": true }The repo may carry a full plugin.yaml or be a bare skills/ library (a skills-only manifest is synthesized, as with iterion plugin install). The credential is consumed by reference — passed to git through an askpass helper, never argv, never the URL, and redacted from output.
Pin ref to a tag or sha. A pinned ref makes the checkout immutable: every launch after the first costs no network, and updating the plugin becomes an explicit, auditable bump instead of a skill that changes under a running bot. A moving branch is allowed but warns, and pinned_ref is exposed on the API so the UI can flag it.
Registration verifies the source. POST/PATCH clone the repository, parse the manifest and read every contribution — exactly what a launch will do — and refuse a source that cannot be materialised with 422 and the underlying error verbatim (the YAML parser's line, git's refusal). The operator is right there to fix it; a launch is where this used to surface, one delivery at a time. A PATCH that changes git_url/ref/secret_id, that enables the source, or that touches a source currently flagged degraded, re-verifies it.
A source that breaks later is quarantined, not launch-fatal. If a launch cannot materialise an enabled source (the remote refuses the fetch, the manifest stopped parsing on a moving ref, a contribution file cannot be read), the launch proceeds without that source and the failure is recorded on the record: degraded: true with degraded_reason / degraded_at on the API (GET /api/teams/{id}/plugin-sources, iterion remote api), plus a WARN naming the run and the source in the server log. One team's broken plugin.yaml once failed every launch of the team for 2h22 (2026-08-26) — a launch path down for everyone hides more than one run missing one skill. The flag clears on the next launch that resolves the source, or on a PATCH that re-verifies it. Only a listing failure (the store itself unreadable) still fails the launch: nothing can then tell a healthy team from one whose sources are all lost.
Manifest reference (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", "{{command}}"] # exactly one arg holds {{command}} (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
run_env: # env of a shell whose commands it compresses (wins over the inherited env)
RTK_DB_PATH: /dev/null/iterion-rtk-history.db # rtk's history: a path nothing can create (no switch in rtk)
RTK_RECALL: "0" # rtk's store of the output lines it left out: off
mcp_servers:
- { name: falcon, transport: stdio, command: falcon,
args: ["mcp","serve","--snapshot","{{workspace}}/.falcon/artifacts","--repo","{{workspace}}"] }
skills:
- skills/code-knowledge-graph.md
lifecycle:
index: "falcon index --repo {{workspace}}"
refresh: "falcon refresh --repo {{workspace}}"Activation-time placeholders in mcp_servers and lifecycle: {{workspace}}, {{plugin.dir}}, {{plugin.cache}} (~/.iterion/plugins/<name>/cache), and {{config.<key>}} for any declared config field (see below). The rewriter {{command}} placeholder is substituted at rewrite time with the full shell command line; a rewriter's invoke.env, invoke.argv and run_env also resolve {{config.<key>}}.
A rewriter's compressed command runs its binary, which may keep what the command ran and printed in stores of its own, past the run — secrets included (secrets.md). run_env turns them off: iterion sets it on the process that runs a compressing node's commands (claude_code's CLI, claw's bash, a tool node's shell), over the inherited environment and the run's own, and a command it compresses exports it first (export K=v; …) — the shell running it may have replaced that environment (claude_code's settings env, the operator's shell rc the CLI sources before each command), and whatever the node's compression mode (the agent may run the rewriter itself). Its names are shell identifiers. To keep rtk's stores for iterion's sessions, disable the builtin (iterion plugin disable rtk) and install a rewriter plugin for rtk that declares no run_env, under another name — the disabled builtin shadows a plugin named rtk.
Configuration (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>}}.
config:
- key: max_depth # referenced as {{config.max_depth}}
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 {{workspace}} --depth {{config.max_depth}}"
mcp_servers:
- { name: g, command: graphify, args: ["mcp"], env: { GRAPH_TOKEN: "{{config.api_key}}" } }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 blockEndpoints: the plugin list/info DTO carries config_schema + config_values (+ config_secret_set); PUT /api/v1/plugins/{name}/config (super-admin) persists values.
Command-output compression (rtk, generalized)
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.
- Agent/judge nodes are opt-OUT: when a rewriter plugin is enabled and its binary is present (the chain is available), the default is on — so rtk compresses agent shell output out of the box. An explicit
offat any level wins: per-run via--compress off/ the studio toggle, or globally viaiterion plugin disable rtk(chain empty → off) orITERION_COMPRESS=off. - Tool nodes are opt-IN only (a run override can force-off as a kill switch but never force-on), so a review loop's
git diffstays full-fidelity unless the node setscompress:.
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.
Knowledge-graph explorers
Three are shipped as disabled builtins; enable any to give agents code-graph context:
- repo-falcon — a deterministic Go MCP server. Enabling it injects the
falconMCP server into every run's catalog (so agent/judge nodes getfalcon_*tools: symbol lookup, file context, architecture, call paths) and mirrors a skill telling agents to query the graph before reading code. Itslifecyclebuilds/refreshes the.falcon/artifactssnapshot. - graphify — a CLI + skill that builds a queryable graph (
graphify-out/) spanning code and docs; the skill guides agents tographify query/ read the report. Itslifecyclebuilds/updates the graph. - codeindex — a deterministic, zero-dependency repo-indexing engine distributed on npm (
@maxgfr/codeindex, version-pinnednpxlikefirecrawl). It is the broadest contributor shipped:mcp_servers(26 tools — link-graph, symbols, callers, references, BM25/semantic search, hotspots, change coupling, complexity, dead code, architecture rules, agent memories and symbolic edits),lifecycle(incremental.codeindex/artifacts),skills,commands(/codeindex-map,/codeindex-impact,/codeindex-risk),agents(codebase-cartographer) and arewriter. Its MCP server is pinned to the workspace (mcp --repo {{workspace}}), so agents never pass an absolute path. Its rewriter maps a tree-widegrep -r/rgonto the indexed equivalent — it locates a realcodeindexbinary (Homebrew,npm -g, or the runner image's pre-installed global) and degrades to passthrough when none is present, so it never paysnpxstartup on a per-command hook. Priming the index first turns MCP activation into a load rather than a rebuild, which is whatauto_index: truedoes before a run.
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 enable codeindex
iterion plugin run codeindex index # writes <workspace>/.codeindex/
iterion plugin config codeindex --set max_files=50000 # large monorepo
# optional: RRF-fused semantic search instead of lexical-only
iterion plugin config codeindex --set embed_endpoint=http://iterion-codeindex-embed:8756CLI
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 pluginMarketplace
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.
Contribution parity status
The contributes: design covers the Claude Code plugin taxonomy from the UI, CLI, and marketplace. Skills and MCP servers reach both claude_code and claw. Pi also consumes the resolved plugin skills through an explicit --skill directory in both transports, and the MCP catalog through its embedded RPC extension. Named agents resolve on claw too — what remains there is frontmatter model: (#1717); the live e2e exists (task test:live:claw-workspace-agents, #1714), its last verdict in the ledger. Kimi, Grok, and Codex do not consume these plugin contribution surfaces:
| Claude plugin type | iterion kind | parity note |
|---|---|---|
| skills | skills ✅ shipped | claude_code native lookup, claw's skill tool, and pi's explicit --skill path consume .claude/skills/ |
| MCP servers | mcp_servers ✅ shipped | claude_code, claw, and pi RPC consume the resolved MCP catalog |
| slash commands | commands ✅ shipped | mirrored to .claude/commands/; claude_code discovers via --setting-sources project, claw resolves the same files in-process (claw-code-go pkg/api/commands). A prompt opening with /<name> becomes that file's body on either backend — see the capability matrix for the measured differences — allowed-tools: is honoured on claw since #1717 (it narrows, never widens); the frontmatter that stays ignored is model: and its siblings — the runtime warns and names them, plus workspace-only scope, one-based $1, and the unevaluated !`cmd`/@path/$0 forms |
| subagents | agents ✅ shipped (claude_code) · ✅ resolved (claw) | mirrored to .claude/agents/; claude_code discovers via --setting-sources project, claw serves the agent tool — both hosts wire ClawDefaults.Subagent, and the child runs within the node's tools, policy and budget (#1940) — and resolves the same <name>.md in-process when the tool's subagent_type names it (iterion-side resolver pkg/backend/model/workspace_agents.go, read through os.Root at the workspace — ARB-83: the consumer of a spawn is iterion's SubagentRunner, not claw's own loop). The file's body is the child's system prompt; tools: INTERSECTS the node's effective set (never widens it; Claude spellings fold — Read is read_file); model: REFUSES the delegation, naming #1717 — a contributed file does not re-route the node's spend. Live e2e: task test:live:claw-workspace-agents (#1714); last verdict: see the ledger |
| hooks | hooks ✅ shipped (claude_code) · ✅ bridged (claw) | plugin hooks idempotently merged into .claude/settings.json; claude_code fires them via --setting-sources project. claw bridges the same document (registerSettingsHooks, pkg/backend/model/settings_hooks.go) and runs the command-type entries of every event claw-code-go exports — PreToolUse / PostToolUse / PostToolUseFailure / UserPromptSubmit / PreCompact / PostCompact / Stop — on the in-process path AND the sandboxed one (the launcher ships the parsed document over the wire; the in-container runner registers it). Matcher semantics follow claude_code: a regex on the tool name for tool events, ignored (always fires) on UserPromptSubmit/Stop, and the compaction source on PreCompact/PostCompact — "auto" matches, "manual" never does (claw compacts only at the threshold). UserPromptSubmit fires once per operator prompt: the harness's nudge/recovery re-asks and resumed tool_result turns do not re-fire, and a blocked prompt fires no Stop. A hook payload is truncated at 100 KiB with an explicit marker (the kernel's per-arg limit would otherwise fail the exec — a silent fail-open on a screening hook). Two known edges, both deliberate: operator-chatbox messages injected mid-loop (the inbox drain) are NOT re-screened by UserPromptSubmit — only the node's own prompt is, a hook arming prompt-injection screening should know the channel exists; and a matcher left on Stop/UserPromptSubmit sees its hook ARMED where a strict regex reading would have killed it — the matcher is ignored (with a warning), never applied. prompt-type entries, events claw does not export (SessionStart, …), meaningless matchers and invalid regexes are all skipped with a warning naming them; a hook exiting non-zero (≠ 2), timing out or failing to exec is logged and continues; the forced context-overflow compaction fires no PreCompact by design |
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 — unless iterion is the capability's only real consumer: the named-subagent resolver lives iterion-side (pkg/backend/model/workspace_agents.go, ARB-83), because iterion's SubagentRunner executes every spawn a workflow drives and claw's own loop serves none of them; a claw-side re-export for third-party embedders of claw-code-go is the bump lane's call. Adaptation bridges are acceptable as an interim only when native parity is impractical.
The commands, agents, and hooks manifest kinds are shipped today and all three reach claw as well as claude_code (see the rows above); on claw the named-subagent row's live witness is task test:live:claw-workspace-agents (#1714) — its last verdict lives in the ledger, and until a run records one the row reads never, not proven.
Public skill libraries (shipped)
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).
Implementation pointers
- Manifest + registry: pkg/plugin/manifest.go, pkg/plugin/registry.go; builtins under pkg/plugin/builtin/.
- Rewriter chain: pkg/backend/rewrite/rewrite.go. Callsites: claude_code hook (
installRewriteHook), claw bash builtin (RewriteCommandFieldCtx), tool node (executor_tool.go). - MCP-server contribution merge: pkg/backend/mcp/plugin_servers.go.
- Skill mirroring: pkg/runtime/plugin_skills.go.
- Sandbox bind-mounts:
addRewriterMountsin pkg/runtime/sandbox_mounts.go. - CLI: cmd/iterion/plugin.go + pkg/cli/plugin.go.
