Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions agent/turn_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -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: <date>`` 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).

Expand Down Expand Up @@ -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")
Expand Down
58 changes: 58 additions & 0 deletions tests/agent/test_date_change_note.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Tests for the date-change note (ported from MoonshotAI/kimi-code#2564).

The system prompt bakes in "Conversation started: <date>" 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"
Loading