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

> Explore the Paperclip AI plugin architecture and out-of-process worker communication. Learn how JSON-RPC 2.0 and stdin/stdout ensure secure, isolated, and high-performance messaging.

- Repository: [Paperclip/paperclip](https://github.com/paperclipai/paperclip)
- Tags: architecture
- Published: 2026-08-12

---

**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`](https://github.com/paperclipai/paperclip/blob/main/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/main/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`](https://github.com/paperclipai/paperclip/blob/main/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/main/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`](https://github.com/paperclipai/paperclip/blob/main/protocol.ts))

The [[`packages/plugins/sdk/src/protocol.ts`](https://github.com/paperclipai/paperclip/blob/main/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`](https://github.com/paperclipai/paperclip/blob/main/worker.ts) entry point that imports the SDK and starts the RPC host within its specific isolated environment:

- **[[`kubernetes/worker.ts`](https://github.com/paperclipai/paperclip/blob/main/kubernetes/worker.ts)](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sandbox-providers/kubernetes/src/worker.ts)**: Launches workers inside Kubernetes pods
- **[[`modal/worker.ts`](https://github.com/paperclipai/paperclip/blob/main/modal/worker.ts)](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sandbox-providers/modal/src/worker.ts)**: Executes within Modal sandboxes  
- **[[`novita/worker.ts`](https://github.com/paperclipai/paperclip/blob/main/novita/worker.ts)](https://github.com/paperclipai/paperclip/blob/master/packages/plugins/sandbox-providers/novita/src/worker.ts)**: Runs in Novita containers

### UI Bridge ([`bridge.ts`](https://github.com/paperclipai/paperclip/blob/main/bridge.ts))

Frontend components communicate with plugins through the [[`ui/src/plugins/bridge.ts`](https://github.com/paperclipai/paperclip/blob/main/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/main/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`](https://github.com/paperclipai/paperclip/blob/main/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:

```typescript
// 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:

```typescript
// 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:

```typescript
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:

```typescript
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`](https://github.com/paperclipai/paperclip/blob/main/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`](https://github.com/paperclipai/paperclip/blob/main/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`](https://github.com/paperclipai/paperclip/blob/main/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`](https://github.com/paperclipai/paperclip/blob/main/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`](https://github.com/paperclipai/paperclip/blob/main/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.