> ## Documentation Index
> Fetch the complete documentation index at: https://documentation.api.odyssey.ml/llms.txt
> Use this file to discover all available pages before exploring further.

# Session Management

> Understanding interactive stream sessions, concurrent limits, and common troubleshooting scenarios.

## Concurrent Stream Limits

Your Odyssey plan includes a limit on the number of **concurrent interactive streams** you can run simultaneously. This limit is displayed in your [Developer Portal](https://developer.odyssey.ml) dashboard.

For example, a Free tier account allows up to **5 concurrent streams**. This means you can have 5 active interactive sessions running at the same time across all your applications and devices.

<Note>
  Concurrent stream limits apply only to **interactive streaming sessions** created with `startStream()`.

  Simulation jobs created through the **Simulate API** use a separate queue with its own limit - currently **up to 10 queued simulation jobs**.
</Note>

***

## Why My Concurrent Count Seems Higher Than Expected

You may occasionally notice that your concurrent stream count appears higher than the number of streams you believe are active. This is typically caused by **dangling sessions**—sessions that were not properly closed.

<Note>
  If you're consistently hitting your concurrent limit due to dangling sessions, try implementing proper disconnect handling in your application (see best practices below).
</Note>

### Common Causes of Dangling Sessions

1. **Browser crashes or tab closures**: If you close a browser tab without properly disconnecting, the session may remain active on our servers until it times out.
2. **Network interruptions**: Sudden network disconnections (Wi-Fi drops, mobile network switches) can prevent the disconnect signal from reaching our servers.
3. **Application crashes**: If your application crashes or is force-quit, it may not have a chance to send the disconnect signal.
4. **Hot Module Replacement (HMR)**: Development tools like Vite, Webpack, and Next.js use HMR to update your code without a full page reload. When a component that holds an Odyssey connection is replaced, the old connection may not be properly cleaned up, leaving orphaned sessions.
5. **Development restarts**: Restarting your dev server (Ctrl+C and restart) without proper cleanup can leave sessions open on the server side.
6. **Multiple browser tabs**: Opening the same application in multiple tabs can create multiple sessions, which may not all be cleaned up when you close the window.

### Idle Session Timeout

Sessions that remain connected but have no active stream for **15 minutes** are automatically disconnected. This prevents idle connections from holding GPU resources.

If your application needs to keep a connection open between streams, ensure you start a new stream within 15 minutes or handle the disconnection gracefully.

<Info>
  Individual streams also have a separate per-stream duration limit. See [**Stream Duration Limits**](/stream-duration-limits) for details and reconnection patterns.
</Info>

### How Dangling Sessions Are Resolved

Odyssey automatically cleans up stale sessions through a background process:

* Sessions that haven't sent a **heartbeat** within the timeout window are marked as stale
* Stale sessions are automatically terminated and won't count against your concurrent limit
* This cleanup process runs continuously, typically resolving dangling sessions within a few minutes

***

## Typical Session Lifecycle

An Odyssey session follows a simple lifecycle:

```mermaid theme={null}
flowchart TD
  A["connect()"] --> B["startStream()"]
  B --> C["interact() optional"]
  C --> D["endStream()"]
  D --> B
  B --> E["disconnect()"]
```

A connection is established with `connect()`. Once connected, your application can start and stop multiple streams within the same connection.

When finished, call `disconnect()` to release resources.

<Info>
  For a conceptual explanation of how connections and streams relate, see [**Connections vs Streams**](/connection-vs-stream).
</Info>

<CodeGroup>
  ```typescript JavaScript SDK theme={null}
  await client.connect();

  try {
    await client.startStream({ prompt: "A mountain lake at sunrise" });

    await client.interact({ prompt: "Add fog drifting over the water" });

    await client.endStream();
  } finally {
    client.disconnect();
  }
  ```

  ```python Python SDK theme={null}
  await client.connect()

  try:
      await client.start_stream(prompt="A mountain lake at sunrise")

      await client.interact(prompt="Add fog drifting over the water")

      await client.end_stream()
  finally:
      await client.disconnect()
  ```
</CodeGroup>

## Best Practices for Clean Session Management

### Always Disconnect When Done

Call the disconnect method when your user leaves or when the session is no longer needed:

<CodeGroup>
  ```typescript JavaScript SDK theme={null}
  // Clean up when component unmounts or user navigates away
  window.addEventListener('beforeunload', () => {
    odyssey.disconnect();
  });

  // Or in React with useEffect cleanup
  useEffect(() => {
    return () => {
      odyssey.disconnect();
    };
  }, []);
  ```

  ```python Python SDK theme={null}
  # Use context manager for automatic cleanup
  async with odyssey.stream(prompt="...") as session:
      # Session is automatically closed when exiting the context
      pass

  # Or explicitly disconnect
  await odyssey.disconnect()
  ```
</CodeGroup>

### Handle Page Visibility Changes

For web applications, consider pausing or disconnecting when the user switches tabs:

```typescript theme={null}
document.addEventListener('visibilitychange', () => {
  if (document.hidden) {
    // Optionally disconnect or pause when tab is hidden
    odyssey.disconnect();
  }
});
```

### Implement Reconnection Logic

If your application needs to maintain long-running sessions, implement reconnection logic that properly cleans up the old session:

```typescript theme={null}
async function reconnect() {
  // Always disconnect the old session first
  await odyssey.disconnect();

  // Then establish a new connection
  await odyssey.connect(config);
}
```

### Handle Hot Module Replacement (HMR)

HMR is one of the most common causes of dangling sessions during development. When your bundler (Vite, Webpack, Next.js) hot-reloads a component, the previous instance's cleanup function may not run correctly, leaving WebRTC connections open.

<CodeGroup>
  ```typescript React with useEffect theme={null}
  import { useEffect, useRef } from 'react';
  import { Odyssey } from '@odysseyml/odyssey';

  function VideoStream() {
    // Use a ref to persist the client across HMR
    const clientRef = useRef<Odyssey | null>(null);

    useEffect(() => {
      // Create client only if one doesn't exist
      if (!clientRef.current) {
        clientRef.current = new Odyssey({ apiKey: 'ody_...' });
      }

      const client = clientRef.current;
      client.connect();

      // Critical: cleanup on unmount AND HMR
      return () => {
        client.disconnect();
        clientRef.current = null;
      };
    }, []);

    return <video id="stream" />;
  }
  ```

  ```typescript Vite HMR API theme={null}
  import { Odyssey } from '@odysseyml/odyssey';

  let client: Odyssey | null = null;

  // Clean up before HMR replaces this module
  if (import.meta.hot) {
    import.meta.hot.dispose(() => {
      client?.disconnect();
      client = null;
    });
  }

  export function initOdyssey() {
    if (!client) {
      client = new Odyssey({ apiKey: 'ody_...' });
    }
    return client;
  }
  ```

  ```typescript Next.js with cleanup theme={null}
  'use client';
  import { useEffect } from 'react';
  import { Odyssey } from '@odysseyml/odyssey';

  // Store client at module level to survive HMR
  let globalClient: Odyssey | null = null;

  export default function StreamPage() {
    useEffect(() => {
      // Reuse existing client or create new one
      if (!globalClient) {
        globalClient = new Odyssey({ apiKey: 'ody_...' });
      }

      globalClient.connect();

      // Cleanup function for both unmount and HMR
      return () => {
        globalClient?.disconnect();
        globalClient = null;
      };
    }, []);

    return <video id="stream" autoPlay />;
  }
  ```
</CodeGroup>

<Tip>
  **Development vs Production**: These HMR patterns add complexity. Consider using environment checks to simplify your production code:

  ```typescript theme={null}
  const isDev = process.env.NODE_ENV === 'development';
  // Only use module-level singleton pattern in development
  ```
</Tip>

***

## Finding and Closing Dangling Sessions

If you need to immediately free up your concurrent session slots, you can find and terminate processes that may have orphaned Odyssey connections.

### Identify Processes with Active Connections

Odyssey sessions use WebRTC connections to our streaming servers. You can find processes holding these connections using network inspection tools.

<Tabs>
  <Tab title="macOS">
    ```bash theme={null}
    # Find processes with connections to Odyssey servers
    lsof -i | grep -E "odyssey|stun|turn" | awk '{print $1, $2}' | sort -u

    # Find Node.js processes (common for SDK usage)
    pgrep -l node

    # Find browser processes that may have WebRTC connections
    pgrep -l "Chrome|Firefox|Safari|Electron"

    # Kill a specific process by PID
    kill <PID>

    # Force kill if unresponsive
    kill -9 <PID>
    ```

    You can also use **Activity Monitor**:

    1. Open Activity Monitor (Cmd + Space, type "Activity Monitor")
    2. Search for "node", "python", or your browser name
    3. Select the process and click the X button to quit
  </Tab>

  <Tab title="Linux">
    ```bash theme={null}
    # Find processes with connections to Odyssey servers
    ss -tp | grep -E "odyssey|stun|turn"

    # Alternative using netstat
    netstat -tp 2>/dev/null | grep -E "odyssey|stun|turn"

    # Find Node.js processes
    pgrep -la node

    # Find Python processes
    pgrep -la python

    # List all processes with network connections
    lsof -i -P -n | grep ESTABLISHED

    # Kill a specific process by PID
    kill <PID>

    # Force kill if unresponsive
    kill -9 <PID>
    ```
  </Tab>

  <Tab title="Windows">
    ```powershell theme={null}
    # Find processes with network connections (PowerShell)
    Get-NetTCPConnection | Where-Object {$_.State -eq "Established"} |
      Select-Object LocalPort, RemoteAddress, OwningProcess |
      ForEach-Object {
        $proc = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue
        [PSCustomObject]@{
          Process = $proc.ProcessName
          PID = $_.OwningProcess
          RemoteAddress = $_.RemoteAddress
        }
      } | Where-Object {$_.Process -match "node|python|chrome|firefox|edge"}

    # Find Node.js processes
    Get-Process node -ErrorAction SilentlyContinue

    # Find Python processes
    Get-Process python* -ErrorAction SilentlyContinue

    # Kill a specific process by PID
    Stop-Process -Id <PID>

    # Force kill
    Stop-Process -Id <PID> -Force
    ```

    You can also use **Task Manager** or **Resource Monitor**:

    1. Press Ctrl + Shift + Esc to open Task Manager
    2. Go to the "Details" tab
    3. Look for `node.exe`, `python.exe`, or browser processes
    4. Right-click and select "End task"
  </Tab>
</Tabs>

### Browser-Specific Cleanup

For web applications, dangling sessions often persist in browser tabs or service workers:

<Tabs>
  <Tab title="Chrome">
    1. **Close all tabs** running your Odyssey application
    2. **Clear site data**: Settings → Privacy → Clear browsing data → Cookies and site data
    3. **Check service workers**: DevTools (F12) → Application → Service Workers → Unregister
    4. **Force close**: `chrome://restart` in the address bar
  </Tab>

  <Tab title="Firefox">
    1. **Close all tabs** running your Odyssey application
    2. **Clear site data**: Settings → Privacy & Security → Cookies and Site Data → Clear Data
    3. **Check service workers**: `about:debugging#/runtime/this-firefox` → Service Workers
    4. **Force close**: Close and reopen Firefox
  </Tab>

  <Tab title="Safari">
    1. **Close all tabs** running your Odyssey application
    2. **Clear site data**: Preferences → Privacy → Manage Website Data → Remove
    3. **Check service workers**: Develop menu → Service Workers
    4. **Force close**: Force Quit from Apple menu
  </Tab>
</Tabs>

### Development Environment Cleanup

During development, orphaned processes are common. Here's how to clean up:

<CodeGroup>
  ```bash Node.js theme={null}
  # Kill all Node.js processes (use with caution)
  pkill -f node

  # Kill only processes running your specific script
  pkill -f "node.*your-app-name"

  # On Windows (PowerShell)
  Get-Process node | Stop-Process
  ```

  ```bash Python theme={null}
  # Kill all Python processes (use with caution)
  pkill -f python

  # Kill only processes running your specific script
  pkill -f "python.*your_script.py"

  # On Windows (PowerShell)
  Get-Process python* | Stop-Process
  ```
</CodeGroup>

<Warning>
  Using `pkill` or `Stop-Process` on broad patterns like `node` or `python` will terminate **all** matching processes, not just Odyssey-related ones. Use specific patterns when possible.
</Warning>

***

## Monitoring Your Usage

The [Developer Portal](https://developer.odyssey.ml) dashboard shows your current usage in real-time:

* **Concurrent Streams**: The number of active interactive sessions right now
* **Total Hours Used**: Cumulative streaming time this billing period
* **Simulation Hours**: Time spent on simulation/headless jobs

If you believe your concurrent count is incorrect after waiting for automatic cleanup (5+ minutes), please contact [support@odyssey.ml](mailto:support@odyssey.ml) with your account details.
