personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(activity-state-machine): remove `__` no-facet sentinel

facets: [] is now schema-rejected (minItems: 1) for any journal with
configured facets. The state machine and thinking.py persist paths no
longer need the `__` pseudo-facet; deleted alongside the tests that
exercised it.

Empty-facets-configured journals (a separate fallback in
`hydrate_runtime_enums`) become a state-machine no-op rather than
producing phantom `facets/__/` rows.

Follow-on to lodes 50dac8eb, b0d167d6.

+13 -103
-11
tests/test_activity_state_machine.py
··· 245 245 assert sm.state["work"]["segments"] == ["090000_300", "090500_300"] 246 246 247 247 248 - class TestPseudoFacet: 249 - def test_no_facets_uses_underscore(self): 250 - from think.activity_state_machine import ActivityStateMachine 251 - 252 - sm = ActivityStateMachine() 253 - changes = sm.update(_sense(facets=[]), "090000_300", "20260304") 254 - 255 - assert len(changes) == 1 256 - assert changes[0]["facet"] == "__" 257 - 258 - 259 248 class TestEntityTracking: 260 249 def test_extracts_names(self): 261 250 from think.activity_state_machine import ActivityStateMachine
+1 -34
tests/test_activity_state_machine_durability.py
··· 51 51 continue 52 52 record = completed_lookup.get(change["id"]) 53 53 if record: 54 - append_activity_record(change.get("facet", "__"), day, record) 54 + append_activity_record(change["facet"], day, record) 55 55 56 56 57 57 def test_state_survives_subprocess_boundary(tmp_path: Path, monkeypatch): ··· 109 109 assert prior_day_records[0]["segments"] == ["233000_300"] 110 110 assert sm2.state["test"]["since"] == "001500_300" 111 111 assert sm2.last_segment_day == "20260305" 112 - 113 - 114 - def test_legacy_flat_list_format_promotes(tmp_path: Path, monkeypatch): 115 - monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) 116 - legacy_state = [ 117 - { 118 - "id": make_activity_id("coding", "090000_300"), 119 - "activity": "coding", 120 - "state": "active", 121 - "since": "090000_300", 122 - "description": "legacy coding", 123 - "level": "medium", 124 - "active_entities": [], 125 - } 126 - ] 127 - _write_json_atomic(tmp_path / "awareness" / "activity_state.json", legacy_state) 128 - 129 - sm = ActivityStateMachine(journal_root=tmp_path) 130 - assert set(sm.state) == {"__"} 131 - assert sm.last_segment_key is None 132 - assert sm.last_segment_day is None 133 - assert sm.state["__"]["facet"] == "__" 134 - assert sm.state["__"]["segments"] == ["090000_300"] 135 - 136 - changes = sm.update(_sense(content_type="meeting"), "090500_300", "20260427") 137 - ended = [change for change in changes if change.get("state") == "ended"] 138 - assert len(ended) == 1 139 - _append_ended_records(sm, changes, "20260427") 140 - 141 - records = load_activity_records("__", "20260427") 142 - assert len(records) == 1 143 - assert records[0]["segments"] == ["090000_300"] 144 - assert sm.state["test"]["since"] == "090500_300" 145 112 146 113 147 114 def test_three_active_segments_then_idle_writes_one_record(tmp_path: Path, monkeypatch):
+7 -49
tests/test_think_activity.py
··· 777 777 778 778 # Simulate thinking.py facet_by_id logic 779 779 facet_by_id = { 780 - c["id"]: c.get("facet", "__") 781 - for c in changes 782 - if c.get("state") == "ended" 780 + c["id"]: c["facet"] for c in changes if c.get("state") == "ended" 783 781 } 784 782 for rec in sm.get_completed_activities(): 785 783 if rec["id"] in facet_by_id: ··· 808 806 changes = sm.update(self._sense(density="idle"), "091000_300", "20260304") 809 807 810 808 facet_by_id = { 811 - c["id"]: c.get("facet", "__") 812 - for c in changes 813 - if c.get("state") == "ended" 809 + c["id"]: c["facet"] for c in changes if c.get("state") == "ended" 814 810 } 815 811 for rec in sm.get_completed_activities(): 816 812 if rec["id"] in facet_by_id: ··· 859 855 self._sense(content_type="meeting"), "090500_300", "20260304" 860 856 ) 861 857 facet_by_id = { 862 - c["id"]: c.get("facet", "__") 863 - for c in changes1 864 - if c.get("state") == "ended" 858 + c["id"]: c["facet"] for c in changes1 if c.get("state") == "ended" 865 859 } 866 860 for rec in sm.get_completed_activities(): 867 861 if rec["id"] in facet_by_id: ··· 873 867 ) 874 868 # No ended changes in this update 875 869 facet_by_id2 = { 876 - c["id"]: c.get("facet", "__") 877 - for c in changes2 878 - if c.get("state") == "ended" 870 + c["id"]: c["facet"] for c in changes2 if c.get("state") == "ended" 879 871 } 880 872 # get_completed_activities() still returns activity 1, but 881 873 # facet_by_id2 is empty so nothing should be re-written ··· 923 915 assert loaded["active_entities"] == rec["active_entities"] 924 916 assert loaded["created_at"] == rec["created_at"] 925 917 926 - def test_pseudo_facet_persistence(self, monkeypatch): 927 - """Activities with no facets use '__' pseudo-facet for persistence.""" 928 - from think.activities import append_activity_record, load_activity_records 929 - from think.activity_state_machine import ActivityStateMachine 930 - 931 - with tempfile.TemporaryDirectory() as tmpdir: 932 - monkeypatch.setenv("SOLSTONE_JOURNAL", tmpdir) 933 - 934 - sm = ActivityStateMachine() 935 - sm.update( 936 - self._sense(content_type="coding", facets=[]), "090000_300", "20260304" 937 - ) 938 - changes = sm.update(self._sense(density="idle"), "090500_300", "20260304") 939 - 940 - facet_by_id = { 941 - c["id"]: c.get("facet", "__") 942 - for c in changes 943 - if c.get("state") == "ended" 944 - } 945 - for rec in sm.get_completed_activities(): 946 - if rec["id"] in facet_by_id: 947 - append_activity_record(facet_by_id[rec["id"]], "20260304", rec) 948 - 949 - # Record lands under the "__" pseudo-facet 950 - records = load_activity_records("__", "20260304") 951 - assert len(records) == 1 952 - assert records[0]["activity"] == "coding" 953 - 954 918 def test_multi_facet_ending_persists_both(self, monkeypatch): 955 919 """Multiple facets ending simultaneously all persist correctly. 956 920 ··· 976 940 977 941 # Use the fixed ended_pairs approach (matches thinking.py) 978 942 ended_pairs = [ 979 - (c["id"], c.get("facet", "__")) 980 - for c in changes 981 - if c.get("state") == "ended" 943 + (c["id"], c["facet"]) for c in changes if c.get("state") == "ended" 982 944 ] 983 945 completed_lookup = {} 984 946 for rec in sm.get_completed_activities(): ··· 1111 1073 ) 1112 1074 # Persist first completed 1113 1075 facet_by_id = { 1114 - c["id"]: c.get("facet", "__") 1115 - for c in changes1 1116 - if c.get("state") == "ended" 1076 + c["id"]: c["facet"] for c in changes1 if c.get("state") == "ended" 1117 1077 } 1118 1078 for rec in sm.get_completed_activities(): 1119 1079 if rec["id"] in facet_by_id: ··· 1139 1099 "20260304", 1140 1100 ) 1141 1101 facet_by_id2 = { 1142 - c["id"]: c.get("facet", "__") 1143 - for c in changes2 1144 - if c.get("state") == "ended" 1102 + c["id"]: c["facet"] for c in changes2 if c.get("state") == "ended" 1145 1103 } 1146 1104 for rec in sm.get_completed_activities(): 1147 1105 if rec["id"] in facet_by_id2:
+3 -3
think/activity_state_machine.py
··· 37 37 38 38 if isinstance(data, list): 39 39 active = { 40 - str(entry.get("facet") or "__"): entry 40 + str(entry["facet"]): entry 41 41 for entry in data 42 - if isinstance(entry, dict) 42 + if isinstance(entry, dict) and entry.get("facet") 43 43 } 44 44 elif isinstance(data, dict): 45 45 raw_active = data.get("active") ··· 161 161 for facet in raw_facets: 162 162 if isinstance(facet, dict) and facet.get("facet"): 163 163 facet_map[facet["facet"]] = facet 164 - current_facets = set(facet_map.keys()) if facet_map else {"__"} 164 + current_facets = set(facet_map.keys()) 165 165 166 166 for facet in sorted(set(self.state.keys()) - current_facets): 167 167 prior = self.state.pop(facet)
+2 -6
think/thinking.py
··· 671 671 idle_changes = state_machine.update(sense_json, segment, day) 672 672 # Persist completed activity records from idle transitions 673 673 ended_pairs = [ 674 - (c["id"], c.get("facet", "__")) 675 - for c in idle_changes 676 - if c.get("state") == "ended" 674 + (c["id"], c["facet"]) for c in idle_changes if c.get("state") == "ended" 677 675 ] 678 676 completed_lookup = {} 679 677 for rec in state_machine.get_completed_activities(): ··· 916 914 changes = state_machine.update(sense_json, segment, day) 917 915 # Persist completed activity records before running activity agents 918 916 ended_pairs = [ 919 - (c["id"], c.get("facet", "__")) 920 - for c in changes 921 - if c.get("state") == "ended" 917 + (c["id"], c["facet"]) for c in changes if c.get("state") == "ended" 922 918 ] 923 919 completed_lookup = {} 924 920 for rec in state_machine.get_completed_activities():