Sessions
CMDOP sessions are persistent, first-class objects stored in the database — not tied to a network connection. Sessions survive client disconnects via a 30-second grace period, support multiple concurrent clients (CLI, mobile, web), and maintain full execution context including running processes, environment variables, and output buffers. Each session is scoped to a workspace for tenant isolation.
Sessions are the core abstraction in CMDOP. Unlike traditional remote access where a session equals a connection, CMDOP treats sessions as independent, persistent objects.
What is a session object?
A session in CMDOP is:
┌─────────────────────────────────────────────────────────────┐
│ Session Object │
├─────────────────────────────────────────────────────────────┤
│ │
│ id: UUID # Globally unique identifier │
│ status: SessionStatus # Lifecycle state │
│ workspace: Workspace # Tenant isolation │
│ machine: Machine # Associated agent │
│ created_at: datetime │
│ connected_at: datetime │
│ last_heartbeat_at: datetime │
│ grace_period_started_at: datetime │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Execution Context │ │
│ │ • Running processes │ │
│ │ • Environment variables │ │
│ │ • Working directory │ │
│ │ • Command history │ │
│ │ • Output buffer │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘What are the key properties of a session?
1. How are sessions independent of connections?
The session exists in the database, not in a network connection:
# SSH ties session lifetime to a single connection; CMDOP decouples them
Traditional SSH:
Connection ═══════ Session
└─ Connection closes → Session dies
CMDOP:
Connection ────┐
Connection ────┼───▶ Session (persistent object)
Connection ────┘
└─ Connections come and go, session persists2. How do sessions survive disconnects?
When a client disconnects:
# Demonstrates session persistence across client disconnects
# Client 1: Start long-running process
await client.terminal.execute("./train_model.py")
# Client 1 disconnects (laptop closes)
# Session enters GRACE_PERIOD, but:
# - Process keeps running
# - Output is buffered
# - Session is not terminated
# Client 2 (or same client): Reconnect and reattach
await client.terminal.attach(session_id)
# See live output, process still running3. How do multiple clients share a session?
One session, many clients:
4. How are sessions scoped to workspaces?
Sessions belong to a workspace for isolation:
# Each session belongs to exactly one workspace and one machine
Workspace: "acme-corp"
├── Machine: "prod-server-1"
│ └── Session: abc-123
├── Machine: "prod-server-2"
│ └── Session: def-456
└── Machine: "staging-1"
└── Session: ghi-789What is the session lifecycle?
What are the session states?
| State | Description | Transitions To |
|---|---|---|
PENDING | Created, waiting for agent | CONNECTED |
CONNECTED | Active, bidirectional streaming | BACKGROUND, GRACE_PERIOD |
BACKGROUND | Mobile app backgrounded | CONNECTED, DISCONNECTED |
GRACE_PERIOD | Agent disconnected, waiting for reconnect | CONNECTED, DISCONNECTED |
DISCONNECTED | Final state | — |
ERROR | Fatal error occurred | DISCONNECTED |
How do session state transitions work?
What is the grace period?
The grace period is a 30-second window after agent disconnect:
What happens during the grace period?
- Agent disconnects (network issue, laptop close, etc.)
- Session enters GRACE_PERIOD — not immediately terminated
- Processes keep running on the agent’s machine
- Output is buffered for later retrieval
- Agent has 30 seconds to reconnect
- If reconnected — seamless continuation
- If timeout — session moves to DISCONNECTED
Why does the grace period matter?
# Scenario: Network hiccup during critical operation
# Without grace period (traditional SSH):
# Command may be half-executed if connection drops — disaster!
await client.terminal.execute("rm -rf /old/data && mv /new/data /current")
# Network drops for 5 seconds
# SSH session dies
# Command may be half-executed — disaster!
# With CMDOP grace period:
# Session persists through brief network interruptions
await client.terminal.execute("rm -rf /old/data && mv /new/data /current")
# Network drops for 5 seconds
# Session enters GRACE_PERIOD
# Command continues executing
# Network recovers, client reconnects
# Session continues normallyHow do you perform session operations?
How do you create a session?
Sessions are created when an agent registers:
# Sessions are created server-side when the agent connects
# On the agent (Go)
agent.connect() # Creates session on Control PlaneHow do you list sessions?
# Retrieve all sessions in the current workspace
response = await client.terminal.list_sessions()
for session in response.sessions:
print(f"{session.machine_hostname}: {session.status}")How do you attach to a session?
# Look up the active session for a specific machine by hostname
session = await client.terminal.get_active_session("my-server")
# Open a bidirectional stream and attach to the session
stream = client.terminal.stream()
await stream.attach(session.session_id)How do you detach from a session?
# Close the stream without terminating the session
await stream.close()
# Session continues, can be reattachedWhat are the multi-client patterns?
How does collaborative debugging work?
How does the AI observer pattern work?
# AI attaches in read-only mode to analyze terminal output in real-time
session = await client.terminal.get_active_session("prod-server")
# Attach AI as observer — can read output but cannot send input
ai_stream = client.terminal.stream(mode="observer")
await ai_stream.attach(session.session_id)
# Register callback to analyze each output chunk
ai_stream.on_output(analyze_and_suggest)How does session handoff work?
# Dev starts session at office
await stream.attach(session_id)
await stream.send_input("./deploy.sh")
# Dev leaves office (detaches, but session continues on the agent)
await stream.close()
# Ops takes over from phone — same session, different device
mobile_stream = client.terminal.stream()
await mobile_stream.attach(session_id) # Same session!
# Sees deploy progress, can interveneHow do sessions differ from connections?
| Aspect | Connection | Session |
|---|---|---|
| Lifetime | Network socket | Database object |
| Persistence | Ends on disconnect | Survives disconnects |
| Multiplicity | 1:1 | N:1 (many connections, one session) |
| State | Transient | Persistent |
| Recovery | None | Grace period |
What are the best practices for sessions?
1. Don’t Create Unnecessary Sessions
Sessions are meant to be long-lived. Reuse existing sessions:
# Good: Reuse an existing active session instead of creating a new one
session = await client.terminal.get_active_session("my-server")
await stream.attach(session.session_id)
# Avoid: Creating new sessions frequently
# (Only agents should create sessions)2. Use Grace Period Aware Operations
Design operations to be graceful:
# Good: Atomic operation that completes fully or not at all
await client.terminal.execute("mv /tmp/new /app/current")
# Better: Resumable operation that can recover from interruption
await client.terminal.execute("rsync -avz --partial /src /dst")3. Monitor Session Health
# Always verify session state before running commands
session = await client.terminal.get_active_session("server")
if session.status != "CONNECTED":
print(f"Warning: Session is {session.status}")Next
- Agents — How agents manage sessions
- Multi-Client — Collaborative access patterns
- How It Works — Connection flow details