# How Maka Maps SessionEvents to RuntimeEvents: A Deep Dive into the Transformation Layer

> Discover how Maka transforms SessionEvents into RuntimeEvents via its deterministic mapping layer. Understand the core logic in the Maka repository and enhance your event processing.

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

---

**Maka converts low-level SessionEvent objects from the AI SDK backend into canonical RuntimeEvent ledgers through a deterministic mapping layer in [`packages/runtime/src/session-event-runtime-mapper.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/session-event-runtime-mapper.ts).**

The Apache Maka project implements a clean separation between backend session events and the runtime event stream consumed by its kernel. This article explains the exact mechanism that transforms raw backend events into structured runtime events, including the specific functions, file paths, and architectural decisions that make this mapping reliable and replayable.

## Overview of the Session-to-Runtime Event Pipeline

Maka's event translation serves a critical architectural purpose: it **normalizes heterogeneous backend event formats** into a single, versioned `RuntimeEvent` schema that the runtime kernel can process uniformly. The mapping is **pure and deterministic**—given the same session event and context, it always produces the same runtime event.

The pipeline has two primary components:

- **`mapSessionEventToRuntimeEvent`** in [`packages/runtime/src/session-event-runtime-mapper.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/session-event-runtime-mapper.ts) — the core transformation function
- **`createDirectRuntimeTurnLedger`** in `scripts/computer-use/direct-runtime-ledger.mjs` — the per-turn ledger wrapper that orchestrates multiple mappings

## Entry Point: Mapping a Single Session Event

