personal memory agent
1# SPDX-License-Identifier: AGPL-3.0-only
2# Copyright (c) 2026 sol pbc
3
4import json
5
6
7def _write_detected_entities(tmp_path, facet: str, day: str, rows: list[dict]) -> None:
8 entities_path = tmp_path / "facets" / facet / "entities" / f"{day}.jsonl"
9 entities_path.parent.mkdir(parents=True, exist_ok=True)
10 entities_path.write_text(
11 "".join(json.dumps(row, ensure_ascii=False) + "\n" for row in rows),
12 encoding="utf-8",
13 )
14
15
16def _activity_record():
17 return {
18 "id": "meeting_090000_300",
19 "activity": "meeting",
20 "segments": ["090000_300"],
21 "level_avg": 1.0,
22 "description": "Team sync",
23 "active_entities": ["JB", "Alex"],
24 "created_at": 1,
25 }
26
27
28def test_participation_post_hook_merges_fields_and_preserves_active_entities(
29 tmp_path, monkeypatch
30):
31 from solstone.talent.participation import post_process
32 from solstone.think.activities import append_activity_record, load_activity_records
33
34 facet = "work"
35 day = "20260418"
36 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
37
38 _write_detected_entities(
39 tmp_path,
40 facet,
41 day,
42 [
43 {
44 "id": "john_borthwick",
45 "type": "Person",
46 "name": "John Borthwick",
47 "aka": ["JB"],
48 }
49 ],
50 )
51 append_activity_record(facet, day, _activity_record())
52
53 post_process(
54 json.dumps(
55 {
56 "participation": [
57 {
58 "name": "JB",
59 "role": "attendee",
60 "source": "voice",
61 "confidence": 0.91,
62 "context": "Spoke during the meeting",
63 "entity_id": None,
64 },
65 {
66 "name": "Alex",
67 "role": "mentioned",
68 "source": "transcript",
69 "confidence": 0.42,
70 "context": "Mentioned as a collaborator",
71 "entity_id": None,
72 },
73 ],
74 "participation_confidence": 0.77,
75 }
76 ),
77 {"facet": facet, "day": day, "activity": {"id": "meeting_090000_300"}},
78 )
79
80 record = load_activity_records(facet, day)[0]
81 assert record["active_entities"] == ["JB", "Alex"]
82 assert record["participation_confidence"] == 0.77
83 assert record["participation"][0]["entity_id"] == "john_borthwick"
84 assert record["participation"][1]["entity_id"] is None
85 assert record["title"] == "Team sync"
86 assert record["details"] == ""
87 assert record["hidden"] is False
88
89
90def test_participation_post_hook_leaves_file_unchanged_on_malformed_json(
91 tmp_path, monkeypatch, caplog
92):
93 from solstone.talent.participation import post_process
94 from solstone.think.activities import append_activity_record
95
96 facet = "work"
97 day = "20260418"
98 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
99
100 append_activity_record(facet, day, _activity_record())
101 record_path = tmp_path / "facets" / facet / "activities" / f"{day}.jsonl"
102 before = record_path.read_bytes()
103
104 post_process(
105 "{not valid json",
106 {"facet": facet, "day": day, "activity": {"id": "meeting_090000_300"}},
107 )
108
109 assert record_path.read_bytes() == before
110 assert "failed to parse JSON" in caplog.text
111
112
113def test_participation_post_hook_requires_activity_context(
114 tmp_path, monkeypatch, caplog
115):
116 from solstone.talent.participation import post_process
117 from solstone.think.activities import append_activity_record
118
119 facet = "work"
120 day = "20260418"
121 monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))
122
123 append_activity_record(facet, day, _activity_record())
124 record_path = tmp_path / "facets" / facet / "activities" / f"{day}.jsonl"
125 before = record_path.read_bytes()
126
127 post_process(
128 json.dumps({"participation": []}),
129 {"facet": facet, "day": day},
130 )
131
132 assert record_path.read_bytes() == before
133 assert "missing activity context" in caplog.text