agents/openai.yaml
interface:
display_name: "AI Coding Agents — Tools"
short_description: "Design tool runtimes for coding agents"
default_prompt: "Use $ai-coding-agents-tools to design tool registries, deferred loading, permission-aware execution, tool search, or remote tool-result rendering for a coding-agent runtime."
data/sources.json
{
"metadata": {
"skill": "ai-coding-agents-tools",
"title": "AI Coding Agents Tools - Sources",
"description": "Official documentation and implementation references for tool registries, deferred loading, tool search, and execution runtimes",
"last_updated": "2026-07-11",
"updated": "2026-07-11",
"total_sources": 9,
"version": "1.1"
},
"categories": {
"official_documentation": [
{
"name": "Claude Code Documentation",
"url": "https://code.claude.com/docs/en",
"type": "documentation",
"relevance": "Primary product documentation for built-in tools, MCP usage, and runtime behavior",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Tools Reference",
"url": "https://code.claude.com/docs/en/tools-reference",
"type": "reference",
"relevance": "Complete tool list including LSP tool, Agent(type) parameterized tool, all built-ins with permission requirements; confirms Agent background-by-default since v2.1.198 and background-subagent permission prompt routing since v2.1.186 (verified 2026-07-11)",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Claude Code Plugins Reference",
"url": "https://code.claude.com/docs/en/plugins-reference",
"type": "reference",
"relevance": "lspServers plugin capability, LSP tool activation semantics, plugin manifest schema",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Model Context Protocol Specification",
"url": "https://modelcontextprotocol.io/",
"type": "specification",
"relevance": "Reference for MCP-backed tools and remote capability contracts",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
}
],
"implementation_references": [
{
"name": "Claude Code GitHub Repository",
"url": "https://github.com/anthropics/claude-code",
"type": "repository",
"relevance": "Authoritative implementation reference for tool contracts, pool assembly, deferred loading, and execution",
"update_frequency": "weekly",
"access": "free",
"add_as_web_search": false
},
{
"name": "Codex CLI Repository",
"url": "https://github.com/openai/codex",
"type": "repository",
"relevance": "Cross-runtime comparison point for agent tool design and CLI execution surfaces",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Unified Exec Tool Source",
"url": "https://github.com/openai/codex/blob/9f42c89c0112771dc29100a6f3fc904049b2655f/codex-rs/core/src/tools/handlers/shell_spec.rs",
"type": "repository_source",
"relevance": "Pinned first-party source for unified exec, PTY sessions, write_stdin, output budgets, and permission-aware execution parameters",
"update_frequency": "pinned",
"access": "free",
"add_as_web_search": false
},
{
"name": "OpenAI Codex Use Cases",
"url": "https://developers.openai.com/codex/use-cases",
"type": "documentation",
"relevance": "May 2026 source for composable CLI tools Codex can use and production workflow patterns",
"update_frequency": "monthly",
"access": "free",
"add_as_web_search": true
},
{
"name": "Anthropic: Building Effective Agents",
"url": "https://www.anthropic.com/engineering/building-effective-agents",
"type": "guide",
"relevance": "High-level guidance on reliable tool usage in agent systems",
"update_frequency": "quarterly",
"access": "free",
"add_as_web_search": true
}
]
}
}
learnings.consolidated.md
# ai-coding-agents-tools — Consolidated Learnings
Curated, dated, committed memory for this skill. Pruned from raw `learnings.md` via `agents-skills-feedback-loop/scripts/consolidate.py`. Human-approved.
Cap: 60 entries. When exceeded, promote durable rules to `references/`.
## Filter Override
<!-- Add 2-4 bullets that sharpen what counts as a learning for this skill. Leave empty to use the default filter from agents-skills-feedback-loop/references/learnings-format.md. -->
## Patterns That Work
## Mistakes to Avoid
## Domain Knowledge
## Open Questions
## Consolidated Principles
learnings.md
# ai-coding-agents-tools — Raw Learnings
Append-only. Dated bullets. Consolidated periodically into `learnings.consolidated.md`.
- 2026-07-11: Web-verified against `code.claude.com/docs/en` (tools-reference, sub-agents, mcp) and the `anthropics/claude-code` changelog: (1) Agent-tool subagents run in the background by default as of v2.1.198 (previously foreground/blocking was the default); (2) background-subagent permission prompts route to and surface in the parent session as of v2.1.186 — before that they silently auto-denied and the subagent continued without the capability, a silent-failure trap worth designing against explicitly; (3) nested subagent spawn via `Agent(type)` is capped at a hard, server-enforced depth of 5 with no override, live since v2.1.172; (4) since v2.1.69, ToolSearch/deferred-loading covers a meaningful subset of built-in tools, not only MCP tools — directly observed in a live session: `Read/Edit/Write/Bash/Grep/Glob/Agent/Skill/ToolSearch` stayed always-loaded while `WebFetch/WebSearch/Monitor/NotebookEdit/SendMessage/TaskStop/EnterWorktree/ExitWorktree` were deferred. Folded all four into SKILL.md and `references/deferral-eligibility-decision-tree.md` with version gates so they can be re-verified as they drift.
references/deferral-eligibility-decision-tree.md
# Deferral Eligibility Decision Tree
Use this reference when deciding whether a tool should be deferred behind ToolSearch (withheld from turn-one tool list) or always loaded. Decide at tool-registration time, before session startup.
## Table of Contents
- [Decision Tree](#decision-tree)
- [Summary Table](#summary-table)
- [Criteria explained](#criteria-explained)
- [Override: alwaysLoad_overrides](#override-alwaysload_overrides)
- [Anti-patterns](#anti-patterns)
- [Related](#related)
## Decision Tree
```
Start: Should this tool be deferred?
│
├── Is the tool required for the agent to function on turn one?
│ (e.g., Read, Bash, ToolSearch itself)
│ └── YES → alwaysLoad = true. Never defer.
│
├── Is the tool used in > 50% of sessions?
│ (analytics-based; default to NO if unknown)
│ └── YES → alwaysLoad = true. Deferral cost (extra round-trip) outweighs
│ prompt-cache savings for tools the model almost always needs.
│
├── Does the tool schema contribute significantly to token count?
│ (rough threshold: > 800 tokens for the schema + description)
│ └── NO → alwaysLoad = true. Small schemas add negligible cache-miss cost.
│ └── YES → continue ↓
│
├── Is the tool from an MCP server that may not be connected every session?
│ └── YES → shouldDefer = true. MCP tools are per-session; loading all of them
│ on turn one forces cache invalidation when any server connects/disconnects.
│
├── Is the tool a large or complex MCP extension (e.g., full Stripe API surface)?
│ └── YES → shouldDefer = true. Large extension manifests are the primary
│ motivation for deferral; load only when the model queries for them.
│
├── Is the tool a rarely-used built-in (used in < 10% of sessions)?
│ └── YES → shouldDefer = true. Reduces the stable cache prefix size
│ for the majority of sessions that never use it.
│
├── Is the tool a plugin-provided capability gated by settings/policy?
│ └── YES → shouldDefer = true AND require policy trust class to load
│ (see deferred-tool-policy-layer.md). Loading before policy
│ is confirmed leaks capability into the model's planning.
│
└── None of the above
└── Default: alwaysLoad = true. When in doubt, load on turn one.
Deferral adds a round-trip; only defer when the cache benefit
clearly outweighs the latency cost.
```
## Summary Table
| Criterion | Defer? | Reasoning |
|-----------|--------|-----------|
| Required for turn-one agent function | No — alwaysLoad | Agent cannot operate without it |
| Used in > 50% of sessions | No — alwaysLoad | Round-trip cost outweighs cache benefit |
| Schema < 800 tokens | No — alwaysLoad | Negligible cache impact |
| MCP tool (connection is per-session) | Yes — shouldDefer | Cache stability across session topologies |
| Large/complex MCP extension | Yes — shouldDefer | Primary motivation for deferral |
| Built-in used in < 10% of sessions | Yes — shouldDefer | Stable cache prefix for majority |
| Policy-gated capability | Yes — shouldDefer + trust gate | Prevent premature capability planning |
| Unknown / unclear | No — alwaysLoad | Safe default; defer only when justified |
## Criteria explained
### Turn-one necessity
Tools the agent must call to start any task — file reading, shell execution, ToolSearch itself — must be always-loaded. Deferring them creates a circular dependency: the agent needs the tool to discover the tool.
### Session frequency
If telemetry shows a tool is called in most sessions, deferring it saves no cache hits in practice but adds a round-trip on every session. Use session-frequency data; if it is unavailable, default to alwaysLoad.
### Schema token size
The prompt-cache benefit of deferral comes from keeping the stable prefix smaller. A 200-token schema has minimal impact on cache prefix size. A 3000-token schema for a complex MCP extension is the canonical use case for deferral.
### MCP topology instability
MCP servers connect and disconnect per session. If any MCP tool is in the always-loaded list and its server is not connected, the tool list changes between sessions — cache miss on every topology difference. Deferring all MCP tools except explicit overrides is the standard mitigation.
### Policy gating
A tool that requires a trust class above the current session's settings layer must be deferred. Loading it on turn one makes it visible in the model's planning context before policy is confirmed. The policy layer and deferral eligibility interact: `deferred-tool-policy-layer.md` documents the settings that govern this.
## Override: alwaysLoad_overrides
The settings layer supports an `always_load_overrides` list that promotes specific tools to always-loaded regardless of their `shouldDefer` flag:
```json
{ "always_load_overrides": ["mcp__github__create_pull_request"] }
```
Use this for teams that use a specific MCP tool in nearly every session and want to pay the slightly larger cache prefix for the latency savings.
## Real-World Calibration (Claude Code, July 2026)
Since Claude Code v2.1.69, tool search covers built-in tools too, not only MCP tools — refine the "MCP tools defer, built-ins mostly don't" heuristic above with this observed split rather than guessing from first principles:
- **Always-loaded in practice:** `Read`, `Edit`, `Write`, `Bash`, `Grep`, `Glob`, `Agent`, `Skill`, and `ToolSearch` itself — the tools nearly every coding session needs on turn one.
- **Deferred by default in practice:** `WebFetch`, `WebSearch`, `Monitor`, `NotebookEdit`, `SendMessage`, `TaskStop`, worktree-management tools (`EnterWorktree`, `ExitWorktree`), and most MCP-provided tools.
This lines up with the criteria above: the always-loaded set is turn-one-necessary plus high session frequency; the deferred set is useful sometimes but expensive if loaded unconditionally in every session. Recalibrate a new tool's `alwaysLoad`/`shouldDefer` flag against this reference split before inventing new heuristics.
Caveat: this is a snapshot of one runtime at one point in time and will drift as the always-load set is retuned. Re-verify against the current tools reference or a live session's tool list before treating it as ground truth for a specific deployment or version.
## Anti-patterns
- Deferring all MCP tools unconditionally including the ones used in every session. The round-trip cost accumulates.
- Deferring tools with tiny schemas. The cache savings are below measurement noise.
- Marking a policy-gated tool as `alwaysLoad`. The model will see and plan around a capability that policy may disallow.
- Never reviewing deferral decisions after session-frequency data is available. Decisions made without analytics should be revisited.
## Related
- [`deferred-loading-execution-and-remote-results.md`](deferred-loading-execution-and-remote-results.md) — ToolSearch execution pipeline and remote result normalization
- [`tool-registry-and-pool-assembly.md`](tool-registry-and-pool-assembly.md) — Tool contract and pool assembly
- [`../../ai-coding-agents-settings-policy/references/deferred-tool-policy-layer.md`](../../ai-coding-agents-settings-policy/references/deferred-tool-policy-layer.md) — Settings layer governance of ToolSearch
- [`../../ai-coding-agents-terminal-ui/references/recipe-toolsearch-render.md`](../../ai-coding-agents-terminal-ui/references/recipe-toolsearch-render.md) — How ToolSearch results render in the REPL
references/deferred-loading-execution-and-remote-results.md
# Deferred Loading, Execution, And Remote Results
## Deferred Loading
The runtime uses `ToolSearch` plus two explicit flags:
- `shouldDefer`
- `alwaysLoad`
`tools/ToolSearchTool/prompt.ts` applies the deferral rules:
- MCP tools defer by default
- tools with `shouldDefer` defer
- tools with `alwaysLoad` never defer
- `ToolSearch` itself never defers
- some communication and agent-launch tools are forced to appear on turn one
Reusable rule:
- make deferral explicit on the tool contract
- add explicit opt-outs for tools that are operationally required on turn one
- keep discovery separate from execution
## Tool Search As Discovery Layer
`ToolSearch` is a meta-tool:
- it returns schemas for deferred tools
- the runtime only makes those tools callable after discovery
- the provider receives `defer_loading` metadata instead of full eager schemas
This keeps the initial prompt smaller without losing access to large MCP or workflow-specific tool surfaces.
## Execution Pipeline
`services/tools/toolExecution.ts` is the key reference for execution flow.
It handles:
- tool lookup
- progress events
- input validation
- permission and hook integration
- telemetry and tracing
- result shaping and storage
- follow-up message construction
Important pattern:
- keep execution policy in one central pipeline
- individual tools define their local behavior, but do not own global tracing, hook execution, or transcript shaping
## Transparent Wrapper Tools
The tool interface supports transparent wrappers.
Example pattern:
- a wrapper tool delegates rendering to nested progress events
- the wrapper itself emits no separate visible result block
Use this for:
- REPL or shell wrappers
- composite tools
- orchestration tools that expose inner work as the real visible activity
## Remote Tool Results
`remote/sdkMessageAdapter.ts` converts SDK messages into the same local message model used by the REPL.
Key patterns:
- detect tool-result user messages by content shape, not by unreliable parent IDs
- convert remote tool results into the same `UserMessage` form as local results
- convert historical user text only when needed
- ignore noisy success-result messages when they add no value
This is the right model for hybrid runtimes:
- remote and local tool uses should collapse, search, and render the same way
- normalize transport differences at the adapter boundary
## Remote Permission Bridges
The tool layer also interacts with remote permission flow:
- server-side permission requests arrive as control messages
- remote tool use may need synthetic local tool wrappers
- local UI still needs tool-like artifacts so prompts and decisions remain coherent
If you separate permission architecture into its own subsystem, keep the tool layer compatible with synthetic or proxied tools.
## Design Rules To Reuse
- Use explicit defer and always-load flags.
- Keep discovery and execution separate.
- Centralize tracing, hooks, and result shaping in the execution pipeline.
- Normalize remote results into the same local message model as early as possible.
- Use transparent wrapper tools for composite behaviors.
references/openai-codex-unified-exec-and-tool-contracts.md
# OpenAI Codex Unified Exec And Tool Contracts
Source snapshot: OpenAI Codex commit `9f42c89c0112771dc29100a6f3fc904049b2655f` (2026-05-24), especially `codex-rs/core/src/tools/handlers/shell_spec.rs` and `codex-rs/core/src/tools/handlers`.
Web sources checked 2026-05-25:
- OpenAI, "How OpenAI uses Codex" PDF, May 2026: https://cdn.openai.com/pdf/6a2631dc-783e-479b-b1a4-af0cfbd38630/how-openai-uses-codex.pdf
- Codex use cases, "Create a CLI Codex can use": https://developers.openai.com/codex/use-cases
## Table Of Contents
- [Design Goal](#design-goal)
- [Unified Exec Contract](#unified-exec-contract)
- [Permission-Aware Tool Parameters](#permission-aware-tool-parameters)
- [Output Budgeting](#output-budgeting)
- [Composable CLIs As Tools](#composable-clis-as-tools)
- [Known Traps](#known-traps)
## Design Goal
Treat shell execution as a real tool contract, not a string escape hatch. Codex's current tool surface makes command execution explicit enough for UI rendering, approval routing, PTY sessions, output truncation, and remote environments.
## Unified Exec Contract
A strong exec tool contract includes:
- command text
- optional working directory
- optional shell
- optional login-shell behavior
- TTY allocation flag
- output yield timeout
- maximum output tokens
- session ID for ongoing interactive processes
- separate stdin-writing tool for already-running sessions
This split lets the runtime distinguish one-shot commands from long-lived terminal sessions.
## Permission-Aware Tool Parameters
Codex adds approval fields to shell-like tools rather than hiding escalation in natural language:
- sandbox permission mode
- justification for unsandboxed escalation
- suggested future approval prefix
- optional additional permission profile when fine-grained permission approvals are enabled
Best practice:
- request the narrowest extra permission that can complete the command
- keep unsandboxed execution as the exceptional path
- make the approval prompt carry command, reason, and scope
## Output Budgeting
Codex exposes `max_output_tokens` and `yield_time_ms` in the tool contract. Copy that pattern for every high-volume tool:
- cap output at the tool boundary
- report that truncation happened
- let the model poll long-running commands instead of blocking the whole turn
- preserve enough metadata to resume or cancel the running process
## Composable CLIs As Tools
OpenAI's May 2026 use cases explicitly call out creating CLIs that Codex can use. The runtime lesson is simple: the best agent tool is often a small, typed command-line wrapper around an existing API, log source, export, or team script.
For runtime builders:
- prefer deterministic CLI wrappers over broad browser automation when an API exists
- keep output structured and compact
- provide dry-run or read-only modes
- document auth and environment variables outside the prompt body
## Known Traps
- Using shell commands as an untyped universal tool and losing approval context.
- Returning unlimited logs to the model.
- Blocking a turn on an interactive process that should have become a session.
- Asking for full escalation when a turn-scoped read, write, or network grant would work.
- Building a plugin when a small CLI plus a stable output schema would be enough.
references/tool-registry-and-pool-assembly.md
# Tool Registry And Pool Assembly
## Table Of Contents
- [Core Pattern](#core-pattern)
- [Useful Tool Contract](#useful-tool-contract)
- [Base Tool Set](#base-tool-set)
- [Pool Assembly](#pool-assembly)
- [Filter Before Exposure](#filter-before-exposure)
- [Stable Ordering For Prompt Cache](#stable-ordering-for-prompt-cache)
- [Special Modes](#special-modes)
- [Design Rules To Reuse](#design-rules-to-reuse)
## Core Pattern
Treat tools as first-class runtime objects with a rich contract, not just callables.
From the April 2026 `claude_code` snapshot:
- the shared interface lives in `Tool.ts`
- pool assembly lives in `tools.ts`
- merge and coordinator-mode filtering lives in `utils/toolPool.ts`
## Useful Tool Contract
The runtime’s tool interface includes:
- identity
- `name`
- optional `aliases`
- optional `searchHint`
- execution
- `call`
- input schema
- optional output schema
- safety
- `validateInput`
- `checkPermissions`
- `isReadOnly`
- optional `isDestructive`
- runtime behavior
- `interruptBehavior`
- concurrency safety
- transparent-wrapper behavior
- presentation
- user-facing name
- activity description
- tool-use rendering
- tool-result rendering
- discovery
- `shouldDefer`
- `alwaysLoad`
- MCP metadata
This is a strong pattern for coding-agent CLIs because it keeps validation, rendering, and execution expectations on one stable contract.
## Base Tool Set
`getAllBaseTools()` is the source of truth for built-ins.
It uses:
- feature gates
- environment gates
- runtime mode gates
- helper functions for optional tool families
Reusable rule:
- keep one exhaustive base-tool function
- apply filtering after base enumeration
- avoid duplicating the built-in list across REPL, headless, and worker flows
## Pool Assembly
The runtime uses distinct stages:
1. `getTools(permissionContext)`
- built-ins only
- special mode handling
- blanket deny filtering
- REPL-only tool hiding
- `isEnabled()` checks
2. `assembleToolPool(permissionContext, mcpTools)`
- merge built-ins with MCP tools
- filter MCP tools by deny rules
- dedupe by name
- sort for prompt-cache stability
3. `mergeAndFilterTools(initialTools, assembled, mode)`
- prepend initial or startup tools
- dedupe again
- partition built-ins from MCP
- apply coordinator-mode filtering
This separation is worth copying:
- one function for built-ins
- one function for full runtime pool
- one function for mode-specific merged views
## Filter Before Exposure
`filterToolsByDenyRules()` removes blanket-denied tools before they reach the model.
Important pattern:
- do not rely on call-time denial alone
- hide impossible tools from planning-time context
- apply the same matching rules to built-ins and MCP tools
## Stable Ordering For Prompt Cache
The repo keeps built-ins as a contiguous prefix and sorts built-ins separately from MCP tools.
Why:
- prompt-cache keys depend on tool ordering
- letting MCP tools interleave with built-ins can invalidate downstream cache keys
Reusable rule:
- if your provider caches prompt prefixes, keep tool ordering deterministic and partitioned by source when needed
## Special Modes
`getTools()` handles special modes such as:
- simple mode
- REPL mode hiding primitive tools behind a wrapper
- coordinator mode allowing only orchestration-safe tools
This is a better pattern than cloning multiple tool registries for each mode.
## Design Rules To Reuse
- Keep one rich tool contract.
- Build one canonical base-tool list.
- Assemble built-ins and external tools through a single pool function.
- Filter deny-listed tools before exposure.
- Preserve deterministic ordering for cache-sensitive providers.
scripts/toolsearch_schema_loader_example.py
"""
toolsearch_schema_loader_example.py
Demonstrates the ToolSearch schema-load pattern used in coding-agent runtimes.
ToolSearch is a two-phase mechanism:
Phase 1 — Discovery: the model calls ToolSearch with a query.
The runtime finds matching deferred tools and returns their schemas.
Phase 2 — Execution: the model calls the loaded tool with the schema it just received.
This example shows how a runtime should implement Phase 1: receiving a ToolSearch
call, resolving matching deferred tools, loading their schemas, and returning
the schema payload to the model.
Requirements: Python 3.9+ stdlib only.
"""
from __future__ import annotations
import json
from dataclasses import dataclass, field
from typing import Optional
# ---------------------------------------------------------------------------
# Data types
# ---------------------------------------------------------------------------
@dataclass
class ToolSchema:
"""Minimal representation of a tool's JSON schema."""
name: str
description: str
parameters: dict # JSON Schema object
origin: str = "builtin" # "builtin" | "mcp" | "acp_delegated"
always_load: bool = False
should_defer: bool = False
@dataclass
class DeferredPool:
"""Registry of tools whose schemas are withheld from the initial tool list."""
_tools: dict[str, ToolSchema] = field(default_factory=dict)
def register(self, tool: ToolSchema) -> None:
if tool.always_load:
raise ValueError(
f"Tool '{tool.name}' is marked always_load and must not be "
"added to the deferred pool."
)
self._tools[tool.name] = tool
def search(self, query: str) -> list[ToolSchema]:
"""
Naive substring search over tool name and description.
In production, replace with a vector-similarity or BM25 search.
"""
q = query.lower()
return [
t
for t in self._tools.values()
if q in t.name.lower() or q in t.description.lower()
]
def get(self, name: str) -> Optional[ToolSchema]:
return self._tools.get(name)
# ---------------------------------------------------------------------------
# ToolSearch handler
# ---------------------------------------------------------------------------
def handle_toolsearch(
query: str,
deferred_pool: DeferredPool,
toolsearch_enabled: bool = True,
toolsearch_scope: str = "all", # "all" | "mcp_only" | "builtins_only" | "none"
) -> dict:
"""
Implements the ToolSearch tool call handler.
Returns a dict that the runtime serialises as the ToolSearch tool result
and sends back to the model.
The model uses the returned schemas to construct its next tool call.
"""
if not toolsearch_enabled or toolsearch_scope == "none":
return {
"tools_loaded": [],
"error": "ToolSearch is disabled by policy.",
}
matches = deferred_pool.search(query)
# Apply scope filter
if toolsearch_scope == "mcp_only":
matches = [t for t in matches if t.origin == "mcp"]
elif toolsearch_scope == "builtins_only":
matches = [t for t in matches if t.origin == "builtin"]
if not matches:
return {
"tools_loaded": [],
"message": f"No tools found for query: '{query}'",
}
# Return schemas the model can use to construct its next call
return {
"tools_loaded": [
{
"name": t.name,
"description": t.description,
"parameters": t.parameters,
}
for t in matches
]
}
# ---------------------------------------------------------------------------
# Example: simulated agent turn
# ---------------------------------------------------------------------------
def simulate_toolsearch_turn() -> None:
"""
Simulate a two-turn ToolSearch sequence:
Turn 1: model calls ToolSearch → runtime loads schema
Turn 2: model calls the loaded tool → runtime executes it
"""
# --- Build a deferred pool with a few example tools ---
pool = DeferredPool()
pool.register(
ToolSchema(
name="mcp__slack__send_message",
description="Send a message to a Slack channel",
origin="mcp",
should_defer=True,
parameters={
"type": "object",
"properties": {
"channel": {"type": "string", "description": "Channel name, e.g. #general"},
"text": {"type": "string", "description": "Message text"},
},
"required": ["channel", "text"],
},
)
)
pool.register(
ToolSchema(
name="mcp__github__create_pull_request",
description="Create a GitHub pull request for the current branch",
origin="mcp",
should_defer=True,
parameters={
"type": "object",
"properties": {
"title": {"type": "string"},
"body": {"type": "string"},
"base": {"type": "string", "default": "main"},
},
"required": ["title"],
},
)
)
pool.register(
ToolSchema(
name="builtin__deep_codebase_index",
description="Build a semantic index of the entire codebase",
origin="builtin",
should_defer=True,
parameters={
"type": "object",
"properties": {
"root": {"type": "string", "description": "Repository root path"},
},
"required": ["root"],
},
)
)
# --- Turn 1: model calls ToolSearch ---
print("=== Turn 1: Model calls ToolSearch ===")
toolsearch_call = {"tool_name": "ToolSearch", "input": {"query": "slack send"}}
print(f"Model → {json.dumps(toolsearch_call, indent=2)}\n")
result = handle_toolsearch(
query=toolsearch_call["input"]["query"],
deferred_pool=pool,
toolsearch_enabled=True,
toolsearch_scope="all",
)
print(f"Runtime → ToolSearch result:\n{json.dumps(result, indent=2)}\n")
# --- Turn 2: model calls the loaded tool ---
print("=== Turn 2: Model calls loaded tool ===")
tool_call = {
"tool_name": "mcp__slack__send_message",
"input": {"channel": "#general", "text": "Deployment complete."},
}
print(f"Model → {json.dumps(tool_call, indent=2)}\n")
# Verify the tool is in the deferred pool (runtime would execute it here)
loaded_tool = pool.get(tool_call["tool_name"])
if loaded_tool is None:
print("ERROR: Tool not found in deferred pool. This should not happen after ToolSearch.")
return
print(
f"Runtime → executing '{loaded_tool.name}' "
f"(origin: {loaded_tool.origin})\n"
f"[in production: forward to MCP server, await result, return to model]\n"
)
def simulate_toolsearch_not_found() -> None:
"""Simulate a ToolSearch call that returns no results."""
pool = DeferredPool() # empty pool
print("=== ToolSearch: no results ===")
result = handle_toolsearch(query="jira create ticket", deferred_pool=pool)
print(json.dumps(result, indent=2))
print()
def simulate_policy_disabled() -> None:
"""Simulate a session where ToolSearch is disabled by managed policy."""
pool = DeferredPool()
print("=== ToolSearch: disabled by policy ===")
result = handle_toolsearch(
query="slack send",
deferred_pool=pool,
toolsearch_enabled=False,
)
print(json.dumps(result, indent=2))
print()
def simulate_scope_filter() -> None:
"""Simulate ToolSearch with scope restricted to MCP tools only."""
pool = DeferredPool()
pool.register(
ToolSchema(
name="builtin__deep_codebase_index",
description="Semantic codebase indexer",
origin="builtin",
should_defer=True,
parameters={"type": "object", "properties": {}, "required": []},
)
)
pool.register(
ToolSchema(
name="mcp__search__web",
description="Search the web",
origin="mcp",
should_defer=True,
parameters={
"type": "object",
"properties": {"q": {"type": "string"}},
"required": ["q"],
},
)
)
print("=== ToolSearch: scope=mcp_only (built-in excluded) ===")
result = handle_toolsearch(
query="search",
deferred_pool=pool,
toolsearch_scope="mcp_only",
)
print(json.dumps(result, indent=2))
print()
# ---------------------------------------------------------------------------
# Entry point
# ---------------------------------------------------------------------------
if __name__ == "__main__":
simulate_toolsearch_turn()
simulate_toolsearch_not_found()
simulate_policy_disabled()
simulate_scope_filter()
SKILL.md
---
name: ai-coding-agents-tools
description: "Designs tool runtimes for coding agents. Use when modeling tool registries, deferred loading, permission-aware execution, tool search, or remote tool rendering."
compatibility: Portable core. Works on Claude Code and Codex.
version: "1.1"
last_validated: 2026-07-11
---
# AI Coding Agents Tools
Use this skill to design or review the tool runtime of a coding-agent CLI: tool contracts, tool pool assembly, deferred loading, tool-search behavior, permission-aware execution, and remote rendering of tool results.
This skill owns tool-runtime architecture for coding agents. For command architecture, use [`../ai-coding-agents-command-runtime/SKILL.md`](../ai-coding-agents-command-runtime/SKILL.md).
## ASCII Flow
```text
tool sources
built-ins + MCP + plugins + remote server + deferred catalog
LSP tools (activated when plugin supplies lspServers config; always-load semantics)
|
v
tool pool assembly
shared contract + stable ordering + mode filtering + deny-before-exposure
Agent(type) tool: parameterized by type discriminator gating spawnable subagent types
|
v
model-visible tools
always-loaded subset + ToolSearch discovery path
|
v
execution pipeline
validate -> permission -> hooks -> run -> shape result -> persist -> render
|
v
local or remote result
normalized into the same session message model
```
## Quick Reference
| Question | Read | Outcome |
|----------|------|---------|
| How should tools be modeled and assembled? | [`references/tool-registry-and-pool-assembly.md`](references/tool-registry-and-pool-assembly.md) | Tool contract, built-in vs MCP pool, deny filtering, prompt-cache-stable ordering |
| How should deferred tools, execution, and remote results work? | [`references/deferred-loading-execution-and-remote-results.md`](references/deferred-loading-execution-and-remote-results.md) | ToolSearch, defer rules, execution pipeline, remote tool-result rendering |
| Should this tool be deferred or always-loaded? | [`references/deferral-eligibility-decision-tree.md`](references/deferral-eligibility-decision-tree.md) | Decision tree, criteria table, `alwaysLoad` vs `shouldDefer`, override settings |
| How do I implement the ToolSearch schema-load pattern? | [`scripts/toolsearch_schema_loader_example.py`](scripts/toolsearch_schema_loader_example.py) | Annotated stdlib-only example: deferred pool, handler, reconnect, policy scope |
| How does OpenAI Codex model unified exec and composable CLI tools? | [`references/openai-codex-unified-exec-and-tool-contracts.md`](references/openai-codex-unified-exec-and-tool-contracts.md) | PTY sessions, stdin writes, output budgets, permission-aware params, CLI-wrapper pattern |
## When To Use
- Design a tool registry for a coding-agent runtime
- Add built-in, MCP, or plugin-provided tools to an agent CLI
- Model tool permission checks, result rendering, progress events, or interrupt behavior
- Decide which tools should be deferred behind tool search
- Review how remote or bridged sessions should render tool uses and tool results
## Use Other Skills
| Need | Use Instead |
|------|-------------|
| Broader coding-agent architecture | [`../ai-coding-agents/SKILL.md`](../ai-coding-agents/SKILL.md) |
| Slash-command architecture | [`../ai-coding-agents-command-runtime/SKILL.md`](../ai-coding-agents-command-runtime/SKILL.md) |
| Plugin extension architecture | [`../ai-coding-agents-plugins/SKILL.md`](../ai-coding-agents-plugins/SKILL.md) |
| Permission mode design | [`../ai-coding-agents-permissions/SKILL.md`](../ai-coding-agents-permissions/SKILL.md) |
| MCP server design | [`../agents-mcp/SKILL.md`](../agents-mcp/SKILL.md) |
## Default Workflow
1. **Define the tool contract.** Keep execution, validation, permissions, rendering, and interruption behavior on the tool type itself, ideally through a shared base-tool or factory pattern rather than ad hoc implementations.
2. **Separate built-ins from external tools.** Assemble the full pool from built-ins plus MCP or other external tools through one shared function.
3. **Filter before the model sees tools.** Apply blanket deny rules and mode-specific filtering at assembly time, not only at call time.
4. **Keep ordering stable.** Built-ins should stay a contiguous prefix when prompt-cache behavior depends on tool order.
5. **Mark deferred tools explicitly.** Use a first-class deferred flag plus a never-defer override for tools that must appear on turn one.
6. **Keep ToolSearch separate from execution.** Discovery is one tool; calling the loaded tool is another phase.
7. **Refresh tool access after topology changes.** MCP reconnects, plugin reloads, or coordinator-mode transitions should rebuild the visible tool set through one path instead of mutating scattered registries.
8. **Normalize remote results.** Convert server-side tool uses and tool results into the same local message model used by the REPL, including fallback rendering for tools the local client does not know how to execute directly.
9. **Make the execution pipeline explicit.** Validation, permission checks, telemetry, hook calls, execution, shaping, persistence, and rendering should be separate stages even if they share one host entrypoint.
10. **Test hostile cases.** Cover duplicate tool names, denied MCP tools, disappearing deferred tools, partial server reconnects, coordinator-mode filtering, and remote rendering mismatches.
## Host Rules
- Keep one tool interface for every tool source so built-ins and remote tools share the same lifecycle.
- Put validation and permission checks close to the tool, but keep global policy orchestration outside individual tools.
- Exclude blanket-denied tools from the visible registry so the model never plans around unavailable tools.
- Prefer explicit `shouldDefer` and `alwaysLoad` semantics over heuristic deferral.
- Keep built-ins as a stable contiguous prefix when ordering affects prompt-cache reuse or provider planning behavior.
- Rebuild or refresh the visible tool pool after MCP connection changes instead of assuming the registry is static for the whole session.
- Unknown remote tools should degrade to renderable stubs, not invisible failures.
- Treat transparent wrapper tools differently from direct tools when rendering results.
- Keep the execution pipeline responsible for telemetry, hooks, permission reasons, and storage-side result shaping.
## Build Order
1. Define one shared tool interface and execution contract.
2. Implement built-in tool registration separately from external tool ingestion.
3. Add assembly-time filtering for deny rules and mode-specific visibility.
4. Add deferred loading and ToolSearch as explicit phases.
5. Build one host execution pipeline from validation through rendering.
6. Add remote normalization so server-side tool traffic can render locally.
## Core Invariants
- The model should only see tools that are truly callable in the current mode.
- Built-ins and external tools must share one lifecycle contract.
- Tool discovery is separate from tool execution.
- Ordering must stay stable when provider behavior depends on tool order.
- Remote tool uses must be renderable even if the local client cannot execute them.
## Failure Modes
- Duplicate tool names with inconsistent semantics.
- Blanket-denied tools still being advertised to the model.
- Deferred tools disappearing after discovery due to stale registry state.
- MCP reconnects leaving the visible tool pool stale.
- Remote tool uses becoming invisible because the local client lacks the implementation.
## Minimal Viable Version
- One tool interface with execution, validation, and rendering hooks.
- One assembly path for built-ins and one for external tools.
- One deny filter applied before tool exposure.
- One ToolSearch-style mechanism for deferred capability discovery.
- One central execution pipeline with permission and telemetry hooks.
## What Strong Implementations Add
- Base-tool factory patterns for consistent contracts.
- Feature-gated built-in enumeration and coordinator-mode filtering.
- Refreshable registries after plugin or MCP topology changes.
- Wrapper-tool versus direct-tool rendering distinctions.
- Storage-aware result shaping and normalized remote replay.
## Known Traps
- Treating built-ins, wrappers, and MCP tools as separate conceptual systems and ending up with different permission, telemetry, and rendering semantics.
- Filtering tools only at execution time after the model has already planned around capabilities that are unavailable in the current mode.
- Binding the registry once at startup and never rebuilding it after plugin reloads, MCP topology changes, or feature-gate updates.
- Assuming remote tool execution can always be replayed or rendered locally without transport-aware adaptation.
- Using deferred loading heuristics that the runtime itself cannot inspect, explain, or invalidate.
- Assuming subagent dispatch is synchronous by default. Since v2.1.198 that assumption is backwards for the reference implementation, and any runtime copying the pattern needs an explicit background-completion event, not a blocking call.
## Common Anti-Patterns
- Treating MCP tools as a side registry with different semantics from built-ins.
- Deferring tools with heuristics that the rest of the runtime cannot inspect.
- Filtering only at call time after the model has already planned around a tool.
- Binding registry state once at startup and never refreshing it.
- Assuming remote tool execution can always be replayed locally without adaptation.
## Claude Code Tool System Extensions (2026)
### LSP tools as a built-in always-load origin class
The `LSP` tool is a built-in tool that activates automatically when a plugin supplies `lspServers` configuration. It is not an MCP-backed tool and not deferred. Origin class: `plugin-activated-builtin`. Semantics: always-load — the LSP tool is added to the model-visible tool set for the session as soon as the plugin activates; no ToolSearch step is needed.
Capabilities the LSP tool exposes: GoToDefinition, FindReferences, hover type info, ListSymbols, SearchSymbols, FindImplementations, CallHierarchy, and automatic post-edit diagnostics injection. The diagnostics injection is the highest-value path: after every `Edit` or `Write` the runtime sends a `textDocument/publishDiagnostics` notification and the LSP tool surfaces the result to Claude without a separate tool call. This shortens the write → observe → fix loop from a Bash round-trip to an in-pipeline event.
Implications for tool pool assembly: a `lspServers`-providing plugin expands the always-load set. Tool pool rebuild on plugin reload must include LSP tool activation/deactivation. Deny rules against `LSP(path:...)` follow the same path-pattern format as `Read` rules.
### Agent(type) — parameterized subagent tool
`Agent` is the tool name for subagent spawning. In v2.1.63 it replaced the legacy `Task` tool name. The rule format `Agent(type)` gates which subagent types are spawnable in a given permission context. `type` is the subagent's `name` field from its agent definition file. Permission rules use this format: `Agent(code-reviewer)` allows spawning the `code-reviewer` subagent; `Agent(*)` allows all; `deny: [Agent(*)]` blocks subagent spawning entirely.
This is a parameterized tool in the same family as `Bash(command)`, `Read(path)`, `Edit(path)`, and `WebFetch(domain:...)`. The `type` specifier is matched against the subagent name at spawn time, not at tool-registration time, so the rule can be written before the subagent definition exists.
### Agent tool: background-by-default, nested depth cap, and fork mode (v2.1.172–v2.1.198)
Three dispatch-mode changes to the `Agent` tool matter for execution-pipeline design, not just for end users:
- **Background-by-default (v2.1.198).** Subagents launched via `Agent` now run in the background by default; the parent runs one in the foreground only when it needs the result before continuing. This flips the historical default (foreground, blocking) — a runtime that still assumes synchronous return-on-call will race or hang on background completions. Design the execution pipeline so tool dispatch returns a handle immediately and completion is a separate event, not a return value.
- **Background permission routing (v2.1.186) is not optional.** Before v2.1.186, a background subagent's tool call that would otherwise prompt was auto-denied silently and the subagent kept going without that capability — a silent-failure trap. Current behavior surfaces the prompt in the parent session, named by subagent, with a per-call deny that doesn't kill the subagent. Any tool runtime that adds background dispatch must route permission prompts to a session the user can actually see, not fail closed silently.
- **Nested spawn depth is capped at 5, server-enforced, no override (since v2.1.172).** A subagent at depth 5 does not receive the `Agent` tool at all. Model the depth counter as part of the `Agent(type)` dispatch contract itself — the runtime should refuse a depth-6 spawn attempt locally with a clear error, rather than letting it round-trip to a server rejection.
- **Fork mode is a third dispatch mode, not a variant of foreground/background.** A forked subagent inherits the full parent conversation (rather than starting fresh) and always runs in the background, but still surfaces permission prompts in the parent's terminal like a foreground call would. Treat fork, named-background, and named-foreground as three branches of the same dispatch contract, each with its own inheritance and visibility rules — collapsing them into one code path tends to leak conversation state or silently swallow prompts.
Cross-cutting judgment call: a message delivered to a resumed or running subagent (via `SendMessage`) is task direction from its own launcher, not user consent or approval for a permission-gated action — the same trust boundary that applies to any agent-to-agent message applies here. A tool runtime's permission layer must not treat "another agent said so" as equivalent to a human granting a permission.
## Cross-Platform Patterns (Goose)
Goose's tool runtime lines up with this skill's existing tool contract, but two patterns are worth lifting explicitly.
### Unified tool origin (`type:` + `name:`)
Goose tools come from extensions declared as `{type: builtin|mcp, name: ...}`. Every tool surfaces to the model under one addressing scheme regardless of origin, and the tool registry's entry type carries `origin` rather than splitting across parallel registries.
- **Pattern:** model tool entries with a single discriminated shape: `{origin: Builtin|Mcp|AcpDelegated, name, schema, schema_version, activation_scope}`. Prompt-cache ordering and deny filtering apply uniformly.
- **Anti-pattern:** a "built-in tools table" separate from an "MCP tools table" with parallel permission and rendering semantics — exactly the pattern this skill already flags, but worth reinforcing.
### Toolshim as a tool-layer adapter
When the provider is a non-function-calling model (see `ai-coding-agents-provider-runtime`), the toolshim presents normalized tool-call events to the tool registry. The tool runtime does not care that the provider synthesized the call from text — the contract at the registry boundary stays the same.
- **Pattern:** the tool registry's call-in interface must not assume native function calling exists. The registry receives a `ToolInvocation` event; who produced it (native provider, toolshim adapter, ACP-delegated agent) is a provenance field, not a branching condition.
- **Anti-pattern:** tool registry code that reaches back into provider internals to decide whether to execute a call. That couples tool dispatch to provider brand and makes toolshim-wrapped providers unusable.
- **Recipe:** every `ToolInvocation` carries `invoked_by: ProviderId | ToolshimId | AcpAgentId`. Telemetry attributes cost, latency, and failure back to the invoker class, but execution flow does not branch on it.
### Codex dual role: MCP client AND MCP server
OpenAI Codex is both an MCP client (it connects to external MCP servers to acquire tools) and an MCP server (via `codex mcp-server`, it exposes itself as a tool to editors and orchestrators). This dual role matters for tool-runtime design: a runtime that acts as a server must apply its full tool-permission model (`exec_approval`, `patch_approval`) to requests arriving over the MCP wire, not just to local interactive sessions. Approval bypasses for "trusted AI callers" are architectural holes — the server-side `AskForApproval` policy applies regardless of caller identity.
For the server-side detail — wire protocol, crate structure, contrast with the HTTP app-server-daemon — see [`../ai-coding-agents-remote-runtime/references/openai-codex-as-mcp-server.md`](../ai-coding-agents-remote-runtime/references/openai-codex-as-mcp-server.md).
### Remote / ACP-delegated tool rendering
When a delegated ACP agent uses tools, their invocations and results must render in the orchestrator's REPL like local tool uses. This extends the skill's existing "normalize remote results" rule across the agent-delegation boundary.
- **Pattern:** the REPL treats tool events with `origin: AcpAgentId` identically to local tool events for rendering purposes; differences are in permission routing (orchestrator approves for the delegated agent) and accounting (costs attributed to the delegated agent row).
## Navigation
### References
- [`references/tool-registry-and-pool-assembly.md`](references/tool-registry-and-pool-assembly.md) — Tool contract, registry composition, and pool assembly
- [`references/deferred-loading-execution-and-remote-results.md`](references/deferred-loading-execution-and-remote-results.md) — Tool search, execution pipeline, and remote result normalization
- [`references/deferral-eligibility-decision-tree.md`](references/deferral-eligibility-decision-tree.md) — When a tool should be deferred behind ToolSearch
- [`references/openai-codex-unified-exec-and-tool-contracts.md`](references/openai-codex-unified-exec-and-tool-contracts.md) — OpenAI Codex unified exec, output budgeting, permission-aware execution, and composable CLI tools
### Scripts
- [`scripts/toolsearch_schema_loader_example.py`](scripts/toolsearch_schema_loader_example.py) — Annotated stdlib-only example of the ToolSearch schema-load pattern
### Data
- [`data/sources.json`](data/sources.json) — Primary documentation and implementation references for tool-runtime guidance
### Related Skills
- [`../ai-coding-agents-command-runtime/SKILL.md`](../ai-coding-agents-command-runtime/SKILL.md) — Command registry and forked command execution
- [`../ai-coding-agents-permissions/SKILL.md`](../ai-coding-agents-permissions/SKILL.md) — Approval and permission routing
- [`../agents-mcp/SKILL.md`](../agents-mcp/SKILL.md) — MCP server connectivity and capability design
## Fact-Checking
- Known bugs, regressions, framework/compiler/runtime footguns, and version-specific crash or workaround guidance must be verified against current primary web sources before being treated as current fact.
- These patterns are grounded in a local April 2026 `claude_code` source snapshot, cross-checked against the July 2026 hosted `tools-reference` and `sub-agents` docs (`code.claude.com/docs/en`) and the `anthropics/claude-code` changelog through v2.1.206. Re-check upstream code or docs before relying on volatile runtime details — version gates cited here (v2.1.63, v2.1.69, v2.1.172, v2.1.186, v2.1.198) are the ones verified live; anything else in this file should be treated as architectural pattern, not a version-pinned fact.
- Tool-search semantics, deferred loading, and remote rendering paths are especially product-specific. Preserve the architecture, but verify the target runtime’s exact tool transport and UI contract.
## Learnings Loop
Before applying this skill on a non-trivial task, read `learnings.consolidated.md` in this directory (and `learnings.md` if present).
After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to `learnings.md` via `agents-skills-feedback-loop/scripts/append_learning.py`. Do not modify `SKILL.md` itself.