Paperclip AI Plugin Architecture and Out-of-Process Worker Communication: A Complete Guide

Paperclip AI isolates each plugin in a dedicated child process, communicating via JSON-RPC 2.0 over newline-delimited stdin/stdout streams to ensure security and resource isolation while maintaining high-performance bidirectional messaging.

The paperclipai/paperclip repository implements a robust plugin SDK that enables third-party extensions to run safely in isolated environments. This architecture leverages out-of-process workers to execute plugin code, ensuring that malfunctioning or malicious plugins cannot compromise the host server or access unauthorized data. The system uses a lightweight yet complete RPC protocol to bridge the gap between the host process and sandboxed plugin workers.

Core Components of the Plugin SDK

The plugin architecture consists of six primary components that handle everything from plugin definition to UI integration.

Plugin Definition API (define-plugin.ts)

The entry point for plugin authors is the definePlugin function exported from [packages/plugins/sdk/src/define-plugin.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sdk/src/define-plugin.ts). This utility provides a typed PluginContext that exposes APIs for event handling, state management, HTTP requests, and UI data registration.

Plugin authors implement a setup hook that receives the context, allowing registration of event listeners and data providers before the worker begins processing messages.

Worker RPC Host (worker-rpc-host.ts)

Running inside each child process, the startWorkerRpcHost function (located in [packages/plugins/sdk/src/worker-rpc-host.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sdk/src/worker-rpc-host.ts)) implements the worker-side JSON-RPC server. This module reads RPC requests from process.stdin, dispatches them to the appropriate plugin handlers, and writes responses to process.stdout.

The host handles critical lifecycle events including initialization, event notifications, state calls, and graceful shutdown sequences.

Protocol Layer (protocol.ts)

The [packages/plugins/sdk/src/protocol.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sdk/src/protocol.ts) file defines the JSON-RPC schema, request/response types, and error codes (PLUGIN_RPC_ERROR_CODES). It exports helper functions parseMessage and serializeMessage for handling the newline-delimited JSON format used across all communication channels.

Host-Side Manager (PluginWorkerManager)

On the server side, the PluginWorkerManager (located in server/src/plugin-environment/) coordinates worker lifecycle management. This class spawns the child process, connects its streams to the RPC host, and exposes a high-level API to the rest of the Paperclip server infrastructure.

Sandbox Providers

Concrete isolation implementations reside in the sandbox providers package. Each provider (Kubernetes, Modal, Novita) implements a minimal worker.ts entry point that imports the SDK and starts the RPC host within its specific isolated environment:

UI Bridge (bridge.ts)

Frontend components communicate with plugins through the [ui/src/plugins/bridge.ts](https://github.com/paperclipai/paperclip/blob/master/ui/src/plugins/bridge.ts) module. This thin bridge forwards UI-side RPC calls to the host via the plugin SDK, enabling browser-based components to invoke plugin actions as if they were local function calls.

JSON-RPC Communication Flow

The worker RPC host implements a strict message protocol following the sequence defined in [worker-rpc-host.ts](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sdk/src/worker-rpc-host.ts) (lines 17-30):

  1. Initialization: The host sends an initialize request; the worker loads the plugin via definePlugin and executes its setup hook
  2. Event Notifications: The host pushes onEvent notifications to the worker, which dispatches them to registered plugin handlers
  3. SDK Calls: Plugin code invoking SDK methods (e.g., ctx.state.get) generate RPC requests back to the host, which fulfills them and returns responses
  4. Shutdown: A final shutdown request triggers plugin.onShutdown() before the process exits

All messages follow the JSON-RPC 2.0 specification with custom error mapping to PLUGIN_RPC_ERROR_CODES. The protocol uses line-delimited JSON strings parsed by the protocol.ts utilities, ensuring compatibility with standard Unix pipe semantics.

Implementing a Plugin Worker

Plugin authors create a worker entry point that defines event handlers and data providers:

// my-plugin/worker.ts
import { definePlugin } from "@paperclipai/plugin-sdk";

export default definePlugin({
  async setup(ctx) {
    // Subscribe to system events
    ctx.events.on("issue.created", async (event) => {
      const companyId = event.companyId;
      const cfg = await ctx.config.get(companyId);
      const apiKey = await ctx.secrets.resolve(cfg.apiKeyRef);
      
      await ctx.http.fetch("https://api.example.com/...", {
        method: "POST",
        headers: { Authorization: `Bearer ${apiKey}` },
        body: JSON.stringify({ title: event.payload.title }),
      });
    });

    // Register UI data endpoint
    ctx.data.register("health", async ({ companyId }) => {
      const state = await ctx.state.get({
        scopeKind: "company",
        scopeId: String(companyId),
        stateKey: "lastSync",
      });
      return { lastSync: state };
    });
  },
});

The compiled plugin is referenced in the plugin manifest and loaded by the sandbox provider's worker script.

Sandbox Provider Integration

Each sandbox provider implements a minimal bootstrap script that imports the plugin SDK and starts the RPC host:

// packages/plugins/sandbox-providers/kubernetes/src/worker.ts
import { startWorkerRpcHost } from "@paperclipai/plugin-sdk";
import plugin from "./plugin.js"; // compiled plugin

// Launch the RPC host with stdin/stdout streams
startWorkerRpcHost({ plugin });

This design pattern repeats across the Modal and Novita providers, allowing the platform to deploy plugins in Kubernetes pods, serverless sandboxes, or specialized containers while maintaining identical communication semantics.

Host-Side Process Management

The server spawns plugin workers using Node.js child processes and connects them to the RPC manager:

import { spawn } from "node:child_process";
import { PluginWorkerManager } from "server/src/plugin-environment/manager";

async function runPlugin(pluginPath: string) {
  const child = spawn("node", [pluginPath], {
    stdio: ["pipe", "pipe", "inherit"],
  });

  const manager = new PluginWorkerManager({
    stdin: child.stdout!,
    stdout: child.stdin!,
  });

  await manager.initialize(); // sends `initialize` RPC, triggers plugin.setup()
}

The PluginWorkerManager handles stream buffering, message framing, and error recovery, presenting a promise-based API to the rest of the application.

Frontend Integration via the UI Bridge

Browser components interact with out-of-process plugins through a React hook that ultimately routes through the bridge:

import { usePlugin } from "@paperclipai/ui-plugin-sdk";

function IssueSyncStatus({ issueId }: { issueId: string }) {
  const { data, isLoading } = usePlugin("health", { companyId: "123" });
  if (isLoading) return <span>Loading…</span>;
  return <span>Last sync: {data.lastSync}</span>;
}

The usePlugin hook communicates with the bridge (bridge.ts), which forwards the request to the Paperclip server. The server then routes the call to the appropriate worker process via the established JSON-RPC connection, returning the result through the same chain.

Summary

  • Out-of-process isolation ensures plugin code runs in separate child processes, preventing host compromise and enabling resource quotas
  • JSON-RPC 2.0 protocol over newline-delimited stdin/stdout provides lightweight, bidirectional communication between host and workers
  • SDK abstractions including definePlugin and startWorkerRpcHost hide RPC complexity behind typed JavaScript APIs
  • Sandbox providers (Kubernetes, Modal, Novita) enable flexible deployment strategies while maintaining identical communication patterns
  • UI bridge architecture allows frontend components to invoke plugin methods transparently, treating remote workers as local services

Frequently Asked Questions

Why does Paperclip AI use out-of-process workers instead of in-process plugins?

Running plugins in separate child processes provides critical security isolation and resource management. According to the source code in worker-rpc-host.ts, this architecture prevents plugin crashes from affecting the host server, allows enforcement of memory and CPU limits through containerization, and ensures that plugins cannot directly access the host's memory space or internal APIs. The JSON-RPC communication layer adds minimal overhead while enabling these safety guarantees.

How does the bidirectional JSON-RPC protocol handle plugin-initiated requests?

When plugin code calls SDK methods like ctx.state.get, the SDK runtime (implemented in the SDK's internal runtime module) constructs a JSON-RPC request object and writes it to process.stdout. The host process reads this request, executes the corresponding handler (e.g., retrieving state from the database), and returns a response through process.stdin. The startWorkerRpcHost function in worker-rpc-host.ts manages the correlation between requests and responses using standard JSON-RPC message IDs.

What sandbox providers are available for deploying Paperclip AI plugins?

The repository includes three reference implementations in the sandbox providers package: Kubernetes (packages/plugins/sandbox-providers/kubernetes), Modal (packages/plugins/sandbox-providers/modal), and Novita (packages/plugins/sandbox-providers/novita). Each provides a worker.ts entry point that imports @paperclipai/plugin-sdk and calls startWorkerRpcHost, allowing the same plugin code to run in pods, serverless functions, or specialized AI containers without modification.

How do frontend components communicate with plugins running in isolated processes?

The UI bridge (ui/src/plugins/bridge.ts) creates an abstraction layer where browser-based React hooks like usePlugin generate RPC calls that traverse through the Paperclip server to the appropriate worker. When a component requests data from a plugin's registered endpoint (e.g., ctx.data.register("health", ...)), the bridge forwards the request via the host's PluginWorkerManager, which routes it through the established stdin/stdout JSON-RPC connection to the worker process.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →