How Apache Maka Uses an Append‑Only Ledger for State Management
Apache Maka treats every observable fact of a running agent as an immutable record in a Runtime Event Log—an append‑only ledger that serves as the single source of truth for all model messages, tool calls, tool results, permission decisions, and termination events.
The apache/maka repository implements a crash-tolerant, replayable state management system for AI agents. Instead of mutating in-memory state, Maka persists every state change as an append-only event sequence. This design enables deterministic recovery, exact-once semantics for model-visible facts, and fail-closed safety for tool side-effects.
The Runtime Event Log as Canonical Source of Truth
At the core of Maka's architecture is the Runtime Event Log, defined by the RuntimeEvent contract in packages/core/src/runtime-event.ts. Every event carries:
- A UUID (
id) - Temporal and scope identifiers (
ts,invocationId,runId,sessionId,turnId) - A
roleandauthor(e.g.,user,assistant,tool,host) - A
contentpayload with strongly-typedkinddiscriminator - Optional
actionsfor control flow (tool dispatches, continuation markers)
No event is ever overwritten. New state—whether a user message, model response, or tool result—is expressed solely by appending a later event to the ledger.
Durable Storage with SQLite
The append-only guarantee extends to persistent storage via packages/storage/src/sqlite-runtime-store.ts. At host startup, the store opens a SQLite database where:
- All writes occur through atomic transactions
- Existing rows are never mutated
- The log survives process crashes and can be replayed verbatim
This implementation treats the database as a sequential log rather than a mutable key-value store, ensuring durability without sacrificing the immutable semantics of the event model.
Tool Boundaries: The T₁/T₂ Protocol
Maka's most critical safety mechanism is the two-phase tool boundary protocol implemented in packages/runtime/src/tool-runtime.ts. When a model requests a tool:
- T₁ (Tool Dispatch) — Before any external side-effect, Maka appends a
toolDispatchaction recording the intent, operation ID, and canonical argument hash:
import { RuntimeEventToolDispatch, TOOL_BOUNDARY_PROTOCOL_V1 } from
'@maka/core/runtime-event';
import { RuntimeEventStore } from '@maka/storage/sqlite-runtime-store';
const dispatch: RuntimeEvent = {
id: crypto.randomUUID(),
invocationId: invId,
runId: runId,
sessionId,
turnId,
ts: Date.now(),
partial: false,
role: 'tool' as const,
author: 'tool' as const,
actions: {
toolDispatch: {
protocol: TOOL_BOUNDARY_PROTOCOL_V1,
operationId: crypto.randomUUID(),
providerToolCallId: toolCallId,
toolName: 'write_file',
canonicalArgsHash: hash(args),
recoveryMode: 'replay_safe',
},
},
};
await RuntimeEventStore.append(dispatch);
- T₂ (Tool Outcome) — After the external operation completes, a second event appends the result with a
modelProjection:
import { RuntimeEvent } from '@maka/core/runtime-event';
import { RuntimeEventStore } from '@maka/storage/sqlite-runtime-store';
const outcome: RuntimeEvent = {
id: crypto.randomUUID(),
invocationId: invId,
runId: runId,
sessionId,
turnId,
ts: Date.now(),
partial: false,
role: 'tool' as const,
author: 'tool' as const,
content: {
kind: 'function_response',
id: toolCallId,
name: 'write_file',
result: { ok: true },
modelProjection: {
kind: 'content',
parts: [],
},
},
};
await RuntimeEventStore.append(outcome);
Because T₁ and T₂ are separate immutable events, crash recovery can reason about ambiguity: the presence of T₁ without T₂ indicates a potentially-executed side-effect requiring idempotency checks or replay-safe handling based on the recoveryMode field.
Crash Recovery and Session Continuation
On restart, packages/runtime/src/session-manager.ts orchestrates Phase 0 recovery:
import { SessionManager } from '@maka/runtime/session-manager';
import { RecoveryResolver } from '@maka/runtime/recovery-resolver';
const prefix = await SessionManager.readLedgerPrefix(sessionId);
const decisions = await RecoveryResolver.classify(prefix);
The RecoveryResolver in packages/runtime/src/recovery-resolver.ts classifies each in-flight operation:
| Classification | Meaning |
|---|---|
| Completed | Both T₁ and T₂ present—operation succeeded |
| Definitely not dispatched | No T₁ found—safe to re-dispatch |
| Indeterminate | T₁ present, T₂ absent—external effect may have occurred |
| Parked | Operation requires human intervention |
| Corrupt | Ledger integrity violation detected |
If the prefix is safe, the system appends a RuntimeEventContinuationStartV2 event with a fresh runId, guaranteeing that new execution only sees provably committed history. The RuntimeResume orchestrator in packages/runtime/src/runtime-resume.ts manages this Phase 0→1 transition.
Projections: Derived State Without Authority
All fast lookups—UI rendering, token accounting, artifact deltas—are built as projections (e.g., the tool_operations table in SQLite). Key properties:
- Projections are rebuilt entirely from the immutable log
- They never become source of truth
- Any divergence from the ledger is treated as corruption requiring projection rebuild
This separation enables optimized queries without compromising the append-only invariant.
Idempotent State and Deterministic Replay
Because every change is an append, Maka can safely replay the log to:
- Reconstruct any prior state for debugging
- Generate test scenarios from production traces
- Create checkpoints by truncating at known-good high-water marks
No mutable "snapshot" of model state exists. The current session view is always derived from the immutable prefix up to a cursor position.
Summary
- Append-only ledger —
RuntimeEventcontract inpackages/core/src/runtime-event.tsdefines immutable, never-overwritten records - Durable storage — SQLite implementation in
packages/storage/src/sqlite-runtime-store.tswith atomic transactions - Tool safety — T₁/T₂ boundary protocol in
packages/runtime/src/tool-runtime.tsenables crash reasoning about side-effects - Recovery —
RecoveryResolverclassifies ledger state;SessionManagerandRuntimeResumeorchestrate safe continuation - Projections — Derived tables for performance, with ledger as sole authority
- Replayability — Complete state reconstruction from any prefix enables debugging, testing, and checkpointing
Frequently Asked Questions
What makes Apache Maka's ledger "append-only" rather than just a transaction log?
In Maka's design, no mutation operation exists at any layer. The RuntimeEventStore.append() method in packages/storage/src/sqlite-runtime-store.ts only executes INSERT statements within atomic transactions. There are no UPDATE or DELETE operations on event rows. Even "continuation" semantics—resuming after a crash—are implemented by appending a new RuntimeEventContinuationStartV2 event with a fresh runId, not by modifying session state. This is stricter than typical transaction logging where a logical update might overwrite prior values.
How does the T₁/T₂ protocol prevent duplicate tool execution?
The T₁ event serves as a commitment record that the system intended to dispatch a tool. If a crash occurs after T₁ but before T₂, the RecoveryResolver classifies this as indeterminate state. Rather than blindly re-executing—which could cause duplicate side-effects—the system consults the recoveryMode field (e.g., replay_safe). For idempotent operations, it may re-execute; for destructive operations, it may park the operation pending human review. The separate T₂ event provides the only proof of completion, ensuring that absence of T₂ means no model-visible confirmation of success.
Can the Runtime Event Log be inspected or replayed for debugging?
Yes. Because the ledger in packages/storage/src/sqlite-runtime-store.ts contains complete, immutable history, operators can:
- Query events directly via SQL for any
sessionIdorrunId - Reconstruct exact model context at any
turnIdby replaying the prefix - Export sessions for regression testing or audit trails
The SessionManager.readLedgerPrefix() method returns events in chronological order, enabling deterministic reconstruction without running the agent.
What happens if a projection diverges from the ledger?
Divergence is treated as corruption. Projections—such as the tool_operations table—are purely performance optimizations. If a query against projection tables returns results inconsistent with recomputing from the raw RuntimeEvent rows, the system considers the projection invalid and rebuilds it from the ledger. This fail-safe ensures that bugs in projection logic cannot permanently corrupt state, as the authoritative event sequence always remains intact in SQLite.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →