From 572338b9a6143cc3065b1b0ecc39d412d1a5d283 Mon Sep 17 00:00:00 2001 From: Gavriel Date: Sun, 1 Feb 2026 22:23:38 +0200 Subject: [PATCH] Add context_mode option for scheduled tasks Scheduled tasks can now run in either: - "group" mode: uses the group's conversation session for context - "isolated" mode: runs with a fresh session (previous behavior) The tool description guides the agent on when to use each mode and prompts them to ask the user if unclear. Group mode is now the default. Co-Authored-By: Claude Opus 4.5 --- container/agent-runner/src/ipc-mcp.ts | 16 ++++++++++++++-- src/db.ts | 10 ++++++++-- src/index.ts | 13 +++++++++++-- src/task-scheduler.ts | 6 ++++++ src/types.ts | 1 + 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/container/agent-runner/src/ipc-mcp.ts b/container/agent-runner/src/ipc-mcp.ts index 4b98216..dd42078 100644 --- a/container/agent-runner/src/ipc-mcp.ts +++ b/container/agent-runner/src/ipc-mcp.ts @@ -70,14 +70,25 @@ export function createIpcMcp(ctx: IpcMcpContext) { 'schedule_task', `Schedule a recurring or one-time task. The task will run as a full agent with access to all tools. -IMPORTANT - schedule_value format depends on schedule_type: +CONTEXT MODE - Choose based on task type: +• "group" (recommended for most tasks): Task runs in the group's conversation context, with access to chat history and memory. Use for tasks that need context about ongoing discussions, user preferences, or previous interactions. +• "isolated": Task runs in a fresh session with no conversation history. Use for independent tasks that don't need prior context. When using isolated mode, include all necessary context in the prompt itself. + +If unsure which mode to use, ask the user. Examples: +- "Remind me about our discussion" → group (needs conversation context) +- "Check the weather every morning" → isolated (self-contained task) +- "Follow up on my request" → group (needs to know what was requested) +- "Generate a daily report" → isolated (just needs instructions in prompt) + +SCHEDULE VALUE FORMAT: • cron: Standard cron expression (e.g., "*/5 * * * *" for every 5 minutes, "0 9 * * *" for daily at 9am) • interval: Milliseconds between runs (e.g., "300000" for 5 minutes, "3600000" for 1 hour) • once: ISO 8601 timestamp (e.g., "2026-02-01T15:30:00.000Z"). Calculate this from current time.`, { - prompt: z.string().describe('What the agent should do when the task runs'), + prompt: z.string().describe('What the agent should do when the task runs. For isolated mode, include all necessary context here.'), schedule_type: z.enum(['cron', 'interval', 'once']).describe('cron=recurring at specific times, interval=recurring every N ms, once=run once at specific time'), schedule_value: z.string().describe('cron: "*/5 * * * *" | interval: milliseconds like "300000" | once: ISO timestamp like "2026-02-01T15:30:00.000Z"'), + context_mode: z.enum(['group', 'isolated']).default('group').describe('group=runs with chat history and memory, isolated=fresh session (include context in prompt)'), target_group: z.string().optional().describe('Target group folder (main only, defaults to current group)') }, async (args) => { @@ -117,6 +128,7 @@ IMPORTANT - schedule_value format depends on schedule_type: prompt: args.prompt, schedule_type: args.schedule_type, schedule_value: args.schedule_value, + context_mode: args.context_mode || 'group', groupFolder: targetGroup, chatJid, createdBy: groupFolder, diff --git a/src/db.ts b/src/db.ts index 76984e2..6571250 100644 --- a/src/db.ts +++ b/src/db.ts @@ -64,6 +64,11 @@ export function initDatabase(): void { try { db.exec(`ALTER TABLE messages ADD COLUMN sender_name TEXT`); } catch { /* column already exists */ } + + // Add context_mode column if it doesn't exist (migration for existing DBs) + try { + db.exec(`ALTER TABLE scheduled_tasks ADD COLUMN context_mode TEXT DEFAULT 'isolated'`); + } catch { /* column already exists */ } } /** @@ -131,8 +136,8 @@ export function getMessagesSince(chatJid: string, sinceTimestamp: string): NewMe export function createTask(task: Omit): void { db.prepare(` - INSERT INTO scheduled_tasks (id, group_folder, chat_jid, prompt, schedule_type, schedule_value, next_run, status, created_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO scheduled_tasks (id, group_folder, chat_jid, prompt, schedule_type, schedule_value, context_mode, next_run, status, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run( task.id, task.group_folder, @@ -140,6 +145,7 @@ export function createTask(task: Omit task.prompt, task.schedule_type, task.schedule_value, + task.context_mode || 'isolated', task.next_run, task.status, task.created_at diff --git a/src/index.ts b/src/index.ts index 211d572..467b512 100644 --- a/src/index.ts +++ b/src/index.ts @@ -237,6 +237,7 @@ async function processTaskIpc( prompt?: string; schedule_type?: string; schedule_value?: string; + context_mode?: string; groupFolder?: string; chatJid?: string; }, @@ -295,6 +296,9 @@ async function processTaskIpc( } const taskId = `task-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + const contextMode = (data.context_mode === 'group' || data.context_mode === 'isolated') + ? data.context_mode + : 'isolated'; createTask({ id: taskId, group_folder: targetGroup, @@ -302,11 +306,12 @@ async function processTaskIpc( prompt: data.prompt, schedule_type: scheduleType, schedule_value: data.schedule_value, + context_mode: contextMode, next_run: nextRun, status: 'active', created_at: new Date().toISOString() }); - logger.info({ taskId, sourceGroup, targetGroup }, 'Task created via IPC'); + logger.info({ taskId, sourceGroup, targetGroup, contextMode }, 'Task created via IPC'); } break; @@ -388,7 +393,11 @@ async function connectWhatsApp(): Promise { } } else if (connection === 'open') { logger.info('Connected to WhatsApp'); - startSchedulerLoop({ sendMessage, registeredGroups: () => registeredGroups }); + startSchedulerLoop({ + sendMessage, + registeredGroups: () => registeredGroups, + getSessions: () => sessions + }); startIpcWatcher(); startMessageLoop(); } diff --git a/src/task-scheduler.ts b/src/task-scheduler.ts index 918b51d..65c353d 100644 --- a/src/task-scheduler.ts +++ b/src/task-scheduler.ts @@ -15,6 +15,7 @@ const logger = pino({ export interface SchedulerDependencies { sendMessage: (jid: string, text: string) => Promise; registeredGroups: () => Record; + getSessions: () => Record; } async function runTask(task: ScheduledTask, deps: SchedulerDependencies): Promise { @@ -56,9 +57,14 @@ async function runTask(task: ScheduledTask, deps: SchedulerDependencies): Promis let result: string | null = null; let error: string | null = null; + // For group context mode, use the group's current session + const sessions = deps.getSessions(); + const sessionId = task.context_mode === 'group' ? sessions[task.group_folder] : undefined; + try { const output = await runContainerAgent(group, { prompt: task.prompt, + sessionId, groupFolder: task.group_folder, chatJid: task.chat_jid, isMain, diff --git a/src/types.ts b/src/types.ts index 3f5fa62..869bd8f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -38,6 +38,7 @@ export interface ScheduledTask { prompt: string; schedule_type: 'cron' | 'interval' | 'once'; schedule_value: string; + context_mode: 'group' | 'isolated'; next_run: string | null; last_run: string | null; last_result: string | null;