Merge remote-tracking branch 'origin/farm/1c6abd01/mnemopi-consolidation-gists-edges'
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `consolidateToEpisodic` (the function backing `sleep` / `sleepAllSessions`) never populating the episodic graph: the `gists` and `graph_edges` tables stayed at 0 rows across every bank even after multiple consolidation cycles, so Polyphonic Recall's `graph` voice (BFS over `findGistsByParticipant` / `findRelatedMemories`) always returned nothing. Consolidation now best-effort ingests the new episodic memory into `EpisodicGraph` so the gist row, gist→memory `ctx` edge, fact edges, and cross-memory similarity/entity/temporal edges land alongside the episodic row. Independent of the existing `MNEMOPI_PROACTIVE_LINKING` flag, which still gates the same enrichment on the `remember()` write path. ([#2435](https://github.com/can1357/oh-my-pi/issues/2435))
|
||||
|
||||
## [15.12.0] - 2026-06-12
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { SQLQueryBindings } from "bun:sqlite";
|
||||
import { generateId, stableMemoryId } from "../../util/ids";
|
||||
import { aaakEncode } from "../aaak";
|
||||
import { EpisodicGraph } from "../episodic-graph";
|
||||
import { heuristicExtractFacts } from "../extraction";
|
||||
import { clampVeracity } from "../veracity-consolidation";
|
||||
import { scheduleEmbedding } from "./helpers";
|
||||
@@ -261,6 +262,33 @@ function insertKg(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the episodic graph (gists, facts, edges) for a freshly consolidated
|
||||
* memory. The graph backs Polyphonic Recall's `graph` voice, which relies on
|
||||
* `findGistsByParticipant`, `findFactsBySubject`, and `findRelatedMemories`.
|
||||
*
|
||||
* Best-effort: failures (closed DB, missing optional tables) MUST NOT roll back
|
||||
* the consolidation that already wrote the episodic row, so any throw here is
|
||||
* swallowed. Unlike the proactive-link path in `store.ts` this is unconditional
|
||||
* — consolidation is the explicit "settle and compress" step where the graph
|
||||
* is supposed to gain edges.
|
||||
*/
|
||||
function ingestIntoEpisodicGraph(beam: BeamMemoryState, memoryId: string, summary: string): void {
|
||||
try {
|
||||
const graph =
|
||||
beam.episodicGraph instanceof EpisodicGraph
|
||||
? beam.episodicGraph
|
||||
: new EpisodicGraph({ db: beam.db, dbPath: beam.dbPath });
|
||||
graph.ingestMemory(summary, memoryId, {
|
||||
sessionId: sourceSession(beam),
|
||||
linkExisting: true,
|
||||
extractEntities: true,
|
||||
});
|
||||
} catch {
|
||||
// Graph enrichment is best-effort and never blocks consolidation.
|
||||
}
|
||||
}
|
||||
|
||||
export function consolidateToEpisodic(
|
||||
beam: BeamMemoryState,
|
||||
summary: string,
|
||||
@@ -299,6 +327,7 @@ export function consolidateToEpisodic(
|
||||
],
|
||||
);
|
||||
extractAndStoreFacts(beam, summary, 0, memoryId);
|
||||
ingestIntoEpisodicGraph(beam, memoryId, summary);
|
||||
scheduleEmbedding(beam, [{ memoryId, content: summary }]);
|
||||
emitEvent(beam, "MEMORY_CONSOLIDATED", memoryId, summary, source, importance, {
|
||||
summary_of: [...sourceWmIds],
|
||||
|
||||
@@ -93,6 +93,45 @@ describe("beam consolidation free functions", () => {
|
||||
expect(getEpisodicStats(beam).total).toBe(1);
|
||||
});
|
||||
|
||||
it("consolidateToEpisodic populates the episodic graph (gists, edges) for the new memory (#2435)", () => {
|
||||
const beam = trackedState();
|
||||
insertWorking(beam.db, "wm1", "s1", "Alice deployed the staging cluster checklist");
|
||||
|
||||
const id = consolidateToEpisodic(
|
||||
beam,
|
||||
"Alice deployed the staging cluster checklist",
|
||||
["wm1"],
|
||||
"consolidation",
|
||||
0.7,
|
||||
);
|
||||
|
||||
const gist = beam.db.query("SELECT id, memory_id FROM gists WHERE memory_id = ?").get(id) as {
|
||||
id: string;
|
||||
memory_id: string;
|
||||
} | null;
|
||||
expect(gist).not.toBeNull();
|
||||
expect(gist?.id).toBe(`gist_${id}`);
|
||||
const edges = beam.db
|
||||
.query("SELECT source, target, edge_type FROM graph_edges WHERE source = ? OR target = ?")
|
||||
.all(id, id) as { source: string; target: string; edge_type: string }[];
|
||||
expect(edges.some(edge => edge.source === id && edge.target === `gist_${id}` && edge.edge_type === "ctx")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it("sleepAllSessions adds gists and edges for every consolidated session (#2435)", () => {
|
||||
const beam = trackedState("maintenance");
|
||||
insertWorking(beam.db, "wm-a1", "a", "Alpha launch checklist");
|
||||
insertWorking(beam.db, "wm-b1", "b", "Beta launch checklist");
|
||||
|
||||
const result = sleepAllSessions(beam, false);
|
||||
expect(result.items_consolidated).toBe(2);
|
||||
const gistCount = (beam.db.query("SELECT COUNT(*) AS count FROM gists").get() as { count: number }).count;
|
||||
const edgeCount = (beam.db.query("SELECT COUNT(*) AS count FROM graph_edges").get() as { count: number }).count;
|
||||
expect(gistCount).toBe(2);
|
||||
expect(edgeCount).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("sleep dry-run is side-effect-free and real sleep marks originals, writes summary and log", () => {
|
||||
const beam = trackedState();
|
||||
insertWorking(beam.db, "wm1", "s1", "task alpha", "conversation");
|
||||
|
||||
Reference in New Issue
Block a user