ToolRuntime in @maka/runtime: Core Responsibilities and Architecture Explained
The ToolRuntime in @maka/runtime serves as the central orchestrator that manages tool lifecycle, enforces safety boundaries, coordinates sandbox requests, limits concurrency, and ensures durable, observable execution of all tool calls within a Maka session.
The ToolRuntime class provides the Apache Maka platform with a deterministic, sandboxed environment for executing tools such as web-search, apply-patch, sub-agents, and user-question prompts. Defined in packages/runtime/src/tool-runtime.ts, this runtime acts as a single source of truth for tool semantics, handling everything from admission control to durable commit boundaries. Its architecture ensures that every tool invocation is authorized, recorded, and resource-bounded.
Turn and Lifecycle Management
The runtime creates a fresh ToolRuntime instance for each turn, encapsulating all state for that specific execution context. When a turn ends, the endTurn method (lines 669‑730) orchestrates a graceful shutdown sequence: it settles or rejects every outstanding sandbox-boundary request and user-question promise, awaits all in-flight tool settlements, and then invokes resetTurnState (lines 809‑833) to clear internal buffers before the next turn begins.
This lifecycle management prevents state leakage between turns and ensures that dangling asynchronous operations are properly cleaned up. The runtime maintains registries for pending operations, allowing it to force settlement when thresholds for denials or unresolved rounds are reached.
Tool Invocation Coordination
At the heart of the runtime lies the tool-settlement pipeline. The settleToolCall and settleToolCallRaw methods (starting at line 733) transform a concrete MakaTool implementation into a durable ToolResult. This process flows through performToolSettlement (lines 778‑795), which handles the actual execution and result formatting.
When tools fail, the runtime writes synthetic "tool_result" events via writeSyntheticToolResult (lines 916‑981) to ensure the session ledger remains complete. For successful executions, it emits standard events and records telemetry through recordToolInvocation. The runtime also enforces result-size limits by calling truncateToolOutput (around line 773) to prevent oversized payloads from reaching the model.
Admission Control and Gating
Before any tool executes, admitToolForStep (called around line 998) validates the request against admission policies. The runtime enforces exclusive-step and direct-only rules to control which tools may run in specific contexts. A loop-gate mechanism tracks identical failures via recordLoopGateOutcome (lines 884‑914) and blocks tools after a configurable threshold, preventing infinite retry loops.
Per-step ToolGating hides tools that are not searchable in the current step, allowing the runtime to dynamically restrict the tool palette based on execution context. This gating ensures that agents only access appropriate capabilities for their current task phase.
Sandbox Boundaries and User Questions
The runtime manages two distinct AwaitRegistry instances: one for sandbox-boundary requests and one for user-question answers. These registries track pending promises that require external resolution. The methods respondToSandboxBoundaryResponse (lines 639‑676) and respondToUserQuestion (lines 391‑408) provide the entry points for settling these requests when external systems or users provide responses.
This architecture allows the runtime to pause tool execution safely while awaiting human input or external API callbacks, maintaining clear separation between the tool execution context and the host environment.
Sub-Agent and Concurrency Limits
Resource exhaustion is prevented through hard concurrency caps. The runtime defines MAX_ACTIVE_SUBAGENT_TOOLS_PER_TURN and MAX_ACTIVE_CHILD_AGENT_RUNS_PER_TURN (lines 219‑221) to limit simultaneous executions. An AdmissionLimiter instance, initialized as childAgentRunLimiter (line 220), tracks active child-agent runs and rejects new requests when capacity is reached.
These limits ensure that a single session cannot spawn unbounded subprocesses, protecting both the host system and the model's context window from being overwhelmed by concurrent tool outputs.
Durable Tool Attempts and Commit Boundaries
When a durable commit sink is available, the runtime creates a DurableToolAttempt (lines 746‑754) to track the tool's outcome across T1 and T2 commit phases. This pattern assigns a durable operation ID to each tool call, allowing the system to persist results and recover from failures without re-executing expensive operations.
The runtime handles RuntimeCommitBoundaryError exceptions during this process, ensuring that commit failures are recorded in the session ledger and do not leave the execution state inconsistent.
Context Provision and Error Classification
Every running tool receives a rich execution context via the MakaToolContext interface (lines 191‑267). This context includes session IDs, abort signals, current working directory, permission mode, output emitters, child-agent spawners, and sandbox-boundary request helpers. The runtime constructs this context inside executeTool (around line 975), injecting all necessary dependencies while maintaining sandbox isolation.
Error handling leverages classifyError to categorize provider-side failures and truncateToolOutput to trim oversized results. The runtime formats standardized error messages, ensuring consistent behavior across different tool implementations and failure modes.
Implementation Examples
The following patterns demonstrate typical usage of the ToolRuntime class.
import { ToolRuntime, type ToolRuntimeInput } from '@maka/runtime/tool-runtime';
// Build a minimal ToolRuntimeInput (normally supplied by the backend)
const input: ToolRuntimeInput = {
sessionId: 'sess-123',
header: { /* … */ },
connection: { providerType: 'openai', /* … */ },
modelId: 'gpt-4o',
appendMessage: async (msg) => {/* forward to LLM */},
readExecutionBoundary: async () => ({ /* boundary data */}),
newId: () => crypto.randomUUID(),
now: () => Date.now(),
getPermissionPauseTarget: () => null,
turnId: 'turn-1',
};
// Create a runtime for the current turn
const runtime = new ToolRuntime(input);
// Define a simple tool
const echoTool: MakaTool = {
name: 'echo',
description: 'Returns the same string it receives.',
parameters: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] },
impl: ({ text }: { text: string }, ctx) => text,
};
// Execute the tool inside the turn
const call: ResolvedMakaToolCall = {
tool: echoTool,
turnId: input.turnId,
toolCallId: 'call-001',
input: { text: 'Hello, Maka!' },
abortSignal: new AbortController().signal,
eventSink: { push: () => {}, pushAndWaitUntilConsumed: async () => {} },
};
runtime.settleToolCall(call).then(({ result, modelOutput }) => {
console.log('Result:', result); // → Hello, Maka!
console.log('Model output:', modelOutput); // → { type: 'json', value: { text: 'Hello, Maka!' } }
});
Handling user questions requires tracking the pending request in the runtime's registry:
// Emit a user question via the runtime's message channel
runtime.input.appendMessage({
type: 'tool_call',
id: 'q-001',
turnId: input.turnId,
toolName: 'ask_user_question',
args: { question: 'What is your name?' },
});
// Later, resolve the pending promise
runtime.respondToUserQuestion({
requestId: 'q-001',
answers: ['Alice'],
});
Finalizing a turn ensures all pending operations settle:
// Settles any pending sandbox-boundary requests and awaits in-flight tools
await runtime.endTurn('completed');
Summary
The ToolRuntime in @maka/runtime centralizes tool execution management through eight core responsibility areas:
- Turn Lifecycle: Creates per-turn instances and ensures clean state resets via
endTurnandresetTurnState. - Invocation Coordination: Manages tool settlement pipelines, synthetic event writing, and telemetry recording.
- Admission Control: Enforces exclusive-step rules and loop-gate thresholds to prevent inappropriate or repetitive tool use.
- Sandbox Management: Maintains
AwaitRegistryinstances for boundary requests and user questions with dedicated response handlers. - Concurrency Limits: Applies
MAX_ACTIVE_SUBAGENT_TOOLS_PER_TURNandMAX_ACTIVE_CHILD_AGENT_RUNS_PER_TURNcaps throughAdmissionLimiter. - Durability: Creates
DurableToolAttemptrecords for T1/T2 commit phases when persistent sinks are available. - Context Injection: Builds comprehensive
MakaToolContextobjects containing session metadata, abort signals, and permission states. - Safety Boundaries: Classifies errors, truncates oversized outputs, and standardizes failure event formats.
Collectively, these mechanisms ensure every tool call is authorized, durably recorded, observable, and resource-bounded.
Frequently Asked Questions
How does ToolRuntime handle tool failures?
When a tool fails, the runtime captures the error through performToolSettlement and invokes writeSyntheticToolResult (lines 916‑981) to emit a structured "tool_result" event containing the failure reason. The runtime uses classifyError to categorize the failure type and applies truncateToolOutput to ensure error messages remain within size limits. This synthetic event writing guarantees that the session ledger remains complete even when tools throw exceptions or return invalid results.
What is the purpose of the loop-gate mechanism?
The loop-gate prevents infinite retry cycles by tracking identical failures via recordLoopGateOutcome (lines 884‑914). When a tool fails repeatedly with the same error pattern, the runtime blocks further admissions after a configurable threshold, forcing the agent to select alternative strategies. This protection is essential for autonomous systems that might otherwise spin on broken dependencies or invalid parameters.
How does ToolRuntime manage concurrent sub-agent executions?
The runtime enforces hard limits through constants defined at lines 219‑221: MAX_ACTIVE_SUBAGENT_TOOLS_PER_TURN and MAX_ACTIVE_CHILD_AGENT_RUNS_PER_TURN. An AdmissionLimiter instance named childAgentRunLimiter (line 220) tracks active child processes. When a new sub-agent request exceeds these limits, the runtime rejects the admission before spawning the process, preventing resource exhaustion and context window overflow.
What is the difference between sandbox-boundary requests and user questions?
Sandbox-boundary requests are asynchronous operations requiring external system interaction (such as file system access or network calls) managed through respondToSandboxBoundaryResponse (lines 639‑676). User questions are explicit prompts for human input handled via respondToUserQuestion (lines 391‑408). While both use AwaitRegistry for promise management, sandbox boundaries enforce permission checks and denial counting, whereas user questions typically block execution until the user provides an answer through the UI.
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 →