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

Agent loop hoạt động thế nào

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.

Agent SDK cho phép bạn nhúng agent loop tự động của Claude Code vào ứng dụng của riêng bạn. SDK là một package độc lập, cho bạn quyền kiểm soát lập trình đối với tool, permission, giới hạn chi phí, và output. Bạn không cần cài Claude Code CLI để dùng nó.

Khi bạn khởi động một agent, SDK chạy cùng execution loop đang vận hành Claude Code: Claude đánh giá prompt của bạn, gọi tool để hành động, nhận kết quả, và lặp lại cho tới khi tác vụ hoàn thành. Trang này giải thích điều gì xảy ra bên trong vòng lặp đó để bạn có thể xây dựng, debug, và tối ưu agent của mình hiệu quả.

Mỗi phiên agent đều theo cùng một chu trình:

  1. Nhận prompt. Claude nhận prompt của bạn, cùng với system prompt, định nghĩa tool, và lịch sử hội thoại. SDK yield một SystemMessage với subtype "init" chứa metadata của session.
  2. Đánh giá và phản hồi. Claude đánh giá trạng thái hiện tại và quyết định cách tiến hành. Nó có thể phản hồi bằng text, yêu cầu một hoặc nhiều lời gọi tool, hoặc cả hai. SDK yield một AssistantMessage chứa text và các yêu cầu gọi tool.
  3. Thực thi tool. SDK chạy từng tool được yêu cầu và thu thập kết quả. Mỗi bộ kết quả tool được đưa lại cho Claude để ra quyết định tiếp theo. Bạn có thể dùng hooks để chặn, sửa, hoặc block lời gọi tool trước khi nó chạy.
  4. Lặp lại. Bước 2 và 3 lặp lại thành một chu trình. Mỗi chu trình đầy đủ là một turn. Claude tiếp tục gọi tool và xử lý kết quả cho tới khi nó tạo ra phản hồi không có lời gọi tool nào.
  5. Trả kết quả. SDK yield một AssistantMessage cuối cùng với phản hồi text (không có lời gọi tool), theo sau bởi một ResultMessage chứa text cuối cùng, token usage, chi phí, và session ID.

Một câu hỏi nhanh (“có những file gì ở đây?”) có thể chỉ mất một hoặc hai turn gọi Glob rồi phản hồi kết quả. Một tác vụ phức tạp (“refactor module auth và update test”) có thể chuỗi hàng chục lời gọi tool qua nhiều turn - đọc file, sửa code, chạy test - với Claude điều chỉnh cách tiếp cận dựa trên từng kết quả.

Một turn là một lượt đi-về bên trong vòng lặp: Claude tạo ra output bao gồm lời gọi tool, SDK thực thi các tool đó, và kết quả được đưa lại cho Claude tự động. Điều này xảy ra mà không trả quyền điều khiển lại cho code của bạn. Turn tiếp tục cho tới khi Claude tạo ra output không có lời gọi tool, lúc đó vòng lặp kết thúc và kết quả cuối cùng được trả về.

Xem thử một phiên đầy đủ trông như thế nào với prompt “Fix the failing tests in auth.ts”.

Đầu tiên, SDK gửi prompt của bạn cho Claude và yield một SystemMessage với metadata session. Sau đó vòng lặp bắt đầu:

  1. Turn 1: Claude gọi Bash để chạy npm test. SDK yield một AssistantMessage với lời gọi tool, thực thi lệnh, rồi yield một UserMessage với output (ba lỗi).
  2. Turn 2: Claude gọi Read trên auth.tsauth.test.ts. SDK trả về nội dung file và yield một AssistantMessage.
  3. Turn 3: Claude gọi Edit để sửa auth.ts, rồi gọi Bash để chạy lại npm test. Cả ba test đều pass. SDK yield một AssistantMessage.
  4. Turn cuối: Claude tạo ra phản hồi chỉ có text, không có lời gọi tool: “Fixed the auth bug, all three tests pass now.” SDK yield một AssistantMessage cuối cùng với text này, rồi một ResultMessage với cùng text cộng thêm chi phí và usage.

Đó là bốn turn: ba turn có lời gọi tool, một turn phản hồi chỉ text.

Bạn có thể giới hạn vòng lặp với max_turns / maxTurns, thứ chỉ đếm các turn có dùng tool. Ví dụ, max_turns=2 trong vòng lặp trên sẽ dừng trước bước sửa file. Bạn cũng có thể dùng max_budget_usd / maxBudgetUsd để giới hạn turn dựa trên ngưỡng chi tiêu.

Không có giới hạn, vòng lặp chạy cho tới khi Claude tự hoàn thành - ổn với các tác vụ được scope tốt, nhưng có thể chạy lâu với prompt mở (“improve this codebase”). Đặt một budget là mặc định hợp lý cho agent production. Xem Turn và budget bên dưới để biết tham số cụ thể.

Khi vòng lặp chạy, SDK yield một luồng message. Mỗi message mang một type cho biết nó đến từ giai đoạn nào của vòng lặp. Năm type cốt lõi là:

  • SystemMessage: các sự kiện vòng đời session. Trường subtype phân biệt chúng:

    • "init": metadata session cho lần chạy. Khi hook SessionStart hoặc Setup chạy trong lúc khởi động session, các message vòng đời hook của nó đến trước message init
    • "compact_boundary": xuất hiện sau khi compaction
    • "informational": banner trạng thái dạng text thuần từ vòng lặp
    • "worker_shutting_down": vòng lặp sẽ kết thúc sau turn hiện tại vì host đang thoát hoặc Remote Control ngắt kết nối

    Trong TypeScript, mỗi subtype khác "init" là một type riêng trong union SDKMessage thay vì một subtype của SDKSystemMessage.

  • AssistantMessage: phát ra sau mỗi phản hồi của Claude, kể cả phản hồi chỉ-text cuối cùng. Chứa các content block text và block gọi tool từ turn đó.

  • UserMessage: phát ra sau mỗi lần thực thi tool với nội dung kết quả tool gửi lại cho Claude. Cũng phát ra cho bất kỳ input nào bạn stream giữa chừng vòng lặp.

  • StreamEvent: chỉ phát ra khi partial message được bật. Chứa raw streaming event thô từ API. Xem Stream responses.

  • ResultMessage: đánh dấu kết thúc agent loop. Chứa kết quả text cuối cùng, token usage, chi phí, và session ID. Kiểm tra trường subtype để xác định tác vụ thành công hay chạm giới hạn. Một số ít sự kiện hệ thống theo sau, như prompt_suggestion, có thể xuất hiện sau nó, nên lặp stream tới khi hoàn tất thay vì break ngay khi thấy result. Xem Xử lý kết quả.

Năm type này bao phủ toàn bộ vòng đời agent loop. Cả hai SDK đều yield thêm các sự kiện observability như trạng thái rate-limit và thông báo task, không bắt buộc để vận hành vòng lặp.

Bạn cần xử lý message nào tuỳ thuộc vào thứ bạn đang xây:

  • Chỉ cần kết quả cuối: xử lý ResultMessage để lấy output, chi phí, và tác vụ thành công hay chạm giới hạn.
  • Cập nhật tiến trình: xử lý AssistantMessage để xem Claude đang làm gì mỗi turn, kể cả tool nào nó gọi.
  • Streaming trực tiếp: bật partial message (include_partial_messages trong Python, includePartialMessages trong TypeScript) để nhận StreamEvent theo thời gian thực. Xem Stream response theo thời gian thực.

Cách kiểm tra type của message tuỳ theo SDK:

  • Python: kiểm tra type message bằng isinstance() với các class import từ claude_agent_sdk (ví dụ, isinstance(message, ResultMessage)).
  • TypeScript: kiểm tra trường string type (ví dụ, message.type === "result"). AssistantMessageUserMessage bọc message API thô trong trường .message, nên content block nằm ở message.message.content, không phải message.content.

