feat(coding-agent): made memory.backend the authoritative selector for memory backends

- Updated runtime memory config loading and backend resolution so `memory.backend === "local"` now enables the local pipeline directly, while `memories.enabled` is treated only as a legacy migration input.
- Switched the `memory.backend` default to `off` and kept `memories.enabled` hidden from the Memory tab UI for migration compatibility.
- Adjusted memory runtime, resolver, and documentation/tests to match the new backend-selection semantics.
This commit is contained in:
can1357
2026-05-03 06:44:14 +02:00
parent 7784717935
commit d57275fb5d
7 changed files with 19 additions and 33 deletions
+3 -5
View File
@@ -17,11 +17,9 @@ Package-specific references:
The agent supports three mutually-exclusive memory backends, selected via the `memory.backend` setting (Settings → Memory tab, or `~/.omp/config.yml`):
|Backend|Behaviour|
---|---|
`off`|No memory subsystem runs.|
`local` (default)|Existing rollout-summarisation pipeline. Writes `memory_summary.md` and consolidated artifacts under the agent dir. Additionally gated by the legacy `memories.enabled` boolean — set it to `true` to actually run.|
`hindsight`|Talks to a [Hindsight](https://hindsight.vectorize.io) server (Cloud or self-hosted Docker). Retains transcripts every Nth user turn, recalls memories on the first turn of a session, and exposes three tools: `hindsight_retain`, `hindsight_recall`, `hindsight_reflect`.|
- `off` (default) — no memory subsystem runs.
- `local` — existing rollout-summarisation pipeline; writes `memory_summary.md` and consolidated artifacts under the agent dir.
- `hindsight` — talks to a [Hindsight](https://hindsight.vectorize.io) server (Cloud or self-hosted Docker), retains transcripts every Nth user turn, recalls memories on the first turn of a session, and exposes `hindsight_retain`, `hindsight_recall`, and `hindsight_reflect`.
### Hindsight quickstart
@@ -879,14 +879,11 @@ export const SETTINGS_SCHEMA = {
"branchSummary.reserveTokens": { type: "number", default: 16384 },
// Memories
// Legacy local-memory enable flag kept only for back-compat migration.
// Hidden from UI — users should use `memory.backend` instead.
"memories.enabled": {
type: "boolean",
default: false,
ui: {
tab: "memory",
label: "Memories",
description: "Enable autonomous memory extraction and consolidation",
},
},
"memories.maxRolloutsPerStartup": { type: "number", default: 64 },
@@ -925,11 +922,11 @@ export const SETTINGS_SCHEMA = {
"memory.backend": {
type: "enum",
values: ["off", "local", "hindsight"] as const,
default: "local",
default: "off",
ui: {
tab: "memory",
label: "Memory Backend",
description: "Local memory pipeline, Hindsight remote memory, or off",
description: "Off, local memory pipeline, or Hindsight remote memory",
submenu: true,
},
},
+1 -1
View File
@@ -1077,7 +1077,7 @@ async function resolveMemoryModel(options: {
function loadMemoryConfig(settings: Settings): MemoryRuntimeConfig {
return {
enabled: settings.get("memories.enabled") ?? DEFAULTS.enabled,
enabled: settings.get("memory.backend") === "local" || settings.get("memories.enabled") === true,
maxRolloutsPerStartup: settings.get("memories.maxRolloutsPerStartup") ?? DEFAULTS.maxRolloutsPerStartup,
maxRolloutAgeDays: settings.get("memories.maxRolloutAgeDays") ?? DEFAULTS.maxRolloutAgeDays,
minRolloutIdleHours: settings.get("memories.minRolloutIdleHours") ?? DEFAULTS.minRolloutIdleHours,
@@ -3,9 +3,7 @@ import type { MemoryBackend } from "./types";
/**
* No-op memory backend.
*
* Selected when `memory.backend` is `"off"`, or when `"local"` is selected but
* `memories.enabled` is false (preserves the historical "memories disabled by
* default" behaviour without forcing users to flip both switches).
* Selected when `memory.backend` is `"off"`.
*/
export const offBackend: MemoryBackend = {
id: "off",
@@ -10,16 +10,15 @@ import type { MemoryBackend } from "./types";
* Selection rules (single source of truth — every memory consumer routes
* through this):
* - `memory.backend === "hindsight"` → Hindsight remote memory
* - `memory.backend === "local"` and `memories.enabled === true` → local pipeline
* - everything else → no-op
* - `memory.backend === "local"` → local pipeline
* - everything else → no-op
*
* The legacy `memories.enabled` boolean still gates the local backend so users
* who have it set to `false` keep getting silence, even after the new enum
* defaults to `"local"`.
* `memories.enabled` remains accepted only as a legacy migration input. Once
* a config is loaded, `memory.backend` is the sole runtime selector.
*/
export function resolveMemoryBackend(settings: Settings): MemoryBackend {
const id = settings.get("memory.backend");
if (id === "hindsight") return hindsightBackend;
if (id === "local" && settings.get("memories.enabled")) return localBackend;
if (id === "local") return localBackend;
return offBackend;
}
@@ -349,7 +349,7 @@ describe("buildMemoryToolDeveloperInstructions", () => {
test("returns undefined for missing or empty summaries", async () => {
const agentDir = await makeTempDir("memories-runtime-instructions");
const settings = Settings.isolated({ "memories.enabled": true });
const settings = Settings.isolated({ "memory.backend": "local" });
expect(await buildMemoryToolDeveloperInstructions(agentDir, settings)).toBeUndefined();
@@ -362,7 +362,7 @@ describe("buildMemoryToolDeveloperInstructions", () => {
test("renders payload with truncation for non-empty summary", async () => {
const agentDir = await makeTempDir("memories-runtime-instructions");
const settings = Settings.isolated({
"memories.enabled": true,
"memory.backend": "local",
"memories.summaryInjectionTokenLimit": 8,
});
const memoryRoot = getMemoryRoot(agentDir, settings.getCwd());
@@ -16,18 +16,12 @@ describe("resolveMemoryBackend", () => {
expect(resolveMemoryBackend(settings).id).toBe("off");
});
it("returns the local backend when memory.backend is local AND memories.enabled is true", () => {
const settings = Settings.isolated({ "memory.backend": "local", "memories.enabled": true });
it("returns the local backend when memory.backend is local", () => {
const settings = Settings.isolated({ "memory.backend": "local", "memories.enabled": false });
expect(resolveMemoryBackend(settings).id).toBe("local");
});
it("returns the off backend when memory.backend is local but memories.enabled is false", () => {
const settings = Settings.isolated({ "memory.backend": "local", "memories.enabled": false });
// Falls back to off so existing users with memories.enabled=false keep silence.
expect(resolveMemoryBackend(settings).id).toBe("off");
});
it("returns the hindsight backend when memory.backend is hindsight, regardless of memories.enabled", () => {
it("returns the hindsight backend when memory.backend is hindsight, regardless of legacy memories.enabled", () => {
const a = Settings.isolated({ "memory.backend": "hindsight", "memories.enabled": false });
const b = Settings.isolated({ "memory.backend": "hindsight", "memories.enabled": true });
expect(resolveMemoryBackend(a).id).toBe("hindsight");