Bỏ qua để đến nội dung

Làm việc với session

Bài viết được dịch tự động từ bài viết gốc, chưa được kiểm tra lại bởi con người. Chỉ những bài viết có dấu tick xanh cạnh tiêu đề là đã được kiểm tra.

Một session là lịch sử hội thoại SDK tích luỹ trong khi agent của bạn làm việc. Nó chứa prompt của bạn, mọi lời gọi tool agent thực hiện, mọi kết quả tool, và mọi phản hồi. SDK tự động ghi nó xuống đĩa để bạn quay lại sau.

Quay lại một session nghĩa là agent có đầy đủ context từ trước: file nó đã đọc, phân tích nó đã thực hiện, quyết định nó đã đưa ra. Bạn có thể hỏi tiếp một câu, khôi phục sau gián đoạn, hoặc rẽ nhánh để thử cách tiếp cận khác.

Hướng dẫn này bao gồm cách chọn cách tiếp cận phù hợp cho ứng dụng của bạn, các interface SDK tự động theo dõi session, cách lấy session ID và dùng resumefork thủ công, và những điều cần biết khi resume session xuyên host.

Bạn cần xử lý session nhiều hay ít tuỳ vào hình dạng ứng dụng của bạn. Quản lý session phát huy tác dụng khi bạn gửi nhiều prompt cần chia sẻ context. Trong một lời gọi query() duy nhất, agent đã tự thực hiện bao nhiêu turn nó cần, và permission prompt cùng AskUserQuestion được xử lý trong vòng lặp (chúng không kết thúc lời gọi).

Bạn đang xây gìDùng gì
Tác vụ một lần: một prompt, không có follow-upKhông cần gì thêm. Một lời gọi query() là đủ.
Chat nhiều lượt trong một processClaudeSDKClient (Python) hoặc continue: true (TypeScript). SDK tự theo dõi session cho bạn, không cần xử lý ID.
Tiếp tục từ nơi bạn dừng lại sau khi process restartcontinue_conversation=True (Python) / continue: true (TypeScript). Resume session gần nhất trong thư mục, không cần ID.
Resume một session cụ thể trong quá khứ (không phải gần nhất)Lấy session ID và truyền vào resume.
Thử một cách tiếp cận khác mà không mất bản gốcFork session.
Tác vụ stateless, không muốn ghi gì xuống đĩa (chỉ TypeScript)Đặt persistSession: false. Session chỉ tồn tại trong bộ nhớ trong lúc lời gọi diễn ra. Python luôn ghi xuống đĩa.

Continue, resume, và fork là các trường option bạn đặt trên query() (ClaudeAgentOptions trong Python, Options trong TypeScript).

Continueresume đều lấy một session có sẵn và thêm vào nó. Khác biệt là cách chúng tìm session đó:

  • Continue tìm session gần nhất trong thư mục hiện tại. Bạn không cần theo dõi gì. Phù hợp khi ứng dụng của bạn chạy một hội thoại tại một thời điểm.
  • Resume nhận một session ID cụ thể. Bạn theo dõi ID. Cần thiết khi bạn có nhiều session (ví dụ, một cho mỗi người dùng trong ứng dụng multi-user) hoặc muốn quay lại một session không phải gần nhất.

Fork thì khác: nó tạo một session mới bắt đầu với bản copy lịch sử của bản gốc. Bản gốc giữ nguyên không đổi. Dùng fork để thử một hướng khác trong khi vẫn giữ được lựa chọn quay lại.

Cả hai SDK đều cung cấp một interface tự theo dõi trạng thái session qua các lời gọi, nên bạn không cần tự truyền ID. Dùng chúng cho hội thoại nhiều lượt trong một process.

ClaudeSDKClient tự xử lý session ID nội bộ. Mỗi lời gọi client.query() tự động tiếp tục cùng session. Gọi client.receive_response() để lặp qua message cho query hiện tại. Dùng client như một async context manager để việc thiết lập và dọn dẹp kết nối được xử lý cho bạn, hoặc gọi connect()disconnect() thủ công.

Ví dụ này chạy hai query trên cùng client. Query đầu yêu cầu agent phân tích một module; query thứ hai yêu cầu nó refactor module đó. Vì cả hai lời gọi đi qua cùng một instance client, query thứ hai có đầy đủ context từ query đầu mà không cần resume hay session ID tường minh nào:

import asyncio
from claude_agent_sdk import (
ClaudeSDKClient,
ClaudeAgentOptions,
AssistantMessage,
ResultMessage,
TextBlock,
)
def print_response(message):
"""In chỉ phần dễ đọc của một 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:
# Query đầu: client lưu session ID nội bộ
await client.query("Analyze the auth module")
async for message in client.receive_response():
print_response(message)
# Query thứ hai: tự động tiếp tục cùng session
await client.query("Now refactor it to use JWT")
async for message in client.receive_response():
print_response(message)
asyncio.run(main())

Mỗi query in ra phản hồi text của agent theo sau bởi một dòng trạng thái từ result message, ví dụ [done: success, cost: $0.0042].

TypeScript SDK không có một client object giữ session như ClaudeSDKClient của Python. Thay vào đó, truyền continue: true trên mỗi lời gọi query() tiếp theo và SDK sẽ tìm session gần nhất trong thư mục hiện tại. Không cần theo dõi ID.

Ví dụ này thực hiện hai lời gọi query() riêng biệt. Lời gọi đầu tạo một session mới; lời gọi thứ hai đặt continue: true, báo cho SDK tìm và resume session gần nhất trên đĩa. Agent có đầy đủ context từ lời gọi đầu:

import { query } from "@anthropic-ai/claude-agent-sdk";
// Query đầu: tạo một session mới
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) {
// Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi,
// nên query tiếp theo bên dưới vẫn chạy.
console.error(`Session ended with an error: ${error}`);
}
// Query thứ hai: continue: true resume session gần nhất
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);
}
}

Resume và fork cần một session ID. Đọc nó từ trường session_id trên result message (ResultMessage trong Python, SDKResultMessage trong TypeScript), luôn có mặt trên mọi result bất kể thành công hay lỗi. Trong TypeScript, ID cũng khả dụng sớm hơn như một trường trực tiếp trên SystemMessage init; trong Python nó nằm lồng trong SystemMessage.data.

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:
# Một lời gọi query() single-shot raise lỗi sau khi yield result lỗi. Nếu
# lỗi đến từ result lỗi, vòng lặp trên đã lưu session_id rồi;
# lỗi process không yield result message nào, nên session_id vẫn 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) {
// Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi. Nếu
// lỗi đến từ result lỗi, vòng lặp trên đã lưu sessionId rồi;
// lỗi process không yield result message nào, nên sessionId vẫn undefined.
console.error(`Session ended with an error: ${error}`);
}
console.log(`Session ID: ${sessionId}`);

Khi query hoàn tất, script in ra phản hồi của agent theo sau bởi một dòng như Session ID: 5b3f2c1a-8d4e-4f6b-9a7c-2e1d0f9b8a6c. Ở các phần tiếp theo, bạn truyền ID này vào resume.

Truyền một session ID vào resume để quay lại session đó. Agent tiếp tục với đầy đủ context từ nơi session dừng lại. Lý do phổ biến để resume:

  • Follow up một tác vụ đã hoàn tất. Agent đã phân tích thứ gì đó; giờ bạn muốn nó hành động dựa trên phân tích đó mà không cần đọc lại file.
  • Khôi phục sau khi chạm giới hạn. Lần chạy đầu kết thúc bằng error_max_turns hoặc error_max_budget_usd; resume với giới hạn cao hơn. Trong một lời gọi query() single-shot, SDK raise lỗi sau khi yield result đó, nên bắt lỗi trước khi resume.
  • Restart process của bạn. Bạn đã lưu ID trước khi shutdown và muốn khôi phục hội thoại.

Ví dụ này resume session từ Lấy session ID với một prompt follow-up. Vì bạn đang resume, agent đã có phân tích trước đó trong context:

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
session_id = "..." # ID bạn đã lưu ở ví dụ trước
async def main():
# Session trước đã phân tích code; giờ dựa trên phân tích đó
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 = "..."; // ID bạn đã lưu ở ví dụ trước
// Session trước đã phân tích code; giờ dựa trên phân tích đó
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);
}
}

Bạn sẽ thấy một phản hồi xây dựng dựa trên phân tích trước đó thay vì bắt đầu lại từ đầu. Điều đó xác nhận agent đã resume session với context trước đó còn nguyên vẹn.

Để resume session xuyên máy hoặc trong môi trường serverless, mirror transcript vào shared storage bằng một adapter SessionStore.

Fork tạo một session mới bắt đầu với bản copy lịch sử của bản gốc nhưng rẽ nhánh từ điểm đó. Bản fork có session ID riêng; ID và lịch sử của bản gốc giữ nguyên không đổi. Bạn sẽ có hai session độc lập, mỗi cái có thể resume riêng.

Ví dụ này dựa trên Lấy session ID: bạn đã phân tích một module auth trong session_id và muốn khám phá OAuth2 mà không mất luồng tập trung vào JWT. Khối đầu fork session và lưu ID của bản fork (forked_id); khối thứ hai resume session_id gốc để tiếp tục hướng JWT. Giờ bạn có hai session ID trỏ tới hai lịch sử riêng biệt:

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
session_id = "..." # ID bạn đã lưu ở ví dụ trước
async def main():
# Fork: rẽ nhánh từ session_id thành một session mới
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 # ID của bản fork, khác session_id
if message.subtype == "success":
print(message.result)
except Exception as error:
# Một lời gọi query() single-shot raise lỗi sau khi yield result lỗi. Nếu
# lỗi đến từ result lỗi, forked_id đã được lưu bởi vòng lặp trên;
# lỗi process không yield result message nào cả.
print(f"Session ended with an error: {error}")
print(f"Forked session: {forked_id}")
# Session gốc không bị đụng tới; resume nó tiếp tục luồng JWT
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:
# Một lời gọi query() single-shot raise lỗi sau khi yield result lỗi.
print(f"Session ended with an error: {error}")
asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";
const sessionId = "..."; // ID bạn đã lưu ở ví dụ trước
// Fork: rẽ nhánh từ sessionId thành một session mới
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; // ID của bản fork, khác sessionId
}
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
} catch (error) {
// Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi. Nếu
// lỗi đến từ result lỗi, forkedId đã được lưu bởi vòng lặp trên;
// lỗi process không yield result message nào cả.
console.error(`Session ended with an error: ${error}`);
}
console.log(`Forked session: ${forkedId}`);
// Session gốc không bị đụng tới; resume nó tiếp tục luồng JWT
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) {
// Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi.
console.error(`Session ended with an error: ${error}`);
}

Bạn sẽ thấy forkedId khác với session ID gốc. Resume session gốc vẫn tiếp tục luồng JWT, xác nhận fork không làm thay đổi lịch sử gốc.

File session gắn với máy đã tạo ra nó. Để resume một session trên host khác (CI worker, container ephemeral, serverless), bạn có hai lựa chọn:

  • Di chuyển file session. Lưu ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl từ lần chạy đầu và khôi phục nó vào cùng đường dẫn trên host mới trước khi gọi resume. cwd phải khớp.
  • Không dựa vào resume session. Lưu kết quả bạn cần (output phân tích, quyết định, file diff) làm trạng thái ứng dụng và đưa chúng vào prompt của một session mới. Cách này thường bền vững hơn là gửi transcript file đi.

Cả hai SDK đều expose hàm để liệt kê session trên đĩa và đọc message của chúng: listSessions()getSessionMessages() trong TypeScript, list_sessions()get_session_messages() trong Python. Dùng chúng để xây session picker tuỳ biến, logic dọn dẹp, hoặc transcript viewer.

Cả hai SDK cũng expose hàm để tra cứu và thay đổi từng session riêng lẻ: get_session_info(), rename_session(), và tag_session() trong Python, và getSessionInfo(), renameSession(), và tagSession() trong TypeScript. Dùng chúng để tổ chức session theo tag hay đặt tiêu đề dễ đọc.

  • Agent loop hoạt động thế nào: Hiểu turn, message, và tích luỹ context trong một session
  • File checkpointing: Snapshot và revert thay đổi file agent đã làm trong một session
  • Python ClaudeAgentOptions: Tham khảo đầy đủ option session cho Python
  • TypeScript Options: Tham khảo đầy đủ option session cho TypeScript