Ví dụ: kiểm tra type message và xử lý kết quả

import asyncio
from claude_agent_sdk import query, AssistantMessage, ResultMessage
async def main():
try:
async for message in query(prompt="Summarize this project"):
if isinstance(message, AssistantMessage):
print(f"Turn completed: {len(message.content)} content blocks")
if isinstance(message, ResultMessage):
if message.subtype == "success":
print(message.result)
else:
print(f"Stopped: {message.subtype}")
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, các nhánh subtype ở trên đã chạy rồi;
# lỗi kết nối hoặc process không yield result message nào cả.
print(f"Session ended with an error: {error}")
asyncio.run(main())
import { query } from "@anthropic-ai/claude-agent-sdk";
try {
for await (const message of query({ prompt: "Summarize this project" })) {
if (message.type === "assistant") {
console.log(`Turn completed: ${message.message.content.length} content blocks`);
}
if (message.type === "result") {
if (message.subtype === "success") {
console.log(message.result);
} else {
console.log(`Stopped: ${message.subtype}`);
}
}
}
} 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, các nhánh subtype ở trên đã chạy rồi;
// lỗi kết nối hoặc process không yield result message nào cả.
console.log(`Session ended with an error: ${error}`);
}

Tool cho agent của bạn khả năng hành động. Không có tool, Claude chỉ có thể phản hồi bằng text. Có tool, Claude có thể đọc file, chạy lệnh, tìm kiếm code, và tương tác với dịch vụ bên ngoài.

SDK bao gồm cùng bộ tool đang vận hành Claude Code:

NhómToolsChúng làm gì
File operationsRead, Edit, WriteĐọc, sửa, và tạo file
SearchGlob, GrepTìm file theo pattern, tìm nội dung bằng regex
ExecutionBashChạy shell command, script, thao tác git
WebWebSearch, WebFetchTìm kiếm trên web, fetch và parse trang
DiscoveryToolSearchTìm và load tool động khi cần, thay vì preload hết
OrchestrationAgent, Skill, AskUserQuestion, TaskCreate, TaskUpdateSinh subagent, gọi skill, hỏi người dùng, theo dõi task

Ngoài built-in tool, bạn có thể:

  • Kết nối dịch vụ bên ngoài với MCP servers (database, browser, API)
  • Định nghĩa tool tuỳ biến với custom tool handlers
  • Load skill của project qua setting sources cho workflow tái sử dụng

Claude quyết định gọi tool nào dựa trên tác vụ, nhưng bạn kiểm soát liệu các lời gọi đó có được phép thực thi hay không. Bạn có thể tự động phê duyệt tool cụ thể, chặn tool khác hoàn toàn, hoặc yêu cầu phê duyệt cho mọi thứ. Ba option phối hợp với nhau để quyết định điều gì chạy:

  • allowed_tools / allowedTools tự động phê duyệt các tool trong danh sách. Một agent chỉ-đọc với ["Read", "Glob", "Grep"] trong allowed tools list chạy các tool đó mà không cần hỏi. Tool không nằm trong danh sách vẫn khả dụng nhưng cần phê duyệt.
  • disallowed_tools / disallowedTools chặn tool trong danh sách, bất kể cấu hình khác.
  • permission_mode / permissionMode kiểm soát điều gì xảy ra với tool không nằm trong allow hay deny rule. Xem Permission mode để biết các mode khả dụng.

Bạn cũng có thể scope tool cụ thể với rule như "Bash(npm *)" để chỉ cho phép một số lệnh nhất định.

Khi một tool bị từ chối, Claude nhận được thông báo từ chối làm kết quả tool và thường thử cách tiếp cận khác hoặc báo rằng nó không thể tiếp tục.

Khi Claude yêu cầu nhiều lời gọi tool trong một turn, cả hai SDK có thể chạy chúng đồng thời hoặc tuần tự tuỳ tool. Tool chỉ-đọc (như Read, Glob, Grep, và MCP tool được đánh dấu read-only) có thể chạy đồng thời. Tool làm thay đổi trạng thái (như Edit, Write, và Bash) chạy tuần tự để tránh xung đột.

Custom tool mặc định chạy tuần tự. Để bật thực thi song song cho một custom tool, đặt readOnlyHint trong annotation của nó.

Bạn có thể giới hạn số turn vòng lặp thực hiện, chi phí bao nhiêu, Claude suy luận sâu tới đâu, và tool có cần phê duyệt trước khi chạy hay không. Tất cả đều là trường trên ClaudeAgentOptions (Python) / Options (TypeScript).

OptionNó kiểm soát gìMặc định
Max turns (max_turns / maxTurns)Số round trip có dùng tool tối đaKhông giới hạn
Max budget (max_budget_usd / maxBudgetUsd)Chi phí tối đa trước khi dừngKhông giới hạn

Khi chạm một trong hai giới hạn, SDK trả về ResultMessage với subtype lỗi tương ứng (error_max_turns hoặc error_max_budget_usd). Xem Xử lý kết quả để biết cách kiểm tra các subtype này.

Giới hạn budget bao gồm cả subagent: chi tiêu của chúng tính vào tổng. Khi đã chạm giới hạn, việc sinh subagent mới thất bại với Budget limit reached, và Claude Code dừng mọi subagent chạy nền còn lại.

Với streaming input, một message bạn gửi khi một turn vẫn đang chạy sẽ được xếp hàng chờ khi turn đó kết thúc do chạm giới hạn max-turns, và nó bắt đầu turn riêng với giới hạn max-turns riêng.

Option effort kiểm soát mức độ suy luận Claude áp dụng. Effort level thấp hơn dùng ít token hơn mỗi turn và giảm chi phí. Không phải model nào cũng hỗ trợ tham số effort.

LevelHành viPhù hợp cho
"low"Suy luận tối thiểu, phản hồi nhanhTra cứu file, liệt kê thư mục
"medium"Suy luận cân bằngSửa đổi thông thường, tác vụ tiêu chuẩn
"high"Phân tích kỹ lưỡngRefactor, debug
"xhigh"Suy luận sâu mở rộngTask coding và agentic; khuyến nghị trên Fable 5, Opus 4.7+, và Sonnet 5
"max"Suy luận sâu tối đaBài toán nhiều bước cần phân tích sâu

Nếu bạn không đặt effort, cả hai SDK để tham số này trống và giao cho hành vi mặc định của model.

Dùng effort thấp hơn cho agent làm tác vụ đơn giản, được scope rõ (như liệt kê file hay chạy một grep) để giảm chi phí và độ trễ. Đặt effort trong option top-level của query() cho cả session, hoặc theo từng subagent với trường effort trên AgentDefinition để override cấp session.

Option permission mode (permission_mode trong Python, permissionMode trong TypeScript) kiểm soát agent có hỏi phê duyệt trước khi dùng tool hay không:

ModeHành viUse case
"default"Tool không được cover bởi allow rule sẽ trigger callback canUseTool của bạn; không có callback nghĩa là từ chốiỨng dụng tương tác với callback phê duyệt tuỳ biến
"acceptEdits"Tự động phê duyệt sửa file và các lệnh filesystem thông thường (mkdir, touch, mv, cp, v.v.); các lệnh Bash khác theo rule mặc địnhBạn tin tưởng thay đổi của Claude và muốn lặp nhanh hơn, ví dụ khi prototype hoặc làm việc trong thư mục cô lập
"plan"Claude khám phá và lên kế hoạch mà không sửa file nguồn của bạn; sửa file không bao giờ được tự động phê duyệt và luôn hỏi qua callback canUseToolBạn muốn Claude đề xuất thay đổi mà không thực thi, ví dụ khi review code hoặc cần phê duyệt thay đổi trước khi thực hiện
"dontAsk"Không bao giờ hỏi. Tool được phê duyệt trước bởi permission rule sẽ chạy; mọi thứ khác bị từ chối. AskUserQuestion, connector tool được tổ chức của bạn đặt là ask, và MCP tool đánh dấu requiresUserInteraction bị từ chối ngay cả khi bạn đã allowBạn muốn một môi trường tool cố định, rõ ràng cho headless agent và thích từ chối cứng hơn là phụ thuộc ngầm vào canUseTool bị thiếu
"auto"Dùng một model classifier để phê duyệt hoặc từ chối permission promptAgent tự động vẫn muốn có guardrail an toàn cho việc dùng tool
"bypassPermissions"Chạy mọi tool được phép mà không hỏi, trừ tool khớp một ask rule tường minh, connector tool được tổ chức của bạn đặt là ask, và tool cần tương tác người dùng. Trong TypeScript SDK, cũng cần allowDangerouslySkipPermissions: true trong options. Không thể dùng khi chạy với quyền root trên Unix. Chỉ dùng trong môi trường cô lập mà hành động của agent không ảnh hưởng tới hệ thống bạn quan tâmCI, container, hoặc môi trường cô lập khác

