opt-in-tool-registration · diff
v4 to v5
66 added, 203 removed. Audit A to A.
---
name: opt-in-tool-registration
- description: >
- Pattern for adding opt-in/gated tools to opencode-swarm that are disabled by
- default and activated via config flag. Covers tool metadata registration,
- manifest handler wiring, separate tool map, conditional merge, execute()
- guard ordering, and gating tests.
- effort: medium
- generated_from_knowledge: []
- source_knowledge_ids: ['b02ac9d7-9f2b-4ac0-9afe-5ee5f35f54c3']
- generated_at: 2026-06-14T16:50:00Z
- confidence: 0.8
+ description: opt-in-tool-registration
+ generated_from_knowledge:
+ - 0c7cadd9-4c76-42e7-ba06-c1b475d95f81
+ - 5b89a93a-e54c-4da9-9667-fd6eb583eb2d
+ - 630ca3b5-556b-480b-bfc2-460b1955411d
+ - ee3dec8a-ad0e-406f-858a-8f02ebedb66d
+ - 23e538c0-aca7-46fb-af5e-f591c359fa6e
+ - 80559952-e9a4-44c1-b30d-52f02f35d6c4
+ - 53ecc9e8-b6c9-42b9-a364-15e6890235fb
+ - 7128b66c-66f6-44c3-b98f-10070754ca9a
+ - b50901c1-1027-4e6d-8612-cc38fa8327a9
+ source_knowledge_ids:
+ - 0c7cadd9-4c76-42e7-ba06-c1b475d95f81
+ - 5b89a93a-e54c-4da9-9667-fd6eb583eb2d
+ - 630ca3b5-556b-480b-bfc2-460b1955411d
+ - ee3dec8a-ad0e-406f-858a-8f02ebedb66d
+ - 23e538c0-aca7-46fb-af5e-f591c359fa6e
+ - 80559952-e9a4-44c1-b30d-52f02f35d6c4
+ - 53ecc9e8-b6c9-42b9-a364-15e6890235fb
+ - 7128b66c-66f6-44c3-b98f-10070754ca9a
+ - b50901c1-1027-4e6d-8612-cc38fa8327a9
+ generated_at: 2026-07-02T02:35:50.021Z
+ confidence: 0.60
status: active
- version: 4
+ version: 5
skill_origin: generated
- provenance_note: >
- Re-linked to current knowledge entries (version 4). The original source ID
- 3d4f3ae0... is no longer present in the active knowledge store. The skill
- body and behavior are unchanged; only source_knowledge_ids metadata was
- updated to point to the current lesson about refactoring guards to separate
- functions, which is directly relevant to opt-in tool registration's
- guard ordering.
---
- # 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
+ <!-- generated by opencode-swarm skill-generator. Do not edit by hand; edits will be preserved on regeneration only with controlled update mode. -->
- The registration chain has two compile-checked files:
+ # opt-in-tool-registration
- 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.
+ ## Trigger
- 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.
+ - (no explicit trigger metadata; cluster derived from category/tags)
- Registration is always present regardless of enabled state. The tool is
- registered but non-functional when disabled (returns the disabled message).
+ ## Required Procedure
- ## Step 5 — Write Gating Tests
+ - Verify task description matches target file/scope before implementing
+ - Test all six subprocess attack surfaces: command injection, spawn-arg injection, stdio pipe injection, timeout bypass, path traversal, cross-platform escape sequence neutralization
+ - derive writer verdict schemas from the shared normalization module
+ - add a regression test proving new shared verdicts propagate to all writers without per-writer edits
+ - execute all tasks in the assigned scope before reporting completion
+ - run verification checks on completed work
+ - When failures are pre-existing per constraint, emit outcome=skip or separate pre_existing failure category instead of failure_test
+ - Before reporting outcome=failure_test, verify the failures are attributable to the current PR/changes and not pre-existing in the test suite
+ - run the test suite targeted by changed test files before declaring verification complete
+ - include test execution as a mandatory checklist item when test files are modified
+ - Confirm the file appears in `git diff` output before marking introduced_by_pr: YES
- Three test categories are mandatory:
+ ## Forbidden Shortcuts
- ### 5a. Tools absent when disabled
+ - maintain independent verdict allowlists in individual evidence-writer modules
+ - using linter pass as proxy for test pass
+ - rely solely on syntax checks, naming checks, or grep-based reuse scans to validate test-file changes
- ```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");
- }
- });
- ```
+ ## Delegation Template
- ### 5b. Tools present when enabled
+ When delegating a task affected by this skill, include:
- ```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");
- });
+ SKILLS: file:.opencode/skills/generated/opt-in-tool-registration/SKILL.md
```
- ## 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_TOOL_MAP
-
- Symptom: Tools appear in agent menus even when disabled.
- Fix: Use a separate opt-in map, not the base `AGENT_TOOL_MAP`.
-
- ### Missing tool-metadata entry
+ ## Reviewer Checks
- Symptom: `doctor tools` reports unknown tool name or compile error in manifest.
- Fix: Add the tool entry to `TOOL_METADATA` in `src/tools/tool-metadata.ts`.
- The `ToolName` type and `TOOL_NAMES` are derived automatically from this file.
+ - Cross-reference task ID with the file being modified
+ - verify the source file after any extraction/split still exports what consumers expect
+ - verify all importers reference the correct new locations after any extraction/split
+ - bun test <file> after any edit
+ - grep for 'pre-existing' in test output before labeling outcome
+ - compare failed tests against baseline test run to confirm new vs legacy
- ### Missing manifest handler
+ ## Test Engineer Checks
- Symptom: Compile error in `src/tools/manifest.ts` — handler map does not satisfy
- `Record<ToolName, ...>`.
- Fix: Add a lazy thunk handler for the tool in `src/tools/manifest.ts`.
+ - Add or update tests covering the trigger condition and the forbidden shortcut.
- ## Source Knowledge
+ ## Source Knowledge IDs
- - Config check must precede argument validation in opt-in tool execute() (swarm knowledge)
- - TOCTOU re-validation uses strictest trust level at promotion gate (swarm knowledge)
- - AGENTS.md invariant 11: Tool registration + agent-map coherence
+ - 0c7cadd9-4c76-42e7-ba06-c1b475d95f81 — Before writing any code, confirm the task description and acceptance criteria match the file/scope you're working on. Misaligned task routing wastes effort and produces wrong artifacts.
+ - 5b89a93a-e54c-4da9-9667-fd6eb583eb2d — When testing subprocess security, always cover all six attack surfaces: command injection, spawn-arg injection, stdio pipe injection, timeout bypass, path traversal, and cross-platform escape sequences — a gap in any one surface can compromise the entire sandbox.
+ - 630ca3b5-556b-480b-bfc2-460b1955411d — Evidence writers must derive verdict allowlists from the shared normalization module rather than maintaining independent copies; adding a new verdict should never require per-writer edits
+ - ee3dec8a-ad0e-406f-858a-8f02ebedb66d — When a phase has multiple tasks, coders must execute ALL assigned tasks and run verification before reporting done — completing one task and asking for direction is a partial, not a completion.
+ - 23e538c0-aca7-46fb-af5e-f591c359fa6e — When code has been extracted or split, confirm the extraction is complete and all references are updated before proceeding with dependent changes
+ - 80559952-e9a4-44c1-b30d-52f02f35d6c4 — biome check passes only syntax/style — it does not verify test logic or assertion correctness. A passing linter on a test file is not evidence the tests pass.
+ - 53ecc9e8-b6c9-42b9-a364-15e6890235fb — Test_engineer outcome=failure_test must distinguish pre-existing failures from regressions: flag pre-existing failures as SKIP or document them as expected-legacy rather than reporting them as failures of the current work.
+ - 7128b66c-66f6-44c3-b98f-10070754ca9a — A self-verification checklist that includes syntax/naming checks but omits running the actual test suite is a false positive trap. Coders must execute failing tests and assert green before reporting done.
+ - b50901c1-1027-4e6d-8612-cc38fa8327a9 — Before marking a finding 'introduced_by_pr: YES', verify the file actually appears in the PR diff; a confirmed finding on an unchanged file causes spurious review failures.