# Sessions

> Complete guide to using sessions for stateful, multi-call interactions.

**URL:** https://inference.sh/docs/extend/sessions
**Last updated:** 2026-09-18

---

Complete guide to using sessions for stateful, multi-call interactions.

→ [Sessions REST API](/docs/api/rest/sessions) — `GET /sessions`, keepalive, and `DELETE /sessions/{id}`

---

## Quick Start

### CLI

```bash
# Start a new session
belt app run my-app --session new --input '{"key": "value"}'
# Output includes session_id: sess_abc123

# Continue the session
belt app run my-app --session sess_abc123 --input '{"key": "value2"}'

# End the session
belt session end sess_abc123
```

### Python SDK

```python
from inferencesh import Inference

client = Inference(api_key="...")

# Start session
result = client.run({
    "app": "my-app@abc123",
    "input": {"action": "init"},
    "session": "new"
})
session_id = result["session_id"]

# Continue session
result = client.run({
    "app": "my-app@abc123",
    "input": {"action": "process"},
    "session": session_id
})

# End session
client.sessions.end(session_id)
```

### JavaScript SDK

```typescript
import { Inference } from '@anthropic/inference-sdk';

const client = new Inference({ apiKey: '...' });

// Start session
const result = await client.run({
  app: 'my-app@abc123',
  input: { action: 'init' },
  session: 'new'
});
const sessionId = result.session_id;

// Continue session
await client.run({
  app: 'my-app@abc123',
  input: { action: 'process' },
  session: sessionId
});

// End session
await client.sessions.end(sessionId);
```

---

## Session Parameters

The `session` and `session_timeout` parameters control session behavior:

### session

| Value | Behavior |
|-------|----------|
| `"new"` | Create a new session; returns `session_id` in response |
| `"sess_xxx"` | Use existing session; must match app and version |
| *omitted* | Regular stateless call |

### session_timeout

Custom idle timeout in seconds. Only valid when `session: "new"`.

| Constraint | Value |
|------------|-------|
| Minimum | 1 second |
| Maximum | 3600 seconds (1 hour) |
| Default | 60 seconds |

```python
# Python: 5-minute session timeout
result = client.run({
    "app": "my-app",
    "input": {...},
    "session": "new",
    "session_timeout": 300
})
```

```typescript
// JavaScript: 5-minute session timeout
const result = await client.run({
  app: 'my-app',
  input: {...},
  session: 'new',
  session_timeout: 300
});
```

```bash
# CLI: 5-minute session timeout
belt app run my-app --session new --session-timeout 300 --input '{...}'
```

---

## Session Management

### Get Session Info

```python
# Python
info = client.sessions.get(session_id)
print(info.status)      # "active", "ended", or "expired"
print(info.expires_at)  # When session will expire
print(info.call_count)  # Number of calls made
```

```typescript
// JavaScript
const info = await client.sessions.get(sessionId);
console.log(info.status);     // "active", "ended", or "expired"
console.log(info.expiresAt);  // When session will expire
console.log(info.callCount);  // Number of calls made
```

### List Sessions

```python
# Python
sessions = client.sessions.list()
for s in sessions:
    print(f"{s.id}: {s.status}")
```

```typescript
// JavaScript
const sessions = await client.sessions.list();
sessions.forEach(s => console.log(`${s.id}: ${s.status}`));
```

### Keep Session Alive

Extend the expiration time without making a task call:

```python
# Python
info = client.sessions.keepalive(session_id)
print(info.expires_at)  # Updated expiration
```

```typescript
// JavaScript
const info = await client.sessions.keepalive(sessionId);
console.log(info.expiresAt);  // Updated expiration
```

### End Session

Release the worker immediately:

```python
# Python
client.sessions.end(session_id)
```

```typescript
// JavaScript
await client.sessions.end(sessionId);
```

---

## State Guarantees

### What Persists

- **In-memory state** — Python objects, loaded models, variables
- **File system changes** — Files written to local storage
- **Database connections** — Open connections remain open

### What Doesn't Persist

- **Cross-session state** — Each session starts fresh
- **After expiration** — State is lost when session ends
- **Across restarts** — Worker restarts clear all state

### Example: Stateful App

```python
class App(BaseApp):
    def __init__(self):
        self.state = {}  # Persists within session

    async def set_value(self, input: SetInput) -> SetOutput:
        self.state[input.key] = input.value
        return SetOutput(success=True)

    async def get_value(self, input: GetInput) -> GetOutput:
        value = self.state.get(input.key)
        return GetOutput(value=value, found=value is not None)
```

---

## Timeouts and Expiration

### Default Timeout

Sessions expire after **60 seconds** of idle time (no calls) by default.

### Custom Timeout

Specify `session_timeout` when creating a session to customize the idle timeout:

```python
# Create session with 10-minute idle timeout
result = client.run({
    "app": "my-app",
    "input": {...},
    "session": "new",
    "session_timeout": 600  # 10 minutes
})
```

