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

Brownfield · keep your provider, store, tools · no rewrite
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 row

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

Browse the @pleach scope on npm →
One full AuditableCall row — every column your agent writes per model callA single row from the harness_auditable_calls table with every field: record_id, tenant_id, turn_id, parent_turn_id, tool_name, subagent_depth, model_id, prompt_tokens, completion_tokens, prev_hash, and created_at. The parent_turn_id rollup key, subagent_depth, and prev_hash chain link are highlighted and gently pulse.HARNESS_AUDITABLE_CALLSone row · one model callSELECT *record_id01J9ZK7M…F2tenant_idacme_corpturn_idt_3f1a92parent_turn_idt_3f1a92tool_namesearch.websubagent_depth0model_idclaude-sonnet-4prompt_tokens1,180completion_tokens240prev_hash9af3c1…e7b1created_at2026-06-29T14:03Z
SELECT * FROM harness_auditable_calls
 ORDER BY created_at DESC LIMIT 1;
Every model call your agent makes appends exactly one row like this — to a table in your database. 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 rollup

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

Determinism

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

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

Rewind 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 & fork
AuditableCall row schema — one row per LLM callEach LLM call lands as one row carrying tenant, turn, tool, subagent depth, model, and token usage. Subagent spawns roll back to the parent turn_id rather than stranding under the child session.tenant_idturn_idtool_namesubagent_depthmodel_idtoken_usage1acme_corpt_3f1a92search.web0sonnet-41,4202acme_corpt_3f1a92search.docs1haiku-4612subagent_depth = 1 rolls back to the parent turn_id, not the child session
SELECT tenant_id,
       SUM(token_usage) AS spend
  FROM harness_auditable_calls
 GROUP BY tenant_id, turn_id;
Two rows from the ledger. Row 2 is a subagent spawn — same 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.

Four-stage lattice: anchor-plan, tool-loop, synthesize, post-turnEvery turn flows through four stages in order. The flow is enforced in CI — out-of-lattice edges fail the build.01anchor-planFrame the turn02tool-loopFan out + iterate03synthesizeOne final answerONE ANSWER ONLY04post-turnFlush + close
Every node in @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.

A single stream runs calls in a line; a node graph fans them out and merges the resultsThe top lane is a single message stream: four calls in a straight line, each blocking on the one before it. The bottom lane is the Pleach graph: one plan node fans out to two tool nodes and a subagent node running in parallel, a barrier waits for every branch, and the synthesize node closes the turn.SINGLE STREAMtool · 1tool · 2tool · 3answerOne call after another. Each step blocks on the last; the history is a single line.NODE GRAPHplantooltoolsubagentbarriersynthone answerFan out in parallel; merge through a barrier and a reducer; fork at any event.
A single stream can only run the next call. The graph fans out a tool batch or subagent swarm with 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.

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

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.

Your existing agent loop on top, the @pleach/observe hook in the middle, the destination you pick at the bottomYour agent loop stays where it is — the Vercel AI SDK, LangChain, the OpenAI or Anthropic SDK called directly, or a hand-rolled loop. The @pleach/observe SDK wraps it in about fifteen lines and writes one AuditableCall-subset row per LLM call. The row lands in the destination you pass: your Postgres, Supabase, an OpenTelemetry collector, or an in-memory buffer.YOUR AGENT LOOP · STAYS WHERE IT ISVercel AI SDK · LangChain · custom loopor the OpenAI / Anthropic SDK called directly~15 lines · no rewrite@pleach/observe · BROWNFIELD ENTRY POINTinit + per-turn recorder + destination plugone AuditableCall-subset row per LLM callDESTINATIONS YOU PICK · YOUR STORAGE, YOUR CONTROL PLANEPostgrespostgres()Supabasesupabase()OTel collectorotel()Memorymemory()
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();
The loop you had before stays the loop you have now. The 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 →

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.

Your enterprise contract on top, Pleach composing underneath, both writing into your own PostgresYour Anthropic or OpenAI Enterprise contract keeps doing its job — SSO, ZDR, Workspaces or Projects, the Admin API. Pleach is an npm install plus one Postgres table that adds three walls the contract does not cover: per-customer cost rollup, a tamper-evident hash-chain row, and replay across model snapshots. Both write into the database you already run.YOUR ENTERPRISE CONTRACT · KEEPS DOING ITS JOBAnthropic / OpenAI EnterpriseSSO · ZDR · Workspaces / Projects · Admin API · prompt cachecomposes underneathPLEACH · npm install + one table · adds three wallsWALL 1Per-customer rollupWALL 2Hash-chain rowWALL 3Replay across snapshotsYOUR POSTGRES · NO NEW VENDORharness_auditable_callsone opaque tenant_id per call · sums reconcile to vendor total
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 total
The contract reports the workspace 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.
Session manifest — the ordered, append-only event log an agent reads back and forks fromSeven events on a spine: session start, turn start, a tool call and its result, a checkpoint that is the fork point, a subagent spawn, and the final answer. The checkpoint node gently pulses. Replay or fork the session from any event.SESSION_MANIFEST · APPEND-ONLYs_91c… · ordered by seqwhat the agent reads001session.startchannel: main002turn.startt_3f1a92003tool.callsearch.web004tool.result1,180 tok005checkpointckpt_7 · fork point006subagent.spawndepth 1007turn.finalone answer
SELECT seq, event, payload
  FROM session_manifest
 WHERE session_id = 's_91c'
 ORDER BY seq;
The manifest is append-only and ordered by seq. An agent reads it back to know what it did; you replay or fork from any event — ckpt_7 makes the rewind exact.