docs(07-01): complete research agent plan — SearXNG client, ResearchAgent, trigger endpoint

This commit is contained in:
Mikkel Georgsen 2026-04-10 07:51:58 +00:00
parent 0072aa41bd
commit cbe411996f

View file

@ -0,0 +1,90 @@
---
phase: 07-research-agent-search
plan: "01"
subsystem: research-agent
tags: [research, searxng, ai-enrichment, background-worker]
dependency_graph:
requires: [internal/ai, internal/netbox, internal/inventory, internal/config]
provides: [internal/research, internal/api/handlers/research]
affects: [cmd/hwlab/main.go, internal/api/router.go]
tech_stack:
added: [internal/research package]
patterns: [interface-injection for testability, TDD red-green, background ticker worker]
key_files:
created:
- internal/research/searxng.go
- internal/research/searxng_test.go
- internal/research/agent.go
- internal/research/agent_test.go
- internal/api/handlers/research.go
modified:
- internal/ai/client.go
- internal/netbox/client.go
- internal/config/config.go
- internal/api/router.go
- cmd/hwlab/main.go
decisions:
- "Used interface injection (NetBoxer, TextCompleter, CatalogTransitioner) in Agent instead of concrete types to enable stub-based unit tests without live NetBox"
- "Added TierClient.TextComplete as separate method rather than reusing AnalyzePhotos to keep vision and text paths distinct"
- "SanitizeQuery exported (capitalised) to allow external test package verification of T-07-01 mitigation"
- "Agent.RunOnce returns (int, error) rather than just error so callers and tests can assert enrichment count"
metrics:
duration_seconds: 256
completed_date: "2026-04-10"
tasks_completed: 2
tasks_total: 2
files_created: 5
files_modified: 5
---
# Phase 07 Plan 01: SearXNG Client + ResearchAgent Summary
**One-liner:** SearXNG HTTP client + background ResearchAgent that polls NetBox for `needs_research` items, enriches via SearXNG search + Tier 2 LLM text completion, patches NetBox custom fields, and transitions status to `researched` every 10 minutes or on-demand via `POST /api/research/trigger`.
## Tasks Completed
| Task | Name | Commit | Key Files |
|------|------|--------|-----------|
| 1 | SearXNG client + netbox.ListDevicesWithStatus | 30cd279 | internal/research/searxng.go, internal/netbox/client.go, internal/config/config.go |
| 2 | ResearchAgent worker + main.go wiring + trigger endpoint | 0072aa4 | internal/research/agent.go, internal/api/handlers/research.go, internal/api/router.go, cmd/hwlab/main.go |
## Decisions Made
1. **Interface injection for Agent dependencies**`NetBoxer`, `TextCompleter`, and `CatalogTransitioner` interfaces in `internal/research/agent.go` allow stub injection in tests without a live NetBox or LLM. The concrete `*netbox.Client` and `*ai.TierClient` satisfy these interfaces automatically.
2. **TierClient.TextComplete as distinct method** — Rather than forcing research prompts through `AnalyzePhotos` (which builds a vision multipart message), added a clean `TextComplete(ctx, prompt) (string, error)` method on `TierClient` that posts a simple single-user-message ChatCompletion. This keeps the vision and text paths separate and makes intent clear.
3. **SanitizeQuery exported** — The T-07-01 threat mitigation (strip `[^a-zA-Z0-9 .\-_]+` before SearXNG dispatch) is tested from the `_test` package, which requires the function to be exported. Consistent with the plan's explicit mention of adversarial input testing.
4. **Product URL fallback** — If the LLM does not return a `product_url` in its JSON response, the agent falls back to the first SearXNG result URL. This ensures `product_url` is populated even when the LLM provides incomplete JSON.
## Deviations from Plan
### Auto-fixed Issues
**1. [Rule 1 - Bug] Test expectation for sanitizeQuery with `/` character**
- **Found during:** Task 2 GREEN phase
- **Issue:** Test case `"Dell<script>alert(1)</script>"` expected `"Dell script alert 1 /script "` but `/` is correctly stripped by the `[^a-zA-Z0-9 .\-_]+` regex
- **Fix:** Updated test expectation to `"Dell script alert 1 script"` (correct behavior)
- **Files modified:** internal/research/agent_test.go
- **Commit:** 0072aa4
None otherwise — plan executed as written.
## Known Stubs
None. All interfaces are fully implemented. The `NoOpResearchClient` in `internal/ai/research.go` is still present but is no longer used — it has been superseded by `research.SearXNGClient` wired in `main.go`.
## Threat Surface Scan
No new trust boundaries introduced beyond those documented in the plan's threat model. T-07-01 (query sanitization) is mitigated and tested. T-07-03 (trigger DoS) is mitigated — `RunOnce` is bounded per-item with no queuing amplification.
## Self-Check: PASSED
- internal/research/searxng.go: FOUND
- internal/research/agent.go: FOUND
- internal/api/handlers/research.go: FOUND
- Commit 30cd279: FOUND
- Commit 0072aa4: FOUND
- `go build ./...`: PASSES
- `go test ./internal/research/...`: 9/9 PASS