Agent Ledger is a framework-neutral execution ledger and checkpoint store for agent harnesses and orchestrators. It records immutable execution facts and versioned harness-native recovery baselines so a host can audit execution, derive trajectories, and recover safely.
The specification is the stable product. Language SDKs implement the same object and append contracts; framework adapters bind those contracts to concrete harness hooks and recovery APIs.
Agent Ledger is not an agent loop, workflow engine, scheduler, or universal checkpoint format.
Upstream host ── Session / Run identity ─────────────┐
├── Agent Ledger
Harness ── Lane / Turn / Action / Attempt facts ────┘ │
├── audit
Harness-native state ── Checkpoint Store ───────────────────┼── recovery
└── trajectory / eval
The orchestrator owns control state. Each harness owns the meaning of its native state. Agent Ledger stores that state opaquely as Checkpoints and owns the append-only execution facts that connect it to later work.
Session → Run → Lane → Turn → Action → Attempt
↘ immutable Events
Actor ────────────────────────────────↗
CheckpointKey → Checkpoint revision* ── optional Lane/Event anchor
Sessionis one upstream task.Runis the upstream-defined grouping between Session and Lane; Ledger requires a stablerun_idbut does not define its domain meaning or lifecycle.Laneis one serial line inside a Run and the optimistic-concurrency boundary. A Run normally has amainLane and may have branch or framework-native-state Lanes.Turnis a stable interaction boundary.Actionis logical work such asmodel_call,tool_call, orcompact.Attemptis one physical try of an Action; retrying creates a newattempt_no.Eventis an immutable lifecycle, input, output, or audit fact about any hierarchy subject.Actorstores stable producer identity once; high-volume Events only retainactor_id.
Session and Run IDs are supplied by the host. Ledger-owned IDs use UUIDv7. Requested Events are committed before external calls. A requested Attempt without a terminal Event is unresolved after a crash and must be reconciled; a side-effecting tool is never silently retried.
SDKs export constants for the Core Action and Event vocabulary. The stored fields remain open
strings: framework and application extensions use namespaced values and are preserved by Stores.
The normative list lives in spec/vocabulary.json.
| Area | Responsibility |
|---|---|
spec/ |
Object model, append semantics, adapter capabilities, recovery boundaries, reference SQL |
conformance/ |
Cross-language canonical encoding and digest vectors |
python/ |
Python SDK with Memory, Redis, and SQLAlchemy Stores |
typescript/ |
TypeScript SDK and Pi adapter |
go/ |
Go SDK with Memory, Bolt, and GORM Stores plus AgentGo adapter |
GORM and SQLAlchemy Stores accept application-owned database handles. SQLite is used in contract tests; production applications may inject MySQL or another supported relational driver. The SQL schema deliberately has no foreign-key constraints: Store implementations validate immutable ownership relationships in application code.
Applications inject an EventStore:
create_actor / create_lane / create_turn / create_action / create_attempt
append(lane_id, expected_last_seq, append_id, events)
load_lane(lane_id, after_seq)
load_run(session_id, run_id)
load_session(session_id)
An empty Lane has last_seq = 0; its first Event has seq = 1. Appends are atomic, idempotent by
RFC 8785 canonical Event content, and protected by Lane-local optimistic concurrency. Event and
append IDs are globally unique. seq orders one Lane only; cross-Lane display order never implies
causality. LaneRecorder exposes the same ordered batch append while serializing concurrent calls
and advancing its cached Lane head after each accepted receipt.
Committed Events are append-only. Corrections and redactions are later Events; physical retention is an explicit deployment policy outside the logical Store contract.
Applications that need recovery inject a CheckpointStore:
save_checkpoint(expected_revision, proposed_checkpoint)
get_checkpoint(checkpoint_id)
load_latest_checkpoint(checkpoint_key)
Checkpoint formats are opaque to Ledger, for example
application/vnd.compforge.agentgo.message+json;version=1. A Checkpoint may stand alone or anchor
the last applied Event in a Lane; recovery then reads Events after that seq. See
Checkpoint for the full boundary and save contract.
When a persisted Checkpoint is the safe terminal boundary of a Run, a Recorder batch can append
lane.framework.checkpoint.linked and run.completed in one atomic Lane batch. Run inspection
then exposes terminal Events, linked Checkpoints, and unresolved Attempts without choosing an
orchestrator status or recovery policy.
Go applications inject an already configured GORM handle:
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
store, err := gormstore.New(db, 5*time.Second)
err = store.Initialize(ctx)The application remains responsible for driver choice, credentials, pool sizing, and connection
lifecycle. Initialize creates the Ledger tables without foreign keys.
| Adapter | Recording | Recovery |
|---|---|---|
| Pi AgentHarness | Awaited Turn, model, and tool hooks | Ledger-backed Pi SessionStorage Lane |
| AgentGo | Model wrapper, Turn hooks, message committer, tool middleware | Native messages with HoldRuns, SetMessages, and Continue |
| Plain Python loop | Explicit LaneRecorder calls |
Snapshot plus completed-outcome replay |
Every adapter publishes actual guarantees such as strict, best_effort, or unsupported.
Normalized Events support inspection and trajectories; only a harness-native state binding may
claim lossless recovery.
make fix
make lint
make test
make buildSee RFC 0001 for the core contract and RFC 0002 for adapter boundaries.