peon-mem 1.0.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/LICENSE +21 -0
- package/README.md +301 -0
- package/bin/peon-mem.mjs +273 -0
- package/dist/brain.d.ts +72 -0
- package/dist/brain.js +224 -0
- package/dist/compression.d.ts +9 -0
- package/dist/compression.js +37 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +99 -0
- package/dist/daemon-cli.d.ts +2 -0
- package/dist/daemon-cli.js +54 -0
- package/dist/daemon.d.ts +23 -0
- package/dist/daemon.js +1078 -0
- package/dist/embedding-store.d.ts +43 -0
- package/dist/embedding-store.js +169 -0
- package/dist/embeddings.d.ts +93 -0
- package/dist/embeddings.js +345 -0
- package/dist/entities.d.ts +61 -0
- package/dist/entities.js +191 -0
- package/dist/entity-extraction.d.ts +33 -0
- package/dist/entity-extraction.js +75 -0
- package/dist/eval-metrics.d.ts +27 -0
- package/dist/eval-metrics.js +50 -0
- package/dist/evaluation.d.ts +58 -0
- package/dist/evaluation.js +244 -0
- package/dist/global-extraction.d.ts +15 -0
- package/dist/global-extraction.js +61 -0
- package/dist/global-memory.d.ts +43 -0
- package/dist/global-memory.js +306 -0
- package/dist/global-promotion.d.ts +25 -0
- package/dist/global-promotion.js +29 -0
- package/dist/hyde.d.ts +31 -0
- package/dist/hyde.js +46 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +246 -0
- package/dist/injection.d.ts +38 -0
- package/dist/injection.js +133 -0
- package/dist/logger.d.ts +17 -0
- package/dist/logger.js +63 -0
- package/dist/memory-mutations.d.ts +24 -0
- package/dist/memory-mutations.js +57 -0
- package/dist/memory-store.d.ts +194 -0
- package/dist/memory-store.js +1205 -0
- package/dist/monitor.d.ts +13 -0
- package/dist/monitor.js +977 -0
- package/dist/overview.d.ts +73 -0
- package/dist/overview.js +104 -0
- package/dist/processor.d.ts +90 -0
- package/dist/processor.js +450 -0
- package/dist/quality.d.ts +86 -0
- package/dist/quality.js +338 -0
- package/dist/recuration.d.ts +13 -0
- package/dist/recuration.js +65 -0
- package/dist/reranker.d.ts +34 -0
- package/dist/reranker.js +89 -0
- package/dist/retrieval.d.ts +106 -0
- package/dist/retrieval.js +392 -0
- package/dist/session-index.d.ts +34 -0
- package/dist/session-index.js +87 -0
- package/dist/temporal.d.ts +20 -0
- package/dist/temporal.js +62 -0
- package/dist/token-ab-monitor.d.ts +1 -0
- package/dist/token-ab-monitor.js +7 -0
- package/dist/tools.d.ts +232 -0
- package/dist/tools.js +546 -0
- package/dist/types.d.ts +169 -0
- package/dist/types.js +1 -0
- package/docs/assets/neural-universe.png +0 -0
- package/package.json +57 -0
- package/scripts/claude-peon-hook.mjs +522 -0
- package/scripts/codex-peon-hook.mjs +4 -0
- package/scripts/eval-retrieval-labeled.mjs +135 -0
- package/scripts/eval-retrieval.mjs +96 -0
- package/scripts/evaluate-peon.mjs +47 -0
- package/scripts/install-peon-stl.mjs +82 -0
- package/scripts/install-peon.mjs +318 -0
- package/scripts/lib/eval-ledger.mjs +104 -0
- package/scripts/lib/stl-classify.mjs +44 -0
- package/scripts/longmemeval-eval.mjs +144 -0
- package/scripts/peon-report.mjs +155 -0
- package/scripts/peon-stl.mjs +506 -0
- package/scripts/token-ab-monitor.html +235 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { MemoryRecord } from "./types.js";
|
|
2
|
+
import type { SelectedInjectionMetadata } from "./injection.js";
|
|
3
|
+
/**
|
|
4
|
+
* Pure assembly helpers for the cockpit Overview. The daemon fetches the raw
|
|
5
|
+
* material (project records, global records, the A/B token log, the last
|
|
6
|
+
* injection) and these functions distill it into the at-a-glance payload.
|
|
7
|
+
*/
|
|
8
|
+
export interface BeliefCounts {
|
|
9
|
+
active: number;
|
|
10
|
+
superseded: number;
|
|
11
|
+
conflicts: number;
|
|
12
|
+
stale: number;
|
|
13
|
+
archived: number;
|
|
14
|
+
summaries: number;
|
|
15
|
+
pinned: number;
|
|
16
|
+
total: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function summarizeBeliefs(records: readonly MemoryRecord[]): BeliefCounts;
|
|
19
|
+
export interface ProjectSummary {
|
|
20
|
+
projectPath: string;
|
|
21
|
+
active: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Drop accidental subdirectory brains: a project nested inside another known
|
|
25
|
+
* project that holds NO active beliefs is an empty `.peon` from a session that
|
|
26
|
+
* ran with a subdir as cwd. Real (non-empty) nested projects are kept — we never
|
|
27
|
+
* merge memory, only hide empty noise.
|
|
28
|
+
*/
|
|
29
|
+
export declare function filterStrayProjects<T extends ProjectSummary>(projects: readonly T[]): T[];
|
|
30
|
+
export interface DuplicatePair {
|
|
31
|
+
aId: string;
|
|
32
|
+
aContent: string;
|
|
33
|
+
bId: string;
|
|
34
|
+
bContent: string;
|
|
35
|
+
similarity: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Flag near-duplicate ACTIVE beliefs of the same type — a nudge for the user to
|
|
39
|
+
* merge, not an automatic action. Conservative threshold to avoid false alarms.
|
|
40
|
+
*/
|
|
41
|
+
export declare function detectDuplicates(records: readonly MemoryRecord[], options?: {
|
|
42
|
+
threshold?: number;
|
|
43
|
+
limit?: number;
|
|
44
|
+
}): DuplicatePair[];
|
|
45
|
+
export interface TokenSavings {
|
|
46
|
+
onAvg: number;
|
|
47
|
+
offAvg: number;
|
|
48
|
+
onSessions: number;
|
|
49
|
+
offSessions: number;
|
|
50
|
+
savedPerSession: number;
|
|
51
|
+
}
|
|
52
|
+
export interface AbTokenRecord {
|
|
53
|
+
projectPath?: string;
|
|
54
|
+
peonEnabled?: boolean;
|
|
55
|
+
totalTokens?: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Compare Peon-on vs Peon-off session token totals for a project. Returns null
|
|
59
|
+
* until BOTH arms have at least one session — a savings claim needs a baseline.
|
|
60
|
+
*/
|
|
61
|
+
export declare function computeTokenSavings(records: readonly AbTokenRecord[], projectPath: string): TokenSavings | null;
|
|
62
|
+
export interface InjectionItem {
|
|
63
|
+
id: string;
|
|
64
|
+
scope: string;
|
|
65
|
+
type: string;
|
|
66
|
+
content: string;
|
|
67
|
+
score: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Turn the injection's selected-id metadata into displayable items by joining
|
|
71
|
+
* back to the records' content (project + global). Ids with no match are dropped.
|
|
72
|
+
*/
|
|
73
|
+
export declare function enrichInjection(selected: readonly SelectedInjectionMetadata[], records: readonly MemoryRecord[]): InjectionItem[];
|
package/dist/overview.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export function summarizeBeliefs(records) {
|
|
2
|
+
const counts = { active: 0, superseded: 0, conflicts: 0, stale: 0, archived: 0, summaries: 0, pinned: 0, total: records.length };
|
|
3
|
+
for (const record of records) {
|
|
4
|
+
if (record.status === "active")
|
|
5
|
+
counts.active += 1;
|
|
6
|
+
else if (record.status === "superseded")
|
|
7
|
+
counts.superseded += 1;
|
|
8
|
+
else if (record.status === "conflicted")
|
|
9
|
+
counts.conflicts += 1;
|
|
10
|
+
else if (record.status === "stale")
|
|
11
|
+
counts.stale += 1;
|
|
12
|
+
else if (record.status === "archived")
|
|
13
|
+
counts.archived += 1;
|
|
14
|
+
if (record.summaryOf)
|
|
15
|
+
counts.summaries += 1;
|
|
16
|
+
if (record.pinned)
|
|
17
|
+
counts.pinned += 1;
|
|
18
|
+
}
|
|
19
|
+
return counts;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Drop accidental subdirectory brains: a project nested inside another known
|
|
23
|
+
* project that holds NO active beliefs is an empty `.peon` from a session that
|
|
24
|
+
* ran with a subdir as cwd. Real (non-empty) nested projects are kept — we never
|
|
25
|
+
* merge memory, only hide empty noise.
|
|
26
|
+
*/
|
|
27
|
+
export function filterStrayProjects(projects) {
|
|
28
|
+
const paths = projects.map((p) => p.projectPath);
|
|
29
|
+
return projects.filter((p) => {
|
|
30
|
+
const nested = paths.some((other) => other !== p.projectPath && p.projectPath.startsWith(other + "/"));
|
|
31
|
+
return !(nested && p.active === 0);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function wordSet(text) {
|
|
35
|
+
return new Set(text
|
|
36
|
+
.toLowerCase()
|
|
37
|
+
.split(/[^a-z0-9]+/)
|
|
38
|
+
.filter((word) => word.length >= 3));
|
|
39
|
+
}
|
|
40
|
+
function jaccard(a, b) {
|
|
41
|
+
if (a.size === 0 || b.size === 0)
|
|
42
|
+
return 0;
|
|
43
|
+
let shared = 0;
|
|
44
|
+
for (const word of a)
|
|
45
|
+
if (b.has(word))
|
|
46
|
+
shared += 1;
|
|
47
|
+
return shared / (a.size + b.size - shared);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Flag near-duplicate ACTIVE beliefs of the same type — a nudge for the user to
|
|
51
|
+
* merge, not an automatic action. Conservative threshold to avoid false alarms.
|
|
52
|
+
*/
|
|
53
|
+
export function detectDuplicates(records, options = {}) {
|
|
54
|
+
const threshold = options.threshold ?? 0.6;
|
|
55
|
+
const limit = options.limit ?? 5;
|
|
56
|
+
const active = records.filter((record) => record.status === "active");
|
|
57
|
+
const sets = active.map((record) => wordSet(record.content));
|
|
58
|
+
const pairs = [];
|
|
59
|
+
for (let i = 0; i < active.length; i += 1) {
|
|
60
|
+
for (let j = i + 1; j < active.length; j += 1) {
|
|
61
|
+
if (active[i].type !== active[j].type)
|
|
62
|
+
continue;
|
|
63
|
+
const similarity = jaccard(sets[i], sets[j]);
|
|
64
|
+
if (similarity >= threshold) {
|
|
65
|
+
pairs.push({
|
|
66
|
+
aId: active[i].id,
|
|
67
|
+
aContent: active[i].content,
|
|
68
|
+
bId: active[j].id,
|
|
69
|
+
bContent: active[j].content,
|
|
70
|
+
similarity: Math.round(similarity * 100) / 100
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return pairs.sort((left, right) => right.similarity - left.similarity).slice(0, limit);
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Compare Peon-on vs Peon-off session token totals for a project. Returns null
|
|
79
|
+
* until BOTH arms have at least one session — a savings claim needs a baseline.
|
|
80
|
+
*/
|
|
81
|
+
export function computeTokenSavings(records, projectPath) {
|
|
82
|
+
const forProject = records.filter((record) => record.projectPath === projectPath);
|
|
83
|
+
const on = forProject.filter((record) => record.peonEnabled === true).map((record) => record.totalTokens ?? 0);
|
|
84
|
+
const off = forProject.filter((record) => record.peonEnabled === false).map((record) => record.totalTokens ?? 0);
|
|
85
|
+
if (on.length === 0 || off.length === 0)
|
|
86
|
+
return null;
|
|
87
|
+
const avg = (values) => Math.round(values.reduce((sum, value) => sum + value, 0) / values.length);
|
|
88
|
+
const onAvg = avg(on);
|
|
89
|
+
const offAvg = avg(off);
|
|
90
|
+
return { onAvg, offAvg, onSessions: on.length, offSessions: off.length, savedPerSession: offAvg - onAvg };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Turn the injection's selected-id metadata into displayable items by joining
|
|
94
|
+
* back to the records' content (project + global). Ids with no match are dropped.
|
|
95
|
+
*/
|
|
96
|
+
export function enrichInjection(selected, records) {
|
|
97
|
+
const byId = new Map(records.map((record) => [record.id, record]));
|
|
98
|
+
return selected.flatMap((item) => {
|
|
99
|
+
const record = byId.get(item.id);
|
|
100
|
+
if (!record)
|
|
101
|
+
return [];
|
|
102
|
+
return [{ id: item.id, scope: item.scope, type: item.type, content: record.content, score: Math.round(item.score * 100) / 100 }];
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { type PeonConfig } from "./config.js";
|
|
2
|
+
import type { ProcessedMemory } from "./types.js";
|
|
3
|
+
export interface MemoryModelResult {
|
|
4
|
+
content: string;
|
|
5
|
+
model: string;
|
|
6
|
+
estimatedTokens: number;
|
|
7
|
+
}
|
|
8
|
+
export interface MemoryModelClient {
|
|
9
|
+
processMemory(input: {
|
|
10
|
+
rawMemory: string;
|
|
11
|
+
existingMemory?: string;
|
|
12
|
+
config: PeonConfig;
|
|
13
|
+
reason: string;
|
|
14
|
+
}): Promise<MemoryModelResult>;
|
|
15
|
+
}
|
|
16
|
+
export interface ProcessMemoryInput {
|
|
17
|
+
projectPath: string;
|
|
18
|
+
reason?: string;
|
|
19
|
+
aiResult?: ProcessedMemory;
|
|
20
|
+
}
|
|
21
|
+
/** What a single consolidation run actually did — surfaced for observability. */
|
|
22
|
+
export interface ConsolidationStats {
|
|
23
|
+
operationsEmitted: number;
|
|
24
|
+
superseded: number;
|
|
25
|
+
obsoleted: number;
|
|
26
|
+
recordsAdded: number;
|
|
27
|
+
merged: number;
|
|
28
|
+
}
|
|
29
|
+
export interface ProcessMemoryResult {
|
|
30
|
+
status: "processed";
|
|
31
|
+
model: string;
|
|
32
|
+
estimatedTokens: number;
|
|
33
|
+
applied: ProcessedMemory;
|
|
34
|
+
stats: ConsolidationStats;
|
|
35
|
+
/** True when the raw delta was capped this run (a backlog chunk) — more remains to drain. */
|
|
36
|
+
capped: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface MaybeProcessMemoryInput {
|
|
39
|
+
projectPath: string;
|
|
40
|
+
trigger: string;
|
|
41
|
+
force?: boolean;
|
|
42
|
+
aiResult?: ProcessedMemory;
|
|
43
|
+
}
|
|
44
|
+
export interface ProcessingDecision {
|
|
45
|
+
action: "process" | "skip";
|
|
46
|
+
reason: "forced" | "threshold_reached" | "below_threshold" | "ai_disabled" | "missing_api_key" | "empty_memory";
|
|
47
|
+
trigger: string;
|
|
48
|
+
rawChars: number;
|
|
49
|
+
newChars: number;
|
|
50
|
+
flushMinChars: number;
|
|
51
|
+
estimatedTokens: number;
|
|
52
|
+
}
|
|
53
|
+
export type MaybeProcessMemoryResult = {
|
|
54
|
+
status: "processed";
|
|
55
|
+
decision: ProcessingDecision;
|
|
56
|
+
result: ProcessMemoryResult;
|
|
57
|
+
} | {
|
|
58
|
+
status: "skipped";
|
|
59
|
+
decision: ProcessingDecision;
|
|
60
|
+
};
|
|
61
|
+
export interface PeonMemoryProcessorOptions {
|
|
62
|
+
config?: PeonConfig;
|
|
63
|
+
modelClient?: MemoryModelClient;
|
|
64
|
+
}
|
|
65
|
+
export declare class PeonMemoryProcessor {
|
|
66
|
+
private readonly config;
|
|
67
|
+
private readonly modelClient;
|
|
68
|
+
constructor(options?: PeonMemoryProcessorOptions);
|
|
69
|
+
processMemory(input: ProcessMemoryInput): Promise<ProcessMemoryResult>;
|
|
70
|
+
maybeProcessMemory(input: MaybeProcessMemoryInput): Promise<MaybeProcessMemoryResult>;
|
|
71
|
+
}
|
|
72
|
+
export declare function decideProcessing(input: {
|
|
73
|
+
rawChars: number;
|
|
74
|
+
lastProcessedRawChars: number;
|
|
75
|
+
flushMinChars: number;
|
|
76
|
+
trigger: string;
|
|
77
|
+
force: boolean;
|
|
78
|
+
aiMode: PeonConfig["aiMode"];
|
|
79
|
+
hasApiKey: boolean;
|
|
80
|
+
hasManualAiResult: boolean;
|
|
81
|
+
}): ProcessingDecision;
|
|
82
|
+
export declare class OpenRouterMemoryModelClient implements MemoryModelClient {
|
|
83
|
+
processMemory(input: {
|
|
84
|
+
rawMemory: string;
|
|
85
|
+
existingMemory?: string;
|
|
86
|
+
config: PeonConfig;
|
|
87
|
+
reason: string;
|
|
88
|
+
}): Promise<MemoryModelResult>;
|
|
89
|
+
}
|
|
90
|
+
export declare function parseProcessedMemory(content: string): ProcessedMemory;
|