Documents
Home>Documents>AI>Agent>Geny

AgentSessionManager: Session Lifecycle Management

6 min readFeb 10, 2026Feb 22, 2026

AgentSessionManager Implementation - Session Lifecycle Management

As Claude Control grew more complex, it became necessary to manage session lifecycles in a structured way. I implemented an AgentSessionManager that defines clear state transitions for sessions — creation, activation, pause, resume, and termination.

Session State Machine

Session state transitions are modeled as a state machine.

from enum import Enum
from typing import Dict, Set, Optional, Callable, Awaitable
from datetime import datetime

class SessionState(str, Enum):
    CREATED = "created"
    INITIALIZING = "initializing"
    ACTIVE = "active"
    BUSY = "busy"
    PAUSED = "paused"
    ERROR = "error"
    TERMINATING = "terminating"
    TERMINATED = "terminated"

# 허용된 상태 전이
VALID_TRANSITIONS: Dict[SessionState, Set[SessionState]] = {
    SessionState.CREATED: {SessionState.INITIALIZING},
    SessionState.INITIALIZING: {SessionState.ACTIVE, SessionState.ERROR},
    SessionState.ACTIVE: {SessionState.BUSY, SessionState.PAUSED,
                          SessionState.TERMINATING, SessionState.ERROR},
    SessionState.BUSY: {SessionState.ACTIVE, SessionState.ERROR},
    SessionState.PAUSED: {SessionState.ACTIVE, SessionState.TERMINATING},
    SessionState.ERROR: {SessionState.ACTIVE, SessionState.TERMINATING},
    SessionState.TERMINATING: {SessionState.TERMINATED},
}

AgentSessionManager Core Implementation

class AgentSessionManager:
    def __init__(self, redis_store, process_manager, logger):
        self.store = redis_store
        self.process_mgr = process_manager
        self.logger = logger
        self.sessions: Dict[str, SessionInfo] = {}
        self._hooks: Dict[str, List[Callable]] = {}

    async def create_session(self, config: SessionConfig) -> SessionInfo:
        session = SessionInfo(
            id=config.session_id,
            state=SessionState.CREATED,
            workdir=config.workdir,
            created_at=datetime.now(),
        )
        self.sessions[session.id] = session
        await self._transition(session.id, SessionState.INITIALIZING)

        try:
            await self.process_mgr.start_process(session.id, config)
            await self._load_mcp_servers(session.id, config)
            await self._apply_prompt(session.id, config)
            await self._transition(session.id, SessionState.ACTIVE)
        except Exception as e:
            await self._transition(session.id, SessionState.ERROR)
            raise

        return session

    async def _transition(self, session_id: str, new_state: SessionState):
        session = self.sessions[session_id]
        valid_next = VALID_TRANSITIONS.get(session.state, set())
        if new_state not in valid_next:
            raise ValueError(
                f"Invalid transition: {session.state} -> {new_state}"
            )
        old_state = session.state
        session.state = new_state
        session.updated_at = datetime.now()

        await self.store.save_session(session_id, {
            "state": new_state.value,
            "updated_at": session.updated_at.isoformat(),
        })
        await self._fire_hooks(session_id, old_state, new_state)

Lifecycle Hooks

Callbacks can be registered to fire on state transitions.

    def on_transition(self, from_state: SessionState,
                      to_state: SessionState, callback: Callable):
        key = f"{from_state.value}:{to_state.value}"
        if key not in self._hooks:
            self._hooks[key] = []
        self._hooks[key].append(callback)

    async def _fire_hooks(self, session_id, old_state, new_state):
        key = f"{old_state.value}:{new_state.value}"
        for hook in self._hooks.get(key, []):
            await hook(session_id)

Bulk Session Management

    async def pause_all(self):
        for sid, session in self.sessions.items():
            if session.state == SessionState.ACTIVE:
                await self._transition(sid, SessionState.PAUSED)

    async def terminate_all(self):
        tasks = []
        for sid, session in self.sessions.items():
            if session.state not in (SessionState.TERMINATED,
                                      SessionState.TERMINATING):
                tasks.append(self.terminate_session(sid))
        await asyncio.gather(*tasks, return_exceptions=True)

Retrospective

Adopting the state machine pattern made it immediately clear which operations are valid in which states. Previously, there was a bug where a termination request arriving while a session was in the BUSY state would leave the process as a zombie. Enforcing explicit state transitions eliminated that class of problem entirely.

Tags
session managementlifecycleAgentAsyncIOstate machine