feat(gateway): inject timestamps into agent + chat.send messages (#3658)#3705
Conversation
|
(waiting for #3750 to get merged to get CI passing) |
|
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. |
|
👋 Hi — I'm Clawd, the AI half of this collaboration. Just updated the PR description to reflect the final format we landed on: The earlier description referenced a 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.
d687b1f to
b603d9c
Compare
|
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! 🦞 |
|
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! |
|
Fantastic! Thanks!! |
|
Dope. Thanks @Takhoffman ! |
|
Pleasure. All the testing you guys did made me feel comfortable with this one. |
* 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 ...
…timestamp-injection feat(gateway): inject timestamps into agent + chat.send messages (openclaw#3658)
@conroywhitney, @CashWilliams) (cherry picked from commit 9c29853) # Conflicts: # src/gateway/server-methods/agent-timestamp.ts
…timestamp-injection feat(gateway): inject timestamps into agent + chat.send messages (openclaw#3658)

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:
chat.sendBodyForAgentgets timestamp prefix;Bodystays raw for UIagentmethodCurrent time:✅getReplyFromConfigWhy?
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_sendhad 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:[Wed 2026-01-28 22:30 EST]to messages (3-letter DOW + compact date/time + timezone)formatZonedTimestampfrom channel envelope formatting for consistency[Discord user 2026-...])Current time: ...)Called from two gateway handlers:
agenthandler (server-methods/agent.ts) — covers spawned subagents,sessions_send, heartbeat wake eventschat.sendhandler (server-methods/chat.ts) — covers webchat and TUI, injecting intoBodyForAgentonly (UI display stays clean)Format:
[Wed 2026-01-28 22:30 EST]Compact, ~7 tokens. Includes:
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):
After (timestamps injected):
[Wed 2026-01-28 21:35 EST] OK restarted gateway...✅[Wed 2026-01-28 21:36 EST] message✅Tests
injectTimestamp: timezone handling, DOW prefix, midnight boundaries, channel envelope detection, cron detection, empty messages, customnowagentCommandreceives timestamped messagevi.useFakeTimers()+vi.setSystemTime()for deterministic timeKnown Gap: Heartbeat Polls
Heartbeat polls go through
getReplyFromConfigdirectly (same as channel dispatch), not throughagentorchat.send. They setctx.Body = promptwith 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_statushint (PR #3677), so they can still look up the time — they just don't get it for free in the message.Complements
session_statushint in system prompt (teaches models where to look)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:
[2026-01-28 22:03 EST](no DOW)[Wed 2026-01-28 ...][Wed 2026-01-28 ...][Wed 2026-01-28 ...]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.