pleach
Integrate

Pleach + Anthropic SDK

Coexist with the Anthropic SDK. Keep prompt caching, tools API, batches, files; add @pleach/core for AuditableCall, family-lock, replay.

This page is the coexist pattern: @pleach/core underneath @anthropic-ai/sdk, neither package replacing the other. If you're choosing between keeping the Anthropic SDK and moving an existing Anthropic Enterprise contract behind Pleach, see Migrating from Anthropic Enterprise for the contract-side framing. The other coexist patterns — OpenAI SDK, Mastra, Inngest — follow the same posture.

The Anthropic SDK and Pleach aren't competing for the same slot. @anthropic-ai/sdk owns the transport — the typed client.messages.create surface, prompt-caching breakpoints, the latest tools API, the batches API, the files API, extended thinking. Pleach owns the substrate around the call — the typed AuditableCall row in your Postgres, family-lock at session start, replay-deterministic streaming, subagent cost rollup to the parent turnId.

If you wired up the Anthropic SDK directly, don't tear it out. Drop Pleach underneath via AnthropicSdkProvider — the SDK keeps running, you gain a per-tenant audit row, and the option of adding OpenAI or Google later without rewriting the call site.

If your team graduated from a self-serve API key to an Anthropic Enterprise contract, the coexist shape doesn't change — Pleach runs underneath the SDK regardless of the billing model. The contract closes vendor-side properties (SSO, ZDR, Workspaces, the Admin API). The substrate closes the three downstream walls: per- axis rollup inside one Workspace (external customers or internal employees, teams, cost centers), a hash-chained AuditableCall row in your own Postgres, and replay-deterministic regression across model snapshots. See Migrating from Anthropic Enterprise for the contract-side walk-through.

The architecture

┌─────────────────────────────────────────────────────────────┐
│  Pleach SessionRuntime                                      │
│  - AuditableCall row → your Postgres                        │
│  - Family-lock at session start                             │
│  - Replay-deterministic StreamEvent                         │
│  - Subagent rollup to parent turnId                         │
│                                                             │
│  ┌───────────────────────────────────────────────────────┐  │
│  │  AnthropicSdkProvider                                 │  │
│  │  (wraps @anthropic-ai/sdk directly)                   │  │
│  │                                                       │  │
│  │    Anthropic SDK                                      │  │
│  │    - client.messages.create                           │  │
│  │    - prompt-caching breakpoints                       │  │
│  │    - latest tools API                                 │  │
│  │    - batches API, files API                           │  │
│  │    - extended thinking                                │  │
│  │                                                       │  │
│  └───────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

The Anthropic SDK's request log is the transport trail — what hit the API, what HTTP status came back, which cache prefix hit. Pleach's AuditableCall ledger is the business trail — which turn the user typed, which tools the agent invoked, which subagents spawned, what each cost, attributed to the turn that caused them. Finance reads Pleach; SDK telemetry stays at the transport layer.

The code shape

import {
  createPleachRuntime,
  AnthropicSdkProvider,
  type StreamEvent,
} from "@pleach/core";
import { SupabaseAdapter } from "@pleach/core/sessions";

const runtime = createPleachRuntime({
  tenantId: "acme-corp",
  userId:   "alice@example.com",
  storage:  new SupabaseAdapter({ client: supabase }),
  // AnthropicSdkProvider wraps @anthropic-ai/sdk directly. Prompt
  // caching breakpoints, the latest tools API, batches, and files
  // all keep working — Pleach owns the audit row, not the
  // transport.
  provider: new AnthropicSdkProvider({
    apiKey:    process.env.ANTHROPIC_API_KEY!,
    model:     "claude-sonnet-4-5",
    maxTokens: 4096,
  }),
});

// Lock the family + transport at session creation. The cascade
// walks in-family rungs only — no silent cross-family widening
// mid-conversation if you add OpenAI fallback later.
const session = await runtime.sessions.create({
  provider: { type: "anthropic" },
  model:    { id: "claude-sonnet-4-5" },
});

// Every StreamEvent this iterator yields was paired with an
// AuditableCall row in your Postgres — keyed
// (sessionId, turnId, stageId, seqWithinTurn), carrying tenantId,
// toolName, subagentDepth, modelId, and tokenUsage.
for await (const event of runtime.executeMessage(
  session.id,
  "Summarize the latest filings.",
)) {
  // event: StreamEvent — message.delta, tool.started, tool.completed, ...
}

