Offline eval gate for
Claude Code agentic pipelines.
Run prompt baskets, reduce the recorded transcripts into one canonical
action graph, and gate regressions statistically in CI.
Β Β Β Β
Changing a prompt, a skill, or an MCP tool changes your agent's behavior β but two runs
of the same task are two samples from a distribution, so eyeballing one diff cannot
tell a real regression from sampling noise. Catacomb closes that gap offline:
catacomb bench runs a declarative basket of tasks Γ variants Γ reps, records each
cell's Claude Code transcripts (including every subagent's sub-transcript) as a
secret-redacted evidence directory, and catacomb regress reduces both groups to
canonical execution graphs β prompts, turns, tool calls, MCP calls, subagents β aligns
them by step and phase keys, and returns a statistical verdict with a CI-consumable
exit code. No daemon, no service, no network: the whole loop is plain local files.
It is domain- and evaluation-agnostic: it compares deterministic observables (presence,
errors, duration, cost, tokens) and leaves a per-node annotation slot so external
scorers (such as the shipped DeepEval bridge) can gate on quality through the same
mechanism.
β¨ Highlights
A gate, not a vibe check.regress compares repeated runs with one-sided Wilson bounds and IQR noise bands (ADR-0022), reports regression/notable/insufficient per finding, and maps the verdict to the exit code your CI already understands.
An outline, not a hairball. Each run reduces to a collapsible tree β session β prompt β turn β tool β with each subagent nested under the turn that spawned it, rebuilt deterministically from the transcripts.
Stable identity across runs. Step keys hash each call's redacted, salient input; phase keys name checkpoint windows the agent marks via the shipped catacomb mcp marker tool β so comparisons survive prompt churn.
Redacted evidence you can share. Transcript copies pass through secret redaction on write (ADR-0024); step keys and payload hashes are computed post-redaction, so artifacts never hash or encode pre-redaction bytes. The denylist is best-effort β it shrinks the blast radius of a leaked secret rather than guaranteeing zero leakage.
Longitudinal memory. Pin golden groups as named, version-stamped baselines; --record appends every comparison to an append-only history that trends replays.
Bring your own viewer. Catacomb ships no UI: watching runs live is delegated to a vendor substrate fed by that vendor's first-party Claude Code plugin β Phoenix is the recommended one (ADR-0026). Catacomb stays the offline capture, diff, and regression-gate layer.
Status: the offline gate is implemented end-to-end β the bench runner, redacted evidence capture, the reconciling reducer, step/phase keys, the statistical gate, named baselines with version stamps, recorded history (trends), external score gating, and the DeepEval bridge β built and maintained under a 100%-test-coverage, TDD gate. The daemon, live ingestion, and exporters were removed per ADR-0026.
π¦ Installation
Homebrew (macOS)
brew tap realkarych/tap
brew trust realkarych/tap # newer Homebrew requires trusting third-party taps
brew install --cask catacomb # first install
brew upgrade --cask catacomb # later updates
Migrating from the old formula: brew uninstall catacomb && brew install --cask catacomb.
Download the pre-built archive from the
Releases page, unpack it,
and add the binary to your PATH.
On Windows, you may need Unblock-File .\catacomb.exe before first run.
Go install (Go β₯ 1.26)
go install github.com/realkarych/catacomb/cmd/catacomb@latest
# make sure $GOBIN (default ~/go/bin) is on your PATH
Or build locally with make build.
β Once installed, verify it works
β― catacomb --help
Catacomb is an offline eval gate for Claude Code agentic pipelines. It runs
prompt baskets, reduces the recorded transcripts into a canonical execution
graph, derives step and phase keys, aggregates metrics, and gates regressions
against saved baselines.
Common recipes:
Run a basket and record evidence:
catacomb bench <basket.yaml>
Gate a candidate against a baseline:
catacomb regress --baseline label:variant=main --candidate label:variant=pr
Build a graph from a single recorded transcript:
catacomb replay <session>.jsonl
Run 'catacomb <command> --help' for details on any command.
Usage:
catacomb [command]
Available Commands:
baseline Manage named baselines for regression comparison
bench Run a benchmark basket: expand cells, execute, mark phases, record a manifest
completion Generate the autocompletion script for the specified shell
diff Diff two session transcripts by step_key
export Export a transcript or evidence dir as a JSONL graph snapshot
help Help about any command
mcp Run the catacomb MCP stdio server (exposes the mark checkpoint tool)
regress Compare a candidate run group against a baseline
replay Build a graph from a recorded Claude Code transcript
subgraph Extract the execution subgraph of a checkpoint phase
trends Show the recorded regression history for a baseline
version Print the version
π Quickstart
Declare a basket β the matrix of tasks Γ variants Γ reps you want to compare:
Every cell runs as a plain local process; catacomb resolves its transcripts from
~/.claude/projects, verifies declared checkpoints, and writes a secret-redacted
evidence directory under ~/.catacomb/runs/<run-id>/ plus a manifest line.
Exit code 0 is a pass, 1 is a regression, 2 is an operational error β drop it
straight into CI. Then go deeper:
catacomb baseline set golden --label basket=checkout,variant=main # pin the golden group
catacomb regress --baseline name:golden \
--candidate label:basket=checkout,variant=candidate --record # gate + record history
catacomb trends golden # replay the drift
catacomb diff run-a.jsonl run-b.jsonl # step-level diff
catacomb export ~/.catacomb/runs/<run-id> --out run.jsonl # JSONL graph snapshot
See the user guide for the full loop β checkpoints, external
quality scores, and the DeepEval bridge.
π Privacy
Catacomb runs no daemon and opens no sockets β everything is local files. The
transcript copies it stores as evidence pass through secret redaction on the write path
(ADR-0024) β a denylist that reduces the blast radius of leaked secrets by replacing
API keys, tokens, private keys, connection strings, and high-entropy values with typed
markers before they touch disk. It is best-effort, not a guarantee: no denylist catches
every secret β the classes it deliberately does not catch are listed under
Known residuals. Graphs carry
a content hash per node, computed after redaction; step keys likewise hash only
redacted content. The SQLite store holds baselines and regression reports β never
transcripts or payloads.