How ToolRuntime Manages Tool Execution in Apache Maka: A Deep Dive into the Five-Phase Architecture
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—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) defines the contract:
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):
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 cancellationemitOutput/emitProgress— For streaming partial resultsspawnChildSession— For creating nested execution contextsrequestSandboxBoundary— For security boundary negotiation
A critical helper, composeChildAbortSignal (source), ensures that cancellation propagates correctly between parent and child sessions:
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:
-
Decoding — Raw output is normalized via
decodeCanonicalToolResultContent -
Size enforcement — The runtime applies a max-result-bytes limit, truncating if necessary (source):
const MAX_RESULT_BYTES = 1024 * 1024; // 1 MiB -
Durable projection — Results are encoded into
encodeDurableToolResultOutputformat for persistence -
Event emission — A
ToolResultEventis 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) 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):
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
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
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):
| Subsystem | Responsibility | Interaction with ToolRuntime |
|---|---|---|
Tool Availability Runtime (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) |
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) |
Persistence format encoding | Consumed in Phase 4 for result serialization |
Key Implementation Files
| File | Purpose |
|---|---|
packages/runtime/src/tool-runtime.ts |
Core orchestration with ToolRuntime class and ToolRuntimeInput interface |
packages/runtime/src/tool-availability.ts |
Per-step tool availability validation |
packages/runtime/src/sandbox-boundary-tool.ts |
Boundary protocol constants and types |
packages/core/src/durable-tool-result-projection.ts |
Durable result encoding utilities |
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_BYTESenforcement. - 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →