Skip to content Skip to content

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-adapter skill can walk you through the same structure interactively.


AreaBuilt-inExternal
SourceLives in the Paperclip repoLives in its own package
RegistrationAdded to the host registryLoaded through the adapter plugin store
UI parserStatic importOptional ./ui-parser export
Best forCore adapters and host-owned runtimesIndependent 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.


my-adapter/
package.json
tsconfig.json
src/
index.ts
server/
index.ts
execute.ts
test.ts
ui-parser.ts
cli/
format-event.ts

The 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.


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.


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() receives an AdapterExecutionContext and returns an AdapterExecutionResult.

Use it to:

  1. Read config with the safe helpers from @paperclipai/adapter-utils/server-utils.
  2. Build the runtime environment with buildPaperclipEnv(agent).
  3. Resolve or resume session state from runtime.sessionParams.
  4. Render any prompt template with renderTemplate().
  5. Spawn the command or call the remote service.
  6. Return usage, cost, session, and result metadata.

Key helpers you are likely to use:

HelperUse
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.


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.


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.


Your adapter should make Paperclip skills visible to the runtime without polluting the user’s workspace.

Preferred options:

  1. Create a temporary skills directory and pass it through a CLI flag.
  2. Symlink into the runtime’s global skills location.
  3. Point the runtime at a managed skills directory with an environment variable.
  4. Fall back to prompt injection only when the runtime does not support anything better.

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.


Only built-in adapters should be added to the host registries:

  1. server/src/adapters/registry.ts
  2. ui/src/adapters/registry.ts
  3. cli/src/adapters/registry.ts

External adapters register themselves through the plugin loader instead.


  • 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.