Unified Contract: Every Tool Plays by the Same Rules

What is a "contract"? Imagine you're hiring a courier (a tool). Whether it's FedEx or UPS, as the sender you have the same expectations: tell me if it can deliver, have me sign the right authorization before delivery, update me on progress in real time, tell me the result when done. These expectations are the "contract" — it defines the rights and obligations of both parties. The employer (Agent Loop) doesn't need to know how each courier company is internally organized; it just calls according to the contract.

src/Tool.ts defines the foundational type for the entire tool system. Every tool — whether the built-in BashTool or a user-connected MCP tool — must implement this interface.

The core of the interface is the buildTool() function:

// src/Tool.ts
export function buildTool<D extends AnyToolDef>(def: D): BuiltTool<D> {
  return {
    ...TOOL_DEFAULTS,
    userFacingName: () => def.name,
    ...def,
  } as BuiltTool<D>
}

buildTool() acts like a factory function: tool developers only provide the parts that differ; the rest is filled in by TOOL_DEFAULTS. The defaults follow the fail-closed principle:

// src/Tool.ts
const TOOL_DEFAULTS = {
  isEnabled: () => true,
  isConcurrencySafe: (_input?) => false,  // Default: no concurrency
  isReadOnly: (_input?) => false,          // Default: assumes writes
  isDestructive: (_input?) => false,       // Default: assumes reversible
  checkPermissions: (input, _ctx?) =>
    Promise.resolve({ behavior: 'allow', updatedInput: input }),  // Default: allow
  toAutoClassifierInput: (_input?) => '',  // Default: not part of safety classification
  userFacingName: (_input?) => '',
}

The design is deliberately cautious: isConcurrencySafe defaults to false, meaning new tools disallow concurrent execution by default. A tool must explicitly declare itself concurrency-safe before the Agent Loop will run it in parallel.

What does "explicit declaration" mean? The tool developer actively writes isConcurrencySafe: () => true in the code, telling the system "I support concurrency." Compare with implicit — writing nothing, so the system takes the default value:

Implicit (write nothing)Explicit (active declaration)
Code formOmit isConcurrencySafe fieldWrite isConcurrencySafe: () => true
System behaviorTakes TOOL_DEFAULTS default of false — no concurrencySystem knows this tool can run alongside others
Typical exampleNew tool just created, developer unsure if it's safeGlobTool, GrepTool (read-only searches, naturally concurrency-safe)

This embodies the "fail-closed" principle: it's better to conservatively serialize execution than to risk data races from concurrency. Only when a developer stands up and says "I'm fine" does the system allow concurrency.