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
@dataclass
class AppContext:
 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
AppContext( config: tiphys.TiphysConfig, event_bus: tiphys.server.EventBus = None, hook_manager: tiphys.hooks.HookManager = None, tool_registry: tiphys.tools.base.ToolRegistry = None, agent_registry: tiphys.routing.AgentRegistry = None, plugin_registry: tiphys.plugins.PluginRegistry | None = None, route_resolver: tiphys.routing.RouteResolver | None = None, policy_engine: tiphys.security.PolicyEngine | None = None, scheduler: tiphys.scheduler.service.TaskScheduler | None = None, pairing_manager: tiphys.security.pairing.PairingManager | None = None, dm_enforcer: tiphys.security.dm_policy.DMPolicyEnforcer | None = None, castor_guard: tiphys.kernel.TiphysGuard | None = None, session_budget_mgr: tiphys.kernel.SessionBudgetManager | None = None, hitl_bridge: tiphys.kernel.HITLBridge | None = None, castor_kernel: castor.core.Castor | None = None, checkpoint_store: castor.scheduler.persistence.CheckpointStore | None = None, checkpoint_hitl_bridge: tiphys.kernel.CheckpointHITLBridge | None = None, castor_agent_runner: tiphys.kernel.CastorAgentRunner | None = None)
event_bus: tiphys.server.EventBus = None
hook_manager: tiphys.hooks.HookManager = None
tool_registry: tiphys.tools.base.ToolRegistry = None
agent_registry: tiphys.routing.AgentRegistry = None
plugin_registry: tiphys.plugins.PluginRegistry | None = None
route_resolver: tiphys.routing.RouteResolver | None = None
policy_engine: tiphys.security.PolicyEngine | None = None
scheduler: tiphys.scheduler.service.TaskScheduler | None = None
pairing_manager: tiphys.security.pairing.PairingManager | None = None
dm_enforcer: tiphys.security.dm_policy.DMPolicyEnforcer | None = None
castor_guard: tiphys.kernel.TiphysGuard | None = None
session_budget_mgr: tiphys.kernel.SessionBudgetManager | None = None
hitl_bridge: tiphys.kernel.HITLBridge | None = None
castor_kernel: castor.core.Castor | None = None
checkpoint_store: castor.scheduler.persistence.CheckpointStore | None = None
checkpoint_hitl_bridge: tiphys.kernel.CheckpointHITLBridge | None = None
castor_agent_runner: tiphys.kernel.CastorAgentRunner | None = None
async def startup(self) -> None:
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.

async def shutdown(self) -> None:
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.

def get_context() -> AppContext:
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.

def set_context(ctx: AppContext) -> None:
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.

def has_context() -> bool:
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.