Source-grounded, eval-gated knowledge growth primitives for agents.
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.
- Install
- Start here — pick CLI vs programmatic
- CLI —
init→source-add→index→search→lint - Design — the invariants (immutable sources, cited claims, deterministic graph)
- Benchmark harness — BEIR/MTEB/qrels, RAG answer, hallucination, KB-improvement cases
- Agent-Eval integration — retrieval eval + readiness bundles + release reports
- Memory adapters — generic memory contract + Neo4j Agent Memory bridge
- Research loop —
runKnowledgeResearchLoop+ control-loop adapter - Researcher profile — sandbox
AgentProfileforrunLoop - Pluggable knowledge sources — live authorities → eval re-runs
pnpm add @tangle-network/agent-knowledge @tangle-network/agent-evalTwo ways in, depending on what you're doing:
- Author / inspect a KB by hand → the CLI (
init→source-add→index→search→lint). Fastest way to see the shape on disk. - Drive it from an agent → pick the primitive by intent:
- "Does the agent have enough context to run?" →
buildEvalKnowledgeBundle(block / ask / acquire before execution). - "Grow the KB as a researcher" →
runKnowledgeResearchLoop(deterministic mechanics; your agent owns judgment),runTwoAgentResearchLoop(researcher proposes, verifier checks + fills gaps, offline), or the sandbox researcher profile forrunLoop. - "Spawn one researcher per sub-topic and stop when the KB is ready" →
runResearchSupervisor(a supervisor brain sizes the topology over aScope; LIVE, needs creds). - "Tune retrieval for a knowledge base" →
runRetrievalImprovementLoopin the Agent-Eval integration section. - "Run an operator-grade KB improvement cycle" →
improveKnowledgeBasein the Agent-Eval integration section. 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. - "Expose the lower-level RAG lifecycle phases" →
runRagKnowledgeImprovementLoopin the Agent-Eval integration section. It exposes retrieval tuning, gap diagnosis, knowledge acquisition/update, answer-quality checks, and promotion as one typed lifecycle. - "Evaluate RAG answers or a wiki/KB" →
ragAnswerQualityJudge,createRagAnswerQualityHook, andscoreKnowledgeBaseIndexin the Agent-Eval integration section. - "Run standard RAG/KB benchmarks" →
runKnowledgeBenchmarkSuitein the Benchmark harness section. - "Does this candidate KB actually improve task success?" → run an agent-eval improvement loop over KB variants, then
knowledgeReleaseReportfor the promotion decision. - "Keep live authorities fresh" → pluggable sources +
detectChanges→ eval re-runs.
- "Does the agent have enough context to run?" →
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.
agent-knowledge init --root .
agent-knowledge source-add ./docs/spec.md --root .
agent-knowledge sources --root .
agent-knowledge apply-write-blocks ./proposal.txt --root .
agent-knowledge index --root .
agent-knowledge search "portfolio risk" --root .
agent-knowledge inspect --root .
agent-knowledge explain knowledge/concepts/risk.md --root .
agent-knowledge graph --root . --format json
agent-knowledge lint --root .
agent-knowledge validate --strict --root .
agent-knowledge export --root . --format json
agent-knowledge viz --root .The default layout is:
raw/
sources/
knowledge/
index.md # scaffold: human-navigation only, excluded from the page index
log.md # scaffold: human-navigation only, excluded from the page index
.agent-knowledge/
sources.json
index.jsoninitKnowledgeBase writes knowledge/index.md and knowledge/log.md for
authors to curate by hand. They are deliberately excluded from
buildKnowledgeIndex / searchKnowledge so they do not inflate page counts
or pollute search hits. Any nested <dir>/index.md or <dir>/log.md is
treated the same way. The shared predicate is isScaffoldPath, exported
from @tangle-network/agent-knowledge.
- Raw sources are immutable evidence.
- Generated knowledge is editable but validated.
- Claims should cite source records when promoted.
- Lint fails on pages that cite unknown source IDs.
- Text sources get deterministic anchors (
all,l1,l51, ...) for precise citations like[^src_id#all]. - Agent write proposals can be safely applied with
apply-write-blocks. KbStorekeeps storage consumer-owned; useMemoryKbStore,FileSystemKbStore, or implement D1 in the app.- Discovery uses worker/dispatcher contracts, with a local dispatcher for dev and tests.
runKnowledgeResearchLoop()provides thin loop mechanics for researcher agents: ingest sources, apply safe write blocks, rebuild the index, lint/validate, score readiness, and return a transcript. The agent still decides what to research, what to write, and when the wiki is good enough.createKnowledgeControlLoopAdapter()maps those mechanics intoagent-eval'srunAgentControlLoop()so products can plug in their own proposer, reviewer, and driver policies.runRagKnowledgeImprovementLoop()coordinates the whole RAG improvement lifecycle. Retrieval tuning, diagnosis, acquisition, KB update, answer-quality eval, and promotion are separate typed phases so products can plug in browser agents, coding agents, connectors, or deterministic policies without this package hardcoding an agent runner.improveKnowledgeBase()wraps that lifecycle with durable candidate state, a per-run lock, resume support, isolated candidate workspaces, KB quality scoring, and conflict-safe promotion. Use it when running agents in loops against a real KB rather than only exposing phase hooks.- RAG answer evaluation follows the common open-source shape used by Ragas, DeepEval, TruLens, and RAGChecker: context quality, answer relevance, support/faithfulness, citations, abstention, and failure diagnosis. External tools stay pluggable via score normalization and row exporters.
- Zod schemas define the stable wire shape.
- Graph/search/lint are deterministic and fast.
searchKnowledgereturns hits with three score fields.scoreandrrfScoreare the raw reciprocal-rank-fusion value (typically 0.01–0.05); use them when intent matters or when fusing across queries.normalizedScoreis the same value scaled into [0, 1] relative to the top hit in this result set (top hit = 1, others = score / topScore) — use it when comparing against natural confidence thresholds. The normalization is within-set ranking, not a cross-query absolute confidence.- Release confidence uses
@tangle-network/agent-evalrelease gates (evaluateReleaseConfidence) instead of reimplementing them. - Retrieval eval turns retrieval/RAG configs into
agent-evalsurfaces, auto-searches candidate configs, and scores them against page, source, source-anchor, or source-span targets. buildEvalKnowledgeBundle()maps wiki/search evidence intoagent-evalKnowledgeRequirement,KnowledgeBundle, andKnowledgeReadinessReportcontracts so control loops can block, ask, or acquire data before running an agent.
The /viz subpath exports graph insight helpers without UI dependencies.
The /memory subpath exports an optional memory adapter contract. Use it to
bridge episodic or graph-native memory systems into the same source-grounded
readiness/eval machinery without making agent-knowledge own the database.
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.
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.
import {
buildIndustryRagBenchmarkSmokeCases,
buildRetrievalBenchmarkCasesFromQrels,
parseKnowledgeBenchmarkQrels,
respondToIndustryRagBenchmarkSmokeCase,
runKnowledgeBenchmarkSuite,
} from '@tangle-network/agent-knowledge/benchmarks'
await runKnowledgeBenchmarkSuite({
cases: buildIndustryRagBenchmarkSmokeCases(),
runDir: '.agent-knowledge/benchmark-runs/industry-smoke',
respond: respondToIndustryRagBenchmarkSmokeCase,
})
const cases = buildRetrievalBenchmarkCasesFromQrels({
benchmarkId: 'beir/nfcorpus',
family: 'beir',
queries: [{ id: 'q1', text: 'aspirin heart attack prevention', split: 'holdout' }],
qrels: parseKnowledgeBenchmarkQrels('q1 0 src-aspirin 1'),
targetKind: 'source',
k: 10,
})
const result = await runKnowledgeBenchmarkSuite({
cases,
runDir: '.agent-knowledge/benchmark-runs/beir-nfcorpus-smoke',
respond: async ({ case: testCase }) => {
if (testCase.taskKind !== 'retrieval') return { hits: [] }
const hits = await retrieveFromYourKb(testCase.query)
return { hits, costUsd: 0.001 }
},
})
console.log(result.report.score.mean)Use buildRetrievalBenchmarkCasesFromQrels() for qrels-backed retrieval datasets.
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.
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.
Use taskKind: 'kb-improvement' when the artifact is candidate KB text produced by improveKnowledgeBase().
Use ragAnswerQualityJudge or createRagAnswerQualityHook when the product already has answer traces and needs SOTA-style RAG scoring without rebuilding metrics.
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.
import {
createRagAnswerQualityHook,
scoreKnowledgeBaseIndex,
} from '@tangle-network/agent-knowledge'
const evaluateAnswers = createRagAnswerQualityHook({
scenarios: answerScenarios,
run: async (scenario) => runRagAnswerTrace(scenario),
externalEvaluator: async ({ scenario, artifact }) => runRagasOrDeepEval({
input: scenario.query,
output: artifact.answer,
contexts: artifact.contexts,
}),
})
const answerQuality = await evaluateAnswers()
const kbQuality = scoreKnowledgeBaseIndex(index, {
strict: true,
minCitationRate: 0.8,
maxStaleSourceRate: 0.02,
})Use runRagKnowledgeImprovementLoop when the product question is broader than retrieval:
can the system find the gaps, gather or update knowledge, prove generated answers still behave, and decide whether to promote?
agent-knowledge owns the knowledge/eval contract; the caller supplies the research, coding, connector, and answer-eval hooks.
import { runRagKnowledgeImprovementLoop } from '@tangle-network/agent-knowledge'
const result = await runRagKnowledgeImprovementLoop({
goal: 'Improve the support RAG KB',
retrieval: {
baseline: { k: 5, hybrid: false },
scenarios: trainRetrievalScenarios,
holdoutScenarios,
index,
searchSpace: { k: [5, 10, 20], hybrid: [false, true] },
targetRecall: 0.9,
},
diagnose: async ({ retrieval }) => diagnoseRagGaps(retrieval),
acquireKnowledge: async ({ findings }) => researchMissingSources(findings),
knowledgeResearch: { root: './kb' },
evaluateAnswers,
promote: async ({ retrieval, answerQuality }) =>
decidePromotion({ retrieval, answerQuality }),
requiredPhases: ['retrieval-tuning', 'knowledge-update', 'answer-quality', 'promotion'],
})
console.log(result.promotion)Use improveKnowledgeBase when a program should own the candidate workspace and promotion mechanics.
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.
import { improveKnowledgeBase } from '@tangle-network/agent-knowledge'
const result = await improveKnowledgeBase({
root: './kb',
goal: 'Improve support refund-policy knowledge',
readinessSpecs,
retrieval: {
baseline: { k: 5 },
scenarios: trainRetrievalScenarios,
holdoutScenarios,
searchSpace: { k: [5, 10, 20] },
targetRecall: 0.9,
},
step: async ({ readiness }) => runResearchAgent({ missing: readiness?.report }),
evaluateAnswers,
requiredPhases: ['knowledge-update', 'retrieval-tuning', 'answer-quality'],
})
console.log(result.promoted, result.candidate?.evaluation)Pass promote: false to leave the candidate workspace open for another agent or a human edit.
Calling improveKnowledgeBase again with the same runId re-evaluates that candidate and promotes it only if the original live KB hash still matches.
If a required phase is missing its hook, the loop throws. That keeps the public API from reporting a fake “RAG improved” result when the caller only wired retrieval or only wired a researcher.
Use retrieval eval when the question is whether a retrieval/RAG config can find the right knowledge before an agent reasons over it.
The labels should name stable pages, source records, anchors, or source spans, not ephemeral chunk IDs.
The completion roadmap is in docs/eval/rag-eval-roadmap.md.
import { runRetrievalImprovementLoop } from '@tangle-network/agent-knowledge'
const result = await runRetrievalImprovementLoop({
baseline: { k: 5, hybrid: false, reranker: null },
scenarios: trainRetrievalScenarios,
holdoutScenarios: holdoutRetrievalScenarios,
index,
searchSpace: {
k: [5, 10, 20],
hybrid: [false, true],
reranker: [null, 'bge-reranker'],
},
targetRecall: 0.9,
deltaThreshold: 0.02,
costCeiling: 15,
runDir: '.agent-knowledge/retrieval-runs',
})
console.log(result.winnerConfig)Pass a custom retrieve function to buildRetrievalEvalDispatch when the config controls an external vector store, reranker, hybrid search service, or chunker.
The built-in fallback uses searchKnowledge over the local deterministic index.
Use buildRetrievalEvalDispatch, retrievalRecallJudge, and retrievalParameterSweepProposer directly only when you need custom agent-eval wiring.
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.
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.
Use buildEvalKnowledgeBundle() before execution when the question is whether
the agent has enough task-world context to run:
import { buildEvalKnowledgeBundle } from '@tangle-network/agent-knowledge'
const readiness = buildEvalKnowledgeBundle({
taskId: 'sdk-migration',
index,
specs: [{
id: 'repo-build-command',
description: 'Repository build and typecheck command',
query: 'build typecheck command',
requiredFor: ['coding'],
category: 'codebase_specific',
acquisitionMode: 'inspect_repo',
importance: 'blocking',
freshness: 'weekly',
sensitivity: 'public',
confidenceNeeded: 0.9,
minSources: 1,
}],
})
console.log(readiness.report.recommendedAction)Pass readiness.report to blockingKnowledgeEval() from
@tangle-network/agent-eval; use readiness.questions and
readiness.acquisitionPlans to drive UI or connector workflows.
agent-knowledge does not store operational memory itself. It defines the
contract that lets a runtime read/write memory through any backend, then turn
memory hits into SourceRecord evidence for readiness, linting, and eval gates.
import {
createNeo4jAgentMemoryAdapter,
memoryHitToSourceRecord,
} from '@tangle-network/agent-knowledge/memory'
const memory = createNeo4jAgentMemoryAdapter({ client: neo4jMemoryClient })
const context = await memory.getContext('What does this user prefer?', {
scope: { userId: 'user-123', sessionId: 'session-456' },
limit: 5,
})
const sourceRecords = context.hits.map((hit) =>
memoryHitToSourceRecord(hit, { scope: { userId: 'user-123' } }),
)The Neo4j adapter is runtime dependency-free: pass the real
@neo4j-labs/agent-memory client in products, or a fake client in tests. CI
typechecks against @neo4j-labs/agent-memory@0.4.0 and covers the published
TypeScript SDK surface: shortTerm.addMessage/searchMessages/getContext,
longTerm.addEntity/addPreference/addFact/searchEntities/searchPreferences,
and reasoning.getSimilarTraces. Generic search / getContext and
snake_case bridge-style methods remain supported for non-hosted clients.
Use runKnowledgeResearchLoop() when an agent is acting as a researcher or
librarian. Keep the loop small: the package handles deterministic mechanics;
your agent handles judgment.
import {
defineReadinessSpec,
runKnowledgeResearchLoop,
} from '@tangle-network/agent-knowledge'
await runKnowledgeResearchLoop({
root: './kb',
goal: 'Build a grounded onboarding wiki for billing support',
readinessSpecs: [defineReadinessSpec({
id: 'refund-policy',
description: 'Refund policy grounding',
query: 'refund policy customer request',
requiredFor: ['support-agent'],
})],
async step({ iteration, index, readiness }) {
// Call your researcher/LLM/browser/connector workflow here.
if (iteration > 1 && readiness?.report.blockingMissingRequirements.length === 0) {
return { done: true, notes: 'ready for eval' }
}
return {
sourceTexts: [{
uri: 'research://refund-policy',
title: 'Refund Policy Source',
text: 'Source text gathered by the researcher.',
}],
proposalText: [
'---FILE: knowledge/support/refund-policy.md---',
'---',
'id: refund-policy',
'title: Refund Policy',
'---',
'# Refund Policy',
'Grounded summary written by the researcher.',
'---END FILE---',
].join('\n'),
}
},
})This is intentionally not a crawler, prompt framework, or agent. It is the repeatable shell around one.
For full agent-eval control-loop integration, use
createKnowledgeControlLoopAdapter() and provide decide yourself:
import { runAgentControlLoop } from '@tangle-network/agent-eval'
import { createKnowledgeControlLoopAdapter } from '@tangle-network/agent-knowledge'
const adapter = createKnowledgeControlLoopAdapter({
root: './kb',
goal: 'Maintain the billing support wiki',
readinessSpecs,
})
await runAgentControlLoop({
...adapter,
async decide({ state, evals }) {
if (state.previousSteps.length > 0 && evals.every((e) => e.passed)) {
return { type: 'stop', pass: true, reason: 'knowledge ready' }
}
const proposal = await proposerAgent(state)
const review = await reviewerAgent({ ...state, proposal })
return {
type: 'continue',
reason: review.summary,
action: driverPolicy({ proposal, review }),
}
},
})runTwoAgentResearchLoop() is the offline sibling of runKnowledgeResearchLoop
with a differentiated worker/driver split over ONE knowledge base: the worker
does primary research (discovers sources, proposes pages for the open gaps); the
driver verifies each candidate source before it commits, optionally gap-fills
with its own pass (driverResearches: true), and gates on the readiness check.
Both are yours (no creds) — the loop owns the deterministic mechanics (indexing,
applying write blocks, scoring readiness) and stops once no blocking gap remains.
Does the verifying driver actually earn its keep? See
docs/two-agent-research-ab.md for an equal-compute
A/B (9 ML topics, glm-5.2): the two-agent loop admits ~2.33 fewer sources per topic
at identical coverage — though most of that win is de-duplication, not relevance
filtering. Honest caveats and how to reproduce included.
import {
defineReadinessSpec,
runTwoAgentResearchLoop,
} from '@tangle-network/agent-knowledge'
await runTwoAgentResearchLoop({
root: './kb',
goal: 'Build a grounded onboarding wiki for billing support',
readinessSpecs: [defineReadinessSpec({
id: 'refund-policy',
description: 'Refund policy grounding',
query: 'refund policy customer request',
requiredFor: ['support-agent'],
})],
// WORKER: primary research targeting `ctx.gaps`. Returns a ResearchContribution.
async worker({ gaps, index }) {
return {
sources: [/* AddSourceTextInput for the open gaps */],
proposalText: '/* ---FILE: knowledge/…--- write-protocol blocks */',
}
},
// DRIVER: verifies each candidate source before it commits, then gates.
driver: {
verifySource(source, { gaps }) {
return source.uri ? { accept: true } : { accept: false, reason: 'no uri' }
},
},
})runResearchSupervisor() is the LIVE counterpart: a supervisor brain creates the
topology dynamically — one researcher worker per sub-topic over a Scope — and
stops when the knowledge base is ready. It is a thin wrapper over supervise()
from @tangle-network/agent-runtime/loops; it builds nothing new. The worker
shape is the researcher profile, and the completion oracle
is knowledgeReadinessDeliverable (re-reads the KB from disk and runs the
readiness gate, so it stops on the real grounded state, not a worker's
self-report). Needs creds: a supervisor router brain plus a worker backend.
import { defineReadinessSpec, runResearchSupervisor } from '@tangle-network/agent-knowledge'
await runResearchSupervisor({
root: './kb',
goal: 'Build a grounded onboarding wiki for billing support',
readinessSpecs: [defineReadinessSpec({
id: 'refund-policy',
description: 'Refund policy grounding',
query: 'refund policy customer request',
requiredFor: ['support-agent'],
})],
budget: { maxIterations: 12, maxTokens: 200_000, maxUsd: 5 },
// WHERE researcher workers run — the real backend seam.
backend: {
backend: 'router', // or 'sandbox' / 'cli' / 'bridge'
routerBaseUrl: process.env.ROUTER_BASE_URL!,
routerKey: process.env.ROUTER_KEY!,
model: 'your-worker-model',
},
})@tangle-network/agent-knowledge/profiles ships a sandbox-SDK
AgentProfile preset for source-grounded research agents. Pairs with
runLoop from @tangle-network/agent-runtime/loops — the profile owns
the prompt + output adapter + validator; the kernel owns iteration,
concurrency, cost, and trace emission.
import { runLoop } from '@tangle-network/agent-runtime/loops'
import { multiHarnessResearcherFanout } from '@tangle-network/agent-knowledge/profiles'
const research = multiHarnessResearcherFanout({
harnesses: ['opencode/zai-coding-plan/glm-5.1', 'claude-code', 'codex'],
})
const result = await runLoop({
driver: research.driver,
agentRuns: research.agentRuns,
output: research.output,
validator: research.validator,
task: {
question: 'What content does cpg-founder ICP engage with on Twitter?',
knowledgeNamespace: 'cust_42',
sources: ['twitter', 'web'],
maxItems: 20,
minConfidence: 0.6,
},
ctx: { sandboxClient },
})
if (result.winner?.verdict?.valid) {
// result.winner.output.proposedWrites: KnowledgeUpdate[]
// The profile does NOT materialize. Decide whether to apply.
for (const write of result.winner.output.proposedWrites) {
// route through applyKnowledgeWriteBlocks / a KbStore put when ready
}
}Three invariants are enforced by the validator:
- Namespace isolation — every
KnowledgeItem+KnowledgeUpdatemust carrytask.knowledgeNamespace. Cross-tenant writes hard-fail. - Provenance — every item carries at least one evidence entry.
- Citation density — quotes-with-source / items >= 0.7 by default.
Validator scoring (default; overridable):
score = 0.4 · citation_density
+ 0.2 · source_diversity
+ 0.2 · recency_match
+ 0.2 · gap_coverage
The output preserves agent intelligence — items, citations,
proposedWrites are typed; gaps, notes, and any extras the agent
emitted land in raw rather than getting dropped.
Static knowledge rots. Authorities like Cornell LII, the IRS, and state
Secretaries of State change without warning — a ruling vacates an FTC
non-compete rule, a CFR section renumbers, a state replaces Beverly-Killea
with RULLCA. The @tangle-network/agent-knowledge/sources subpath ships
three primitives that bridge "live authority" → "eval re-runs":
KnowledgeSource— pluggable contract (fetch(opts) → KnowledgeFragment[]). Every fragment carriesprovenance(URL, source-attested timestamp, jurisdiction,verifiableflag) anddimensionHints(which eval dimensions a change in this fragment should re-score).KnowledgeFreshnessStore— per-(workspaceId, sourceId)last-refresh tracker. Filesystem adapter ships in-package; D1 / Postgres adapter scaffold is shipped ascreateD1FreshnessStoreStub(adapter).detectChanges(prev, next)— diffs two fragment snapshots, emitsKnowledgeChange[]tagged with the affected eval dimensions so a cron scheduler knows exactly which campaigns to re-run.
Three concrete sources ship in-package:
import {
createCornellLiiSource,
createIrsPublicationsSource,
createStateSosSource,
createFileSystemFreshnessStore,
detectChanges,
type KnowledgeChange,
type KnowledgeFragment,
} from '@tangle-network/agent-knowledge'
const sources = [
// Federal statutes + Wex encyclopedia from law.cornell.edu.
createCornellLiiSource({
selectors: [
{ kind: 'uscode', path: '18/1836' }, // DTSA
{ kind: 'wex', path: 'restraint_of_trade', dimensionHints: ['jurisdictional_accuracy'] },
],
}),
// IRS publications index + named publications + revenue procedures.
createIrsPublicationsSource({
publications: ['p15', 'p17', 'p463'],
revenueProcedures: [],
}),
// Generic state SOS adapter — one config per state you need tracked.
createStateSosSource({
state: 'CA',
baseUrl: 'https://www.sos.ca.gov',
entities: [{
id: 'business-entities-forms',
path: '/business-programs/business-entities/forms',
title: 'CA Business Entities Forms',
selector: { kind: 'whole' },
}],
}),
]
const freshness = createFileSystemFreshnessStore({ root: './kb' })
// Worked example: Cornell LII updates the Wex `restraint_of_trade` entry
// to reflect Ryan-LLC v. FTC. The cron tick below detects the change,
// extracts the `jurisdictional_accuracy` dimension hint, and hands it to
// the eval scheduler which re-runs only the campaigns tagged with that
// dimension.
async function tick({ workspaceId, prevSnapshots }: {
workspaceId: string
prevSnapshots: Record<string, KnowledgeFragment[]>
}): Promise<KnowledgeChange[]> {
const allChanges: KnowledgeChange[] = []
for (const source of sources) {
const stale = await freshness.stale({
workspaceId,
sourceId: source.id,
ttlMs: 24 * 60 * 60 * 1000,
})
if (!stale) continue
const next = await source.fetch({ cacheDir: './.agent-knowledge/http-cache' })
const prev = prevSnapshots[source.id] ?? []
const { changes } = detectChanges(prev, next)
allChanges.push(...changes)
await freshness.mark({ workspaceId, sourceId: source.id, when: new Date() })
prevSnapshots[source.id] = next
}
return allChanges
}Polite-by-default: every HTTP fetch carries the package User-Agent, is
throttled to 1 req/sec/origin, caches successful responses to disk, and
marks verifiable: false on block pages / 4xx rather than promoting
un-grounded content. See src/sources/http.ts for the invariants.