Understanding commitToolPrepared (T1) and commitToolOutcome (T2) in Apache Maka's Tool Execution

Apache Maka uses a two-phase persistence mechanism where commitToolPrepared (T1) records immutable tool call details before execution, and commitToolOutcome (T2) persists the final result after completion, enabling crash recovery and exactly-once semantics.

Apache Maka executes LLM-provided actions inside a durable tool runtime (TR) designed to survive crashes, restarts, and unexpected interruptions. To guarantee that no tool call is lost or executed twice, the runtime persists two distinct journal entries—commitToolPrepared (T1) and commitToolOutcome (T2)—in the SQLite runtime store, creating a durable audit trail from dispatch to completion.

The Two-Phase Durability Model

Maka's tool execution follows a strict two-phase commit pattern that separates preparation from finalization. This separation ensures that the runtime can recover deterministically regardless of when a failure occurs.

Journal Entry Architecture Symbol When Written Contents
Prepared T1 (commitToolPrepared) Before tool dispatch Immutable replay data: operation ID, journal event ID, dispatch event, tool name, canonical argument hash, recovery mode, and timestamps
Outcome T2 (commitToolOutcome) After tool returns Final runtime event (success/failure), commitment timestamp, and linkage to the prepared entry via journalEventId

Together, these entries form the backbone of Maka's exactly-once execution guarantee.

Step T1: Persisting Tool Preparation with commitToolPrepared

Before dispatching a tool, the runtime constructs a Prepared journal entry using the CommitToolPreparedInput type. This entry captures all information necessary to replay the tool call later if the process crashes.

The runtime calls SqliteRuntimeStore.commitToolPrepared() (implemented in packages/storage/src/sqlite-runtime-store.ts at lines 1666–1684), which inserts a row into the tool_prepared table. This method returns a ToolCommitResult containing:

  • created: Boolean indicating whether this is a new entry or a duplicate
  • runtimeEventSeq: Sequential event number for the session ledger

This persistence step becomes the source of truth for recovery routines. If the system crashes after writing T1 but before obtaining the tool result, the runtime can locate the prepared record and re-invoke the tool with identical arguments.

Step T2: Recording Final Results with commitToolOutcome

Once the tool finishes—or throws a ToolOutcomeUnknownError during boundary-crossing scenarios—the runtime builds an Outcome event using CommitToolOutcomeInput. This event reflects the final state: success, failure, or partial outcome.

The runtime invokes SqliteRuntimeStore.commitToolOutcome() (lines 1746–1765 in packages/storage/src/sqlite-runtime-store.ts), which writes to the tool_outcome table and updates the journal state to outcome_committed. This operation links the outcome to the previously prepared entry via the same journalEventId, completing the lifecycle.

The persisted outcome enables exactly-once semantics during recovery. On restart, the runtime checks for existing outcome records before re-executing tools, preventing duplicate operations.

Implementation Details and Recovery Mechanics

The interaction between T1 and T2 provides several critical guarantees:

  • Duplicate Detection: The created flag in ToolCommitResult allows the runtime to identify idempotent retries and avoid double-committing journal entries
  • Deterministic Recovery: By re-reading the prepared entry from tool_prepared, the runtime can reconstruct the exact tool call context after a crash
  • Boundary-Crossing Safety: When tools execute in remote sandboxes, failures are captured as ToolOutcomeUnknownError, prompting retry logic that relies on the immutable T1 preparation record

According to the architecture documentation in docs/architecture/runtime-resume-architecture.md, the flow appears as:

  • T1 – "TR → DB: commitToolPrepared" (runtime writes preparation record)
  • T2 – "TR → DB: commitToolOutcome" (runtime writes final result)

Code Example: Committing Tool Lifecycle

The following TypeScript demonstrates how a tool runtime interacts with the storage layer to execute the two-phase commit:

// Phase 1: Prepare the tool call (T1)
const prepared: CommitToolPreparedInput = {
  operationId: opId,
  journalEventId: jeId,
  runtimeEvent: dispatchEvent,
  dispatchRuntimeEvent: dispatchEvent,
  providerToolCallId: toolCallId,
  toolName: 'search',
  canonicalArgsHash: canonicalToolArgsHash(args),
  recoveryMode: 'auto',
  committedAt: Date.now(),
};

const prepResult: ToolCommitResult = await runtimeCommitSink.commitToolPrepared(prepared);
// prepResult.created === true indicates first-time recording

// Phase 2: After tool execution, commit the outcome (T2)
const outcome: CommitToolOutcomeInput = {
  operationId: opId,
  journalEventId: jeId,
  runtimeEvent: outcomeEvent,   // ToolCompleted or ToolFailed event
  committedAt: Date.now(),
};

const outResult: ToolCommitResult = await runtimeCommitSink.commitToolOutcome(outcome);
// outResult.created === true confirms outcome persistence

This pattern appears throughout the test suite in packages/storage/src/__tests__/sqlite-runtime-store.test.ts, which verifies durability and idempotency by repeatedly invoking store.commitToolPrepared and store.commitToolOutcome under simulated failure conditions.

Summary

  • Two-phase persistence: commitToolPrepared (T1) writes immutable dispatch details before execution, while commitToolOutcome (T2) records the final result after completion
  • Crash recovery: The tool_prepared table enables deterministic replay of interrupted tool calls by storing canonical arguments and metadata
  • Exactly-once semantics: The tool_outcome table prevents duplicate execution by tracking which operations have already completed
  • Idempotency support: Both commitToolPrepared and commitToolOutcome return a created flag in ToolCommitResult to detect duplicate commit attempts

Frequently Asked Questions

What happens if Apache Maka crashes after T1 but before T2?

If the system crashes after commitToolPrepared (T1) succeeds but before commitToolOutcome (T2) completes, the recovery routine reads the pending entry from the tool_prepared table using the journalEventId. The runtime then re-invokes the tool with the exact same arguments (verified via the canonical argument hash) and proceeds to commit the outcome once execution finishes.

How does Maka prevent duplicate tool execution during recovery?

Maka prevents duplicates by checking the tool_outcome table before re-executing any tool. If commitToolOutcome (T2) already recorded a result for the given journalEventId, the runtime skips execution and returns the persisted outcome. The created boolean in ToolCommitResult further helps callers identify whether they are processing a new request or handling a retry.

What data is stored in the tool_prepared versus tool_outcome tables?

The tool_prepared table stores immutable dispatch context including the operation ID, journal event ID, tool name, canonical argument hash, recovery mode, and the dispatch runtime event. The tool_outcome table stores the final runtime event (success or failure status), the commitment timestamp, and maintains a foreign key relationship to the prepared entry via journalEventId.

Where can I find the implementation of these commit methods?

Both methods are implemented in packages/storage/src/sqlite-runtime-store.ts. The commitToolPrepared method resides around lines 1666–1684, while commitToolOutcome appears at lines 1746–1765. The public interfaces are defined in packages/runtime/src/runtime-commit-sink.ts, and the event type definitions live in packages/core/src/events.ts.

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 →