904eda3388
First step of the cognition control plane (docs/COGNITION.md). The chat turn is now an explicit society of parts over a shared TurnContext blackboard: perceive (stub) -> route (session mode) -> compose (tiered prompt) -> deliberate. - lyra/mind.py (new): TurnContext + the pipeline + assemble(); moved build_messages and the deliberation helpers here (the assembly belongs in the control plane). - lyra/chat.py: slimmed to "speak + persist" — calls mind.assemble(), runs the tool/generation loop, persists. No behavior change (same prompt, same output). - tests: point test_time/test_chat at mind; add an assemble() structure test; make test_chat/test_tools hermetic (CHAT_DELIBERATE off so respond() doesn't make a real LLM call). Suite 86 green in ~5s, ruff clean, no import cycle. This is the frame; perceive/route/learn get filled in next phases — each opt-in. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
54 lines
1.9 KiB
Python
54 lines
1.9 KiB
Python
"""Time-awareness: gap humanizing + the 'now' note injected into chat context."""
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
from datetime import timedelta
|
|
|
|
import pytest
|
|
|
|
from lyra import clock
|
|
|
|
|
|
def test_humanize_gap_scales():
|
|
ref = clock.now()
|
|
assert clock.humanize_gap(None) is None
|
|
assert clock.humanize_gap((ref - timedelta(seconds=10)).isoformat(), ref) == "moments"
|
|
assert clock.humanize_gap((ref - timedelta(minutes=5)).isoformat(), ref) == "5 minutes"
|
|
assert clock.humanize_gap((ref - timedelta(hours=3)).isoformat(), ref) == "3 hours"
|
|
assert clock.humanize_gap((ref - timedelta(days=3)).isoformat(), ref) == "3 days"
|
|
assert clock.humanize_gap((ref - timedelta(days=21)).isoformat(), ref) == "3 weeks"
|
|
assert clock.humanize_gap((ref - timedelta(days=90)).isoformat(), ref) == "3 months"
|
|
|
|
|
|
def test_humanize_gap_handles_future_and_naive():
|
|
ref = clock.now()
|
|
# future timestamp clamps to "moments", never negative
|
|
assert clock.humanize_gap((ref + timedelta(hours=1)).isoformat(), ref) == "moments"
|
|
# naive ISO (no tz) is treated as UTC, doesn't crash
|
|
assert clock.humanize_gap("2026-06-01T00:00:00") is not None
|
|
|
|
|
|
@pytest.fixture
|
|
def lyra(tmp_path, monkeypatch):
|
|
monkeypatch.setenv("LYRA_DB_PATH", str(tmp_path / "test.db"))
|
|
from lyra import llm
|
|
monkeypatch.setattr(llm, "embed", lambda texts: [[0.1, 0.2, 0.3] for _ in texts])
|
|
import lyra.memory as memory
|
|
importlib.reload(memory)
|
|
return memory
|
|
|
|
|
|
def test_now_note_first_contact(lyra):
|
|
from lyra import mind
|
|
note = mind._now_note()["content"]
|
|
assert "current date and time is" in note
|
|
assert "first thing Brian has ever said" in note
|
|
|
|
|
|
def test_now_note_reports_gap(lyra):
|
|
memory = lyra
|
|
memory.remember("s1", "user", "hey")
|
|
from lyra import mind
|
|
note = mind._now_note()["content"]
|
|
assert "since Brian last spoke with you" in note
|