# Comparison (/docs/comparison)



`@pleach/core` differs from the AI SDK, LangChain, LlamaIndex, and
Mastra by treating the `AuditableCall` ledger and the four-stage
lattice as structural invariants, not opt-in middleware. It's a
substrate, not a product, so it overlaps differently with the TS
LLM libraries above and with agent harnesses like Claude Code,
Goose, OpenHands, AutoGen, and CrewAI. This page exists so you can
decide quickly whether `@pleach/core` is the right tool for the
job — and when it isn't.

The shape of the difference is the same shape the name names:
the AI SDK and LangChain hand you a pot and a few stakes —
enough to keep one branch upright. Pleach hands you the
lattice. The matrix below is just that difference, axis by axis.

## TL;DR [#tldr]

Three axes decide whether to reach for Pleach instead of (or
alongside) the alternatives:

| Axis                                                                                                                          | `@pleach/core` | AI SDK              | LangChain                     | LlamaIndex     | Mastra             |
| ----------------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------- | ----------------------------- | -------------- | ------------------ |
| **Per-call audit ledger** (`AuditableCall` row keyed by `turnId`, `tenant_id` stamped, joinable to billing in one `GROUP BY`) | ✅ structural   | ❌                   | partial via `callbacks`       | ❌              | ❌                  |
| **Replay determinism + write-side hash chain**                                                                                | ✅ structural   | ❌                   | partial (LangSmith, external) | partial (eval) | partial (workflow) |
| **Family + transport lock + lattice invariants** (no silent cross-family fallback; four-stage lattice lint-enforced)          | ✅ structural   | ❌ provider-agnostic | ❌                             | ❌              | partial (lattice)  |

Below: the row-by-row matrix. If you don't need any of the three
axes above, the AI SDK is the cleaner pick. If you need all
three, you're on the right page.

## Capability matrix [#capability-matrix]

The header column is framed as "capability," not "who wins." Other
libraries are excellent at their own jobs; this table just records
which capabilities each one ships as a first-class concept. Rows
are grouped into four themes — observability & accounting,
execution shape, routing & replay, storage & contracts — which is
the four-axis value-prop story for the substrate.

| Capability                                                                  | `@pleach/core`                                                                                                                                                                                             | Vercel AI SDK                                 | LangChain                         | LlamaIndex                        | Mastra                          |
| --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | --------------------------------- | --------------------------------- | ------------------------------- |
| **Observability & accounting**                                              |                                                                                                                                                                                                            |                                               |                                   |                                   |                                 |
| Per-call audit row                                                          | `AuditableCall` ledger                                                                                                                                                                                     | none                                          | partial via `callbacks`           | none                              | none                            |
| Append-only event log (typed `StreamEvent`)                                 | yes                                                                                                                                                                                                        | partial (`UIMessage` stream)                  | partial via `callbacks`           | partial (instrumentation)         | yes (workflow events)           |
| Subagent spawn + depth rollup to parent `turnId`                            | `SpawnTreeState`                                                                                                                                                                                           | none                                          | partial (LangGraph subgraphs)     | partial (workflows)               | partial (agent network)         |
| Per-call cost allocation by `turnId`                                        | yes (`tokenUsage` on row)                                                                                                                                                                                  | manual                                        | manual                            | manual                            | partial                         |
| Typed audit records (discriminated `payload.kind`)                          | yes (TS narrowing on `payload.kind`)                                                                                                                                                                       | opaque JSON                                   | per-vendor shapes via `callbacks` | per-module shapes                 | partial (typed workflow events) |
| Tamper-evident hash chain (write-side)                                      | yes (`prev_hash` + `row_hash` at schema level)                                                                                                                                                             | none                                          | none                              | none                              | none                            |
| PII scrubber gate at write time                                             | yes (registered `Scrubber` instances; CI gate `audit:c8-event-type-allowlist-coverage`)                                                                                                                    | consumer-wired                                | consumer-wired                    | consumer-wired                    | consumer-wired                  |
| Built-in OTel span set with auto parent-threading                           | yes (`session.turn`, `llm.invocation`, `graph.stage`, `tool.execution`)                                                                                                                                    | partial (per-call spans, no parent threading) | partial (per-operation spans)     | partial                           | partial                         |
| Multi-tenant facet stamping `tenant_id` on every row + span + outbound HTTP | yes (`runtime.tenant` facet, `withTenantHeader`)                                                                                                                                                           | consumer responsibility                       | consumer responsibility           | consumer responsibility           | consumer responsibility         |
| **Execution shape**                                                         |                                                                                                                                                                                                            |                                               |                                   |                                   |                                 |
| 4-stage lattice (lint-enforced edges)                                       | yes                                                                                                                                                                                                        | none                                          | none                              | none                              | partial                         |
| Singleton synthesize seam                                                   | yes                                                                                                                                                                                                        | none                                          | none                              | none                              | none                            |
| Reactive channels (LangGraph-shaped)                                        | yes                                                                                                                                                                                                        | none                                          | partial (LangGraph)               | none                              | partial (workflow channels)     |
| Plugin contract with lattice invariants                                     | yes                                                                                                                                                                                                        | none                                          | partial (chains)                  | partial (modules)                 | yes                             |
| Human-in-the-loop interrupts                                                | yes                                                                                                                                                                                                        | partial (tool approval)                       | yes (LangGraph `interrupt()`)     | partial (workflows)               | yes (suspend/resume)            |
| **Routing & replay**                                                        |                                                                                                                                                                                                            |                                               |                                   |                                   |                                 |
| Family + transport lock (session-scoped)                                    | yes                                                                                                                                                                                                        | provider-agnostic, no lock                    | none                              | none                              | none                            |
| Replay-deterministic streaming                                              | yes                                                                                                                                                                                                        | none                                          | none                              | none                              | none                            |
| Time-travel checkpoints                                                     | yes                                                                                                                                                                                                        | none                                          | partial (LangGraph checkpointers) | none                              | partial (suspend/resume)        |
| Eval / replay diff engine                                                   | DIY against ledger today; `@pleach/eval@0.1.0` ships the contract + bootstrap/Welch t-test scoring; `@pleach/replay@0.1.0` ships `replayTurn` + chain verifier                                             | none                                          | partial (LangSmith, external)     | partial (eval module)             | none                            |
| **Storage & contracts**                                                     |                                                                                                                                                                                                            |                                               |                                   |                                   |                                 |
| BYO-DB storage adapters                                                     | Memory / IndexedDB / Supabase                                                                                                                                                                              | LocalStorage only                             | none for runtime                  | vector stores                     | yes                             |
| Offline-first sync (IndexedDB ↔ server)                                     | yes                                                                                                                                                                                                        | none                                          | none                              | none                              | none                            |
| Tamper-evident hash chain                                                   | `TamperEvidence` plug-point + writer-side stamping in soak; verifier shipping in `@pleach/core/eventLog` (`verifyChainForChat`, `generateProof`). `@pleach/compliance@0.1.0` adds scrubbers + attestation. | none                                          | none                              | none                              | none                            |
| Language-agnostic wire contract                                             | yes (Go reference impl)                                                                                                                                                                                    | none (TS-only)                                | none (separate JS / Py codebases) | none (separate JS / Py codebases) | none (TS-only)                  |

Source notes for the row data live next to the comparison table in
the `@pleach/core` README; this page is a mirror so the docs site
stays a single discoverable surface. A few cells deserve gloss:

* **"Partial via `callbacks`"** for LangChain means a
  `BaseCallbackHandler` can observe events, but the events don't
  share a row identity — there's no
  `(sessionId, turnId, stageId, seqWithinTurn)` tuple to join one
  callback's observation to the call that caused it.
* **"Manual"** on cost allocation means you can compute it, but you
  write the aggregation pipeline yourself; with `@pleach/core`, the
  [`AuditableCall` row](/docs/auditable-call-row) ships `tokenUsage`
  already rolled up to `turnId`.
