From 66eec295b894bce8333886cfbca3b960c57c4946 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 24 Jan 2026 06:22:54 +0000 Subject: [PATCH] perf: stabilize system prompt time --- CHANGELOG.md | 1 + docs/concepts/system-prompt.md | 10 +++++----- docs/date-time.md | 16 ++++++++-------- src/agents/system-prompt.test.ts | 16 ++++++---------- src/agents/system-prompt.ts | 24 ++++-------------------- src/agents/tools/session-status-tool.ts | 11 ++++++++++- src/auto-reply/status.ts | 2 ++ 7 files changed, 36 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3143ddda9..e93b92ffa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Docs: https://docs.clawd.bot ## 2026.1.23 (Unreleased) ### Changes +- Agents: keep system prompt time zone-only and move current time to `session_status` for better cache hits. - Browser: add node-host proxy auto-routing for remote gateways (configurable per gateway/node). - Plugins: add optional llm-task JSON-only tool for workflows. (#1498) Thanks @vignesh07. - CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it. diff --git a/docs/concepts/system-prompt.md b/docs/concepts/system-prompt.md index 1a52fc501..47d1b37df 100644 --- a/docs/concepts/system-prompt.md +++ b/docs/concepts/system-prompt.md @@ -66,12 +66,12 @@ To inspect how much each injected file contributes (raw vs injected, truncation, ## Time handling -The system prompt includes a dedicated **Current Date & Time** section when user -time or timezone is known. It is explicit about: +The system prompt includes a dedicated **Current Date & Time** section when the +user timezone is known. To keep the prompt cache-stable, it now only includes +the **time zone** (no dynamic clock or time format). -- The user’s **local time** (already converted). -- The **time zone** used for the conversion. -- The **time format** (12-hour / 24-hour). +Use `session_status` when the agent needs the current time; the status card +includes a timestamp line. Configure with: diff --git a/docs/date-time.md b/docs/date-time.md index 8b711350d..423f8154d 100644 --- a/docs/date-time.md +++ b/docs/date-time.md @@ -7,8 +7,8 @@ read_when: # Date & Time -Clawdbot defaults to **host-local time for transport timestamps** and **user-local time only in the system prompt**. -Provider timestamps are preserved so tools keep their native semantics. +Clawdbot defaults to **host-local time for transport timestamps** and **user timezone only in the system prompt**. +Provider timestamps are preserved so tools keep their native semantics (current time is available via `session_status`). ## Message envelopes (local by default) @@ -63,16 +63,16 @@ You can override this behavior: ## System prompt: Current Date & Time -If the user timezone or local time is known, the system prompt includes a dedicated -**Current Date & Time** section: +If the user timezone is known, the system prompt includes a dedicated +**Current Date & Time** section with the **time zone only** (no clock/time format) +to keep prompt caching stable: ``` -Thursday, January 15th, 2026 β€” 3:07 PM (America/Chicago) -Time format: 12-hour +Time zone: America/Chicago ``` -If only the timezone is known, we still include the section and instruct the model -to assume UTC for unknown time references. +When the agent needs the current time, use the `session_status` tool; the status +card includes a timestamp line. ## System event lines (local by default) diff --git a/src/agents/system-prompt.test.ts b/src/agents/system-prompt.test.ts index d1b58c6ab..ece7d8dcc 100644 --- a/src/agents/system-prompt.test.ts +++ b/src/agents/system-prompt.test.ts @@ -124,7 +124,7 @@ describe("buildAgentSystemPrompt", () => { expect(prompt).toContain("Reminder: commit your changes in this workspace after edits."); }); - it("includes user time when provided (12-hour)", () => { + it("includes user timezone when provided (12-hour)", () => { const prompt = buildAgentSystemPrompt({ workspaceDir: "/tmp/clawd", userTimezone: "America/Chicago", @@ -133,11 +133,10 @@ describe("buildAgentSystemPrompt", () => { }); expect(prompt).toContain("## Current Date & Time"); - expect(prompt).toContain("Monday, January 5th, 2026 β€” 3:26 PM (America/Chicago)"); - expect(prompt).toContain("Time format: 12-hour"); + expect(prompt).toContain("Time zone: America/Chicago"); }); - it("includes user time when provided (24-hour)", () => { + it("includes user timezone when provided (24-hour)", () => { const prompt = buildAgentSystemPrompt({ workspaceDir: "/tmp/clawd", userTimezone: "America/Chicago", @@ -146,11 +145,10 @@ describe("buildAgentSystemPrompt", () => { }); expect(prompt).toContain("## Current Date & Time"); - expect(prompt).toContain("Monday, January 5th, 2026 β€” 15:26 (America/Chicago)"); - expect(prompt).toContain("Time format: 24-hour"); + expect(prompt).toContain("Time zone: America/Chicago"); }); - it("shows UTC fallback when only timezone is provided", () => { + it("shows timezone when only timezone is provided", () => { const prompt = buildAgentSystemPrompt({ workspaceDir: "/tmp/clawd", userTimezone: "America/Chicago", @@ -158,9 +156,7 @@ describe("buildAgentSystemPrompt", () => { }); expect(prompt).toContain("## Current Date & Time"); - expect(prompt).toContain( - "Time zone: America/Chicago. Current time unknown; assume UTC for date/time references.", - ); + expect(prompt).toContain("Time zone: America/Chicago"); }); it("includes model alias guidance when aliases are provided", () => { diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index c2f972f78..74be661aa 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -49,22 +49,9 @@ function buildUserIdentitySection(ownerLine: string | undefined, isMinimal: bool return ["## User Identity", ownerLine, ""]; } -function buildTimeSection(params: { - userTimezone?: string; - userTime?: string; - userTimeFormat?: ResolvedTimeFormat; -}) { - if (!params.userTimezone && !params.userTime) return []; - return [ - "## Current Date & Time", - params.userTime - ? `${params.userTime} (${params.userTimezone ?? "unknown"})` - : `Time zone: ${params.userTimezone}. Current time unknown; assume UTC for date/time references.`, - params.userTimeFormat - ? `Time format: ${params.userTimeFormat === "24" ? "24-hour" : "12-hour"}` - : "", - "", - ]; +function buildTimeSection(params: { userTimezone?: string }) { + if (!params.userTimezone) return []; + return ["## Current Date & Time", `Time zone: ${params.userTimezone}`, ""]; } function buildReplyTagsSection(isMinimal: boolean) { @@ -212,7 +199,7 @@ export function buildAgentSystemPrompt(params: { sessions_send: "Send a message to another session/sub-agent", sessions_spawn: "Spawn a sub-agent session", session_status: - "Show a /status-equivalent status card (usage + Reasoning/Verbose/Elevated); use for model-use questions (πŸ“Š session_status); optional per-session model override", + "Show a /status-equivalent status card (usage + time + Reasoning/Verbose/Elevated); use for model-use questions (πŸ“Š session_status); optional per-session model override", image: "Analyze an image with the configured image model", }; @@ -302,7 +289,6 @@ export function buildAgentSystemPrompt(params: { : undefined; const reasoningLevel = params.reasoningLevel ?? "off"; const userTimezone = params.userTimezone?.trim(); - const userTime = params.userTime?.trim(); const skillsPrompt = params.skillsPrompt?.trim(); const heartbeatPrompt = params.heartbeatPrompt?.trim(); const heartbeatPromptLine = heartbeatPrompt @@ -465,8 +451,6 @@ export function buildAgentSystemPrompt(params: { ...buildUserIdentitySection(ownerLine, isMinimal), ...buildTimeSection({ userTimezone, - userTime, - userTimeFormat: params.userTimeFormat, }), "## Workspace Files (injected)", "These user-editable files are loaded by Clawdbot and included below in Project Context.", diff --git a/src/agents/tools/session-status-tool.ts b/src/agents/tools/session-status-tool.ts index 715512519..18b4444c8 100644 --- a/src/agents/tools/session-status-tool.ts +++ b/src/agents/tools/session-status-tool.ts @@ -15,6 +15,7 @@ import { resolveDefaultModelForAgent, resolveModelRefFromString, } from "../../agents/model-selection.js"; +import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone } from "../date-time.js"; import { normalizeGroupActivation } from "../../auto-reply/group-activation.js"; import { getFollowupQueueDepth, resolveQueueSettings } from "../../auto-reply/reply/queue.js"; import { buildStatusMessage } from "../../auto-reply/status.js"; @@ -215,7 +216,7 @@ export function createSessionStatusTool(opts?: { label: "Session Status", name: "session_status", description: - "Show a /status-equivalent session status card (usage + cost when available). Use for model-use questions (πŸ“Š session_status). Optional: set per-session model override (model=default resets overrides).", + "Show a /status-equivalent session status card (usage + time + cost when available). Use for model-use questions (πŸ“Š session_status). Optional: set per-session model override (model=default resets overrides).", parameters: SessionStatusToolSchema, execute: async (_toolCallId, args) => { const params = args as Record; @@ -324,6 +325,13 @@ export function createSessionStatusTool(opts?: { resolved.entry.queueDebounceMs ?? resolved.entry.queueCap ?? resolved.entry.queueDrop, ); + const userTimezone = resolveUserTimezone(cfg.agents?.defaults?.userTimezone); + const userTimeFormat = resolveUserTimeFormat(cfg.agents?.defaults?.timeFormat); + const userTime = formatUserTime(new Date(), userTimezone, userTimeFormat); + const timeLine = userTime + ? `πŸ•’ Time: ${userTime} (${userTimezone})` + : `πŸ•’ Time zone: ${userTimezone}`; + const agentDefaults = cfg.agents?.defaults ?? {}; const defaultLabel = `${configured.provider}/${configured.model}`; const agentModel = @@ -346,6 +354,7 @@ export function createSessionStatusTool(opts?: { agentDir, }), usageLine, + timeLine, queue: { mode: queueSettings.mode, depth: queueDepth, diff --git a/src/auto-reply/status.ts b/src/auto-reply/status.ts index eaf2d20a8..e65f8e35b 100644 --- a/src/auto-reply/status.ts +++ b/src/auto-reply/status.ts @@ -52,6 +52,7 @@ type StatusArgs = { resolvedElevated?: ElevatedLevel; modelAuth?: string; usageLine?: string; + timeLine?: string; queue?: QueueStatus; mediaDecisions?: MediaUnderstandingDecision[]; subagentsLine?: string; @@ -381,6 +382,7 @@ export function buildStatusMessage(args: StatusArgs): string { return [ versionLine, + args.timeLine, modelLine, usageCostLine, `πŸ“š ${contextLine}`,