Với ứng dụng tương tác, dùng "default" cùng callback phê duyệt tool để hiện prompt phê duyệt. Với agent tự động trên máy dev, "acceptEdits" tự động phê duyệt sửa file và lệnh filesystem thông thường trong khi vẫn gate các lệnh Bash khác sau allow rule. Dành "bypassPermissions" cho CI, container, hoặc môi trường cô lập khác.

Nếu bạn không đặt model, SDK dùng mặc định của Claude Code, tuỳ thuộc vào phương thức xác thực và gói đăng ký của bạn. Đặt nó tường minh (ví dụ, model="claude-sonnet-5") để ghim một model cụ thể hoặc dùng model nhỏ hơn cho agent nhanh, rẻ hơn.

Context window là tổng lượng thông tin khả dụng cho Claude trong một session. Nó không reset giữa các turn trong cùng một session. Mọi thứ đều tích luỹ: system prompt, định nghĩa tool, lịch sử hội thoại, input tool, và output tool. Nội dung không đổi qua các turn (system prompt, định nghĩa tool, CLAUDE.md) được tự động prompt cached, giúp giảm chi phí và độ trễ cho các prefix lặp lại.

Đây là cách mỗi thành phần ảnh hưởng tới context trong SDK:

NguồnKhi nào loadẢnh hưởng
System promptMọi requestChi phí cố định nhỏ, luôn hiện diện
File CLAUDE.mdKhi bắt đầu session, qua settingSourcesNội dung đầy đủ trong mỗi request (nhưng được prompt-cached, nên chỉ request đầu tiên trả chi phí đầy đủ)
Định nghĩa toolMọi request; MCP schema mặc định deferredSchema built-in tool load mỗi request. Tool search hoãn schema MCP tool theo mặc định, fallback về load trước trên Google Cloud’s Agent Platform, một ANTHROPIC_BASE_URL không phải first-party, hoặc một deployment Microsoft Foundry host trên Azure
Lịch sử hội thoạiTích luỹ theo turnTăng theo mỗi turn: prompt, phản hồi, input tool, output tool
Mô tả skillKhi bắt đầu session, qua setting sourcesTóm tắt ngắn; nội dung đầy đủ chỉ load khi được gọi

Output tool lớn tiêu tốn context đáng kể. Đọc một file lớn hay chạy lệnh với output dài dòng có thể dùng hàng nghìn token chỉ trong một turn. Context tích luỹ qua các turn, nên session dài với nhiều lời gọi tool xây dựng nhiều context hơn đáng kể so với session ngắn.

Khi context window gần chạm giới hạn, SDK tự động nén hội thoại: nó tóm tắt lịch sử cũ hơn để giải phóng không gian, giữ nguyên các trao đổi gần đây nhất và quyết định quan trọng. SDK phát ra một message với type: "system"subtype: "compact_boundary" trong stream khi điều này xảy ra.

