feat(cron): add contextMessages param to control reminder context

This commit is contained in:
Michael Behr
2026-01-17 09:06:54 -05:00
committed by Peter Steinberger
parent 5fe8c4ab8c
commit 4642fae193
2 changed files with 33 additions and 3 deletions

View File

@@ -85,7 +85,7 @@ describe("cron tool", () => {
}); });
}); });
it("adds recent context for systemEvent reminders when session key is available", async () => { it("adds recent context for systemEvent reminders when contextMessages > 0", async () => {
callGatewayMock callGatewayMock
.mockResolvedValueOnce({ .mockResolvedValueOnce({
messages: [ messages: [
@@ -102,6 +102,7 @@ describe("cron tool", () => {
const tool = createCronTool({ agentSessionKey: "main" }); const tool = createCronTool({ agentSessionKey: "main" });
await tool.execute("call3", { await tool.execute("call3", {
action: "add", action: "add",
contextMessages: 3,
job: { job: {
name: "reminder", name: "reminder",
schedule: { atMs: 123 }, schedule: { atMs: 123 },
@@ -127,4 +128,28 @@ describe("cron tool", () => {
expect(text).toContain("Assistant: We agreed to review on Tuesday."); expect(text).toContain("Assistant: We agreed to review on Tuesday.");
expect(text).toContain("User: Remind me about the thing at 2pm"); expect(text).toContain("User: Remind me about the thing at 2pm");
}); });
it("does not add context when contextMessages is 0 (default)", async () => {
callGatewayMock.mockResolvedValueOnce({ ok: true });
const tool = createCronTool({ agentSessionKey: "main" });
await tool.execute("call4", {
action: "add",
job: {
name: "reminder",
schedule: { atMs: 123 },
payload: { text: "Reminder: the thing." },
},
});
// Should only call cron.add, not chat.history
expect(callGatewayMock).toHaveBeenCalledTimes(1);
const cronCall = callGatewayMock.mock.calls[0]?.[0] as {
method?: string;
params?: { payload?: { text?: string } };
};
expect(cronCall.method).toBe("cron.add");
const text = cronCall.params?.payload?.text ?? "";
expect(text).not.toContain("Recent context:");
});
}); });

View File

@@ -16,7 +16,6 @@ const CRON_ACTIONS = ["status", "list", "add", "update", "remove", "run", "runs"
const CRON_WAKE_MODES = ["now", "next-heartbeat"] as const; const CRON_WAKE_MODES = ["now", "next-heartbeat"] as const;
const REMINDER_CONTEXT_MESSAGES = 3;
const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220; const REMINDER_CONTEXT_PER_MESSAGE_MAX = 220;
const REMINDER_CONTEXT_TOTAL_MAX = 700; const REMINDER_CONTEXT_TOTAL_MAX = 700;
const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n"; const REMINDER_CONTEXT_MARKER = "\n\nRecent context:\n";
@@ -34,6 +33,7 @@ const CronToolSchema = Type.Object({
patch: Type.Optional(Type.Object({}, { additionalProperties: true })), patch: Type.Optional(Type.Object({}, { additionalProperties: true })),
text: Type.Optional(Type.String()), text: Type.Optional(Type.String()),
mode: optionalStringEnum(CRON_WAKE_MODES), mode: optionalStringEnum(CRON_WAKE_MODES),
contextMessages: Type.Optional(Type.Number()),
}); });
type CronToolOptions = { type CronToolOptions = {
@@ -86,7 +86,9 @@ function extractMessageText(message: ChatMessage): { role: string; text: string
async function buildReminderContextLines(params: { async function buildReminderContextLines(params: {
agentSessionKey?: string; agentSessionKey?: string;
gatewayOpts: GatewayCallOptions; gatewayOpts: GatewayCallOptions;
contextMessages: number;
}) { }) {
if (params.contextMessages <= 0) return [];
const sessionKey = params.agentSessionKey?.trim(); const sessionKey = params.agentSessionKey?.trim();
if (!sessionKey) return []; if (!sessionKey) return [];
const cfg = loadConfig(); const cfg = loadConfig();
@@ -101,7 +103,7 @@ async function buildReminderContextLines(params: {
const parsed = messages const parsed = messages
.map((msg) => extractMessageText(msg as ChatMessage)) .map((msg) => extractMessageText(msg as ChatMessage))
.filter((msg): msg is { role: string; text: string } => Boolean(msg)); .filter((msg): msg is { role: string; text: string } => Boolean(msg));
const recent = parsed.slice(-REMINDER_CONTEXT_MESSAGES); const recent = parsed.slice(-params.contextMessages);
if (recent.length === 0) return []; if (recent.length === 0) return [];
const lines: string[] = []; const lines: string[] = [];
let total = 0; let total = 0;
@@ -149,6 +151,8 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool {
throw new Error("job required"); throw new Error("job required");
} }
const job = normalizeCronJobCreate(params.job) ?? params.job; const job = normalizeCronJobCreate(params.job) ?? params.job;
const contextMessages =
typeof params.contextMessages === "number" ? params.contextMessages : 0;
if ( if (
job && job &&
typeof job === "object" && typeof job === "object" &&
@@ -160,6 +164,7 @@ export function createCronTool(opts?: CronToolOptions): AnyAgentTool {
const contextLines = await buildReminderContextLines({ const contextLines = await buildReminderContextLines({
agentSessionKey: opts?.agentSessionKey, agentSessionKey: opts?.agentSessionKey,
gatewayOpts, gatewayOpts,
contextMessages,
}); });
if (contextLines.length > 0) { if (contextLines.length > 0) {
const baseText = stripExistingContext(payload.text); const baseText = stripExistingContext(payload.text);