A git-backed, append-only, machine-partitioned CloudEvents 1.0 event ledger (JSONL) — library, CLI, MCP server, and read-only web visualizer. Stdlib-only core.
Project description
evledger
A git-backed, append-only, machine-partitioned CloudEvents 1.0 event ledger (JSONL). Pure-stdlib core; optional CLI, MCP server, and a zero-build web visualizer.
- Append-only, never compress — events are immutable; the ledger is built for long-lived storage and replay.
- Machine-partitioned — events live at
<root>/<machine>/<YYYY-MM>.jsonl, with a monotonic per-machineseqassigned at append under a file lock. - CloudEvents 1.0 envelope — every record is a valid CloudEvent
(
specversion/id/source/type/time+machine/seq); thedataattribute is an open channel for arbitrary JSON. - Optional per-type schemas — register a
dataschema and validation is advisory (a slightly-off payload is still logged, never dropped). - Stdlib-only core — the library and the visualizer have zero runtime
dependencies. The CLI adds
click; the MCP server adds themcpSDK; both are optional extras.
Install
pip install evledger # core library (stdlib only) + the `evledger` CLI*
pip install "evledger[mcp]" # + the MCP server (`evledger-mcp`)
pip install "evledger[dev]" # + pytest / ruff / mypy for development
* the evledger console script needs click; pip install "evledger[cli]"
pulls it explicitly (it's also included by [dev]).
Library
The core is pure functions over an immutable event stream — easy to test, no hidden I/O beyond the store.
from evledger import LedgerStore, Query, new_event, query_events
from evledger.stats import counts_by_type, pair_events
store = LedgerStore(root="./ledger")
# Append — `seq` is assigned per-machine, monotonic, under a lock.
store.append(new_event(
source="/laptop/app", type="com.example.job.start",
machine="laptop", time="2026-05-30T10:00:00Z", data={"job": "sync"},
))
events = store.read_all().events # ordered by (time, seq)
starts = query_events(events, Query(type="com.example.job.*")) # glob match
counts = counts_by_type(events) # {type: n}
durations = pair_events(events).durations # pair *.start with *.end
See examples/quickstart.py for a runnable end-to-end
walkthrough (python examples/quickstart.py).
Layers
| Module | What it gives you |
|---|---|
evledger.schema |
LedgerEvent, new_event(...), parse_time(...) — the CloudEvents envelope |
evledger.store |
LedgerStore — append (locked, seq-assigning) + read_all() / iter_events() |
evledger.query |
Query + query_events(...) — filter by source/type(glob)/machine/time window |
evledger.stats |
counts_by_type, event_rate, pair_events (→ *.start/*.end durations) |
evledger.registry |
TypeRegistry — register per-type data schemas, validate advisorily |
evledger.digest |
deterministic rollup + rule-based anomaly flags over a window |
evledger.transcript / evledger.reconstruct |
optional model-assisted audit layers (stdlib loader + injectable model client) |
CLI
The evledger command exposes the ledger over a small group:
evledger log --source /laptop/app --type com.example.job.start \
--machine laptop --data '{"job":"sync"}'
evledger show --type 'com.example.*' --since 2026-05-01 --limit 50
evledger stats --by type --pair # counts + paired *.start/*.end durations
evledger digest --since 2026-05-01 # deterministic rollup + anomaly flags
evledger serve # read-only web visualizer (localhost)
The ledger root resolves via --ledger-root → $CLAUDE_LEDGER_ROOT →
<cwd>/ledger.
MCP server
The same log / query / stats operations are exposed to any MCP client
(Claude Desktop, other agents) over stdio. The MCP SDK is imported lazily, so
the core and CLI stay dependency-free.
# zero-install via uvx:
uvx --from "evledger[mcp]" evledger-mcp --ledger-root ~/.local/share/evledger
# or installed:
pipx install "evledger[mcp]"
evledger-mcp --ledger-root ~/.local/share/evledger
The ledger root is server-side configuration (captured at launch via
--ledger-root → $CLAUDE_LEDGER_ROOT → <cwd>/ledger), not a tool argument.
Web visualizer
evledger serve starts a read-only, zero-build single-page app (stdlib
http.server, no framework) bound to localhost: a filter panel, a
wheel-zoom / drag-pan canvas timeline laned by source or type, a zoomable flame
graph of derived *.start/*.end spans, and a sortable event table. JSON APIs
(/api/events, /api/spans, /api/meta) back it. It never writes the ledger.
Storage layout
<root>/
<machine-a>/
2026-05.jsonl # one CloudEvent per line, append-only
2026-06.jsonl
<machine-b>/
2026-05.jsonl
Each line is a complete CloudEvent. Files are never rewritten or compressed — roll-up/aggregation happens at read time, so the raw record is preserved for the life of the ledger.
Development
pip install -e ".[dev,mcp]"
ruff check .
mypy evledger
pytest -q
CI (GitHub Actions) runs ruff + mypy + pytest across Python 3.10–3.13.
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 evledger-0.1.0.tar.gz.
File metadata
- Download URL: evledger-0.1.0.tar.gz
- Upload date:
- Size: 117.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
efcfe48dabc77989cffb89d9de95ec4834485bf910848d2bca3339c3e077300a
|
|
| MD5 |
d071267bcdd089b09a9cdfca992c0fed
|
|
| BLAKE2b-256 |
ecf2a55a9e185e3f7d03b07f8fae76c3728da44eeafbf278091d19dbb765f47e
|
File details
Details for the file evledger-0.1.0-py3-none-any.whl.
File metadata
- Download URL: evledger-0.1.0-py3-none-any.whl
- Upload date:
- Size: 95.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
237888422ef40f8bd9b82820972985222e691da71b37c379176c9e9da0b04bff
|
|
| MD5 |
ae0dae799cd9d3e8e0a330923f77edb7
|
|
| BLAKE2b-256 |
7b8b24132fb55c32348a5cfc6f97b55552402aa82497932090e45cb0841536f2
|