Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 0 additions & 88 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2052,94 +2052,6 @@ const memoryLanceDBProPlugin = {
}
);

// ========================================================================
// Memory Compaction (Progressive Summarization)
// ========================================================================

if (config.enableManagementTools) {
api.registerTool({
name: "memory_compact",
description:
"Consolidate semantically similar old memories into refined single entries " +
"(progressive summarization). Reduces noise and improves retrieval quality over time. " +
"Use dry_run:true first to preview the compaction plan without making changes.",
inputSchema: {
type: "object" as const,
properties: {
dry_run: {
type: "boolean",
description: "Preview clusters without writing changes. Default: false.",
},
min_age_days: {
type: "number",
description: "Only compact memories at least this many days old. Default: 7.",
},
similarity_threshold: {
type: "number",
description: "Cosine similarity threshold for clustering [0-1]. Default: 0.88.",
},
scopes: {
type: "array",
items: { type: "string" },
description: "Scope filter. Omit to compact all scopes.",
},
},
required: [],
},
execute: async (args: Record<string, unknown>) => {
const compactionCfg: CompactionConfig = {
enabled: true,
minAgeDays:
typeof args.min_age_days === "number"
? args.min_age_days
: (config.memoryCompaction?.minAgeDays ?? 7),
similarityThreshold:
typeof args.similarity_threshold === "number"
? Math.max(0, Math.min(1, args.similarity_threshold))
: (config.memoryCompaction?.similarityThreshold ?? 0.88),
minClusterSize: config.memoryCompaction?.minClusterSize ?? 2,
maxMemoriesToScan: config.memoryCompaction?.maxMemoriesToScan ?? 200,
dryRun: args.dry_run === true,
cooldownHours: config.memoryCompaction?.cooldownHours ?? 24,
};
const scopes =
Array.isArray(args.scopes) && args.scopes.length > 0
? (args.scopes as string[])
: undefined;

const result = await runCompaction(
store,
embedder,
compactionCfg,
scopes,
api.logger,
);

return {
content: [
{
type: "text",
text: JSON.stringify(
{
scanned: result.scanned,
clustersFound: result.clustersFound,
memoriesDeleted: result.memoriesDeleted,
memoriesCreated: result.memoriesCreated,
dryRun: result.dryRun,
summary: result.dryRun
? `Dry run: found ${result.clustersFound} cluster(s) in ${result.scanned} memories — no changes made.`
: `Compacted ${result.memoriesDeleted} memories into ${result.memoriesCreated} consolidated entries.`,
},
null,
2,
),
},
],
};
},
});
}

// Auto-compaction at gateway_start (if enabled, respects cooldown)
if (config.memoryCompaction?.enabled) {
api.on("gateway_start", () => {
Expand Down
40 changes: 29 additions & 11 deletions test/plugin-manifest-regression.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,17 @@ try {
plugin.register(api);
assert.equal(services.length, 1, "plugin should register its background service");
assert.equal(typeof api.hooks.agent_end, "function", "autoCapture should remain enabled by default");
assert.equal(api.hooks["command:new"], undefined, "sessionMemory should stay disabled by default");
// selfImprovement defaults to enabled (#391), so command:new will have
// the appendSelfImprovementNote hook. Verify sessionMemory's /new hook
// is NOT registered by checking that the hook (if any) is the
// self-improvement one, not the sessionMemory one.
if (api.hooks["command:new"] !== undefined) {
assert.equal(
api.hooks["command:new"].name,
"appendSelfImprovementNote",
"command:new hook should only be the self-improvement note, not sessionMemory",
);
}
await assert.doesNotReject(
services[0].stop(),
"service stop should not throw when no access tracker is configured",
Expand All @@ -172,11 +182,16 @@ try {
},
});
plugin.register(sessionDefaultApi);
assert.equal(
sessionDefaultApi.hooks["command:new"],
undefined,
"sessionMemory:{} should not implicitly enable the /new hook",
);
// selfImprovement defaults to enabled (#391), so command:new may carry the
// self-improvement hook. The key invariant is that sessionMemory:{} does NOT
// add its own /new hook.
if (sessionDefaultApi.hooks["command:new"] !== undefined) {
assert.equal(
sessionDefaultApi.hooks["command:new"].name,
"appendSelfImprovementNote",
"sessionMemory:{} should not implicitly enable its own /new hook",
);
}

const sessionEnabledApi = createMockApi({
dbPath: path.join(workDir, "db-session-enabled"),
Expand All @@ -197,11 +212,14 @@ try {
"function",
"sessionMemory.enabled=true should register the async before_reset hook",
);
assert.equal(
sessionEnabledApi.hooks["command:new"],
undefined,
"sessionMemory.enabled=true should not register the blocking command:new hook",
);
// Same as above: command:new may be the self-improvement hook, not sessionMemory's.
if (sessionEnabledApi.hooks["command:new"] !== undefined) {
assert.equal(
sessionEnabledApi.hooks["command:new"].name,
"appendSelfImprovementNote",
"sessionMemory.enabled=true should not register the blocking command:new hook",
);
}

const longText = `${"Long embedding payload. ".repeat(420)}tail`;
const threshold = 6000;
Expand Down
10 changes: 9 additions & 1 deletion test/session-summary-before-reset.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,15 @@ describe("systemSessionMemory before_reset", () => {
memoryLanceDBProPlugin.register(api);

assert.equal(typeof api.hooks.before_reset, "function");
assert.equal(api.hooks["command:new"], undefined);
// selfImprovement defaults to enabled (#391), so command:new may carry
// the self-improvement hook — verify sessionMemory didn't register its own.
if (api.hooks["command:new"] !== undefined) {
assert.equal(
api.hooks["command:new"].name,
"appendSelfImprovementNote",
"command:new hook should only be the self-improvement note, not sessionMemory",
);
}

await api.hooks.before_reset(
{
Expand Down
Loading