How Mako Runtime Implements Bash, Read, and Write Built-In Tools
Mako’s runtime registers Bash, Read, and Write as MakaTool objects via the buildBuiltinTools factory in packages/runtime/src/builtin-tools.ts, where each tool executes within sandboxed filesystem and shell boundaries enforced by the active execution context.
The Apache Mako project provides a runtime environment for LLM-driven agents that interact with the filesystem and shell through strictly defined built-in tools. Understanding how Mako implements Bash, Read, and Write at the runtime level reveals the architecture behind its secure, sandboxed execution model. This article examines the source code in apache/maka to explain the factory pattern, schema validation, and security boundaries that govern these core utilities.
Tool Registration Architecture
The runtime initializes its built-in toolkit through the buildBuiltinTools function located at [lines 94-122 of packages/runtime/src/builtin-tools.ts](https://github.com/apache/maka/blob/main/packages/runtime/src/builtin-tools.ts#L94-L122). This factory creates and returns an array of MakaTool objects, always including the three core file-system tools—Read, Write, and Edit—alongside the Bash command tool.
import { buildBuiltinTools } from '@maka/runtime';
const tools = buildBuiltinTools({
permissionProfile: myPermissionProfile,
});
The function wires in auxiliary services—including the workspace executor, sandbox manager, and permission profile—ensuring every tool invocation runs inside a controlled execution boundary. Each tool conforms to the MakaTool interface, which describes its metadata, Zod-based input schema, and asynchronous implementation.
Read Tool Implementation
The Read tool, defined at lines 350-449, serves a dual purpose: reading text files from the sandboxed filesystem or retrieving whole runtime resources referenced by a ref identifier.
Input Schema and Validation
The tool accepts a Zod schema that enforces mutual exclusivity between a path property (with optional offset and limit parameters) and a ref property. The schema is pre-processed to ensure only one input type is provided, then exposed to the provider as a JSON schema.
Execution Logic
When invoked, the implementation branches based on the input type:
- Resource References: If a
refis supplied, the tool resolves the reference viaruntimeResourcesorattachmentResources. - Filesystem Paths: If a
pathis supplied, the request forwards to the sandboxed filesystem worker throughfilesystem.execute({ kind: 'read', … }).
The result can be a plain text payload, an image snapshot handled via snapshotImage, or an error wrapped by internalFilesystemReadFailure.
await tools.find(t => t.name === 'Read')!.impl({
path: 'docs/README.md',
offset: 0,
limit: 20,
}, {
cwd: '/home/user/project',
sessionId: 'sess-123',
abortSignal: new AbortController().signal,
});
Write Tool Implementation
The Write tool, found at lines 450-473, persists string content to a specified file path within the sandboxed environment.
Schema and Operation
The tool uses a simple Zod object schema requiring { path: string, content: string }. The implementation calls the filesystem worker with operation: { kind: 'write', path, content } via the shared filesystem.execute helper at lines 635-645.
Result Handling
On success, the tool returns either a file diff (kind: 'file_diff') or a plain write acknowledgment (kind: 'file_write'). Errors are captured and wrapped by internalFilesystemWriteFailure to ensure consistent error reporting to the LLM.
await tools.find(t => t.name === 'Write')!.impl({
path: 'tmp/out.txt',
content: 'Hello, Mako!',
}, {
cwd: '/home/user/project',
sessionId: 'sess-123',
abortSignal: new AbortController().signal,
});
Bash Tool Implementation
The Bash tool provides arbitrary shell command execution within the session’s current working directory. Its construction and execution logic spans lines 494-511 for tool creation and lines 558-750 for the executor implementation.
Tool Construction Strategy
The runtime selects between two construction strategies:
- Managed Bash: Built via
buildManagedBashToolwhen a customShellRunLauncheris supplied. - Simple Executor: Built via
buildExecutorBashToolfor direct executor-driven execution.
Schema and Validation
The tool schema combines bashBoundaryIntentSchema and sandboxBoundaryExpansionSchema to express sandbox-boundary intent and optional timeout parameters.
Execution Flow
Before execution, the command undergoes validation via throwIfShellSetupFailed and potential transformation by the sandbox manager through sandboxCommand. The runtime then delegates execution to executor.exec, which spawns the command using the detected defaultShellPlan. Results are wrapped by shapeTerminalResult and returned as a TerminalResult object.
await tools.find(t => t.name === 'Bash')!.impl({
command: 'ls -l *.ts',
timeout_ms: 30_000,
}, {
cwd: '/home/user/project',
sessionId: 'sess-123',
abortSignal: new AbortController().signal,
emitOutput: (output) => console.log(output),
});
Sandbox Boundaries and Security
All three tools operate within the active execution boundary (ctx.executionBoundary) enforced by the filesystem.execute method. For Bash commands, additional sandbox transformations occur via sandboxCommand, which may rewrite commands, adjust environment variables, or reject execution if the permission profile demands an unavailable sandbox configuration. This architecture ensures that filesystem and shell operations remain confined to the permitted workspace scope.
Model Output Conversion
Both Read and Write expose a toModelOutput hook that maps raw tool results into LLM-compatible formats. The Bash tool utilizes conversion logic in [bash-model-output.ts](https://github.com/apache/maka/blob/main/packages/runtime/src/bash-model-output.ts), while file operations rely on [file-tool-model-output.ts](https://github.com/apache/maka/blob/main/packages/runtime/src/file-tool-model-output.ts). These hooks transform internal results—such as TerminalResult or file diff objects—into deterministic, JSON-compatible structures via functions like bashToolResultToModelOutput and fileWriteToolResultToModelOutput.
Summary
- Central Registry: Mako defines all built-in tools in
packages/runtime/src/builtin-tools.tsvia thebuildBuiltinToolsfactory (lines 94-122), which returnsMakaToolobjects for Read, Write, and Bash. - Schema Enforcement: Each tool uses Zod schemas to validate inputs, with Read supporting mutually exclusive
pathorrefparameters, while Write requirespathandcontent. - Sandboxed Execution: Filesystem operations route through
filesystem.execute(lines 635-645) with active permission profiles, while Bash commands undergosandboxCommandtransformation beforeexecutor.execspawns them. - Result Normalization: Tools convert raw results to LLM-friendly formats via
toModelOutputhooks located in dedicated model-output modules.
Frequently Asked Questions
How does the Mako runtime register its built-in tools?
The runtime calls the buildBuiltinTools factory function in packages/runtime/src/builtin-tools.ts (lines 94-122), which instantiates MakaTool objects for Read, Write, Edit, and Bash. This factory injects required services like the sandbox manager and permission profile to ensure controlled execution boundaries.
What security mechanisms protect Bash tool execution?
The Bash tool implements multiple security layers: input validation via throwIfShellSetupFailed, command transformation through sandboxCommand, and execution confinement within the ctx.executionBoundary. The tool also checks against the active permission profile and may reject commands requiring unsupported sandbox features.
How does the Read tool differentiate between filesystem paths and runtime resources?
The Read tool uses a Zod schema enforced at lines 350-449 that accepts either a path string or a ref identifier, but never both simultaneously. When ref is provided, the tool queries runtimeResources or attachmentResources; when path is provided, it delegates to the sandboxed filesystem.execute method.
What determines the output format returned to the LLM model?
The output format is determined by the toModelOutput hook implemented on each tool. Read and Write use converters in file-tool-model-output.ts, while Bash uses bash-model-output.ts. These hooks transform internal results into deterministic, JSON-compatible structures via functions like bashToolResultToModelOutput.
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 →