@lazyingart/agintiflow 0.20.241 → 0.20.242
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.
- package/docs/deep-research-engine.md +18 -0
- package/package.json +1 -1
- package/scripts/smoke-deep-research.js +94 -0
- package/skills/deep-research/SKILL.md +13 -1
- package/src/deep-research.js +152 -17
|
@@ -35,6 +35,11 @@ every query an unbounded agent swarm:
|
|
|
35
35
|
paper set erase the requested source class. Search snippets can guide
|
|
36
36
|
discovery, but they cannot satisfy verified first-party coverage or enter
|
|
37
37
|
synthesis as cited evidence.
|
|
38
|
+
Phrases such as “official Temporal docs” retain this requirement even when a
|
|
39
|
+
product name occurs between `official` and `docs`. Product-owned root pages
|
|
40
|
+
and vendor blogs are classified using the named discovery route; unrelated
|
|
41
|
+
third-party pages are not promoted merely because their URL repeats one
|
|
42
|
+
query word.
|
|
38
43
|
Canonical duplicates found by multiple providers are promoted and retain
|
|
39
44
|
per-provider rank evidence. Multi-domain corpora receive separate bounded `site:`
|
|
40
45
|
queries matched to entity-specific subquestions instead of one fragile OR
|
|
@@ -99,6 +104,10 @@ every query an unbounded agent swarm:
|
|
|
99
104
|
rather than arbitrary page text. Every substantive paragraph and finding
|
|
100
105
|
cites exact evidence IDs instead of merely naming a source. A failed main
|
|
101
106
|
synthesis gets one same-provider fast-model fallback.
|
|
107
|
+
A request for practical recommendations becomes a completion obligation:
|
|
108
|
+
every recommendation must cite verified evidence, and the run fails closed
|
|
109
|
+
rather than delivering an unsupported advice section. The renderer uses the
|
|
110
|
+
compact research objective instead of dumping the raw task prompt.
|
|
102
111
|
9. **Audit**: deterministic code removes unknown or unverified evidence IDs,
|
|
103
112
|
derives visible source citations from accepted evidence records, removes
|
|
104
113
|
unsupported synthesis statements, then reports claim, quotation, citation,
|
|
@@ -110,6 +119,13 @@ every query an unbounded agent swarm:
|
|
|
110
119
|
guarded workspace-relative `outputPath` when the caller requests a durable
|
|
111
120
|
filename, then sent to the canvas.
|
|
112
121
|
|
|
122
|
+
The reader-facing report always includes an explicit limitations section and
|
|
123
|
+
separates quote-verified sources from inspected-but-uncited pages. When the
|
|
124
|
+
original request asks for reproducibility or exact quotations, deterministic
|
|
125
|
+
rendering adds a verification procedure, retrieval timestamps and SHA-256
|
|
126
|
+
digests, and a verified-evidence appendix. These sections come from the
|
|
127
|
+
retrieval/evidence ledger, not model memory.
|
|
128
|
+
|
|
113
129
|
This combines the orchestrator/worker and separate citation-pass lessons
|
|
114
130
|
described by [Anthropic's production research
|
|
115
131
|
system](https://www.anthropic.com/engineering/multi-agent-research-system)
|
|
@@ -136,6 +152,8 @@ the authoritative original user goal. A planner may shorten the research
|
|
|
136
152
|
question, but cannot silently drop requirements such as “compare at least three
|
|
137
153
|
independent primary sources,” “read a paper/PDF when available,” or “include
|
|
138
154
|
negative evidence.” These requirements are fingerprinted and checkpointed.
|
|
155
|
+
The same contract covers practical recommendations, reproducible verification
|
|
156
|
+
methods, and exact-quote evidence appendices.
|
|
139
157
|
|
|
140
158
|
Planning and evidence extraction deliberately use the configured routing model
|
|
141
159
|
when it belongs to a hosted active provider. Synthesis uses the configured main
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.242",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -441,6 +441,7 @@ async function main() {
|
|
|
441
441
|
],
|
|
442
442
|
}],
|
|
443
443
|
keyFindings: [{ claim: "Claims retain exact evidence IDs.", evidenceIds: ["S1-C1", "S2-C1"], confidence: "high" }],
|
|
444
|
+
recommendations: [],
|
|
444
445
|
contradictions: [],
|
|
445
446
|
uncertainties: [],
|
|
446
447
|
nextQuestions: [],
|
|
@@ -601,6 +602,7 @@ async function main() {
|
|
|
601
602
|
executiveSummaryEvidenceIds: ["S1-C1"],
|
|
602
603
|
sections: [{ heading: "Claim", paragraphs: [{ text: "The gain was measured.", evidenceIds: ["S1-C1"] }] }],
|
|
603
604
|
keyFindings: [],
|
|
605
|
+
recommendations: [],
|
|
604
606
|
contradictions: [],
|
|
605
607
|
uncertainties: ["The exact source was inaccessible."],
|
|
606
608
|
nextQuestions: [],
|
|
@@ -734,6 +736,7 @@ async function main() {
|
|
|
734
736
|
{ claim: "A second independent source corroborates the measurement.", evidenceIds: ["S2-C1"], confidence: "high" },
|
|
735
737
|
{ claim: "A third independent source supplies separate evidence.", evidenceIds: ["S3-C1"], confidence: "high" },
|
|
736
738
|
],
|
|
739
|
+
recommendations: [],
|
|
737
740
|
contradictions: [],
|
|
738
741
|
uncertainties: ["Benchmark scope remains limited."],
|
|
739
742
|
nextQuestions: [],
|
|
@@ -1004,6 +1007,95 @@ async function main() {
|
|
|
1004
1007
|
"a bounded official PDF was still constrained by the generic 2 MiB page limit"
|
|
1005
1008
|
);
|
|
1006
1009
|
|
|
1010
|
+
const namedOfficialResearch = await deepResearch(
|
|
1011
|
+
{
|
|
1012
|
+
query: "Compare official Temporal docs and official LangGraph docs for durable agent execution.",
|
|
1013
|
+
depth: "quick",
|
|
1014
|
+
sourcePolicy: "primary",
|
|
1015
|
+
maxQueries: 1,
|
|
1016
|
+
maxSources: 2,
|
|
1017
|
+
gapPasses: 0,
|
|
1018
|
+
researchId: "named-official-products-smoke",
|
|
1019
|
+
outputPath: "reports/named-official-products.md",
|
|
1020
|
+
dryRun: true,
|
|
1021
|
+
},
|
|
1022
|
+
{
|
|
1023
|
+
provider: "mock",
|
|
1024
|
+
model: "mock-agent",
|
|
1025
|
+
commandCwd: researchWorkspace,
|
|
1026
|
+
webSearchImpl: async ({ query }) => ({
|
|
1027
|
+
ok: true,
|
|
1028
|
+
toolName: "web_search",
|
|
1029
|
+
provider: "test",
|
|
1030
|
+
query,
|
|
1031
|
+
results: /official system card engineering blog architecture/i.test(query)
|
|
1032
|
+
? [
|
|
1033
|
+
{
|
|
1034
|
+
rank: 1,
|
|
1035
|
+
title: "Durable Execution Solutions",
|
|
1036
|
+
url: "https://temporal.io/blog/durable-execution-solutions",
|
|
1037
|
+
canonicalUrl: "https://temporal.io/blog/durable-execution-solutions",
|
|
1038
|
+
domain: "temporal.io",
|
|
1039
|
+
snippet: "Temporal explains durable execution and workflow recovery.",
|
|
1040
|
+
provider: "test",
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
rank: 2,
|
|
1044
|
+
title: "Third-party retry commentary",
|
|
1045
|
+
url: "https://appscale.example.org/blog/retry-commentary",
|
|
1046
|
+
canonicalUrl: "https://appscale.example.org/blog/retry-commentary",
|
|
1047
|
+
domain: "appscale.example.org",
|
|
1048
|
+
snippet: "A secondary opinion about retry behavior.",
|
|
1049
|
+
provider: "test",
|
|
1050
|
+
},
|
|
1051
|
+
]
|
|
1052
|
+
: [
|
|
1053
|
+
{
|
|
1054
|
+
rank: 1,
|
|
1055
|
+
title: "LangGraph",
|
|
1056
|
+
url: "https://www.langchain.com/langgraph",
|
|
1057
|
+
canonicalUrl: "https://www.langchain.com/langgraph",
|
|
1058
|
+
domain: "langchain.com",
|
|
1059
|
+
snippet: "LangGraph provides durable execution for long-running stateful agents.",
|
|
1060
|
+
provider: "test",
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
rank: 2,
|
|
1064
|
+
title: "Generic durable systems survey",
|
|
1065
|
+
url: "https://link.springer.com/article/generic-durable-systems",
|
|
1066
|
+
canonicalUrl: "https://link.springer.com/article/generic-durable-systems",
|
|
1067
|
+
domain: "link.springer.com",
|
|
1068
|
+
snippet: "A broad survey with little agent-specific implementation detail.",
|
|
1069
|
+
provider: "test",
|
|
1070
|
+
},
|
|
1071
|
+
],
|
|
1072
|
+
}),
|
|
1073
|
+
webPageReaderImpl: async ({ url }) => ({
|
|
1074
|
+
ok: true,
|
|
1075
|
+
toolName: "read_web_page",
|
|
1076
|
+
url,
|
|
1077
|
+
title: url.includes("temporal.io") ? "Temporal Durable Execution" : "LangGraph",
|
|
1078
|
+
readable: true,
|
|
1079
|
+
contentType: "text/html",
|
|
1080
|
+
retrievedAt: "2026-08-25T00:00:00.000Z",
|
|
1081
|
+
sha256: (url.includes("temporal.io") ? "1" : "2").repeat(64),
|
|
1082
|
+
content: "The official product page documents durable execution, checkpointing, and recovery behavior.",
|
|
1083
|
+
passages: ["The official product page documents durable execution, checkpointing, and recovery behavior."],
|
|
1084
|
+
}),
|
|
1085
|
+
},
|
|
1086
|
+
new SessionStore(path.join(tempRoot, "sessions"), "named-official-products-smoke")
|
|
1087
|
+
);
|
|
1088
|
+
assert(namedOfficialResearch.ok, `named official product research failed: ${namedOfficialResearch.error || "unknown"}`);
|
|
1089
|
+
assert(namedOfficialResearch.requirements.officialDiscovery, "'official Temporal docs' did not preserve first-party discovery intent");
|
|
1090
|
+
assert(
|
|
1091
|
+
namedOfficialResearch.sources.map((source) => source.domain).sort().join(",") === "langchain.com,temporal.io",
|
|
1092
|
+
`named first-party product pages were displaced by weaker filler: ${namedOfficialResearch.sources.map((source) => source.domain).join(", ")}`
|
|
1093
|
+
);
|
|
1094
|
+
assert(namedOfficialResearch.sources.every((source) => source.firstParty), "a named official product root was not classified as first-party");
|
|
1095
|
+
const namedOfficialReport = await fs.readFile(namedOfficialResearch.reportPath, "utf8");
|
|
1096
|
+
assert(namedOfficialReport.includes("## Limitations, Uncertainties, And Coverage Gaps"), "research report omitted an explicit limitations section");
|
|
1097
|
+
assert(namedOfficialReport.includes("## Sources Inspected But Not Cited"), "dry-run report hid the uncited-source boundary");
|
|
1098
|
+
|
|
1007
1099
|
const scholarlyGapCalls = [];
|
|
1008
1100
|
const scholarlyGapResearch = await deepResearch(
|
|
1009
1101
|
{
|
|
@@ -1070,6 +1162,7 @@ async function main() {
|
|
|
1070
1162
|
paragraphs: [{ text: "Two primary sources support the result.", evidenceIds: ["S1-C1", "S2-C1"] }],
|
|
1071
1163
|
}],
|
|
1072
1164
|
keyFindings: [{ claim: "Independent evidence was recovered.", evidenceIds: ["S1-C1", "S2-C1"], confidence: "high" }],
|
|
1165
|
+
recommendations: [],
|
|
1073
1166
|
contradictions: [],
|
|
1074
1167
|
uncertainties: [],
|
|
1075
1168
|
nextQuestions: [],
|
|
@@ -1209,6 +1302,7 @@ async function main() {
|
|
|
1209
1302
|
evidenceIds: ["S1-C1", "S2-C1", "S3-C1"],
|
|
1210
1303
|
confidence: "high",
|
|
1211
1304
|
}],
|
|
1305
|
+
recommendations: [],
|
|
1212
1306
|
contradictions: [],
|
|
1213
1307
|
uncertainties: ["Real throughput depends on local hardware."],
|
|
1214
1308
|
nextQuestions: [],
|
|
@@ -34,7 +34,9 @@ When the user requests a specific Markdown report filename, pass it as
|
|
|
34
34
|
`outputPath` so the bounded engine writes the complete report directly into the
|
|
35
35
|
workspace. Do not try to read or copy a private session artifact afterward.
|
|
36
36
|
Preserve explicit evidence requirements with `requirePdf`,
|
|
37
|
-
`minIndependentSources`,
|
|
37
|
+
`minIndependentSources`, `includeNegativeEvidence`, `requireRecommendations`,
|
|
38
|
+
`requireVerificationMethod`, and `includeEvidenceAppendix` when they are
|
|
39
|
+
present.
|
|
38
40
|
The engine also derives them from the authoritative original request so a
|
|
39
41
|
shortened planner query cannot discard them.
|
|
40
42
|
|
|
@@ -86,6 +88,16 @@ reject an ordinary multi-megabyte system card using the lower HTML-page limit.
|
|
|
86
88
|
Browser-verification, CAPTCHA, and access-challenge pages are unreadable; their
|
|
87
89
|
warning text is not evidence.
|
|
88
90
|
|
|
91
|
+
Treat a requested report as a reader-facing decision artifact, never an
|
|
92
|
+
execution transcript. Do not dump the full raw prompt, task IDs, transport IDs,
|
|
93
|
+
schemas, or orchestration instructions into it. When recommendations are
|
|
94
|
+
requested, each must be concrete and traceable to verified evidence. Always
|
|
95
|
+
make limitations explicit. When requested, the engine adds a reproducible
|
|
96
|
+
verification method and an exact-quote evidence appendix from the deterministic
|
|
97
|
+
evidence ledger. It separates verified sources from pages inspected but not
|
|
98
|
+
cited, instead of padding the bibliography with unreadable or unsupported
|
|
99
|
+
entries.
|
|
100
|
+
|
|
89
101
|
Return the substantive answer naturally. Include useful citations and the
|
|
90
102
|
report path; do not narrate internal query loops or model calls. If coverage or
|
|
91
103
|
quote verification is weak, say exactly what remains unsupported instead of
|
package/src/deep-research.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./web-search.js";
|
|
15
15
|
import { resolveWorkspacePath } from "./workspace-tools.js";
|
|
16
16
|
|
|
17
|
-
const RESEARCH_VERSION =
|
|
17
|
+
const RESEARCH_VERSION = 13;
|
|
18
18
|
const DEPTH_BUDGETS = Object.freeze({
|
|
19
19
|
quick: Object.freeze({ maxQueries: 3, maxSources: 6, concurrency: 3, gapPasses: 0 }),
|
|
20
20
|
standard: Object.freeze({ maxQueries: 6, maxSources: 12, concurrency: 4, gapPasses: 1 }),
|
|
@@ -136,14 +136,29 @@ function researchRequirements(args = {}, config = {}) {
|
|
|
136
136
|
/学术|學術|论文|論文|文献综述|文獻綜述|期刊|引用|系统综述|系統綜述/.test(intent);
|
|
137
137
|
const officialDiscovery =
|
|
138
138
|
sourcePolicy(args.sourcePolicy) === "official" ||
|
|
139
|
-
/\b(?:first[- ]party|official
|
|
139
|
+
/\b(?:first[- ]party|official\b[^.\n]{0,100}\b(?:docs?|documentation|engineering|guidance|implementation|technical|writeups?)|(?:docs?|documentation|engineering|guidance|implementation|technical|writeups?)\b[^.\n]{0,100}\b(?:from|by)\s+(?:the\s+)?official|system card|vendor engineering)\b/i.test(intent) ||
|
|
140
140
|
/官方(?:文档|文檔|工程|技术|技術)|第一方|系统卡|系統卡/.test(intent);
|
|
141
|
+
const requireRecommendations =
|
|
142
|
+
args.requireRecommendations === true ||
|
|
143
|
+
/\b(?:actionable|concrete|implementation|practical)\s+(?:advice|recommendations?|steps)|\brecommend(?:ation|ations|ed)?\b|\bwhat (?:we|i|the team) should do\b/i.test(intent) ||
|
|
144
|
+
/建议|建議|推荐|推薦|下一步|怎么做|怎麼做/.test(intent);
|
|
145
|
+
const requireVerificationMethod =
|
|
146
|
+
args.requireVerificationMethod === true ||
|
|
147
|
+
/\b(?:reproducible|repeatable)\s+(?:verification|validation|audit|method|procedure)|\b(?:verification|validation)\s+(?:method|procedure|protocol)|\bhow to (?:reproduce|verify|validate)\b/i.test(intent) ||
|
|
148
|
+
/可复现|可復現|验证方法|驗證方法|验证流程|驗證流程/.test(intent);
|
|
149
|
+
const includeEvidenceAppendix =
|
|
150
|
+
args.includeEvidenceAppendix === true ||
|
|
151
|
+
/\b(?:exact|verbatim|source-grounded)\s+(?:quotes?|quotations?|excerpts?)|\bevidence appendix\b|\bquote-level evidence\b/i.test(intent) ||
|
|
152
|
+
/原文引文|原文引用|证据附录|證據附錄/.test(intent);
|
|
141
153
|
return {
|
|
142
154
|
requirePdf,
|
|
143
155
|
pdfMode: requirePdf ? (pdfWhenAvailable ? "when-available" : "required") : "optional",
|
|
144
156
|
minIndependentSources: Math.max(requestedMinimum, inferredMinimum),
|
|
145
157
|
scholarlyDiscovery,
|
|
146
158
|
officialDiscovery,
|
|
159
|
+
requireRecommendations,
|
|
160
|
+
requireVerificationMethod,
|
|
161
|
+
includeEvidenceAppendix,
|
|
147
162
|
includeNegativeEvidence:
|
|
148
163
|
args.includeNegativeEvidence === true ||
|
|
149
164
|
/\b(?:negative|conflicting|contradictory|counter[- ]?evidence|disagreement|limitations?|falsifying|unresolved)\b/i.test(intent) ||
|
|
@@ -350,6 +365,18 @@ const SYNTHESIS_SCHEMA = {
|
|
|
350
365
|
additionalProperties: false,
|
|
351
366
|
},
|
|
352
367
|
},
|
|
368
|
+
recommendations: {
|
|
369
|
+
type: "array",
|
|
370
|
+
items: {
|
|
371
|
+
type: "object",
|
|
372
|
+
properties: {
|
|
373
|
+
text: { type: "string" },
|
|
374
|
+
evidenceIds: { type: "array", items: { type: "string" } },
|
|
375
|
+
},
|
|
376
|
+
required: ["text", "evidenceIds"],
|
|
377
|
+
additionalProperties: false,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
353
380
|
contradictions: { type: "array", items: { type: "string" } },
|
|
354
381
|
uncertainties: { type: "array", items: { type: "string" } },
|
|
355
382
|
nextQuestions: { type: "array", items: { type: "string" } },
|
|
@@ -360,6 +387,7 @@ const SYNTHESIS_SCHEMA = {
|
|
|
360
387
|
"executiveSummaryEvidenceIds",
|
|
361
388
|
"sections",
|
|
362
389
|
"keyFindings",
|
|
390
|
+
"recommendations",
|
|
363
391
|
"contradictions",
|
|
364
392
|
"uncertainties",
|
|
365
393
|
"nextQuestions",
|
|
@@ -678,7 +706,7 @@ function scholarlyCandidate(result = {}) {
|
|
|
678
706
|
return scholarlyDomain(result.domain, result.url) || indexes.some((index) => ["arxiv", "crossref"].includes(index));
|
|
679
707
|
}
|
|
680
708
|
|
|
681
|
-
function firstPartyCandidate(result = {}) {
|
|
709
|
+
function firstPartyCandidate(result = {}, discoveryQuery = result.discoveryQuery || "") {
|
|
682
710
|
if (repositoryCandidate(result) || scholarlyCandidate(result)) return false;
|
|
683
711
|
const domain = String(result.domain || "").toLowerCase();
|
|
684
712
|
let pathname = "";
|
|
@@ -699,6 +727,8 @@ function firstPartyCandidate(result = {}) {
|
|
|
699
727
|
? suffix
|
|
700
728
|
: secondLevel;
|
|
701
729
|
const titleAndSnippet = `${result.title || ""} ${result.snippet || ""}`.toLowerCase();
|
|
730
|
+
const discoveryText = String(discoveryQuery || "").toLowerCase();
|
|
731
|
+
const pathnameParts = pathname.split("/").filter(Boolean);
|
|
702
732
|
const brandedOfficialArtifact =
|
|
703
733
|
/\b(?:introducing|official|system card|technical report|whitepaper)\b/i.test(titleAndSnippet) &&
|
|
704
734
|
brand.length >= 4 &&
|
|
@@ -706,11 +736,22 @@ function firstPartyCandidate(result = {}) {
|
|
|
706
736
|
const cdnOfficialArtifact =
|
|
707
737
|
/^cdn\./.test(domain) && /(?:system[-_]?card|technical[-_]?report|whitepaper)/.test(pathname);
|
|
708
738
|
const discoveryIntentMatchesBrand =
|
|
709
|
-
result.sourceIntent === "first-party" && brand.length >= 4 &&
|
|
739
|
+
result.sourceIntent === "first-party" && brand.length >= 4 &&
|
|
740
|
+
(titleAndSnippet.includes(brand) || discoveryText.includes(brand));
|
|
710
741
|
const discoveryIntentLooksTechnical =
|
|
711
742
|
result.sourceIntent === "first-party" &&
|
|
712
743
|
domainParts.length >= 3 &&
|
|
713
744
|
/\b(?:architecture|engineering|how (?:we|it) (?:built|works)|implementation|retrieval system|system card|technical (?:article|blog|report|writeup|write-up))\b/i.test(titleAndSnippet);
|
|
745
|
+
const queryNamesDomainBrand =
|
|
746
|
+
brand.length >= 4 &&
|
|
747
|
+
discoveryText.includes(brand) &&
|
|
748
|
+
!/\b(?:review of|comparison of|alternative to)\b/i.test(titleAndSnippet);
|
|
749
|
+
const queryNamesProductRoot =
|
|
750
|
+
domainParts.length === 2 &&
|
|
751
|
+
pathnameParts.length === 1 &&
|
|
752
|
+
pathnameParts[0].length >= 4 &&
|
|
753
|
+
discoveryText.includes(pathnameParts[0]) &&
|
|
754
|
+
titleAndSnippet.includes(pathnameParts[0]);
|
|
714
755
|
return (
|
|
715
756
|
/\.(gov|mil)$/.test(domain) ||
|
|
716
757
|
/^(?:api|developer|developers|docs|engineering|research)\./.test(domain) ||
|
|
@@ -718,7 +759,9 @@ function firstPartyCandidate(result = {}) {
|
|
|
718
759
|
brandedOfficialArtifact ||
|
|
719
760
|
cdnOfficialArtifact ||
|
|
720
761
|
discoveryIntentMatchesBrand ||
|
|
721
|
-
discoveryIntentLooksTechnical
|
|
762
|
+
discoveryIntentLooksTechnical ||
|
|
763
|
+
queryNamesDomainBrand ||
|
|
764
|
+
queryNamesProductRoot
|
|
722
765
|
);
|
|
723
766
|
}
|
|
724
767
|
|
|
@@ -737,9 +780,9 @@ function lowEvidenceCandidate(result = {}) {
|
|
|
737
780
|
);
|
|
738
781
|
}
|
|
739
782
|
|
|
740
|
-
function officialSource(result = {}) {
|
|
783
|
+
function officialSource(result = {}, discoveryQuery = result.discoveryQuery || "") {
|
|
741
784
|
if (repositoryCandidate(result)) return false;
|
|
742
|
-
return scholarlyCandidate(result) || firstPartyCandidate(result);
|
|
785
|
+
return scholarlyCandidate(result) || firstPartyCandidate(result, discoveryQuery);
|
|
743
786
|
}
|
|
744
787
|
|
|
745
788
|
const RANKING_STOPWORDS = new Set([
|
|
@@ -827,6 +870,9 @@ function rankCandidates(searches, policy, query) {
|
|
|
827
870
|
]);
|
|
828
871
|
existing.workVariantCount = existing.variantUrls.length;
|
|
829
872
|
existing.scholarlyIndexes = unique([...(existing.scholarlyIndexes || []), ...(result.scholarlyIndexes || []), result.scholarlyIndex]);
|
|
873
|
+
existing.discoveryRoutes = unique([...(existing.discoveryRoutes || []), search.id || resultDiscoveryQuery]);
|
|
874
|
+
existing.firstParty = Boolean(existing.firstParty || firstPartyCandidate(result, resultDiscoveryQuery));
|
|
875
|
+
existing.official = Boolean(existing.official || officialSource(result, resultDiscoveryQuery));
|
|
830
876
|
if (lexicalHits > Number(existing.lexicalHits || 0)) {
|
|
831
877
|
existing.lexicalHits = lexicalHits;
|
|
832
878
|
existing.queryTermCount = queryTermCount;
|
|
@@ -834,9 +880,9 @@ function rankCandidates(searches, policy, query) {
|
|
|
834
880
|
continue;
|
|
835
881
|
}
|
|
836
882
|
let score = lexicalHits;
|
|
837
|
-
const official = officialSource(result);
|
|
883
|
+
const official = officialSource(result, resultDiscoveryQuery);
|
|
838
884
|
const scholarly = scholarlyCandidate(result);
|
|
839
|
-
const firstParty = firstPartyCandidate(result);
|
|
885
|
+
const firstParty = firstPartyCandidate(result, resultDiscoveryQuery);
|
|
840
886
|
const repository = repositoryCandidate(result);
|
|
841
887
|
const discoveryIndex = discoveryIndexCandidate(result);
|
|
842
888
|
const lowEvidence = lowEvidenceCandidate(result);
|
|
@@ -867,6 +913,7 @@ function rankCandidates(searches, policy, query) {
|
|
|
867
913
|
providerConsensusCount: providers.length,
|
|
868
914
|
domainHint: search.domainHint || "",
|
|
869
915
|
discoveredBy: [resultDiscoveryQuery],
|
|
916
|
+
discoveryRoutes: [search.id || resultDiscoveryQuery],
|
|
870
917
|
workIdentity: result.workIdentity || scholarlyWorkIdentity(result),
|
|
871
918
|
workAliases: aliases,
|
|
872
919
|
variantUrls: unique(result.variantUrls || [result.url]),
|
|
@@ -945,6 +992,34 @@ function selectDiverseCandidates(candidates, limit, policy = "primary", requirem
|
|
|
945
992
|
domainCounts.set(candidate.domain || "", 1);
|
|
946
993
|
}
|
|
947
994
|
}
|
|
995
|
+
// Preserve topical breadth from the planner without allowing one marginal
|
|
996
|
+
// page per query to fill the whole source budget. Reserve at most half of the
|
|
997
|
+
// budget for strong route leaders, then let global quality and domain
|
|
998
|
+
// diversity choose the remainder.
|
|
999
|
+
const routeLeaderBudget = Math.max(0, Math.min(selectionLimit - selected.length, Math.ceil(selectionLimit / 2)));
|
|
1000
|
+
if (routeLeaderBudget > 0) {
|
|
1001
|
+
const routeLeaders = new Map();
|
|
1002
|
+
for (const candidate of remaining) {
|
|
1003
|
+
if (strictPolicy && !preferred.includes(candidate)) continue;
|
|
1004
|
+
if (!candidateIsStronglyRelevant(candidate) && !(candidate.firstParty && Number(candidate.lexicalHits || 0) >= 1)) continue;
|
|
1005
|
+
for (const route of candidate.discoveryRoutes || candidate.discoveredBy || []) {
|
|
1006
|
+
const current = routeLeaders.get(route);
|
|
1007
|
+
if (!current || Number(candidate.score || 0) > Number(current.score || 0)) routeLeaders.set(route, candidate);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const leaders = [...new Set(routeLeaders.values())]
|
|
1011
|
+
.filter((candidate) => !selected.some((item) => item.url === candidate.url))
|
|
1012
|
+
.sort((left, right) => Number(right.firstParty) - Number(left.firstParty) || Number(right.score || 0) - Number(left.score || 0))
|
|
1013
|
+
.slice(0, routeLeaderBudget);
|
|
1014
|
+
for (const leader of leaders) {
|
|
1015
|
+
const index = remaining.findIndex((candidate) => candidate.url === leader.url);
|
|
1016
|
+
if (index < 0 || selected.length >= selectionLimit) continue;
|
|
1017
|
+
const [candidate] = remaining.splice(index, 1);
|
|
1018
|
+
const repeatedDomainCount = domainCounts.get(candidate.domain || "") || 0;
|
|
1019
|
+
selected.push({ ...candidate, diversityAdjustedScore: Number(candidate.score || 0) - repeatedDomainCount * 4 });
|
|
1020
|
+
domainCounts.set(candidate.domain || "", repeatedDomainCount + 1);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
948
1023
|
while (remaining.length && selected.length < selectionLimit) {
|
|
949
1024
|
let bestIndex = 0;
|
|
950
1025
|
let bestAdjustedScore = Number.NEGATIVE_INFINITY;
|
|
@@ -1030,6 +1105,7 @@ async function readCandidates(candidates, args, config, budget, startIndex = 0)
|
|
|
1030
1105
|
firstParty: Boolean(candidate.firstParty),
|
|
1031
1106
|
score: candidate.score,
|
|
1032
1107
|
discoveredBy: candidate.discoveredBy || [],
|
|
1108
|
+
discoveryRoutes: candidate.discoveryRoutes || [],
|
|
1033
1109
|
providers: candidate.providers || [],
|
|
1034
1110
|
providerConsensusCount: candidate.providerConsensusCount || 0,
|
|
1035
1111
|
scholarlyIndexes: candidate.scholarlyIndexes || [],
|
|
@@ -1516,6 +1592,7 @@ function deterministicSynthesis(query, plan, sources, evidence, audit) {
|
|
|
1516
1592
|
sourceIds: finding.sourceIds,
|
|
1517
1593
|
confidence: finding.confidence,
|
|
1518
1594
|
})),
|
|
1595
|
+
recommendations: [],
|
|
1519
1596
|
contradictions: [],
|
|
1520
1597
|
uncertainties: unique([...missingQuestions, ...limitations]),
|
|
1521
1598
|
nextQuestions: (audit.missingQuestions || []).map((question) => conciseLine(question.question, 320)),
|
|
@@ -1549,6 +1626,9 @@ async function synthesize(query, plan, sources, evidence, audit, requirements, a
|
|
|
1549
1626
|
requirements.includeNegativeEvidence
|
|
1550
1627
|
? "The evidence contract explicitly requires negative or conflicting evidence; preserve it in the relevant section, contradictions, or uncertainties."
|
|
1551
1628
|
: "Preserve material negative evidence when supplied.",
|
|
1629
|
+
requirements.requireRecommendations
|
|
1630
|
+
? "The request requires practical recommendations. Return a non-empty recommendations array; each recommendation must be concrete and cite the exact evidenceIds that justify it."
|
|
1631
|
+
: "Add recommendations only when the verified evidence supports a useful action.",
|
|
1552
1632
|
"Prefer primary sources and give dates when claims may change over time.",
|
|
1553
1633
|
"Do not cite a source that does not support the sentence.",
|
|
1554
1634
|
].join(" "),
|
|
@@ -1639,6 +1719,7 @@ export function auditResearchSynthesis(synthesis, sources, evidence = null) {
|
|
|
1639
1719
|
}))
|
|
1640
1720
|
.filter((section) => section.paragraphs.length);
|
|
1641
1721
|
synthesis.keyFindings = (synthesis.keyFindings || []).filter((finding) => acceptStatement(finding));
|
|
1722
|
+
synthesis.recommendations = (synthesis.recommendations || []).filter((recommendation) => acceptStatement(recommendation));
|
|
1642
1723
|
|
|
1643
1724
|
if (!summaryAccepted) {
|
|
1644
1725
|
const supported = [
|
|
@@ -1687,7 +1768,7 @@ function renderMarkdown(state) {
|
|
|
1687
1768
|
const lines = [
|
|
1688
1769
|
`# ${synthesis.title || `Research report: ${state.query}`}`,
|
|
1689
1770
|
"",
|
|
1690
|
-
`**
|
|
1771
|
+
`**Research objective:** ${compact(state.plan?.objective || state.query, 480).replace(/\s+/g, " ")}`,
|
|
1691
1772
|
`**As of:** ${state.updatedAt}`,
|
|
1692
1773
|
`**Depth:** ${state.budget.depth}`,
|
|
1693
1774
|
"",
|
|
@@ -1709,21 +1790,55 @@ function renderMarkdown(state) {
|
|
|
1709
1790
|
}
|
|
1710
1791
|
lines.push("");
|
|
1711
1792
|
}
|
|
1793
|
+
if (synthesis.recommendations?.length) {
|
|
1794
|
+
lines.push("## Practical Recommendations", "");
|
|
1795
|
+
for (const recommendation of synthesis.recommendations) {
|
|
1796
|
+
lines.push(`- ${recommendation.text} ${citationText(recommendation.sourceIds)}`.trim());
|
|
1797
|
+
}
|
|
1798
|
+
lines.push("");
|
|
1799
|
+
}
|
|
1712
1800
|
if (synthesis.contradictions?.length) {
|
|
1713
1801
|
lines.push("## Contradictions", "", ...synthesis.contradictions.map((item) => `- ${item}`), "");
|
|
1714
1802
|
}
|
|
1715
|
-
|
|
1716
|
-
lines.push("## Uncertainties And Coverage Gaps", "");
|
|
1717
|
-
|
|
1803
|
+
{
|
|
1804
|
+
lines.push("## Limitations, Uncertainties, And Coverage Gaps", "");
|
|
1805
|
+
const limitations = unique([
|
|
1718
1806
|
...(synthesis.uncertainties || []),
|
|
1719
1807
|
...(state.coverage.missingQuestions || []).map((question) => `Unresolved: ${question.question}`),
|
|
1720
|
-
])
|
|
1808
|
+
]);
|
|
1809
|
+
if (!limitations.length) {
|
|
1810
|
+
limitations.push("No additional limitation was extracted as a verified claim; consult the source and coverage audit before generalizing the findings.");
|
|
1811
|
+
}
|
|
1812
|
+
for (const item of limitations) {
|
|
1721
1813
|
lines.push(`- ${item}`);
|
|
1722
1814
|
}
|
|
1723
1815
|
lines.push("");
|
|
1724
1816
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1817
|
+
if (state.requirements?.requireVerificationMethod) {
|
|
1818
|
+
lines.push(
|
|
1819
|
+
"## Reproducible Verification Method",
|
|
1820
|
+
"",
|
|
1821
|
+
"1. Re-run the recorded research objective with the same source policy, date boundary, and query/source budgets.",
|
|
1822
|
+
"2. Open each cited exact URL and compare the retrieved timestamp and SHA-256 digest recorded below.",
|
|
1823
|
+
"3. Locate every evidence-appendix quotation verbatim in the corresponding source; exclude any quotation that no longer matches.",
|
|
1824
|
+
"4. Recompute source independence, first-party coverage, citation coverage, and unresolved questions before accepting the recommendations.",
|
|
1825
|
+
""
|
|
1826
|
+
);
|
|
1827
|
+
}
|
|
1828
|
+
if (state.requirements?.includeEvidenceAppendix) {
|
|
1829
|
+
lines.push("## Verified Evidence Appendix", "");
|
|
1830
|
+
for (const item of state.evidence || []) {
|
|
1831
|
+
for (const claim of item.claims || []) {
|
|
1832
|
+
if (!claim.quoteVerified) continue;
|
|
1833
|
+
lines.push(`- **${claim.evidenceId} (${claim.confidence || "confidence not rated"})**: “${claim.quote}” [${item.sourceId}]`);
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
lines.push("");
|
|
1837
|
+
}
|
|
1838
|
+
const verifiedSources = state.sources.filter((source) => verifiedSourceIds.has(source.id));
|
|
1839
|
+
const otherSources = state.sources.filter((source) => !verifiedSourceIds.has(source.id));
|
|
1840
|
+
lines.push("## Verified Sources", "");
|
|
1841
|
+
for (const source of verifiedSources) {
|
|
1727
1842
|
const labels = [
|
|
1728
1843
|
source.firstParty ? "first-party official" : "",
|
|
1729
1844
|
source.scholarly ? "scholarly" : "",
|
|
@@ -1736,7 +1851,19 @@ function renderMarkdown(state) {
|
|
|
1736
1851
|
: "no verified evidence",
|
|
1737
1852
|
].filter(Boolean).join(", ");
|
|
1738
1853
|
const pdf = source.pdfTextExtracted && source.pdfUrl ? `; [parsed PDF](${source.pdfUrl})` : "";
|
|
1739
|
-
|
|
1854
|
+
const digest = source.sha256 ? `; SHA-256 ${source.sha256}` : "";
|
|
1855
|
+
lines.push(`- [${source.id}] [${source.title}](${source.url})${labels ? ` — ${labels}` : ""}${source.publishedAt ? `; ${source.publishedAt}` : ""}${source.retrievedAt ? `; retrieved ${source.retrievedAt}` : ""}${digest}${pdf}`);
|
|
1856
|
+
}
|
|
1857
|
+
if (otherSources.length) {
|
|
1858
|
+
lines.push("", "## Sources Inspected But Not Cited", "");
|
|
1859
|
+
for (const source of otherSources) {
|
|
1860
|
+
const status = searchExcerptSourceIds.has(source.id)
|
|
1861
|
+
? "search-excerpt-only, not cited"
|
|
1862
|
+
: source.readable
|
|
1863
|
+
? "readable but no quote-verified claim"
|
|
1864
|
+
: `unreadable: ${source.readError || "no readable content"}`;
|
|
1865
|
+
lines.push(`- [${source.id}] [${source.title}](${source.url}) — ${status}`);
|
|
1866
|
+
}
|
|
1740
1867
|
}
|
|
1741
1868
|
lines.push(
|
|
1742
1869
|
"",
|
|
@@ -1930,6 +2057,7 @@ export async function deepResearch(args = {}, config = {}, store = null) {
|
|
|
1930
2057
|
|
|
1931
2058
|
if (existing?.status === "failed") {
|
|
1932
2059
|
state.status = "running";
|
|
2060
|
+
const previousError = String(state.error || "");
|
|
1933
2061
|
delete state.error;
|
|
1934
2062
|
if (!state.sources?.length) {
|
|
1935
2063
|
state.searches = [];
|
|
@@ -1939,6 +2067,10 @@ export async function deepResearch(args = {}, config = {}, store = null) {
|
|
|
1939
2067
|
state.audit = {};
|
|
1940
2068
|
state.gapPassCompleted = false;
|
|
1941
2069
|
}
|
|
2070
|
+
if (/requested practical recommendations/i.test(previousError)) {
|
|
2071
|
+
state.synthesis = null;
|
|
2072
|
+
state.audit = {};
|
|
2073
|
+
}
|
|
1942
2074
|
}
|
|
1943
2075
|
|
|
1944
2076
|
try {
|
|
@@ -2155,6 +2287,9 @@ export async function deepResearch(args = {}, config = {}, store = null) {
|
|
|
2155
2287
|
state.stage = "synthesized";
|
|
2156
2288
|
await saveState(state, paths, store);
|
|
2157
2289
|
}
|
|
2290
|
+
if (requirements.requireRecommendations && !state.synthesis.recommendations?.length) {
|
|
2291
|
+
throw new Error("The requested practical recommendations were not supported by verified evidence.");
|
|
2292
|
+
}
|
|
2158
2293
|
state.status = "completed";
|
|
2159
2294
|
state.stage = "completed";
|
|
2160
2295
|
await saveState(state, paths, store);
|