personal memory agent
0
fork

Configure Feed

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

Remove journal_root from EventContext to fix anti-pattern

Event handlers now access journal path the same way as route handlers:
via `from convey import state` then `state.journal_root`. This removes
the duplicate access pattern where EventContext passed journal_root
as a field while the rest of the codebase uses state.journal_root.

- Remove journal_root field from EventContext dataclass
- Update module docstring to document the import pattern
- Remove unused os import from apps/events.py
- Update tests to not pass journal_root to EventContext
- Document available imports for event handlers in APPS.md

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

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

+14 -34
+2 -5
apps/events.py
··· 29 29 - ctx.app: The app name that owns this handler 30 30 - ctx.tract: Event tract (e.g., "observe") 31 31 - ctx.event: Event type (e.g., "observed") 32 - - ctx.journal_root: Path to the journal directory 32 + 33 + Handlers can access journal path via `from convey import state` then `state.journal_root`. 33 34 """ 34 35 35 36 from __future__ import annotations 36 37 37 38 import importlib 38 39 import logging 39 - import os 40 40 from concurrent.futures import Future, ThreadPoolExecutor, TimeoutError 41 41 from dataclasses import dataclass 42 42 from pathlib import Path ··· 59 59 app: str 60 60 tract: str 61 61 event: str 62 - journal_root: str 63 62 64 63 65 64 # Handler registry: (tract, event) -> [(app_name, handler_fn), ...] ··· 270 269 271 270 tract = msg.get("tract", "") 272 271 event = msg.get("event", "") 273 - journal_root = os.environ.get("JOURNAL_PATH", "") 274 272 275 273 futures: List[Tuple[str, str, Future]] = [] 276 274 ··· 280 278 app=app_name, 281 279 tract=tract, 282 280 event=event, 283 - journal_root=journal_root, 284 281 ) 285 282 future = _executor.submit(_run_handler, app_name, handler, ctx) 286 283 futures.append((app_name, handler.__name__, future))
+1 -8
apps/remote/tests/test_events.py
··· 21 21 journal = tmp_path / "journal" 22 22 journal.mkdir() 23 23 24 - # Set JOURNAL_PATH env var and convey state 25 - monkeypatch.setenv("JOURNAL_PATH", str(journal)) 24 + # Set convey state (used by apps.utils for storage paths) 26 25 monkeypatch.setattr(state, "journal_root", str(journal)) 27 26 28 27 # Create remotes directory ··· 71 70 app="remote", 72 71 tract="observe", 73 72 event="observed", 74 - journal_root=str(remote_journal.journal), 75 73 ) 76 74 77 75 handle_observed(ctx) ··· 106 104 app="remote", 107 105 tract="observe", 108 106 event="observed", 109 - journal_root=str(remote_journal.journal), 110 107 ) 111 108 handle_observed(ctx) 112 109 ··· 137 134 app="remote", 138 135 tract="observe", 139 136 event="observed", 140 - journal_root=str(remote_journal.journal), 141 137 ) 142 138 143 139 handle_observed(ctx) ··· 159 155 app="remote", 160 156 tract="observe", 161 157 event="observed", 162 - journal_root=str(remote_journal.journal), 163 158 ) 164 159 165 160 handle_observed(ctx) ··· 180 175 app="remote", 181 176 tract="observe", 182 177 event="observed", 183 - journal_root=str(remote_journal.journal), 184 178 ) 185 179 186 180 # Should not raise ··· 202 196 app="remote", 203 197 tract="observe", 204 198 event="observed", 205 - journal_root=str(remote_journal.journal), 206 199 ) 207 200 208 201 # Should not raise
+11 -1
docs/APPS.md
··· 329 329 330 330 **Key Points:** 331 331 - Create `events.py` with functions decorated with `@on_event(tract, event)` 332 - - Handlers receive an `EventContext` with message data and app context 332 + - Handlers receive an `EventContext` with `msg`, `app`, `tract`, `event` fields 333 333 - Discovered at Convey startup; events processed serially with 30s timeout per handler 334 334 - Errors are logged but don't affect other handlers or the web server 335 335 - Wildcards supported: `@on_event("*", "*")` matches all events 336 + 337 + **Available imports** (same as route handlers): 338 + - `from convey import state` - Access `state.journal_root` 339 + - `from convey import emit` - Emit events back to Callosum 340 + - `from apps.utils import get_app_storage_path, log_app_action` - App storage 341 + - `from convey.utils import load_json, save_json, spawn_agent` - Utilities 342 + 343 + **Not available** (no Flask request context): 344 + - `request`, `session`, `current_app` 345 + - `error_response()`, `success_response()`, `parse_pagination_params()` 336 346 337 347 **Reference implementations:** 338 348 - Framework: `apps/events.py` - `EventContext` dataclass, decorator, discovery
-20
tests/test_app_events.py
··· 4 4 """Tests for the app event handling framework.""" 5 5 6 6 import threading 7 - import time 8 - from unittest.mock import patch 9 7 10 8 import pytest 11 9 ··· 225 223 # Second handler should still run despite first failing 226 224 assert success_called.wait(timeout=2.0) 227 225 228 - def test_dispatch_with_journal_root(self): 229 - """Dispatch passes journal root from environment.""" 230 - received_root = {} 231 - 232 - @on_event("test", "event") 233 - def handler(ctx): 234 - received_root["value"] = ctx.journal_root 235 - 236 - start_dispatcher(workers=1) 237 - 238 - with patch.dict("os.environ", {"JOURNAL_PATH": "/test/journal"}): 239 - dispatch({"tract": "test", "event": "event"}) 240 - time.sleep(0.1) 241 - 242 - assert received_root["value"] == "/test/journal" 243 - 244 226 245 227 class TestDiscovery: 246 228 """Tests for handler discovery.""" ··· 264 246 app="test_app", 265 247 tract="test", 266 248 event="event", 267 - journal_root="/path/to/journal", 268 249 ) 269 250 270 251 assert ctx.msg["data"] == "value" 271 252 assert ctx.app == "test_app" 272 253 assert ctx.tract == "test" 273 254 assert ctx.event == "event" 274 - assert ctx.journal_root == "/path/to/journal" 275 255 276 256 277 257 class TestDispatcherLifecycle: