diff --git a/packages/@n8n/instance-ai/src/agent/sub-agent-factory.ts b/packages/@n8n/instance-ai/src/agent/sub-agent-factory.ts index d22ddccd67e..c669069617a 100644 --- a/packages/@n8n/instance-ai/src/agent/sub-agent-factory.ts +++ b/packages/@n8n/instance-ai/src/agent/sub-agent-factory.ts @@ -1,6 +1,7 @@ import { Agent } from '@mastra/core/agent'; import type { ToolsInput } from '@mastra/core/agent'; +import { getDateTimeSection } from './system-prompt'; import { buildAgentTraceInputs, mergeTraceRunInputs } from '../tracing/langsmith-tracing'; import type { InstanceAiTraceRun, ModelConfig } from '../types'; @@ -17,6 +18,9 @@ export interface SubAgentOptions { modelId: ModelConfig; /** Optional trace run to annotate with the sub-agent's static config */ traceRun?: InstanceAiTraceRun; + /** IANA time zone for the current user — used to render the datetime section so + * the sub-agent resolves "now" consistently with the orchestrator. */ + timeZone?: string; } /** Hard protocol injected into every sub-agent — cannot be overridden by orchestrator instructions. */ @@ -44,8 +48,9 @@ Keep diagnostics to 2-3 sentences maximum. Omit entirely when the task succeeded export { SUB_AGENT_PROTOCOL }; -function buildSubAgentPrompt(role: string, instructions: string): string { +function buildSubAgentPrompt(role: string, instructions: string, timeZone?: string): string { return `${SUB_AGENT_PROTOCOL} +${getDateTimeSection(timeZone)} You are a sub-agent with the role: ${role}. @@ -54,9 +59,9 @@ ${instructions}`; } export function createSubAgent(options: SubAgentOptions): Agent { - const { agentId, role, instructions, tools, modelId, traceRun } = options; + const { agentId, role, instructions, tools, modelId, traceRun, timeZone } = options; - const systemPrompt = buildSubAgentPrompt(role, instructions); + const systemPrompt = buildSubAgentPrompt(role, instructions, timeZone); const agent = new Agent({ id: agentId, diff --git a/packages/@n8n/instance-ai/src/agent/system-prompt.ts b/packages/@n8n/instance-ai/src/agent/system-prompt.ts index 7964582aaa5..4725bdcb0ea 100644 --- a/packages/@n8n/instance-ai/src/agent/system-prompt.ts +++ b/packages/@n8n/instance-ai/src/agent/system-prompt.ts @@ -17,7 +17,7 @@ interface SystemPromptOptions { branchReadOnly?: boolean; } -function getDateTimeSection(timeZone?: string): string { +export function getDateTimeSection(timeZone?: string): string { const now = timeZone ? DateTime.now().setZone(timeZone) : DateTime.now(); const isoTime = now.toISO({ includeOffset: true }); const tzLabel = timeZone ? ` (timezone: ${timeZone})` : ''; diff --git a/packages/@n8n/instance-ai/src/tools/orchestration/delegate.tool.ts b/packages/@n8n/instance-ai/src/tools/orchestration/delegate.tool.ts index 5c2f9c8f5f6..10985970a38 100644 --- a/packages/@n8n/instance-ai/src/tools/orchestration/delegate.tool.ts +++ b/packages/@n8n/instance-ai/src/tools/orchestration/delegate.tool.ts @@ -184,6 +184,7 @@ export async function startDetachedDelegateTask( tools: tracedTools, modelId: context.modelId, traceRun: traceContext?.actorRun, + timeZone: context.timeZone, }); registerWithMastra(subAgentId, subAgent, context.storage); @@ -293,6 +294,7 @@ export function createDelegateTool(context: OrchestrationContext) { tools: tracedTools, modelId: context.modelId, traceRun, + timeZone: context.timeZone, }); registerWithMastra(subAgentId, subAgent, context.storage); diff --git a/packages/@n8n/instance-ai/src/types.ts b/packages/@n8n/instance-ai/src/types.ts index 82ab40dc47b..71fd318e22c 100644 --- a/packages/@n8n/instance-ai/src/types.ts +++ b/packages/@n8n/instance-ai/src/types.ts @@ -859,6 +859,9 @@ export interface OrchestrationContext { /** Summaries of currently running background tasks in this thread. * Used to give sub-agents thread-state awareness (what else is happening). */ getRunningTaskSummaries?: () => Array<{ taskId: string; role: string; goal?: string }>; + /** IANA time zone for the current user (e.g. "Europe/Helsinki"). Propagated to sub-agents + * so they can resolve "now" consistently with the orchestrator. */ + timeZone?: string; } // ── Agent factory options ──────────────────────────────────────────────────── diff --git a/packages/cli/src/modules/instance-ai/instance-ai.service.ts b/packages/cli/src/modules/instance-ai/instance-ai.service.ts index d0dfefdf384..a9d63883854 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai.service.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai.service.ts @@ -1292,6 +1292,7 @@ export class InstanceAiService { abortSignal, taskStorage, researchMode, + timeZone: this.defaultTimeZone, browserMcpConfig: this.instanceAiConfig.browserMcp ? { name: 'chrome-devtools', command: 'npx', args: ['-y', 'chrome-devtools-mcp@latest'] } : undefined, @@ -1611,6 +1612,7 @@ export class InstanceAiService { // Make the current user message available to sub-agents (e.g. planner) // since memory.recall() only returns previously-saved messages. orchestrationContext.currentUserMessage = message; + orchestrationContext.timeZone = timeZone ?? this.defaultTimeZone; // Thread attachments into the domain context so parse-file can access them if (attachments && attachments.length > 0) {