@pleach/*
The agent runtime built around the audit row.
Every model call your agent makes writes one row you own — enough to bill each customer, answer an auditor in SQL, and replay last week's bug exactly. Add it to the agent you already run: your provider, database, and tools stay put, no loop rewrite.
The name is horticultural. Pleaching weaves living branches into one load-bearing hedge. Pleach weaves every call, tool, and subagent your agent produces into one structure you can query, prune, and replay.
Free and fair-source under FSL-1.1-Apache-2.0. Runs on your own database — no phone-home, no license check, no account. In production today.
import { createPleachRuntime, setOrchestratorAdapterCtor } from "@pleach/core/runtime";
// wrap the provider + store you already run — no loop rewrite
setOrchestratorAdapterCtor(MyProviderAdapter);
const runtime = createPleachRuntime({
storage: new MyPostgresStorageAdapter(pool),
checkpointer: new MyPostgresCheckpointer(pool),
plugins: [myDomainPlugin],
});
// core now governs YOUR loop: family-lock, replay, checkpoints, audit rowShipped in @pleach/core@0.1.0. Core drives the loop you already run — family-lock, replay, checkpoints, and one audit row per call, over your infrastructure. The runnable end-to-end is on Adoption paths.
Even lighter: @pleach/observe adds just the audit row in ~15 lines — days away on npm. Starting a fresh build instead? createPleachRuntime() gives you the runtime on defaults.
SELECT * FROM harness_auditable_calls ORDER BY created_at DESC LIMIT 1;
prev_hash links it to the call before it; subagent spend rolls up to parent_turn_id.What you can do with it
We graded twenty agent-runtime capabilities against the market — Helicone, Langfuse, LangSmith, Portkey, LangGraph, Temporal, and the rest. These are the few the field admits it doesn't ship. The honest version, in both directions, is the landscape.
See every tool call and subagent, rolled up to the turn that caused it
Each LLM call writes one typed row keyed to the turn — which tool ran, how deep the subagent went, how many tokens it burned. Subagent spend rolls up to the parent turn as a stored number, not something you recompute by crawling a trace tree every time you query. Per-customer billing then falls out of one SQL query.
Subagent & tool-call rollupReplay any run, byte-identical
Replay from a saved snapshot (a checkpoint) with the same version and input and you get the same run exactly — across Node, Browser, and Edge — or strict mode stops and names the first event that diverged. Durable-execution platforms (Temporal, Inngest) save the output; most frameworks re-fire the call and may drift. This replays the path, not just the result.
DeterminismA tamper-evident record only you hold
Every row lands in your Postgres carrying a hash of the row before it (a hash chain). Backfill or delete a row and the chain breaks when the verifier runs. No observability or gateway tool we surveyed ships one over the call log.
The hash chainRouting that keeps replay honest
At session start the lock freezes how the model tokenizes, caches prompts, formats tool calls, and phrases refusals. A transient fallback stays inside the same model family (models that share those — the GPT-5.6 line, say, or the Claude line); any switch to another provider is logged on the row. Replay stays valid because the lock holds.
Family-lockRewind to before the bad decision
Fork a session at any past point in the run, not just the last message, then try a different path. Each piece of state is snapshotted on its own (a per-channel checkpoint), so the rewind is exact — an agent can catch its own mistake instead of escalating.
Time-travel & forkSELECT tenant_id,
SUM(token_usage) AS spend
FROM harness_auditable_calls
GROUP BY tenant_id, turn_id;turn_id as row 1, subagent_depth = 1. Finance reads the per-turn rollup as a single GROUP BY rather than stitching child sessions together after the fact.How it works
Three structural decisions, not three best practices. Everything above reduces to these. Every turn walks the same four stages in the same order — a fixed path (the lattice) a regulator can read off this page.
A turn walks a four-stage lattice
Each turn moves through anchor-plan, tool-loop, synthesize, and post-turn, under a discipline that fires exactly one final answer. The stages are a fixed shape a regulator can read off this page — which frees the audit row to carry what changes.
One append-only audit row per call
Each row is written once and never edited or deleted (append-only). It carries the turn, the tool, the model, and the tokens, with subagent cost rolled up to the parent turn. Joinable to your billing or compliance schema today; replayable from the recorded inputs (the fingerprint stream).
Routing stays in one model family
How the model tokenizes, caches prompts, formats tool calls, and phrases refusals all freeze at session start. A transient fallback stays in the same family; any switch to another provider is logged on the row.
The runtime is a contract, not a TypeScript API — a set of wire formats: HTTP and streaming (SSE) routes, the audit row, the checkpoint format (the checkpoint envelope), and the sync clock (the version vector). @pleach/coreis the reference implementation; a Go implementation round-trips the same recorded turns, so the wire shapes aren't TypeScript-flavored. Read the contract or the architecture deep-dive.
@pleach/core sits in exactly one of these four stages. The audit:graph-stages CI gate fails the build on an out-of-lattice edge — the diagram is the invariant, not a best-practice diagram.Node graph, not a single stream
You've already wired a graph by hand. Pleach makes it explicit.
Call the model, run the tool it asks for, call it again: a tool-use loop is already a little graph. Each step is a node, the state you carry between steps is a channel, and every branch that decides what runs next is an edge. Pleach makes that graph explicit and typed. So instead of running every tool and subagent in a line, you fan them out in parallel, merge the results the same way on every replay, and rewind to any past step. The default agent graph is dozens of these nodes across four stages; a raw message loop is one line through it.
Fan out in parallel, merge deterministically
One branch fans out into many: Send[] runs the tool batch or the subagents at once, each with its own copy of the state. Their writes merge through an order-independent merge function (a reducer), so the result is identical on every replay. A barrier (NamedBarrier) holds the final answer until every branch reports in. Channels →
Branch on state, not on a hardcoded path
addConditionalEdges picks the next node from what the turn actually produced — call tools, retry, or finish. The route function reads the latest state and returns the target; in a single stream that logic tangles into the loop body. The graph →
Rewind to any past event, not just the last message
Checkpointing is a channel operation: every channel snapshots independently, so a session forks at any past event — not just a node boundary — and re-runs a different path. A linear stream can only truncate to a prior message; it can't replay the branch that produced it. Time-travel & fork →
The graph stays auditable — but the row is per model call, not per node. Only the nodes wired to a model actually call one. The pure checks between them — a garble detector, a repetition guard, a cost rollup — never touch a model, so they write nothing. Each model call writes one turn-keyed audit row, tagged with the stage its node sits in — parallel fan-out included. The graph shape is language-agnostic; a Go implementation replays the same recorded turns. Nodes and subagents carry the wiring.
Send, gates the synthesizer on a NamedBarrier, and merges concurrent writes through commutative reducers — so the same fan-out replays byte-identically. Simplified: the default agent graph spans dozens of typed nodes across the four stages.Should you stay where you are?
The honest version. If your shape fits one of these, reach for it — you'll ship faster. The full comparison adds LlamaIndex, Mastra, Goose, OpenHands, AutoGen, and CrewAI.
Multi-tenant SaaS, an audit-bound vertical, replay-as- regression-test, or the agent isthe product? That's the target. The same agent built four ways shows what you write yourself versus what the runtime hands you.
Vercel AI SDK
Reach for it when: A single-shot chat with tools. No persistence, no per-customer cost, no audit obligation.
Give up vs Pleach: No turn-keyed audit row, no replay determinism, no family-locked routing.
LangChain / LangGraph
Reach for it when: Chain abstractions, a large component catalog, hosted LangSmith for tracing.
Give up vs Pleach: Callbacks without shared row identity. Per-call cost is manual. No language-agnostic wire contract.
Claude Code / Goose
Reach for it when: A dev harness you run on your own machine — code review, refactor, terminal-native agents.
Give up vs Pleach: Single-user shell, not embeddable in a customer-facing SaaS. No multi-tenant audit.
Inngest / Trigger.dev
Reach for it when: Durable function chains, step-memoized retries, cron, fan-out, a hosted run dashboard.
Give up vs Pleach: Run history is the vendor's shape, not your billing schema. No family-lock, no hash chain. Use both — see Pleach + Inngest.
OpenRouter SDK
Reach for it when: One client across 300+ models, with automatic tool execution, provider fallback, and a maxCost stop condition to cap a run.
Give up vs Pleach: Cost is a runtime guardrail, not a stored column. No turn-keyed audit row in your DB, no replay determinism, no recorded family provenance after a fallback.
what you give up detail.Three ways in
Pick the smallest entry point that fits.
Core governs the loop you already run — your provider, store, and tools stay put. Observe watchesit with just the audit row, for when that's all you need. Adopt the least you need; the same row travels with you if you grow into more. Compare all three →
Core brownfield
Keep your provider, store, and tools — let core govern the loop over them, no rewrite. Shipped today.
createBrownfieldRuntime →Observe brownfield · soon
Keep your loop. Add one typed audit row per LLM call in ~15 lines — no runtime migration. Days away on npm.
@pleach/observe →Greenfield
A fresh SessionRuntimeon core's defaults — sessions, graph, replay, checkpoints, interrupts.
@pleach/core →Keep your stack
Already shipping on the AI SDK or LangChain? Add the audit row without a rewrite.
@pleach/observe bolts onto an app you already ship (a brownfield adoption). Wrap the agent loop you already run in about fifteen lines and write one typed audit row per LLM call to a backend you pick. No runtime migration, no separate service to run (no hosted control plane), no new vendor.
Wrap the loop you already have
It sits in front of the Vercel AI SDK, LangChain, the OpenAI or Anthropic SDK called directly, or a hand-rolled loop. Three moving parts: init, a per-turn recorder, one destination plug. BYOK observability →
Your storage, your control plane
The row lands in the destination you pass — your Postgres, Supabase, an OpenTelemetry collector, or an in-memory buffer. The OTel path is buyer-callback only: zero @opentelemetry/* runtime dependencies. @pleach/observe →
A runway, not a dead end
The ObserveRow is a strict subset of core's AuditableCall, so rows the SDK writes today stay valid rows the runtime reads tomorrow. Adopt @pleach/core later and the history comes with you — the migration only moves forward, and today's rows are never rewritten (monotonic by construction). Adoption paths →
Per-customer attribution falls out of the row — (tenantId, turnId, toolName, model, tokens, costUSD) in one GROUP BY. Need replay determinism, family-locked routing, channels, or time-travel? Those live in the runtime — the greenfield path (a fresh build) through @pleach/core. @pleach/observe is in alpha; the SDK layer stays free.
import { init, startTurn } from "@pleach/observe";
import { postgres } from "@pleach/observe/destinations";
init({ destination: postgres({ pgClient }) });
const turn = startTurn({ tenantId, sessionId });
await turn.recordCall({ model, tokens, costUSD }); // one row per call
await turn.end();ObserveRowis a strict subset of core's AuditableCall, so rows the SDK writes today stay valid rows the runtime reads tomorrow — adopt @pleach/core later and the history comes with you.Keep your infrastructure
Already have a provider, a database, and tools? Let core govern the loop — without giving them up.
@pleach/core can drive the turn loop over infrastructure you already run. Wrap your LLM client as an OrchestratorAdapter, your database as a StorageAdapter, fold your prompts and tools into one plugin — and get family-locked routing, replay, checkpoints, and interrupts over your stack. No loop rewrite; no ripping out your provider or store.
Observe watches; core governs
The SDK records a row about a call you already made. The runtime acts during the turn — enforces safety policies, corrects fabrications, halts a bad chunk mid-stream, walks a family-locked cascade. When you need behavior, not just the row, you need the runtime. Adoption paths →
Your infra stays yours
Bespoke gateway, wrapped Bedrock client, a Postgres schema that isn't Supabase? There's no built-in adapter and you don't need one — each adapter is a small interface you implement over code you already have. Host adapters →
A graft, not a rewrite
Core owns the four-stage lattice, family-lock, and the audit ledger; your provider, store, and tools stay exactly where they are, wrapped underneath. The examples/brownfield-adapter/ example is the runnable end-to-end. Adoption paths →
On a recognized provider (the Vercel AI SDK, LangChain) the graft is a drop-in — see the migration guides. On bespoke infra you implement the adapter interface; it's still not a loop rewrite.
import { createPleachRuntime, setOrchestratorAdapterCtor } from "@pleach/core/runtime";
import { definePleachPlugin } from "@pleach/core";
// 1. Wrap your existing LLM call as an OrchestratorAdapter (core drives it).
setOrchestratorAdapterCtor(MyExistingProviderAdapter);
// 2. Wrap your existing DB as a StorageAdapter + Checkpointer, and fold
// your prompts / tools / safety rules into ONE plugin.
const runtime = createPleachRuntime({
storage: new MyPostgresStorageAdapter(pool),
checkpointer: new MyPostgresCheckpointer(pool),
plugins: [myDomainPlugin],
host: { strategies: { orchestratorConfig: { /* your provider config */ } } },
});
// Core now owns the lattice, family-lock, replay, checkpoints, interrupts —
// over YOUR provider, YOUR store, YOUR tools. No loop rewrite.Already paying the lab
On Anthropic or OpenAI Enterprise? Pleach composes underneath it.
Your contract keeps doing its job — SSO, ZDR, Workspaces or Projects, the Admin API, prompt caching, snapshot pinning. Pleach is an npm install plus a Postgres table you already run: no new vendor, no new SOC 2 boundary, no new ZDR review. It adds the three walls the contract doesn't cover.
Per-customer rollup inside one Workspace
The Admin API reports the Workspace or Project total — not the customers, teams, or cost centers inside it. The row carries an opaque tenant id on every call; wire it to whatever you bill or audit against. One GROUP BY, and the sums reconcile to the vendor total. Multi-tenant →
Tamper-evident row in your own DB
ZDR governs what the vendor stores. The downstream auditor asks about what you stored. Every row lands in your Postgres with a prev-hash chain that breaks at the verifier on any backfill or removal. Hash chain →
Replay across snapshots, not just inside one
A pinned snapshot doesn't make a turn replayable. The fingerprint records the inputs per turn, so drift between two snapshots surfaces as a diff in CI — early warning before a customer ticket. Determinism →
Anthropic Enterprise
Keep SSO, ZDR, Workspaces, Admin API
The Admin API tells you what the Workspace owed Anthropic. Pleach tells you which of your customers caused it.
OpenAI Enterprise
Keep SSO/SCIM, ZDR, Projects, Usage API
The Usage API tells you what the Project spent. Pleach tells you which of your customers caused it.
Bedrock, Azure OpenAI, or Vertex in the path? Same walls, different transport — cloud-mediated adapters land in @pleach/gateway. Two siblings ride the same contract footprint: the sandboxed coding agent and the language-agnostic contract that answers "is this TypeScript-only?" before IT asks.
SELECT tenant_id,
SUM(token_usage) AS spend
FROM harness_auditable_calls
WHERE workspace_id = 'ws_acme' -- one Admin-API Workspace
GROUP BY tenant_id; -- sums reconcile to the vendor totalworkspace total. Pleach adds the tenant_id on every row, so one GROUP BY tells you which customer caused it — and the per-customer sums add back up to what the Admin API billed.Quick answers
The short version. The full FAQ covers the rest.
Built so an agent can read its own history and rewind itself — the same typed rows and checkpoints a developer reads. The docs render to /llms.txt and /llms-full.txt.
- What is Pleach?
- Pleach is a free, fair-source agent runtime. Every LLM call your agent makes writes one typed row to a database you own — which gives you per-customer cost, compliance you can query in SQL, and deterministic replay. @pleach/core is the TypeScript runtime, in production today.
- How does Pleach attribute LLM cost per customer?
- Every audit row carries which tenant, which turn, which model, and how many tokens — with subagent spend rolled up to the turn that caused it. Per-customer billing falls out of one SQL GROUP BY, not a separate cost pipeline.
- Already on Anthropic Enterprise or OpenAI Enterprise — what does Pleach add?
- It composes underneath. Your contract keeps doing its job; Pleach adds per-customer cost rollup inside one Workspace or Project, a tamper-evident audit row in your own Postgres, and replay across model snapshots. No new vendor — an npm install plus a table. See /docs/migrating-from-anthropic-enterprise.
- What license does Pleach use? Is it open source?
- Fair source — FSL-1.1-Apache-2.0. Source-available, free in production. Each release converts to Apache-2.0 two years after it ships. Not OSI-approved open source during that window.
- How is Pleach different from the AI SDK or LangChain?
- They ship faster for single-shot agents and broad chain composition. Pleach is the runtime when the agent is the product: per-customer cost, deterministic replay, time-travel checkpoints, and a typed audit row joinable to your billing and compliance schemas. See /docs/comparison.
SELECT seq, event, payload FROM session_manifest WHERE session_id = 's_91c' ORDER BY seq;
seq. An agent reads it back to know what it did; you replay or fork from any event — ckpt_7 makes the rewind exact.