Skip to content
Open
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
4 changes: 4 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
} from "./src/utils/clean-context-runner.js";
import { SessionFilter } from "./src/utils/session-filter.js";
import { LocalMemoryCleaner } from "./src/utils/memory-cleaner.js";
import { CheckpointManager } from "./src/utils/checkpoint.js";
import { registerMemoryTdaiCli } from "./src/cli/index.js";
import { initDataDirectories, resetStores } from "./src/utils/pipeline-factory.js";
import { getOrCreateInstanceId, initReporter, report, resetReporter } from "./src/core/report/reporter.js";
Expand Down Expand Up @@ -269,6 +270,7 @@ export default function register(api: OpenClawPluginApi) {
config: cfg,
sessionFilter,
});
const checkpointManager = new CheckpointManager(pluginDataDir, api.logger);

// Initialize TdaiCore (async — store init, pipeline wiring)
const coreReady = core.initialize().then(() => {
Expand Down Expand Up @@ -305,10 +307,12 @@ export default function register(api: OpenClawPluginApi) {
retentionDays: cfg.memoryCleanup.retentionDays,
cleanTime: cfg.memoryCleanup.cleanTime,
logger: api.logger,
checkpointManager,
});
sharedMemoryCleaner.start();
api.logger.debug?.(`${TAG} Memory cleaner started (singleton)`);
} else {
sharedMemoryCleaner.setCheckpointManager(checkpointManager);
api.logger.debug?.(`${TAG} Memory cleaner already started in this process, reusing existing instance`);
}
memoryCleaner = sharedMemoryCleaner;
Expand Down
36 changes: 33 additions & 3 deletions src/core/tdai-core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -511,14 +511,44 @@ export class TdaiCore {
// Capture scheduler locally so TypeScript narrows inside the closure
// even after ``this.scheduler`` is re-assigned by handleSessionEnd.
const scheduler = this.scheduler;
const vectorStore = this.vectorStore;
const logger = this.logger;
const dataDir = this.dataDir;
this.schedulerStartPromise = (async () => {
try {
const checkpoint = new CheckpointManager(this.dataDir, this.logger);
const checkpoint = new CheckpointManager(dataDir, logger);

// Phase 1: Recalculate checkpoint to fix any counter drift
// This fixes counters AND clamps stale cursors before scheduler starts
try {
const storeCounts = vectorStore
? {
l0ConversationsCount: await vectorStore.countL0().catch(() => undefined),
totalMemoriesExtracted: await vectorStore.countL1().catch(() => undefined),
}
: undefined;

const result = await checkpoint.recalculate({ storeCounts });
logger.debug?.(
`${TAG} Checkpoint recalculated: l0=${result.l0_conversations_count}, ` +
`l1=${result.total_memories_extracted}, ` +
`runner_cursors=${result.runner_cursors_corrected}, ` +
`pipeline_cursors=${result.pipeline_cursors_corrected}`,
);
} catch (recalcErr) {
// Non-fatal: proceed with stale checkpoint rather than failing startup
logger.warn?.(
`${TAG} Checkpoint recalculation failed (proceeding with stale data): ` +
`${recalcErr instanceof Error ? recalcErr.message : String(recalcErr)}`,
);
}

// Phase 2: Start scheduler with recalculated pipeline states
const cp = await checkpoint.read();
scheduler.start(checkpoint.getAllPipelineStates(cp));
this.logger.debug?.(`${TAG} Scheduler started`);
logger.debug?.(`${TAG} Scheduler started`);
} catch (err) {
this.logger.error(`${TAG} Failed to restore checkpoint: ${err instanceof Error ? err.message : String(err)}`);
logger.error(`${TAG} Failed to restore checkpoint: ${err instanceof Error ? err.message : String(err)}`);
scheduler.start({});
}
})();
Expand Down
Loading