@graphorin/memory 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +71 -0
- package/README.md +1 -1
- package/dist/conflict/index.js +0 -1
- package/dist/conflict/pipeline.js +0 -1
- package/dist/conflict/pipeline.js.map +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js +0 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
- package/dist/conflict/types.d.ts +1 -1
- package/dist/conflict/types.js.map +1 -1
- package/dist/consolidator/index.js +2 -0
- package/dist/consolidator/index.js.map +1 -1
- package/dist/consolidator/lock.js +7 -1
- package/dist/consolidator/lock.js.map +1 -1
- package/dist/consolidator/phases/deep.js +19 -12
- package/dist/consolidator/phases/deep.js.map +1 -1
- package/dist/consolidator/phases/induce.d.ts.map +1 -1
- package/dist/consolidator/phases/induce.js +2 -3
- package/dist/consolidator/phases/induce.js.map +1 -1
- package/dist/consolidator/phases/reflect.js +35 -8
- package/dist/consolidator/phases/reflect.js.map +1 -1
- package/dist/consolidator/phases/standard.js +199 -28
- package/dist/consolidator/phases/standard.js.map +1 -1
- package/dist/consolidator/reconcile.js +16 -15
- package/dist/consolidator/reconcile.js.map +1 -1
- package/dist/consolidator/runtime.d.ts.map +1 -1
- package/dist/consolidator/runtime.js +129 -47
- package/dist/consolidator/runtime.js.map +1 -1
- package/dist/consolidator/scheduler.d.ts +17 -3
- package/dist/consolidator/scheduler.d.ts.map +1 -1
- package/dist/consolidator/scheduler.js.map +1 -1
- package/dist/consolidator/types.d.ts +30 -1
- package/dist/consolidator/types.d.ts.map +1 -1
- package/dist/consolidator/types.js +10 -0
- package/dist/consolidator/types.js.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.d.ts +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
- package/dist/context-engine/compaction/compactor.d.ts +1 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -1
- package/dist/context-engine/compaction/compactor.js +32 -8
- package/dist/context-engine/compaction/compactor.js.map +1 -1
- package/dist/context-engine/compaction/hooks/types.d.ts +1 -1
- package/dist/context-engine/compaction/index.d.ts +1 -0
- package/dist/context-engine/compaction/templates/marker.d.ts +33 -0
- package/dist/context-engine/compaction/templates/marker.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/marker.js +34 -0
- package/dist/context-engine/compaction/templates/marker.js.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.js +4 -3
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
- package/dist/context-engine/engine.d.ts +7 -176
- package/dist/context-engine/engine.d.ts.map +1 -1
- package/dist/context-engine/engine.js +0 -2
- package/dist/context-engine/engine.js.map +1 -1
- package/dist/context-engine/index.d.ts +8 -49
- package/dist/context-engine/io-types.d.ts +144 -0
- package/dist/context-engine/io-types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +1 -1
- package/dist/context-engine/locale-packs/types.js.map +1 -1
- package/dist/context-engine/privacy-filter.d.ts +1 -2
- package/dist/context-engine/privacy-filter.d.ts.map +1 -1
- package/dist/context-engine/privacy-filter.js +1 -2
- package/dist/context-engine/privacy-filter.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +2 -2
- package/dist/errors/index.js.map +1 -1
- package/dist/facade.d.ts +43 -52
- package/dist/facade.d.ts.map +1 -1
- package/dist/facade.js +5 -3
- package/dist/facade.js.map +1 -1
- package/dist/graph/entity-resolver.js +1 -1
- package/dist/graph/entity-resolver.js.map +1 -1
- package/dist/index.d.ts +16 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/index.js.map +1 -1
- package/dist/internal/envelope.js +57 -0
- package/dist/internal/envelope.js.map +1 -0
- package/dist/internal/injection-heuristics.js +0 -0
- package/dist/internal/injection-heuristics.js.map +1 -1
- package/dist/internal/llm-json.js +40 -0
- package/dist/internal/llm-json.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +23 -10
- package/dist/internal/storage-adapter.d.ts.map +1 -1
- package/dist/memory-interface.d.ts +131 -0
- package/dist/memory-interface.d.ts.map +1 -0
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/dist/search/iterative.d.ts.map +1 -1
- package/dist/search/iterative.js +2 -3
- package/dist/search/iterative.js.map +1 -1
- package/dist/search/query-transform.d.ts.map +1 -1
- package/dist/search/query-transform.js +2 -3
- package/dist/search/query-transform.js.map +1 -1
- package/dist/tiers/episodic-memory.js +3 -3
- package/dist/tiers/episodic-memory.js.map +1 -1
- package/dist/tiers/index.d.ts +2 -2
- package/dist/tiers/insight-memory.js +1 -1
- package/dist/tiers/insight-memory.js.map +1 -1
- package/dist/tiers/procedural-memory.d.ts +1 -1
- package/dist/tiers/procedural-memory.js +2 -2
- package/dist/tiers/procedural-memory.js.map +1 -1
- package/dist/tiers/semantic-memory.d.ts +58 -4
- package/dist/tiers/semantic-memory.d.ts.map +1 -1
- package/dist/tiers/semantic-memory.js +103 -20
- package/dist/tiers/semantic-memory.js.map +1 -1
- package/dist/tools/block-tools.d.ts +19 -57
- package/dist/tools/block-tools.d.ts.map +1 -1
- package/dist/tools/block-tools.js.map +1 -1
- package/dist/tools/fact-tools.d.ts +53 -214
- package/dist/tools/fact-tools.d.ts.map +1 -1
- package/dist/tools/fact-tools.js +3 -2
- package/dist/tools/fact-tools.js.map +1 -1
- package/dist/tools/recall-tools.d.ts +34 -175
- package/dist/tools/recall-tools.d.ts.map +1 -1
- package/dist/tools/recall-tools.js +5 -4
- package/dist/tools/recall-tools.js.map +1 -1
- package/dist/tools/runbook-tools.d.ts +15 -58
- package/dist/tools/runbook-tools.d.ts.map +1 -1
- package/dist/tools/runbook-tools.js.map +1 -1
- package/dist/tools/types.d.ts +1 -1
- package/package.json +18 -17
- package/src/conflict/index.ts +29 -0
- package/src/conflict/locale-packs/en.ts +142 -0
- package/src/conflict/locale-packs/index.ts +17 -0
- package/src/conflict/locale-packs/types.ts +138 -0
- package/src/conflict/pipeline.ts +371 -0
- package/src/conflict/stages/helpers.ts +65 -0
- package/src/conflict/stages/stage1-exact-dedup.ts +40 -0
- package/src/conflict/stages/stage2-embedding-three-zone.ts +54 -0
- package/src/conflict/stages/stage3-heuristic-regex.ts +51 -0
- package/src/conflict/stages/stage4-subject-predicate.ts +46 -0
- package/src/conflict/stages/stage5-defer-to-deep.ts +34 -0
- package/src/conflict/types.ts +263 -0
- package/src/consolidator/budget.ts +312 -0
- package/src/consolidator/decay.ts +246 -0
- package/src/consolidator/dlq.ts +61 -0
- package/src/consolidator/errors.ts +87 -0
- package/src/consolidator/idempotency.ts +27 -0
- package/src/consolidator/index.ts +269 -0
- package/src/consolidator/lock.ts +141 -0
- package/src/consolidator/noise-filter.ts +235 -0
- package/src/consolidator/phases/deep.ts +308 -0
- package/src/consolidator/phases/induce.ts +423 -0
- package/src/consolidator/phases/learned-context.ts +233 -0
- package/src/consolidator/phases/light.ts +166 -0
- package/src/consolidator/phases/reflect.ts +504 -0
- package/src/consolidator/phases/standard.ts +1002 -0
- package/src/consolidator/reconcile.ts +273 -0
- package/src/consolidator/runtime.ts +1049 -0
- package/src/consolidator/scheduler.ts +182 -0
- package/src/consolidator/triggers.ts +134 -0
- package/src/consolidator/types.ts +622 -0
- package/src/context-engine/annotations.ts +161 -0
- package/src/context-engine/auto-recall.ts +92 -0
- package/src/context-engine/compaction/clear-tool-results.ts +232 -0
- package/src/context-engine/compaction/compactor.ts +403 -0
- package/src/context-engine/compaction/hooks/reanchor-persona-block.ts +55 -0
- package/src/context-engine/compaction/hooks/reanchor-pinned-facts.ts +66 -0
- package/src/context-engine/compaction/hooks/reanchor-project-rules.ts +57 -0
- package/src/context-engine/compaction/hooks/reanchor-recent-results.ts +116 -0
- package/src/context-engine/compaction/hooks/types.ts +68 -0
- package/src/context-engine/compaction/index.ts +60 -0
- package/src/context-engine/compaction/templates/marker.ts +33 -0
- package/src/context-engine/compaction/templates/summary-9-section.ts +223 -0
- package/src/context-engine/compaction/thresholds.ts +107 -0
- package/src/context-engine/compaction/types.ts +255 -0
- package/src/context-engine/engine.ts +970 -0
- package/src/context-engine/index.ts +118 -0
- package/src/context-engine/io-types.ts +162 -0
- package/src/context-engine/locale-packs/en.ts +107 -0
- package/src/context-engine/locale-packs/index.ts +19 -0
- package/src/context-engine/locale-packs/resolver.ts +197 -0
- package/src/context-engine/locale-packs/types.ts +177 -0
- package/src/context-engine/metadata.ts +114 -0
- package/src/context-engine/preambles/inbound-en.ts +19 -0
- package/src/context-engine/privacy-filter.ts +190 -0
- package/src/context-engine/templates/base-en.ts +19 -0
- package/src/context-engine/templates/composer.ts +91 -0
- package/src/context-engine/token-budget.ts +319 -0
- package/src/context-engine/token-counter.ts +157 -0
- package/src/errors/index.ts +273 -0
- package/src/facade.ts +750 -0
- package/src/graph/entity-resolver.ts +364 -0
- package/src/graph/index.ts +24 -0
- package/src/index.ts +259 -0
- package/src/internal/contextualize.ts +173 -0
- package/src/internal/embedder-binding.ts +53 -0
- package/src/internal/envelope.ts +70 -0
- package/src/internal/id.ts +23 -0
- package/src/internal/injection-heuristics.ts +0 -0
- package/src/internal/llm-json.ts +38 -0
- package/src/internal/spans.ts +35 -0
- package/src/internal/storage-adapter.ts +963 -0
- package/src/memory-interface.ts +158 -0
- package/src/migration/embedder-migration.ts +237 -0
- package/src/migration/index.ts +13 -0
- package/src/search/explain.ts +123 -0
- package/src/search/fit-weights.ts +109 -0
- package/src/search/graph-ppr.ts +38 -0
- package/src/search/index.ts +55 -0
- package/src/search/iterative.ts +658 -0
- package/src/search/query-transform.ts +285 -0
- package/src/search/rrf.ts +225 -0
- package/src/search/trust.ts +55 -0
- package/src/search/types.ts +50 -0
- package/src/tiers/episodic-memory.ts +463 -0
- package/src/tiers/index.ts +50 -0
- package/src/tiers/insight-memory.ts +208 -0
- package/src/tiers/procedural-memory.ts +482 -0
- package/src/tiers/semantic-memory.ts +1729 -0
- package/src/tiers/session-memory.ts +271 -0
- package/src/tiers/shared-memory.ts +62 -0
- package/src/tiers/working-memory.ts +368 -0
- package/src/tools/block-tools.ts +172 -0
- package/src/tools/fact-tools.ts +473 -0
- package/src/tools/index.ts +109 -0
- package/src/tools/recall-tools.ts +303 -0
- package/src/tools/runbook-tools.ts +107 -0
- package/src/tools/taint.ts +39 -0
- package/src/tools/types.ts +37 -0
- package/dist/conflict/locale-packs/index.js +0 -4
- package/dist/context-engine/compaction/index.js +0 -10
- package/dist/context-engine/index.d.ts.map +0 -1
- package/dist/context-engine/index.js +0 -25
- package/dist/context-engine/locale-packs/index.js +0 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"insight-memory.js","names":["#store","#tracer"],"sources":["../../src/tiers/insight-memory.ts"],"sourcesContent":["/**\n * `InsightMemory` - the thin, read-only surface over the reflection\n * insights the consolidator synthesizes (P1-1). Insights are *derived*\n * (quarantined + `provenance: 'reflection'`), carry mandatory citations,\n * and are retrieval-ranked **below the primary facts they cite**. The\n * write + salience-management paths live in the consolidator's\n * reflection pass; this tier only lists / searches for the validation /\n * inspector path.\n *\n * Search is FTS-only - insights are a soft inspector surface, not\n * primary recall, so no embedder is wired here.\n *\n * @packageDocumentation\n */\n\nimport type { Fact, Insight, MemoryHit, SessionScope, Tracer } from '@graphorin/core';\nimport { QuarantinePromotionRefusedError } from '../errors/index.js';\nimport { detectMemoryInjection } from '../internal/injection-heuristics.js';\nimport { withMemorySpan } from '../internal/spans.js';\nimport type { MemoryStoreAdapter } from '../internal/storage-adapter.js';\n\n/** Per-call options accepted by {@link InsightMemory.search}. */\nexport interface InsightSearchOptions {\n readonly topK?: number;\n /**\n * Include quarantined insights (P1-4). Defaults to `false`. Since\n * reflection-synthesized insights *always* land quarantined, set this\n * `true` to surface them for the validation / inspector path - never\n * for auto-recall fed back into the model.\n */\n readonly includeQuarantined?: boolean;\n readonly signal?: AbortSignal;\n}\n\n/** Per-call options accepted by {@link InsightMemory.list}. */\nexport interface InsightListOptions {\n readonly limit?: number;\n /** Include quarantined insights (validation / inspector path). */\n readonly includeQuarantined?: boolean;\n}\n\n/**\n * `InsightMemory` - list / search reflection insights. A no-op (returns\n * empty) when the storage adapter does not expose the optional\n * `insights` surface.\n *\n * @stable\n */\nexport class InsightMemory {\n readonly #store: MemoryStoreAdapter;\n readonly #tracer: Tracer;\n\n constructor(args: { store: MemoryStoreAdapter; tracer: Tracer }) {\n this.#store = args.store;\n this.#tracer = args.tracer;\n }\n\n /** FTS keyword search over insight text. */\n async search(\n scope: SessionScope,\n query: string,\n opts: InsightSearchOptions = {},\n ): Promise<ReadonlyArray<MemoryHit<Insight>>> {\n return withMemorySpan(\n this.#tracer,\n 'memory.search.insight',\n scope,\n { 'memory.search.query_length': query.length },\n async (span) => {\n const store = this.#store.insights;\n if (store === undefined) {\n span.setAttributes({ 'memory.search.insight.count': 0 });\n return [];\n }\n const hits = await store.search(scope, query, {\n topK: opts.topK ?? 10,\n ...(opts.includeQuarantined !== undefined\n ? { includeQuarantined: opts.includeQuarantined }\n : {}),\n });\n // MCON-16: retrieval reinforces (ExpeL) - a recalled insight gains\n // +1 salience, offsetting the per-reflection-pass -1 decay, so\n // used insights persist while unused ones slide to the prune\n // floor. Best-effort bookkeeping: never break the read path.\n for (const hit of hits) {\n try {\n await store.bumpSalience(hit.record.id, 1, 'retrieval');\n } catch {\n // Advisory only.\n }\n }\n span.setAttributes({ 'memory.search.insight.count': hits.length });\n return hits;\n },\n );\n }\n\n /** Most-recent insights for the scope (newest first). */\n async list(scope: SessionScope, opts: InsightListOptions = {}): Promise<ReadonlyArray<Insight>> {\n return withMemorySpan(this.#tracer, 'memory.read.insight', scope, {}, async (span) => {\n const store = this.#store.insights;\n if (store === undefined) {\n span.setAttributes({ 'memory.read.insight.count': 0 });\n return [];\n }\n const out = await store.list(scope, {\n ...(opts.limit !== undefined ? { limit: opts.limit } : {}),\n ...(opts.includeQuarantined !== undefined\n ? { includeQuarantined: opts.includeQuarantined }\n : {}),\n });\n span.setAttributes({ 'memory.read.insight.count': out.length });\n return out;\n });\n }\n\n /** Lookup a single insight by id. */\n async get(id: string): Promise<Insight | null> {\n const store = this.#store.insights;\n if (store === undefined || typeof store.get !== 'function') return null;\n return store.get(id);\n }\n\n /**\n * Promote a quarantined insight out of quarantine (MCON-2). Mirrors\n * {@link SemanticMemory.validate}: re-derives the injection verdict from the\n * stored text and **refuses** promotion of an injection-flagged insight\n * unless an operator passes `{ force: true }` from a trusted context.\n */\n async validate(\n scope: SessionScope,\n insightId: string,\n reason?: string,\n options?: { readonly force?: boolean },\n ): Promise<void> {\n await withMemorySpan(\n this.#tracer,\n 'memory.write.insight',\n scope,\n { 'memory.insight.action': 'validate', 'memory.insight.insight_id': insightId },\n async (span) => {\n const store = this.#store.insights;\n if (store === undefined || typeof store.setStatus !== 'function') {\n throw new TypeError(\n '[graphorin/memory] InsightMemory.validate(...) requires a storage adapter that implements `insights.setStatus(id, status)`. ' +\n 'The default `@graphorin/store-sqlite` adapter implements it; custom adapters can opt in via InsightMemoryStoreExt.',\n );\n }\n const force = options?.force === true;\n const existing = typeof store.get === 'function' ? await store.get(insightId) : null;\n if (existing !== null && !force) {\n const injection = detectMemoryInjection(existing.text);\n if (injection.flagged) {\n span.setAttributes({ 'memory.insight.validate.refused': true });\n throw new QuarantinePromotionRefusedError(insightId, injection.markers);\n }\n }\n span.setAttributes({ 'memory.insight.validate.forced': force });\n await store.setStatus(insightId, 'active', reason);\n },\n );\n }\n}\n\n/** Tie-break margin keeping a capped insight strictly below its cited fact. */\nconst RANK_CAP_EPSILON = 1e-6;\n\n/**\n * Enforce the P1-1 rank ceiling: an insight may never outrank a primary\n * fact **it cites**. For each insight hit, if any fact it cites is\n * present in `factHits`, its score is lowered to strictly below that\n * cited fact's score - so concatenating the two lists and sorting by\n * score descending can never place the insight above the evidence it\n * was synthesized from. Insights whose cited facts are absent from\n * `factHits` are returned unchanged; this is a relative, not a global,\n * cap (per the execution plan: \"never outrank the primary facts they\n * cite\").\n *\n * Pure + deterministic - does not mutate its inputs.\n *\n * @stable\n */\nexport function capInsightsBelowFacts(\n factHits: ReadonlyArray<MemoryHit<Fact>>,\n insightHits: ReadonlyArray<MemoryHit<Insight>>,\n): ReadonlyArray<MemoryHit<Insight>> {\n if (factHits.length === 0 || insightHits.length === 0) return insightHits;\n const factScoreById = new Map<string, number>();\n for (const hit of factHits) {\n const prev = factScoreById.get(hit.record.id);\n if (prev === undefined || hit.score > prev) factScoreById.set(hit.record.id, hit.score);\n }\n return insightHits.map((hit) => {\n let citedMax = Number.NEGATIVE_INFINITY;\n for (const id of hit.record.cites) {\n const score = factScoreById.get(id);\n if (score !== undefined && score > citedMax) citedMax = score;\n }\n // No cited fact present in the fused set, or already below it ⇒ keep.\n if (citedMax === Number.NEGATIVE_INFINITY || hit.score < citedMax) return hit;\n const capped = citedMax - RANK_CAP_EPSILON;\n return {\n ...hit,\n score: capped,\n signals: Object.freeze({ ...(hit.signals ?? {}), 'rank-cap': capped }),\n };\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"insight-memory.js","names":["#store","#tracer"],"sources":["../../src/tiers/insight-memory.ts"],"sourcesContent":["/**\n * `InsightMemory` - the thin, read-only surface over the reflection\n * insights the consolidator synthesizes (P1-1). Insights are *derived*\n * (quarantined + `provenance: 'reflection'`), carry mandatory citations,\n * and are retrieval-ranked **below the primary facts they cite**. The\n * write + salience-management paths live in the consolidator's\n * reflection pass; this tier only lists / searches for the validation /\n * inspector path.\n *\n * Search is FTS-only - insights are a soft inspector surface, not\n * primary recall, so no embedder is wired here.\n *\n * @packageDocumentation\n */\n\nimport type { Fact, Insight, MemoryHit, SessionScope, Tracer } from '@graphorin/core';\nimport { QuarantinePromotionRefusedError } from '../errors/index.js';\nimport { detectMemoryInjection } from '../internal/injection-heuristics.js';\nimport { withMemorySpan } from '../internal/spans.js';\nimport type { MemoryStoreAdapter } from '../internal/storage-adapter.js';\n\n/** Per-call options accepted by {@link InsightMemory.search}. */\nexport interface InsightSearchOptions {\n readonly topK?: number;\n /**\n * Include quarantined insights (P1-4). Defaults to `false`. Since\n * reflection-synthesized insights *always* land quarantined, set this\n * `true` to surface them for the validation / inspector path - never\n * for auto-recall fed back into the model.\n */\n readonly includeQuarantined?: boolean;\n readonly signal?: AbortSignal;\n}\n\n/** Per-call options accepted by {@link InsightMemory.list}. */\nexport interface InsightListOptions {\n readonly limit?: number;\n /** Include quarantined insights (validation / inspector path). */\n readonly includeQuarantined?: boolean;\n}\n\n/**\n * `InsightMemory` - list / search reflection insights. A no-op (returns\n * empty) when the storage adapter does not expose the optional\n * `insights` surface.\n *\n * @stable\n */\nexport class InsightMemory {\n readonly #store: MemoryStoreAdapter;\n readonly #tracer: Tracer;\n\n constructor(args: { store: MemoryStoreAdapter; tracer: Tracer }) {\n this.#store = args.store;\n this.#tracer = args.tracer;\n }\n\n /** FTS keyword search over insight text. */\n async search(\n scope: SessionScope,\n query: string,\n opts: InsightSearchOptions = {},\n ): Promise<ReadonlyArray<MemoryHit<Insight>>> {\n return withMemorySpan(\n this.#tracer,\n 'memory.search.insight',\n scope,\n { 'memory.search.query_length': query.length },\n async (span) => {\n const store = this.#store.insights;\n if (store === undefined) {\n span.setAttributes({ 'memory.search.insight.count': 0 });\n return [];\n }\n const hits = await store.search(scope, query, {\n topK: opts.topK ?? 10,\n ...(opts.includeQuarantined !== undefined\n ? { includeQuarantined: opts.includeQuarantined }\n : {}),\n });\n // MCON-16: retrieval reinforces (ExpeL) - a recalled insight gains\n // +1 salience, offsetting the per-reflection-pass -1 decay, so\n // used insights persist while unused ones slide to the prune\n // floor. Best-effort bookkeeping: never break the read path.\n for (const hit of hits) {\n try {\n await store.bumpSalience(hit.record.id, 1, 'retrieval');\n } catch {\n // Advisory only.\n }\n }\n span.setAttributes({ 'memory.search.insight.count': hits.length });\n return hits;\n },\n );\n }\n\n /** Most-recent insights for the scope (newest first). */\n async list(scope: SessionScope, opts: InsightListOptions = {}): Promise<ReadonlyArray<Insight>> {\n return withMemorySpan(this.#tracer, 'memory.read.insight', scope, {}, async (span) => {\n const store = this.#store.insights;\n if (store === undefined) {\n span.setAttributes({ 'memory.read.insight.count': 0 });\n return [];\n }\n const out = await store.list(scope, {\n ...(opts.limit !== undefined ? { limit: opts.limit } : {}),\n ...(opts.includeQuarantined !== undefined\n ? { includeQuarantined: opts.includeQuarantined }\n : {}),\n });\n span.setAttributes({ 'memory.read.insight.count': out.length });\n return out;\n });\n }\n\n /** Lookup a single insight by id. */\n async get(id: string): Promise<Insight | null> {\n const store = this.#store.insights;\n if (store === undefined || typeof store.get !== 'function') return null;\n return store.get(id);\n }\n\n /**\n * Promote a quarantined insight out of quarantine (MCON-2). Mirrors\n * {@link SemanticMemory.validate}: re-derives the injection verdict from the\n * stored text and **refuses** promotion of an injection-flagged insight\n * unless an operator passes `{ force: true }` from a trusted context.\n */\n async validate(\n scope: SessionScope,\n insightId: string,\n reason?: string,\n options?: { readonly force?: boolean },\n ): Promise<void> {\n await withMemorySpan(\n this.#tracer,\n 'memory.write.insight',\n scope,\n { 'memory.insight.action': 'validate', 'memory.insight.insight_id': insightId },\n async (span) => {\n const store = this.#store.insights;\n if (store === undefined || typeof store.setStatus !== 'function') {\n throw new TypeError(\n '[graphorin/memory] InsightMemory.validate(...) requires a storage adapter that implements `insights.setStatus(id, status)`. ' +\n 'The default `@graphorin/store-sqlite` adapter implements it; custom adapters can opt in via InsightMemoryStoreExt.',\n );\n }\n const force = options?.force === true;\n const existing = typeof store.get === 'function' ? await store.get(insightId) : null;\n if (existing !== null && !force) {\n const injection = detectMemoryInjection(existing.text);\n if (injection.flagged) {\n span.setAttributes({ 'memory.insight.validate.refused': true });\n throw new QuarantinePromotionRefusedError(insightId, injection.markers);\n }\n }\n span.setAttributes({ 'memory.insight.validate.forced': force });\n await store.setStatus(insightId, 'active', reason, scope);\n },\n );\n }\n}\n\n/** Tie-break margin keeping a capped insight strictly below its cited fact. */\nconst RANK_CAP_EPSILON = 1e-6;\n\n/**\n * Enforce the P1-1 rank ceiling: an insight may never outrank a primary\n * fact **it cites**. For each insight hit, if any fact it cites is\n * present in `factHits`, its score is lowered to strictly below that\n * cited fact's score - so concatenating the two lists and sorting by\n * score descending can never place the insight above the evidence it\n * was synthesized from. Insights whose cited facts are absent from\n * `factHits` are returned unchanged; this is a relative, not a global,\n * cap (per the execution plan: \"never outrank the primary facts they\n * cite\").\n *\n * Pure + deterministic - does not mutate its inputs.\n *\n * @stable\n */\nexport function capInsightsBelowFacts(\n factHits: ReadonlyArray<MemoryHit<Fact>>,\n insightHits: ReadonlyArray<MemoryHit<Insight>>,\n): ReadonlyArray<MemoryHit<Insight>> {\n if (factHits.length === 0 || insightHits.length === 0) return insightHits;\n const factScoreById = new Map<string, number>();\n for (const hit of factHits) {\n const prev = factScoreById.get(hit.record.id);\n if (prev === undefined || hit.score > prev) factScoreById.set(hit.record.id, hit.score);\n }\n return insightHits.map((hit) => {\n let citedMax = Number.NEGATIVE_INFINITY;\n for (const id of hit.record.cites) {\n const score = factScoreById.get(id);\n if (score !== undefined && score > citedMax) citedMax = score;\n }\n // No cited fact present in the fused set, or already below it ⇒ keep.\n if (citedMax === Number.NEGATIVE_INFINITY || hit.score < citedMax) return hit;\n const capped = citedMax - RANK_CAP_EPSILON;\n return {\n ...hit,\n score: capped,\n signals: Object.freeze({ ...(hit.signals ?? {}), 'rank-cap': capped }),\n };\n });\n}\n"],"mappings":"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"}
|
|
@@ -44,7 +44,7 @@ interface RuleInput {
|
|
|
44
44
|
interface InduceOptions {
|
|
45
45
|
/** Sensitivity of the stored procedure. Default `'internal'`. */
|
|
46
46
|
readonly sensitivity?: Sensitivity;
|
|
47
|
-
/** Priority of the stored procedure. Default
|
|
47
|
+
/** Priority of the stored procedure. Default `INDUCED_PRIORITY` (40). */
|
|
48
48
|
readonly priority?: number;
|
|
49
49
|
}
|
|
50
50
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { withMemorySpan } from "../internal/spans.js";
|
|
2
2
|
import { ProcedureInductionNotConfiguredError, QuarantinePromotionRefusedError } from "../errors/index.js";
|
|
3
|
+
import { detectMemoryInjection } from "../internal/injection-heuristics.js";
|
|
3
4
|
import { runWorkflowInduction, trajectoryFromRunState } from "../consolidator/phases/induce.js";
|
|
4
5
|
import { newMemoryId } from "../internal/id.js";
|
|
5
|
-
import { detectMemoryInjection } from "../internal/injection-heuristics.js";
|
|
6
6
|
|
|
7
7
|
//#region src/tiers/procedural-memory.ts
|
|
8
8
|
/** Default priority for an *induced* procedure - below the author default
|
|
@@ -297,7 +297,7 @@ var ProceduralMemory = class {
|
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
span.setAttributes({ "memory.procedural.validate.forced": force });
|
|
300
|
-
await store.setStatus(ruleId, "active", reason);
|
|
300
|
+
await store.setStatus(ruleId, "active", reason, scope);
|
|
301
301
|
});
|
|
302
302
|
}
|
|
303
303
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"procedural-memory.js","names":["#store","#tracer","#inducer","#promoteAfterSuccesses","rule: Rule"],"sources":["../../src/tiers/procedural-memory.ts"],"sourcesContent":["import type {\n MemoryHit,\n MemoryOwner,\n MemoryProvenance,\n MemoryStatus,\n Rule,\n RunState,\n Sensitivity,\n SessionScope,\n Tracer,\n} from '@graphorin/core';\nimport {\n type InducedProcedure,\n runWorkflowInduction,\n type Trajectory,\n trajectoryFromRunState,\n type WorkflowInducer,\n} from '../consolidator/phases/induce.js';\nimport {\n ProcedureInductionNotConfiguredError,\n QuarantinePromotionRefusedError,\n} from '../errors/index.js';\nimport { newMemoryId } from '../internal/id.js';\nimport { detectMemoryInjection } from '../internal/injection-heuristics.js';\nimport { withMemorySpan } from '../internal/spans.js';\nimport type { MemoryStoreAdapter } from '../internal/storage-adapter.js';\n\n/** Default priority for an *induced* procedure - below the author default\n * (50) because an induced, still-quarantined procedure is provisional. */\nconst INDUCED_PRIORITY = 40;\n\n/**\n * Author-time rule payload accepted by {@link ProceduralMemory.define}.\n *\n * @stable\n */\nexport interface RuleInput {\n readonly text: string;\n /**\n * Free-form predicate evaluated by {@link ProceduralMemory.activate}.\n * The predicate language is intentionally narrow in v0.1: either\n * the literal string `'always'`, or a `'topic=...'` / `'tag=...'`\n * shorthand. Custom predicates should be expressed as a callable\n * matched in `activate(...)`'s `customMatchers` argument.\n */\n readonly condition?: string;\n /** Default `'public'` per DEC-126 - rules are NOT user data. */\n readonly sensitivity?: Sensitivity;\n readonly priority?: number;\n readonly tags?: ReadonlyArray<string>;\n /**\n * Optional structured workflow payload (P2-2). Usually set by\n * {@link ProceduralMemory.induce}, but accepted here so an author can\n * round-trip a hand-written procedure. See {@link Rule.steps}.\n */\n readonly steps?: ReadonlyArray<string>;\n /** Variable names abstracted into {@link RuleInput.steps} (P2-2). */\n readonly variables?: ReadonlyArray<string>;\n /** Verifiable success criteria stored with the procedure (P2-2). */\n readonly successCriteria?: ReadonlyArray<string>;\n /** Principal dimension (D3). Absent ⇒ NULL (treated `'user'`). */\n readonly owner?: MemoryOwner;\n}\n\n/**\n * Options for {@link ProceduralMemory.induce}.\n *\n * @stable\n */\nexport interface InduceOptions {\n /** Sensitivity of the stored procedure. Default `'internal'`. */\n readonly sensitivity?: Sensitivity;\n /** Priority of the stored procedure. Default {@link INDUCED_PRIORITY} (40). */\n readonly priority?: number;\n}\n\n/**\n * Predicate context passed to {@link ProceduralMemory.activate}.\n *\n * @stable\n */\nexport interface RuleActivationContext {\n readonly topic?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * `ProceduralMemory` - standing orders activated when the agent's\n * current context matches the rule's predicate. The activation rules\n * are deterministic so the agent runtime + ContextEngine can render\n * the active set into the system prompt every step.\n *\n * P2-2 adds {@link ProceduralMemory.induce}: distil a reusable workflow\n * from a successful agent trajectory and store it **quarantined** (it must\n * not drive actions until validated). Quarantined procedures are excluded\n * from {@link ProceduralMemory.activate} but remain visible to\n * {@link ProceduralMemory.list}.\n *\n * @stable\n */\nexport class ProceduralMemory {\n readonly #store: MemoryStoreAdapter;\n readonly #tracer: Tracer;\n /** Opt-in workflow inducer (P2-2). `null` ⇒ {@link induce} throws. */\n readonly #inducer: WorkflowInducer | null;\n /**\n * Promotion-by-demonstrated-success threshold (MCON-2 part 4).\n * `null` ⇒ outcomes are counted but never auto-promote.\n */\n readonly #promoteAfterSuccesses: number | null;\n\n constructor(args: {\n store: MemoryStoreAdapter;\n tracer: Tracer;\n inducer?: WorkflowInducer | null;\n promoteAfterSuccesses?: number | null;\n }) {\n this.#store = args.store;\n this.#tracer = args.tracer;\n this.#inducer = args.inducer ?? null;\n const k = args.promoteAfterSuccesses;\n this.#promoteAfterSuccesses =\n typeof k === 'number' && Number.isFinite(k) && k >= 1 ? Math.floor(k) : null;\n }\n\n /**\n * Record the outcome of one demonstrated reuse of a procedure\n * (MCON-2 part 4). A success increments the rule's persistent\n * `successCount`; when `procedurePromotion.afterSuccesses` is\n * configured and a QUARANTINED procedure reaches the threshold it is\n * promoted through {@link validate} - the injection gate still\n * applies, so a flagged text refuses promotion (surfaced as\n * `refused: true`) no matter how many successes accumulate.\n * Failures are observed but not persisted (no negative counter yet).\n *\n * Callers decide what \"success\" means - typically\n * `checkSuccessCriteria(...)` over the procedure's stored\n * `successCriteria`, or an operator's judgement.\n *\n * @stable\n */\n async recordOutcome(\n scope: SessionScope,\n ruleId: string,\n succeeded: boolean,\n ): Promise<{\n readonly successCount: number;\n readonly promoted: boolean;\n readonly refused: boolean;\n }> {\n return withMemorySpan(\n this.#tracer,\n 'memory.write.procedural',\n scope,\n { 'memory.procedural.action': 'record-outcome', 'memory.procedural.rule_id': ruleId },\n async (span) => {\n const store = this.#store.procedural;\n if (!succeeded || typeof store.recordSuccess !== 'function') {\n span.setAttributes({ 'memory.procedural.outcome.counted': false });\n return { successCount: 0, promoted: false, refused: false };\n }\n const successCount = await store.recordSuccess(ruleId);\n span.setAttributes({ 'memory.procedural.outcome.success_count': successCount });\n const threshold = this.#promoteAfterSuccesses;\n if (threshold === null || successCount < threshold) {\n return { successCount, promoted: false, refused: false };\n }\n const existing = (await store.list(scope)).find((rule) => rule.id === ruleId) ?? null;\n if (existing === null || existing.status !== 'quarantined') {\n return { successCount, promoted: false, refused: false };\n }\n try {\n await this.validate(\n scope,\n ruleId,\n `demonstrated-success: ${successCount} >= ${threshold}`,\n );\n span.setAttributes({ 'memory.procedural.outcome.promoted': true });\n return { successCount, promoted: true, refused: false };\n } catch (err) {\n if (err instanceof QuarantinePromotionRefusedError) {\n span.setAttributes({ 'memory.procedural.outcome.refused': true });\n return { successCount, promoted: false, refused: true };\n }\n throw err;\n }\n },\n );\n }\n\n /** Persist a rule. Returns the stored record. */\n async define(scope: SessionScope, input: RuleInput): Promise<Rule> {\n return withMemorySpan(this.#tracer, 'memory.write.procedural', scope, {}, async (span) => {\n const now = new Date().toISOString();\n const rule: Rule = {\n id: newMemoryId('rule'),\n kind: 'procedural',\n userId: scope.userId,\n ...(scope.sessionId !== undefined ? { sessionId: scope.sessionId } : {}),\n ...(scope.agentId !== undefined ? { agentId: scope.agentId } : {}),\n sensitivity: input.sensitivity ?? 'public',\n text: input.text,\n ...(input.condition !== undefined ? { condition: input.condition } : {}),\n priority: input.priority ?? 50,\n ...(input.tags !== undefined ? { tags: Object.freeze([...input.tags]) } : {}),\n ...(input.steps !== undefined ? { steps: Object.freeze([...input.steps]) } : {}),\n ...(input.variables !== undefined\n ? { variables: Object.freeze([...input.variables]) }\n : {}),\n ...(input.successCriteria !== undefined\n ? { successCriteria: Object.freeze([...input.successCriteria]) }\n : {}),\n ...(input.owner !== undefined ? { owner: input.owner } : {}),\n createdAt: now,\n updatedAt: now,\n };\n await this.#store.procedural.add(rule);\n span.setAttributes({\n 'memory.procedural.priority': rule.priority,\n 'memory.procedural.text_length': input.text.length,\n });\n return rule;\n });\n }\n\n /**\n * Induce a reusable procedure (P2-2) from a successful agent trajectory\n * and store it **quarantined** + `provenance: 'induction'` (P1-4). Returns\n * the stored {@link Rule}, or `null` when the trajectory was unsuccessful /\n * empty or the inducer produced nothing inducible.\n *\n * Throws {@link ProcedureInductionNotConfiguredError} when no inducer was\n * configured (`createMemory({ procedureInduction: { provider } })`).\n */\n async induce(\n scope: SessionScope,\n trajectory: Trajectory,\n opts: InduceOptions = {},\n ): Promise<Rule | null> {\n const inducer = this.#inducer;\n if (inducer === null) throw new ProcedureInductionNotConfiguredError();\n return withMemorySpan(\n this.#tracer,\n 'memory.write.procedural',\n scope,\n { 'memory.procedural.action': 'induce' },\n async (span) => {\n const induced = await runWorkflowInduction(trajectory, inducer, {});\n if (induced === null) {\n span.setAttributes({ 'memory.procedural.induced': 0 });\n return null;\n }\n const now = new Date().toISOString();\n const rule: Rule = {\n id: newMemoryId('rule'),\n kind: 'procedural',\n userId: scope.userId,\n ...(scope.sessionId !== undefined ? { sessionId: scope.sessionId } : {}),\n ...(scope.agentId !== undefined ? { agentId: scope.agentId } : {}),\n // Induced from a run ⇒ user-derived, so 'internal' (not the public\n // default for author-defined rules).\n sensitivity: opts.sensitivity ?? 'internal',\n text: renderProcedureText(induced),\n priority: opts.priority ?? INDUCED_PRIORITY,\n steps: Object.freeze([...induced.steps]),\n variables: Object.freeze([...induced.variables]),\n successCriteria: Object.freeze([...induced.successCriteria]),\n // Highest-poisoning-risk write in the system - procedures drive\n // actions, so it lands quarantined + provenance-tagged (P1-4).\n provenance: 'induction' satisfies MemoryProvenance,\n status: 'quarantined' satisfies MemoryStatus,\n // D3: an induced procedure is the agent's own inference.\n owner: 'agent',\n createdAt: now,\n updatedAt: now,\n };\n await this.#store.procedural.add(rule);\n span.setAttributes({\n 'memory.procedural.induced': 1,\n 'memory.procedural.steps': induced.steps.length,\n 'memory.procedural.variables': induced.variables.length,\n });\n return rule;\n },\n );\n }\n\n /**\n * Convenience over {@link induce}: distil the {@link Trajectory} from a\n * completed {@link RunState} (the agent's already-emitted run state) and\n * induce a procedure. The success signal is `status === 'completed'`.\n */\n async induceFromRun(\n scope: SessionScope,\n run: RunState,\n opts: InduceOptions = {},\n ): Promise<Rule | null> {\n return this.induce(scope, trajectoryFromRunState(run), opts);\n }\n\n /** Soft-delete a rule. */\n async remove(scope: SessionScope, ruleId: string, reason?: string): Promise<void> {\n await withMemorySpan(\n this.#tracer,\n 'memory.write.procedural',\n scope,\n { 'memory.procedural.action': 'remove', 'memory.procedural.rule_id': ruleId },\n async () => {\n await this.#store.procedural.remove(ruleId, reason);\n },\n );\n }\n\n /** List every active (non-deleted) rule for the supplied scope. */\n async list(scope: SessionScope): Promise<ReadonlyArray<Rule>> {\n return withMemorySpan(this.#tracer, 'memory.read.procedural', scope, {}, async (span) => {\n const out = await this.#store.procedural.list(scope);\n span.setAttributes({ 'memory.read.procedural.count': out.length });\n return out;\n });\n }\n\n /**\n * Return the rules active under `context`. Rules without a\n * `condition` are always active; the bundled predicate vocabulary\n * supports the literals `'always'`, `'topic=<topic>'`, and\n * `'tag=<tag>'`. Anything outside that grammar is treated as\n * always-active so callers do not silently lose rules.\n *\n * **Quarantined procedures are excluded** (P1-4 / P2-2): an induced\n * procedure must not drive actions until validated, so activation - which\n * feeds the system prompt - never surfaces it.\n */\n async activate(\n scope: SessionScope,\n context: RuleActivationContext = {},\n ): Promise<ReadonlyArray<Rule>> {\n const rules = await this.list(scope);\n return rules\n .filter((rule) => rule.status !== 'quarantined' && predicateMatches(rule, context))\n .sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Runbook content search (D3): \"find the procedure for this task\" -\n * lexical recall over rule text, as opposed to predicate\n * {@link activate}. Returns **whole validated procedures** (the full\n * {@link Rule} incl. steps / variables / success criteria) so a match\n * can be followed file-style rather than re-synthesized from\n * fragments. Quarantined (unvalidated induced) procedures are\n * excluded - they must not drive actions - unless the inspector opts\n * in via `includeQuarantined`.\n *\n * Uses the storage adapter's FTS surface when available\n * (`procedural.search`, the default `@graphorin/store-sqlite` adapter\n * implements it via migration 028); otherwise degrades to an offline\n * in-memory token-overlap scan over {@link list}, so custom adapters\n * keep working without the index.\n */\n async search(\n scope: SessionScope,\n query: string,\n opts: { readonly topK?: number; readonly includeQuarantined?: boolean } = {},\n ): Promise<ReadonlyArray<MemoryHit<Rule>>> {\n return withMemorySpan(\n this.#tracer,\n 'memory.read.procedural',\n scope,\n { 'memory.procedural.action': 'search' },\n async (span) => {\n const topK = opts.topK ?? 5;\n const store = this.#store.procedural;\n if (typeof store.search === 'function') {\n const hits = await store.search(scope, query, {\n topK,\n ...(opts.includeQuarantined === true ? { includeQuarantined: true } : {}),\n });\n span.setAttributes({\n 'memory.read.procedural.count': hits.length,\n 'memory.read.procedural.search_backend': 'store',\n });\n return hits;\n }\n // Fallback: offline token-overlap scoring over list(). Coarse by\n // design - it exists so the runbook surface degrades instead of\n // disappearing on adapters without an FTS index.\n const tokens = tokenizeForRunbook(query);\n const rules = await this.list(scope);\n const scored = rules\n .filter((rule) => opts.includeQuarantined === true || rule.status !== 'quarantined')\n .map((rule) => {\n const haystack = tokenizeForRunbook(\n [rule.text, ...(rule.steps ?? []), ...(rule.tags ?? [])].join(' '),\n );\n const overlap = tokens.filter((t) => haystack.includes(t)).length;\n return { rule, overlap };\n })\n .filter((entry) => entry.overlap > 0)\n .sort((a, b) => b.overlap - a.overlap || (a.rule.id < b.rule.id ? -1 : 1))\n .slice(0, topK);\n span.setAttributes({\n 'memory.read.procedural.count': scored.length,\n 'memory.read.procedural.search_backend': 'fallback',\n });\n return scored.map((entry) => ({\n record: entry.rule,\n score: tokens.length === 0 ? 0 : entry.overlap / tokens.length,\n signals: { lexical: entry.overlap },\n }));\n },\n );\n }\n\n /**\n * Promote a quarantined (induced) procedure into `activate()` (MCON-2).\n * Mirrors {@link SemanticMemory.validate}: re-derives the injection verdict\n * from the stored rule text and **refuses** promotion of an injection-flagged\n * procedure unless an operator passes `{ force: true }`. Induced procedures\n * drive *actions*, so this gate matters most for them.\n */\n async validate(\n scope: SessionScope,\n ruleId: string,\n reason?: string,\n options?: { readonly force?: boolean },\n ): Promise<void> {\n await withMemorySpan(\n this.#tracer,\n 'memory.write.procedural',\n scope,\n { 'memory.procedural.action': 'validate', 'memory.procedural.rule_id': ruleId },\n async (span) => {\n const store = this.#store.procedural;\n if (typeof store.setStatus !== 'function') {\n throw new TypeError(\n '[graphorin/memory] ProceduralMemory.validate(...) requires a storage adapter that implements `procedural.setStatus(id, status)`. ' +\n 'The default `@graphorin/store-sqlite` adapter implements it; custom adapters can opt in via ProceduralMemoryStoreExt.',\n );\n }\n const force = options?.force === true;\n // No store get-by-id for rules - list() already surfaces quarantined.\n const existing = (await store.list(scope)).find((rule) => rule.id === ruleId) ?? null;\n if (existing !== null && !force) {\n const injection = detectMemoryInjection(existing.text);\n if (injection.flagged) {\n span.setAttributes({ 'memory.procedural.validate.refused': true });\n throw new QuarantinePromotionRefusedError(ruleId, injection.markers);\n }\n }\n span.setAttributes({ 'memory.procedural.validate.forced': force });\n await store.setStatus(ruleId, 'active', reason);\n },\n );\n }\n}\n\n/** Render an induced procedure into the human-readable `Rule.text`. */\nfunction renderProcedureText(procedure: InducedProcedure): string {\n const lines = [procedure.title, ...procedure.steps.map((step, i) => `${i + 1}. ${step}`)];\n return lines.join('\\n');\n}\n\n/** Lowercased word tokens for the offline runbook-search fallback (D3). */\nfunction tokenizeForRunbook(text: string): ReadonlyArray<string> {\n return text\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter((token) => token.length > 1);\n}\n\nfunction predicateMatches(rule: Rule, context: RuleActivationContext): boolean {\n if (rule.condition === undefined || rule.condition === 'always') return true;\n if (rule.condition.startsWith('topic=')) {\n const wanted = rule.condition.slice('topic='.length).trim();\n return context.topic === wanted;\n }\n if (rule.condition.startsWith('tag=')) {\n const wanted = rule.condition.slice('tag='.length).trim();\n return (context.tags ?? []).includes(wanted);\n }\n return true;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"procedural-memory.js","names":["#store","#tracer","#inducer","#promoteAfterSuccesses","rule: Rule"],"sources":["../../src/tiers/procedural-memory.ts"],"sourcesContent":["import type {\n MemoryHit,\n MemoryOwner,\n MemoryProvenance,\n MemoryStatus,\n Rule,\n RunState,\n Sensitivity,\n SessionScope,\n Tracer,\n} from '@graphorin/core';\nimport {\n type InducedProcedure,\n runWorkflowInduction,\n type Trajectory,\n trajectoryFromRunState,\n type WorkflowInducer,\n} from '../consolidator/phases/induce.js';\nimport {\n ProcedureInductionNotConfiguredError,\n QuarantinePromotionRefusedError,\n} from '../errors/index.js';\nimport { newMemoryId } from '../internal/id.js';\nimport { detectMemoryInjection } from '../internal/injection-heuristics.js';\nimport { withMemorySpan } from '../internal/spans.js';\nimport type { MemoryStoreAdapter } from '../internal/storage-adapter.js';\n\n/** Default priority for an *induced* procedure - below the author default\n * (50) because an induced, still-quarantined procedure is provisional. */\nconst INDUCED_PRIORITY = 40;\n\n/**\n * Author-time rule payload accepted by {@link ProceduralMemory.define}.\n *\n * @stable\n */\nexport interface RuleInput {\n readonly text: string;\n /**\n * Free-form predicate evaluated by {@link ProceduralMemory.activate}.\n * The predicate language is intentionally narrow in v0.1: either\n * the literal string `'always'`, or a `'topic=...'` / `'tag=...'`\n * shorthand. Custom predicates should be expressed as a callable\n * matched in `activate(...)`'s `customMatchers` argument.\n */\n readonly condition?: string;\n /** Default `'public'` per DEC-126 - rules are NOT user data. */\n readonly sensitivity?: Sensitivity;\n readonly priority?: number;\n readonly tags?: ReadonlyArray<string>;\n /**\n * Optional structured workflow payload (P2-2). Usually set by\n * {@link ProceduralMemory.induce}, but accepted here so an author can\n * round-trip a hand-written procedure. See {@link Rule.steps}.\n */\n readonly steps?: ReadonlyArray<string>;\n /** Variable names abstracted into {@link RuleInput.steps} (P2-2). */\n readonly variables?: ReadonlyArray<string>;\n /** Verifiable success criteria stored with the procedure (P2-2). */\n readonly successCriteria?: ReadonlyArray<string>;\n /** Principal dimension (D3). Absent ⇒ NULL (treated `'user'`). */\n readonly owner?: MemoryOwner;\n}\n\n/**\n * Options for {@link ProceduralMemory.induce}.\n *\n * @stable\n */\nexport interface InduceOptions {\n /** Sensitivity of the stored procedure. Default `'internal'`. */\n readonly sensitivity?: Sensitivity;\n /** Priority of the stored procedure. Default `INDUCED_PRIORITY` (40). */\n readonly priority?: number;\n}\n\n/**\n * Predicate context passed to {@link ProceduralMemory.activate}.\n *\n * @stable\n */\nexport interface RuleActivationContext {\n readonly topic?: string;\n readonly tags?: ReadonlyArray<string>;\n}\n\n/**\n * `ProceduralMemory` - standing orders activated when the agent's\n * current context matches the rule's predicate. The activation rules\n * are deterministic so the agent runtime + ContextEngine can render\n * the active set into the system prompt every step.\n *\n * P2-2 adds {@link ProceduralMemory.induce}: distil a reusable workflow\n * from a successful agent trajectory and store it **quarantined** (it must\n * not drive actions until validated). Quarantined procedures are excluded\n * from {@link ProceduralMemory.activate} but remain visible to\n * {@link ProceduralMemory.list}.\n *\n * @stable\n */\nexport class ProceduralMemory {\n readonly #store: MemoryStoreAdapter;\n readonly #tracer: Tracer;\n /** Opt-in workflow inducer (P2-2). `null` ⇒ {@link induce} throws. */\n readonly #inducer: WorkflowInducer | null;\n /**\n * Promotion-by-demonstrated-success threshold (MCON-2 part 4).\n * `null` ⇒ outcomes are counted but never auto-promote.\n */\n readonly #promoteAfterSuccesses: number | null;\n\n constructor(args: {\n store: MemoryStoreAdapter;\n tracer: Tracer;\n inducer?: WorkflowInducer | null;\n promoteAfterSuccesses?: number | null;\n }) {\n this.#store = args.store;\n this.#tracer = args.tracer;\n this.#inducer = args.inducer ?? null;\n const k = args.promoteAfterSuccesses;\n this.#promoteAfterSuccesses =\n typeof k === 'number' && Number.isFinite(k) && k >= 1 ? Math.floor(k) : null;\n }\n\n /**\n * Record the outcome of one demonstrated reuse of a procedure\n * (MCON-2 part 4). A success increments the rule's persistent\n * `successCount`; when `procedurePromotion.afterSuccesses` is\n * configured and a QUARANTINED procedure reaches the threshold it is\n * promoted through {@link validate} - the injection gate still\n * applies, so a flagged text refuses promotion (surfaced as\n * `refused: true`) no matter how many successes accumulate.\n * Failures are observed but not persisted (no negative counter yet).\n *\n * Callers decide what \"success\" means - typically\n * `checkSuccessCriteria(...)` over the procedure's stored\n * `successCriteria`, or an operator's judgement.\n *\n * @stable\n */\n async recordOutcome(\n scope: SessionScope,\n ruleId: string,\n succeeded: boolean,\n ): Promise<{\n readonly successCount: number;\n readonly promoted: boolean;\n readonly refused: boolean;\n }> {\n return withMemorySpan(\n this.#tracer,\n 'memory.write.procedural',\n scope,\n { 'memory.procedural.action': 'record-outcome', 'memory.procedural.rule_id': ruleId },\n async (span) => {\n const store = this.#store.procedural;\n if (!succeeded || typeof store.recordSuccess !== 'function') {\n span.setAttributes({ 'memory.procedural.outcome.counted': false });\n return { successCount: 0, promoted: false, refused: false };\n }\n const successCount = await store.recordSuccess(ruleId);\n span.setAttributes({ 'memory.procedural.outcome.success_count': successCount });\n const threshold = this.#promoteAfterSuccesses;\n if (threshold === null || successCount < threshold) {\n return { successCount, promoted: false, refused: false };\n }\n const existing = (await store.list(scope)).find((rule) => rule.id === ruleId) ?? null;\n if (existing === null || existing.status !== 'quarantined') {\n return { successCount, promoted: false, refused: false };\n }\n try {\n await this.validate(\n scope,\n ruleId,\n `demonstrated-success: ${successCount} >= ${threshold}`,\n );\n span.setAttributes({ 'memory.procedural.outcome.promoted': true });\n return { successCount, promoted: true, refused: false };\n } catch (err) {\n if (err instanceof QuarantinePromotionRefusedError) {\n span.setAttributes({ 'memory.procedural.outcome.refused': true });\n return { successCount, promoted: false, refused: true };\n }\n throw err;\n }\n },\n );\n }\n\n /** Persist a rule. Returns the stored record. */\n async define(scope: SessionScope, input: RuleInput): Promise<Rule> {\n return withMemorySpan(this.#tracer, 'memory.write.procedural', scope, {}, async (span) => {\n const now = new Date().toISOString();\n const rule: Rule = {\n id: newMemoryId('rule'),\n kind: 'procedural',\n userId: scope.userId,\n ...(scope.sessionId !== undefined ? { sessionId: scope.sessionId } : {}),\n ...(scope.agentId !== undefined ? { agentId: scope.agentId } : {}),\n sensitivity: input.sensitivity ?? 'public',\n text: input.text,\n ...(input.condition !== undefined ? { condition: input.condition } : {}),\n priority: input.priority ?? 50,\n ...(input.tags !== undefined ? { tags: Object.freeze([...input.tags]) } : {}),\n ...(input.steps !== undefined ? { steps: Object.freeze([...input.steps]) } : {}),\n ...(input.variables !== undefined\n ? { variables: Object.freeze([...input.variables]) }\n : {}),\n ...(input.successCriteria !== undefined\n ? { successCriteria: Object.freeze([...input.successCriteria]) }\n : {}),\n ...(input.owner !== undefined ? { owner: input.owner } : {}),\n createdAt: now,\n updatedAt: now,\n };\n await this.#store.procedural.add(rule);\n span.setAttributes({\n 'memory.procedural.priority': rule.priority,\n 'memory.procedural.text_length': input.text.length,\n });\n return rule;\n });\n }\n\n /**\n * Induce a reusable procedure (P2-2) from a successful agent trajectory\n * and store it **quarantined** + `provenance: 'induction'` (P1-4). Returns\n * the stored {@link Rule}, or `null` when the trajectory was unsuccessful /\n * empty or the inducer produced nothing inducible.\n *\n * Throws {@link ProcedureInductionNotConfiguredError} when no inducer was\n * configured (`createMemory({ procedureInduction: { provider } })`).\n */\n async induce(\n scope: SessionScope,\n trajectory: Trajectory,\n opts: InduceOptions = {},\n ): Promise<Rule | null> {\n const inducer = this.#inducer;\n if (inducer === null) throw new ProcedureInductionNotConfiguredError();\n return withMemorySpan(\n this.#tracer,\n 'memory.write.procedural',\n scope,\n { 'memory.procedural.action': 'induce' },\n async (span) => {\n const induced = await runWorkflowInduction(trajectory, inducer, {});\n if (induced === null) {\n span.setAttributes({ 'memory.procedural.induced': 0 });\n return null;\n }\n const now = new Date().toISOString();\n const rule: Rule = {\n id: newMemoryId('rule'),\n kind: 'procedural',\n userId: scope.userId,\n ...(scope.sessionId !== undefined ? { sessionId: scope.sessionId } : {}),\n ...(scope.agentId !== undefined ? { agentId: scope.agentId } : {}),\n // Induced from a run ⇒ user-derived, so 'internal' (not the public\n // default for author-defined rules).\n sensitivity: opts.sensitivity ?? 'internal',\n text: renderProcedureText(induced),\n priority: opts.priority ?? INDUCED_PRIORITY,\n steps: Object.freeze([...induced.steps]),\n variables: Object.freeze([...induced.variables]),\n successCriteria: Object.freeze([...induced.successCriteria]),\n // Highest-poisoning-risk write in the system - procedures drive\n // actions, so it lands quarantined + provenance-tagged (P1-4).\n provenance: 'induction' satisfies MemoryProvenance,\n status: 'quarantined' satisfies MemoryStatus,\n // D3: an induced procedure is the agent's own inference.\n owner: 'agent',\n createdAt: now,\n updatedAt: now,\n };\n await this.#store.procedural.add(rule);\n span.setAttributes({\n 'memory.procedural.induced': 1,\n 'memory.procedural.steps': induced.steps.length,\n 'memory.procedural.variables': induced.variables.length,\n });\n return rule;\n },\n );\n }\n\n /**\n * Convenience over {@link induce}: distil the {@link Trajectory} from a\n * completed {@link RunState} (the agent's already-emitted run state) and\n * induce a procedure. The success signal is `status === 'completed'`.\n */\n async induceFromRun(\n scope: SessionScope,\n run: RunState,\n opts: InduceOptions = {},\n ): Promise<Rule | null> {\n return this.induce(scope, trajectoryFromRunState(run), opts);\n }\n\n /** Soft-delete a rule. */\n async remove(scope: SessionScope, ruleId: string, reason?: string): Promise<void> {\n await withMemorySpan(\n this.#tracer,\n 'memory.write.procedural',\n scope,\n { 'memory.procedural.action': 'remove', 'memory.procedural.rule_id': ruleId },\n async () => {\n await this.#store.procedural.remove(ruleId, reason);\n },\n );\n }\n\n /** List every active (non-deleted) rule for the supplied scope. */\n async list(scope: SessionScope): Promise<ReadonlyArray<Rule>> {\n return withMemorySpan(this.#tracer, 'memory.read.procedural', scope, {}, async (span) => {\n const out = await this.#store.procedural.list(scope);\n span.setAttributes({ 'memory.read.procedural.count': out.length });\n return out;\n });\n }\n\n /**\n * Return the rules active under `context`. Rules without a\n * `condition` are always active; the bundled predicate vocabulary\n * supports the literals `'always'`, `'topic=<topic>'`, and\n * `'tag=<tag>'`. Anything outside that grammar is treated as\n * always-active so callers do not silently lose rules.\n *\n * **Quarantined procedures are excluded** (P1-4 / P2-2): an induced\n * procedure must not drive actions until validated, so activation - which\n * feeds the system prompt - never surfaces it.\n */\n async activate(\n scope: SessionScope,\n context: RuleActivationContext = {},\n ): Promise<ReadonlyArray<Rule>> {\n const rules = await this.list(scope);\n return rules\n .filter((rule) => rule.status !== 'quarantined' && predicateMatches(rule, context))\n .sort((a, b) => b.priority - a.priority);\n }\n\n /**\n * Runbook content search (D3): \"find the procedure for this task\" -\n * lexical recall over rule text, as opposed to predicate\n * {@link activate}. Returns **whole validated procedures** (the full\n * {@link Rule} incl. steps / variables / success criteria) so a match\n * can be followed file-style rather than re-synthesized from\n * fragments. Quarantined (unvalidated induced) procedures are\n * excluded - they must not drive actions - unless the inspector opts\n * in via `includeQuarantined`.\n *\n * Uses the storage adapter's FTS surface when available\n * (`procedural.search`, the default `@graphorin/store-sqlite` adapter\n * implements it via migration 028); otherwise degrades to an offline\n * in-memory token-overlap scan over {@link list}, so custom adapters\n * keep working without the index.\n */\n async search(\n scope: SessionScope,\n query: string,\n opts: { readonly topK?: number; readonly includeQuarantined?: boolean } = {},\n ): Promise<ReadonlyArray<MemoryHit<Rule>>> {\n return withMemorySpan(\n this.#tracer,\n 'memory.read.procedural',\n scope,\n { 'memory.procedural.action': 'search' },\n async (span) => {\n const topK = opts.topK ?? 5;\n const store = this.#store.procedural;\n if (typeof store.search === 'function') {\n const hits = await store.search(scope, query, {\n topK,\n ...(opts.includeQuarantined === true ? { includeQuarantined: true } : {}),\n });\n span.setAttributes({\n 'memory.read.procedural.count': hits.length,\n 'memory.read.procedural.search_backend': 'store',\n });\n return hits;\n }\n // Fallback: offline token-overlap scoring over list(). Coarse by\n // design - it exists so the runbook surface degrades instead of\n // disappearing on adapters without an FTS index.\n const tokens = tokenizeForRunbook(query);\n const rules = await this.list(scope);\n const scored = rules\n .filter((rule) => opts.includeQuarantined === true || rule.status !== 'quarantined')\n .map((rule) => {\n const haystack = tokenizeForRunbook(\n [rule.text, ...(rule.steps ?? []), ...(rule.tags ?? [])].join(' '),\n );\n const overlap = tokens.filter((t) => haystack.includes(t)).length;\n return { rule, overlap };\n })\n .filter((entry) => entry.overlap > 0)\n .sort((a, b) => b.overlap - a.overlap || (a.rule.id < b.rule.id ? -1 : 1))\n .slice(0, topK);\n span.setAttributes({\n 'memory.read.procedural.count': scored.length,\n 'memory.read.procedural.search_backend': 'fallback',\n });\n return scored.map((entry) => ({\n record: entry.rule,\n score: tokens.length === 0 ? 0 : entry.overlap / tokens.length,\n signals: { lexical: entry.overlap },\n }));\n },\n );\n }\n\n /**\n * Promote a quarantined (induced) procedure into `activate()` (MCON-2).\n * Mirrors {@link SemanticMemory.validate}: re-derives the injection verdict\n * from the stored rule text and **refuses** promotion of an injection-flagged\n * procedure unless an operator passes `{ force: true }`. Induced procedures\n * drive *actions*, so this gate matters most for them.\n */\n async validate(\n scope: SessionScope,\n ruleId: string,\n reason?: string,\n options?: { readonly force?: boolean },\n ): Promise<void> {\n await withMemorySpan(\n this.#tracer,\n 'memory.write.procedural',\n scope,\n { 'memory.procedural.action': 'validate', 'memory.procedural.rule_id': ruleId },\n async (span) => {\n const store = this.#store.procedural;\n if (typeof store.setStatus !== 'function') {\n throw new TypeError(\n '[graphorin/memory] ProceduralMemory.validate(...) requires a storage adapter that implements `procedural.setStatus(id, status)`. ' +\n 'The default `@graphorin/store-sqlite` adapter implements it; custom adapters can opt in via ProceduralMemoryStoreExt.',\n );\n }\n const force = options?.force === true;\n // No store get-by-id for rules - list() already surfaces quarantined.\n const existing = (await store.list(scope)).find((rule) => rule.id === ruleId) ?? null;\n if (existing !== null && !force) {\n const injection = detectMemoryInjection(existing.text);\n if (injection.flagged) {\n span.setAttributes({ 'memory.procedural.validate.refused': true });\n throw new QuarantinePromotionRefusedError(ruleId, injection.markers);\n }\n }\n span.setAttributes({ 'memory.procedural.validate.forced': force });\n await store.setStatus(ruleId, 'active', reason, scope);\n },\n );\n }\n}\n\n/** Render an induced procedure into the human-readable `Rule.text`. */\nfunction renderProcedureText(procedure: InducedProcedure): string {\n const lines = [procedure.title, ...procedure.steps.map((step, i) => `${i + 1}. ${step}`)];\n return lines.join('\\n');\n}\n\n/** Lowercased word tokens for the offline runbook-search fallback (D3). */\nfunction tokenizeForRunbook(text: string): ReadonlyArray<string> {\n return text\n .toLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter((token) => token.length > 1);\n}\n\nfunction predicateMatches(rule: Rule, context: RuleActivationContext): boolean {\n if (rule.condition === undefined || rule.condition === 'always') return true;\n if (rule.condition.startsWith('topic=')) {\n const wanted = rule.condition.slice('topic='.length).trim();\n return context.topic === wanted;\n }\n if (rule.condition.startsWith('tag=')) {\n const wanted = rule.condition.slice('tag='.length).trim();\n return (context.tags ?? []).includes(wanted);\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -105,6 +105,20 @@ type FusionStrategy = {
|
|
|
105
105
|
*
|
|
106
106
|
* @stable
|
|
107
107
|
*/
|
|
108
|
+
/**
|
|
109
|
+
* Search options an operator may default at construction time via
|
|
110
|
+
* `createMemory({ searchDefaults })` (W-086) - the advanced-retrieval
|
|
111
|
+
* switches (fan-out, HyDE, graph expansion, fusion, decay) that the
|
|
112
|
+
* model-facing surfaces (`fact_search`, auto-recall, `deep_recall`)
|
|
113
|
+
* cannot reach per-call. Deliberately a `Pick` that EXCLUDES the
|
|
114
|
+
* trust-sensitive predicates (`includeQuarantined`, `includeSuperseded`,
|
|
115
|
+
* `trustWeighting`, `owner`): configuration must not be able to silently
|
|
116
|
+
* weaken trust gates for every caller. Per-call options always win
|
|
117
|
+
* key-by-key.
|
|
118
|
+
*
|
|
119
|
+
* @stable
|
|
120
|
+
*/
|
|
121
|
+
type SemanticSearchDefaults = Pick<FactSearchOptions, 'multiQuery' | 'hyde' | 'expandHops' | 'entityMatch' | 'graphScoring' | 'fusion' | 'decay' | 'candidateTopK'>;
|
|
108
122
|
interface FactSearchOptions {
|
|
109
123
|
readonly topK?: number;
|
|
110
124
|
readonly signal?: AbortSignal;
|
|
@@ -287,6 +301,14 @@ interface IterativeSearchOptions extends FactSearchOptions {
|
|
|
287
301
|
* requests and tests.
|
|
288
302
|
*/
|
|
289
303
|
readonly forceHard?: boolean;
|
|
304
|
+
/**
|
|
305
|
+
* Difficulty-gate threshold in `[0, 1]` for THIS call (W-088). The
|
|
306
|
+
* gate's signal lexicon is **English-only** - for non-English
|
|
307
|
+
* deployments lower the threshold or use {@link forceHard}. Omitted ⇒
|
|
308
|
+
* the facade default (`iterativeRetrieval.difficultyThreshold`) or the
|
|
309
|
+
* built-in `0.5`.
|
|
310
|
+
*/
|
|
311
|
+
readonly difficultyThreshold?: number;
|
|
290
312
|
}
|
|
291
313
|
/**
|
|
292
314
|
* Outcome of {@link SemanticMemory.searchIterative}. Beyond the ranked
|
|
@@ -407,12 +429,27 @@ declare class SemanticMemory {
|
|
|
407
429
|
grader?: RetrievalGrader;
|
|
408
430
|
/** Default total-pass cap for `searchIterative`. Default 3. */
|
|
409
431
|
iterativeMaxIterations?: number;
|
|
432
|
+
/**
|
|
433
|
+
* Default difficulty-gate threshold for `searchIterative` (W-088).
|
|
434
|
+
* Omitted ⇒ the gate's built-in `0.5`. Per-call
|
|
435
|
+
* `difficultyThreshold` overrides it.
|
|
436
|
+
*/
|
|
437
|
+
iterativeDifficultyThreshold?: number;
|
|
410
438
|
/**
|
|
411
439
|
* Weights for the rank-time trust discount (C5). Reuses the
|
|
412
440
|
* eviction-path `SalienceWeights` semantics; defaults to
|
|
413
441
|
* `DEFAULT_SALIENCE_WEIGHTS`.
|
|
414
442
|
*/
|
|
415
443
|
trustWeights?: SalienceWeights;
|
|
444
|
+
/**
|
|
445
|
+
* Construction-time retrieval defaults (W-086) merged under every
|
|
446
|
+
* `search(...)` call - see {@link SemanticSearchDefaults}. Because
|
|
447
|
+
* the merge happens inside `search()`, the model-facing surfaces
|
|
448
|
+
* (`fact_search`, auto-recall, `deep_recall`) inherit them without
|
|
449
|
+
* any per-surface wiring; per-call options override key-by-key (so
|
|
450
|
+
* e.g. `deep_recall`'s widen-pass `expandHops` still wins).
|
|
451
|
+
*/
|
|
452
|
+
searchDefaults?: SemanticSearchDefaults;
|
|
416
453
|
});
|
|
417
454
|
/** Replace the active reranker. Returns the previous instance. */
|
|
418
455
|
setReranker(reranker: ReRanker): ReRanker;
|
|
@@ -435,7 +472,7 @@ declare class SemanticMemory {
|
|
|
435
472
|
*/
|
|
436
473
|
rememberWithDecision(scope: SessionScope, input: FactInput, options?: FactRememberOptions): Promise<RememberOutcome>;
|
|
437
474
|
/** Hybrid (vector + FTS5) search merged through the configured reranker. */
|
|
438
|
-
search(scope: SessionScope, query: string,
|
|
475
|
+
search(scope: SessionScope, query: string, callOpts?: FactSearchOptions): Promise<ReadonlyArray<MemoryHit<Fact>>>;
|
|
439
476
|
/**
|
|
440
477
|
* Gated, iterative ("deep") recall for hard queries (P2-4). A cheap
|
|
441
478
|
* local heuristic ({@link assessQueryDifficulty}) decides whether the
|
|
@@ -507,8 +544,25 @@ declare class SemanticMemory {
|
|
|
507
544
|
validate(scope: SessionScope, factId: string, reason?: string, options?: {
|
|
508
545
|
readonly force?: boolean;
|
|
509
546
|
}): Promise<void>;
|
|
510
|
-
/**
|
|
511
|
-
|
|
547
|
+
/**
|
|
548
|
+
* Mark `oldId` superseded by a new fact. Returns the new record.
|
|
549
|
+
*
|
|
550
|
+
* W-019 (security-first, knowledge-preserving): when the successor
|
|
551
|
+
* lands QUARANTINED (the default for `extraction`/synthesized
|
|
552
|
+
* provenance), the old ACTIVE fact's validity interval is NOT closed
|
|
553
|
+
* - default recall keeps returning the old knowledge until the
|
|
554
|
+
* successor passes {@link validate}, which completes the closure.
|
|
555
|
+
* The link is recorded on the successor's `supersedes` field. With
|
|
556
|
+
* `autoPromoteSynthesized` (threaded from the consolidator's
|
|
557
|
+
* `autoPromoteExtraction` escape hatch) an injection-clean successor
|
|
558
|
+
* is active immediately and the interval closes right away - the
|
|
559
|
+
* pre-W-019 behaviour. Inverting the default (auto-activating the
|
|
560
|
+
* successor) would hand a MINJA attacker instant active memory via
|
|
561
|
+
* any text the reconciler classifies as an 'update'.
|
|
562
|
+
*/
|
|
563
|
+
supersede(scope: SessionScope, oldId: string, newInput: FactInput, reason?: string, options?: {
|
|
564
|
+
readonly autoPromoteSynthesized?: boolean;
|
|
565
|
+
}): Promise<{
|
|
512
566
|
readonly old: string;
|
|
513
567
|
readonly new: Fact;
|
|
514
568
|
}>;
|
|
@@ -538,5 +592,5 @@ declare class SemanticMemory {
|
|
|
538
592
|
static fuseWeighted<TRecord extends Fact>(lists: ReadonlyArray<ReadonlyArray<MemoryHit<TRecord>>>, weights: ReadonlyArray<number> | undefined, k?: number): ReadonlyArray<MemoryHit<TRecord>>;
|
|
539
593
|
}
|
|
540
594
|
//#endregion
|
|
541
|
-
export { FactInput, FactRememberOptions, FactSearchOptions, FusionStrategy, FusionWeights, IterativeRecallResult, IterativeSearchOptions, RememberOutcome, SemanticMemory };
|
|
595
|
+
export { FactInput, FactRememberOptions, FactSearchOptions, FusionStrategy, FusionWeights, IterativeRecallResult, IterativeSearchOptions, RememberOutcome, SemanticMemory, SemanticSearchDefaults };
|
|
542
596
|
//# sourceMappingURL=semantic-memory.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-memory.d.ts","names":[],"sources":["../../src/tiers/semantic-memory.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;AA0CA;;;;;AAmC8B,UAnCb,SAAA,CAmCa;EAYb,SAAA,IAAA,EAAA,MAAa;EA6BlB,SAAA,OAAA,CAAc,EAAA,MAAA;
|
|
1
|
+
{"version":3,"file":"semantic-memory.d.ts","names":[],"sources":["../../src/tiers/semantic-memory.ts"],"sourcesContent":[],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { withMemorySpan } from "../internal/spans.js";
|
|
2
2
|
import { DEFAULT_SALIENCE_WEIGHTS } from "../consolidator/decay.js";
|
|
3
3
|
import { QuarantinePromotionRefusedError } from "../errors/index.js";
|
|
4
|
+
import { detectMemoryInjection } from "../internal/injection-heuristics.js";
|
|
4
5
|
import { contextualize } from "../internal/contextualize.js";
|
|
5
6
|
import { newMemoryId } from "../internal/id.js";
|
|
6
7
|
import { normalizeEntityName } from "../graph/entity-resolver.js";
|
|
7
8
|
import { DEFAULT_MAX_ITERATIONS, runIterativeRetrieval } from "../search/iterative.js";
|
|
8
9
|
import { WeightedRRFReranker, fuseRrf, fuseWeighted } from "../search/rrf.js";
|
|
9
|
-
import { detectMemoryInjection } from "../internal/injection-heuristics.js";
|
|
10
10
|
import { explainRecall } from "../search/explain.js";
|
|
11
11
|
import { pprActivation } from "../search/graph-ppr.js";
|
|
12
12
|
import { trustDiscount } from "../search/trust.js";
|
|
@@ -37,8 +37,10 @@ var SemanticMemory = class {
|
|
|
37
37
|
#entityResolver;
|
|
38
38
|
#grader;
|
|
39
39
|
#iterativeMaxIterations;
|
|
40
|
+
#iterativeDifficultyThreshold;
|
|
40
41
|
#reranker;
|
|
41
42
|
#trustWeights;
|
|
43
|
+
#searchDefaults;
|
|
42
44
|
constructor(args) {
|
|
43
45
|
this.#store = args.store;
|
|
44
46
|
this.#tracer = args.tracer;
|
|
@@ -51,7 +53,9 @@ var SemanticMemory = class {
|
|
|
51
53
|
this.#entityResolver = args.entityResolver ?? null;
|
|
52
54
|
this.#grader = args.grader ?? null;
|
|
53
55
|
this.#iterativeMaxIterations = args.iterativeMaxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
56
|
+
this.#iterativeDifficultyThreshold = args.iterativeDifficultyThreshold;
|
|
54
57
|
this.#trustWeights = args.trustWeights ?? DEFAULT_SALIENCE_WEIGHTS;
|
|
58
|
+
this.#searchDefaults = args.searchDefaults ?? {};
|
|
55
59
|
}
|
|
56
60
|
/** Replace the active reranker. Returns the previous instance. */
|
|
57
61
|
setReranker(reranker) {
|
|
@@ -230,7 +234,11 @@ var SemanticMemory = class {
|
|
|
230
234
|
} catch {}
|
|
231
235
|
}
|
|
232
236
|
/** Hybrid (vector + FTS5) search merged through the configured reranker. */
|
|
233
|
-
async search(scope, query,
|
|
237
|
+
async search(scope, query, callOpts = {}) {
|
|
238
|
+
const opts = {
|
|
239
|
+
...this.#searchDefaults,
|
|
240
|
+
...callOpts
|
|
241
|
+
};
|
|
234
242
|
return withMemorySpan(this.#tracer, "memory.search.semantic", scope, { "memory.search.query_length": query.length }, async (span) => {
|
|
235
243
|
const candidateTopK = opts.candidateTopK ?? 60;
|
|
236
244
|
const finalTopK = opts.topK ?? 10;
|
|
@@ -240,6 +248,8 @@ var SemanticMemory = class {
|
|
|
240
248
|
const wGraph = weighted?.weights.graph ?? 1;
|
|
241
249
|
const wEntity = weighted?.weights.entity ?? 1;
|
|
242
250
|
const queries = await this.#expandQueries(query, opts);
|
|
251
|
+
const variantVectors = await this.#tryBatchEmbedQueries(queries);
|
|
252
|
+
const hydeHitsPromise = this.#tryHyde(scope, query, opts, candidateTopK);
|
|
243
253
|
const lists = [];
|
|
244
254
|
const listWeights = [];
|
|
245
255
|
const listLabels = [];
|
|
@@ -260,7 +270,8 @@ var SemanticMemory = class {
|
|
|
260
270
|
lists.push(ftsHits);
|
|
261
271
|
listWeights.push(wFts);
|
|
262
272
|
listLabels.push(`fts_${queries.indexOf(q)}`);
|
|
263
|
-
const
|
|
273
|
+
const preVector = variantVectors?.get(q);
|
|
274
|
+
const vectorHits = preVector !== void 0 ? await this.#vectorSearchWithVector(scope, preVector, candidateTopK, opts.asOf, opts.includeQuarantined, opts.includeSuperseded, opts.owner) : await this.#tryVectorSearch(scope, q, candidateTopK, opts.asOf, opts.includeQuarantined, opts.includeSuperseded, opts.owner);
|
|
264
275
|
if (vectorHits.length > 0) {
|
|
265
276
|
lists.push(vectorHits);
|
|
266
277
|
listWeights.push(wVector);
|
|
@@ -272,7 +283,7 @@ var SemanticMemory = class {
|
|
|
272
283
|
isPrimary = false;
|
|
273
284
|
}
|
|
274
285
|
}
|
|
275
|
-
const hydeHits = await
|
|
286
|
+
const hydeHits = await hydeHitsPromise;
|
|
276
287
|
if (hydeHits.length > 0) {
|
|
277
288
|
lists.push(hydeHits);
|
|
278
289
|
listWeights.push(wVector);
|
|
@@ -294,7 +305,7 @@ var SemanticMemory = class {
|
|
|
294
305
|
weights: listWeights,
|
|
295
306
|
...weighted.k !== void 0 ? { k: weighted.k } : {}
|
|
296
307
|
}) : this.#reranker;
|
|
297
|
-
const fusedTopK = opts.decay !== void 0 || (opts.tags?.length ?? 0) > 0 || opts.owner !== void 0 || (opts.expandHops ?? 0) > 0 || opts.entityMatch === true ? Math.max(finalTopK, lists.reduce((n, l) => n + l.length, 0)) : finalTopK;
|
|
308
|
+
const fusedTopK = opts.decay !== void 0 || (opts.tags?.length ?? 0) > 0 || opts.owner !== void 0 || (opts.expandHops ?? 0) > 0 || opts.entityMatch === true || opts.trustWeighting !== "off" || opts.includeQuarantined === true ? Math.max(finalTopK, lists.reduce((n, l) => n + l.length, 0)) : finalTopK;
|
|
298
309
|
const fused = await reranker.rerank(query, lists, {
|
|
299
310
|
topK: fusedTopK,
|
|
300
311
|
labels: listLabels,
|
|
@@ -310,7 +321,7 @@ var SemanticMemory = class {
|
|
|
310
321
|
const ownerFiltered = wantedOwners !== null ? filtered.filter((h) => wantedOwners.includes(h.record.owner ?? "user")) : filtered;
|
|
311
322
|
const ranked = (opts.trustWeighting === "off" ? ownerFiltered : this.#applyTrustDiscount(ownerFiltered)).slice(0, finalTopK);
|
|
312
323
|
if (ranked.length > 0 && typeof this.#store.semantic.markAccessed === "function") try {
|
|
313
|
-
await this.#store.semantic.markAccessed(ranked.map((h) => h.record.id));
|
|
324
|
+
await this.#store.semantic.markAccessed(ranked.map((h) => h.record.id), void 0, scope);
|
|
314
325
|
} catch {}
|
|
315
326
|
const explanation = explainRecall(ranked, {
|
|
316
327
|
query,
|
|
@@ -364,12 +375,16 @@ var SemanticMemory = class {
|
|
|
364
375
|
idOf: (hit) => hit.record.id,
|
|
365
376
|
grader: this.#grader,
|
|
366
377
|
fuse: (lists) => fuseRrf(lists, 60)
|
|
367
|
-
}, {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
378
|
+
}, (() => {
|
|
379
|
+
const difficultyThreshold = opts.difficultyThreshold ?? this.#iterativeDifficultyThreshold;
|
|
380
|
+
return {
|
|
381
|
+
maxIterations: opts.maxIterations ?? this.#iterativeMaxIterations,
|
|
382
|
+
maxResults: opts.topK ?? 10,
|
|
383
|
+
...opts.forceHard !== void 0 ? { forceHard: opts.forceHard } : {},
|
|
384
|
+
...opts.signal !== void 0 ? { signal: opts.signal } : {},
|
|
385
|
+
...difficultyThreshold !== void 0 ? { difficulty: { threshold: difficultyThreshold } } : {}
|
|
386
|
+
};
|
|
387
|
+
})());
|
|
373
388
|
span.setAttributes({
|
|
374
389
|
"memory.search.semantic.iterative.gate_hard": result.gateHard,
|
|
375
390
|
"memory.search.semantic.iterative.iterations": result.iterations,
|
|
@@ -465,16 +480,51 @@ var SemanticMemory = class {
|
|
|
465
480
|
}
|
|
466
481
|
}
|
|
467
482
|
span.setAttributes({ "memory.semantic.validate.forced": force });
|
|
468
|
-
await this.#store.semantic.setStatus(factId, "active", reason);
|
|
483
|
+
await this.#store.semantic.setStatus(factId, "active", reason, scope);
|
|
484
|
+
if (existing !== null && existing.supersedes !== void 0) {
|
|
485
|
+
const oldFact = await this.#fetchExisting(existing.supersedes);
|
|
486
|
+
if (oldFact !== null && oldFact.supersededBy === void 0 && oldFact.validTo === void 0) {
|
|
487
|
+
span.setAttributes({ "memory.semantic.validate.completed_supersede": true });
|
|
488
|
+
await this.#store.semantic.supersede(existing.supersedes, {
|
|
489
|
+
...existing,
|
|
490
|
+
status: "active"
|
|
491
|
+
}, reason ?? "validated-supersede");
|
|
492
|
+
}
|
|
493
|
+
}
|
|
469
494
|
});
|
|
470
495
|
}
|
|
471
|
-
/**
|
|
472
|
-
|
|
473
|
-
|
|
496
|
+
/**
|
|
497
|
+
* Mark `oldId` superseded by a new fact. Returns the new record.
|
|
498
|
+
*
|
|
499
|
+
* W-019 (security-first, knowledge-preserving): when the successor
|
|
500
|
+
* lands QUARANTINED (the default for `extraction`/synthesized
|
|
501
|
+
* provenance), the old ACTIVE fact's validity interval is NOT closed
|
|
502
|
+
* - default recall keeps returning the old knowledge until the
|
|
503
|
+
* successor passes {@link validate}, which completes the closure.
|
|
504
|
+
* The link is recorded on the successor's `supersedes` field. With
|
|
505
|
+
* `autoPromoteSynthesized` (threaded from the consolidator's
|
|
506
|
+
* `autoPromoteExtraction` escape hatch) an injection-clean successor
|
|
507
|
+
* is active immediately and the interval closes right away - the
|
|
508
|
+
* pre-W-019 behaviour. Inverting the default (auto-activating the
|
|
509
|
+
* successor) would hand a MINJA attacker instant active memory via
|
|
510
|
+
* any text the reconciler classifies as an 'update'.
|
|
511
|
+
*/
|
|
512
|
+
async supersede(scope, oldId, newInput, reason, options) {
|
|
513
|
+
return withMemorySpan(this.#tracer, "memory.write.semantic", scope, { "memory.semantic.action": "supersede" }, async (span) => {
|
|
474
514
|
const newFact = await this.remember(scope, {
|
|
475
515
|
...newInput,
|
|
476
516
|
supersedes: oldId
|
|
477
|
-
}, {
|
|
517
|
+
}, {
|
|
518
|
+
pipeline: "off",
|
|
519
|
+
...options?.autoPromoteSynthesized === true ? { autoPromoteSynthesized: true } : {}
|
|
520
|
+
});
|
|
521
|
+
if (newFact.status === "quarantined") {
|
|
522
|
+
span.setAttributes({ "memory.semantic.supersede.pending": true });
|
|
523
|
+
return {
|
|
524
|
+
old: oldId,
|
|
525
|
+
new: newFact
|
|
526
|
+
};
|
|
527
|
+
}
|
|
478
528
|
await this.#store.semantic.supersede(oldId, newFact, reason);
|
|
479
529
|
return {
|
|
480
530
|
old: oldId,
|
|
@@ -488,7 +538,7 @@ var SemanticMemory = class {
|
|
|
488
538
|
"memory.semantic.action": "forget",
|
|
489
539
|
"memory.semantic.fact_id": factId
|
|
490
540
|
}, async () => {
|
|
491
|
-
await this.#store.semantic.forget(factId, reason);
|
|
541
|
+
await this.#store.semantic.forget(factId, reason, scope);
|
|
492
542
|
});
|
|
493
543
|
}
|
|
494
544
|
/**
|
|
@@ -504,7 +554,7 @@ var SemanticMemory = class {
|
|
|
504
554
|
"memory.semantic.fact_id": factId
|
|
505
555
|
}, async () => {
|
|
506
556
|
if (typeof this.#store.semantic.purge !== "function") throw new TypeError("[graphorin/memory] SemanticMemory.purge(...) requires a storage adapter that implements `semantic.purge(id)`. For storage adapters without GDPR-grade hard-delete, prefer `SemanticMemory.forget(...)`.");
|
|
507
|
-
await this.#store.semantic.purge(factId, reason);
|
|
557
|
+
await this.#store.semantic.purge(factId, reason, scope);
|
|
508
558
|
});
|
|
509
559
|
}
|
|
510
560
|
/** Fuse multiple ranked lists outside of a `search()` call. */
|
|
@@ -565,7 +615,7 @@ var SemanticMemory = class {
|
|
|
565
615
|
try {
|
|
566
616
|
const pseudo = await this.#queryTransformer.hypothetical(query, { ...opts.signal !== void 0 ? { signal: opts.signal } : {} });
|
|
567
617
|
if (pseudo === null || pseudo.trim().length === 0) return [];
|
|
568
|
-
return this.#tryVectorSearch(scope, pseudo, topK, opts.asOf, opts.includeQuarantined);
|
|
618
|
+
return this.#tryVectorSearch(scope, pseudo, topK, opts.asOf, opts.includeQuarantined, opts.includeSuperseded, opts.owner);
|
|
569
619
|
} catch {
|
|
570
620
|
return [];
|
|
571
621
|
}
|
|
@@ -661,9 +711,42 @@ var SemanticMemory = class {
|
|
|
661
711
|
if (this.#embedder === null || embedderId === null || typeof this.#store.semantic.searchVector !== "function") return [];
|
|
662
712
|
const [vector] = await this.#embedder.embed([query], { taskType: "query" });
|
|
663
713
|
if (vector === void 0) return [];
|
|
714
|
+
return this.#vectorSearchWithVector(scope, vector, topK, asOf, includeQuarantined, includeSuperseded, owner);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* W-087: the store half of {@link SemanticMemory.#tryVectorSearch} -
|
|
718
|
+
* runs the KNN read against an ALREADY-COMPUTED query vector so the
|
|
719
|
+
* multi-query fan-out can batch its N variant embeddings into one
|
|
720
|
+
* `embed([q1..qN])` call instead of N provider round-trips.
|
|
721
|
+
*/
|
|
722
|
+
async #vectorSearchWithVector(scope, vector, topK, asOf, includeQuarantined, includeSuperseded, owner) {
|
|
723
|
+
const embedderId = this.#embedderIdProvider();
|
|
724
|
+
if (embedderId === null || typeof this.#store.semantic.searchVector !== "function") return [];
|
|
664
725
|
return this.#store.semantic.searchVector(scope, vector, embedderId, topK, asOf, includeQuarantined, includeSuperseded, owner);
|
|
665
726
|
}
|
|
666
727
|
/**
|
|
728
|
+
* W-087: embed every fan-out variant in ONE embedder call. Returns
|
|
729
|
+
* `null` - and the caller degrades to the per-variant path - when
|
|
730
|
+
* there is no fan-out, no vector surface, or the batch call fails; a
|
|
731
|
+
* batching failure must never fail the search.
|
|
732
|
+
*/
|
|
733
|
+
async #tryBatchEmbedQueries(queries) {
|
|
734
|
+
if (queries.length <= 1) return null;
|
|
735
|
+
if (this.#embedder === null || this.#embedderIdProvider() === null || typeof this.#store.semantic.searchVector !== "function") return null;
|
|
736
|
+
try {
|
|
737
|
+
const vectors = await this.#embedder.embed([...queries], { taskType: "query" });
|
|
738
|
+
if (vectors.length !== queries.length) return null;
|
|
739
|
+
const byQuery = /* @__PURE__ */ new Map();
|
|
740
|
+
queries.forEach((q, i) => {
|
|
741
|
+
const v = vectors[i];
|
|
742
|
+
if (v !== void 0) byQuery.set(q, v);
|
|
743
|
+
});
|
|
744
|
+
return byQuery;
|
|
745
|
+
} catch {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
667
750
|
* C5: multiply each hit's fused score by its trust factor and re-sort.
|
|
668
751
|
* No-ops (returns the same array) when every factor is 1.
|
|
669
752
|
*/
|