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:
- The
AuditableCallrow lands in your Postgres during the call, not as an afterthought. Finance canSELECT 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. - 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. - Subagents spawned by the agent roll their cost back to the
parent
turnIdviaSpawnTreeState. 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-lineGROUP BY turn_idwith 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
| Concern | Anthropic SDK's slot | Pleach's slot |
|---|---|---|
client.messages.create typed surface | yes (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 flags | yes | None — provider-side feature |
| Batches API, files API | yes | None — provider-side feature |
| Extended thinking | yes | Surfaced as thinking.delta StreamEvents |
| HTTP retry / 429 backoff | yes (SDK-internal) | Re-runs at the seam if you want it there |
| Per-tenant LLM cost rollup in YOUR schema | None — request log is opaque | AuditableCall row keyed (tenantId, turnId) |
| Tamper-evident audit trail | None | prev_hash + row_hash in your DB |
| Family-locked provider routing | Single-vendor by construction | locked at runtime.sessions.create({ provider, model }) |
| Deterministic replay of the LLM stream | None | Fingerprint replays byte-identical StreamEvents |
Subagent cost rollup to parent turnId | None | SpawnTreeState |
| Time-travel checkpoints inside a session | None | runtime.checkpoints.rollback() / .list() |
| Multi-provider portability (add OpenAI later) | None (Anthropic-only) | Same call site, new family + lock |
| OTel span set with auto parent-threading | None | runtime.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
AiSdkProviderroute through@ai-sdk/anthropicis 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
AiSdkProviderand 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.createand afor awaitloop 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
AuditableCallrow 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
Migrating from Anthropic Enterprise
The matching contract-side page — keep the Enterprise SLA, add the AuditableCall row, per-end-customer rollup, and replay-deterministic eval.
Pleach + OpenAI SDK
The same coexist posture for OpenAI's transport — Chat Completions, the Responses API, plus the Assistants API caveat.
Pleach + Mastra
Coexist with Mastra's workflow + hosted dashboard, with the audited LLM turn inside it.
The AuditableCall row
What lands in your Postgres on every LLM call, and what you can join it against.
Migrating from OpenAI Enterprise
Keep your OpenAI Enterprise contract. Pleach sits underneath — AuditableCall row in your Postgres, per-axis rollup inside a Project, replay-deterministic eval.
Pleach + OpenAI SDK
Coexist with the OpenAI SDK. Keep Chat Completions, Responses API, structured outputs; add @pleach/core for AuditableCall, family-lock, replay.