Plugin System
July 27, 2026 · View on GitHub
Corvin extends without forking and without restarting any process. All four plugin surfaces are declarative, hot-reloadable, and operate within the existing security and compliance guarantees.
1. Personas — who responds (Layer 4)
Personas are JSON files that define which role an agent takes in a particular chat: which tools are allowed, which MCP server is loaded, which system prompt is appended, and which working directory is used.
| Property | Value |
|---|---|
| Bundle personas | operator/cowork/personas/<name>.json |
| User override | ~/.corvin/cowork/personas/<name>.json |
| Hot-reload | immediate, re-read per message |
| Bind per chat | /cowork-bind <name> or chat_profiles.persona in settings.json |
| Auto-routing | heuristic (no API call) or embedding-based |
Example persona:
{
"name": "research",
"description": "Deep-search researcher with web access",
"permission_mode": "bypassPermissions",
"mcp_servers": {
"brave": { "command": "npx", "args": ["@modelcontextprotocol/server-brave-search"] }
},
"append_system": "Answer with cited sources and URLs only.",
"working_dir": "/home/user/research"
}
Creating a custom persona:
/cowork-add research # copy bundle persona into user dir
# edit the file — active immediately, no restart
/cowork-bind research # bind current chat to this persona
/cowork-list # list all known personas
→ Details: Personas & Routing · Layer Plugins Ref
2. Forge — runtime tools (Layer 6)
Forge generates schema-bound, sandboxed Python tools via chat command. A tool is callable via MCP immediately after creation — no deployment pipeline, no restart.
How it works:
- Describe the tool in natural language in the chat.
forge_tool()is called via the MCP server; name and schema are validated.- The code runs in a
bwrapsandbox: no network, no subprocess, fresh/tmp, read-only/usr. - Creation is hash-chain logged to
audit.jsonl(GDPR Art. 30/32). - The tool is immediately available as
mcp__forge__code_<name>— same turn.
Scopes (tool lifetime):
| Scope | Lifetime |
|---|---|
task | One LLM request |
session | Until /new or /reset |
project | Repo-wide persistent (.corvin/) |
user | Globally persistent (~/.corvin/) |
Operator control via ~/.corvin/global/forge/policy.json (hot-reloaded, path-gate protected):
{
"max_tools_per_session": 20,
"network": "deny",
"allow_scopes": ["session", "project"]
}
Secrets — tools declare env-var names in meta.secrets; values are injected at runtime
from the vault (~/.config/corvin-voice/secrets.json) via bwrap env-inject — never in the
prompt, never in the audit log.
→ Details: Forge · Layer Plugins Ref
3. SkillForge — runtime skills (Layer 7)
SkillForge generates Markdown skills that are prompt-injected into future subprocess turns. The agent learns new behaviours purely through Markdown — no code required.
Promotion chain:
| From | To | Condition |
|---|---|---|
task | session | ≥ 1 positive grade |
session | project | ≥ 3 grades, mean ≥ 0.5 |
project | user | force=True (operator decision) |
Auto-grading — after each bridge turn the adapter automatically grades active skills:
| Signal | Score |
|---|---|
| Mention / paraphrase in reply | 0.7 |
| Explicit user approval | 0.9 |
| User rejection / correction | 0.1 |
| Rephrase on next turn | 0.3 |
Example — create a new skill:
/skill-create csv_workflow session
Then enter the Markdown body:
# CSV Workflow
Load both files via pandas.read_csv with the same dtype map.
Sort by primary key. Emit a row-wise Markdown diff.
The skill is active in the current session immediately and can work its way up to project scope
via grading, where it applies to all chats in the repo.
Linter (fail-closed): NFKC normalisation, prompt-injection check, secrets scan, persona-boundary check, size limit.
Slot-mirror — for project- and user-scoped skills a
operator/skill-forge/skills/dyn/<name>/SKILL.md is written (gitignored),
injected directly into the engine. Task/session skills have no slot-mirror
(prevents cross-chat leak).
→ Details: Runtime Generation · Layer Plugins Ref
4. Bridge Configuration — hot-reload
operator/bridges/<channel>/settings.json is re-read per incoming message.
Changes take effect immediately — no process restart required.
What hot-reloads:
| Field | Effect |
|---|---|
whitelist | Allowed users |
chat_profiles | Persona, voice mode, rate limit, LDD preset per chat |
enabled_chats / debug_chats | Enable chats or put them in debug mode |
voice_summary_mode | auto · full · summary |
progress_updates | Progress display during stream |
rate_limit_per_hour | Message budget per hour |
What requires restart: bridge tokens (discord_token, telegram_token etc.),
HTTP ports, structural daemon code changes.
Activate a persona per chat (without /cowork-bind):
{
"chat_profiles": {
"1234567890": {
"persona": "research",
"voice_summary_mode": "summary"
}
}
}
Control LDD layers per chat:
/ldd-on # enable LDD for this chat
/ldd-set layer=per_subtask_e2e on
/ldd-preset full # enable all layers
/ldd-status # show current status
→ Details: Architecture · Layer Voice/LDD Ref
5. Plugin Registry — installable extensions (ADR-0233)
The four surfaces above extend Corvin declaratively. The plugin registry extends it
with code: a Python class that implements the CorvinPlugin lifecycle
(on_load / on_unload / health_check) and registers itself with the layer it
extends — an engine, a compute backend, a bridge channel, an STT provider, a
notification / recall / summary / router provider, or an audit / user backend.
Ships dark. Three flags in Settings → Features, all off on a fresh install and after an upgrade:
| Flag | What it turns on |
|---|---|
plugin_console_surface | the Plugins page and its REST routes |
plugin_runtime_lifecycle | installing / enabling / reconfiguring at runtime |
plugin_health_monitoring | health polling + metrics export |
With all three off — the state of a fresh install — no plugins are loaded at all,
and nothing about an existing install changes. Note that spec.plugins.installed
from ADR-0030 is declared but not wired: loader.discover_and_load() has no
caller, so putting entries there has no effect today. Loading happens only through
the registry, which requires plugin_runtime_lifecycle.
Per-tenant, not global:
~/.corvin/tenants/<tenant>/plugins/
├── registry.yaml # what is installed, enabled, and configured (mode 0600)
└── instances/<plugin_id>/ # the plugin's own state
Settings without UI code. A plugin declares a JSON Schema; the Console renders the form from it (text, select, slider, checkbox, nested objects) and the backend re-validates against the same schema before saving. A rejected save leaves the previous configuration intact.
Consent before enable. A plugin whose origin is community, or whose declared
pii_risk is high, cannot be enabled without an explicit confirmation; the grant
is recorded in the audit chain (GDPR Art. 6, 7).
Extension is additive — never a replacement. This is the part that matters for
compliance: an audit_backend plugin does not own the audit trail. Corvin writes
every event to its own hash-chained audit.jsonl first, and only then hands the
plugin a copy to forward elsewhere. A plugin cannot suppress, rewrite or delay a
compliance record, and a boot tripwire refuses to start Corvin if the core audit
writer is unreachable or its chain does not verify. A user_backend that fails,
times out, or rejects a credential always means deny — never a guest session.
That last sentence describes a mechanism that is implemented but unreached
(verified 2026-07-27): CorvinOS has no credential auth path for it to sit on. The
only live login is localhost-only and credential-less, so there is no guest to fall
back to and nothing calls authenticate(). The rule binds the first credential login
that gets built; it is not a guarantee any surface currently exercises. See
PLUGIN_SYSTEM_ACTIVATION_PLAN.md
Stage 2 for why wiring it into the localhost login would lock the operator out.
Failure containment. Each plugin has its own circuit breaker: after repeated failures it stops being called for a cooldown instead of slowing every request that touches it. Health and breaker state are visible on the Plugins page.
→ Details: Layer Plugins Ref
Scope Model
Forge tools and skills share the same five-scope model:
| Scope | Lifetime | Storage | Slot-mirror |
|---|---|---|---|
task | One LLM request | in-memory | no |
session | Until /new or /reset | <corvin_home>/sessions/<bridge>:<chat>/ | no |
project | Repo-wide persistent | .corvin/ in project | yes |
user | Globally persistent | ~/.corvin/ | yes |
tenant | Tenant-wide | ~/.corvin/tenants/<id>/ | yes |
Session cleanup: /new / /clear / /reset atomically deletes all session-scoped skills and
Forge tools — a session.reset event is written to audit.jsonl before any files are removed.
Security & Compliance
All four plugin surfaces run behind the same security stack:
| Mechanism | Protects |
|---|---|
| Path-Gate Hook (Layer 10) | Prevents direct writes to forge/skill/audit/policy paths |
| bwrap sandbox | Tool code runs without network, without subprocess, fresh filesystem |
| Linter (fail-closed) | Prompt-injection, secrets, persona-boundary in skills |
| Policy hot-reload | Operator can restrict tool creation at any time |
| Hash-chained audit | Every plugin event inseparably recorded in audit.jsonl |
| Consent gate | No automatic activation without user consent |
MCP servers are trusted in-process code
Unlike Forge tools — which run inside the bwrap sandbox as subprocesses and
cannot reach the adapter's Python memory — MCP servers load in-process.
A compromised or malicious MCP server binary has unrestricted access to the
adapter's Python namespace. No MappingProxyType or env-var
snapshot protects against this; it is an accepted, documented limitation
of the in-process trust boundary.
Operator vetting of MCP server binaries is mandatory before deployment.
Only add an MCP server you trust at the same level as the adapter itself. Treat
mcp_servers entries in a persona as a trust grant, not a sandbox.
→ Audit & Compliance · Layer Security Ref · Licensing baseline
Further Reading
| Topic | Document |
|---|---|
| System architecture & message flow | Architecture |
| Personas & auto-routing | Personas & Routing |
| Forge in depth | Forge |
| SkillForge & runtime generation | Runtime Generation |
| Engine layer (Claude / Codex / OpenCode / Hermes) | Engine Layer |
| Memory & conversation recall | Memory Model |
| Data & Compute | Data & Compute |
| EU AI Act + GDPR compliance | Audit & Compliance |
| Multi-tenant | Multi-tenant architecture |
| Layer plugins technical reference | Layer Plugins |
| Security hardening | Layer Security |