@tangle-network/agent-knowledge 1.10.0 → 1.11.0
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/README.md +250 -74
- package/dist/benchmarks/index.d.ts +5 -0
- package/dist/benchmarks/index.js +52 -0
- package/dist/chunk-DQ3PDMDP.js +115 -0
- package/dist/chunk-DQ3PDMDP.js.map +1 -0
- package/dist/chunk-ULXZI235.js +1855 -0
- package/dist/chunk-ULXZI235.js.map +1 -0
- package/dist/{chunk-U6KQ4FS3.js → chunk-VN2OGUUP.js} +306 -40
- package/dist/chunk-VN2OGUUP.js.map +1 -0
- package/dist/{chunk-WWY5FTKQ.js → chunk-W6VWYTNH.js} +10 -115
- package/dist/chunk-W6VWYTNH.js.map +1 -0
- package/dist/chunk-XVU5FFQA.js +49 -0
- package/dist/chunk-XVU5FFQA.js.map +1 -0
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/index-Cj5jRXOz.d.ts +426 -0
- package/dist/index.d.ts +1036 -169
- package/dist/index.js +3372 -558
- package/dist/index.js.map +1 -1
- package/dist/memory/index.d.ts +5 -70
- package/dist/memory/index.js +20 -4
- package/dist/types-CjqPcTTK.d.ts +319 -0
- package/docs/architecture.md +7 -5
- package/docs/eval/investment-material-facts.md +252 -0
- package/docs/eval/rag-eval-roadmap.md +119 -0
- package/docs/results/investment-thesis.md +306 -0
- package/docs/results/research-driving.md +265 -0
- package/docs/two-agent-research-ab.md +121 -10
- package/package.json +8 -9
- package/dist/chunk-MU5CEOGE.js +0 -387
- package/dist/chunk-MU5CEOGE.js.map +0 -1
- package/dist/chunk-U6KQ4FS3.js.map +0 -1
- package/dist/chunk-WWY5FTKQ.js.map +0 -1
- package/dist/profiles/index.d.ts +0 -194
- package/dist/profiles/index.js +0 -11
- package/docs/recursive-research-leaf.md +0 -86
- package/docs/supervisor-profiles.md +0 -295
- /package/dist/{profiles → benchmarks}/index.js.map +0 -0
package/README.md
CHANGED
|
@@ -10,10 +10,11 @@ This package turns raw sources and generated markdown knowledge into a versionab
|
|
|
10
10
|
- [Start here](#start-here) — pick CLI vs programmatic
|
|
11
11
|
- [CLI](#cli) — `init` → `source-add` → `index` → `search` → `lint`
|
|
12
12
|
- [Design](#design) — the invariants (immutable sources, cited claims, deterministic graph)
|
|
13
|
-
- [
|
|
13
|
+
- [Benchmark harness](#benchmark-harness) — BEIR/MTEB/qrels, RAG answer, hallucination, KB-improvement cases
|
|
14
|
+
- [Agent-Eval integration](#agent-eval-integration) — retrieval eval + readiness bundles + release reports
|
|
14
15
|
- [Memory adapters](#memory-adapters) — generic memory contract + Neo4j Agent Memory bridge
|
|
15
16
|
- [Research loop](#research-loop) — `runKnowledgeResearchLoop` + control-loop adapter
|
|
16
|
-
- [
|
|
17
|
+
- [Runtime integration](#runtime-integration) — how agent runners plug into the pure KB loop
|
|
17
18
|
- [Pluggable knowledge sources](#pluggable-knowledge-sources) — live authorities → eval re-runs
|
|
18
19
|
|
|
19
20
|
## Install
|
|
@@ -29,8 +30,15 @@ Two ways in, depending on what you're doing:
|
|
|
29
30
|
- **Author / inspect a KB by hand** → the [CLI](#cli) (`init` → `source-add` → `index` → `search` → `lint`). Fastest way to see the shape on disk.
|
|
30
31
|
- **Drive it from an agent** → pick the primitive by intent:
|
|
31
32
|
- *"Does the agent have enough context to run?"* → [`buildEvalKnowledgeBundle`](#agent-eval-integration) (block / ask / acquire before execution).
|
|
32
|
-
- *"Grow the KB as a researcher"* → [`runKnowledgeResearchLoop`](#research-loop) (deterministic mechanics; your agent owns judgment)
|
|
33
|
-
- *"Spawn
|
|
33
|
+
- *"Grow the KB as a researcher"* → [`runKnowledgeResearchLoop`](#research-loop) (deterministic mechanics; your agent owns judgment) or [`runTwoAgentResearchLoop`](#two-agent-research-loop) (researcher proposes, verifier checks + fills gaps).
|
|
34
|
+
- *"Spawn live agents to improve a KB"* → pass an `updateKnowledge` callback to `improveKnowledgeBase`; runtime-backed supervisors live in `@tangle-network/agent-runtime`.
|
|
35
|
+
- *"Tune retrieval for a knowledge base"* → `runRetrievalImprovementLoop` in the [Agent-Eval integration](#agent-eval-integration) section.
|
|
36
|
+
- *"Run an operator-grade KB improvement cycle"* → `improveKnowledgeBase` in the [Agent-Eval integration](#agent-eval-integration) section.
|
|
37
|
+
It creates a candidate KB, lets agents or deterministic hooks improve it, runs configured evals, can be resumed, and promotes only when the live KB has not changed underneath it.
|
|
38
|
+
- *"Expose the lower-level RAG lifecycle phases"* → `runRagKnowledgeImprovementLoop` in the [Agent-Eval integration](#agent-eval-integration) section.
|
|
39
|
+
It exposes retrieval tuning, gap diagnosis, knowledge acquisition/update, answer-quality checks, and promotion as one typed lifecycle.
|
|
40
|
+
- *"Evaluate RAG answers or a wiki/KB"* → `ragAnswerQualityJudge`, `createRagAnswerQualityHook`, and `scoreKnowledgeBaseIndex` in the [Agent-Eval integration](#agent-eval-integration) section.
|
|
41
|
+
- *"Run standard RAG/KB benchmarks"* → `runKnowledgeBenchmarkSuite` in the [Benchmark harness](#benchmark-harness) section.
|
|
34
42
|
- *"Does this candidate KB actually improve task success?"* → run an [agent-eval improvement loop](#agent-eval-integration) over KB variants, then `knowledgeReleaseReport` for the promotion decision.
|
|
35
43
|
- *"Keep live authorities fresh"* → [pluggable sources](#pluggable-knowledge-sources) + `detectChanges` → eval re-runs.
|
|
36
44
|
|
|
@@ -91,6 +99,23 @@ from `@tangle-network/agent-knowledge`.
|
|
|
91
99
|
- `createKnowledgeControlLoopAdapter()` maps those mechanics into
|
|
92
100
|
`agent-eval`'s `runAgentControlLoop()` so products can plug in their own
|
|
93
101
|
proposer, reviewer, and driver policies.
|
|
102
|
+
- `runRagKnowledgeImprovementLoop()` coordinates the whole RAG improvement
|
|
103
|
+
lifecycle. Retrieval tuning, diagnosis, acquisition, KB update,
|
|
104
|
+
answer-quality eval, and promotion are separate typed phases so products can
|
|
105
|
+
plug in browser agents, coding agents, connectors, or deterministic policies
|
|
106
|
+
without this package hardcoding an agent runner.
|
|
107
|
+
- `improveKnowledgeBase()` wraps that lifecycle with durable candidate state,
|
|
108
|
+
a per-run lock, resume support, isolated candidate workspaces, KB quality
|
|
109
|
+
scoring, and conflict-safe promotion.
|
|
110
|
+
Use it when running agents in loops against a real KB rather than only
|
|
111
|
+
exposing phase hooks.
|
|
112
|
+
- `evaluateKnowledgeBaseReadiness()` checks one KB root without running an
|
|
113
|
+
improvement loop: it rebuilds the index, validates/lints it, scores KB
|
|
114
|
+
quality, and evaluates configured readiness specs.
|
|
115
|
+
- RAG answer evaluation follows the common open-source shape used by Ragas,
|
|
116
|
+
DeepEval, TruLens, and RAGChecker: context quality, answer relevance,
|
|
117
|
+
support/faithfulness, citations, abstention, and failure diagnosis.
|
|
118
|
+
External tools stay pluggable via score normalization and row exporters.
|
|
94
119
|
- Zod schemas define the stable wire shape.
|
|
95
120
|
- Graph/search/lint are deterministic and fast.
|
|
96
121
|
- `searchKnowledge` returns hits with three score fields. `score` and
|
|
@@ -101,6 +126,7 @@ from `@tangle-network/agent-knowledge`.
|
|
|
101
126
|
when comparing against natural confidence thresholds. The normalization is
|
|
102
127
|
within-set ranking, not a cross-query absolute confidence.
|
|
103
128
|
- Release confidence uses `@tangle-network/agent-eval` release gates (`evaluateReleaseConfidence`) instead of reimplementing them.
|
|
129
|
+
- Retrieval eval turns retrieval/RAG configs into `agent-eval` surfaces, auto-searches candidate configs, and scores them against page, source, source-anchor, or source-span targets.
|
|
104
130
|
- `buildEvalKnowledgeBundle()` maps wiki/search evidence into
|
|
105
131
|
`agent-eval` `KnowledgeRequirement`, `KnowledgeBundle`, and
|
|
106
132
|
`KnowledgeReadinessReport` contracts so control loops can block, ask, or
|
|
@@ -112,8 +138,214 @@ The `/memory` subpath exports an optional memory adapter contract. Use it to
|
|
|
112
138
|
bridge episodic or graph-native memory systems into the same source-grounded
|
|
113
139
|
readiness/eval machinery without making `agent-knowledge` own the database.
|
|
114
140
|
|
|
141
|
+
## Benchmark Harness
|
|
142
|
+
|
|
143
|
+
Use `runKnowledgeBenchmarkSuite()` when the product goal is to run a fixed RAG/KB benchmark pack and get one report across retrieval, answer quality, hallucination, and candidate-KB improvement cases.
|
|
144
|
+
The module also exports `INDUSTRY_RAG_BENCHMARKS`, a compact manifest for BEIR, MTEB retrieval, MS MARCO, TREC DL, MIRACL, LoTTE, BRIGHT, CRAG, HotpotQA, KILT, RAGTruth, FaithBench, and first-party KB-improvement tasks.
|
|
145
|
+
|
|
146
|
+
```ts
|
|
147
|
+
import {
|
|
148
|
+
buildFirstPartyMemoryLifecycleBenchmarkCases,
|
|
149
|
+
buildIndustryMemoryBenchmarkSmokeCases,
|
|
150
|
+
buildIndustryRagBenchmarkSmokeCases,
|
|
151
|
+
buildRetrievalBenchmarkCasesFromQrels,
|
|
152
|
+
createInMemoryBenchmarkAdapter,
|
|
153
|
+
createNoopMemoryBenchmarkAdapter,
|
|
154
|
+
isKnowledgeMemoryBenchmarkCase,
|
|
155
|
+
parseKnowledgeBenchmarkQrels,
|
|
156
|
+
respondToIndustryMemoryBenchmarkSmokeCase,
|
|
157
|
+
respondToIndustryRagBenchmarkSmokeCase,
|
|
158
|
+
runMemoryAdapterBenchmark,
|
|
159
|
+
runKnowledgeBenchmarkSuite,
|
|
160
|
+
} from '@tangle-network/agent-knowledge/benchmarks'
|
|
161
|
+
import { createNeo4jAgentMemoryAdapter } from '@tangle-network/agent-knowledge/memory'
|
|
162
|
+
|
|
163
|
+
await runKnowledgeBenchmarkSuite({
|
|
164
|
+
cases: buildIndustryRagBenchmarkSmokeCases(),
|
|
165
|
+
runDir: '.agent-knowledge/benchmark-runs/industry-smoke',
|
|
166
|
+
respond: respondToIndustryRagBenchmarkSmokeCase,
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
await runKnowledgeBenchmarkSuite({
|
|
170
|
+
cases: buildIndustryMemoryBenchmarkSmokeCases(),
|
|
171
|
+
runDir: '.agent-knowledge/benchmark-runs/memory-smoke',
|
|
172
|
+
respond: ({ case: testCase }) => {
|
|
173
|
+
if (!isKnowledgeMemoryBenchmarkCase(testCase)) return { answer: '' }
|
|
174
|
+
return respondToIndustryMemoryBenchmarkSmokeCase({ case: testCase })
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
const memoryRanking = await runMemoryAdapterBenchmark({
|
|
179
|
+
cases: buildFirstPartyMemoryLifecycleBenchmarkCases(),
|
|
180
|
+
runDir: '.agent-knowledge/benchmark-runs/memory-ranking',
|
|
181
|
+
candidates: [
|
|
182
|
+
{ id: 'no-memory', createAdapter: () => createNoopMemoryBenchmarkAdapter() },
|
|
183
|
+
{
|
|
184
|
+
id: 'in-memory',
|
|
185
|
+
createAdapter: () => createInMemoryBenchmarkAdapter(),
|
|
186
|
+
searchLimit: 1,
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
id: 'neo4j-agent-memory',
|
|
190
|
+
createAdapter: () => createNeo4jAgentMemoryAdapter({ client: neo4jMemoryClient }),
|
|
191
|
+
searchLimit: 5,
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
console.log(memoryRanking.rows)
|
|
197
|
+
|
|
198
|
+
const cases = buildRetrievalBenchmarkCasesFromQrels({
|
|
199
|
+
benchmarkId: 'beir/nfcorpus',
|
|
200
|
+
family: 'beir',
|
|
201
|
+
queries: [{ id: 'q1', text: 'aspirin heart attack prevention', split: 'holdout' }],
|
|
202
|
+
qrels: parseKnowledgeBenchmarkQrels('q1 0 src-aspirin 1'),
|
|
203
|
+
targetKind: 'source',
|
|
204
|
+
k: 10,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
const result = await runKnowledgeBenchmarkSuite({
|
|
208
|
+
cases,
|
|
209
|
+
runDir: '.agent-knowledge/benchmark-runs/beir-nfcorpus-smoke',
|
|
210
|
+
respond: async ({ case: testCase }) => {
|
|
211
|
+
if (testCase.taskKind !== 'retrieval') return { hits: [] }
|
|
212
|
+
const hits = await retrieveFromYourKb(testCase.query)
|
|
213
|
+
return { hits, costUsd: 0.001 }
|
|
214
|
+
},
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
console.log(result.report.score.mean)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Use `buildRetrievalBenchmarkCasesFromQrels()` for qrels-backed retrieval datasets.
|
|
221
|
+
The smoke pack proves that every declared benchmark family is wired through the runner; full BEIR, MTEB, MS MARCO, TREC DL, MIRACL, LoTTE, BRIGHT, CRAG, HotpotQA, KILT, RAGTruth, and FaithBench runs should pass real dataset rows through the same case shapes.
|
|
222
|
+
Use `KnowledgeAnswerBenchmarkCase` for CRAG/HotpotQA/KILT-style answer checks and RAGTruth/FaithBench-style hallucination checks by encoding required claims, forbidden claims, and expected source IDs.
|
|
223
|
+
Use `taskKind: 'kb-improvement'` when the artifact is candidate KB text produced by `improveKnowledgeBase()`.
|
|
224
|
+
Use `KnowledgeMemoryBenchmarkCase` for memory systems.
|
|
225
|
+
Memory cases carry ordered events plus current required facts, stale forbidden facts, expected event IDs, and expected actor IDs.
|
|
226
|
+
The built-in memory scorer reports current fact recall, stale-memory safety, event recall, and actor recall, which maps to LoCoMo, LongMemEval, Memora, MemoryAgentBench, MemoryBank-style personalization, GroupMemBench, and first-party memory lifecycle checks.
|
|
227
|
+
Use `runMemoryAdapterBenchmark()` to rank real memory systems that implement `AgentMemoryAdapter`.
|
|
228
|
+
This is the path for Neo4j Agent Memory, Mem0, Zep, Letta, Graphiti, a vector store, or a product-owned memory backend.
|
|
229
|
+
|
|
115
230
|
## Agent-Eval Integration
|
|
116
231
|
|
|
232
|
+
Use `ragAnswerQualityJudge` or `createRagAnswerQualityHook` when the product already has answer traces and needs SOTA-style RAG scoring without rebuilding metrics.
|
|
233
|
+
The built-in checks are deterministic and general; pass external scores from Ragas, DeepEval, TruLens, RAGChecker, or a custom evaluator when you want model-based judging.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import {
|
|
237
|
+
createRagAnswerQualityHook,
|
|
238
|
+
scoreKnowledgeBaseIndex,
|
|
239
|
+
} from '@tangle-network/agent-knowledge'
|
|
240
|
+
|
|
241
|
+
const evaluateAnswers = createRagAnswerQualityHook({
|
|
242
|
+
scenarios: answerScenarios,
|
|
243
|
+
run: async (scenario) => runRagAnswerTrace(scenario),
|
|
244
|
+
externalEvaluator: async ({ scenario, artifact }) => runRagasOrDeepEval({
|
|
245
|
+
input: scenario.query,
|
|
246
|
+
output: artifact.answer,
|
|
247
|
+
contexts: artifact.contexts,
|
|
248
|
+
}),
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const answerQuality = await evaluateAnswers()
|
|
252
|
+
const kbQuality = scoreKnowledgeBaseIndex(index, {
|
|
253
|
+
strict: true,
|
|
254
|
+
minCitationRate: 0.8,
|
|
255
|
+
maxStaleSourceRate: 0.02,
|
|
256
|
+
})
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Use `runRagKnowledgeImprovementLoop` when the product question is broader than retrieval:
|
|
260
|
+
can the system find the gaps, gather or update knowledge, prove generated answers still behave, and decide whether to promote?
|
|
261
|
+
`agent-knowledge` owns the knowledge/eval contract; the caller supplies the research, coding, connector, and answer-eval hooks.
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
import { runRagKnowledgeImprovementLoop } from '@tangle-network/agent-knowledge'
|
|
265
|
+
|
|
266
|
+
const result = await runRagKnowledgeImprovementLoop({
|
|
267
|
+
goal: 'Improve the support RAG KB',
|
|
268
|
+
retrieval: {
|
|
269
|
+
baseline: { k: 5, hybrid: false },
|
|
270
|
+
scenarios: trainRetrievalScenarios,
|
|
271
|
+
holdoutScenarios,
|
|
272
|
+
index,
|
|
273
|
+
searchSpace: { k: [5, 10, 20], hybrid: [false, true] },
|
|
274
|
+
targetRecall: 0.9,
|
|
275
|
+
},
|
|
276
|
+
diagnose: async ({ retrieval }) => diagnoseRagGaps(retrieval),
|
|
277
|
+
acquireKnowledge: async ({ findings }) => researchMissingSources(findings),
|
|
278
|
+
knowledgeResearch: { root: './kb' },
|
|
279
|
+
evaluateAnswers,
|
|
280
|
+
promote: async ({ retrieval, answerQuality }) =>
|
|
281
|
+
decidePromotion({ retrieval, answerQuality }),
|
|
282
|
+
requiredPhases: ['retrieval-tuning', 'knowledge-update', 'answer-quality', 'promotion'],
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
console.log(result.promotion)
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Use `improveKnowledgeBase` when a program should own the candidate workspace and promotion mechanics.
|
|
289
|
+
The wrapper composes the same RAG lifecycle, but adds resumable state under `.agent-knowledge/improvements/<runId>/`, a lock lease for parallel operators, and a base-hash check before copying the candidate over the live KB.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
import { improveKnowledgeBase } from '@tangle-network/agent-knowledge'
|
|
293
|
+
|
|
294
|
+
const result = await improveKnowledgeBase({
|
|
295
|
+
root: './kb',
|
|
296
|
+
goal: 'Improve support refund-policy knowledge',
|
|
297
|
+
readinessSpecs,
|
|
298
|
+
retrieval: {
|
|
299
|
+
baseline: { k: 5 },
|
|
300
|
+
scenarios: trainRetrievalScenarios,
|
|
301
|
+
holdoutScenarios,
|
|
302
|
+
searchSpace: { k: [5, 10, 20] },
|
|
303
|
+
targetRecall: 0.9,
|
|
304
|
+
},
|
|
305
|
+
step: async ({ readiness }) => runResearchAgent({ missing: readiness?.report }),
|
|
306
|
+
evaluateAnswers,
|
|
307
|
+
requiredPhases: ['knowledge-update', 'retrieval-tuning', 'answer-quality'],
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
console.log(result.promoted, result.candidate?.evaluation)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Pass `promote: false` to leave the candidate workspace open for another agent or a human edit.
|
|
314
|
+
Calling `improveKnowledgeBase` again with the same `runId` re-evaluates that candidate and promotes it only if the original live KB hash still matches.
|
|
315
|
+
|
|
316
|
+
If a required phase is missing its hook, the loop throws.
|
|
317
|
+
That keeps the public API from reporting a fake “RAG improved” result when the caller only wired retrieval or only wired a researcher.
|
|
318
|
+
|
|
319
|
+
Use retrieval eval when the question is whether a retrieval/RAG config can find the right knowledge before an agent reasons over it.
|
|
320
|
+
The labels should name stable pages, source records, anchors, or source spans, not ephemeral chunk IDs.
|
|
321
|
+
The completion roadmap is in [`docs/eval/rag-eval-roadmap.md`](docs/eval/rag-eval-roadmap.md).
|
|
322
|
+
|
|
323
|
+
```ts
|
|
324
|
+
import { runRetrievalImprovementLoop } from '@tangle-network/agent-knowledge'
|
|
325
|
+
|
|
326
|
+
const result = await runRetrievalImprovementLoop({
|
|
327
|
+
baseline: { k: 5, hybrid: false, reranker: null },
|
|
328
|
+
scenarios: trainRetrievalScenarios,
|
|
329
|
+
holdoutScenarios: holdoutRetrievalScenarios,
|
|
330
|
+
index,
|
|
331
|
+
searchSpace: {
|
|
332
|
+
k: [5, 10, 20],
|
|
333
|
+
hybrid: [false, true],
|
|
334
|
+
reranker: [null, 'bge-reranker'],
|
|
335
|
+
},
|
|
336
|
+
targetRecall: 0.9,
|
|
337
|
+
deltaThreshold: 0.02,
|
|
338
|
+
costCeiling: 15,
|
|
339
|
+
runDir: '.agent-knowledge/retrieval-runs',
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
console.log(result.winnerConfig)
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
Pass a custom `retrieve` function to `buildRetrievalEvalDispatch` when the config controls an external vector store, reranker, hybrid search service, or chunker.
|
|
346
|
+
The built-in fallback uses `searchKnowledge` over the local deterministic index.
|
|
347
|
+
Use `buildRetrievalEvalDispatch`, `retrievalRecallJudge`, and `retrievalParameterSweepProposer` directly only when you need custom `agent-eval` wiring.
|
|
348
|
+
|
|
117
349
|
To answer whether a candidate knowledge base actually improves agent task success, run an `@tangle-network/agent-eval` improvement loop (`runImprovementLoop`) over your KB variants on a real task corpus; each run is scored into a `RunRecord`.
|
|
118
350
|
|
|
119
351
|
Use `knowledgeReleaseReport()` before promotion: pass the candidate and baseline `RunRecord[]` (plus optional `ReleaseTraceEvidence` and the gate decision) and it folds them into a `ReleaseConfidenceScorecard` and a `KnowledgeRelease` using `agent-eval`'s release gates and `RunRecord` validation.
|
|
@@ -308,86 +540,30 @@ await runTwoAgentResearchLoop({
|
|
|
308
540
|
})
|
|
309
541
|
```
|
|
310
542
|
|
|
311
|
-
##
|
|
543
|
+
## Runtime integration
|
|
312
544
|
|
|
313
|
-
`
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
from `@tangle-network/agent-runtime/loops`; it builds nothing new. The worker
|
|
317
|
-
shape is the [researcher profile](#researcher-profile), and the completion oracle
|
|
318
|
-
is `knowledgeReadinessDeliverable` (re-reads the KB from disk and runs the
|
|
319
|
-
readiness gate, so it stops on the real grounded state, not a worker's
|
|
320
|
-
self-report). Needs creds: a supervisor router brain plus a worker backend.
|
|
545
|
+
`agent-knowledge` owns knowledge state and measurement.
|
|
546
|
+
It deliberately does not own an agent runner.
|
|
547
|
+
Live agent orchestration belongs in `@tangle-network/agent-runtime`, which can call `improveKnowledgeBase` by passing an `updateKnowledge` callback:
|
|
321
548
|
|
|
322
549
|
```ts
|
|
323
|
-
import {
|
|
550
|
+
import { improveKnowledgeBase } from '@tangle-network/agent-knowledge'
|
|
324
551
|
|
|
325
|
-
await
|
|
552
|
+
await improveKnowledgeBase({
|
|
326
553
|
root: './kb',
|
|
327
554
|
goal: 'Build a grounded onboarding wiki for billing support',
|
|
328
|
-
readinessSpecs
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
// WHERE researcher workers run — the real backend seam.
|
|
336
|
-
backend: {
|
|
337
|
-
backend: 'router', // or 'sandbox' / 'cli' / 'bridge'
|
|
338
|
-
routerBaseUrl: process.env.ROUTER_BASE_URL!,
|
|
339
|
-
routerKey: process.env.ROUTER_KEY!,
|
|
340
|
-
model: 'your-worker-model',
|
|
341
|
-
},
|
|
342
|
-
})
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
## Researcher profile
|
|
346
|
-
|
|
347
|
-
`@tangle-network/agent-knowledge/profiles` ships a sandbox-SDK
|
|
348
|
-
`AgentProfile` preset for source-grounded research agents. Pairs with
|
|
349
|
-
`runLoop` from `@tangle-network/agent-runtime/loops` — the profile owns
|
|
350
|
-
the prompt + output adapter + validator; the kernel owns iteration,
|
|
351
|
-
concurrency, cost, and trace emission.
|
|
352
|
-
|
|
353
|
-
```ts
|
|
354
|
-
import { runLoop } from '@tangle-network/agent-runtime/loops'
|
|
355
|
-
import { multiHarnessResearcherFanout } from '@tangle-network/agent-knowledge/profiles'
|
|
356
|
-
|
|
357
|
-
const research = multiHarnessResearcherFanout({
|
|
358
|
-
harnesses: ['opencode/zai-coding-plan/glm-5.1', 'claude-code', 'codex'],
|
|
359
|
-
})
|
|
360
|
-
|
|
361
|
-
const result = await runLoop({
|
|
362
|
-
driver: research.driver,
|
|
363
|
-
agentRuns: research.agentRuns,
|
|
364
|
-
output: research.output,
|
|
365
|
-
validator: research.validator,
|
|
366
|
-
task: {
|
|
367
|
-
question: 'What content does cpg-founder ICP engage with on Twitter?',
|
|
368
|
-
knowledgeNamespace: 'cust_42',
|
|
369
|
-
sources: ['twitter', 'web'],
|
|
370
|
-
maxItems: 20,
|
|
371
|
-
minConfidence: 0.6,
|
|
555
|
+
readinessSpecs,
|
|
556
|
+
updateKnowledge: async ({ goal, findings }) => {
|
|
557
|
+
// Call your agent runner here, then apply source-backed write blocks.
|
|
558
|
+
return {
|
|
559
|
+
applied: true,
|
|
560
|
+
summary: `updated KB for ${goal} with ${findings.length} finding(s)`,
|
|
561
|
+
}
|
|
372
562
|
},
|
|
373
|
-
ctx: { sandboxClient },
|
|
374
563
|
})
|
|
375
|
-
|
|
376
|
-
if (result.winner?.verdict?.valid) {
|
|
377
|
-
// result.winner.output.proposedWrites: KnowledgeUpdate[]
|
|
378
|
-
// The profile does NOT materialize. Decide whether to apply.
|
|
379
|
-
for (const write of result.winner.output.proposedWrites) {
|
|
380
|
-
// route through applyKnowledgeWriteBlocks / a KbStore put when ready
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
564
|
```
|
|
384
565
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
- **Namespace isolation** — every `KnowledgeItem` + `KnowledgeUpdate`
|
|
388
|
-
must carry `task.knowledgeNamespace`. Cross-tenant writes hard-fail.
|
|
389
|
-
- **Provenance** — every item carries at least one evidence entry.
|
|
390
|
-
- **Citation density** — quotes-with-source / items >= 0.7 by default.
|
|
566
|
+
This keeps the dependency graph acyclic: `agent-knowledge` depends on `agent-eval`; agent runners depend on `agent-knowledge`, not the reverse.
|
|
391
567
|
|
|
392
568
|
Validator scoring (default; overridable):
|
|
393
569
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import '@tangle-network/agent-eval/campaign';
|
|
2
|
+
import '../types-CjqPcTTK.js';
|
|
3
|
+
export { B as BuildRetrievalBenchmarkCasesFromQrelsOptions, I as INDUSTRY_MEMORY_BENCHMARKS, e as INDUSTRY_RAG_BENCHMARKS, K as KnowledgeAnswerBenchmarkCase, f as KnowledgeAnswerBenchmarkTaskKind, g as KnowledgeBenchmarkArtifact, h as KnowledgeBenchmarkCase, i as KnowledgeBenchmarkCaseBase, j as KnowledgeBenchmarkDistribution, k as KnowledgeBenchmarkEvaluation, l as KnowledgeBenchmarkFamily, m as KnowledgeBenchmarkReport, n as KnowledgeBenchmarkResponder, o as KnowledgeBenchmarkScenario, p as KnowledgeBenchmarkSliceSummary, q as KnowledgeBenchmarkSource, r as KnowledgeBenchmarkSpec, s as KnowledgeBenchmarkSplit, t as KnowledgeBenchmarkTaskKind, u as KnowledgeClaimMatcher, v as KnowledgeMemoryBenchmarkCase, w as KnowledgeMemoryBenchmarkTaskKind, x as KnowledgeMemoryEvent, y as KnowledgeMemoryFactMatcher, z as KnowledgeRetrievalBenchmarkCase, A as KnowledgeRetrievalBenchmarkQrel, C as KnowledgeRetrievalBenchmarkQuery, M as MemoryAdapterBenchmarkCandidate, D as MemoryAdapterBenchmarkRankingRow, V as RunKnowledgeBenchmarkSuiteOptions, W as RunKnowledgeBenchmarkSuiteResult, X as RunMemoryAdapterBenchmarkOptions, Y as RunMemoryAdapterBenchmarkResult, Z as buildFirstPartyMemoryLifecycleBenchmarkCases, _ as buildIndustryMemoryBenchmarkSmokeCases, $ as buildIndustryRagBenchmarkSmokeCases, a0 as buildKnowledgeBenchmarkScenarios, a1 as buildRetrievalBenchmarkCasesFromQrels, a4 as createInMemoryBenchmarkAdapter, a5 as createMemoryAdapterBenchmarkResponder, a6 as createNoopMemoryBenchmarkAdapter, a7 as isKnowledgeMemoryBenchmarkCase, a8 as knowledgeBenchmarkJudge, a9 as parseKnowledgeBenchmarkJsonl, aa as parseKnowledgeBenchmarkQrels, ab as renderKnowledgeBenchmarkReportMarkdown, ac as respondToIndustryMemoryBenchmarkSmokeCase, ad as respondToIndustryRagBenchmarkSmokeCase, ai as runKnowledgeBenchmarkSuite, aj as runMemoryAdapterBenchmark, al as scoreKnowledgeBenchmarkArtifact, am as scoreMemoryBenchmarkArtifact, ao as summarizeKnowledgeBenchmarkCampaign } from '../index-Cj5jRXOz.js';
|
|
4
|
+
import '../types-C17sAROL.js';
|
|
5
|
+
import '@tangle-network/agent-eval/rl';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import {
|
|
2
|
+
INDUSTRY_MEMORY_BENCHMARKS,
|
|
3
|
+
INDUSTRY_RAG_BENCHMARKS,
|
|
4
|
+
buildFirstPartyMemoryLifecycleBenchmarkCases,
|
|
5
|
+
buildIndustryMemoryBenchmarkSmokeCases,
|
|
6
|
+
buildIndustryRagBenchmarkSmokeCases,
|
|
7
|
+
buildKnowledgeBenchmarkScenarios,
|
|
8
|
+
buildRetrievalBenchmarkCasesFromQrels,
|
|
9
|
+
createInMemoryBenchmarkAdapter,
|
|
10
|
+
createMemoryAdapterBenchmarkResponder,
|
|
11
|
+
createNoopMemoryBenchmarkAdapter,
|
|
12
|
+
isKnowledgeMemoryBenchmarkCase,
|
|
13
|
+
knowledgeBenchmarkJudge,
|
|
14
|
+
parseKnowledgeBenchmarkJsonl,
|
|
15
|
+
parseKnowledgeBenchmarkQrels,
|
|
16
|
+
renderKnowledgeBenchmarkReportMarkdown,
|
|
17
|
+
respondToIndustryMemoryBenchmarkSmokeCase,
|
|
18
|
+
respondToIndustryRagBenchmarkSmokeCase,
|
|
19
|
+
runKnowledgeBenchmarkSuite,
|
|
20
|
+
runMemoryAdapterBenchmark,
|
|
21
|
+
scoreKnowledgeBenchmarkArtifact,
|
|
22
|
+
scoreMemoryBenchmarkArtifact,
|
|
23
|
+
summarizeKnowledgeBenchmarkCampaign
|
|
24
|
+
} from "../chunk-ULXZI235.js";
|
|
25
|
+
import "../chunk-DQ3PDMDP.js";
|
|
26
|
+
import "../chunk-XVU5FFQA.js";
|
|
27
|
+
import "../chunk-YMKHCTS2.js";
|
|
28
|
+
export {
|
|
29
|
+
INDUSTRY_MEMORY_BENCHMARKS,
|
|
30
|
+
INDUSTRY_RAG_BENCHMARKS,
|
|
31
|
+
buildFirstPartyMemoryLifecycleBenchmarkCases,
|
|
32
|
+
buildIndustryMemoryBenchmarkSmokeCases,
|
|
33
|
+
buildIndustryRagBenchmarkSmokeCases,
|
|
34
|
+
buildKnowledgeBenchmarkScenarios,
|
|
35
|
+
buildRetrievalBenchmarkCasesFromQrels,
|
|
36
|
+
createInMemoryBenchmarkAdapter,
|
|
37
|
+
createMemoryAdapterBenchmarkResponder,
|
|
38
|
+
createNoopMemoryBenchmarkAdapter,
|
|
39
|
+
isKnowledgeMemoryBenchmarkCase,
|
|
40
|
+
knowledgeBenchmarkJudge,
|
|
41
|
+
parseKnowledgeBenchmarkJsonl,
|
|
42
|
+
parseKnowledgeBenchmarkQrels,
|
|
43
|
+
renderKnowledgeBenchmarkReportMarkdown,
|
|
44
|
+
respondToIndustryMemoryBenchmarkSmokeCase,
|
|
45
|
+
respondToIndustryRagBenchmarkSmokeCase,
|
|
46
|
+
runKnowledgeBenchmarkSuite,
|
|
47
|
+
runMemoryAdapterBenchmark,
|
|
48
|
+
scoreKnowledgeBenchmarkArtifact,
|
|
49
|
+
scoreMemoryBenchmarkArtifact,
|
|
50
|
+
summarizeKnowledgeBenchmarkCampaign
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// src/search.ts
|
|
2
|
+
var RRF_K = 60;
|
|
3
|
+
var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
4
|
+
"the",
|
|
5
|
+
"is",
|
|
6
|
+
"a",
|
|
7
|
+
"an",
|
|
8
|
+
"what",
|
|
9
|
+
"how",
|
|
10
|
+
"are",
|
|
11
|
+
"was",
|
|
12
|
+
"were",
|
|
13
|
+
"to",
|
|
14
|
+
"for",
|
|
15
|
+
"of",
|
|
16
|
+
"with",
|
|
17
|
+
"by",
|
|
18
|
+
"in",
|
|
19
|
+
"on",
|
|
20
|
+
"and"
|
|
21
|
+
]);
|
|
22
|
+
function searchKnowledge(index, query, limit = 10) {
|
|
23
|
+
const trimmed = query.trim();
|
|
24
|
+
if (trimmed === "") return [];
|
|
25
|
+
const tokenRanked = rankByTokens(index.pages, trimmed);
|
|
26
|
+
const graphRanked = rankByGraph(index.pages, tokenRanked);
|
|
27
|
+
const scores = reciprocalRankFusion([tokenRanked.map((p) => p.id), graphRanked.map((p) => p.id)]);
|
|
28
|
+
const byId = new Map(index.pages.map((page) => [page.id, page]));
|
|
29
|
+
const ranked = [...scores.entries()].map(([id, score]) => ({ page: byId.get(id), score })).filter((item) => Boolean(item.page)).sort((a, b) => b.score - a.score || a.page.path.localeCompare(b.page.path)).slice(0, limit);
|
|
30
|
+
const topScore = ranked[0]?.score ?? 0;
|
|
31
|
+
return ranked.map((item, i) => ({
|
|
32
|
+
page: item.page,
|
|
33
|
+
score: item.score,
|
|
34
|
+
rrfScore: item.score,
|
|
35
|
+
normalizedScore: topScore > 0 ? item.score / topScore : 0,
|
|
36
|
+
rank: i + 1,
|
|
37
|
+
snippet: buildSnippet(item.page.text, trimmed),
|
|
38
|
+
reasons: reasonsFor(item.page, trimmed)
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
function tokenizeQuery(query) {
|
|
42
|
+
const raw = query.toLowerCase().split(/[\s,,。!?、;:""''()()\-_/\\·~~…]+/).filter((token) => token.length > 1 && !STOP_WORDS.has(token));
|
|
43
|
+
const tokens = [];
|
|
44
|
+
for (const token of raw) {
|
|
45
|
+
if (/[\u4e00-\u9fff\u3400-\u4dbf]/.test(token) && token.length > 2) {
|
|
46
|
+
const chars = [...token];
|
|
47
|
+
for (let i = 0; i < chars.length - 1; i++) tokens.push(chars[i] + chars[i + 1]);
|
|
48
|
+
tokens.push(...chars);
|
|
49
|
+
}
|
|
50
|
+
tokens.push(token);
|
|
51
|
+
}
|
|
52
|
+
return [...new Set(tokens)];
|
|
53
|
+
}
|
|
54
|
+
function reciprocalRankFusion(rankLists, k = RRF_K) {
|
|
55
|
+
const scores = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const list of rankLists) {
|
|
57
|
+
list.forEach((id, idx) => {
|
|
58
|
+
scores.set(id, (scores.get(id) ?? 0) + 1 / (k + idx + 1));
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return scores;
|
|
62
|
+
}
|
|
63
|
+
function rankByTokens(pages, query) {
|
|
64
|
+
const tokens = tokenizeQuery(query);
|
|
65
|
+
const effective = tokens.length > 0 ? tokens : [query.toLowerCase()];
|
|
66
|
+
return pages.map((page) => ({ page, score: tokenScore(page, query, effective) })).filter((item) => item.score > 0).sort((a, b) => b.score - a.score || a.page.path.localeCompare(b.page.path)).map((item) => item.page);
|
|
67
|
+
}
|
|
68
|
+
function rankByGraph(pages, tokenRanked) {
|
|
69
|
+
if (tokenRanked.length === 0) return [];
|
|
70
|
+
const seeds = new Set(tokenRanked.slice(0, 5).map((page) => page.id));
|
|
71
|
+
return pages.map((page) => ({
|
|
72
|
+
page,
|
|
73
|
+
score: page.outLinks.filter((link) => seeds.has(link)).length + page.sourceIds.filter(
|
|
74
|
+
(source) => tokenRanked.some((seed) => seed.sourceIds.includes(source))
|
|
75
|
+
).length
|
|
76
|
+
})).filter((item) => item.score > 0).sort((a, b) => b.score - a.score || a.page.path.localeCompare(b.page.path)).map((item) => item.page);
|
|
77
|
+
}
|
|
78
|
+
function tokenScore(page, query, tokens) {
|
|
79
|
+
const title = page.title.toLowerCase();
|
|
80
|
+
const path = page.path.toLowerCase();
|
|
81
|
+
const body = page.text.toLowerCase();
|
|
82
|
+
const phrase = query.toLowerCase();
|
|
83
|
+
let score = 0;
|
|
84
|
+
if (path.endsWith(`${phrase}.md`) || title === phrase) score += 200;
|
|
85
|
+
if (title.includes(phrase)) score += 50;
|
|
86
|
+
if (body.includes(phrase)) score += 20;
|
|
87
|
+
for (const token of tokens) {
|
|
88
|
+
if (title.includes(token)) score += 5;
|
|
89
|
+
if (body.includes(token)) score += 1;
|
|
90
|
+
if (path.includes(token)) score += 3;
|
|
91
|
+
}
|
|
92
|
+
return score;
|
|
93
|
+
}
|
|
94
|
+
function buildSnippet(text, query) {
|
|
95
|
+
const compact = text.replace(/\s+/g, " ").trim();
|
|
96
|
+
const idx = compact.toLowerCase().indexOf(query.toLowerCase());
|
|
97
|
+
if (idx < 0) return compact.slice(0, 180);
|
|
98
|
+
return compact.slice(Math.max(0, idx - 80), Math.min(compact.length, idx + query.length + 100));
|
|
99
|
+
}
|
|
100
|
+
function reasonsFor(page, query) {
|
|
101
|
+
const lower = `${page.title}
|
|
102
|
+
${page.text}`.toLowerCase();
|
|
103
|
+
const reasons = [];
|
|
104
|
+
if (lower.includes(query.toLowerCase())) reasons.push("phrase");
|
|
105
|
+
if (page.sourceIds.length > 0) reasons.push("sourced");
|
|
106
|
+
if (page.outLinks.length > 0) reasons.push("linked");
|
|
107
|
+
return reasons;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export {
|
|
111
|
+
searchKnowledge,
|
|
112
|
+
tokenizeQuery,
|
|
113
|
+
reciprocalRankFusion
|
|
114
|
+
};
|
|
115
|
+
//# sourceMappingURL=chunk-DQ3PDMDP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/search.ts"],"sourcesContent":["import type { KnowledgeIndex, KnowledgePage, KnowledgeSearchResult } from './types'\n\nconst RRF_K = 60\nconst STOP_WORDS = new Set([\n 'the',\n 'is',\n 'a',\n 'an',\n 'what',\n 'how',\n 'are',\n 'was',\n 'were',\n 'to',\n 'for',\n 'of',\n 'with',\n 'by',\n 'in',\n 'on',\n 'and',\n])\n\nexport function searchKnowledge(\n index: KnowledgeIndex,\n query: string,\n limit = 10,\n): KnowledgeSearchResult[] {\n const trimmed = query.trim()\n if (trimmed === '') return []\n const tokenRanked = rankByTokens(index.pages, trimmed)\n const graphRanked = rankByGraph(index.pages, tokenRanked)\n const scores = reciprocalRankFusion([tokenRanked.map((p) => p.id), graphRanked.map((p) => p.id)])\n const byId = new Map(index.pages.map((page) => [page.id, page]))\n\n const ranked = [...scores.entries()]\n .map(([id, score]) => ({ page: byId.get(id), score }))\n .filter((item): item is { page: KnowledgePage; score: number } => Boolean(item.page))\n .sort((a, b) => b.score - a.score || a.page.path.localeCompare(b.page.path))\n .slice(0, limit)\n\n // Normalize against the top hit so callers can compare against natural\n // [0, 1] thresholds. Raw RRF values are typically ~0.016, which reads as\n // \"no relevance\" to humans even when the result is the best available.\n // The top hit becomes 1 by definition; lower-ranked hits scale linearly.\n const topScore = ranked[0]?.score ?? 0\n\n return ranked.map((item, i) => ({\n page: item.page,\n score: item.score,\n rrfScore: item.score,\n normalizedScore: topScore > 0 ? item.score / topScore : 0,\n rank: i + 1,\n snippet: buildSnippet(item.page.text, trimmed),\n reasons: reasonsFor(item.page, trimmed),\n }))\n}\n\nexport function tokenizeQuery(query: string): string[] {\n const raw = query\n .toLowerCase()\n .split(/[\\s,,。!?、;:\"\"''()()\\-_/\\\\·~~…]+/)\n .filter((token) => token.length > 1 && !STOP_WORDS.has(token))\n const tokens: string[] = []\n for (const token of raw) {\n if (/[\\u4e00-\\u9fff\\u3400-\\u4dbf]/.test(token) && token.length > 2) {\n const chars = [...token]\n for (let i = 0; i < chars.length - 1; i++) tokens.push(chars[i]! + chars[i + 1]!)\n tokens.push(...chars)\n }\n tokens.push(token)\n }\n return [...new Set(tokens)]\n}\n\nexport function reciprocalRankFusion(rankLists: string[][], k = RRF_K): Map<string, number> {\n const scores = new Map<string, number>()\n for (const list of rankLists) {\n list.forEach((id, idx) => {\n scores.set(id, (scores.get(id) ?? 0) + 1 / (k + idx + 1))\n })\n }\n return scores\n}\n\nfunction rankByTokens(pages: KnowledgePage[], query: string): KnowledgePage[] {\n const tokens = tokenizeQuery(query)\n const effective = tokens.length > 0 ? tokens : [query.toLowerCase()]\n return pages\n .map((page) => ({ page, score: tokenScore(page, query, effective) }))\n .filter((item) => item.score > 0)\n .sort((a, b) => b.score - a.score || a.page.path.localeCompare(b.page.path))\n .map((item) => item.page)\n}\n\nfunction rankByGraph(pages: KnowledgePage[], tokenRanked: KnowledgePage[]): KnowledgePage[] {\n if (tokenRanked.length === 0) return []\n const seeds = new Set(tokenRanked.slice(0, 5).map((page) => page.id))\n return pages\n .map((page) => ({\n page,\n score:\n page.outLinks.filter((link) => seeds.has(link)).length +\n page.sourceIds.filter((source) =>\n tokenRanked.some((seed) => seed.sourceIds.includes(source)),\n ).length,\n }))\n .filter((item) => item.score > 0)\n .sort((a, b) => b.score - a.score || a.page.path.localeCompare(b.page.path))\n .map((item) => item.page)\n}\n\nfunction tokenScore(page: KnowledgePage, query: string, tokens: string[]): number {\n const title = page.title.toLowerCase()\n const path = page.path.toLowerCase()\n const body = page.text.toLowerCase()\n const phrase = query.toLowerCase()\n let score = 0\n if (path.endsWith(`${phrase}.md`) || title === phrase) score += 200\n if (title.includes(phrase)) score += 50\n if (body.includes(phrase)) score += 20\n for (const token of tokens) {\n if (title.includes(token)) score += 5\n if (body.includes(token)) score += 1\n if (path.includes(token)) score += 3\n }\n return score\n}\n\nfunction buildSnippet(text: string, query: string): string {\n const compact = text.replace(/\\s+/g, ' ').trim()\n const idx = compact.toLowerCase().indexOf(query.toLowerCase())\n if (idx < 0) return compact.slice(0, 180)\n return compact.slice(Math.max(0, idx - 80), Math.min(compact.length, idx + query.length + 100))\n}\n\nfunction reasonsFor(page: KnowledgePage, query: string): string[] {\n const lower = `${page.title}\\n${page.text}`.toLowerCase()\n const reasons: string[] = []\n if (lower.includes(query.toLowerCase())) reasons.push('phrase')\n if (page.sourceIds.length > 0) reasons.push('sourced')\n if (page.outLinks.length > 0) reasons.push('linked')\n return reasons\n}\n"],"mappings":"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","names":[]}
|