Understanding the Lifecycle Concepts of sessionId, turnId, and runId in Apache Maka

Apache Maka employs a three-tier identifier hierarchy where sessionId represents the top-level conversation container, runId tracks logical model executions within that session, and turnId captures atomic request-response pairs, each with distinct creation scopes and persistence lifecycles.

Apache Maka's runtime architecture separates conversational AI workflows into three distinct identification layers. Understanding the lifecycle concepts of sessionId, turnId, and runId in Maka is essential for debugging persistence, replaying executions, and building responsive user interfaces. These identifiers form a strict parent-child-grandchild relationship that drives how the framework stores events, manages state, and renders interactions.

Session ID: The Top-Level Conversation Container

The sessionId represents the overarching interactive conversation between a user and a Runtime Host. It functions as the root anchor for all activity within a single chat window, CLI session, or automated workflow.

According to the source code, the session ID is generated when a user opens a new conversation and is stored in the session-scoped Task Ledger. As documented in docs/session-task-ledger-lifecycle.md, this identifier persists until the user explicitly ends the session or an automatic timeout/archival policy triggers. All subsequent runs and turns inherit this same sessionId, making it the primary key for session-level metadata and task persistence.

The session remains valid across multiple model invocations, ensuring that task history and context remain accessible regardless of how many individual runs execute within the conversation.

Run ID: The Logical Execution Context

The runId identifies a logical execution of a model or agent within a session. Unlike the session identifier, which spans the entire conversation, a run represents a specific model invocation that may span multiple turns—such as multi-turn tool-calling sessions or long-running autonomous goals.

In src/scripts/computer-use/real-model.mjs, the framework assigns a fresh runId using randomUUID() the first time a model invocation is created for a given session. The identifier is recorded in the Runtime Store (src/packages/storage/src/sqlite-runtime-store.ts) and attached to every RuntimeEvent belonging to that execution.

A run remains valid for its entire execution lifecycle. The run is considered terminal when the model reports a final status—such as completing a goal, returning from a tool call, or receiving a user abort signal. After termination, the runId is closed, though the session continues. A new model invocation within the same session receives a fresh runId, allowing the framework to separate distinct execution phases while maintaining session continuity.

Turn ID: The Atomic Interaction Unit

The turnId represents the smallest unit of interaction that Maka tracks—a single request-response pair. This atomic identifier enables precise UI rendering, event ordering, and scroll positioning.

At the start of each turn, the system generates a fresh turnId using crypto.randomUUID() (or static constants for testing). As implemented in src/packages/ui/src/use-turn-virtualizer.ts, the turn ID is stored on turn-level events and used by the UI to locate corresponding DOM elements via data-turn-id attributes.

The turn lifecycle exists only for the duration of that specific interaction. Once the turn's output is rendered and persisted, the turnId is never reused. New turns within the same run receive unique identifiers, enabling precise scroll-and-highlight behavior without confusing the boundaries between separate turns or runs.

Hierarchy and Relationships

The three identifiers form a strict containment hierarchy:

  • Session ID → Parent container for all activity
  • Run ID → Child of a session; may contain many turns
  • Turn ID → Grandchild of a session (and child of a run)

A typical execution flow follows this pattern:

  1. Create session → sessionId = "s-abc123" (persists throughout)
  2. Start run → runId = "r-xyz789" (valid until terminal status)
  3. Execute turns → turnId = "t-1", turnId = "t-2" (atomic, single-use)

When a new model invocation is required—whether triggered by a user command or autonomous goal—a fresh runId is allocated while the original sessionId is preserved. Each turn inside that run receives its own turnId, ensuring that persistence, replay, and UI virtualization operate independently at each layer.

Implementation in Source Code

Deriving Identifiers in the Runtime Ledger

In src/scripts/computer-use/direct-runtime-ledger.mjs, the framework demonstrates how the three identifiers relate during ledger creation:

