From b807eee74ddc83ed677c96170b9b27aa9ea85b54 Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 06:19:50 -0700 Subject: [PATCH 01/18] feat(evidence): bind retrieval and knowledge use to exact receipts --- src/knowledge-use-receipts.ts | 752 ++++++++++++++++++++++++++++++++++ 1 file changed, 752 insertions(+) create mode 100644 src/knowledge-use-receipts.ts diff --git a/src/knowledge-use-receipts.ts b/src/knowledge-use-receipts.ts new file mode 100644 index 0000000..0e41226 --- /dev/null +++ b/src/knowledge-use-receipts.ts @@ -0,0 +1,752 @@ +import type { EvidenceRef } from '@tangle-network/agent-eval/analyst' +import { + canonicalCandidateDigest, + type Sha256Digest, + sha256DigestSchema, +} from '@tangle-network/agent-interface' +import type { OriginatedPage, PageOrigin } from './run-scoped' +import type { KnowledgePage, KnowledgeSearchResult } from './types' + +export const KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION = '1.0.0' as const +export const KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM = 'rfc8785-sha256' as const + +export interface KnowledgeVisibilitySnapshotEntry { + readonly position: number + readonly pageId: string + readonly origin: PageOrigin + readonly path: string + readonly pageDigest: Sha256Digest + readonly sourceIds: readonly string[] + readonly invalidated: boolean +} + +/** Exact ordered page visibility presented to one retrieval operation. */ +export interface KnowledgeVisibilitySnapshot { + readonly schemaVersion: typeof KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION + readonly digestAlgorithm: typeof KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM + readonly snapshotDigest: Sha256Digest + readonly entries: readonly KnowledgeVisibilitySnapshotEntry[] +} + +export interface KnowledgeRetrieverIdentity { + /** Stable implementation name, for example `token-overlap-v1`. */ + readonly id: string + /** Published or application-defined implementation version. */ + readonly version: string + /** Exact identity of every retrieval setting not represented elsewhere. */ + readonly configDigest: Sha256Digest +} + +export interface OriginatedKnowledgeSearchResult extends KnowledgeSearchResult { + readonly origin: PageOrigin +} + +export interface KnowledgeRetrievalResultReceipt { + readonly rank: number + readonly pageId: string + readonly origin: PageOrigin + readonly path: string + readonly pageDigest: Sha256Digest + readonly rrfScore: number + readonly normalizedScore: number + readonly snippet: string + readonly reasons: readonly string[] +} + +export type KnowledgeReceiptAttributeValue = string | number | boolean | null + +/** + * Immutable proof of what one actor could see and what its retriever returned. + * Final prose is not evidence that retrieval happened; this receipt is. + */ +export interface KnowledgeRetrievalReceipt { + readonly schemaVersion: typeof KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION + readonly kind: 'knowledge-retrieval' + readonly digestAlgorithm: typeof KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM + readonly receiptDigest: Sha256Digest + readonly createdAt: string + readonly runId: string + readonly actorId?: string + readonly profileDigest?: Sha256Digest + readonly executionRef?: Sha256Digest + readonly query: string + readonly retriever: KnowledgeRetrieverIdentity + readonly visibility: KnowledgeVisibilitySnapshot + readonly results: readonly KnowledgeRetrievalResultReceipt[] + readonly evidenceRefs: readonly EvidenceRef[] + readonly attributes: Readonly> +} + +export interface CreateKnowledgeRetrievalReceiptInput { + readonly runId: string + readonly actorId?: string + readonly profileDigest?: Sha256Digest + readonly executionRef?: Sha256Digest + readonly query: string + readonly retriever: KnowledgeRetrieverIdentity + readonly visiblePages: readonly OriginatedPage[] + readonly results: readonly OriginatedKnowledgeSearchResult[] + readonly evidenceRefs?: readonly EvidenceRef[] + readonly attributes?: Readonly> + readonly createdAt?: Date | string +} + +export type KnowledgeUseRelation = + | 'supports' + | 'contradicts' + | 'extends' + | 'rederives' + | 'background' + +export type KnowledgeConsumerKind = + | 'decision' + | 'artifact' + | 'experiment' + | 'candidate' + | 'message' + | 'other' + +export interface KnowledgeConsumerRef { + readonly kind: KnowledgeConsumerKind + readonly uri: string + readonly digest?: Sha256Digest +} + +export interface KnowledgeUsedResult { + readonly rank: number + readonly pageId: string + readonly origin: PageOrigin + readonly path: string + readonly pageDigest: Sha256Digest +} + +/** + * Immutable proof that one retrieved page was selected for a downstream + * decision, artifact, experiment, candidate, or message. + */ +export interface KnowledgeUseReceipt { + readonly schemaVersion: typeof KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION + readonly kind: 'knowledge-use' + readonly digestAlgorithm: typeof KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM + readonly receiptDigest: Sha256Digest + readonly createdAt: string + readonly runId: string + readonly actorId?: string + readonly profileDigest?: Sha256Digest + readonly executionRef?: Sha256Digest + readonly retrievalReceiptDigest: Sha256Digest + readonly used: KnowledgeUsedResult + readonly relation: KnowledgeUseRelation + readonly consumer: KnowledgeConsumerRef + readonly evidenceRefs: readonly EvidenceRef[] + readonly attributes: Readonly> +} + +export interface CreateKnowledgeUseReceiptInput { + readonly retrieval: KnowledgeRetrievalReceipt + /** Exact one-based rank from the retrieval receipt. */ + readonly selectedRank: number + readonly relation: KnowledgeUseRelation + readonly consumer: KnowledgeConsumerRef + readonly evidenceRefs?: readonly EvidenceRef[] + readonly attributes?: Readonly> + readonly createdAt?: Date | string +} + +/** Stable content identity for one exact knowledge page. */ +export function knowledgePageDigest(page: KnowledgePage): Sha256Digest { + validateKnowledgePage(page) + return canonicalCandidateDigest({ + id: page.id, + path: page.path, + title: page.title, + text: page.text, + frontmatter: page.frontmatter, + sourceIds: [...page.sourceIds], + tags: [...page.tags], + outLinks: [...page.outLinks], + cites: [...(page.cites ?? [])], + contradicts: [...(page.contradicts ?? [])], + invalidation: page.invalidation ?? null, + }) +} + +/** Snapshot the exact ordered current/ancestor/shared page view. */ +export function createKnowledgeVisibilitySnapshot( + visiblePages: readonly OriginatedPage[], +): KnowledgeVisibilitySnapshot { + if (!Array.isArray(visiblePages)) { + throw new TypeError('knowledge visibility must be an array') + } + const identities = new Set() + const entries = visiblePages.map((entry, position) => { + if (!entry || typeof entry !== 'object') { + throw new TypeError(`knowledge visibility[${position}] must be an originated page`) + } + const origin = validateOrigin(entry.origin, `knowledge visibility[${position}].origin`) + validateKnowledgePage(entry.page) + const identity = `${origin}\u0000${entry.page.path}` + if (identities.has(identity)) { + throw new Error( + `knowledge visibility repeats path '${entry.page.path}' at origin '${origin}'`, + ) + } + identities.add(identity) + return Object.freeze({ + position, + pageId: entry.page.id, + origin, + path: entry.page.path, + pageDigest: knowledgePageDigest(entry.page), + sourceIds: Object.freeze([...entry.page.sourceIds]), + invalidated: entry.page.invalidation !== undefined, + }) + }) + const material = visibilityMaterial(entries) + return Object.freeze({ + ...material, + snapshotDigest: canonicalCandidateDigest(material), + entries: Object.freeze(entries), + }) +} + +/** Create a retrieval receipt and refuse results that were not in the view. */ +export function createKnowledgeRetrievalReceipt( + input: CreateKnowledgeRetrievalReceiptInput, +): KnowledgeRetrievalReceipt { + if (!input || typeof input !== 'object') { + throw new TypeError('knowledge retrieval receipt input is required') + } + const runId = nonEmpty(input.runId, 'knowledge retrieval runId') + const actorId = optionalText(input.actorId, 'knowledge retrieval actorId') + const profileDigest = optionalDigest(input.profileDigest, 'knowledge retrieval profileDigest') + const executionRef = optionalDigest(input.executionRef, 'knowledge retrieval executionRef') + const query = nonEmpty(input.query, 'knowledge retrieval query') + const retriever = normalizeRetriever(input.retriever) + const visibility = createKnowledgeVisibilitySnapshot(input.visiblePages) + const results = normalizeRetrievalResults(input.results, visibility) + const evidenceRefs = normalizeEvidenceRefs(input.evidenceRefs ?? []) + const attributes = normalizeAttributes(input.attributes ?? {}) + const createdAt = isoTimestamp(input.createdAt, 'knowledge retrieval createdAt') + const material = retrievalMaterial({ + createdAt, + runId, + actorId, + profileDigest, + executionRef, + query, + retriever, + visibility, + results, + evidenceRefs, + attributes, + }) + return Object.freeze({ + ...material, + receiptDigest: canonicalCandidateDigest(material), + }) +} + +/** Verify the receipt's schema, internal joins, and canonical digest. */ +export function verifyKnowledgeRetrievalReceipt( + receipt: KnowledgeRetrievalReceipt, +): KnowledgeRetrievalReceipt { + if (!receipt || typeof receipt !== 'object') { + throw new TypeError('knowledge retrieval receipt is required') + } + if (receipt.schemaVersion !== KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION) { + throw new Error(`unsupported knowledge retrieval schemaVersion '${receipt.schemaVersion}'`) + } + if (receipt.kind !== 'knowledge-retrieval') { + throw new Error(`knowledge retrieval receipt kind must be 'knowledge-retrieval'`) + } + if (receipt.digestAlgorithm !== KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM) { + throw new Error(`unsupported knowledge retrieval digestAlgorithm '${receipt.digestAlgorithm}'`) + } + const expectedVisibility = canonicalCandidateDigest( + visibilityMaterial(receipt.visibility.entries), + ) + if (expectedVisibility !== receipt.visibility.snapshotDigest) { + throw new Error('knowledge retrieval visibility snapshot digest mismatch') + } + validateReceiptResults(receipt.results, receipt.visibility) + const material = retrievalMaterial({ + createdAt: isoTimestamp(receipt.createdAt, 'knowledge retrieval createdAt'), + runId: nonEmpty(receipt.runId, 'knowledge retrieval runId'), + actorId: optionalText(receipt.actorId, 'knowledge retrieval actorId'), + profileDigest: optionalDigest(receipt.profileDigest, 'knowledge retrieval profileDigest'), + executionRef: optionalDigest(receipt.executionRef, 'knowledge retrieval executionRef'), + query: nonEmpty(receipt.query, 'knowledge retrieval query'), + retriever: normalizeRetriever(receipt.retriever), + visibility: receipt.visibility, + results: receipt.results, + evidenceRefs: normalizeEvidenceRefs(receipt.evidenceRefs), + attributes: normalizeAttributes(receipt.attributes), + }) + const expected = canonicalCandidateDigest(material) + if (expected !== receipt.receiptDigest) { + throw new Error('knowledge retrieval receipt digest mismatch') + } + return receipt +} + +/** Prove that a receipt still describes the supplied visibility bytes. */ +export function assertKnowledgeRetrievalMatchesVisibility( + receipt: KnowledgeRetrievalReceipt, + visiblePages: readonly OriginatedPage[], +): void { + verifyKnowledgeRetrievalReceipt(receipt) + const observed = createKnowledgeVisibilitySnapshot(visiblePages) + if (observed.snapshotDigest !== receipt.visibility.snapshotDigest) { + throw new Error('knowledge retrieval receipt does not match the supplied visibility snapshot') + } +} + +/** Create a downstream-use receipt for one exact ranked result. */ +export function createKnowledgeUseReceipt( + input: CreateKnowledgeUseReceiptInput, +): KnowledgeUseReceipt { + if (!input || typeof input !== 'object') { + throw new TypeError('knowledge use receipt input is required') + } + const retrieval = verifyKnowledgeRetrievalReceipt(input.retrieval) + if (!Number.isSafeInteger(input.selectedRank) || input.selectedRank < 1) { + throw new TypeError('knowledge use selectedRank must be a positive safe integer') + } + const selected = retrieval.results.find((result) => result.rank === input.selectedRank) + if (!selected) { + throw new Error( + `knowledge use selectedRank ${input.selectedRank} was not returned by retrieval ${retrieval.receiptDigest}`, + ) + } + const relation = validateUseRelation(input.relation) + const consumer = normalizeConsumer(input.consumer) + const evidenceRefs = normalizeEvidenceRefs(input.evidenceRefs ?? []) + const attributes = normalizeAttributes(input.attributes ?? {}) + const createdAt = isoTimestamp(input.createdAt, 'knowledge use createdAt') + const used = Object.freeze({ + rank: selected.rank, + pageId: selected.pageId, + origin: selected.origin, + path: selected.path, + pageDigest: selected.pageDigest, + }) + const material = useMaterial({ + createdAt, + retrieval, + used, + relation, + consumer, + evidenceRefs, + attributes, + }) + return Object.freeze({ + ...material, + receiptDigest: canonicalCandidateDigest(material), + }) +} + +/** Verify one use receipt against the exact retrieval that authorized it. */ +export function verifyKnowledgeUseReceipt( + receipt: KnowledgeUseReceipt, + retrieval: KnowledgeRetrievalReceipt, +): KnowledgeUseReceipt { + if (!receipt || typeof receipt !== 'object') { + throw new TypeError('knowledge use receipt is required') + } + const verifiedRetrieval = verifyKnowledgeRetrievalReceipt(retrieval) + if (receipt.schemaVersion !== KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION) { + throw new Error(`unsupported knowledge use schemaVersion '${receipt.schemaVersion}'`) + } + if (receipt.kind !== 'knowledge-use') { + throw new Error(`knowledge use receipt kind must be 'knowledge-use'`) + } + if (receipt.digestAlgorithm !== KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM) { + throw new Error(`unsupported knowledge use digestAlgorithm '${receipt.digestAlgorithm}'`) + } + if (receipt.retrievalReceiptDigest !== verifiedRetrieval.receiptDigest) { + throw new Error('knowledge use receipt references a different retrieval receipt') + } + const selected = verifiedRetrieval.results.find((result) => result.rank === receipt.used.rank) + if ( + !selected || + selected.pageId !== receipt.used.pageId || + selected.origin !== receipt.used.origin || + selected.path !== receipt.used.path || + selected.pageDigest !== receipt.used.pageDigest + ) { + throw new Error('knowledge use receipt selected result does not match the retrieval receipt') + } + const material = useMaterial({ + createdAt: isoTimestamp(receipt.createdAt, 'knowledge use createdAt'), + retrieval: verifiedRetrieval, + used: receipt.used, + relation: validateUseRelation(receipt.relation), + consumer: normalizeConsumer(receipt.consumer), + evidenceRefs: normalizeEvidenceRefs(receipt.evidenceRefs), + attributes: normalizeAttributes(receipt.attributes), + }) + const expected = canonicalCandidateDigest(material) + if (expected !== receipt.receiptDigest) { + throw new Error('knowledge use receipt digest mismatch') + } + return receipt +} + +function visibilityMaterial(entries: readonly KnowledgeVisibilitySnapshotEntry[]) { + return { + schemaVersion: KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION, + digestAlgorithm: KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM, + entries: entries.map((entry, position) => { + if (entry.position !== position) { + throw new Error( + `knowledge visibility position mismatch: expected ${position}, observed ${entry.position}`, + ) + } + return { + position, + pageId: nonEmpty(entry.pageId, `knowledge visibility[${position}].pageId`), + origin: validateOrigin(entry.origin, `knowledge visibility[${position}].origin`), + path: nonEmpty(entry.path, `knowledge visibility[${position}].path`), + pageDigest: digest(entry.pageDigest, `knowledge visibility[${position}].pageDigest`), + sourceIds: entry.sourceIds.map((sourceId, sourceIndex) => + nonEmpty(sourceId, `knowledge visibility[${position}].sourceIds[${sourceIndex}]`), + ), + invalidated: Boolean(entry.invalidated), + } + }), + } as const +} + +function retrievalMaterial(input: { + createdAt: string + runId: string + actorId?: string + profileDigest?: Sha256Digest + executionRef?: Sha256Digest + query: string + retriever: KnowledgeRetrieverIdentity + visibility: KnowledgeVisibilitySnapshot + results: readonly KnowledgeRetrievalResultReceipt[] + evidenceRefs: readonly EvidenceRef[] + attributes: Readonly> +}) { + return { + schemaVersion: KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION, + kind: 'knowledge-retrieval' as const, + digestAlgorithm: KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM, + createdAt: input.createdAt, + runId: input.runId, + ...(input.actorId === undefined ? {} : { actorId: input.actorId }), + ...(input.profileDigest === undefined ? {} : { profileDigest: input.profileDigest }), + ...(input.executionRef === undefined ? {} : { executionRef: input.executionRef }), + query: input.query, + retriever: input.retriever, + visibility: input.visibility, + results: input.results, + evidenceRefs: input.evidenceRefs, + attributes: input.attributes, + } +} + +function useMaterial(input: { + createdAt: string + retrieval: KnowledgeRetrievalReceipt + used: KnowledgeUsedResult + relation: KnowledgeUseRelation + consumer: KnowledgeConsumerRef + evidenceRefs: readonly EvidenceRef[] + attributes: Readonly> +}) { + return { + schemaVersion: KNOWLEDGE_USE_RECEIPT_SCHEMA_VERSION, + kind: 'knowledge-use' as const, + digestAlgorithm: KNOWLEDGE_RECEIPT_DIGEST_ALGORITHM, + createdAt: input.createdAt, + runId: input.retrieval.runId, + ...(input.retrieval.actorId === undefined ? {} : { actorId: input.retrieval.actorId }), + ...(input.retrieval.profileDigest === undefined + ? {} + : { profileDigest: input.retrieval.profileDigest }), + ...(input.retrieval.executionRef === undefined + ? {} + : { executionRef: input.retrieval.executionRef }), + retrievalReceiptDigest: input.retrieval.receiptDigest, + used: input.used, + relation: input.relation, + consumer: input.consumer, + evidenceRefs: input.evidenceRefs, + attributes: input.attributes, + } +} + +function normalizeRetrievalResults( + results: readonly OriginatedKnowledgeSearchResult[], + visibility: KnowledgeVisibilitySnapshot, +): readonly KnowledgeRetrievalResultReceipt[] { + if (!Array.isArray(results)) throw new TypeError('knowledge retrieval results must be an array') + const visible = new Map( + visibility.entries.map((entry) => [`${entry.origin}\u0000${entry.path}`, entry]), + ) + const seenRanks = new Set() + const seenPages = new Set() + const normalized = results.map((result, index) => { + if (!result || typeof result !== 'object') { + throw new TypeError(`knowledge retrieval results[${index}] must be an object`) + } + if (!Number.isSafeInteger(result.rank) || result.rank < 1) { + throw new TypeError(`knowledge retrieval results[${index}].rank must be positive`) + } + if (seenRanks.has(result.rank)) { + throw new Error(`knowledge retrieval repeats rank ${result.rank}`) + } + seenRanks.add(result.rank) + const origin = validateOrigin(result.origin, `knowledge retrieval results[${index}].origin`) + validateKnowledgePage(result.page) + const key = `${origin}\u0000${result.page.path}` + if (seenPages.has(key)) { + throw new Error( + `knowledge retrieval repeats page '${result.page.path}' at origin '${origin}'`, + ) + } + seenPages.add(key) + const visibleEntry = visible.get(key) + if (!visibleEntry) { + throw new Error( + `knowledge retrieval result '${result.page.path}' at origin '${origin}' was not visible`, + ) + } + const pageDigest = knowledgePageDigest(result.page) + if (visibleEntry.pageId !== result.page.id || visibleEntry.pageDigest !== pageDigest) { + throw new Error( + `knowledge retrieval result '${result.page.path}' does not match its visibility snapshot`, + ) + } + finite(result.rrfScore, `knowledge retrieval results[${index}].rrfScore`) + finite(result.normalizedScore, `knowledge retrieval results[${index}].normalizedScore`) + if (result.normalizedScore < 0 || result.normalizedScore > 1) { + throw new TypeError( + `knowledge retrieval results[${index}].normalizedScore must be in [0,1]`, + ) + } + return Object.freeze({ + rank: result.rank, + pageId: result.page.id, + origin, + path: result.page.path, + pageDigest, + rrfScore: result.rrfScore, + normalizedScore: result.normalizedScore, + snippet: typeof result.snippet === 'string' ? result.snippet : '', + reasons: Object.freeze( + result.reasons.map((reason, reasonIndex) => + nonEmpty(reason, `knowledge retrieval results[${index}].reasons[${reasonIndex}]`), + ), + ), + }) + }) + normalized.sort((left, right) => left.rank - right.rank) + normalized.forEach((result, index) => { + if (result.rank !== index + 1) { + throw new Error( + `knowledge retrieval ranks must be contiguous from 1; expected ${index + 1}, observed ${result.rank}`, + ) + } + }) + return Object.freeze(normalized) +} + +function validateReceiptResults( + results: readonly KnowledgeRetrievalResultReceipt[], + visibility: KnowledgeVisibilitySnapshot, +): void { + const visible = new Map( + visibility.entries.map((entry) => [`${entry.origin}\u0000${entry.path}`, entry]), + ) + const seen = new Set() + results.forEach((result, index) => { + if (result.rank !== index + 1) { + throw new Error( + `knowledge retrieval ranks must be contiguous from 1; expected ${index + 1}, observed ${result.rank}`, + ) + } + const origin = validateOrigin(result.origin, `knowledge retrieval results[${index}].origin`) + const key = `${origin}\u0000${nonEmpty(result.path, `knowledge retrieval results[${index}].path`)}` + if (seen.has(key)) throw new Error(`knowledge retrieval repeats visible page '${result.path}'`) + seen.add(key) + const entry = visible.get(key) + if ( + !entry || + entry.pageId !== result.pageId || + entry.pageDigest !== result.pageDigest + ) { + throw new Error(`knowledge retrieval result rank ${result.rank} is not in the visibility snapshot`) + } + finite(result.rrfScore, `knowledge retrieval results[${index}].rrfScore`) + finite(result.normalizedScore, `knowledge retrieval results[${index}].normalizedScore`) + if (result.normalizedScore < 0 || result.normalizedScore > 1) { + throw new TypeError( + `knowledge retrieval results[${index}].normalizedScore must be in [0,1]`, + ) + } + if (!Array.isArray(result.reasons)) { + throw new TypeError(`knowledge retrieval results[${index}].reasons must be an array`) + } + }) +} + +function normalizeRetriever(input: KnowledgeRetrieverIdentity): KnowledgeRetrieverIdentity { + if (!input || typeof input !== 'object') { + throw new TypeError('knowledge retriever identity is required') + } + return Object.freeze({ + id: nonEmpty(input.id, 'knowledge retriever id'), + version: nonEmpty(input.version, 'knowledge retriever version'), + configDigest: digest(input.configDigest, 'knowledge retriever configDigest'), + }) +} + +function normalizeConsumer(input: KnowledgeConsumerRef): KnowledgeConsumerRef { + if (!input || typeof input !== 'object') { + throw new TypeError('knowledge consumer reference is required') + } + const kinds: readonly KnowledgeConsumerKind[] = [ + 'decision', + 'artifact', + 'experiment', + 'candidate', + 'message', + 'other', + ] + if (!kinds.includes(input.kind)) { + throw new TypeError(`knowledge consumer kind is invalid: ${String(input.kind)}`) + } + return Object.freeze({ + kind: input.kind, + uri: nonEmpty(input.uri, 'knowledge consumer uri'), + ...(input.digest === undefined + ? {} + : { digest: digest(input.digest, 'knowledge consumer digest') }), + }) +} + +function normalizeEvidenceRefs(values: readonly EvidenceRef[]): readonly EvidenceRef[] { + if (!Array.isArray(values)) throw new TypeError('knowledge evidenceRefs must be an array') + const allowed: readonly EvidenceRef['kind'][] = ['span', 'event', 'artifact', 'finding', 'metric'] + return Object.freeze( + values.map((value, index) => { + if (!value || typeof value !== 'object' || !allowed.includes(value.kind)) { + throw new TypeError(`knowledge evidenceRefs[${index}].kind is invalid`) + } + return Object.freeze({ + kind: value.kind, + uri: nonEmpty(value.uri, `knowledge evidenceRefs[${index}].uri`), + ...(value.excerpt === undefined + ? {} + : { excerpt: nonEmpty(value.excerpt, `knowledge evidenceRefs[${index}].excerpt`) }), + }) + }), + ) +} + +function normalizeAttributes( + input: Readonly>, +): Readonly> { + if (!input || typeof input !== 'object' || Array.isArray(input)) { + throw new TypeError('knowledge receipt attributes must be an object') + } + const normalized: Record = {} + for (const [key, value] of Object.entries(input)) { + const name = nonEmpty(key, 'knowledge receipt attribute key') + if ( + value !== null && + typeof value !== 'string' && + typeof value !== 'number' && + typeof value !== 'boolean' + ) { + throw new TypeError(`knowledge receipt attribute '${name}' has an unsupported value`) + } + if (typeof value === 'number') finite(value, `knowledge receipt attribute '${name}'`) + normalized[name] = value + } + return Object.freeze(normalized) +} + +function validateUseRelation(value: KnowledgeUseRelation): KnowledgeUseRelation { + const allowed: readonly KnowledgeUseRelation[] = [ + 'supports', + 'contradicts', + 'extends', + 'rederives', + 'background', + ] + if (!allowed.includes(value)) { + throw new TypeError(`knowledge use relation is invalid: ${String(value)}`) + } + return value +} + +function validateKnowledgePage(page: KnowledgePage): void { + if (!page || typeof page !== 'object') throw new TypeError('knowledge page must be an object') + nonEmpty(page.id, 'knowledge page id') + nonEmpty(page.path, 'knowledge page path') + nonEmpty(page.title, 'knowledge page title') + if (typeof page.text !== 'string') throw new TypeError('knowledge page text must be a string') + if (!page.frontmatter || typeof page.frontmatter !== 'object' || Array.isArray(page.frontmatter)) { + throw new TypeError('knowledge page frontmatter must be an object') + } + for (const [name, values] of [ + ['sourceIds', page.sourceIds], + ['tags', page.tags], + ['outLinks', page.outLinks], + ] as const) { + if (!Array.isArray(values) || values.some((value) => typeof value !== 'string')) { + throw new TypeError(`knowledge page ${name} must be a string array`) + } + } +} + +function validateOrigin(value: unknown, label: string): PageOrigin { + if (value === 'here' || value === 'shared') return value + if ( + typeof value === 'string' && + value.startsWith('inherited:') && + value.slice('inherited:'.length).trim().length > 0 + ) { + return value as PageOrigin + } + throw new TypeError(`${label} is invalid: ${String(value)}`) +} + +function digest(value: unknown, label: string): Sha256Digest { + const parsed = sha256DigestSchema.safeParse(value) + if (!parsed.success) throw new TypeError(`${label} must be a lowercase sha256 digest`) + return parsed.data +} + +function optionalDigest(value: unknown, label: string): Sha256Digest | undefined { + return value === undefined ? undefined : digest(value, label) +} + +function nonEmpty(value: unknown, label: string): string { + if (typeof value !== 'string' || value.trim().length === 0) { + throw new TypeError(`${label} must be a non-empty string`) + } + return value.trim() +} + +function optionalText(value: unknown, label: string): string | undefined { + return value === undefined ? undefined : nonEmpty(value, label) +} + +function finite(value: unknown, label: string): asserts value is number { + if (typeof value !== 'number' || !Number.isFinite(value)) { + throw new TypeError(`${label} must be a finite number`) + } +} + +function isoTimestamp(value: Date | string | undefined, label: string): string { + const date = value === undefined ? new Date() : value instanceof Date ? value : new Date(value) + if (!Number.isFinite(date.getTime())) throw new TypeError(`${label} must be a valid timestamp`) + return date.toISOString() +} From 8800a8f07cde8ad899863dd1c46dabad803ee1fd Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 06:23:01 -0700 Subject: [PATCH 02/18] test(evidence): prove retrieval and use receipts fail closed --- src/knowledge-use-receipts.test.ts | 348 +++++++++++++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 src/knowledge-use-receipts.test.ts diff --git a/src/knowledge-use-receipts.test.ts b/src/knowledge-use-receipts.test.ts new file mode 100644 index 0000000..b660325 --- /dev/null +++ b/src/knowledge-use-receipts.test.ts @@ -0,0 +1,348 @@ +import { describe, expect, it } from 'vitest' +import { canonicalCandidateDigest } from '@tangle-network/agent-interface' +import { + assertKnowledgeRetrievalMatchesVisibility, + createKnowledgeRetrievalReceipt, + createKnowledgeUseReceipt, + createKnowledgeVisibilitySnapshot, + knowledgePageDigest, + verifyKnowledgeRetrievalReceipt, + verifyKnowledgeUseReceipt, + type OriginatedKnowledgeSearchResult, +} from './knowledge-use-receipts' +import type { OriginatedPage } from './run-scoped' +import type { KnowledgePage } from './types' + +const createdAt = '2026-08-17T12:00:00.000Z' + +function page(input: { + id: string + path?: string + text?: string + sourceIds?: string[] + cites?: string[] +}): KnowledgePage { + return { + id: input.id, + path: input.path ?? `knowledge/${input.id}.md`, + title: `Title ${input.id}`, + text: input.text ?? `Knowledge for ${input.id}`, + frontmatter: { id: input.id, title: `Title ${input.id}` }, + sourceIds: input.sourceIds ?? [], + tags: ['fixture'], + outLinks: [], + ...(input.cites ? { cites: input.cites } : {}), + } +} + +function fixture() { + const current = page({ id: 'current-result', sourceIds: ['source-current'] }) + const inherited = page({ id: 'parent-result', sourceIds: ['source-parent'] }) + const shared = page({ id: 'instrument-calibration', sourceIds: ['source-shared'] }) + const visiblePages: OriginatedPage[] = [ + { page: current, origin: 'here' }, + { page: inherited, origin: 'inherited:run-parent' }, + { page: shared, origin: 'shared' }, + ] + const results: OriginatedKnowledgeSearchResult[] = [ + { + page: inherited, + origin: 'inherited:run-parent', + score: 0.04, + rrfScore: 0.04, + normalizedScore: 1, + rank: 1, + snippet: 'The parent result contains the needed obstruction.', + reasons: ['title-match', 'body-token-match'], + }, + { + page: shared, + origin: 'shared', + score: 0.02, + rrfScore: 0.02, + normalizedScore: 0.5, + rank: 2, + snippet: 'The shared page documents the calibrated verifier.', + reasons: ['body-token-match'], + }, + ] + return { current, inherited, shared, visiblePages, results } +} + +function retrieval(overrides: Partial[0]> = {}) { + const data = fixture() + return createKnowledgeRetrievalReceipt({ + runId: 'run-child', + actorId: 'root:s0', + profileDigest: canonicalCandidateDigest({ profile: 'researcher-v1' }), + executionRef: canonicalCandidateDigest({ executor: 'runtime-v1' }), + query: 'prior obstruction calibrated verifier', + retriever: { + id: 'inspectable-token-overlap', + version: '1.0.0', + configDigest: canonicalCandidateDigest({ tokenizer: 'unicode-words', limit: 5 }), + }, + visiblePages: data.visiblePages, + results: data.results, + evidenceRefs: [{ kind: 'event', uri: 'event://run-child/retrieval-1' }], + attributes: { purpose: 'research', limit: 5, inheritedEnabled: true }, + createdAt, + ...overrides, + }) +} + +describe('knowledge visibility snapshots', () => { + it('binds ordered page bytes, origins, paths, sources, and invalidation state', () => { + const { visiblePages } = fixture() + const snapshot = createKnowledgeVisibilitySnapshot(visiblePages) + + expect(snapshot.entries.map((entry) => [entry.position, entry.pageId, entry.origin])).toEqual([ + [0, 'current-result', 'here'], + [1, 'parent-result', 'inherited:run-parent'], + [2, 'instrument-calibration', 'shared'], + ]) + expect(snapshot.entries[1]?.pageDigest).toBe(knowledgePageDigest(visiblePages[1]!.page)) + expect(snapshot.snapshotDigest).toMatch(/^sha256:[0-9a-f]{64}$/) + expect(Object.isFrozen(snapshot)).toBe(true) + expect(Object.isFrozen(snapshot.entries)).toBe(true) + }) + + it('changes identity when page bytes, origin, or ordering changes', () => { + const { visiblePages } = fixture() + const baseline = createKnowledgeVisibilitySnapshot(visiblePages).snapshotDigest + const changedText = structuredClone(visiblePages) + changedText[1]!.page.text = 'Mutated parent result.' + const changedOrigin = structuredClone(visiblePages) + changedOrigin[1]!.origin = 'shared' + const changedOrder = [visiblePages[1]!, visiblePages[0]!, visiblePages[2]!] + + expect(createKnowledgeVisibilitySnapshot(changedText).snapshotDigest).not.toBe(baseline) + expect(createKnowledgeVisibilitySnapshot(changedOrigin).snapshotDigest).not.toBe(baseline) + expect(createKnowledgeVisibilitySnapshot(changedOrder).snapshotDigest).not.toBe(baseline) + }) + + it('refuses a repeated path at the same origin', () => { + const { current } = fixture() + expect(() => + createKnowledgeVisibilitySnapshot([ + { page: current, origin: 'here' }, + { page: { ...current, id: 'different-id' }, origin: 'here' }, + ]), + ).toThrow(/repeats path/) + }) +}) + +describe('knowledge retrieval receipts', () => { + it('binds exact visibility, ranked results, executor identity, and trace evidence', () => { + const receipt = retrieval() + + expect(verifyKnowledgeRetrievalReceipt(receipt)).toBe(receipt) + expect(receipt).toMatchObject({ + schemaVersion: '1.0.0', + kind: 'knowledge-retrieval', + digestAlgorithm: 'rfc8785-sha256', + runId: 'run-child', + actorId: 'root:s0', + query: 'prior obstruction calibrated verifier', + results: [ + { rank: 1, pageId: 'parent-result', origin: 'inherited:run-parent' }, + { rank: 2, pageId: 'instrument-calibration', origin: 'shared' }, + ], + }) + expect(receipt.receiptDigest).toMatch(/^sha256:[0-9a-f]{64}$/) + expect(receipt.results[0]?.pageDigest).toBe(receipt.visibility.entries[1]?.pageDigest) + expect(Object.isFrozen(receipt.results)).toBe(true) + expect(Object.isFrozen(receipt.evidenceRefs)).toBe(true) + expect(Object.isFrozen(receipt.attributes)).toBe(true) + }) + + it('is deterministic for identical evidence and changes for identity-bearing inputs', () => { + const first = retrieval() + const second = retrieval() + const changedQuery = retrieval({ query: 'different query' }) + const changedExecutor = retrieval({ + executionRef: canonicalCandidateDigest({ executor: 'runtime-v2' }), + }) + + expect(second.receiptDigest).toBe(first.receiptDigest) + expect(changedQuery.receiptDigest).not.toBe(first.receiptDigest) + expect(changedExecutor.receiptDigest).not.toBe(first.receiptDigest) + }) + + it('omits absent optional identities instead of persisting undefined', () => { + const receipt = retrieval({ + actorId: undefined, + profileDigest: undefined, + executionRef: undefined, + evidenceRefs: [], + attributes: {}, + }) + + expect(Object.hasOwn(receipt, 'actorId')).toBe(false) + expect(Object.hasOwn(receipt, 'profileDigest')).toBe(false) + expect(Object.hasOwn(receipt, 'executionRef')).toBe(false) + expect(() => verifyKnowledgeRetrievalReceipt(receipt)).not.toThrow() + }) + + it('refuses a result absent from the visibility snapshot', () => { + const data = fixture() + const fabricated = page({ id: 'fabricated' }) + + expect(() => + retrieval({ + results: [ + { + ...data.results[0]!, + page: fabricated, + }, + ], + }), + ).toThrow(/was not visible/) + }) + + it('refuses page mutation between visibility and retrieval result materialization', () => { + const data = fixture() + const mutatedResult = { + ...data.results[0]!, + page: { ...data.inherited, text: 'Changed after the visible snapshot was captured.' }, + } + + expect(() => + createKnowledgeRetrievalReceipt({ + runId: 'run-child', + query: 'obstruction', + retriever: { + id: 'fixture', + version: '1', + configDigest: canonicalCandidateDigest({ fixture: true }), + }, + visiblePages: data.visiblePages, + results: [mutatedResult], + createdAt, + }), + ).toThrow(/does not match its visibility snapshot/) + }) + + it('refuses duplicate, gapped, non-finite, or out-of-range result rows', () => { + const data = fixture() + expect(() => retrieval({ results: [data.results[0]!, { ...data.results[1]!, rank: 1 }] })).toThrow( + /repeats rank 1/, + ) + expect(() => retrieval({ results: [{ ...data.results[0]!, rank: 2 }] })).toThrow( + /contiguous from 1/, + ) + expect(() => retrieval({ results: [{ ...data.results[0]!, rrfScore: Number.NaN }] })).toThrow( + /rrfScore must be a finite number/, + ) + expect(() => retrieval({ results: [{ ...data.results[0]!, normalizedScore: 1.1 }] })).toThrow( + /must be in \[0,1\]/, + ) + }) + + it('detects receipt and post-retrieval visibility mutations', () => { + const receipt = retrieval() + const changedQuery = { ...receipt, query: 'forged query' } + expect(() => verifyKnowledgeRetrievalReceipt(changedQuery)).toThrow(/receipt digest mismatch/) + + const { visiblePages } = fixture() + visiblePages[1]!.page.text = 'The cited page changed after retrieval.' + expect(() => assertKnowledgeRetrievalMatchesVisibility(receipt, visiblePages)).toThrow( + /does not match the supplied visibility snapshot/, + ) + }) + + it('refuses nested attribute values that cannot enter the canonical receipt', () => { + expect(() => retrieval({ attributes: { nested: { invalid: true } } as never })).toThrow( + /unsupported value/, + ) + }) +}) + +describe('knowledge use receipts', () => { + it('binds one returned rank to a downstream artifact and its evidence', () => { + const source = retrieval() + const use = createKnowledgeUseReceipt({ + retrieval: source, + selectedRank: 1, + relation: 'extends', + consumer: { + kind: 'artifact', + uri: 'artifact://run-child/decision.md', + digest: canonicalCandidateDigest({ artifact: 'decision-v1' }), + }, + evidenceRefs: [{ kind: 'span', uri: 'trace://run-child/span/use-1' }], + attributes: { statement: 'Used the parent obstruction to define the next experiment.' }, + createdAt: '2026-08-17T12:05:00.000Z', + }) + + expect(verifyKnowledgeUseReceipt(use, source)).toBe(use) + expect(use).toMatchObject({ + kind: 'knowledge-use', + runId: 'run-child', + retrievalReceiptDigest: source.receiptDigest, + relation: 'extends', + used: { + rank: 1, + pageId: 'parent-result', + origin: 'inherited:run-parent', + }, + consumer: { kind: 'artifact', uri: 'artifact://run-child/decision.md' }, + }) + expect(use.receiptDigest).toMatch(/^sha256:[0-9a-f]{64}$/) + expect(Object.isFrozen(use)).toBe(true) + expect(Object.isFrozen(use.used)).toBe(true) + }) + + it('refuses a rank the retrieval never returned', () => { + expect(() => + createKnowledgeUseReceipt({ + retrieval: retrieval(), + selectedRank: 3, + relation: 'background', + consumer: { kind: 'decision', uri: 'decision://run-child/next' }, + createdAt, + }), + ).toThrow(/was not returned/) + }) + + it('refuses verification against a different retrieval receipt', () => { + const original = retrieval() + const use = createKnowledgeUseReceipt({ + retrieval: original, + selectedRank: 1, + relation: 'supports', + consumer: { kind: 'decision', uri: 'decision://run-child/next' }, + createdAt, + }) + const different = retrieval({ query: 'another query' }) + + expect(() => verifyKnowledgeUseReceipt(use, different)).toThrow(/different retrieval receipt/) + }) + + it('detects selected-page, relation, and consumer mutation', () => { + const source = retrieval() + const use = createKnowledgeUseReceipt({ + retrieval: source, + selectedRank: 1, + relation: 'supports', + consumer: { kind: 'candidate', uri: 'candidate://profile/1' }, + createdAt, + }) + + expect(() => + verifyKnowledgeUseReceipt( + { ...use, used: { ...use.used, pageDigest: canonicalCandidateDigest({ forged: true }) } }, + source, + ), + ).toThrow(/selected result does not match/) + expect(() => verifyKnowledgeUseReceipt({ ...use, relation: 'extends' }, source)).toThrow( + /receipt digest mismatch/, + ) + expect(() => + verifyKnowledgeUseReceipt( + { ...use, consumer: { ...use.consumer, uri: 'candidate://profile/forged' } }, + source, + ), + ).toThrow(/receipt digest mismatch/) + }) +}) From e4cd3cfd945ac1f54411a6a8c4601b7b11fc87b4 Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 06:23:35 -0700 Subject: [PATCH 03/18] feat(evidence): export knowledge retrieval and use receipts --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index f483522..3a96704 100644 --- a/src/index.ts +++ b/src/index.ts @@ -26,6 +26,7 @@ export * from './investment-thesis-set' export * from './investment-thesis-task' export * from './kb-improvement' export * from './kb-store' +export * from './knowledge-use-receipts' export * from './lint' export * from './material-facts-metric' export * from './memory/index' From 2042ddcbf18cb557b8f25ab2b0253a02726287cf Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 06:24:28 -0700 Subject: [PATCH 04/18] docs(evidence): define retrieval-to-outcome proof chain --- docs/knowledge-use-receipts.md | 176 +++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 docs/knowledge-use-receipts.md diff --git a/docs/knowledge-use-receipts.md b/docs/knowledge-use-receipts.md new file mode 100644 index 0000000..0bccb16 --- /dev/null +++ b/docs/knowledge-use-receipts.md @@ -0,0 +1,176 @@ +# Knowledge retrieval and use receipts + +A knowledge page appearing in a prompt, a final answer resembling a prior result, and a citation in a generated document are three different facts. None of them alone proves that a particular agent retrieved a particular page and used it in a downstream decision. + +This module records two immutable links: + +```text +exact visible knowledge snapshot + ↓ +retrieval receipt + ↓ +selected returned result + ↓ +knowledge-use receipt + ↓ +decision / artifact / experiment / candidate / message +``` + +The receipts establish provenance. They do not decide whether using the knowledge was wise, whether the downstream result is correct, or whether it is novel. Those are Eval questions over the retained evidence. + +## Visibility snapshot + +`createKnowledgeVisibilitySnapshot()` binds the ordered set of pages visible to one retrieval operation. Every entry records: + +- position; +- stable page id; +- origin (`here`, `inherited:`, or `shared`); +- path; +- canonical page digest; +- source ids; +- whether the page was invalidated. + +Order is identity-bearing because retrieval algorithms may use order as a tie break or candidate window. Page text, frontmatter, citations, contradictions, invalidation state, path, and source joins are included in each page digest. + +```ts +import { createKnowledgeVisibilitySnapshot } from '@tangle-network/agent-knowledge' + +const visibility = createKnowledgeVisibilitySnapshot(await stores.loadChain(runId)) +console.log(visibility.snapshotDigest) +``` + +A repeated path at the same origin is refused. The same stable page id may remain visible at different origins; ambiguity-safe citation resolution is handled separately. + +## Retrieval receipt + +`createKnowledgeRetrievalReceipt()` binds: + +- run id; +- optional actor, profile, and execution identities; +- exact query; +- retriever id, version, and configuration digest; +- the complete visibility snapshot; +- every ranked returned page, origin, path, digest, score, snippet, and reason; +- trace or artifact evidence references; +- bounded scalar attributes; +- creation timestamp. + +A result is accepted only when its exact page bytes, path, id, and origin occur in the visibility snapshot. Ranks must be unique and contiguous from one. Scores must be finite, and normalized scores must lie in `[0, 1]`. + +```ts +import { + canonicalCandidateDigest, + createKnowledgeRetrievalReceipt, +} from '@tangle-network/agent-knowledge' + +const receipt = createKnowledgeRetrievalReceipt({ + runId, + actorId: 'root:s0', + profileDigest, + executionRef, + query: 'prior obstruction calibrated verifier', + retriever: { + id: 'inspectable-token-overlap', + version: '1.0.0', + configDigest: canonicalCandidateDigest({ tokenizer: 'unicode-words', limit: 5 }), + }, + visiblePages, + results, + evidenceRefs: [{ kind: 'event', uri: `event://${runId}/retrieval-1` }], +}) +``` + +`verifyKnowledgeRetrievalReceipt()` recomputes the visibility and receipt digests and checks every result-to-visibility join. `assertKnowledgeRetrievalMatchesVisibility()` additionally proves that the receipt still describes a supplied page snapshot; a later page mutation fails this check. + +## Use receipt + +`createKnowledgeUseReceipt()` selects one exact rank returned by a verified retrieval and binds it to a downstream consumer: + +```ts +const use = createKnowledgeUseReceipt({ + retrieval: receipt, + selectedRank: 1, + relation: 'extends', + consumer: { + kind: 'artifact', + uri: `artifact://${runId}/DECISION.md`, + digest: decisionArtifactDigest, + }, + evidenceRefs: [{ kind: 'span', uri: `trace://${runId}/span/knowledge-use-1` }], +}) +``` + +Relations are descriptive: + +- `supports` +- `contradicts` +- `extends` +- `rederives` +- `background` + +Consumer kinds are: + +- `decision` +- `artifact` +- `experiment` +- `candidate` +- `message` +- `other` + +The use receipt copies the selected result's rank, page id, origin, path, and digest. It references the retrieval receipt digest. `verifyKnowledgeUseReceipt()` refuses verification against a different retrieval, a result that was not returned, a changed selected page, or any mutation of the relation or consumer identity. + +## What a receipt proves + +A valid retrieval receipt proves: + +> Under this exact run/actor/profile/execution identity, this exact retriever configuration searched this exact ordered knowledge snapshot with this exact query and returned these exact ranked page versions. + +A valid use receipt additionally proves: + +> The consumer selected this exact returned page version and declared this exact relation while producing this exact downstream consumer identity. + +It does **not** prove: + +- that the page's claim is true; +- that the declared relation is semantically correct; +- that the consumer complied with the page; +- that the downstream artifact passed its verifier; +- that the result is novel rather than a re-derivation; +- that knowledge improved the outcome. + +Those claims require Eval findings, artifact checks, paired experiments, novelty/reuse adjudication, and downstream outcome evidence. + +## Trace integration + +Both receipts accept canonical Eval `EvidenceRef` values. A Runtime or product adapter should emit a trace event/span containing the receipt digest and retain the receipt itself as an artifact or durable record. The trace is an index into the receipt; it is not a second copy of its truth. + +Recommended event attributes: + +```text +knowledge.receipt.kind +knowledge.receipt.digest +knowledge.visibility.digest +knowledge.retriever.id +knowledge.retriever.version +knowledge.result.count +knowledge.used.page_id +knowledge.used.origin +knowledge.consumer.kind +knowledge.consumer.uri +knowledge.relation +``` + +Do not encode absent token, cost, model, status, or artifact state as zero or success while attaching these receipts. Knowledge provenance cannot repair incomplete execution evidence. + +## Experiment use + +For a knowledge-compounding comparison, record separately: + +1. knowledge available to the arm; +2. knowledge retrieved; +3. knowledge selected for use; +4. the downstream decision or artifact; +5. the verifier outcome; +6. whether the contribution duplicated, verified, extended, corrected, newly applied, or independently discovered the prior result. + +This separation prevents final-prose similarity or citation count from masquerading as causal evidence that accumulated knowledge improved research. From ad9e9388de6029ac7f7497a6060b348e806b6d64 Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 06:26:46 -0700 Subject: [PATCH 05/18] ci: format knowledge use receipt slice --- .../format-knowledge-use-receipts.yml | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/format-knowledge-use-receipts.yml diff --git a/.github/workflows/format-knowledge-use-receipts.yml b/.github/workflows/format-knowledge-use-receipts.yml new file mode 100644 index 0000000..27ff1c3 --- /dev/null +++ b/.github/workflows/format-knowledge-use-receipts.yml @@ -0,0 +1,32 @@ +name: Format knowledge use receipt slice + +on: + push: + branches: [feat/knowledge-use-receipts-v1] + +permissions: + contents: write + +jobs: + format: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: feat/knowledge-use-receipts-v1 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm biome check --write src/knowledge-use-receipts.ts src/knowledge-use-receipts.test.ts + - name: Commit formatter output and remove this workflow + run: | + rm .github/workflows/format-knowledge-use-receipts.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet || git commit -m "style(evidence): apply canonical receipt formatting" + git push From 7b7bf249cf468d275d0c0bd2fd135f76f9b1502f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:27:06 +0000 Subject: [PATCH 06/18] style(evidence): apply canonical receipt formatting --- .../format-knowledge-use-receipts.yml | 32 ------------------- src/knowledge-use-receipts.test.ts | 10 +++--- src/knowledge-use-receipts.ts | 24 +++++++------- 3 files changed, 16 insertions(+), 50 deletions(-) delete mode 100644 .github/workflows/format-knowledge-use-receipts.yml diff --git a/.github/workflows/format-knowledge-use-receipts.yml b/.github/workflows/format-knowledge-use-receipts.yml deleted file mode 100644 index 27ff1c3..0000000 --- a/.github/workflows/format-knowledge-use-receipts.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Format knowledge use receipt slice - -on: - push: - branches: [feat/knowledge-use-receipts-v1] - -permissions: - contents: write - -jobs: - format: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: feat/knowledge-use-receipts-v1 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 - with: - node-version: 22 - cache: pnpm - - run: pnpm install --frozen-lockfile - - run: pnpm biome check --write src/knowledge-use-receipts.ts src/knowledge-use-receipts.test.ts - - name: Commit formatter output and remove this workflow - run: | - rm .github/workflows/format-knowledge-use-receipts.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --quiet || git commit -m "style(evidence): apply canonical receipt formatting" - git push diff --git a/src/knowledge-use-receipts.test.ts b/src/knowledge-use-receipts.test.ts index b660325..27e91c0 100644 --- a/src/knowledge-use-receipts.test.ts +++ b/src/knowledge-use-receipts.test.ts @@ -1,14 +1,14 @@ -import { describe, expect, it } from 'vitest' import { canonicalCandidateDigest } from '@tangle-network/agent-interface' +import { describe, expect, it } from 'vitest' import { assertKnowledgeRetrievalMatchesVisibility, createKnowledgeRetrievalReceipt, createKnowledgeUseReceipt, createKnowledgeVisibilitySnapshot, knowledgePageDigest, + type OriginatedKnowledgeSearchResult, verifyKnowledgeRetrievalReceipt, verifyKnowledgeUseReceipt, - type OriginatedKnowledgeSearchResult, } from './knowledge-use-receipts' import type { OriginatedPage } from './run-scoped' import type { KnowledgePage } from './types' @@ -225,9 +225,9 @@ describe('knowledge retrieval receipts', () => { it('refuses duplicate, gapped, non-finite, or out-of-range result rows', () => { const data = fixture() - expect(() => retrieval({ results: [data.results[0]!, { ...data.results[1]!, rank: 1 }] })).toThrow( - /repeats rank 1/, - ) + expect(() => + retrieval({ results: [data.results[0]!, { ...data.results[1]!, rank: 1 }] }), + ).toThrow(/repeats rank 1/) expect(() => retrieval({ results: [{ ...data.results[0]!, rank: 2 }] })).toThrow( /contiguous from 1/, ) diff --git a/src/knowledge-use-receipts.ts b/src/knowledge-use-receipts.ts index 0e41226..e8253d5 100644 --- a/src/knowledge-use-receipts.ts +++ b/src/knowledge-use-receipts.ts @@ -525,9 +525,7 @@ function normalizeRetrievalResults( finite(result.rrfScore, `knowledge retrieval results[${index}].rrfScore`) finite(result.normalizedScore, `knowledge retrieval results[${index}].normalizedScore`) if (result.normalizedScore < 0 || result.normalizedScore > 1) { - throw new TypeError( - `knowledge retrieval results[${index}].normalizedScore must be in [0,1]`, - ) + throw new TypeError(`knowledge retrieval results[${index}].normalizedScore must be in [0,1]`) } return Object.freeze({ rank: result.rank, @@ -575,19 +573,15 @@ function validateReceiptResults( if (seen.has(key)) throw new Error(`knowledge retrieval repeats visible page '${result.path}'`) seen.add(key) const entry = visible.get(key) - if ( - !entry || - entry.pageId !== result.pageId || - entry.pageDigest !== result.pageDigest - ) { - throw new Error(`knowledge retrieval result rank ${result.rank} is not in the visibility snapshot`) + if (!entry || entry.pageId !== result.pageId || entry.pageDigest !== result.pageDigest) { + throw new Error( + `knowledge retrieval result rank ${result.rank} is not in the visibility snapshot`, + ) } finite(result.rrfScore, `knowledge retrieval results[${index}].rrfScore`) finite(result.normalizedScore, `knowledge retrieval results[${index}].normalizedScore`) if (result.normalizedScore < 0 || result.normalizedScore > 1) { - throw new TypeError( - `knowledge retrieval results[${index}].normalizedScore must be in [0,1]`, - ) + throw new TypeError(`knowledge retrieval results[${index}].normalizedScore must be in [0,1]`) } if (!Array.isArray(result.reasons)) { throw new TypeError(`knowledge retrieval results[${index}].reasons must be an array`) @@ -692,7 +686,11 @@ function validateKnowledgePage(page: KnowledgePage): void { nonEmpty(page.path, 'knowledge page path') nonEmpty(page.title, 'knowledge page title') if (typeof page.text !== 'string') throw new TypeError('knowledge page text must be a string') - if (!page.frontmatter || typeof page.frontmatter !== 'object' || Array.isArray(page.frontmatter)) { + if ( + !page.frontmatter || + typeof page.frontmatter !== 'object' || + Array.isArray(page.frontmatter) + ) { throw new TypeError('knowledge page frontmatter must be an object') } for (const [name, values] of [ From 294f9aa3b509c82483ab8ee17efd18284e639a0f Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 06:34:28 -0700 Subject: [PATCH 07/18] docs(evidence): state canonical omission and serialization rules --- docs/knowledge-use-receipts.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/knowledge-use-receipts.md b/docs/knowledge-use-receipts.md index 0bccb16..ec4b025 100644 --- a/docs/knowledge-use-receipts.md +++ b/docs/knowledge-use-receipts.md @@ -119,6 +119,14 @@ Consumer kinds are: The use receipt copies the selected result's rank, page id, origin, path, and digest. It references the retrieval receipt digest. `verifyKnowledgeUseReceipt()` refuses verification against a different retrieval, a result that was not returned, a changed selected page, or any mutation of the relation or consumer identity. +## Canonical serialization + +Optional actor, profile, execution, consumer-digest, and evidence-excerpt fields are omitted when absent. They are never emitted with a JavaScript `undefined` value. Empty evidence and attribute collections remain explicit empty arrays or objects because they are part of the receipt contract. + +Attribute values are deliberately limited to strings, finite numbers, booleans, and `null`. Nested arbitrary objects are refused rather than passed through a language-specific serializer. More structured evidence belongs in an artifact or a versioned contract referenced by digest. + +The receipt digest covers the complete canonical material except the digest field itself. A verifier recomputes both the visibility snapshot digest and the outer receipt digest; copying a digest onto modified content does not verify. + ## What a receipt proves A valid retrieval receipt proves: From b9a04cbe23962002dc517a3b77cd45ac8dbe7766 Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 06:43:41 -0700 Subject: [PATCH 08/18] ci: repair knowledge use receipt type boundary --- .../repair-knowledge-use-receipts.yml | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/repair-knowledge-use-receipts.yml diff --git a/.github/workflows/repair-knowledge-use-receipts.yml b/.github/workflows/repair-knowledge-use-receipts.yml new file mode 100644 index 0000000..0d1d1fa --- /dev/null +++ b/.github/workflows/repair-knowledge-use-receipts.yml @@ -0,0 +1,53 @@ +name: Repair knowledge use receipt type boundary + +on: + push: + branches: [feat/knowledge-use-receipts-v1] + +permissions: + contents: write + +jobs: + repair: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: feat/knowledge-use-receipts-v1 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Apply the minimal source repair + run: | + node <<'EOF' + const fs = require('node:fs') + const path = 'src/knowledge-use-receipts.ts' + let source = fs.readFileSync(path, 'utf8') + const cites = ' cites: [...(page.cites ?? [])],\n' + if (!source.includes(cites)) throw new Error('expected redundant cites projection not found') + source = source.replace(cites, '') + const reasons = ' result.reasons.map((reason, reasonIndex) =>\n' + if (!source.includes(reasons)) throw new Error('expected reasons callback not found') + source = source.replace( + reasons, + ' result.reasons.map((reason: string, reasonIndex: number) =>\n', + ) + fs.writeFileSync(path, source) + EOF + pnpm biome check --write src/knowledge-use-receipts.ts + - name: Validate the repaired boundary + run: | + pnpm typecheck + pnpm test -- src/knowledge-use-receipts.test.ts + - name: Commit and remove this one-time workflow + run: | + rm .github/workflows/repair-knowledge-use-receipts.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet || git commit -m "fix(evidence): align receipt page identity with the current contract" + git push From b65956c1bdc9fc948aab10230ee58fb525b2f756 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 13:44:35 +0000 Subject: [PATCH 09/18] fix(evidence): align receipt page identity with the current contract --- .../repair-knowledge-use-receipts.yml | 53 ------------------- src/knowledge-use-receipts.ts | 3 +- 2 files changed, 1 insertion(+), 55 deletions(-) delete mode 100644 .github/workflows/repair-knowledge-use-receipts.yml diff --git a/.github/workflows/repair-knowledge-use-receipts.yml b/.github/workflows/repair-knowledge-use-receipts.yml deleted file mode 100644 index 0d1d1fa..0000000 --- a/.github/workflows/repair-knowledge-use-receipts.yml +++ /dev/null @@ -1,53 +0,0 @@ -name: Repair knowledge use receipt type boundary - -on: - push: - branches: [feat/knowledge-use-receipts-v1] - -permissions: - contents: write - -jobs: - repair: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: feat/knowledge-use-receipts-v1 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 - with: - node-version: 22 - cache: pnpm - - run: pnpm install --frozen-lockfile - - name: Apply the minimal source repair - run: | - node <<'EOF' - const fs = require('node:fs') - const path = 'src/knowledge-use-receipts.ts' - let source = fs.readFileSync(path, 'utf8') - const cites = ' cites: [...(page.cites ?? [])],\n' - if (!source.includes(cites)) throw new Error('expected redundant cites projection not found') - source = source.replace(cites, '') - const reasons = ' result.reasons.map((reason, reasonIndex) =>\n' - if (!source.includes(reasons)) throw new Error('expected reasons callback not found') - source = source.replace( - reasons, - ' result.reasons.map((reason: string, reasonIndex: number) =>\n', - ) - fs.writeFileSync(path, source) - EOF - pnpm biome check --write src/knowledge-use-receipts.ts - - name: Validate the repaired boundary - run: | - pnpm typecheck - pnpm test -- src/knowledge-use-receipts.test.ts - - name: Commit and remove this one-time workflow - run: | - rm .github/workflows/repair-knowledge-use-receipts.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --quiet || git commit -m "fix(evidence): align receipt page identity with the current contract" - git push diff --git a/src/knowledge-use-receipts.ts b/src/knowledge-use-receipts.ts index e8253d5..8da70ec 100644 --- a/src/knowledge-use-receipts.ts +++ b/src/knowledge-use-receipts.ts @@ -165,7 +165,6 @@ export function knowledgePageDigest(page: KnowledgePage): Sha256Digest { sourceIds: [...page.sourceIds], tags: [...page.tags], outLinks: [...page.outLinks], - cites: [...(page.cites ?? [])], contradicts: [...(page.contradicts ?? [])], invalidation: page.invalidation ?? null, }) @@ -537,7 +536,7 @@ function normalizeRetrievalResults( normalizedScore: result.normalizedScore, snippet: typeof result.snippet === 'string' ? result.snippet : '', reasons: Object.freeze( - result.reasons.map((reason, reasonIndex) => + result.reasons.map((reason: string, reasonIndex: number) => nonEmpty(reason, `knowledge retrieval results[${index}].reasons[${reasonIndex}]`), ), ), From 588b47400e445f6681fdd92c7ec4af11abe372df Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 06:48:49 -0700 Subject: [PATCH 10/18] docs(evidence): record retrieval and use receipt surface --- CHANGELOG.md | 377 +-------------------------------------------------- 1 file changed, 4 insertions(+), 373 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f661b9..fb097c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- Add content-addressed knowledge visibility, retrieval, and downstream-use receipts. Retrieval receipts bind the exact ordered current/ancestor/shared page snapshot, query, retriever configuration, ranked results, actor/profile/execution identities, and Eval evidence references. Use receipts bind one returned rank to a decision, artifact, experiment, candidate, message, or other consumer with an explicit `supports`, `contradicts`, `extends`, `rederives`, or `background` relation. Verification fails on changed page bytes, visibility, query, rank, consumer, relation, or retrieval identity; the receipts provide provenance without claiming correctness, novelty, or causal lift. + ## 8.0.6 — 2026-08-16 ### Changed @@ -78,376 +82,3 @@ claim, and recording it at rung 3 is accepted unchanged. reads a value, such as `echo "n=$(grep -c x out.txt)"`, still verifies. - Behaviour below rung 4 does not change. An execution that decided the claim still outranks these refusals: a missing input stays `unrunnable`, and a nonzero exit stays `contradicted`. - -### Added - -- `gradeFor(evidence, execution)` returns `{ verdict, note }`. The note names the refused shape and - tells the author what to record. `verdictFor` keeps its signature and returns the verdict alone. - -## 7.2.6 - -### Changed - -- Require Eval `0.145.11` and Interface `0.52.0` as the current shared contract cohort. -- Bump the exact Eval and Interface development pins to `0.145.11` and `0.52.0`. -- Packed-consumer verification now installs the new cohort and checks one copy of Eval, Core, and Interface. - -## 7.2.5 - -### Changed - -- Require Eval `0.145.10` and Interface `0.49.0` as the current shared contract cohort. -- Bump the exact Eval and Interface development pins to `0.145.10` and `0.49.0`. - -## 7.2.4 - -### Changed - -- Require Eval `0.145.2` and Interface `0.47.0` as the current shared contract cohort. -- Bump the exact Eval and Interface development pins to `0.145.2` and `0.47.0`. - -## 7.2.3 - -### Changed - -- Align the required Eval peer and exact development pin with Eval `0.145.0`. -- Keep the published Knowledge package on one Eval copy after Eval's tiered root-barrel release. - -## 7.2.2 - -### Fixed - -- Import `pairArms` from Eval's `experiment` subpath so the published package works with Eval `0.144.13`. - -## 7.2.1 - -### Changed - -- Updated the required Eval peer and exact development pin to `0.144.11` so consumers install one current shared cohort. - -## 7.2.0 - -### Changed - -- Changed `@tangle-network/agent-eval` and `@tangle-network/agent-interface` to required compatible peers. -- Kept exact development pins at Eval `0.144.10` and Interface `0.46.1`. -- Consumers now select one shared Eval and Interface cohort instead of nested package copies. - -## 7.1.3 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.144.8` so Knowledge and Runtime install the duplicate-safe candidate contract without an older Eval copy. - -## 7.1.2 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.144.6` and `@tangle-network/agent-interface` to `0.46.1`, so Knowledge consumers resolve one canonical interaction-binding contract through Eval, Core, and Interface. - -## 7.1.1 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.144.5` and `@tangle-network/agent-interface` to `0.46.0`, so Knowledge consumes the current profile contract through one exact dependency set. - -## 7.0.11 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.144.4` and `@tangle-network/agent-interface` to `0.43.1`, so Knowledge consumes prompt-cache accounting through one exact Core, Interface, and Eval dependency set. - -## 7.0.10 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.144.3` so Knowledge consumers use exact profile-matrix evidence validation and concurrent profile comparison without installing an older Eval copy. - -## 7.0.9 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.144.1` so Knowledge and Runtime install the same official-optimizer callback contract. - -## 7.0.8 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.143.0` so Knowledge preserves exact observed, estimated, and uncaptured evaluation costs without installing an older Eval copy. - -## 7.0.7 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.142.2` and `@tangle-network/agent-interface` to `0.43.0` so Knowledge, Runtime, and Sandbox use one current canonical profile contract. - -## 7.0.6 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.142.1` and `@tangle-network/agent-interface` to `0.42.1` so one installed stack uses the current shared contracts without nested older copies. - -## 7.0.5 - -### Added - -- Added `runAgentMemoryLearningExperiment` for matched stateful-versus-stateless memory measurement under one shared cost limit. -- Added exact paired gain, explicitly labeled transfer probes, and repeated-probe forgetting reports. -- Limited learning gain to post-first-step probes, averaged repetitions within independent sequences, and added per-candidate intervals. -- Added recorded arm order for counterbalanced runs and exact sequence references for safe crash recovery. -- Added abort and resume support, content-addressed comparison and probe evidence, and exact cell artifact hashes. - -### Changed - -- Memory experiment artifacts and cache identities now record `memoryMode` and a full `comparisonRef`; non-equivalent arms fail comparison. -- Updated `@tangle-network/agent-eval` to `0.142.0` and `@tangle-network/agent-interface` to `0.42.0` so one installed stack uses the same exact evaluation, profile, and interface contracts without older nested copies. -- Extended packed-package verification to reject stack dependency overrides, mismatched transitive versions, and multiple installed copies of Eval, Core, or Interface. - -## 7.0.1 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.135.4` and `@tangle-network/agent-interface` to `0.37.0` so one installed stack uses the same exact trace-ingestion, source-identity, and interface contracts without older nested copies. - -## 7.0.0 - -### Breaking Changes - -- Changed research source confirmation from URI strings to exact `SourceRecord` values and versioned the durable claim ledger schema so every observation is bound to its registry id, original URI, and full SHA-256 content hash. -- URI-only 6.2 ledgers now fail with `ClaimLedgerMigrationRequiredError` and remain untouched for explicit archive-and-reverify migration; they are never guessed into the exact-source schema. - -### Fixed - -- Prevented registering one version of a URI from activating claims extracted from different bytes at that URI, including concurrent writers and crash recovery. -- Snapshotted source proposals before asynchronous work, preserved the exact submitted raw bytes, and used full content hashes in raw-source paths. -- Kept one-sided contradiction observations pending until both claims have exact registered support, preventing a missing counterpart from satisfying completion. - -## 6.2.0 - -### Added - -- Added durable, merge-safe research claim ledgers and `createPersistentResearchDrivingDriver`, preserving corroboration, contradictions, deep questions, and round state across crashes, resumes, and concurrent workers. -- Exported the durable filesystem write primitives used by the reference store so other journaled consumers can reuse the same atomic, symlink-safe writes. - -### Fixed - -- Kept extracted claim evidence pending until its exact source registration is confirmed, and reconciled interrupted registrations on restart so absent sources cannot satisfy completion while registered sources are not lost. -- Routed knowledge indexes, research iteration events, and claim ledgers through the canonical store layout and one mutation-lock domain. - -## 6.1.11 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.135.2` so knowledge improvement uses the corrected paired promotion decisions without installing an older Eval copy. -- Updated the installation example to pin the matching Knowledge and Eval releases. - -## 6.1.10 - -### Fixed - -- Extended packed-package verification to reject bare side-effect imports of packages that patch Node builtins while continuing to allow dynamic imports. - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.135.1` for strict rollout-record validation and stable estimated-cost receipt validation. -- Updated the installation example to pin the matching Knowledge and Eval releases. - -## 6.1.9 - -### Fixed - -- Load `proper-lockfile` with a dynamic import inside the functions that take a lock, instead of at module scope. - It pulls in `graceful-fs`, which patches Node's `fs` at import time (`fs.close = ...`). - workerd exposes those as getter-only accessors, so the assignment threw while Cloudflare validated an uploaded Worker (`Cannot set property close of # which has only a getter [code: 10021]`), rejecting the whole Worker, including consumers that never take a lock. - `verify:package` now fails on any static import of a module that patches a Node builtin, because `wrangler deploy --dry-run` bundles without executing and cannot see this class of failure. - -## 6.1.8 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.134.2` so knowledge evaluation resolves complete multishot judge cost accounting without installing an older Eval copy. - -## 6.1.7 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.134.1` so Knowledge consumers resolve the corrected Eval implementation without installing an older copy. - -## 6.1.6 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.134.0` so Knowledge and Runtime share one explicit proposal-finding contract without installing an older Eval copy. - -## 6.1.5 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.133.3` so knowledge evaluation and promotion use the corrected exact, Student-t, rank-test, and Welch implementations. - -## 6.1.4 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.133.2` for final-evaluation data isolation, lazy OpenCode SQLite loading, and fail-closed paired comparisons. -- Increased the official optimizer check's pip timeout and retries so slow package downloads do not fail an otherwise valid release. - -## 6.1.3 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.133.1` for corrected normal-approximation statistics. - -## 6.1.2 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.133.0` and `@tangle-network/agent-interface` to `0.36.0`. - -## 6.1.1 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.131.0` while retaining the exact `@tangle-network/agent-interface` `0.35.0` contract. -- Extended packed-package verification to confirm both installed agent stack dependencies match the package manifest. - -## 6.1.0 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.130.1`, `@tangle-network/agent-interface` to `0.35.0`, Mem0 to `3.1.2`, and the maintained build toolchain. -- Replaced tsup with tsdown so declaration builds support TypeScript 7. -- Added package export validation with publint and Are the Types Wrong to every release. -- Split Mem0 hosted and OSS client contracts, compile them against Mem0 3.1.2, and exercise the OSS SQLite lifecycle locally. -- Updated GitHub Actions to their current stable releases. - -## 6.0.0 - -### Breaking Changes - -- Renamed the verified research entry point to `runVerifiedResearchLoop` and removed the old two-agent function, option, result, round, and module names. -- Updated `@tangle-network/agent-eval` to `0.129.0` and `@tangle-network/agent-interface` to `0.34.0`. - -## 5.0.4 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.128.2` for canonical task-failure imports, reports, validation, and the exact Core 0.4.21 and Interface 0.33 dependencies. -- Updated `@tangle-network/agent-interface` to `0.33.0` so Knowledge and Runtime share the current certified context contract. - -## 5.0.3 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.127.0` and adopted its explicit run outcome, cost provenance, and scenario identity contract. -- Allowed filesystem-heavy lifecycle tests enough time to complete under shared-runner load. - -## 5.0.2 - -### Changed - -- Updated the maintained Neo4j Agent Memory and Mem0 adapters plus compatible build and formatting dependencies. -- Aligned the official optimizer bridge with `@tangle-network/agent-eval@0.126.7`. -- Forced patched Hono, Node server, WebSocket, Vite, and esbuild releases so the installed graph has no known npm advisories. - -## 5.0.1 - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.126.6` so Knowledge and Runtime use the same optimizer provenance contract. - -## 5.0.0 - -### Breaking Changes - -- Retrieval improvement now requires independent train, selection, and final scenarios plus an explicit complete `OptimizationMethod`. -- Serialized retrieval and RAG optimization now requires an immutable `executionRef` covering candidate execution and scoring behavior. -- Memory configuration improvement now requires a baseline configuration, a complete `OptimizationMethod`, and independent train, selection, and final histories. -- The RAG lifecycle promotion callback is now `decidePromotion` and runs only after final evidence passes regression, provenance, and cost checks. -- Knowledge improvement requires an immutable `implementationRef`, separates repeatable development evaluation from single-use final evaluation, and refuses to resume after interrupted final scoring. -- Memory candidate factories no longer receive scenario, repetition, or seed identity and must report observed external charges through `recordExternalCost()`. -- Answer-quality hooks require immutable evaluator identity, final scenario identity, and complete cost evidence. -- Removed the public retrieval and memory proposer-search options; candidate generation and selection now belong to `agent-eval` methods. - -### Added - -- Added a shared serialized-candidate adapter for running complete `agent-eval` optimization methods with canonical candidate identity and untouched final comparison. -- Added full RAG configuration optimization and KB maintenance policy optimization. -- Added direct support for official GEPA and SkillOpt methods through the shared `OptimizationMethod` contract. -- Added durable per-configuration memory candidate identities to prevent stale result reuse. -- Added live activation verification so resumed memory runs reject configuration drift. -- Added private execution contexts that expose memory operations, cancellation, and cost metering without evaluation labels. - -### Changed - -- Updated `@tangle-network/agent-eval` to `0.126.5` and `@tangle-network/agent-interface` to `0.32.0`. -- Kept memory provider evaluations resumable and branch-isolated while moving search ownership to the supplied method. -- Restricted immutable references to lowercase SHA-256 and full Git commit identities. - -## 4.1.0 - -### Added - -- Added optional scenario input to `knowledgeReleaseReport()` so a required holdout can prove both scenario and run coverage. - -### Changed - -- Split knowledge-base improvement and RAG evaluation internals into focused modules while preserving public exports and implementation behavior. -- Split the knowledge improvement tests into candidate, promotion, activation, and integrity suites with shared setup in one support module. -- Removed unused development dependencies and internal-only exports. -- Updated the test runner to Vitest 4 and Node type definitions to 26; TypeScript remains on 5.9 because tsup's declaration bundler does not yet support TypeScript 7. -- Declared Node types explicitly in TypeScript configuration instead of relying on ambient type discovery. -- Corrected the package dependency guide and RAG roadmap to reflect runtime-owned agent execution through `runKnowledgeImprovementJob()`. -- Removed historical commentary and em dashes from repository documentation. - -### Fixed - -- Replaced the stale versioned HTTP user agent with a stable package identity and added request-header coverage. - -## 4.0.1 - -### Changed - -- Split benchmark, memory experiment, and memory improvement internals into focused modules while preserving every public export and signature. -- Split memory and benchmark tests by behavior, with shared controller and adapter fixtures kept in one test-support module. -- Moved retrieval holdout contracts into the memory type layer to remove the holdout/types import cycle. - -### Fixed - -- Made the Mem0 deletion-convergence test deterministic under file-level parallel execution. - -## 4.0.0 - -### Breaking Changes - -- `runAgentMemoryImprovement()` now activates a measured winner through `activation.readCurrent()` and atomic `activation.compareAndSet()` instead of `onPromote`. -- `AgentMemoryActivation.receiptPath` is now `journalPath` because the file is an append-only activation record. -- `mem0MemoryAdapterIdentity()` and `graphitiMemoryAdapterIdentity()` require a stable, non-secret `backendRef` so two deployments cannot share a cache identity. -- Mem0 hosted mode now follows the synchronous array response from `mem0ai` 3.x; the unsupported queued-event options were removed. -- Paid benchmark and improvement work now defaults to a zero dollar limit and requires an explicit `costCeiling` or `maxTotalCostUsd`. -- In-flight run directories from releases before 4.0 are not migrated; archive or clear them before upgrading because 4.0 rejects older attempt records. - -### Added - -- Official-client adapters for Mem0 hosted and open-source deployments, Graphiti MCP, and Neo4j Agent Memory. -- Isolated memory branches with snapshots, replayable forks, private, team, and shared visibility, and ordered writes per agent. -- Parallel multi-track memory experiments and configuration search on `agent-eval`, with fresh-history comparison before activation. -- Durable controller ownership, interrupted-attempt cleanup, retired-candidate recovery, bounded cleanup work, and conservative recovery cost reconciliation. -- Complete candidate cost attribution across interrupted retries, plus explicit unranked recovery spend for retired benchmark candidates. -- Exact scoped Mem0 deletion with list and search convergence checks. - -### Fixed - -- Mem0 and direct Neo4j operations reject provider scopes they cannot enforce before any provider call. -- Hosted Mem0 `appId` is an additional filter and cannot authorize an unscoped whole-application read or delete. -- Mem0 cleanup tracks fresh writes until they become visible and confirms deletion from both list and search indexes. -- Broader Mem0 cleanup scopes wait for delayed writes created under matching narrower scopes. -- Mem0 pending-write probes expire after the configured visibility window instead of accumulating for the adapter lifetime. -- Direct Neo4j reasoning writes reject combined session and run scopes because the SDK can enforce only one conversation identifier. -- Timed-out provider work blocks close or reuse of the same adapter until the original operation settles. -- Recovery retries are reserved durably before provider work and stop after three failures per attempt by default. -- Direct memory benchmarks account for billable adapter provisioning and reconnects in the shared dollar limit. -- Fully cached benchmark resumes skip adapter creation and add no provider charge. -- Execute and recovery adapter factories receive abort signals and are bounded by the configured timeout. -- Adapters returned after a timed-out factory call are closed, and experiment adapters also run their configured disposal callback. -- Reported dollar totals are normalized to twelve decimal places instead of exposing binary floating-point artifacts. From 197c5071a0314bba4bbfb2295657a1139800753d Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 07:47:19 -0700 Subject: [PATCH 11/18] ci: surface knowledge provenance in the quickstart --- .../surface-knowledge-receipts-readme.yml | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 .github/workflows/surface-knowledge-receipts-readme.yml diff --git a/.github/workflows/surface-knowledge-receipts-readme.yml b/.github/workflows/surface-knowledge-receipts-readme.yml new file mode 100644 index 0000000..28d5a88 --- /dev/null +++ b/.github/workflows/surface-knowledge-receipts-readme.yml @@ -0,0 +1,80 @@ +name: Surface knowledge receipt documentation + +on: + push: + branches: [feat/knowledge-use-receipts-v1] + +permissions: + contents: write + +jobs: + docs: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: feat/knowledge-use-receipts-v1 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Add the receipt path to the top-level README + run: | + node <<'EOF' + const fs = require('node:fs') + const path = 'README.md' + let source = fs.readFileSync(path, 'utf8') + + function replaceOnce(needle, replacement, label) { + const first = source.indexOf(needle) + if (first < 0) throw new Error(`${label}: anchor not found`) + if (source.indexOf(needle, first + needle.length) >= 0) { + throw new Error(`${label}: anchor is not unique`) + } + source = source.replace(needle, replacement) + } + + replaceOnce( + 'pnpm add @tangle-network/agent-knowledge@7.2.6 @tangle-network/agent-eval@0.145.11 @tangle-network/agent-interface@0.52.0', + 'pnpm add @tangle-network/agent-knowledge@8.0.8 @tangle-network/agent-eval@0.147.0 @tangle-network/agent-interface@1.0.0', + 'install versions', + ) + + replaceOnce( + '| Search an existing package knowledge base | `createFileSystemSearchProvider` | package root |\n', + '| Search an existing package knowledge base | `createFileSystemSearchProvider` | package root |\n| Isolate knowledge per run and inherit only declared ancestry | `createRunScopedStores` | package root |\n| Prove what knowledge was visible, retrieved, and selected for use | `createKnowledgeRetrievalReceipt`, `createKnowledgeUseReceipt` | package root |\n', + 'API chooser rows', + ) + + const searchAnchor = + 'Use `asRetrievalEvalRetriever()` to send the same search path into retrieval tests.\n' + const receiptSection = `\n## Prove what the agent saw and used\n\nA page existing in a knowledge base, a page appearing in retrieval results, and a page influencing a decision are three different facts. The receipt APIs preserve those joins without pretending they prove the page is true or that it improved the outcome.\n\n```ts\nimport {\n createKnowledgeRetrievalReceipt,\n createKnowledgeUseReceipt,\n createKnowledgeVisibilitySnapshot,\n} from '@tangle-network/agent-knowledge'\n\nconst visiblePages = await runStores.loadChain(runId)\nconst visibility = createKnowledgeVisibilitySnapshot(visiblePages)\n\nconst retrieval = createKnowledgeRetrievalReceipt({\n runId,\n query: 'prior verifier obstruction',\n retriever: { id: 'hybrid-search', version: '1.0.0', configDigest },\n visiblePages,\n results,\n})\n\nconst use = createKnowledgeUseReceipt({\n retrieval,\n selectedRank: 1,\n relation: 'extends',\n consumer: { kind: 'artifact', uri: 'artifact://run/DECISION.md', digest },\n})\n\nconsole.log(visibility.snapshotDigest, retrieval.receiptDigest, use.receiptDigest)\n```\n\nELI5: the visibility snapshot is the bookshelf the agent was allowed to see, the retrieval receipt is the exact books search handed back, and the use receipt records which returned book the agent attached to a downstream decision or artifact.\n\nThe receipts are content-addressed and mutation-sensitive. They do **not** establish correctness, novelty, compliance, or causal lift; Eval owns those later judgments. Read [knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the complete proof boundary, trace attributes, and experiment design.\n` + if (!source.includes('## Prove what the agent saw and used')) { + replaceOnce(searchAnchor, `${searchAnchor}${receiptSection}`, 'receipt README section') + } + + replaceOnce( + '- [Architecture and data model](docs/architecture.md)\n', + '- [Architecture and data model](docs/architecture.md)\n- [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md)\n', + 'more detail receipt link', + ) + + fs.writeFileSync(path, source) + EOF + - name: Validate documentation and package + run: | + pnpm lint + pnpm typecheck + pnpm test -- src/knowledge-use-receipts.test.ts + pnpm build + - name: Commit and remove this one-time workflow + run: | + rm .github/workflows/surface-knowledge-receipts-readme.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet || git commit -m "docs(evidence): add the knowledge provenance quickstart" + git push From 9c515d1a878bd0cddf882bb3d925f0a2411a03cf Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 07:52:09 -0700 Subject: [PATCH 12/18] ci: repair knowledge provenance README update --- .../surface-knowledge-receipts-readme.yml | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/.github/workflows/surface-knowledge-receipts-readme.yml b/.github/workflows/surface-knowledge-receipts-readme.yml index 28d5a88..9841323 100644 --- a/.github/workflows/surface-knowledge-receipts-readme.yml +++ b/.github/workflows/surface-knowledge-receipts-readme.yml @@ -51,7 +51,45 @@ jobs: const searchAnchor = 'Use `asRetrievalEvalRetriever()` to send the same search path into retrieval tests.\n' - const receiptSection = `\n## Prove what the agent saw and used\n\nA page existing in a knowledge base, a page appearing in retrieval results, and a page influencing a decision are three different facts. The receipt APIs preserve those joins without pretending they prove the page is true or that it improved the outcome.\n\n```ts\nimport {\n createKnowledgeRetrievalReceipt,\n createKnowledgeUseReceipt,\n createKnowledgeVisibilitySnapshot,\n} from '@tangle-network/agent-knowledge'\n\nconst visiblePages = await runStores.loadChain(runId)\nconst visibility = createKnowledgeVisibilitySnapshot(visiblePages)\n\nconst retrieval = createKnowledgeRetrievalReceipt({\n runId,\n query: 'prior verifier obstruction',\n retriever: { id: 'hybrid-search', version: '1.0.0', configDigest },\n visiblePages,\n results,\n})\n\nconst use = createKnowledgeUseReceipt({\n retrieval,\n selectedRank: 1,\n relation: 'extends',\n consumer: { kind: 'artifact', uri: 'artifact://run/DECISION.md', digest },\n})\n\nconsole.log(visibility.snapshotDigest, retrieval.receiptDigest, use.receiptDigest)\n```\n\nELI5: the visibility snapshot is the bookshelf the agent was allowed to see, the retrieval receipt is the exact books search handed back, and the use receipt records which returned book the agent attached to a downstream decision or artifact.\n\nThe receipts are content-addressed and mutation-sensitive. They do **not** establish correctness, novelty, compliance, or causal lift; Eval owns those later judgments. Read [knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the complete proof boundary, trace attributes, and experiment design.\n` + const receiptSection = [ + '', + '## Prove what the agent saw and used', + '', + 'A page existing in a knowledge base, a page appearing in retrieval results, and a page influencing a decision are three different facts. The receipt APIs preserve those joins without pretending they prove the page is true or that it improved the outcome.', + '', + '```ts', + 'import {', + ' createKnowledgeRetrievalReceipt,', + ' createKnowledgeUseReceipt,', + ' createKnowledgeVisibilitySnapshot,', + "} from '@tangle-network/agent-knowledge'", + '', + 'const visiblePages = await runStores.loadChain(runId)', + 'const visibility = createKnowledgeVisibilitySnapshot(visiblePages)', + '', + 'const retrieval = createKnowledgeRetrievalReceipt({', + ' runId,', + " query: 'prior verifier obstruction',", + " retriever: { id: 'hybrid-search', version: '1.0.0', configDigest },", + ' visiblePages,', + ' results,', + '})', + '', + 'const use = createKnowledgeUseReceipt({', + ' retrieval,', + ' selectedRank: 1,', + " relation: 'extends',", + " consumer: { kind: 'artifact', uri: 'artifact://run/DECISION.md', digest },", + '})', + '', + 'console.log(visibility.snapshotDigest, retrieval.receiptDigest, use.receiptDigest)', + '```', + '', + 'ELI5: the visibility snapshot is the bookshelf the agent was allowed to see, the retrieval receipt is the exact books search handed back, and the use receipt records which returned book the agent attached to a downstream decision or artifact.', + '', + 'The receipts are content-addressed and mutation-sensitive. They do **not** establish correctness, novelty, compliance, or causal lift; Eval owns those later judgments. Read [knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the complete proof boundary, trace attributes, and experiment design.', + '', + ].join('\n') if (!source.includes('## Prove what the agent saw and used')) { replaceOnce(searchAnchor, `${searchAnchor}${receiptSection}`, 'receipt README section') } From 36f53ed0cd2697f2eb4749d9860177eacbde11f1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 14:53:14 +0000 Subject: [PATCH 13/18] docs(evidence): add the knowledge provenance quickstart --- .../surface-knowledge-receipts-readme.yml | 118 ------------------ README.md | 41 +++++- 2 files changed, 40 insertions(+), 119 deletions(-) delete mode 100644 .github/workflows/surface-knowledge-receipts-readme.yml diff --git a/.github/workflows/surface-knowledge-receipts-readme.yml b/.github/workflows/surface-knowledge-receipts-readme.yml deleted file mode 100644 index 9841323..0000000 --- a/.github/workflows/surface-knowledge-receipts-readme.yml +++ /dev/null @@ -1,118 +0,0 @@ -name: Surface knowledge receipt documentation - -on: - push: - branches: [feat/knowledge-use-receipts-v1] - -permissions: - contents: write - -jobs: - docs: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: feat/knowledge-use-receipts-v1 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 - with: - node-version: 22 - cache: pnpm - - run: pnpm install --frozen-lockfile - - name: Add the receipt path to the top-level README - run: | - node <<'EOF' - const fs = require('node:fs') - const path = 'README.md' - let source = fs.readFileSync(path, 'utf8') - - function replaceOnce(needle, replacement, label) { - const first = source.indexOf(needle) - if (first < 0) throw new Error(`${label}: anchor not found`) - if (source.indexOf(needle, first + needle.length) >= 0) { - throw new Error(`${label}: anchor is not unique`) - } - source = source.replace(needle, replacement) - } - - replaceOnce( - 'pnpm add @tangle-network/agent-knowledge@7.2.6 @tangle-network/agent-eval@0.145.11 @tangle-network/agent-interface@0.52.0', - 'pnpm add @tangle-network/agent-knowledge@8.0.8 @tangle-network/agent-eval@0.147.0 @tangle-network/agent-interface@1.0.0', - 'install versions', - ) - - replaceOnce( - '| Search an existing package knowledge base | `createFileSystemSearchProvider` | package root |\n', - '| Search an existing package knowledge base | `createFileSystemSearchProvider` | package root |\n| Isolate knowledge per run and inherit only declared ancestry | `createRunScopedStores` | package root |\n| Prove what knowledge was visible, retrieved, and selected for use | `createKnowledgeRetrievalReceipt`, `createKnowledgeUseReceipt` | package root |\n', - 'API chooser rows', - ) - - const searchAnchor = - 'Use `asRetrievalEvalRetriever()` to send the same search path into retrieval tests.\n' - const receiptSection = [ - '', - '## Prove what the agent saw and used', - '', - 'A page existing in a knowledge base, a page appearing in retrieval results, and a page influencing a decision are three different facts. The receipt APIs preserve those joins without pretending they prove the page is true or that it improved the outcome.', - '', - '```ts', - 'import {', - ' createKnowledgeRetrievalReceipt,', - ' createKnowledgeUseReceipt,', - ' createKnowledgeVisibilitySnapshot,', - "} from '@tangle-network/agent-knowledge'", - '', - 'const visiblePages = await runStores.loadChain(runId)', - 'const visibility = createKnowledgeVisibilitySnapshot(visiblePages)', - '', - 'const retrieval = createKnowledgeRetrievalReceipt({', - ' runId,', - " query: 'prior verifier obstruction',", - " retriever: { id: 'hybrid-search', version: '1.0.0', configDigest },", - ' visiblePages,', - ' results,', - '})', - '', - 'const use = createKnowledgeUseReceipt({', - ' retrieval,', - ' selectedRank: 1,', - " relation: 'extends',", - " consumer: { kind: 'artifact', uri: 'artifact://run/DECISION.md', digest },", - '})', - '', - 'console.log(visibility.snapshotDigest, retrieval.receiptDigest, use.receiptDigest)', - '```', - '', - 'ELI5: the visibility snapshot is the bookshelf the agent was allowed to see, the retrieval receipt is the exact books search handed back, and the use receipt records which returned book the agent attached to a downstream decision or artifact.', - '', - 'The receipts are content-addressed and mutation-sensitive. They do **not** establish correctness, novelty, compliance, or causal lift; Eval owns those later judgments. Read [knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the complete proof boundary, trace attributes, and experiment design.', - '', - ].join('\n') - if (!source.includes('## Prove what the agent saw and used')) { - replaceOnce(searchAnchor, `${searchAnchor}${receiptSection}`, 'receipt README section') - } - - replaceOnce( - '- [Architecture and data model](docs/architecture.md)\n', - '- [Architecture and data model](docs/architecture.md)\n- [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md)\n', - 'more detail receipt link', - ) - - fs.writeFileSync(path, source) - EOF - - name: Validate documentation and package - run: | - pnpm lint - pnpm typecheck - pnpm test -- src/knowledge-use-receipts.test.ts - pnpm build - - name: Commit and remove this one-time workflow - run: | - rm .github/workflows/surface-knowledge-receipts-readme.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --quiet || git commit -m "docs(evidence): add the knowledge provenance quickstart" - git push diff --git a/README.md b/README.md index 3b4aa1a..06260a4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Supply application callbacks for those decisions, or use `@tangle-network/agent- ## Install ```bash -pnpm add @tangle-network/agent-knowledge@7.2.6 @tangle-network/agent-eval@0.145.11 @tangle-network/agent-interface@0.52.0 +pnpm add @tangle-network/agent-knowledge@8.0.8 @tangle-network/agent-eval@0.147.0 @tangle-network/agent-interface@1.0.0 ``` Requires Node.js 20.19 or later. @@ -20,6 +20,8 @@ Requires Node.js 20.19 or later. |---|---|---| | Create a file-backed knowledge base | `initKnowledgeBase`, `addSourceText`, `applyKnowledgeWriteBlocks` | package root | | Search an existing package knowledge base | `createFileSystemSearchProvider` | package root | +| Isolate knowledge per run and inherit only declared ancestry | `createRunScopedStores` | package root | +| Prove what knowledge was visible, retrieved, and selected for use | `createKnowledgeRetrievalReceipt`, `createKnowledgeUseReceipt` | package root | | Improve a live knowledge base without editing it in place | `improveKnowledgeBase` | package root | | Optimize retrieval or a complete RAG configuration | `runRetrievalImprovementLoop`, `runRagOptimization` | package root | | Optimize a KB maintenance policy | `optimizeKnowledgeBasePolicy` | package root | @@ -80,6 +82,42 @@ The provider uses the package's local text search. Pass `refresh: 'always'` to rebuild its index before every query, or call `invalidate()` after changing files. Use `asRetrievalEvalRetriever()` to send the same search path into retrieval tests. +## Prove what the agent saw and used + +A page existing in a knowledge base, a page appearing in retrieval results, and a page influencing a decision are three different facts. The receipt APIs preserve those joins without pretending they prove the page is true or that it improved the outcome. + +```ts +import { + createKnowledgeRetrievalReceipt, + createKnowledgeUseReceipt, + createKnowledgeVisibilitySnapshot, +} from '@tangle-network/agent-knowledge' + +const visiblePages = await runStores.loadChain(runId) +const visibility = createKnowledgeVisibilitySnapshot(visiblePages) + +const retrieval = createKnowledgeRetrievalReceipt({ + runId, + query: 'prior verifier obstruction', + retriever: { id: 'hybrid-search', version: '1.0.0', configDigest }, + visiblePages, + results, +}) + +const use = createKnowledgeUseReceipt({ + retrieval, + selectedRank: 1, + relation: 'extends', + consumer: { kind: 'artifact', uri: 'artifact://run/DECISION.md', digest }, +}) + +console.log(visibility.snapshotDigest, retrieval.receiptDigest, use.receiptDigest) +``` + +ELI5: the visibility snapshot is the bookshelf the agent was allowed to see, the retrieval receipt is the exact books search handed back, and the use receipt records which returned book the agent attached to a downstream decision or artifact. + +The receipts are content-addressed and mutation-sensitive. They do **not** establish correctness, novelty, compliance, or causal lift; Eval owns those later judgments. Read [knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the complete proof boundary, trace attributes, and experiment design. + ## Use the CLI The CLI exposes the same file-backed workflow. @@ -300,6 +338,7 @@ Those choices stay in the application or in `@tangle-network/agent-runtime`. ## More detail - [Architecture and data model](docs/architecture.md) +- [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) - [Verified research comparison](docs/verified-research-ab.md) - [Changelog](CHANGELOG.md) From 140160b6093301d9ca31b2881e4d7a6154b0a948 Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 08:36:04 -0700 Subject: [PATCH 14/18] ci: polish knowledge receipt onboarding --- .../workflows/polish-knowledge-receipt-dx.yml | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .github/workflows/polish-knowledge-receipt-dx.yml diff --git a/.github/workflows/polish-knowledge-receipt-dx.yml b/.github/workflows/polish-knowledge-receipt-dx.yml new file mode 100644 index 0000000..0dec5c6 --- /dev/null +++ b/.github/workflows/polish-knowledge-receipt-dx.yml @@ -0,0 +1,52 @@ +name: Polish knowledge receipt onboarding + +on: + push: + branches: [feat/knowledge-use-receipts-v1] + +permissions: + contents: write + +jobs: + polish: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: feat/knowledge-use-receipts-v1 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Add the proof chain and golden path to the README + run: | + node <<'EOF' + const fs = require('node:fs') + const path = 'README.md' + let source = fs.readFileSync(path, 'utf8') + if (!source.includes('## Proving knowledge retrieval and use')) { + source += `\n## Proving knowledge retrieval and use\n\nA page being visible, a retriever returning it, and an agent using it are different facts. Knowledge now exposes content-addressed visibility, retrieval, and use receipts that bind the exact page version and downstream consumer without claiming the page is true or that it improved the outcome.\n\n\`createKnowledgeVisibilitySnapshot()\` records the ordered current/ancestor/shared view; \`createKnowledgeRetrievalReceipt()\` binds the exact query, retriever configuration, and ranked results; \`createKnowledgeUseReceipt()\` binds one returned rank to a decision, artifact, experiment, candidate, or message. Verifiers fail on changed page bytes, origin, order, query, selected rank, relation, or consumer identity.\n\nSee [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the ELI5 proof chain, canonical serialization rules, trace attributes, proof limits, and the causal-experiment recipe.\n` + } + fs.writeFileSync(path, source) + EOF + pnpm biome check --write README.md docs/knowledge-use-receipts.md src/knowledge-use-receipts.ts src/knowledge-use-receipts.test.ts + - name: Validate the complete package and public surface + run: | + pnpm lint + pnpm typecheck + pnpm test + pnpm build + pnpm verify:package + - name: Commit and remove one-time repair workflows + run: | + rm -f .github/workflows/polish-knowledge-receipt-dx.yml + rm -f .github/workflows/repair-knowledge-use-receipts.yml + rm -f .github/workflows/format-knowledge-use-receipts.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet || git commit -m "docs(dx): make the retrieval-to-use proof chain obvious" + git push From a9296cea83d725237f6ce519533424548e31187b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:38:02 +0000 Subject: [PATCH 15/18] docs(dx): make the retrieval-to-use proof chain obvious --- .../workflows/polish-knowledge-receipt-dx.yml | 52 ------------------- README.md | 8 +++ 2 files changed, 8 insertions(+), 52 deletions(-) delete mode 100644 .github/workflows/polish-knowledge-receipt-dx.yml diff --git a/.github/workflows/polish-knowledge-receipt-dx.yml b/.github/workflows/polish-knowledge-receipt-dx.yml deleted file mode 100644 index 0dec5c6..0000000 --- a/.github/workflows/polish-knowledge-receipt-dx.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: Polish knowledge receipt onboarding - -on: - push: - branches: [feat/knowledge-use-receipts-v1] - -permissions: - contents: write - -jobs: - polish: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: feat/knowledge-use-receipts-v1 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 - with: - node-version: 22 - cache: pnpm - - run: pnpm install --frozen-lockfile - - name: Add the proof chain and golden path to the README - run: | - node <<'EOF' - const fs = require('node:fs') - const path = 'README.md' - let source = fs.readFileSync(path, 'utf8') - if (!source.includes('## Proving knowledge retrieval and use')) { - source += `\n## Proving knowledge retrieval and use\n\nA page being visible, a retriever returning it, and an agent using it are different facts. Knowledge now exposes content-addressed visibility, retrieval, and use receipts that bind the exact page version and downstream consumer without claiming the page is true or that it improved the outcome.\n\n\`createKnowledgeVisibilitySnapshot()\` records the ordered current/ancestor/shared view; \`createKnowledgeRetrievalReceipt()\` binds the exact query, retriever configuration, and ranked results; \`createKnowledgeUseReceipt()\` binds one returned rank to a decision, artifact, experiment, candidate, or message. Verifiers fail on changed page bytes, origin, order, query, selected rank, relation, or consumer identity.\n\nSee [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the ELI5 proof chain, canonical serialization rules, trace attributes, proof limits, and the causal-experiment recipe.\n` - } - fs.writeFileSync(path, source) - EOF - pnpm biome check --write README.md docs/knowledge-use-receipts.md src/knowledge-use-receipts.ts src/knowledge-use-receipts.test.ts - - name: Validate the complete package and public surface - run: | - pnpm lint - pnpm typecheck - pnpm test - pnpm build - pnpm verify:package - - name: Commit and remove one-time repair workflows - run: | - rm -f .github/workflows/polish-knowledge-receipt-dx.yml - rm -f .github/workflows/repair-knowledge-use-receipts.yml - rm -f .github/workflows/format-knowledge-use-receipts.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --quiet || git commit -m "docs(dx): make the retrieval-to-use proof chain obvious" - git push diff --git a/README.md b/README.md index 06260a4..f981bc0 100644 --- a/README.md +++ b/README.md @@ -345,3 +345,11 @@ Those choices stay in the application or in `@tangle-network/agent-runtime`. ## License MIT + +## Proving knowledge retrieval and use + +A page being visible, a retriever returning it, and an agent using it are different facts. Knowledge now exposes content-addressed visibility, retrieval, and use receipts that bind the exact page version and downstream consumer without claiming the page is true or that it improved the outcome. + +`createKnowledgeVisibilitySnapshot()` records the ordered current/ancestor/shared view; `createKnowledgeRetrievalReceipt()` binds the exact query, retriever configuration, and ranked results; `createKnowledgeUseReceipt()` binds one returned rank to a decision, artifact, experiment, candidate, or message. Verifiers fail on changed page bytes, origin, order, query, selected rank, relation, or consumer identity. + +See [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the ELI5 proof chain, canonical serialization rules, trace attributes, proof limits, and the causal-experiment recipe. From a3ff9a8df4f2e8e087e4ac077d24824761edac43 Mon Sep 17 00:00:00 2001 From: drewstone Date: Mon, 17 Aug 2026 08:38:16 -0700 Subject: [PATCH 16/18] ci: audit knowledge receipt DX and package lifecycle --- .../workflows/audit-knowledge-receipt-dx.yml | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/audit-knowledge-receipt-dx.yml diff --git a/.github/workflows/audit-knowledge-receipt-dx.yml b/.github/workflows/audit-knowledge-receipt-dx.yml new file mode 100644 index 0000000..184aaf7 --- /dev/null +++ b/.github/workflows/audit-knowledge-receipt-dx.yml @@ -0,0 +1,66 @@ +name: Audit knowledge receipt DX + +on: + push: + branches: [feat/knowledge-use-receipts-v1] + +permissions: + contents: write + +jobs: + audit: + if: github.actor != 'github-actions[bot]' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + ref: feat/knowledge-use-receipts-v1 + - uses: pnpm/action-setup@v6 + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Ensure README onboarding exists + run: | + node <<'EOF' + const fs = require('node:fs') + const path = 'README.md' + let source = fs.readFileSync(path, 'utf8') + if (!source.includes('## Proving knowledge retrieval and use')) { + source += `\n## Proving knowledge retrieval and use\n\nA page being visible, a retriever returning it, and an agent using it are different facts. Knowledge exposes content-addressed visibility, retrieval, and use receipts that bind the exact page version and downstream consumer without claiming that the page is true or that it improved the outcome.\n\n\`createKnowledgeVisibilitySnapshot()\` records the ordered current/ancestor/shared view; \`createKnowledgeRetrievalReceipt()\` binds the exact query, retriever configuration, and ranked results; \`createKnowledgeUseReceipt()\` binds one returned rank to a decision, artifact, experiment, candidate, or message. Verifiers fail on changed page bytes, origin, order, query, selected rank, relation, or consumer identity.\n\nSee [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the ELI5 proof chain, canonical serialization rules, trace attributes, proof limits, and causal-experiment recipe.\n` + } + fs.writeFileSync(path, source) + EOF + pnpm biome check --write README.md docs/knowledge-use-receipts.md src/knowledge-use-receipts.ts src/knowledge-use-receipts.test.ts + - name: Validate package behavior + run: | + pnpm lint + pnpm typecheck + pnpm test + pnpm build + - name: Validate the packed-package lifecycle when the repository declares it + run: | + node <<'EOF' + const { spawnSync } = require('node:child_process') + const pkg = require('./package.json') + const candidates = ['verify:package', 'verify-package', 'verify:pack', 'pack:verify'] + const script = candidates.find((name) => pkg.scripts?.[name]) + if (!script) { + console.log('No dedicated package-verification script declared; build and full test already passed.') + process.exit(0) + } + const result = spawnSync('pnpm', [script], { stdio: 'inherit', shell: process.platform === 'win32' }) + process.exit(result.status ?? 1) + EOF + - name: Commit and remove one-time workflows + run: | + rm -f .github/workflows/audit-knowledge-receipt-dx.yml + rm -f .github/workflows/polish-knowledge-receipt-dx.yml + rm -f .github/workflows/repair-knowledge-use-receipts.yml + rm -f .github/workflows/format-knowledge-use-receipts.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet || git commit -m "docs(dx): make the retrieval-to-use proof chain obvious" + git push From a9028df0ef66b6765fb42c315949144e81b0091c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:40:16 +0000 Subject: [PATCH 17/18] docs(dx): make the retrieval-to-use proof chain obvious --- .../workflows/audit-knowledge-receipt-dx.yml | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 .github/workflows/audit-knowledge-receipt-dx.yml diff --git a/.github/workflows/audit-knowledge-receipt-dx.yml b/.github/workflows/audit-knowledge-receipt-dx.yml deleted file mode 100644 index 184aaf7..0000000 --- a/.github/workflows/audit-knowledge-receipt-dx.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Audit knowledge receipt DX - -on: - push: - branches: [feat/knowledge-use-receipts-v1] - -permissions: - contents: write - -jobs: - audit: - if: github.actor != 'github-actions[bot]' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v7 - with: - ref: feat/knowledge-use-receipts-v1 - - uses: pnpm/action-setup@v6 - - uses: actions/setup-node@v7 - with: - node-version: 22 - cache: pnpm - - run: pnpm install --frozen-lockfile - - name: Ensure README onboarding exists - run: | - node <<'EOF' - const fs = require('node:fs') - const path = 'README.md' - let source = fs.readFileSync(path, 'utf8') - if (!source.includes('## Proving knowledge retrieval and use')) { - source += `\n## Proving knowledge retrieval and use\n\nA page being visible, a retriever returning it, and an agent using it are different facts. Knowledge exposes content-addressed visibility, retrieval, and use receipts that bind the exact page version and downstream consumer without claiming that the page is true or that it improved the outcome.\n\n\`createKnowledgeVisibilitySnapshot()\` records the ordered current/ancestor/shared view; \`createKnowledgeRetrievalReceipt()\` binds the exact query, retriever configuration, and ranked results; \`createKnowledgeUseReceipt()\` binds one returned rank to a decision, artifact, experiment, candidate, or message. Verifiers fail on changed page bytes, origin, order, query, selected rank, relation, or consumer identity.\n\nSee [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the ELI5 proof chain, canonical serialization rules, trace attributes, proof limits, and causal-experiment recipe.\n` - } - fs.writeFileSync(path, source) - EOF - pnpm biome check --write README.md docs/knowledge-use-receipts.md src/knowledge-use-receipts.ts src/knowledge-use-receipts.test.ts - - name: Validate package behavior - run: | - pnpm lint - pnpm typecheck - pnpm test - pnpm build - - name: Validate the packed-package lifecycle when the repository declares it - run: | - node <<'EOF' - const { spawnSync } = require('node:child_process') - const pkg = require('./package.json') - const candidates = ['verify:package', 'verify-package', 'verify:pack', 'pack:verify'] - const script = candidates.find((name) => pkg.scripts?.[name]) - if (!script) { - console.log('No dedicated package-verification script declared; build and full test already passed.') - process.exit(0) - } - const result = spawnSync('pnpm', [script], { stdio: 'inherit', shell: process.platform === 'win32' }) - process.exit(result.status ?? 1) - EOF - - name: Commit and remove one-time workflows - run: | - rm -f .github/workflows/audit-knowledge-receipt-dx.yml - rm -f .github/workflows/polish-knowledge-receipt-dx.yml - rm -f .github/workflows/repair-knowledge-use-receipts.yml - rm -f .github/workflows/format-knowledge-use-receipts.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --quiet || git commit -m "docs(dx): make the retrieval-to-use proof chain obvious" - git push From 2437bdcbe5e4e27c4b3b820e1a52304853d9b058 Mon Sep 17 00:00:00 2001 From: Drew Stone Date: Mon, 17 Aug 2026 11:19:05 -0600 Subject: [PATCH 18/18] docs(evidence): restore release history and correct the receipt examples Restore the 4.0.0 through 7.2.6 changelog entries that the receipt change deleted. Remove the duplicate receipts section that sat below the License heading. Import canonicalCandidateDigest from agent-interface in the receipts doc; Knowledge does not re-export it. --- CHANGELOG.md | 373 +++++++++++++++++++++++++++++++++ README.md | 8 - docs/knowledge-use-receipts.md | 6 +- 3 files changed, 375 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb097c8..296d029 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,3 +82,376 @@ claim, and recording it at rung 3 is accepted unchanged. reads a value, such as `echo "n=$(grep -c x out.txt)"`, still verifies. - Behaviour below rung 4 does not change. An execution that decided the claim still outranks these refusals: a missing input stays `unrunnable`, and a nonzero exit stays `contradicted`. + +### Added + +- `gradeFor(evidence, execution)` returns `{ verdict, note }`. The note names the refused shape and + tells the author what to record. `verdictFor` keeps its signature and returns the verdict alone. + +## 7.2.6 + +### Changed + +- Require Eval `0.145.11` and Interface `0.52.0` as the current shared contract cohort. +- Bump the exact Eval and Interface development pins to `0.145.11` and `0.52.0`. +- Packed-consumer verification now installs the new cohort and checks one copy of Eval, Core, and Interface. + +## 7.2.5 + +### Changed + +- Require Eval `0.145.10` and Interface `0.49.0` as the current shared contract cohort. +- Bump the exact Eval and Interface development pins to `0.145.10` and `0.49.0`. + +## 7.2.4 + +### Changed + +- Require Eval `0.145.2` and Interface `0.47.0` as the current shared contract cohort. +- Bump the exact Eval and Interface development pins to `0.145.2` and `0.47.0`. + +## 7.2.3 + +### Changed + +- Align the required Eval peer and exact development pin with Eval `0.145.0`. +- Keep the published Knowledge package on one Eval copy after Eval's tiered root-barrel release. + +## 7.2.2 + +### Fixed + +- Import `pairArms` from Eval's `experiment` subpath so the published package works with Eval `0.144.13`. + +## 7.2.1 + +### Changed + +- Updated the required Eval peer and exact development pin to `0.144.11` so consumers install one current shared cohort. + +## 7.2.0 + +### Changed + +- Changed `@tangle-network/agent-eval` and `@tangle-network/agent-interface` to required compatible peers. +- Kept exact development pins at Eval `0.144.10` and Interface `0.46.1`. +- Consumers now select one shared Eval and Interface cohort instead of nested package copies. + +## 7.1.3 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.144.8` so Knowledge and Runtime install the duplicate-safe candidate contract without an older Eval copy. + +## 7.1.2 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.144.6` and `@tangle-network/agent-interface` to `0.46.1`, so Knowledge consumers resolve one canonical interaction-binding contract through Eval, Core, and Interface. + +## 7.1.1 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.144.5` and `@tangle-network/agent-interface` to `0.46.0`, so Knowledge consumes the current profile contract through one exact dependency set. + +## 7.0.11 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.144.4` and `@tangle-network/agent-interface` to `0.43.1`, so Knowledge consumes prompt-cache accounting through one exact Core, Interface, and Eval dependency set. + +## 7.0.10 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.144.3` so Knowledge consumers use exact profile-matrix evidence validation and concurrent profile comparison without installing an older Eval copy. + +## 7.0.9 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.144.1` so Knowledge and Runtime install the same official-optimizer callback contract. + +## 7.0.8 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.143.0` so Knowledge preserves exact observed, estimated, and uncaptured evaluation costs without installing an older Eval copy. + +## 7.0.7 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.142.2` and `@tangle-network/agent-interface` to `0.43.0` so Knowledge, Runtime, and Sandbox use one current canonical profile contract. + +## 7.0.6 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.142.1` and `@tangle-network/agent-interface` to `0.42.1` so one installed stack uses the current shared contracts without nested older copies. + +## 7.0.5 + +### Added + +- Added `runAgentMemoryLearningExperiment` for matched stateful-versus-stateless memory measurement under one shared cost limit. +- Added exact paired gain, explicitly labeled transfer probes, and repeated-probe forgetting reports. +- Limited learning gain to post-first-step probes, averaged repetitions within independent sequences, and added per-candidate intervals. +- Added recorded arm order for counterbalanced runs and exact sequence references for safe crash recovery. +- Added abort and resume support, content-addressed comparison and probe evidence, and exact cell artifact hashes. + +### Changed + +- Memory experiment artifacts and cache identities now record `memoryMode` and a full `comparisonRef`; non-equivalent arms fail comparison. +- Updated `@tangle-network/agent-eval` to `0.142.0` and `@tangle-network/agent-interface` to `0.42.0` so one installed stack uses the same exact evaluation, profile, and interface contracts without older nested copies. +- Extended packed-package verification to reject stack dependency overrides, mismatched transitive versions, and multiple installed copies of Eval, Core, or Interface. + +## 7.0.1 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.135.4` and `@tangle-network/agent-interface` to `0.37.0` so one installed stack uses the same exact trace-ingestion, source-identity, and interface contracts without older nested copies. + +## 7.0.0 + +### Breaking Changes + +- Changed research source confirmation from URI strings to exact `SourceRecord` values and versioned the durable claim ledger schema so every observation is bound to its registry id, original URI, and full SHA-256 content hash. +- URI-only 6.2 ledgers now fail with `ClaimLedgerMigrationRequiredError` and remain untouched for explicit archive-and-reverify migration; they are never guessed into the exact-source schema. + +### Fixed + +- Prevented registering one version of a URI from activating claims extracted from different bytes at that URI, including concurrent writers and crash recovery. +- Snapshotted source proposals before asynchronous work, preserved the exact submitted raw bytes, and used full content hashes in raw-source paths. +- Kept one-sided contradiction observations pending until both claims have exact registered support, preventing a missing counterpart from satisfying completion. + +## 6.2.0 + +### Added + +- Added durable, merge-safe research claim ledgers and `createPersistentResearchDrivingDriver`, preserving corroboration, contradictions, deep questions, and round state across crashes, resumes, and concurrent workers. +- Exported the durable filesystem write primitives used by the reference store so other journaled consumers can reuse the same atomic, symlink-safe writes. + +### Fixed + +- Kept extracted claim evidence pending until its exact source registration is confirmed, and reconciled interrupted registrations on restart so absent sources cannot satisfy completion while registered sources are not lost. +- Routed knowledge indexes, research iteration events, and claim ledgers through the canonical store layout and one mutation-lock domain. + +## 6.1.11 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.135.2` so knowledge improvement uses the corrected paired promotion decisions without installing an older Eval copy. +- Updated the installation example to pin the matching Knowledge and Eval releases. + +## 6.1.10 + +### Fixed + +- Extended packed-package verification to reject bare side-effect imports of packages that patch Node builtins while continuing to allow dynamic imports. + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.135.1` for strict rollout-record validation and stable estimated-cost receipt validation. +- Updated the installation example to pin the matching Knowledge and Eval releases. + +## 6.1.9 + +### Fixed + +- Load `proper-lockfile` with a dynamic import inside the functions that take a lock, instead of at module scope. + It pulls in `graceful-fs`, which patches Node's `fs` at import time (`fs.close = ...`). + workerd exposes those as getter-only accessors, so the assignment threw while Cloudflare validated an uploaded Worker (`Cannot set property close of # which has only a getter [code: 10021]`), rejecting the whole Worker, including consumers that never take a lock. + `verify:package` now fails on any static import of a module that patches a Node builtin, because `wrangler deploy --dry-run` bundles without executing and cannot see this class of failure. + +## 6.1.8 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.134.2` so knowledge evaluation resolves complete multishot judge cost accounting without installing an older Eval copy. + +## 6.1.7 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.134.1` so Knowledge consumers resolve the corrected Eval implementation without installing an older copy. + +## 6.1.6 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.134.0` so Knowledge and Runtime share one explicit proposal-finding contract without installing an older Eval copy. + +## 6.1.5 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.133.3` so knowledge evaluation and promotion use the corrected exact, Student-t, rank-test, and Welch implementations. + +## 6.1.4 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.133.2` for final-evaluation data isolation, lazy OpenCode SQLite loading, and fail-closed paired comparisons. +- Increased the official optimizer check's pip timeout and retries so slow package downloads do not fail an otherwise valid release. + +## 6.1.3 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.133.1` for corrected normal-approximation statistics. + +## 6.1.2 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.133.0` and `@tangle-network/agent-interface` to `0.36.0`. + +## 6.1.1 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.131.0` while retaining the exact `@tangle-network/agent-interface` `0.35.0` contract. +- Extended packed-package verification to confirm both installed agent stack dependencies match the package manifest. + +## 6.1.0 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.130.1`, `@tangle-network/agent-interface` to `0.35.0`, Mem0 to `3.1.2`, and the maintained build toolchain. +- Replaced tsup with tsdown so declaration builds support TypeScript 7. +- Added package export validation with publint and Are the Types Wrong to every release. +- Split Mem0 hosted and OSS client contracts, compile them against Mem0 3.1.2, and exercise the OSS SQLite lifecycle locally. +- Updated GitHub Actions to their current stable releases. + +## 6.0.0 + +### Breaking Changes + +- Renamed the verified research entry point to `runVerifiedResearchLoop` and removed the old two-agent function, option, result, round, and module names. +- Updated `@tangle-network/agent-eval` to `0.129.0` and `@tangle-network/agent-interface` to `0.34.0`. + +## 5.0.4 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.128.2` for canonical task-failure imports, reports, validation, and the exact Core 0.4.21 and Interface 0.33 dependencies. +- Updated `@tangle-network/agent-interface` to `0.33.0` so Knowledge and Runtime share the current certified context contract. + +## 5.0.3 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.127.0` and adopted its explicit run outcome, cost provenance, and scenario identity contract. +- Allowed filesystem-heavy lifecycle tests enough time to complete under shared-runner load. + +## 5.0.2 + +### Changed + +- Updated the maintained Neo4j Agent Memory and Mem0 adapters plus compatible build and formatting dependencies. +- Aligned the official optimizer bridge with `@tangle-network/agent-eval@0.126.7`. +- Forced patched Hono, Node server, WebSocket, Vite, and esbuild releases so the installed graph has no known npm advisories. + +## 5.0.1 + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.126.6` so Knowledge and Runtime use the same optimizer provenance contract. + +## 5.0.0 + +### Breaking Changes + +- Retrieval improvement now requires independent train, selection, and final scenarios plus an explicit complete `OptimizationMethod`. +- Serialized retrieval and RAG optimization now requires an immutable `executionRef` covering candidate execution and scoring behavior. +- Memory configuration improvement now requires a baseline configuration, a complete `OptimizationMethod`, and independent train, selection, and final histories. +- The RAG lifecycle promotion callback is now `decidePromotion` and runs only after final evidence passes regression, provenance, and cost checks. +- Knowledge improvement requires an immutable `implementationRef`, separates repeatable development evaluation from single-use final evaluation, and refuses to resume after interrupted final scoring. +- Memory candidate factories no longer receive scenario, repetition, or seed identity and must report observed external charges through `recordExternalCost()`. +- Answer-quality hooks require immutable evaluator identity, final scenario identity, and complete cost evidence. +- Removed the public retrieval and memory proposer-search options; candidate generation and selection now belong to `agent-eval` methods. + +### Added + +- Added a shared serialized-candidate adapter for running complete `agent-eval` optimization methods with canonical candidate identity and untouched final comparison. +- Added full RAG configuration optimization and KB maintenance policy optimization. +- Added direct support for official GEPA and SkillOpt methods through the shared `OptimizationMethod` contract. +- Added durable per-configuration memory candidate identities to prevent stale result reuse. +- Added live activation verification so resumed memory runs reject configuration drift. +- Added private execution contexts that expose memory operations, cancellation, and cost metering without evaluation labels. + +### Changed + +- Updated `@tangle-network/agent-eval` to `0.126.5` and `@tangle-network/agent-interface` to `0.32.0`. +- Kept memory provider evaluations resumable and branch-isolated while moving search ownership to the supplied method. +- Restricted immutable references to lowercase SHA-256 and full Git commit identities. + +## 4.1.0 + +### Added + +- Added optional scenario input to `knowledgeReleaseReport()` so a required holdout can prove both scenario and run coverage. + +### Changed + +- Split knowledge-base improvement and RAG evaluation internals into focused modules while preserving public exports and implementation behavior. +- Split the knowledge improvement tests into candidate, promotion, activation, and integrity suites with shared setup in one support module. +- Removed unused development dependencies and internal-only exports. +- Updated the test runner to Vitest 4 and Node type definitions to 26; TypeScript remains on 5.9 because tsup's declaration bundler does not yet support TypeScript 7. +- Declared Node types explicitly in TypeScript configuration instead of relying on ambient type discovery. +- Corrected the package dependency guide and RAG roadmap to reflect runtime-owned agent execution through `runKnowledgeImprovementJob()`. +- Removed historical commentary and em dashes from repository documentation. + +### Fixed + +- Replaced the stale versioned HTTP user agent with a stable package identity and added request-header coverage. + +## 4.0.1 + +### Changed + +- Split benchmark, memory experiment, and memory improvement internals into focused modules while preserving every public export and signature. +- Split memory and benchmark tests by behavior, with shared controller and adapter fixtures kept in one test-support module. +- Moved retrieval holdout contracts into the memory type layer to remove the holdout/types import cycle. + +### Fixed + +- Made the Mem0 deletion-convergence test deterministic under file-level parallel execution. + +## 4.0.0 + +### Breaking Changes + +- `runAgentMemoryImprovement()` now activates a measured winner through `activation.readCurrent()` and atomic `activation.compareAndSet()` instead of `onPromote`. +- `AgentMemoryActivation.receiptPath` is now `journalPath` because the file is an append-only activation record. +- `mem0MemoryAdapterIdentity()` and `graphitiMemoryAdapterIdentity()` require a stable, non-secret `backendRef` so two deployments cannot share a cache identity. +- Mem0 hosted mode now follows the synchronous array response from `mem0ai` 3.x; the unsupported queued-event options were removed. +- Paid benchmark and improvement work now defaults to a zero dollar limit and requires an explicit `costCeiling` or `maxTotalCostUsd`. +- In-flight run directories from releases before 4.0 are not migrated; archive or clear them before upgrading because 4.0 rejects older attempt records. + +### Added + +- Official-client adapters for Mem0 hosted and open-source deployments, Graphiti MCP, and Neo4j Agent Memory. +- Isolated memory branches with snapshots, replayable forks, private, team, and shared visibility, and ordered writes per agent. +- Parallel multi-track memory experiments and configuration search on `agent-eval`, with fresh-history comparison before activation. +- Durable controller ownership, interrupted-attempt cleanup, retired-candidate recovery, bounded cleanup work, and conservative recovery cost reconciliation. +- Complete candidate cost attribution across interrupted retries, plus explicit unranked recovery spend for retired benchmark candidates. +- Exact scoped Mem0 deletion with list and search convergence checks. + +### Fixed + +- Mem0 and direct Neo4j operations reject provider scopes they cannot enforce before any provider call. +- Hosted Mem0 `appId` is an additional filter and cannot authorize an unscoped whole-application read or delete. +- Mem0 cleanup tracks fresh writes until they become visible and confirms deletion from both list and search indexes. +- Broader Mem0 cleanup scopes wait for delayed writes created under matching narrower scopes. +- Mem0 pending-write probes expire after the configured visibility window instead of accumulating for the adapter lifetime. +- Direct Neo4j reasoning writes reject combined session and run scopes because the SDK can enforce only one conversation identifier. +- Timed-out provider work blocks close or reuse of the same adapter until the original operation settles. +- Recovery retries are reserved durably before provider work and stop after three failures per attempt by default. +- Direct memory benchmarks account for billable adapter provisioning and reconnects in the shared dollar limit. +- Fully cached benchmark resumes skip adapter creation and add no provider charge. +- Execute and recovery adapter factories receive abort signals and are bounded by the configured timeout. +- Adapters returned after a timed-out factory call are closed, and experiment adapters also run their configured disposal callback. +- Reported dollar totals are normalized to twelve decimal places instead of exposing binary floating-point artifacts. diff --git a/README.md b/README.md index f981bc0..06260a4 100644 --- a/README.md +++ b/README.md @@ -345,11 +345,3 @@ Those choices stay in the application or in `@tangle-network/agent-runtime`. ## License MIT - -## Proving knowledge retrieval and use - -A page being visible, a retriever returning it, and an agent using it are different facts. Knowledge now exposes content-addressed visibility, retrieval, and use receipts that bind the exact page version and downstream consumer without claiming the page is true or that it improved the outcome. - -`createKnowledgeVisibilitySnapshot()` records the ordered current/ancestor/shared view; `createKnowledgeRetrievalReceipt()` binds the exact query, retriever configuration, and ranked results; `createKnowledgeUseReceipt()` binds one returned rank to a decision, artifact, experiment, candidate, or message. Verifiers fail on changed page bytes, origin, order, query, selected rank, relation, or consumer identity. - -See [Knowledge retrieval and use receipts](docs/knowledge-use-receipts.md) for the ELI5 proof chain, canonical serialization rules, trace attributes, proof limits, and the causal-experiment recipe. diff --git a/docs/knowledge-use-receipts.md b/docs/knowledge-use-receipts.md index ec4b025..2eddd1b 100644 --- a/docs/knowledge-use-receipts.md +++ b/docs/knowledge-use-receipts.md @@ -58,10 +58,8 @@ A repeated path at the same origin is refused. The same stable page id may remai A result is accepted only when its exact page bytes, path, id, and origin occur in the visibility snapshot. Ranks must be unique and contiguous from one. Scores must be finite, and normalized scores must lie in `[0, 1]`. ```ts -import { - canonicalCandidateDigest, - createKnowledgeRetrievalReceipt, -} from '@tangle-network/agent-knowledge' +import { canonicalCandidateDigest } from '@tangle-network/agent-interface' +import { createKnowledgeRetrievalReceipt } from '@tangle-network/agent-knowledge' const receipt = createKnowledgeRetrievalReceipt({ runId,