askill
agent-tool-creator

agent-tool-creatorSafety 90Repository

Guide for implementing new agent tools for ClosedClaw's AI assistant. Use when adding capabilities like web search, file operations, API integrations, or custom actions. Covers tool factory pattern, parameter validation, registration, and testing.

0 stars
1.2k downloads
Updated 2/15/2026

Package Files

Loading files...
SKILL.md

Agent Tool Creator

This skill helps you create new tools that extend ClosedClaw's AI agent capabilities. Tools are implemented as factory functions that return AnyAgentTool objects and are registered in tool assembly modules.

When to Use

  • Adding new agent capabilities (web search, file ops, API calls)
  • Implementing custom tool logic
  • Understanding tool factory patterns
  • Setting up tool parameters and validation

Prerequisites

  • Review existing tools in src/agents/tools/
  • Understand AnyAgentTool type from src/agents/tools/common.ts
  • Familiarize yourself with tool registration in src/agents/openclaw-tools.ts

Step-by-Step Workflow

1. Create Tool File

Create src/agents/tools/my-tool.ts:

import type { ClosedClawConfig } from "../../config/config.js";
import { type AnyAgentTool, jsonResult, readStringParam } from "./common.js";

export type MyToolOptions = {
  config?: ClosedClawConfig;
  agentDir?: string;
  sandboxed?: boolean;
};

export function createMyTool(options?: MyToolOptions): AnyAgentTool {
  return {
    name: "my_tool",
    description:
      "Clear, concise description of what this tool does and when to use it. The AI uses this to decide when to invoke the tool.",
    parameters: {
      input: {
        type: "string",
        description: "Description of the input parameter",
        required: true,
      },
      mode: {
        type: "string",
        description: "Optional mode parameter",
        required: false,
      },
    },
    handler: async (params) => {
      // Extract and validate parameters
      const input = readStringParam(params, "input", { required: true });
      const mode = readStringParam(params, "mode") ?? "default";

      // Implement tool logic
      try {
        const result = await processInput(input, mode);

        // Return result
        return jsonResult({
          success: true,
          data: result,
        });
      } catch (error) {
        // Handle errors gracefully
        return jsonResult({
          success: false,
          error: error instanceof Error ? error.message : String(error),
        });
      }
    },
  };
}

async function processInput(input: string, mode: string): Promise<unknown> {
  // Implementation
  return { processed: input, mode };
}

2. Parameter Helpers

Use helpers from src/agents/tools/common.ts:

// String parameters
const required = readStringParam(params, "key", { required: true });
const optional = readStringParam(params, "key"); // Returns undefined if not present
const trimmed = readStringParam(params, "key", { trim: true }); // Default behavior
const raw = readStringParam(params, "key", { trim: false });
const allowEmpty = readStringParam(params, "key", { allowEmpty: true });

// String or number parameters
const value = readStringOrNumberParam(params, "key", { required: true });

// Boolean parameters
const enabled = readBooleanParam(params, "enabled", { defaultValue: true });

// Number parameters
const count = readNumberParam(params, "count", { required: true });
const bounded = readNumberParam(params, "count", { min: 0, max: 100 });

// Array parameters
const items = readArrayParam(params, "items", { required: true });
const numbers = readNumberArrayParam(params, "numbers");

3. Result Helpers

Use result helpers for consistent output:

// JSON result
return jsonResult({ key: "value" });

// Text result
return textResult("Plain text response");

// Image result (from file)
return imageResultFromFile({
  path: "/path/to/image.png",
  mimeType: "image/png",
});

// Image result (from data)
return imageResult({
  data: buffer,
  mimeType: "image/png",
});

4. Register Tool

Add to appropriate assembly module:

For OpenClaw tools (src/agents/openclaw-tools.ts):

import { createMyTool } from "./tools/my-tool.js";

