feat(research): wire tools + research workflow graph (research-workflow §2/§3)

Makes the research feature runnable end-to-end, off by default.

- config: [tools.research] (enabled, searxng_url, max_results, max_fetch_bytes).
- registration: web_search/web_fetch are built into BOTH the default and per-workspace
  tool registries when research.enabled, sharing one HTTP client threaded from Main
  (none built on the static path). Egress stays harness-enforced: web_fetch is T2
  (operator-approved) and the existing NetworkHostRule still applies.
- workflow: examples/workflows/research.toml — decompose → gather → report, with the
  three artifact schemas and prompts. Fan-out (search per sub-question, fetch per source)
  runs as repeated tool calls inside the gather stage (Correx has no parallel agents);
  per-source synthesis into the dossier is the compression step, so the report stage
  consumes summaries, never raw pages. ResearchWorkflowTest validates the graph contract.

To run: set [tools.research].enabled, register the 3 [[artifacts]], copy research.toml +
prompts + schemas into the workflows dir, start SearXNG. Launch like any workflow (the
T2 fetch approval surfaces as an approval card; the report opens in the artifact viewer).

Follow-ups (noted, not blocking): batch fetch-approval at the source-list level (§3),
a dedicated SourceFetched/LowQualityExtraction event (quality + content hash are already
in tool-result metadata), dynamic per-session egress allowlist, and the web approval client (§6).
This commit is contained in:
2026-06-13 23:18:19 +04:00
parent ad2d38ce46
commit 7a0d4d0ee2
11 changed files with 321 additions and 1 deletions
@@ -0,0 +1,21 @@
You are the **Research Gatherer** — you run the plan and build a dossier of summarized sources.
You have two tools:
- `web_search(query)` — searches the local SearXNG instance; returns result titles, URLs, snippets.
- `web_fetch(url)` — fetches a URL and returns its main content as clean markdown. Fetches are
approved by the operator before they run, so choose sources deliberately — quality over quantity.
Steps:
1. For each search query in the `research_plan` above, call `web_search`.
2. From the results, pick the most promising, authoritative sources. Skip duplicates, SEO spam,
and pages unlikely to contain primary information.
3. `web_fetch` each chosen source. If a fetch comes back empty or clearly low-quality (a paywall
or JS-only page), drop it and move on — do not retry it.
4. **Summarize each fetched source on its own**, in your own words: what it contributes to the
question and which sub-question(s) it bears on. This is the most important step — the next
stage sees only your summaries, never the raw pages, so a faithful summary is the whole product.
Never copy raw page text into a summary. Cite each source by its exact URL.
Emit your result as the `source_dossier` artifact (JSON, schema provided):
- `sources`: one entry per fetched source, each with `url`, `title`, `summary`, and `relevance`.