@ddwang/magnitude-core 0.3.1-ddwang.6 → 0.3.1-ddwang.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -325,7 +325,12 @@ export class Agent {
325
325
  ...(this.options.prompt ? [this.options.prompt] : []),
326
326
  ...(options.prompt ? [options.prompt] : []),
327
327
  ].join('\n');
328
- const taskMemory = options.memory ?? new AgentMemory({ ...this.memoryOptions, instructions: instructions === '' ? undefined : instructions });
328
+ const taskMemory = options.memory ?? new AgentMemory();
329
+ taskMemory.configure({
330
+ ...this.memoryOptions,
331
+ // Current prompts replace checkpoint instructions; omitted prompts preserve them.
332
+ ...(this.options.prompt != null || options.prompt !== undefined ? { instructions: instructions || null } : {}),
333
+ });
329
334
  if (Array.isArray(taskOrSteps)) {
330
335
  const steps = taskOrSteps;
331
336
  //this.events.emit('actStarted', steps.join(', '));
package/dist/index.cjs CHANGED
@@ -1555,6 +1555,15 @@ class AgentMemory {
1555
1555
  get instructions() {
1556
1556
  return this.options.instructions;
1557
1557
  }
1558
+ /** Apply current instructions and provider caching policy without replacing task evidence. */
1559
+ configure(options) {
1560
+ if (options.instructions !== void 0 && options.instructions !== this.options.instructions || options.promptCaching !== void 0 && options.promptCaching !== this.options.promptCaching) {
1561
+ this.freezeMask = void 0;
1562
+ this.cacheControlIndices = [];
1563
+ }
1564
+ if (options.instructions !== void 0) this.options.instructions = options.instructions;
1565
+ if (options.promptCaching !== void 0) this.options.promptCaching = options.promptCaching;
1566
+ }
1558
1567
  async render(options) {
1559
1568
  if (options?.history === "full") {
1560
1569
  const messages2 = [];
@@ -1657,14 +1666,16 @@ class AgentMemory {
1657
1666
  }
1658
1667
  const notes = this.notebook.toJSON();
1659
1668
  return {
1660
- // TODO: include other options as well
1661
- ...this.options.instructions ? { instructions: this.options.instructions } : {},
1669
+ ...this.options.instructions !== null ? { instructions: this.options.instructions } : {},
1662
1670
  ...notes.length ? { notes } : {},
1663
1671
  observations
1664
1672
  };
1665
1673
  }
1666
- // TODO: turn into class static method / rework cons
1674
+ /** Replace saved state atomically; runtime caching and thought limits stay with this instance. */
1667
1675
  async loadJSON(data) {
1676
+ if (data.instructions !== void 0 && typeof data.instructions !== "string") {
1677
+ throw new Error("Checkpoint instructions must be a string");
1678
+ }
1668
1679
  const observations = [];
1669
1680
  for (const observation of data.observations) {
1670
1681
  observations.push(new Observation(
@@ -1677,6 +1688,7 @@ class AgentMemory {
1677
1688
  }
1678
1689
  const notebook = new TaskNotebook();
1679
1690
  for (const note of data.notes ?? []) notebook.put(note, (id) => this.resolveNoteSource(observations, id));
1691
+ this.options.instructions = data.instructions ?? null;
1680
1692
  this.observations = observations;
1681
1693
  this.notebook = notebook;
1682
1694
  this.visibleSourceIds.clear();
@@ -4261,7 +4273,12 @@ class Agent {
4261
4273
  ...this.options.prompt ? [this.options.prompt] : [],
4262
4274
  ...options.prompt ? [options.prompt] : []
4263
4275
  ].join("\n");
4264
- const taskMemory = options.memory ?? new AgentMemory({ ...this.memoryOptions, instructions: instructions === "" ? void 0 : instructions });
4276
+ const taskMemory = options.memory ?? new AgentMemory();
4277
+ taskMemory.configure({
4278
+ ...this.memoryOptions,
4279
+ // Current prompts replace checkpoint instructions; omitted prompts preserve them.
4280
+ ...this.options.prompt != null || options.prompt !== void 0 ? { instructions: instructions || null } : {}
4281
+ });
4265
4282
  if (Array.isArray(taskOrSteps)) {
4266
4283
  const steps = taskOrSteps;
4267
4284
  await traceAsync("multistep", async (steps2, options2) => {
package/dist/index.d.cts CHANGED
@@ -455,6 +455,8 @@ declare class AgentMemory {
455
455
  private cacheControlIndices;
456
456
  constructor(options?: AgentMemoryOptions);
457
457
  get instructions(): string | null;
458
+ /** Apply current instructions and provider caching policy without replacing task evidence. */
459
+ configure(options: Pick<AgentMemoryOptions, 'instructions' | 'promptCaching'>): void;
458
460
  render(options?: MemoryRenderOptions): Promise<MultiMediaMessage[]>;
459
461
  simpleRender(): Promise<(Image$1 | string)[]>;
460
462
  private observationPrefix;
@@ -466,6 +468,7 @@ declare class AgentMemory {
466
468
  recordObservation(obs: Observation): void;
467
469
  getLastThoughtMessage(): string | null;
468
470
  toJSON(): Promise<SerializedAgentMemory>;
471
+ /** Replace saved state atomically; runtime caching and thought limits stay with this instance. */
469
472
  loadJSON(data: SerializedAgentMemory): Promise<void>;
470
473
  }
471
474
 
package/dist/index.mjs CHANGED
@@ -1532,6 +1532,15 @@ class AgentMemory {
1532
1532
  get instructions() {
1533
1533
  return this.options.instructions;
1534
1534
  }
1535
+ /** Apply current instructions and provider caching policy without replacing task evidence. */
1536
+ configure(options) {
1537
+ if (options.instructions !== void 0 && options.instructions !== this.options.instructions || options.promptCaching !== void 0 && options.promptCaching !== this.options.promptCaching) {
1538
+ this.freezeMask = void 0;
1539
+ this.cacheControlIndices = [];
1540
+ }
1541
+ if (options.instructions !== void 0) this.options.instructions = options.instructions;
1542
+ if (options.promptCaching !== void 0) this.options.promptCaching = options.promptCaching;
1543
+ }
1535
1544
  async render(options) {
1536
1545
  if (options?.history === "full") {
1537
1546
  const messages2 = [];
@@ -1634,14 +1643,16 @@ class AgentMemory {
1634
1643
  }
1635
1644
  const notes = this.notebook.toJSON();
1636
1645
  return {
1637
- // TODO: include other options as well
1638
- ...this.options.instructions ? { instructions: this.options.instructions } : {},
1646
+ ...this.options.instructions !== null ? { instructions: this.options.instructions } : {},
1639
1647
  ...notes.length ? { notes } : {},
1640
1648
  observations
1641
1649
  };
1642
1650
  }
1643
- // TODO: turn into class static method / rework cons
1651
+ /** Replace saved state atomically; runtime caching and thought limits stay with this instance. */
1644
1652
  async loadJSON(data) {
1653
+ if (data.instructions !== void 0 && typeof data.instructions !== "string") {
1654
+ throw new Error("Checkpoint instructions must be a string");
1655
+ }
1645
1656
  const observations = [];
1646
1657
  for (const observation of data.observations) {
1647
1658
  observations.push(new Observation(
@@ -1654,6 +1665,7 @@ class AgentMemory {
1654
1665
  }
1655
1666
  const notebook = new TaskNotebook();
1656
1667
  for (const note of data.notes ?? []) notebook.put(note, (id) => this.resolveNoteSource(observations, id));
1668
+ this.options.instructions = data.instructions ?? null;
1657
1669
  this.observations = observations;
1658
1670
  this.notebook = notebook;
1659
1671
  this.visibleSourceIds.clear();
@@ -4238,7 +4250,12 @@ class Agent {
4238
4250
  ...this.options.prompt ? [this.options.prompt] : [],
4239
4251
  ...options.prompt ? [options.prompt] : []
4240
4252
  ].join("\n");
4241
- const taskMemory = options.memory ?? new AgentMemory({ ...this.memoryOptions, instructions: instructions === "" ? void 0 : instructions });
4253
+ const taskMemory = options.memory ?? new AgentMemory();
4254
+ taskMemory.configure({
4255
+ ...this.memoryOptions,
4256
+ // Current prompts replace checkpoint instructions; omitted prompts preserve them.
4257
+ ...this.options.prompt != null || options.prompt !== void 0 ? { instructions: instructions || null } : {}
4258
+ });
4242
4259
  if (Array.isArray(taskOrSteps)) {
4243
4260
  const steps = taskOrSteps;
4244
4261
  await traceAsync("multistep", async (steps2, options2) => {
@@ -32,6 +32,8 @@ export declare class AgentMemory {
32
32
  private cacheControlIndices;
33
33
  constructor(options?: AgentMemoryOptions);
34
34
  get instructions(): string | null;
35
+ /** Apply current instructions and provider caching policy without replacing task evidence. */
36
+ configure(options: Pick<AgentMemoryOptions, 'instructions' | 'promptCaching'>): void;
35
37
  render(options?: MemoryRenderOptions): Promise<MultiMediaMessage[]>;
36
38
  simpleRender(): Promise<(BamlImage | string)[]>;
37
39
  private observationPrefix;
@@ -43,5 +45,6 @@ export declare class AgentMemory {
43
45
  recordObservation(obs: Observation): void;
44
46
  getLastThoughtMessage(): string | null;
45
47
  toJSON(): Promise<SerializedAgentMemory>;
48
+ /** Replace saved state atomically; runtime caching and thought limits stay with this instance. */
46
49
  loadJSON(data: SerializedAgentMemory): Promise<void>;
47
50
  }
@@ -32,6 +32,18 @@ export class AgentMemory {
32
32
  // why is this on memory? prob should just be on agent
33
33
  return this.options.instructions;
34
34
  }
35
+ /** Apply current instructions and provider caching policy without replacing task evidence. */
36
+ configure(options) {
37
+ if ((options.instructions !== undefined && options.instructions !== this.options.instructions)
38
+ || (options.promptCaching !== undefined && options.promptCaching !== this.options.promptCaching)) {
39
+ this.freezeMask = undefined;
40
+ this.cacheControlIndices = [];
41
+ }
42
+ if (options.instructions !== undefined)
43
+ this.options.instructions = options.instructions;
44
+ if (options.promptCaching !== undefined)
45
+ this.options.promptCaching = options.promptCaching;
46
+ }
35
47
  async render(options) {
36
48
  if (options?.history === 'full') {
37
49
  const messages = [];
@@ -145,32 +157,28 @@ export class AgentMemory {
145
157
  }
146
158
  const notes = this.notebook.toJSON();
147
159
  return {
148
- // TODO: include other options as well
149
- ...(this.options.instructions ? { instructions: this.options.instructions } : {}),
160
+ ...(this.options.instructions !== null ? { instructions: this.options.instructions } : {}),
150
161
  ...(notes.length ? { notes } : {}),
151
162
  observations: observations
152
163
  };
153
164
  }
154
- // TODO: turn into class static method / rework cons
165
+ /** Replace saved state atomically; runtime caching and thought limits stay with this instance. */
155
166
  async loadJSON(data) {
156
- //jsonToObservableData(data);
167
+ if (data.instructions !== undefined && typeof data.instructions !== 'string') {
168
+ throw new Error('Checkpoint instructions must be a string');
169
+ }
157
170
  const observations = [];
158
171
  for (const observation of data.observations) {
159
172
  observations.push(new Observation(observation.source, observation.role, await jsonToObservableData(observation.data), observation.options, observation.timestamp));
160
173
  }
161
- // nvm
162
- //this.instructions = this.instructions;
163
174
  const notebook = new TaskNotebook();
164
175
  for (const note of data.notes ?? [])
165
176
  notebook.put(note, id => this.resolveNoteSource(observations, id));
177
+ this.options.instructions = data.instructions ?? null;
166
178
  this.observations = observations;
167
179
  this.notebook = notebook;
168
180
  this.visibleSourceIds.clear();
169
181
  this.freezeMask = undefined;
170
182
  this.cacheControlIndices = [];
171
- // return {
172
- // ...(this.instructions ? { instructions: this.instructions } : {}),
173
- // observations: observations
174
- // };
175
183
  }
176
184
  }
@@ -9,6 +9,66 @@ const screen = (n) => Observation.fromConnector('fixture', {
9
9
  }, { type: 'screenshot', limit: 2, dedupe: true });
10
10
  const text = async (memory) => (await memory.render()).flatMap(message => message.content)
11
11
  .filter(part => typeof part === 'string').join('');
12
+ for (const instructions of ['Saved planner constraint', '', undefined]) {
13
+ for (const promptCaching of [false, true])
14
+ test(`checkpoint instructions round-trip with receiving runtime configuration: ${JSON.stringify(instructions)}, caching ${promptCaching}`, async () => {
15
+ const original = new AgentMemory({ instructions, promptCaching: !promptCaching });
16
+ original.recordObservation(Observation.fromConnector('fixture', 'Saved evidence'));
17
+ await original.render();
18
+ original.remember({ key: 'saved', text: 'Saved fact', sources: [0] });
19
+ const saved = JSON.parse(JSON.stringify(await original.toJSON()));
20
+ const restored = new AgentMemory({ instructions: 'Previous task instructions', promptCaching });
21
+ await restored.loadJSON(saved);
22
+ expect(restored.instructions).toBe(instructions ?? null);
23
+ expect(await restored.toJSON()).toEqual(saved);
24
+ expect((await restored.render()).some(message => message.cacheControl)).toBe(promptCaching);
25
+ });
26
+ }
27
+ test('changing runtime caching discards frozen retention without changing the audit', async () => {
28
+ const memory = new AgentMemory({ instructions: 'Original constraint', promptCaching: true });
29
+ memory.recordObservation(Observation.fromConnector('fixture', 'Old state', { type: 'state', limit: 1 }));
30
+ await memory.render();
31
+ memory.recordObservation(Observation.fromConnector('fixture', 'New state', { type: 'state', limit: 1 }));
32
+ expect(await text(memory)).toContain('Old state');
33
+ const saved = await memory.toJSON();
34
+ memory.configure({ promptCaching: false });
35
+ expect(await text(memory)).not.toContain('Old state');
36
+ expect((await memory.render()).every(message => !message.cacheControl)).toBe(true);
37
+ expect(await memory.toJSON()).toEqual(saved);
38
+ memory.configure({ promptCaching: true, instructions: 'Current constraint' });
39
+ expect((await memory.render()).some(message => message.cacheControl)).toBe(true);
40
+ memory.configure({ instructions: undefined, promptCaching: undefined });
41
+ expect(memory.instructions).toBe('Current constraint');
42
+ expect((await memory.render()).some(message => message.cacheControl)).toBe(true);
43
+ expect((await memory.toJSON()).observations).toEqual(saved.observations);
44
+ });
45
+ for (const instructions of ['Current constraint', '', null, 'Original constraint', undefined]) {
46
+ test(`same-policy instruction update invalidates frozen retention only when changed: ${JSON.stringify(instructions)}`, async () => {
47
+ const memory = new AgentMemory({ instructions: 'Original constraint', promptCaching: true });
48
+ memory.recordObservation(Observation.fromConnector('fixture', 'Old state', { type: 'state', limit: 1 }));
49
+ await memory.render();
50
+ await memory.render();
51
+ memory.remember({ key: 'saved', text: 'Retained fact', sources: [0] });
52
+ memory.recordObservation(Observation.fromConnector('fixture', 'New state', { type: 'state', limit: 1 }));
53
+ const saved = await memory.toJSON();
54
+ memory.configure({ instructions, promptCaching: true });
55
+ const changed = instructions !== undefined && instructions !== 'Original constraint';
56
+ const rendered = await text(memory);
57
+ expect(rendered.includes('Old state')).toBe(!changed);
58
+ expect(rendered).toContain('New state');
59
+ expect(rendered).toContain('Retained fact');
60
+ expect(memory.instructions).toBe(instructions === undefined ? 'Original constraint' : instructions);
61
+ const after = await memory.toJSON();
62
+ expect(after.observations).toEqual(saved.observations);
63
+ expect(after.notes).toEqual(saved.notes);
64
+ // Changed instructions reset the cache rotation; unchanged instructions preserve its cadence.
65
+ memory.recordObservation(Observation.fromConnector('fixture', 'Newest state', { type: 'state', limit: 1 }));
66
+ const next = await memory.render();
67
+ expect(JSON.stringify(next).includes('New state')).toBe(changed);
68
+ expect(JSON.stringify(next)).toContain('Newest state');
69
+ expect(next.some(message => message.cacheControl)).toBe(true);
70
+ });
71
+ }
12
72
  for (const promptCaching of [false, true])
13
73
  test(`current observations replace cached state without changing history or audit, caching ${promptCaching}`, async () => {
14
74
  const memory = new AgentMemory({ promptCaching });
@@ -142,11 +202,14 @@ test('loading legacy memory clears notes and visibility/cache state', async () =
142
202
  expect(await text(memory)).toContain('New task observation');
143
203
  });
144
204
  test('malformed checkpoint provenance does not replace current memory', async () => {
145
- const memory = new AgentMemory();
205
+ const memory = new AgentMemory({ instructions: 'Keep these instructions', promptCaching: true });
146
206
  memory.recordObservation(screen(0));
147
207
  const saved = await memory.toJSON();
148
- await expect(memory.loadJSON({ ...saved, notes: [{ key: 'bad', text: 'claim', sources: [50] }] })).rejects.toThrow();
208
+ await expect(memory.loadJSON({ ...saved, instructions: 42 })).rejects.toThrow('instructions must be a string');
209
+ expect(await memory.toJSON()).toEqual(saved);
210
+ await expect(memory.loadJSON({ ...saved, instructions: 'Rejected instructions', notes: [{ key: 'bad', text: 'claim', sources: [50] }] })).rejects.toThrow();
149
211
  expect(await memory.toJSON()).toEqual(saved);
212
+ expect((await memory.render()).some(message => message.cacheControl)).toBe(true);
150
213
  });
151
214
  for (const promptCaching of [false, true])
152
215
  test(`completed checks outlive the default thought window and can be corrected, caching ${promptCaching}`, async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ddwang/magnitude-core",
3
- "version": "0.3.1-ddwang.6",
3
+ "version": "0.3.1-ddwang.7",
4
4
  "description": "Magnitude e2e testing agent",
5
5
  "publishConfig": {
6
6
  "access": "public"