
こんにちは。よっしーです(^^)
背景
この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。
1. 一言でいうと
セッションは、エージェントが動いている間に SDK が積み上げる会話の履歴です。プロンプト、エージェントのツール呼び出し、ツールの結果、応答がすべて含まれ、SDK が自動でディスクに書き込みます。このページは、その履歴に後から戻る3つの方法(continue・resume・fork)の使い分けを定めたものです。
セッションに戻ると、エージェントは前回の文脈をそのまま持っています。すでに読んだファイル、済ませた分析、下した判断を覚えているので、追加の質問をしたり、中断から立て直したり、別のやり方を試すために分岐したりできます。
最初に1つ押さえておくべき点があります。セッションが保持するのは会話であって、ファイルの状態ではありません。 エージェントが書き換えたファイルを元に戻したいなら、セッションではなく「ファイルチェックポイント」という別の仕組みを使います。
2. どういう場面で役立つか
シーン1:分析してから、その結果に基づいて直させる
1回目で「認証モジュールを分析して」と頼み、2回目で「その提案どおりに直して」と頼む場合です。2回目が1回目と同じセッションなら、エージェントはファイルを読み直さずに、前回の分析の続きとして作業できます。
シーン2:ユーザーごとの会話を保存し、後日続きから再開する
社内向けのチャットアプリで、ユーザーごとに会話を持ち、翌日にアプリを開いたら昨日の続きから話せるようにする場合です。セッション ID をユーザーに結び付けて保存しておき、次回はその ID で再開します。
シーン3:2つの方針を比べる
「JWT で作る案」を進めているセッションから分岐し、「OAuth2 ならどうなるか」を別に検討させる場合です。分岐(fork)すると、元のセッションには手を付けずに新しいセッションで別の方向を試せます。
不要・向かないケース
- 1回の質問で完結する作業:1回の
query()呼び出しの中で、エージェントは必要なだけターンを回します。権限の確認やAskUserQuestionもその呼び出しの中で処理され、呼び出しは終わりません(第81回)。セッションの管理が必要になるのは、文脈を共有する複数のプロンプトを別々に送るときだけです。 - ファイルの変更を取り消したい:セッションはファイルを戻しません。ファイルチェックポイントを使います。
- 別のマシンでそのまま再開したい:セッションのファイルは作ったマシンにしかありません。共有ストレージにミラーリングする仕組みや、ファイルを運ぶ手間が要ります(後述)。
3. コードと仕組みの解説
原文の fenced コードブロックは8本(Python 単独1本、TypeScript 単独1本、対訳3組)です。どれも内容が違うので、全数を引用します。コードは原文から機械的に抜き出したもので、入れ子に由来する先頭インデントを外した以外は変えていません。
3-0. バージョン要件
| 挙動 | 必要バージョン |
|---|---|
| セッション ID の検索が、現在のプロジェクトディレクトリを越えて行われる(それ以前は、現在のプロジェクトディレクトリとその git worktree の中だけ) | Claude Code v2.1.223 以降 |
CLAUDE_CODE_PROJECT_DIR_NAME でプロジェクトのディレクトリ名を自分で決める | TypeScript Agent SDK v0.3.234 以降/Python Agent SDK v0.2.140 以降 |
実験的な V2 セッション API(createSession() と send/stream)の削除 | TypeScript Agent SDK 0.3.142 で削除済み |
1行目は、連載で残っていた疑問の答えでもあります。第68回(CLI のセッション)では「再開はプロジェクトディレクトリとその git worktree に限られる」と書き、第75回(ヘッドレス)では「このマシン上の任意のプロジェクトから見つける」と書いて、食い違いがありました。今回のページは、v2.1.223 以降は後者、それより前は前者と明記しています。SDK は Claude Code の CLI を同梱しているので、古い CLI を同梱した SDK のバージョンは、今でも前者の動きになります。どちらの動きになるかは、使っている SDK のバージョンで決まります。
3-1. 方法を選ぶ
必要なセッションの扱いは、アプリの形で決まります。
| 作るもの | 使うもの |
|---|---|
| 1回きりの作業(プロンプト1つ、追加の質問なし) | 何も要らない。1回の query() 呼び出しで済む |
| 1つのプロセスの中で続くチャット | ClaudeSDKClient(Python)か continue: true(TypeScript)。SDK がセッションを自動で追跡し、ID を扱う必要はない |
| プロセスを再起動したあと、中断したところから続ける | continue_conversation=True(Python)/continue: true(TypeScript)。そのディレクトリの最新のセッションを再開し、ID は要らない |
| 最新ではない特定の過去のセッションを再開する | セッション ID を記録して resume に渡す |
| 元のセッションを残したまま別のやり方を試す | セッションを fork する |
| ディスクに何も残さない使い捨ての作業 | persistSession: false(TypeScript のみ)。セッションは呼び出しの間だけメモリにある。Python では env オプションで CLAUDE_CODE_SKIP_PROMPT_HISTORY を設定し、トランスクリプトの書き込みを止める |
continue・resume・fork の違い
3つとも query() に渡すオプションです(Python は ClaudeAgentOptions、TypeScript は Options)。
| 方法 | 何をするか | どのセッションか | 向いている場面 |
|---|---|---|---|
| continue | 既存のセッションに続きを足す | 現在のディレクトリの最新のセッション。何も追跡しなくてよい | アプリが一度に1つの会話しか動かさない |
| resume | 既存のセッションに続きを足す | 指定したセッション ID。ID を自分で追跡する | 複数のセッションがある(マルチユーザーのアプリでユーザーごとに1つ、など)/最新ではないセッションに戻る |
| fork | 元の履歴のコピーから始まる新しいセッションを作る。元は変わらない | 分岐元として指定したセッション | 別の方向を試しつつ、元に戻れる状態を残す |
continue と resume はどちらも既存のセッションに足していくもので、違いは「どうやってセッションを見つけるか」だけです。fork だけは新しいセッションを作ります。
continue の「現在のディレクトリの最新のセッション」という決め方には注意が要ります。同じ作業ディレクトリで複数のユーザーやジョブのセッションが動くアプリでは、「最新」が自分のセッションとは限りません。原文が「複数のセッションがあるなら resume」と書いているのはこのためと読めます。第79回で見たように cwd はセッションの保存先も決めるので、ユーザーごとに cwd を分けるか、ID で resume するのが安全です(筆者の指摘)。
3-2. 自動のセッション管理
どちらの SDK にも、呼び出しをまたいでセッションを追跡してくれる仕組みがあり、ID を手で渡す必要はありません。1つのプロセスの中で続く会話に使います。
Python:ClaudeSDKClient
ClaudeSDKClient はセッション ID を内部で扱います。client.query() を呼ぶたびに、自動で同じセッションの続きになります。今の問い合わせのメッセージは client.receive_response() で順に受け取ります。クライアントを非同期のコンテキストマネージャー(async with)として使うと、接続の準備と後片付けを自動でしてくれます。connect() と disconnect() を手で呼ぶこともできます。
次の例は、同じ client で2回問い合わせます。1回目はモジュールの分析、2回目はそのモジュールのリファクタリングです。どちらも同じクライアントを通るので、2回目は resume もセッション ID も指定せずに、1回目の文脈を全て持っています。
import asyncio
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
AssistantMessage,
ResultMessage,
TextBlock,
)
def print_response(message):
"""Print only the human-readable parts of a message."""
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, TextBlock):
print(block.text)
elif isinstance(message, ResultMessage):
cost = (
f"${message.total_cost_usd:.4f}"
if message.total_cost_usd is not None
else "N/A"
)
print(f"[done: {message.subtype}, cost: {cost}]")
async def main():
options = ClaudeAgentOptions(
allowed_tools=["Read", "Edit", "Glob", "Grep"],
)
async with ClaudeSDKClient(options=options) as client:
# First query: client captures the session ID internally
await client.query("Analyze the auth module")
async for message in client.receive_response():
print_response(message)
# Second query: automatically continues the same session
await client.query("Now refactor it to use JWT")
async for message in client.receive_response():
print_response(message)
asyncio.run(main())
各問い合わせで、エージェントの文章の後に、結果メッセージから作った状態の行(例:[done: success, cost: $0.0042])が表示されます。print_response は、アシスタントのメッセージからは TextBlock の文章だけを、結果メッセージからは subtype と費用(total_cost_usd)を出しています。費用が取れないときは N/A になります。
ClaudeSDKClient と単独の query() 関数のどちらを使うかは、Python SDK リファレンスに比較があります。第79回で見た「Python の query() は制御メソッドを持たない」も、使い分けの材料の1つです。
TypeScript:continue: true
TypeScript SDK には、Python の ClaudeSDKClient のようなセッションを抱えるクライアントがありません。 代わりに、2回目以降の query() 呼び出しで continue: true を渡すと、SDK が現在のディレクトリの最新のセッションを探して再開します。ID を追跡する必要はありません。
次の例は query() を2回別々に呼びます。1回目は新しいセッションを作り、2回目は continue: true で「ディスク上の最新のセッションを探して再開する」よう指示します。エージェントは1回目の文脈を全て持っています。
import { query } from "@anthropic-ai/claude-agent-sdk";
// First query: creates a new session
try {
for await (const message of query({
prompt: "Analyze the auth module",
options: { allowedTools: ["Read", "Glob", "Grep"] }
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result,
// so the follow-up query below still runs.
console.error(`Session ended with an error: ${error}`);
}
// Second query: continue: true resumes the most recent session
for await (const message of query({
prompt: "Now refactor it to use JWT",
options: {
continue: true,
allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
1回目を try で囲んでいるのは、1回きりの query() がエラーの結果を出したあとで例外を投げるからです(第83〜85回と同じ)。囲んでおけば、1回目が失敗しても2回目の問い合わせは実行されます。
2回目では allowedTools に Edit と Write が加わっています。1回目は読むだけ、2回目は書き換える、という段階の違いを、呼び出しごとの許可リストで表しています。許可リストのような設定は呼び出しのたびに渡すもので、セッションに保存されて引き継がれるわけではない、とこの例から読み取れます(筆者の読み。第86回で見たように、システムプロンプトは例外的にセッションの最初に記録されます)。
なお、以前あった実験的な V2 セッション API(createSession() と send/stream の形)は、TypeScript Agent SDK 0.3.142 で削除されました。このページの query() とセッションのオプションを使います。
3-3. query() でセッションのオプションを使う
セッション ID を記録する
resume と fork にはセッション ID が要ります。ID は結果メッセージ(Python は ResultMessage、TypeScript は SDKResultMessage)の session_id フィールドから読みます。このフィールドは、成功でもエラーでも、全ての結果メッセージにあります。
ID は最初のシステムメッセージからも読めますが、取り出し方が言語で違います。TypeScript では SystemMessage の直接のフィールドで、Python では SystemMessage.data の中に入っています(第77・84回と同じ違い)。
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main():
session_id = None
try:
async for message in query(
prompt="Analyze the auth module and suggest improvements",
options=ClaudeAgentOptions(
allowed_tools=["Read", "Glob", "Grep"],
),
):
if isinstance(message, ResultMessage):
session_id = message.session_id
if message.subtype == "success":
print(message.result)
except Exception as error:
# A single-shot query() raises after yielding an error result. If the
# failure was an error result, the loop above already captured session_id;
# connection or process failures yield no result message, so session_id stays None.
print(f"Session ended with an error: {error}")
print(f"Session ID: {session_id}")
return session_id
session_id = asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";
let sessionId: string | undefined;
try {
for await (const message of query({
prompt: "Analyze the auth module and suggest improvements",
options: { allowedTools: ["Read", "Glob", "Grep"] }
})) {
if (message.type === "result") {
sessionId = message.session_id;
if (message.subtype === "success") {
console.log(message.result);
}
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result. If the
// failure was an error result, the loop above already captured sessionId;
// connection or process failures yield no result message, so sessionId stays undefined.
console.error(`Session ended with an error: ${error}`);
}
console.log(`Session ID: ${sessionId}`);
問い合わせが終わると、エージェントの応答と、Session ID: 5b3f2c1a-8d4e-4f6b-9a7c-2e1d0f9b8a6c のような行が表示されます。
コメントに大事な説明があります。
- 失敗がエラーの結果だった場合、例外が投げられる前にループがすでに
session_idを記録しています。つまり、エラーで終わったセッションも ID が取れて、後で再開できます。 - 接続やプロセスの失敗では結果メッセージ自体が出ないので、
session_idはNone(TypeScript ではundefined)のままです。
ID で再開する(resume)
セッション ID を resume に渡すと、その特定のセッションに戻ります。エージェントはセッションが終わった時点の文脈を全て持って再開します。原文が挙げる再開の理由は3つです。
| 理由 | 内容 |
|---|---|
| 終わった作業の続きをさせる | すでに何かを分析させた。ファイルを読み直させずに、その分析に基づいて動いてほしい |
| 上限に達したところから立て直す | 最初の実行が error_max_turns(ターン数の上限)や error_max_budget_usd(予算の上限)で終わった。より高い上限を設定して再開する。1回きりの query() はそのエラー結果の後で例外を投げるので、再開する前に例外を捕まえる |
| プロセスを再起動する | 終了前に ID を記録しておき、会話を復元したい |
2つ目の理由は、上の「エラーで終わったセッションも ID が取れる」と組み合わさります。上限で止まった作業を、最初からやり直さずに続けられます(第77回で見た終了の分類との対応)。
次の例は、前の例で記録したセッションを、追加のプロンプトで再開します。再開しているので、エージェントは前回の分析をすでに文脈に持っています。
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
session_id = "..." # The ID you captured in the previous example
async def main():
# Earlier session analyzed the code; now build on that analysis
async for message in query(
prompt="Now implement the refactoring you suggested",
options=ClaudeAgentOptions(
resume=session_id,
allowed_tools=["Read", "Edit", "Write", "Glob", "Grep"],
),
):
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)
asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";
const sessionId = "..."; // The ID you captured in the previous example
// Earlier session analyzed the code; now build on that analysis
for await (const message of query({
prompt: "Now implement the refactoring you suggested",
options: {
resume: sessionId,
allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
}
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
前回の分析を踏まえた応答が返れば、文脈を保ったまま再開できたことが確認できます。
セッションの保存場所
Claude Code はセッションを ~/.claude/projects/<encoded-cwd>/*.jsonl に保存します。環境変数 CLAUDE_CONFIG_DIR を設定している場合は、$CLAUDE_CONFIG_DIR/projects/ の下になります。
<encoded-cwd> は、作業ディレクトリの絶対パスの英数字以外の文字を全て - に置き換えたものです。たとえば /Users/me/proj は -Users-me-proj になります。変換後の名前が200文字を超える場合、Claude Code は名前を切り詰めてハッシュを付けるので、projects/ を一覧したときは変換後の名前の先頭200文字で照合します。CLAUDE_CONFIG_DIR と一緒に CLAUDE_CODE_PROJECT_DIR_NAME を設定している場合は、projects/ の中のその名前を探します(バージョンは 3-0)。
どの作業ディレクトリからでも再開できます。 ただし条件があります。
- ディレクトリをまたいだ検索:Claude Code は現在のプロジェクトディレクトリを越えて ID を探します(v2.1.223 以降。3-0 参照)。検索の順序や、同じ ID のコピーが複数あるときの扱いは、CLI のセッションのページ(第68回)の範囲です。
- 同じマシンだけ:セッションのファイルが今のマシンにある必要があります。
マシンをまたいで、あるいはサーバーレスの環境で再開するには、SessionStore アダプターでトランスクリプトを共有ストレージにミラーリングします(3-4)。
第68回では「JSONL を直接解析するのは禁止(内部形式)」と整理しました。このページが保存場所を示しているのは、ファイルの場所を見つけたり運んだりするためで、中身を自分で読むためではありません。中身を読むなら、後述の getSessionMessages() などの関数を使います(筆者の整理)。
fork して別の案を探る
fork は、元の履歴のコピーから始まり、そこから分かれる新しいセッションを作ります。fork したセッションは自分の ID を持ち、元の ID と履歴は変わりません。 結果として、それぞれ別々に再開できる独立した2つのセッションができます。
fork が分岐させるのは会話の履歴で、ファイルシステムではありません。 fork したエージェントがファイルを書き換えれば、その変更は本物で、同じディレクトリで動いている全てのセッションから見えます。ファイルの変更も分岐させて戻したいなら、ファイルチェックポイントを使います。
次の例は、「セッション ID を記録する」の続きです。session_id で認証モジュールをすでに分析していて、JWT の方針を失わずに OAuth2 を検討したい、という状況です。最初のブロックでセッションを fork して、fork の ID(forked_id)を記録します。2つ目のブロックで元の session_id を再開し、JWT の方針を続けます。これで、別々の履歴を指す2つのセッション ID ができます。
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
session_id = "..." # The ID you captured in the previous example
async def main():
# Fork: branch from session_id into a new session
forked_id = None
try:
async for message in query(
prompt="Instead of JWT, outline how OAuth2 would work for the auth module",
options=ClaudeAgentOptions(
resume=session_id,
fork_session=True,
max_turns=5,
),
):
if isinstance(message, ResultMessage):
forked_id = message.session_id # The fork's ID, distinct from session_id
if message.subtype == "success":
print(message.result)
except Exception as error:
# A single-shot query() raises after yielding an error result. If the
# failure was an error result, forked_id was already captured by the
# loop above; connection or process failures yield no result message.
print(f"Session ended with an error: {error}")
print(f"Forked session: {forked_id}")
# Original session is untouched; resuming it continues the JWT thread
try:
async for message in query(
prompt="Continue with the JWT approach",
options=ClaudeAgentOptions(resume=session_id),
):
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)
except Exception as error:
# A single-shot query() raises after yielding an error result.
print(f"Session ended with an error: {error}")
asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";
const sessionId = "..."; // The ID you captured in the previous example
// Fork: branch from sessionId into a new session
let forkedId: string | undefined;
try {
for await (const message of query({
prompt: "Instead of JWT, outline how OAuth2 would work for the auth module",
options: {
resume: sessionId,
forkSession: true,
maxTurns: 5
}
})) {
if (message.type === "system" && message.subtype === "init") {
forkedId = message.session_id; // The fork's ID, distinct from sessionId
}
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result. If the
// failure was an error result, forkedId was already captured by the loop
// above; connection or process failures yield no result message.
console.error(`Session ended with an error: ${error}`);
}
console.log(`Forked session: ${forkedId}`);
// Original session is untouched; resuming it continues the JWT thread
try {
for await (const message of query({
prompt: "Continue with the JWT approach",
options: { resume: sessionId }
})) {
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result.
console.error(`Session ended with an error: ${error}`);
}
forkedId が元のセッション ID と違うこと、元のセッションを再開すると JWT の話が続くことを確かめれば、fork が元の履歴を変えていないことが分かります。
言語の違いが2つあります。
- オプション名:Python は
fork_session=True、max_turns=5、TypeScript はforkSession: true、maxTurns: 5。 - fork の ID の取り方:Python は結果メッセージの
session_idから、TypeScript はinitのシステムメッセージのsession_idから取っています。どちらも fork 側の新しい ID です。
fork 側は max_turns=5 でターン数を絞っています。比較のための下調べなので、長く走らせない工夫と読めます(筆者の読み)。
fork でファイルが分岐しないことの実務上の意味も押さえておきます。この例の OAuth2 側は「どう動くかを説明して」という依頼なので、ファイルは書き換えません。もし fork 側に実装までさせると、その変更は JWT 側のセッションが作業しているのと同じファイルに入ります。2つの案を実際のコードで比べたいなら、会話の fork に加えて、第63回で見た worktree のような、ファイルの置き場所を分ける仕組みを組み合わせる必要があります(第63回との組み合わせの提案は筆者)。
また第68回では、同じセッションを分岐させずに2か所で開くとトランスクリプトが混ざる、と見ました。同じセッション ID を2つのプロセスから同時に resume するのは避け、並行して進めたいなら fork するのが安全です(第68回の内容の当てはめは筆者)。
3-4. 別のホストで再開する
セッションのファイルは、それを作ったマシンにあります。別のホスト(CI のワーカー、使い捨てのコンテナ、サーバーレス)でセッションを再開するには、次の3つから選びます。
| 方法 | 内容 | 注意点 |
|---|---|---|
| セッションストアを渡す | sessionStore/session_store アダプターをつなぎ、SDK がトランスクリプトを自分のバックエンドにミラーリングする。別のホストはそこから再開できる | ストアの検索キーは作業ディレクトリから作られるので、元の実行と同じ cwd から再開する |
| セッションのファイルを運ぶ | 最初の実行の ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl を保存しておき、resume の前に新しいホストの ~/.claude/projects/ の下のどこかのディレクトリに戻す | Claude Code は現在のプロジェクトディレクトリを越えて ID を探す。ただし v2.1.223 より前(とそれを同梱する古い SDK)は、現在のプロジェクトディレクトリと git worktree の中しか探さない |
| セッションの再開に頼らない | 必要な結果(分析の出力、判断、ファイルの差分)をアプリの状態として保存し、新しいセッションのプロンプトに渡す | 原文は、トランスクリプトのファイルを運び回すより堅牢なことが多いと書いている |
3つ目の方法が「多くの場合より堅牢」とされている点は、設計の指針として重要です。セッションを丸ごと持ち運ぶと、会話の全て(途中で読んだファイルの中身やツールの結果も含む)を運ぶことになり、依存するものも多くなります。必要な結論だけを取り出して次に渡すほうが、壊れにくく、扱うデータも最小限で済みます。トランスクリプトには、エージェントが読んだ設定ファイルの中身なども残りうるので、ファイルやストアで運ぶ場合は、その保管場所を秘密情報と同じ扱いにするべきです(この評価は筆者)。
SessionStore アダプターの詳細は、セッションの保存先のページに回されています。
セッションを一覧・操作する関数。 両方の SDK に、ディスク上のセッションを扱う関数があります。
| 用途 | TypeScript | Python |
|---|---|---|
| セッションの一覧 | listSessions() | list_sessions() |
| セッションのメッセージを読む | getSessionMessages() | get_session_messages() |
| 1つのセッションの情報を取る | getSessionInfo() | get_session_info() |
| 名前を付け直す | renameSession() | rename_session() |
| タグを付ける | tagSession() | tag_session() |
一覧とメッセージの読み出しは、独自のセッション選択画面、古いセッションの掃除、トランスクリプトの閲覧画面を作るのに使います。情報・名前・タグの関数は、セッションをタグで整理したり、人が読める題名を付けたりするのに使います。3-1 のシーン2のようなマルチユーザーのアプリでは、ユーザーの ID をタグにしておけば、そのユーザーのセッションだけを選択画面に出す、といった作り方ができそうです(筆者の応用例。タグの具体的な仕様はリファレンスの範囲)。
4. まとめ
- セッションは会話の履歴(プロンプト、ツール呼び出し、結果、応答)で、SDK が自動でディスクに書く。保持するのは会話で、ファイルではない。ファイルを戻すのはファイルチェックポイント。
- セッション管理が要るのは、文脈を共有する複数のプロンプトを別々に送るときだけ。1回の
query()の中では、必要なだけターンが回る。 - continue=ディレクトリの最新のセッションに続ける、resume=ID で指定したセッションに続ける、fork=履歴のコピーから新しいセッションを作る(元は変わらない)。複数のセッションがあるなら resume。
- 1つのプロセス内のチャットは、Python は
ClaudeSDKClient、TypeScript はcontinue: true。TypeScript にはセッションを抱えるクライアントが無い。V2 セッション API は削除済み。 - セッション ID は成功でもエラーでも結果メッセージの
session_idにある。上限(error_max_turns・error_max_budget_usd)で止まった作業も、上限を上げて resume できる。 - 保存先は
~/.claude/projects/<encoded-cwd>/*.jsonl。v2.1.223 以降は現在のプロジェクトを越えて ID を探す(それ以前と、古い CLI を同梱した SDK は、プロジェクトと git worktree の中だけ)。同じマシンに限る。 - fork はファイルを分岐させない。fork 側の書き換えは、同じディレクトリの全セッションに見える。
- 別のホストで再開するには、セッションストア(同じ
cwdから)、ファイルの持ち運び、または必要な結果だけをアプリで保存して新しいセッションに渡す(多くの場合これが最も堅牢)。 - 使い捨ての作業でディスクに残さないなら、TypeScript は
persistSession: false、Python はCLAUDE_CODE_SKIP_PROMPT_HISTORY。 - 一覧・読み出し・情報・改名・タグ付けの関数で、独自のセッション管理画面を作れる。
次回予告
次回は 「ファイルチェックポイント」(agent-sdk/file-checkpointing) を取り上げる予定です。
今回、「セッションは会話を保持するがファイルは保持しない」「fork してもファイルは分岐しない」と何度も書きました。そのたびに案内されていたのが、ファイルチェックポイントです。次回は、エージェントが書き換えたファイルをスナップショットして元に戻す仕組みを SDK からどう使うのかを、第69回の CLI のチェックポイントと比べながら見ていきます。
※テーマは変更になる場合があります。

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)

コメント