epitaph
Every agent memory accumulates successes and preferences. tombstone accumulates refusals: it records rejected patches, rolled-back approaches, and abandoned paths as structured tombstones inside your repo — so the next agent checks before walking the same dead end. "This approach was buried here, in this module, on August 12."
Status: v0.1 (deterministic core). Distribution name is epitaph (decided 2026-09-05; the working name tombstone collided with Android crash dumps / Cassandra). Import module and CLI are epitaph too; records are still "tombstones" living in .tombstones/.
The problem: failure knowledge evaporates three times
- git history —
Revert "Add Redis lock"records what was reverted but destroys why, under what conditions, and what the alternative was. - Review — PR closes and objection threads bury rejection reasons in unsearchable form.
- Sessions — the moment an agent declares "I'll try a different approach" lives only in the transcript, and dies with the session.
The result is the worst item on any inter-session yield audit: re-discovering the same failure. Human teams solved this with brains and folklore ("we tried that in August, it went badly"). In the agent era, that brain is not stored anywhere.
tombstone is the repo-scoped attempt ledger: one JSON file per rejected attempt, committed to the repo, queryable by machine before the retry happens.
Quickstart
# install (from a checkout; PyPI release planned)
pip install .
cd path/to/your/repo
epitaph init
epitaph add \
--attempt "Redis-based distributed lock to serialize session writes" \
--reason "Race window was not closed; retry storm under load" \
--scope src/session/lock.py src/session/manager.py \
--evidence "PR #412" "revert 9f2e1a" \
--retry-when "once a fencing token sits in front of the lock"
epitaph list
epitaph check "redis lock for sessions" # what an agent runs BEFORE retrying
epitaph approve ts-20260812-a3f2 # one human line: candidate -> approved
Automatic detection
detect scans git log for revert commits (subject starting with Revert " and/or a body containing This reverts commit <sha>) and drafts candidate-confidence tombstones linking the evidence:
epitaph detect
# created ts-20260902-1b7e (candidate)
# 1 revert commit(s) scanned, 1 created, 0 already recorded
Or install a post-commit hook that runs detect automatically (it can never fail your commit):
epitaph install-hook
detect is incremental and idempotent: it remembers the last scanned commit in .tombstones/.cursor (so the post-commit hook only pays for new history), and the tombstone id is derived from the revert sha, so even a forced full rescan (tombstone detect --full) never duplicates. If a history rewrite strands the cursor, detect falls back to a full scan automatically.
Record schema (one JSON file per tombstone in .tombstones/)
{
"id": "ts-20260812-a3f2",
"attempt": "Redis-based distributed lock to serialize session writes",
"scope": ["src/session/lock.py", "src/session/manager.py"],
"rejected_at": "2026-08-12",
"rejected_by": "human-review",
"reason": "Race window was not actually closed; 3 tests went flaky in CI.",
"evidence": ["PR #412", "revert 9f2e1a"],
"retry_when": "Revisit once a fencing token sits in front of the lock.",
"status": "active",
"confidence": "approved"
}
id—ts-YYYYMMDD-<4 hex>rejected_by—human-review|ci|agent-gaveupstatus—active|stale(target code gone) |overturned(a retry succeeded — kept on purpose; honest failure data includes its own refutations)confidence—approved(human-confirmed) |candidate(not yet approved, shown with lower confidence on query)
One file per tombstone minimizes git merge conflicts and makes partial adoption easy. .tombstones/ is meant to be committed — team sharing is the core value. Working solo? Add it to your personal gitignore.
See examples/ for full records.
MCP server (Claude Code and any MCP client)
Zero-dependency stdio MCP server (JSON-RPC 2.0, no mcp package needed):
python -m epitaph.mcp # repo resolved from cwd, walking up
python -m epitaph.mcp --repo /path/to/repo
Two tools:
check_nogo(attempt?, files?)— match an intended approach / target files against the ledger.recent_tombstones(scope?, limit?)— browse the ledger, newest first.
Claude Code config (.mcp.json in the project root, or ~/.claude.json):
{
"mcpServers": {
"epitaph": {
"command": "python3",
"args": ["-m", "epitaph.mcp"],
"env": { "EPITAPH_REPO": "/absolute/path/to/your/repo" }
}
}
}
Or with the CLI: claude mcp add epitaph -- python3 -m epitaph.mcp (run from the repo root; the server resolves the repo from its working directory if EPITAPH_REPO is unset).
The server is a plain stdio JSON-RPC process — any MCP-capable client works. Cursor (.cursor/mcp.json):
{
"mcpServers": {
"epitaph": {
"command": "python3",
"args": ["-m", "epitaph.mcp"],
"env": { "EPITAPH_REPO": "/absolute/path/to/your/repo" }
}
}
}
Codex (~/.codex/config.toml):
[mcp_servers.epitaph]
command = "python3"
args = ["-m", "epitaph.mcp"]
env = { EPITAPH_REPO = "/absolute/path/to/your/repo" }
Clients without MCP support can use the CLI instead — tombstone check "..." --file path produces the same match report through the same renderer.
Recommended one-line rule for AGENTS.md / CLAUDE.md — inject it with tombstone snippets (creates AGENTS.md if absent, appends to CLAUDE.md only when it already exists, idempotent):
Before implementing an approach, call check_nogo with your planned approach and target
files. On a match, read `reason` and `retry_when`: either address `retry_when` or pick
a different path. Tombstones are records of past rejections, not bans.
CLI reference
| Command | Purpose |
|---|---|
tombstone init |
Create .tombstones/ in the target repo (--snippets also injects the rule below) |
tombstone snippets |
Inject the recommended check_nogo rule into AGENTS.md (and CLAUDE.md if present) — idempotent |
tombstone add --attempt T --reason R [--scope P...] [--evidence R...] [--rejected-by WHO] [--retry-when W] [--date YYYY-MM-DD] [--confidence C] [--status S] |
Record a rejection (defaults to candidate) |
tombstone approve <id> |
Promote a tombstone to approved (one human line) |
tombstone overturn <id> --reason R |
A retry succeeded — keep the refutation on record |
tombstone list [--status S] [--scope P] |
List tombstones, newest first |
tombstone show <id> |
Print one tombstone in full |
tombstone check [TEXT] [--file P]... |
Query by attempt text and/or files before retrying |
tombstone detect [--full] |
Scan git history for reverts, draft candidate tombstones (incremental via .cursor) |
tombstone install-hook |
Install a post-commit hook that runs detect |
Global: --repo PATH (default: cwd, walking up), --version.
check uses deterministic normalized substring and token-overlap matching (0.5 containment for multi-token queries, exact token for single tokens) against attempt + reason — scope paths are full of common words and only match via --file — and also matches queried files against scope anchors in both directions. Every hit prints its confidence and why it matched; on a repo without a ledger it answers softly (nothing recorded here yet) instead of erroring, matching the MCP behavior agents see.
Workflow: detect -> draft -> approve -> query
| Stage | Owner | Notes |
|---|---|---|
| Detect | deterministic rules (no LLM) | git revert commits today; never-merged PR closes, session give-up transitions, and CI-failure branch abandonment are on the roadmap |
| Draft | optional LLM (v0.2) | v0.1 works fully without drafting |
| Approve | one human line | tombstone approve ts-.... An agent may never judge on its own — a tombstone is testimony, not a verdict |
| Query | MCP tools + CLI | check_nogo / recent_tombstones / tombstone check |
Principles
- Fully local by default — tombstones live only in your repo and on your machine. Zero network calls at runtime.
- Tombstones do not assert — they record "this was rejected on this date for this reason"; they never claim "this is impossible".
retry_when(the refutation condition) is expected on every record. - Candidates allowed — unapproved
candidaterecords are kept and shown with lower confidence; approval friction is one line. - Deterministic core (v0.1) — detection and matching are plain rules; LLM drafting is a v0.2 option, never a dependency.
- Human approval required — only
tombstone approve(a human action) reachesapprovedconfidence.
Differentiation
| Adjacent tool | How tombstone differs |
|---|---|
| deadends.dev | Global, error-signature-centric ("don't sudo pip when you hit CUDA OOM") — not repo-scoped, community-curated, manually reported. tombstone: repo-scoped, attempt-level (the approach, not the error message), auto-detected, human-approved, expiring. Not competitors — two layers: global error signatures live there, project-context rejections live here. v1.x plans canon-ID cross-links so the layers interoperate. |
| Mem0 / Letta / CLAUDE.md memory | Positive memory of successes and preferences. Negative memory needs its own data structure and its own query pattern (pre-flight lookup in the planning stage, not mid-chat recall). |
| ADR (Architecture Decision Record) | Manual documents of human design decisions. tombstone records rejected attempts, with automatic detection and machine querying built in. |
.cursorrules negative rules |
Context-free commands ("don't do X"). A tombstone is a record with evidence, conditions, and an expiry path. |
| yield-audit M9 (repeated inter-session knowledge cost) | Measurement (the size of the repeated tax) ↔ tombstone (removal of its single most expensive line item). They sell each other. |
Privacy
Tombstone records can contain sensitive reasons. Everything stays local: no telemetry, no network calls, no cloud sync. The records are plain JSON files inside your repo — audit them with git grep before sharing, the same way you audit any other committed file.
Limitations (v0.1)
- Revert detection is heuristic (subject/body patterns); it drafts candidates, humans confirm.
- Matching is lexical — synonyms and paraphrases may need the v0.2 embedding option.
- No stale audit yet (v0.3: symbol-graph check that the anchored code still exists).
detectdefaultsrejected_bytohuman-reviewbecause git alone cannot tell who drove the revert; correct it during review.
License
Apache-2.0. See LICENSE.
Metadata
Release files for epitaph 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| epitaph-0.1.0.tar.gz | 36.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| epitaph-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 64.6 kB
Release files / epitaph-0.1.0.tar.gz
| Download URL | epitaph-0.1.0.tar.gz |
|---|---|
| Size | 36.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b676e54d1950c1955c0be7082d5e6492b3320488121499b5e986c5e695779e67
|
|
BLAKE2b-256 checksum How to use checksums |
5d100f1b05966c51851e9cdb5076e95aa4233f9933949bb4d3a249bcd6166262
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.
Transparency logRelease files / epitaph-0.1.0-py3-none-any.whl
| Download URL | epitaph-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
eed5a4157ce9f05869d57d3a0ad8a24f52590bae8d16b06a4f66169a310f2e88
|
|
BLAKE2b-256 checksum How to use checksums |
a9160c18c133c1252b8928b284b66dd77356cf9c61a00cfe6ad5886d9b536368
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 5, 2026.
Transparency log