# How Tools Are Registered and Loaded in OpenClaude: A Complete Developer Guide

> Discover how OpenClaude dynamically registers and loads tools from src/tools/ at startup. Learn about the ToolRegistry and result association for developers.

- Repository: [Gitlawb/openclaude](https://github.com/Gitlawb/openclaude)
- Tags: developer-guide
- Published: 2026-09-05

---

**OpenClaude registers tools dynamically by scanning the `src/tools/` directory at startup, loading each tool class into a central ToolRegistry, and associating tool-use results with unique IDs during execution via `registerToolResult` in the micro-compact service.**

OpenClaude is a modular CLI framework where every capability—from sending messages to running verification agents—is implemented as a **tool**. Understanding how these tools are registered and loaded is essential for extending the system or debugging execution flows. This guide walks through the complete lifecycle based on the OpenClaude source code.

---

## Tool Architecture Overview

OpenClaude's tool system rests on three core abstractions:

1. **Tool** – an interface requiring `name`, `description`, `parameters` (JSON Schema), and an async `run()` method
2. **ToolRegistry** – a global registry that maps tool names to their implementing classes
3. **AgentTool** – a specialized tool type that bundles multiple tools into reusable agents

Each tool lives as a standalone module under `src/tools/`, making the system inherently extensible.

---

## Stage 1: Tool Definition

Developers create tools by implementing the Tool interface in a dedicated file. The `SendMessageTool` exemplifies this pattern.

In [`src/tools/SendMessageTool/SendMessageTool.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/tools/SendMessageTool/SendMessageTool.ts), a tool defines its contract:

```typescript
import { Tool, ToolResult } from '../../utils/tool-types';

export default class SendMessageTool implements Tool {
  name = 'send_message';
  description = 'Sends a message to a specified recipient.';
  parameters = {
    type: 'object',
    properties: {
      recipient: { type: 'string' },
      content: { type: 'string' }
    },
    required: ['recipient', 'content']
  };

  async run(args: { recipient: string; content: string }): Promise<ToolResult> {
    // Implementation logic
    return { output: `Message sent to ${args.recipient}` };
  }
}

```

**Key requirements:**
- Default export of the tool class
- Unique `name` property used for registry lookup
- JSON Schema compliance in `parameters` for LLM compatibility

---

## Stage 2: Tool Loading at Startup

OpenClaude discovers and loads tools through the **AgentTool subsystem**. The entry point is [`src/tools/AgentTool/loadAgentsDir.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/tools/AgentTool/loadAgentsDir.ts).

The `loadAgentsDir` function performs filesystem crawling and dynamic imports:

```typescript
import { loadAgentsDir } from '../tools/AgentTool/loadAgentsDir';

async function initializeToolRegistry() {
  // Scans src/tools/AgentTool/built-in/ and registers each export
  await loadAgentsDir();
  // Registry now contains all built-in agents and their tools
}

```

This process:
- Walks `src/tools/AgentTool/built-in/` for built-in agents
- Accepts additional user-provided agent directories via configuration
- Dynamically imports each module using `import()`
- Registers discovered tools with the global **ToolRegistry**

Built-in agents like [`verificationAgent.ts`](https://github.com/Gitlawb/openclaude/blob/main/verificationAgent.ts) demonstrate how agents bundle related tools for complex workflows.

---

## Stage 3: Tool Registration During Execution

When the LLM generates a `tool_use` block, the **micro-compact executor** handles registration. The critical functions reside in [`src/services/compact/cachedMicrocompact.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/compact/cachedMicrocompact.ts).

The execution flow uses `registerToolResult` to persist outcomes:

```typescript
import { registerToolResult } from '../../services/compact/cachedMicrocompact';

async function handleToolUse(state: CompactState, block: ToolUseBlock) {
  // block.tool_use_id: unique identifier from LLM plan
  // block.name: tool name to look up
  const tool = toolRegistry.get(block.name);
  
  if (!tool) {
    throw new Error(`Unknown tool: ${block.name}`);
  }
  
  const result = await tool.run(block.input);
  
  // Associate result with the tool-use ID for later retrieval
  registerToolResult(state, block.tool_use_id, result);
}

```

**Two registration helpers exist:**
- `registerToolResult(state, tool_use_id, result)` – stores execution output
- `registerToolMessage(state, tool_use_id, message)` – stores intermediate messages

This stateful registration enables multi-step plans where later steps reference earlier tool outputs via their `tool_use_id`.

---

## Complete Runtime Flow

```

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   CLI Startup   │────▶│  loadAgentsDir  │────▶│  ToolRegistry   │
│                 │     │  (src/tools/)   │     │  (name→class)   │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                                         │
┌─────────────────┐     ┌─────────────────┐              │
│  LLM generates  │────▶│  micro-compact  │──────────────┘
│  tool_use block │     │    executor     │
└─────────────────┘     └─────────────────┘
                               │
                               ▼
                        ┌─────────────────┐
                        │  tool.run()     │
                        │  registerTool   │
                        │  Result(state)  │
                        └─────────────────┘

```

---

## Extending the Tool System

To add a custom tool:

1. **Create the implementation** in [`src/tools/YourTool/YourTool.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/tools/YourTool/YourTool.ts)
2. **Export as default** – `export default class YourTool`
3. **Rebuild** – `loadAgentsDir` picks up new tools automatically on restart

For agent-level bundling, place related tools in a subdirectory under `src/tools/AgentTool/built-in/` and export them from an index file.

---

## Summary

- **Tools implement** a strict interface with `name`, `description`, `parameters`, and `run()` in `src/tools/`
- **Discovery happens** via `loadAgentsDir()` in [`src/tools/AgentTool/loadAgentsDir.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/tools/AgentTool/loadAgentsDir.ts), which crawls directories and populates the global ToolRegistry
- **Runtime registration** uses `registerToolResult()` in [`src/services/compact/cachedMicrocompact.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/compact/cachedMicrocompact.ts) to associate tool-use IDs with execution results
- **Extension requires** only adding new files to `src/tools/`—no registry edits needed

---

## Frequently Asked Questions

### What is the ToolRegistry in OpenClaude?

The **ToolRegistry** is a global mapping structure that associates tool names (strings) with their implementing class constructors. It enables runtime lookup when the micro-compact executor processes a `tool_use` block from the LLM. The registry is populated during startup by `loadAgentsDir()` and consulted during execution via `toolRegistry.get(name)`.

### How does OpenClaude handle unknown tool names?

If `toolRegistry.get(block.name)` returns undefined, the executor throws an error indicating an unknown tool. This typically occurs when a tool is referenced in an LLM plan but not properly exported from a file in `src/tools/` or not included in the scanned directories.

### Can I register tools dynamically without restarting OpenClaude?

No. OpenClaude's current architecture loads and registers tools once at startup through `loadAgentsDir()`. Dynamic reloading would require modifying the executor to re-scan directories or implementing a file watcher that triggers registry updates.

### Where is the tool result actually stored during execution?

Tool results are stored in the **compact state** object maintained by the micro-compact executor. The `registerToolResult()` function in [`src/services/compact/cachedMicrocompact.ts`](https://github.com/Gitlawb/openclaude/blob/main/src/services/compact/cachedMicrocompact.ts) writes to this state, using the `tool_use_id` as the lookup key. This state persists across execution steps in a single plan.