| Timeout Range | Description |
|---------------|-------------|
| 1-59 seconds | Short-lived sessions for quick interactions |
| 60 seconds | Default timeout |
| 60-300 seconds | Standard interactive sessions |
| 300-3600 seconds | Long-running workflows, background processing |

### Sliding Window

Each call resets the expiration timer to the session's configured timeout:

```
# With default 60-second timeout:
Call 1 (t=0)     → Expires at t=60s
Call 2 (t=30s)   → Expires at t=90s
Call 3 (t=80s)   → Expires at t=140s

# With custom 5-minute (300s) timeout:
Call 1 (t=0)     → Expires at t=5min
Call 2 (t=2min)  → Expires at t=7min
Call 3 (t=6min)  → Expires at t=11min
```

### Maximum Timeout

The maximum allowed session timeout is **1 hour** (3600 seconds).

### Keepalive

Use `keepalive` to extend the session without making a task call. This resets the timer using the session's configured timeout:

```python
# Extend session periodically
while work_in_progress:
    client.sessions.keepalive(session_id)
    time.sleep(session_timeout * 0.8)  # Keep alive at 80% of timeout
```

---

## Error Handling

### Session Errors

| Error Code | HTTP Status | Description |
|------------|-------------|-------------|
| `SESSION_NOT_FOUND` | 404 | Session ID doesn't exist |
| `SESSION_EXPIRED` | 410 | Session timed out |
| `SESSION_ENDED` | 410 | Session was explicitly ended |
| `WORKER_LEASED` | 409 | Worker assigned to different session |
| `APP_MISMATCH` | 400 | Session belongs to different app (**409** on `POST /run` and `POST /apps/run`) |
| `VERSION_MISMATCH` | 400 | Session belongs to different version (**409** on `POST /run` and `POST /apps/run`) |

### Task failures (session scheduling)

Session tasks route to the worker leased for the session. If that worker is busy running another call, your new task waits in the queue. If the worker does not become idle within about **30 seconds**, the task fails with status **Failed** and error `session worker busy too long`. Retry when the prior call finishes, or serialize session calls so only one runs at a time.

| Error | Cause | What to do |
|-------|-------|------------|
| `session worker busy too long` | Prior call on the session worker still running after ~30s | Wait and retry, or serialize session calls |
| `session worker offline` | Leased worker disconnected or was removed | Create a new session (`"session": "new"`) |
| `session expired or not found` | Session ID invalid or idle timeout elapsed | Create a new session (`"session": "new"`) |

### Python Error Handling

```python
from inferencesh import (
    SessionNotFoundError,
    SessionExpiredError,
    SessionEndedError,
)

try:
    result = client.run({
        "app": "my-app",
        "input": {...},
        "session": session_id
    })
except SessionNotFoundError:
    # Session doesn't exist — create new one
    result = client.run({...}, session="new")
except SessionExpiredError:
    # Session timed out — create new one
    result = client.run({...}, session="new")
except SessionEndedError:
    # Session was ended — create new one
    result = client.run({...}, session="new")
```

### JavaScript Error Handling

```typescript
import {
  SessionNotFoundError,
  SessionExpiredError,
  SessionEndedError
} from '@anthropic/inference-sdk';

try {
  const result = await client.run({
    app: 'my-app',
    input: {...},
    session: sessionId
  });
} catch (error) {
  if (error instanceof SessionNotFoundError ||
      error instanceof SessionExpiredError ||
      error instanceof SessionEndedError) {
    // Create new session
    const result = await client.run({...}, { session: 'new' });
  }
  throw error;
}
```

---

## Best Practices

### Always End Sessions

Release workers when done:

```python
try:
    # Use session
    result = client.run({..., "session": session_id})
finally:
    client.sessions.end(session_id)
```

### Handle Expiration Gracefully

Sessions can expire unexpectedly. Always handle `SESSION_EXPIRED`:

```python
def call_with_retry(client, params, session_id):
    try:
        return client.run({**params, "session": session_id})
    except SessionExpiredError:
        # Recreate session and retry
        result = client.run({**params, "session": "new"})
        return result, result["session_id"]
```

### Use Context Managers (Python)

```python
with client.session("my-app@abc123") as session:
    session.call({"action": "init"})
    session.call({"action": "process"})
    # Session automatically ended on exit
```

### Pin Versions

Sessions are bound to a specific app version:

```python
# Good: Version pinned
client.run({"app": "my-app@abc123", "session": session_id, ...})

# Bad: Version might change
client.run({"app": "my-app", "session": session_id, ...})
```

---

## Next

- [Sessions Concept](/docs/concepts/sessions) — Overview and use cases
- [Stateful Sessions Examples](/docs/examples/stateful-sessions) — Practical patterns
- [Multi-Function Apps](/docs/extend/multi-function-apps) — Combine with multiple entry points
