@lincoln504/pi-research 1.3.12 → 1.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.mjs +5670 -4705
  3. package/dist/prompts/research-tool-usage.md +1 -1
  4. package/dist/prompts/researcher.md +1 -0
  5. package/dist/prompts/system-lead-router.md +122 -0
  6. package/dist/prompts/system-lead-synthesizer.md +46 -0
  7. package/dist/thread-worker.mjs +169 -62
  8. package/docs/ARCHITECTURE.md +18 -10
  9. package/docs/CHANGELOG.md +225 -0
  10. package/docs/CONFIGURATION.md +106 -7
  11. package/package.json +2 -2
  12. package/scripts/build.cjs +8 -1
  13. package/skills/pi-research/SKILL.md +1 -1
  14. package/src/cli.ts +5 -1
  15. package/src/config.ts +22 -12
  16. package/src/constants.ts +1 -1
  17. package/src/core/interfaces/observer-interfaces.ts +3 -3
  18. package/src/core/interfaces/orchestration-interfaces.ts +2 -0
  19. package/src/core/interfaces/planning-interfaces.ts +7 -1
  20. package/src/core/interfaces/scheduler-interfaces.ts +18 -2
  21. package/src/core/llm/agentic-repair.ts +7 -2
  22. package/src/core/llm/inject-date.ts +4 -1
  23. package/src/core/llm/llm-utils.ts +7 -0
  24. package/src/core/planning-service.ts +766 -91
  25. package/src/core/planning-utils.ts +21 -5
  26. package/src/core/service-initialization.ts +19 -3
  27. package/src/healthcheck/index.ts +11 -2
  28. package/src/index.ts +4 -3
  29. package/src/infrastructure/browser/browser-client.ts +68 -34
  30. package/src/infrastructure/browser/browser-task-scheduler.ts +236 -43
  31. package/src/infrastructure/browser/config.ts +79 -1
  32. package/src/infrastructure/browser/priority-task-queue.ts +55 -1
  33. package/src/infrastructure/browser/scheduler-factory.ts +12 -1
  34. package/src/infrastructure/browser/task-execution-service.ts +5 -5
  35. package/src/infrastructure/browser/thread-worker-browser.ts +87 -36
  36. package/src/infrastructure/browser/thread-worker-messaging.ts +99 -10
  37. package/src/infrastructure/browser/thread-worker.mjs +169 -62
  38. package/src/infrastructure/browser/thread-worker.ts +13 -5
  39. package/src/infrastructure/browser/worker-pool-manager.ts +127 -12
  40. package/src/infrastructure/embedding/embedding-client.ts +14 -4
  41. package/src/infrastructure/embedding/embedding-factory.ts +83 -15
  42. package/src/infrastructure/embedding/embedding-server.ts +34 -0
  43. package/src/infrastructure/file-lock-service.ts +214 -19
  44. package/src/infrastructure/knowledge-store-service.ts +14 -0
  45. package/src/infrastructure/research-run-semaphore.ts +1 -1
  46. package/src/infrastructure/service-initialization.ts +7 -0
  47. package/src/knowledge/embedder-init.ts +46 -19
  48. package/src/knowledge/embedder.ts +11 -3
  49. package/src/knowledge/index.ts +36 -10
  50. package/src/knowledge/store.ts +29 -5
  51. package/src/logger.ts +11 -0
  52. package/src/orchestration/deep-research-orchestrator.ts +143 -87
  53. package/src/orchestration/quick-research-orchestrator.ts +11 -2
  54. package/src/orchestration/research-orchestration-service.ts +8 -0
  55. package/src/orchestration/research-synthesis-service.ts +53 -1
  56. package/src/orchestration/researcher-executor.ts +53 -2
  57. package/src/orchestration/researcher.ts +15 -2
  58. package/src/orchestration/session-state.ts +16 -0
  59. package/src/prompts/research-tool-usage.md +1 -1
  60. package/src/prompts/researcher.md +1 -0
  61. package/src/prompts/system-lead-router.md +122 -0
  62. package/src/prompts/system-lead-synthesizer.md +46 -0
  63. package/src/sdk.ts +10 -0
  64. package/src/skill-install/skill-installer.ts +5 -1
  65. package/src/stackexchange/index.ts +3 -0
  66. package/src/stackexchange/rest-client.ts +7 -12
  67. package/src/tools/research-knowledge-search.ts +11 -3
  68. package/src/tools/research-tool-definition.ts +38 -7
  69. package/src/tools/scrape.ts +6 -0
  70. package/src/tools/search.ts +7 -0
  71. package/src/tools/security.ts +31 -10
  72. package/src/tools/stackexchange.ts +13 -7
  73. package/src/tools/youtube-transcript.ts +26 -12
  74. package/src/utils/cancellation.ts +36 -0
  75. package/src/utils/coverage-digest.ts +204 -0
  76. package/src/utils/host-config.ts +12 -1
  77. package/src/utils/llm-usage.ts +33 -6
  78. package/src/utils/log-utils.ts +10 -0
  79. package/src/utils/metrics-summary.ts +20 -4
  80. package/src/utils/metrics.ts +27 -1
  81. package/src/web-research/browser-search.ts +102 -22
  82. package/src/web-research/web-scraper.ts +27 -16
  83. package/src/youtube/potoken.ts +8 -1
  84. package/src/youtube/transcript-client.ts +56 -5
  85. package/dist/prompts/system-lead-evaluator.md +0 -118
  86. package/src/prompts/system-lead-evaluator.md +0 -118
@@ -123,23 +123,34 @@ async function extractPdfToMarkdown(bytes: Uint8Array): Promise<string> {
123
123
 
124
124
  try {
125
125
  const doc = new WasmPdfDocument(bytes);
126
- const pageCount = doc.pageCount();
127
-
128
- let markdown = `# PDF Document\n\n**Pages:** ${pageCount}\n\n`;
129
-
126
+ // free() is native/WASM-side memory, not GC-reclaimed — it must run on
127
+ // every exit, including the case toMarkdownAll() AND the per-page fallback
128
+ // both throw (a malformed/encrypted PDF the codebase already treats as
129
+ // routine on the open web, so this path fires regularly). It used to sit
130
+ // after the fallback loop, reachable only when nothing below it threw —
131
+ // the one case that needed the fallback landing squarely on the one case
132
+ // that skipped the free(), leaking one WasmPdfDocument per occurrence in a
133
+ // browser-server process that stays warm across many research sessions.
130
134
  try {
131
- markdown += doc.toMarkdownAll();
132
- } catch {
133
- for (let i = 0; i < pageCount; i++) {
134
- markdown += `## Page ${i + 1}\n\n${doc.toMarkdown(i)}\n\n`;
135
+ const pageCount = doc.pageCount();
136
+
137
+ let markdown = `# PDF Document\n\n**Pages:** ${pageCount}\n\n`;
138
+
139
+ try {
140
+ markdown += doc.toMarkdownAll();
141
+ } catch {
142
+ for (let i = 0; i < pageCount; i++) {
143
+ markdown += `## Page ${i + 1}\n\n${doc.toMarkdown(i)}\n\n`;
144
+ }
135
145
  }
146
+
147
+ const pdfExtractionDuration = Date.now() - pdfExtractionStart;
148
+ metrics.observe('scrape_pdf_conversion_ms', pdfExtractionDuration);
149
+ metrics.increment('scrape_pdf_conversions_total', 1, { status: 'success', pages: String(pageCount) });
150
+ return markdown;
151
+ } finally {
152
+ doc.free();
136
153
  }
137
-
138
- doc.free();
139
- const pdfExtractionDuration = Date.now() - pdfExtractionStart;
140
- metrics.observe('scrape_pdf_conversion_ms', pdfExtractionDuration);
141
- metrics.increment('scrape_pdf_conversions_total', 1, { status: 'success', pages: String(pageCount) });
142
- return markdown;
143
154
  } catch (e) {
144
155
  const msg = e instanceof Error ? e.message : String(e);
145
156
  // debug, not error: a malformed/encrypted/scanned-image-only PDF is an
@@ -300,7 +311,7 @@ async function scrapeWithFetch(url: string, signal?: AbortSignal): Promise<Scrap
300
311
  const contentLength = response.headers.get('content-length');
301
312
  if (contentLength) {
302
313
  const size = parseInt(contentLength, 10);
303
- if (contentType.includes('application/pdf') || url.toLowerCase().endsWith('.pdf')) {
314
+ if (contentType.includes('application/pdf') || currentUrl.toLowerCase().endsWith('.pdf')) {
304
315
  if (size > MAX_PDF_SIZE) {
305
316
  const sizeMB = Math.round(size / 1024 / 1024);
306
317
  logger.warn(`[Scrapers] PDF too large (Content-Length: ${sizeMB}MB, max 100MB), skipping`);
@@ -319,7 +330,7 @@ async function scrapeWithFetch(url: string, signal?: AbortSignal): Promise<Scrap
319
330
  }
320
331
  }
321
332
 
322
- if (contentType.includes('application/pdf') || url.toLowerCase().endsWith('.pdf')) {
333
+ if (contentType.includes('application/pdf') || currentUrl.toLowerCase().endsWith('.pdf')) {
323
334
  let pdfBytes: Uint8Array;
324
335
  try {
325
336
  pdfBytes = await readBodyCapped(response, MAX_PDF_SIZE);
@@ -44,6 +44,7 @@
44
44
  import { logger } from '../logger.ts';
45
45
  import { safeUnref } from '../utils/safe-unref.ts';
46
46
  import { metrics } from '../utils/metrics.ts';
47
+ import { readJsonCapped } from '../utils/http-body.ts';
47
48
 
48
49
  /**
49
50
  * Well-known request key for the YouTube web PoToken program. This is a public
@@ -229,7 +230,13 @@ async function doMint(
229
230
  body: JSON.stringify([requestKey, botguardResponse]),
230
231
  signal: opts.signal,
231
232
  });
232
- const integrityJson = (await integrityResponse.json()) as unknown[];
233
+ if (!integrityResponse.ok) {
234
+ throw new Error(`HTTP ${integrityResponse.status} from attestation server`);
235
+ }
236
+ // The attestation response is a short JSON array; readJsonCapped bounds it the
237
+ // same way as every other externally-controlled body this codebase reads (the
238
+ // default 32MB cap is already generous for this endpoint).
239
+ const integrityJson = await readJsonCapped<unknown[]>(integrityResponse);
233
240
  const integrityToken = integrityJson?.[0] as string | undefined;
234
241
  if (!integrityToken) {
235
242
  throw new Error('Integrity token rejected by attestation server (likely a non-residential IP)');
@@ -18,6 +18,7 @@
18
18
  */
19
19
 
20
20
  import { withTimeout, retryWithBackoff, createTimeoutSignal } from '../web-research/retry-utils.ts';
21
+ import { getSsrfSafeFetcher } from '../web-research/scraper-utils.ts';
21
22
  import { CircuitBreaker } from '../utils/circuit-breaker.ts';
22
23
  import { logger } from '../logger.ts';
23
24
  import { redactSecrets } from '../utils/log-utils.ts';
@@ -109,7 +110,31 @@ export async function fetchVideoTranscripts(
109
110
  } catch {
110
111
  // non-fatal: logging stays at default if the API ever changes
111
112
  }
112
- const fetchImpl: typeof fetch = (input, init) => fetch(input, init);
113
+ // Connect-time SSRF pin (see scraper-utils.ts's getSsrfSafeFetcher): closes the
114
+ // gap between assertTrustedTimedtextUrl's host check below and fetch's own
115
+ // re-resolution at connect time. The host allowlist only constrains WHICH
116
+ // hostname a redirect can name; it says nothing about what that hostname
117
+ // resolves to at the moment the socket opens, and this codebase already
118
+ // treats a resolver answering differently between validation and connect as
119
+ // a real threat elsewhere (thread-worker-messaging.ts's DNS-rebinding
120
+ // backstop applies even to already-trusted hosts). One fetchImpl closure
121
+ // covers every network call in this batch: the BotGuard/attestation fetches
122
+ // inside mintPoTokens() and the timedtext fetch below. Falls back to the
123
+ // plain global fetch for non-string/URL input (never seen in practice here)
124
+ // since the dispatcher pairing requires a URL string; see getSsrfSafeFetcher's
125
+ // own doc comment for why the fetch and its dispatcher must travel together.
126
+ const ssrfFetcher = await getSsrfSafeFetcher();
127
+ const fetchImpl: typeof fetch = ssrfFetcher
128
+ ? (input, init) => {
129
+ if (typeof input === 'string' || input instanceof URL) {
130
+ return ssrfFetcher.fetch(String(input), {
131
+ ...(init ?? {}),
132
+ dispatcher: ssrfFetcher.dispatcher,
133
+ }) as unknown as Promise<Response>;
134
+ }
135
+ return fetch(input, init);
136
+ }
137
+ : (input, init) => fetch(input, init);
113
138
 
114
139
  // 1) Throwaway session to obtain stable visitor data for the PoToken binding.
115
140
  let visitorData: string;
@@ -245,10 +270,36 @@ async function fetchOne(
245
270
  // response headers AND the body read are bounded and tear the socket
246
271
  // down on timeout/cancellation (withTimeout alone would not bound text()).
247
272
  const fetchSignal = createTimeoutSignal(timeoutMs, signal);
248
- const response = await fetchImpl(url, {
249
- signal: fetchSignal,
250
- headers: { 'User-Agent': 'Mozilla/5.0', 'Accept-Language': lang },
251
- });
273
+ // Manual redirect following, re-validating each hop against the same
274
+ // trusted-host allowlist as the first hop. `redirect: 'follow' (the
275
+ // default) would carry the PoToken-bearing request to whatever host a
276
+ // 3xx Location header names with no revalidation — assertTrustedTimedtextUrl
277
+ // above only ever checked base_url, the pre-redirect URL. fetchImpl
278
+ // carries the connect-time IP pin (see its construction above), so each
279
+ // hop is also protected against a resolver answering differently
280
+ // between this host check and the moment the socket actually opens.
281
+ const MAX_REDIRECTS = 5;
282
+ let currentUrl = url;
283
+ let response!: Response;
284
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
285
+ response = await fetchImpl(currentUrl, {
286
+ signal: fetchSignal,
287
+ redirect: 'manual',
288
+ headers: { 'User-Agent': 'Mozilla/5.0', 'Accept-Language': lang },
289
+ });
290
+ if (response.status >= 300 && response.status < 400) {
291
+ const location = response.headers.get('location');
292
+ if (!location) break; // No Location header — treat as final response
293
+ const resolved = new URL(location, currentUrl).href;
294
+ assertTrustedTimedtextUrl(resolved);
295
+ // Drain the redirect response before following: an unconsumed body
296
+ // pins its socket (undici keeps it open until read/cancelled or GC).
297
+ void response.body?.cancel()?.catch(() => { /* best-effort */ });
298
+ currentUrl = resolved;
299
+ continue;
300
+ }
301
+ break; // Non-redirect response
302
+ }
252
303
  // Surface the real HTTP status. Without this, a 403 (age-restricted) or
253
304
  // 404 (removed) returns HTML that parseJson3 yields '' for, which would
254
305
  // be misreported below as a bot-protection/PoToken failure.
@@ -1,118 +0,0 @@
1
- # Lead Evaluator
2
-
3
- You orchestrate the next phase of research.
4
-
5
- ## Your Context
6
- - **ROOT QUERY**: {{root_query}}
7
- - **Current round**: {{round_number}} / {{max_rounds}}
8
- - **Complexity Level**: {{complexity_label}}
9
- {{initial_agenda_section}}
10
- {{previous_queries_section}}
11
- {{additional_considerations}}
12
-
13
- ## CRITICAL: Steering Compliance Checkpoint
14
- - **STRICT STEERING**: If there is an "ADDITIONAL USER GUIDANCE" section above, you MUST treat every point in it as a **mandatory rule and instruction** for your decisions, next round goals, and queries. Do not ignore them. They are directional requirements.
15
- - **STEERING SATISFACTION**: Before deciding to SYNTHESIZE, you MUST explicitly verify that ALL user steering requirements have been satisfied by the existing findings. If ANY steering requirement remains unaddressed or only superficially covered, you MUST DELEGATE — do NOT synthesize. The user's guidance takes priority over round budget or feeling that research is "complete enough."
16
-
17
- {{disabled_tools_section}}
18
-
19
- ---
20
-
21
- ## Complexity-Aware Decision Thresholds
22
-
23
- {{complexity_guidance}}
24
-
25
- {{round_phase_guidance}}
26
-
27
- ---
28
-
29
- ## Decision Framework
30
-
31
- **SYNTHESIZE if:** Research meets the complexity-specific synthesis criteria above.
32
- **DELEGATE if:** Research meets the complexity-specific delegation criteria above.
33
- **Note on Guidance**: If the user provides additional guidance mid-research (via Alt+Enter or direct follow-up), those messages will be provided to you. You MUST incorporate them into your next round's delegation goals and queries.
34
-
35
- **STAY IN SCOPE (every delegation):**
36
- - Going deeper on in-scope material is always welcome. Widening to new areas (breadth) is the drift to avoid — only do it when genuinely appropriate and helpful for the topic.
37
- - A gap is missing depth on what was asked — not a broader topic next to it. Don't add anything the query didn't ask for, even if it came up in earlier findings.
38
- - Match the query's scope-bounding constraints exactly — don't broaden them, don't narrow them.
39
-
40
- **COMMON MISTAKES TO AVOID:**
41
- - Do NOT synthesize early just because you've done "enough" rounds. Synthesis should only occur when you have comprehensive, high-quality findings across all major topics.
42
- - Do NOT synthesize with gaps remaining, hoping they can be "filled in later". Complete the research first.
43
- - Do NOT invent a gap outside the query's scope just to keep going or fill the budget. Finish what was asked; don't add what wasn't.
44
-
45
- Use unique, targeted queries for any new researchers.
46
-
47
- **Decision**: Return valid JSON in a code block:
48
-
49
- **If synthesizing**:
50
- ```json
51
- {
52
- "action": "synthesize",
53
- "content": "<Plain prose report with [N] citations, organized by topic. No markdown headings, bullets, or bold text. Followed by the mandatory CITED LINKS section.>"
54
- }
55
- ```
56
-
57
- **If delegating**:
58
- ```json
59
- {
60
- "action": "delegate",
61
- "researchers": [
62
- {
63
- "id": "{{round_number}}.1",
64
- "name": "<Researcher Specialty>",
65
- "goal": "<Focused gap or new angle to investigate>",
66
- "queries": ["<query_1>", "<query_2>", "<query_3>", "..."]
67
- }
68
- ],
69
- "allQueries": ["<flat_list_of_all_queries_from_all_new_researchers>"]
70
- }
71
- ```
72
-
73
- **DELEGATION REQUIREMENTS**:
74
- - **CRITICAL — Queries are mandatory**: Every researcher MUST have at least one query. Never plan a researcher without queries. Researchers receive ONLY the search results you delegate to them.
75
- - **Maximize queries**: For EACH researcher, generate the maximum number of targeted, specific queries within the budget. Do not plan fewer than needed — fill the budget. Queries should target primary sources and authoritative evidence.
76
- - **YouTube discovery**: For roughly one in {{youtube_query_every_n}} queries, append the word `youtube` to the query text (e.g. `"<topic> analysis youtube"`). DuckDuckGo rarely surfaces YouTube on its own, and YouTube links let researchers read video transcripts. Skip only when the gap is clearly unsuited to video.
77
- - **Temporal anchoring**: Anchor every new time-sensitive query to the current month and year shown at the top of this prompt. Use that exact year (and month where relevant); never default to an older year than the one shown there unless the user explicitly asks about the past.
78
- - **Flexible coverage**: Use up to {{max_team_size}} researchers to cover distinct angles in parallel. Scale based on research gaps — a single well-targeted researcher is often sufficient for focused gaps.
79
- - **Source diversity**: Encourage researchers to find multiple authoritative sources per topic area to enable comprehensive citations in the final synthesis.
80
-
81
- ---
82
-
83
- ## Quality Standards for Delegation
84
-
85
- When delegating, ensure:
86
- - **Query Specificity**: Each new query targets distinct, unexplored territory
87
- - **No Redundancy**: Do not repeat queries from previous rounds
88
- - **Specialized Focus**: Each new researcher has a clear, distinct angle
89
- - **Gap-Driven**: Only delegate when gaps cannot be resolved from existing findings
90
- - **Progressive Depth**: New queries go deeper, or to a new angle still inside the query's scope — never outside it, and never repeating surface-level coverage
91
-
92
- ---
93
-
94
- ## Synthesis Protocol (action = synthesize)
95
-
96
- 1. **Organization**: Organize the report logically **BY TOPIC**. Do NOT structure it by researcher or round.
97
- 2. **Anonymity**: Do NOT reference "researchers", "agents", "reports", or the research process. Present the findings as a direct, unified knowledge base.
98
- 3. **Master Links List**: A **Global Source List** has been provided to you. Use these sequential numbers [1], [2], [3], etc., for all inline citations. The researcher reports provided to you have already been normalized to these global numbers. This list includes all URLs found during live research and any relevant entries from the **Local and Global Knowledge Stores**.
99
- 4. **Exhaustive Synthesis**: Use ALL findings from ALL reports. Include every fact, date, name, and statistic verbatim. Longer is better.
100
- 5. **Strict Grounding**: Every sentence must come from a report. Use [N] inline citations. No prior knowledge.
101
- 6. **CRITICAL — Links at Bottom Only**:
102
- - Write all topic sections first with inline citations [1], [2], etc., using the numbers from the **Global Source List**.
103
- - Place exactly ONE `CITED LINKS` section at the VERY END of the entire synthesis.
104
- - This section must contain the complete master list of all unique URLs exactly as provided in the **Global Source List**.
105
- - Do NOT include any links within topic sections or subsections.
106
- - Format: `[1] https://url.com [Source: ...] — brief description` on each line.
107
-
108
- > **MANDATORY TERMINATION RULE**: Your synthesis is not complete until it ends with `CITED LINKS`. Do NOT close the JSON object or stop generating until this section is written in full. A synthesis without `CITED LINKS` is a failed output.
109
-
110
- ---
111
-
112
- ## Output Requirements
113
-
114
- - **Researcher IDs**: Use Round.Index format (e.g. **{{round_number}}.1**, **{{round_number}}.2**).
115
- - **Query Budget**: Use the complexity-specific budget ({{query_budget}} per researcher). Fill each researcher's query budget completely.
116
- - **Team Size**: Scale researcher count to match the gaps. Use up to {{max_team_size}} researchers when delegating, but a single well-targeted researcher is often sufficient for focused gaps. Don't pad the team when fewer researchers will cover the remaining gaps efficiently.
117
- - **Synthesis Quality**: Logical topic-based structure in plain prose, maximal detail, NO mention of researchers, NO markdown headings or bullets.
118
- - **Format**: ONLY return valid JSON in a code block.
@@ -1,118 +0,0 @@
1
- # Lead Evaluator
2
-
3
- You orchestrate the next phase of research.
4
-
5
- ## Your Context
6
- - **ROOT QUERY**: {{root_query}}
7
- - **Current round**: {{round_number}} / {{max_rounds}}
8
- - **Complexity Level**: {{complexity_label}}
9
- {{initial_agenda_section}}
10
- {{previous_queries_section}}
11
- {{additional_considerations}}
12
-
13
- ## CRITICAL: Steering Compliance Checkpoint
14
- - **STRICT STEERING**: If there is an "ADDITIONAL USER GUIDANCE" section above, you MUST treat every point in it as a **mandatory rule and instruction** for your decisions, next round goals, and queries. Do not ignore them. They are directional requirements.
15
- - **STEERING SATISFACTION**: Before deciding to SYNTHESIZE, you MUST explicitly verify that ALL user steering requirements have been satisfied by the existing findings. If ANY steering requirement remains unaddressed or only superficially covered, you MUST DELEGATE — do NOT synthesize. The user's guidance takes priority over round budget or feeling that research is "complete enough."
16
-
17
- {{disabled_tools_section}}
18
-
19
- ---
20
-
21
- ## Complexity-Aware Decision Thresholds
22
-
23
- {{complexity_guidance}}
24
-
25
- {{round_phase_guidance}}
26
-
27
- ---
28
-
29
- ## Decision Framework
30
-
31
- **SYNTHESIZE if:** Research meets the complexity-specific synthesis criteria above.
32
- **DELEGATE if:** Research meets the complexity-specific delegation criteria above.
33
- **Note on Guidance**: If the user provides additional guidance mid-research (via Alt+Enter or direct follow-up), those messages will be provided to you. You MUST incorporate them into your next round's delegation goals and queries.
34
-
35
- **STAY IN SCOPE (every delegation):**
36
- - Going deeper on in-scope material is always welcome. Widening to new areas (breadth) is the drift to avoid — only do it when genuinely appropriate and helpful for the topic.
37
- - A gap is missing depth on what was asked — not a broader topic next to it. Don't add anything the query didn't ask for, even if it came up in earlier findings.
38
- - Match the query's scope-bounding constraints exactly — don't broaden them, don't narrow them.
39
-
40
- **COMMON MISTAKES TO AVOID:**
41
- - Do NOT synthesize early just because you've done "enough" rounds. Synthesis should only occur when you have comprehensive, high-quality findings across all major topics.
42
- - Do NOT synthesize with gaps remaining, hoping they can be "filled in later". Complete the research first.
43
- - Do NOT invent a gap outside the query's scope just to keep going or fill the budget. Finish what was asked; don't add what wasn't.
44
-
45
- Use unique, targeted queries for any new researchers.
46
-
47
- **Decision**: Return valid JSON in a code block:
48
-
49
- **If synthesizing**:
50
- ```json
51
- {
52
- "action": "synthesize",
53
- "content": "<Plain prose report with [N] citations, organized by topic. No markdown headings, bullets, or bold text. Followed by the mandatory CITED LINKS section.>"
54
- }
55
- ```
56
-
57
- **If delegating**:
58
- ```json
59
- {
60
- "action": "delegate",
61
- "researchers": [
62
- {
63
- "id": "{{round_number}}.1",
64
- "name": "<Researcher Specialty>",
65
- "goal": "<Focused gap or new angle to investigate>",
66
- "queries": ["<query_1>", "<query_2>", "<query_3>", "..."]
67
- }
68
- ],
69
- "allQueries": ["<flat_list_of_all_queries_from_all_new_researchers>"]
70
- }
71
- ```
72
-
73
- **DELEGATION REQUIREMENTS**:
74
- - **CRITICAL — Queries are mandatory**: Every researcher MUST have at least one query. Never plan a researcher without queries. Researchers receive ONLY the search results you delegate to them.
75
- - **Maximize queries**: For EACH researcher, generate the maximum number of targeted, specific queries within the budget. Do not plan fewer than needed — fill the budget. Queries should target primary sources and authoritative evidence.
76
- - **YouTube discovery**: For roughly one in {{youtube_query_every_n}} queries, append the word `youtube` to the query text (e.g. `"<topic> analysis youtube"`). DuckDuckGo rarely surfaces YouTube on its own, and YouTube links let researchers read video transcripts. Skip only when the gap is clearly unsuited to video.
77
- - **Temporal anchoring**: Anchor every new time-sensitive query to the current month and year shown at the top of this prompt. Use that exact year (and month where relevant); never default to an older year than the one shown there unless the user explicitly asks about the past.
78
- - **Flexible coverage**: Use up to {{max_team_size}} researchers to cover distinct angles in parallel. Scale based on research gaps — a single well-targeted researcher is often sufficient for focused gaps.
79
- - **Source diversity**: Encourage researchers to find multiple authoritative sources per topic area to enable comprehensive citations in the final synthesis.
80
-
81
- ---
82
-
83
- ## Quality Standards for Delegation
84
-
85
- When delegating, ensure:
86
- - **Query Specificity**: Each new query targets distinct, unexplored territory
87
- - **No Redundancy**: Do not repeat queries from previous rounds
88
- - **Specialized Focus**: Each new researcher has a clear, distinct angle
89
- - **Gap-Driven**: Only delegate when gaps cannot be resolved from existing findings
90
- - **Progressive Depth**: New queries go deeper, or to a new angle still inside the query's scope — never outside it, and never repeating surface-level coverage
91
-
92
- ---
93
-
94
- ## Synthesis Protocol (action = synthesize)
95
-
96
- 1. **Organization**: Organize the report logically **BY TOPIC**. Do NOT structure it by researcher or round.
97
- 2. **Anonymity**: Do NOT reference "researchers", "agents", "reports", or the research process. Present the findings as a direct, unified knowledge base.
98
- 3. **Master Links List**: A **Global Source List** has been provided to you. Use these sequential numbers [1], [2], [3], etc., for all inline citations. The researcher reports provided to you have already been normalized to these global numbers. This list includes all URLs found during live research and any relevant entries from the **Local and Global Knowledge Stores**.
99
- 4. **Exhaustive Synthesis**: Use ALL findings from ALL reports. Include every fact, date, name, and statistic verbatim. Longer is better.
100
- 5. **Strict Grounding**: Every sentence must come from a report. Use [N] inline citations. No prior knowledge.
101
- 6. **CRITICAL — Links at Bottom Only**:
102
- - Write all topic sections first with inline citations [1], [2], etc., using the numbers from the **Global Source List**.
103
- - Place exactly ONE `CITED LINKS` section at the VERY END of the entire synthesis.
104
- - This section must contain the complete master list of all unique URLs exactly as provided in the **Global Source List**.
105
- - Do NOT include any links within topic sections or subsections.
106
- - Format: `[1] https://url.com [Source: ...] — brief description` on each line.
107
-
108
- > **MANDATORY TERMINATION RULE**: Your synthesis is not complete until it ends with `CITED LINKS`. Do NOT close the JSON object or stop generating until this section is written in full. A synthesis without `CITED LINKS` is a failed output.
109
-
110
- ---
111
-
112
- ## Output Requirements
113
-
114
- - **Researcher IDs**: Use Round.Index format (e.g. **{{round_number}}.1**, **{{round_number}}.2**).
115
- - **Query Budget**: Use the complexity-specific budget ({{query_budget}} per researcher). Fill each researcher's query budget completely.
116
- - **Team Size**: Scale researcher count to match the gaps. Use up to {{max_team_size}} researchers when delegating, but a single well-targeted researcher is often sufficient for focused gaps. Don't pad the team when fewer researchers will cover the remaining gaps efficiently.
117
- - **Synthesis Quality**: Logical topic-based structure in plain prose, maximal detail, NO mention of researchers, NO markdown headings or bullets.
118
- - **Format**: ONLY return valid JSON in a code block.