Make memory more resilient to failure

This commit is contained in:
Vignesh Natarajan
2026-01-27 22:17:56 -08:00
parent 1cb4af074f
commit 4e8000e745
8 changed files with 26 additions and 10 deletions

View File

@@ -126,7 +126,7 @@ out to QMD for retrieval. Key points:
- `paths[]`: add extra directories/files (`path`, optional `pattern`, optional
stable `name`).
- `sessions`: opt into session JSONL indexing (`enabled`, `retentionDays`,
`exportDir`, `redactToolOutputs`—defaults to redacting tool payloads).
`exportDir`).
- `update`: controls refresh cadence (`interval`, `debounceMs`, `onBoot`).
- `limits`: clamp recall payload (`maxResults`, `maxSnippetChars`,
`maxInjectedChars`, `timeoutMs`).

View File

@@ -264,7 +264,6 @@ const FIELD_LABELS: Record<string, string> = {
"memory.qmd.sessions.enabled": "QMD Session Indexing",
"memory.qmd.sessions.exportDir": "QMD Session Export Directory",
"memory.qmd.sessions.retentionDays": "QMD Session Retention (days)",
"memory.qmd.sessions.redactToolOutputs": "QMD Session Tool Redaction",
"memory.qmd.update.interval": "QMD Update Interval",
"memory.qmd.update.debounceMs": "QMD Update Debounce (ms)",
"memory.qmd.update.onBoot": "QMD Update on Startup",
@@ -576,8 +575,6 @@ const FIELD_HELP: Record<string, string> = {
"Override directory for sanitized session exports before indexing.",
"memory.qmd.sessions.retentionDays":
"Retention window for exported sessions before pruning (default: unlimited).",
"memory.qmd.sessions.redactToolOutputs":
"Strip tool call payloads/results when exporting sessions (default: true).",
"memory.qmd.update.interval":
"How often the QMD sidecar refreshes indexes (duration string, default: 5m).",
"memory.qmd.update.debounceMs":

View File

@@ -29,7 +29,6 @@ export type MemoryQmdSessionConfig = {
enabled?: boolean;
exportDir?: string;
retentionDays?: number;
redactToolOutputs?: boolean;
};
export type MemoryQmdUpdateConfig = {

View File

@@ -45,7 +45,6 @@ const MemoryQmdSessionSchema = z
enabled: z.boolean().optional(),
exportDir: z.string().optional(),
retentionDays: z.number().int().nonnegative().optional(),
redactToolOutputs: z.boolean().optional(),
})
.strict();

View File

@@ -42,7 +42,6 @@ export type ResolvedQmdSessionConfig = {
enabled: boolean;
exportDir?: string;
retentionDays?: number;
redactToolOutputs: boolean;
};
export type ResolvedQmdConfig = {
@@ -147,12 +146,10 @@ function resolveSessionConfig(
const exportDir = exportDirRaw ? resolvePath(exportDirRaw, workspaceDir) : undefined;
const retentionDays =
cfg?.retentionDays && cfg.retentionDays > 0 ? Math.floor(cfg.retentionDays) : undefined;
const redactToolOutputs = cfg?.redactToolOutputs !== false;
return {
enabled,
exportDir,
retentionDays,
redactToolOutputs,
};
}

View File

@@ -225,7 +225,7 @@ export class QmdMemoryManager implements MemorySearchManager {
source: doc.source,
});
}
return results.slice(0, limit);
return this.clampResultsByInjectedChars(results.slice(0, limit));
}
async sync(params?: {
@@ -609,4 +609,24 @@ export class QmdMemoryManager implements MemorySearchManager {
const next = candidate.endsWith(path.sep) ? candidate : `${candidate}${path.sep}`;
return next.startsWith(normalizedRoot);
}
private clampResultsByInjectedChars(results: MemorySearchResult[]): MemorySearchResult[] {
const budget = this.qmd.limits.maxInjectedChars;
if (!budget || budget <= 0) return results;
let remaining = budget;
const clamped: MemorySearchResult[] = [];
for (const entry of results) {
if (remaining <= 0) break;
const snippet = entry.snippet ?? "";
if (snippet.length <= remaining) {
clamped.push(entry);
remaining -= snippet.length;
} else {
const trimmed = snippet.slice(0, Math.max(0, remaining));
clamped.push({ ...entry, snippet: trimmed });
break;
}
}
return clamped;
}
}

View File

@@ -111,10 +111,12 @@ class FallbackMemoryManager implements MemorySearchManager {
return this.deps.primary.status();
}
const fallbackStatus = this.fallback?.status();
const fallbackInfo = { from: "qmd", reason: this.lastError ?? "unknown" };
if (fallbackStatus) {
const custom = fallbackStatus.custom ?? {};
return {
...fallbackStatus,
fallback: fallbackInfo,
custom: {
...custom,
fallback: { disabled: true, reason: this.lastError ?? "unknown" },
@@ -125,6 +127,7 @@ class FallbackMemoryManager implements MemorySearchManager {
const custom = primaryStatus.custom ?? {};
return {
...primaryStatus,
fallback: fallbackInfo,
custom: {
...custom,
fallback: { disabled: true, reason: this.lastError ?? "unknown" },

View File

@@ -27,6 +27,7 @@ export type MemoryProviderStatus = {
workspaceDir?: string;
dbPath?: string;
sources?: MemorySource[];
sourceCounts?: Array<{ source: MemorySource; files: number; chunks: number }>;
cache?: { enabled: boolean; entries?: number; maxEntries?: number };
fts?: { enabled: boolean; available: boolean; error?: string };
fallback?: { from: string; reason?: string };