pleach
Get Started

Comparison

How @pleach/core compares to the AI SDK, LangChain, LlamaIndex, Mastra, and to agent harnesses like Claude Code, Goose, OpenHands, AutoGen, and CrewAI.

@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

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

Axis@pleach/coreAI SDKLangChainLlamaIndexMastra
Per-call audit ledger (AuditableCall row keyed by turnId, tenant_id stamped, joinable to billing in one GROUP BY)✅ structuralpartial via callbacks
Replay determinism + write-side hash chain✅ structuralpartial (LangSmith, external)partial (eval)partial (workflow)
Family + transport lock + lattice invariants (no silent cross-family fallback; four-stage lattice lint-enforced)✅ structural❌ provider-agnosticpartial (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

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/coreVercel AI SDKLangChainLlamaIndexMastra
Observability & accounting
Per-call audit rowAuditableCall ledgernonepartial via callbacksnonenone
Append-only event log (typed StreamEvent)yespartial (UIMessage stream)partial via callbackspartial (instrumentation)yes (workflow events)
Subagent spawn + depth rollup to parent turnIdSpawnTreeStatenonepartial (LangGraph subgraphs)partial (workflows)partial (agent network)
Per-call cost allocation by turnIdyes (tokenUsage on row)manualmanualmanualpartial
Typed audit records (discriminated payload.kind)yes (TS narrowing on payload.kind)opaque JSONper-vendor shapes via callbacksper-module shapespartial (typed workflow events)
Tamper-evident hash chain (write-side)yes (prev_hash + row_hash at schema level)nonenonenonenone
PII scrubber gate at write timeyes (registered Scrubber instances; CI gate audit:c8-event-type-allowlist-coverage)consumer-wiredconsumer-wiredconsumer-wiredconsumer-wired
Built-in OTel span set with auto parent-threadingyes (session.turn, llm.invocation, graph.stage, tool.execution)partial (per-call spans, no parent threading)partial (per-operation spans)partialpartial
Multi-tenant facet stamping tenant_id on every row + span + outbound HTTPyes (runtime.tenant facet, withTenantHeader)consumer responsibilityconsumer responsibilityconsumer responsibilityconsumer responsibility
Execution shape
4-stage lattice (lint-enforced edges)yesnonenonenonepartial
Singleton synthesize seamyesnonenonenonenone
Reactive channels (LangGraph-shaped)yesnonepartial (LangGraph)nonepartial (workflow channels)
Plugin contract with lattice invariantsyesnonepartial (chains)partial (modules)yes
Human-in-the-loop interruptsyespartial (tool approval)yes (LangGraph interrupt())partial (workflows)yes (suspend/resume)
Routing & replay
Family + transport lock (session-scoped)yesprovider-agnostic, no locknonenonenone
Replay-deterministic streamingyesnonenonenonenone
Time-travel checkpointsyesnonepartial (LangGraph checkpointers)nonepartial (suspend/resume)
Eval / replay diff engineDIY against ledger today; @pleach/eval@0.1.0 ships the contract + bootstrap/Welch t-test scoring; @pleach/replay@0.1.0 ships replayTurn + chain verifiernonepartial (LangSmith, external)partial (eval module)none
Storage & contracts
BYO-DB storage adaptersMemory / IndexedDB / SupabaseLocalStorage onlynone for runtimevector storesyes
Offline-first sync (IndexedDB ↔ server)yesnonenonenonenone
Tamper-evident hash chainTamperEvidence plug-point + writer-side stamping in soak; verifier shipping in @pleach/core/eventLog (verifyChainForChat, generateProof). @pleach/compliance@0.1.0 adds scrubbers + attestation.nonenonenonenone
Language-agnostic wire contractyes (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 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

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/coreAI SDKLangChainLlamaIndexMastra
Define the "query this customer's DB" tooldefineTool + Zodtool() + Zod@tool / DynamicStructuredToolquery engine + ZodcreateTool + Zod
Keep tenant A's agent out of tenant B's dataruntime.tenant facet — RLS-aware, structuralclosure you thread by handconfig / closure by handper-index, manualruntimeContext per request
Bill each customer for their agent spendAuditableCall row, tenant_id-stamped, one GROUP BYread usage, attribute yourselfcallbacks, manualmanualpartial
Show a customer an audit of what the agent didhash-chained event log, queryable per tenantbuild itexternal (LangSmith)nonepartial
Stream the answer into your UI@pleach/react hooks + canonical SSE routesfirst-class (useChat)stream events, partialpartialyes
Let an enterprise tenant bring their own model keytransport + family locked per sessionswap provider, manualmanual per chainmanualper-workflow
Debug one customer's broken session after the factevent-granular replay (@pleach/replay)re-run and hopelogs onlynonepartial

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

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 for what to reach for instead.

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

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)

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/coreInngest / Trigger.dev / DBOS
CategoryLLM-turn runtimedurable-execution platform
Step memoization across crashesnone — per-turn checkpoints onlyyes (their core primitive)
Cron / event-triggered executionnoneyes
Concurrency, throttling, fan-outnoneyes
Hosted run dashboardnoneyes
Per-call audit row in YOUR schemayes (AuditableCall in your Postgres)run history is vendor-shaped
Family-locked provider routingyesnot provider-aware
Replay-deterministic LLM streamyes (StreamEvent byte-identical)code-level memoization, not stream-level
Subagent rollup to parent turnIdyes (SpawnTreeState)manual
Tamper-evident audit trailyes (prev_hash + row_hash)run history isn't cryptographically signed
Multi-tenant tenant_id stampingenforced by CI gatesyour 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 for the architecture and code shape.

When you don't 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

  • 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)

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/coreHelicone / Langfuse / LangSmithLiteLLMMLflow / LogfireBraintrustOpenMeter
Categorywrite-side substrateobservability (proxy / spans)gateway proxytracingeval platformusage metering
Per-tenant cost allocationrow, tenant_id-stampedyes (metadata tag)yes (customer_id)via span attrsmetadatayes (its core job)
Subagent spend rolled to parent turnparent turnId on each AuditableCall rowreconstructed from trace treenonetree walkgrouping keynone
One row per call in your DByes (AuditableCall)their store (self-host = their schema)yes (SpendLogs in your Postgres)MLflow: your Postgrestheir storeevents, not call rows
SQL-queryable, joinable to billingyesfilter DSL / APIyes (spend rows)MLflow yes / Logfire their cloudproprietarymetering join, not call shape
Tamper-evident hash chainprev_hash + row_hashnonenonenonenonenone
Deterministic replay of the turnyes (byte-identical)none (re-run)nonenonere-generate + scoren/a
PII redaction at writeScrubber gatehook (delegates to Presidio)Presidio guardrailmasking hooknone first-classn/a
Samplingnone — every callexpected / recommendedn/aconfigurablen/an/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" vs "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 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)

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.

