Skip to content
pleach
Get Started

Comparison

How @pleach/core compares to the AI SDK, LangChain, LlamaIndex, Mastra, agent harnesses and toolkits like Claude Code, pi, and CrewAI, and governance control planes / AI gateways like Portkey, Cloudflare AI Gateway, and LiteLLM.

@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 and toolkits like Claude Code, pi, 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:

Competitor columns reflect each project's public docs as of 2026-06 and may have changed since.
Axis@pleach/coreAI SDKLangChainLlamaIndexMastra
Per-call audit ledger (AuditableCall row keyed by turnId, tenant_id stamped, joinable to billing in one GROUP BY)✅ structuralnopartial via callbacksnono
Replay determinism + write-side hash chain✅ structuralnopartial (LangSmith, external)partial (eval)partial (workflow)
Family + transport lock + lattice invariants (no silent cross-family fallback; four-stage lattice lint-enforced)✅ structuralno provider-agnosticnonopartial (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.

Competitor columns reflect each project's public docs as of 2026-06 to 2026-07 for most rows. The rows for context compaction, sandboxed code execution, safety policies, Agent Skills, durable execution, background runs, long-term memory, MCP, and OTel spans were last checked as of 2026-09. Any of them may have changed since.
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; core spans use pleach.* attributes — the GenAI-semconv gen_ai.* set ships in @pleach/observe's OTel destination)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)
Context compaction (history summarization + tool-result eviction)yes, opt-in (SummarizationMiddleware, ResultEvictionMiddleware)manualpartialpartialpartial
Sandboxed code executioncontract + adapters (@pleach/sandbox; core's code_exec refuses until a runner is injected)consumer-wiredconsumer-wiredconsumer-wiredconsumer-wired
Safety-policy API (typed, registerable)defineSafetyPolicy — advisory / guardrail / refusal; enforced as prompt prose + audit record today, blocking effects declared but not wirednoneconsumer-wirednonenone
Agent Skills (markdown skills, intent-matched injection)yes (SkillLoader — SKILL.md + frontmatter, three sources)nonenonenonenone
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
Durable execution (resume after a crash)partial — implicit per-turn checkpoints, checkpoints.rollback + sessions.resume, event-granular replay; no automatic mid-turn resume of in-flight worknonepartial (LangGraph checkpointers)partial (workflow checkpoints)partial (suspend/resume)
Background / scheduled runspartial — schedule tools persist intent to the store; the runtime has no timer of its own, the host fires due tasks via readScheduledTasksnonepartial (hosted platform)nonepartial (hosted platform)
Storage & contracts
BYO-DB storage adaptersMemory / IndexedDB / SupabaseLocalStorage onlynone for runtimevector storesyes
Long-term memory (fact extraction + semantic recall)yes, executor-gated (@pleach/core/memory extraction + cross-agent consolidation; pgvector search via SupabaseStore.search)nonepartialpartialyes
MCP (Model Context Protocol)server (@pleach/mcp — MCPServer wraps the runtime; stdio / SSE / WebSocket); no client-side mcpServers attachclient (experimental)client (adapters)client (tool adapters)client + server
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.
  • "Opt-in" and "executor-gated" mean the mechanism ships in the package but installs only when the host wires it. A bare createPleachRoute passes tool results through unmodified until you set resultOffloadBackend; the post-turn memory-extraction and consolidation nodes register only when the matching executor is supplied. Shipped, not silently active.
  • "Blocking effects declared but not wired" on the safety row is the measured state: the advisory / guardrail / refusal levels are typed and registerable, and all three currently map to a log_only effect — policy text reaches the prompt and the audit record, but no code path yet refuses a tool call or a synthesis on a policy verdict. The blocking effects (block_tool_call, block_synthesis) exist as declared future values.
  • "No automatic mid-turn resume" on the durable-execution row: checkpoints are created implicitly each turn and sessions.resume / checkpoints.rollback are real, but nothing scans for an in-flight turn on process restart — AsyncTaskManager snapshots pending tasks into checkpoint metadata and nothing rehydrates them into a live manager. The durable-execution platforms section below is the compose-don't-compete version of this row.
  • "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.

Which tool fits your shape — four shapes route to a faster tool; the multi-tenant, audit-bound shape routes to PleachEach row maps the shape of your agent on the left to the tool that fits it on the right. Single-shot chat, chain composition, a local dev harness, and durable function chains each have a tool that ships faster. The multi-tenant, audit-bound shape — where the agent is the product — is the Pleach target.IF YOUR SHAPE ISREACH FORSingle-shot chat + toolsVercel AI SDKno audit row · no replayChain compositionLangChain / LangGraphper-call cost is manualLocal dev harnessClaude Code / Goosesingle-user, not embeddableDurable function chainsInngest / Trigger.devvendor's run shape, no hash chainOrg-wide model-call governanceapps you don't ownMerge / Portkey / LiteLLMcall-level row, no turn treeMulti-tenant, audit-bound SaaSthe agent is the productPleachthe target
Four shapes have a tool that ships faster — reach for it. The fifth row, where the agent is multi-tenant and audit-bound, is the one Pleach is built for. The table below carries the full what you give up detail.
  • 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 deterministically 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 replay (@pleach/replay/strict, 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.

Competitor columns reflect each project's public docs as of 2026-08 and may have changed since.
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 (deterministic replay; divergence-detected)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.

Competitor columns reflect each project's public docs as of 2026-08 and may have changed since.
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 (deterministic; divergence-detected)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.

Governance control planes and AI gateways (a proxy-side category)

The category with the largest surface overlap on this page, and the newest. A governance control plane — Portkey, Cloudflare AI Gateway, Kong AI Gateway, LiteLLM, TrueFoundry, and the enterprise governed-proxy control planes now emerging above them (Merge Gateway and Concentrate are the clearest-drawn of these) — sits in the request path as a proxy. One URL swap, OpenAI-compatible, zero code change. It enforces in flight: access control bound to SSO, budget refusal before the model is contacted, inline guardrails and PII redaction, model routing and failover, and a tamper-evident log of every request exported for SOC 2 or ISO.

The newest of these have pushed the overlap along two axes. Merge Gateway prices at provider cost plus 5% and offers budget caps per customer tier, per-tenant usage reporting, and pass-through billing — the closest a proxy has come to the AuditableCall cost row. Concentrate went the other way on price. It launched in June 2026 on a $5M pre-seed with routing, cross-provider fallback, PII redaction, RBAC, SSO, and audit logs in a free tier at zero markup on tokens; what it charges for is log retention, org- and model-level rate limits, and the contract. Routing is not a priced surface in this category any more. It is worth being precise about what the overlap is and isn't, because the remaining gap is structural, not a feature the proxies haven't gotten to yet.

That overlaps @pleach/gateway and @pleach/compliance more than anything else on this page — and the reason is worth stating plainly, because it kills a tempting-but-wrong differentiator. A control plane enforces at runtime, same as the substrate. "We're runtime-native, they analyze after the fact" is the true line against the read-side observability tools above; it is false against a governed proxy. Both refuse the over-budget call before any model is contacted.

The axis that actually splits them is altitude, not timing: a proxy governs the call; the substrate governs the agent.

Competitor columns reflect each project's public docs as of 2026-09 and may have changed since.
Capability@pleach/core (+ @pleach/gateway)Governed-proxy control plane
Categoryagent execution substratein-path model proxy
Vantage pointthe agent's execution graph — session, turn, subagent treeflattened HTTP calls to the provider
What the audit record carriesturnId, toolName, subagentDepth, cost rolled to the parent turnwho / what / when / model, per call
Where the record livesthe AuditableCall row in your Postgresthe proxy vendor's plane; you export it out
Cross-provider routingfamily + transport lock (provenance of which model ran)routing + failover across providers
PII redaction at writeScrubber gateinline redaction
Cost attribution per axistenant_id-stamped row, one GROUP BYper-key / per-team on the proxy
Deploymentfair-source, self-host, no phone-home, BYO audit destinationproprietary SaaS; all traffic routes through the vendor
Adoption cost~15 LoC around the loop you already run (observe brownfield); adapter graft to add governanceone-URL swap, zero code change

Two cells carry the boundary. Vantage point: a proxy sees the HTTP call to the provider, so it cannot record subagent depth or roll a fan-out up to the turn that caused it — that structure never crosses the wire. The substrate sits inside the agent, so subagentDepth and the turn tree are columns, not a reconstruction. The same asymmetry runs through attribution itself: a proxy's cost dimensions — project, tag, customer — are whatever header the calling code sent, so the plane is recording your own claim about yourself. The substrate derives tenant_id, turnId, and delegated_from from having executed the work, which is the difference between a log and an attestation. Deployment: a governed proxy routes 100% of your model traffic through a third party — the exact posture a security team is usually trying to reduce — while the substrate records inside your own perimeter and writes the row to a database you own.

The reverse is just as honest. The proxy's one-URL swap governs apps you don't control and code you'll never touch; the substrate can't, because it is the code.

Inside code you do control, that gap is narrower than it reads. @pleach/observe wraps the loop you already run — init() once, recordCall() per call, roughly fifteen lines — and writes the audit row without owning the turn or changing behavior. That closes the distance on the row. It does not close it on family-lock, replay, or checkpoints, which need the runtime to drive the loop; adoption paths is the decision between the two. So for routing and cost they compose: put a gateway in front of org-wide traffic that isn't on the substrate yet, and use the substrate where the thing you must audit is the agent's execution rather than the model call.

For provenance they don't compose — they contradict. The proxy's headline reliability feature, automatic cross-provider failover, is definitionally non-reproducible: the record says which model the router happened to pick this time, under that moment's latency and quota. The substrate's family + transport lock is the opposite bet — pin the provider family and transport for the session so the same turn replays through the same path, and record the lock as part of the row. If deterministic replay is part of your audit posture, the substrate's own model calls should go direct, not through a failover proxy: keep the gateway fencing the unmanaged traffic beside it, not stacked underneath it.

When to reach for a governed proxy instead

  • You need org-wide governance over apps you don't own — today. Personal keys, unmanaged tools, a dozen teams shipping against a provider directly. A proxy's one-URL swap fences all of it without a code change; a substrate can't retrofit code you don't control.
  • Closed SaaS is acceptable and the call is the unit. If "who called which model, under which policy" is the whole audit question and the agent's internal structure isn't, a proxy answers it with less to adopt.

When you'd build on @pleach/core instead

  • The object of record is the agent's execution, not the model call. Subagent depth, the turn tree, and per-turn cost rollup are the audit surface — and a proxy structurally can't see them.
  • The record has to live in your own database. The AuditableCall row lands in your Postgres, joinable to your billing schema, with no traffic routed through a vendor plane and nothing to export back out.
  • Routing all traffic through a third-party proxy is the posture you're trying to avoid. Self-hosted and fair-source, the substrate keeps the audit inside your perimeter.

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.

One clarification first: memory is not a category the substrate lacks. @pleach/core ships its own memory layer — LLM-driven fact extraction, cross-agent consolidation, a confidence-decay auditor, and pgvector-backed semantic recall through SupabaseStore.search (Memory). The table below therefore compares a dedicated memory product against the audit ledger — the substrate's object of record — not against the package as a whole.

Competitor columns reflect each project's public docs as of 2026-09 and may have changed since.
CapabilityMemory / knowledge-graph substratethe AuditableCall ledger
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 dedicated memory product when the agent's recall outgrows the built-in layer — knowledge-graph traversal, entity resolution, memory shared across applications. 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.

Competitor columns reflect each project's public docs as of 2026-09 and may have changed since.
Capability@pleach/corepiClaude CodeGooseOpenHandsAutoGenCrewAI
Categoryruntime substratedev harness (CLI) + embeddable toolkitdev harness (CLI)dev harness (CLI / desktop)agentic dev environmentmulti-agent frameworkmulti-agent framework
Embeddable in your productyes (npm dep)yes (pi-ai / pi-agent-core); CLI is notno (end-user tool)no (end-user tool)partial (server mode)yes (library)yes (library)
Per-call audit row keyed (sessionId, turnId, stageId, seqWithinTurn)yesno — spans, no exporter or persistence by designnonononono
Family + transport lockyes30+ providers, no lockprovider-locked (Anthropic)configurable, no lockconfigurable, no lockprovider-agnostic, no lockprovider-agnostic, no lock
Replay determinismyesnononononono
Multi-tenant cost attribution by turnIdyesper-call usage.cost.total, not persisted or rolled to a turnn/a (single-user)n/a (single-user)n/a (single-user)manualmanual
Time-travel checkpointsyessession tree with branch + compactionsession resumesession resumesession resumenonenone
Lattice-enforced stage edgesyesimplicit loopimplicit loopimplicit loopimplicit loopfreeformrole-graph

pi is the case this axis handles least cleanly, and it is worth being explicit about why. Most harnesses are a single opinionated product. pi ships as five packages, and two of them — pi-ai (a unified API over 30+ providers) and pi-agent-core (the agent loop, tool contract, and event stream) — are ordinary embeddable libraries. So pi sits on both sides of the opinionation-vs-embeddability line: its CLI is squarely the category above, while its core is a genuine alternative in the same slot @pleach/core occupies. If you are choosing a loop to build a product on rather than a tool to use, compare against pi-agent-core specifically, not the CLI.

Its pi-telemetry package is the closest thing in the harness cohort to Pleach's recording surface, and the comparison is a narrow one: it defines vendor-neutral spans with a conformance suite for adapter authors, and explicitly ships no exporter, no persistence, and no cost or audit concept. That is a deliberate scope, not a gap — but it means a span tree, not a typed row you can join to billing. The distinction is the one drawn in Landscape between a trace and an AuditableCall.

Verdicts in the table above are read from each project's published documentation. Where a cell says "no", that is absence of a first-class concept in those docs, not proof that the capability cannot be assembled.

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.

An agent-forward surface (a usage axis)

One axis cuts across every category above, and it's about who operates the surface, not what the surface does. Most of these tools are a human console first — a dashboard you read, an API you wire — with agent access added on top. Pleach is built the other way: the runtime, the docs, and the discovery surface are made to be read and driven by an agent, and a developer reads the same surface.

Concretely, and all shipping today:

  • The runtime exposes a typed, introspectable facet API — runtime.tenant, runtime.events, runtime.graph.* — so an agent introspecting the runtime to plan its next action reads the same short list a developer does. CI gates (audit:facet-coverage) fail the build if a surface skips it.
  • The docs render to /llms.txt and /llms-full.txt, with a per-page Markdown twin on every URL (append .md), plus per-page TechArticle JSON-LD whose keywords / about / mentions come from one controlled tag vocabulary, not per-page guesswork.
  • The site ships a live MCP server (search_docs, get_doc_markdown) and an agent-skills index with a SHA-256 digest per page, so a tool can discover and verify the surface without scraping HTML.

The line, reused from the FAQ: agent-friendly first, usable by developers because they read the same surface. A dashboard-first tool can bolt on an agent endpoint; it can't retro-fit the whole surface into one an agent was meant to operate.

Where to go next