File size: 3,788 Bytes
8a652b8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import json
from pathlib import Path
from unittest.mock import MagicMock

import pytest

import peachy.prompts as prompts_mod
from peachy.config import config
from peachy.memory import (
    MAX_FACTS,
    MAX_FACT_CHARS,
    MemoryFact,
    add_memory_fact,
    list_memory_facts,
    clear_memory_facts,
    forget_memory_fact,
    format_memory_for_prompt,
    memory_path_for_instance,
)
from peachy.tools.forget import Forget
from peachy.tools.remember import Remember
from peachy.tools.core_tools import ToolDependencies


def test_memory_store_adds_dedupes_caps_and_formats(tmp_path: Path) -> None:
    """Memory facts should be normalized, deduplicated, capped, and prompt-formatted."""
    first = add_memory_fact(tmp_path, "  Likes   jazz  ")
    duplicate = add_memory_fact(tmp_path, "likes jazz")

    assert first is not None
    assert duplicate == first
    assert [fact.text for fact in list_memory_facts(tmp_path)] == ["Likes jazz"]

    long_text = "x" * (MAX_FACT_CHARS + 20)
    stored_long = add_memory_fact(tmp_path, long_text)

    assert stored_long is not None
    assert len(stored_long.text) == MAX_FACT_CHARS
    assert stored_long.text.endswith("...")

    for index in range(MAX_FACTS + 5):
        add_memory_fact(tmp_path, f"Fact {index}")

    facts = list_memory_facts(tmp_path)
    assert len(facts) == MAX_FACTS
    assert facts[0].text == f"Fact {MAX_FACTS + 4}"
    assert "Likes jazz" not in [fact.text for fact in facts]

    prompt = format_memory_for_prompt(tmp_path)
    assert prompt.startswith("Things you remember about the user")
    assert f"- Fact {MAX_FACTS + 4}" in prompt


def test_memory_store_reads_mobile_json_shape_and_forgets_by_query(tmp_path: Path) -> None:
    """The Python store should preserve the mobile app JSON envelope shape."""
    path = memory_path_for_instance(tmp_path)
    path.write_text(
        json.dumps(
            {
                "version": 1,
                "facts": [
                    {"id": "m_1", "text": "Likes jazz", "createdAt": 1000},
                    {"id": "m_2", "text": "Likes jazz piano", "createdAt": 900},
                    {"id": "bad"},
                ],
            }
        ),
        encoding="utf-8",
    )

    result = forget_memory_fact(tmp_path, query="jazz")

    assert result.removed == MemoryFact(id="m_1", text="Likes jazz", created_at=1000)
    assert [candidate.text for candidate in result.candidates] == ["Likes jazz", "Likes jazz piano"]
    assert [fact.text for fact in list_memory_facts(tmp_path)] == ["Likes jazz piano"]


@pytest.mark.asyncio
async def test_memory_tools_use_instance_storage(tmp_path: Path) -> None:
    """Remember and forget tools should read/write through ToolDependencies.instance_path."""
    deps = ToolDependencies(
        reachy_mini=MagicMock(),
        movement_manager=MagicMock(),
        instance_path=tmp_path,
    )

    remember_result = await Remember()(deps, fact="Has a dog named Mochi")
    forget_result = await Forget()(deps, query="Mochi")

    assert remember_result["saved"] == "Has a dog named Mochi"
    assert forget_result["removed"] == "Has a dog named Mochi"
    assert list_memory_facts(tmp_path) == []


def test_prompt_includes_memory_fragment(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
    """Session instructions should prepend saved memories for the active app instance."""
    monkeypatch.setattr(config, "REACHY_MINI_CUSTOM_PROFILE", None)
    clear_memory_facts(tmp_path)
    add_memory_fact(tmp_path, "Prefers concise answers")

    instructions = prompts_mod.get_session_instructions(instance_path=tmp_path)

    assert instructions.startswith("Things you remember about the user")
    assert "- Prefers concise answers" in instructions
    assert "## IDENTITY" in instructions