Series 2 - Bài 3: Tool Registry Pattern

Tool Registry là pattern giữ cho agent core không bị ô nhiễm bởi implementation của từng tool. Bài này thiết kế một registry tập trung, pluggable và type-safe.

4 phút đọc
Tập này đang được chuẩn bị, quay lại sau nhé.

Không có registry, tool system thường kết thúc thành một giant switch statement trong Agent Core. Thêm tool mới = sửa Agent Core. Xóa tool = sửa Agent Core. Test một tool = phải bootstrap toàn bộ agent.

Registry pattern giải quyết đó bằng cách tách biệt hoàn toàn tool implementation khỏi orchestration logic.

Vấn đề không có Registry

Anti-pattern: Giant switch trong Agent Core

class AgentCore {
  async executeTool(toolUse) {
    switch (toolUse.name) {
      case "read_file":
        return await fs.readFile(toolUse.input.path);
      case "write_file":
        return await fs.writeFile(toolUse.input.path, toolUse.input.content);
      case "run_command":
        return await exec(toolUse.input.command);
      case "search_files":
        return await glob(toolUse.input.pattern);
      // ... thêm mới = sửa file này
    }
  }
}

Problems:
  ✕ Agent Core biết quá nhiều về implementation
  ✕ Mỗi tool mới = sửa Agent Core
  ✕ Không thể test tool độc lập
  ✕ Tool definitions và implementation nằm rải rác

Tool Interface: Contract chung

Tất cả tools implement cùng một interface:

Tool Interface:

  Metadata:
    name: string           ← unique identifier
    description: string    ← LLM đọc để biết khi nào dùng
    input_schema: Schema   ← LLM đọc để biết truyền gì
    requires_confirm: bool ← có cần user confirm không

  Execution:
    execute(input) → ToolResult
      │
      ├─ Success: { ok: true, output: string }
      └─ Failure: { ok: false, error: string, recoverable: bool }

recoverable là property quan trọng: nó cho Dispatcher biết nên trả error về LLM để retry, hay nên stop loop và escalate cho user.

Registry: Tập trung, không centralize logic

Tool Registry:

┌────────────────────────────────────────────────────────────┐
│   Map<string, Tool>                                       │
│                                                          │
│   register(tool: Tool): void                             │
│     └─ Kiểm tra name duplicate                           │
│     └─ Validate schema format                           │
│     └─ Add vào map                                     │
│                                                          │
│   get(name: string): Tool | undefined                    │
│     └─ Lookup từ map                                    │
│     └─ Return undefined nếu không tồn tại               │
│                                                          │
│   getDefinitions(): ToolDefinition[]                     │
│     └─ Return array của tất cả { name, description,    │
│          input_schema }                                  │
│     └─ Đây là thứ gửi cho LLM trong mỗi request      │
└────────────────────────────────────────────────────────────┘

Registry không chứa logic. Nó chỉ là map từ name đến tool instance.

Tool Groups: Tổ chức tools theo domain

Với nhiều tools, tổ chức thành groups:

 Registry
    │
    ├── FileTools group
    │     read_file
    │     write_file
    │     list_directory
    │     delete_file
    │
    ├── ShellTools group
    │     run_command
    │     get_env
    │
    ├── SearchTools group
    │     search_files
    │     grep_content
    │     find_symbol
    │
    └── Custom tools (project-specific)
          get_linear_issues
          call_cloudflare_api

Groups cho phép register/unregister theo batch, và enable/disable theo config.

Dynamic Registration: Tool có thể thêm runtime

Một điểm mạnh của Registry là tools có thể được register dynamically:

Use cases:

Project-specific tools:
  Agent start trong project A → load tools từ /project-a/tools/
  Agent start trong project B → load tools từ /project-b/tools/
  Core tools giống nhau, project tools khác nhau

Permission-based tools:
  User không có write permission → chỉ register read tools
  User là admin → register cả write và delete tools

Feature flags:
  Experimental tool X → chỉ register khi flag enable
  Core tools luôn có mặt

Plugin Pattern: Tools từ external sources

Mở rộng hơn: tools có thể load từ:

Built-in tools:
  Compiled vào binary
  Always available

Project tools (AGENT.md định nghĩa):
  Load từ /project-root/.agent/tools/
  Mỗi file = một tool
  Auto-register khi agent start

Remote tools (advanced):
  Load từ MCP server
  Register vào cùng registry
  Agent không phân biệt built-in vs remote

Agent Core không biết tool đến từ đâu — nó chỉ biết tên và interface. Đây là sức mạnh của Registry.

getDefinitions(): Bridge đến LLM

getDefinitions() là method quan trọng nhất của Registry. Nó tạo ra array mà bạn gửi cho LLM trong mỗi request:

Registry.getDefinitions() → [
  {
    name: "read_file",
    description: "...",
    input_schema: { ... }
  },
  {
    name: "write_file",
    description: "...",
    input_schema: { ... }
  },
  // ... all registered tools
]

Sử dụng trong LLMClient:
  POST /v1/messages
  {
    messages: [...],
    tools: registry.getDefinitions()  ← đây
  }

LLM thấy tôi đây. Nó đọc tất cả tool definitions mỗi request và quyết định dùng cái nào.

Implication: Nếu bạn muốn giấu tool khỏi LLM (ví dụ tool chỉ dùng cho internal), không register nó. LLM chỉ biết những gì Registry expose.

Trade-off: Dynamic Registry và Type Safety

Dynamic registry advantages:
  + Flexible: thêm tool không cần sửa core
  + Pluggable: load tools từ nhiều sources
  + Testable: mock individual tools dễ dàng

Dynamic registry disadvantages:
  - Runtime errors thay vì compile-time
  - Tool không tồn tại được phát hiện lúc runtime
  - Type system không giúp nhiều

Mitigation:
  - Validate registry ở startup (fail fast)
  - Test với fixture tool definitions
  - Log unknown tool call attempts rõ ràng

Kết luận

  • Registry pattern tách tool implementation khỏi Agent Core bằng một map name → tool instance.
  • Tất cả tools implement cùng một interface: metadata + execute method.
  • getDefinitions() là bridge giữa Registry và LLM — LLM chỉ thấy tools được expose.
  • Dynamic registration cho phép project-specific tools, permission-based tools, và feature flags.
  • Trade-off: flexibility và pluggability đổi lấy runtime errors thay vì compile-time — nên validate ở startup.