# How ToolRuntime Manages Tool Execution in Apache Maka: A Deep Dive into the Five-Phase Architecture

> Discover how ToolRuntime orchestrates tool execution in Apache Maka using its five-phase pipeline: preparation, admission, execution, result processing, and error handling. Learn about its cancellation and compensation features.

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

---

**ToolRuntime is the central orchestrator that manages every tool invocation in Apache Maka through a five-phase pipeline: preparation, admission and gating, execution with cancellation support, result processing with size limits, and error handling with compensation hooks.**

Apache Maka's execution model depends on a robust runtime to safely invoke tools while maintaining sandbox boundaries, handling abort signals, and persisting results. The `ToolRuntime` class—located in [`packages/runtime/src/tool-runtime.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-runtime.ts)—implements this coordination. According to the Apache Maka source code, this component bridges the model's tool requests with concrete tool implementations, enforcing security and durability guarantees at each step.

## The Five Phases of Tool Execution

The `ToolRuntime` manages tool execution through a deterministic pipeline. Each phase is designed to handle specific concerns: request normalization, permission validation, actual invocation, result projection, and failure recovery.

### Phase 1: Preparation and Request Normalization

Before any tool runs, the runtime constructs a complete execution context from a `ToolRuntimeInput` object. This input bundles session state, backend connections, and helper functions that tools will need.

The `ToolRuntimeInput` interface ([source](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-runtime.ts#L71-L96)) defines the contract:

```typescript
interface ToolRuntimeInput {
  sessionId: string;
  header: SessionHeader;
  connection: ConnectionLike;
  modelId: string;
  appendMessage: (message: Message) => Promise<void>;
  readExecutionBoundary: () => Promise<ExecutionBoundary>;
  newId: () => string;
  now: () => number;
  turnId: string;
  // Additional runtime capabilities...
}

```

The runtime uses this input to build a `ResolvedMakaToolCall`, which pairs the raw tool call with its concrete `MakaTool` definition, an abort signal, and a durable event sink for persistence.

### Phase 2: Admission and Gating

The runtime enforces two critical gates before execution begins:

- **Tool-availability gating** — Validates whether the tool is permitted in the current execution step
- **Sandbox-boundary gating** — Handles requests for boundary expansion through `requestSandboxBoundary`

To prevent infinite loops from repeatedly failing tool calls, the runtime implements loop-gating constants ([source](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-runtime.ts#L41-L51)):

```typescript
const LOOP_GATE = {
  MAX_CONSECUTIVE_FAILURES: 3,
  BACKOFF_MS: [0, 100, 500],
  RESET_AFTER_MS: 30000,
} as const;

```

These limits ensure that a misbehaving tool or hostile input cannot exhaust system resources through rapid retry cycles.

### Phase 3: Execution with Robust Cancellation

The core execution happens in the tool's `impl` function, which receives a `MakaToolContext`. This context provides:

- `abortSignal` — For cooperative cancellation
- `emitOutput` / `emitProgress` — For streaming partial results
- `spawnChildSession` — For creating nested execution contexts
- `requestSandboxBoundary` — For security boundary negotiation

A critical helper, `composeChildAbortSignal` ([source](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-runtime.ts#L63-L68)), ensures that cancellation propagates correctly between parent and child sessions:

```typescript
function composeChildAbortSignal(
  parentSignal: AbortSignal,
  childSignal: AbortSignal
): AbortSignal {
  const controller = new AbortController();
  const onAbort = () => controller.abort();
  parentSignal.addEventListener('abort', onAbort);
  childSignal.addEventListener('abort', onAbort);
  return controller.signal;
}

```

This composition guarantees that if either the parent call or any child session aborts, the entire operation terminates cleanly.

### Phase 4: Result Processing and Size Limits

After the tool completes, the runtime processes its output through several transformations:

1. **Decoding** — Raw output is normalized via `decodeCanonicalToolResultContent`
2. **Size enforcement** — The runtime applies a **max-result-bytes** limit, truncating if necessary ([source](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-runtime.ts#L36-L38)):
   ```typescript
   const MAX_RESULT_BYTES = 1024 * 1024; // 1 MiB
   ```

3. **Durable projection** — Results are encoded into `encodeDurableToolResultOutput` format for persistence
4. **Event emission** — A `ToolResultEvent` is pushed to the durable session event sink

This pipeline ensures that results are both bounded in size and recoverable after system restarts.

### Phase 5: Error Handling and Compensation

When tools fail, the runtime classifies errors via `classifyError` ([source](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-runtime.ts#L94)) and wraps them in `ToolResultEvent` objects with appropriate error flags.

For durability failures, the runtime supports an optional compensation hook defined in the `MakaTool` interface ([source](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-runtime.ts#L37-L44)):

```typescript
interface MakaTool<TInput, TOutput> {
  name: string;
  parameters: JSONSchema7;
  impl: (args: TInput, ctx: MakaToolContext) => Promise<TOutput>;
  compensateDurableOutcomeCommitFailure?: (
    outcome: DurableOutcome,
    reason: Error
  ) => Promise<void>;
}

```

This hook allows tools to execute cleanup logic when the durable boundary rejects a committed result.

## Practical Usage Examples

### Instantiating ToolRuntime and Running a Tool

```typescript
import { ToolRuntime } from '@maka/runtime';
import { fileReadTool } from './tools/file-read';

const runtimeInput = {
  sessionId: 'sess-abc-123',
  header: { /* session metadata */ },
  connection: { /* backend connection */ },
  modelId: 'gpt-4o',
  appendMessage: async (msg) => await persistMessage(msg),
  readExecutionBoundary: async () => await fetchCurrentBoundary(),
  newId: () => crypto.randomUUID(),
  now: () => Date.now(),
  turnId: 'turn-42',
};

const toolRuntime = new ToolRuntime(runtimeInput);

await toolRuntime.runTool({
  tool: fileReadTool,
  turnId: runtimeInput.turnId,
  toolCallId: runtimeInput.newId(),
  input: { path: '/workspace/data.txt' },
  abortSignal: new AbortController().signal,
  eventSink: {
    push: (ev) => console.log('Event emitted:', ev),
    pushAndWaitUntilConsumed: async (ev) => await waitForAck(ev),
  },
});

```

This pattern demonstrates the complete lifecycle: the runtime is constructed from input, then `runTool` orchestrates the five phases internally.

### Implementing a Tool with Sandbox Boundary Requests

```typescript
export const databaseQueryTool: MakaTool<{ query: string }, QueryResult> = {
  name: 'database_query',
  description: 'Execute a read-only SQL query',
  parameters: {
    type: 'object',
    properties: {
      query: { type: 'string', description: 'SQL SELECT statement' },
    },
    required: ['query'],
  },
  
  impl: async (args, ctx) => {
    // Request expanded boundary for database access
    const boundary = await ctx.requestSandboxBoundary(
      { expand: { databaseRead: true, queryLength: args.query.length } },
      'Executing user-requested SQL query'
    );
    
    if (!boundary.granted) {
      throw new Error('Database access denied by sandbox policy');
    }
    
    // Emit progress for long-running queries
    ctx.emitProgress({ stage: 'parsing', percent: 10 });
    
    const result = await ctx.executeQuery(args.query, {
      signal: ctx.abortSignal,
    });
    
    ctx.emitProgress({ stage: 'complete', percent: 100 });
    return result;
  },
  
  compensateDurableOutcomeCommitFailure: async (outcome, reason) => {
    // Log compensation attempt for audit
    await auditLog.record({
      event: 'query_result_commit_failed',
      outcomeId: outcome.id,
      failureReason: reason.message,
      timestamp: Date.now(),
    });
  },
};

```

This example shows cooperative cancellation via `ctx.abortSignal`, progressive output via `emitProgress`, and the compensation hook for durability failures.

## Integration with Maka's Architecture

The `ToolRuntime` does not operate in isolation. It coordinates with several specialized subsystems, as illustrated in the architecture documentation ([source](https://github.com/apache/maka/blob/main/ARCHITECTURE.md#L31-L31)):

| Subsystem | Responsibility | Interaction with ToolRuntime |
|-----------|---------------|------------------------------|
| **Tool Availability Runtime** ([`packages/runtime/src/tool-availability.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-availability.ts)) | Per-step tool permission validation | Checked during Phase 2 admission |
| **AdmissionLimiter** | Throttles sandbox boundary requests | Enforces rate limits on `requestSandboxBoundary` calls |
| **Sandbox-Boundary Tool** ([`packages/runtime/src/sandbox-boundary-tool.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/sandbox-boundary-tool.ts)) | Protocol constants for boundary negotiation | Defines `REQUEST_SANDBOX_BOUNDARY_TOOL_NAME` and related types |
| **Durable Result Projection** ([`packages/core/src/durable-tool-result-projection.ts`](https://github.com/apache/maka/blob/main/packages/core/src/durable-tool-result-projection.ts)) | Persistence format encoding | Consumed in Phase 4 for result serialization |

## Key Implementation Files

| File | Purpose |
|------|---------|
| [`packages/runtime/src/tool-runtime.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-runtime.ts) | Core orchestration with `ToolRuntime` class and `ToolRuntimeInput` interface |
| [`packages/runtime/src/tool-availability.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/tool-availability.ts) | Per-step tool availability validation |
| [`packages/runtime/src/sandbox-boundary-tool.ts`](https://github.com/apache/maka/blob/main/packages/runtime/src/sandbox-boundary-tool.ts) | Boundary protocol constants and types |
| [`packages/core/src/durable-tool-result-projection.ts`](https://github.com/apache/maka/blob/main/packages/core/src/durable-tool-result-projection.ts) | Durable result encoding utilities |
| [`ARCHITECTURE.md`](https://github.com/apache/maka/blob/main/ARCHITECTURE.md) | High-level system diagram positioning ToolRuntime |

## Summary

- **ToolRuntime** centralizes tool execution in Apache Maka through five deterministic phases: preparation, admission, execution, result processing, and error handling.
- **Cancellation safety** is achieved through `composeChildAbortSignal`, which links parent and child abort signals.
- **Resource protection** comes from loop-gating constants that limit consecutive failures and enforce backoff.
- **Size limits** prevent unbounded result growth via `MAX_RESULT_BYTES` enforcement.
- **Durability** is guaranteed through result projection and event emission to persistent sinks.
- **Extensibility** is supported via compensation hooks for handling commit failures.

## Frequently Asked Questions

### What happens if a tool exceeds the maximum result size?

The runtime truncates the output to `MAX_RESULT_BYTES` (1 MiB by default) and marks the result as truncated in the emitted `ToolResultEvent`. The original tool implementation is unaware of this truncation; it occurs during the projection phase before persistence.

### How does ToolRuntime handle nested tool calls or child sessions?

The runtime provides `spawnChildSession` in the `MakaToolContext`. When invoked, it creates a new execution context with its own abort signal, which is composed with the parent's signal via `composeChildAbortSignal`. This ensures that cancellation propagates correctly through arbitrarily deep call trees.

### Can the sandbox boundary be expanded dynamically during tool execution?

Yes. Tools may call `requestSandboxBoundary` with a desired expansion and justification string. The runtime delegates this to the `AdmissionLimiter` and the sandbox-boundary subsystem. The expansion may be granted, partially granted, or denied based on current execution policy and throttling limits.