Three things this pattern gives you that calling @anthropic-ai/sdk directly doesn't:

  1. The AuditableCall row lands in your Postgres during the call, not as an afterthought. Finance can SELECT SUM(token_usage) FROM harness_auditable_calls WHERE tenant_id = ? AND created_at > ? without parsing SDK request logs or scraping the Anthropic console. The row exists whether the HTTP call succeeds, retries, or fails permanently.
  2. Family-lock prevents silent cross-family widening. The session locks tokenizer, prompt-cache key, tool-call dialect, and refusal pattern at runtime.sessions.create(). When you add OpenAI as a fallback later, the cascade walks in-family rungs only — a user mid-conversation on Anthropic never gets silently routed to GPT because of a transient 5xx.
  3. Subagents spawned by the agent roll their cost back to the parent turnId via SpawnTreeState. The Anthropic SDK doesn't model subagents — per-token cost attribution between a parent turn and the children it spawned is your code's problem without Pleach, and a one-line GROUP BY turn_id with it.

A note on prompt caching: Anthropic's prompt-cache breakpoints (the cache_control: { type: "ephemeral" } markers on system prompts and tool definitions) keep working unchanged — the SDK speaks to the API, Pleach doesn't intercept the request body. Pleach's own fingerprint is the cache key for replay, a different concern from provider-side prefix reuse. See Prompt caching for the distinction.

Where each is load-bearing

ConcernAnthropic SDK's slotPleach's slot
client.messages.create typed surfaceyes (their core primitive)None — wraps it
Prompt-caching breakpoints (cache_control: ephemeral)yes (passes through to API)None — provider-side feature
Latest tools API + tool-use beta flagsyesNone — provider-side feature
Batches API, files APIyesNone — provider-side feature
Extended thinkingyesSurfaced as thinking.delta StreamEvents
HTTP retry / 429 backoffyes (SDK-internal)Re-runs at the seam if you want it there
Per-tenant LLM cost rollup in YOUR schemaNone — request log is opaqueAuditableCall row keyed (tenantId, turnId)
Tamper-evident audit trailNoneprev_hash + row_hash in your DB
Family-locked provider routingSingle-vendor by constructionlocked at runtime.sessions.create({ provider, model })
Deterministic replay of the LLM streamNoneFingerprint replays byte-identical StreamEvents
Subagent cost rollup to parent turnIdNoneSpawnTreeState
Time-travel checkpoints inside a sessionNoneruntime.checkpoints.rollback() / .list()
Multi-provider portability (add OpenAI later)None (Anthropic-only)Same call site, new family + lock
OTel span set with auto parent-threadingNoneruntime.spans facet

The two coverage maps barely overlap. The Anthropic SDK is the right tool for talking to Anthropic; Pleach is the right tool for everything around talking to Anthropic. Use the Anthropic SDK for its native primitives. Use Pleach for the audit row, family-lock, and replay. Neither does the other's job.

When you don't need the Anthropic SDK directly

  • You don't need prompt-caching breakpoints, batches, files, or any other Anthropic-specific feature. The AiSdkProvider route through @ai-sdk/anthropic is simpler, and the audit row looks the same. See Providers.
  • You're already on multi-provider routing from day one and the unified AI SDK shape pays back across families. Reach for AiSdkProvider and stop one layer earlier.

When you don't need Pleach

  • A single-shot chat with the Anthropic SDK, tools, no persistence, no per-tenant audit obligation. Twenty lines of client.messages.create and a for await loop is the right floor — Pleach's overhead doesn't pay back. The comparison page covers when to stay on a direct SDK call.
  • Single-tenant internal tooling where the SDK's request log is a good-enough audit trail and you're not going to add a second provider. The family-lock and the AuditableCall row are optimizing for problems you don't have.

Migrating from a direct Anthropic SDK call

The migration shape is small: keep your @anthropic-ai/sdk dependency, install @pleach/core, wrap your client construction in AnthropicSdkProvider, and replace the client.messages.create call with runtime.executeMessage. Your prompt-caching breakpoints, tool definitions, and beta headers carry through unchanged. The first session you run produces an AuditableCall row in your Postgres; everything downstream of that — per-tenant cost queries, replay determinism, subagent rollup — is then available, whether or not you use it on day one.

Where to go next

On this page