export function createClosedClawTools(options?: {
  config?: ClosedClawConfig;
  agentDir?: string;
  // ... other options
}): AnyAgentTool[] {
  const tools: AnyAgentTool[] = [
    // ... existing tools
    createMyTool({
      config: options?.config,
      agentDir: options?.agentDir,
    }),
  ];

  return tools;
}

For Bash tools (src/agents/bash-tools.ts):

import { createMyTool } from "./tools/my-tool.js";

export function createBashTools(options?: {
  config?: ClosedClawConfig;
  // ... other options
}): AnyAgentTool[] {
  return [
    // ... existing tools
    createMyTool(options),
  ];
}

5. Create Tests

Create src/agents/tools/my-tool.test.ts:

import { describe, it, expect, vi } from "vitest";
import { createMyTool } from "./my-tool.js";

describe("createMyTool", () => {
  it("processes input successfully", async () => {
    const tool = createMyTool();

    const result = await tool.handler({
      input: "test input",
      mode: "fast",
    });

    expect(result).toEqual({
      type: "json",
      json: {
        success: true,
        data: expect.objectContaining({
          processed: "test input",
          mode: "fast",
        }),
      },
    });
  });

  it("requires input parameter", async () => {
    const tool = createMyTool();

    await expect(tool.handler({})).rejects.toThrow(/input required/i);
  });

  it("handles errors gracefully", async () => {
    const tool = createMyTool();

    const result = await tool.handler({
      input: "invalid-input-that-causes-error",
    });

    expect(result).toMatchObject({
      type: "json",
      json: {
        success: false,
        error: expect.any(String),
      },
    });
  });

  it("uses default mode when not specified", async () => {
    const tool = createMyTool();

    const result = await tool.handler({ input: "test" });

    expect(result).toMatchObject({
      type: "json",
      json: {
        data: expect.objectContaining({ mode: "default" }),
      },
    });
  });
});

6. Add Documentation

Create docs/tools/my-tool.md:

# My Tool

Description of what the tool does and when to use it.

## Parameters

- `input` (string, required): Description of input
- `mode` (string, optional): Description of mode options

## Examples

### Basic Usage

