web.research
web.research is web.search + web.fetch + a synthesis pass, composed into one skill so an agent
gets a real, cited answer instead of needing to orchestrate three tool calls itself. The Studio
planner’s mandatory research task (see Briefs) is built on this skill.
Schema
Input = { question: string, depth?: 1 | 2 | 3, // default 1 — "1 = quick, 3 = thorough" recency?: 'day' | 'week' | 'month' | 'year',}
Output = { answer: string, sources: Array<{ title: string, url: string }>, notes: string, queries: string[], // every search actually run, including follow-ups costUsd: number,}Depth controls everything
| Depth | Pages read | Follow-up queries | Chars/page | Search results fetched | Synthesis tier |
|---|---|---|---|---|---|
| 1 (quick) | 3 | 0 | 5,000 | 6 | cheap |
| 2 | 6 | 2 | 6,000 | 8 | cheap |
| 3 (thorough) | 10 | 3 | 7,000 | 10 | standard |
At depth 2 and 3, before reading anything, one cheap-tier call proposes up to followUps
additional search queries based on the first page of results — filling gaps like specifics, opposing
views, or primary sources — and those results are merged in (deduplicated by URL) before the top N
pages are chosen and fetched in parallel.
Synthesis, and what happens when a page fails
Every chosen page is read via web.fetch; a page that fails to
fetch still contributes its search snippet rather than being dropped silently, so it can still be
cited. The corpus (title + URL + text per page) goes to one structured-free completion instructed to
cite claims inline as [2]-style bracketed numbers, say plainly when sources disagree, and say what’s
missing if the sources don’t answer the question — capped at roughly 350 words for depth 1, 700 for
deeper research.
Without an OpenRouter key, or if synthesis fails, extractiveAnswer() returns a plain listing of
the top 6 sources with their snippets instead of prose — the skill always resolves with something
useful, never an error, as long as at least one page was found.
Example
curl -s -X POST http://localhost:4000/api/studio/skills/invoke \ -H 'content-type: application/json' -b cookies.txt \ -d '{"name":"web.research","input":{"question":"What changed in Node.js 22 for TypeScript projects?","depth":1}}'{ "ok": true, "output": { "answer": "Node.js 22 stabilises the built-in TypeScript stripping (`--experimental-strip-types`)... [1][2]", "sources": [ { "title": "Node.js 22 release notes", "url": "https://nodejs.org/en/blog/release/v22.0.0" }, { "title": "TypeScript support in Node.js", "url": "https://nodejs.org/en/learn/typescript/run-natively" } ], "notes": "search provider: duckduckgo", "queries": ["What changed in Node.js 22 for TypeScript projects?"], "costUsd": 0.0009 }}