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/core | AI SDK | LangChain | LlamaIndex | Mastra |
|---|---|---|---|---|---|
Per-call audit ledger (AuditableCall row keyed by turnId, tenant_id stamped, joinable to billing in one GROUP BY) | ✅ structural | ❌ | partial via callbacks | ❌ | ❌ |
| Replay determinism + write-side hash chain | ✅ structural | ❌ | partial (LangSmith, external) | partial (eval) | partial (workflow) |
| Family + transport lock + lattice invariants (no silent cross-family fallback; four-stage lattice lint-enforced) | ✅ structural | ❌ provider-agnostic | ❌ | ❌ | partial (lattice) |
Below: the row-by-row matrix. If you don't need any of the three axes above, the AI SDK is the cleaner pick. If you need all three, you're on the right page.
Capability matrix
The header column is framed as "capability," not "who wins." Other libraries are excellent at their own jobs; this table just records which capabilities each one ships as a first-class concept. Rows are grouped into four themes — observability & accounting, execution shape, routing & replay, storage & contracts — which is the four-axis value-prop story for the substrate.
| Capability | @pleach/core | Vercel AI SDK | LangChain | LlamaIndex | Mastra |
|---|---|---|---|---|---|
| Observability & accounting | |||||
| Per-call audit row | AuditableCall ledger | none | partial via callbacks | none | none |
Append-only event log (typed StreamEvent) | yes | partial (UIMessage stream) | partial via callbacks | partial (instrumentation) | yes (workflow events) |
Subagent spawn + depth rollup to parent turnId | SpawnTreeState | none | partial (LangGraph subgraphs) | partial (workflows) | partial (agent network) |
Per-call cost allocation by turnId | yes (tokenUsage on row) | manual | manual | manual | partial |
Typed audit records (discriminated payload.kind) | yes (TS narrowing on payload.kind) | opaque JSON | per-vendor shapes via callbacks | per-module shapes | partial (typed workflow events) |
| Tamper-evident hash chain (write-side) | yes (prev_hash + row_hash at schema level) | none | none | none | none |
| PII scrubber gate at write time | yes (registered Scrubber instances; CI gate audit:c8-event-type-allowlist-coverage) | consumer-wired | consumer-wired | consumer-wired | consumer-wired |
| Built-in OTel span set with auto parent-threading | yes (session.turn, llm.invocation, graph.stage, tool.execution) | partial (per-call spans, no parent threading) | partial (per-operation spans) | partial | partial |
Multi-tenant facet stamping tenant_id on every row + span + outbound HTTP | yes (runtime.tenant facet, withTenantHeader) | consumer responsibility | consumer responsibility | consumer responsibility | consumer responsibility |
| Execution shape | |||||
| 4-stage lattice (lint-enforced edges) | yes | none | none | none | partial |
| Singleton synthesize seam | yes | none | none | none | none |
| Reactive channels (LangGraph-shaped) | yes | none | partial (LangGraph) | none | partial (workflow channels) |
| Plugin contract with lattice invariants | yes | none | partial (chains) | partial (modules) | yes |
| Human-in-the-loop interrupts | yes | partial (tool approval) | yes (LangGraph interrupt()) | partial (workflows) | yes (suspend/resume) |
| Routing & replay | |||||
| Family + transport lock (session-scoped) | yes | provider-agnostic, no lock | none | none | none |
| Replay-deterministic streaming | yes | none | none | none | none |
| Time-travel checkpoints | yes | none | partial (LangGraph checkpointers) | none | partial (suspend/resume) |
| Eval / replay diff engine | DIY against ledger today; @pleach/eval@0.1.0 ships the contract + bootstrap/Welch t-test scoring; @pleach/replay@0.1.0 ships replayTurn + chain verifier | none | partial (LangSmith, external) | partial (eval module) | none |
| Storage & contracts | |||||
| BYO-DB storage adapters | Memory / IndexedDB / Supabase | LocalStorage only | none for runtime | vector stores | yes |
| Offline-first sync (IndexedDB ↔ server) | yes | none | none | none | none |
| Tamper-evident hash chain | TamperEvidence plug-point + writer-side stamping in soak; verifier shipping in @pleach/core/eventLog (verifyChainForChat, generateProof). @pleach/compliance@0.1.0 adds scrubbers + attestation. | none | none | none | none |
| Language-agnostic wire contract | yes (Go reference impl) | none (TS-only) | none (separate JS / Py codebases) | none (separate JS / Py codebases) | none (TS-only) |
Source notes for the row data live next to the comparison table in
the @pleach/core README; this page is a mirror so the docs site
stays a single discoverable surface. A few cells deserve gloss:
- "Partial via
callbacks" for LangChain means 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. - "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.
- 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 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, 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 (StreamEvent byte-identical) | code-level memoization, not stream-level |
Subagent rollup to parent turnId | yes (SpawnTreeState) | manual |
| Tamper-evident audit trail | yes (prev_hash + row_hash) | run history isn't cryptographically signed |
Multi-tenant tenant_id stamping | enforced by CI gates | your event metadata |
The honest pattern is use both: wrap runtime.executeMessage
inside step.run, get Inngest's operational trail AND Pleach's
business trail. See Pleach + Inngest 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 (byte-identical) | none (re-run) | none | none | re-generate + score | n/a |
| PII redaction at write | Scrubber gate | hook (delegates to Presidio) | Presidio guardrail | masking hook | none first-class | n/a |
| Sampling | none — every call | expected / recommended | n/a | configurable | n/a | n/a |
Glosses where the cell is doing work:
- "Reconstructed from trace tree" on subagent spend means the child tokens are captured as nested spans, but the parent's cost is summed at query time by walking the tree — not stored on the parent. This is the documented gap most observability tools share.
- "Their store" 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.
| Capability | Memory / knowledge-graph substrate | @pleach/core |
|---|---|---|
| Object of record | the agent's knowledge and decisions — files, entities, notes, rationale | one provider call — model, tokens, cost, family |
| Read by | the agent itself, to shape its next step | people — billing, audit, compliance |
| Read paradigm | graph traversal / Datalog over entities | SQL rows joinable to your billing schema |
| Fields about the model call | none — no provider, model, token, or cost column | the whole row |
| Which path is the product | the read — you query it to reason | the write — record every call, then read to account |
The split in one line: a memory substrate is a read substrate —
the graph exists so the agent can query it and decide. @pleach/core
is a write substrate — the ledger exists so every call is
recorded, and the reads serve the people who answer for the spend.
Even where the two overlap — both can record a tool call — a memory
graph stores the tool's semantics so the agent recalls causally;
the ledger stores the call's economics so finance attributes it.
A memory graph carries no provider, model, or cost column; the
ledger was never the agent's recall surface.
So they compose rather than compete. A memory graph can sit beside
the AuditableCall row, 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/core | Claude Code | Goose | OpenHands | AutoGen | CrewAI |
|---|---|---|---|---|---|---|
| Category | runtime substrate | dev harness (CLI) | dev harness (CLI / desktop) | agentic dev environment | multi-agent framework | multi-agent framework |
| Embeddable in your product | yes (npm dep) | no (end-user tool) | no (end-user tool) | partial (server mode) | yes (library) | yes (library) |
Per-call audit row keyed (sessionId, turnId, stageId, seqWithinTurn) | yes | no | no | no | no | no |
| Family + transport lock | yes | provider-locked (Anthropic) | configurable, no lock | configurable, no lock | provider-agnostic, no lock | provider-agnostic, no lock |
| Replay determinism | yes | no | no | no | no | no |
Multi-tenant cost attribution by turnId | yes | n/a (single-user) | n/a (single-user) | n/a (single-user) | manual | manual |
| Time-travel checkpoints | yes | session resume | session resume | session resume | none | none |
| Lattice-enforced stage edges | yes | implicit loop | implicit loop | implicit loop | freeform | role-graph |
When to reach for a harness instead
- 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.
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.