# Adoption paths (/docs/adoption-paths)



There are two ways to adopt Pleach. One is to leave your agent
loop where it is and add the
[`@pleach/observe`](/docs/observe) SDK around it. The other is
to take [`@pleach/core`](/docs/core) as the substrate and
migrate the loop onto it. Both land you at the same audit row;
the cost shape and the feature ceiling are different.

This page is the decision. The per-stack walkthroughs
([Vercel AI SDK](/docs/migrating-from-ai-sdk),
[LangChain](/docs/migrating-from-langchain),
[Anthropic Enterprise](/docs/migrating-from-anthropic-enterprise),
[OpenAI Enterprise](/docs/migrating-from-openai-enterprise))
sit downstream of it.

## The two paths [#the-two-paths]

|                                                  | Brownfield (SDK)                                    | Greenfield (runtime)                                         |
| ------------------------------------------------ | --------------------------------------------------- | ------------------------------------------------------------ |
| Package                                          | [`@pleach/observe`](/docs/observe)                  | [`@pleach/core`](/docs/core)                                 |
| Adoption shape                                   | \~15 lines around your existing LLM calls.          | New `SessionRuntime` per process; rewrite the loop.          |
| Audit row                                        | `ObserveRow` — strict subset of `AuditableCall` v7. | Full `AuditableCall` v7.                                     |
| Destinations                                     | Postgres / Supabase / OTel / Memory / custom.       | Postgres / Supabase / Memory / any `ProviderDecisionLedger`. |
| Family-locked routing                            | No.                                                 | Yes.                                                         |
| Replay determinism                               | No.                                                 | Yes.                                                         |
| Channels + interrupts                            | No.                                                 | Yes.                                                         |
| Checkpoint / restore                             | No.                                                 | Yes.                                                         |
| Time-travel via [`@pleach/replay`](/docs/replay) | No.                                                 | Yes.                                                         |
| Provider swap without breaking dialect           | Partial — observability only.                       | Yes — substrate-level.                                       |
| Migration cost                                   | \~15 LoC per loop.                                  | Loop rewrite; runtime config; tests.                         |

The rows aren't a hierarchy. The greenfield path is bigger
because it gives more; brownfield is smaller because it asks
less. Picking the smaller one when you don't need the bigger one
is the right call.

<Callout title="The brownfield path is a runway, not a dead end">
  Every choice you make on the SDK destination travels with you
  when you adopt the full runtime. The two surfaces are designed
  to compose:

  * The runtime detects an `@pleach/observe` `init` config at
    startup. Runtime-side audit writes route through the SDK's
    transport rather than opening a parallel sink.
  * `recordCall()` is a no-op when `init()` hasn't run, and
    inside a runtime-managed turn the runtime's turn id wins — SDK
    call sites can drop their `if (observe)` guards while the
    runtime writes through the same destination.
  * `ObserveRow` is a strict subset of `AuditableCall` v7, so
    rows the SDK wrote yesterday remain valid v7 rows the
    runtime can read tomorrow. The `tenantId`, `turnId`, and
    `(model, family, callClass)` join keys don't change.
  * Fingerprint compute is shared. Both surfaces import from
    `@pleach/core/fingerprint`; no two implementations to drift.

  A buyer who adopts the SDK first and the runtime second keeps
  the audit history. The migration is monotonic by construction,
  not by a `legacy_*` column.
</Callout>

## Pick brownfield when [#pick-brownfield-when]

* You already run an agent loop on the Vercel AI SDK,
  LangChain, or a thin wrapper over a provider SDK, and you
  don't want to rewrite it this quarter.
* A regulator, a finance team, or a customer is asking for
  per-tenant cost attribution — `(tenantId, turnId, toolName,
  model, tokens, costUSD)` joinable to your billing schema —
  and the row is what's missing, not the loop.
