26 KiB
Paperclip Plugin System Specification
Status: proposed complete spec for the post-V1 plugin system
This document is the complete specification for Paperclip's plugin and extension architecture. It expands the brief plugin notes in doc/SPEC.md and should be read alongside the comparative analysis in doc/plugins/ideas-from-opencode.md.
This is not part of the V1 implementation contract in doc/SPEC-implementation.md. It is the full target architecture for the plugin system that should follow V1.
1. Scope
This spec covers:
- plugin packaging and installation
- runtime model
- trust model
- capability system
- UI extension surfaces
- event, job, and webhook surfaces
- workspace-oriented extension surfaces
- Postgres persistence for extensions
- operator workflows
- compatibility and upgrade rules
This spec does not cover:
- a public marketplace
- cloud/SaaS multi-tenancy
- arbitrary third-party schema migrations in the first plugin version
- arbitrary frontend bundle injection in the first plugin version
2. Core Assumptions
Paperclip plugin design is based on the following assumptions:
- Paperclip is single-tenant and self-hosted.
- Plugin installation is global to the instance.
- "Companies" remain core Paperclip business objects, but they are not plugin trust boundaries.
- Board governance, approval gates, budget hard-stops, and core task invariants remain owned by Paperclip core.
- Projects already have a real workspace model via
project_workspaces, and local/runtime plugins should build on that instead of inventing a separate workspace abstraction.
3. Goals
The plugin system must:
- Let operators install global instance-wide plugins.
- Let plugins add major capabilities without editing Paperclip core.
- Keep core governance and auditing intact.
- Support both local/runtime plugins and external SaaS connectors.
- Support future plugin categories such as:
- new agent adapters
- revenue tracking
- knowledge base
- issue tracker sync
- metrics/dashboards
- file/project tooling
- Use simple, explicit, typed contracts.
- Keep failures isolated so one plugin does not crash the entire instance.
4. Non-Goals
The first plugin system must not:
- Allow arbitrary plugins to override core routes or core invariants.
- Allow arbitrary plugins to mutate approval, auth, issue checkout, or budget enforcement logic.
- Allow arbitrary third-party plugins to run free-form DB migrations.
- Depend on project-local plugin folders such as
.paperclip/plugins. - Depend on automatic install-and-execute behavior at server startup from arbitrary config files.
5. Terminology
5.1 Instance
The single Paperclip deployment an operator installs and controls.
5.2 Company
A first-class Paperclip business object inside the instance.
5.3 Project Workspace
A workspace attached to a project through project_workspaces.
This is the primary local runtime anchor for file, terminal, git, and process tooling.
5.4 Platform Module
A trusted in-process extension loaded directly by Paperclip core.
Examples:
- agent adapters
- storage providers
- secret providers
- run-log backends
5.5 Plugin
An installable instance-wide extension package loaded through the Paperclip plugin runtime.
Examples:
- Linear sync
- GitHub Issues sync
- Grafana widgets
- Stripe revenue sync
- file browser
- terminal
- git workflow
5.6 Plugin Worker
The runtime process used for a plugin. In this spec, third-party plugins run out-of-process by default.
5.7 Capability
A named permission the host grants to a plugin. Plugins may only call host APIs that are covered by granted capabilities.
6. Extension Classes
Paperclip has two extension classes.
6.1 Platform Modules
Platform modules are:
- trusted
- in-process
- host-integrated
- low-level
They use explicit registries, not the general plugin worker protocol.
Platform module surfaces:
registerAgentAdapter()registerStorageProvider()registerSecretProvider()registerRunLogStore()- future
registerWorkspaceRuntime()if needed
Platform modules are the right place for:
- new agent adapter packages
- new storage backends
- new secret backends
- other host-internal systems that need direct process or DB integration
6.2 Plugins
Plugins are:
- globally installed per instance
- loaded through the plugin runtime
- additive
- capability-gated
- isolated from core via a stable SDK and host protocol
Plugin categories:
connectorworkspaceautomationui
A plugin may declare more than one category.
7. Project Workspaces Are The Local Tooling Anchor
Paperclip already has a concrete workspace model:
- projects expose
workspaces - projects expose
primaryWorkspace - the database contains
project_workspaces - project routes already manage workspaces
- heartbeat resolution already prefers project workspaces before falling back to task-session or agent-home workspaces
Therefore:
- File plugins should browse project workspaces first.
- Terminal sessions should launch against project workspaces by default.
- Git plugins should treat the selected project workspace as the repo root anchor.
- Process/server tracking should attach to project workspaces whenever possible.
- Issue and agent views may deep-link into project workspace context.
Project workspaces may exist in two modes:
- local directory mode:
cwdis present - repo-only mode:
repoUrland optionalrepoRefexist, but there is no localcwd
Plugins must handle repo-only workspaces explicitly:
- they may show metadata
- they may show sync state
- they may not assume local file/PTY/git access until a real
cwdexists
8. Installation Model
Plugin installation is global and operator-driven.
There is no per-company install table and no per-company enable/disable switch.
If a plugin needs business-object-specific mappings, those are stored as plugin configuration or plugin state.
Examples:
- one global Linear plugin install
- mappings from company A to Linear team X and company B to Linear team Y
- one global git plugin install
- per-project workspace state stored under
project_workspace
8.1 On-Disk Layout
Plugins live under the Paperclip instance directory.
Suggested layout:
~/.paperclip/instances/default/plugins/package.json~/.paperclip/instances/default/plugins/node_modules/~/.paperclip/instances/default/plugins/.cache/~/.paperclip/instances/default/data/plugins/<plugin-id>/
The package install directory and the plugin data directory are separate.
8.2 Operator Commands
Paperclip should add CLI commands:
pnpm paperclipai plugin listpnpm paperclipai plugin install <package[@version]>pnpm paperclipai plugin uninstall <plugin-id>pnpm paperclipai plugin upgrade <plugin-id> [version]pnpm paperclipai plugin doctor <plugin-id>
These commands are instance-level operations.
8.3 Install Process
The install process is:
- Resolve npm package and version.
- Install into the instance plugin directory.
- Read and validate plugin manifest.
- Reject incompatible plugin API versions.
- Display requested capabilities to the operator.
- Persist install record in Postgres.
- Start plugin worker and run health/validation.
- Mark plugin
readyorerror.
9. Load Order And Precedence
Load order must be deterministic.
- core platform modules
- built-in first-party plugins
- installed plugins sorted by:
- explicit operator-configured order if present
- otherwise manifest
id
Rules:
- plugin contributions are additive by default
- plugins may not override core routes or core actions by name collision
- if two plugins contribute the same route slug or UI slot id, the host must reject startup or force the operator to resolve the collision explicitly
10. Package Contract
Each plugin package must export a manifest and a worker entrypoint.
Suggested package layout:
dist/manifest.jsdist/worker.js
Suggested package.json keys:
{
"name": "@paperclip/plugin-linear",
"version": "0.1.0",
"paperclipPlugin": {
"manifest": "./dist/manifest.js",
"worker": "./dist/worker.js"
}
}
10.1 Manifest Shape
Normative manifest shape:
export interface PaperclipPluginManifestV1 {
id: string;
apiVersion: 1;
version: string;
displayName: string;
description: string;
categories: Array<"connector" | "workspace" | "automation" | "ui">;
minimumPaperclipVersion?: string;
capabilities: string[];
entrypoints: {
worker: string;
};
instanceConfigSchema?: JsonSchema;
jobs?: PluginJobDeclaration[];
webhooks?: PluginWebhookDeclaration[];
ui?: PluginUiDeclaration;
}
Rules:
idmust be globally uniqueidshould normally equal the npm package nameapiVersionmust match the host-supported plugin API versioncapabilitiesmust be static and install-time visible- config schema must be JSON Schema compatible
11. Runtime Model
11.1 Process Model
Third-party plugins run out-of-process by default.
Default runtime:
- Paperclip server starts one worker process per installed plugin
- the worker process is a Node process
- host and worker communicate over JSON-RPC on stdio
This design provides:
- failure isolation
- clearer logging boundaries
- easier resource limits
- a cleaner trust boundary than arbitrary in-process execution
11.2 Host Responsibilities
The host is responsible for:
- package install
- manifest validation
- capability enforcement
- process supervision
- job scheduling
- webhook routing
- activity log writes
- secret resolution
- workspace service enforcement
- UI route registration
11.3 Worker Responsibilities
The plugin worker is responsible for:
- validating its own config
- handling domain events
- handling scheduled jobs
- handling webhooks
- producing UI view models
- invoking host services through the SDK
- reporting health information
11.4 Failure Policy
If a worker fails:
- mark plugin status
error - surface error in plugin health UI
- keep the rest of the instance running
- retry start with bounded backoff
- do not drop other plugins or core services
12. Host-Worker Protocol
The host must support the following worker RPC methods.
Required methods:
initialize(input)health()shutdown()
Optional methods:
validateConfig(input)onEvent(input)runJob(input)handleWebhook(input)getPageModel(input)getWidgetModel(input)getDetailTabModel(input)performAction(input)
12.1 initialize
Called once on worker startup.
Input includes:
- plugin manifest
- resolved plugin config
- instance info
- host API version
12.2 health
Returns:
- status
- current error if any
- optional plugin-reported diagnostics
12.3 validateConfig
Runs after config changes and startup.
Returns:
ok- warnings
- errors
12.4 onEvent
Receives one typed Paperclip domain event.
Delivery semantics:
- at least once
- plugin must be idempotent
- no global ordering guarantee across all event types
- per-entity ordering is best effort but not guaranteed after retries
12.5 runJob
Runs a declared scheduled job.
The host provides:
- job key
- trigger source
- run id
- schedule metadata
12.6 handleWebhook
Receives inbound webhook payload routed by the host.
The host provides:
- endpoint key
- headers
- raw body
- parsed body if applicable
- request id
12.7 getPageModel
Returns a schema-driven view model for the plugin's main page.
12.8 getWidgetModel
Returns a schema-driven view model for a dashboard widget.
12.9 getDetailTabModel
Returns a schema-driven view model for a project, issue, agent, goal, or run detail tab.
12.10 performAction
Runs an explicit plugin action initiated by the board UI.
Examples:
- "resync now"
- "link GitHub issue"
- "create branch from issue"
- "restart process"
13. SDK Surface
Plugins do not talk to the DB directly. Plugins do not read raw secret material from persisted config. Plugins do not touch the filesystem directly outside the host services.
The SDK exposed to workers must provide typed host clients.
Required SDK clients:
ctx.configctx.eventsctx.jobsctx.httpctx.secretsctx.assetsctx.activityctx.statectx.entitiesctx.projectsctx.issuesctx.agentsctx.goalsctx.workspacectx.logger
13.1 Example SDK Shape
export interface PluginContext {
manifest: PaperclipPluginManifestV1;
config: {
get(): Promise<Record<string, unknown>>;
};
events: {
on(name: string, fn: (event: unknown) => Promise<void>): void;
};
jobs: {
register(key: string, input: { cron: string }, fn: (job: PluginJobContext) => Promise<void>): void;
};
state: {
get(input: ScopeKey): Promise<unknown | null>;
set(input: ScopeKey, value: unknown): Promise<void>;
delete(input: ScopeKey): Promise<void>;
};
entities: {
upsert(input: PluginEntityUpsert): Promise<void>;
list(input: PluginEntityQuery): Promise<PluginEntityRecord[]>;
};
workspace: WorkspacePluginApi;
}
14. Capability Model
Capabilities are mandatory and static. Every plugin declares them up front.
The host enforces capabilities in the SDK layer and refuses calls outside the granted set.
14.1 Capability Categories
Data Read
companies.readprojects.readproject.workspaces.readissues.readissue.comments.readagents.readgoals.readactivity.readcosts.read
Data Write
issues.createissues.updateissue.comments.createassets.writeassets.readactivity.log.writemetrics.write
Runtime / Integration
events.subscribejobs.schedulewebhooks.receivehttp.outboundsecrets.read-ref
UI
instance.settings.registerui.sidebar.registerui.page.registerui.detailTab.registerui.dashboardWidget.registerui.action.register
Workspace
workspace.fs.readworkspace.fs.writeworkspace.fs.statworkspace.fs.searchworkspace.pty.openworkspace.pty.inputworkspace.pty.resizeworkspace.pty.terminateworkspace.pty.subscribeworkspace.git.statusworkspace.git.diffworkspace.git.logworkspace.git.branch.createworkspace.git.commitworkspace.git.worktree.createworkspace.git.pushworkspace.process.registerworkspace.process.listworkspace.process.readworkspace.process.terminateworkspace.process.restartworkspace.process.logs.readworkspace.http.probe
14.2 Forbidden Capabilities
The host must not expose capabilities for:
- approval decisions
- budget override
- auth bypass
- issue checkout lock override
- direct DB access
- direct filesystem access outside approved workspace services
14.3 Upgrade Rules
If a plugin upgrade adds capabilities:
- the host must mark the plugin
upgrade_pending - the operator must explicitly approve the new capability set
- the new version does not become
readyuntil approval completes
15. Event System
The host must emit typed domain events that plugins may subscribe to.
Minimum event set:
company.createdcompany.updatedproject.createdproject.updatedproject.workspace_createdproject.workspace_updatedproject.workspace_deletedissue.createdissue.updatedissue.comment.createdagent.createdagent.updatedagent.status_changedagent.run.startedagent.run.finishedagent.run.failedagent.run.cancelledapproval.createdapproval.decidedcost_event.createdactivity.logged
Each event must include:
- event id
- event type
- occurred at
- actor metadata when applicable
- primary entity metadata
- typed payload
16. Scheduled Jobs
Plugins may declare scheduled jobs in their manifest.
Job rules:
- Each job has a stable
job_key. - The host is the scheduler of record.
- The host prevents overlapping execution of the same plugin/job combination unless explicitly allowed later.
- Every job run is recorded in Postgres.
- Failed jobs are retryable.
17. Webhooks
Plugins may declare webhook endpoints in their manifest.
Webhook route shape:
POST /api/plugins/:pluginId/webhooks/:endpointKey
Rules:
- The host owns the public route.
- The worker receives the request body through
handleWebhook. - Signature verification happens in plugin code using secret refs resolved by the host.
- Every delivery is recorded.
- Webhook handling must be idempotent.
18. UI Extension Model
The first plugin UI system is schema-driven.
The host renders plugin data using built-in UI components. Plugins return view models, not arbitrary React bundles.
18.1 Global Operator Routes
/settings/plugins/settings/plugins/:pluginId
These routes are instance-level.
18.2 Company-Context Routes
/:companyPrefix/plugins/:pluginId
These routes exist because the board UI is organized around companies even though plugin installation is global.
18.3 Detail Tabs
Plugins may add tabs to:
- project detail
- issue detail
- agent detail
- goal detail
- run detail
Recommended route pattern:
/:companyPrefix/<entity>/:id?tab=<plugin-tab-id>
18.4 Dashboard Widgets
Plugins may add cards or sections to the dashboard.
18.5 Sidebar Entries
Plugins may add sidebar links to:
- global plugin settings
- company-context plugin pages
18.6 Allowed View Model Types
The host should support a limited set of schema-rendered components:
- metric cards
- status lists
- tables
- timeseries charts
- markdown text
- key/value blocks
- action bars
- log views
- JSON/debug views
Arbitrary frontend bundle injection is explicitly out of scope for the first plugin system.
19. Workspace Service APIs
Workspace service APIs are the foundation for local tooling plugins.
All workspace APIs must route through the host and validate against known project workspace roots.
19.1 Project Workspace APIs
Required host APIs:
- list project workspaces
- get project primary workspace
- resolve project workspace from issue
- resolve current workspace from agent/run when available
19.2 File APIs
- read file
- write file
- stat path
- search path or filename
- list directory
All file APIs take a resolved workspace anchor plus a relative path.
19.3 PTY APIs
- open terminal session
- send input
- resize
- terminate
- subscribe to output
PTY sessions should default to the selected project workspace when one exists.
19.4 Git APIs
- status
- diff
- log
- branch create
- worktree create
- commit
- push
Git APIs require a local cwd.
If the workspace is repo-only, the host must reject local git operations until a local checkout exists.
19.5 Process APIs
- register process
- list processes
- read process metadata
- terminate
- restart
- read logs
- probe health endpoint
Process tracking should attach to project_workspace when possible.
20. Persistence And Postgres
20.1 Database Principles
- Core Paperclip data stays in first-party tables.
- Most plugin-owned data starts in generic extension tables.
- Plugin data should scope to existing Paperclip objects before new tables are introduced.
- Arbitrary third-party schema migrations are out of scope for the first plugin system.
20.2 Core Table Reuse
If data becomes part of the actual Paperclip product model, it should become a first-party table.
Examples:
project_workspacesis already first-party- if Paperclip later decides git state is core product data, it should become a first-party table too
20.3 Required Tables
plugins
iduuid pkplugin_keytext unique not nullpackage_nametext not nullversiontext not nullapi_versionint not nullcategoriestext[] not nullmanifest_jsonjsonb not nullstatusenum:installed | ready | error | upgrade_pendinginstall_orderint nullinstalled_attimestamptz not nullupdated_attimestamptz not nulllast_errortext null
Indexes:
- unique
plugin_key status
plugin_config
iduuid pkplugin_iduuid fkplugins.idunique not nullconfig_jsonjsonb not nullinstalled_attimestamptz not nullupdated_attimestamptz not nulllast_errortext null
plugin_state
iduuid pkplugin_iduuid fkplugins.idnot nullscope_kindenum:instance | company | project | project_workspace | agent | issue | goal | runscope_iduuid/text nullnamespacetext not nullstate_keytext not nullvalue_jsonjsonb not nullupdated_attimestamptz not null
Constraints:
- unique
(plugin_id, scope_kind, scope_id, namespace, state_key)
Examples:
- Linear external IDs keyed by
issue - GitHub sync cursors keyed by
project - file browser preferences keyed by
project_workspace - git branch metadata keyed by
project_workspace - process metadata keyed by
project_workspaceorrun
plugin_jobs
iduuid pkplugin_iduuid fkplugins.idnot nullscope_kindenum nullablescope_iduuid/text nulljob_keytext not nullscheduletext nullstatusenum:idle | queued | running | errornext_run_attimestamptz nulllast_started_attimestamptz nulllast_finished_attimestamptz nulllast_succeeded_attimestamptz nulllast_errortext null
Constraints:
- unique
(plugin_id, scope_kind, scope_id, job_key)
plugin_job_runs
iduuid pkplugin_job_iduuid fkplugin_jobs.idnot nullplugin_iduuid fkplugins.idnot nullstatusenum:queued | running | succeeded | failed | cancelledtriggerenum:schedule | manual | retrystarted_attimestamptz nullfinished_attimestamptz nullerrortext nulldetails_jsonjsonb null
Indexes:
(plugin_id, started_at desc)(plugin_job_id, started_at desc)
plugin_webhook_deliveries
iduuid pkplugin_iduuid fkplugins.idnot nullscope_kindenum nullablescope_iduuid/text nullendpoint_keytext not nullstatusenum:received | processed | failed | ignoredrequest_idtext nullheaders_jsonjsonb nullbody_jsonjsonb nullreceived_attimestamptz not nullhandled_attimestamptz nullresponse_codeint nullerrortext null
Indexes:
(plugin_id, received_at desc)(plugin_id, endpoint_key, received_at desc)
plugin_entities (optional but recommended)
iduuid pkplugin_iduuid fkplugins.idnot nullentity_typetext not nullscope_kindenum not nullscope_iduuid/text nullexternal_idtext nulltitletext nullstatustext nulldata_jsonjsonb not nullcreated_attimestamptz not nullupdated_attimestamptz not null
Indexes:
(plugin_id, entity_type, external_id)unique whenexternal_idis not null(plugin_id, scope_kind, scope_id, entity_type)
Use cases:
- imported Linear issues
- imported GitHub issues
- plugin-owned process records
- plugin-owned external metric bindings
20.4 Activity Log Changes
The activity log should extend actor_type to include plugin.
New actor enum:
agentusersystemplugin
Plugin-originated mutations should write:
actor_type = pluginactor_id = <plugin-id>
20.5 Plugin Migrations
The first plugin system does not allow arbitrary third-party migrations.
Later, if custom tables become necessary, the system may add a trusted-module-only migration path.
21. Secrets
Plugin config must never persist raw secret values.
Rules:
- Plugin config stores secret refs only.
- Secret refs resolve through the existing Paperclip secret provider system.
- Plugin workers receive resolved secrets only at execution time.
- Secret values must never be written to:
- plugin config JSON
- activity logs
- webhook delivery rows
- error messages
22. Auditing
All plugin-originated mutating actions must be auditable.
Minimum requirements:
- activity log entry for every mutation
- job run history
- webhook delivery history
- plugin health page
- install/upgrade history in
plugins
23. Operator UX
23.1 Global Settings
Global plugin settings page must show:
- installed plugins
- versions
- status
- requested capabilities
- current errors
- install/upgrade/remove actions
23.2 Plugin Settings Page
Each plugin may expose:
- config form derived from
instanceConfigSchema - health details
- recent job history
- recent webhook history
- capability list
Route:
/settings/plugins/:pluginId
23.3 Company-Context Plugin Page
Each plugin may expose a company-context main page:
/:companyPrefix/plugins/:pluginId
This page is where board users do most day-to-day work.
24. Example Mappings
This spec directly supports the following plugin types:
@paperclip/plugin-workspace-files@paperclip/plugin-terminal@paperclip/plugin-git@paperclip/plugin-linear@paperclip/plugin-github-issues@paperclip/plugin-grafana@paperclip/plugin-runtime-processes@paperclip/plugin-stripe
25. Compatibility And Versioning
Rules:
- Host supports one or more explicit plugin API versions.
- Plugin manifest declares exactly one
apiVersion. - Host rejects unsupported versions at install time.
- SDK packages are versioned with the host protocol.
- Plugin upgrades are explicit operator actions.
- Capability expansion requires explicit operator approval.
26. Recommended Delivery Order
Phase 1
- plugin manifest
- install/list/remove/upgrade CLI
- global settings UI
- plugin process manager
- capability enforcement
plugins,plugin_config,plugin_state,plugin_jobs,plugin_job_runs,plugin_webhook_deliveries- event bus
- jobs
- webhooks
- settings page
- dashboard widget/page/tab schema rendering
This phase is enough for:
- Linear
- GitHub Issues
- Grafana
- Stripe
Phase 2
- project workspace service built on
project_workspaces - file APIs
- PTY APIs
- git APIs
- process APIs
- project-context tabs for plugin pages
This phase is enough for:
- file browser
- terminal
- git workflow
- process/server tracking
Phase 3
- optional
plugin_entities - richer action systems
- trusted-module migration path if truly needed
- optional richer frontend extension model
- plugin ecosystem/distribution work
27. Final Design Decision
Paperclip should not implement a generic in-process hook bag modeled directly after local coding tools.
Paperclip should implement:
- trusted platform modules for low-level host integration
- globally installed out-of-process plugins for additive instance-wide capabilities
- schema-driven UI contributions
- project workspace-based local tooling
- generic extension tables for most plugin state
- strict preservation of core governance and audit rules
That is the complete target design for the Paperclip plugin system.