---
name: missing-tool-detect
description: Engine substrate for the heartbeat-driven detector that finds skills referencing tool names not present in the live tool registry, and proposes implementing them for user approval.
title: Missing Tool Detector
category: _meta
status: active
author: agentic
metadata:
  priority: 4
---
## Purpose

Closes one of the three Rule 21 gaps named in CLAUDE.md but unaddressed
before this skill: the agent can declare in a skill's
`tools_granted: [...]` that it intends to use tool `X`, but if `X`
isn't registered in the live `ToolRegistry` the agent silently fails
when it tries to invoke that tool. The skill reads as "well-equipped"
on disk while being structurally broken at runtime.

This detector closes that gap: it walks every loaded skill, collects
the union of every `tools_granted` entry, subtracts the live registry,
and proposes the missing tools for user approval (or for a developer
follow-up — creating a tool requires Python code).

**Why this is a Rule 21 capability and not just a lint check**: the
proposal is the user-facing artifact. Lint output gets ignored; a
`/proposals` inbox entry is reviewed.

## When this fires

Heartbeat-driven, daily by default. Pure deterministic scan — no LLM,
no log analysis, just set arithmetic over loaded skills + registry.

For each missing tool name, the proposal carries:

* the **list of skills** that grant it (so the user sees the blast
  radius of approving / dismissing)
* one or two **example skills** as templates for the implementation
  sketch
* an **idempotent stable id** keyed on the tool name — re-running
  daily on a vault with no changes is a no-op

## What it doesn't do

* **Never creates the tool itself.** Tools require Python — the
  approval handler records a structured "wanted" marker that lives in
  the action plan; a developer (or the healer agent in a future
  session) implements it. Approving here is acknowledgment, not
  automation.
* **Never proposes a tool the registry already provides.** Membership
  in `ToolRegistry.list_tools()` short-circuits.
* **Never proposes from a skill with no triggers.** Skills declared
  but never wired (no `triggers:` and not granted to any agent) are
  excluded — they wouldn't fire at runtime anyway.
* **Never blocks dispatch.** A missing tool is a future improvement
  signal; the agent's current dispatch loop already handles unknown
  tools at runtime.

## Output format

For each missing-tool cluster ≥ threshold, the detector writes one
proposal to:

```
<vault>/agntspace/memory/pending/tool-proposals/cap-<sha8>.json
```

The proposal carries:

* `kind: "tool"`
* `target`: the tool name (e.g. `send_email_v2`)
* `title`: "Implement tool: <name>"
* `description`: short one-paragraph rationale
* `rationale`: longer explanation listing the granting skills + an
  example skill body excerpt
* `examples`: up to N skill slugs that grant the tool
* `payload`: structured data for the approve handler:
  - `granting_skills`: list of skill names with this tool granted
  - `granting_skill_count`: integer

## Tuning knobs (`config/detect.yaml`)

* `enabled` (bool, default true) — gate the whole detector
* `min_granting_skills` (int, default 1) — minimum skills referencing
  the tool before a proposal is emitted (1 = strict, 2 = noise floor)
* `skip_if_pending` (bool, default true) — dedup vs in-flight
  proposals
* `cadence_hours` (int, default 24) — heartbeat interval
* `exclude_tool_prefixes` (list, default empty) — prefixes to skip
  (e.g. `mcp_` if you only want first-party tool gaps)

Edit YAML, restart server, no code change. (Rule 12.)

## Approval semantics

Approval is acknowledgment + queueing — implementing the tool is a
code change, not a YAML change. The approve handler records a
medium-importance activity event and removes the pending file. The
user (or a developer) implements the tool in
`server/src/agntspace/agent/tools.py` and registers it in `main.py`.

Rejection is silent — removes the pending file, emits a
`user_override` activity event, no further side effects. The skill's
`tools_granted` line stays unchanged; if the user wants to also strip
the dangling reference from the skill, that's a separate edit.