tiphys.context
Centralized application context (Composition Root pattern).
Replaces scattered global singletons with explicit dependency injection. All components receive dependencies through constructor parameters.
1""" 2Centralized application context (Composition Root pattern). 3 4Replaces scattered global singletons with explicit dependency injection. 5All components receive dependencies through constructor parameters. 6""" 7 8from __future__ import annotations 9 10from contextvars import ContextVar 11from dataclasses import dataclass, field 12from typing import TYPE_CHECKING 13 14if TYPE_CHECKING: 15 from castor import Castor, CheckpointStore 16 17 from tiphys.config import TiphysConfig 18 from tiphys.hooks.manager import HookManager 19 from tiphys.kernel.bridge import CheckpointHITLBridge, HITLBridge 20 from tiphys.kernel.budget import SessionBudgetManager 21 from tiphys.kernel.guard import TiphysGuard 22 from tiphys.kernel.runner import CastorAgentRunner 23 from tiphys.plugins.registry import PluginRegistry 24 from tiphys.routing.registry import AgentRegistry 25 from tiphys.routing.resolver import RouteResolver 26 from tiphys.scheduler.service import TaskScheduler 27 from tiphys.security.dm_policy import DMPolicyEnforcer 28 from tiphys.security.pairing import PairingManager 29 from tiphys.security.policy import PolicyEngine 30 from tiphys.server.bus import EventBus 31 from tiphys.tools.base import ToolRegistry 32 33 34@dataclass 35class AppContext: 36 """ 37 Centralized application context holding all shared services. 38 39 This is the Composition Root pattern - the single location where 40 the entire object graph is assembled. All components receive the 41 context they need through constructor parameters. 42 43 Usage: 44 # At startup (in bootstrap.py): 45 ctx = AppContext(config=load_config()) 46 set_context(ctx) 47 48 # In components: 49 def __init__(self, context: AppContext): 50 self.event_bus = context.event_bus 51 self.tools = context.tool_registry 52 """ 53 54 config: TiphysConfig 55 56 # Core services - created with defaults if not provided 57 event_bus: EventBus = field(default=None) # type: ignore[assignment] 58 hook_manager: HookManager = field(default=None) # type: ignore[assignment] 59 tool_registry: ToolRegistry = field(default=None) # type: ignore[assignment] 60 agent_registry: AgentRegistry = field(default=None) # type: ignore[assignment] 61 62 # Optional services - created in __post_init__ with cross-wiring 63 plugin_registry: PluginRegistry | None = None 64 route_resolver: RouteResolver | None = None 65 policy_engine: PolicyEngine | None = None 66 scheduler: TaskScheduler | None = None 67 pairing_manager: PairingManager | None = None 68 dm_enforcer: DMPolicyEnforcer | None = None 69 70 # Castor kernel services (L1 Guard) 71 castor_guard: TiphysGuard | None = None 72 session_budget_mgr: SessionBudgetManager | None = None 73 hitl_bridge: HITLBridge | None = None 74 75 # Castor kernel services (L2 Deep Guard) 76 castor_kernel: Castor | None = None 77 checkpoint_store: CheckpointStore | None = None 78 checkpoint_hitl_bridge: CheckpointHITLBridge | None = None 79 castor_agent_runner: CastorAgentRunner | None = None 80 81 def __post_init__(self) -> None: 82 """Wire up cross-component dependencies.""" 83 # Import here to avoid circular imports 84 from tiphys.hooks.manager import HookManager 85 from tiphys.plugins.registry import PluginRegistry 86 from tiphys.routing.registry import AgentRegistry 87 from tiphys.routing.resolver import RouteResolver 88 from tiphys.scheduler.service import TaskScheduler 89 from tiphys.security.dm_policy import DMPolicyEnforcer 90 from tiphys.security.pairing import PairingManager 91 from tiphys.security.policy import create_default_policy 92 from tiphys.server.bus import EventBus 93 from tiphys.tools.base import ToolRegistry 94 95 # Create core services if not provided 96 if self.event_bus is None: 97 self.event_bus = EventBus() 98 99 if self.hook_manager is None: 100 self.hook_manager = HookManager() 101 102 if self.tool_registry is None: 103 self.tool_registry = ToolRegistry() 104 105 if self.agent_registry is None: 106 self.agent_registry = AgentRegistry() 107 108 # Create optional services with cross-wiring 109 if self.plugin_registry is None: 110 self.plugin_registry = PluginRegistry(hook_manager=self.hook_manager) 111 112 if self.route_resolver is None: 113 self.route_resolver = RouteResolver(agent_registry=self.agent_registry) 114 115 if self.policy_engine is None: 116 self.policy_engine = create_default_policy(self.config) 117 118 if self.scheduler is None: 119 self.scheduler = TaskScheduler(context=self) 120 121 if self.pairing_manager is None: 122 self.pairing_manager = PairingManager() 123 124 if self.dm_enforcer is None: 125 self.dm_enforcer = DMPolicyEnforcer(pairing_manager=self.pairing_manager) 126 127 async def startup(self) -> None: 128 """ 129 Initialize all components. 130 131 Called during application lifespan startup. 132 Emits GatewayBootEvent to notify hooks. 133 """ 134 from tiphys.hooks.events import GatewayBootEvent 135 136 if self.scheduler: 137 await self.scheduler.start() 138 139 await self.hook_manager.emit(GatewayBootEvent(config=self.config.model_dump())) 140 141 async def shutdown(self) -> None: 142 """ 143 Clean shutdown of all components. 144 145 Called during application lifespan shutdown. 146 Emits GatewayShutdownEvent to notify hooks. 147 """ 148 from tiphys.hooks.events import GatewayShutdownEvent 149 150 if self.scheduler: 151 await self.scheduler.stop() 152 153 await self.hook_manager.emit(GatewayShutdownEvent()) 154 155 156# Context variable for request-scoped access 157_current_context: ContextVar[AppContext | None] = ContextVar("app_context", default=None) 158 159 160def get_context() -> AppContext: 161 """ 162 Get the current application context. 163 164 Returns: 165 The current AppContext. 166 167 Raises: 168 RuntimeError: If context has not been initialized. 169 """ 170 ctx = _current_context.get() 171 if ctx is None: 172 raise RuntimeError( 173 "AppContext not initialized. " 174 "Call set_context() first, or use create_app_context() from bootstrap." 175 ) 176 return ctx 177 178 179def set_context(ctx: AppContext) -> None: 180 """ 181 Set the current application context. 182 183 Args: 184 ctx: The AppContext to set as current. 185 """ 186 _current_context.set(ctx) 187 188 189def has_context() -> bool: 190 """ 191 Check if an application context is set. 192 193 Returns: 194 True if context is available, False otherwise. 195 """ 196 return _current_context.get() is not None
35@dataclass 36class AppContext: 37 """ 38 Centralized application context holding all shared services. 39 40 This is the Composition Root pattern - the single location where 41 the entire object graph is assembled. All components receive the 42 context they need through constructor parameters. 43 44 Usage: 45 # At startup (in bootstrap.py): 46 ctx = AppContext(config=load_config()) 47 set_context(ctx) 48 49 # In components: 50 def __init__(self, context: AppContext): 51 self.event_bus = context.event_bus 52 self.tools = context.tool_registry 53 """ 54 55 config: TiphysConfig 56 57 # Core services - created with defaults if not provided 58 event_bus: EventBus = field(default=None) # type: ignore[assignment] 59 hook_manager: HookManager = field(default=None) # type: ignore[assignment] 60 tool_registry: ToolRegistry = field(default=None) # type: ignore[assignment] 61 agent_registry: AgentRegistry = field(default=None) # type: ignore[assignment] 62 63 # Optional services - created in __post_init__ with cross-wiring 64 plugin_registry: PluginRegistry | None = None 65 route_resolver: RouteResolver | None = None 66 policy_engine: PolicyEngine | None = None 67 scheduler: TaskScheduler | None = None 68 pairing_manager: PairingManager | None = None 69 dm_enforcer: DMPolicyEnforcer | None = None 70 71 # Castor kernel services (L1 Guard) 72 castor_guard: TiphysGuard | None = None 73 session_budget_mgr: SessionBudgetManager | None = None 74 hitl_bridge: HITLBridge | None = None 75 76 # Castor kernel services (L2 Deep Guard) 77 castor_kernel: Castor | None = None 78 checkpoint_store: CheckpointStore | None = None 79 checkpoint_hitl_bridge: CheckpointHITLBridge | None = None 80 castor_agent_runner: CastorAgentRunner | None = None 81 82 def __post_init__(self) -> None: 83 """Wire up cross-component dependencies.""" 84 # Import here to avoid circular imports 85 from tiphys.hooks.manager import HookManager 86 from tiphys.plugins.registry import PluginRegistry 87 from tiphys.routing.registry import AgentRegistry 88 from tiphys.routing.resolver import RouteResolver 89 from tiphys.scheduler.service import TaskScheduler 90 from tiphys.security.dm_policy import DMPolicyEnforcer 91 from tiphys.security.pairing import PairingManager 92 from tiphys.security.policy import create_default_policy 93 from tiphys.server.bus import EventBus 94 from tiphys.tools.base import ToolRegistry 95 96 # Create core services if not provided 97 if self.event_bus is None: 98 self.event_bus = EventBus() 99 100 if self.hook_manager is None: 101 self.hook_manager = HookManager() 102 103 if self.tool_registry is None: 104 self.tool_registry = ToolRegistry() 105 106 if self.agent_registry is None: 107 self.agent_registry = AgentRegistry() 108 109 # Create optional services with cross-wiring 110 if self.plugin_registry is None: 111 self.plugin_registry = PluginRegistry(hook_manager=self.hook_manager) 112 113 if self.route_resolver is None: 114 self.route_resolver = RouteResolver(agent_registry=self.agent_registry) 115 116 if self.policy_engine is None: 117 self.policy_engine = create_default_policy(self.config) 118 119 if self.scheduler is None: 120 self.scheduler = TaskScheduler(context=self) 121 122 if self.pairing_manager is None: 123 self.pairing_manager = PairingManager() 124 125 if self.dm_enforcer is None: 126 self.dm_enforcer = DMPolicyEnforcer(pairing_manager=self.pairing_manager) 127 128 async def startup(self) -> None: 129 """ 130 Initialize all components. 131 132 Called during application lifespan startup. 133 Emits GatewayBootEvent to notify hooks. 134 """ 135 from tiphys.hooks.events import GatewayBootEvent 136 137 if self.scheduler: 138 await self.scheduler.start() 139 140 await self.hook_manager.emit(GatewayBootEvent(config=self.config.model_dump())) 141 142 async def shutdown(self) -> None: 143 """ 144 Clean shutdown of all components. 145 146 Called during application lifespan shutdown. 147 Emits GatewayShutdownEvent to notify hooks. 148 """ 149 from tiphys.hooks.events import GatewayShutdownEvent 150 151 if self.scheduler: 152 await self.scheduler.stop() 153 154 await self.hook_manager.emit(GatewayShutdownEvent())
Centralized application context holding all shared services.
This is the Composition Root pattern - the single location where the entire object graph is assembled. All components receive the context they need through constructor parameters.
Usage: # At startup (in bootstrap.py): ctx = AppContext(config=load_config()) set_context(ctx)
# In components:
def __init__(self, context: AppContext):
self.event_bus = context.event_bus
self.tools = context.tool_registry
128 async def startup(self) -> None: 129 """ 130 Initialize all components. 131 132 Called during application lifespan startup. 133 Emits GatewayBootEvent to notify hooks. 134 """ 135 from tiphys.hooks.events import GatewayBootEvent 136 137 if self.scheduler: 138 await self.scheduler.start() 139 140 await self.hook_manager.emit(GatewayBootEvent(config=self.config.model_dump()))
Initialize all components.
Called during application lifespan startup. Emits GatewayBootEvent to notify hooks.
142 async def shutdown(self) -> None: 143 """ 144 Clean shutdown of all components. 145 146 Called during application lifespan shutdown. 147 Emits GatewayShutdownEvent to notify hooks. 148 """ 149 from tiphys.hooks.events import GatewayShutdownEvent 150 151 if self.scheduler: 152 await self.scheduler.stop() 153 154 await self.hook_manager.emit(GatewayShutdownEvent())
Clean shutdown of all components.
Called during application lifespan shutdown. Emits GatewayShutdownEvent to notify hooks.
161def get_context() -> AppContext: 162 """ 163 Get the current application context. 164 165 Returns: 166 The current AppContext. 167 168 Raises: 169 RuntimeError: If context has not been initialized. 170 """ 171 ctx = _current_context.get() 172 if ctx is None: 173 raise RuntimeError( 174 "AppContext not initialized. " 175 "Call set_context() first, or use create_app_context() from bootstrap." 176 ) 177 return ctx
Get the current application context.
Returns: The current AppContext.
Raises: RuntimeError: If context has not been initialized.
180def set_context(ctx: AppContext) -> None: 181 """ 182 Set the current application context. 183 184 Args: 185 ctx: The AppContext to set as current. 186 """ 187 _current_context.set(ctx)
Set the current application context.
Args: ctx: The AppContext to set as current.
190def has_context() -> bool: 191 """ 192 Check if an application context is set. 193 194 Returns: 195 True if context is available, False otherwise. 196 """ 197 return _current_context.get() is not None
Check if an application context is set.
Returns: True if context is available, False otherwise.