|
| 1 | +# Agent Ledger |
| 2 | + |
| 3 | +Agent Ledger is a framework-neutral event ledger for durable agent sessions. It records agent |
| 4 | +steps before model and tool execution, keeps causal links across distributed agent runs, and gives |
| 5 | +framework adapters enough facts to rebuild their own run context after a restart. |
| 6 | + |
| 7 | +The ledger is the source of truth for **what happened**. Recovery remains owned by the framework |
| 8 | +integration that understands its checkpoint and `RunContext` types. |
| 9 | + |
| 10 | +## Why another event log? |
| 11 | + |
| 12 | +Traditional logs and traces explain service execution. Agent Ledger adds agent-native invariants: |
| 13 | + |
| 14 | +- `Session` groups one end-to-end task across processes and agents. |
| 15 | +- `Run` is the optimistic-concurrency stream written by one agent loop. |
| 16 | +- `Step` is a logical unit; `Attempt` is one physical model or tool invocation. |
| 17 | +- requested events are committed before external calls, so interrupted calls remain visible. |
| 18 | +- `parent_run_id` and `caused_by_event_id` form a causal DAG without relying on timestamps. |
| 19 | +- trajectories such as ATIF are projections, not the durable source of truth. |
| 20 | + |
| 21 | +## Quick start |
| 22 | + |
| 23 | +```python |
| 24 | +from agent_ledger import Actor, SessionRecorder |
| 25 | +from agent_ledger.stores.memory import MemoryEventStore |
| 26 | + |
| 27 | +store = MemoryEventStore() |
| 28 | +recorder = SessionRecorder( |
| 29 | + store=store, |
| 30 | + session_id="session-1", |
| 31 | + run_id="run-1", |
| 32 | + actor=Actor(type="agent", id="researcher"), |
| 33 | +) |
| 34 | + |
| 35 | +await recorder.start_run(payload={"task": "summarize"}) |
| 36 | +attempt = await recorder.before_model_call( |
| 37 | + step_id="step-1", |
| 38 | + payload={"model": "example-model", "messages": [{"role": "user", "content": "Hi"}]}, |
| 39 | +) |
| 40 | + |
| 41 | +# The real model call starts only after model.requested is durably appended. |
| 42 | +response = await model.generate() |
| 43 | +await recorder.model_completed(attempt, payload={"message": response}) |
| 44 | +``` |
| 45 | + |
| 46 | +If the process stops after `before_model_call`, inspection reports an unresolved attempt. An adapter |
| 47 | +can then ask the provider for a result, require human confirmation, or retry with a new |
| 48 | +`attempt_id`; the generic library never silently repeats an external side effect. |
| 49 | + |
| 50 | +## Stores |
| 51 | + |
| 52 | +`EventStore` has three implementations: |
| 53 | + |
| 54 | +- `MemoryEventStore`: process-local reference implementation and test double. |
| 55 | +- `RedisEventStore`: atomic append through Lua, with per-session cluster key co-location. |
| 56 | +- `SqlEventStore`: one SQLAlchemy 2.x implementation for SQLite, MySQL, and PostgreSQL. |
| 57 | + |
| 58 | +Redis and SQL clients are supplied by the application so pool size, connection timeout, and |
| 59 | +deployment-specific durability are explicit. Install optional dependencies with |
| 60 | +`agent-ledger[redis]`, `agent-ledger[sql]`, `agent-ledger[mysql]`, or |
| 61 | +`agent-ledger[postgres]`. |
| 62 | + |
| 63 | +## Design boundaries |
| 64 | + |
| 65 | +- No collector or mandatory network service in v1. |
| 66 | +- No generic cross-framework `RunContext` serializer. |
| 67 | +- No automatic replay of an unresolved tool side effect. |
| 68 | +- No exactly-once claim. Appends are atomic and idempotent; external calls are not transactional |
| 69 | + with the ledger. |
| 70 | +- No global ordering claim. `commit_cursor` orders one session's stored events for display and |
| 71 | + pagination; causal links define execution relationships. |
| 72 | + |
| 73 | +See [RFC 0001](spec/rfcs/0001-agent-ledger.md) for the contract and |
| 74 | +[the plain-loop example](examples/plain_loop.py) for adapter-owned recovery. |
| 75 | + |
| 76 | +## Development |
| 77 | + |
| 78 | +```bash |
| 79 | +uv sync --all-extras |
| 80 | +make fix |
| 81 | +make lint |
| 82 | +make test |
| 83 | +``` |
| 84 | + |
0 commit comments