CapabilityMemory / knowledge-graph substrate@pleach/core
Object of recordthe agent's knowledge and decisions — files, entities, notes, rationaleone provider call — model, tokens, cost, family
Read bythe agent itself, to shape its next steppeople — billing, audit, compliance
Read paradigmgraph traversal / Datalog over entitiesSQL rows joinable to your billing schema
Fields about the model callnone — no provider, model, token, or cost columnthe whole row
Which path is the productthe read — you query it to reasonthe 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, 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)

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/coreClaude CodeGooseOpenHandsAutoGenCrewAI
Categoryruntime substratedev harness (CLI)dev harness (CLI / desktop)agentic dev environmentmulti-agent frameworkmulti-agent framework
Embeddable in your productyes (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)yesnonononono
Family + transport lockyesprovider-locked (Anthropic)configurable, no lockconfigurable, no lockprovider-agnostic, no lockprovider-agnostic, no lock
Replay determinismyesnonononono
Multi-tenant cost attribution by turnIdyesn/a (single-user)n/a (single-user)n/a (single-user)manualmanual
Time-travel checkpointsyessession resumesession resumesession resumenonenone
Lattice-enforced stage edgesyesimplicit loopimplicit loopimplicit loopfreeformrole-graph

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

  • 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

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.

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

On this page