Creating an Adapter
Build a custom adapter when the built-in adapters do not fit your runtime. This page covers the code shape and runtime contracts; if you are packaging an installable plugin, read External Adapters first.
Tip: If you are using Claude Code to scaffold the adapter, the
.agents/skills/create-agent-adapterskill can walk you through the same structure interactively.
Built-In Vs External
Section titled “Built-In Vs External”| Area | Built-in | External |
|---|---|---|
| Source | Lives in the Paperclip repo | Lives in its own package |
| Registration | Added to the host registry | Loaded through the adapter plugin store |
| UI parser | Static import | Optional ./ui-parser export |
| Best for | Core adapters and host-owned runtimes | Independent distribution and local plugins |
For most new runtime integrations, start as an external adapter package. Move to a built-in only if Paperclip itself needs to ship it.
Recommended Package Layout
Section titled “Recommended Package Layout”my-adapter/ package.json tsconfig.json src/ index.ts server/ index.ts execute.ts test.ts ui-parser.ts cli/ format-event.tsThe important rule is simple: keep the package self-contained and make the package root export the metadata and server factory.
The cli/format-event.ts file is optional. Add it only if you want a custom live-watch event formatter for local adapter development and testing.
Root Metadata
Section titled “Root Metadata”src/index.ts is imported by the host and should stay dependency-light.
export const type = "my_adapter";export const label = "My Adapter";export const models = [{ id: "model-a", label: "Model A" }];export const agentConfigurationDoc = `# my_adapter agent configuration
Use when:- ...
Don't use when:- ...
Core fields:- ...`;
export { createServerAdapter } from "./server/index.js";The agentConfigurationDoc string is what the UI shows when a user configures the adapter.
Server Factory
Section titled “Server Factory”createServerAdapter() is the server-side entrypoint. It should return a ServerAdapterModule that wires execution and environment tests together.
import type { ServerAdapterModule } from "@paperclipai/adapter-utils";import { execute } from "./execute.js";import { testEnvironment } from "./test.js";
export function createServerAdapter(): ServerAdapterModule { return { type: "my_adapter", execute, testEnvironment, models: [{ id: "model-a", label: "Model A" }], agentConfigurationDoc, };}That module is the contract Paperclip relies on for the adapter lifecycle.
Execute
Section titled “Execute”execute() receives an AdapterExecutionContext and returns an AdapterExecutionResult.
Use it to:
- Read config with the safe helpers from
@paperclipai/adapter-utils/server-utils. - Build the runtime environment with
buildPaperclipEnv(agent). - Resolve or resume session state from
runtime.sessionParams. - Render any prompt template with
renderTemplate(). - Spawn the command or call the remote service.
- Return usage, cost, session, and result metadata.
Key helpers you are likely to use:
| Helper | Use |
|---|---|
runChildProcess() | Spawn a local command with streaming logs and timeouts. |
buildPaperclipEnv() | Inject the standard PAPERCLIP_* variables. |
renderTemplate() | Substitute template variables like {{agentId}}. |
asString(), asNumber(), asBoolean() | Read config values safely. |
Note: Treat adapter output as untrusted. Parse defensively and never execute its stdout blindly.
Environment Test
Section titled “Environment Test”testEnvironment() validates the adapter config before a run starts.
Use it to check:
- the command or endpoint exists
- the working directory is valid
- required auth or environment variables are present
- a lightweight hello probe actually succeeds
Return info, warn, and error checks so the UI can explain what is ready and what still needs attention.
Session Persistence
Section titled “Session Persistence”If the runtime can resume state across heartbeats, persist that state in sessionParams and restore it on the next wake.
export const sessionCodec = { deserialize(raw) { // Validate the raw payload and convert it into session params. }, serialize(params) { // Convert session params back into a storable shape. }, getDisplayId(params) { // Return a human-readable label for the session, if available. },};Use clearSession: true when the runtime reports that the previous session cannot be resumed.
Skills Injection
Section titled “Skills Injection”Your adapter should make Paperclip skills visible to the runtime without polluting the user’s workspace.
Preferred options:
- Create a temporary skills directory and pass it through a CLI flag.
- Symlink into the runtime’s global skills location.
- Point the runtime at a managed skills directory with an environment variable.
- Fall back to prompt injection only when the runtime does not support anything better.
UI Parser
Section titled “UI Parser”If your adapter needs richer transcript rendering than the generic shell parser, ship a self-contained ui-parser.ts.
See:
For built-in adapters, the UI parser can live inside Paperclip source. For external adapters, it must be standalone and browser-safe.
Registering Built-In Adapters
Section titled “Registering Built-In Adapters”Only built-in adapters should be added to the host registries:
server/src/adapters/registry.tsui/src/adapters/registry.tscli/src/adapters/registry.ts
External adapters register themselves through the plugin loader instead.
Security
Section titled “Security”- Keep secrets in environment variables or secret refs, not in prompts.
- Treat runtime output as untrusted input.
- Enforce timeouts and grace periods.
- Keep the UI parser free of DOM and Node APIs.