* You already have an OTel collector (Honeycomb, Datadog,
  Grafana Tempo, an OTLP gateway). The
  [OTel destination](/docs/observe#otel) emits each row as a
  span on a backend you already operate.
* You want to start small. The brownfield path lets you wire
  one loop, watch the row land, and decide whether to widen.

The brownfield shape:

```typescript
import { init, recordCall } from "@pleach/observe";
import { postgres } from "@pleach/observe/destinations";

init({ destination: postgres({ connectionString: process.env.PG_URL! }) });

const startedAt = Date.now();
const reply = await yourExistingLLMCall(messages);

recordCall({
  turnId:       sessionId,
  providerId:   "anthropic",
  family:       "anthropic",
  callClass:    "synthesize",
  model:        "claude-sonnet-4-6",
  inputTokens:  reply.usage.inputTokens,
  outputTokens: reply.usage.outputTokens,
  costUSD:      reply.usage.costUSD,
  startedAt,
  completedAt:  Date.now(),
  tags:         { tenantId },
});
```

That's the whole brownfield surface for the common case. The
loop you had before stays the loop you have now.

## Pick greenfield when [#pick-greenfield-when]

* You need **replay determinism** — recording a turn today and
  replaying it against a fresh runtime tomorrow, byte-for-byte,
  for regression tests or an
  [`@pleach/eval`](/docs/eval) suite. The SDK writes a row;
  replay needs the event log.
* You need **family-locked routing** — the cascade-on-503 walk
  that stays inside one provider family and refuses to silently
  widen across families. That's a
  [`GatewayClient`](/docs/gateway) decision, made before the
  wire call; the SDK observes calls, it doesn't make them.
* You need **reactive channels** — fan-out, back-pressure, and
  interrupt handling tied to the same session. Those live in
  [`@pleach/core/channels`](/docs/channels).
* You need **checkpoint / restore** for long-running sessions
  that survive process restarts, or **time-travel forking** via
  [`@pleach/replay`](/docs/replay) for what-if analysis. Both
  consume the canonical event log.
* You're starting fresh, the loop doesn't exist yet, and the
  cost of building on the runtime substrate is the same as the
  cost of building any other way.

The greenfield shape leads with the runtime:

```typescript
import { SessionRuntime, AiSdkProvider } from "@pleach/core";
import { anthropic } from "@ai-sdk/anthropic";

const runtime = new SessionRuntime({
  provider: new AiSdkProvider({
    model:    anthropic("claude-sonnet-4-6"),
    maxSteps: 5,
  }),
  userId: req.user.id,
});

const session = await runtime.createSession({
  tools: { enabled: Object.keys(tools) },
});

for await (const event of runtime.executeMessage(session.id, prompt)) {
  // stream events, write audit rows, walk the family cascade —
  // all of it is the runtime's job, not yours.
}
```

The migration walkthroughs for the four common starting
points — [AI SDK](/docs/migrating-from-ai-sdk),
[LangChain](/docs/migrating-from-langchain),
[Anthropic Enterprise](/docs/migrating-from-anthropic-enterprise),
[OpenAI Enterprise](/docs/migrating-from-openai-enterprise) —
each show the loop rewrite step by step.

## Pick neither when [#pick-neither-when]

The two paths cover most starting points, but not all of them.
Stay where you are when:

* You're shipping a single-shot RAG bot with no tools, no
  sub-agents, and no per-tenant attribution requirement. The
  row is overhead you won't recoup.
* Your audit need is satisfied by the provider's own dashboard
  (an Anthropic Workspace, an OpenAI Project, a Bedrock
  invocation log). Adding a second ledger to maintain isn't
  free.
* You're prototyping an agent shape and the loop is going to
  change three more times this week. Wire the SDK once the
  shape stabilizes.

The voice on those constraints is information, not gatekeeping —
if you do need the row later, the brownfield path is \~15 lines
away.

## Where to go next [#where-to-go-next]

<Cards>
  <Card title="@pleach/observe" href="/docs/observe" description="The brownfield SDK — facade surface, the four destinations, what's hookable and what isn't." />

  <Card title="@pleach/core" href="/docs/core" description="The greenfield substrate. SessionRuntime, channels, family-locked routing, replay determinism, checkpointing." />

  <Card title="Migrating from the Vercel AI SDK" href="/docs/migrating-from-ai-sdk" description="The greenfield walkthrough for the most common starting point. Keep the AI SDK as your provider; swap the loop for a runtime." />

  <Card title="Migrating from LangChain" href="/docs/migrating-from-langchain" description="The greenfield walkthrough from LangChain. Adapter-bridged tools and agents stay; the orchestrator becomes a SessionRuntime." />
</Cards>
