@tangle-network/agent-knowledge 4.1.0 → 5.0.1

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 CHANGED
@@ -1,1013 +1,278 @@
1
- # agent-knowledge
1
+ # @tangle-network/agent-knowledge
2
2
 
3
- Source-grounded, eval-gated knowledge growth primitives for agents.
3
+ Build, search, test, and improve source-backed knowledge bases in TypeScript.
4
4
 
5
- This package turns raw sources and generated markdown knowledge into a versionable graph that agents can search, lint, evaluate, and improve over time. It is intentionally domain-agnostic: legal, tax, coding, research, finance, business, and scientific workflows define their own policies and rubrics on top.
6
-
7
- ## Contents
8
-
9
- - [Install](#install)
10
- - [Start here](#start-here): pick CLI vs programmatic
11
- - [Common uses](#common-uses): wiki, RAG, memory, new KBs, existing KBs, and parallel agents
12
- - [CLI](#cli): `init` → `source-add` → `index` → `search` → `lint`
13
- - [Design](#design): the invariants, including immutable sources, cited claims, and deterministic graph output
14
- - [Benchmark runner](#benchmark-runner): BEIR/MTEB/qrels, RAG answer, hallucination, KB-improvement cases
15
- - [Agent-Eval integration](#agent-eval-integration): retrieval eval, readiness bundles, and release reports
16
- - [Memory adapters](#memory-adapters): Mem0, Graphiti, Neo4j, branching, and automatic improvement
17
- - [Research loops](#research-loop): direct and worker/reviewer knowledge growth
18
- - [Runtime integration](#runtime-integration): how agent runners plug into the pure KB loop
19
- - [Pluggable knowledge sources](#pluggable-knowledge-sources): live authorities → eval re-runs
5
+ The package manages source records, Markdown knowledge, indexes, retrieval tests, candidate workspaces, and memory adapters.
6
+ It does not browse, call models, or run agents.
7
+ Supply application callbacks for those decisions, or use `@tangle-network/agent-runtime/knowledge` to run them with agents.
20
8
 
21
9
  ## Install
22
10
 
23
11
  ```bash
24
- pnpm add @tangle-network/agent-knowledge @tangle-network/agent-eval
12
+ pnpm add @tangle-network/agent-knowledge @tangle-network/agent-eval@0.126.6
25
13
  ```
26
14
 
27
- ## Start here
28
-
29
- Two ways in, depending on what you're doing:
30
-
31
- - **Author / inspect a KB by hand** → the [CLI](#cli) (`init` → `source-add` → `index` → `search` → `lint`). Fastest way to see the shape on disk.
32
- - **Drive it from an agent** → pick the primitive by intent:
33
- - *"Does the agent have enough context to run?"* → [`buildEvalKnowledgeBundle`](#agent-eval-integration) (block / ask / acquire before execution).
34
- - *"Grow the KB as a researcher"* → [`runKnowledgeResearchLoop`](#research-loop) (deterministic mechanics; your agent owns judgment) or [`runVerifiedResearchLoop`](#verified-research-loop) (researcher proposes, reviewer checks and fills gaps).
35
- - *"Spawn live agents to improve a KB"* → pass an `updateKnowledge` callback to `improveKnowledgeBase`; runtime-backed supervisors live in `@tangle-network/agent-runtime`.
36
- - *"Tune retrieval for a knowledge base"* → `runRetrievalImprovementLoop` in the [Agent-Eval integration](#agent-eval-integration) section.
37
- - *"Run an operator-grade KB improvement cycle"* → `improveKnowledgeBase` in the [Agent-Eval integration](#agent-eval-integration) section.
38
- It creates a candidate KB, lets agents or deterministic hooks improve it, runs configured evals, and returns exact candidate identity for later approval.
39
- - *"Expose the lower-level RAG lifecycle phases"* → `runRagKnowledgeImprovementLoop` in the [Agent-Eval integration](#agent-eval-integration) section.
40
- It exposes retrieval tuning, gap diagnosis, knowledge acquisition/update, answer-quality checks, and promotion as one typed lifecycle.
41
- - *"Evaluate RAG answers or a wiki/KB"* → `ragAnswerQualityJudge`, `createRagAnswerQualityHook`, and `scoreKnowledgeBaseIndex` in the [Agent-Eval integration](#agent-eval-integration) section.
42
- - *"Run standard RAG/KB benchmarks"* → `runKnowledgeBenchmarkSuite` in the [Benchmark runner](#benchmark-runner) section.
43
- - *"Does this candidate KB improve task success?"* → run an [agent-eval improvement loop](#agent-eval-integration) over KB variants, then `knowledgeReleaseReport` for the promotion decision.
44
- - *"Keep live authorities fresh"* → [pluggable sources](#pluggable-knowledge-sources) + `detectChanges` → eval re-runs.
45
-
46
- Storage stays consumer-owned via `KbStore` (`MemoryKbStore`, `FileSystemKbStore`, or your own D1/Postgres). Every primitive below is source-grounded: claims cite immutable source records, and lint fails on un-grounded citations.
15
+ Requires Node.js 20.19 or later.
47
16
 
48
- ## Common Uses
17
+ ## Choose an API
49
18
 
50
- | Use case | What this package owns | Front door |
19
+ | Goal | Start with | Import |
51
20
  |---|---|---|
52
- | LLM wiki or docs KB | Source records, cited markdown pages, lint, readiness scoring, candidate promotion | `improveKnowledgeBase` or `runKnowledgeResearchLoop` |
53
- | RAG | Retrieval eval, source-span labels, answer quality checks, missing/stale/noisy-source diagnosis | `runRagKnowledgeImprovementLoop` |
54
- | Memory DB | Provider adapters, isolated branches, sharing policies, ordered histories, and fresh comparison | `/memory` plus `runAgentMemoryExperiment` |
55
- | From scratch | Empty KB layout, source ingestion, write-block application, indexing, readiness checks | CLI `init` or `runKnowledgeResearchLoop` |
56
- | Existing KB | Candidate workspace, resume state, base-hash conflict check, exact approved promotion | `improveKnowledgeBase` |
57
- | Parallel agents | Isolated KB candidates or memory branches, ordered per-agent writes, concurrent histories, and explicit activation | `improveKnowledgeBase` or `runAgentMemoryImprovement` |
58
- | One-call agent job | Runtime supervisor plus all KB mechanics above | `runKnowledgeImprovementJob` from `@tangle-network/agent-runtime/knowledge` |
59
-
60
- ## CLI
61
-
62
- ```bash
63
- agent-knowledge init --root .
64
- agent-knowledge source-add ./docs/spec.md --root .
65
- agent-knowledge sources --root .
66
- agent-knowledge apply-write-blocks ./proposal.txt --root .
67
- agent-knowledge index --root .
68
- agent-knowledge search "portfolio risk" --root .
69
- agent-knowledge inspect --root .
70
- agent-knowledge explain knowledge/concepts/risk.md --root .
71
- agent-knowledge graph --root . --format json
72
- agent-knowledge lint --root .
73
- agent-knowledge validate --strict --root .
74
- agent-knowledge export --root . --format json
75
- agent-knowledge viz --root .
76
- ```
21
+ | Create a file-backed knowledge base | `initKnowledgeBase`, `addSourceText`, `applyKnowledgeWriteBlocks` | package root |
22
+ | Search an existing package knowledge base | `createFileSystemSearchProvider` | package root |
23
+ | Improve a live knowledge base without editing it in place | `improveKnowledgeBase` | package root |
24
+ | Optimize retrieval or a complete RAG configuration | `runRetrievalImprovementLoop`, `runRagOptimization` | package root |
25
+ | Optimize a KB maintenance policy | `optimizeKnowledgeBasePolicy` | package root |
26
+ | Run retrieval, research, answer checks, and promotion as one process | `runRagKnowledgeImprovementLoop` | package root |
27
+ | Compare providers or optimize memory configuration | `AgentMemoryAdapter`, `runAgentMemoryImprovement` | `/memory` |
28
+ | Read from external authorities | `KnowledgeSource` and source adapters | `/sources` |
29
+ | Run retrieval, answer, KB, or memory benchmark cases | `runKnowledgeBenchmarkSuite` | `/benchmarks` |
30
+ | Use live research or coding agents | `runKnowledgeImprovementJob` | `@tangle-network/agent-runtime/knowledge` |
77
31
 
78
- The default layout is:
79
-
80
- ```txt
81
- raw/
82
- sources/
83
- knowledge/
84
- index.md # scaffold: human-navigation only, excluded from the page index
85
- log.md # scaffold: human-navigation only, excluded from the page index
86
- .agent-knowledge/
87
- sources.json
88
- index.json
89
- ```
32
+ ## Create and search a knowledge base
90
33
 
91
- `initKnowledgeBase` writes `knowledge/index.md` and `knowledge/log.md` for
92
- authors to curate by hand. They are deliberately excluded from
93
- `buildKnowledgeIndex` / `searchKnowledge` so they do not inflate page counts
94
- or pollute search hits. Any nested `<dir>/index.md` or `<dir>/log.md` is
95
- treated the same way. The shared predicate is `isScaffoldPath`, exported
96
- from `@tangle-network/agent-knowledge`.
97
-
98
- ## Design
99
-
100
- - Raw sources are immutable evidence.
101
- - Generated knowledge is editable but validated.
102
- - Claims should cite source records when promoted.
103
- - Lint fails on pages that cite unknown source IDs.
104
- - Text sources get deterministic anchors (`all`, `l1`, `l51`, ...) for precise citations like `[^src_id#all]`.
105
- - Agent write proposals can be safely applied with `apply-write-blocks`.
106
- - `KbStore` keeps storage consumer-owned; use `MemoryKbStore`, `FileSystemKbStore`, or implement D1 in the app.
107
- - Discovery uses worker/dispatcher contracts, with a local dispatcher for dev and tests.
108
- - `runKnowledgeResearchLoop()` provides thin loop mechanics for researcher
109
- agents: ingest sources, apply safe write blocks, rebuild the index,
110
- lint/validate, score readiness, and return a transcript. The agent still
111
- decides what to research, what to write, and when the wiki is good enough.
112
- - `createKnowledgeControlLoopAdapter()` maps those mechanics into
113
- `agent-eval`'s `runAgentControlLoop()` so products can plug in their own
114
- proposer, reviewer, and driver policies.
115
- - `runRagKnowledgeImprovementLoop()` coordinates the whole RAG improvement
116
- lifecycle. Retrieval tuning, diagnosis, acquisition, KB update,
117
- answer-quality eval, and promotion are separate typed phases so products can
118
- plug in browser agents, coding agents, connectors, or deterministic policies
119
- without this package hardcoding an agent runner.
120
- - `improveKnowledgeBase()` wraps that lifecycle with durable candidate state,
121
- a per-run lock, resume support, isolated candidate workspaces, KB quality
122
- scoring, and an exact candidate reference for explicit promotion.
123
- Use it when running agents in loops against a real KB rather than only
124
- exposing phase hooks.
125
- - `evaluateKnowledgeBaseReadiness()` checks one KB root without running an
126
- improvement loop: it rebuilds the index, validates/lints it, scores KB
127
- quality, and evaluates configured readiness specs.
128
- - RAG answer evaluation follows the common open-source shape used by Ragas,
129
- DeepEval, TruLens, and RAGChecker: context quality, answer relevance,
130
- support/faithfulness, citations, abstention, and failure diagnosis.
131
- External tools stay pluggable via score normalization and row exporters.
132
- - Zod schemas define the stable wire shape.
133
- - Graph/search/lint are deterministic and fast.
134
- - `searchKnowledge` returns hits with three score fields. `score` and
135
- `rrfScore` are the raw reciprocal-rank-fusion value (typically 0.01 to 0.05);
136
- use them when intent matters or when fusing across queries.
137
- `normalizedScore` is the same value scaled into [0, 1] relative to the top
138
- hit *in this result set* (top hit = 1, others = score / topScore). Use it
139
- when comparing against natural confidence thresholds. The normalization is
140
- within-set ranking, not a cross-query absolute confidence.
141
- - Release confidence uses `@tangle-network/agent-eval` release gates (`evaluateReleaseConfidence`) instead of reimplementing them.
142
- - 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.
143
- - `buildEvalKnowledgeBundle()` maps wiki/search evidence into
144
- `agent-eval` `KnowledgeRequirement`, `KnowledgeBundle`, and
145
- `KnowledgeReadinessReport` contracts so control loops can block, ask, or
146
- acquire data before running an agent.
147
-
148
- The `/viz` subpath exports graph insight helpers without UI dependencies.
149
-
150
- The `/memory` subpath exports an optional memory adapter contract. Use it to
151
- bridge episodic or graph-native memory systems into the same source-grounded
152
- readiness/eval machinery without making `agent-knowledge` own the database.
153
-
154
- ## Benchmark Runner
155
-
156
- 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.
157
- 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.
34
+ This example stores one source, writes one cited page, validates the result, and searches it.
158
35
 
159
36
  ```ts
160
37
  import {
161
- buildFirstPartyMemoryLifecycleBenchmarkCases,
162
- buildIndustryMemoryBenchmarkSmokeCases,
163
- buildIndustryRagBenchmarkSmokeCases,
164
- buildRetrievalBenchmarkCasesFromQrels,
165
- createInMemoryBenchmarkAdapter,
166
- createNoopMemoryBenchmarkAdapter,
167
- isKnowledgeMemoryBenchmarkCase,
168
- parseKnowledgeBenchmarkQrels,
169
- respondToIndustryMemoryBenchmarkSmokeCase,
170
- respondToIndustryRagBenchmarkSmokeCase,
171
- runMemoryAdapterBenchmark,
172
- runKnowledgeBenchmarkSuite,
173
- } from '@tangle-network/agent-knowledge/benchmarks'
174
-
175
- await runKnowledgeBenchmarkSuite({
176
- cases: buildIndustryRagBenchmarkSmokeCases(),
177
- runDir: '.agent-knowledge/benchmark-runs/industry-smoke',
178
- respondRef: 'industry-rag-smoke:v1',
179
- respond: respondToIndustryRagBenchmarkSmokeCase,
180
- })
181
-
182
- await runKnowledgeBenchmarkSuite({
183
- cases: buildIndustryMemoryBenchmarkSmokeCases(),
184
- runDir: '.agent-knowledge/benchmark-runs/memory-smoke',
185
- respondRef: 'industry-memory-smoke:v1',
186
- respond: ({ case: testCase }) => {
187
- if (!isKnowledgeMemoryBenchmarkCase(testCase)) return { answer: '' }
188
- return respondToIndustryMemoryBenchmarkSmokeCase({ case: testCase })
189
- },
190
- })
191
-
192
- const memoryRanking = await runMemoryAdapterBenchmark({
193
- cases: buildFirstPartyMemoryLifecycleBenchmarkCases(),
194
- runDir: '.agent-knowledge/benchmark-runs/memory-ranking',
195
- costCeiling: 5,
196
- candidates: [
197
- { id: 'no-memory', ref: 'no-memory:v1', createAdapter: () => createNoopMemoryBenchmarkAdapter() },
198
- {
199
- id: 'in-memory',
200
- ref: 'in-memory:v1',
201
- createAdapter: () => createInMemoryBenchmarkAdapter(),
202
- searchLimit: 1,
203
- },
204
- ],
205
- })
38
+ addSourceText,
39
+ applyKnowledgeWriteBlocks,
40
+ createFileSystemSearchProvider,
41
+ initKnowledgeBase,
42
+ validateKnowledgeIndex,
43
+ writeKnowledgeIndex,
44
+ } from '@tangle-network/agent-knowledge'
206
45
 
207
- console.log(memoryRanking.rows)
46
+ const root = './support-kb'
47
+ await initKnowledgeBase(root)
208
48
 
209
- const cases = buildRetrievalBenchmarkCasesFromQrels({
210
- benchmarkId: 'beir/nfcorpus',
211
- family: 'beir',
212
- queries: [{ id: 'q1', text: 'aspirin heart attack prevention', split: 'holdout' }],
213
- qrels: parseKnowledgeBenchmarkQrels('q1 0 src-aspirin 1'),
214
- targetKind: 'source',
215
- k: 10,
49
+ const source = await addSourceText(root, {
50
+ uri: 'inline://refund-policy',
51
+ title: 'Refund policy',
52
+ text: 'Customers may request a refund within 30 days of purchase.',
216
53
  })
217
54
 
218
- const result = await runKnowledgeBenchmarkSuite({
219
- cases,
220
- runDir: '.agent-knowledge/benchmark-runs/beir-nfcorpus-smoke',
221
- respondRef: 'product-retriever:v3',
222
- costCeiling: 5,
223
- respond: async ({ case: testCase }) => {
224
- if (testCase.taskKind !== 'retrieval') return { hits: [] }
225
- const hits = await retrieveFromYourKb(testCase.query)
226
- return { hits }
227
- },
228
- })
229
-
230
- console.log(result.report.score.mean)
55
+ await applyKnowledgeWriteBlocks(
56
+ root,
57
+ [
58
+ '---FILE: knowledge/refunds.md---',
59
+ '---',
60
+ 'id: refunds',
61
+ 'title: Refunds',
62
+ 'sources:',
63
+ ` - ${source.id}`,
64
+ '---',
65
+ '# Refunds',
66
+ `The refund window is 30 days.[^${source.id}#all]`,
67
+ '---END FILE---',
68
+ ].join('\n'),
69
+ )
70
+
71
+ const index = await writeKnowledgeIndex(root)
72
+ const validation = validateKnowledgeIndex(index, { strict: true })
73
+ if (!validation.ok) throw new Error(JSON.stringify(validation.findings))
74
+
75
+ const search = createFileSystemSearchProvider({ root, index })
76
+ console.log(await search.search('How long is the refund window?', { limit: 3 }))
231
77
  ```
232
78
 
233
- `runMemoryAdapterBenchmark()` is for adapters that enforce every supplied scope and support exact scoped deletion.
234
- Every cell records its fresh provider namespace before the first write and deletes it after measurement, so concurrent repetitions and retries cannot share memory.
235
- On restart, unfinished scopes are deleted before any new benchmark cell runs.
236
- The candidate factory receives `purpose: 'execute' | 'recovery'`, `signal`, and `markExternalCall()`; both paths must reconnect to the same provider and adapter identity.
237
- Pass `signal` to provider connection and provisioning calls so timeout cancellation reaches the SDK.
238
- Set `adapterId` when the returned adapter ID differs from the candidate ID.
239
- The declared ID lets a fully cached resume skip adapter creation entirely; a returned mismatch fails before any case runs.
240
- Keep local construction free.
241
- When construction provisions or reconnects billable provider state, set `adapterCreationCostUsd` and call `markExternalCall()` immediately before the external action.
242
- Normal read and write work belongs in adapter methods so it runs inside per-case cost accounting.
243
- Use `runAgentMemoryExperiment()` below when an agent profile needs ordered multi-step histories, branch snapshots, runtime callbacks, or automatic configuration improvement.
244
- Set `cleanupTimeoutMs` to bound each provider cleanup and close operation; the default is 180 seconds.
245
- Set `costUsdPerCase` for normal provider work, `adapterCreationCostUsd` for billable construction or reconnects, and `recoveryCostUsdPerAttempt` when deleting an interrupted case can add another provider charge.
246
- One `costCeiling` covers every candidate and recovery call in the comparison, and `totalCostUsd` reports the complete comparison cost.
247
- `unrankedRecoveryCostUsd` identifies cleanup spend from retired candidates that cannot appear in ranking rows.
248
- The default `costCeiling` is zero, so paid work is refused until the caller sets a limit.
249
- Keep removed implementations in `recoveryCandidates` until their unfinished scopes are gone, and use `maxRecoveryAttempts` to reject an unexpectedly large backlog before provider work starts.
250
- Each failed recovery is recorded before its provider call.
251
- `maxRecoveryRetriesPerAttempt` defaults to three, and `recoveryLogPath` identifies the durable retry history returned by the run.
252
- The default filesystem storage uses an OS lock; custom storage requires `controllerMode: 'process-local'` or a distributed `acquireRunLease` implementation.
253
-
254
- Billable responders must execute paid work through `context.cost.runPaidCall()`; `costUsd` on an artifact is display-only.
255
- Change `respondRef` whenever the model, prompt, retrieval, tools, or runtime behavior changes; resumable runs reject a missing reference instead of reusing ambiguous cached rows.
256
- Use `buildRetrievalBenchmarkCasesFromQrels()` for qrels-backed retrieval datasets.
257
- 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.
258
- 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.
259
- Use `taskKind: 'kb-improvement'` when the artifact is candidate KB text produced by `improveKnowledgeBase()`.
260
- Use `KnowledgeMemoryBenchmarkCase` for memory systems.
261
- Memory cases carry ordered events plus current required facts, stale forbidden facts, expected event IDs, and expected actor IDs.
262
- 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.
263
- Use `runMemoryAdapterBenchmark()` to rank real memory systems that implement `AgentMemoryAdapter`.
264
- Built-in adapters cover Neo4j Agent Memory, Mem0 hosted and open source, and the official Graphiti MCP server.
265
- Zep, Letta, a vector store, or a product-owned backend can implement the same small contract.
266
-
267
- ## Agent-Eval Integration
268
-
269
- Use `ragAnswerQualityJudge` or `createRagAnswerQualityHook` when the product already has answer traces and needs RAG answer scoring without rebuilding metrics.
270
- 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.
79
+ The provider uses the package's local text search.
80
+ Pass `refresh: 'always'` to rebuild its index before every query, or call `invalidate()` after changing files.
81
+ Use `asRetrievalEvalRetriever()` to send the same search path into retrieval tests.
271
82
 
272
- ```ts
273
- import {
274
- createRagAnswerQualityHook,
275
- scoreKnowledgeBaseIndex,
276
- } from '@tangle-network/agent-knowledge'
83
+ ## Use the CLI
277
84
 
278
- const evaluateAnswers = createRagAnswerQualityHook({
279
- scenarios: answerScenarios,
280
- run: async (scenario) => runRagAnswerTrace(scenario),
281
- externalEvaluator: async ({ scenario, artifact }) => runRagasOrDeepEval({
282
- input: scenario.query,
283
- output: artifact.answer,
284
- contexts: artifact.contexts,
285
- }),
286
- })
85
+ The CLI exposes the same file-backed workflow.
287
86
 
288
- const answerQuality = await evaluateAnswers()
289
- const kbQuality = scoreKnowledgeBaseIndex(index, {
290
- strict: true,
291
- minCitationRate: 0.8,
292
- maxStaleSourceRate: 0.02,
293
- })
87
+ ```bash
88
+ pnpm exec agent-knowledge init --root ./support-kb
89
+ pnpm exec agent-knowledge source-add ./refund-policy.md --root ./support-kb
90
+ pnpm exec agent-knowledge apply-write-blocks ./proposal.txt --root ./support-kb
91
+ pnpm exec agent-knowledge index --root ./support-kb
92
+ pnpm exec agent-knowledge search "refund window" --root ./support-kb
93
+ pnpm exec agent-knowledge validate --strict --root ./support-kb
294
94
  ```
295
95
 
296
- Use `runRagKnowledgeImprovementLoop` when the product question is broader than retrieval:
297
- can the system find the gaps, gather or update knowledge, prove generated answers still behave, and decide whether to promote?
298
- `agent-knowledge` owns the knowledge/eval contract; the caller supplies the research, coding, connector, and answer-eval hooks.
299
- This lower-level loop returns a promotion recommendation; it never writes to the live knowledge base.
96
+ Run `pnpm exec agent-knowledge help` for every command.
300
97
 
301
- ```ts
302
- import { runRagKnowledgeImprovementLoop } from '@tangle-network/agent-knowledge'
303
-
304
- const result = await runRagKnowledgeImprovementLoop({
305
- goal: 'Improve the support RAG KB',
306
- retrieval: {
307
- baseline: { k: 5, hybrid: false },
308
- scenarios: trainRetrievalScenarios,
309
- holdoutScenarios,
310
- index,
311
- searchSpace: { k: [5, 10, 20], hybrid: [false, true] },
312
- targetRecall: 0.9,
313
- },
314
- diagnose: async ({ retrieval }) => diagnoseRagGaps(retrieval),
315
- acquireKnowledge: async ({ findings }) => researchMissingSources(findings),
316
- knowledgeResearch: { root: './kb' },
317
- evaluateAnswers,
318
- promote: async ({ retrieval, answerQuality }) =>
319
- decidePromotion({ retrieval, answerQuality }),
320
- requiredPhases: ['retrieval-tuning', 'knowledge-update', 'answer-quality', 'promotion'],
321
- })
98
+ The default layout is:
322
99
 
323
- console.log(result.promotion)
100
+ ```text
101
+ support-kb/
102
+ raw/sources/ # immutable imported content
103
+ knowledge/ # editable Markdown pages
104
+ .agent-knowledge/
105
+ sources.json # source registry
106
+ index.json # generated search index
324
107
  ```
325
108
 
326
- Use `improveKnowledgeBase` when a program should own the candidate workspace and promotion mechanics.
327
- The wrapper composes the same RAG lifecycle, but adds resumable state under `.agent-knowledge/improvements/<runId>/`, a lock lease for parallel operators, and exact candidate bytes that can be approved later.
109
+ ## Improve a live knowledge base
110
+
111
+ `improveKnowledgeBase` creates an isolated candidate, runs your update callback, measures the candidate, and returns an exact candidate reference.
112
+ It does not overwrite the live knowledge base.
328
113
 
329
114
  ```ts
330
115
  import {
331
116
  improveKnowledgeBase,
332
117
  knowledgeImprovementCandidateRef,
333
118
  promoteKnowledgeCandidate,
334
- restoreKnowledgeCandidateBaseline,
335
- withKnowledgeImprovementComparison,
336
119
  } from '@tangle-network/agent-knowledge'
337
120
 
338
- const staged = await improveKnowledgeBase({
339
- root: './kb',
340
- goal: 'Improve support refund-policy knowledge',
341
- readinessSpecs,
342
- retrieval: {
343
- baseline: { k: 5 },
344
- scenarios: trainRetrievalScenarios,
345
- holdoutScenarios,
346
- searchSpace: { k: [5, 10, 20] },
347
- targetRecall: 0.9,
348
- },
349
- step: async ({ readiness }) => runResearchAgent({ missing: readiness?.report }),
350
- evaluateAnswers,
351
- requiredPhases: ['knowledge-update', 'retrieval-tuning', 'answer-quality'],
352
- })
353
-
354
- const candidate = knowledgeImprovementCandidateRef(staged)
355
- console.log(staged.evaluation, candidate)
356
-
357
- await withKnowledgeImprovementComparison({ root: './kb', candidate }, async (comparison) => {
358
- await compareKnowledgeFiles(
359
- comparison.baseline.root,
360
- comparison.candidate.root,
361
- comparison.evaluation,
362
- )
363
- })
364
-
365
- // Call this only after your product records approval for this exact candidate.
366
- const promoted = await promoteKnowledgeCandidate({ root: './kb', candidate })
367
- console.log(promoted.promoted, promoted.mutation)
368
-
369
- // The same candidate reference can restore its exact frozen baseline.
370
- await restoreKnowledgeCandidateBaseline({ root: './kb', candidate })
371
- ```
372
-
373
- `improveKnowledgeBase` stages a measured candidate by default and does not change the live knowledge base.
374
- Calling it again with the same `runId` resumes interrupted candidate generation.
375
- Resume an interrupted promotion or restore through the same transition function with its exact candidate and activation.
376
- `withKnowledgeImprovementComparison` materializes the exact measured baseline and candidate in isolated temporary directories for one trusted callback, checks both again afterward, and removes them.
377
- `withKnowledgeImprovementCandidate` remains the focused candidate-only read.
378
- `promoteKnowledgeCandidate` applies only the frozen bytes identified by the approved candidate reference, and refuses if the live base changed.
379
- Promotion and restore results require `mutation` with the logical before/after hashes and transaction identity observed under the knowledge write lock; resumed transactions report `recovered: true`, while a later call that finds no pending work reports `changed: false`.
380
- Passing `activation` makes the shared `AgentImprovementActivationResult` durable before the file transaction closes; `loadKnowledgeImprovementActivationResult` provides the read-only retry path.
381
- Runtime supplies the result builder and product-owned result store, while this package owns knowledge files and their co-located result.
382
- The current release accepts one strict run-state format; incomplete runs created before 3.0 must be completed or restarted before upgrading.
383
- The exact candidate workflow requires Linux; other knowledge, retrieval, and evaluation APIs remain cross-platform.
384
-
385
- If a required phase is missing its hook, the loop throws.
386
- That keeps the public API from reporting a fake “RAG improved” result when the caller only wired retrieval or only wired a researcher.
387
-
388
- Use retrieval eval when the question is whether a retrieval/RAG config can find the right knowledge before an agent reasons over it.
389
- The labels should name stable pages, source records, anchors, or source spans, not ephemeral chunk IDs.
390
- Benchmark coverage and completion criteria are documented in [`docs/eval/rag-eval-roadmap.md`](docs/eval/rag-eval-roadmap.md).
391
-
392
- ```ts
393
- import { runRetrievalImprovementLoop } from '@tangle-network/agent-knowledge'
394
-
395
- const result = await runRetrievalImprovementLoop({
396
- baseline: { k: 5, hybrid: false, reranker: null },
397
- scenarios: trainRetrievalScenarios,
398
- holdoutScenarios: holdoutRetrievalScenarios,
399
- index,
400
- searchSpace: {
401
- k: [5, 10, 20],
402
- hybrid: [false, true],
403
- reranker: [null, 'bge-reranker'],
121
+ const result = await improveKnowledgeBase({
122
+ root: './support-kb',
123
+ goal: 'Answer refund questions using the current policy',
124
+ implementationRef: 'git:0123456789abcdef0123456789abcdef01234567',
125
+ runId: 'refund-policy-2026-07',
126
+ maxCandidates: 3,
127
+ updateKnowledge: async ({ candidateRoot }) => {
128
+ await updateCandidate(candidateRoot) // Your code or agent writes only to this copy.
129
+ return { applied: true, summary: 'Updated refund knowledge' }
404
130
  },
405
- targetRecall: 0.9,
406
- deltaThreshold: 0.02,
407
- costCeiling: 15,
408
- runDir: '.agent-knowledge/retrieval-runs',
409
- })
410
-
411
- console.log(result.winnerConfig)
412
- ```
413
-
414
- Pass a custom `retrieve` function to `buildRetrievalEvalDispatch` when the config controls an external vector store, reranker, hybrid search service, or chunker.
415
- The built-in fallback uses `searchKnowledge` over the local deterministic index.
416
- Use `buildRetrievalEvalDispatch`, `retrievalRecallJudge`, and `retrievalParameterSweepProposer` directly only when you need custom `agent-eval` wiring.
417
-
418
- To answer whether a candidate knowledge base 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`.
419
-
420
- 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.
421
-
422
- When holdout evidence is required, pass both the holdout-tagged run records and the scenario split:
423
-
424
- ```ts
425
- const release = knowledgeReleaseReport({
426
- candidateId,
427
- candidateRuns,
428
- scenarios,
429
- hasHoldout: true,
430
- })
431
- ```
432
-
433
- Use `buildEvalKnowledgeBundle()` before execution when the question is whether
434
- the agent has enough task-world context to run:
435
-
436
- ```ts
437
- import { buildEvalKnowledgeBundle } from '@tangle-network/agent-knowledge'
438
-
439
- const readiness = buildEvalKnowledgeBundle({
440
- taskId: 'sdk-migration',
441
- index,
442
- specs: [{
443
- id: 'repo-build-command',
444
- description: 'Repository build and typecheck command',
445
- query: 'build typecheck command',
446
- requiredFor: ['coding'],
447
- category: 'codebase_specific',
448
- acquisitionMode: 'inspect_repo',
449
- importance: 'blocking',
450
- freshness: 'weekly',
451
- sensitivity: 'public',
452
- confidenceNeeded: 0.9,
453
- minSources: 1,
454
- }],
455
- })
456
-
457
- console.log(readiness.report.recommendedAction)
458
- ```
459
-
460
- Pass `readiness.report` to `blockingKnowledgeEval()` from
461
- `@tangle-network/agent-eval`; use `readiness.questions` and
462
- `readiness.acquisitionPlans` to drive UI or connector workflows.
463
-
464
- ## Memory Adapters
465
-
466
- `agent-knowledge` does not replace a memory database.
467
- It gives existing memory systems one contract, isolated branches, ordered multi-agent histories, comparable measurements, and an automatic improvement loop.
468
-
469
- Use the provider adapter that matches the system you already run:
470
-
471
- ```ts
472
- import {
473
- createGraphitiMemoryAdapter,
474
- createMem0MemoryAdapter,
475
- createNeo4jAgentMemoryAdapter,
476
- } from '@tangle-network/agent-knowledge/memory'
477
-
478
- const mem0Hosted = createMem0MemoryAdapter({
479
- client: mem0Client,
480
- mode: 'hosted',
481
- backendRef: 'mem0:production-account',
482
- })
483
-
484
- const mem0Oss = createMem0MemoryAdapter({
485
- client: mem0OssClient,
486
- mode: 'oss',
487
- backendRef: 'mem0:local-postgres',
488
- })
489
-
490
- const graphiti = createGraphitiMemoryAdapter({
491
- client: graphitiMcpClient,
492
- backendRef: 'graphiti:production-cluster',
131
+ evaluate: ({ baselineRoot, candidateRoot }) =>
132
+ compareOnProductTasks({ baselineRoot, candidateRoot }),
493
133
  })
494
134
 
495
- const neo4j = createNeo4jAgentMemoryAdapter({
496
- client: neo4jMemoryClient,
497
- transport: 'rest',
498
- contextMode: 'search',
499
- })
500
- ```
501
-
502
- The Graphiti defaults match the current official server tools: `add_memory`, `search_memory_facts`, `search_nodes`, `get_episodes`, and `clear_graph`.
503
- Set `toolNames` explicitly when your server lists different names.
504
- The adapter never retries a write under another name because that could enqueue it twice.
505
- Visible writes expand the episode scan when Graphiti truncates a long group and fail explicitly at `episodeScanLimit` instead of reporting a false success.
506
- Hosted Mem0 follows the synchronous memory-array response from `mem0ai` 3.x and rejects legacy asynchronous event responses instead of treating them as successful writes.
507
- Mem0 cleanup requires `getAll` plus per-memory `delete`, refuses an empty identity scope, and waits until both list and search results stop returning deleted IDs.
508
- Every Mem0 operation requires `userId`, `agentId`, `runId`, or `namespace`.
509
- `appId`, tenant, team, session, and tag filters refine that identity but cannot replace it.
510
- Fresh-write probes expire after `ingestionTimeoutMs`, so a long-lived adapter retains only its current visibility window rather than its lifetime write history.
511
- The adapter does not call Mem0's account-wide `deleteAll` method.
512
- Use a stable, non-secret `backendRef` in Mem0 and Graphiti adapter identities so caches and dispatch keys cannot alias two accounts or clusters.
513
-
514
- Each adapter uses the same `search`, `getContext`, and `write` method shape, but provider capabilities remain explicit.
515
-
516
- | Adapter | Searchable through this adapter | Writable through this adapter |
517
- | --- | --- | --- |
518
- | Mem0 hosted or OSS | messages, observations, facts, entities, preferences, reasoning traces | all six kinds |
519
- | Graphiti MCP | extracted facts and entities | all six kinds as episodes |
520
- | Neo4j hosted REST | messages, observations, entities | messages, observations, entities, conversation reasoning steps |
521
- | Neo4j bridge | messages, observations, entities, preferences, similar reasoning traces | all six kinds |
522
-
523
- Neo4j bridge facts are graph writes.
524
- The official `@neo4j-labs/agent-memory` 0.4 API has no corresponding fact-search method, so fact-only retrieval experiments should use another provider or a custom Neo4j query adapter.
525
- Mem0 and Graphiti apply tenant, user, agent, team, run, session, namespace, and tag filters per request, subject to Mem0's required provider identity above.
526
- Neo4j Agent Memory experiments require disposable external state per branch because the SDK cannot clear every memory kind atomically.
527
- On a shared Neo4j client, the adapter rejects scope fields the selected SDK method cannot enforce before making a provider call.
528
- Conversation messages can use `sessionId`, and reasoning writes can use either `sessionId` or `runId`; other direct operations are unscoped unless a dedicated client is used.
529
- Pass `branchId` only after the caller has provisioned one physically isolated instance for that exact branch.
530
- Provider-specific identifiers remain internal to the adapter.
531
- Custom adapters must declare `branchIsolation: { mode: 'scoped' }` only when every operation enforces the supplied scope; undeclared adapters are rejected by branch execution.
532
-
533
- ```ts
534
- const neo4jInstances = new WeakMap<object, string>()
535
-
536
- const neo4jCandidate = {
537
- id: 'neo4j',
538
- ref: 'neo4j-agent-memory:0.4.0:rest',
539
- policy: { read: ['shared'], write: 'shared' },
540
- async createAdapter({ branchId, purpose, signal, markExternalCall }: {
541
- branchId: string
542
- purpose: 'execute' | 'recovery'
543
- signal: AbortSignal
544
- markExternalCall(): void
545
- }) {
546
- markExternalCall()
547
- const client = purpose === 'recovery'
548
- ? await openDedicatedNeo4jMemory({ branchId, transport: 'rest', signal })
549
- : await provisionDedicatedNeo4jMemory({ branchId, transport: 'rest', signal })
550
- if (!client) return null
551
- const adapter = createNeo4jAgentMemoryAdapter({ client, transport: 'rest', branchId })
552
- neo4jInstances.set(adapter, branchId)
553
- return adapter
554
- },
555
- async disposeAdapter(adapter: object) {
556
- const branchId = neo4jInstances.get(adapter)
557
- if (!branchId) throw new Error('unknown Neo4j branch')
558
- await destroyDedicatedNeo4jMemory({ branchId })
559
- },
135
+ if (result.candidate && result.evaluation?.passed) {
136
+ const candidate = knowledgeImprovementCandidateRef(result)
137
+ // Call this only after your application approves the measured candidate.
138
+ await promoteKnowledgeCandidate({ root: './support-kb', candidate })
560
139
  }
561
140
  ```
562
141
 
563
- The adapter requires `transport: 'rest' | 'bridge'` and rejects unsupported searches or writes before making a provider call.
564
- `contextMode: 'search'` is the default and preserves query-based behavior across providers.
565
- Set `contextMode: 'native'` with hosted REST to use Neo4j's whole-conversation context for message and observation memory.
566
-
567
- Use a branch when agents or candidate configurations must not share state accidentally:
568
-
569
- ```ts
570
- import {
571
- createAgentMemoryBranch,
572
- createMem0MemoryAdapter,
573
- } from '@tangle-network/agent-knowledge/memory'
574
-
575
- const memory = createAgentMemoryBranch({
576
- adapter: mem0Oss,
577
- branchId: 'candidate-17',
578
- policy: { read: ['private', 'team'], write: 'team' },
579
- baseScope: { tenantId: 'acme', teamId: 'research' },
580
- })
581
-
582
- await memory.write({
583
- kind: 'fact',
584
- text: 'The launch date is Friday.',
585
- scope: { agentId: 'researcher' },
586
- })
587
-
588
- const context = await memory.getContext('When is launch?', {
589
- scope: { agentId: 'builder' },
590
- limit: 5,
591
- })
592
-
593
- const snapshot = await memory.snapshot()
594
- await memory.close?.()
595
- const reopenedMem0Oss = createMem0MemoryAdapter({
596
- client: mem0OssClient,
597
- mode: 'oss',
598
- id: snapshot.adapterId,
599
- })
600
- const resumed = createAgentMemoryBranch({
601
- adapter: reopenedMem0Oss,
602
- branchId: 'candidate-17',
603
- snapshot,
604
- })
605
- ```
606
-
607
- Private writes stay visible only to one agent.
608
- Team writes are shared inside one team.
609
- Shared writes are visible to every agent in the branch.
610
- `fork()` replays accepted writes into a child branch, including a branch backed by a different provider.
611
- One adapter object can have only one live handle for a given branch ID in a process.
612
- Close the prior handle before resuming it, and use the run lease to prevent distributed controllers from owning the same durable run at once.
613
-
614
- Use `runAgentMemoryExperiment()` to compare known providers or configurations across ordered histories.
615
- `buildAgentMemorySequencesFromBenchmarkCases()` feeds existing LoCoMo, LongMemEval, GroupMemBench, or first-party memory cases into the same runner without changing their event order.
616
- Use `runAgentMemoryImprovement()` when a proposer should search configurations, measure them on training histories, compare the winner with the baseline on fresh histories, and activate it only after a statistically supported win.
617
-
618
- ```ts
619
- const result = await runAgentMemoryImprovement({
620
- experimentId: 'support-memory-v3',
621
- trainSequences,
622
- holdoutSequences,
623
- seeds: [
624
- {
625
- config: currentConfig,
626
- track: 'conservative',
627
- proposer: 'conservative',
628
- vision: 'Improve recall without increasing stale-memory reuse.',
629
- },
630
- {
631
- config: graphConfig,
632
- track: 'graph',
633
- proposer: 'graph',
634
- vision: 'Improve temporal and multi-actor reasoning.',
635
- },
636
- ],
637
- proposer: configProposer,
638
- proposers: {
639
- conservative: conservativeConfigProposer,
640
- graph: graphConfigProposer,
641
- },
642
- improvementRef: 'memory-stack@8f2c1a7',
643
- budget: { maxSteps: 12 },
644
- maxTotalCostUsd: 25,
645
- candidateConcurrency: 4,
646
- sequenceConcurrency: 8,
647
- createCandidate: ({ config }) => buildMemoryCandidate(config),
648
- executeStep: ({ memory, step, context }) =>
649
- runProfileStep({ memory, step, context }),
650
- executeStepRef: 'support-profile@8f2c1a7',
651
- runDir: '.agent-knowledge/memory-runs/support-v3',
652
- activation: {
653
- ref: 'config-store:support-memory:v1',
654
- readCurrent: () => configStore.read('support-memory'),
655
- compareAndSet: ({
656
- activationId,
657
- expectedSurfaceHash,
658
- config,
659
- surfaceHash,
660
- }) => configStore.compareAndSet({
661
- key: 'support-memory',
662
- operationId: activationId,
663
- expectedHash: expectedSurfaceHash,
664
- nextHash: surfaceHash,
665
- value: config,
666
- }),
667
- },
668
- })
669
- ```
670
-
671
- `candidateConcurrency` controls how many configurations are measured at once.
672
- `sequenceConcurrency` controls how many independent histories run at once.
673
- Every experiment candidate must set `ref` to a version or commit that changes whenever its provider configuration or adapter behavior changes.
674
- The first seed is always the deployed baseline.
675
- Each seed starts an independent track with its own objective and optional proposer, and a branch inherits its parent track's proposer unless the governor chooses another one.
676
- Candidate factories should perform local construction only when possible.
677
- When a dedicated instance must be provisioned or reconnected, call `markExternalCall()` immediately before that action.
678
- Set each candidate's `externalCostUsdPerSequence` to a conservative memory-provider charge; it is counted once an adapter method or a marked factory action attempts provider work, while a local factory failure costs zero.
679
- Set `externalRecoveryCostUsdPerAttempt` when reconnecting and deleting an interrupted history adds a separate provider charge.
680
- Recovery uses the same durable run-wide cost account as candidate execution, proposers, profile steps, and the governor, so a resumed run cannot exceed `maxTotalCostUsd` by treating cleanup as free work.
681
- `runAgentMemoryExperiment()` reports all attempt and recovery spend in `totalCostUsd`, with retired-candidate cleanup separated as `unrankedRecoveryCostUsd`.
682
- Cleanup completion is journaled before a paid-call receipt.
683
- If a process exits between those writes, resume charges the reserved maximum without repeating provider cleanup and continues only after the account is complete.
684
- The default `maxTotalCostUsd` is zero, so model and paid provider calls require an explicit limit.
685
- Model calls inside `executeStep` should use `context.cost.runPaidCall()` so `maxTotalCostUsd` counts both sources before starting more work.
686
- The same budget is supplied to named proposers as `context.costLedger` and to the governor as `context.costLedger`; the standard `agent-eval` proposers charge their model calls there.
687
- Steps inside one history remain ordered, and writes from one agent remain ordered while independent agents can write concurrently.
688
- Runtime callbacks may write only to scopes declared on a step, write, probe, or `sequence.cleanupScopes`; this lets an interrupted retry clear every actor and session it could have touched.
689
- Search never changes the live configuration.
690
- Only `activation.compareAndSet` can activate the fresh-history winner.
691
- The activation target must provide an exact current read and an atomic compare-and-set from the measured baseline to the measured winner.
692
- The durable activation journal is written before that operation.
693
- After a crash, the runner reads the live configuration and records the completed activation without applying it twice.
694
- Change `activation.ref` whenever the target or activation behavior changes.
695
- The default filesystem path permits one controller and many parallel workers, persists candidate history, and resumes without adding more than `maxSteps` candidate nodes.
696
- Custom storage must set `controllerMode: 'process-local'` when all controllers share one process, or provide `acquireRunLease` for distributed controllers.
697
- Independent workers still use `candidateConcurrency` and `sequenceConcurrency`.
698
- Every measured cell receives a fresh provider branch ID for that execution attempt.
699
- This prevents a late asynchronous write from a crashed worker from entering a retried cell.
700
- `experimentRunId` keeps the logical experiment identity stable across workers without reusing physical branch state.
701
- Timed-out histories enter bounded branch cleanup before `runAgentMemoryExperiment()` returns.
702
- The run waits for cleanup to succeed or for `cleanupTimeoutMs` to expire, and either cleanup failure leaves the attempt available for recovery.
703
- The runner appends a `started` event before adapter creation and a `cleaned` event only after provider cleanup and disposal succeed.
704
- On resume it recovers every unfinished attempt before starting new cells.
705
- Independent unfinished attempts recover up to `maxConcurrency` at once, and no new cell starts until every cleanup succeeds.
706
- Recovery refuses more than `maxRecoveryAttempts`, which defaults to 1000.
707
- Recovery also refuses a fourth failed cleanup for the same attempt by default.
708
- Set `maxRecoveryRetriesPerAttempt` to change that limit; its durable retry record is returned as `recoveryLogPath`.
709
- `runAgentMemoryImprovement()` forwards the same option to every training and fresh-history experiment.
710
- Pass retired implementations through `recoveryCandidates` until their recorded attempts are cleaned.
711
- `createAdapter` receives `purpose: 'recovery'` during that pass so a dedicated-instance candidate can reconnect to the existing branch instead of provisioning another one.
712
- It may return `null` during recovery when the recorded attempt never created external state.
713
- Hosted Mem0 and Graphiti recovery waits for their configured ingestion timeout before deletion so an accepted asynchronous write can become visible first.
714
- That visibility wait is bounded by `cleanupTimeoutMs`; a longer required delay fails before new provider work starts.
715
- The default filesystem storage survives process restarts and uses an OS lock.
716
- Custom durable storage must implement atomic `append`; process-local custom storage opts in with `controllerMode: 'process-local'`, and distributed controllers provide `acquireRunLease`.
717
- Provider SDK calls should also have their own network timeout because a caller-side cleanup timeout cannot cancel an arbitrary third-party promise.
718
- Candidate factories receive an `AbortSignal` and must pass it into provider connection or provisioning calls.
719
- The persisted run identity includes `budget.maxSteps`; start a new `runDir` to enlarge a completed search instead of mutating its history.
720
- Promotion is blocked when a configured critical dimension is missing or incomplete on either fresh-history arm.
721
- Improvement runs clear each scoped branch before and after a measured history by default, including timeout and thrown-error cleanup.
722
- Set `cleanupBranches: false` only when the candidate creates and disposes a physically isolated provider instance per cell.
723
- The current Neo4j SDK has no complete all-memory clear operation, so a Neo4j improvement run must use disposable per-cell service instances with `cleanupBranches: false` and `disposeAdapter`; a persistent shared workspace is rejected because failed retries could read partial state.
724
- If recovery cannot confirm deletion, the resumed run stops before launching new provider work and leaves the attempt active for another retry.
725
-
726
- The package stays independent of `agent-runtime`.
727
- The `executeStep` callback is where a runtime profile, coding agent, research agent, or deterministic worker uses the branch.
728
- This keeps provider and measurement code reusable while the profile owns what agents do and what they choose to remember.
729
-
730
- The Neo4j adapter accepts the real `@neo4j-labs/agent-memory` client and rejects unsafe branch reuse unless `branchId` identifies caller-provisioned isolated state.
731
- The Mem0 adapter typechecks against `mem0ai` hosted and open-source clients and accepts only the current hosted SDK response shape.
732
- The Graphiti adapter calls the official MCP tools and waits for queued episodes to become visible before evaluating them by default.
733
- Hosted Mem0 and Graphiti visible writes are safe only in `lifetime: 'attempt'` branches because their writes may outlive a worker process.
734
- `runAgentMemoryExperiment()` creates those fresh attempt branches automatically.
735
- Attempt snapshots are audit and fork inputs, not restart points; use `forkAgentMemoryBranchSnapshot()` to replay one into a fresh branch.
736
- Mem0 OSS and caller-provisioned disposable instances can use resumable branches.
737
- Graphiti `consistency: 'queued'` is available only for direct ingestion and is rejected by branch execution.
738
-
739
- ## Research Loop
740
-
741
- Use `runKnowledgeResearchLoop()` when an agent is acting as a researcher or
742
- librarian. Keep the loop small: the package handles deterministic mechanics;
743
- your agent handles judgment.
744
-
745
- ```ts
746
- import {
747
- defineReadinessSpec,
748
- runKnowledgeResearchLoop,
749
- } from '@tangle-network/agent-knowledge'
750
-
751
- await runKnowledgeResearchLoop({
752
- root: './kb',
753
- goal: 'Build a grounded onboarding wiki for billing support',
754
- readinessSpecs: [defineReadinessSpec({
755
- id: 'refund-policy',
756
- description: 'Refund policy grounding',
757
- query: 'refund policy customer request',
758
- requiredFor: ['support-agent'],
759
- })],
760
- async step({ iteration, index, readiness }) {
761
- // Call your researcher/LLM/browser/connector workflow here.
762
- if (iteration > 1 && readiness?.report.blockingMissingRequirements.length === 0) {
763
- return { done: true, notes: 'ready for eval' }
764
- }
765
- return {
766
- sourceTexts: [{
767
- uri: 'research://refund-policy',
768
- title: 'Refund Policy Source',
769
- text: 'Source text gathered by the researcher.',
770
- }],
771
- proposalText: [
772
- '---FILE: knowledge/support/refund-policy.md---',
773
- '---',
774
- 'id: refund-policy',
775
- 'title: Refund Policy',
776
- '---',
777
- '# Refund Policy',
778
- 'Grounded summary written by the researcher.',
779
- '---END FILE---',
780
- ].join('\n'),
781
- }
782
- },
783
- })
784
- ```
785
-
786
- This is intentionally not a crawler, prompt framework, or agent. It is the
787
- repeatable shell around one.
788
-
789
- For full `agent-eval` control-loop integration, use
790
- `createKnowledgeControlLoopAdapter()` and provide `decide` yourself:
791
-
792
- ```ts
793
- import { runAgentControlLoop } from '@tangle-network/agent-eval'
794
- import { createKnowledgeControlLoopAdapter } from '@tangle-network/agent-knowledge'
795
-
796
- const adapter = createKnowledgeControlLoopAdapter({
797
- root: './kb',
798
- goal: 'Maintain the billing support wiki',
799
- readinessSpecs,
800
- })
801
-
802
- await runAgentControlLoop({
803
- ...adapter,
804
- async decide({ state, evals }) {
805
- if (state.previousSteps.length > 0 && evals.every((e) => e.passed)) {
806
- return { type: 'stop', pass: true, reason: 'knowledge ready' }
807
- }
808
- const proposal = await proposerAgent(state)
809
- const review = await reviewerAgent({ ...state, proposal })
810
- return {
811
- type: 'continue',
812
- reason: review.summary,
813
- action: driverPolicy({ proposal, review }),
814
- }
815
- },
816
- })
817
- ```
818
-
819
- ## Verified research loop
820
-
821
- `runVerifiedResearchLoop()` coordinates a worker and reviewer over one knowledge base.
822
- The worker discovers sources and proposes pages for open gaps.
823
- The reviewer checks each source before admission and can run its own gap-filling pass with `driverResearches: true`.
824
- Callers provide both roles and any credentials.
825
- The loop owns indexing, write-block application, readiness scoring, and stopping when no blocking gap remains.
826
-
827
- An equal-compute comparison across 9 ML topics using `glm-5.2` is documented in
828
- [docs/two-agent-research-ab.md](docs/two-agent-research-ab.md).
829
- The two-agent loop admitted about 2.33 fewer sources per topic at identical coverage.
830
- Most of that difference came from de-duplication rather than relevance filtering.
831
- The document includes limitations and reproduction steps.
832
-
833
- ```ts
834
- import {
835
- defineReadinessSpec,
836
- runVerifiedResearchLoop,
837
- } from '@tangle-network/agent-knowledge'
838
-
839
- await runVerifiedResearchLoop({
840
- root: './kb',
841
- goal: 'Build a grounded onboarding wiki for billing support',
842
- readinessSpecs: [defineReadinessSpec({
843
- id: 'refund-policy',
844
- description: 'Refund policy grounding',
845
- query: 'refund policy customer request',
846
- requiredFor: ['support-agent'],
847
- })],
848
- // The worker researches the current gaps.
849
- async worker({ gaps, index }) {
850
- return {
851
- sources: [/* AddSourceTextInput for the open gaps */],
852
- proposalText: '/* ---FILE: knowledge/…--- write-protocol blocks */',
853
- }
854
- },
855
- // The reviewer checks each candidate source before admission.
856
- driver: {
857
- verifySource(source, { gaps }) {
858
- return source.uri ? { accept: true } : { accept: false, reason: 'no uri' }
859
- },
860
- },
861
- })
862
- ```
863
-
864
- ## Runtime integration
865
-
866
- `agent-knowledge` owns knowledge state and measurement.
867
- It deliberately does not own an agent runner.
868
- Live agent orchestration belongs in `@tangle-network/agent-runtime`.
869
- Use the one-call runtime job when you want agents, candidate workspaces, readiness checks, exact candidate handoff, and spend measurement wired together:
870
-
871
- ```ts
872
- import { runKnowledgeImprovementJob } from '@tangle-network/agent-runtime/knowledge'
873
-
874
- await runKnowledgeImprovementJob({
875
- root: './kb',
876
- goal: 'Build a grounded onboarding wiki for billing support',
877
- readinessSpecs,
878
- budget: { maxIterations: 8, maxTokens: 120_000, maxUsd: 10 },
879
- backend,
880
- })
881
- ```
882
-
883
- Use `improveKnowledgeBase` directly when your product already owns the agent runner and only needs the pure KB mechanics:
142
+ `updateCandidate` and `compareOnProductTasks` are application callbacks.
143
+ The update can come from a research agent, a coding agent, a source connector, or deterministic code.
144
+ The comparison must return the product measures that decide whether the change helped.
884
145
 
885
- ```ts
886
- import { improveKnowledgeBase } from '@tangle-network/agent-knowledge'
887
-
888
- await improveKnowledgeBase({
889
- root: './kb',
890
- goal: 'Build a grounded onboarding wiki for billing support',
891
- readinessSpecs,
892
- updateKnowledge: async ({ goal, findings }) => {
893
- // Call your agent runner here, then apply source-backed write blocks.
894
- return {
895
- applied: true,
896
- summary: `updated KB for ${goal} with ${findings.length} finding(s)`,
897
- }
898
- },
899
- })
900
- ```
146
+ Use the same `runId` and `implementationRef` to resume an interrupted run.
147
+ Change `implementationRef` whenever callbacks, evaluation policy, models, indexes, or external configuration change.
148
+ Reusing a run ID with a different implementation reference fails before cached work or callbacks run.
149
+ Different run IDs create separate candidate workspaces, so workers can explore in parallel.
150
+ Promotion checks the original base hash and rejects a stale candidate instead of replacing newer work.
151
+ Candidate retries use `evaluateDevelopment` when provided, otherwise they use deterministic validation, readiness, and KB quality checks.
152
+ Development evaluation must use only train or selection data.
153
+ The configured `evaluate` callback and final RAG phases run once, on the first candidate that passes those development checks.
154
+ A failed final evaluation ends the run instead of selecting another candidate against final data.
901
155
 
902
- This keeps the dependency graph acyclic: `agent-knowledge` depends on `agent-eval`; agent runners depend on `agent-knowledge`, not the reverse.
156
+ Candidate promotion currently requires Linux because it relies on Linux directory descriptors for exact file identity.
903
157
 
904
- Validator scoring (default; overridable):
158
+ ## Evaluate and improve RAG
905
159
 
906
- ```
907
- score = 0.4 · citation_density
908
- + 0.2 · source_diversity
909
- + 0.2 · recency_match
910
- + 0.2 · gap_coverage
911
- ```
160
+ Use the narrowest API that matches the job:
912
161
 
913
- The output preserves agent intelligence: `items`, `citations`,
914
- `proposedWrites` are typed; `gaps`, `notes`, and any extras the agent
915
- emitted land in `raw` rather than getting dropped.
916
-
917
- ## Pluggable Knowledge Sources
918
-
919
- Static knowledge rots. Authorities like Cornell LII, the IRS, and state
920
- Secretaries of State change without warning. A ruling vacates an FTC
921
- non-compete rule, a CFR section renumbers, a state replaces Beverly-Killea
922
- with RULLCA. The `@tangle-network/agent-knowledge/sources` subpath ships
923
- three primitives that bridge "live authority" → "eval re-runs":
924
-
925
- - `KnowledgeSource`: pluggable contract (`fetch(opts) → KnowledgeFragment[]`).
926
- Every fragment carries `provenance` (URL, source-attested timestamp,
927
- jurisdiction, and fetch/extraction status) and `dimensionHints` (which eval
928
- dimensions a change in this fragment should re-score). The `verifiable`
929
- flag rejects failed, blocked, or malformed responses; it does not
930
- cryptographically authenticate the publisher.
931
- - `KnowledgeFreshnessStore`: per-`(workspaceId, sourceId)` last-refresh
932
- tracker. The package ships a filesystem implementation and
933
- `createD1FreshnessStoreStub(adapter)`, a complete bridge for an
934
- application-owned D1, PostgreSQL, SQLite, or equivalent adapter.
935
- - `detectChanges(prev, next)`: diffs two fragment snapshots, emits
936
- `KnowledgeChange[]` tagged with the affected eval dimensions so a cron
937
- scheduler knows exactly which campaigns to re-run.
938
-
939
- Three concrete sources ship in-package:
162
+ | API | What it does |
163
+ |---|---|
164
+ | `runRetrievalImprovementLoop` | Runs one complete `OptimizationMethod` over serialized retrieval configuration. |
165
+ | `runRagOptimization` | Optimizes retrieval and answer behavior as one serialized RAG configuration. |
166
+ | `optimizeKnowledgeBasePolicy` | Optimizes a KB maintenance policy, then applies only the selected policy to an isolated candidate. |
167
+ | `scoreKnowledgeBaseIndex` | Measures KB structure, citations, source freshness, and configured quality thresholds. |
168
+ | `createRagAnswerQualityHook` | Adapts answer-quality checks such as support, relevance, citations, and abstention. |
169
+ | `runRagKnowledgeImprovementLoop` | Connects retrieval tuning, gap diagnosis, source acquisition, KB updates, answer checks, and a promotion decision. |
170
+ | `improveKnowledgeBase` | Adds resumable state, isolated candidates, exact promotion, and conflict detection around that process. |
940
171
 
941
172
  ```ts
173
+ import type {
174
+ OptimizationMethod,
175
+ } from '@tangle-network/agent-eval/campaign'
942
176
  import {
943
- createCornellLiiSource,
944
- createIrsPublicationsSource,
945
- createStateSosSource,
946
- createFileSystemFreshnessStore,
947
- detectChanges,
948
- type KnowledgeChange,
949
- type KnowledgeFragment,
177
+ runRetrievalImprovementLoop,
178
+ type RetrievalEvalArtifact,
179
+ type RetrievalEvalScenario,
950
180
  } from '@tangle-network/agent-knowledge'
951
181
 
952
- const sources = [
953
- // Federal statutes + Wex encyclopedia from law.cornell.edu.
954
- createCornellLiiSource({
955
- selectors: [
956
- { kind: 'uscode', path: '18/1836' }, // DTSA
957
- { kind: 'wex', path: 'restraint_of_trade', dimensionHints: ['jurisdictional_accuracy'] },
958
- ],
959
- }),
960
- // IRS publications index + named publications + revenue procedures.
961
- createIrsPublicationsSource({
962
- publications: ['p15', 'p17', 'p463'],
963
- revenueProcedures: [],
964
- }),
965
- // Generic state SOS adapter: one config per state you need tracked.
966
- createStateSosSource({
967
- state: 'CA',
968
- baseUrl: 'https://www.sos.ca.gov',
969
- entities: [{
970
- id: 'business-entities-forms',
971
- path: '/business-programs/business-entities/forms',
972
- title: 'CA Business Entities Forms',
973
- selector: { kind: 'whole' },
974
- }],
975
- }),
976
- ]
977
-
978
- const freshness = createFileSystemFreshnessStore({ root: './kb' })
979
-
980
- // Worked example: Cornell LII updates the Wex `restraint_of_trade` entry
981
- // to reflect Ryan-LLC v. FTC. The cron tick below detects the change,
982
- // extracts the `jurisdictional_accuracy` dimension hint, and hands it to
983
- // the eval scheduler which re-runs only the campaigns tagged with that
984
- // dimension.
985
- async function tick({ workspaceId, prevSnapshots }: {
986
- workspaceId: string
987
- prevSnapshots: Record<string, KnowledgeFragment[]>
988
- }): Promise<KnowledgeChange[]> {
989
- const allChanges: KnowledgeChange[] = []
990
- for (const source of sources) {
991
- const stale = await freshness.stale({
992
- workspaceId,
993
- sourceId: source.id,
994
- ttlMs: 24 * 60 * 60 * 1000,
995
- })
996
- if (!stale) continue
997
-
998
- const next = await source.fetch({ cacheDir: './.agent-knowledge/http-cache' })
999
- const prev = prevSnapshots[source.id] ?? []
1000
- const { changes } = detectChanges(prev, next)
1001
- allChanges.push(...changes)
1002
-
1003
- await freshness.mark({ workspaceId, sourceId: source.id, when: new Date() })
1004
- prevSnapshots[source.id] = next
1005
- }
1006
- return allChanges
182
+ async function tuneRetrieval(
183
+ method: OptimizationMethod<RetrievalEvalScenario, RetrievalEvalArtifact>,
184
+ ) {
185
+ return runRetrievalImprovementLoop({
186
+ executionRef: 'git:0123456789abcdef0123456789abcdef01234567',
187
+ baseline: { k: 5, reranker: false },
188
+ method,
189
+ trainScenarios,
190
+ selectionScenarios,
191
+ finalScenarios,
192
+ retrieve: ({ scenario, config, k }) => search(scenario.query, { ...config, k }),
193
+ runDir: 'refund-retrieval',
194
+ expectUsage: 'off', // Local search does not make a billable model call.
195
+ })
1007
196
  }
1008
197
  ```
1009
198
 
1010
- Polite-by-default: every HTTP fetch carries the package User-Agent, is
1011
- throttled to 1 req/sec/origin, caches successful responses to disk, and
1012
- marks `verifiable: false` on block pages / 4xx rather than promoting
1013
- unusable content. See `src/sources/http.ts` for the checks.
199
+ The method receives train and selection cases.
200
+ `agent-eval` keeps final cases out of the search and measures the exact selected configuration on them afterward.
201
+ Every optimization call requires an explicit complete method.
202
+ Create an official method with `gepaOptimizationMethod()` or `skillOptOptimizationMethod()` from `@tangle-network/agent-eval/campaign`, or pass another complete public `OptimizationMethod`.
203
+ Reuse the run directory only with the method's compatible resume mode.
204
+ Use separate run directories to explore branches in parallel.
205
+ Optimizer-specific identity and resume settings stay on the supplied method; this package does not reinterpret them.
206
+ The supplied method owns its engine inputs and resume state.
207
+ `executionRef` owns retrieval, index, judge, model, and external-service identity.
208
+ Use `git:<40 lowercase hex>` or `sha256:<64 lowercase hex>` and change it whenever any candidate execution or scoring behavior changes.
209
+ Each candidate has one canonical serialized identity.
210
+ See the [`agent-eval` method guide](https://github.com/tangle-network/agent-eval/blob/main/docs/campaign-proposers.md) for official GEPA and SkillOpt methods.
211
+ SkillOpt is skill-only; use it here only when the serialized candidate is itself a skill.
212
+ Read reported spend from `result.comparison.totalCost` and upstream source and run identity from `result.comparison.best.provenance`.
213
+ Official external methods must report observed package identity.
214
+ Custom in-process methods have no external package identity, so their behavior must be covered by `executionRef`.
215
+ Treat `accountingComplete: false` as incomplete evidence for activation.
216
+
217
+ Retrieval and answer generation remain callbacks.
218
+ This lets the same evaluation code work with local search, vector databases, hybrid search, rerankers, and hosted RAG services.
219
+ Adaptive diagnosis, acquisition, and update callbacks finish before retrieval or RAG final scoring starts.
220
+ Only answer evaluation, the terminal promotion decision, and the returned result can observe selected configurations.
221
+ Answer-quality evidence must name at least two final scenario IDs, immutable dataset and evaluator references, non-empty finite metrics, and observed cost accounting.
222
+ Promotion also requires `answerQualityCostCeiling`.
223
+
224
+ ## Integrate memory systems
225
+
226
+ `@tangle-network/agent-knowledge/memory` defines `AgentMemoryAdapter` and adapters for Mem0, Graphiti, and Neo4j Agent Memory.
227
+ The package is not a memory database.
228
+ Install the provider you use, create its client, and pass that client to the adapter.
229
+
230
+ The memory APIs support scoped reads and writes, isolated branches, ordered histories, independent train, selection, and final comparisons, and adapter experiments.
231
+ Use them to compare a provider against no memory or another provider on the same tasks before changing production behavior.
232
+ `runAgentMemoryImprovement` accepts a complete `OptimizationMethod`, evaluates each serialized configuration in an isolated provider branch, and activates only a winner that passes a separate final comparison.
233
+ Set `implementationRef` to `git:<40 lowercase hex>` or `sha256:<64 lowercase hex>` covering the installed implementation, method configuration, candidate construction, execution behavior, and external configuration so incompatible state cannot resume.
234
+ The run records one immutable candidate reference for each memory configuration and refuses cached results if that reference changes.
235
+ Each improvement candidate declares a maximum for one sequence and one recovery attempt.
236
+ The adapter must enforce that maximum with its provider before starting external work.
237
+ The adapter callback must call `recordExternalCost()` with each observed charge.
238
+ Positive external work without a receipt is recorded as incomplete cost accounting, not as the configured maximum.
239
+ Use `0` only for a free local path.
240
+ Paid memory improvement defaults to a zero-dollar total limit; set `maxTotalCostUsd` and `maximumEvaluationCostUsd` before enabling paid work.
241
+
242
+ ## Run benchmarks
243
+
244
+ `@tangle-network/agent-knowledge/benchmarks` provides common case and report types for:
245
+
246
+ - retrieval from qrels datasets
247
+ - RAG answer quality and unsupported claims
248
+ - knowledge-base improvement
249
+ - multi-turn memory behavior and provider comparison
250
+
251
+ The bundled industry cases are small smoke checks for adapter wiring.
252
+ They are not copies of full BEIR, MTEB, MS MARCO, LongMemEval, or other external datasets, and they do not produce a public leaderboard by themselves.
253
+ Import the real dataset rows or qrels and run them through `runKnowledgeBenchmarkSuite` for benchmark results.
254
+
255
+ ## Package boundaries
256
+
257
+ | Import | Contents |
258
+ |---|---|
259
+ | `@tangle-network/agent-knowledge` | KB files, indexes, search, validation, research callbacks, RAG evaluation, and candidate improvement |
260
+ | `@tangle-network/agent-knowledge/memory` | Memory contracts, provider adapters, branches, and experiments |
261
+ | `@tangle-network/agent-knowledge/sources` | HTTP and authority-specific source adapters |
262
+ | `@tangle-network/agent-knowledge/benchmarks` | Benchmark cases, qrels import, execution, and reports |
263
+ | `@tangle-network/agent-knowledge/viz` | Dependency-free graph analysis helpers |
264
+
265
+ Use the `agent-knowledge` binary for CLI commands rather than importing its CLI module.
266
+
267
+ The package intentionally does not own model selection, prompts, browser access, agent scheduling, or a vector database.
268
+ Those choices stay in the application or in `@tangle-network/agent-runtime`.
269
+
270
+ ## More detail
271
+
272
+ - [Architecture and data model](docs/architecture.md)
273
+ - [Two-agent research comparison](docs/two-agent-research-ab.md)
274
+ - [Changelog](CHANGELOG.md)
275
+
276
+ ## License
277
+
278
+ MIT