diff --git a/packages/agent-core-v2/src/agent/contextOps/contextOps.ts b/packages/agent-core-v2/src/agent/contextOps/contextOps.ts index dae051989..98bf8d0d7 100644 --- a/packages/agent-core-v2/src/agent/contextOps/contextOps.ts +++ b/packages/agent-core-v2/src/agent/contextOps/contextOps.ts @@ -16,7 +16,7 @@ import { createDecorator } from "#/_base/di"; import type { ContextMessage, PromptOrigin } from '#/agent/contextMemory'; export type ContextAppendArgs = readonly ContextMessage[]; -export type ContextAppendSystemReminderArgs = [message: ContextMessage]; +export type ContextAppendSystemReminderArgs = [message: string, origin: PromptOrigin]; export type ContextReplaceArgs = [index: number, message: ContextMessage]; /** @@ -41,7 +41,7 @@ export interface IAgentContextOpsService { /** * Append a `` message to the end of the history. - * Returns the raw message recorded by the wire operation. + * Returns the message inserted into history. */ appendSystemReminder(content: string, origin: PromptOrigin): ContextMessage; diff --git a/packages/agent-core-v2/src/agent/contextOps/contextOpsService.ts b/packages/agent-core-v2/src/agent/contextOps/contextOpsService.ts index ad534ba22..b40adb4ee 100644 --- a/packages/agent-core-v2/src/agent/contextOps/contextOpsService.ts +++ b/packages/agent-core-v2/src/agent/contextOps/contextOpsService.ts @@ -6,9 +6,9 @@ * (Infinity start) and mirror each message into the replay read model; * `remove` splices out each resolved target and drops the matching replay * messages; `clear` drops the whole history and cuts a new replay segment; - * legacy `replace` swaps one message in place for old migrated wire. Message - * content is offloaded to the blob store through each operation's blob - * selector. Bound at Agent scope. + * legacy `replace` swaps one message in place for old migrated wire. Structured + * message content is offloaded to the blob store through the operations that + * carry full messages. Bound at Agent scope. */ import { Disposable } from "#/_base/di"; @@ -63,17 +63,6 @@ function messageContentBlobTarget( }; } -function toSystemReminderMessage(message: ContextMessage): ContextMessage { - return { - ...message, - content: message.content.map((part) => - part.type === 'text' - ? { ...part, text: toSystemReminderText(part.text) } - : part, - ), - }; -} - function toSystemReminderText(content: string): string { const trimmed = content.trim(); if (trimmed.startsWith('') && trimmed.endsWith('')) { @@ -82,6 +71,20 @@ function toSystemReminderText(content: string): string { return `\n${trimmed}\n`; } +function toSystemReminderMessage(content: string, origin: PromptOrigin): ContextMessage { + return ensureMessageId({ + role: 'user', + content: [ + { + type: 'text', + text: toSystemReminderText(content), + }, + ], + toolCalls: [], + origin, + }); +} + export class AgentContextOpsService extends Disposable implements IAgentContextOpsService { declare readonly _serviceBrand: undefined; @@ -121,19 +124,15 @@ export class AgentContextOpsService extends Disposable implements IAgentContextO this.appendSystemReminderOperation = context.defineOperation({ type: 'append_system_reminder', - apply: (splice, message) => { - splice(Number.POSITIVE_INFINITY, 0, [toSystemReminderMessage(message)]); + apply: (splice, message, origin) => { + splice(Number.POSITIVE_INFINITY, 0, [toSystemReminderMessage(message, origin)]); }, - replay: (replay, message) => { - replay.push({ type: 'message', message: toSystemReminderMessage(message) }); + replay: (replay, message, origin) => { + replay.push({ + type: 'message', + message: toSystemReminderMessage(message, origin), + }); }, - blobs: ([message]) => [ - messageContentBlobTarget( - message, - (_current, next) => [next], - ([current]) => current, - ), - ], }); this.removeOperation = context.defineOperation({ @@ -187,19 +186,8 @@ export class AgentContextOpsService extends Disposable implements IAgentContextO } appendSystemReminder(content: string, origin: PromptOrigin): ContextMessage { - const message = ensureMessageId({ - role: 'user', - content: [ - { - type: 'text', - text: content.trim(), - }, - ], - toolCalls: [], - origin, - }); - this.appendSystemReminderOperation(message); - return message; + this.appendSystemReminderOperation(content.trim(), origin); + return this.context.get().at(-1)!; } remove(removals: readonly ContextRemovalTarget[]): void { diff --git a/packages/agent-core-v2/test/wireRecord/persistence.test.ts b/packages/agent-core-v2/test/wireRecord/persistence.test.ts index ca71d8008..d8845b934 100644 --- a/packages/agent-core-v2/test/wireRecord/persistence.test.ts +++ b/packages/agent-core-v2/test/wireRecord/persistence.test.ts @@ -310,6 +310,10 @@ describe('AgentWireRecordService persistence', () => { expect(context.get()[0]?.content).toEqual([ { type: 'text', text: '\nRemember this.\n' }, ]); + expect(context.get()[0]?.origin).toEqual({ + kind: 'injection', + variant: 'host', + }); const records = await readPersistedWireRecords(storage); expect(records.map((record) => record.type)).toEqual([ @@ -318,9 +322,34 @@ describe('AgentWireRecordService persistence', () => { ]); const record = records[1] as { type: 'context.append_system_reminder'; - args: readonly [ContextMessage]; + args: readonly [string, ContextMessage['origin']]; }; - expect(record.args[0].content).toEqual([{ type: 'text', text: 'Remember this.' }]); + expect(record.args).toEqual(['Remember this.', { kind: 'injection', variant: 'host' }]); + }); + + it('restores string system reminder args with origin as wrapped context messages', async () => { + const { wire, context } = await createWireHarness(); + + await wire.restore([ + { + type: 'metadata', + protocol_version: AGENT_WIRE_PROTOCOL_VERSION, + created_at: 1, + }, + { + type: 'context.append_system_reminder', + args: ['Remember this.', { kind: 'injection', variant: 'host' }], + }, + ]); + + const message = context.get()[0]; + expect(message?.content).toEqual([ + { type: 'text', text: '\nRemember this.\n' }, + ]); + expect(message?.origin).toEqual({ + kind: 'injection', + variant: 'host', + }); }); it('offloads large content part data URIs to blobsDir during append', async () => {