@jungjaehoon/mama-core 1.4.2 → 1.5.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/db/migrations/026-create-canonical-entity-tables.sql +99 -0
- package/db/migrations/027-create-entity-review-support.sql +44 -0
- package/db/migrations/028-create-entity-audit-tables.sql +45 -0
- package/db/migrations/029-extend-entity-observations-observation-type.sql +60 -0
- package/db/migrations/030-case-first-memory-substrate.sql +901 -0
- package/db/migrations/031-wiki-page-index-provenance-columns.sql +2 -0
- package/dist/canonicalize.d.ts +38 -0
- package/dist/canonicalize.js +106 -0
- package/dist/cases/case-links.d.ts +116 -0
- package/dist/cases/case-links.js +794 -0
- package/dist/cases/composition-overrides.d.ts +90 -0
- package/dist/cases/composition-overrides.js +724 -0
- package/dist/cases/corrections.d.ts +156 -0
- package/dist/cases/corrections.js +1045 -0
- package/dist/cases/freshness.d.ts +57 -0
- package/dist/cases/freshness.js +283 -0
- package/dist/cases/live-state.d.ts +48 -0
- package/dist/cases/live-state.js +256 -0
- package/dist/cases/membership-explain.d.ts +56 -0
- package/dist/cases/membership-explain.js +140 -0
- package/dist/cases/membership-matcher.d.ts +23 -0
- package/dist/cases/membership-matcher.js +193 -0
- package/dist/cases/merge-split.d.ts +61 -0
- package/dist/cases/merge-split.js +368 -0
- package/dist/cases/role-inference.d.ts +10 -0
- package/dist/cases/role-inference.js +85 -0
- package/dist/cases/search-rollup.d.ts +23 -0
- package/dist/cases/search-rollup.js +225 -0
- package/dist/cases/sqlite-transaction.d.ts +10 -0
- package/dist/cases/sqlite-transaction.js +62 -0
- package/dist/cases/store.d.ts +74 -0
- package/dist/cases/store.js +816 -0
- package/dist/cases/target-ref.d.ts +25 -0
- package/dist/cases/target-ref.js +22 -0
- package/dist/cases/timeline-range.d.ts +6 -0
- package/dist/cases/timeline-range.js +322 -0
- package/dist/cases/tombstone-sweeper.d.ts +13 -0
- package/dist/cases/tombstone-sweeper.js +128 -0
- package/dist/cases/types.d.ts +244 -0
- package/dist/cases/types.js +47 -0
- package/dist/cases/wiki-page-index.d.ts +50 -0
- package/dist/cases/wiki-page-index.js +350 -0
- package/dist/connectors/event-index.d.ts +44 -0
- package/dist/connectors/event-index.js +366 -0
- package/dist/connectors/types.d.ts +69 -0
- package/dist/connectors/types.js +3 -0
- package/dist/db-manager.d.ts +4 -0
- package/dist/db-manager.js +10 -3
- package/dist/decision-formatter.d.ts +2 -0
- package/dist/decision-formatter.js +11 -8
- package/dist/embedding-server/mobile/daemon.d.ts +1 -0
- package/dist/embedding-server/mobile/daemon.js +11 -1
- package/dist/entities/audit-metrics.d.ts +73 -0
- package/dist/entities/audit-metrics.js +128 -0
- package/dist/entities/candidate-generator.d.ts +9 -0
- package/dist/entities/candidate-generator.js +436 -0
- package/dist/entities/entity-impact.d.ts +41 -0
- package/dist/entities/entity-impact.js +140 -0
- package/dist/entities/entity-linked-decision-counts.d.ts +2 -0
- package/dist/entities/entity-linked-decision-counts.js +29 -0
- package/dist/entities/entity-list.d.ts +23 -0
- package/dist/entities/entity-list.js +99 -0
- package/dist/entities/entity-orphan-list.d.ts +26 -0
- package/dist/entities/entity-orphan-list.js +91 -0
- package/dist/entities/entity-search.d.ts +21 -0
- package/dist/entities/entity-search.js +150 -0
- package/dist/entities/errors.d.ts +65 -0
- package/dist/entities/errors.js +124 -0
- package/dist/entities/exact-merge-backfill.d.ts +11 -0
- package/dist/entities/exact-merge-backfill.js +161 -0
- package/dist/entities/lineage-backfill.d.ts +12 -0
- package/dist/entities/lineage-backfill.js +101 -0
- package/dist/entities/lineage-store.d.ts +51 -0
- package/dist/entities/lineage-store.js +308 -0
- package/dist/entities/normalization.d.ts +14 -0
- package/dist/entities/normalization.js +63 -0
- package/dist/entities/policy-store.d.ts +17 -0
- package/dist/entities/policy-store.js +263 -0
- package/dist/entities/policy-types.d.ts +72 -0
- package/dist/entities/policy-types.js +59 -0
- package/dist/entities/projection.d.ts +8 -0
- package/dist/entities/projection.js +82 -0
- package/dist/entities/provenance-query.d.ts +33 -0
- package/dist/entities/provenance-query.js +167 -0
- package/dist/entities/read-identity.d.ts +31 -0
- package/dist/entities/read-identity.js +264 -0
- package/dist/entities/recall-bridge.d.ts +5 -0
- package/dist/entities/recall-bridge.js +171 -0
- package/dist/entities/resolution-engine.d.ts +15 -0
- package/dist/entities/resolution-engine.js +69 -0
- package/dist/entities/rollback-preview.d.ts +39 -0
- package/dist/entities/rollback-preview.js +235 -0
- package/dist/entities/source-locator.d.ts +3 -0
- package/dist/entities/source-locator.js +32 -0
- package/dist/entities/store.d.ts +75 -0
- package/dist/entities/store.js +465 -0
- package/dist/entities/types.d.ts +162 -0
- package/dist/entities/types.js +30 -0
- package/dist/index.d.ts +47 -2
- package/dist/index.js +66 -25
- package/dist/mama-api.d.ts +22 -1
- package/dist/mama-api.js +272 -64
- package/dist/memory/api.d.ts +28 -1
- package/dist/memory/api.js +293 -31
- package/dist/memory/types.d.ts +11 -2
- package/dist/search/feedback-store.d.ts +59 -0
- package/dist/search/feedback-store.js +211 -0
- package/dist/search/question-type.d.ts +5 -0
- package/dist/search/question-type.js +41 -0
- package/dist/search/ranker-features.d.ts +23 -0
- package/dist/search/ranker-features.js +104 -0
- package/dist/search/ranker-rescore.d.ts +31 -0
- package/dist/search/ranker-rescore.js +226 -0
- package/dist/search/ranker-trainer.d.ts +94 -0
- package/dist/search/ranker-trainer.js +860 -0
- package/dist/test-utils.d.ts +14 -0
- package/dist/test-utils.js +52 -0
- package/package.json +21 -3
package/dist/mama-api.js
CHANGED
|
@@ -80,6 +80,9 @@ const finding_store_js_1 = require("./memory/finding-store.js");
|
|
|
80
80
|
Object.defineProperty(exports, "listOpenAuditFindings", { enumerable: true, get: function () { return finding_store_js_1.listOpenAuditFindings; } });
|
|
81
81
|
const event_store_js_1 = require("./memory/event-store.js");
|
|
82
82
|
Object.defineProperty(exports, "listRecentMemoryEvents", { enumerable: true, get: function () { return event_store_js_1.listRecentMemoryEvents; } });
|
|
83
|
+
const search_rollup_js_1 = require("./cases/search-rollup.js");
|
|
84
|
+
const ranker_rescore_js_1 = require("./search/ranker-rescore.js");
|
|
85
|
+
const ranker_features_js_1 = require("./search/ranker-features.js");
|
|
83
86
|
// Session-level warning cooldown cache (Story 1.1, 1.2)
|
|
84
87
|
// Prevents spam by tracking warned topics per session
|
|
85
88
|
const warnedTopicsCache = new Map();
|
|
@@ -111,7 +114,7 @@ const WARNING_COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
|
|
|
111
114
|
* outcome: 'success'
|
|
112
115
|
* });
|
|
113
116
|
*/
|
|
114
|
-
async function save({ topic, decision, reasoning, confidence = 0.5, type = 'user_decision', outcome = 'pending', failure_reason = null, limitation = null, trust_context: _trust_context = null, is_static, scopes: inputScopes, event_date, }) {
|
|
117
|
+
async function save({ topic, decision, reasoning, confidence = 0.5, type = 'user_decision', outcome = 'pending', failure_reason = null, limitation = null, trust_context: _trust_context = null, is_static, scopes: inputScopes, event_date, timelineEvent, }) {
|
|
115
118
|
// Validate required fields
|
|
116
119
|
if (!topic || typeof topic !== 'string') {
|
|
117
120
|
throw new Error('mama.save() requires topic (string)');
|
|
@@ -161,7 +164,7 @@ async function save({ topic, decision, reasoning, confidence = 0.5, type = 'user
|
|
|
161
164
|
// Future: Will use decision_type column for proper distinction
|
|
162
165
|
const _userInvolvement = type === 'user_decision' ? 'approved' : null;
|
|
163
166
|
(0, progress_indicator_js_1.logProgress)(`Saving decision: ${topic.substring(0, 30)}...`);
|
|
164
|
-
const { id: decisionId } = await (0, api_js_1.saveMemory)({
|
|
167
|
+
const { id: decisionId, timeline_event_id: timelineEventId, timeline_event_ids: timelineEventIds, } = await (0, api_js_1.saveMemory)({
|
|
165
168
|
topic,
|
|
166
169
|
kind: is_static === 1 ? 'preference' : 'decision',
|
|
167
170
|
summary: decision,
|
|
@@ -173,6 +176,7 @@ async function save({ topic, decision, reasoning, confidence = 0.5, type = 'user
|
|
|
173
176
|
source_type: 'legacy_save',
|
|
174
177
|
},
|
|
175
178
|
eventDate: event_date ?? undefined,
|
|
179
|
+
timelineEvent,
|
|
176
180
|
});
|
|
177
181
|
(0, progress_indicator_js_1.logComplete)(`Decision saved: ${decisionId.substring(0, 20)}...`);
|
|
178
182
|
// Update user_involvement, outcome, failure_reason, limitation
|
|
@@ -255,13 +259,17 @@ async function save({ topic, decision, reasoning, confidence = 0.5, type = 'user
|
|
|
255
259
|
if (searchResults && typeof searchResults === 'object' && 'results' in searchResults) {
|
|
256
260
|
// Filter out the decision we just saved
|
|
257
261
|
similar_decisions = searchResults.results
|
|
262
|
+
.filter((d) => d.source_type === 'decision')
|
|
258
263
|
.filter((d) => d.id !== decisionId)
|
|
259
264
|
.map((d) => ({
|
|
260
265
|
id: d.id,
|
|
261
266
|
topic: d.topic,
|
|
262
267
|
decision: d.decision,
|
|
263
268
|
similarity: d.similarity,
|
|
269
|
+
retrieval_score: d.retrieval_score ?? null,
|
|
264
270
|
created_at: d.created_at,
|
|
271
|
+
event_date: d.event_date ?? null,
|
|
272
|
+
event_datetime: d.event_datetime ?? null,
|
|
265
273
|
}));
|
|
266
274
|
if (similar_decisions.length > 0) {
|
|
267
275
|
(0, progress_indicator_js_1.logComplete)(`Found ${similar_decisions.length} related decision(s)`);
|
|
@@ -308,6 +316,9 @@ async function save({ topic, decision, reasoning, confidence = 0.5, type = 'user
|
|
|
308
316
|
return {
|
|
309
317
|
success: true,
|
|
310
318
|
id: decisionId,
|
|
319
|
+
saved_decision_id: decisionId,
|
|
320
|
+
timeline_event_id: timelineEventId ?? null,
|
|
321
|
+
timeline_event_ids: timelineEventIds ?? [],
|
|
311
322
|
...(similar_decisions.length > 0 && { similar_decisions }),
|
|
312
323
|
...(warning && { warning }),
|
|
313
324
|
...(collaboration_hint && { collaboration_hint }),
|
|
@@ -698,60 +709,260 @@ function applyRecencyBoost(results, options = {}) {
|
|
|
698
709
|
})
|
|
699
710
|
.sort((a, b) => (b.final_score ?? 0) - (a.final_score ?? 0));
|
|
700
711
|
}
|
|
712
|
+
/** Phase 3 Task 33: learned-ranker meta attached to mama.suggest response. */
|
|
713
|
+
function buildRankerMeta(applied, modelId, skippedReason) {
|
|
714
|
+
const meta = {
|
|
715
|
+
model_id: modelId,
|
|
716
|
+
feature_set_version: ranker_features_js_1.SEARCH_RANKER_FEATURE_SET_VERSION,
|
|
717
|
+
applied,
|
|
718
|
+
mode: 'offline',
|
|
719
|
+
};
|
|
720
|
+
if (skippedReason) {
|
|
721
|
+
meta.skipped_reason = skippedReason;
|
|
722
|
+
}
|
|
723
|
+
return meta;
|
|
724
|
+
}
|
|
725
|
+
function resultRecord(result) {
|
|
726
|
+
if (typeof result.record === 'object' &&
|
|
727
|
+
result.record !== null &&
|
|
728
|
+
!Array.isArray(result.record)) {
|
|
729
|
+
return result.record;
|
|
730
|
+
}
|
|
731
|
+
return {};
|
|
732
|
+
}
|
|
733
|
+
function stringOrNull(value) {
|
|
734
|
+
if (value === null || value === undefined) {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
737
|
+
return String(value);
|
|
738
|
+
}
|
|
739
|
+
function numberOrNull(value) {
|
|
740
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
741
|
+
return null;
|
|
742
|
+
}
|
|
743
|
+
return value;
|
|
744
|
+
}
|
|
745
|
+
function confidenceValue(record, fallback) {
|
|
746
|
+
const numeric = numberOrNull(record.confidence);
|
|
747
|
+
if (numeric !== null) {
|
|
748
|
+
return numeric;
|
|
749
|
+
}
|
|
750
|
+
switch (record.confidence) {
|
|
751
|
+
case 'high':
|
|
752
|
+
return 0.9;
|
|
753
|
+
case 'medium':
|
|
754
|
+
return 0.6;
|
|
755
|
+
case 'low':
|
|
756
|
+
return 0.3;
|
|
757
|
+
default:
|
|
758
|
+
return fallback;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
function mapRolledUpResult(result) {
|
|
762
|
+
const record = resultRecord(result);
|
|
763
|
+
const topic = stringOrNull(record.topic ?? record.title) ?? result.source_id;
|
|
764
|
+
// For wiki_page leaves, prefer the markdown body (`content`) in `decision` so
|
|
765
|
+
// downstream consumers see the meaningful body rather than the short title.
|
|
766
|
+
// Decision/checkpoint records use their own fields (summary/decision).
|
|
767
|
+
const isWikiPageLeaf = result.source_type === 'wiki_page';
|
|
768
|
+
const decision = isWikiPageLeaf
|
|
769
|
+
? (stringOrNull(record.content ?? record.summary ?? record.decision ?? record.title) ??
|
|
770
|
+
result.source_id)
|
|
771
|
+
: (stringOrNull(record.summary ?? record.decision ?? record.title ?? record.content) ??
|
|
772
|
+
result.source_id);
|
|
773
|
+
const reasoning = stringOrNull(record.details ?? record.reasoning ?? record.status_reason ?? record.content) ??
|
|
774
|
+
'';
|
|
775
|
+
return {
|
|
776
|
+
id: result.source_id,
|
|
777
|
+
topic,
|
|
778
|
+
decision,
|
|
779
|
+
reasoning,
|
|
780
|
+
confidence: confidenceValue(record, result.score),
|
|
781
|
+
// Back-compat with pre-rollup consumers (swarm-mama-adapter tests,
|
|
782
|
+
// older callers): similarity mirrors the retrieval score when we don't
|
|
783
|
+
// have a separate similarity measure. retrieval_score remains the
|
|
784
|
+
// authoritative field for Phase 3 code paths.
|
|
785
|
+
similarity: result.score,
|
|
786
|
+
retrieval_score: result.score,
|
|
787
|
+
created_at: record.created_at ?? null,
|
|
788
|
+
event_date: record.event_date ?? null,
|
|
789
|
+
event_datetime: record.event_datetime ?? null,
|
|
790
|
+
graph_source: 'primary',
|
|
791
|
+
graph_rank: 1,
|
|
792
|
+
related_to: null,
|
|
793
|
+
edge_reason: null,
|
|
794
|
+
case_id: result.case_id,
|
|
795
|
+
source_type: result.source_type,
|
|
796
|
+
contributing_leaves: result.contributing_leaves ?? null,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
701
799
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
702
800
|
async function suggest(userQuestion, options = {}) {
|
|
703
801
|
if (!userQuestion || typeof userQuestion !== 'string') {
|
|
704
802
|
throw new Error('mama.suggest() requires userQuestion (string)');
|
|
705
803
|
}
|
|
706
|
-
const { format = 'json', limit = 5, threshold, useReranking = false,
|
|
804
|
+
const { format = 'json', limit = 5, threshold, useReranking = false, rerankWithLearned = false,
|
|
707
805
|
// Recency boosting parameters (Gaussian Decay - Elasticsearch style)
|
|
708
806
|
recencyWeight = 0.3, // 0-1: How much to weight recency (0.3 = 70% semantic, 30% recency)
|
|
709
807
|
recencyScale = 7, // Days until recency score drops to 50%
|
|
710
808
|
recencyDecay = 0.5, // Score at scale point (0.5 = 50%)
|
|
711
809
|
disableRecency = false, // Set true to disable recency boosting entirely
|
|
712
810
|
} = options;
|
|
811
|
+
const rerankPoolLimit = rerankWithLearned ? Math.max(limit * 4, limit + 5) : limit;
|
|
713
812
|
try {
|
|
714
813
|
const bundle = await (0, api_js_1.recallMemory)(userQuestion, {
|
|
715
814
|
includeProfile: false,
|
|
716
815
|
topicPrefix: options.topicPrefix,
|
|
816
|
+
limit: rerankPoolLimit,
|
|
717
817
|
...(options.scopes && { scopes: options.scopes }),
|
|
718
818
|
});
|
|
819
|
+
const fusedHits = bundle.fused_hits ?? [];
|
|
820
|
+
const rolledUp = fusedHits.length > 0 ? (0, search_rollup_js_1.rollUpSearchHits)({ fusedHits, adapter: (0, memory_store_js_1.getAdapter)() }) : [];
|
|
821
|
+
// Phase 3 Task 33: compute base ranker meta once so every return path
|
|
822
|
+
// (rolledUp, memories fallback, vector-search fallback) can attach it.
|
|
823
|
+
// rerankWithLearned-driven rescoring still only applies to result arrays
|
|
824
|
+
// that match the ranker's expected shape (id + source_type + case_id).
|
|
825
|
+
const baseRankerMeta = useReranking
|
|
826
|
+
? buildRankerMeta(false, null, 'llm_reranking_requested')
|
|
827
|
+
: rerankWithLearned
|
|
828
|
+
? null // marker: rescoring requested, actual meta set per-path after rescore
|
|
829
|
+
: buildRankerMeta(false, null, 'feature_disabled');
|
|
830
|
+
const applyLearnedRanker = (results) => {
|
|
831
|
+
if (baseRankerMeta !== null) {
|
|
832
|
+
return { results, meta: baseRankerMeta };
|
|
833
|
+
}
|
|
834
|
+
// Phase 3 Task 33: the caller opted in via rerankWithLearned, but the
|
|
835
|
+
// runtime `search_ranker_enabled` gate still has final say. This lets
|
|
836
|
+
// operators disable the learned ranker globally during rollback without
|
|
837
|
+
// touching any caller code.
|
|
838
|
+
let runtimeEnabled = true;
|
|
839
|
+
try {
|
|
840
|
+
runtimeEnabled = (0, ranker_rescore_js_1.isSearchRankerEnabled)((0, memory_store_js_1.getAdapter)());
|
|
841
|
+
}
|
|
842
|
+
catch (err) {
|
|
843
|
+
(0, debug_logger_js_1.warn)(`[mama.suggest] isSearchRankerEnabled check failed: ${String(err)}`);
|
|
844
|
+
}
|
|
845
|
+
if (!runtimeEnabled) {
|
|
846
|
+
return { results, meta: buildRankerMeta(false, null, 'feature_disabled') };
|
|
847
|
+
}
|
|
848
|
+
try {
|
|
849
|
+
const rescored = (0, ranker_rescore_js_1.rescoreSearchResults)((0, memory_store_js_1.getAdapter)(), {
|
|
850
|
+
query: userQuestion,
|
|
851
|
+
results,
|
|
852
|
+
});
|
|
853
|
+
return {
|
|
854
|
+
results: rescored.results,
|
|
855
|
+
meta: buildRankerMeta(rescored.skipped_reason === undefined, rescored.model_id, rescored.skipped_reason),
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
catch (err) {
|
|
859
|
+
(0, debug_logger_js_1.warn)(`[mama.suggest] learned-ranker rescore failed: ${String(err)}`);
|
|
860
|
+
return { results, meta: buildRankerMeta(false, null, 'rescore_error') };
|
|
861
|
+
}
|
|
862
|
+
};
|
|
863
|
+
const summarizeGraphExpansion = (rows) => {
|
|
864
|
+
const sources = {
|
|
865
|
+
primary: 0,
|
|
866
|
+
supersedes_chain: 0,
|
|
867
|
+
refines: 0,
|
|
868
|
+
refined_by: 0,
|
|
869
|
+
contradicts: 0,
|
|
870
|
+
};
|
|
871
|
+
let expandedCount = 0;
|
|
872
|
+
for (const row of rows) {
|
|
873
|
+
const graphSource = row.graph_source ?? 'primary';
|
|
874
|
+
if (graphSource === 'primary') {
|
|
875
|
+
sources.primary += 1;
|
|
876
|
+
continue;
|
|
877
|
+
}
|
|
878
|
+
expandedCount += 1;
|
|
879
|
+
if (graphSource in sources) {
|
|
880
|
+
const key = graphSource;
|
|
881
|
+
sources[key] += 1;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
total_results: rows.length,
|
|
886
|
+
primary_count: sources.primary,
|
|
887
|
+
expanded_count: expandedCount,
|
|
888
|
+
sources,
|
|
889
|
+
};
|
|
890
|
+
};
|
|
891
|
+
if (rolledUp.length > 0) {
|
|
892
|
+
const filteredResults = rolledUp.slice(0, rerankPoolLimit);
|
|
893
|
+
const { results: mappedResults, meta: rankerMeta } = applyLearnedRanker(filteredResults.map(mapRolledUpResult));
|
|
894
|
+
const limitedResults = mappedResults.slice(0, limit);
|
|
895
|
+
if (format === 'markdown') {
|
|
896
|
+
const context = limitedResults
|
|
897
|
+
.map((result, index) => `${index + 1}. [${result.topic}] ${result.decision}\n ${result.reasoning}`)
|
|
898
|
+
.join('\n');
|
|
899
|
+
return `🔍 Search method: memory_v2\n${context}`;
|
|
900
|
+
}
|
|
901
|
+
return {
|
|
902
|
+
query: userQuestion,
|
|
903
|
+
results: limitedResults,
|
|
904
|
+
meta: {
|
|
905
|
+
count: limitedResults.length,
|
|
906
|
+
search_method: 'memory_v2',
|
|
907
|
+
threshold: threshold || 'adaptive',
|
|
908
|
+
recency_boost: disableRecency
|
|
909
|
+
? null
|
|
910
|
+
: {
|
|
911
|
+
weight: recencyWeight,
|
|
912
|
+
scale: recencyScale,
|
|
913
|
+
decay: recencyDecay,
|
|
914
|
+
},
|
|
915
|
+
graph_expansion: summarizeGraphExpansion(limitedResults),
|
|
916
|
+
ranker: rankerMeta,
|
|
917
|
+
},
|
|
918
|
+
};
|
|
919
|
+
}
|
|
719
920
|
if (bundle.memories.length > 0) {
|
|
720
921
|
// recallMemory uses RRF fusion — confidence is overwritten with the normalized
|
|
721
922
|
// retrieval score (0-1 range, where 1.0 = best match in this result set).
|
|
722
923
|
// The original stored confidence is lost after RRF normalization.
|
|
723
924
|
// We capture the retrieval score separately so `similarity` reflects search
|
|
724
925
|
// relevance while `confidence` is passed through as-is from the bundle.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
926
|
+
const filteredMemories = bundle.memories.slice(0, rerankPoolLimit);
|
|
927
|
+
const baseRows = filteredMemories.map((memory) => ({
|
|
928
|
+
id: memory.id,
|
|
929
|
+
topic: memory.topic,
|
|
930
|
+
decision: memory.summary,
|
|
931
|
+
reasoning: memory.details,
|
|
932
|
+
confidence: memory.confidence,
|
|
933
|
+
// recallMemory currently normalizes fused retrieval rank into `confidence`.
|
|
934
|
+
// Keep that value visible as retrieval_score, but do not pretend it is
|
|
935
|
+
// semantic similarity; save-time warning logic keys off `similarity`.
|
|
936
|
+
similarity: null,
|
|
937
|
+
retrieval_score: memory.confidence ?? null,
|
|
938
|
+
final_score: memory.confidence ?? null,
|
|
939
|
+
created_at: memory.created_at,
|
|
940
|
+
event_date: memory.event_date ?? null,
|
|
941
|
+
event_datetime: memory.event_datetime ?? null,
|
|
942
|
+
graph_source: 'primary',
|
|
943
|
+
graph_rank: 1,
|
|
944
|
+
related_to: null,
|
|
945
|
+
edge_reason: null,
|
|
946
|
+
case_id: null,
|
|
947
|
+
source_type: memory.kind ??
|
|
948
|
+
memory.source?.source_type ??
|
|
949
|
+
memory.source_type ??
|
|
950
|
+
memory.type ??
|
|
951
|
+
'decision',
|
|
952
|
+
}));
|
|
953
|
+
const { results: rankedRows, meta: rankerMeta } = applyLearnedRanker(baseRows);
|
|
954
|
+
const limitedRows = rankedRows.slice(0, limit);
|
|
728
955
|
if (format === 'markdown') {
|
|
729
|
-
const context =
|
|
730
|
-
.map((
|
|
956
|
+
const context = limitedRows
|
|
957
|
+
.map((row, index) => `${index + 1}. [${row.topic}] ${row.decision}\n ${row.reasoning}`)
|
|
731
958
|
.join('\n');
|
|
732
959
|
return `🔍 Search method: memory_v2\n${context}`;
|
|
733
960
|
}
|
|
734
961
|
return {
|
|
735
962
|
query: userQuestion,
|
|
736
|
-
results:
|
|
737
|
-
id: memory.id,
|
|
738
|
-
topic: memory.topic,
|
|
739
|
-
decision: memory.summary,
|
|
740
|
-
reasoning: memory.details,
|
|
741
|
-
confidence: memory.confidence,
|
|
742
|
-
// memory.confidence is the normalized RRF retrieval score (0-1) after
|
|
743
|
-
// recallMemory. Use it as similarity so the field reflects search relevance
|
|
744
|
-
// rather than the original stored trust score.
|
|
745
|
-
similarity: memory.confidence ?? 1 - idx * 0.01,
|
|
746
|
-
created_at: memory.created_at,
|
|
747
|
-
event_date: memory.event_date ?? null,
|
|
748
|
-
graph_source: 'primary',
|
|
749
|
-
graph_rank: 1,
|
|
750
|
-
related_to: null,
|
|
751
|
-
edge_reason: null,
|
|
752
|
-
})),
|
|
963
|
+
results: limitedRows,
|
|
753
964
|
meta: {
|
|
754
|
-
count:
|
|
965
|
+
count: limitedRows.length,
|
|
755
966
|
search_method: 'memory_v2',
|
|
756
967
|
threshold: threshold || 'adaptive',
|
|
757
968
|
recency_boost: disableRecency
|
|
@@ -761,18 +972,8 @@ async function suggest(userQuestion, options = {}) {
|
|
|
761
972
|
scale: recencyScale,
|
|
762
973
|
decay: recencyDecay,
|
|
763
974
|
},
|
|
764
|
-
graph_expansion:
|
|
765
|
-
|
|
766
|
-
primary_count: bundle.graph_context.primary.length,
|
|
767
|
-
expanded_count: bundle.graph_context.expanded.length,
|
|
768
|
-
sources: {
|
|
769
|
-
primary: bundle.graph_context.primary.length,
|
|
770
|
-
supersedes_chain: 0,
|
|
771
|
-
refines: 0,
|
|
772
|
-
refined_by: 0,
|
|
773
|
-
contradicts: 0,
|
|
774
|
-
},
|
|
775
|
-
},
|
|
975
|
+
graph_expansion: summarizeGraphExpansion(limitedRows),
|
|
976
|
+
ranker: rankerMeta,
|
|
776
977
|
},
|
|
777
978
|
};
|
|
778
979
|
}
|
|
@@ -791,7 +992,7 @@ async function suggest(userQuestion, options = {}) {
|
|
|
791
992
|
const wordCount = userQuestion.split(/\s+/).length;
|
|
792
993
|
const adaptiveThreshold = threshold !== undefined ? threshold : wordCount < 3 ? 0.7 : 0.6;
|
|
793
994
|
// Vector search
|
|
794
|
-
results = await (0, memory_store_js_1.vectorSearch)(queryEmbedding,
|
|
995
|
+
results = await (0, memory_store_js_1.vectorSearch)(queryEmbedding, rerankPoolLimit * 2, 0.5); // Get more candidates
|
|
795
996
|
// Filter by adaptive threshold
|
|
796
997
|
results = results.filter((r) => r.similarity >= adaptiveThreshold);
|
|
797
998
|
// Stage 1.4: Temporal boost — detect time-related queries and boost matching results
|
|
@@ -839,7 +1040,7 @@ async function suggest(userQuestion, options = {}) {
|
|
|
839
1040
|
// Stage 1.7: FTS5 hybrid merge (Haiku Memory Layer)
|
|
840
1041
|
{
|
|
841
1042
|
try {
|
|
842
|
-
const ftsResults = await (0, db_manager_js_1.fts5Search)(userQuestion,
|
|
1043
|
+
const ftsResults = await (0, db_manager_js_1.fts5Search)(userQuestion, rerankPoolLimit * 2);
|
|
843
1044
|
if (ftsResults.length > 0) {
|
|
844
1045
|
// Normalize FTS5 ranks (BM25 returns negative values, closer to 0 = better)
|
|
845
1046
|
const maxRank = Math.max(...ftsResults.map((r) => Math.abs(r.rank)));
|
|
@@ -924,7 +1125,7 @@ async function suggest(userQuestion, options = {}) {
|
|
|
924
1125
|
ORDER BY created_at DESC
|
|
925
1126
|
LIMIT ?
|
|
926
1127
|
`);
|
|
927
|
-
const rows = (await stmt.all(...likeParams,
|
|
1128
|
+
const rows = (await stmt.all(...likeParams, rerankPoolLimit));
|
|
928
1129
|
results = rows.map((row) => ({
|
|
929
1130
|
...row,
|
|
930
1131
|
similarity: 0.75, // Assign moderate similarity for keyword matches
|
|
@@ -949,8 +1150,32 @@ async function suggest(userQuestion, options = {}) {
|
|
|
949
1150
|
if (useReranking) {
|
|
950
1151
|
results = await rerankWithLLM(userQuestion, results);
|
|
951
1152
|
}
|
|
952
|
-
|
|
953
|
-
const
|
|
1153
|
+
const rerankCandidateResults = results.slice(0, rerankPoolLimit);
|
|
1154
|
+
const vectorRows = rerankCandidateResults.map((r) => ({
|
|
1155
|
+
id: r.id,
|
|
1156
|
+
topic: r.topic,
|
|
1157
|
+
decision: r.decision,
|
|
1158
|
+
reasoning: r.reasoning,
|
|
1159
|
+
confidence: r.confidence,
|
|
1160
|
+
similarity: r.similarity,
|
|
1161
|
+
created_at: r.created_at,
|
|
1162
|
+
event_date: r.event_date ?? null,
|
|
1163
|
+
event_datetime: r.event_datetime ?? null,
|
|
1164
|
+
// Recency metadata (NEW - Gaussian Decay)
|
|
1165
|
+
recency_score: r.recency_score,
|
|
1166
|
+
recency_age_days: r.recency_age_days,
|
|
1167
|
+
final_score: r.final_score || r.similarity, // Falls back to similarity if no recency
|
|
1168
|
+
retrieval_score: r.similarity ?? null,
|
|
1169
|
+
// Graph metadata (NEW - Phase 1)
|
|
1170
|
+
graph_source: r.graph_source || 'primary',
|
|
1171
|
+
graph_rank: r.graph_rank || 1.0,
|
|
1172
|
+
related_to: r.related_to || null,
|
|
1173
|
+
edge_reason: r.edge_reason || null,
|
|
1174
|
+
case_id: null,
|
|
1175
|
+
source_type: 'decision',
|
|
1176
|
+
}));
|
|
1177
|
+
const { results: rankedVectorRows, meta: rankerMeta } = applyLearnedRanker(vectorRows);
|
|
1178
|
+
const finalResults = rankedVectorRows.slice(0, limit);
|
|
954
1179
|
// Markdown format (for human display)
|
|
955
1180
|
if (format === 'markdown') {
|
|
956
1181
|
const context = (0, decision_formatter_js_1.formatContext)(finalResults, { maxTokens: 500 });
|
|
@@ -976,27 +1201,9 @@ async function suggest(userQuestion, options = {}) {
|
|
|
976
1201
|
contradicts: finalResults.filter((r) => r.graph_source === 'contradicts').length,
|
|
977
1202
|
},
|
|
978
1203
|
};
|
|
979
|
-
// JSON format (default - LLM-first)
|
|
980
1204
|
return {
|
|
981
1205
|
query: userQuestion,
|
|
982
|
-
results: finalResults
|
|
983
|
-
id: r.id,
|
|
984
|
-
topic: r.topic,
|
|
985
|
-
decision: r.decision,
|
|
986
|
-
reasoning: r.reasoning,
|
|
987
|
-
confidence: r.confidence,
|
|
988
|
-
similarity: r.similarity,
|
|
989
|
-
created_at: r.created_at,
|
|
990
|
-
// Recency metadata (NEW - Gaussian Decay)
|
|
991
|
-
recency_score: r.recency_score,
|
|
992
|
-
recency_age_days: r.recency_age_days,
|
|
993
|
-
final_score: r.final_score || r.similarity, // Falls back to similarity if no recency
|
|
994
|
-
// Graph metadata (NEW - Phase 1)
|
|
995
|
-
graph_source: r.graph_source || 'primary',
|
|
996
|
-
graph_rank: r.graph_rank || 1.0,
|
|
997
|
-
related_to: r.related_to || null,
|
|
998
|
-
edge_reason: r.edge_reason || null,
|
|
999
|
-
})),
|
|
1206
|
+
results: finalResults,
|
|
1000
1207
|
meta: {
|
|
1001
1208
|
count: finalResults.length,
|
|
1002
1209
|
search_method: searchMethod,
|
|
@@ -1011,6 +1218,7 @@ async function suggest(userQuestion, options = {}) {
|
|
|
1011
1218
|
},
|
|
1012
1219
|
// Graph expansion stats (NEW - Phase 1)
|
|
1013
1220
|
graph_expansion: searchMethod.includes('graph') ? graphStats : null,
|
|
1221
|
+
ranker: rankerMeta,
|
|
1014
1222
|
},
|
|
1015
1223
|
};
|
|
1016
1224
|
}
|
|
@@ -1068,7 +1276,7 @@ async function listDecisions(options = {}) {
|
|
|
1068
1276
|
JOIN memory_scope_bindings msb ON msb.memory_id = d.id
|
|
1069
1277
|
WHERE msb.scope_id IN (${placeholders})
|
|
1070
1278
|
AND d.superseded_by IS NULL
|
|
1071
|
-
ORDER BY d.created_at DESC
|
|
1279
|
+
ORDER BY COALESCE(d.event_datetime, d.created_at) DESC, d.created_at DESC
|
|
1072
1280
|
LIMIT ?
|
|
1073
1281
|
`);
|
|
1074
1282
|
decisions = await stmt.all(...scopeIds, limit);
|
|
@@ -1077,7 +1285,7 @@ async function listDecisions(options = {}) {
|
|
|
1077
1285
|
const stmt = adapter.prepare(`
|
|
1078
1286
|
SELECT * FROM decisions
|
|
1079
1287
|
WHERE superseded_by IS NULL
|
|
1080
|
-
ORDER BY created_at DESC
|
|
1288
|
+
ORDER BY COALESCE(event_datetime, created_at) DESC, created_at DESC
|
|
1081
1289
|
LIMIT ?
|
|
1082
1290
|
`);
|
|
1083
1291
|
decisions = await stmt.all(limit);
|
package/dist/memory/api.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { type WikiPageIndexRecord } from '../cases/wiki-page-index.js';
|
|
1
2
|
import { resolveMemoryEvolution } from './evolution-engine.js';
|
|
2
3
|
import { getChannelSummary, upsertChannelSummary } from './channel-summary-store.js';
|
|
3
|
-
import type { MemoryKind, MemoryAgentBootstrap, MemoryAuditAck, MemoryEdge, MemoryScopeRef, MemoryStatus, ProfileSnapshot, RecallBundle, IngestConversationInput, ExtractedMemoryUnit, IngestConversationResult } from './types.js';
|
|
4
|
+
import type { MemoryKind, MemoryAgentBootstrap, MemoryAuditAck, MemoryEdge, MemoryRecord, MemoryScopeRef, MemoryStatus, ProfileSnapshot, RecallBundle, IngestConversationInput, ExtractedMemoryUnit, IngestConversationResult } from './types.js';
|
|
4
5
|
interface SaveMemoryInput {
|
|
5
6
|
topic: string;
|
|
6
7
|
kind: MemoryKind;
|
|
@@ -23,6 +24,21 @@ interface SaveMemoryInput {
|
|
|
23
24
|
* Stored in the event_date column. Defaults to created_at if omitted.
|
|
24
25
|
*/
|
|
25
26
|
eventDate?: string;
|
|
27
|
+
/** Source event timestamp in milliseconds when known. */
|
|
28
|
+
eventDateTime?: number;
|
|
29
|
+
entityObservationIds?: string[];
|
|
30
|
+
timelineEvent?: {
|
|
31
|
+
id?: string;
|
|
32
|
+
entity_id?: string;
|
|
33
|
+
event_type: string;
|
|
34
|
+
role?: string | null;
|
|
35
|
+
valid_from?: number | null;
|
|
36
|
+
valid_to?: number | null;
|
|
37
|
+
observed_at?: number | null;
|
|
38
|
+
source_ref?: string | null;
|
|
39
|
+
summary: string;
|
|
40
|
+
details?: string | null;
|
|
41
|
+
};
|
|
26
42
|
}
|
|
27
43
|
interface RecallMemoryOptions {
|
|
28
44
|
scopes?: MemoryScopeRef[];
|
|
@@ -30,12 +46,20 @@ interface RecallMemoryOptions {
|
|
|
30
46
|
includeHistory?: boolean;
|
|
31
47
|
skipGraphExpansion?: boolean;
|
|
32
48
|
topicPrefix?: string;
|
|
49
|
+
limit?: number;
|
|
33
50
|
}
|
|
34
51
|
interface IngestMemoryInput {
|
|
35
52
|
content: string;
|
|
36
53
|
scopes?: MemoryScopeRef[];
|
|
37
54
|
source: SaveMemoryInput['source'];
|
|
38
55
|
eventDate?: string;
|
|
56
|
+
eventDateTime?: number;
|
|
57
|
+
}
|
|
58
|
+
export interface FusedHit {
|
|
59
|
+
source_type: 'decision' | 'wiki_page';
|
|
60
|
+
source_id: string;
|
|
61
|
+
record: MemoryRecord | WikiPageIndexRecord;
|
|
62
|
+
fused_rank_score: number;
|
|
39
63
|
}
|
|
40
64
|
export declare const LEXICAL_STOPWORDS: Set<string>;
|
|
41
65
|
export declare function getLexicalQueryTokens(query: string): string[];
|
|
@@ -43,6 +67,9 @@ export declare function loadEdgesForIds(ids: string[]): Promise<MemoryEdge[]>;
|
|
|
43
67
|
export declare function saveMemory(input: SaveMemoryInput): Promise<{
|
|
44
68
|
success: boolean;
|
|
45
69
|
id: string;
|
|
70
|
+
saved_decision_id?: string;
|
|
71
|
+
timeline_event_id?: string | null;
|
|
72
|
+
timeline_event_ids?: string[];
|
|
46
73
|
}>;
|
|
47
74
|
export declare function buildProfile(scopes: MemoryScopeRef[]): Promise<ProfileSnapshot>;
|
|
48
75
|
export declare function recallMemory(query: string, options?: RecallMemoryOptions): Promise<RecallBundle>;
|