Compaction thay thế message cũ bằng một bản tóm tắt, nên các chỉ dẫn cụ thể từ đầu hội thoại có thể không được giữ lại. Các quy tắc lâu dài nên nằm trong CLAUDE.md (load qua settingSources) thay vì trong prompt ban đầu, vì nội dung CLAUDE.md được đưa lại vào mỗi request.

Bạn có thể tuỳ biến hành vi compaction theo vài cách:

  • Chỉ dẫn tóm tắt trong CLAUDE.md: Bộ nén đọc CLAUDE.md của bạn như mọi context khác, nên bạn có thể thêm một phần nói cho nó biết cần giữ gì khi tóm tắt. Tiêu đề phần là tự do (không phải magic string); bộ nén khớp theo ý định.
  • Hook PreCompact: Chạy logic tuỳ biến trước khi compaction xảy ra, ví dụ để lưu trữ toàn bộ transcript. Hook nhận trường trigger (manual hoặc auto).
  • Compaction thủ công: Gửi /compact như một chuỗi prompt để trigger compaction theo yêu cầu. Lệnh gửi theo cách này là SDK input, không phải shortcut chỉ dành cho CLI.

Ví dụ: chỉ dẫn tóm tắt trong CLAUDE.md

Thêm một phần vào CLAUDE.md của project nói cho bộ nén biết cần giữ gì. Tên tiêu đề không đặc biệt; dùng bất kỳ nhãn rõ ràng nào.

# Summary instructions
When summarizing this conversation, always preserve:
- The current task objective and acceptance criteria
- File paths that have been read or modified
- Test results and error messages
- Decisions made and the reasoning behind them

Vài chiến lược cho agent chạy dài:

  • Dùng subagent cho subtask. Mỗi subagent bắt đầu với hội thoại mới hoàn toàn (không có lịch sử message trước, dù nó vẫn load system prompt riêng và context cấp project như CLAUDE.md). Nó không thấy các turn của parent, và chỉ phản hồi cuối cùng trả về parent như kết quả tool. Context của agent chính tăng theo bản tóm tắt đó, không phải toàn bộ transcript subtask.
  • Chọn lọc tool. Mỗi định nghĩa tool chiếm không gian context. Dùng trường tools trên AgentDefinition để scope subagent về tập tối thiểu chúng cần.
  • Theo dõi chi phí MCP server. Tool search MCP hoãn schema MCP tool theo mặc định và load theo yêu cầu. Khi tool search tắt hoặc đã fallback về load trước, mỗi MCP server thêm toàn bộ schema tool của nó vào mỗi request, nên vài server với nhiều tool có thể tiêu tốn context đáng kể trước khi agent làm bất kỳ việc gì.
  • Dùng effort thấp hơn cho tác vụ thường ngày. Đặt effort thành "low" cho agent chỉ cần đọc file hay liệt kê thư mục. Điều này giảm token usage và chi phí.

Mỗi tương tác với SDK tạo hoặc tiếp tục một session. Lấy session ID từ ResultMessage.session_id (có ở cả hai SDK) để resume sau này. TypeScript SDK cũng expose nó như một trường trực tiếp trên SystemMessage init; trong Python nó nằm lồng trong SystemMessage.data.

Khi bạn resume, toàn bộ context từ các turn trước được khôi phục: file đã đọc, phân tích đã thực hiện, và hành động đã làm. Bạn cũng có thể fork một session để rẽ nhánh sang cách tiếp cận khác mà không sửa bản gốc.

Xem Quản lý session để biết hướng dẫn đầy đủ về resume, continue, và fork. Để resume session xuyên các container stateless hay serverless host, truyền một session_store / sessionStore adapter để transcript được mirror sang backend riêng của bạn và bất kỳ host nào cũng resume được. Subprocess Claude Code vẫn ghi vào đĩa local trước; đặt CLAUDE_CONFIG_DIR trỏ tới một thư mục tạm trong options.env nếu bản copy local cần là ephemeral.

