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 role and author (e.g., user, assistant, tool, host)
  • A content payload with strongly-typed kind discriminator
  • Optional actions for 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:

  1. T₁ (Tool Dispatch) — Before any external side-effect, Maka appends a toolDispatch action 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);
  1. 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 — RuntimeEvent contract in packages/core/src/runtime-event.ts defines immutable, never-overwritten records
  • Durable storage — SQLite implementation in packages/storage/src/sqlite-runtime-store.ts with atomic transactions
  • Tool safety — T₁/T₂ boundary protocol in packages/runtime/src/tool-runtime.ts enables crash reasoning about side-effects
  • Recovery — RecoveryResolver classifies ledger state; SessionManager and RuntimeResume orchestrate 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 sessionId or runId
  • Reconstruct exact model context at any turnId by 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →