Skip to content

Commit 90ac526

Browse files
committed
fix(agent-memory): preserve live weight components on a partial config write
applyConfig rebuilt the weights vector from the constructor defaults each pass, so a config that set only one weight component silently reset the other two — a footgun for the proposal engine, which nudges a single knob. A partial write now starts from the live weights and overlays only what's present; a fully-absent weights config still falls back to the constructor values.
1 parent 1545179 commit 90ac526

2 files changed

Lines changed: 28 additions & 1 deletion

File tree

packages/agent-memory/src/MemoryStore.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,15 @@ export class MemoryStore {
147147

148148
private applyConfig(raw: Record<string, string>): void {
149149
let threshold = this.initialThreshold;
150-
const weights: RecallWeights = { ...this.initialWeights };
150+
// Weights are a partial update: if any component is in the config, start
151+
// from the LIVE weights and overlay only what's present, so tuning one knob
152+
// (the proposal engine's common case) doesn't reset the others. With no
153+
// weight field at all, fall back to the constructor values like the rest.
154+
const weightFieldPresent =
155+
raw['recall.weights.similarity'] !== undefined ||
156+
raw['recall.weights.recency'] !== undefined ||
157+
raw['recall.weights.importance'] !== undefined;
158+
const weights: RecallWeights = { ...(weightFieldPresent ? this.weights : this.initialWeights) };
151159
let halfLifeSeconds = this.initialHalfLifeSeconds;
152160
let maxItemsPerScope = this.initialMaxItemsPerScope;
153161

packages/agent-memory/src/__tests__/MemoryStore.config.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,25 @@ describe('MemoryStore config refresh', () => {
159159
expect(store.currentConfig().weights).toEqual(DEFAULT_WEIGHTS);
160160
});
161161

162+
it('preserves the live weight components when only a subset is written', async () => {
163+
let hash: Record<string, string> = {
164+
'recall.weights.similarity': '0.2',
165+
'recall.weights.recency': '0.7',
166+
'recall.weights.importance': '0.1',
167+
};
168+
const client = mockClient((command) => (command === 'HGETALL' ? configReply(hash) : 'OK'));
169+
const store = new MemoryStore({ client, name: 'mem', embedFn: fakeEmbed(8) });
170+
171+
await store.refreshConfig();
172+
expect(store.currentConfig().weights).toEqual({ similarity: 0.2, recency: 0.7, importance: 0.1 });
173+
174+
// A partial write that nudges only similarity must keep the live recency
175+
// and importance, not reset them to the constructor defaults.
176+
hash = { 'recall.weights.similarity': '0.5' };
177+
await store.refreshConfig();
178+
expect(store.currentConfig().weights).toEqual({ similarity: 0.5, recency: 0.7, importance: 0.1 });
179+
});
180+
162181
it('live-applies a looser threshold to recall', async () => {
163182
const client = mockClient((command) => {
164183
if (command === 'HGETALL') {

0 commit comments

Comments
 (0)