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:
| 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 | no | partial via callbacks | no | no |
| Replay determinism + write-side hash chain | ✅ structural | no | partial (LangSmith, external) | partial (eval) | partial (workflow) |
| Family + transport lock + lattice invariants (no silent cross-family fallback; four-stage lattice lint-enforced) | ✅ structural | no provider-agnostic | no | no | 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
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; 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) | 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) |
| Context compaction (history summarization + tool-result eviction) | yes, opt-in (SummarizationMiddleware, ResultEvictionMiddleware) | manual | partial | partial | partial |
| Sandboxed code execution | contract + adapters (@pleach/sandbox; core's code_exec refuses until a runner is injected) | consumer-wired | consumer-wired | consumer-wired | consumer-wired |
| Safety-policy API (typed, registerable) | defineSafetyPolicy — advisory / guardrail / refusal; enforced as prompt prose + audit record today, blocking effects declared but not wired | none | consumer-wired | none | none |
| Agent Skills (markdown skills, intent-matched injection) | yes (SkillLoader — SKILL.md + frontmatter, three sources) | none | none | none | none |
| 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 |
| 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 work | none | partial (LangGraph checkpointers) | partial (workflow checkpoints) | partial (suspend/resume) |
| Background / scheduled runs | partial — schedule tools persist intent to the store; the runtime has no timer of its own, the host fires due tasks via readScheduledTasks | none | partial (hosted platform) | none | partial (hosted platform) |
| Storage & contracts | |||||
| BYO-DB storage adapters | Memory / IndexedDB / Supabase | LocalStorage only | none for runtime | vector stores | yes |
| Long-term memory (fact extraction + semantic recall) | yes, executor-gated (@pleach/core/memory extraction + cross-agent consolidation; pgvector search via SupabaseStore.search) | none | partial | partial | yes |
| MCP (Model Context Protocol) | server (@pleach/mcp — MCPServer wraps the runtime; stdio / SSE / WebSocket); no client-side mcpServers attach | client (experimental) | client (adapters) | client (tool adapters) | client + server |
| 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 aBaseCallbackHandlercan 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, theAuditableCallrow shipstokenUsagealready rolled up toturnId. - "Partial (
UIMessagestream)" 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 canGROUP BYsix months later. - "Opt-in" and "executor-gated" mean the mechanism ships in
the package but installs only when the host wires it. A bare
createPleachRoutepasses tool results through unmodified until you setresultOffloadBackend; 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/refusallevels are typed and registerable, and all three currently map to alog_onlyeffect — 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.rollbackare real, but nothing scans for an in-flight turn on process restart —AsyncTaskManagersnapshots 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
AuditableCallrow 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/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
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.
what you give up detail.- Want a chat shell with tools and nothing more? The AI SDK's
streamText/generateTextis 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 theAuditableCallrow. - Want chain abstractions and a large pre-built component
catalog? LangChain. The ecosystem breadth is the value; the
Runnablecontract 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:
- 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.
- 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
AuditableCallrow carriesturnId,toolName, andsubagentDepthso the math is oneGROUP BY, not a regex pass over logs. - 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, andaggregateMultiTenantbodies all land at the0.1.0cut): a recorded turn replayed against the same@pleach/coreversion + the same fingerprint inputs must yield the sameStreamEventsequence in the same order, or strict mode throwsReplayDivergenceErrorwith 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/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 (deterministic replay; divergence-detected) | 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 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.executeMessagefrom 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/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 (deterministic; divergence-detected) | 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" 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.
| Capability | @pleach/core (+ @pleach/gateway) | Governed-proxy control plane |
|---|---|---|
| Category | agent execution substrate | in-path model proxy |
| Vantage point | the agent's execution graph — session, turn, subagent tree | flattened HTTP calls to the provider |
| What the audit record carries | turnId, toolName, subagentDepth, cost rolled to the parent turn | who / what / when / model, per call |
| Where the record lives | the AuditableCall row in your Postgres | the proxy vendor's plane; you export it out |
| Cross-provider routing | family + transport lock (provenance of which model ran) | routing + failover across providers |
| PII redaction at write | Scrubber gate | inline redaction |
| Cost attribution per axis | tenant_id-stamped row, one GROUP BY | per-key / per-team on the proxy |
| Deployment | fair-source, self-host, no phone-home, BYO audit destination | proprietary SaaS; all traffic routes through the vendor |
| Adoption cost | ~15 LoC around the loop you already run (observe brownfield); adapter graft to add governance | one-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
AuditableCallrow 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.
| Capability | Memory / knowledge-graph substrate | the AuditableCall ledger |
|---|---|---|
| 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, 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.
| Capability | @pleach/core | pi | Claude Code | Goose | OpenHands | AutoGen | CrewAI |
|---|---|---|---|---|---|---|---|
| Category | runtime substrate | dev harness (CLI) + embeddable toolkit | dev harness (CLI) | dev harness (CLI / desktop) | agentic dev environment | multi-agent framework | multi-agent framework |
| Embeddable in your product | yes (npm dep) | yes (pi-ai / pi-agent-core); CLI is not | 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 — spans, no exporter or persistence by design | no | no | no | no | no |
| Family + transport lock | yes | 30+ providers, no lock | 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 | no |
Multi-tenant cost attribution by turnId | yes | per-call usage.cost.total, not persisted or rolled to a turn | n/a (single-user) | n/a (single-user) | n/a (single-user) | manual | manual |
| Time-travel checkpoints | yes | session tree with branch + compaction | session resume | session resume | session resume | none | none |
| Lattice-enforced stage edges | yes | implicit loop | implicit loop | implicit loop | implicit loop | freeform | role-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/coreis 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:
- Per-axis rollup inside one Workspace / Project. The Admin or
Usage API reports the Workspace or Project total. Pleach's
AuditableCallrow carries an opaquetenantIdyou 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. - Hash-chained audit row in your own DB. ZDR governs what the
vendor stores. The
prev_hash+row_hashchain on everyAuditableCallrow 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. - 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.
Migrating from Anthropic Enterprise
Keep SSO, ZDR, Workspaces, the Admin API. Close the three gaps the vendor contract doesn't cover.
Migrating from OpenAI Enterprise
Keep SSO/SCIM, ZDR, Projects, the Usage API. Close the three gaps the vendor contract doesn't cover.
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.txtand/llms-full.txt, with a per-page Markdown twin on every URL (append.md), plus per-pageTechArticleJSON-LD whosekeywords/about/mentionscome 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
Landscape
Twenty capabilities ranked by how differentiated each is — structural to the audit row, assembled here, or table stakes.
The AuditableCall row
What's in the per-call audit row, and what you can join it against.
Architecture
The six pieces of the substrate — stage lattice, call classes, seams, family-lock, audit ledger, event log.
Packages
The @pleach/* matrix — which sibling layers on top of core for which job.
Typed records
Discriminated-union audit payloads that narrow on payload.kind in TypeScript.
Hash chain
Event-log rows carry prev_hash + row_hash at the schema level for tamper-evidence.
Decisions
Internal "when to use X vs Y" aids — Session vs Turn vs Subagent, Ledger vs Observability, Scrubber vs ComplianceRuntime, and the rest of the close-cousin pairs.
Landscape
Twenty agent-runtime capabilities, ordered by how differentiated @pleach/core actually is — what's structural to the audit row, what Pleach assembles from parts the market sells separately, and what's table stakes you shouldn't switch for.