Khi vòng lặp kết thúc, ResultMessage cho bạn biết điều gì đã xảy ra và trả kết quả. Trường subtype (có ở cả hai SDK) là cách chính để kiểm tra trạng thái kết thúc.

Result subtypeĐiều gì đã xảy raCó trường result?
successClaude hoàn thành tác vụ bình thường
error_max_turnsChạm giới hạn maxTurns trước khi hoàn thànhKhông
error_max_budget_usdChạm giới hạn maxBudgetUsd trước khi hoàn thànhKhông
error_during_executionMột lỗi làm gián đoạn vòng lặp (ví dụ, lỗi API hoặc request bị huỷ)Không
error_max_structured_output_retriesKhông có structured output hợp lệ nào được tạo trong giới hạn retry cấu hình: mọi lần thử đều lỗi validation, hoặc model fallback rút lại output đã hoàn thành mà không có retry thành côngKhông

Trường result (output text cuối cùng) chỉ có ở variant success, nên luôn kiểm tra subtype trước khi đọc nó. Mọi result subtype đều mang total_cost_usd, usage, num_turns, và session_id để bạn theo dõi chi phí và resume ngay cả sau lỗi. Trong Python, total_cost_usdusage được đánh kiểu optional và có thể là None trên một số path lỗi, nên hãy guard trước khi format chúng.

Kết quả cũng bao gồm trường stop_reason (string | null trong TypeScript, str | None trong Python) cho biết vì sao model dừng tạo output ở turn cuối. Giá trị phổ biến gồm end_turn (model hoàn thành bình thường), max_tokens (chạm giới hạn token output), và refusal (model từ chối yêu cầu). Trên result subtype lỗi, stop_reason mang giá trị từ phản hồi assistant cuối cùng trước khi vòng lặp kết thúc. Để phát hiện refusal, kiểm tra stop_reason === "refusal" (TypeScript) hoặc stop_reason == "refusal" (Python).

Hooks là callback chạy tại các điểm cụ thể trong vòng lặp: trước khi tool chạy, sau khi nó trả về, khi agent hoàn thành, v.v. Một số hook thường dùng:

HookKhi nó chạyDùng phổ biến
PreToolUseTrước khi tool thực thiValidate input, chặn lệnh nguy hiểm
PostToolUseSau khi tool trả vềAudit output, trigger side effect
UserPromptSubmitKhi một prompt được gửiChèn thêm context vào prompt
StopKhi agent hoàn thànhValidate kết quả, lưu trạng thái session
SubagentStart / SubagentStopKhi một subagent sinh ra hoặc hoàn tấtTheo dõi và tổng hợp kết quả task song song
PreCompactTrước khi context compactionLưu trữ toàn bộ transcript trước khi tóm tắt

Hooks chạy trong process ứng dụng của bạn, không phải bên trong context window của agent, nên chúng không tiêu tốn context. Hooks cũng có thể short-circuit vòng lặp: một hook PreToolUse từ chối một lời gọi tool ngăn nó thực thi, và Claude nhận được thông báo từ chối thay vào đó.

Cả hai SDK hỗ trợ mọi sự kiện ở trên. TypeScript SDK bao gồm thêm các sự kiện mà Python chưa hỗ trợ.

Ví dụ này kết hợp các khái niệm chính từ trang này thành một agent duy nhất sửa test thất bại. Nó cấu hình agent với allowed tools (tự động phê duyệt để agent chạy tự động), project settings, và giới hạn an toàn về turn và effort suy luận. Khi vòng lặp chạy, nó lưu session ID để có thể resume sau, xử lý kết quả cuối cùng, và in tổng chi phí.

Vì một lời gọi query() single-shot raise lỗi sau khi yield result lỗi, vòng lặp được bọc trong try block để script thoát gọn gàng khi chạm giới hạn.

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def run_agent():
session_id = None
try:
async for message in query(
prompt="Find and fix the bug causing test failures in the auth module",
options=ClaudeAgentOptions(
allowed_tools=[
"Read",
"Edit",
"Bash",
"Glob",
"Grep",
], # Liệt kê tool ở đây sẽ tự động phê duyệt (không hỏi)
setting_sources=[
"project"
], # Load CLAUDE.md, skill, hook từ thư mục hiện tại
max_turns=30, # Ngăn session chạy vô tận
effort="high", # Suy luận kỹ lưỡng cho debug phức tạp
),
):
# Xử lý kết quả cuối cùng
if isinstance(message, ResultMessage):
session_id = message.session_id # Lưu để có thể resume sau
if message.subtype == "success":
print(f"Done: {message.result}")
elif message.subtype == "error_max_turns":
# Agent hết turn. Resume với giới hạn cao hơn.
print(f"Hit turn limit. Resume session {session_id} to continue.")
elif message.subtype == "error_max_budget_usd":
print("Hit budget limit.")
else:
print(f"Stopped: {message.subtype}")
if message.total_cost_usd is not None:
print(f"Cost: ${message.total_cost_usd:.4f}")
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, các nhánh subtype ở trên đã chạy rồi;
# lỗi kết nối hoặc process không yield result message nào cả.
print(f"Session ended with an error: {error}")
asyncio.run(run_agent())
import { query } from "@anthropic-ai/claude-agent-sdk";
let sessionId: string | undefined;
try {
for await (const message of query({
prompt: "Find and fix the bug causing test failures in the auth module",
options: {
allowedTools: ["Read", "Edit", "Bash", "Glob", "Grep"], // Liệt kê tool ở đây sẽ tự động phê duyệt (không hỏi)
settingSources: ["project"], // Load CLAUDE.md, skill, hook từ thư mục hiện tại
maxTurns: 30, // Ngăn session chạy vô tận
effort: "high" // Suy luận kỹ lưỡng cho debug phức tạp
}
})) {
// Lưu session ID để resume sau nếu cần
if (message.type === "system" && message.subtype === "init") {
sessionId = message.session_id;
}
// Xử lý kết quả cuối cùng
if (message.type === "result") {
if (message.subtype === "success") {
console.log(`Done: ${message.result}`);
} else if (message.subtype === "error_max_turns") {
// Agent hết turn. Resume với giới hạn cao hơn.
console.log(`Hit turn limit. Resume session ${sessionId} to continue.`);
} else if (message.subtype === "error_max_budget_usd") {
console.log("Hit budget limit.");
} else {
console.log(`Stopped: ${message.subtype}`);
}
console.log(`Cost: $${message.total_cost_usd.toFixed(4)}`);
}
}
} 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, các nhánh subtype ở trên đã chạy rồi;
// lỗi kết nối hoặc process không yield result message nào cả.
console.log(`Session ended with an error: ${error}`);
}

Khi agent hoàn thành thành công, ví dụ in ra một dòng Done: với bản tóm tắt sửa lỗi của agent, sau đó là một dòng như Cost: $0.0312.

Giờ bạn đã hiểu vòng lặp, đây là nơi để đi tiếp tuỳ vào bạn đang xây gì:

  • Chưa chạy agent nào? Bắt đầu với quickstart để cài SDK và xem một ví dụ đầy đủ chạy từ đầu đến cuối.
  • Sẵn sàng tích hợp vào project? Load CLAUDE.md, skill, và filesystem hook để agent tự động theo quy ước project của bạn - xem Dùng tính năng Claude Code trong SDK.
  • Xây UI tương tác? Bật streaming để hiện text và lời gọi tool trực tiếp khi vòng lặp chạy.
  • Cần kiểm soát chặt chẽ hơn agent được làm gì? Khoá quyền truy cập tool bằng permissions, và dùng hooks để audit, block, hoặc transform lời gọi tool trước khi thực thi.
  • Chạy tác vụ dài hoặc tốn kém? Đẩy công việc độc lập sang subagent để giữ context chính gọn nhẹ.
  • Triển khai như một dịch vụ? Xem hướng dẫn hosting Agent SDK cho container và serverless, và Session storage để lưu session vào backend riêng.