@matthewfl/pi-contemplator 0.0.9 → 0.1.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/README.md +17 -11
- package/package.json +8 -6
- package/src/agents/contemplator/agent.ts +325 -91
- package/src/agents/contemplator/prompts.ts +6 -6
- package/src/agents/observer/agent.ts +14 -6
- package/src/agents/observer/prompts.ts +16 -7
- package/src/agents/reviewer/agent.ts +24 -4
- package/src/agents/reviewer/prompts.ts +1 -1
- package/src/agents/reviewer/tools.ts +24 -9
- package/src/agents/stream-errors.ts +1 -1
- package/src/agents/summarizer/agent.ts +597 -0
- package/src/agents/summarizer/prompts.ts +46 -0
- package/src/agents/summarizer/sampling.ts +80 -0
- package/src/commands/contemplator-view.ts +22 -1
- package/src/commands/settings.ts +73 -69
- package/src/commands/status.ts +60 -36
- package/src/commands/summarizer-view.ts +58 -0
- package/src/commands/view.ts +22 -10
- package/src/config.ts +25 -32
- package/src/hooks/compaction-hook.ts +36 -19
- package/src/hooks/compaction-resume.ts +4 -4
- package/src/hooks/compaction-trigger.ts +96 -56
- package/src/hooks/consolidation-trigger.ts +213 -196
- package/src/memory-citations.ts +37 -0
- package/src/required-tool-choice.ts +28 -0
- package/src/runtime.ts +116 -33
- package/src/session-ledger/fold.ts +82 -53
- package/src/session-ledger/index.ts +1 -0
- package/src/session-ledger/pools.ts +77 -0
- package/src/session-ledger/progress.ts +7 -18
- package/src/session-ledger/projection.ts +45 -177
- package/src/session-ledger/recall.ts +129 -127
- package/src/session-ledger/render-summary.ts +20 -19
- package/src/session-ledger/search.ts +99 -115
- package/src/session-ledger/types.ts +102 -75
- package/src/tools/compact-context.ts +1 -1
- package/src/tools/recall-observation.ts +99 -459
- package/src/tools/search-memories.ts +31 -72
- package/src/agents/dropper/agent.ts +0 -291
- package/src/agents/dropper/coverage.ts +0 -128
- package/src/agents/dropper/pool.ts +0 -67
- package/src/agents/dropper/prompts.ts +0 -48
- package/src/agents/reflector/agent.ts +0 -213
- package/src/agents/reflector/prompts.ts +0 -81
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
OM_FOLDED,
|
|
3
3
|
isMemoryDetails,
|
|
4
|
-
isObservationsDroppedEntry,
|
|
5
|
-
isObservationsRecordedEntry,
|
|
6
|
-
isReflectionsRecordedEntry,
|
|
7
|
-
isReviewResultEntry,
|
|
8
4
|
type Entry,
|
|
9
5
|
type MemoryDetails,
|
|
10
6
|
type Observation,
|
|
11
|
-
type Reflection,
|
|
12
7
|
type ReviewResult,
|
|
8
|
+
type Summary,
|
|
13
9
|
} from "./types.js";
|
|
10
|
+
import { foldLedger } from "./fold.js";
|
|
14
11
|
|
|
15
12
|
export type Projection = {
|
|
16
13
|
observations: Observation[];
|
|
17
|
-
|
|
18
|
-
/**
|
|
14
|
+
summaries: Summary[];
|
|
15
|
+
/** Reviews are retained for search/recall and are never rendered into automatic memory. */
|
|
19
16
|
reviews?: ReviewResult[];
|
|
20
17
|
};
|
|
21
18
|
|
|
22
19
|
export type ProjectionDiff = {
|
|
23
20
|
observationsOnlyInFull: Observation[];
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export type CompactionProjectionConfig = {
|
|
29
|
-
observationsPoolMaxTokens: number;
|
|
21
|
+
summariesOnlyInFull: Summary[];
|
|
22
|
+
observationsOnlyInVisible: Observation[];
|
|
23
|
+
summariesOnlyInVisible: Summary[];
|
|
30
24
|
};
|
|
31
25
|
|
|
32
26
|
export type CompactionProjection = Projection & {
|
|
@@ -34,108 +28,12 @@ export type CompactionProjection = Projection & {
|
|
|
34
28
|
details: MemoryDetails;
|
|
35
29
|
};
|
|
36
30
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| { kind: "tip" }
|
|
40
|
-
| { kind: "none" };
|
|
41
|
-
|
|
42
|
-
type ProjectionFoldOptions = {
|
|
43
|
-
observationsBoundary: ProjectionBoundary;
|
|
44
|
-
reflectionsBoundary: ProjectionBoundary;
|
|
45
|
-
dropsBoundary: ProjectionBoundary;
|
|
46
|
-
reviewsBoundary: ProjectionBoundary;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
function entryIndexById(entries: Entry[]): Map<string, number> {
|
|
50
|
-
const indexes = new Map<string, number>();
|
|
51
|
-
for (let i = 0; i < entries.length; i++) indexes.set(entries[i].id, i);
|
|
52
|
-
return indexes;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function entryBoundary(entryId: string): ProjectionBoundary {
|
|
56
|
-
return { kind: "entry", entryId };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function tipBoundary(): ProjectionBoundary {
|
|
60
|
-
return { kind: "tip" };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function noneBoundary(): ProjectionBoundary {
|
|
64
|
-
return { kind: "none" };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function boundaryIndex(entries: Entry[], indexes: Map<string, number>, boundary: ProjectionBoundary): number {
|
|
68
|
-
if (boundary.kind === "tip") return entries.length - 1;
|
|
69
|
-
if (boundary.kind === "none") return -1;
|
|
70
|
-
return indexes.get(boundary.entryId) ?? -1;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function coverageIndex(entry: Entry & { data: { coversUpToId: string } }, indexes: Map<string, number>): number {
|
|
74
|
-
return indexes.get(entry.data.coversUpToId) ?? -1;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function isAtOrBefore(index: number, boundaryIndex: number): boolean {
|
|
78
|
-
return index >= 0 && boundaryIndex >= 0 && index <= boundaryIndex;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function isCoveredAtOrBefore(
|
|
82
|
-
entry: Entry & { data: { coversUpToId: string } },
|
|
83
|
-
indexes: Map<string, number>,
|
|
84
|
-
boundaryIndex: number,
|
|
85
|
-
): boolean {
|
|
86
|
-
return isAtOrBefore(coverageIndex(entry, indexes), boundaryIndex);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function foldProjection(entries: Entry[], options: ProjectionFoldOptions): Projection {
|
|
90
|
-
const indexes = entryIndexById(entries);
|
|
91
|
-
const observationsBoundary = boundaryIndex(entries, indexes, options.observationsBoundary);
|
|
92
|
-
const reflectionsBoundary = boundaryIndex(entries, indexes, options.reflectionsBoundary);
|
|
93
|
-
const dropsBoundary = boundaryIndex(entries, indexes, options.dropsBoundary);
|
|
94
|
-
const reviewsBoundary = boundaryIndex(entries, indexes, options.reviewsBoundary);
|
|
95
|
-
const observations: Observation[] = [];
|
|
96
|
-
const reflections: Reflection[] = [];
|
|
97
|
-
const reviews: ReviewResult[] = [];
|
|
98
|
-
const observationsById = new Set<string>();
|
|
99
|
-
const reflectionsById = new Set<string>();
|
|
100
|
-
const reviewIds = new Set<string>();
|
|
101
|
-
const droppedObservationIds = new Set<string>();
|
|
102
|
-
|
|
103
|
-
for (const entry of entries) {
|
|
104
|
-
if (isObservationsRecordedEntry(entry) && isCoveredAtOrBefore(entry, indexes, observationsBoundary)) {
|
|
105
|
-
for (const observation of entry.data.observations) {
|
|
106
|
-
if (observationsById.has(observation.id)) continue;
|
|
107
|
-
observationsById.add(observation.id);
|
|
108
|
-
observations.push(observation);
|
|
109
|
-
}
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (isReflectionsRecordedEntry(entry) && isCoveredAtOrBefore(entry, indexes, reflectionsBoundary)) {
|
|
114
|
-
for (const reflection of entry.data.reflections) {
|
|
115
|
-
if (reflectionsById.has(reflection.id)) continue;
|
|
116
|
-
reflectionsById.add(reflection.id);
|
|
117
|
-
reflections.push(reflection);
|
|
118
|
-
}
|
|
119
|
-
continue;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (isObservationsDroppedEntry(entry) && isCoveredAtOrBefore(entry, indexes, dropsBoundary)) {
|
|
123
|
-
for (const observationId of entry.data.observationIds) droppedObservationIds.add(observationId);
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (isReviewResultEntry(entry) && isAtOrBefore(indexes.get(entry.id) ?? -1, reviewsBoundary)) {
|
|
128
|
-
if (!reviewIds.has(entry.data.result.id)) {
|
|
129
|
-
reviewIds.add(entry.data.result.id);
|
|
130
|
-
reviews.push(entry.data.result);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
31
|
+
function projectionFromFold(entries: Entry[], upToEntryId?: string): Projection {
|
|
32
|
+
const folded = foldLedger(entries, { upToEntryId });
|
|
135
33
|
return {
|
|
136
|
-
observations:
|
|
137
|
-
|
|
138
|
-
...(reviews.length > 0 ? { reviews } : {}),
|
|
34
|
+
observations: folded.activeObservations,
|
|
35
|
+
summaries: folded.activeSummaries,
|
|
36
|
+
...(folded.reviews.length > 0 ? { reviews: folded.reviews } : {}),
|
|
139
37
|
};
|
|
140
38
|
}
|
|
141
39
|
|
|
@@ -143,101 +41,71 @@ function projectionFromMemoryDetails(details: MemoryDetails): Projection {
|
|
|
143
41
|
const reviews = details.reviews ?? [];
|
|
144
42
|
return {
|
|
145
43
|
observations: [...details.observations],
|
|
146
|
-
|
|
44
|
+
summaries: [...details.summaries],
|
|
147
45
|
...(reviews.length > 0 ? { reviews: [...reviews] } : {}),
|
|
148
46
|
};
|
|
149
47
|
}
|
|
150
48
|
|
|
151
|
-
function
|
|
49
|
+
function latestCompactionDetails(entries: Entry[]): MemoryDetails | undefined {
|
|
152
50
|
for (let i = entries.length - 1; i >= 0; i--) {
|
|
153
51
|
const entry = entries[i];
|
|
154
|
-
if (entry.type
|
|
155
|
-
if (isMemoryDetails(entry.details)) return entry.details;
|
|
52
|
+
if (entry.type === "compaction" && isMemoryDetails(entry.details)) return entry.details;
|
|
156
53
|
}
|
|
157
54
|
return undefined;
|
|
158
55
|
}
|
|
159
56
|
|
|
160
57
|
export function fullProjection(entries: Entry[], upToEntryId?: string): Projection {
|
|
161
|
-
|
|
162
|
-
return foldProjection(entries, {
|
|
163
|
-
observationsBoundary: boundary,
|
|
164
|
-
reflectionsBoundary: boundary,
|
|
165
|
-
dropsBoundary: boundary,
|
|
166
|
-
reviewsBoundary: boundary,
|
|
167
|
-
});
|
|
58
|
+
return projectionFromFold(entries, upToEntryId);
|
|
168
59
|
}
|
|
169
60
|
|
|
61
|
+
/** Memories already present in the latest compacted context. */
|
|
170
62
|
export function visibleProjection(entries: Entry[], upToEntryId?: string): Projection {
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return buildCompactionProjection(entries, upToEntryId, { observationsPoolMaxTokens: Number.POSITIVE_INFINITY });
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export function latestFullFoldBoundaryId(entries: Entry[]): string | undefined {
|
|
180
|
-
const indexes = entryIndexById(entries);
|
|
181
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
182
|
-
const entry = entries[i];
|
|
183
|
-
if (entry.type !== "compaction") continue;
|
|
184
|
-
if (!isMemoryDetails(entry.details)) continue;
|
|
185
|
-
if (!entry.details.fullFold) continue;
|
|
186
|
-
if (!entry.firstKeptEntryId) continue;
|
|
187
|
-
if (!indexes.has(entry.firstKeptEntryId)) continue;
|
|
188
|
-
return entry.firstKeptEntryId;
|
|
189
|
-
}
|
|
190
|
-
return undefined;
|
|
63
|
+
if (upToEntryId) return projectionFromFold(entries, upToEntryId);
|
|
64
|
+
const details = latestCompactionDetails(entries);
|
|
65
|
+
return details ? projectionFromMemoryDetails(details) : { observations: [], summaries: [] };
|
|
191
66
|
}
|
|
192
67
|
|
|
193
68
|
export function buildCompactionProjection(
|
|
194
69
|
entries: Entry[],
|
|
195
70
|
firstKeptEntryId: string,
|
|
196
|
-
config: CompactionProjectionConfig,
|
|
197
71
|
): CompactionProjection {
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
reviewsBoundary: entryBoundary(firstKeptEntryId),
|
|
205
|
-
});
|
|
206
|
-
const observationTokens = normalProjection.observations.reduce(
|
|
207
|
-
(total, observation) => total + observation.tokenCount,
|
|
208
|
-
0,
|
|
209
|
-
);
|
|
210
|
-
const fullFold = observationTokens >= config.observationsPoolMaxTokens;
|
|
211
|
-
const projection = fullFold
|
|
212
|
-
? fullProjection(entries, firstKeptEntryId)
|
|
213
|
-
: normalProjection;
|
|
214
|
-
|
|
72
|
+
const durable = foldLedger(entries, { upToEntryId: firstKeptEntryId });
|
|
73
|
+
const observations = durable.activeObservations;
|
|
74
|
+
const summaries = durable.activeSummaries;
|
|
75
|
+
// Retained in the v2 detail shape for schema stability. The old observation-
|
|
76
|
+
// pool pressure mode no longer exists; summarizer consumption controls visibility.
|
|
77
|
+
const fullFold = false;
|
|
215
78
|
const details: MemoryDetails = {
|
|
216
79
|
type: OM_FOLDED,
|
|
217
|
-
version:
|
|
80
|
+
version: 2,
|
|
218
81
|
fullFold,
|
|
219
|
-
observations
|
|
220
|
-
|
|
221
|
-
|
|
82
|
+
observations,
|
|
83
|
+
summaries,
|
|
84
|
+
archive: {
|
|
85
|
+
observations: durable.observations,
|
|
86
|
+
summaries: durable.summaries,
|
|
87
|
+
},
|
|
88
|
+
...(durable.reviews.length > 0 ? { reviews: durable.reviews } : {}),
|
|
222
89
|
};
|
|
223
90
|
|
|
224
91
|
return {
|
|
225
92
|
fullFold,
|
|
226
|
-
observations
|
|
227
|
-
|
|
228
|
-
...(
|
|
93
|
+
observations,
|
|
94
|
+
summaries,
|
|
95
|
+
...(durable.reviews.length > 0 ? { reviews: durable.reviews } : {}),
|
|
229
96
|
details,
|
|
230
97
|
};
|
|
231
98
|
}
|
|
232
99
|
|
|
233
100
|
export function diffProjection(visible: Projection, full: Projection): ProjectionDiff {
|
|
234
|
-
const visibleObservationIds = new Set(visible.observations.map((
|
|
235
|
-
const fullObservationIds = new Set(full.observations.map((
|
|
236
|
-
const
|
|
237
|
-
|
|
101
|
+
const visibleObservationIds = new Set(visible.observations.map((memory) => memory.id));
|
|
102
|
+
const fullObservationIds = new Set(full.observations.map((memory) => memory.id));
|
|
103
|
+
const visibleSummaryIds = new Set(visible.summaries.map((memory) => memory.id));
|
|
104
|
+
const fullSummaryIds = new Set(full.summaries.map((memory) => memory.id));
|
|
238
105
|
return {
|
|
239
|
-
observationsOnlyInFull: full.observations.filter((
|
|
240
|
-
|
|
241
|
-
|
|
106
|
+
observationsOnlyInFull: full.observations.filter((memory) => !visibleObservationIds.has(memory.id)),
|
|
107
|
+
summariesOnlyInFull: full.summaries.filter((memory) => !visibleSummaryIds.has(memory.id)),
|
|
108
|
+
observationsOnlyInVisible: visible.observations.filter((memory) => !fullObservationIds.has(memory.id)),
|
|
109
|
+
summariesOnlyInVisible: visible.summaries.filter((memory) => !fullSummaryIds.has(memory.id)),
|
|
242
110
|
};
|
|
243
111
|
}
|
|
@@ -1,25 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
isMemoryDetails,
|
|
3
3
|
isObservationsRecordedEntry,
|
|
4
|
-
isReflectionsRecordedEntry,
|
|
5
4
|
isReviewResultEntry,
|
|
5
|
+
isSummarizerCommitEntry,
|
|
6
6
|
type Entry,
|
|
7
|
+
type MemoryVisibility,
|
|
7
8
|
type Observation,
|
|
8
|
-
type Reflection,
|
|
9
9
|
type ReviewResult,
|
|
10
|
+
type Summary,
|
|
10
11
|
} from "./types.js";
|
|
12
|
+
import { foldLedger } from "./fold.js";
|
|
11
13
|
|
|
12
14
|
const SOURCE_TYPES = new Set(["message", "custom_message", "branch_summary"]);
|
|
13
15
|
|
|
14
|
-
export type { Entry, Observation,
|
|
16
|
+
export type { Entry, Observation, Summary };
|
|
15
17
|
|
|
16
|
-
type
|
|
17
|
-
entryId: string;
|
|
18
|
-
entryIndex: number;
|
|
19
|
-
recordIndex: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type ReflectionLedgerLocation = {
|
|
18
|
+
type LedgerLocation = {
|
|
23
19
|
entryId: string;
|
|
24
20
|
entryIndex: number;
|
|
25
21
|
recordIndex: number;
|
|
@@ -29,22 +25,31 @@ export type RecalledObservation = {
|
|
|
29
25
|
observation: Observation;
|
|
30
26
|
observationEntryId: string;
|
|
31
27
|
observationRecordIndex: number;
|
|
32
|
-
|
|
28
|
+
visibility: MemoryVisibility;
|
|
29
|
+
consumedBySummaryId?: string;
|
|
30
|
+
citedBySummaryIds: string[];
|
|
33
31
|
sourceEntryIds: string[];
|
|
34
32
|
sourceEntries: Entry[];
|
|
35
33
|
missingSourceEntryIds: string[];
|
|
36
34
|
nonSourceEntryIds: string[];
|
|
37
35
|
};
|
|
38
36
|
|
|
39
|
-
export type
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
export type RecalledSummary = {
|
|
38
|
+
summary: Summary;
|
|
39
|
+
summaryEntryId: string;
|
|
40
|
+
summaryRecordIndex: number;
|
|
41
|
+
visibility: MemoryVisibility;
|
|
42
|
+
consumedBySummaryId?: string;
|
|
43
|
+
citedBySummaryIds: string[];
|
|
44
|
+
sourceMemoryIds: string[];
|
|
45
|
+
consumedMemoryIds: string[];
|
|
46
|
+
missingSourceMemoryIds: string[];
|
|
43
47
|
};
|
|
44
48
|
|
|
45
49
|
export type RecalledReviewResult = {
|
|
46
50
|
review: ReviewResult;
|
|
47
51
|
reviewEntryId: string;
|
|
52
|
+
citedBySummaryIds: string[];
|
|
48
53
|
};
|
|
49
54
|
|
|
50
55
|
export type RecallResult =
|
|
@@ -52,34 +57,34 @@ export type RecallResult =
|
|
|
52
57
|
status: "not_found";
|
|
53
58
|
memoryId: string;
|
|
54
59
|
kind: undefined;
|
|
55
|
-
|
|
60
|
+
summaries: [];
|
|
56
61
|
reviews: [];
|
|
57
62
|
observations: [];
|
|
58
63
|
sourceEntries: [];
|
|
59
64
|
missingSourceEntryIds: [];
|
|
60
65
|
nonSourceEntryIds: [];
|
|
61
|
-
|
|
66
|
+
missingSourceMemoryIds: [];
|
|
62
67
|
collision: false;
|
|
63
68
|
partial: false;
|
|
64
69
|
}
|
|
65
70
|
| {
|
|
66
71
|
status: "found";
|
|
67
72
|
memoryId: string;
|
|
68
|
-
kind: "observation" | "
|
|
69
|
-
|
|
73
|
+
kind: "observation" | "summary" | "review" | "mixed";
|
|
74
|
+
summaries: RecalledSummary[];
|
|
70
75
|
reviews: RecalledReviewResult[];
|
|
71
76
|
observations: RecalledObservation[];
|
|
72
77
|
sourceEntries: Entry[];
|
|
73
78
|
missingSourceEntryIds: string[];
|
|
74
79
|
nonSourceEntryIds: string[];
|
|
75
|
-
|
|
80
|
+
missingSourceMemoryIds: string[];
|
|
76
81
|
collision: boolean;
|
|
77
82
|
partial: boolean;
|
|
78
83
|
};
|
|
79
84
|
|
|
80
|
-
type IndexedObservation =
|
|
81
|
-
type
|
|
82
|
-
type IndexedReviewResult = { review: ReviewResult; entryId: string };
|
|
85
|
+
type IndexedObservation = LedgerLocation & { observation: Observation };
|
|
86
|
+
type IndexedSummary = LedgerLocation & { summary: Summary };
|
|
87
|
+
type IndexedReviewResult = { review: ReviewResult; entryId: string; entryIndex: number };
|
|
83
88
|
|
|
84
89
|
function isSourceEntry(entry: Entry): boolean {
|
|
85
90
|
return SOURCE_TYPES.has(entry.type);
|
|
@@ -87,79 +92,85 @@ function isSourceEntry(entry: Entry): boolean {
|
|
|
87
92
|
|
|
88
93
|
function uniqueById(entries: Entry[]): Entry[] {
|
|
89
94
|
const seen = new Set<string>();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (seen.has(entry.id)) continue;
|
|
95
|
+
return entries.filter((entry) => {
|
|
96
|
+
if (seen.has(entry.id)) return false;
|
|
93
97
|
seen.add(entry.id);
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
return result;
|
|
98
|
+
return true;
|
|
99
|
+
});
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
function uniqueStrings(values: string[]): string[] {
|
|
102
|
+
function uniqueStrings(values: readonly string[]): string[] {
|
|
100
103
|
return Array.from(new Set(values));
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
function indexLedger(entries: Entry[]): {
|
|
104
107
|
observations: IndexedObservation[];
|
|
105
|
-
|
|
108
|
+
summaries: IndexedSummary[];
|
|
106
109
|
reviews: IndexedReviewResult[];
|
|
107
|
-
droppedIds: Set<string>;
|
|
108
110
|
} {
|
|
109
111
|
const observations: IndexedObservation[] = [];
|
|
110
|
-
const
|
|
112
|
+
const summaries: IndexedSummary[] = [];
|
|
111
113
|
const reviews: IndexedReviewResult[] = [];
|
|
112
|
-
const
|
|
114
|
+
const observationKeys = new Set<string>();
|
|
115
|
+
const summaryKeys = new Set<string>();
|
|
116
|
+
const reviewKeys = new Set<string>();
|
|
117
|
+
const addObservation = (observation: Observation, location: LedgerLocation): void => {
|
|
118
|
+
const key = `${observation.id}:${uniqueStrings(observation.sourceEntryIds).sort().join(",")}`;
|
|
119
|
+
if (observationKeys.has(key)) return;
|
|
120
|
+
observationKeys.add(key);
|
|
121
|
+
observations.push({ observation, ...location });
|
|
122
|
+
};
|
|
123
|
+
const addSummary = (summary: Summary, location: LedgerLocation): void => {
|
|
124
|
+
const key = `${summary.id}:${summary.sourceMemoryIds.join(",")}:${summary.consumedMemoryIds.join(",")}`;
|
|
125
|
+
if (summaryKeys.has(key)) return;
|
|
126
|
+
summaryKeys.add(key);
|
|
127
|
+
summaries.push({ summary, ...location });
|
|
128
|
+
};
|
|
129
|
+
const addReview = (review: ReviewResult, entryId: string, entryIndex: number): void => {
|
|
130
|
+
if (reviewKeys.has(review.id)) return;
|
|
131
|
+
reviewKeys.add(review.id);
|
|
132
|
+
reviews.push({ review, entryId, entryIndex });
|
|
133
|
+
};
|
|
113
134
|
|
|
114
135
|
for (let entryIndex = 0; entryIndex < entries.length; entryIndex++) {
|
|
115
136
|
const entry = entries[entryIndex];
|
|
116
|
-
if (
|
|
117
|
-
entry.
|
|
118
|
-
|
|
119
|
-
});
|
|
137
|
+
if (entry.type === "compaction" && isMemoryDetails(entry.details)) {
|
|
138
|
+
const archivedObservations = entry.details.archive?.observations ?? entry.details.observations;
|
|
139
|
+
const archivedSummaries = entry.details.archive?.summaries ?? entry.details.summaries;
|
|
140
|
+
archivedObservations.forEach((observation, recordIndex) => addObservation(observation, { entryId: entry.id, entryIndex, recordIndex }));
|
|
141
|
+
archivedSummaries.forEach((summary, recordIndex) => addSummary(summary, { entryId: entry.id, entryIndex, recordIndex }));
|
|
142
|
+
for (const review of entry.details.reviews ?? []) addReview(review, entry.id, entryIndex);
|
|
120
143
|
continue;
|
|
121
144
|
}
|
|
122
|
-
if (
|
|
123
|
-
entry.data.
|
|
124
|
-
reflections.push({ reflection, entryId: entry.id, entryIndex, recordIndex });
|
|
125
|
-
});
|
|
145
|
+
if (isObservationsRecordedEntry(entry)) {
|
|
146
|
+
entry.data.observations.forEach((observation, recordIndex) => addObservation(observation, { entryId: entry.id, entryIndex, recordIndex }));
|
|
126
147
|
continue;
|
|
127
148
|
}
|
|
128
|
-
if (
|
|
129
|
-
entry.data.
|
|
149
|
+
if (isSummarizerCommitEntry(entry)) {
|
|
150
|
+
entry.data.summaries.forEach((summary, recordIndex) => addSummary(summary, { entryId: entry.id, entryIndex, recordIndex }));
|
|
130
151
|
continue;
|
|
131
152
|
}
|
|
132
|
-
if (isReviewResultEntry(entry))
|
|
153
|
+
if (isReviewResultEntry(entry)) addReview(entry.data.result, entry.id, entryIndex);
|
|
133
154
|
}
|
|
134
|
-
|
|
135
|
-
return { observations, reflections, reviews, droppedIds };
|
|
155
|
+
return { observations, summaries, reviews };
|
|
136
156
|
}
|
|
137
157
|
|
|
138
|
-
function resolveObservationSources(entries: Entry[],
|
|
139
|
-
const sourceEntryIds = uniqueStrings(observation.sourceEntryIds);
|
|
158
|
+
function resolveObservationSources(entries: Entry[], indexed: IndexedObservation): Omit<RecalledObservation, "visibility" | "consumedBySummaryId" | "citedBySummaryIds"> {
|
|
159
|
+
const sourceEntryIds = uniqueStrings(indexed.observation.sourceEntryIds);
|
|
140
160
|
const byId = new Map(entries.map((entry) => [entry.id, entry]));
|
|
141
161
|
const sourceEntries: Entry[] = [];
|
|
142
162
|
const missingSourceEntryIds: string[] = [];
|
|
143
163
|
const nonSourceEntryIds: string[] = [];
|
|
144
|
-
|
|
145
164
|
for (const sourceEntryId of sourceEntryIds) {
|
|
146
165
|
const sourceEntry = byId.get(sourceEntryId);
|
|
147
|
-
if (!sourceEntry)
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
if (!isSourceEntry(sourceEntry)) {
|
|
152
|
-
nonSourceEntryIds.push(sourceEntryId);
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
sourceEntries.push(sourceEntry);
|
|
166
|
+
if (!sourceEntry) missingSourceEntryIds.push(sourceEntryId);
|
|
167
|
+
else if (!isSourceEntry(sourceEntry)) nonSourceEntryIds.push(sourceEntryId);
|
|
168
|
+
else sourceEntries.push(sourceEntry);
|
|
156
169
|
}
|
|
157
|
-
|
|
158
170
|
return {
|
|
159
|
-
observation,
|
|
160
|
-
observationEntryId:
|
|
161
|
-
observationRecordIndex:
|
|
162
|
-
status: "active",
|
|
171
|
+
observation: indexed.observation,
|
|
172
|
+
observationEntryId: indexed.entryId,
|
|
173
|
+
observationRecordIndex: indexed.recordIndex,
|
|
163
174
|
sourceEntryIds,
|
|
164
175
|
sourceEntries,
|
|
165
176
|
missingSourceEntryIds,
|
|
@@ -172,87 +183,78 @@ function notFound(memoryId: string): RecallResult {
|
|
|
172
183
|
status: "not_found",
|
|
173
184
|
memoryId,
|
|
174
185
|
kind: undefined,
|
|
175
|
-
|
|
186
|
+
summaries: [],
|
|
176
187
|
reviews: [],
|
|
177
188
|
observations: [],
|
|
178
189
|
sourceEntries: [],
|
|
179
190
|
missingSourceEntryIds: [],
|
|
180
191
|
nonSourceEntryIds: [],
|
|
181
|
-
|
|
192
|
+
missingSourceMemoryIds: [],
|
|
182
193
|
collision: false,
|
|
183
194
|
partial: false,
|
|
184
195
|
};
|
|
185
196
|
}
|
|
186
197
|
|
|
198
|
+
/** Recall exactly one graph node plus immediate backward/forward pointers. */
|
|
187
199
|
export function recallMemorySources(entries: Entry[], memoryId: string): RecallResult {
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
const
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
const observationsById = new Map<string, IndexedObservation>();
|
|
196
|
-
for (const indexed of indexedObservations) {
|
|
197
|
-
if (!observationsById.has(indexed.observation.id)) observationsById.set(indexed.observation.id, indexed);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const recalledByKey = new Map<string, RecalledObservation>();
|
|
201
|
-
const missingSupportingObservationIds: string[] = [];
|
|
200
|
+
const indexed = indexLedger(entries);
|
|
201
|
+
const folded = foldLedger(entries);
|
|
202
|
+
const observationMatches = indexed.observations.filter(({ observation }) => observation.id === memoryId);
|
|
203
|
+
const summaryMatches = indexed.summaries.filter(({ summary }) => summary.id === memoryId);
|
|
204
|
+
const reviewMatches = indexed.reviews.filter(({ review }) => review.id === memoryId);
|
|
205
|
+
if (observationMatches.length === 0 && summaryMatches.length === 0 && reviewMatches.length === 0) return notFound(memoryId);
|
|
202
206
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
207
|
+
const knownMemoryIds = new Set([
|
|
208
|
+
...folded.observationsById.keys(),
|
|
209
|
+
...folded.summariesById.keys(),
|
|
210
|
+
...folded.reviewsById.keys(),
|
|
211
|
+
]);
|
|
212
|
+
const observations: RecalledObservation[] = observationMatches.map((match) => {
|
|
213
|
+
const consumedBySummaryId = folded.consumedBySummaryId.get(match.observation.id);
|
|
214
|
+
return {
|
|
215
|
+
...resolveObservationSources(entries, match),
|
|
216
|
+
visibility: consumedBySummaryId ? "summarized" : "visible",
|
|
217
|
+
...(consumedBySummaryId ? { consumedBySummaryId } : {}),
|
|
218
|
+
citedBySummaryIds: folded.citedBySummaryIds.get(match.observation.id) ?? [],
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
const summaries: RecalledSummary[] = summaryMatches.map((match) => {
|
|
222
|
+
const consumedBySummaryId = folded.consumedBySummaryId.get(match.summary.id);
|
|
223
|
+
return {
|
|
224
|
+
summary: match.summary,
|
|
225
|
+
summaryEntryId: match.entryId,
|
|
226
|
+
summaryRecordIndex: match.recordIndex,
|
|
227
|
+
visibility: consumedBySummaryId ? "summarized" : "visible",
|
|
228
|
+
...(consumedBySummaryId ? { consumedBySummaryId } : {}),
|
|
229
|
+
citedBySummaryIds: folded.citedBySummaryIds.get(match.summary.id) ?? [],
|
|
230
|
+
sourceMemoryIds: match.summary.sourceMemoryIds,
|
|
231
|
+
consumedMemoryIds: match.summary.consumedMemoryIds,
|
|
232
|
+
missingSourceMemoryIds: match.summary.sourceMemoryIds.filter((id) => !knownMemoryIds.has(id)),
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
const reviews: RecalledReviewResult[] = reviewMatches.map(({ review, entryId }) => ({
|
|
236
|
+
review,
|
|
237
|
+
reviewEntryId: entryId,
|
|
238
|
+
citedBySummaryIds: folded.citedBySummaryIds.get(review.id) ?? [],
|
|
229
239
|
}));
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
const
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
const matchCount =
|
|
236
|
-
const kinds = [directObservationMatches.length > 0, reflectionMatches.length > 0, reviewMatches.length > 0].filter(Boolean).length;
|
|
237
|
-
|
|
240
|
+
const sourceEntries = uniqueById(observations.flatMap((match) => match.sourceEntries));
|
|
241
|
+
const missingSourceEntryIds = uniqueStrings(observations.flatMap((match) => match.missingSourceEntryIds));
|
|
242
|
+
const nonSourceEntryIds = uniqueStrings(observations.flatMap((match) => match.nonSourceEntryIds));
|
|
243
|
+
const missingSourceMemoryIds = uniqueStrings(summaries.flatMap((match) => match.missingSourceMemoryIds));
|
|
244
|
+
const kinds = [observationMatches.length > 0, summaryMatches.length > 0, reviewMatches.length > 0].filter(Boolean).length;
|
|
245
|
+
const matchCount = observationMatches.length + summaryMatches.length + reviewMatches.length;
|
|
238
246
|
return {
|
|
239
247
|
status: "found",
|
|
240
248
|
memoryId,
|
|
241
|
-
kind: kinds > 1
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
: reflectionMatches.length > 0
|
|
246
|
-
? "reflection"
|
|
247
|
-
: "observation",
|
|
248
|
-
reflections: recalledReflections,
|
|
249
|
-
reviews: recalledReviews,
|
|
250
|
-
observations: recalledObservations,
|
|
249
|
+
kind: kinds > 1 ? "mixed" : reviewMatches.length > 0 ? "review" : summaryMatches.length > 0 ? "summary" : "observation",
|
|
250
|
+
summaries,
|
|
251
|
+
reviews,
|
|
252
|
+
observations,
|
|
251
253
|
sourceEntries,
|
|
252
254
|
missingSourceEntryIds,
|
|
253
255
|
nonSourceEntryIds,
|
|
254
|
-
|
|
256
|
+
missingSourceMemoryIds,
|
|
255
257
|
collision: matchCount > 1,
|
|
256
|
-
partial: missingSourceEntryIds.length > 0 || nonSourceEntryIds.length > 0 ||
|
|
258
|
+
partial: missingSourceEntryIds.length > 0 || nonSourceEntryIds.length > 0 || missingSourceMemoryIds.length > 0,
|
|
257
259
|
};
|
|
258
260
|
}
|