Skip to content

Tamper-evident agent audit

Every model call your agent makes, as one row you own.

Each row is hash-chained to the one before it and lands in your Postgres. Add it to the agent you already run; no loop rewrite.

Free · FSL-1.1 fair-source · your database · no phone-home · no account

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.

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.
  • verifyChain ships in @pleach/core; you run it, not us.
  • audit:graph-stages fails the build on an out-of-lattice edge.
  • A Go implementation round-trips the same recorded turns.
  • Competitor claims carry the month they were written. Landscape →

Capabilities

The row is the product. Everything else reads it.

As of 2026-06, these are the capabilities we didn't find shipping elsewhere among Helicone, Langfuse, LangSmith, Portkey, LangGraph, Temporal, and the rest. The landscape has the honest version, in both directions.

Backfill or delete a row and the chain breaks.

Every row carries a hash of the row before it, in a database only you hold. The verifier is yours to run, and it names the first row that doesn't match.

verifyChain(rows)
// → { ok: true }
// → { ok: false, failedIndex, expected, actual, reason }

The hash chain

Replay a recorded turn. Any drift is named.

Replay re-serves the recorded responses against the same version and input, and names the first event that diverges. A live model can't promise byte-identical output, so rerunning the same model and judging the result equivalent is the next step; it isn't shipped yet.

headless-replay: cache miss → ReplayDivergenceError

DeterminismEval and replayFamily-lock

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 by API contract 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.

Every subagent call carries the turn that caused it.

Each row stores parent_turn_id and subagent_depth, along with the tool that ran and the tokens it used. One GROUP BY rolls nested fan-out back to the user message.

GROUP BY parent_turn_id

Subagents

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.

Every turn runs the same four stages.

Four-stage lattice: anchor-plan, tool-loop, synthesize, post-turnA turn is drawn as a four-layer network. Anchor-plan has three nodes, tool-loop fans out to six and loops back on itself, synthesize narrows to one node, and post-turn flushes to two. Every edge runs from one stage to the next, 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.

The runtime is a contract, not a TypeScript API: HTTP + SSE routes, the audit row, the checkpoint envelope, the sync clock. @pleach/core is the reference implementation; the Go implementation isn't published as a SKU yet. Read the contract or the architecture deep-dive.

Agent-native

Agents read the same surface you do.

An agent can read its own history and rewind itself from the same typed rows and checkpoints a developer reads.

  • /llms.txt indexes the docs, and every page has a Markdown twin.
  • The runtime's facet API is built to be introspected the same way.

A gateway governs the model call. Pleach governs the agent that made it.

A proxy's row is what your code claimed: project, tag, customer, sent as a header. Pleach's row is what the runtime did: turn, tool, subagent tree, written to your Postgres.

How that plays against Merge Gateway, Portkey, and the observability planes →

Ways in

Keep your loop. Add the runtime around it.

Your provider, store, and tools stay put. Start with the smallest entry point that fits; the same row travels with you if you grow into more. Compare the paths →

Core brownfield

Wrap your LLM client and database as small adapters; core drives the turn loop over them, with family-lock, replay, checkpoints, and interrupts. On the AI SDK or LangChain, the migration guides cover the graft.

createBrownfieldRuntime →From the AI SDK →From LangChain →

Greenfield

A fresh SessionRuntimeon core's defaults: sessions, graph, replay, checkpoints, interrupts.

@pleach/core →

Soon · @pleach/observe keeps your loop and writes only the audit row, to Postgres, Supabase, or an OTel callback. Not on npm yet.

FAQ

What teams ask before adopting

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, replay that names drift, time-travel checkpoints, and a typed audit row joinable to your billing and compliance schemas. See /docs/comparison.
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.
More questions

Give every model call a row you own.

Add Pleach to your agent