Background
Tools today are atomic, independent units. Each execute() call is a black box. There is no mechanism to:
- Chain tools (
read → validate → write as a single user-confirmed unit)
- Wrap a tool with retry, timeout, or fallback logic
- Share intermediate state between tool calls in the same logical operation
This becomes a real limitation for operations where the LLM must perform multiple tool calls to complete one logical action. For example, an "atomic refactor" requires: read file → apply transform → run type check → write file. Today this requires 4 separate LLM-visible round trips, each with its own confirmation prompt. The user must approve each step independently; there is no way to say "do the whole refactor atomically."
Proposed design
Extend the Tool interface to support composition as a future-facing but non-breaking addition:
export interface Tool {
name: string;
description: string;
parameters: JSONSchema;
readonly?: boolean;
execute: (params: Record<string, unknown>, ctx?: ToolExecutionContext) => Promise<ToolResult>;
requiresConfirmation?: (args: Record<string, unknown>) => boolean;
// Future composition fields (optional — existing tools unaffected)
composedOf?: string[]; // names of sub-tools this delegates to
atomic?: boolean; // true = confirm once for the entire composed operation
}
export interface ToolExecutionContext {
registry: ToolRegistry; // access to other tools for delegation
tmpDir?: string;
}
A composed tool declares its sub-tools via composedOf. The executor confirms the composed tool once (if atomic: true), then grants its sub-tools implicit confirmation for the duration of the composed call.
Example — an atomic_edit tool that reads, validates, and writes as one confirmed unit:
{
name: "atomic_edit",
composedOf: ["read", "edit"],
atomic: true,
requiresConfirmation: () => true,
execute: async (params, ctx) => {
const current = await ctx.registry.execute("read", { file_path: params.file_path });
// ... validate, transform ...
return ctx.registry.execute("edit", { file_path: params.file_path, old_string: ..., new_string: ... });
}
}
Why design this now
The Tool interface is the lowest-level contract in the system. Retrofitting composition later requires changing every tool's signature. Adding optional fields now (composedOf, atomic, ctx parameter) costs nothing and establishes the interface without breaking existing tools.
Acceptance criteria
Location
src/tools/base.ts — Tool interface
src/tools/registry.ts — execute() method
src/agent/executor.ts — confirmation flow
Background
Tools today are atomic, independent units. Each
execute()call is a black box. There is no mechanism to:read → validate → writeas a single user-confirmed unit)This becomes a real limitation for operations where the LLM must perform multiple tool calls to complete one logical action. For example, an "atomic refactor" requires: read file → apply transform → run type check → write file. Today this requires 4 separate LLM-visible round trips, each with its own confirmation prompt. The user must approve each step independently; there is no way to say "do the whole refactor atomically."
Proposed design
Extend the
Toolinterface to support composition as a future-facing but non-breaking addition:A composed tool declares its sub-tools via
composedOf. The executor confirms the composed tool once (ifatomic: true), then grants its sub-tools implicit confirmation for the duration of the composed call.Example — an
atomic_edittool that reads, validates, and writes as one confirmed unit:Why design this now
The
Toolinterface is the lowest-level contract in the system. Retrofitting composition later requires changing every tool's signature. Adding optional fields now (composedOf,atomic,ctxparameter) costs nothing and establishes the interface without breaking existing tools.Acceptance criteria
Toolinterface gains optionalcomposedOf,atomicfields andctxparameter onexecuteToolExecutionContexttype is defined withregistryandtmpDirToolRegistry.execute()passes aToolExecutionContextto each toolatomic_edit)atomic: truetools fires once, not once per sub-toolLocation
src/tools/base.ts—Toolinterfacesrc/tools/registry.ts—execute()methodsrc/agent/executor.ts— confirmation flow