langgraph-ledger
Full, tamper-evident traceability for LangGraph agents — a port of DeepSeek Harness (dsh)'s traceability design: content-addressed labels on every tool call, a hash-chained append-only event ledger, the execution DAG, replay, crash recovery, and first-class fork/rollback.
Why
LangGraph already checkpoints state and can time-travel. What it does not give you is an audit-grade record: were these events edited after the fact? Which tool call exactly — same name, or same content? What did the run look like as a graph, and can I fork from any point of it?
This plugin ports the traceability design of DeepSeek Harness (dsh) — append-only session log as the single source of truth, format versioning, fork lineage — onto LangGraph's checkpointer contract, and adds what neither has out of the box: content hashing.
The combination is the point:
| Capability | Mechanism |
|---|---|
| Tool-call identity | content-addressed label tl_<hash> — same (name, input) ⇒ same label |
| Tamper evidence | every event hash-chained to its predecessor (merkle-style) |
| Execution structure | DAG over chain + call/result + checkpoint-parent edges |
| Rollback | time_travel_config (native fork) and fork_thread (dsh-style seeded fork with lineage event) |
| Failure analysis | error timeline, exact-repeat loop detection (falls out of the labels) |
| Verification | verify_log re-checks the chain; verify_thread re-hashes stored checkpoints against logged claims |
Differentiation (as of 2026-08)
Honest snapshot of the neighborhood — see POSITIONING for details:
| Project | Hash chain | Execution DAG | State rollback/fork | LangGraph-native |
|---|---|---|---|---|
| langgraph-ledger | ✅ | ✅ | ✅ | ✅ (checkpointer drop-in) |
| LangSmith / Langfuse / AgentOps | ✗ (hosted observability) | trace view | ✗ | SDK |
| CONTINUUM | ✅ | ✗ | crash recovery focus | ✗ (MCP server) |
| burnout / VeritasAgent / memtrail | ✅ | ✗ | ✗ | partial |
| langgraph checkpointers (redis/mysql/…) | ✗ | parent links only | time-travel only | ✅ |
Nobody else ships hash-labels + DAG + rollback as one LangGraph-native unit. That is the slot this project occupies.
Install
pip install langgraph-ledger
Quickstart
from langgraph.checkpoint.memory import InMemorySaver
from langgraph_ledger import TracingCheckpointSaver, DshTraceCallbackHandler
saver = TracingCheckpointSaver(InMemorySaver(), trace_root="./traces")
graph = builder.compile(checkpointer=saver) # your graph, unchanged
graph.invoke(input, config={
"configurable": {"thread_id": "run-42"},
"callbacks": [DshTraceCallbackHandler()], # LLM/tool/node events
})
Every run now leaves ./traces/run-42.jsonl — one hash-chained JSON event per line:
{"v":0,"seq":7,"ts":"…","kind":"tool/call","payload":{"label":"tl_9f2e…","name":"search",…},"id":"…","prev":"…"}
{"v":0,"seq":8,"ts":"…","kind":"state/snapshot","payload":{"checkpoint_id":"…","label":"cp_2c01…","parent_checkpoint_id":"…","checkpoint_sha256":"…"},"id":"…","prev":"…"}
Rollback & fork
from langgraph_ledger import time_travel_config, fork_thread
# resume/fork from any recorded checkpoint (LangGraph-native time travel)
cfg = time_travel_config("run-42", checkpoint_id="<past-id>")
graph.update_state(cfg, {"count": 100}) # forks from that point
# dsh-style: a new thread seeded with the ancestry up to a checkpoint
new_tid = fork_thread(saver, "run-42", at_checkpoint_id="<past-id>")
Audit
python -m langgraph_ledger verify traces/run-42.jsonl # hash chain intact?
python -m langgraph_ledger analyze traces/run-42.jsonl # errors, loops, timeline
python -m langgraph_ledger dag traces/run-42.jsonl --mermaid
python -m langgraph_ledger repair traces/ # close crash-orphaned runs
python -m langgraph_ledger replay traces/run-42.jsonl # rebuild the message timeline
from langgraph_ledger import verify_thread
report = verify_thread(saver, "traces/run-42.jsonl") # stored state == logged claim?
assert report["ok"]
Crash recovery & replay
If the process dies mid-run, the log ends with an unclosed run/start.
verify flags it as an open run; repair appends an honest
run/end {status: "interrupted"} through the hash chain (idempotent, never
rewrites history). replay_messages() rebuilds the conversation timeline from
the log — digests by default, full text when the handler was created with
record_full=True. For fail-closed operation (a run that cannot record must
not proceed), use TraceRecorder(..., strict=True).
Design mapping from DeepSeek Harness
A design study, not a port of code (dsh is TypeScript/Node; this is Python/LangGraph):
| dsh concept | here |
|---|---|
SessionEvent append-only log, single source of truth |
one hash-chained JSONL per thread |
SESSION_FORMAT_VERSION + ignorable marker |
v field; unknown kinds counted, not rejected |
| turn / step hierarchy | run/* / node/* events |
parentSession + seedLength fork lineage |
fork event payload |
sourceEventSeqs provenance |
DAG edges: chain, call→result, snapshot→parent |
| Model-Visible ⟺ Logged invariant | payload digests (sha256 + head); strict=True enforces fail-closed recording |
crash-orphaned turns closed as interrupted on reload |
repair — appends run/end {status: interrupted} through the hash chain |
deriveMessages (log → conversation) |
replay_messages — digests or full text (record_full=True) |
| fail-closed invariants | append-time strict JSON validation; verify_* refuse on mismatch |
Deliberate deviations: prompt/response full text is not stored by default (digest only, opt-in via record_full=True); dsh's byte-level replay and compaction are out of scope.
What it is NOT
- Not a hosted observability platform — the log is a local file you own.
- Not byte-level replay of model streams — it is for error localization, audit and state rollback.
- Rollback restores agent state; side effects your tools made in the world are yours to undo (pair tool calls with your own preimages if you need that).
Development
pip install -e ".[test]"
pytest tests/
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file langgraph_ledger-0.2.0.tar.gz.
File metadata
- Download URL: langgraph_ledger-0.2.0.tar.gz
- Upload date:
- Size: 33.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6448c26ac072b38b7e672000eb56ceefa8d8fcb2a0652501c0a546a2d844a454
|
|
| MD5 |
38e4260e15d467e13cd1f05b6f25ddea
|
|
| BLAKE2b-256 |
12ed966fbeb130f82728d43f2b7f83f7bbdd40401b1bcf4308353fb951579e1b
|
Provenance
The following attestation bundles were made for langgraph_ledger-0.2.0.tar.gz:
Publisher:
publish.yml on hty8870/langgraph-ledger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langgraph_ledger-0.2.0.tar.gz -
Subject digest:
6448c26ac072b38b7e672000eb56ceefa8d8fcb2a0652501c0a546a2d844a454 - Sigstore transparency entry: 2597280344
- Sigstore integration time:
-
Permalink:
hty8870/langgraph-ledger@99f09458740f9b1fa745af04242a9306eb394e1a -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/hty8870
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@99f09458740f9b1fa745af04242a9306eb394e1a -
Trigger Event:
release
-
Statement type:
File details
Details for the file langgraph_ledger-0.2.0-py3-none-any.whl.
File metadata
- Download URL: langgraph_ledger-0.2.0-py3-none-any.whl
- Upload date:
- Size: 31.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ab16ff8499f9b53e2f980389ebae5b0eeb35929e70056fb851b13259f79d04c6
|
|
| MD5 |
b7b80caa15afb38579615f0b10490b98
|
|
| BLAKE2b-256 |
da0c99b6680daa9e6db32353ae44a9940893ed8e072ab52988a2cc666be99f54
|
Provenance
The following attestation bundles were made for langgraph_ledger-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on hty8870/langgraph-ledger
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
langgraph_ledger-0.2.0-py3-none-any.whl -
Subject digest:
ab16ff8499f9b53e2f980389ebae5b0eeb35929e70056fb851b13259f79d04c6 - Sigstore transparency entry: 2597280687
- Sigstore integration time:
-
Permalink:
hty8870/langgraph-ledger@99f09458740f9b1fa745af04242a9306eb394e1a -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/hty8870
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@99f09458740f9b1fa745af04242a9306eb394e1a -
Trigger Event:
release
-
Statement type: