pleach
Get Started

Adoption paths

Two ways to adopt Pleach — brownfield (hook @pleach/observe into your existing agent loop) or greenfield (take @pleach/core as your substrate).

There are two ways to adopt Pleach. One is to leave your agent loop where it is and add the @pleach/observe SDK around it. The other is to take @pleach/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, LangChain, Anthropic Enterprise, OpenAI Enterprise) sit downstream of it.

The two paths

Brownfield (SDK)Greenfield (runtime)
Package@pleach/observe@pleach/core
Adoption shape~15 lines around your existing LLM calls.New SessionRuntime per process; rewrite the loop.
Audit rowObserveRow — strict subset of AuditableCall v7.Full AuditableCall v7.
DestinationsPostgres / Supabase / OTel / Memory / custom.Postgres / Supabase / Memory / any ProviderDecisionLedger.
Family-locked routingNo.Yes.
Replay determinismNo.Yes.
Channels + interruptsNo.Yes.
Checkpoint / restoreNo.Yes.
Time-travel via @pleach/replayNo.Yes.
Provider swap without breaking dialectPartial — 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.

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.

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 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:

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

  • 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 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 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.
  • You need checkpoint / restore for long-running sessions that survive process restarts, or time-travel forking via @pleach/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:

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, LangChain, Anthropic Enterprise, OpenAI Enterprise — each show the loop rewrite step by step.

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

On this page