obsidian/wiki/agent-sdk/sessions.md
2026-04-17 13:24:18 +01:00

176 lines
7 KiB
Markdown

---
title: "Sessions — Persisting and Resuming Conversation History"
aliases: [session-management, continue-resume-fork]
tags: [agent-sdk, sessions, python, typescript, conversation-history]
sources: [raw/Work with sessions.md]
created: 2026-04-17
updated: 2026-04-17
---
# Sessions — Persisting and Resuming Conversation History
A **session** is the full conversation history the SDK accumulates during a `query()` run: your prompt, every tool call, every tool result, every response. The SDK writes it to disk automatically under `~/.claude/projects/<encoded-cwd>/<session-id>.jsonl`.
Sessions persist the **conversation**, not the filesystem. To snapshot/revert file changes use [[wiki/agent-sdk/file-checkpointing|file-checkpointing]].
---
## Choosing an Approach
| Scenario | What to use |
|---|---|
| One-shot task, no follow-up | Nothing extra. One `query()` call. |
| Multi-turn chat in one process | `ClaudeSDKClient` (Python) or `continue: true` (TypeScript) |
| Pick up after a process restart | `continue_conversation=True` / `continue: true` — resumes most recent session, no ID needed |
| Resume a specific past session | Capture session ID → pass to `resume` |
| Try an alternative without losing original | Fork the session |
| Stateless, no disk writes (TypeScript only) | `persistSession: false` |
---
## Automatic Session Management
### Python — `ClaudeSDKClient`
`ClaudeSDKClient` tracks the session ID internally. Every `client.query()` call automatically continues the same session. Use it as an async context manager.
```python
async with ClaudeSDKClient(options=options) as client:
await client.query("Analyze the auth module")
async for message in client.receive_response():
print_response(message)
# automatically continues same session
await client.query("Now refactor it to use JWT")
async for message in client.receive_response():
print_response(message)
```
### TypeScript — `continue: true`
The stable V1 `query()` has no session-holding client object. Pass `continue: true` on each subsequent call — SDK finds and resumes the most recent session on disk.
```typescript
// First query — creates a new session
for await (const message of query({ prompt: "Analyze the auth module", ... })) { ... }
// Second query — resumes most recent session
for await (const message of query({
prompt: "Now refactor it to use JWT",
options: { continue: true, allowedTools: [...] }
})) { ... }
```
> V2 preview (`createSession()` / `send` / `stream`) is closer to Python's client feel but is unstable — stick to V1 for production. See [[wiki/agent-sdk/typescript-v2-interface|typescript-v2-interface]].
---
## Manual Session Options with `query()`
### Capture the Session ID
Read `session_id` from the `ResultMessage` — present on every result, success or error.
```python
async for message in query(prompt="...", options=ClaudeAgentOptions(...)):
if isinstance(message, ResultMessage):
session_id = message.session_id
```
In TypeScript the ID is also on the init `SystemMessage` (earlier than `ResultMessage`).
### Resume by ID
```python
async for message in query(
prompt="Now implement the refactoring you suggested",
options=ClaudeAgentOptions(
resume=session_id,
allowed_tools=["Read", "Edit", "Write", "Glob", "Grep"],
),
): ...
```
**Common resume use cases:**
- Follow up on a completed task without re-reading files
- Recover from `error_max_turns` or `error_max_budget_usd` with higher limits
- Restore a conversation after process restart
**Gotcha — mismatched `cwd`:** Sessions are stored under `~/.claude/projects/<encoded-cwd>/`. If the resume call runs from a different directory, the SDK looks in the wrong folder and returns a fresh session. The `cwd` must match exactly.
### Fork to Explore Alternatives
Fork creates a new session starting with a copy of the original's history. The original stays unchanged. Both sessions get their own IDs and can be resumed independently.
```python
# Fork: branch from session_id, try OAuth2
forked_id = None
async for message in query(
prompt="Instead of JWT, implement OAuth2 for the auth module",
options=ClaudeAgentOptions(resume=session_id, fork_session=True),
):
if isinstance(message, ResultMessage):
forked_id = message.session_id
# Original session untouched — continue JWT thread
async for message in query(
prompt="Continue with the JWT approach",
options=ClaudeAgentOptions(resume=session_id),
): ...
```
> Fork branches conversation history only. File changes from a forked agent are real and visible to all sessions in the directory. To branch files too, combine with [[wiki/agent-sdk/file-checkpointing|file-checkpointing]].
---
## Resume Across Hosts
Session files are **local** to the machine that created them.
| Option | How |
|---|---|
| Move the file | Persist `~/.claude/projects/<encoded-cwd>/<session-id>.jsonl`, restore it to the same path on the new host, ensure `cwd` matches |
| Skip session resume | Capture the output you need (analysis, decisions, diffs) and pass it into a fresh session's prompt — often more robust |
---
## Session Utility Functions
Both SDKs expose helpers for working with sessions on disk:
| Purpose | Python | TypeScript |
|---|---|---|
| List sessions | `list_sessions()` | `listSessions()` |
| Read messages | `get_session_messages()` | `getSessionMessages()` |
| Get session info | `get_session_info()` | `getSessionInfo()` |
| Rename session | `rename_session()` | `renameSession()` |
| Tag session | `tag_session()` | `tagSession()` |
Use these to build custom session pickers, cleanup logic, or transcript viewers.
---
## Key Takeaways
- Sessions = serialized conversation history (prompts + tool calls + results), written as `.jsonl` files
- **Continue** = resume most recent session by directory; no ID needed
- **Resume** = return to a specific session by ID; required for multi-user or non-latest sessions
- **Fork** = copy history into a new session; original untouched; both independently resumable
- Mismatched `cwd` is the #1 cause of resume returning a blank session
- For cross-host resuming, moving the `.jsonl` file is possible but passing output as a fresh prompt is more robust
- `persistSession: false` (TypeScript only) keeps the session in memory only — nothing written to disk
---
## Related
- [[wiki/agent-sdk/agent-loop|agent-loop]] — how turns, messages, and context accumulate within a session
- [[wiki/agent-sdk/file-checkpointing|file-checkpointing]] — track and revert file changes across sessions
- [[wiki/agent-sdk/python-api-reference|python-api-reference]] — `ClaudeAgentOptions` session fields reference
- [[wiki/agent-sdk/typescript-api-reference|typescript-api-reference]] — `Options` session fields reference
- [[wiki/agent-sdk/typescript-v2-interface|typescript-v2-interface]] — V2 `createSession()` / `resumeSession()` preview
- [[wiki/agent-sdk/streaming-input|streaming-input]] — notes on session resuming with streaming input
## Sources
- `raw/Work with sessions.md` — official Agent SDK sessions documentation