* **"Partial (`UIMessage` stream)"** on the event log for the AI
  SDK means the streaming surface is typed and parseable, but it's
  the rendered-message stream — not a durable audit-keyed log you
  can `GROUP BY` six months later.
* **"Language-agnostic wire contract"** means the substrate ships a
  contract that pins HTTP+SSE shapes, fingerprint canonicalization,
  and `AuditableCall` row layout — independent implementations
  (TypeScript reference + Go) must produce the same bytes on the
  wire. Other libraries publish per-language SDKs that share a name
  but not a contract; a recorded turn from one can't replay against
  the other.

## Worked example: adding an agent to a multi-tenant SaaS [#worked-example-adding-an-agent-to-a-multi-tenant-saas]

The matrix above is axis-by-axis. Here is the same difference as
one narrative. You already run a SaaS where every customer owns
their data — per-tenant rows, or a schema or database each. You
want an in-app agent that answers questions and takes actions over
*that customer's* data. Same feature, five runtimes — what you
build yourself versus what the runtime hands you:

| The job                                            | `@pleach/core`                                           | AI SDK                           | LangChain                         | LlamaIndex         | Mastra                       |
| -------------------------------------------------- | -------------------------------------------------------- | -------------------------------- | --------------------------------- | ------------------ | ---------------------------- |
| Define the "query this customer's DB" tool         | `defineTool` + Zod                                       | `tool()` + Zod                   | `@tool` / `DynamicStructuredTool` | query engine + Zod | `createTool` + Zod           |
| Keep tenant A's agent out of tenant B's data       | `runtime.tenant` facet — RLS-aware, structural           | closure you thread by hand       | `config` / closure by hand        | per-index, manual  | `runtimeContext` per request |
| Bill each customer for their agent spend           | `AuditableCall` row, `tenant_id`-stamped, one `GROUP BY` | read `usage`, attribute yourself | callbacks, manual                 | manual             | partial                      |
| Show a customer an audit of what the agent did     | hash-chained event log, queryable per tenant             | build it                         | external (LangSmith)              | none               | partial                      |
| Stream the answer into your UI                     | `@pleach/react` hooks + canonical SSE routes             | first-class (`useChat`)          | stream events, partial            | partial            | yes                          |
| Let an enterprise tenant bring their own model key | transport + family locked per session                    | swap provider, manual            | manual per chain                  | manual             | per-workflow                 |
| Debug one customer's broken session after the fact | event-granular replay (`@pleach/replay`)                 | re-run and hope                  | logs only                         | none               | partial                      |

The first two rows are close to a wash — everyone writes a tool,
everyone can thread a tenant id through a closure. The split is
everything below: per-tenant **cost**, **audit**, and **replay**
are what a SaaS discovers it needs *after* the agent ships, and
where "manual" turns into next quarter's backlog. If you'll ship
the DB tool and stop there, the AI SDK or Mastra gets you there
with less to learn. If the customer-facing billing, audit, and
replay surface is on the roadmap, that's the substrate's territory
— and the rest of this page is the axis-by-axis version of why.

## Where Pleach differs structurally [#where-pleach-differs-structurally]

Most TS LLM frameworks treat audit, multi-tenancy, OTel, and
redaction as *consumer responsibilities* — surfaces you wire on
top of the runtime. The runtime stays small; the integration
work moves to you.

`@pleach/core` treats those four as *substrate-level
invariants*. Audit rows have a typed shape. Event-log rows carry
`prev_hash` + `row_hash`. OTel spans get auto parent-threading
and an auto `tenant_id` attribute. PII scrubbers gate every
write before persistence.

The mechanism that keeps those properties from drifting is a set
of CI audit gates that fail the build when coverage regresses:
`audit:facet-coverage`, `audit:c8-event-type-allowlist-coverage`,
`audit:tenant-scoping`, `audit:otel-noop-soak`. A feature can't
ship a new event type, a new facet, or a new tool surface
without the corresponding row, span, scrubber slot, and tenant
stamp showing up in the right places.

