# Pleach + Anthropic SDK (/docs/with-anthropic-sdk)



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](/docs/migrating-from-anthropic-enterprise)
for the contract-side framing. The other coexist patterns —
[OpenAI SDK](/docs/with-openai-sdk),
[Mastra](/docs/with-mastra),
[Inngest](/docs/with-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](/docs/migrating-from-anthropic-enterprise)
for the contract-side walk-through.

## The architecture [#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 [#the-code-shape]

```typescript
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](/docs/fingerprint) is the cache key for
*replay*, a different concern from provider-side prefix reuse.
See [Prompt caching](/docs/prompt-caching) for the distinction.

## Where each is load-bearing [#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` `StreamEvent`s              |
| 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 `StreamEvent`s        |
| 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 [#when-you-dont-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](/docs/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 [#when-you-dont-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](/docs/comparison) 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 [#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 [#where-to-go-next]

<Cards>
  <Card title="Migrating from Anthropic Enterprise" href="/docs/migrating-from-anthropic-enterprise" description="The matching contract-side page — keep the Enterprise SLA, add the AuditableCall row, per-end-customer rollup, and replay-deterministic eval." />

  <Card title="Pleach + OpenAI SDK" href="/docs/with-openai-sdk" description="The same coexist posture for OpenAI's transport — Chat Completions, the Responses API, plus the Assistants API caveat." />

  <Card title="Pleach + Mastra" href="/docs/with-mastra" description="Coexist with Mastra's workflow + hosted dashboard, with the audited LLM turn inside it." />

  <Card title="The AuditableCall row" href="/docs/auditable-call-row" description="What lands in your Postgres on every LLM call, and what you can join it against." />
</Cards>
