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:
@@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user