# How Apache Maka Uses an Append‑Only Ledger for State Management

> Discover how Apache Maka uses an append-only ledger as its single source of truth for runtime events, ensuring immutable state management for agent operations.

- Repository: [The Apache Software Foundation/maka](https://github.com/apache/maka)
- Tags: internals
- Published: 2026-09-05

---

**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`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/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:

```typescript
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);

```

2. **T₂ (Tool Outcome)** — After the external operation completes, a second event appends the result with a `modelProjection`:

```typescript
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`](https://github.com/apache/maka/blob/main/packages/runtime/src/session-manager.ts) orchestrates **Phase 0 recovery**:

```typescript
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`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/packages/core/src/runtime-event.ts) defines immutable, never-overwritten records
- **Durable storage** — SQLite implementation in [`packages/storage/src/sqlite-runtime-store.ts`](https://github.com/apache/maka/blob/main/packages/storage/src/sqlite-runtime-store.ts) with atomic transactions
- **Tool safety** — T₁/T₂ boundary protocol in [`packages/runtime/src/tool-runtime.ts`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/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`](https://github.com/apache/maka/blob/main/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.