orchestrata_ultimate.session.event_emitting_session_controller
Reference › orchestrata_ultimate › session › event_emitting_session_controller
EventEmittingSessionController wraps a SessionController so that session lifecycle events reach the kernel's event bus (spec 016). It takes the controller and an optional event bus. begin, end and log_work call the wrapped controller and then emit session.opened, session.closed and session.work.logged, with the sequence number, purpose and objectives in the payload of the first. Emission is fire-and-forget on a daemon thread, so a synchronous caller is not blocked. With no bus the wrapper is a transparent pass-through, and __getattr__ forwards every other attribute to the wrapped controller.
At a glance
| Source | src/orchestrata_ultimate/session/event_emitting_session_controller.py |
| Lines of code | 152 |
| Classes | 1 |
| Functions | 1 |
| Imports (internal) | orchestrata_ultimate.core.base.traces, orchestrata_ultimate.core.protocols, orchestrata_ultimate.session.session |
| Imported by | orchestrata_ultimate.session |
| Tests that import it | tests/session/test_session_signatures.py |
Class diagram
Dependency diagram
API
Classes
EventEmittingSessionController
Decorators: traces(spec='016', story='US-013', reqs=['REQ-016-251']) · Line 30
SessionController wrapper that emits session lifecycle events.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__(session_controller: SessionController, event_bus: IEventBus | None = None) -> None |
Initialise the wrapper. |
__getattr__ |
__getattr__(name: str) -> Any |
Delegate all other attribute access to the wrapped SessionController. |
begin |
begin(purpose: str, objectives: list[str] | None = None) -> SessionRecord |
Begin a new session and emit session.opened. |
end |
end(achievements: list[str] | None = None, upcoming: list[str] | None = None, *, skip_goal_check: bool = False) -> SessionRecord | None |
End the current session and emit session.closed. |
log_work |
log_work(desc: str, details: list[str] | None = None) -> None |
Log work and emit session.work.logged. |
Functions
| Function | Signature | Summary |
|---|---|---|
_fire_and_forget traces |
_fire_and_forget(event_bus: IEventBus, event: SystemEvent) -> None |
Emit event in a daemon thread — sync callers do not block on it. |
Source
# mypy: ignore-errors
"""Event-emitting wrapper for SessionController lifecycle events."""
import asyncio
import logging
import threading
from typing import Any
from orchestrata_ultimate.core.base.traces import traces
from orchestrata_ultimate.core.protocols import IEventBus, SystemEvent
from orchestrata_ultimate.session.session import SessionController, SessionRecord
logger = logging.getLogger(__name__)
@traces(spec="016", story="US-013", reqs=["REQ-016-251"])
def _fire_and_forget(event_bus: IEventBus, event: SystemEvent) -> None:
"""Emit event in a daemon thread — sync callers do not block on it."""
def _run() -> None:
try:
asyncio.run(event_bus.emit(event))
except Exception:
logger.exception("Failed to emit %s", event.type)
threading.Thread(target=_run, daemon=True).start()
@traces(spec="016", story="US-013", reqs=["REQ-016-251"])
class EventEmittingSessionController:
"""SessionController wrapper that emits session lifecycle events.
Wraps a SessionController instance and emits events to an optional
IEventBus on begin, end, and log_work operations. Emission is
fire-and-forget (daemon thread) so sync callers are not blocked.
When event_bus is None the wrapper is a transparent pass-through.
Events emitted:
session.opened — on begin()
session.closed — on end()
session.work.logged — on log_work()
"""
def __init__(
self,
session_controller: SessionController,
event_bus: IEventBus | None = None,
) -> None:
"""Initialise the wrapper.
Args:
session_controller: The SessionController instance to wrap.
event_bus: Optional event bus for emission. No events are emitted
if None.
"""
self._session_controller = session_controller
self._event_bus = event_bus
def __getattr__(self, name: str) -> Any:
"""Delegate all other attribute access to the wrapped SessionController."""
return getattr(self._session_controller, name)
@traces(spec="016", story="US-013", reqs=["REQ-016-251"])
def begin(
self,
purpose: str,
objectives: list[str] | None = None,
) -> SessionRecord:
"""Begin a new session and emit session.opened.
Args:
purpose: Session focus/purpose.
objectives: Optional list of goals.
Returns:
Created SessionRecord.
"""
result = self._session_controller.begin(purpose=purpose, objectives=objectives)
if self._event_bus is not None:
_fire_and_forget(
self._event_bus,
SystemEvent(
type="session.opened",
source="session_controller",
payload={
"seq": result.seq,
"purpose": purpose,
"objectives": objectives or [],
},
),
)
return result
@traces(spec="016", story="US-013", reqs=["REQ-016-251"])
def end(
self,
achievements: list[str] | None = None,
upcoming: list[str] | None = None,
*,
skip_goal_check: bool = False,
) -> SessionRecord | None:
"""End the current session and emit session.closed.
Args:
achievements: Optional list of achievements.
upcoming: Optional list of upcoming tasks.
skip_goal_check: Passed through to the wrapped controller's
SPEC-023 Stop gate escape hatch.
Returns:
Closed SessionRecord, or None if there was no active session.
"""
result = self._session_controller.end(
achievements=achievements,
upcoming=upcoming,
skip_goal_check=skip_goal_check,
)
if result is not None and self._event_bus is not None:
_fire_and_forget(
self._event_bus,
SystemEvent(
type="session.closed",
source="session_controller",
payload={
"seq": result.seq,
"achievements": achievements or [],
"upcoming": upcoming or [],
},
),
)
return result
@traces(spec="016", story="US-013", reqs=["REQ-016-251"])
def log_work(self, desc: str, details: list[str] | None = None) -> None:
"""Log work and emit session.work.logged.
Args:
desc: Description of the work done.
details: Optional bullet-point details.
"""
self._session_controller.log_work(desc, details)
if self._event_bus is not None:
active = self._session_controller.active_session
if active is not None:
_fire_and_forget(
self._event_bus,
SystemEvent(
type="session.work.logged",
source="session_controller",
payload={"seq": active.seq, "desc": desc},
),
)