Local-first, content-addressed, git-style debugger for AI agent reasoning traces.
Project description
clew
git for AI reasoning. Trace, branch, replay, and diff your agent runs — locally, content-addressed, with a portable bundle format.
What you get
@t.agent/@t.spandecorators — instrument any sync, async, or generator function.- Merkle DAG of content-addressed spans — two identical inputs collapse to one id.
- Git-style branching —
clew branch alt <span>for a fork;clew difffor what changed. - Replay engine —
clew replay <trace>re-executes through a mock or recording executor. - MCP server — talk to your traces from Claude Desktop, Cursor, Cline.
- HTML reports —
clew show <id> --htmlfor a self-contained interactive page. - OTel round-trip —
clew exportto NDJSON,clew otel-importfrom any OTel stream. - Signed bundles — Ed25519 over the manifest + content hash over the spans.
- LangChain / OpenAI / Anthropic bridges — auto-instrument without changing your code.
- GitHub Action — record a trace during CI runs.
What's new in v1.1.4 (verification)
- Real end-to-end tests:
tests/test_e2e_cli.pybuilds the wheel in a fresh venv, installs it, and exercises every CLI via real subprocess invocations — including a JSON-RPC stdio roundtrip with the MCP server and a bundle-tamper test that mutates a span and confirmsclew verifyrejects it. clew mcpactually starts: the inner argparse was picking up["mcp"]fromsys.argvand bailing withunrecognized arguments: mcp. Nowmain()takes no args.- Clean error messages for invalid branch names — no more Python traceback for
clew branch "evil/branch". - 361 unit tests + 11 e2e tests; mypy --strict clean; ruff clean; mkdocs --strict clean; pip-audit clean.
What's new in v1.1.3 (polish)
- TOCTOU + symlink defense in
Store.putviaO_CREAT | O_EXCL | O_NOFOLLOW+ atomic rename. clew trace --clean-envflag to strip parent env from the subprocess.- +35 tests covering MCP, OTel SDK, langchain error paths, and store security.
mkdocs build --strictverified — site builds with no broken links.pip-auditagainst the resolved dep tree: no known vulnerabilities.- CHANGELOG dates fixed for 0.1.0, 1.0.0, 1.1.0, 1.1.1.
- 350 tests, 87% coverage, mypy --strict clean across 29 source files.
What's new in v1.1.2 (security hardening)
- TOCTOU + symlink defense in
Store.putviaO_CREAT | O_EXCL | O_NOFOLLOW+ atomic rename. clew trace --clean-envflag to strip parent env from the subprocess.- +35 tests covering MCP, OTel SDK, langchain error paths, and store security.
mkdocs build --strictverified — site builds with no broken links.pip-auditagainst the resolved dep tree: no known vulnerabilities.- CHANGELOG dates fixed for 0.1.0, 1.0.0, 1.1.0, 1.1.1.
- 350 tests, 87% coverage, mypy --strict clean across 29 source files.
What's new in v1.1.2 (security hardening)
- Bundle extraction defense-in-depth (CVE-2025-4138/4330/4517/7774). Strict member allowlist; symlinks, hard links,
..traversal, absolute paths, and NUL/newline names are refused. 256MB size cap, 1M member cap. - Span-id path traversal blocked.
Store._span_pathrejects any non-lowercase-hex id. - Branch name hardening. Control characters, leading
., and symlinks inrefs/are rejected. - HTML report XSS defense.
{and}in user-supplied values are HTML-entity-encoded. - NDJSON bomb defense. 64MB / 1M-span caps on
import_ndjsonandread_ndjson. SECURITY.mdwith threat model, hardening checklist, and supported-versions table.- 315 tests, 87% coverage, mypy --strict clean.
What's new in v1.1.1
- Polish sweep: docstrings, type hints, missing version, click 8.4+ --version fix.
- New
clew benchcommand — runs the in-process scaling benchmark with--outJSON. - Polished HTML report — stats panel, search/filter, expand-all, max depth, total time.
- Three new internals docs:
content-addressing.md,replay.md,bundle-format.md. - Full
pyproject.tomlpolish: upper-bound version pins, full classifiers, full ruff+mypy config. - 287 tests, 87% coverage,
mypy --strictclean across 29 source files.
What's new in v1.1.0
- MCP server —
clew mcpexposes 12 tools + 2 resources over the Model Context Protocol. - HTML reports —
clew show <id> --html <path>for a single self-contained interactive page. - LangChain callback handler —
from clew.integrations.langchain import ClewCallbackHandler. - GitHub Action —
.github/actions/clew-trace/action.ymlforclew tracein CI. - Real LLM example —
examples/real_llm_agent.pyworks with OpenAI, Anthropic, or a mock fallback. - mkdocs site — full documentation under
docs/withmkdocs serve.
The problem
You shipped an AI agent. It works. Mostly. When it doesn't:
- You have no way to see what it was thinking, step by step.
- You have no way to reproduce a bad run.
- You have no way to branch a reasoning trace and try a different model.
- You have no way to diff two runs to see exactly what changed.
Current tools — LangSmith, Arize, Langfuse, agentlens — observe telemetry: timing, tokens, cost. They tell you what happened but not why. And they're mostly cloud.
The clew insight
Apply git's Merkle DAG + content-addressing + branching to AI reasoning traces.
Every step is a content-addressed span. Steps form a DAG by parent pointers. Branches are named pointers into the DAG. Replay re-executes a span through any executor. Diff compares two DAGs by path. Sharing is a signed tarball.
clew is the first tool that lets you git checkout a reasoning trace and try a different path.
Quickstart (30 seconds)
# Install
uv add clew-ai
# Or: pip install clew-ai
# Initialize a store
cd my-project
clew init
# Run the example agent (or use the SDK in your own)
uv run python -c "
from clew.sdk import Tracer, SpanType
t = Tracer()
@t.agent
def run():
@t.span('plan', type=SpanType.DECISION)
def plan(): return {'query': 'what is clew'}
return plan()
run()
"
# Inspect
clew log # list traces
clew show <trace_id> # span tree
clew branch experiment <span_id> # fork at a span
clew replay <trace_id> # re-execute, get new trace
clew diff <orig> <new> # see what changed
clew share <trace_id> # export .clew.tgz
clew tui # launch the browser
Killer features
- Git-style branching. Fork a reasoning trace at any span. Two branches from the same root are independent. HEAD points to the current branch.
- Content-addressed. Every span is hashed. Same input → same id. The store dedupes automatically.
- Append-only, immutable. Spans are never mutated. Branching is just moving a pointer. Originals are sacred.
- Replay with any executor. A
MockExecutorre-uses recorded outputs (deterministic). ARecordingExecutorcalls your async function. Replay never mutates the original — it creates a new trace. - Structural diff. Match spans by their path from the root, not by id. Two replays with different models show "modified", not "added" + "removed".
- OTel-compatible. Reads and writes
gen_ai.*attributes. Instrument an OpenAI or Anthropic client in two lines. - Portable signed bundles.
clew share→.clew.tgzwith a manifest, all spans, and a SHA-256 signature. - Single binary, zero cloud. Runs on a Raspberry Pi. No accounts. No telemetry. Your traces stay on your disk.
- Textual TUI. A keyboard-driven browser for traces, branches, and diffs.
Architecture
┌──────────────────────────────────────────────────────────┐
│ your agent │
│ ┌────────────────────────────────────────────────────┐ │
│ │ @t.agent → new trace_id, root span │ │
│ │ └─ @t.span("plan", type=DECISION) │ │
│ │ └─ @t.span("search", type=TOOL) │ │
│ │ └─ @t.span("answer", type=LLM) │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────┬───────────────────────────────┘
│ append-only JSONL
▼
┌──────────────────────────────────────────────────────────┐
│ .clew/ │
│ ├── spans/<id[:2]>/<id>.jsonl ← content-addressed │
│ ├── index.sqlite ← queryable │
│ ├── refs/<name> ← branches │
│ ├── HEAD ← current branch │
│ └── manifest.json ← store metadata │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ clew CLI (single binary) │
│ log · show · branch · branches · checkout · replay · │
│ diff · share · tui · init · version │
└──────────────────────────────────────────────────────────┘
Every span is hashed via canonical-JSON → SHA-256. Spans form a DAG by parent_ids. Branches are named pointers into the DAG (just like git refs). Replay rewrites the DAG with a fresh root; the original is untouched. Diff matches spans by their path from the root (concatenation of span.name along the parent chain), so two replays with different models show what really changed.
Compare to the field
| clew | LangSmith | Arize | Langfuse | agentlens | |
|---|---|---|---|---|---|
| Local-first | ✅ | ❌ | ❌ | ❌ | ⚠️ |
| Git-style branching | ✅ | ❌ | ❌ | ❌ | ❌ |
| Content-addressed | ✅ | ❌ | ❌ | ❌ | ❌ |
| OTel-compatible | ✅ | ✅ | ✅ | ✅ | ✅ |
| Open source | ✅ | ❌ | ⚠️ | ✅ | ✅ |
| Single binary | ✅ | ❌ | ❌ | ❌ | ❌ |
| Time-travel replay | ✅ | ⚠️ | ⚠️ | ⚠️ | ✅ |
| Portable bundles | ✅ | ❌ | ❌ | ❌ | ❌ |
| Python SDK | ✅ | ✅ | ✅ | ✅ | ✅ |
| TUI | ✅ | ❌ | ❌ | ❌ | ❌ |
clew is the only tool that combines local-first + branching + content-addressing + portable bundles in a single open-source binary.
SDK
from clew.sdk import Tracer, SpanType
t = Tracer() # writes to ./.clew
@t.agent
def my_agent(question: str) -> str:
@t.span("plan", type=SpanType.DECISION)
def plan() -> dict:
return {"query": question}
@t.span("search", type=SpanType.TOOL)
def search(plan: dict) -> list[str]:
return ["result 1", "result 2"]
@t.span("answer", type=SpanType.LLM)
def answer(plan: dict, hits: list[str]) -> str:
return f"{plan['query']}: {' '.join(hits)}"
p = plan()
s = search(p)
return answer(p, s)
my_agent("What is clew?")
OpenAI / Anthropic auto-instrumentation:
from openai import OpenAI
from clew.sdk import instrument_openai
client = OpenAI()
instrument_openai(client)
# Every chat.completions.create call now writes a span to .clew
client.chat.completions.create(model="gpt-4o", messages=[...])
CLI
clew init [path] Initialize a .clew/ store
clew log [--json] List traces
clew show <trace_id> [--json] Show span tree
clew branch <name> [<span_id>] Create a branch at a span
clew branches List branches
clew checkout <name> Switch current branch
clew replay <trace_id> [--from <span>] [--executor mock|recording]
clew diff <trace_a> <trace_b> Structural diff
clew share <trace_id> [--out PATH] Export signed bundle
clew tui Launch the interactive TUI
clew version Print version
Every command has --help and exits 0 on success.
Why clew wins
The OSS winner thesis is straightforward:
- Local-first is the future. GDPR, data residency, and "we don't want our prompts on a vendor's server" are not edge cases.
- Standards need a foothold. MCP just won the protocol war. There is no equivalent for trace format. clew is the candidate — open, OTel-friendly, local-first, simple to vendor.
- Network effects. The more agents use clew, the more valuable it becomes: shared trace format, shared diffs, shared bundles. SQLite won the embedded DB war not by being the best but by being everywhere.
- Tooling compounds. A TUI today, a web viewer tomorrow, a hosted diff service the day after. Each layer pulls in the next wave of users.
clew is not just a tool. It's a candidate for the trace format that AI agents will speak. Local-first, content-addressed, OTel-friendly. The same role that SQLite plays for databases or git plays for source control.
Contributing
PRs welcome. The bar is high: the architecture is small and every addition must justify its weight. Read ARCHITECTURE.md first.
Run the tests:
uv run pytest
Run the linter:
uv run ruff check .
uv run mypy --strict src/
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
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 clew_ai-1.1.4.tar.gz.
File metadata
- Download URL: clew_ai-1.1.4.tar.gz
- Upload date:
- Size: 171.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c10fa75a831ddac790b6ce8633ce2d93cbc3f6b6bf4064f3a5f8b29d58df2a79
|
|
| MD5 |
4d6c3dde60e073175b9a41bc694f464b
|
|
| BLAKE2b-256 |
ce653910e7c88adbb4bda6badbd3e03280975236ad1c975d1a77a30ec87223ab
|
File details
Details for the file clew_ai-1.1.4-py3-none-any.whl.
File metadata
- Download URL: clew_ai-1.1.4-py3-none-any.whl
- Upload date:
- Size: 88.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5ba17532d9f4012e24d48e19eb014ce774ac4ba2f2045d65b76235f9311b283a
|
|
| MD5 |
5be77744a6df31b02d8b039ff7d19c2e
|
|
| BLAKE2b-256 |
5a5eb3472e3024c3225a88483c1c08975a38878f454e43ef78db96d9150b22ba
|