opt-in-tool-registration
The opt-in-tool-registration skill provides a structured pattern for conditionally registering tools in Claude agents behind feature flags, ensuring tools remain invisible until explicitly enabled. Use this when adding new tool sets that should default to disabled, requiring developers to implement separate tool maps, conditional merging into agent configs, execute-level guards that check feature enablement before argument validation, and gating tests to verify disabled behavior.
git clone --depth 1 https://github.com/zaxbysauce/opencode-swarm /tmp/opt-in-tool-registration && cp -r /tmp/opt-in-tool-registration/.opencode/skills/generated/opt-in-tool-registration ~/.claude/skills/opt-in-tool-registrationSKILL.md
# Opt-In Tool Registration Pattern
Activates when adding new tools that are gated behind a config flag (disabled by
default). Use this pattern to ensure tools are invisible when the feature is off
and properly guarded when on.
## When to Use
- Adding a new set of tools behind a feature flag (e.g., `external_skills`,
`memory`, `curation_enabled`)
- Registering tools that should not appear in agent menus until explicitly
enabled
- Any tool group where the default state is "off" and the user must opt in
## Pattern Overview
The opt-in tool registration pattern has four components:
1. **Separate tool map** — isolated constant in `constants.ts`
2. **Conditional merge** — merge into agent configs only when enabled
3. **Execute guard** — config check BEFORE argument validation in `execute()`
4. **Gating tests** — verify absent/present/disabled-message behavior
## Step 1 — Create the Tool Files
Create tool files in `src/tools/` following the standard `createSwarmTool`
pattern. Each tool's `execute()` function must:
1. Load the relevant config section
2. Check if the feature is enabled
3. Return the disabled message if not enabled
4. **Only then** validate arguments
```typescript
// src/tools/my-feature-tool.ts
export const myFeatureTool = createSwarmTool({
name: "my_feature_tool",
description: "...",
parameters: { ... },
execute: async (args, ctx) => {
// 1. Load config — MUST come first
// (example: replace with actual config loading for your feature)
const config = resolveConfig(ctx.directory).my_feature;
if (!config?.enabled) {
return {
content: [{ type: "text", text: "My feature is not enabled. ..." }],
};
}
// 2. Validate arguments — AFTER enabled check
const parsed = mySchema.safeParse(args);
if (!parsed.success) {
return { content: [{ type: "text", text: `Invalid args: ${parsed.error.message}` }] };
}
// 3. Business logic
...
},
});
```
**Critical**: The enabled check MUST precede argument validation. Otherwise,
calling with empty args while disabled returns validation errors instead of the
disabled message. This is the most common bug in opt-in tool implementations.
## Step 2 — Create the Agent Tool Map
Add a separate constant in `src/config/constants.ts`:
```typescript
// DO NOT add to AGENT_TOOL_MAP directly
export const MY_FEATURE_AGENT_TOOL_MAP: Record<string, string[]> = {
architect: ["my_feature_tool", ...],
coder: [...],
reviewer: [...],
// Only include agents that need the tools
};
```
Export from `constants.ts`. `TOOL_NAMES` is derived automatically from
`TOOL_METADATA` in `src/tools/tool-metadata.ts` — do not edit
`src/tools/tool-names.ts` directly (it is a re-export facade).
## Step 3 — Conditional Merge in Agent Config Builder
In the agent config builder (typically `src/agents/index.ts` or similar),
conditionally merge the opt-in map:
```typescript
import { MY_FEATURE_AGENT_TOOL_MAP } from "./constants";
function buildAgentConfigs(config: PluginConfig) {
// ... base config building ...
// Conditional merge
if (config.my_feature?.enabled) {
for (const [role, tools] of Object.entries(MY_FEATURE_AGENT_TOOL_MAP)) {
if (agentConfigs[role]) {
agentConfigs[role].tools = [...agentConfigs[role].tools, ...tools];
}
}
}
return agentConfigs;
}
```
## Step 4 — Register in Tool Metadata and Manifest
The registration chain has two compile-checked files:
1. **`src/tools/tool-metadata.ts`** — Add a `TOOL_METADATA` entry with the tool's
name, description, and default agents. This is the single source of truth for
tool registration metadata. `ToolName`, `TOOL_NAMES`, and `TOOL_NAME_SET` are
derived automatically.
2. **`src/tools/manifest.ts`** — Add a lazy thunk handler (`() => tool`) for the
tool. This file is compile-checked against `TOOL_METADATA`: a missing entry in
either file is a compile error.
Registration is always present regardless of enabled state. The tool is
registered but non-functional when disabled (returns the disabled message).
## Step 5 — Write Gating Tests
Three test categories are mandatory:
### 5a. Tools absent when disabled
```typescript
test("my_feature tools not in agent config when disabled", () => {
const config = { my_feature: { enabled: false } };
const agents = buildAgentConfigs(config);
for (const agent of Object.values(agents)) {
expect(agent.tools).not.toContain("my_feature_tool");
}
});
```
### 5b. Tools present when enabled
```typescript
test("my_feature tools in agent config when enabled", () => {
const config = { my_feature: { enabled: true } };
const agents = buildAgentConfigs(config);
expect(agents.architect.tools).toContain("my_feature_tool");
});
```
### 5c. Disabled message before validation errors
```typescript
test("disabled tool returns disabled message, not validation error", async () => {
const config = { my_feature: { enabled: false } };
const result = await myFeatureTool.execute({}, mockCtx(config));
expect(result.content[0].text).toContain("not enabled");
expect(result.content[0].text).not.toContain("Invalid args");
});
```
## Step 6 — Export and Wire
Complete the registration chain:
1. Add a `TOOL_METADATA` entry in `src/tools/tool-metadata.ts` (name, description, agents)
2. Add a lazy thunk handler in `src/tools/manifest.ts` (compile-checked against metadata)
3. Add the tool name to the opt-in map in `src/config/constants.ts`
4. Add to the conditional merge in agent config builder (typically `src/agents/index.ts`)
5. Add to help/documentation surfaces
6. Write tests covering all 5a/5b/5c categories
Run `tests/unit/config/*.test.ts` and `/swarm doctor tools` after any changes.
## Common Failures
### Enabled check after validation
Symptom: Calling tool with empty args while disabled returns "Invalid args"
instead of "Feature not enabled".
Fix: Move the config load + enabled check to the top of `execute()`.
### Tools in base AGENT_>
Run a rigorous, quote-grounded codebase review or security/QA/accessibility/performance/AI-slop/enhancement audit. Use for full-repo or large-subsystem review reports; not for normal implementation. Performs Phase 0 inventory, selected exhaustive tracks with non-diluting depth, coverage closure, reviewer/critic validation, and writes .swarm/review-v8 artifacts without modifying source files.
>
>
Use when asked to trace, investigate, root-cause, plan, fix, close, or prepare a PR for a GitHub issue or bug report. Runs an evidence-first issue workflow: GitHub intake, reproduction, reasoning-guided localization, no-gap fix planning, independent critic review, user approval gate, implementation, tests, and PR-ready closure.
>
>