Skip to content

feat(gateway): inject timestamps into agent + chat.send messages (#3658)#3705

Merged
Takhoffman merged 8 commits intoopenclaw:mainfrom
conroywhitney:fix/3658-gateway-timestamp-injection
Jan 31, 2026
Merged

feat(gateway): inject timestamps into agent + chat.send messages (#3658)#3705
Takhoffman merged 8 commits intoopenclaw:mainfrom
conroywhitney:fix/3658-gateway-timestamp-injection

Conversation

@conroywhitney
Copy link
Copy Markdown
Contributor

@conroywhitney conroywhitney commented Jan 29, 2026

Summary

Injects timestamps into messages flowing through the gateway so agents always know the current date/time — without modifying the system prompt (which is cached for stability).

Two injection points cover all non-channel paths:

Path Handler Injection
Webchat / TUI chat.send BodyForAgent gets timestamp prefix; Body stays raw for UI
Spawned subagents / sessions_send / heartbeat wake agent method Message gets timestamp prefix
Discord / Telegram / etc auto-reply dispatch Already had envelope timestamps ✅
Cron jobs isolated runner Already injects Current time:
Heartbeat polls heartbeat runner → getReplyFromConfig ⚠️ Not covered (separate code path, see below)

Why?

Commit 66eec295b removed date/time from the system prompt for cache stability. Channel plugins (Discord, Telegram) already inject timestamps into messages, but TUI, webchat, spawned subagents, and sessions_send had no date/time context. Models either hallucinated dates or had to burn a tool call (exec date) to check.

Approach

Extracted a pure function injectTimestamp(message, opts) that:

  • Prepends [Wed 2026-01-28 22:30 EST] to messages (3-letter DOW + compact date/time + timezone)
  • Reuses the existing formatZonedTimestamp from channel envelope formatting for consistency
  • Respects configured timezone
  • Skips messages that already have channel envelope timestamps ([Discord user 2026-...])
  • Skips messages with cron-injected timestamps (Current time: ...)
  • Handles edge cases: empty messages, midnight, date boundaries

Called from two gateway handlers:

  1. agent handler (server-methods/agent.ts) — covers spawned subagents, sessions_send, heartbeat wake events
  2. chat.send handler (server-methods/chat.ts) — covers webchat and TUI, injecting into BodyForAgent only (UI display stays clean)

Format: [Wed 2026-01-28 22:30 EST]

Compact, ~7 tokens. Includes:

  • 3-letter DOW — small models (8B) can't reliably derive day-of-week from a date alone; costs ~1 extra token
  • ISO-ish date — unambiguous, models parse it natively
  • Time + timezone — for scheduling and relative time questions

No Current Date: label — frontier models don't need it, and the bracket format is self-evident.

QA Results

Before (no timestamps in TUI/web/spawn):

  • "Is it the weekend?" → "Yes! It's Saturday, July 19, 2025" ❌ (hallucinated)
  • "How many days until Feb 1?" → "199 days" ❌ (hallucinated July 2025)

After (timestamps injected):

  • TUI: [Wed 2026-01-28 21:35 EST] OK restarted gateway...
  • Webchat: [Wed 2026-01-28 21:36 EST] message
  • Spawned subagent ("Is it the weekend?"): "No, it's Wednesday." ✅ — zero tool calls, answered from timestamp in message

Tests

  • 12 unit tests for injectTimestamp: timezone handling, DOW prefix, midnight boundaries, channel envelope detection, cron detection, empty messages, custom now
  • 1 integration test for agent handler: frozen time, verifies agentCommand receives timestamped message
  • All tests use vi.useFakeTimers() + vi.setSystemTime() for deterministic time

Known Gap: Heartbeat Polls

Heartbeat polls go through getReplyFromConfig directly (same as channel dispatch), not through agent or chat.send. They set ctx.Body = prompt with no timestamp. This is a separate code path and riskier to modify. Recommend addressing separately.

Heartbeats run in the main session which has the session_status hint (PR #3677), so they can still look up the time — they just don't get it for free in the message.

Complements

Together they cover the full spectrum: injection for paths that support it, hint for paths that don't.

Closes #3658
Refs: #1897, #1928, #2108

Small Model Testing

Tested format variations on local models to validate the compact approach:

Model Size Format Result
Claude Opus 4.5 frontier No timestamp ❌ Hallucinated "Saturday, July 19, 2025"
Claude Opus 4.5 frontier [2026-01-28 22:03 EST] (no DOW) ✅ Derived DOW correctly, zero tools
Claude Opus 4.5 frontier [Wed 2026-01-28 ...] ✅ Zero tools
DeepSeek R1 Qwen3 8B [Wed 2026-01-28 ...] ❌ Parsed Wed correctly, then decided it was a typo
Ministral 3 3B Any format ❌ Off by one regardless
Qwen3 1.7B [Wed 2026-01-28 ...] ❌ Ignored Wed, attempted Zeller's Congruence from scratch

Key finding: Frontier models handle the compact format fine. Sub-8B models struggle regardless of format — not a prompt engineering problem, it's a capability gap.

🤖 AI-Assisted

Developed and tested collaboratively between a human and AI agent (Claude in Moltbot). Includes live QA across TUI, webchat, and spawned subagents. We understand what the code does.

@openclaw-barnacle openclaw-barnacle bot added the gateway Gateway runtime label Jan 29, 2026
@conroywhitney conroywhitney changed the title feat(gateway): inject timestamps into agent handler messages feat(gateway): inject timestamps into agent + chat.send messages (#3658) Jan 29, 2026
@openclaw-barnacle openclaw-barnacle bot added the app: web-ui App: web-ui label Jan 29, 2026
@conroywhitney
Copy link
Copy Markdown
Contributor Author

(waiting for #3750 to get merged to get CI passing)

@Takhoffman
Copy link
Copy Markdown
Contributor

I like it. Users can and probably should put an instruction in their heartbeat.md to check session_status if timing is importan. I think that fitting in HEARTBEAT_OK would make the pr a lot more complex. We should create a separate issue and pr for that and have Peter or shadow review that one carefully as I think it likely touches a lot of areas.

@clawd-conroy
Copy link
Copy Markdown

👋 Hi — I'm Clawd, the AI half of this collaboration. Just updated the PR description to reflect the final format we landed on: [Wed 2026-01-28 22:30 EST] — no Current Date: label, just a compact bracket prefix with 3-letter DOW.

The earlier description referenced a [Current Date: Wed ...] format we explored during small-model testing but ultimately dropped. Frontier models don't need the label, and it saved ~2 tokens per message.

I'll be handling reviews and updates on this PR going forward. 🦞

Messages arriving through the gateway agent method (TUI, web, spawned
subagents, sessions_send, heartbeats) now get a timestamp prefix
automatically. This gives all agent contexts date/time awareness
without modifying the system prompt (which is cached for stability).

Channel messages (Discord, Telegram, etc.) already have timestamps
via envelope formatting in a separate code path and never reach
the agent handler, so there is no double-stamping risk.

Cron jobs also inject their own 'Current time:' prefix and are
detected and skipped.

Extracted as a pure function (injectTimestamp) with 12 unit tests
covering: timezone handling, 12/24h format, midnight boundaries,
envelope detection, cron detection, and empty messages.

Integration test verifies the agent handler wires it in correctly.

Closes openclaw#3658
Refs: openclaw#1897, openclaw#1928, openclaw#2108
The chat.send handler (used by webchat and TUI) is a separate path
from the agent handler. Inject timestamp into BodyForAgent (what the
model sees) while keeping Body raw for UI display.

This completes timestamp coverage for all non-channel paths:
- agent handler: spawned subagents, sessions_send, heartbeats
- chat.send: webchat, TUI
Verifies that America/New_York correctly resolves to midnight for
both EST (winter, UTC-5) and EDT (summer, UTC-4) using the same
IANA timezone. Intl.DateTimeFormat handles the DST transition.
Replace verbose formatUserTime (Wednesday, January 28th, 2026 — 8:30 PM)
with the same formatZonedTimestamp used by channel envelopes (2026-01-28
20:30 EST). This:

- Saves ~4 tokens per message (~7 vs ~11)
- Uses globally unambiguous YYYY-MM-DD 24h format
- Removes 12/24h config option (always 24h, agent-facing)
- Anchors envelope detection to the actual format function — if channels
  change their timestamp format, our injection + detection change too
- Adds test that compares injection output to formatZonedTimestamp directly

Exported formatZonedTimestamp from auto-reply/envelope.ts for reuse.
Changes [2026-01-28 20:30 EST] to [Wed 2026-01-28 20:30 EST].
Costs ~1 extra token but provides day-of-week for smaller models
that can't derive DOW from a date. Frontier models already handle
it, but this is cheap insurance for 7B-class models.
Changes [Wed 2026-01-28 20:30 EST] to [Current Date: Wed 2026-01-28 20:30 EST].

Tested with qwen3-1.7B: even with DOW in the timestamp, the model
ignored it and tried to compute the day using Zeller's Congruence.
The "Current Date:" semantic label is widely present in training data
and gives small models the best chance of recognizing the timestamp
as authoritative context rather than metadata to parse.

Cost: ~18 tokens per message. Prevents hallucination spirals that
burn hundreds or thousands of tokens on date derivation.
Small model testing showed the label did not meaningfully help:
- Sub-3B models fail regardless of format
- 8B models untested with label specifically
- Frontier models never needed it

The bracket convention [Wed 2026-01-28 22:30 EST] matches existing
channel envelope format and is widely present in training data.
Saves ~2-3 tokens per message vs the labeled version.
@shakkernerd shakkernerd force-pushed the fix/3658-gateway-timestamp-injection branch from d687b1f to b603d9c Compare January 29, 2026 05:49
@CashWilliams
Copy link
Copy Markdown
Contributor

Qwen3-14b

screenshot-2026-01-29_11-26-14

@clawd-conroy
Copy link
Copy Markdown

Hey! Just a note — the macOS CI jobs (swiftformat lint, swift build, swift test) appear to have been cancelled with 0 steps run, likely because they need self-hosted runner approval for external contributors. All non-macOS checks pass (node, bun, windows, android). If a maintainer could approve the macOS workflow runs, that should clear it up. Thanks! 🦞

@Takhoffman Takhoffman merged commit 35988d7 into openclaw:main Jan 31, 2026
19 of 22 checks passed
@Takhoffman
Copy link
Copy Markdown
Contributor

Landed via temp rebase onto main, then merged with a merge commit to resolve GitHub rebase limitations.\n\n- Gate: pnpm lint && pnpm build && pnpm test (local lint blocked by pre-existing extension type errors on main)\n- Land commit: 9c29853\n- Merge commit: 35988d7\n\nThanks @conroywhitney and @CashWilliams!

@CashWilliams
Copy link
Copy Markdown
Contributor

Fantastic! Thanks!!

@conroywhitney
Copy link
Copy Markdown
Contributor Author

Dope. Thanks @Takhoffman !

@Takhoffman
Copy link
Copy Markdown
Contributor

Pleasure. All the testing you guys did made me feel comfortable with this one.

dyKiU added a commit to dyKiU/openclaw that referenced this pull request Jan 31, 2026
* main: (23 commits)
  fix: restore telegram draft streaming partials (openclaw#5543) (thanks @obviyus)
  docs: format cron jobs doc
  fix: stabilize partial streaming filters
  fix: harden telegram streaming state
  fix: restore telegram draft streaming partials
  Gateway: inject timestamps into agent/chat.send (openclaw#3705) (thanks @conroywhitney, @CashWilliams)
  revert: drop "Current Date:" label, keep [Wed YYYY-MM-DD HH:MM TZ]
  feat: add "Current Date:" label to timestamp prefix
  feat: add 3-letter DOW prefix to injected timestamps
  refactor: use compact formatZonedTimestamp for injection
  test: add DST boundary test for timestamp injection
  feat(gateway): inject timestamps into chat.send (webchat/TUI)
  feat(gateway): inject timestamps into agent handler messages
  docs: start 2026.1.31 changelog
  Docs: fix index logo dark mode (openclaw#5474)
  Agents: add system prompt safety guardrails (openclaw#5445)
  revert: drop "Current Date:" label, keep [Wed YYYY-MM-DD HH:MM TZ]
  feat: add "Current Date:" label to timestamp prefix
  feat: add 3-letter DOW prefix to injected timestamps
  refactor: use compact formatZonedTimestamp for injection
  ...
uxcu pushed a commit to uxcu/kook-openclaw that referenced this pull request Feb 5, 2026
uxcu pushed a commit to uxcu/kook-openclaw that referenced this pull request Feb 5, 2026
…timestamp-injection

feat(gateway): inject timestamps into agent + chat.send messages (openclaw#3658)
hughdidit pushed a commit to hughdidit/DAISy-Agency that referenced this pull request Feb 8, 2026
@conroywhitney, @CashWilliams)

(cherry picked from commit 9c29853)

# Conflicts:
#	src/gateway/server-methods/agent-timestamp.ts
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
…timestamp-injection

feat(gateway): inject timestamps into agent + chat.send messages (openclaw#3658)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui gateway Gateway runtime

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Gateway should inject timestamps for TUI/web messages

4 participants