---
name: recon-ai-enrichment
description: >
  Wiring an LLM into a recon tool's decisions ("let AI pick {feature} for
  {tool}"): the never-raise contract, the per-target cache, the full+partial
  coverage, and the two UI toggles bound to one field. A raising AI helper or an
  empty fallback silently breaks or disables a live scan.
  Trigger: adding or editing a recon AI hook; a new
  recon/helpers/ai_planner/{tool}_{feature}.py; a /llm/{tool}-{feature} endpoint
  in agentic/api.py; a data.{tool}Ai{feature} toggle; editing
  apply_ai_pipeline_overrides in recon/project_settings.py.
license: MIT
metadata:
  author: redamon
  version: "1.0.0"
  scope: [recon]
  auto_invoke:
    - "Wiring an LLM into a recon tool's decisions (AI in pipeline)"
    - "Adding or editing a recon/helpers/ai_planner hook or its /llm endpoint"
---

## When to Use

- Adding AI decision-making to an existing recon tool (tag selection, extension
  guessing, WAF classification, etc.).

For adding a whole new recon tool, use `recon-tool-integration`. For the setting
that toggles it, use `project-settings-cascade`.

---

## Critical Rules

- **NEVER let the AI helper raise.** Every failure path returns the user's
  current value. Recon stdout tails into the webapp's SSE recon drawer, so an
  exception both breaks the scan and blanks the stream. Pattern:
  [recon/helpers/ai_planner/nuclei_tags.py:94](../../recon/helpers/ai_planner/nuclei_tags.py#L94)
  ("Never raises -- returns `current_tags` on any failure").
- **NEVER fall back to an empty list/string.** For tools where empty means "skip
  the work" (nuclei tags, ffuf extensions) that **silently turns detection off**.
  Fall back to the user's current value, not `[]`/`""`.
- **NEVER call the LLM with no signal.** Empty fingerprint -> return the current
  value; do not send an empty prompt.
- **NEVER hook the AI separately in partial recon.** Most tools share one entry
  function (e.g. `run_vuln_scan` is called by both `main_recon_modules/` and
  `partial_recon_modules/`); hook it **once** and both paths inherit. `grep` the
  function name to confirm before you edit. The feature must work in the full
  pipeline AND partial recon.
- **NEVER touch [webapp/src/lib/recon-presets/presets/](../../webapp/src/lib/recon-presets/):**
  the `aiInPipeline` cascade (`apply_ai_pipeline_overrides`,
  [recon/project_settings.py:1968](../../recon/project_settings.py#L1968)) is the
  single source of truth for per-tool AI flags. Presets must not hard-code them;
  update the Zod schema instead.
- **ALWAYS cache a per-target hook** keyed by tech fingerprint (Server,
  X-Powered-By, ...) so N targets behind one stack collapse to one LLM call
  ([ffuf_extensions.py](../../recon/helpers/ai_planner/ffuf_extensions.py)). A
  per-scan hook ([nuclei_tags.py](../../recon/helpers/ai_planner/nuclei_tags.py))
  runs once and needs no cache.
- **ALWAYS put the toggle in two places bound to the same field**
  `data.{tool}Ai{Feature}`: the master AI-in-Pipeline panel
  ([TargetSection.tsx:362](../../webapp/src/components/projects/ProjectForm/sections/TargetSection.tsx#L362))
  and the tool's own section (e.g.
  [NucleiSection.tsx](../../webapp/src/components/projects/ProjectForm/sections/NucleiSection.tsx)).
  Read AND write the same field; **no copy-on-flip** (they stay in sync because
  they share the field).

---

## The pieces

| Piece | File | Note |
| --- | --- | --- |
| Helper | `recon/helpers/ai_planner/{tool}_{feature}.py` | POSTs to the agent; never raises; logs `[*][{Tool}-AI]` / `[!][{Tool}-AI]` to stdout |
| Agent endpoint | [agentic/api.py](../../agentic/api.py) (e.g. `/llm/nuclei-tags` at :641, `/llm/ffuf-extensions` at :543) | Pydantic model; returns 422 (bad body) / 503 (no key), never 500 |
| Setting | [recon/project_settings.py](../../recon/project_settings.py) `DEFAULT_SETTINGS` + `fetch_project_settings` + both branches of `apply_ai_pipeline_overrides` | see `project-settings-cascade` |
| Zod | [webapp/src/lib/recon-preset-schema.ts](../../webapp/src/lib/recon-preset-schema.ts) | so AI-generated presets see the field |
| UI | `TargetSection.tsx` + the tool's section | two toggles, one field |

## Commands

```bash
docker compose build agent && docker compose up -d agent   # the /llm endpoint lives in agentic/ (baked)
# recon/*.py is volume-mounted at spawn - no rebuild
# verify: a minimal POST returns 422/503, never 500; and a live scan logs
# [*][{Tool}-AI] in BOTH a full run and a partial recon run; stop the agent -> scan still completes.
```

## Resources

- [docs/readmes/coding_agent_prompts/PROMPT.ADD_AI_IN_RECON.md](../../docs/readmes/coding_agent_prompts/PROMPT.ADD_AI_IN_RECON.md) - full walkthrough, per-target vs per-scan, verify steps
- Related skills: `recon-tool-integration`, `project-settings-cascade`