The trade-off is real: more substrate machinery to learn
upfront, and a stricter contract for what counts as "a feature."
The return is fewer ways for a load-bearing property to regress
silently after the feature ships. If the four invariants above
don't matter to your product, the upfront cost won't pay back —
[see below](#when-to-reach-for-something-else) for what to reach
for instead.

## When to reach for something else [#when-to-reach-for-something-else]

`@pleach/core` is a substrate, not a chat shell. It pays its
overhead in two places: a runtime contract you have to model your
agent inside, and a lattice that constrains where calls live.
That overhead is the price of the audit ledger, the family lock,
and the replay determinism. If you don't need those, the overhead
isn't free.

A single-shot RAG bot — no persistence, no multi-tenancy, no
audit obligation — gets little out of the substrate compared to
a 30-line AI SDK script. The lattice and ledger pay off when
you're shipping a multi-tenant production agent with replay,
OTel, and compliance gates. Below the second category, the AI
SDK is the right floor.

* **Want a chat shell with tools and nothing more?** The AI SDK's
  `streamText` / `generateText` is the right shape. Less to learn,
  fewer constraints, no ledger. The audit story you'd get is "the
  request id from the provider response and your own log line";
  that's enough until finance asks for a per-axis monthly rollup
  — per end customer in a SaaS, or per employee, team, or
  cost-center under one Anthropic Workspace or OpenAI Project —
  at which point you're either re-keying logs or reaching for the
  `AuditableCall` row.
* **Want chain abstractions and a large pre-built component
  catalog?** LangChain. The ecosystem breadth is the value; the
  `Runnable` contract is mature.
* **Want a retrieval-first system with indexes as the primary
  primitive?** LlamaIndex. RAG-shaped problems map cleanly.
* **Want a workflow runtime with a hosted control plane?**
  Mastra. The hosted surface is the differentiator.

## When `@pleach/core` is the right call [#when-pleachcore-is-the-right-call]

Three signals that move the runtime from overkill to undersell:

1. **Audit obligations.** You need to point a regulator, a
   customer, or your future self at the **tool invocations and
   subagent spawns** a session made, attributed to the turn that
   caused them. The lattice gives you the skeleton for free —
   anchor-plan, synthesize, post-turn fire every turn by
   construction. The ledger gives you the dynamic part: which
   tool ran with what input, which subagent spawned, how deep.
2. **Cost attribution at the turn level.** Synthesis shape is
   fixed by the lattice; synthesis *size* isn't, and tool-call
   fan-out is fully variable. A turn is the unit that rolls those
   variable costs up. The `AuditableCall` row carries `turnId`,
   `toolName`, and `subagentDepth` so the math is one `GROUP BY`,
   not a regex pass over logs.
3. **Replay determinism as a hard requirement.** Sync-only stream
   observers, deterministic channel reducers, and
   `Math.floor`-quantized prompt budgets exist so a recorded turn
   replays byte-identical against the same input. If "what would
   the agent have said?" is a question you need to answer six
   months from now, this is the property you want. The
   load-bearing test is strict-mode replay (shipping in
   `@pleach/replay@0.1.0`; `replayTurn`, `fromSnapshot`, `fork`,
   and `aggregateMultiTenant` bodies all land at the `0.1.0` cut):
   a recorded turn replayed against the
   same `@pleach/core` version + the same fingerprint inputs must
   yield the same `StreamEvent` sequence in the same order, or
   strict mode throws `ReplayDivergenceError` with the first
   diverging event named.

If none of the three apply, the AI SDK is probably the right
floor.

## Durable-execution platforms (Inngest, Trigger.dev, DBOS, Temporal) [#durable-execution-platforms-inngest-triggerdev-dbos-temporal]

Inngest and its siblings are **not** competitors to `@pleach/core` —
they occupy the slot one layer up. A durable-execution platform
wraps the **request**: step memoization across crashes, retries,
scheduling, cron, fan-out, a hosted run dashboard. `@pleach/core`
owns the **LLM turn inside the request**: the typed `AuditableCall`
row, family-lock, replay-deterministic streaming, subagent cost
rollup.

| Capability                         | `@pleach/core`                         | Inngest / Trigger.dev / DBOS               |
| ---------------------------------- | -------------------------------------- | ------------------------------------------ |
| Category                           | LLM-turn runtime                       | durable-execution platform                 |
| Step memoization across crashes    | none — per-turn checkpoints only       | yes (their core primitive)                 |
| Cron / event-triggered execution   | none                                   | yes                                        |
| Concurrency, throttling, fan-out   | none                                   | yes                                        |
| Hosted run dashboard               | none                                   | yes                                        |
| Per-call audit row in YOUR schema  | yes (`AuditableCall` in your Postgres) | run history is vendor-shaped               |
| Family-locked provider routing     | yes                                    | not provider-aware                         |
| Replay-deterministic LLM stream    | yes (`StreamEvent` byte-identical)     | code-level memoization, not stream-level   |
| Subagent rollup to parent `turnId` | yes (`SpawnTreeState`)                 | manual                                     |
| Tamper-evident audit trail         | yes (`prev_hash` + `row_hash`)         | run history isn't cryptographically signed |
| Multi-tenant `tenant_id` stamping  | enforced by CI gates                   | your event metadata                        |

The honest pattern is **use both**: wrap `runtime.executeMessage`
inside `step.run`, get Inngest's operational trail AND Pleach's
business trail. See [Pleach + Inngest](/docs/with-inngest) for the
architecture and code shape.

### When you don't need the durable-execution platform [#when-you-dont-need-the-durable-execution-platform]

* A request-scoped agent turn that completes inside one HTTP
  request, no external waits, no cron, no fan-out. Direct
  `runtime.executeMessage` from an API route is the right shape.

### When you don't need `@pleach/core` [#when-you-dont-need-pleachcore]

* The AI SDK + Inngest combo covers single-tenant chatbots with
  tools, no audit obligation, no replay requirement. Pleach's
  overhead doesn't pay back at this size.

## Observability, cost, and eval tools (a read-side category) [#observability-cost-and-eval-tools-a-read-side-category]

Helicone, Langfuse, LangSmith, LiteLLM, MLflow, Logfire,
Braintrust, and OpenMeter are the tools a team actually shops
against when it wants "audit, cost, and replay." They're worth a
table of their own because the overlap with `@pleach/core` is
real — and so is the boundary. These tools **read**: they ingest
spans or proxy requests into an analytical store and give you
dashboards, eval suites, and cost rollups. `@pleach/core`
**writes**: it produces the typed `AuditableCall` row, in your
own database, that those tools could read.

The split below is the honest one — most of these are excellent
at the read job, and several compose with Pleach rather than
replace it.

| Capability                           | `@pleach/core`                              | Helicone / Langfuse / LangSmith        | LiteLLM                            | MLflow / Logfire                 | Braintrust          | OpenMeter                     |
| ------------------------------------ | ------------------------------------------- | -------------------------------------- | ---------------------------------- | -------------------------------- | ------------------- | ----------------------------- |
| Category                             | write-side substrate                        | observability (proxy / spans)          | gateway proxy                      | tracing                          | eval platform       | usage metering                |
| Per-tenant cost allocation           | row, `tenant_id`-stamped                    | yes (metadata tag)                     | yes (`customer_id`)                | via span attrs                   | metadata            | yes (its core job)            |
| Subagent spend rolled to parent turn | parent `turnId` on each `AuditableCall` row | reconstructed from trace tree          | none                               | tree walk                        | grouping key        | none                          |
| One row per call **in your DB**      | yes (`AuditableCall`)                       | their store (self-host = their schema) | yes (`SpendLogs` in your Postgres) | MLflow: your Postgres            | their store         | events, not call rows         |
| SQL-queryable, joinable to billing   | yes                                         | filter DSL / API                       | yes (spend rows)                   | MLflow yes / Logfire their cloud | proprietary         | metering join, not call shape |
| Tamper-evident hash chain            | `prev_hash` + `row_hash`                    | none                                   | none                               | none                             | none                | none                          |
| Deterministic replay of the turn     | yes (byte-identical)                        | none (re-run)                          | none                               | none                             | re-generate + score | n/a                           |
| PII redaction at write               | `Scrubber` gate                             | hook (delegates to Presidio)           | Presidio guardrail                 | masking hook                     | none first-class    | n/a                           |
| Sampling                             | none — every call                           | expected / recommended                 | n/a                                | configurable                     | n/a                 | n/a                           |

Glosses where the cell is doing work:

* **"Reconstructed from trace tree"** on subagent spend means the
  child tokens are captured as nested spans, but the parent's cost
  is summed at query time by walking the tree — not stored on the
  parent. This is the documented gap most observability tools share.
* **"Their store"*&#x2A; vs &#x2A;*"your DB"** is the load-bearing axis.
  LiteLLM and MLflow are the two that write call/trace records into
  a Postgres you own; the rest land the data in a vendor-shaped
  analytical store, and "self-host" means *your infra* running
  *their schema*, not joinable rows in your application database.
* **OpenMeter** is the billing destination, not the attribution
  source — it ingests usage events and syncs to Stripe, but it
  doesn't carry the agent run's structure. It pairs with the
  ledger; it doesn't replace it.

The honest pattern, again, is **compose**: let `@pleach/core` write
the row, point Langfuse or your OTel collector at the same events
for dashboards, and meter to OpenMeter or Stripe off the
`tenant_id`-stamped row. The substrate owns the write; the read
tools stay good at reading. The [landscape page](/docs/landscape)
ranks all twenty capabilities by how much of each is structural
here versus available in the tools above.

## Agent-memory & knowledge-graph substrates (a read-side category) [#agent-memory--knowledge-graph-substrates-a-read-side-category]

A second read-side category shares Pleach's *primitives* without
sharing its object of record. Memory layers and knowledge-graph
engines — Letta, Mem0, Zep — persist what an agent has learned so a
later turn can read it back. The sharpest form of the shape is
event-sourced: an append-only, content-addressed graph where each
tool call and decision is an immutable node, and the agent recalls
by querying the graph instead of embedding-searching its own
scrollback.

That form looks adjacent to `@pleach/core`, because the skeleton is
the same — append-only rows, content hashes, a causal chain, fork
and replay. The object of record is where they split.

| Capability                  | Memory / knowledge-graph substrate                                          | `@pleach/core`                                          |
| --------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------- |
| Object of record            | the agent's **knowledge and decisions** — files, entities, notes, rationale | one **provider call** — model, tokens, cost, family     |
| Read by                     | the **agent itself**, to shape its next step                                | **people** — billing, audit, compliance                 |
| Read paradigm               | graph traversal / Datalog over entities                                     | SQL rows joinable to your billing schema                |
| Fields about the model call | none — no provider, model, token, or cost column                            | the whole row                                           |
| Which path is the product   | the **read** — you query it to reason                                       | the **write** — record every call, then read to account |

The split in one line: a memory substrate is a **read substrate** —
the graph exists so the agent can query it and decide. `@pleach/core`
is a **write substrate** — the ledger exists so every call is
recorded, and the reads serve the people who answer for the spend.
Even where the two overlap — both can record a tool call — a memory
graph stores the tool's *semantics* so the agent recalls causally;
the ledger stores the call's *economics* so finance attributes it.
A memory graph carries no provider, model, or cost column; the
ledger was never the agent's recall surface.

So they compose rather than compete. A memory graph can sit beside
the [`AuditableCall` row](/docs/auditable-call-row), reading the
same session — recording what the agent *learned* takes nothing
from recording what it *called and cost*. Reach for a memory
substrate when the agent's recall is the problem. Reach for
`@pleach/core` when the account of what the agent spent is the
problem.

This is a different read-side from the observability cohort above:
those tools read for a **human dashboard**; a memory substrate
reads for the **agent's own next step**. Neither is the write-side
ledger, and both can read the rows Pleach writes.

## Agent harnesses (a different category) [#agent-harnesses-a-different-category]

Harnesses like Claude Code, Goose, OpenHands, AutoGen, and
CrewAI aren't competitors to `@pleach/core` — they're the
category one layer up. A harness ships a complete agent loop you
launch and use; a substrate ships the primitives you'd build a
harness on top of. The axis is opinionation vs. embeddability: a
harness picks the UX, the tool surface, the loop shape, and the
session model for you; a substrate gives you those decisions
back. Many shipped harnesses are session/turn/tool-loop/synthesis
internally — the same shape the 4-stage lattice formalizes.

| Capability                                                             | `@pleach/core`    | Claude Code                 | Goose                       | OpenHands               | AutoGen                    | CrewAI                     |
| ---------------------------------------------------------------------- | ----------------- | --------------------------- | --------------------------- | ----------------------- | -------------------------- | -------------------------- |
| Category                                                               | runtime substrate | dev harness (CLI)           | dev harness (CLI / desktop) | agentic dev environment | multi-agent framework      | multi-agent framework      |
| Embeddable in your product                                             | yes (npm dep)     | no (end-user tool)          | no (end-user tool)          | partial (server mode)   | yes (library)              | yes (library)              |
| Per-call audit row keyed `(sessionId, turnId, stageId, seqWithinTurn)` | yes               | no                          | no                          | no                      | no                         | no                         |
| Family + transport lock                                                | yes               | provider-locked (Anthropic) | configurable, no lock       | configurable, no lock   | provider-agnostic, no lock | provider-agnostic, no lock |
| Replay determinism                                                     | yes               | no                          | no                          | no                      | no                         | no                         |
| Multi-tenant cost attribution by `turnId`                              | yes               | n/a (single-user)           | n/a (single-user)           | n/a (single-user)       | manual                     | manual                     |
| Time-travel checkpoints                                                | yes               | session resume              | session resume              | session resume          | none                       | none                       |
| Lattice-enforced stage edges                                           | yes               | implicit loop               | implicit loop               | implicit loop           | freeform                   | role-graph                 |

### When to reach for a harness instead [#when-to-reach-for-a-harness-instead]

* **You're the user, not the vendor.** You want to run an agent
  on your own machine to write code, refactor a repo, or drive a
  browser. Install Claude Code or Goose; you don't need a
  substrate.
* **The harness's loop is the loop you want.** Off-the-shelf
  harnesses ship a planning / tool-loop / synthesis cycle that
  works well for the dev-tooling use case. If your product needs
  the same shape, the harness saves you the build.
* **You're prototyping a multi-agent topology.** AutoGen and
  CrewAI give you role primitives and conversation patterns out
  of the box. Reach for them before you reach for a substrate.

### When you'd build on `@pleach/core` instead [#when-youd-build-on-pleachcore-instead]

* **You're shipping a customer-facing agent product, not running
  one yourself.** Harnesses are end-user tools; a substrate is
  what you embed in a SaaS surface where customers each get
  their own session, their own audit trail, and their own
  invoice.
* **The audit ledger is load-bearing.** Per-call rows joinable to
  a billing schema and to a compliance review are the kind of
  property a harness's session log can't substitute for —
  different shape, different durability guarantees.
* **You're already past a harness.** Teams sometimes start on a
  harness, hit the per-tenant accounting wall or the replay
  wall, and discover the harness's session log isn't the row
  shape they need. `@pleach/core` is where that road leads.

A note on positioning: `@pleach/core` itself is vibe-coded
inside harnesses — they're how the runtime gets written. The
runtime they're writing is the one a *product team* embeds.
Different consumer, same surface.

## If you already buy direct from Anthropic or OpenAI [#if-you-already-buy-direct-from-anthropic-or-openai]

Different shape from the libraries above. Pleach is not an
alternative to an Anthropic Enterprise or OpenAI Enterprise
contract — it composes underneath one. SSO/SAML, ZDR, Workspaces
or Projects, the Admin / Usage API, dedicated capacity, prompt
caching, snapshot pinning, evals, fine-tuning — those stay where
they are. The runtime adds the three pieces the vendor contract
does not cover:

1. **Per-axis rollup inside one Workspace / Project.** The Admin or
   Usage API reports the Workspace or Project total. Pleach's
   `AuditableCall` row carries an opaque `tenantId` you wire to
   whichever axis you bill, chargeback, or audit against — external
   customers in a multi-tenant SaaS, or employees / teams /
   cost-centers when the lab is used internally. Sums reconcile to
   the vendor total because Pleach records the same calls your
   workspace key is making.
2. **Hash-chained audit row in your own DB.** ZDR governs what the
   vendor stores. The `prev_hash` + `row_hash` chain on every
   `AuditableCall` row governs what *you* store and how you defend
   the chain to a downstream auditor — a customer's compliance
   team, an SEC inquiry, an EU AI Act review.
3. **Replay-determinism across snapshots.** A pinned snapshot
   doesn't make a turn replayable; a fingerprint match does. Drift
   across snapshots surfaces as a fingerprint-keyed diff in CI
   eval, not as a customer ticket.

No new vendor, no new SOC 2 boundary, no new ZDR review. Pleach is
`npm install @pleach/core` plus a Postgres table you already run.

<Cards>
  <Card title="Migrating from Anthropic Enterprise" href="/docs/migrating-from-anthropic-enterprise" description="Keep SSO, ZDR, Workspaces, the Admin API. Close the three gaps the vendor contract doesn't cover." />

  <Card title="Migrating from OpenAI Enterprise" href="/docs/migrating-from-openai-enterprise" description="Keep SSO/SCIM, ZDR, Projects, the Usage API. Close the three gaps the vendor contract doesn't cover." />
</Cards>

A note on cloud-mediated transports (Bedrock, Azure OpenAI,
Vertex): same three walls, different BYOK substrate (IAM
federation rather than API key) and different network posture
(VPC, region pinning, residency). The ledger, fingerprint, and
family-lock primitives already compose with any provider you wire
through the SDK; cloud-mediated provider adapters land in
`@pleach/gateway` v1.x.

Two sibling SKUs reach the same Enterprise-contract reader
through adjacent doors. [`@pleach/coding-agent`](/docs/coding-agent)
is the sandboxed coding-agent surface for the internal dev-tools
team that often sits inside the same contract footprint — same
audit row, same `tenantId` axis, same chargeback query. The
[language-agnostic contract](/docs/language-agnostic-contract) is
the procurement-visible answer when Enterprise IT asks "is this
TypeScript-only?" — the substrate is wire shapes, and a Go
implementation already round-trips a shared corpus of recorded
turns against the contract; the official `@pleach` Go runtime SKU
is the next planned published implementation.

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

<Cards>
  <Card title="Landscape" href="/docs/landscape" description="Twenty capabilities ranked by how differentiated each is — structural to the audit row, assembled here, or table stakes." />

  <Card title="The AuditableCall row" href="/docs/auditable-call-row" description="What's in the per-call audit row, and what you can join it against." />

  <Card title="Architecture" href="/docs/architecture" description="The six pieces of the substrate — stage lattice, call classes, seams, family-lock, audit ledger, event log." />

  <Card title="Packages" href="/docs/packages" description="The @pleach/* matrix — which sibling layers on top of core for which job." />

  <Card title="Typed records" href="/docs/typed-records" description="Discriminated-union audit payloads that narrow on payload.kind in TypeScript." />

  <Card title="Hash chain" href="/docs/hash-chain" description="Event-log rows carry prev_hash + row_hash at the schema level for tamper-evidence." />
</Cards>
