@graphorin/memory 0.5.0 → 0.6.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 +90 -1
- package/README.md +31 -31
- package/dist/conflict/index.d.ts +3 -1
- package/dist/conflict/index.js +1 -1
- package/dist/conflict/locale-packs/en.d.ts +13 -0
- package/dist/conflict/locale-packs/en.d.ts.map +1 -0
- package/dist/conflict/locale-packs/en.js +1 -1
- package/dist/conflict/locale-packs/en.js.map +1 -1
- package/dist/conflict/pipeline.d.ts +2 -2
- package/dist/conflict/pipeline.js +3 -3
- package/dist/conflict/pipeline.js.map +1 -1
- package/dist/conflict/stages/helpers.js +1 -1
- package/dist/conflict/stages/helpers.js.map +1 -1
- package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -1
- package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js +1 -1
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -1
- package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -1
- package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -1
- package/dist/conflict/types.d.ts +7 -7
- package/dist/conflict/types.js +1 -1
- package/dist/conflict/types.js.map +1 -1
- package/dist/consolidator/budget.js +16 -3
- package/dist/consolidator/budget.js.map +1 -1
- package/dist/consolidator/decay.d.ts +13 -2
- package/dist/consolidator/decay.js +17 -5
- package/dist/consolidator/decay.js.map +1 -1
- package/dist/consolidator/dlq.js +1 -1
- package/dist/consolidator/dlq.js.map +1 -1
- package/dist/consolidator/errors.js +2 -2
- package/dist/consolidator/errors.js.map +1 -1
- package/dist/consolidator/index.js +8 -6
- package/dist/consolidator/index.js.map +1 -1
- package/dist/consolidator/lock.js +1 -1
- package/dist/consolidator/lock.js.map +1 -1
- package/dist/consolidator/noise-filter.js.map +1 -1
- package/dist/consolidator/phases/deep.js +6 -2
- package/dist/consolidator/phases/deep.js.map +1 -1
- package/dist/consolidator/phases/induce.d.ts +10 -10
- package/dist/consolidator/phases/induce.js +6 -6
- package/dist/consolidator/phases/induce.js.map +1 -1
- package/dist/consolidator/phases/learned-context.js +129 -0
- package/dist/consolidator/phases/learned-context.js.map +1 -0
- package/dist/consolidator/phases/light.js +2 -1
- package/dist/consolidator/phases/light.js.map +1 -1
- package/dist/consolidator/phases/reflect.js +5 -4
- package/dist/consolidator/phases/reflect.js.map +1 -1
- package/dist/consolidator/phases/standard.js +37 -8
- package/dist/consolidator/phases/standard.js.map +1 -1
- package/dist/consolidator/reconcile.js +1 -1
- package/dist/consolidator/reconcile.js.map +1 -1
- package/dist/consolidator/runtime.d.ts +4 -4
- package/dist/consolidator/runtime.d.ts.map +1 -1
- package/dist/consolidator/runtime.js +73 -16
- package/dist/consolidator/runtime.js.map +1 -1
- package/dist/consolidator/scheduler.d.ts +5 -5
- package/dist/consolidator/scheduler.js +1 -1
- package/dist/consolidator/scheduler.js.map +1 -1
- package/dist/consolidator/triggers.js +7 -7
- package/dist/consolidator/triggers.js.map +1 -1
- package/dist/consolidator/types.d.ts +68 -20
- package/dist/consolidator/types.d.ts.map +1 -1
- package/dist/consolidator/types.js +16 -6
- package/dist/consolidator/types.js.map +1 -1
- package/dist/context-engine/annotations.d.ts +9 -9
- package/dist/context-engine/annotations.js +3 -3
- package/dist/context-engine/annotations.js.map +1 -1
- package/dist/context-engine/auto-recall.js.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.d.ts +18 -3
- package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -1
- package/dist/context-engine/compaction/clear-tool-results.js +26 -5
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -1
- package/dist/context-engine/compaction/compactor.js +19 -6
- package/dist/context-engine/compaction/compactor.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +4 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +2 -1
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -1
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.d.ts +28 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.js +71 -0
- package/dist/context-engine/compaction/hooks/reanchor-recent-results.js.map +1 -0
- package/dist/context-engine/compaction/hooks/types.d.ts +15 -3
- package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -1
- package/dist/context-engine/compaction/index.d.ts +1 -0
- package/dist/context-engine/compaction/index.js +2 -1
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +10 -3
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -1
- package/dist/context-engine/compaction/templates/summary-9-section.js +42 -5
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -1
- package/dist/context-engine/compaction/thresholds.js.map +1 -1
- package/dist/context-engine/compaction/types.d.ts +47 -7
- package/dist/context-engine/compaction/types.d.ts.map +1 -1
- package/dist/context-engine/engine.d.ts +23 -4
- package/dist/context-engine/engine.d.ts.map +1 -1
- package/dist/context-engine/engine.js +69 -15
- package/dist/context-engine/engine.js.map +1 -1
- package/dist/context-engine/index.d.ts +1 -0
- package/dist/context-engine/index.d.ts.map +1 -1
- package/dist/context-engine/index.js +2 -1
- package/dist/context-engine/locale-packs/en.js +14 -13
- package/dist/context-engine/locale-packs/en.js.map +1 -1
- package/dist/context-engine/locale-packs/types.d.ts +10 -10
- package/dist/context-engine/locale-packs/types.d.ts.map +1 -1
- package/dist/context-engine/locale-packs/types.js.map +1 -1
- package/dist/context-engine/metadata.d.ts +1 -1
- package/dist/context-engine/metadata.js +2 -2
- package/dist/context-engine/metadata.js.map +1 -1
- package/dist/context-engine/preambles/inbound-en.d.ts +1 -1
- package/dist/context-engine/preambles/inbound-en.js +1 -1
- package/dist/context-engine/preambles/inbound-en.js.map +1 -1
- package/dist/context-engine/privacy-filter.d.ts +2 -2
- package/dist/context-engine/privacy-filter.js +1 -1
- package/dist/context-engine/privacy-filter.js.map +1 -1
- package/dist/context-engine/templates/base-en.d.ts +1 -1
- package/dist/context-engine/templates/base-en.js +1 -1
- package/dist/context-engine/templates/base-en.js.map +1 -1
- package/dist/context-engine/templates/composer.d.ts +1 -1
- package/dist/context-engine/templates/composer.js.map +1 -1
- package/dist/context-engine/token-budget.d.ts +1 -1
- package/dist/context-engine/token-budget.js +1 -1
- package/dist/context-engine/token-budget.js.map +1 -1
- package/dist/context-engine/token-counter.d.ts +16 -7
- package/dist/context-engine/token-counter.d.ts.map +1 -1
- package/dist/context-engine/token-counter.js +31 -9
- package/dist/context-engine/token-counter.js.map +1 -1
- package/dist/errors/index.d.ts +2 -2
- package/dist/errors/index.js +4 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/facade.d.ts +44 -15
- package/dist/facade.d.ts.map +1 -1
- package/dist/facade.js +16 -8
- package/dist/facade.js.map +1 -1
- package/dist/graph/entity-resolver.d.ts +2 -2
- package/dist/graph/entity-resolver.js +2 -2
- package/dist/graph/entity-resolver.js.map +1 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/contextualize.js +2 -2
- package/dist/internal/contextualize.js.map +1 -1
- package/dist/internal/embedder-binding.js +1 -1
- package/dist/internal/embedder-binding.js.map +1 -1
- package/dist/internal/id.js +1 -1
- package/dist/internal/id.js.map +1 -1
- package/dist/internal/injection-heuristics.js +25 -2
- package/dist/internal/injection-heuristics.js.map +1 -1
- package/dist/internal/spans.js +1 -1
- package/dist/internal/spans.js.map +1 -1
- package/dist/internal/storage-adapter.d.ts +71 -22
- package/dist/internal/storage-adapter.d.ts.map +1 -1
- package/dist/migration/embedder-migration.d.ts +7 -7
- package/dist/migration/embedder-migration.js.map +1 -1
- package/dist/search/explain.d.ts +7 -4
- package/dist/search/explain.d.ts.map +1 -1
- package/dist/search/explain.js +1 -1
- package/dist/search/explain.js.map +1 -1
- package/dist/search/fit-weights.d.ts +48 -0
- package/dist/search/fit-weights.d.ts.map +1 -0
- package/dist/search/fit-weights.js +70 -0
- package/dist/search/fit-weights.js.map +1 -0
- package/dist/search/graph-ppr.d.ts +36 -0
- package/dist/search/graph-ppr.d.ts.map +1 -0
- package/dist/search/graph-ppr.js +38 -0
- package/dist/search/graph-ppr.js.map +1 -0
- package/dist/search/index.d.ts +4 -1
- package/dist/search/index.js +4 -1
- package/dist/search/iterative.d.ts +15 -8
- package/dist/search/iterative.d.ts.map +1 -1
- package/dist/search/iterative.js +10 -9
- package/dist/search/iterative.js.map +1 -1
- package/dist/search/query-transform.d.ts +6 -6
- package/dist/search/query-transform.js +6 -6
- package/dist/search/query-transform.js.map +1 -1
- package/dist/search/rrf.d.ts +4 -4
- package/dist/search/rrf.js +4 -4
- package/dist/search/rrf.js.map +1 -1
- package/dist/search/trust.d.ts +30 -0
- package/dist/search/trust.d.ts.map +1 -0
- package/dist/search/trust.js +50 -0
- package/dist/search/trust.js.map +1 -0
- package/dist/search/types.d.ts +1 -1
- package/dist/tiers/episodic-memory.d.ts +6 -4
- package/dist/tiers/episodic-memory.d.ts.map +1 -1
- package/dist/tiers/episodic-memory.js +3 -2
- package/dist/tiers/episodic-memory.js.map +1 -1
- package/dist/tiers/index.d.ts +1 -1
- package/dist/tiers/index.js +1 -1
- package/dist/tiers/insight-memory.d.ts +4 -4
- package/dist/tiers/insight-memory.js +3 -3
- package/dist/tiers/insight-memory.js.map +1 -1
- package/dist/tiers/procedural-memory.d.ts +29 -7
- package/dist/tiers/procedural-memory.d.ts.map +1 -1
- package/dist/tiers/procedural-memory.js +66 -6
- package/dist/tiers/procedural-memory.js.map +1 -1
- package/dist/tiers/semantic-memory.d.ts +111 -30
- package/dist/tiers/semantic-memory.d.ts.map +1 -1
- package/dist/tiers/semantic-memory.js +120 -27
- package/dist/tiers/semantic-memory.js.map +1 -1
- package/dist/tiers/session-memory.d.ts +7 -7
- package/dist/tiers/session-memory.js +7 -7
- package/dist/tiers/session-memory.js.map +1 -1
- package/dist/tiers/shared-memory.d.ts +1 -1
- package/dist/tiers/shared-memory.js +1 -1
- package/dist/tiers/shared-memory.js.map +1 -1
- package/dist/tiers/working-memory.d.ts +3 -3
- package/dist/tiers/working-memory.js +2 -2
- package/dist/tiers/working-memory.js.map +1 -1
- package/dist/tools/block-tools.d.ts +9 -9
- package/dist/tools/block-tools.d.ts.map +1 -1
- package/dist/tools/block-tools.js +3 -3
- package/dist/tools/block-tools.js.map +1 -1
- package/dist/tools/fact-tools.d.ts +48 -31
- package/dist/tools/fact-tools.d.ts.map +1 -1
- package/dist/tools/fact-tools.js +30 -18
- package/dist/tools/fact-tools.js.map +1 -1
- package/dist/tools/index.d.ts +9 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -2
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/recall-tools.d.ts +40 -20
- package/dist/tools/recall-tools.d.ts.map +1 -1
- package/dist/tools/recall-tools.js +29 -10
- package/dist/tools/recall-tools.js.map +1 -1
- package/dist/tools/runbook-tools.d.ts +76 -0
- package/dist/tools/runbook-tools.d.ts.map +1 -0
- package/dist/tools/runbook-tools.js +53 -0
- package/dist/tools/runbook-tools.js.map +1 -0
- package/dist/tools/taint.js +33 -0
- package/dist/tools/taint.js.map +1 -0
- package/dist/tools/types.d.ts +1 -1
- package/package.json +8 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconcile.js","names":["ctx: StageContext","fallback: ReconcileDecision","parsed: unknown"],"sources":["../../src/consolidator/reconcile.ts"],"sourcesContent":["/**\n * Neighbour-aware write reconciliation (P0-3) — the `reconcile` half of\n * Mem0's extract→reconcile loop (arXiv:2504.19413), with Graphorin's\n * bi-temporal twist.\n *\n * Two pieces live here:\n *\n * - {@link preFilterCandidate} — a *cheap*, LLM-free classifier that\n * reuses the conflict pipeline's Stage 1 (exact-dedup) + Stage 2\n * (embedding three-zone) over the candidate's nearest neighbours.\n * Clear hot / near-dup neighbours short-circuit to `noop`; a clearly\n * cold field (or no neighbours at all) short-circuits to `add`. Only\n * the ambiguous CONFLICT-CHECK mid-zone is routed to `reconcile`, so\n * the consolidator spends an LLM call only where it actually helps.\n *\n * - {@link reconcileCandidate} — one provider pass that, with the\n * neighbours *in view*, chooses ADD / UPDATE / NOOP / CONFLICT.\n * Parsing is defensive: malformed output, an unknown action, or a\n * `targetId` that is not one of the supplied neighbours all fall back\n * to a safe additive write (audited) so a flaky model never wedges\n * the pipeline or rewrites the wrong fact.\n *\n * @packageDocumentation\n */\n\nimport type { Fact, MemoryHit, Provider, ProviderRequest, SessionScope } from '@graphorin/core';\nimport {\n type ConflictThresholds,\n DEFAULT_CONFLICT_THRESHOLDS,\n type ReconcileDecision,\n type StageContext,\n} from '../conflict/index.js';\nimport { enLocalePack, type LocalePack } from '../conflict/locale-packs/index.js';\nimport { stage1ExactDedup } from '../conflict/stages/stage1-exact-dedup.js';\nimport { stage2EmbeddingThreeZone } from '../conflict/stages/stage2-embedding-three-zone.js';\n\n/**\n * Pre-filter routing decision. `add` and `noop` are resolved without an\n * LLM call; `reconcile` means the candidate landed in the ambiguous\n * mid-zone and should be handed to {@link reconcileCandidate}.\n *\n * @stable\n */\nexport type PreFilterRoute =\n | { readonly route: 'add'; readonly reason: string }\n | {\n readonly route: 'noop';\n readonly targetId: string;\n readonly similarity?: number;\n readonly reason: string;\n }\n | { readonly route: 'reconcile' };\n\n/**\n * Classify a candidate against its nearest neighbours using the conflict\n * pipeline's first two (cheap, LLM-free) stages. Reuses\n * {@link stage1ExactDedup} + {@link stage2EmbeddingThreeZone} verbatim so\n * the zone thresholds stay aligned with the inline write path.\n *\n * @stable\n */\nexport async function preFilterCandidate(\n candidateText: string,\n neighbors: ReadonlyArray<MemoryHit<Fact>>,\n opts: { readonly thresholds?: ConflictThresholds; readonly localePack?: LocalePack } = {},\n): Promise<PreFilterRoute> {\n const thresholds = opts.thresholds ?? DEFAULT_CONFLICT_THRESHOLDS;\n const localePack = opts.localePack ?? enLocalePack;\n const ctx: StageContext = {\n candidate: synthCandidate(candidateText),\n existing: neighbors,\n localePack,\n thresholds,\n };\n\n const s1 = await stage1ExactDedup.evaluate(ctx);\n if (s1.kind === 'dedup') {\n return {\n route: 'noop',\n targetId: s1.existingId,\n ...(s1.similarity !== undefined ? { similarity: s1.similarity } : {}),\n reason: s1.reason ?? 'exact-hash-match',\n };\n }\n\n const s2 = await stage2EmbeddingThreeZone.evaluate(ctx);\n if (s2.kind === 'dedup') {\n return {\n route: 'noop',\n targetId: s2.existingId,\n ...(s2.similarity !== undefined ? { similarity: s2.similarity } : {}),\n reason: s2.reason ?? 'embedding-zone',\n };\n }\n if (s2.kind === 'admit') {\n return { route: 'add', reason: s2.reason ?? 'embedding-cold-zone' };\n }\n // s2.kind === 'continue' → CONFLICT-CHECK mid-zone → spend an LLM call.\n return { route: 'reconcile' };\n}\n\n/** A neighbour surfaced to the reconcile prompt. */\nexport interface ReconcileNeighbor {\n readonly id: string;\n readonly text: string;\n readonly validFrom?: string;\n}\n\n/** Token usage for the single reconcile provider call (budget accounting). */\nexport interface ReconcileUsage {\n readonly promptTokens: number;\n readonly completionTokens: number;\n readonly totalTokens: number;\n}\n\n/** Result of {@link reconcileCandidate}: the decision + its token cost. */\nexport interface ReconcileResult {\n readonly decision: ReconcileDecision;\n readonly usage: ReconcileUsage;\n}\n\n/** Inputs to {@link reconcileCandidate}. */\nexport interface ReconcileCandidateArgs {\n readonly candidateText: string;\n readonly neighbors: ReadonlyArray<ReconcileNeighbor>;\n readonly provider: Provider;\n readonly scope: SessionScope;\n}\n\n/**\n * System prompt for the reconcile pass. Mirrors the deep-phase judge\n * style but expands the decision space to the full ADD/UPDATE/NOOP/\n * CONFLICT loop and asks for the `targetId` of the resolved neighbour.\n *\n * @internal\n */\nexport const RECONCILE_SYSTEM_PROMPT = [\n \"You reconcile a candidate memory against the user's existing related memories.\",\n 'Return strictly JSON: { \"action\": \"add\" | \"update\" | \"noop\" | \"conflict\", \"targetId\"?: string, \"reason\": string }.',\n 'Use \"update\" when the candidate is a newer version of an existing memory (a changed preference, location, job, or fact).',\n 'Use \"noop\" when the candidate adds nothing new (a duplicate or subset of an existing memory).',\n 'Use \"conflict\" when the candidate contradicts an existing memory and the older one should be closed.',\n 'Use \"add\" when the candidate is independent of every listed memory, or when you are unsure.',\n 'For \"update\", \"noop\", and \"conflict\", set \"targetId\" to the id of the existing memory you are resolving against.',\n].join(' ');\n\n/**\n * Run one reconcile pass. The decision is always safe to apply: parse\n * failures and dangling `targetId`s degrade to an additive write.\n *\n * @stable\n */\nexport async function reconcileCandidate(args: ReconcileCandidateArgs): Promise<ReconcileResult> {\n const request = buildReconcileRequest(args);\n const response = await args.provider.generate(request);\n const usage = response.usage;\n const promptTokens = usage.promptTokens ?? 0;\n const completionTokens = usage.completionTokens ?? 0;\n const totalTokens = promptTokens + completionTokens + (usage.reasoningTokens ?? 0);\n const validIds = new Set(args.neighbors.map((n) => n.id));\n const decision = parseReconcile(response.text, validIds);\n return { decision, usage: { promptTokens, completionTokens, totalTokens } };\n}\n\n/**\n * Parse the reconcile model output into a {@link ReconcileDecision}.\n * Tolerates fenced blocks + trailing commentary; an unparseable body,\n * an unknown action, or a `targetId` that is not in `validTargetIds`\n * all degrade to `{ action: 'add' }` (audited via the reason).\n *\n * @internal\n */\nexport function parseReconcile(\n text: string | undefined,\n validTargetIds: ReadonlySet<string>,\n): ReconcileDecision {\n const fallback: ReconcileDecision = { action: 'add', reason: 'reconcile-parse-failure' };\n if (text === undefined || text.length === 0) return fallback;\n const candidate = stripFence(text).trim();\n if (candidate.length === 0) return fallback;\n let parsed: unknown;\n try {\n parsed = JSON.parse(candidate);\n } catch {\n const slice = sliceJsonObject(candidate);\n if (slice === null) return fallback;\n try {\n parsed = JSON.parse(slice);\n } catch {\n return fallback;\n }\n }\n if (parsed === null || typeof parsed !== 'object') return fallback;\n const obj = parsed as { action?: unknown; targetId?: unknown; reason?: unknown };\n const reason = typeof obj.reason === 'string' && obj.reason.length > 0 ? obj.reason : undefined;\n if (obj.action === 'add') {\n return { action: 'add', ...(reason !== undefined ? { reason } : {}) };\n }\n if (obj.action === 'update' || obj.action === 'noop' || obj.action === 'conflict') {\n const targetId = typeof obj.targetId === 'string' ? obj.targetId : null;\n if (targetId === null || !validTargetIds.has(targetId)) {\n // The model referenced a fact that is not one of the supplied\n // neighbours — never rewrite an unverified id; add instead.\n return { action: 'add', reason: 'reconcile-invalid-target' };\n }\n if (obj.action === 'noop') {\n return { action: 'noop', targetId, ...(reason !== undefined ? { reason } : {}) };\n }\n return { action: obj.action, targetId, reason: reason ?? `reconcile-${obj.action}` };\n }\n return fallback;\n}\n\nfunction buildReconcileRequest(args: ReconcileCandidateArgs): ProviderRequest {\n const neighborLines = args.neighbors\n .map((n, i) => {\n const vf = n.validFrom !== undefined ? ` (validFrom: ${n.validFrom})` : '';\n return `${i + 1}. [id: ${n.id}]${vf} ${n.text}`;\n })\n .join('\\n');\n const userBlock = [\n `Candidate memory: ${args.candidateText}`,\n '',\n 'Existing related memories:',\n neighborLines.length > 0 ? neighborLines : '(none)',\n ].join('\\n');\n return {\n messages: [{ role: 'user', content: userBlock }],\n systemMessage: RECONCILE_SYSTEM_PROMPT,\n temperature: 0,\n // MCON-14: per-call output cap — the decision shape is tiny.\n maxTokens: 256,\n metadata: {\n userId: args.scope.userId,\n ...(args.scope.sessionId !== undefined ? { sessionId: args.scope.sessionId } : {}),\n ...(args.scope.agentId !== undefined ? { agentId: args.scope.agentId } : {}),\n },\n outputType: { kind: 'structured' },\n };\n}\n\n/**\n * Minimal candidate `Fact` for the pre-filter stage context. Stage 1\n * reads only `text`; Stage 2 reads only the neighbour scores — the\n * other fields are never inspected, so a synthetic id/scope is safe.\n */\nfunction synthCandidate(text: string): Fact {\n return {\n id: '__reconcile_candidate__',\n kind: 'semantic',\n userId: '__reconcile_candidate__',\n sensitivity: 'internal',\n text,\n createdAt: new Date(0).toISOString(),\n };\n}\n\nfunction stripFence(text: string): string {\n const match = /^```[^\\n]*\\n([\\s\\S]*?)\\n```/u.exec(text.trim());\n return match?.[1] ?? text;\n}\n\nfunction sliceJsonObject(text: string): string | null {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start < 0 || end < start) return null;\n return text.slice(start, end + 1);\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA6DA,eAAsB,mBACpB,eACA,WACA,OAAuF,EAAE,EAChE;CACzB,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,aAAa,KAAK,cAAc;CACtC,MAAMA,MAAoB;EACxB,WAAW,eAAe,cAAc;EACxC,UAAU;EACV;EACA;EACD;CAED,MAAM,KAAK,MAAM,iBAAiB,SAAS,IAAI;AAC/C,KAAI,GAAG,SAAS,QACd,QAAO;EACL,OAAO;EACP,UAAU,GAAG;EACb,GAAI,GAAG,eAAe,SAAY,EAAE,YAAY,GAAG,YAAY,GAAG,EAAE;EACpE,QAAQ,GAAG,UAAU;EACtB;CAGH,MAAM,KAAK,MAAM,yBAAyB,SAAS,IAAI;AACvD,KAAI,GAAG,SAAS,QACd,QAAO;EACL,OAAO;EACP,UAAU,GAAG;EACb,GAAI,GAAG,eAAe,SAAY,EAAE,YAAY,GAAG,YAAY,GAAG,EAAE;EACpE,QAAQ,GAAG,UAAU;EACtB;AAEH,KAAI,GAAG,SAAS,QACd,QAAO;EAAE,OAAO;EAAO,QAAQ,GAAG,UAAU;EAAuB;AAGrE,QAAO,EAAE,OAAO,aAAa;;;;;;;;;AAsC/B,MAAa,0BAA0B;CACrC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,KAAK,IAAI;;;;;;;AAQX,eAAsB,mBAAmB,MAAwD;CAC/F,MAAM,UAAU,sBAAsB,KAAK;CAC3C,MAAM,WAAW,MAAM,KAAK,SAAS,SAAS,QAAQ;CACtD,MAAM,QAAQ,SAAS;CACvB,MAAM,eAAe,MAAM,gBAAgB;CAC3C,MAAM,mBAAmB,MAAM,oBAAoB;CACnD,MAAM,cAAc,eAAe,oBAAoB,MAAM,mBAAmB;CAChF,MAAM,WAAW,IAAI,IAAI,KAAK,UAAU,KAAK,MAAM,EAAE,GAAG,CAAC;AAEzD,QAAO;EAAE,UADQ,eAAe,SAAS,MAAM,SAAS;EACrC,OAAO;GAAE;GAAc;GAAkB;GAAa;EAAE;;;;;;;;;;AAW7E,SAAgB,eACd,MACA,gBACmB;CACnB,MAAMC,WAA8B;EAAE,QAAQ;EAAO,QAAQ;EAA2B;AACxF,KAAI,SAAS,UAAa,KAAK,WAAW,EAAG,QAAO;CACpD,MAAM,YAAY,WAAW,KAAK,CAAC,MAAM;AACzC,KAAI,UAAU,WAAW,EAAG,QAAO;CACnC,IAAIC;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,UAAU;SACxB;EACN,MAAM,QAAQ,gBAAgB,UAAU;AACxC,MAAI,UAAU,KAAM,QAAO;AAC3B,MAAI;AACF,YAAS,KAAK,MAAM,MAAM;UACpB;AACN,UAAO;;;AAGX,KAAI,WAAW,QAAQ,OAAO,WAAW,SAAU,QAAO;CAC1D,MAAM,MAAM;CACZ,MAAM,SAAS,OAAO,IAAI,WAAW,YAAY,IAAI,OAAO,SAAS,IAAI,IAAI,SAAS;AACtF,KAAI,IAAI,WAAW,MACjB,QAAO;EAAE,QAAQ;EAAO,GAAI,WAAW,SAAY,EAAE,QAAQ,GAAG,EAAE;EAAG;AAEvE,KAAI,IAAI,WAAW,YAAY,IAAI,WAAW,UAAU,IAAI,WAAW,YAAY;EACjF,MAAM,WAAW,OAAO,IAAI,aAAa,WAAW,IAAI,WAAW;AACnE,MAAI,aAAa,QAAQ,CAAC,eAAe,IAAI,SAAS,CAGpD,QAAO;GAAE,QAAQ;GAAO,QAAQ;GAA4B;AAE9D,MAAI,IAAI,WAAW,OACjB,QAAO;GAAE,QAAQ;GAAQ;GAAU,GAAI,WAAW,SAAY,EAAE,QAAQ,GAAG,EAAE;GAAG;AAElF,SAAO;GAAE,QAAQ,IAAI;GAAQ;GAAU,QAAQ,UAAU,aAAa,IAAI;GAAU;;AAEtF,QAAO;;AAGT,SAAS,sBAAsB,MAA+C;CAC5E,MAAM,gBAAgB,KAAK,UACxB,KAAK,GAAG,MAAM;EACb,MAAM,KAAK,EAAE,cAAc,SAAY,gBAAgB,EAAE,UAAU,KAAK;AACxE,SAAO,GAAG,IAAI,EAAE,SAAS,EAAE,GAAG,GAAG,GAAG,GAAG,EAAE;GACzC,CACD,KAAK,KAAK;AAOb,QAAO;EACL,UAAU,CAAC;GAAE,MAAM;GAAQ,SAPX;IAChB,qBAAqB,KAAK;IAC1B;IACA;IACA,cAAc,SAAS,IAAI,gBAAgB;IAC5C,CAAC,KAAK,KAAK;GAEqC,CAAC;EAChD,eAAe;EACf,aAAa;EAEb,WAAW;EACX,UAAU;GACR,QAAQ,KAAK,MAAM;GACnB,GAAI,KAAK,MAAM,cAAc,SAAY,EAAE,WAAW,KAAK,MAAM,WAAW,GAAG,EAAE;GACjF,GAAI,KAAK,MAAM,YAAY,SAAY,EAAE,SAAS,KAAK,MAAM,SAAS,GAAG,EAAE;GAC5E;EACD,YAAY,EAAE,MAAM,cAAc;EACnC;;;;;;;AAQH,SAAS,eAAe,MAAoB;AAC1C,QAAO;EACL,IAAI;EACJ,MAAM;EACN,QAAQ;EACR,aAAa;EACb;EACA,4BAAW,IAAI,KAAK,EAAE,EAAC,aAAa;EACrC;;AAGH,SAAS,WAAW,MAAsB;AAExC,QADc,+BAA+B,KAAK,KAAK,MAAM,CAAC,GAC/C,MAAM;;AAGvB,SAAS,gBAAgB,MAA6B;CACpD,MAAM,QAAQ,KAAK,QAAQ,IAAI;CAC/B,MAAM,MAAM,KAAK,YAAY,IAAI;AACjC,KAAI,QAAQ,KAAK,MAAM,MAAO,QAAO;AACrC,QAAO,KAAK,MAAM,OAAO,MAAM,EAAE"}
|
|
1
|
+
{"version":3,"file":"reconcile.js","names":["ctx: StageContext","fallback: ReconcileDecision","parsed: unknown"],"sources":["../../src/consolidator/reconcile.ts"],"sourcesContent":["/**\n * Neighbour-aware write reconciliation (P0-3) - the `reconcile` half of\n * Mem0's extract→reconcile loop (arXiv:2504.19413), with Graphorin's\n * bi-temporal twist.\n *\n * Two pieces live here:\n *\n * - {@link preFilterCandidate} - a *cheap*, LLM-free classifier that\n * reuses the conflict pipeline's Stage 1 (exact-dedup) + Stage 2\n * (embedding three-zone) over the candidate's nearest neighbours.\n * Clear hot / near-dup neighbours short-circuit to `noop`; a clearly\n * cold field (or no neighbours at all) short-circuits to `add`. Only\n * the ambiguous CONFLICT-CHECK mid-zone is routed to `reconcile`, so\n * the consolidator spends an LLM call only where it actually helps.\n *\n * - {@link reconcileCandidate} - one provider pass that, with the\n * neighbours *in view*, chooses ADD / UPDATE / NOOP / CONFLICT.\n * Parsing is defensive: malformed output, an unknown action, or a\n * `targetId` that is not one of the supplied neighbours all fall back\n * to a safe additive write (audited) so a flaky model never wedges\n * the pipeline or rewrites the wrong fact.\n *\n * @packageDocumentation\n */\n\nimport type { Fact, MemoryHit, Provider, ProviderRequest, SessionScope } from '@graphorin/core';\nimport {\n type ConflictThresholds,\n DEFAULT_CONFLICT_THRESHOLDS,\n type ReconcileDecision,\n type StageContext,\n} from '../conflict/index.js';\nimport { enLocalePack, type LocalePack } from '../conflict/locale-packs/index.js';\nimport { stage1ExactDedup } from '../conflict/stages/stage1-exact-dedup.js';\nimport { stage2EmbeddingThreeZone } from '../conflict/stages/stage2-embedding-three-zone.js';\n\n/**\n * Pre-filter routing decision. `add` and `noop` are resolved without an\n * LLM call; `reconcile` means the candidate landed in the ambiguous\n * mid-zone and should be handed to {@link reconcileCandidate}.\n *\n * @stable\n */\nexport type PreFilterRoute =\n | { readonly route: 'add'; readonly reason: string }\n | {\n readonly route: 'noop';\n readonly targetId: string;\n readonly similarity?: number;\n readonly reason: string;\n }\n | { readonly route: 'reconcile' };\n\n/**\n * Classify a candidate against its nearest neighbours using the conflict\n * pipeline's first two (cheap, LLM-free) stages. Reuses\n * {@link stage1ExactDedup} + {@link stage2EmbeddingThreeZone} verbatim so\n * the zone thresholds stay aligned with the inline write path.\n *\n * @stable\n */\nexport async function preFilterCandidate(\n candidateText: string,\n neighbors: ReadonlyArray<MemoryHit<Fact>>,\n opts: { readonly thresholds?: ConflictThresholds; readonly localePack?: LocalePack } = {},\n): Promise<PreFilterRoute> {\n const thresholds = opts.thresholds ?? DEFAULT_CONFLICT_THRESHOLDS;\n const localePack = opts.localePack ?? enLocalePack;\n const ctx: StageContext = {\n candidate: synthCandidate(candidateText),\n existing: neighbors,\n localePack,\n thresholds,\n };\n\n const s1 = await stage1ExactDedup.evaluate(ctx);\n if (s1.kind === 'dedup') {\n return {\n route: 'noop',\n targetId: s1.existingId,\n ...(s1.similarity !== undefined ? { similarity: s1.similarity } : {}),\n reason: s1.reason ?? 'exact-hash-match',\n };\n }\n\n const s2 = await stage2EmbeddingThreeZone.evaluate(ctx);\n if (s2.kind === 'dedup') {\n return {\n route: 'noop',\n targetId: s2.existingId,\n ...(s2.similarity !== undefined ? { similarity: s2.similarity } : {}),\n reason: s2.reason ?? 'embedding-zone',\n };\n }\n if (s2.kind === 'admit') {\n return { route: 'add', reason: s2.reason ?? 'embedding-cold-zone' };\n }\n // s2.kind === 'continue' → CONFLICT-CHECK mid-zone → spend an LLM call.\n return { route: 'reconcile' };\n}\n\n/** A neighbour surfaced to the reconcile prompt. */\nexport interface ReconcileNeighbor {\n readonly id: string;\n readonly text: string;\n readonly validFrom?: string;\n}\n\n/** Token usage for the single reconcile provider call (budget accounting). */\nexport interface ReconcileUsage {\n readonly promptTokens: number;\n readonly completionTokens: number;\n readonly totalTokens: number;\n}\n\n/** Result of {@link reconcileCandidate}: the decision + its token cost. */\nexport interface ReconcileResult {\n readonly decision: ReconcileDecision;\n readonly usage: ReconcileUsage;\n}\n\n/** Inputs to {@link reconcileCandidate}. */\nexport interface ReconcileCandidateArgs {\n readonly candidateText: string;\n readonly neighbors: ReadonlyArray<ReconcileNeighbor>;\n readonly provider: Provider;\n readonly scope: SessionScope;\n}\n\n/**\n * System prompt for the reconcile pass. Mirrors the deep-phase judge\n * style but expands the decision space to the full ADD/UPDATE/NOOP/\n * CONFLICT loop and asks for the `targetId` of the resolved neighbour.\n *\n * @internal\n */\nexport const RECONCILE_SYSTEM_PROMPT = [\n \"You reconcile a candidate memory against the user's existing related memories.\",\n 'Return strictly JSON: { \"action\": \"add\" | \"update\" | \"noop\" | \"conflict\", \"targetId\"?: string, \"reason\": string }.',\n 'Use \"update\" when the candidate is a newer version of an existing memory (a changed preference, location, job, or fact).',\n 'Use \"noop\" when the candidate adds nothing new (a duplicate or subset of an existing memory).',\n 'Use \"conflict\" when the candidate contradicts an existing memory and the older one should be closed.',\n 'Use \"add\" when the candidate is independent of every listed memory, or when you are unsure.',\n 'For \"update\", \"noop\", and \"conflict\", set \"targetId\" to the id of the existing memory you are resolving against.',\n].join(' ');\n\n/**\n * Run one reconcile pass. The decision is always safe to apply: parse\n * failures and dangling `targetId`s degrade to an additive write.\n *\n * @stable\n */\nexport async function reconcileCandidate(args: ReconcileCandidateArgs): Promise<ReconcileResult> {\n const request = buildReconcileRequest(args);\n const response = await args.provider.generate(request);\n const usage = response.usage;\n const promptTokens = usage.promptTokens ?? 0;\n const completionTokens = usage.completionTokens ?? 0;\n const totalTokens = promptTokens + completionTokens + (usage.reasoningTokens ?? 0);\n const validIds = new Set(args.neighbors.map((n) => n.id));\n const decision = parseReconcile(response.text, validIds);\n return { decision, usage: { promptTokens, completionTokens, totalTokens } };\n}\n\n/**\n * Parse the reconcile model output into a {@link ReconcileDecision}.\n * Tolerates fenced blocks + trailing commentary; an unparseable body,\n * an unknown action, or a `targetId` that is not in `validTargetIds`\n * all degrade to `{ action: 'add' }` (audited via the reason).\n *\n * @internal\n */\nexport function parseReconcile(\n text: string | undefined,\n validTargetIds: ReadonlySet<string>,\n): ReconcileDecision {\n const fallback: ReconcileDecision = { action: 'add', reason: 'reconcile-parse-failure' };\n if (text === undefined || text.length === 0) return fallback;\n const candidate = stripFence(text).trim();\n if (candidate.length === 0) return fallback;\n let parsed: unknown;\n try {\n parsed = JSON.parse(candidate);\n } catch {\n const slice = sliceJsonObject(candidate);\n if (slice === null) return fallback;\n try {\n parsed = JSON.parse(slice);\n } catch {\n return fallback;\n }\n }\n if (parsed === null || typeof parsed !== 'object') return fallback;\n const obj = parsed as { action?: unknown; targetId?: unknown; reason?: unknown };\n const reason = typeof obj.reason === 'string' && obj.reason.length > 0 ? obj.reason : undefined;\n if (obj.action === 'add') {\n return { action: 'add', ...(reason !== undefined ? { reason } : {}) };\n }\n if (obj.action === 'update' || obj.action === 'noop' || obj.action === 'conflict') {\n const targetId = typeof obj.targetId === 'string' ? obj.targetId : null;\n if (targetId === null || !validTargetIds.has(targetId)) {\n // The model referenced a fact that is not one of the supplied\n // neighbours - never rewrite an unverified id; add instead.\n return { action: 'add', reason: 'reconcile-invalid-target' };\n }\n if (obj.action === 'noop') {\n return { action: 'noop', targetId, ...(reason !== undefined ? { reason } : {}) };\n }\n return { action: obj.action, targetId, reason: reason ?? `reconcile-${obj.action}` };\n }\n return fallback;\n}\n\nfunction buildReconcileRequest(args: ReconcileCandidateArgs): ProviderRequest {\n const neighborLines = args.neighbors\n .map((n, i) => {\n const vf = n.validFrom !== undefined ? ` (validFrom: ${n.validFrom})` : '';\n return `${i + 1}. [id: ${n.id}]${vf} ${n.text}`;\n })\n .join('\\n');\n const userBlock = [\n `Candidate memory: ${args.candidateText}`,\n '',\n 'Existing related memories:',\n neighborLines.length > 0 ? neighborLines : '(none)',\n ].join('\\n');\n return {\n messages: [{ role: 'user', content: userBlock }],\n systemMessage: RECONCILE_SYSTEM_PROMPT,\n temperature: 0,\n // MCON-14: per-call output cap - the decision shape is tiny.\n maxTokens: 256,\n metadata: {\n userId: args.scope.userId,\n ...(args.scope.sessionId !== undefined ? { sessionId: args.scope.sessionId } : {}),\n ...(args.scope.agentId !== undefined ? { agentId: args.scope.agentId } : {}),\n },\n outputType: { kind: 'structured' },\n };\n}\n\n/**\n * Minimal candidate `Fact` for the pre-filter stage context. Stage 1\n * reads only `text`; Stage 2 reads only the neighbour scores - the\n * other fields are never inspected, so a synthetic id/scope is safe.\n */\nfunction synthCandidate(text: string): Fact {\n return {\n id: '__reconcile_candidate__',\n kind: 'semantic',\n userId: '__reconcile_candidate__',\n sensitivity: 'internal',\n text,\n createdAt: new Date(0).toISOString(),\n };\n}\n\nfunction stripFence(text: string): string {\n const match = /^```[^\\n]*\\n([\\s\\S]*?)\\n```/u.exec(text.trim());\n return match?.[1] ?? text;\n}\n\nfunction sliceJsonObject(text: string): string | null {\n const start = text.indexOf('{');\n const end = text.lastIndexOf('}');\n if (start < 0 || end < start) return null;\n return text.slice(start, end + 1);\n}\n"],"mappings":"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"}
|
|
@@ -21,7 +21,7 @@ interface Consolidator {
|
|
|
21
21
|
* idle/cron scheduler so admins can flush the queue on demand.
|
|
22
22
|
*/
|
|
23
23
|
fireNow(phase: ConsolidatorPhase, scope?: SessionScope): Promise<PhaseOutcome | null>;
|
|
24
|
-
/** Replace the active tier
|
|
24
|
+
/** Replace the active tier - recomputes ceilings + phase set. */
|
|
25
25
|
setTier(tier: ConsolidatorTier): Promise<void>;
|
|
26
26
|
/** Pause the consolidator until the next budget reset. */
|
|
27
27
|
pause(): Promise<void>;
|
|
@@ -31,16 +31,16 @@ interface Consolidator {
|
|
|
31
31
|
onPhaseFinished(listener: PhaseListener): () => void;
|
|
32
32
|
/**
|
|
33
33
|
* Record memory-pipeline LLM spend that happened OUTSIDE a phase run
|
|
34
|
-
* (MCON-15
|
|
34
|
+
* (MCON-15 - e.g. workflow induction) so the daily ceilings cover it.
|
|
35
35
|
* Counted under the deep-phase bucket.
|
|
36
36
|
*/
|
|
37
37
|
recordExternalSpend(tokens: number, costUsd?: number): void;
|
|
38
|
-
/** Active config
|
|
38
|
+
/** Active config - frozen snapshot. */
|
|
39
39
|
config(): ConsolidatorConfig;
|
|
40
40
|
/**
|
|
41
41
|
* Register this consolidator's cron / idle triggers with a
|
|
42
42
|
* `@graphorin/triggers` scheduler so they fire `trigger(...)`
|
|
43
|
-
* automatically (the daemon ↔ triggers bridge
|
|
43
|
+
* automatically (the daemon ↔ triggers bridge - MCON-4). Uses the
|
|
44
44
|
* configured `defaultScope`; throws if none was set. Turn / event
|
|
45
45
|
* triggers are skipped (consumer-emitted). The standalone server calls
|
|
46
46
|
* this in `beforeStart`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","names":[],"sources":["../../src/consolidator/runtime.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","names":[],"sources":["../../src/consolidator/runtime.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AA+GiC,UAtChB,YAAA,CAsCgB;EAAO,KAAA,EAAA,EArC7B,OAqC6B,CAAA,IAAA,CAAA;EAQxB,IAAA,EAAA,EA5CN,OA4CM,CAAA,IAAA,CAAkB;kBA3ChB,kCAAkC,eAAe,QAAQ;YAC/D,QAAQ;;;;;iBAKH,2BAA2B,eAAe,QAAQ;;gBAEnD,mBAAmB;;WAExB;;YAEC;;4BAEgB;;;;;;;;YAQhB;;;;;;;;;mCASuB,gBAAgB,QAAQ;;;;kBAIzC,eAAe;;;;;;;iBAQjB,kBAAA,OAAyB,4BAA4B"}
|
|
@@ -7,6 +7,7 @@ import { LockManager } from "./lock.js";
|
|
|
7
7
|
import { runDeepPhase } from "./phases/deep.js";
|
|
8
8
|
import { runLightPhase } from "./phases/light.js";
|
|
9
9
|
import { runStandardPhase } from "./phases/standard.js";
|
|
10
|
+
import { runLearnedContextPass } from "./phases/learned-context.js";
|
|
10
11
|
import { runReflectionPass } from "./phases/reflect.js";
|
|
11
12
|
import { registerConsolidatorTriggers } from "./scheduler.js";
|
|
12
13
|
import { CONSOLIDATOR_TIER_DEFAULTS } from "./types.js";
|
|
@@ -14,6 +15,12 @@ import { NOOP_TRACER } from "@graphorin/core";
|
|
|
14
15
|
|
|
15
16
|
//#region src/consolidator/runtime.ts
|
|
16
17
|
/**
|
|
18
|
+
* Age after which an unresolved CONFLICT-CHECK row is expired as
|
|
19
|
+
* `'admit'` at tiers with no deep phase (memory-consolidation-04) -
|
|
20
|
+
* the judge that would resolve it never runs there.
|
|
21
|
+
*/
|
|
22
|
+
const PENDING_CONFLICT_TTL_MS = 10080 * 60 * 1e3;
|
|
23
|
+
/**
|
|
17
24
|
* Build the runtime consolidator.
|
|
18
25
|
*
|
|
19
26
|
* @stable
|
|
@@ -24,6 +31,7 @@ function createConsolidator(opts) {
|
|
|
24
31
|
var ConsolidatorImpl = class {
|
|
25
32
|
#semantic;
|
|
26
33
|
#episodic;
|
|
34
|
+
#working;
|
|
27
35
|
#store;
|
|
28
36
|
#consolidatorStore;
|
|
29
37
|
#tracer;
|
|
@@ -33,6 +41,12 @@ var ConsolidatorImpl = class {
|
|
|
33
41
|
/** Per-phase provider overrides (MCON-7); fall back to `#provider`. */
|
|
34
42
|
#cheapProvider;
|
|
35
43
|
#deepProvider;
|
|
44
|
+
/**
|
|
45
|
+
* USD pricer for phase LLM usage (memory-consolidation-02). Without
|
|
46
|
+
* it every phase's `priceUsage?.(...) ?? 0` evaluates to zero and the
|
|
47
|
+
* `maxCostPerDay` ceiling can never trip.
|
|
48
|
+
*/
|
|
49
|
+
#priceUsage;
|
|
36
50
|
#defaultScope;
|
|
37
51
|
#listeners = /* @__PURE__ */ new Set();
|
|
38
52
|
#lockManager;
|
|
@@ -44,7 +58,7 @@ var ConsolidatorImpl = class {
|
|
|
44
58
|
/**
|
|
45
59
|
* Message ids of the batch the most recent `#dispatch` operated on
|
|
46
60
|
* (MCON-10). Captured so a thrown phase can enqueue its DLQ row with
|
|
47
|
-
* the REAL failed slice instead of `[]`
|
|
61
|
+
* the REAL failed slice instead of `[]` - replays must target the
|
|
48
62
|
* window that failed, not whatever the cursor points at later.
|
|
49
63
|
*/
|
|
50
64
|
#lastDispatchMessageIds = [];
|
|
@@ -59,6 +73,7 @@ var ConsolidatorImpl = class {
|
|
|
59
73
|
constructor(opts) {
|
|
60
74
|
this.#semantic = opts.semantic;
|
|
61
75
|
this.#episodic = opts.episodic ?? null;
|
|
76
|
+
this.#working = opts.working ?? null;
|
|
62
77
|
this.#store = opts.store;
|
|
63
78
|
this.#consolidatorStore = this.#store.consolidator ?? null;
|
|
64
79
|
this.#tracer = opts.tracer ?? NOOP_TRACER;
|
|
@@ -69,6 +84,7 @@ var ConsolidatorImpl = class {
|
|
|
69
84
|
this.#provider = opts.provider ?? null;
|
|
70
85
|
this.#cheapProvider = opts.cheapProvider ?? null;
|
|
71
86
|
this.#deepProvider = opts.deepProvider ?? null;
|
|
87
|
+
this.#priceUsage = opts.priceUsage ?? null;
|
|
72
88
|
this.#defaultScope = opts.defaultScope ?? null;
|
|
73
89
|
this.#config = resolveConfig(opts);
|
|
74
90
|
this.#lockManager = new LockManager({
|
|
@@ -125,7 +141,7 @@ var ConsolidatorImpl = class {
|
|
|
125
141
|
async setTier(tier) {
|
|
126
142
|
const preset = CONSOLIDATOR_TIER_DEFAULTS[tier];
|
|
127
143
|
if (preset === void 0) throw new Error(`[graphorin/memory] unknown consolidator tier '${tier}'`);
|
|
128
|
-
if (tier === "custom") throw new Error("[graphorin/memory] consolidator.setTier(\"custom\") requires explicit ceilings
|
|
144
|
+
if (tier === "custom") throw new Error("[graphorin/memory] consolidator.setTier(\"custom\") requires explicit ceilings - re-create the consolidator with `tier: \"custom\"` + `ceilings`.");
|
|
129
145
|
this.#config = Object.freeze({
|
|
130
146
|
...this.#config,
|
|
131
147
|
tier,
|
|
@@ -205,6 +221,8 @@ var ConsolidatorImpl = class {
|
|
|
205
221
|
async trigger(reason, scope) {
|
|
206
222
|
if (!this.#running) return null;
|
|
207
223
|
if (this.#manuallyPaused) return null;
|
|
224
|
+
await this.drainDlq(scope).catch(() => 0);
|
|
225
|
+
await this.#expireStalePendingConflicts(scope).catch(() => {});
|
|
208
226
|
const phases = this.#planPhases(reason);
|
|
209
227
|
if (phases.length === 0) return null;
|
|
210
228
|
if (reason.kind !== "manual" && this.#config.ceilings.cooldownMs > 0) {
|
|
@@ -225,12 +243,26 @@ var ConsolidatorImpl = class {
|
|
|
225
243
|
async fireNow(phase, scope) {
|
|
226
244
|
const target = scope ?? this.#defaultScope;
|
|
227
245
|
if (target === null) throw new Error("[graphorin/memory] consolidator.fireNow requires a scope (default scope was not configured).");
|
|
228
|
-
if (!this.#config.phases.includes(phase) && phase !== "light") process.stderr.write(`[graphorin/memory] consolidator.fireNow('${phase}') runs a phase not enabled for tier '${this.#config.tier}'
|
|
246
|
+
if (!this.#config.phases.includes(phase) && phase !== "light") process.stderr.write(`[graphorin/memory] consolidator.fireNow('${phase}') runs a phase not enabled for tier '${this.#config.tier}' - proceeding (manual flushes bypass phase gating).\n`);
|
|
229
247
|
return this.#runPhase(phase, target, {
|
|
230
248
|
kind: "manual",
|
|
231
249
|
value: phase
|
|
232
250
|
});
|
|
233
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* memory-consolidation-04: at tiers with no deep phase the pending
|
|
254
|
+
* CONFLICT-CHECK queue has no drain. Resolve rows older than 7 days
|
|
255
|
+
* as `'admit'` (the safe direction - the candidate fact stays live;
|
|
256
|
+
* only the never-coming judge call is skipped), bounded per sweep.
|
|
257
|
+
*/
|
|
258
|
+
async #expireStalePendingConflicts(scope) {
|
|
259
|
+
if (this.#config.phases.includes("deep")) return;
|
|
260
|
+
const conflicts = this.#store.conflicts;
|
|
261
|
+
if (conflicts === void 0) return;
|
|
262
|
+
const cutoff = this.#now() - PENDING_CONFLICT_TTL_MS;
|
|
263
|
+
const pending = await conflicts.listPending(scope, 200);
|
|
264
|
+
for (const row of pending) if (row.enqueuedAt <= cutoff) await conflicts.markResolved(row.id, "admit");
|
|
265
|
+
}
|
|
234
266
|
async drainDlq(scope) {
|
|
235
267
|
const store = this.#consolidatorStore;
|
|
236
268
|
if (store === null) return 0;
|
|
@@ -433,7 +465,7 @@ var ConsolidatorImpl = class {
|
|
|
433
465
|
const session = this.#store.session;
|
|
434
466
|
const rawBatch = typeof session.listMessagesSince === "function" ? await session.listMessagesSince(scope, lastProcessedMessageId, this.#config.maxStandardBatchSize) : [];
|
|
435
467
|
this.#lastDispatchMessageIds = rawBatch.map((r) => r.id);
|
|
436
|
-
const out = await runStandardPhase({
|
|
468
|
+
const out$1 = await runStandardPhase({
|
|
437
469
|
semantic: this.#semantic,
|
|
438
470
|
episodic: this.#episodic,
|
|
439
471
|
formEpisodes: this.#config.formEpisodes,
|
|
@@ -452,15 +484,16 @@ var ConsolidatorImpl = class {
|
|
|
452
484
|
budget: this.#budget,
|
|
453
485
|
tier: this.#config.tier === "free" ? "cheap" : this.#config.tier,
|
|
454
486
|
now: this.#now,
|
|
455
|
-
batch: rawBatch
|
|
487
|
+
batch: rawBatch,
|
|
488
|
+
...this.#priceUsage !== null ? { priceUsage: this.#priceUsage } : {}
|
|
456
489
|
});
|
|
457
490
|
const cursor = tipMessageId(rawBatch);
|
|
458
491
|
if (cursor !== null && this.#consolidatorStore !== null) await this.#consolidatorStore.upsertState(scope, { lastProcessedMessageId: cursor });
|
|
459
|
-
return out;
|
|
492
|
+
return out$1;
|
|
460
493
|
}
|
|
461
494
|
const deepProvider = this.#deepProvider ?? this.#provider;
|
|
462
495
|
if (deepProvider === null) throw new ProviderNotConfiguredError("deep");
|
|
463
|
-
|
|
496
|
+
let out = await runDeepPhase({
|
|
464
497
|
store: this.#store,
|
|
465
498
|
consolidatorStore: this.#consolidatorStore,
|
|
466
499
|
provider: deepProvider,
|
|
@@ -470,7 +503,8 @@ var ConsolidatorImpl = class {
|
|
|
470
503
|
maxConflictsPerRun: this.#config.maxDeepConflictsPerRun,
|
|
471
504
|
budget: this.#budget,
|
|
472
505
|
tier: this.#config.tier === "free" || this.#config.tier === "cheap" ? "standard" : this.#config.tier,
|
|
473
|
-
now: this.#now
|
|
506
|
+
now: this.#now,
|
|
507
|
+
...this.#priceUsage !== null ? { priceUsage: this.#priceUsage } : {}
|
|
474
508
|
});
|
|
475
509
|
const insightStore = this.#store.insights;
|
|
476
510
|
if (this.#config.reflection && this.#episodic !== null && insightStore !== void 0) {
|
|
@@ -486,17 +520,38 @@ var ConsolidatorImpl = class {
|
|
|
486
520
|
importanceThreshold: this.#config.importanceThreshold,
|
|
487
521
|
reflectionWatermark: priorWatermark,
|
|
488
522
|
maxQuestions: this.#config.reflectionMaxQuestions,
|
|
489
|
-
now: this.#now
|
|
523
|
+
now: this.#now,
|
|
524
|
+
...this.#priceUsage !== null ? { priceUsage: this.#priceUsage } : {}
|
|
490
525
|
});
|
|
491
526
|
if (this.#consolidatorStore !== null && reflection.nextWatermark !== priorWatermark) await this.#consolidatorStore.upsertState(scope, { reflectionWatermark: reflection.nextWatermark });
|
|
492
|
-
|
|
493
|
-
...
|
|
527
|
+
out = {
|
|
528
|
+
...out,
|
|
494
529
|
insightsCreated: reflection.insightsCreated,
|
|
495
|
-
llmTokensUsed:
|
|
496
|
-
llmCostUsd:
|
|
530
|
+
llmTokensUsed: out.llmTokensUsed + reflection.tokens,
|
|
531
|
+
llmCostUsd: out.llmCostUsd === null && reflection.costUsd === 0 ? null : (out.llmCostUsd ?? 0) + reflection.costUsd
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
if (this.#config.learnedContext && this.#working !== null) {
|
|
535
|
+
const learned = await runLearnedContextPass({
|
|
536
|
+
provider: deepProvider,
|
|
537
|
+
tracer: this.#tracer,
|
|
538
|
+
scope,
|
|
539
|
+
working: this.#working,
|
|
540
|
+
episodic: this.#episodic,
|
|
541
|
+
store: this.#store,
|
|
542
|
+
budget: this.#budget,
|
|
543
|
+
maxChars: this.#config.learnedContextMaxChars,
|
|
544
|
+
now: this.#now,
|
|
545
|
+
...this.#priceUsage !== null ? { priceUsage: this.#priceUsage } : {}
|
|
546
|
+
});
|
|
547
|
+
out = {
|
|
548
|
+
...out,
|
|
549
|
+
learnedContextUpdated: learned.updated,
|
|
550
|
+
llmTokensUsed: out.llmTokensUsed + learned.tokens,
|
|
551
|
+
llmCostUsd: out.llmCostUsd === null && learned.costUsd === 0 ? null : (out.llmCostUsd ?? 0) + learned.costUsd
|
|
497
552
|
};
|
|
498
553
|
}
|
|
499
|
-
return
|
|
554
|
+
return out;
|
|
500
555
|
}
|
|
501
556
|
#emit(outcome, scope, reason) {
|
|
502
557
|
for (const listener of this.#listeners) try {
|
|
@@ -522,7 +577,7 @@ function skipOutcome(phase, reason) {
|
|
|
522
577
|
emptyExtractions: 0,
|
|
523
578
|
llmTokensUsed: 0,
|
|
524
579
|
llmCostUsd: null,
|
|
525
|
-
errorMessage: `phase skipped
|
|
580
|
+
errorMessage: `phase skipped - ${reason}`
|
|
526
581
|
};
|
|
527
582
|
}
|
|
528
583
|
/** Resolve operator-supplied options into a fully-defaulted config. */
|
|
@@ -568,7 +623,9 @@ function resolveConfig(opts) {
|
|
|
568
623
|
reflection: opts.reflection ?? preset.reflection,
|
|
569
624
|
importanceThreshold: opts.importanceThreshold ?? preset.importanceThreshold,
|
|
570
625
|
reflectionMaxQuestions: opts.reflectionMaxQuestions ?? preset.reflectionMaxQuestions,
|
|
571
|
-
contextualRetrieval: opts.contextualRetrieval ?? preset.contextualRetrieval
|
|
626
|
+
contextualRetrieval: opts.contextualRetrieval ?? preset.contextualRetrieval,
|
|
627
|
+
learnedContext: opts.learnedContext ?? preset.learnedContext,
|
|
628
|
+
learnedContextMaxChars: opts.learnedContextMaxChars ?? preset.learnedContextMaxChars
|
|
572
629
|
});
|
|
573
630
|
}
|
|
574
631
|
function defaultTriggers() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","names":["#semantic","#episodic","#store","#consolidatorStore","#tracer","#now","#randomId","#provider","#cheapProvider","#deepProvider","#defaultScope","#listeners","#lockManager","#budget","#config","#running","#manuallyPaused","lastRunAt: string | undefined","lastPhase: ConsolidatorPhase | undefined","lastRuns: { -readonly [K in keyof ConsolidatorLastRuns]: ConsolidatorLastRuns[K] }","#deferredRunsAdjustment","#deferredRuns","#emptyExtractions","#planPhases","#emit","last: PhaseOutcome | null","#runPhase","replayPhase: ConsolidatorPhase","lastError: unknown","planned: ConsolidatorPhase[]","outcome: PhaseOutcome","#dispatch","#lastDispatchMessageIds","tier: ConsolidatorTier","missing: string[]"],"sources":["../../src/consolidator/runtime.ts"],"sourcesContent":["/**\n * Consolidator runtime — orchestrates the three phases, the lock,\n * the budget, the cursor, and the DLQ. The factory is the public\n * surface; the class lives below it as a private implementation\n * detail.\n *\n * The runtime is **library-mode** by default — it does not start a\n * background scheduler. Cron / idle triggers are realised by\n * registering them with a `@graphorin/triggers` scheduler via\n * `registerWithScheduler(...)` (the standalone server calls this in\n * `beforeStart` whenever a consolidator + a triggers scheduler are both\n * supplied). Turn / event triggers are consumer-emitted — the scheduler\n * cannot fire them on its own, so the consumer must call\n * `trigger({ kind: 'turn' | 'event' }, ...)` from its own loop. Manual\n * `trigger(...)` / `fireNow(...)` calls work in either mode.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, SessionScope, Tracer } from '@graphorin/core';\nimport { NOOP_TRACER } from '@graphorin/core';\nimport type {\n ConsolidatorMemoryStoreExt,\n MemoryStoreAdapter,\n} from '../internal/storage-adapter.js';\nimport type { EpisodicMemory } from '../tiers/episodic-memory.js';\nimport type { SemanticMemory } from '../tiers/semantic-memory.js';\nimport { BudgetTracker } from './budget.js';\nimport { DEFAULT_SALIENCE_WEIGHTS } from './decay.js';\nimport { classifyError, describeError, nextBackoffMs } from './dlq.js';\nimport { CustomTierMisconfiguredError, ProviderNotConfiguredError } from './errors.js';\nimport { tipMessageId } from './idempotency.js';\nimport { LockManager } from './lock.js';\nimport type { NoiseFilterPreset } from './noise-filter.js';\nimport { runDeepPhase } from './phases/deep.js';\nimport { runLightPhase } from './phases/light.js';\nimport { runReflectionPass } from './phases/reflect.js';\nimport { runStandardPhase } from './phases/standard.js';\nimport {\n type RegisterTriggersResult,\n registerConsolidatorTriggers,\n type SchedulerLike,\n} from './scheduler.js';\nimport {\n CONSOLIDATOR_TIER_DEFAULTS,\n type ConsolidatorConfig,\n type ConsolidatorLastRuns,\n type ConsolidatorPhase,\n type ConsolidatorStatus,\n type ConsolidatorTier,\n type ConsolidatorTriggerReason,\n type ConsolidatorTriggerSpec,\n type CreateConsolidatorOptions,\n type PhaseListener,\n type PhaseOutcome,\n} from './types.js';\n\n/**\n * Consolidator runtime surface returned by {@link createConsolidator}.\n * Compatible with the placeholder shape so the facade can swap the\n * implementation without breaking consumers.\n *\n * @stable\n */\nexport interface Consolidator {\n start(): Promise<void>;\n stop(): Promise<void>;\n trigger(reason: ConsolidatorTriggerReason, scope: SessionScope): Promise<PhaseOutcome | null>;\n status(): Promise<ConsolidatorStatus>;\n /**\n * Manual trigger for the requested phase. Skips phase gating + the\n * idle/cron scheduler so admins can flush the queue on demand.\n */\n fireNow(phase: ConsolidatorPhase, scope?: SessionScope): Promise<PhaseOutcome | null>;\n /** Replace the active tier — recomputes ceilings + phase set. */\n setTier(tier: ConsolidatorTier): Promise<void>;\n /** Pause the consolidator until the next budget reset. */\n pause(): Promise<void>;\n /** Resume after `pause()`. */\n resume(): Promise<void>;\n /** Subscribe to phase-finished notifications. Returns an unsubscribe. */\n onPhaseFinished(listener: PhaseListener): () => void;\n /**\n * Record memory-pipeline LLM spend that happened OUTSIDE a phase run\n * (MCON-15 — e.g. workflow induction) so the daily ceilings cover it.\n * Counted under the deep-phase bucket.\n */\n recordExternalSpend(tokens: number, costUsd?: number): void;\n /** Active config — frozen snapshot. */\n config(): ConsolidatorConfig;\n /**\n * Register this consolidator's cron / idle triggers with a\n * `@graphorin/triggers` scheduler so they fire `trigger(...)`\n * automatically (the daemon ↔ triggers bridge — MCON-4). Uses the\n * configured `defaultScope`; throws if none was set. Turn / event\n * triggers are skipped (consumer-emitted). The standalone server calls\n * this in `beforeStart`.\n */\n registerWithScheduler(scheduler: SchedulerLike): Promise<RegisterTriggersResult>;\n /** True when `tier === 'free'`. */\n isFree(): boolean;\n /** Drain DLQ rows whose `nextRetryAt` <= now. */\n drainDlq(scope: SessionScope): Promise<number>;\n}\n\n/**\n * Build the runtime consolidator.\n *\n * @stable\n */\nexport function createConsolidator(opts: CreateConsolidatorOptions): Consolidator {\n return new ConsolidatorImpl(opts);\n}\n\nclass ConsolidatorImpl implements Consolidator {\n readonly #semantic: SemanticMemory;\n readonly #episodic: EpisodicMemory | null;\n readonly #store: MemoryStoreAdapter;\n readonly #consolidatorStore: ConsolidatorMemoryStoreExt | null;\n readonly #tracer: Tracer;\n readonly #now: () => number;\n readonly #randomId: () => string;\n readonly #provider: Provider | null;\n /** Per-phase provider overrides (MCON-7); fall back to `#provider`. */\n readonly #cheapProvider: Provider | null;\n readonly #deepProvider: Provider | null;\n readonly #defaultScope: SessionScope | null;\n readonly #listeners = new Set<PhaseListener>();\n readonly #lockManager: LockManager;\n readonly #budget: BudgetTracker;\n #config: ConsolidatorConfig;\n #running = false;\n #manuallyPaused = false;\n #deferredRuns = 0;\n /**\n * Message ids of the batch the most recent `#dispatch` operated on\n * (MCON-10). Captured so a thrown phase can enqueue its DLQ row with\n * the REAL failed slice instead of `[]` — replays must target the\n * window that failed, not whatever the cursor points at later.\n */\n #lastDispatchMessageIds: ReadonlyArray<string> = [];\n /**\n * Bumped when the runtime cannot persist a deferred run to the\n * audit log (e.g., adapter omits the consolidator surface). The\n * `status()` reader merges this with the persisted count so\n * library-mode callers still observe the deferral.\n */\n #deferredRunsAdjustment = 0;\n #emptyExtractions = 0;\n\n constructor(opts: CreateConsolidatorOptions) {\n this.#semantic = opts.semantic;\n this.#episodic = opts.episodic ?? null;\n this.#store = opts.store;\n this.#consolidatorStore = this.#store.consolidator ?? null;\n this.#tracer = opts.tracer ?? NOOP_TRACER;\n this.#now = opts.now ?? Date.now;\n this.#randomId =\n opts.randomId ??\n ((): string => {\n const a = Math.floor(Math.random() * 1e9).toString(36);\n const b = Math.floor(Math.random() * 1e9).toString(36);\n return `cr_${a}${b}`;\n });\n this.#provider = opts.provider ?? null;\n this.#cheapProvider = opts.cheapProvider ?? null;\n this.#deepProvider = opts.deepProvider ?? null;\n this.#defaultScope = opts.defaultScope ?? null;\n\n this.#config = resolveConfig(opts);\n this.#lockManager = new LockManager({\n store: this.#consolidatorStore,\n waitMs: this.#config.lockWaitMs,\n maxRunDurationMs: this.#config.ceilings.maxRunDurationMs,\n now: this.#now,\n randomId: this.#randomId,\n });\n this.#budget = new BudgetTracker({\n maxTokensPerDay: this.#config.ceilings.maxTokensPerDay,\n maxCostPerDay: this.#config.ceilings.maxCostPerDay,\n onExceed: this.#config.onExceed,\n resetSemantics: this.#config.budgetResetSemantics,\n now: this.#now,\n });\n }\n\n async start(): Promise<void> {\n this.#running = true;\n }\n\n async stop(): Promise<void> {\n this.#running = false;\n }\n\n async pause(): Promise<void> {\n this.#manuallyPaused = true;\n }\n\n async resume(): Promise<void> {\n this.#manuallyPaused = false;\n }\n\n config(): ConsolidatorConfig {\n return this.#config;\n }\n\n async registerWithScheduler(scheduler: SchedulerLike): Promise<RegisterTriggersResult> {\n if (this.#defaultScope === null) {\n throw new Error(\n '[graphorin/memory] Consolidator.registerWithScheduler requires a defaultScope. ' +\n 'Pass `defaultScope` to createConsolidator / createMemory, or call ' +\n 'registerConsolidatorTriggers(consolidator, scheduler, { scope }) directly.',\n );\n }\n return registerConsolidatorTriggers(this, scheduler, { scope: this.#defaultScope });\n }\n\n isFree(): boolean {\n return this.#config.tier === 'free';\n }\n\n onPhaseFinished(listener: PhaseListener): () => void {\n this.#listeners.add(listener);\n return () => {\n this.#listeners.delete(listener);\n };\n }\n\n recordExternalSpend(tokens: number, costUsd?: number): void {\n if (!Number.isFinite(tokens) || tokens <= 0) return;\n this.#budget.record({ phase: 'deep', tokens, costUsd: costUsd ?? 0 });\n }\n\n async setTier(tier: ConsolidatorTier): Promise<void> {\n const preset = CONSOLIDATOR_TIER_DEFAULTS[tier];\n if (preset === undefined) {\n throw new Error(`[graphorin/memory] unknown consolidator tier '${tier}'`);\n }\n if (tier === 'custom') {\n throw new Error(\n '[graphorin/memory] consolidator.setTier(\"custom\") requires explicit ceilings — re-create the consolidator with `tier: \"custom\"` + `ceilings`.',\n );\n }\n this.#config = Object.freeze({\n ...this.#config,\n tier,\n phases: preset.phases,\n ceilings: preset.ceilings,\n onExceed: preset.onExceed,\n formEpisodes: preset.formEpisodes,\n importanceScoring: preset.importanceScoring,\n reflection: preset.reflection,\n importanceThreshold: preset.importanceThreshold,\n reflectionMaxQuestions: preset.reflectionMaxQuestions,\n contextualRetrieval: preset.contextualRetrieval,\n });\n this.#budget.reconfigure({\n maxTokensPerDay: preset.ceilings.maxTokensPerDay,\n maxCostPerDay: preset.ceilings.maxCostPerDay,\n });\n }\n\n async status(): Promise<ConsolidatorStatus> {\n let lastRunAt: string | undefined;\n let lastPhase: ConsolidatorPhase | undefined;\n const lastRuns: { -readonly [K in keyof ConsolidatorLastRuns]: ConsolidatorLastRuns[K] } = {};\n let pendingConflicts = 0;\n let dlqSize = 0;\n let deferredFromStore = 0;\n if (this.#consolidatorStore !== null && this.#defaultScope !== null) {\n const state = await this.#consolidatorStore.getState(this.#defaultScope);\n if (state !== null) {\n if (state.lastCompletedAt !== null) {\n lastRunAt = new Date(state.lastCompletedAt).toISOString();\n }\n if (state.lastPhase !== null) {\n lastPhase = state.lastPhase;\n }\n }\n const failed = await this.#consolidatorStore.listFailedBatches(this.#defaultScope, 1000);\n dlqSize = failed.length;\n // Hydrate per-phase `lastRuns` and the persisted deferred_runs\n // counter from the run audit log so process restart preserves\n // both surfaces.\n const recent = await this.#consolidatorStore.listRecentRuns(this.#defaultScope, 500);\n for (const run of recent) {\n if (run.status === 'deferred') {\n deferredFromStore += 1;\n continue;\n }\n if (run.status !== 'completed' && run.status !== 'partial') continue;\n const stamp = new Date(run.finishedAt ?? run.startedAt).toISOString();\n if (run.phase === 'light' && lastRuns.light === undefined) lastRuns.light = stamp;\n else if (run.phase === 'standard' && lastRuns.standard === undefined)\n lastRuns.standard = stamp;\n else if (run.phase === 'deep' && lastRuns.deep === undefined) lastRuns.deep = stamp;\n }\n }\n if (this.#store.conflicts !== undefined && this.#defaultScope !== null) {\n const list = await this.#store.conflicts.listPending(this.#defaultScope, 1000);\n pendingConflicts = list.length;\n }\n const snapshot = this.#budget.snapshot();\n const persistedDeferred = deferredFromStore + this.#deferredRunsAdjustment;\n const totalDeferred = Math.max(persistedDeferred, this.#deferredRuns);\n const out: ConsolidatorStatus = {\n tier: this.#config.tier,\n triggers: this.#config.triggers,\n phases: this.#config.phases,\n running: this.#running,\n paused: this.#manuallyPaused || snapshot.paused,\n pendingConflicts,\n queueDepth: pendingConflicts,\n dlqSize,\n deferredRuns: totalDeferred,\n emptyExtractions: this.#emptyExtractions,\n lastRuns: Object.freeze(lastRuns),\n budget: {\n tokensUsedToday: snapshot.tokensUsedToday,\n costUsedToday: snapshot.costUsedToday,\n tokensRemaining: snapshot.tokensRemaining,\n costRemaining: snapshot.costRemaining,\n resetAt: snapshot.resetAt,\n },\n budgetRemaining: {\n tokens: snapshot.tokensRemaining,\n costUsd: snapshot.costRemaining,\n },\n ...(lastRunAt !== undefined ? { lastRunAt } : {}),\n ...(lastPhase !== undefined ? { lastPhase } : {}),\n };\n return out;\n }\n\n async trigger(\n reason: ConsolidatorTriggerReason,\n scope: SessionScope,\n ): Promise<PhaseOutcome | null> {\n if (!this.#running) return null;\n if (this.#manuallyPaused) return null;\n const phases = this.#planPhases(reason);\n if (phases.length === 0) return null;\n // MCON-8: enforce the cooldown the runtime always PERSISTED but never\n // read — back-to-back triggers used to fire with zero quiet period.\n // Checked ONCE per trigger dispatch (phases within one dispatch chain\n // freely); manual fireNow(...) / DLQ replays bypass it.\n if (reason.kind !== 'manual' && this.#config.ceilings.cooldownMs > 0) {\n const state =\n this.#consolidatorStore !== null ? await this.#consolidatorStore.getState(scope) : null;\n const eligibleAt = state?.nextEligibleAt ?? null;\n const firstPhase = phases[0] ?? 'light';\n if (eligibleAt !== null && this.#now() < eligibleAt) {\n return this.#emit(\n { ...skipOutcome(firstPhase, 'cooldown'), phase: firstPhase },\n scope,\n reason,\n );\n }\n }\n let last: PhaseOutcome | null = null;\n for (const phase of phases) {\n last = await this.#runPhase(phase, scope, reason);\n if (last?.status === 'failed' || last?.status === 'deferred') break;\n }\n return last;\n }\n\n async fireNow(phase: ConsolidatorPhase, scope?: SessionScope): Promise<PhaseOutcome | null> {\n const target = scope ?? this.#defaultScope;\n if (target === null) {\n throw new Error(\n '[graphorin/memory] consolidator.fireNow requires a scope (default scope was not configured).',\n );\n }\n if (!this.#config.phases.includes(phase) && phase !== 'light') {\n // MCON-17: manual flushes bypass phase gating ON PURPOSE — but the\n // operator should know they are running a phase the tier disabled\n // (the old empty branch promised a warn and emitted nothing).\n process.stderr.write(\n `[graphorin/memory] consolidator.fireNow('${phase}') runs a phase not enabled for tier '${this.#config.tier}' — proceeding (manual flushes bypass phase gating).\\n`,\n );\n }\n return this.#runPhase(phase, target, { kind: 'manual', value: phase });\n }\n\n async drainDlq(scope: SessionScope): Promise<number> {\n const store = this.#consolidatorStore;\n if (store === null) return 0;\n const ready = await store.claimReadyBatches(scope, this.#now(), 50);\n let drained = 0;\n for (const row of ready) {\n // MCON-10: replay the phase that actually failed (persisted at\n // enqueue); legacy rows without one fall back to 'standard',\n // matching the old behaviour.\n const replayPhase: ConsolidatorPhase = row.phase ?? 'standard';\n let succeeded = false;\n let deferred = false;\n let lastError: unknown = null;\n try {\n // MCON-10: replays run through the FULL phase envelope — budget\n // precheck, scope lock, and recordRunStart/Finish — so they are\n // visible in `consolidator_runs` and cannot race a live run or\n // spend past a pause.\n const outcome = await this.#runPhase(replayPhase, scope, {\n kind: 'manual',\n value: `dlq-replay:${replayPhase}`,\n });\n if (outcome?.status === 'completed' || outcome?.status === 'partial') {\n succeeded = true;\n } else if (outcome?.status === 'deferred') {\n deferred = true;\n lastError = new Error(outcome.errorMessage ?? 'replay deferred');\n } else if (outcome?.errorMessage != null) {\n lastError = new Error(outcome.errorMessage);\n }\n } catch (err) {\n lastError = err;\n }\n if (succeeded) {\n await store.markBatchSucceeded(row.id);\n drained += 1;\n continue;\n }\n if (deferred) {\n // Lock contention / budget pause is transient — reschedule at the\n // SAME retry count so contention cannot exhaust the row.\n const delayMs = nextBackoffMs({\n retryCount: row.retryCount + 1,\n baseMs: this.#config.dlqBaseBackoffMs,\n maxMs: this.#config.dlqMaxBackoffMs,\n });\n await store.rescheduleBatch(row.id, row.retryCount, this.#now() + delayMs);\n continue;\n }\n const nextRetryCount = row.retryCount + 1;\n if (nextRetryCount >= this.#config.dlqMaxRetries) {\n await store.markBatchExhausted(row.id, describeError(lastError), nextRetryCount);\n } else {\n const delayMs = nextBackoffMs({\n retryCount: nextRetryCount,\n baseMs: this.#config.dlqBaseBackoffMs,\n maxMs: this.#config.dlqMaxBackoffMs,\n });\n await store.rescheduleBatch(row.id, nextRetryCount, this.#now() + delayMs);\n }\n }\n return drained;\n }\n\n // ---------------------------------------------------------------------------\n\n #planPhases(reason: ConsolidatorTriggerReason): ConsolidatorPhase[] {\n const enabled = new Set(this.#config.phases);\n const planned: ConsolidatorPhase[] = [];\n if (reason.kind === 'turn' || reason.kind === 'idle' || reason.kind === 'event') {\n if (enabled.has('light')) planned.push('light');\n if (enabled.has('standard')) planned.push('standard');\n } else if (reason.kind === 'cron' || reason.kind === 'manual' || reason.kind === 'budget') {\n if (enabled.has('light')) planned.push('light');\n if (enabled.has('standard')) planned.push('standard');\n if (enabled.has('deep')) planned.push('deep');\n }\n return planned;\n }\n\n async #runPhase(\n phase: ConsolidatorPhase,\n scope: SessionScope,\n reason: ConsolidatorTriggerReason,\n ): Promise<PhaseOutcome | null> {\n const precheck = this.#budget.precheck(phase);\n if (!precheck.allowed && phase !== 'light') {\n return this.#emit(\n { ...skipOutcome(phase, precheck.reason ?? 'paused'), phase },\n scope,\n reason,\n );\n }\n const acquisition = await this.#lockManager.acquire(scope);\n if (acquisition.kind === 'deferred') {\n this.#deferredRuns += 1;\n const deferredRunId = this.#randomId();\n const deferredAt = this.#now();\n // Persist the deferral as a `consolidator_runs` row so the\n // counter survives process restart (DEC-150 — same code path\n // lib + server). Adapters that do not expose the consolidator\n // surface fall through to the in-memory adjustment counter.\n if (this.#consolidatorStore !== null) {\n try {\n await this.#consolidatorStore.recordRunStart({\n id: deferredRunId,\n scope,\n triggerKind: reason.kind,\n phase,\n startedAt: deferredAt,\n });\n await this.#consolidatorStore.recordRunFinish({\n id: deferredRunId,\n finishedAt: deferredAt,\n status: 'deferred',\n errorMessage: `lock held by ${acquisition.heldBy ?? 'unknown'}`,\n });\n } catch {\n this.#deferredRunsAdjustment += 1;\n }\n } else {\n this.#deferredRunsAdjustment += 1;\n }\n return this.#emit(\n {\n phase,\n status: 'deferred',\n factsCreated: 0,\n factsUpdated: 0,\n conflictsResolved: 0,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount: 0,\n emptyExtractions: 0,\n llmTokensUsed: 0,\n llmCostUsd: null,\n errorMessage: `lock held by ${acquisition.heldBy ?? 'unknown'}`,\n },\n scope,\n reason,\n );\n }\n const runId = acquisition.runId;\n const startedAt = this.#now();\n if (this.#consolidatorStore !== null) {\n await this.#consolidatorStore.recordRunStart({\n id: runId,\n scope,\n triggerKind: reason.kind,\n phase,\n startedAt,\n });\n }\n let outcome: PhaseOutcome;\n try {\n outcome = await this.#dispatch(phase, scope);\n } catch (err) {\n outcome = {\n phase,\n status: 'failed',\n factsCreated: 0,\n factsUpdated: 0,\n conflictsResolved: 0,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount: 0,\n emptyExtractions: 0,\n llmTokensUsed: 0,\n llmCostUsd: null,\n errorMessage: describeError(err),\n };\n // MCON-10: a failed DLQ *replay* must not enqueue a fresh DLQ row —\n // the original row already tracks the failure (drainDlq reschedules\n // or exhausts it); double-tracking multiplies rows per drain.\n const isDlqReplay =\n reason.kind === 'manual' &&\n typeof reason.value === 'string' &&\n reason.value.startsWith('dlq-replay:');\n if (this.#consolidatorStore !== null && !isDlqReplay) {\n await this.#consolidatorStore.enqueueFailedBatch({\n id: this.#randomId(),\n consolidatorRunId: runId,\n scope,\n // MCON-10: capture the slice that actually failed so replays\n // can be audited against it; the cursor may move past this\n // window before the replay fires.\n messageIds: this.#lastDispatchMessageIds,\n errorKind: classifyError(err),\n errorMessage: describeError(err),\n failedAt: this.#now(),\n nextRetryAt:\n this.#now() +\n nextBackoffMs({\n retryCount: 0,\n baseMs: this.#config.dlqBaseBackoffMs,\n maxMs: this.#config.dlqMaxBackoffMs,\n }),\n retryCount: 0,\n // MCON-10: persist the failed phase — replays must re-run the\n // SAME phase, not an inferred 'standard'.\n phase,\n });\n }\n }\n if (outcome.emptyExtractions > 0) {\n this.#emptyExtractions += outcome.emptyExtractions;\n }\n if (this.#consolidatorStore !== null) {\n await this.#consolidatorStore.recordRunFinish({\n id: runId,\n finishedAt: this.#now(),\n status: outcome.status,\n llmTokensUsed: outcome.llmTokensUsed,\n llmCostUsd: outcome.llmCostUsd,\n factsCreated: outcome.factsCreated,\n factsUpdated: outcome.factsUpdated,\n conflictsResolved: outcome.conflictsResolved,\n noiseFilteredCount: outcome.noiseFilteredCount,\n emptyExtractions: outcome.emptyExtractions,\n // MCON-17: the P1-2 / P1-1 counters were computed on the outcome\n // and then dropped — the run audit lost them.\n episodesFormed: outcome.episodesFormed,\n insightsCreated: outcome.insightsCreated,\n errorMessage: outcome.errorMessage,\n });\n await this.#consolidatorStore.upsertState(scope, {\n lastPhase: phase,\n lastCompletedAt: this.#now(),\n nextEligibleAt: this.#now() + this.#config.ceilings.cooldownMs,\n });\n }\n await this.#lockManager.release(scope, runId);\n return this.#emit(outcome, scope, reason);\n }\n\n async #dispatch(phase: ConsolidatorPhase, scope: SessionScope): Promise<PhaseOutcome> {\n // MCON-10: reset the failed-slice capture per dispatch; the standard\n // branch refills it from the batch it actually processes.\n this.#lastDispatchMessageIds = [];\n const state =\n this.#consolidatorStore !== null ? await this.#consolidatorStore.getState(scope) : null;\n const lastProcessedMessageId = state?.lastProcessedMessageId ?? null;\n if (phase === 'light') {\n const out = await runLightPhase({\n store: this.#store,\n consolidatorStore: this.#consolidatorStore,\n tracer: this.#tracer,\n scope,\n now: this.#now,\n decayTauDays: this.#config.decayTauDays,\n decayArchiveThreshold: this.#config.decayArchiveThreshold,\n decayCapacity: this.#config.decayCapacity,\n salienceWeights: this.#config.salienceWeights,\n noiseFilters: this.#config.noiseFilters as ReadonlyArray<NoiseFilterPreset>,\n maxBatchSize: this.#config.maxStandardBatchSize,\n lastProcessedMessageId,\n tier: this.#config.tier,\n });\n return out;\n }\n if (phase === 'standard') {\n const standardProvider = this.#cheapProvider ?? this.#provider;\n if (standardProvider === null) {\n throw new ProviderNotConfiguredError('standard');\n }\n const session = this.#store.session;\n const rawBatch =\n typeof session.listMessagesSince === 'function'\n ? await session.listMessagesSince(\n scope,\n lastProcessedMessageId,\n this.#config.maxStandardBatchSize,\n )\n : [];\n this.#lastDispatchMessageIds = rawBatch.map((r) => r.id);\n const out = await runStandardPhase({\n semantic: this.#semantic,\n episodic: this.#episodic,\n formEpisodes: this.#config.formEpisodes,\n importanceScoring: this.#config.importanceScoring,\n autoPromoteExtraction: this.#config.autoPromoteExtraction,\n contextualRetrieval: this.#config.contextualRetrieval,\n store: this.#store,\n consolidatorStore: this.#consolidatorStore,\n // MCON-7: the standard phase routes to the cheap-tier provider\n // when one is configured.\n provider: standardProvider,\n tracer: this.#tracer,\n scope,\n cheapModel: this.#config.cheapModel,\n noiseFilters: this.#config.noiseFilters as ReadonlyArray<NoiseFilterPreset>,\n maxBatchSize: this.#config.maxStandardBatchSize,\n lastProcessedMessageId,\n budget: this.#budget,\n tier: this.#config.tier === 'free' ? 'cheap' : this.#config.tier,\n now: this.#now,\n batch: rawBatch,\n });\n const cursor = tipMessageId(rawBatch);\n if (cursor !== null && this.#consolidatorStore !== null) {\n await this.#consolidatorStore.upsertState(scope, {\n lastProcessedMessageId: cursor,\n });\n }\n return out;\n }\n const deepProvider = this.#deepProvider ?? this.#provider;\n if (deepProvider === null) {\n throw new ProviderNotConfiguredError('deep');\n }\n const deepOut = await runDeepPhase({\n store: this.#store,\n consolidatorStore: this.#consolidatorStore,\n provider: deepProvider,\n tracer: this.#tracer,\n scope,\n deepModel: this.#config.deepModel,\n maxConflictsPerRun: this.#config.maxDeepConflictsPerRun,\n budget: this.#budget,\n tier:\n this.#config.tier === 'free' || this.#config.tier === 'cheap'\n ? 'standard'\n : this.#config.tier,\n now: this.#now,\n });\n // Reflection pass (P1-1) runs after the conflict drain, reusing the\n // deep run's lock + budget + audit window. Triple-gated: enabled by\n // config, an episodic tier present (importance source), and an\n // insight-capable storage adapter. The accumulated-importance\n // threshold is enforced inside the pass.\n const insightStore = this.#store.insights;\n if (this.#config.reflection && this.#episodic !== null && insightStore !== undefined) {\n // MCON-13: read the persisted reflection watermark so the gate only\n // accumulates importance from episodes newer than the last pass, and\n // persist the advanced value afterwards (a no-op when unchanged).\n const priorWatermark =\n this.#consolidatorStore !== null\n ? ((await this.#consolidatorStore.getState(scope))?.reflectionWatermark ?? null)\n : null;\n const reflection = await runReflectionPass({\n // MCON-7: reflection rides the deep-tier provider.\n provider: deepProvider,\n tracer: this.#tracer,\n scope,\n semantic: this.#semantic,\n episodic: this.#episodic,\n insights: insightStore,\n budget: this.#budget,\n importanceThreshold: this.#config.importanceThreshold,\n reflectionWatermark: priorWatermark,\n maxQuestions: this.#config.reflectionMaxQuestions,\n now: this.#now,\n });\n if (this.#consolidatorStore !== null && reflection.nextWatermark !== priorWatermark) {\n await this.#consolidatorStore.upsertState(scope, {\n reflectionWatermark: reflection.nextWatermark,\n });\n }\n return {\n ...deepOut,\n insightsCreated: reflection.insightsCreated,\n llmTokensUsed: deepOut.llmTokensUsed + reflection.tokens,\n llmCostUsd:\n deepOut.llmCostUsd === null && reflection.costUsd === 0\n ? null\n : (deepOut.llmCostUsd ?? 0) + reflection.costUsd,\n };\n }\n return deepOut;\n }\n\n #emit(\n outcome: PhaseOutcome,\n scope: SessionScope,\n reason: ConsolidatorTriggerReason,\n ): PhaseOutcome {\n for (const listener of this.#listeners) {\n try {\n listener({ ...outcome, scope, trigger: reason });\n } catch {\n // Listeners must never break the runtime.\n }\n }\n return outcome;\n }\n}\n\nfunction skipOutcome(\n phase: ConsolidatorPhase,\n reason: 'tokens-exceeded' | 'cost-exceeded' | 'paused' | 'cooldown',\n): PhaseOutcome {\n return {\n phase,\n status: 'deferred',\n factsCreated: 0,\n factsUpdated: 0,\n conflictsResolved: 0,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount: 0,\n emptyExtractions: 0,\n llmTokensUsed: 0,\n llmCostUsd: null,\n errorMessage: `phase skipped — ${reason}`,\n };\n}\n\n/** Resolve operator-supplied options into a fully-defaulted config. */\nfunction resolveConfig(opts: CreateConsolidatorOptions): ConsolidatorConfig {\n const tier: ConsolidatorTier = opts.tier ?? 'free';\n const preset = CONSOLIDATOR_TIER_DEFAULTS[tier];\n if (preset === undefined) {\n throw new Error(`[graphorin/memory] unknown consolidator tier '${tier}'`);\n }\n const ceilings = {\n ...preset.ceilings,\n ...(opts.ceilings ?? {}),\n };\n const phases = opts.phases ?? preset.phases;\n if (tier === 'custom') {\n const missing: string[] = [];\n if (ceilings.maxTokensPerDay <= 0) missing.push('ceilings.maxTokensPerDay');\n if (ceilings.maxCostPerDay <= 0) missing.push('ceilings.maxCostPerDay');\n if (phases.length === 0) missing.push('phases');\n if (missing.length > 0) {\n throw new CustomTierMisconfiguredError(missing);\n }\n }\n return Object.freeze({\n triggers: Object.freeze([...(opts.triggers ?? defaultTriggers())] as ConsolidatorTriggerSpec[]),\n tier,\n phases: Object.freeze([...phases]) as ReadonlyArray<ConsolidatorPhase>,\n ceilings,\n onExceed: opts.onExceed ?? preset.onExceed,\n cheapModel: opts.cheapModel ?? preset.cheapModel,\n deepModel: opts.deepModel ?? preset.deepModel,\n budgetResetSemantics: opts.budgetResetSemantics ?? 'utc',\n noiseFilters: Object.freeze([...(opts.noiseFilters ?? ['default'])]),\n lockWaitMs: opts.lockWaitMs ?? 30_000,\n decayTauDays: opts.decayTauDays ?? 7,\n decayArchiveThreshold: opts.decayArchiveThreshold ?? 0.05,\n decayCapacity: opts.decayCapacity ?? null,\n salienceWeights: opts.salienceWeights ?? DEFAULT_SALIENCE_WEIGHTS,\n maxStandardBatchSize: opts.maxStandardBatchSize ?? 50,\n maxDeepConflictsPerRun: opts.maxDeepConflictsPerRun ?? 20,\n dlqMaxRetries: opts.dlqMaxRetries ?? 5,\n dlqBaseBackoffMs: opts.dlqBaseBackoffMs ?? 60_000,\n dlqMaxBackoffMs: opts.dlqMaxBackoffMs ?? 60 * 60 * 1000,\n formEpisodes: opts.formEpisodes ?? preset.formEpisodes,\n importanceScoring: opts.importanceScoring ?? preset.importanceScoring,\n // MCON-2: opt-in only — fail-safe (quarantine) stays the default at every tier.\n autoPromoteExtraction: opts.autoPromoteExtraction ?? false,\n reflection: opts.reflection ?? preset.reflection,\n importanceThreshold: opts.importanceThreshold ?? preset.importanceThreshold,\n reflectionMaxQuestions: opts.reflectionMaxQuestions ?? preset.reflectionMaxQuestions,\n contextualRetrieval: opts.contextualRetrieval ?? preset.contextualRetrieval,\n });\n}\n\nfunction defaultTriggers(): ConsolidatorTriggerSpec[] {\n // `idle:5m` drives the light + standard phases between sessions; the daily\n // cron is what makes the **deep** phase reachable (it drains the deferred\n // conflict-check queue + runs reflection — `#planPhases` only schedules deep\n // for cron / manual / budget reasons). A `turn:N` default was dropped: the\n // scheduler cannot count user turns, so it was inert unless a consumer\n // emitted `trigger({ kind: 'turn' })` itself (MCON-4).\n return ['idle:5m', 'cron:0 4 * * *'];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"runtime.js","names":["#semantic","#episodic","#working","#store","#consolidatorStore","#tracer","#now","#randomId","#provider","#cheapProvider","#deepProvider","#priceUsage","#defaultScope","#listeners","#lockManager","#budget","#config","#running","#manuallyPaused","lastRunAt: string | undefined","lastPhase: ConsolidatorPhase | undefined","lastRuns: { -readonly [K in keyof ConsolidatorLastRuns]: ConsolidatorLastRuns[K] }","#deferredRunsAdjustment","#deferredRuns","#emptyExtractions","#expireStalePendingConflicts","#planPhases","#emit","last: PhaseOutcome | null","#runPhase","replayPhase: ConsolidatorPhase","lastError: unknown","planned: ConsolidatorPhase[]","outcome: PhaseOutcome","#dispatch","#lastDispatchMessageIds","out","out: PhaseOutcome","tier: ConsolidatorTier","missing: string[]"],"sources":["../../src/consolidator/runtime.ts"],"sourcesContent":["/**\n * Consolidator runtime - orchestrates the three phases, the lock,\n * the budget, the cursor, and the DLQ. The factory is the public\n * surface; the class lives below it as a private implementation\n * detail.\n *\n * The runtime is **library-mode** by default - it does not start a\n * background scheduler. Cron / idle triggers are realised by\n * registering them with a `@graphorin/triggers` scheduler via\n * `registerWithScheduler(...)` (the standalone server calls this in\n * `beforeStart` whenever a consolidator + a triggers scheduler are both\n * supplied). Turn / event triggers are consumer-emitted - the scheduler\n * cannot fire them on its own, so the consumer must call\n * `trigger({ kind: 'turn' | 'event' }, ...)` from its own loop. Manual\n * `trigger(...)` / `fireNow(...)` calls work in either mode.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, SessionScope, Tracer } from '@graphorin/core';\nimport { NOOP_TRACER } from '@graphorin/core';\nimport type {\n ConsolidatorMemoryStoreExt,\n MemoryStoreAdapter,\n} from '../internal/storage-adapter.js';\nimport type { EpisodicMemory } from '../tiers/episodic-memory.js';\nimport type { SemanticMemory } from '../tiers/semantic-memory.js';\nimport type { WorkingMemory } from '../tiers/working-memory.js';\nimport { BudgetTracker } from './budget.js';\nimport { DEFAULT_SALIENCE_WEIGHTS } from './decay.js';\nimport { classifyError, describeError, nextBackoffMs } from './dlq.js';\nimport { CustomTierMisconfiguredError, ProviderNotConfiguredError } from './errors.js';\nimport { tipMessageId } from './idempotency.js';\nimport { LockManager } from './lock.js';\nimport type { NoiseFilterPreset } from './noise-filter.js';\nimport { runDeepPhase } from './phases/deep.js';\nimport { runLearnedContextPass } from './phases/learned-context.js';\nimport { runLightPhase } from './phases/light.js';\nimport { runReflectionPass } from './phases/reflect.js';\nimport { runStandardPhase } from './phases/standard.js';\nimport {\n type RegisterTriggersResult,\n registerConsolidatorTriggers,\n type SchedulerLike,\n} from './scheduler.js';\nimport {\n CONSOLIDATOR_TIER_DEFAULTS,\n type ConsolidatorConfig,\n type ConsolidatorLastRuns,\n type ConsolidatorPhase,\n type ConsolidatorStatus,\n type ConsolidatorTier,\n type ConsolidatorTriggerReason,\n type ConsolidatorTriggerSpec,\n type CreateConsolidatorOptions,\n type PhaseListener,\n type PhaseOutcome,\n} from './types.js';\n\n/**\n * Age after which an unresolved CONFLICT-CHECK row is expired as\n * `'admit'` at tiers with no deep phase (memory-consolidation-04) -\n * the judge that would resolve it never runs there.\n */\nconst PENDING_CONFLICT_TTL_MS = 7 * 24 * 60 * 60 * 1000;\n\n/**\n * Consolidator runtime surface returned by {@link createConsolidator}.\n * Compatible with the placeholder shape so the facade can swap the\n * implementation without breaking consumers.\n *\n * @stable\n */\nexport interface Consolidator {\n start(): Promise<void>;\n stop(): Promise<void>;\n trigger(reason: ConsolidatorTriggerReason, scope: SessionScope): Promise<PhaseOutcome | null>;\n status(): Promise<ConsolidatorStatus>;\n /**\n * Manual trigger for the requested phase. Skips phase gating + the\n * idle/cron scheduler so admins can flush the queue on demand.\n */\n fireNow(phase: ConsolidatorPhase, scope?: SessionScope): Promise<PhaseOutcome | null>;\n /** Replace the active tier - recomputes ceilings + phase set. */\n setTier(tier: ConsolidatorTier): Promise<void>;\n /** Pause the consolidator until the next budget reset. */\n pause(): Promise<void>;\n /** Resume after `pause()`. */\n resume(): Promise<void>;\n /** Subscribe to phase-finished notifications. Returns an unsubscribe. */\n onPhaseFinished(listener: PhaseListener): () => void;\n /**\n * Record memory-pipeline LLM spend that happened OUTSIDE a phase run\n * (MCON-15 - e.g. workflow induction) so the daily ceilings cover it.\n * Counted under the deep-phase bucket.\n */\n recordExternalSpend(tokens: number, costUsd?: number): void;\n /** Active config - frozen snapshot. */\n config(): ConsolidatorConfig;\n /**\n * Register this consolidator's cron / idle triggers with a\n * `@graphorin/triggers` scheduler so they fire `trigger(...)`\n * automatically (the daemon ↔ triggers bridge - MCON-4). Uses the\n * configured `defaultScope`; throws if none was set. Turn / event\n * triggers are skipped (consumer-emitted). The standalone server calls\n * this in `beforeStart`.\n */\n registerWithScheduler(scheduler: SchedulerLike): Promise<RegisterTriggersResult>;\n /** True when `tier === 'free'`. */\n isFree(): boolean;\n /** Drain DLQ rows whose `nextRetryAt` <= now. */\n drainDlq(scope: SessionScope): Promise<number>;\n}\n\n/**\n * Build the runtime consolidator.\n *\n * @stable\n */\nexport function createConsolidator(opts: CreateConsolidatorOptions): Consolidator {\n return new ConsolidatorImpl(opts);\n}\n\nclass ConsolidatorImpl implements Consolidator {\n readonly #semantic: SemanticMemory;\n readonly #episodic: EpisodicMemory | null;\n readonly #working: WorkingMemory | null;\n readonly #store: MemoryStoreAdapter;\n readonly #consolidatorStore: ConsolidatorMemoryStoreExt | null;\n readonly #tracer: Tracer;\n readonly #now: () => number;\n readonly #randomId: () => string;\n readonly #provider: Provider | null;\n /** Per-phase provider overrides (MCON-7); fall back to `#provider`. */\n readonly #cheapProvider: Provider | null;\n readonly #deepProvider: Provider | null;\n /**\n * USD pricer for phase LLM usage (memory-consolidation-02). Without\n * it every phase's `priceUsage?.(...) ?? 0` evaluates to zero and the\n * `maxCostPerDay` ceiling can never trip.\n */\n readonly #priceUsage:\n | ((usage: { promptTokens: number; completionTokens: number }) => number)\n | null;\n readonly #defaultScope: SessionScope | null;\n readonly #listeners = new Set<PhaseListener>();\n readonly #lockManager: LockManager;\n readonly #budget: BudgetTracker;\n #config: ConsolidatorConfig;\n #running = false;\n #manuallyPaused = false;\n #deferredRuns = 0;\n /**\n * Message ids of the batch the most recent `#dispatch` operated on\n * (MCON-10). Captured so a thrown phase can enqueue its DLQ row with\n * the REAL failed slice instead of `[]` - replays must target the\n * window that failed, not whatever the cursor points at later.\n */\n #lastDispatchMessageIds: ReadonlyArray<string> = [];\n /**\n * Bumped when the runtime cannot persist a deferred run to the\n * audit log (e.g., adapter omits the consolidator surface). The\n * `status()` reader merges this with the persisted count so\n * library-mode callers still observe the deferral.\n */\n #deferredRunsAdjustment = 0;\n #emptyExtractions = 0;\n\n constructor(opts: CreateConsolidatorOptions) {\n this.#semantic = opts.semantic;\n this.#episodic = opts.episodic ?? null;\n this.#working = opts.working ?? null;\n this.#store = opts.store;\n this.#consolidatorStore = this.#store.consolidator ?? null;\n this.#tracer = opts.tracer ?? NOOP_TRACER;\n this.#now = opts.now ?? Date.now;\n this.#randomId =\n opts.randomId ??\n ((): string => {\n const a = Math.floor(Math.random() * 1e9).toString(36);\n const b = Math.floor(Math.random() * 1e9).toString(36);\n return `cr_${a}${b}`;\n });\n this.#provider = opts.provider ?? null;\n this.#cheapProvider = opts.cheapProvider ?? null;\n this.#deepProvider = opts.deepProvider ?? null;\n this.#priceUsage = opts.priceUsage ?? null;\n this.#defaultScope = opts.defaultScope ?? null;\n\n this.#config = resolveConfig(opts);\n this.#lockManager = new LockManager({\n store: this.#consolidatorStore,\n waitMs: this.#config.lockWaitMs,\n maxRunDurationMs: this.#config.ceilings.maxRunDurationMs,\n now: this.#now,\n randomId: this.#randomId,\n });\n this.#budget = new BudgetTracker({\n maxTokensPerDay: this.#config.ceilings.maxTokensPerDay,\n maxCostPerDay: this.#config.ceilings.maxCostPerDay,\n onExceed: this.#config.onExceed,\n resetSemantics: this.#config.budgetResetSemantics,\n now: this.#now,\n });\n }\n\n async start(): Promise<void> {\n this.#running = true;\n }\n\n async stop(): Promise<void> {\n this.#running = false;\n }\n\n async pause(): Promise<void> {\n this.#manuallyPaused = true;\n }\n\n async resume(): Promise<void> {\n this.#manuallyPaused = false;\n }\n\n config(): ConsolidatorConfig {\n return this.#config;\n }\n\n async registerWithScheduler(scheduler: SchedulerLike): Promise<RegisterTriggersResult> {\n if (this.#defaultScope === null) {\n throw new Error(\n '[graphorin/memory] Consolidator.registerWithScheduler requires a defaultScope. ' +\n 'Pass `defaultScope` to createConsolidator / createMemory, or call ' +\n 'registerConsolidatorTriggers(consolidator, scheduler, { scope }) directly.',\n );\n }\n return registerConsolidatorTriggers(this, scheduler, { scope: this.#defaultScope });\n }\n\n isFree(): boolean {\n return this.#config.tier === 'free';\n }\n\n onPhaseFinished(listener: PhaseListener): () => void {\n this.#listeners.add(listener);\n return () => {\n this.#listeners.delete(listener);\n };\n }\n\n recordExternalSpend(tokens: number, costUsd?: number): void {\n if (!Number.isFinite(tokens) || tokens <= 0) return;\n this.#budget.record({ phase: 'deep', tokens, costUsd: costUsd ?? 0 });\n }\n\n async setTier(tier: ConsolidatorTier): Promise<void> {\n const preset = CONSOLIDATOR_TIER_DEFAULTS[tier];\n if (preset === undefined) {\n throw new Error(`[graphorin/memory] unknown consolidator tier '${tier}'`);\n }\n if (tier === 'custom') {\n throw new Error(\n '[graphorin/memory] consolidator.setTier(\"custom\") requires explicit ceilings - re-create the consolidator with `tier: \"custom\"` + `ceilings`.',\n );\n }\n this.#config = Object.freeze({\n ...this.#config,\n tier,\n phases: preset.phases,\n ceilings: preset.ceilings,\n onExceed: preset.onExceed,\n formEpisodes: preset.formEpisodes,\n importanceScoring: preset.importanceScoring,\n reflection: preset.reflection,\n importanceThreshold: preset.importanceThreshold,\n reflectionMaxQuestions: preset.reflectionMaxQuestions,\n contextualRetrieval: preset.contextualRetrieval,\n });\n this.#budget.reconfigure({\n maxTokensPerDay: preset.ceilings.maxTokensPerDay,\n maxCostPerDay: preset.ceilings.maxCostPerDay,\n });\n }\n\n async status(): Promise<ConsolidatorStatus> {\n let lastRunAt: string | undefined;\n let lastPhase: ConsolidatorPhase | undefined;\n const lastRuns: { -readonly [K in keyof ConsolidatorLastRuns]: ConsolidatorLastRuns[K] } = {};\n let pendingConflicts = 0;\n let dlqSize = 0;\n let deferredFromStore = 0;\n if (this.#consolidatorStore !== null && this.#defaultScope !== null) {\n const state = await this.#consolidatorStore.getState(this.#defaultScope);\n if (state !== null) {\n if (state.lastCompletedAt !== null) {\n lastRunAt = new Date(state.lastCompletedAt).toISOString();\n }\n if (state.lastPhase !== null) {\n lastPhase = state.lastPhase;\n }\n }\n const failed = await this.#consolidatorStore.listFailedBatches(this.#defaultScope, 1000);\n dlqSize = failed.length;\n // Hydrate per-phase `lastRuns` and the persisted deferred_runs\n // counter from the run audit log so process restart preserves\n // both surfaces.\n const recent = await this.#consolidatorStore.listRecentRuns(this.#defaultScope, 500);\n for (const run of recent) {\n if (run.status === 'deferred') {\n deferredFromStore += 1;\n continue;\n }\n if (run.status !== 'completed' && run.status !== 'partial') continue;\n const stamp = new Date(run.finishedAt ?? run.startedAt).toISOString();\n if (run.phase === 'light' && lastRuns.light === undefined) lastRuns.light = stamp;\n else if (run.phase === 'standard' && lastRuns.standard === undefined)\n lastRuns.standard = stamp;\n else if (run.phase === 'deep' && lastRuns.deep === undefined) lastRuns.deep = stamp;\n }\n }\n if (this.#store.conflicts !== undefined && this.#defaultScope !== null) {\n const list = await this.#store.conflicts.listPending(this.#defaultScope, 1000);\n pendingConflicts = list.length;\n }\n const snapshot = this.#budget.snapshot();\n const persistedDeferred = deferredFromStore + this.#deferredRunsAdjustment;\n const totalDeferred = Math.max(persistedDeferred, this.#deferredRuns);\n const out: ConsolidatorStatus = {\n tier: this.#config.tier,\n triggers: this.#config.triggers,\n phases: this.#config.phases,\n running: this.#running,\n paused: this.#manuallyPaused || snapshot.paused,\n pendingConflicts,\n queueDepth: pendingConflicts,\n dlqSize,\n deferredRuns: totalDeferred,\n emptyExtractions: this.#emptyExtractions,\n lastRuns: Object.freeze(lastRuns),\n budget: {\n tokensUsedToday: snapshot.tokensUsedToday,\n costUsedToday: snapshot.costUsedToday,\n tokensRemaining: snapshot.tokensRemaining,\n costRemaining: snapshot.costRemaining,\n resetAt: snapshot.resetAt,\n },\n budgetRemaining: {\n tokens: snapshot.tokensRemaining,\n costUsd: snapshot.costRemaining,\n },\n ...(lastRunAt !== undefined ? { lastRunAt } : {}),\n ...(lastPhase !== undefined ? { lastPhase } : {}),\n };\n return out;\n }\n\n async trigger(\n reason: ConsolidatorTriggerReason,\n scope: SessionScope,\n ): Promise<PhaseOutcome | null> {\n if (!this.#running) return null;\n if (this.#manuallyPaused) return null;\n // memory-consolidation-03: `drainDlq` finally has a production\n // caller - failed batches whose backoff has elapsed replay here\n // instead of accumulating forever. Cheap when the queue is empty\n // (one SELECT); replays are backoff-gated by the store.\n await this.drainDlq(scope).catch(() => 0);\n // memory-consolidation-04: tiers without a deep phase never drain\n // the pending CONFLICT-CHECK queue - expire stale rows as 'admit'\n // so it cannot grow monotonically at the free/cheap defaults.\n await this.#expireStalePendingConflicts(scope).catch(() => {});\n const phases = this.#planPhases(reason);\n if (phases.length === 0) return null;\n // MCON-8: enforce the cooldown the runtime always PERSISTED but never\n // read - back-to-back triggers used to fire with zero quiet period.\n // Checked ONCE per trigger dispatch (phases within one dispatch chain\n // freely); manual fireNow(...) / DLQ replays bypass it.\n if (reason.kind !== 'manual' && this.#config.ceilings.cooldownMs > 0) {\n const state =\n this.#consolidatorStore !== null ? await this.#consolidatorStore.getState(scope) : null;\n const eligibleAt = state?.nextEligibleAt ?? null;\n const firstPhase = phases[0] ?? 'light';\n if (eligibleAt !== null && this.#now() < eligibleAt) {\n return this.#emit(\n { ...skipOutcome(firstPhase, 'cooldown'), phase: firstPhase },\n scope,\n reason,\n );\n }\n }\n let last: PhaseOutcome | null = null;\n for (const phase of phases) {\n last = await this.#runPhase(phase, scope, reason);\n if (last?.status === 'failed' || last?.status === 'deferred') break;\n }\n return last;\n }\n\n async fireNow(phase: ConsolidatorPhase, scope?: SessionScope): Promise<PhaseOutcome | null> {\n const target = scope ?? this.#defaultScope;\n if (target === null) {\n throw new Error(\n '[graphorin/memory] consolidator.fireNow requires a scope (default scope was not configured).',\n );\n }\n if (!this.#config.phases.includes(phase) && phase !== 'light') {\n // MCON-17: manual flushes bypass phase gating ON PURPOSE - but the\n // operator should know they are running a phase the tier disabled\n // (the old empty branch promised a warn and emitted nothing).\n process.stderr.write(\n `[graphorin/memory] consolidator.fireNow('${phase}') runs a phase not enabled for tier '${this.#config.tier}' - proceeding (manual flushes bypass phase gating).\\n`,\n );\n }\n return this.#runPhase(phase, target, { kind: 'manual', value: phase });\n }\n\n /**\n * memory-consolidation-04: at tiers with no deep phase the pending\n * CONFLICT-CHECK queue has no drain. Resolve rows older than 7 days\n * as `'admit'` (the safe direction - the candidate fact stays live;\n * only the never-coming judge call is skipped), bounded per sweep.\n */\n async #expireStalePendingConflicts(scope: SessionScope): Promise<void> {\n if (this.#config.phases.includes('deep')) return;\n const conflicts = this.#store.conflicts;\n if (conflicts === undefined) return;\n const cutoff = this.#now() - PENDING_CONFLICT_TTL_MS;\n const pending = await conflicts.listPending(scope, 200);\n for (const row of pending) {\n if (row.enqueuedAt <= cutoff) {\n await conflicts.markResolved(row.id, 'admit');\n }\n }\n }\n\n async drainDlq(scope: SessionScope): Promise<number> {\n const store = this.#consolidatorStore;\n if (store === null) return 0;\n const ready = await store.claimReadyBatches(scope, this.#now(), 50);\n let drained = 0;\n for (const row of ready) {\n // MCON-10: replay the phase that actually failed (persisted at\n // enqueue); legacy rows without one fall back to 'standard',\n // matching the old behaviour.\n const replayPhase: ConsolidatorPhase = row.phase ?? 'standard';\n let succeeded = false;\n let deferred = false;\n let lastError: unknown = null;\n try {\n // MCON-10: replays run through the FULL phase envelope - budget\n // precheck, scope lock, and recordRunStart/Finish - so they are\n // visible in `consolidator_runs` and cannot race a live run or\n // spend past a pause.\n const outcome = await this.#runPhase(replayPhase, scope, {\n kind: 'manual',\n value: `dlq-replay:${replayPhase}`,\n });\n if (outcome?.status === 'completed' || outcome?.status === 'partial') {\n succeeded = true;\n } else if (outcome?.status === 'deferred') {\n deferred = true;\n lastError = new Error(outcome.errorMessage ?? 'replay deferred');\n } else if (outcome?.errorMessage != null) {\n lastError = new Error(outcome.errorMessage);\n }\n } catch (err) {\n lastError = err;\n }\n if (succeeded) {\n await store.markBatchSucceeded(row.id);\n drained += 1;\n continue;\n }\n if (deferred) {\n // Lock contention / budget pause is transient - reschedule at the\n // SAME retry count so contention cannot exhaust the row.\n const delayMs = nextBackoffMs({\n retryCount: row.retryCount + 1,\n baseMs: this.#config.dlqBaseBackoffMs,\n maxMs: this.#config.dlqMaxBackoffMs,\n });\n await store.rescheduleBatch(row.id, row.retryCount, this.#now() + delayMs);\n continue;\n }\n const nextRetryCount = row.retryCount + 1;\n if (nextRetryCount >= this.#config.dlqMaxRetries) {\n await store.markBatchExhausted(row.id, describeError(lastError), nextRetryCount);\n } else {\n const delayMs = nextBackoffMs({\n retryCount: nextRetryCount,\n baseMs: this.#config.dlqBaseBackoffMs,\n maxMs: this.#config.dlqMaxBackoffMs,\n });\n await store.rescheduleBatch(row.id, nextRetryCount, this.#now() + delayMs);\n }\n }\n return drained;\n }\n\n // ---------------------------------------------------------------------------\n\n #planPhases(reason: ConsolidatorTriggerReason): ConsolidatorPhase[] {\n const enabled = new Set(this.#config.phases);\n const planned: ConsolidatorPhase[] = [];\n if (reason.kind === 'turn' || reason.kind === 'idle' || reason.kind === 'event') {\n if (enabled.has('light')) planned.push('light');\n if (enabled.has('standard')) planned.push('standard');\n } else if (reason.kind === 'cron' || reason.kind === 'manual' || reason.kind === 'budget') {\n if (enabled.has('light')) planned.push('light');\n if (enabled.has('standard')) planned.push('standard');\n if (enabled.has('deep')) planned.push('deep');\n }\n return planned;\n }\n\n async #runPhase(\n phase: ConsolidatorPhase,\n scope: SessionScope,\n reason: ConsolidatorTriggerReason,\n ): Promise<PhaseOutcome | null> {\n const precheck = this.#budget.precheck(phase);\n if (!precheck.allowed && phase !== 'light') {\n return this.#emit(\n { ...skipOutcome(phase, precheck.reason ?? 'paused'), phase },\n scope,\n reason,\n );\n }\n const acquisition = await this.#lockManager.acquire(scope);\n if (acquisition.kind === 'deferred') {\n this.#deferredRuns += 1;\n const deferredRunId = this.#randomId();\n const deferredAt = this.#now();\n // Persist the deferral as a `consolidator_runs` row so the\n // counter survives process restart (DEC-150 - same code path\n // lib + server). Adapters that do not expose the consolidator\n // surface fall through to the in-memory adjustment counter.\n if (this.#consolidatorStore !== null) {\n try {\n await this.#consolidatorStore.recordRunStart({\n id: deferredRunId,\n scope,\n triggerKind: reason.kind,\n phase,\n startedAt: deferredAt,\n });\n await this.#consolidatorStore.recordRunFinish({\n id: deferredRunId,\n finishedAt: deferredAt,\n status: 'deferred',\n errorMessage: `lock held by ${acquisition.heldBy ?? 'unknown'}`,\n });\n } catch {\n this.#deferredRunsAdjustment += 1;\n }\n } else {\n this.#deferredRunsAdjustment += 1;\n }\n return this.#emit(\n {\n phase,\n status: 'deferred',\n factsCreated: 0,\n factsUpdated: 0,\n conflictsResolved: 0,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount: 0,\n emptyExtractions: 0,\n llmTokensUsed: 0,\n llmCostUsd: null,\n errorMessage: `lock held by ${acquisition.heldBy ?? 'unknown'}`,\n },\n scope,\n reason,\n );\n }\n const runId = acquisition.runId;\n const startedAt = this.#now();\n if (this.#consolidatorStore !== null) {\n await this.#consolidatorStore.recordRunStart({\n id: runId,\n scope,\n triggerKind: reason.kind,\n phase,\n startedAt,\n });\n }\n let outcome: PhaseOutcome;\n try {\n outcome = await this.#dispatch(phase, scope);\n } catch (err) {\n outcome = {\n phase,\n status: 'failed',\n factsCreated: 0,\n factsUpdated: 0,\n conflictsResolved: 0,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount: 0,\n emptyExtractions: 0,\n llmTokensUsed: 0,\n llmCostUsd: null,\n errorMessage: describeError(err),\n };\n // MCON-10: a failed DLQ *replay* must not enqueue a fresh DLQ row -\n // the original row already tracks the failure (drainDlq reschedules\n // or exhausts it); double-tracking multiplies rows per drain.\n const isDlqReplay =\n reason.kind === 'manual' &&\n typeof reason.value === 'string' &&\n reason.value.startsWith('dlq-replay:');\n if (this.#consolidatorStore !== null && !isDlqReplay) {\n await this.#consolidatorStore.enqueueFailedBatch({\n id: this.#randomId(),\n consolidatorRunId: runId,\n scope,\n // MCON-10: capture the slice that actually failed so replays\n // can be audited against it; the cursor may move past this\n // window before the replay fires.\n messageIds: this.#lastDispatchMessageIds,\n errorKind: classifyError(err),\n errorMessage: describeError(err),\n failedAt: this.#now(),\n nextRetryAt:\n this.#now() +\n nextBackoffMs({\n retryCount: 0,\n baseMs: this.#config.dlqBaseBackoffMs,\n maxMs: this.#config.dlqMaxBackoffMs,\n }),\n retryCount: 0,\n // MCON-10: persist the failed phase - replays must re-run the\n // SAME phase, not an inferred 'standard'.\n phase,\n });\n }\n }\n if (outcome.emptyExtractions > 0) {\n this.#emptyExtractions += outcome.emptyExtractions;\n }\n if (this.#consolidatorStore !== null) {\n await this.#consolidatorStore.recordRunFinish({\n id: runId,\n finishedAt: this.#now(),\n status: outcome.status,\n llmTokensUsed: outcome.llmTokensUsed,\n llmCostUsd: outcome.llmCostUsd,\n factsCreated: outcome.factsCreated,\n factsUpdated: outcome.factsUpdated,\n conflictsResolved: outcome.conflictsResolved,\n noiseFilteredCount: outcome.noiseFilteredCount,\n emptyExtractions: outcome.emptyExtractions,\n // MCON-17: the P1-2 / P1-1 counters were computed on the outcome\n // and then dropped - the run audit lost them.\n episodesFormed: outcome.episodesFormed,\n insightsCreated: outcome.insightsCreated,\n errorMessage: outcome.errorMessage,\n });\n await this.#consolidatorStore.upsertState(scope, {\n lastPhase: phase,\n lastCompletedAt: this.#now(),\n nextEligibleAt: this.#now() + this.#config.ceilings.cooldownMs,\n });\n }\n await this.#lockManager.release(scope, runId);\n return this.#emit(outcome, scope, reason);\n }\n\n async #dispatch(phase: ConsolidatorPhase, scope: SessionScope): Promise<PhaseOutcome> {\n // MCON-10: reset the failed-slice capture per dispatch; the standard\n // branch refills it from the batch it actually processes.\n this.#lastDispatchMessageIds = [];\n const state =\n this.#consolidatorStore !== null ? await this.#consolidatorStore.getState(scope) : null;\n const lastProcessedMessageId = state?.lastProcessedMessageId ?? null;\n if (phase === 'light') {\n const out = await runLightPhase({\n store: this.#store,\n consolidatorStore: this.#consolidatorStore,\n tracer: this.#tracer,\n scope,\n now: this.#now,\n decayTauDays: this.#config.decayTauDays,\n decayArchiveThreshold: this.#config.decayArchiveThreshold,\n decayCapacity: this.#config.decayCapacity,\n salienceWeights: this.#config.salienceWeights,\n noiseFilters: this.#config.noiseFilters as ReadonlyArray<NoiseFilterPreset>,\n maxBatchSize: this.#config.maxStandardBatchSize,\n lastProcessedMessageId,\n tier: this.#config.tier,\n });\n return out;\n }\n if (phase === 'standard') {\n const standardProvider = this.#cheapProvider ?? this.#provider;\n if (standardProvider === null) {\n throw new ProviderNotConfiguredError('standard');\n }\n const session = this.#store.session;\n const rawBatch =\n typeof session.listMessagesSince === 'function'\n ? await session.listMessagesSince(\n scope,\n lastProcessedMessageId,\n this.#config.maxStandardBatchSize,\n )\n : [];\n this.#lastDispatchMessageIds = rawBatch.map((r) => r.id);\n const out = await runStandardPhase({\n semantic: this.#semantic,\n episodic: this.#episodic,\n formEpisodes: this.#config.formEpisodes,\n importanceScoring: this.#config.importanceScoring,\n autoPromoteExtraction: this.#config.autoPromoteExtraction,\n contextualRetrieval: this.#config.contextualRetrieval,\n store: this.#store,\n consolidatorStore: this.#consolidatorStore,\n // MCON-7: the standard phase routes to the cheap-tier provider\n // when one is configured.\n provider: standardProvider,\n tracer: this.#tracer,\n scope,\n cheapModel: this.#config.cheapModel,\n noiseFilters: this.#config.noiseFilters as ReadonlyArray<NoiseFilterPreset>,\n maxBatchSize: this.#config.maxStandardBatchSize,\n lastProcessedMessageId,\n budget: this.#budget,\n tier: this.#config.tier === 'free' ? 'cheap' : this.#config.tier,\n now: this.#now,\n batch: rawBatch,\n ...(this.#priceUsage !== null ? { priceUsage: this.#priceUsage } : {}),\n });\n const cursor = tipMessageId(rawBatch);\n if (cursor !== null && this.#consolidatorStore !== null) {\n await this.#consolidatorStore.upsertState(scope, {\n lastProcessedMessageId: cursor,\n });\n }\n return out;\n }\n const deepProvider = this.#deepProvider ?? this.#provider;\n if (deepProvider === null) {\n throw new ProviderNotConfiguredError('deep');\n }\n const deepOut = await runDeepPhase({\n store: this.#store,\n consolidatorStore: this.#consolidatorStore,\n provider: deepProvider,\n tracer: this.#tracer,\n scope,\n deepModel: this.#config.deepModel,\n maxConflictsPerRun: this.#config.maxDeepConflictsPerRun,\n budget: this.#budget,\n tier:\n this.#config.tier === 'free' || this.#config.tier === 'cheap'\n ? 'standard'\n : this.#config.tier,\n now: this.#now,\n ...(this.#priceUsage !== null ? { priceUsage: this.#priceUsage } : {}),\n });\n // Reflection pass (P1-1) runs after the conflict drain, reusing the\n // deep run's lock + budget + audit window. Triple-gated: enabled by\n // config, an episodic tier present (importance source), and an\n // insight-capable storage adapter. The accumulated-importance\n // threshold is enforced inside the pass.\n let out: PhaseOutcome = deepOut;\n const insightStore = this.#store.insights;\n if (this.#config.reflection && this.#episodic !== null && insightStore !== undefined) {\n // MCON-13: read the persisted reflection watermark so the gate only\n // accumulates importance from episodes newer than the last pass, and\n // persist the advanced value afterwards (a no-op when unchanged).\n const priorWatermark =\n this.#consolidatorStore !== null\n ? ((await this.#consolidatorStore.getState(scope))?.reflectionWatermark ?? null)\n : null;\n const reflection = await runReflectionPass({\n // MCON-7: reflection rides the deep-tier provider.\n provider: deepProvider,\n tracer: this.#tracer,\n scope,\n semantic: this.#semantic,\n episodic: this.#episodic,\n insights: insightStore,\n budget: this.#budget,\n importanceThreshold: this.#config.importanceThreshold,\n reflectionWatermark: priorWatermark,\n maxQuestions: this.#config.reflectionMaxQuestions,\n now: this.#now,\n ...(this.#priceUsage !== null ? { priceUsage: this.#priceUsage } : {}),\n });\n if (this.#consolidatorStore !== null && reflection.nextWatermark !== priorWatermark) {\n await this.#consolidatorStore.upsertState(scope, {\n reflectionWatermark: reflection.nextWatermark,\n });\n }\n out = {\n ...out,\n insightsCreated: reflection.insightsCreated,\n llmTokensUsed: out.llmTokensUsed + reflection.tokens,\n llmCostUsd:\n out.llmCostUsd === null && reflection.costUsd === 0\n ? null\n : (out.llmCostUsd ?? 0) + reflection.costUsd,\n };\n }\n // Learned-context pass (D3) runs last, folding the run's fresh\n // synthesis into the standing digest block. Double-gated: enabled by\n // config and a working-tier handle wired. Rides the deep provider +\n // budget like reflection.\n if (this.#config.learnedContext && this.#working !== null) {\n const learned = await runLearnedContextPass({\n provider: deepProvider,\n tracer: this.#tracer,\n scope,\n working: this.#working,\n episodic: this.#episodic,\n store: this.#store,\n budget: this.#budget,\n maxChars: this.#config.learnedContextMaxChars,\n now: this.#now,\n ...(this.#priceUsage !== null ? { priceUsage: this.#priceUsage } : {}),\n });\n out = {\n ...out,\n learnedContextUpdated: learned.updated,\n llmTokensUsed: out.llmTokensUsed + learned.tokens,\n llmCostUsd:\n out.llmCostUsd === null && learned.costUsd === 0\n ? null\n : (out.llmCostUsd ?? 0) + learned.costUsd,\n };\n }\n return out;\n }\n\n #emit(\n outcome: PhaseOutcome,\n scope: SessionScope,\n reason: ConsolidatorTriggerReason,\n ): PhaseOutcome {\n for (const listener of this.#listeners) {\n try {\n listener({ ...outcome, scope, trigger: reason });\n } catch {\n // Listeners must never break the runtime.\n }\n }\n return outcome;\n }\n}\n\nfunction skipOutcome(\n phase: ConsolidatorPhase,\n reason: 'tokens-exceeded' | 'cost-exceeded' | 'paused' | 'cooldown',\n): PhaseOutcome {\n return {\n phase,\n status: 'deferred',\n factsCreated: 0,\n factsUpdated: 0,\n conflictsResolved: 0,\n episodesFormed: 0,\n insightsCreated: 0,\n noiseFilteredCount: 0,\n emptyExtractions: 0,\n llmTokensUsed: 0,\n llmCostUsd: null,\n errorMessage: `phase skipped - ${reason}`,\n };\n}\n\n/** Resolve operator-supplied options into a fully-defaulted config. */\nfunction resolveConfig(opts: CreateConsolidatorOptions): ConsolidatorConfig {\n const tier: ConsolidatorTier = opts.tier ?? 'free';\n const preset = CONSOLIDATOR_TIER_DEFAULTS[tier];\n if (preset === undefined) {\n throw new Error(`[graphorin/memory] unknown consolidator tier '${tier}'`);\n }\n const ceilings = {\n ...preset.ceilings,\n ...(opts.ceilings ?? {}),\n };\n const phases = opts.phases ?? preset.phases;\n if (tier === 'custom') {\n const missing: string[] = [];\n if (ceilings.maxTokensPerDay <= 0) missing.push('ceilings.maxTokensPerDay');\n if (ceilings.maxCostPerDay <= 0) missing.push('ceilings.maxCostPerDay');\n if (phases.length === 0) missing.push('phases');\n if (missing.length > 0) {\n throw new CustomTierMisconfiguredError(missing);\n }\n }\n return Object.freeze({\n triggers: Object.freeze([...(opts.triggers ?? defaultTriggers())] as ConsolidatorTriggerSpec[]),\n tier,\n phases: Object.freeze([...phases]) as ReadonlyArray<ConsolidatorPhase>,\n ceilings,\n onExceed: opts.onExceed ?? preset.onExceed,\n cheapModel: opts.cheapModel ?? preset.cheapModel,\n deepModel: opts.deepModel ?? preset.deepModel,\n budgetResetSemantics: opts.budgetResetSemantics ?? 'utc',\n noiseFilters: Object.freeze([...(opts.noiseFilters ?? ['default'])]),\n lockWaitMs: opts.lockWaitMs ?? 30_000,\n decayTauDays: opts.decayTauDays ?? 7,\n decayArchiveThreshold: opts.decayArchiveThreshold ?? 0.05,\n decayCapacity: opts.decayCapacity ?? null,\n salienceWeights: opts.salienceWeights ?? DEFAULT_SALIENCE_WEIGHTS,\n maxStandardBatchSize: opts.maxStandardBatchSize ?? 50,\n maxDeepConflictsPerRun: opts.maxDeepConflictsPerRun ?? 20,\n dlqMaxRetries: opts.dlqMaxRetries ?? 5,\n dlqBaseBackoffMs: opts.dlqBaseBackoffMs ?? 60_000,\n dlqMaxBackoffMs: opts.dlqMaxBackoffMs ?? 60 * 60 * 1000,\n formEpisodes: opts.formEpisodes ?? preset.formEpisodes,\n importanceScoring: opts.importanceScoring ?? preset.importanceScoring,\n // MCON-2: opt-in only - fail-safe (quarantine) stays the default at every tier.\n autoPromoteExtraction: opts.autoPromoteExtraction ?? false,\n reflection: opts.reflection ?? preset.reflection,\n importanceThreshold: opts.importanceThreshold ?? preset.importanceThreshold,\n reflectionMaxQuestions: opts.reflectionMaxQuestions ?? preset.reflectionMaxQuestions,\n contextualRetrieval: opts.contextualRetrieval ?? preset.contextualRetrieval,\n learnedContext: opts.learnedContext ?? preset.learnedContext,\n learnedContextMaxChars: opts.learnedContextMaxChars ?? preset.learnedContextMaxChars,\n });\n}\n\nfunction defaultTriggers(): ConsolidatorTriggerSpec[] {\n // `idle:5m` drives the light + standard phases between sessions; the daily\n // cron is what makes the **deep** phase reachable (it drains the deferred\n // conflict-check queue + runs reflection - `#planPhases` only schedules deep\n // for cron / manual / budget reasons). A `turn:N` default was dropped: the\n // scheduler cannot count user turns, so it was inert unless a consumer\n // emitted `trigger({ kind: 'turn' })` itself (MCON-4).\n return ['idle:5m', 'cron:0 4 * * *'];\n}\n"],"mappings":"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"}
|
|
@@ -8,7 +8,7 @@ import { SessionScope } from "@graphorin/core";
|
|
|
8
8
|
/**
|
|
9
9
|
* Catch-up policy applied when a trigger missed one or more fires
|
|
10
10
|
* while the scheduler was offline. Mirrors
|
|
11
|
-
* `@graphorin/triggers`'s `CatchupPolicy`
|
|
11
|
+
* `@graphorin/triggers`'s `CatchupPolicy` - duplicated here so the
|
|
12
12
|
* memory package stays import-free at the type level.
|
|
13
13
|
*
|
|
14
14
|
* @stable
|
|
@@ -57,18 +57,18 @@ interface RegisterTriggersOptions {
|
|
|
57
57
|
readonly scope: SessionScope;
|
|
58
58
|
/**
|
|
59
59
|
* Catch-up policy applied to every registered trigger. Defaults
|
|
60
|
-
* to `'none'` per DEC-150
|
|
60
|
+
* to `'none'` per DEC-150 - safest for personal-assistant
|
|
61
61
|
* scenarios.
|
|
62
62
|
*/
|
|
63
63
|
readonly catchupPolicy?: ConsolidatorCatchupPolicy;
|
|
64
64
|
/** Suppress the per-process library-mode WARN. Defaults to `true`. */
|
|
65
65
|
readonly acknowledgeLibMode?: boolean;
|
|
66
|
-
/** Optional id prefix
|
|
66
|
+
/** Optional id prefix - useful when multiple scopes share a scheduler. */
|
|
67
67
|
readonly idPrefix?: string;
|
|
68
68
|
/** Optional tags forwarded to the scheduler. */
|
|
69
69
|
readonly tags?: ReadonlyArray<string>;
|
|
70
70
|
/**
|
|
71
|
-
* Filter
|
|
71
|
+
* Filter - only register the supplied subset of triggers. By
|
|
72
72
|
* default every parseable trigger declared on the consolidator is
|
|
73
73
|
* registered (turn / event triggers are skipped because the
|
|
74
74
|
* Scheduler has no way to fire them on its own).
|
|
@@ -98,7 +98,7 @@ interface RegisterTriggersResult {
|
|
|
98
98
|
* paths converge on the same handler.
|
|
99
99
|
*
|
|
100
100
|
* Turn triggers (`turn:N`) and event triggers (`event:NAME`) are
|
|
101
|
-
* skipped
|
|
101
|
+
* skipped - the scheduler cannot count user turns autonomously, and
|
|
102
102
|
* event triggers fire from the consumer's emit path. The runtime
|
|
103
103
|
* caller is responsible for those (e.g. invoking
|
|
104
104
|
* `consolidator.trigger(...)` from the agent loop).
|
|
@@ -8,7 +8,7 @@ import { parseTriggerSpec, reasonFromTrigger } from "./triggers.js";
|
|
|
8
8
|
* paths converge on the same handler.
|
|
9
9
|
*
|
|
10
10
|
* Turn triggers (`turn:N`) and event triggers (`event:NAME`) are
|
|
11
|
-
* skipped
|
|
11
|
+
* skipped - the scheduler cannot count user turns autonomously, and
|
|
12
12
|
* event triggers fire from the consumer's emit path. The runtime
|
|
13
13
|
* caller is responsible for those (e.g. invoking
|
|
14
14
|
* `consolidator.trigger(...)` from the agent loop).
|