$npx -y skills add IncomeStreamSurfer/paperclip-surfers --skill create-agent-adapterTechnical guide for creating a new Paperclip agent adapter. Use when building a new adapter package, adding support for a new AI coding tool (e.g. a new CLI agent, API-based agent, or custom process), or when modifying the adapter system. Covers the required interfaces, module st
| 1 | # Creating a Paperclip Agent Adapter |
| 2 | |
| 3 | An adapter bridges Paperclip's orchestration layer to a specific AI agent runtime (Claude Code, Codex CLI, a custom process, an HTTP endpoint, etc.). Each adapter is a self-contained package that provides implementations for **three consumers**: the server, the UI, and the CLI. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | ## 1. Architecture Overview |
| 8 | |
| 9 | ``` |
| 10 | packages/adapters/<name>/ |
| 11 | src/ |
| 12 | index.ts # Shared metadata (type, label, models, agentConfigurationDoc) |
| 13 | server/ |
| 14 | index.ts # Server exports: execute, sessionCodec, parse helpers |
| 15 | execute.ts # Core execution logic (AdapterExecutionContext -> AdapterExecutionResult) |
| 16 | parse.ts # Stdout/result parsing for the agent's output format |
| 17 | ui/ |
| 18 | index.ts # UI exports: parseStdoutLine, buildConfig |
| 19 | parse-stdout.ts # Line-by-line stdout -> TranscriptEntry[] for the run viewer |
| 20 | build-config.ts # CreateConfigValues -> adapterConfig JSON for agent creation form |
| 21 | cli/ |
| 22 | index.ts # CLI exports: formatStdoutEvent |
| 23 | format-event.ts # Colored terminal output for `paperclipai run --watch` |
| 24 | package.json |
| 25 | tsconfig.json |
| 26 | ``` |
| 27 | |
| 28 | Three separate registries consume adapter modules: |
| 29 | |
| 30 | | Registry | Location | Interface | |
| 31 | |----------|----------|-----------| |
| 32 | | Server | `server/src/adapters/registry.ts` | `ServerAdapterModule` | |
| 33 | | UI | `ui/src/adapters/registry.ts` | `UIAdapterModule` | |
| 34 | | CLI | `cli/src/adapters/registry.ts` | `CLIAdapterModule` | |
| 35 | |
| 36 | --- |
| 37 | |
| 38 | ## 2. Shared Types (`@paperclipai/adapter-utils`) |
| 39 | |
| 40 | All adapter interfaces live in `packages/adapter-utils/src/types.ts`. Import from `@paperclipai/adapter-utils` (types) or `@paperclipai/adapter-utils/server-utils` (runtime helpers). |
| 41 | |
| 42 | ### Core Interfaces |
| 43 | |
| 44 | ```ts |
| 45 | // The execute function signature — every adapter must implement this |
| 46 | interface AdapterExecutionContext { |
| 47 | runId: string; |
| 48 | agent: AdapterAgent; // { id, companyId, name, adapterType, adapterConfig } |
| 49 | runtime: AdapterRuntime; // { sessionId, sessionParams, sessionDisplayId, taskKey } |
| 50 | config: Record<string, unknown>; // The agent's adapterConfig blob |
| 51 | context: Record<string, unknown>; // Runtime context (taskId, wakeReason, approvalId, etc.) |
| 52 | onLog: (stream: "stdout" | "stderr", chunk: string) => Promise<void>; |
| 53 | onMeta?: (meta: AdapterInvocationMeta) => Promise<void>; |
| 54 | authToken?: string; |
| 55 | } |
| 56 | |
| 57 | interface AdapterExecutionResult { |
| 58 | exitCode: number | null; |
| 59 | signal: string | null; |
| 60 | timedOut: boolean; |
| 61 | errorMessage?: string | null; |
| 62 | usage?: UsageSummary; // { inputTokens, outputTokens, cachedInputTokens? } |
| 63 | sessionId?: string | null; // Legacy — prefer sessionParams |
| 64 | sessionParams?: Record<string, unknown> | null; // Opaque session state persisted between runs |
| 65 | sessionDisplayId?: string | null; |
| 66 | provider?: string | null; // "anthropic", "openai", etc. |
| 67 | model?: string | null; |
| 68 | costUsd?: number | null; |
| 69 | resultJson?: Record<string, unknown> | null; |
| 70 | summary?: string | null; // Human-readable summary of what the agent did |
| 71 | clearSession?: boolean; // true = tell Paperclip to forget the stored session |
| 72 | } |
| 73 | |
| 74 | interface AdapterSessionCodec { |
| 75 | deserialize(raw: unknown): Record<string, unknown> | null; |
| 76 | serialize(params: Record<string, unknown> | null): Record<string, unknown> | null; |
| 77 | getDisplayId?(params: Record<string, unknown> | null): string | null; |
| 78 | } |
| 79 | ``` |
| 80 | |
| 81 | ### Module Interfaces |
| 82 | |
| 83 | ```ts |
| 84 | // Server — registered in server/src/adapters/registry.ts |
| 85 | interface ServerAdapterModule { |
| 86 | type: string; |
| 87 | execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>; |
| 88 | testEnvironment(ctx: AdapterEnvironmentTestContext): Promise<AdapterEnvironmentTestResult>; |
| 89 | sessionCodec?: AdapterSessionCodec; |
| 90 | supportsLocalAgentJwt?: boolean; |
| 91 | models?: { id: string; label: string }[]; |
| 92 | agentConfigurationDoc?: string; |
| 93 | } |
| 94 | |
| 95 | // UI — registered in ui/src/adapters/registry.ts |
| 96 | interface UIAdapterModule { |
| 97 | type: string; |
| 98 | label: string; |
| 99 | parseStdoutLine: (line: string, ts: string) => TranscriptEntry[]; |
| 100 | ConfigFields: ComponentType<AdapterConfigFieldsProps>; |
| 101 | buildAdapterConfig: (values: CreateConfigValues) => Record<string, unknown>; |
| 102 | } |
| 103 | |
| 104 | // CLI — registered in cli/src/adapters/registry.ts |
| 105 | interface CLIAdapterModule { |
| 106 | type: string; |
| 107 | formatStdoutEvent: (line: string, debug: boolean) => void; |
| 108 | } |
| 109 | ``` |
| 110 | |
| 111 | --- |
| 112 | |
| 113 | ## 2.1 Adapter Environment Test Contract |
| 114 | |
| 115 | Every server adapter must implement `testEnvironment(...)`. This powers the board UI "Test environment" button in agent configuration. |
| 116 | |
| 117 | ```ts |
| 118 | type AdapterEn |