Session Management (Ops)¶
Unified, config-driven session management for tmux and containers (Docker/Podman).
kstlib.ops wraps tmux and Docker/Podman behind a single API to start, stop, attach,
monitor, and collect logs from persistent processes. All session definitions live in
kstlib.conf.yml, making the config file the single source of truth.
Container interoperability: the list, status, logs, attach, and stop commands
work with any container running on the host, not only those created by kstlib.
The start command creates new containers from an image definition.
Tip
Pair this reference with Ops for the feature guide.
Quick overview¶
SessionManageris the main facade for session lifecycle managementTmuxRunnerprovides direct tmux session controlContainerRunnerprovides direct Podman/Docker container controlSessionConfigandSessionStatusare the core data modelsConfiguration follows the standard priority chain: constructor args >
kstlib.conf.yml> defaults
Configuration cascade¶
The module consults the loaded config for session definitions:
ops:
default_backend: tmux
tmux_binary: tmux
container_runtime: null # auto-detect (podman or docker)
sessions:
astro:
backend: tmux
command: "python -m astro.bot"
working_dir: "/opt/astro"
astro-prod:
backend: container
image: "astro-bot:latest"
volumes:
- "./data:/app/data"
Load a session from config:
from kstlib.ops import SessionManager
session = SessionManager.from_config("astro")
Note
Sessions defined in config are visible in kstlib ops list with state
DEFINED even before they are started. The container_runtime field
accepts null for auto-detection (checks podman first, then docker).
CLI arguments override config values when both are provided.
Usage patterns¶
Basic tmux session¶
from kstlib.ops import SessionManager
session = SessionManager("dev", backend="tmux")
session.start("python app.py")
session.attach() # Ctrl+B D to detach
session.stop()
Container with volumes¶
from kstlib.ops import SessionManager
session = SessionManager(
"prod",
backend="container",
image="app:latest",
volumes=["./data:/app/data"],
log_volume="./logs:/app/logs",
)
session.start()
Session lifecycle¶
from kstlib.ops import SessionManager
session = SessionManager.from_config("astro")
# Check state
if session.exists():
status = session.status()
print(f"State: {status.state.value}, PID: {status.pid}")
# Logs
logs = session.logs(lines=100)
# Running check
if session.is_running():
session.stop(graceful=True, timeout=10)
Low-level runner access¶
from kstlib.ops import TmuxRunner, ContainerRunner, SessionConfig, BackendType
# Direct tmux
tmux = TmuxRunner()
config = SessionConfig(name="test", backend=BackendType.TMUX, command="bash")
tmux.start(config)
tmux.send_keys("test", "echo hello")
tmux.stop("test")
# Direct container
container = ContainerRunner(runtime="podman")
config = SessionConfig(
name="test",
backend=BackendType.CONTAINER,
image="python:3.10-slim",
)
container.start(config)
container.exec("test", "pip list")
container.stop("test")
Module reference¶
Session management with tmux and container backends.
This module provides config-driven session management for running persistent processes like trading bots. It supports two backends:
tmux: For local development and backtesting with detach/attach
container: For production with Podman/Docker and persistent logs
The module is designed around the principle of backend abstraction, allowing the same code to work with either tmux sessions or containers.
Examples
Local development with tmux:
>>> from kstlib.ops import SessionManager
>>> session = SessionManager("astro", backend="tmux")
>>> session.start("python -m astro.bot")
>>> session.attach() # tmux attach-session -t astro
Production with containers:
>>> session = SessionManager(
... "astro",
... backend="container",
... image="astro-bot:latest",
... )
>>> session.start()
>>> session.attach() # podman attach astro
Config-driven usage:
>>> session = SessionManager.from_config("astro")
>>> session.start()
Note
The attach() method uses os.execvp and replaces the current process. It does not return on success.
- class kstlib.ops.AbstractRunner(*args, **kwargs)[source]
Bases:
ProtocolProtocol defining the interface for session runners.
All session runners (TmuxRunner, ContainerRunner) must implement this protocol to ensure consistent behavior across backends.
The protocol defines core operations for session lifecycle management: - start: Create and start a new session - stop: Stop a running session - attach: Attach terminal to a session (replaces current process) - status: Get current session status - logs: Retrieve session logs - exists: Check if a session exists - list_sessions: List all sessions managed by this runner
Examples
>>> def run_session(runner: AbstractRunner, config: SessionConfig) -> None: ... status = runner.start(config) ... print(f"Session {status.name} started with PID {status.pid}")
- start(self, config: 'SessionConfig') 'SessionStatus' -> SessionStatus[source]
Create and start a new session.
- Parameters:
config (SessionConfig) – Session configuration including name, command, etc.
- Returns:
SessionStatus with current state after starting.
- Raises:
SessionExistsError – If a session with this name already exists.
SessionStartError – If the session failed to start.
BackendNotFoundError – If the backend binary is not available.
- Return type:
SessionStatus
- stop(self, name: 'str', *, graceful: 'bool' = True, timeout: 'int' = 10) 'bool' -> bool[source]
Stop a running session.
- Parameters:
- Returns:
True if the session was stopped, False if it was not running.
- Raises:
SessionNotFoundError – If the session does not exist.
SessionStopError – If the session could not be stopped.
- Return type:
- attach(self, name: 'str') 'None' -> None[source]
Attach terminal to a running session.
This method replaces the current process with the attach command using os.execvp. It does not return on success.
- Parameters:
name (str) – Session name to attach to.
- Raises:
SessionNotFoundError – If the session does not exist.
SessionAttachError – If attachment failed.
Note
This method uses os.execvp and does not return on success. The calling process is replaced by the attach command.
- status(self, name: 'str') 'SessionStatus' -> SessionStatus[source]
Get the current status of a session.
- Parameters:
name (str) – Session name to query.
- Returns:
SessionStatus with current state information.
- Raises:
SessionNotFoundError – If the session does not exist.
- Return type:
SessionStatus
- logs(self, name: 'str', lines: 'int' = 100) 'str' -> str[source]
Retrieve recent logs from a session.
- Parameters:
- Returns:
String containing the log output with ANSI codes preserved.
- Raises:
SessionNotFoundError – If the session does not exist.
- Return type:
- exists(self, name: 'str') 'bool' -> bool[source]
Check if a session with the given name exists.
- list_sessions(self) 'list[SessionStatus]' -> list[SessionStatus][source]
List all sessions managed by this runner.
- Returns:
List of SessionStatus for all sessions. Empty list if no sessions exist.
- Return type:
list[SessionStatus]
- __init__(self, *args, **kwargs)
- exception kstlib.ops.BackendNotFoundError[source]
Bases:
OpsError,FileNotFoundErrorBackend binary (tmux, podman, docker) not found in PATH.
Raised when the required backend binary is not installed or not accessible from the current PATH.
- class kstlib.ops.BackendType(value)[source]
-
Backend type for session management.
- TMUX
Use tmux for session management (dev/local).
- CONTAINER
Use Podman/Docker for session management (prod).
- TMUX = 'tmux'
- CONTAINER = 'container'
- __format__(self, format_spec)
Returns format using actual value type unless __str__ has been overridden.
- class kstlib.ops.ContainerRunner(runtime=None)[source]
Bases:
objectContainer runner for Podman/Docker containers.
Manages containers for running persistent processes with pseudo-terminal support for TUI applications.
- Parameters:
runtime (str | None) – Container runtime to use (“podman” or “docker”).
- runtime
The container runtime name.
- binary
The validated runtime binary path.
Examples
>>> runner = ContainerRunner() # Uses podman by default >>> runner = ContainerRunner(runtime="docker") >>> config = SessionConfig( ... name="app", ... backend=BackendType.CONTAINER, ... image="python:3.10-slim", ... ) >>> status = runner.start(config)
- __init__(self, runtime: 'str | None' = None) 'None' -> None[source]
Initialize ContainerRunner.
- Parameters:
runtime (str | None) – Container runtime (“podman”, “docker”, or None for auto-detect). Auto-detection tries podman first, then docker.
- property runtime: str
Return the configured runtime name.
- property binary: str
Return validated container runtime binary path.
- Raises:
ContainerRuntimeNotFoundError – If runtime is not installed.
- start(self, config: 'SessionConfig') 'SessionStatus' -> SessionStatus[source]
Create and start a new container.
- Parameters:
config (SessionConfig) – Session configuration with image and options.
- Returns:
SessionStatus with state information.
- Raises:
SessionExistsError – If container already exists and is running.
SessionStartError – If container failed to start.
ContainerRuntimeNotFoundError – If runtime is not installed.
- Return type:
SessionStatus
- stop(self, name: 'str', *, graceful: 'bool' = True, timeout: 'int' = 10) 'bool' -> bool[source]
Stop a running container.
- Parameters:
- Returns:
True if stopped, False if not running.
- Raises:
SessionNotFoundError – If container doesn’t exist.
SessionStopError – If container couldn’t be stopped.
- Return type:
- attach(self, name: 'str') 'None' -> None[source]
Attach to a running container.
This method replaces the current process with container attach. It does not return on success.
- Parameters:
name (str) – Container name to attach to.
- Raises:
SessionNotFoundError – If container doesn’t exist.
SessionAttachError – If attachment failed.
Note
Use Ctrl+P Ctrl+Q to detach from the container.
- status(self, name: 'str') 'SessionStatus' -> SessionStatus[source]
Get status of a container.
- Parameters:
name (str) – Container name to query.
- Returns:
SessionStatus with current state.
- Raises:
SessionNotFoundError – If container doesn’t exist.
- Return type:
SessionStatus
- logs(self, name: 'str', lines: 'int' = 100) 'str' -> str[source]
Retrieve recent logs from a container.
- Parameters:
- Returns:
String with log output (ANSI codes preserved).
- Raises:
SessionNotFoundError – If container doesn’t exist.
- Return type:
- exists(self, name: 'str') 'bool' -> bool[source]
Check if a container with the given name exists.
- list_sessions(self) 'list[SessionStatus]' -> list[SessionStatus][source]
List all containers.
- Returns:
List of SessionStatus for all containers.
- Return type:
list[SessionStatus]
- exec(self, name: 'str', command: 'str', *, interactive: 'bool' = False) 'subprocess.CompletedProcess[str]' -> subprocess.CompletedProcess[str][source]
Execute a command in a running container.
- Parameters:
- Returns:
CompletedProcess with stdout/stderr.
- Raises:
SessionNotFoundError – If container doesn’t exist.
- Return type:
- exception kstlib.ops.ContainerRuntimeNotFoundError[source]
Bases:
BackendNotFoundErrorContainer runtime (podman or docker) not found in PATH.
Install podman or docker to use the container backend: - Podman: https://podman.io/getting-started/installation - Docker: https://docs.docker.com/get-docker/
- exception kstlib.ops.OpsError[source]
Bases:
KstlibErrorBase exception for all ops module errors.
All ops-specific exceptions inherit from this class, allowing for easy catching of any ops error.
- exception kstlib.ops.SessionAmbiguousError(name, backends)[source]
Bases:
SessionErrorSession exists in multiple backends.
Raised when auto-detection finds a session in both tmux and container backends, requiring explicit backend specification.
- exception kstlib.ops.SessionAttachError(name, backend, reason)[source]
Bases:
SessionErrorFailed to attach to session or container.
Raised when the backend command to attach to a session fails. This can happen if the session is not running or if the terminal is not interactive.
- class kstlib.ops.SessionConfig(name, backend=BackendType.TMUX, command=None, working_dir=None, env=<factory>, image=None, volumes=<factory>, ports=<factory>, runtime=None, log_volume=None)[source]
Bases:
objectConfiguration for creating a session.
This dataclass holds all configuration options for both tmux and container backends. Options that are not applicable to the selected backend are ignored.
- name
Unique session name (required).
- Type:
- backend
Backend type (tmux or container).
- Type:
kstlib.ops.models.BackendType
- command
Command to run in the session (tmux) or container.
- Type:
str | None
- working_dir
Working directory for the session.
- Type:
str | None
- image
Container image to use (container backend only).
- Type:
str | None
- runtime
Container runtime to use (“podman” or “docker”).
- Type:
str | None
- log_volume
Log volume mount for persistence (auto-mounted).
- Type:
str | None
Examples
>>> config = SessionConfig( ... name="astro", ... backend=BackendType.TMUX, ... command="python -m astro.bot", ... )
>>> config = SessionConfig( ... name="astro-prod", ... backend=BackendType.CONTAINER, ... image="astro-bot:latest", ... volumes=["./data:/app/data"], ... log_volume="./logs:/app/logs", ... )
- name: str
- backend: BackendType
- __post_init__(self) 'None' -> None[source]
Validate configuration values.
- Raises:
ValueError – If any configuration value is invalid.
- __init__(self, name: 'str', backend: 'BackendType' = <BackendType.TMUX: 'tmux'>, command: 'str | None' = None, working_dir: 'str | None' = None, env: 'dict[str, str]' = <factory>, image: 'str | None' = None, volumes: 'list[str]' = <factory>, ports: 'list[str]' = <factory>, runtime: 'str | None' = None, log_volume: 'str | None' = None) None -> None
- exception kstlib.ops.SessionConfigError[source]
Bases:
OpsErrorConfiguration error for session management.
Raised when session configuration is invalid or missing required fields.
- exception kstlib.ops.SessionError[source]
Bases:
OpsErrorBase exception for session-related errors.
All session operation exceptions inherit from this class.
- exception kstlib.ops.SessionExistsError(name, backend)[source]
Bases:
SessionErrorSession or container with this name already exists.
Raised when attempting to create a session with a name that is already in use by another session or container.
- class kstlib.ops.SessionManager(name, *, backend=BackendType.TMUX, **kwargs)[source]
Bases:
objectConfig-driven session manager with backend abstraction.
Provides a unified interface for managing sessions across different backends (tmux, container). The backend can be specified directly or loaded from kstlib.conf.yml configuration.
- Parameters:
- name
The session name.
- backend
The backend type being used.
- config
The full session configuration.
Examples
>>> # Direct instantiation with tmux >>> session = SessionManager("dev", backend="tmux") >>> session.start("python app.py") >>> session.attach()
>>> # Direct instantiation with container >>> session = SessionManager( ... "prod", ... backend="container", ... image="app:latest", ... volumes=["./data:/app/data"], ... ) >>> session.start()
>>> # From config file (recommended) >>> session = SessionManager.from_config("astro")
- __init__(self, name: 'str', *, backend: 'str | BackendType' = <BackendType.TMUX: 'tmux'>, **kwargs: 'Any') 'None' -> None[source]
Initialize SessionManager.
- property name: str
Return the session name.
- property backend: BackendType
Return the backend type.
- property config: SessionConfig
Return the session configuration.
- classmethod from_config(name: 'str') 'SessionManager' -> SessionManager[source]
Create SessionManager from kstlib configuration.
Loads session configuration from kstlib.conf.yml under the ops.sessions.{name} key.
- Parameters:
name (str) – Session name to load from config.
- Returns:
SessionManager configured from the config file.
- Raises:
SessionConfigError – If session not found in config.
- Return type:
SessionManager
Example
Config file (kstlib.conf.yml):
ops: sessions: astro: backend: tmux command: "python -m astro.bot" working_dir: "/opt/astro"
Usage:
>>> session = SessionManager.from_config("astro")
- start(self, command: 'str | None' = None, **kwargs: 'Any') 'SessionStatus' -> SessionStatus[source]
Start the session.
- Parameters:
- Returns:
SessionStatus with current state.
- Raises:
SessionExistsError – If session already exists.
SessionStartError – If session failed to start.
- Return type:
SessionStatus
- stop(self, *, graceful: 'bool' = True, timeout: 'int' = 10) 'bool' -> bool[source]
Stop the session.
- Parameters:
- Returns:
True if stopped successfully.
- Raises:
SessionNotFoundError – If session does not exist.
SessionStopError – If session could not be stopped.
- Return type:
- attach(self) 'None' -> None[source]
Attach to the session.
This method replaces the current process. It does not return on success.
- Raises:
SessionNotFoundError – If session does not exist.
SessionAttachError – If attachment failed.
- status(self) 'SessionStatus' -> SessionStatus[source]
Get current session status.
- Returns:
SessionStatus with current state.
- Raises:
SessionNotFoundError – If session does not exist.
- Return type:
SessionStatus
- logs(self, lines: 'int' = 100) 'str' -> str[source]
Get recent session logs.
- Parameters:
lines (int) – Number of lines to retrieve.
- Returns:
Log output as string (ANSI codes preserved).
- Raises:
SessionNotFoundError – If session does not exist.
- Return type:
- exists(self) 'bool' -> bool[source]
Check if the session exists.
- Returns:
True if session exists, False otherwise.
- Return type:
- exception kstlib.ops.SessionNotFoundError(name, backend)[source]
Bases:
SessionErrorSession or container not found.
Raised when attempting to access a session that does not exist.
- exception kstlib.ops.SessionStartError(name, backend, reason)[source]
Bases:
SessionErrorFailed to start session or container.
Raised when the backend command to create a new session fails.
- class kstlib.ops.SessionState(value)[source]
-
State of a session or container.
- RUNNING
Session is active and running.
- STOPPED
Session was stopped gracefully.
- EXITED
Container exited (with exit code).
- DEFINED
Session exists in config but has not been started.
- UNKNOWN
State cannot be determined.
- RUNNING = 'running'
- STOPPED = 'stopped'
- EXITED = 'exited'
- DEFINED = 'defined'
- UNKNOWN = 'unknown'
- __format__(self, format_spec)
Returns format using actual value type unless __str__ has been overridden.
- class kstlib.ops.SessionStatus(name, state, backend, pid=None, created_at=None, window_count=0, socket_name=None, image=None, exit_code=None)[source]
Bases:
objectCurrent status of a session or container.
This dataclass holds the runtime status information for a session, including state, PID, creation time, and backend-specific details.
- name
Session name.
- Type:
- state
Current state (running, stopped, exited, unknown).
- Type:
kstlib.ops.models.SessionState
- backend
Backend type used for this session.
- Type:
kstlib.ops.models.BackendType
- pid
Process ID (tmux server PID or container main PID).
- Type:
int | None
- created_at
ISO timestamp when the session was created.
- Type:
str | None
- window_count
Number of tmux windows (tmux backend only).
- Type:
- socket_name
tmux socket name for custom sockets (tmux backend only).
- Type:
str | None
- image
Container image name (container backend only).
- Type:
str | None
- exit_code
Container exit code if exited (container backend only).
- Type:
int | None
Examples
>>> status = SessionStatus( ... name="astro", ... state=SessionState.RUNNING, ... backend=BackendType.TMUX, ... pid=12345, ... window_count=1, ... )
>>> status = SessionStatus( ... name="astro-prod", ... state=SessionState.RUNNING, ... backend=BackendType.CONTAINER, ... pid=67890, ... image="astro-bot:latest", ... )
- name: str
- state: SessionState
- backend: BackendType
- window_count: int
- __init__(self, name: 'str', state: 'SessionState', backend: 'BackendType', pid: 'int | None' = None, created_at: 'str | None' = None, window_count: 'int' = 0, socket_name: 'str | None' = None, image: 'str | None' = None, exit_code: 'int | None' = None) None -> None
- exception kstlib.ops.SessionStopError(name, backend, reason)[source]
Bases:
SessionErrorFailed to stop session or container.
Raised when the backend command to stop a session fails.
- exception kstlib.ops.TmuxNotFoundError[source]
Bases:
BackendNotFoundErrortmux binary not found in PATH.
Install tmux to use the tmux backend: - macOS: brew install tmux - Ubuntu/Debian: apt install tmux - Windows: Use WSL2 with tmux installed
- class kstlib.ops.TmuxRunner(binary='tmux', socket_name=None)[source]
Bases:
objecttmux session runner for local development.
Manages tmux sessions for running persistent processes with detach/attach capability. Supports custom sockets via the
-Lflag for multi-instance setups (e.g. multiple bots on a single host).- Parameters:
- binary
The tmux binary path (validated on first use).
Examples
>>> runner = TmuxRunner() >>> config = SessionConfig(name="bot", command="python bot.py") >>> status = runner.start(config) >>> runner.attach("bot")
- __init__(self, binary: 'str' = 'tmux', socket_name: 'str | None' = None) 'None' -> None[source]
Initialize TmuxRunner.
- property binary: str
Return validated tmux binary path.
- Raises:
TmuxNotFoundError – If tmux is not installed.
- start(self, config: 'SessionConfig') 'SessionStatus' -> SessionStatus[source]
Create and start a new tmux session.
- Parameters:
config (SessionConfig) – Session configuration.
- Returns:
SessionStatus with state information.
- Raises:
SessionExistsError – If session already exists.
SessionStartError – If session failed to start.
TmuxNotFoundError – If tmux is not installed.
- Return type:
SessionStatus
Note
The exists() check is subject to TOCTOU but provides a clear error message. tmux itself will reject duplicate session names.
- stop(self, name: 'str', *, graceful: 'bool' = True, timeout: 'int' = 10) 'bool' -> bool[source]
Stop a tmux session.
- Parameters:
- Returns:
True if stopped, False if not running.
- Raises:
SessionNotFoundError – If session doesn’t exist.
SessionStopError – If session couldn’t be stopped.
- Return type:
- attach(self, name: 'str') 'None' -> None[source]
Attach to a tmux session.
This method replaces the current process with tmux attach. It does not return on success.
- Parameters:
name (str) – Session name to attach to.
- Raises:
SessionNotFoundError – If session doesn’t exist.
SessionAttachError – If attach failed.
- status(self, name: 'str') 'SessionStatus' -> SessionStatus[source]
Get status of a tmux session.
- Parameters:
name (str) – Session name to query.
- Returns:
SessionStatus with current state.
- Raises:
SessionNotFoundError – If session doesn’t exist.
- Return type:
SessionStatus
- logs(self, name: 'str', lines: 'int' = 100) 'str' -> str[source]
Capture recent output from a tmux session.
- Parameters:
- Returns:
String with captured output (ANSI codes preserved).
- Raises:
SessionNotFoundError – If session doesn’t exist.
- Return type:
- exists(self, name: 'str') 'bool' -> bool[source]
Check if a tmux session exists.
- list_sessions(self) 'list[SessionStatus]' -> list[SessionStatus][source]
List all tmux sessions.
- Returns:
List of SessionStatus for all sessions.
- Return type:
list[SessionStatus]
- send_keys(self, name: 'str', keys: 'str', *, enter: 'bool' = True) 'None' -> None[source]
Send keys to a tmux session.
- Parameters:
- Raises:
SessionNotFoundError – If session doesn’t exist.
- kstlib.ops.auto_detect_backend(name: 'str', *, socket_name: 'str | None' = None) 'BackendType | None' -> BackendType | None[source]
Auto-detect which backend a session exists in.
Checks both tmux and container backends to find where a session with the given name exists. Skips backends that are not available (binary not found).
- Parameters:
- Returns:
BackendType if found in exactly one backend, None if not found.
- Raises:
SessionAmbiguousError – If session exists in multiple backends.
- Return type:
BackendType | None
Examples
>>> # Session exists in tmux only >>> backend = auto_detect_backend("mybot") >>> backend == BackendType.TMUX True
>>> # Session not found >>> auto_detect_backend("nonexistent") is None True
Exceptions¶
Specialized exceptions raised by the kstlib.ops module.
Exception hierarchy:
KstlibError
OpsError (base for all ops errors)
BackendNotFoundError (binary not in PATH)
TmuxNotFoundError
ContainerRuntimeNotFoundError
SessionError (session-related errors)
SessionExistsError
SessionNotFoundError
SessionStartError
SessionAttachError
SessionStopError
SessionAmbiguousError
- exception kstlib.ops.exceptions.BackendNotFoundError[source]
Bases:
OpsError,FileNotFoundErrorBackend binary (tmux, podman, docker) not found in PATH.
Raised when the required backend binary is not installed or not accessible from the current PATH.
- exception kstlib.ops.exceptions.ContainerRuntimeNotFoundError[source]
Bases:
BackendNotFoundErrorContainer runtime (podman or docker) not found in PATH.
Install podman or docker to use the container backend: - Podman: https://podman.io/getting-started/installation - Docker: https://docs.docker.com/get-docker/
- exception kstlib.ops.exceptions.OpsError[source]
Bases:
KstlibErrorBase exception for all ops module errors.
All ops-specific exceptions inherit from this class, allowing for easy catching of any ops error.
- exception kstlib.ops.exceptions.SessionAmbiguousError(name, backends)[source]
Bases:
SessionErrorSession exists in multiple backends.
Raised when auto-detection finds a session in both tmux and container backends, requiring explicit backend specification.
- exception kstlib.ops.exceptions.SessionAttachError(name, backend, reason)[source]
Bases:
SessionErrorFailed to attach to session or container.
Raised when the backend command to attach to a session fails. This can happen if the session is not running or if the terminal is not interactive.
- exception kstlib.ops.exceptions.SessionError[source]
Bases:
OpsErrorBase exception for session-related errors.
All session operation exceptions inherit from this class.
- exception kstlib.ops.exceptions.SessionExistsError(name, backend)[source]
Bases:
SessionErrorSession or container with this name already exists.
Raised when attempting to create a session with a name that is already in use by another session or container.
- exception kstlib.ops.exceptions.SessionNotFoundError(name, backend)[source]
Bases:
SessionErrorSession or container not found.
Raised when attempting to access a session that does not exist.
- exception kstlib.ops.exceptions.SessionStartError(name, backend, reason)[source]
Bases:
SessionErrorFailed to start session or container.
Raised when the backend command to create a new session fails.
- exception kstlib.ops.exceptions.SessionStopError(name, backend, reason)[source]
Bases:
SessionErrorFailed to stop session or container.
Raised when the backend command to stop a session fails.
- exception kstlib.ops.exceptions.TmuxNotFoundError[source]
Bases:
BackendNotFoundErrortmux binary not found in PATH.
Install tmux to use the tmux backend: - macOS: brew install tmux - Ubuntu/Debian: apt install tmux - Windows: Use WSL2 with tmux installed
Models¶
Data models for the kstlib.ops module.
This module defines the core data structures used by the ops module:
BackendType: Enum for backend selection (tmux, container)
SessionState: Enum for session state (running, stopped, exited, unknown)
SessionConfig: Configuration for creating a session
SessionStatus: Current status of a session
- class kstlib.ops.models.BackendType(value)[source]
-
Backend type for session management.
- TMUX
Use tmux for session management (dev/local).
- CONTAINER
Use Podman/Docker for session management (prod).
- TMUX = 'tmux'
- CONTAINER = 'container'
- __format__(self, format_spec)
Returns format using actual value type unless __str__ has been overridden.
- class kstlib.ops.models.SessionConfig(name, backend=BackendType.TMUX, command=None, working_dir=None, env=<factory>, image=None, volumes=<factory>, ports=<factory>, runtime=None, log_volume=None)[source]
Bases:
objectConfiguration for creating a session.
This dataclass holds all configuration options for both tmux and container backends. Options that are not applicable to the selected backend are ignored.
- name
Unique session name (required).
- Type:
- backend
Backend type (tmux or container).
- Type:
kstlib.ops.models.BackendType
- command
Command to run in the session (tmux) or container.
- Type:
str | None
- working_dir
Working directory for the session.
- Type:
str | None
- image
Container image to use (container backend only).
- Type:
str | None
- runtime
Container runtime to use (“podman” or “docker”).
- Type:
str | None
- log_volume
Log volume mount for persistence (auto-mounted).
- Type:
str | None
Examples
>>> config = SessionConfig( ... name="astro", ... backend=BackendType.TMUX, ... command="python -m astro.bot", ... )
>>> config = SessionConfig( ... name="astro-prod", ... backend=BackendType.CONTAINER, ... image="astro-bot:latest", ... volumes=["./data:/app/data"], ... log_volume="./logs:/app/logs", ... )
- name: str
- backend: BackendType
- __post_init__(self) 'None' -> None[source]
Validate configuration values.
- Raises:
ValueError – If any configuration value is invalid.
- __init__(self, name: 'str', backend: 'BackendType' = <BackendType.TMUX: 'tmux'>, command: 'str | None' = None, working_dir: 'str | None' = None, env: 'dict[str, str]' = <factory>, image: 'str | None' = None, volumes: 'list[str]' = <factory>, ports: 'list[str]' = <factory>, runtime: 'str | None' = None, log_volume: 'str | None' = None) None -> None
- class kstlib.ops.models.SessionState(value)[source]
-
State of a session or container.
- RUNNING
Session is active and running.
- STOPPED
Session was stopped gracefully.
- EXITED
Container exited (with exit code).
- DEFINED
Session exists in config but has not been started.
- UNKNOWN
State cannot be determined.
- RUNNING = 'running'
- STOPPED = 'stopped'
- EXITED = 'exited'
- DEFINED = 'defined'
- UNKNOWN = 'unknown'
- __format__(self, format_spec)
Returns format using actual value type unless __str__ has been overridden.
- class kstlib.ops.models.SessionStatus(name, state, backend, pid=None, created_at=None, window_count=0, socket_name=None, image=None, exit_code=None)[source]
Bases:
objectCurrent status of a session or container.
This dataclass holds the runtime status information for a session, including state, PID, creation time, and backend-specific details.
- name
Session name.
- Type:
- state
Current state (running, stopped, exited, unknown).
- Type:
kstlib.ops.models.SessionState
- backend
Backend type used for this session.
- Type:
kstlib.ops.models.BackendType
- pid
Process ID (tmux server PID or container main PID).
- Type:
int | None
- created_at
ISO timestamp when the session was created.
- Type:
str | None
- window_count
Number of tmux windows (tmux backend only).
- Type:
- socket_name
tmux socket name for custom sockets (tmux backend only).
- Type:
str | None
- image
Container image name (container backend only).
- Type:
str | None
- exit_code
Container exit code if exited (container backend only).
- Type:
int | None
Examples
>>> status = SessionStatus( ... name="astro", ... state=SessionState.RUNNING, ... backend=BackendType.TMUX, ... pid=12345, ... window_count=1, ... )
>>> status = SessionStatus( ... name="astro-prod", ... state=SessionState.RUNNING, ... backend=BackendType.CONTAINER, ... pid=67890, ... image="astro-bot:latest", ... )
- name: str
- state: SessionState
- backend: BackendType
- window_count: int
- __init__(self, name: 'str', state: 'SessionState', backend: 'BackendType', pid: 'int | None' = None, created_at: 'str | None' = None, window_count: 'int' = 0, socket_name: 'str | None' = None, image: 'str | None' = None, exit_code: 'int | None' = None) None -> None
TmuxRunner¶
tmux session runner for local development.
This module provides the TmuxRunner class for managing tmux sessions, enabling detach/attach workflows for local development and backtesting.
Features: - Create named sessions with custom commands and working directories - Attach to running sessions (replaces current process) - Capture session logs with ANSI codes preserved - List all sessions with status information
Example
>>> from kstlib.ops import SessionConfig, BackendType
>>> from kstlib.ops.tmux import TmuxRunner
>>> runner = TmuxRunner()
>>> config = SessionConfig(
... name="dev",
... backend=BackendType.TMUX,
... command="python app.py",
... )
>>> status = runner.start(config)
>>> runner.attach("dev") # Replaces process
- class kstlib.ops.tmux.TmuxRunner(binary='tmux', socket_name=None)[source]
Bases:
objecttmux session runner for local development.
Manages tmux sessions for running persistent processes with detach/attach capability. Supports custom sockets via the
-Lflag for multi-instance setups (e.g. multiple bots on a single host).- Parameters:
- binary
The tmux binary path (validated on first use).
Examples
>>> runner = TmuxRunner() >>> config = SessionConfig(name="bot", command="python bot.py") >>> status = runner.start(config) >>> runner.attach("bot")
- __init__(self, binary: 'str' = 'tmux', socket_name: 'str | None' = None) 'None' -> None[source]
Initialize TmuxRunner.
- property binary: str
Return validated tmux binary path.
- Raises:
TmuxNotFoundError – If tmux is not installed.
- start(self, config: 'SessionConfig') 'SessionStatus' -> SessionStatus[source]
Create and start a new tmux session.
- Parameters:
config (SessionConfig) – Session configuration.
- Returns:
SessionStatus with state information.
- Raises:
SessionExistsError – If session already exists.
SessionStartError – If session failed to start.
TmuxNotFoundError – If tmux is not installed.
- Return type:
SessionStatus
Note
The exists() check is subject to TOCTOU but provides a clear error message. tmux itself will reject duplicate session names.
- stop(self, name: 'str', *, graceful: 'bool' = True, timeout: 'int' = 10) 'bool' -> bool[source]
Stop a tmux session.
- Parameters:
- Returns:
True if stopped, False if not running.
- Raises:
SessionNotFoundError – If session doesn’t exist.
SessionStopError – If session couldn’t be stopped.
- Return type:
- attach(self, name: 'str') 'None' -> None[source]
Attach to a tmux session.
This method replaces the current process with tmux attach. It does not return on success.
- Parameters:
name (str) – Session name to attach to.
- Raises:
SessionNotFoundError – If session doesn’t exist.
SessionAttachError – If attach failed.
- status(self, name: 'str') 'SessionStatus' -> SessionStatus[source]
Get status of a tmux session.
- Parameters:
name (str) – Session name to query.
- Returns:
SessionStatus with current state.
- Raises:
SessionNotFoundError – If session doesn’t exist.
- Return type:
SessionStatus
- logs(self, name: 'str', lines: 'int' = 100) 'str' -> str[source]
Capture recent output from a tmux session.
- Parameters:
- Returns:
String with captured output (ANSI codes preserved).
- Raises:
SessionNotFoundError – If session doesn’t exist.
- Return type:
- exists(self, name: 'str') 'bool' -> bool[source]
Check if a tmux session exists.
- list_sessions(self) 'list[SessionStatus]' -> list[SessionStatus][source]
List all tmux sessions.
- Returns:
List of SessionStatus for all sessions.
- Return type:
list[SessionStatus]
- send_keys(self, name: 'str', keys: 'str', *, enter: 'bool' = True) 'None' -> None[source]
Send keys to a tmux session.
- Parameters:
- Raises:
SessionNotFoundError – If session doesn’t exist.
ContainerRunner¶
Container session runner for production environments.
This module provides the ContainerRunner class for managing Podman/Docker containers, enabling persistent processes with pseudo-terminal support.
Features: - Create named containers with custom images and volumes - Attach to running containers (replaces current process) - Retrieve container logs with ANSI codes preserved - Support for both Podman and Docker runtimes - Automatic log volume mounting for post-mortem analysis
Example
>>> from kstlib.ops import SessionConfig, BackendType
>>> from kstlib.ops.container import ContainerRunner
>>> runner = ContainerRunner(runtime="podman")
>>> config = SessionConfig(
... name="bot",
... backend=BackendType.CONTAINER,
... image="bot:latest",
... volumes=["./data:/app/data"],
... )
>>> status = runner.start(config)
>>> runner.attach("bot") # Replaces process
- class kstlib.ops.container.ContainerRunner(runtime=None)[source]
Bases:
objectContainer runner for Podman/Docker containers.
Manages containers for running persistent processes with pseudo-terminal support for TUI applications.
- Parameters:
runtime (str | None) – Container runtime to use (“podman” or “docker”).
- runtime
The container runtime name.
- binary
The validated runtime binary path.
Examples
>>> runner = ContainerRunner() # Uses podman by default >>> runner = ContainerRunner(runtime="docker") >>> config = SessionConfig( ... name="app", ... backend=BackendType.CONTAINER, ... image="python:3.10-slim", ... ) >>> status = runner.start(config)
- __init__(self, runtime: 'str | None' = None) 'None' -> None[source]
Initialize ContainerRunner.
- Parameters:
runtime (str | None) – Container runtime (“podman”, “docker”, or None for auto-detect). Auto-detection tries podman first, then docker.
- property runtime: str
Return the configured runtime name.
- property binary: str
Return validated container runtime binary path.
- Raises:
ContainerRuntimeNotFoundError – If runtime is not installed.
- start(self, config: 'SessionConfig') 'SessionStatus' -> SessionStatus[source]
Create and start a new container.
- Parameters:
config (SessionConfig) – Session configuration with image and options.
- Returns:
SessionStatus with state information.
- Raises:
SessionExistsError – If container already exists and is running.
SessionStartError – If container failed to start.
ContainerRuntimeNotFoundError – If runtime is not installed.
- Return type:
SessionStatus
- stop(self, name: 'str', *, graceful: 'bool' = True, timeout: 'int' = 10) 'bool' -> bool[source]
Stop a running container.
- Parameters:
- Returns:
True if stopped, False if not running.
- Raises:
SessionNotFoundError – If container doesn’t exist.
SessionStopError – If container couldn’t be stopped.
- Return type:
- attach(self, name: 'str') 'None' -> None[source]
Attach to a running container.
This method replaces the current process with container attach. It does not return on success.
- Parameters:
name (str) – Container name to attach to.
- Raises:
SessionNotFoundError – If container doesn’t exist.
SessionAttachError – If attachment failed.
Note
Use Ctrl+P Ctrl+Q to detach from the container.
- status(self, name: 'str') 'SessionStatus' -> SessionStatus[source]
Get status of a container.
- Parameters:
name (str) – Container name to query.
- Returns:
SessionStatus with current state.
- Raises:
SessionNotFoundError – If container doesn’t exist.
- Return type:
SessionStatus
- logs(self, name: 'str', lines: 'int' = 100) 'str' -> str[source]
Retrieve recent logs from a container.
- Parameters:
- Returns:
String with log output (ANSI codes preserved).
- Raises:
SessionNotFoundError – If container doesn’t exist.
- Return type:
- exists(self, name: 'str') 'bool' -> bool[source]
Check if a container with the given name exists.
- list_sessions(self) 'list[SessionStatus]' -> list[SessionStatus][source]
List all containers.
- Returns:
List of SessionStatus for all containers.
- Return type:
list[SessionStatus]
- exec(self, name: 'str', command: 'str', *, interactive: 'bool' = False) 'subprocess.CompletedProcess[str]' -> subprocess.CompletedProcess[str][source]
Execute a command in a running container.
- Parameters:
- Returns:
CompletedProcess with stdout/stderr.
- Raises:
SessionNotFoundError – If container doesn’t exist.
- Return type:
SessionManager¶
Session manager facade for unified backend access.
This module provides the SessionManager class, a config-driven facade that abstracts the underlying backend (tmux or container) and provides a unified interface for session management.
Features: - Automatic backend selection based on configuration - Config-driven session creation from kstlib.conf.yml - Unified API for start, stop, attach, status, and logs - Support for both tmux and container backends
Example
>>> from kstlib.ops import SessionManager
>>> # Local dev with tmux
>>> session = SessionManager("dev", backend="tmux")
>>> session.start("python -m app")
>>> session.attach()
>>> # From config file
>>> session = SessionManager.from_config("astro")
>>> session.start()
- exception kstlib.ops.manager.SessionConfigError[source]
Bases:
OpsErrorConfiguration error for session management.
Raised when session configuration is invalid or missing required fields.
- class kstlib.ops.manager.SessionManager(name, *, backend=BackendType.TMUX, **kwargs)[source]
Bases:
objectConfig-driven session manager with backend abstraction.
Provides a unified interface for managing sessions across different backends (tmux, container). The backend can be specified directly or loaded from kstlib.conf.yml configuration.
- Parameters:
- name
The session name.
- backend
The backend type being used.
- config
The full session configuration.
Examples
>>> # Direct instantiation with tmux >>> session = SessionManager("dev", backend="tmux") >>> session.start("python app.py") >>> session.attach()
>>> # Direct instantiation with container >>> session = SessionManager( ... "prod", ... backend="container", ... image="app:latest", ... volumes=["./data:/app/data"], ... ) >>> session.start()
>>> # From config file (recommended) >>> session = SessionManager.from_config("astro")
- __init__(self, name: 'str', *, backend: 'str | BackendType' = <BackendType.TMUX: 'tmux'>, **kwargs: 'Any') 'None' -> None[source]
Initialize SessionManager.
- property name: str
Return the session name.
- property backend: BackendType
Return the backend type.
- property config: SessionConfig
Return the session configuration.
- classmethod from_config(name: 'str') 'SessionManager' -> SessionManager[source]
Create SessionManager from kstlib configuration.
Loads session configuration from kstlib.conf.yml under the ops.sessions.{name} key.
- Parameters:
name (str) – Session name to load from config.
- Returns:
SessionManager configured from the config file.
- Raises:
SessionConfigError – If session not found in config.
- Return type:
SessionManager
Example
Config file (kstlib.conf.yml):
ops: sessions: astro: backend: tmux command: "python -m astro.bot" working_dir: "/opt/astro"
Usage:
>>> session = SessionManager.from_config("astro")
- start(self, command: 'str | None' = None, **kwargs: 'Any') 'SessionStatus' -> SessionStatus[source]
Start the session.
- Parameters:
- Returns:
SessionStatus with current state.
- Raises:
SessionExistsError – If session already exists.
SessionStartError – If session failed to start.
- Return type:
SessionStatus
- stop(self, *, graceful: 'bool' = True, timeout: 'int' = 10) 'bool' -> bool[source]
Stop the session.
- Parameters:
- Returns:
True if stopped successfully.
- Raises:
SessionNotFoundError – If session does not exist.
SessionStopError – If session could not be stopped.
- Return type:
- attach(self) 'None' -> None[source]
Attach to the session.
This method replaces the current process. It does not return on success.
- Raises:
SessionNotFoundError – If session does not exist.
SessionAttachError – If attachment failed.
- status(self) 'SessionStatus' -> SessionStatus[source]
Get current session status.
- Returns:
SessionStatus with current state.
- Raises:
SessionNotFoundError – If session does not exist.
- Return type:
SessionStatus
- logs(self, lines: 'int' = 100) 'str' -> str[source]
Get recent session logs.
- Parameters:
lines (int) – Number of lines to retrieve.
- Returns:
Log output as string (ANSI codes preserved).
- Raises:
SessionNotFoundError – If session does not exist.
- Return type:
- exists(self) 'bool' -> bool[source]
Check if the session exists.
- Returns:
True if session exists, False otherwise.
- Return type:
- kstlib.ops.manager.auto_detect_backend(name: 'str', *, socket_name: 'str | None' = None) 'BackendType | None' -> BackendType | None[source]
Auto-detect which backend a session exists in.
Checks both tmux and container backends to find where a session with the given name exists. Skips backends that are not available (binary not found).
- Parameters:
- Returns:
BackendType if found in exactly one backend, None if not found.
- Raises:
SessionAmbiguousError – If session exists in multiple backends.
- Return type:
BackendType | None
Examples
>>> # Session exists in tmux only >>> backend = auto_detect_backend("mybot") >>> backend == BackendType.TMUX True
>>> # Session not found >>> auto_detect_backend("nonexistent") is None True