mirror of
https://github.com/openclaw/openclaw.git
synced 2026-02-19 18:39:20 -05:00
refactor(memory-neo4j): remove in-process auto sleep cycle, use system cron instead
Sleep cycle is now triggered by a system cron job (`0 3 * * *`) calling `openclaw memory neo4j sleep` rather than an in-process 6-hour interval timer with mutex. Simpler, more reliable, and easier to manage. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -474,14 +474,6 @@ describe("memoryNeo4jConfigSchema.parse", () => {
|
||||
expect(config.sleepCycle.auto).toBe(true);
|
||||
});
|
||||
|
||||
it("should default sleepCycle.autoIntervalMs to 6 hours (21600000)", () => {
|
||||
const config = memoryNeo4jConfigSchema.parse({
|
||||
neo4j: { uri: "bolt://localhost:7687", password: "" },
|
||||
embedding: { provider: "ollama" },
|
||||
});
|
||||
expect(config.sleepCycle.autoIntervalMs).toBe(21600000);
|
||||
});
|
||||
|
||||
it("should respect explicit sleepCycle.auto = false", () => {
|
||||
const config = memoryNeo4jConfigSchema.parse({
|
||||
neo4j: { uri: "bolt://localhost:7687", password: "" },
|
||||
@@ -491,33 +483,13 @@ describe("memoryNeo4jConfigSchema.parse", () => {
|
||||
expect(config.sleepCycle.auto).toBe(false);
|
||||
});
|
||||
|
||||
it("should respect explicit sleepCycle.autoIntervalMs", () => {
|
||||
it("should still accept autoIntervalMs without error (backwards compat)", () => {
|
||||
const config = memoryNeo4jConfigSchema.parse({
|
||||
neo4j: { uri: "bolt://localhost:7687", password: "" },
|
||||
embedding: { provider: "ollama" },
|
||||
sleepCycle: { autoIntervalMs: 3600000 },
|
||||
});
|
||||
expect(config.sleepCycle.autoIntervalMs).toBe(3600000);
|
||||
});
|
||||
|
||||
it("should throw when sleepCycle.autoIntervalMs is not positive", () => {
|
||||
expect(() =>
|
||||
memoryNeo4jConfigSchema.parse({
|
||||
neo4j: { uri: "bolt://localhost:7687", password: "" },
|
||||
embedding: { provider: "ollama" },
|
||||
sleepCycle: { autoIntervalMs: 0 },
|
||||
}),
|
||||
).toThrow("sleepCycle.autoIntervalMs must be positive");
|
||||
});
|
||||
|
||||
it("should throw when sleepCycle.autoIntervalMs is negative", () => {
|
||||
expect(() =>
|
||||
memoryNeo4jConfigSchema.parse({
|
||||
neo4j: { uri: "bolt://localhost:7687", password: "" },
|
||||
embedding: { provider: "ollama" },
|
||||
sleepCycle: { autoIntervalMs: -1000 },
|
||||
}),
|
||||
).toThrow("sleepCycle.autoIntervalMs must be positive");
|
||||
expect(config.sleepCycle.auto).toBe(true);
|
||||
});
|
||||
|
||||
it("should reject unknown sleepCycle keys", () => {
|
||||
|
||||
@@ -64,7 +64,6 @@ export type MemoryNeo4jConfig = {
|
||||
decayCurves: Record<string, { halfLifeDays: number }>;
|
||||
sleepCycle: {
|
||||
auto: boolean;
|
||||
autoIntervalMs: number;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -359,15 +358,6 @@ export const memoryNeo4jConfigSchema = {
|
||||
const sleepCycleRaw = cfg.sleepCycle as Record<string, unknown> | undefined;
|
||||
assertAllowedKeys(sleepCycleRaw ?? {}, ["auto", "autoIntervalMs"], "sleepCycle config");
|
||||
const sleepCycleAuto = sleepCycleRaw?.auto !== false; // enabled by default
|
||||
const sleepCycleAutoIntervalMs =
|
||||
typeof sleepCycleRaw?.autoIntervalMs === "number"
|
||||
? sleepCycleRaw.autoIntervalMs
|
||||
: 6 * 60 * 60 * 1000; // 6 hours
|
||||
if (sleepCycleAutoIntervalMs <= 0) {
|
||||
throw new Error(
|
||||
`sleepCycle.autoIntervalMs must be positive, got: ${sleepCycleAutoIntervalMs}`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
neo4j: {
|
||||
@@ -401,7 +391,6 @@ export const memoryNeo4jConfigSchema = {
|
||||
decayCurves,
|
||||
sleepCycle: {
|
||||
auto: sleepCycleAuto,
|
||||
autoIntervalMs: sleepCycleAutoIntervalMs,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
@@ -32,7 +32,6 @@ import { isSemanticDuplicate, rateImportance } from "./extractor.js";
|
||||
import { extractUserMessages, extractAssistantMessages } from "./message-utils.js";
|
||||
import { Neo4jMemoryClient } from "./neo4j-client.js";
|
||||
import { hybridSearch } from "./search.js";
|
||||
import { runSleepCycle } from "./sleep-cycle.js";
|
||||
|
||||
// ============================================================================
|
||||
// Plugin Definition
|
||||
@@ -386,9 +385,6 @@ const memoryNeo4jPlugin = {
|
||||
const sessionLastSeen = new Map<string, number>();
|
||||
let lastTtlSweep = Date.now();
|
||||
|
||||
// Auto sleep cycle state
|
||||
let lastSleepCycleAt = 0;
|
||||
let sleepCycleRunning = false;
|
||||
const sleepAbortController = new AbortController();
|
||||
|
||||
/** Evict stale entries from session tracking maps older than SESSION_TTL_MS. */
|
||||
@@ -728,36 +724,6 @@ const memoryNeo4jPlugin = {
|
||||
extractionConfig,
|
||||
api.logger,
|
||||
);
|
||||
|
||||
// Auto sleep cycle: fire-and-forget if interval has elapsed
|
||||
if (
|
||||
cfg.sleepCycle.auto &&
|
||||
!sleepCycleRunning &&
|
||||
Date.now() - lastSleepCycleAt >= cfg.sleepCycle.autoIntervalMs
|
||||
) {
|
||||
sleepCycleRunning = true;
|
||||
void (async () => {
|
||||
try {
|
||||
api.logger.info("memory-neo4j: [auto-sleep] starting background sleep cycle");
|
||||
const t0 = Date.now();
|
||||
const result = await runSleepCycle(db, embeddings, extractionConfig, api.logger, {
|
||||
abortSignal: sleepAbortController.signal,
|
||||
decayCurves: Object.keys(cfg.decayCurves).length > 0 ? cfg.decayCurves : undefined,
|
||||
});
|
||||
lastSleepCycleAt = Date.now();
|
||||
api.logger.info(
|
||||
`memory-neo4j: [auto-sleep] complete in ${((Date.now() - t0) / 1000).toFixed(1)}s` +
|
||||
` — dedup=${result.dedup.memoriesMerged}, extracted=${result.extraction.succeeded},` +
|
||||
` decayed=${result.decay.memoriesPruned}, credentials=${result.credentialScan.credentialsFound}` +
|
||||
(result.aborted ? " (aborted)" : ""),
|
||||
);
|
||||
} catch (err) {
|
||||
api.logger.warn(`memory-neo4j: [auto-sleep] failed: ${String(err)}`);
|
||||
} finally {
|
||||
sleepCycleRunning = false;
|
||||
}
|
||||
})();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -82,11 +82,7 @@
|
||||
},
|
||||
"sleepCycle.auto": {
|
||||
"label": "Auto Sleep Cycle",
|
||||
"help": "Automatically run memory consolidation (dedup, extraction, decay) in the background (default: on)"
|
||||
},
|
||||
"sleepCycle.autoIntervalMs": {
|
||||
"label": "Sleep Cycle Interval (ms)",
|
||||
"help": "Minimum time between automatic sleep cycles (default: 6 hours = 21600000)"
|
||||
"help": "Automatically run memory consolidation (dedup, extraction, decay) daily at 3:00 AM local time (default: on)"
|
||||
}
|
||||
},
|
||||
"configSchema": {
|
||||
@@ -201,8 +197,7 @@
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"auto": { "type": "boolean" },
|
||||
"autoIntervalMs": { "type": "number", "minimum": 60000 }
|
||||
"auto": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user