From 38929dae9f4f18c25c14603c8fc5b95e255bb533 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 6 Aug 2026 19:27:16 -0700 Subject: [PATCH] Port from MoonshotAI/kimi-code#2564: announce date changes via a per-turn system note MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The system prompt bakes in "Conversation started: " and stays byte-stable for the life of a conversation (prompt-cache invariant), so a session running past midnight leaves the model with a stale idea of today's date. maybe_date_change_note() tracks the last announced date on the agent and, on a genuine rollover, emits a one-line note delivered through the existing gateway-notes / api_content sidecar channel — the same byte-stable per-turn user-message path used for auto-reset notes — so the system prompt and cache prefix are never touched. First call seeds quietly (fresh/restored agents announce only real rollovers), matching the source behavior. --- agent/turn_context.py | 46 ++++++++++++++++++++++ tests/agent/test_date_change_note.py | 58 ++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 tests/agent/test_date_change_note.py diff --git a/agent/turn_context.py b/agent/turn_context.py index 92ebce833823..4c23c60a8551 100644 --- a/agent/turn_context.py +++ b/agent/turn_context.py @@ -130,6 +130,43 @@ def extract_api_content_sidecar(msg: Mapping[str, Any]) -> Optional[str]: return v if isinstance(v, str) else None +def maybe_date_change_note(agent: Any, now: Any = None) -> str: + """Return a one-line date-change note when the session crossed midnight. + + The system prompt bakes in ``Conversation started: `` and is + byte-stable for the life of a conversation (prompt-cache invariant), so a + session that runs past midnight leaves the model with a stale idea of + today's date. This helper tracks the last date the model was told about + on ``agent._last_known_date`` and, when the wall-clock date has moved on, + emits a note for the current user message's API-bound copy (the + ``api_content`` sidecar channel — persisted and replayed byte-for-byte, + so the cache prefix never diverges). + + Seeding is quiet: the first call on a fresh or restored agent records + today without announcing (matching the source behavior in + MoonshotAI/kimi-code#2564 — a profile with no recorded date seeds + silently so only genuine rollovers announce). + """ + try: + from datetime import datetime + + current = (now or datetime.now()).strftime("%A, %B %d, %Y") + except Exception: + return "" + last = getattr(agent, "_last_known_date", None) + try: + agent._last_known_date = current + except Exception: + pass + if last is None or last == current: + return "" + return ( + f"[System note: the date has changed since this conversation's " + f"context was established. It is now {current} (was {last}). " + f"Use the new date for any date-sensitive reasoning.]" + ) + + def consume_gateway_turn_context_notes(agent: Any) -> str: """Pop the gateway's per-turn must-deliver notes off the agent (one-shot). @@ -1117,6 +1154,15 @@ def build_turn_context( # Multimodal (list) content can't take the string sidecar — append a # durable text part instead of dropping the fact. _gateway_notes = consume_gateway_turn_context_notes(agent) + # Date-change note (ported from MoonshotAI/kimi-code#2564): the system + # prompt's "Conversation started:" date is byte-stable for cache + # integrity, so announce midnight rollovers on the same per-turn user- + # message channel instead of touching the prompt. + _date_note = maybe_date_change_note(agent) + if _date_note: + _gateway_notes = ( + _gateway_notes + "\n\n" + _date_note if _gateway_notes else _date_note + ) if _gateway_notes: _gw_turn_content = ( messages[current_turn_user_idx].get("content") diff --git a/tests/agent/test_date_change_note.py b/tests/agent/test_date_change_note.py new file mode 100644 index 000000000000..b8bd1b9b6c2e --- /dev/null +++ b/tests/agent/test_date_change_note.py @@ -0,0 +1,58 @@ +"""Tests for the date-change note (ported from MoonshotAI/kimi-code#2564). + +The system prompt bakes in "Conversation started: " and stays +byte-stable for prompt-cache integrity, so a session that crosses midnight +leaves the model with a stale date. ``maybe_date_change_note`` tracks the +last announced date on the agent and emits a note (delivered via the +api_content sidecar channel) only on genuine rollovers. +""" + +from datetime import datetime +from types import SimpleNamespace + +from agent.turn_context import maybe_date_change_note + + +def _agent(): + return SimpleNamespace() + + +class TestMaybeDateChangeNote: + def test_first_call_seeds_quietly(self): + agent = _agent() + note = maybe_date_change_note(agent, now=datetime(2026, 8, 6, 23, 50)) + assert note == "" + assert agent._last_known_date == "Thursday, August 06, 2026" + + def test_same_day_no_note(self): + agent = _agent() + maybe_date_change_note(agent, now=datetime(2026, 8, 6, 9, 0)) + note = maybe_date_change_note(agent, now=datetime(2026, 8, 6, 23, 59)) + assert note == "" + + def test_midnight_rollover_announces(self): + agent = _agent() + maybe_date_change_note(agent, now=datetime(2026, 8, 6, 23, 50)) + note = maybe_date_change_note(agent, now=datetime(2026, 8, 7, 0, 10)) + assert "Friday, August 07, 2026" in note + assert "Thursday, August 06, 2026" in note + assert note.startswith("[System note:") + + def test_announces_once_then_quiet(self): + agent = _agent() + maybe_date_change_note(agent, now=datetime(2026, 8, 6, 12, 0)) + assert maybe_date_change_note(agent, now=datetime(2026, 8, 7, 12, 0)) != "" + assert maybe_date_change_note(agent, now=datetime(2026, 8, 7, 18, 0)) == "" + + def test_multi_day_gap_announces_current_date(self): + agent = _agent() + maybe_date_change_note(agent, now=datetime(2026, 8, 1, 12, 0)) + note = maybe_date_change_note(agent, now=datetime(2026, 8, 6, 12, 0)) + assert "Thursday, August 06, 2026" in note + assert "Saturday, August 01, 2026" in note + + def test_updates_tracker_on_rollover(self): + agent = _agent() + maybe_date_change_note(agent, now=datetime(2026, 8, 6, 12, 0)) + maybe_date_change_note(agent, now=datetime(2026, 8, 7, 12, 0)) + assert agent._last_known_date == "Friday, August 07, 2026"