personal memory agent
0
fork

Configure Feed

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

Fix --no-observers to only disable local capture, not sense

The --no-observers flag was disabling both the observer AND observe-sense,
which broke remote upload processing and manual imports. These workflows
depend on sense listening for observe.observing events on Callosum.

Now sense always runs (handles remote/imports) while --no-observers only
disables local audio/video capture. Health checks are also skipped when
observer is disabled since there's nothing to monitor.

- Split start_observers() into start_observer() and start_sense()
- Sense starts unconditionally, observer respects --no-observers
- Added _observer_enabled flag to skip health checks when disabled
- Updated help text and docs to clarify new behavior

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+53 -17
+1 -1
docs/THINK.md
··· 18 18 - `think-cluster` groups audio and screen JSON files into report sections. Use `--start` and 19 19 `--length` to limit the report to a specific time range. 20 20 - `think-dream` runs the above tools for a single day. 21 - - `think-supervisor` monitors observation heartbeats. Use `--no-observers` to skip starting them automatically. 21 + - `think-supervisor` monitors observation heartbeats. Use `--no-observers` to disable local capture (sense still runs for remote uploads and imports). 22 22 - `muse-mcp-tools` starts an MCP server exposing search capabilities for both summary text and raw transcripts. 23 23 - `muse-cortex` starts a WebSocket API server for managing AI agent instances. 24 24
+27 -3
tests/test_supervisor.py
··· 19 19 """ 20 20 mod = importlib.import_module("think.supervisor") 21 21 22 + # Ensure observer is enabled for this test 23 + mod._observer_enabled = True 24 + 22 25 # Reset state for clean test 23 26 mod._observe_status_state["last_ts"] = 0.0 24 27 mod._observe_status_state["ever_received"] = False ··· 44 47 assert sorted(stale) == ["hear", "see"] 45 48 46 49 50 + def test_check_health_observer_disabled(monkeypatch): 51 + """Test that health checks are skipped when observer is disabled (--no-observers).""" 52 + mod = importlib.import_module("think.supervisor") 53 + 54 + # Simulate --no-observers mode (monkeypatch auto-restores after test) 55 + monkeypatch.setattr(mod, "_observer_enabled", False) 56 + 57 + # Even with stale status, should return empty (no health alerts) 58 + mod._observe_status_state["ever_received"] = True 59 + mod._observe_status_state["last_ts"] = 0.0 # Very stale 60 + stale = mod.check_health(threshold=60) 61 + assert stale == [] # No alerts when observer disabled 62 + 63 + 47 64 def test_handle_observe_status(): 48 65 """Test that observe.status events update health state. 49 66 ··· 123 140 assert len(cleared) == 1 # Still just one clear call 124 141 125 142 126 - def test_start_runners(tmp_path, mock_callosum, monkeypatch): 143 + def test_start_observer_and_sense(tmp_path, mock_callosum, monkeypatch): 144 + """Test that start_observer() and start_sense() launch their respective processes.""" 127 145 mod = importlib.import_module("think.supervisor") 128 146 129 147 started = [] ··· 156 174 monkeypatch.setattr(mod.subprocess, "Popen", fake_popen) 157 175 monkeypatch.setenv("JOURNAL_PATH", str(tmp_path)) 158 176 159 - procs = mod.start_observers() 160 - assert len(procs) == 2 177 + # Test start_observer() 178 + observer_proc = mod.start_observer() 179 + assert observer_proc is not None 161 180 assert any(cmd == ["observer", "-v"] for cmd, _, _ in started) 181 + 182 + # Test start_sense() 183 + sense_proc = mod.start_sense() 184 + assert sense_proc is not None 162 185 assert any(cmd == ["observe-sense", "-v"] for cmd, _, _ in started) 186 + 163 187 # Check that stdout and stderr capture pipes 164 188 for cmd, stdout, stderr in started: 165 189 assert stdout == subprocess.PIPE
+25 -13
think/supervisor.py
··· 111 111 "ever_received": False, # Whether we've received at least one status event 112 112 } 113 113 114 + # Track whether observer was started (for health check conditioning) 115 + _observer_enabled: bool = True 116 + 114 117 115 118 def _get_journal_path() -> Path: 116 119 journal = os.getenv("JOURNAL_PATH") ··· 225 228 Returns ["hear", "see"] if no status received within threshold, 226 229 empty list otherwise. During startup grace period (before first 227 230 status event received), returns empty list to avoid false alerts. 231 + 232 + When observer is disabled (--no-observers), always returns empty list 233 + since there's no local capture to monitor. 228 234 """ 235 + # Skip health checks if observer was not started 236 + if not _observer_enabled: 237 + return [] 238 + 229 239 # Grace period: don't alert until we've received at least one status event 230 240 if not _observe_status_state["ever_received"]: 231 241 return [] ··· 742 752 } 743 753 744 754 745 - def start_observers() -> list[ManagedProcess]: 746 - """Launch observer (platform-detected) and observe-sense with output logging.""" 747 - procs: list[ManagedProcess] = [] 748 - commands = { 749 - "observer": ["observer", "-v"], 750 - "sense": ["observe-sense", "-v"], 751 - } 752 - for name, cmd in commands.items(): 753 - procs.append(_launch_process(name, cmd, restart=True)) 754 - return procs 755 + def start_observer() -> ManagedProcess: 756 + """Launch platform-detected observer with output logging.""" 757 + return _launch_process("observer", ["observer", "-v"], restart=True) 758 + 759 + 760 + def start_sense() -> ManagedProcess: 761 + """Launch observe-sense with output logging.""" 762 + return _launch_process("sense", ["observe-sense", "-v"], restart=True) 755 763 756 764 757 765 def start_callosum_in_process() -> CallosumServer: ··· 1132 1140 parser.add_argument( 1133 1141 "--no-observers", 1134 1142 action="store_true", 1135 - help="Do not automatically start observer and observe-sense", 1143 + help="Do not start local observer (sense still runs for remote/imports)", 1136 1144 ) 1137 1145 parser.add_argument( 1138 1146 "--no-daily", ··· 1194 1202 1195 1203 logging.info("Supervisor starting...") 1196 1204 1197 - global _managed_procs, _supervisor_callosum 1205 + global _managed_procs, _supervisor_callosum, _observer_enabled 1198 1206 procs: list[ManagedProcess] = [] 1207 + _observer_enabled = not args.no_observers 1199 1208 1200 1209 # Start Callosum in-process first - it's the message bus that other services depend on 1201 1210 try: ··· 1214 1223 logging.warning(f"Failed to start Callosum connection: {e}") 1215 1224 1216 1225 # Now start other services (their startup events will be captured) 1226 + # Sense always runs (handles remote uploads and imports) 1227 + procs.append(start_sense()) 1228 + # Observer only runs if not disabled (local capture) 1217 1229 if not args.no_observers: 1218 - procs.extend(start_observers()) 1230 + procs.append(start_observer()) 1219 1231 if not args.no_cortex: 1220 1232 procs.append(start_cortex_server()) 1221 1233 if not args.no_convey: