All Beep services are now managed as singletons through a central AppService registry. This ensures services are not recreated on each access and provides a single source of truth for service instances.
- AppService class that owns all singletons
- Lazy initialization of services (created on first access)
- Keyed singleton support for services that need different instances per configuration (e.g., WatcherService per root path)
get_app_service()global accessorreset()andreset_registry()methods for testing
The following services are now managed as singletons:
| Service | Type | Access Pattern |
|---|---|---|
| CodeAnalysisService | Simple singleton | app.code_analysis |
| BookmarkManager | Simple singleton | app.bookmarks |
| TaskManager | Simple singleton | app.tasks |
| PermissionManager | Simple singleton | app.permissions |
| HookConfig | Simple singleton | app.hooks |
| WatcherService | Keyed singleton | app.watcher(root_path) |
| SessionManager | Factory (per-session) | app.session_manager(config, client) |
Replaced direct instantiation with AppService access:
beep/agent/graph_runner.py- PermissionManagerbeep/chat/commands/model.py- PermissionManagerbeep/chat/commands/productivity.py- BookmarkManagerbeep/chat/session_runtime_state.py- TaskManager, WatcherServicebeep/commands/watch.py- WatcherService
- TaskManager (
beep/tasks/manager.py:79): Fixed NameError whereewas used instead ofexc
Created tests/test_app_service.py with 10 tests covering:
- AppService singleton behavior
- Each managed service returns same instance
- Watcher keyed singleton (same root = same instance, different roots = different instances)
- Reset functionality
tests/test_chat_session_commands.py: Updated monkeypatch targets to also patchbeep.app_service.*since imports now flow through AppService
from beep.app_service import get_app_service
app = get_app_service()
# Access singleton services
app.code_analysis.analyze_project("/path/to/project")
app.bookmarks.add("myfile", Path("/path"))
app.permissions.evaluate_permission("shell", {}, workspace)
# Access keyed singleton (one per root path)
watcher = app.watcher("/workspace/path")
# Create session manager (not globally singleton - per session)
session_mgr = app.session_manager(config, client)- Single source of truth: All services owned in one place
- Lazy initialization: Services created only when first accessed
- Testability: Easy to reset registry between tests
- No scattered singleton code: Individual service modules are clean classes
- Consistent pattern: All services follow same lifecycle management
beep/app_service.py(new)beep/utils/singleton.py(new)beep/codeanalysis/service.pybeep/watcher/service.pybeep/bookmarks/manager.pybeep/tasks/manager.pybeep/agent/graph_runner.pybeep/chat/commands/model.pybeep/chat/commands/productivity.pybeep/chat/session_runtime_state.pybeep/commands/watch.pytests/test_app_service.py(new)tests/test_chat_session_commands.py