exlab_wizard.controller.session_store#
In-memory session store + GC for creation sessions. Backend Spec §4.4.7.
The SessionStore is a dict[session_id, Session] keyed by
UUID4. v1 is intentionally non-persistent: the store lives in the
long-lived tray-server process, and a server crash forfeits all
in-flight sessions (Backend Spec §4.8). Persistence may return in v2
when unattended workflows ship.
The GC pass closes any session in SessionState.INPUT_REQUIRED
with no client heartbeat for >1 hour
(SESSION_GC_AFTER_SECONDS); see Backend Spec §4.4.7.
The transition method is the single mutation surface for a
session’s SessionState and current_phase – both fields
are updated atomically (under no-lock by virtue of the asyncio
single-threaded event loop) so the WebSocket subscriber sees a
consistent view.
Classes
|
One creation session. |
In-memory session store. |
- class exlab_wizard.controller.session_store.Session(session_id, kind, state, request, created_at, last_heartbeat, current_phase=None, next_action=NextAction.NONE, event_queue=None, pending_input=None, error=None, result=None)[source]#
Bases:
objectOne creation session. Backend Spec §4.4.7.
- session_id#
UUID4 string assigned by the store on
open().
- kind#
"project"or"run"– mirrors the controller’screate_*entry point.
- state#
Current
SessionState. Mutated only viaSessionStore.transition().
- request#
The original create request bundle (
ProjectCreateRequestorRunCreateRequest).
- created_at#
UTC timestamp at
SessionStore.open().
- last_heartbeat#
Most recent client-driven heartbeat. Refreshed by
SessionStore.heartbeat(); consulted by the GC.
- current_phase#
Mirrors
state_to_phase()ofstate. Maintained bySessionStore.transition().
- next_action#
"awaiting_input"while the session is inSessionState.INPUT_REQUIRED;"none"otherwise.
- event_queue#
WebSocket fan-out queue. Set by
SessionStore.attach_event_queue().
- pending_input#
Latest
InputRequiredPayloaddict surfaced by the plugin host; cleared on resume.
- error#
Structured error envelope (
{code, message, ...}) on failure.Nonewhile the session is in flight.
- result#
Structured
donepayload at session close.Nonewhile in flight or on failure.
- Parameters:
session_id (
str)kind (
SessionKind)state (
SessionState)request (
Any)created_at (
datetime)last_heartbeat (
datetime)next_action (
NextAction)
- kind: SessionKind#
- next_action: NextAction = 'none'#
- state: SessionState#
- class exlab_wizard.controller.session_store.SessionStore[source]#
Bases:
objectIn-memory session store. Backend Spec §4.4.7.
Sessions are keyed by UUID4 string; the dict is in-memory for v1 (no persistence across server restarts – Backend Spec §4.8).
- abandoned_older_than(age)[source]#
Return ids of
SessionState.INPUT_REQUIREDsessions whoselast_heartbeatis older thanage.Used by
gc_loop()to identify sessions abandoned by their operator (no client heartbeat for the configured window). OnlyINPUT_REQUIREDsessions are eligible – transient states are owned by the controller and finish on their own.
- attach_event_queue(session_id, queue)[source]#
Attach a WebSocket fan-out queue to the session.
The controller pushes WebSocket frames onto the queue; the
WS /api/v1/sessions/{id}/eventschannel reads from it. One queue per session; re-attaching replaces the prior queue.
- close(session_id, outcome)[source]#
Stamp a terminal-state session with the outcome envelope.
outcomeis the structured payload that the WebSocketdone/failedframe carried.DONEoutcomes go intoresult;FAILEDoutcomes go intoerror;ABORTEDsessions store the outcome underresultso the operator can recover the partial-creation summary if the cancel was a deliberate abort.
- async gc_loop(interval_seconds=300.0)[source]#
Run the abandoned-session GC forever. Backend Spec §4.4.7.
Sleeps
interval_secondsbetween passes (default 5 min); on each wake closes everyINPUT_REQUIREDsession whose heartbeat is older thanSESSION_GC_AFTER_SECONDS(default 1 hour). Cancellation is honored cleanly: the loop catchesasyncio.CancelledErrorand re-raises so the caller’s cancellation propagates.
- heartbeat(session_id)[source]#
Refresh
last_heartbeatso the GC will not close this session.No-op when the session is unknown so a stale client does not crash the server.
- transition(session_id, new_state)[source]#
Move
session_idtonew_state, updatingcurrent_phase.Validates the transition against
exlab_wizard.controller.state_machine.VALID_TRANSITIONSand raisesValueErroron illegal edges.next_actionis updated alongsidestate:INPUT_REQUIRED->"awaiting_input", every other state ->"none".- Parameters:
session_id (
str)new_state (
SessionState)
- Return type: