Plugin System

July 27, 2026 · View on GitHub

Plugin System — four extension surfaces without code

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.

PropertyValue
Bundle personasoperator/cowork/personas/<name>.json
User override~/.corvin/cowork/personas/<name>.json
Hot-reloadimmediate, re-read per message
Bind per chat/cowork-bind <name> or chat_profiles.persona in settings.json
Auto-routingheuristic (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.

Forge Tool Lifecycle

How it works:

  1. Describe the tool in natural language in the chat.
  2. forge_tool() is called via the MCP server; name and schema are validated.
  3. The code runs in a bwrap sandbox: no network, no subprocess, fresh /tmp, read-only /usr.
  4. Creation is hash-chain logged to audit.jsonl (GDPR Art. 30/32).
  5. The tool is immediately available as mcp__forge__code_<name> — same turn.

Scopes (tool lifetime):

ScopeLifetime
taskOne LLM request
sessionUntil /new or /reset
projectRepo-wide persistent (.corvin/)
userGlobally 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.

Skill Promotion Scope Ladder

Promotion chain:

FromToCondition
tasksession≥ 1 positive grade
sessionproject≥ 3 grades, mean ≥ 0.5
projectuserforce=True (operator decision)

Auto-grading — after each bridge turn the adapter automatically grades active skills:

SignalScore
Mention / paraphrase in reply0.7
Explicit user approval0.9
User rejection / correction0.1
Rephrase on next turn0.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:

FieldEffect
whitelistAllowed users
chat_profilesPersona, voice mode, rate limit, LDD preset per chat
enabled_chats / debug_chatsEnable chats or put them in debug mode
voice_summary_modeauto · full · summary
progress_updatesProgress display during stream
rate_limit_per_hourMessage 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:

FlagWhat it turns on
plugin_console_surfacethe Plugins page and its REST routes
plugin_runtime_lifecycleinstalling / enabling / reconfiguring at runtime
plugin_health_monitoringhealth 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:

ScopeLifetimeStorageSlot-mirror
taskOne LLM requestin-memoryno
sessionUntil /new or /reset<corvin_home>/sessions/<bridge>:<chat>/no
projectRepo-wide persistent.corvin/ in projectyes
userGlobally persistent~/.corvin/yes
tenantTenant-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:

MechanismProtects
Path-Gate Hook (Layer 10)Prevents direct writes to forge/skill/audit/policy paths
bwrap sandboxTool code runs without network, without subprocess, fresh filesystem
Linter (fail-closed)Prompt-injection, secrets, persona-boundary in skills
Policy hot-reloadOperator can restrict tool creation at any time
Hash-chained auditEvery plugin event inseparably recorded in audit.jsonl
Consent gateNo 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

TopicDocument
System architecture & message flowArchitecture
Personas & auto-routingPersonas & Routing
Forge in depthForge
SkillForge & runtime generationRuntime Generation
Engine layer (Claude / Codex / OpenCode / Hermes)Engine Layer
Memory & conversation recallMemory Model
Data & ComputeData & Compute
EU AI Act + GDPR complianceAudit & Compliance
Multi-tenantMulti-tenant architecture
Layer plugins technical referenceLayer Plugins
Security hardeningLayer Security