\`\`\`typescript
const tool = createMyTool();
const result = await tool.handler({ input: "test" });
\`\`\`

### With Options

\`\`\`typescript
const tool = createMyTool({
config: myConfig,
agentDir: "/path/to/agent",
});
\`\`\`

## Configuration

Respects these config options:

- `myTool.enabled`: Enable/disable tool
- `myTool.mode`: Default mode

## Error Handling

Returns errors in result object:
\`\`\`json
{
"success": false,
"error": "Error message"
}
\`\`\`

7. Test Integration

# Run tool tests
pnpm test -- src/agents/tools/my-tool.test.ts

# Run with specific test case
pnpm test -- src/agents/tools/my-tool.test.ts -t "processes input"

# Watch mode during development
pnpm test:watch -- src/agents/tools/my-tool.test.ts

# Full test suite
pnpm build && pnpm check && pnpm test

Common Patterns

Config-Based Gating

export function createMyTool(options?: MyToolOptions): AnyAgentTool | null {
  const config = options?.config;

  // Return null to disable tool
  if (!config?.myTool?.enabled) {
    return null;
  }

  return {
    name: "my_tool",
    // ... tool implementation
  };
}

Action Gating

import { createActionGate } from "./common.js";

export function createMyTool(options?: MyToolOptions): AnyAgentTool {
  const actions = options?.config?.myTool?.actions;
  const can = createActionGate(actions);

  return {
    name: "my_tool",
    handler: async (params) => {
      if (!can("perform_action", true)) {
        return jsonResult({ error: "Action not allowed" });
      }
      // ... implementation
    },
  };
}

File Operations

import fs from "node:fs/promises";
import path from "node:path";

// Safe file reading
const content = await fs.readFile(filePath, "utf-8");

// Safe file writing with temp + atomic rename
const tempPath = `${targetPath}.tmp`;
await fs.writeFile(tempPath, content, "utf-8");
await fs.rename(tempPath, targetPath);

// Directory operations
await fs.mkdir(dirPath, { recursive: true });
const files = await fs.readdir(dirPath);

Sandbox Safety

export function createMyTool(options?: MyToolOptions): AnyAgentTool {
  const sandboxed = options?.sandboxed ?? false;

  return {
    name: "my_tool",
    handler: async (params) => {
      if (sandboxed) {
        // Restrict operations in sandbox mode
        return jsonResult({ error: "Not available in sandbox" });
      }
      // ... normal implementation
    },
  };
}

Async Operations

// Use AbortSignal for cancellation
handler: async (params, context) => {
  const controller = new AbortController();

  try {
    const result = await fetch(url, { signal: controller.signal });
    return jsonResult({ data: await result.json() });
  } catch (error) {
    if (error.name === "AbortError") {
      return jsonResult({ error: "Operation cancelled" });
    }
    throw error;
  }
};

Tool Types and Use Cases

Information Retrieval

  • Web search, API queries, database lookups
  • Return jsonResult() with structured data
  • Example: web-search.ts, web-fetch.ts

Action/Command

  • File operations, system commands, external services
  • Return jsonResult() with success/error status
  • Example: bash-tools.exec.ts, message-tool.ts

Transformation

  • Data processing, format conversion, analysis
  • Return jsonResult() or textResult()
  • Example: image-tool.ts, canvas-tool.ts

Session Management

  • Agent spawning, session control, state management
  • Return jsonResult() with session info
  • Example: sessions-spawn-tool.ts, sessions-send-tool.ts

Reference Implementations

  • Simple tool: src/agents/tools/agents-list-tool.ts
  • Config gating: src/agents/tools/memory-tool.ts
  • Action gating: src/agents/tools/web-tools.ts
  • File operations: src/agents/tools/image-tool.ts
  • External API: src/agents/tools/web-fetch.ts
  • Session management: src/agents/tools/sessions-spawn-tool.ts

Checklist

  • Tool file created (src/agents/tools/my-tool.ts)
  • Factory function implements AnyAgentTool return type
  • Clear description explains when to use tool
  • Parameters defined with descriptions and required flags
  • Parameter validation uses helper functions
  • Handler implements error handling
  • Results use appropriate helper (jsonResult, textResult, etc.)
  • Tool registered in assembly module (openclaw-tools.ts or bash-tools.ts)
  • Test file created (src/agents/tools/my-tool.test.ts)
  • Tests cover success, errors, edge cases
  • Tests verify parameter validation
  • Documentation added (docs/tools/my-tool.md)
  • Config options documented (if any)
  • Integration tested with agent

Troubleshooting

Tool not showing up: Check registration in createClosedClawTools() or createBashTools()

Parameter validation failing: Use correct helper function and check required flag

Tests failing: Ensure mock data matches expected types, check async/await

Tool not invoked: Make description more specific about when to use

Type errors: Import types from common.ts and ensure correct return type

Related Files

  • src/agents/tools/common.ts - Tool types and helpers
  • src/agents/openclaw-tools.ts - OpenClaw tool assembly
  • src/agents/bash-tools.ts - Bash tool assembly
  • src/config/types.ts - Config types for tool options
  • docs/tools/ - Tool documentation

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

78/100Analyzed 2/18/2026

Comprehensive internal guide for creating agent tools in the ClosedClaw project. Excellent step-by-step instructions with code examples, parameter/result helpers, testing patterns, and common patterns like config gating and sandbox safety. Very well organized but highly specific to ClosedClaw's internal codebase with references to proprietary types and file structures. Includes useful 'When to Use' section and relevant tags. Score: 78 (high quality but internal-only)."

90
95
55
95
95

Metadata

Licenseunknown
Version-
Updated2/15/2026
Publisherasafelobotomy

Tags

apici-cddatabasegithub-actionstesting