The transformation begins with `mapSessionEventToRuntimeEvent(event, ctx, memory)` at lines 30–34 of [`session-event-runtime-mapper.ts`](https://github.com/apache/maka/blob/main/session-event-runtime-mapper.ts). This function performs three essential checks before proceeding:

1. Validates that the event is a genuine backend event (not a host projection)
2. Excludes legacy permission events that lack proper runtime representation
3. Initializes a fresh `SessionEventMapMemory` for temporary state storage

```typescript
import {
  mapSessionEventToRuntimeEvent,
  createSessionEventMapMemory,
} from '@maka/runtime';

// Context shared by all events of a turn
const ctx = {
  sessionId: 'sess-1',
  invocationId: 'sess-1-invocation',
  runId: 'sess-1-run',
  turnId: 'turn-1',
  now: () => Date.now(),
};

// Memory to keep tool-name linkage across events
const memory = createSessionEventMapMemory();

// Map a backend event to runtime format
const runtimeEvent = mapSessionEventToRuntimeEvent(toolStart, ctx, memory);

```

The `ctx` parameter carries invocation-wide identifiers that ensure every `RuntimeEvent` can be traced back to its originating session, run, and turn.

## Building the Common Event Skeleton

Before type-specific conversion, `resolveBase(event, ctx)` (lines 89–102) constructs the foundational structure shared by all runtime events. This function populates:

- **Identity fields**: `id`, `invocationId`, `runId`, `sessionId`, `turnId`
- **Temporal data**: timestamps via `ctx.now()`
- **Streaming state**: the `partial` flag indicating whether more content follows
- **Branch metadata**: optional branch information for conversation forking

This base construction ensures **cross-cutting concerns are handled once**, rather than duplicated across every event-type handler.

## Event-Type Specific Conversion with mapBackendSessionEvent

The core logic resides in `mapBackendSessionEvent`, which uses a `switch` statement to dispatch each `SessionEvent` variant to its appropriate handler. Every handler produces a `RuntimeEvent` with four key properties:

| Property | Purpose | Example Values |
|----------|---------|--------------|
| **role** | Conversation participant type | `model`, `tool`, `system`, `user` |
| **author** | Source of the event | `agent`, `tool`, `system`, `user` |
| **content** | Typed payload | `text`, `thinking`, `function_call`, `function_response`, `error` |
| **refs** | Cross-reference identifiers | `toolCallId`, `operationId`, `providerEventId` |
| **actions/stateDelta** | Side effects and state changes | sandbox requests, token usage, plan submissions |

### Model Text Events: text_delta and text_complete

Streaming text from the backend generates partial and complete events:

- **`text_delta`** → partial `model` event with `partial: true`
- **`text_complete`** → finished `model` event with `partial: false`

Both map to `content.kind: 'text'` with the accumulated or incremental text payload (lines 95–100).

### Tool Call Lifecycle: tool_start and tool_result

Tool execution spans two events that must be correlated:

```typescript
// Tool start: record name in memory, create function_call event
{
  type: 'tool_start',
  id: 'event-123',
  ts: 1_712_345_678,
  toolUseId: 'use-42',
  toolName: 'search',
  args: { query: 'Maka architecture' },
}
// → RuntimeEvent with role: 'model', author: 'agent', content.kind: 'function_call'

// Tool result: look up name from memory, create function_response event
{
  type: 'tool_result',
  id: 'event-124',
  ts: 1_712_345_679,
  toolUseId: 'use-42',  // Links back to tool_start
  content: { result: 'Maka is an AI agent runtime...' },
}
// → RuntimeEvent with role: 'tool', author: 'tool', content.kind: 'function_response'

```

The `toolNameByUseId` map in `SessionEventMapMemory` preserves the tool name across this asynchronous boundary since `tool_result` events do not carry the original tool name.

### Sandbox Boundary Events

Security-sensitive sandbox operations become `system`-role events with specialized state deltas:

- **`sandbox_boundary_request`** → `system` event with `sandboxBoundaryRequest` state delta (lines 97–104)
- **Sandbox decision events** → `system` event with `sandboxBoundaryDecision` state delta

These events enable the runtime to track and audit all sandbox escape requests without mixing them into the conversational stream.

### User Steering Messages

`steering_message` events transform into `user`-role text events marked with `steering: true` (lines 84–90). This marker allows the UI to distinguish organic user messages from steering interventions while maintaining a consistent event structure.

### Completion Events and Final Status

The `complete` event triggers `completeRuntimeEvent` (lines 60–68 and 115–120), which determines the final `status` field:

```typescript
// Status determination logic in completeRuntimeEvent
if (stopReason === 'completed') → status: 'completed'
if (stopReason === 'aborted') → status: 'aborted'
if (previousError) → status: 'failed'  // Overrides other statuses

```

This post-processing ensures the runtime has a single, authoritative signal for turn completion regardless of how the backend signaled termination.

## State Management with SessionEventMapMemory

The `SessionEventMapMemory` interface (lines 73–78) solves a specific correlation problem:

```typescript
interface SessionEventMapMemory {
  toolNameByUseId: Map<string, string>;  // toolUseId → toolName
  failureToReport?: unknown;             // Captured error for status override
}

```

This mutable state is **scoped to a single mapping operation** and discarded afterward. It enables:

- **Tool name recovery** when processing `tool_result` events
- **Error propagation** for status determination in completion handling

## Per-Turn Ledger Orchestration

For practical use, `createDirectRuntimeTurnLedger` in `scripts/computer-use/direct-runtime-ledger.mjs` (lines 25–68) wraps the mapper in a Ledger interface:

```typescript
import { createDirectRuntimeTurnLedger } from '../../scripts/computer-use/direct-runtime-ledger.mjs';

const ledger = createDirectRuntimeTurnLedger({
  sessionId: 'sess-1',
  turnId: 'turn-1',
  text: '',
  newId: () => crypto.randomUUID(),
  now: () => Date.now(),
});

// Record events—each triggers mapping and conditional storage
ledger.record(toolStart);
ledger.record(toolResultEvent);

// Retrieve final event list
const events = await ledger.loadTurnRuntimeEvents('turn-1');

```

The ledger **filters transient events**: partial events and error-only events are mapped but not stored, ensuring the persisted ledger contains only meaningful, complete runtime events.

## Testing and Verification

The mapping logic is exercised in [`packages/runtime/src/__tests__/session-event-runtime-mapper.test.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/__tests__/session-event-runtime-mapper.test.ts), which covers:

- All event type variants
- Partial vs. complete streaming states
- Tool call correlation across start/result pairs
- Sandbox boundary event handling
- Error propagation and status override

These tests guarantee that backend SDK changes do not silently break the runtime contract.

## Summary

- **Maka's session-to-runtime event mapping** lives in [`packages/runtime/src/session-event-runtime-mapper.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/session-event-runtime-mapper.ts) and provides deterministic, pure transformation of backend events
- **`mapSessionEventToRuntimeEvent`** validates, enriches, and dispatches events through `mapBackendSessionEvent` with type-specific handlers
- **`resolveBase`** normalizes cross-cutting identity and temporal fields across all event types
- **Tool correlation** uses `SessionEventMapMemory` to link `tool_start` and `tool_result` events without backend support for round-tripping tool names
- **Per-turn orchestration** via `createDirectRuntimeTurnLedger` in `scripts/computer-use/direct-runtime-ledger.mjs` handles multiple events with filtering for persistence

## Frequently Asked Questions

### What is the difference between SessionEvent and RuntimeEvent in Maka?

`SessionEvent` represents raw backend output from the AI SDK—tightly coupled to specific provider formats and streaming patterns. `RuntimeEvent` is Maka's canonical, versioned ledger format that the runtime kernel consumes. The mapping layer decouples the system from backend changes while providing consistent identifiers, roles, and content structures for downstream processing.

### Why does Maka need a memory object for mapping events?

The `SessionEventMapMemory` object solves a protocol limitation: `tool_result` events do not include the original tool name, only a `toolUseId`. Maka stores the name in memory during `tool_start` processing so the subsequent `tool_result` can produce a complete `function_response` event with both `id` and `name` populated. This state is strictly temporary and scoped to a single turn's event sequence.

### How does Maka handle incomplete or streaming events from the backend?

Streaming text generates `text_delta` SessionEvents that map to `RuntimeEvent` objects with `partial: true`. The runtime can render these incrementally. Final `text_complete` events clear the partial flag. The ledger in `direct-runtime-ledger.mjs` filters partial events from persistent storage, ensuring only complete, meaningful events are retained for replay or audit purposes.

### Where does the mapping actually get invoked in production code?

Production code typically calls `createDirectRuntimeTurnLedger` from `scripts/computer-use/direct-runtime-ledger.mjs`, which returns a ledger object with `record()` and `loadTurnRuntimeEvents()` methods. Each `record()` call invokes `mapSessionEventToRuntimeEvent` with the appropriate context and memory, collecting results for the turn. This indirection provides a stable Ledger interface while allowing the mapper implementation to evolve independently.