Session Management
Sessions enable multi-turn conversations by persisting message history between requests. The server manages session creation, expiration, and cleanup automatically.
Overview
Cleanup sweep runs every 5 minutes
Storage
Sessions are stored as individual JSON files on disk:
- 📁
.seepient/sessions/- 📄
550e8400-e29b-41d4-a716-446655440000.json - 📄
660f9511-f3ac-52e5-b827-557766551111.json - 📄
...
- 📄
The session directory defaults to ./.seepient/sessions/ relative to the working directory, and can be overridden with the SEEPIENT_SESSION_DIR environment variable.
Session file format
Each file contains a single session as JSON:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"messages": [
{
"id": "msg_001",
"role": "user",
"content": "What is closures in JavaScript?",
"timestamp": 1712505600000
},
{
"id": "msg_002",
"role": "assistant",
"content": "A closure is a function that has access to variables from its outer scope...",
"timestamp": 1712505602000
}
],
"createdAt": 1712505600000,
"updatedAt": 1712505602000,
"provider": "anthropic",
"model": "claude-sonnet-4-6-20260320"
}| Field | Type | Description |
|---|---|---|
id | string | Session UUID (v4) |
messages | array | Ordered list of messages |
messages[].id | string | Message UUID |
messages[].role | string | "user" or "assistant" |
messages[].content | string | Message text |
messages[].timestamp | number | Unix timestamp in milliseconds |
createdAt | number | Session creation timestamp (ms) |
updatedAt | number | Last update timestamp (ms) |
provider | string | LLM provider (optional) |
model | string | Model ID (optional) |
TTL and expiration
Sessions expire based on two independent conditions:
| Condition | Default | Configurable |
|---|---|---|
| Absolute TTL | 24 hours from creation | SEEPIENT_SESSION_TTL env (seconds) or sessionTTL server option |
| Inactivity timeout | 30 minutes since last activity | inactivityTimeout server option (milliseconds) |
A session is considered expired when either condition is met. Expired sessions are automatically deleted from memory and disk.
Configuring TTL
import { runSeepientServer } from "seepient/server";
await runSeepientServer({
sessionTTL: 7200, // 2 hours in seconds
});Or via environment variable:
SEEPIENT_SESSION_TTL=7200 seepient serverConcurrency limits
Each API key is limited to 5 concurrent active sessions by default. When the limit is reached, creating a new session returns an error.
Maximum concurrent sessions (5) reached for this API key.To free up a slot, either wait for sessions to expire or delete them explicitly.
Auto-cleanup
The server runs a cleanup sweep every 5 minutes that:
- Iterates all in-memory sessions
- Checks TTL and inactivity conditions
- Deletes expired sessions from memory and disk
The cleanup timer uses .unref() so it does not prevent graceful process shutdown.
Session creation & attachment
Sessions are created and attached explicitly by specifying a sessionId string. Omission of sessionId (or sessionId: null/undefined) executes a stateless one-shot turn per Decision D1: no session is created in memory or on disk, and message history is neither loaded nor persisted.
Via WebSocket
To execute a session-backed turn, provide a client-generated or known sessionId:
{
"type": "chat",
"id": "msg-001",
"message": "Hello!",
"sessionId": "550e8400-e29b-41d4-a716-446655440000"
}If the session does not exist, the server creates it under the authenticated API key; subsequent messages referencing that sessionId append to its history.
If sessionId is omitted or null, the WebSocket executes a stateless one-shot turn without attaching to or creating a session.
Via REST
The REST /v1/chat endpoint supports both stateless turns and session-backed turns:
curl -X POST http://localhost:7337/v1/chat \
-H "Authorization: Bearer seepient_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"message": "Hello!",
"sessionId": "550e8400-e29b-41d4-a716-446655440000"
}'When sessionId is provided in the request body, the server loads the session history, acquires an in-flight turn lock, persists the user message before generation, and persists the assistant response on completion. If sessionId is omitted, the turn executes statelessly without persistence.
Reconnection protocol
When a WebSocket connection drops, clients can recover their session state:
1. Reconnect with reconnect
{
"type": "reconnect",
"sessionId": "550e8400-...",
"lastSeenId": "msg_005"
}The server responds with a replay message containing any messages after lastSeenId:
{
"type": "replay",
"messages": [
{ "id": "msg_006", "role": "assistant", "content": "...", "timestamp": 1712505610000 }
],
"currentStatus": "ready"
}2. Resume with resume
{
"type": "resume",
"sessionId": "550e8400-..."
}The server responds with the full session history:
{
"type": "session_resumed",
"sessionId": "550e8400-...",
"messages": [/* all messages */]
}3. Session not found
If the session has expired or does not exist:
{
"type": "error",
"code": "SESSION_NOT_FOUND",
"retryable": false,
"message": "Session 550e8400-... not found or expired"
}Reconnect vs Resume
Use reconnect when you know the last message you received and only need the delta. Use resume when you need the full conversation history (e.g., page refresh).
Persistence behavior
- Sessions are persisted to disk after every message
- The server loads sessions from disk on demand (lazy loading)
- Writes are atomic (v0.2.2+): data is written to a temporary file first, then renamed into place. A crash mid-write never leaves a corrupt session file.
- Persistence is best-effort -- write failures do not crash the server
- In-memory sessions are rebuilt from disk on server restart