Skip to Content

Sessions

TL;DR

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 persists

2. 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 running

3. 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-789

What is the session lifecycle?

What are the session states?

StateDescriptionTransitions To
PENDINGCreated, waiting for agentCONNECTED
CONNECTEDActive, bidirectional streamingBACKGROUND, GRACE_PERIOD
BACKGROUNDMobile app backgroundedCONNECTED, DISCONNECTED
GRACE_PERIODAgent disconnected, waiting for reconnectCONNECTED, DISCONNECTED
DISCONNECTEDFinal state
ERRORFatal error occurredDISCONNECTED

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?

  1. Agent disconnects (network issue, laptop close, etc.)
  2. Session enters GRACE_PERIOD — not immediately terminated
  3. Processes keep running on the agent’s machine
  4. Output is buffered for later retrieval
  5. Agent has 30 seconds to reconnect
  6. If reconnected — seamless continuation
  7. 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 normally

How 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 Plane

How 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 reattached

What 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 intervene

How do sessions differ from connections?

AspectConnectionSession
LifetimeNetwork socketDatabase object
PersistenceEnds on disconnectSurvives disconnects
Multiplicity1:1N:1 (many connections, one session)
StateTransientPersistent
RecoveryNoneGrace 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

Last updated on