// src/scripts/computer-use/direct-runtime-ledger.mjs
export function createDirectRuntimeTurnLedger({ sessionId, turnId, text, newId, now }) {
  const invocationId = `${sessionId}-invocation`;
  const runId = `${sessionId}-run`;       // run ID derived from the session
  // …
}

This function shows the derivation pattern where the runId is constructed from the sessionId, establishing the parent-child relationship at the code level.

Generating Fresh IDs for Model Invocation

When initiating actual model execution, src/scripts/computer-use/real-model.mjs generates cryptographically secure identifiers:

// src/scripts/computer-use/real-model.mjs
const runId = randomUUID();             // fresh run ID
const turnId = crypto.randomUUID();      // fresh turn ID
const runResult = await runModel({
  sessionId,
  runId,
  turnId,
});

This separation ensures that each model invocation receives a unique execution context (runId) while maintaining the conversation context (sessionId).

Persisting Runtime Events

The SQLite runtime store enforces identifier relationships during persistence. In src/packages/storage/src/sqlite-runtime-store.ts, the importRuntimeEvent method validates that events belong to the correct session and run:

// src/packages/storage/src/sqlite-runtime-store.ts
async importRuntimeEvent(sessionId: string, runId: string, canonicalEvent: RuntimeEvent) {
  if (sessionId !== canonicalEvent.sessionId || runId !== canonicalEvent.runId) {
    throw new Error(`RuntimeEvent ${canonicalEvent.id} appears more than once in run ${runId}`);
  }
  // …
}

This validation ensures data integrity across the identifier hierarchy.

UI Virtualization by Turn

The UI layer leverages turnId for precise rendering control. In src/packages/ui/src/use-turn-virtualizer.ts:

// src/packages/ui/src/use-turn-virtualizer.ts
const heights = input.sessionId && layoutKey
    ? turnHeightIndex.lookup(input.sessionId, layoutKey)
    : undefined;
// … later
const index = turnIds.indexOf(turnId);   // locate a specific turn for scrolling

This implementation allows the interface to scroll to specific turns using stable identifiers without reloading entire sessions or runs.

Summary

  • sessionId persists for the entire conversation lifetime, storing session-scoped tasks and metadata in the Task Ledger.
  • runId represents discrete model executions within a session, tracked in the Runtime Store, and terminates when the model reports final status.
  • turnId provides atomic tracking of individual request-response pairs, enabling precise UI virtualization and scroll positioning.
  • The hierarchy—Session → Run → Turn—allows Maka to persist state independently, replay executions by runId, and render turns without confusion between execution contexts.

Frequently Asked Questions

What is the relationship between sessionId and runId in Maka?

The sessionId acts as the parent container that persists for the entire conversation, while runId represents child executions spawned within that session. Multiple runId values can exist sequentially within a single sessionId, but each run belongs to exactly one session. This relationship is enforced in the SQLite runtime store, which validates that every runtime event matches both its session and run identifiers.

How long does a turnId persist in Apache Maka?

A turnId exists only for the duration of its specific request-response cycle. Generated at the start of each turn using crypto.randomUUID(), the identifier is attached to runtime events and UI elements, then retired once the turn completes. The identifier is never reused, ensuring that scroll positions and event logs maintain precise references to historical interactions without ambiguity.

Can multiple runIds exist simultaneously within the same session?

While a session can contain many runs over its lifetime, each runId represents a distinct execution phase that begins with a model invocation and ends with a terminal status. After a run terminates—whether through completion, tool return, or user abort—a new model invocation generates a fresh runId. The architecture does not support overlapping active runs within the same session context; instead, runs proceed sequentially while the sessionId maintains continuity.

Where are these identifiers stored in the Maka codebase?

The sessionId is persisted in the session-scoped Task Ledger as described in docs/session-task-ledger-lifecycle.md. The runId and associated events are stored in the Runtime Store implemented in src/packages/storage/src/sqlite-runtime-store.ts. The turnId is utilized by the UI layer in src/packages/ui/src/use-turn-virtualizer.ts for DOM element tracking and layout management. Collectively, these storage locations implement the hierarchical relationship defined in ARCHITECTURE.md.

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 →