perf: stabilize system prompt time

This commit is contained in:
Peter Steinberger
2026-01-24 06:22:54 +00:00
parent 675019cb6f
commit 66eec295b8
7 changed files with 36 additions and 44 deletions

View File

@@ -5,6 +5,7 @@ Docs: https://docs.clawd.bot
## 2026.1.23 (Unreleased) ## 2026.1.23 (Unreleased)
### Changes ### 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). - 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. - 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. - CLI: restart the gateway by default after `clawdbot update`; add `--no-restart` to skip it.

View File

@@ -66,12 +66,12 @@ To inspect how much each injected file contributes (raw vs injected, truncation,
## Time handling ## Time handling
The system prompt includes a dedicated **Current Date & Time** section when user The system prompt includes a dedicated **Current Date & Time** section when the
time or timezone is known. It is explicit about: user timezone is known. To keep the prompt cache-stable, it now only includes
the **time zone** (no dynamic clock or time format).
- The users **local time** (already converted). Use `session_status` when the agent needs the current time; the status card
- The **time zone** used for the conversion. includes a timestamp line.
- The **time format** (12-hour / 24-hour).
Configure with: Configure with:

View File

@@ -7,8 +7,8 @@ read_when:
# Date & Time # Date & Time
Clawdbot defaults to **host-local time for transport timestamps** and **user-local time only in the system prompt**. 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. Provider timestamps are preserved so tools keep their native semantics (current time is available via `session_status`).
## Message envelopes (local by default) ## Message envelopes (local by default)
@@ -63,16 +63,16 @@ You can override this behavior:
## System prompt: Current Date & Time ## System prompt: Current Date & Time
If the user timezone or local time is known, the system prompt includes a dedicated If the user timezone is known, the system prompt includes a dedicated
**Current Date & Time** section: **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 zone: America/Chicago
Time format: 12-hour
``` ```
If only the timezone is known, we still include the section and instruct the model When the agent needs the current time, use the `session_status` tool; the status
to assume UTC for unknown time references. card includes a timestamp line.
## System event lines (local by default) ## System event lines (local by default)

View File

@@ -124,7 +124,7 @@ describe("buildAgentSystemPrompt", () => {
expect(prompt).toContain("Reminder: commit your changes in this workspace after edits."); 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({ const prompt = buildAgentSystemPrompt({
workspaceDir: "/tmp/clawd", workspaceDir: "/tmp/clawd",
userTimezone: "America/Chicago", userTimezone: "America/Chicago",
@@ -133,11 +133,10 @@ describe("buildAgentSystemPrompt", () => {
}); });
expect(prompt).toContain("## Current Date & Time"); expect(prompt).toContain("## Current Date & Time");
expect(prompt).toContain("Monday, January 5th, 2026 — 3:26 PM (America/Chicago)"); expect(prompt).toContain("Time zone: America/Chicago");
expect(prompt).toContain("Time format: 12-hour");
}); });
it("includes user time when provided (24-hour)", () => { it("includes user timezone when provided (24-hour)", () => {
const prompt = buildAgentSystemPrompt({ const prompt = buildAgentSystemPrompt({
workspaceDir: "/tmp/clawd", workspaceDir: "/tmp/clawd",
userTimezone: "America/Chicago", userTimezone: "America/Chicago",
@@ -146,11 +145,10 @@ describe("buildAgentSystemPrompt", () => {
}); });
expect(prompt).toContain("## Current Date & Time"); expect(prompt).toContain("## Current Date & Time");
expect(prompt).toContain("Monday, January 5th, 2026 — 15:26 (America/Chicago)"); expect(prompt).toContain("Time zone: America/Chicago");
expect(prompt).toContain("Time format: 24-hour");
}); });
it("shows UTC fallback when only timezone is provided", () => { it("shows timezone when only timezone is provided", () => {
const prompt = buildAgentSystemPrompt({ const prompt = buildAgentSystemPrompt({
workspaceDir: "/tmp/clawd", workspaceDir: "/tmp/clawd",
userTimezone: "America/Chicago", userTimezone: "America/Chicago",
@@ -158,9 +156,7 @@ describe("buildAgentSystemPrompt", () => {
}); });
expect(prompt).toContain("## Current Date & Time"); expect(prompt).toContain("## Current Date & Time");
expect(prompt).toContain( expect(prompt).toContain("Time zone: America/Chicago");
"Time zone: America/Chicago. Current time unknown; assume UTC for date/time references.",
);
}); });
it("includes model alias guidance when aliases are provided", () => { it("includes model alias guidance when aliases are provided", () => {

View File

@@ -49,22 +49,9 @@ function buildUserIdentitySection(ownerLine: string | undefined, isMinimal: bool
return ["## User Identity", ownerLine, ""]; return ["## User Identity", ownerLine, ""];
} }
function buildTimeSection(params: { function buildTimeSection(params: { userTimezone?: string }) {
userTimezone?: string; if (!params.userTimezone) return [];
userTime?: string; return ["## Current Date & Time", `Time zone: ${params.userTimezone}`, ""];
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 buildReplyTagsSection(isMinimal: boolean) { function buildReplyTagsSection(isMinimal: boolean) {
@@ -212,7 +199,7 @@ export function buildAgentSystemPrompt(params: {
sessions_send: "Send a message to another session/sub-agent", sessions_send: "Send a message to another session/sub-agent",
sessions_spawn: "Spawn a sub-agent session", sessions_spawn: "Spawn a sub-agent session",
session_status: 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", image: "Analyze an image with the configured image model",
}; };
@@ -302,7 +289,6 @@ export function buildAgentSystemPrompt(params: {
: undefined; : undefined;
const reasoningLevel = params.reasoningLevel ?? "off"; const reasoningLevel = params.reasoningLevel ?? "off";
const userTimezone = params.userTimezone?.trim(); const userTimezone = params.userTimezone?.trim();
const userTime = params.userTime?.trim();
const skillsPrompt = params.skillsPrompt?.trim(); const skillsPrompt = params.skillsPrompt?.trim();
const heartbeatPrompt = params.heartbeatPrompt?.trim(); const heartbeatPrompt = params.heartbeatPrompt?.trim();
const heartbeatPromptLine = heartbeatPrompt const heartbeatPromptLine = heartbeatPrompt
@@ -465,8 +451,6 @@ export function buildAgentSystemPrompt(params: {
...buildUserIdentitySection(ownerLine, isMinimal), ...buildUserIdentitySection(ownerLine, isMinimal),
...buildTimeSection({ ...buildTimeSection({
userTimezone, userTimezone,
userTime,
userTimeFormat: params.userTimeFormat,
}), }),
"## Workspace Files (injected)", "## Workspace Files (injected)",
"These user-editable files are loaded by Clawdbot and included below in Project Context.", "These user-editable files are loaded by Clawdbot and included below in Project Context.",

View File

@@ -15,6 +15,7 @@ import {
resolveDefaultModelForAgent, resolveDefaultModelForAgent,
resolveModelRefFromString, resolveModelRefFromString,
} from "../../agents/model-selection.js"; } from "../../agents/model-selection.js";
import { formatUserTime, resolveUserTimeFormat, resolveUserTimezone } from "../date-time.js";
import { normalizeGroupActivation } from "../../auto-reply/group-activation.js"; import { normalizeGroupActivation } from "../../auto-reply/group-activation.js";
import { getFollowupQueueDepth, resolveQueueSettings } from "../../auto-reply/reply/queue.js"; import { getFollowupQueueDepth, resolveQueueSettings } from "../../auto-reply/reply/queue.js";
import { buildStatusMessage } from "../../auto-reply/status.js"; import { buildStatusMessage } from "../../auto-reply/status.js";
@@ -215,7 +216,7 @@ export function createSessionStatusTool(opts?: {
label: "Session Status", label: "Session Status",
name: "session_status", name: "session_status",
description: 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, parameters: SessionStatusToolSchema,
execute: async (_toolCallId, args) => { execute: async (_toolCallId, args) => {
const params = args as Record<string, unknown>; const params = args as Record<string, unknown>;
@@ -324,6 +325,13 @@ export function createSessionStatusTool(opts?: {
resolved.entry.queueDebounceMs ?? resolved.entry.queueCap ?? resolved.entry.queueDrop, 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 agentDefaults = cfg.agents?.defaults ?? {};
const defaultLabel = `${configured.provider}/${configured.model}`; const defaultLabel = `${configured.provider}/${configured.model}`;
const agentModel = const agentModel =
@@ -346,6 +354,7 @@ export function createSessionStatusTool(opts?: {
agentDir, agentDir,
}), }),
usageLine, usageLine,
timeLine,
queue: { queue: {
mode: queueSettings.mode, mode: queueSettings.mode,
depth: queueDepth, depth: queueDepth,

View File

@@ -52,6 +52,7 @@ type StatusArgs = {
resolvedElevated?: ElevatedLevel; resolvedElevated?: ElevatedLevel;
modelAuth?: string; modelAuth?: string;
usageLine?: string; usageLine?: string;
timeLine?: string;
queue?: QueueStatus; queue?: QueueStatus;
mediaDecisions?: MediaUnderstandingDecision[]; mediaDecisions?: MediaUnderstandingDecision[];
subagentsLine?: string; subagentsLine?: string;
@@ -381,6 +382,7 @@ export function buildStatusMessage(args: StatusArgs): string {
return [ return [
versionLine, versionLine,
args.timeLine,
modelLine, modelLine,
usageCostLine, usageCostLine,
`📚 ${contextLine}`, `📚 ${contextLine}`,