$npx -y skills add simbajigege/book2skills --skill agent-tool-builderDefine agent tools using the fail-closed design pattern — unified name/schema/security/execution in one class, with three-layer execution (validate → permission → call). Use this skill whenever the user wants to define a new agent tool, add permission or validation logic to an ex
| 1 | # Agent Tool Builder |
| 2 | |
| 3 | Helps define agent tools using the fail-closed design pattern: |
| 4 | a unified class that co-locates identity, schema, security properties, and execution logic, |
| 5 | with fail-closed defaults so new tools are safe by default. |
| 6 | |
| 7 | ## Why this pattern matters |
| 8 | |
| 9 | Three things that ad-hoc tool definitions lack: |
| 10 | 1. **Fail-closed defaults** — `is_read_only`, `is_destructive`, `is_concurrency_safe` all default to False. |
| 11 | A tool that forgets to declare its properties is conservatively treated as write-capable. |
| 12 | 2. **Layered execution** — `validate_semantics → check_permissions → _call` are separate methods, |
| 13 | so validation logic doesn't bleed into permission logic or business logic. |
| 14 | 3. **Self-contained definition** — schema, description, security metadata, and execution all live |
| 15 | in one place. No separate middleware to wire up. |
| 16 | |
| 17 | --- |
| 18 | |
| 19 | ## Workflow |
| 20 | |
| 21 | ### Step 1 — Identify the target framework |
| 22 | |
| 23 | Ask which agent framework the tool will be registered in (e.g. hermes-agent, LangChain, plain Python). |
| 24 | This determines the import path and registration method, but the design principles are identical. |
| 25 | |
| 26 | Check if `agent_tool_base.py` exists in the project's utils/tools directory. |
| 27 | If not, copy it from `references/agent_tool_base.py` in this skill directory. |
| 28 | Tell the user where it was placed. |
| 29 | |
| 30 | ### Step 2 — Interview the user |
| 31 | |
| 32 | Collect answers to these questions. Defaults are shown — skip questions where the default is clearly fine. |
| 33 | |
| 34 | **Naming convention**: use `{service}_{action}_{resource}` format with a service prefix so the tool stays unambiguous when multiple tool sets are loaded simultaneously (e.g. `stock_get_price`, `stock_list_symbols`, `stock_search_news`). Start with a verb: `get`, `list`, `search`, `create`, `delete`. |
| 35 | |
| 36 | | Field | Question | Default | |
| 37 | |---|---|---| |
| 38 | | `name` | 工具名(格式:`{service}_{action}_{resource}`,例如 `stock_get_price`) | — required | |
| 39 | | `description` | 给 LLM 看的一句话描述:**精确匹配**实际功能,不要模糊扩大,否则 agent 会在不该用的场景误调用 | — required | |
| 40 | | Schema fields | 工具接受哪些参数?(字段名、类型、说明;在 Field description 里加 example) | — required | |
| 41 | | `is_read_only` | 这个工具只读数据,不写入/不产生副作用吗? | `False` | |
| 42 | | `is_destructive` | 这个工具会做不可逆操作(删除、覆盖)吗? | `False` | |
| 43 | | `is_concurrency_safe` | 这个工具可以和其他工具同时运行吗? | `False` | |
| 44 | | `response_format` | 返回数据是给 agent 程序化处理(JSON)还是给用户展示(Markdown)? | 视场景,默认 Markdown | |
| 45 | | 是否列表工具 | 如果返回多条记录,要支持分页吗? | 超过 50 条建议加 | |
| 46 | | `_validate_input_semantics` | 有没有需要在执行前拦截的语义问题?(如:参数太短、格式不对) | 不需要 | |
| 47 | | `_check_permissions` | 有没有需要检查的权限?(如:需要某个 env var、调用方身份限制) | 不需要 | |
| 48 | | `_call` | 工具的核心执行逻辑是什么? | — required | |
| 49 | |
| 50 | You don't have to ask all questions upfront — infer reasonable answers from context. |
| 51 | For example, a "search" or "get" tool is almost certainly `is_read_only=True, is_concurrency_safe=True`. |
| 52 | |
| 53 | ### Step 3 — Generate the tool file |
| 54 | |
| 55 | Create a `.py` file for the tool. Follow this field order: |
| 56 | |
| 57 | ``` |
| 58 | 1. imports |
| 59 | 2. Input schema (Pydantic BaseModel) |
| 60 | 3. Tool class: |
| 61 | a. name, description, args_schema — identity |
| 62 | b. is_read_only, is_destructive, is_concurrency_safe, max_result_chars — security metadata |
| 63 | c. _validate_input_semantics() — semantic validation (omit if unneeded) |
| 64 | d. _check_permissions() — permission check (omit if unneeded) |
| 65 | e. _call() — actual logic |
| 66 | ``` |
| 67 | |
| 68 | Suggest a file path consistent with the project's tool directory structure. |
| 69 | |
| 70 | ### Step 4 — Show security property summary |
| 71 | |
| 72 | After generating, print a one-line summary of the tool's security posture: |
| 73 | |
| 74 | ``` |
| 75 | StockGetPriceTool: read_only=True destructive=False concurrency_safe=True max_result=10K |
| 76 | ``` |
| 77 | |
| 78 | --- |
| 79 | |
| 80 | ## Output template |
| 81 | |
| 82 | ```python |
| 83 | """<tool_name>.py — <one-line description>""" |
| 84 | |
| 85 | from typing import Optional |
| 86 | from pydantic import BaseModel, Field |
| 87 | from base.utils.agent_tool_base import AgentTool |
| 88 | |
| 89 | |
| 90 | # --------------------------------------------------------------------------- |
| 91 | # Input schema |
| 92 | # --------------------------------------------------------------------------- |
| 93 | |
| 94 | class <ToolName>Input(BaseModel): |
| 95 | <field_name>: <type> = Field(description="<description>. e.g. '<example>'") |
| 96 | # ... more fields |
| 97 | |
| 98 | |
| 99 | # --------------------------------------------------------------------------- |
| 100 | # Tool class |
| 101 | # --------------------------------------------------------------------------- |
| 102 | |
| 103 | class <ToolName>Tool(AgentTool): |
| 104 | # — identity — |
| 105 | name: str = "<tool_name>" |
| 106 | description: str = "<one-sentence description for the LLM>" |
| 107 | args_schema = <ToolName>Input |
| 108 | |
| 109 | # — security metadata (fail-closed: only set True when verified) — |
| 110 | is_read_only: bool = |