Điểm chính
Vụ rò rỉ mã nguồn Claude Code đã tiết lộ một codebase TypeScript gồm 512.000 dòng vào ngày 31 tháng 3 năm 2026. Kiến trúc cốt lõi là một vòng lặp `while` gọi API Claude, phân phối các lệnh gọi công cụ và trả về kết quả. Bạn có thể tự xây dựng phiên bản của riêng mình với Python, Anthropic SDK và khoảng 200 dòng mã cho vòng lặp chính. Hướng dẫn này sẽ phân tích từng thành phần và chỉ cho bạn cách tái tạo chúng.
Giới thiệu
Vào ngày 31 tháng 3 năm 2026, Anthropic đã phát hành một tệp source map 59,8 MB bên trong phiên bản 2.1.88 của gói npm @anthropic-ai/claude-code của họ. Source map là các tạo phẩm gỡ lỗi giúp đảo ngược mã JavaScript đã được rút gọn về mã nguồn gốc. Vì công cụ xây dựng của Anthropic (Bun's bundler) tạo ra các tệp này theo mặc định, toàn bộ codebase TypeScript đã có thể được phục hồi.
Trong vòng vài giờ, các nhà phát triển đã sao chép mã nguồn trên hàng tá kho lưu trữ GitHub. Cộng đồng nhanh chóng mổ xẻ mọi module, từ vòng lặp agent chính đến các tính năng ẩn như "chế độ bí mật" và tiêm công cụ giả.
Phản ứng chia làm nhiều luồng. Một số người chỉ trích các thực hành bảo mật của Anthropic. Những người khác bị cuốn hút bởi kiến trúc. Nhưng phản ứng hiệu quả nhất đến từ các nhà phát triển đã hỏi: "Tôi có thể tự xây dựng cái này không?"
Câu trả lời là có. Các mẫu cốt lõi rất đơn giản. Hướng dẫn này sẽ đi sâu vào từng lớp kiến trúc, giải thích lý do tại sao Anthropic đưa ra các lựa chọn đó, và cung cấp mã hoạt động bạn có thể sử dụng làm điểm khởi đầu. Bạn cũng sẽ học cách kiểm tra các tương tác API của agent tùy chỉnh của mình với Apidog, điều này giúp gỡ lỗi các cuộc hội thoại API nhiều lượt dễ dàng hơn nhiều so với các lệnh curl thô.
Tải xuống ứng dụng
Những gì vụ rò rỉ tiết lộ về kiến trúc của Claude Code
Tổng quan về codebase
Claude Code, có tên mã nội bộ là “Tengu,” bao gồm khoảng 1.900 tệp. Tổ chức module được chia thành các lớp rõ ràng:
cli/ - Giao diện người dùng Terminal (React + Ink)
tools/ - Hơn 40 triển khai công cụ
core/ - Lời nhắc hệ thống, quyền, hằng số
assistant/ - Điều phối Agent
services/ - Các lệnh gọi API, nén, OAuth, đo từ xa
CLI tự nó là một ứng dụng React được hiển thị thông qua Ink, một trình kết xuất React cho đầu ra terminal. Nó sử dụng Yoga (một công cụ flexbox CSS) để bố cục và các mã thoát ANSI để tạo kiểu. Mọi chế độ xem cuộc hội thoại, khu vực nhập liệu, hiển thị lệnh gọi công cụ và hộp thoại cấp phép đều là một thành phần React.
Điều này là quá phức tạp đối với hầu hết các dự án tự làm (DIY). Bạn không cần giao diện người dùng terminal dựa trên React để xây dựng một agent lập trình hoạt động. Một vòng lặp REPL đơn giản là đủ.
Vòng lặp agent chính
Bỏ qua giao diện người dùng, đo từ xa và các cờ tính năng, thì cốt lõi của Claude Code là một vòng lặp `while`. Anthropic nội bộ gọi nó là "nO". Đây là những gì nó làm:
- Gửi tin nhắn đến API Claude (lời nhắc hệ thống + định nghĩa công cụ)
- Nhận một phản hồi chứa văn bản và/hoặc các khối
tool_use - Thực thi từng công cụ được yêu cầu thông qua một bản đồ phân phối từ tên đến hàm xử lý
- Thêm kết quả công cụ trở lại danh sách tin nhắn
- Nếu phản hồi chứa thêm các lệnh gọi công cụ, quay lại bước 1
- Nếu phản hồi là văn bản thuần túy không có lệnh gọi công cụ, trả lại cho người dùng
Một "lượt" là một chu trình hoàn chỉnh. Các lượt tiếp tục cho đến khi Claude tạo ra văn bản không có lệnh gọi công cụ. Đó là toàn bộ mẫu agent.
Dưới đây là một phiên bản Python tối thiểu nắm bắt được cốt lõi:
import anthropic
client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-6"
def agent_loop(system_prompt: str, tools: list, messages: list) -> str:
"""Vòng lặp agent chính - tiếp tục gọi cho đến khi không còn sử dụng công cụ nào nữa."""
while True:
response = client.messages.create(
model=MODEL,
max_tokens=16384,
system=system_prompt,
tools=tools,
messages=messages,
)
# Thêm phản hồi của trợ lý vào cuộc hội thoại
messages.append({"role": "assistant", "content": response.content})
# Nếu mô hình dừng mà không yêu cầu công cụ, chúng ta đã hoàn thành
if response.stop_reason != "tool_use":
# Trích xuất văn bản cuối cùng
return "".join(
block.text for block in response.content
if hasattr(block, "text")
)
# Thực thi từng lệnh gọi công cụ và thu thập kết quả
tool_results = []
for block in response.content:
if block.type == "tool_use":
result = execute_tool(block.name, block.input)
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result,
})
# Trả lại kết quả dưới dạng tin nhắn người dùng
messages.append({"role": "user", "content": tool_results})
Đó là khoảng 30 dòng. Phần còn lại của sự phức tạp của Claude Code đến từ chính các công cụ, hệ thống quyền, quản lý ngữ cảnh và bộ nhớ.
Xây dựng hệ thống công cụ
Tại sao các công cụ chuyên dụng tốt hơn một lệnh bash đơn lẻ
Một trong những quyết định kiến trúc rõ ràng nhất trong vụ rò rỉ: Claude Code sử dụng các công cụ chuyên dụng cho các thao tác tệp thay vì định tuyến mọi thứ thông qua bash.
Có một công cụ Read (không phải cat), một công cụ Edit (không phải sed), một công cụ Grep (không phải grep), và một công cụ Glob (không phải find). Lời nhắc hệ thống rõ ràng yêu cầu mô hình ưu tiên các công cụ này hơn các công cụ bash tương đương.
Tại sao? Ba lý do:
- Đầu ra có cấu trúc. Một công cụ
Grepchuyên dụng trả về kết quả ở định dạng nhất quán mà mô hình có thể phân tích cú pháp. Việc nối các lệnh bash tạo ra đầu ra không thể đoán trước mà mô hình khó diễn giải. - An toàn.
BashTooltrong Claude Code chặn dấu backtick và cú pháp subshell$()để ngăn chặn việc tiêm mã. Các công cụ chuyên dụng hoàn toàn tránh được rủi ro này. - Hiệu quả về token. Kết quả công cụ có thể được cắt ngắn, lấy mẫu hoặc chuyển sang đĩa. Đầu ra
catlớn làm lãng phí các token trong cửa sổ ngữ cảnh.
Bộ công cụ thiết yếu
Từ vụ rò rỉ, Claude Code mặc định chỉ lộ ra dưới 20 công cụ, với hơn 60 công cụ đằng sau các cờ tính năng. Đối với một agent tự làm (DIY), bạn cần năm công cụ:
TOOLS = [
{
"name": "read_file",
"description": "Đọc một tệp từ hệ thống tệp. Trả về nội dung kèm số dòng.",
"input_schema": {
"type": "object",
"properties": {
"file_path": {
"type": "string",
"description": "Đường dẫn tuyệt đối đến tệp"
},
"offset": {
"type": "integer",
"description": "Số dòng bắt đầu đọc từ (chỉ mục 0)"
},
"limit": {
"type": "integer",
"description": "Số dòng tối đa để đọc. Mặc định là 2000."
}
},
"required": ["file_path"]
}
},
{
"name": "write_file",
"description": "Ghi nội dung vào một tệp. Tạo tệp nếu nó không tồn tại.",
"input_schema": {
"type": "object",
"properties": {
"file_path": {"type": "string", "description": "Đường dẫn tuyệt đối"},
"content": {"type": "string", "description": "Nội dung tệp để ghi"}
},
"required": ["file_path", "content"]
}
},
{
"name": "edit_file",
"description": "Thay thế một chuỗi cụ thể trong một tệp. old_string phải là duy nhất.",
"input_schema": {
"type": "object",
"properties": {
"file_path": {"type": "string", "description": "Đường dẫn tuyệt đối"},
"old_string": {"type": "string", "description": "Văn bản cần tìm"},
"new_string": {"type": "string", "description": "Văn bản thay thế"}
},
"required": ["file_path", "old_string", "new_string"]
}
},
{
"name": "run_command",
"description": "Thực thi một lệnh shell và trả về stdout/stderr.",
"input_schema": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "Lệnh Shell để chạy"},
"timeout": {"type": "integer", "description": "Thời gian chờ (giây). Mặc định 120."}
},
"required": ["command"]
}
},
{
"name": "search_code",
"description": "Tìm kiếm một mẫu regex trong các tệp trong một thư mục.",
"input_schema": {
"type": "object",
"properties": {
"pattern": {"type": "string", "description": "Mẫu Regex"},
"path": {"type": "string", "description": "Thư mục để tìm kiếm"},
"file_glob": {"type": "string", "description": "Bộ lọc mẫu tệp, ví dụ: '*.py'"}
},
"required": ["pattern"]
}
}
]
Phân phối trình xử lý công cụ
Hàm thực thi công cụ ánh xạ tên công cụ đến các hàm xử lý:
import subprocess
import os
import re
def execute_tool(name: str, params: dict) -> str:
"""Phân phối các lệnh gọi công cụ đến các trình xử lý của chúng."""
handlers = {
"read_file": handle_read_file,
"write_file": handle_write_file,
"edit_file": handle_edit_file,
"run_command": handle_run_command,
"search_code": handle_search_code,
}
handler = handlers.get(name)
if not handler:
return f"Lỗi: Công cụ không xác định '{name}'"
try:
return handler(params)
except Exception as e:
return f"Lỗi: {str(e)}"
def handle_read_file(params: dict) -> str:
path = params["file_path"]
offset = params.get("offset", 0)
limit = params.get("limit", 2000)
with open(path, "r") as f:
lines = f.readlines()
selected = lines[offset:offset + limit]
numbered = [f"{i + offset + 1}\t{line}" for i, line in enumerate(selected)]
return "".join(numbered)
def handle_write_file(params: dict) -> str:
path = params["file_path"]
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w") as f:
f.write(params["content"])
return f"Đã ghi thành công vào {path}"
def handle_edit_file(params: dict) -> str:
path = params["file_path"]
with open(path, "r") as f:
content = f.read()
old = params["old_string"]
if content.count(old) == 0:
return f"Lỗi: '{old[:50]}...' không tìm thấy trong {path}"
if content.count(old) > 1:
return f"Lỗi: '{old[:50]}...' khớp với {content.count(old)} vị trí. Hãy cụ thể hơn."
new_content = content.replace(old, params["new_string"], 1)
with open(path, "w") as f:
f.write(new_content)
return f"Đã chỉnh sửa thành công {path}"
def handle_run_command(params: dict) -> str:
cmd = params["command"]
timeout = params.get("timeout", 120)
# An toàn cơ bản: chặn các mẫu nguy hiểm
blocked = ["rm -rf /", "mkfs", "> /dev/"]
for pattern in blocked:
if pattern in cmd:
return f"Lỗi: Đã chặn mẫu lệnh nguy hiểm: {pattern}"
result = subprocess.run(
cmd, shell=True, capture_output=True, text=True,
timeout=timeout, cwd=os.getcwd()
)
output = ""
if result.stdout:
output += result.stdout
if result.stderr:
output += f"\nSTDERR:\n{result.stderr}"
if not output.strip():
output = f"Lệnh đã hoàn thành với mã thoát {result.returncode}"
# Cắt ngắn đầu ra lớn để tiết kiệm token ngữ cảnh
if len(output) > 30000:
output = output[:15000] + "\n\n... [đã cắt ngắn] ...\n\n" + output[-15000:]
return output
def handle_search_code(params: dict) -> str:
pattern = params["pattern"]
path = params.get("path", os.getcwd())
file_glob = params.get("file_glob", "")
cmd = ["grep", "-rn", "--include", file_glob, pattern, path] if file_glob else \
["grep", "-rn", pattern, path]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
if not result.stdout.strip():
return f"Không tìm thấy kết quả khớp cho mẫu: {pattern}"
lines = result.stdout.strip().split("\n")
if len(lines) > 50:
return "\n".join(lines[:50]) + f"\n\n... ({len(lines) - 50} kết quả khớp khác)"
return result.stdout
Quản lý ngữ cảnh: vấn đề khó khăn
Tại sao ngữ cảnh quan trọng hơn kỹ thuật prompt
Mã nguồn bị rò rỉ tiết lộ Claude Code dành nhiều nỗ lực kỹ thuật hơn cho việc quản lý ngữ cảnh hơn là cho chính lời nhắc hệ thống. Trình nén ngữ cảnh (nội bộ gọi là “wU2”) có năm chiến lược.
Đối với một bản dựng tự làm (DIY), bạn cần hai:
Tự động nén kích hoạt khi cuộc hội thoại gần đạt đến giới hạn cửa sổ ngữ cảnh. Claude Code kích hoạt ở mức sử dụng khoảng 92%, dành một vùng đệm 13.000 token cho chính phần tóm tắt.
Tái chèn CLAUDE.md đảm bảo các nguyên tắc dự án không bị lệch trong các phiên làm việc dài. Claude Code tái chèn cấu hình dự án ở mỗi lượt, chứ không phải khi khởi tạo. Đây là mẫu có tác động lớn nhất để giữ cho một agent lập trình đi đúng hướng.
Xây dựng một trình nén đơn giản
def maybe_compact(messages: list, system_prompt: str, max_tokens: int = 180000) -> list:
"""Nén cuộc hội thoại khi nó trở nên quá dài."""
# Ước tính sơ bộ: 4 ký tự mỗi token
total_chars = sum(
len(str(m.get("content", ""))) for m in messages
)
estimated_tokens = total_chars // 4
if estimated_tokens < max_tokens * 0.85:
return messages # Chưa đạt giới hạn
# Yêu cầu mô hình tóm tắt cuộc hội thoại cho đến nay
summary_response = client.messages.create(
model=MODEL,
max_tokens=4096,
system="Summarize this conversation. Keep all file paths, decisions made, errors encountered, and current task state. Be specific about what was changed and why.",
messages=messages,
)
summary_text = summary_response.content[0].text
# Thay thế cuộc hội thoại bằng tóm tắt + tin nhắn gần đây
compacted = [
{"role": "user", "content": f"[Tóm tắt cuộc hội thoại]\n{summary_text}"},
{"role": "assistant", "content": "Tôi có ngữ cảnh từ cuộc hội thoại trước của chúng ta. Tôi nên làm gì tiếp theo?"},
]
# Giữ lại 4 tin nhắn cuối cùng để có ngữ cảnh tức thời
compacted.extend(messages[-4:])
return compacted
Tái chèn ngữ cảnh dự án
Claude Code đọc tệp .claude/CLAUDE.md và chèn nó vào mỗi lượt. Dưới đây là cách tái tạo nó:
def build_system_prompt(project_dir: str) -> str:
"""Xây dựng lời nhắc hệ thống với việc tái chèn ngữ cảnh dự án."""
base_prompt = """Bạn là một trợ lý lập trình giúp đỡ trong các nhiệm vụ kỹ thuật phần mềm.
Bạn có quyền truy cập vào các công cụ để đọc, ghi, chỉnh sửa tệp, chạy lệnh và tìm kiếm mã.
Luôn đọc tệp trước khi sửa đổi chúng. Ưu tiên edit_file hơn write_file cho các tệp hiện có.
Giữ phản hồi ngắn gọn. Tập trung vào mã, không phải giải thích."""
# Tìm kiếm các nguyên tắc dự án
claude_md_path = os.path.join(project_dir, ".claude", "CLAUDE.md")
if os.path.exists(claude_md_path):
with open(claude_md_path, "r") as f:
project_context = f.read()
base_prompt += f"\n\n# Nguyên tắc dự án\n{project_context}"
# Cũng kiểm tra tệp CLAUDE.md gốc
root_md = os.path.join(project_dir, "CLAUDE.md")
if os.path.exists(root_md):
with open(root_md, "r") as f:
root_context = f.read()
base_prompt += f"\n\n# Nguyên tắc kho lưu trữ\n{root_context}"
return base_prompt
Hệ thống bộ nhớ ba lớp
Mã nguồn bị rò rỉ cho thấy Claude Code sử dụng kiến trúc bộ nhớ ba tầng. Đây là một trong những phần bị đánh giá thấp nhất của hệ thống.
Lớp 1: MEMORY.md (luôn được tải)
Một chỉ mục nhẹ luôn nằm trong lời nhắc hệ thống. Mỗi mục là một dòng, dưới 150 ký tự. Hoạt động như một mục lục chỉ đến kiến thức sâu hơn. Giới hạn ở 200 dòng / 25KB.
- [Tùy chọn người dùng](memory/user-prefs.md) - ưu tiên TypeScript, sử dụng phím tắt Vim
- [Quy ước API](memory/api-conventions.md) - REST với thông số kỹ thuật JSON:API, snake_case
- [Quy trình triển khai](memory/deploy.md) - sử dụng GitHub Actions, triển khai tới AWS EKS
Lớp 2: các tệp chủ đề (tải theo yêu cầu)
Các tệp kiến thức chi tiết được tải khi chỉ mục gợi ý mức độ liên quan. Chúng chứa các quy ước dự án, quyết định kiến trúc và các mẫu đã học.
Lớp 3: bản ghi phiên (chỉ tìm kiếm, không đọc)
Nhật ký phiên đầy đủ không bao giờ được tải toàn bộ. Agent tìm kiếm chúng bằng các định danh cụ thể. Điều này ngăn chặn việc phình to ngữ cảnh trong khi vẫn duy trì khả năng tìm kiếm.
Xây dựng một hệ thống bộ nhớ tối thiểu
import json
MEMORY_DIR = ".agent/memory"
def load_memory_index() -> str:
"""Tải chỉ mục bộ nhớ để chèn vào lời nhắc hệ thống."""
index_path = os.path.join(MEMORY_DIR, "MEMORY.md")
if os.path.exists(index_path):
with open(index_path, "r") as f:
return f.read()
return ""
def save_memory(key: str, content: str, description: str):
"""Lưu một mục bộ nhớ và cập nhật chỉ mục."""
os.makedirs(MEMORY_DIR, exist_ok=True)
# Ghi tệp bộ nhớ
filename = f"{key.replace(' ', '-').lower()}.md"
filepath = os.path.join(MEMORY_DIR, filename)
with open(filepath, "w") as f:
f.write(f"---\nname: {key}\ndescription: {description}\n---\n\n{content}")
# Cập nhật chỉ mục
index_path = os.path.join(MEMORY_DIR, "MEMORY.md")
index_lines = []
if os.path.exists(index_path):
with open(index_path, "r") as f:
index_lines = f.readlines()
# Thêm hoặc cập nhật mục
new_entry = f"- [{key}]({filename}) - {description}\n"
updated = False
for i, line in enumerate(index_lines):
if filename in line:
index_lines[i] = new_entry
updated = True
break
if not updated:
index_lines.append(new_entry)
with open(index_path, "w") as f:
f.writelines(index_lines)
Thêm một công cụ save_memory vào danh sách công cụ của bạn để agent có thể lưu giữ kiến thức giữa các phiên làm việc.
Thêm hệ thống cấp phép
Vụ rò rỉ tiết lộ năm chế độ cấp phép: default (lời nhắc tương tác), auto (phê duyệt dựa trên ML), bypass, yolo (phê duyệt mọi thứ), và deny. Mọi hành động của công cụ đều được phân loại là rủi ro THẤP, TRUNG BÌNH hoặc CAO.
Đối với một agent tự làm (DIY), một hệ thống ba tầng đơn giản hoạt động:
# Mức độ rủi ro cho các hoạt động
RISK_LEVELS = {
"read_file": "low",
"search_code": "low",
"edit_file": "medium",
"write_file": "medium",
"run_command": "high",
}
def check_permission(tool_name: str, params: dict, auto_approve_low: bool = True) -> bool:
"""Kiểm tra xem người dùng có phê duyệt lệnh gọi công cụ này không."""
risk = RISK_LEVELS.get(tool_name, "high")
if risk == "low" and auto_approve_low:
return True
# Hiển thị cho người dùng những gì sắp xảy ra
print(f"\n--- Kiểm tra quyền (rủi ro {risk.upper()}) ---")
print(f"Công cụ: {tool_name}")
for key, value in params.items():
display = str(value)[:200]
print(f" {key}: {display}")
response = input("Cho phép? [y/n/luôn luôn]: ").strip().lower()
if response == "always":
RISK_LEVELS[tool_name] = "low" # Tự động phê duyệt công cụ này trong tương lai
return True
return response == "y"
Kiểm tra các lệnh gọi API của agent của bạn với Apidog
Xây dựng một agent lập trình có nghĩa là thực hiện hàng trăm lệnh gọi API đến Claude. Gỡ lỗi các tương tác này, đặc biệt là các cuộc hội thoại nhiều lượt với việc sử dụng công cụ, rất khó khăn với nhật ký thô.

Apidog giúp bạn kiểm tra và thử nghiệm chính xác các yêu cầu API mà agent của bạn gửi. Dưới đây là cách sử dụng nó trong quá trình phát triển:
Chụp và phát lại các yêu cầu API
Thiết lập Apidog làm proxy để chặn các lệnh gọi của agent của bạn đến API Anthropic:
- Mở Apidog và tạo một dự án mới cho agent của bạn
- Nhập điểm cuối API tin nhắn Anthropic:
POST https://api.anthropic.com/v1/messages - Thiết lập phần thân yêu cầu với lời nhắc hệ thống, mảng công cụ và tin nhắn của bạn
- Kiểm tra từng lượt bằng cách phát lại các yêu cầu đã chụp với các tham số đã sửa đổi
Điều này cho phép bạn cô lập các lượt sử dụng công cụ cụ thể mà không cần chạy toàn bộ vòng lặp agent. Khi mô hình trả về một lệnh gọi công cụ không mong muốn hoặc tham số bị ảo giác, bạn có thể sửa đổi phần thân yêu cầu trong trình chỉnh sửa trực quan của Apidog và gửi lại để xem các đầu vào khác nhau thay đổi phản hồi như thế nào.
Gỡ lỗi các cuộc hội thoại nhiều lượt
Phần khó nhất của việc gỡ lỗi agent là tái tạo trạng thái cuộc hội thoại. Các biến môi trường của Apidog cho phép bạn lưu ảnh chụp nhanh cuộc hội thoại:
- Lưu toàn bộ mảng
messageslàm biến môi trường sau mỗi lượt - Phát lại từ bất kỳ điểm nào trong cuộc hội thoại
- So sánh kết quả công cụ giữa các lần chạy để tìm nơi hành vi khác nhau
Xác thực schema công cụ
Các định nghĩa công cụ của bạn (các schema JSON bạn truyền đến API) xác định những gì mô hình có thể yêu cầu. Các schema bị định dạng sai gây ra lỗi ẩn khiến mô hình bỏ qua một công cụ hoặc truyền các tham số sai.
Nhập các schema công cụ của bạn vào Apidog và sử dụng trình xác thực JSON Schema của nó để phát hiện các vấn đề trước khi chúng đến API. Tải xuống Apidog để bắt đầu gỡ lỗi các tương tác API của agent của bạn.
Tải xuống ứng dụng
Tổng hợp tất cả: REPL hoàn chỉnh
Dưới đây là agent hoàn chỉnh được kết nối với nhau dưới dạng một REPL hoạt động:
#!/usr/bin/env python3
"""Một agent lập trình tối thiểu theo phong cách Claude Code."""
import anthropic
import os
import sys
client = anthropic.Anthropic()
MODEL = "claude-sonnet-4-6"
PROJECT_DIR = os.getcwd()
def main():
system_prompt = build_system_prompt(PROJECT_DIR)
memory = load_memory_index()
if memory:
system_prompt += f"\n\n# Bộ nhớ\n{memory}"
messages = []
print("Agent lập trình đã sẵn sàng. Gõ 'quit' để thoát.\n")
while True:
user_input = input("> ").strip()
if user_input.lower() in ("quit", "exit"):
break
if not user_input:
continue
messages.append({"role": "user", "content": user_input})
# Nén nếu cần
messages = maybe_compact(messages, system_prompt)
# Tái chèn ngữ cảnh dự án (Claude Code làm điều này ở mỗi lượt)
current_system = build_system_prompt(PROJECT_DIR)
memory = load_memory_index()
if memory:
current_system += f"\n\n# Bộ nhớ\n{memory}"
# Chạy vòng lặp agent
result = agent_loop(current_system, TOOLS, messages)
print(f"\n{result}\n")
if __name__ == "__main__":
main()
Điều này mang lại cho bạn một agent lập trình hoạt động chỉ với dưới 300 dòng Python. Nó đọc tệp, chỉnh sửa mã, chạy lệnh, tìm kiếm codebase, quản lý ngữ cảnh và lưu giữ bộ nhớ giữa các phiên.
Những gì cần thêm tiếp theo
Mã nguồn bị rò rỉ tiết lộ một số tính năng đáng xây dựng khi vòng lặp cốt lõi của bạn hoạt động:
Các agent phụ để làm việc song song
Claude Code tạo ra các agent phụ (gọi là agent “forked”) cho các tác vụ độc lập. Agent phụ nhận một bản sao ngữ cảnh của agent cha, thực thi tác vụ của nó và trả về kết quả. Điều này tránh làm ô nhiễm cuộc hội thoại chính bằng công việc thăm dò.
Mẫu: tạo một agent_loop() mới với mô tả tác vụ tập trung và một tập hợp con các công cụ. Trả về kết quả dưới dạng một chuỗi.
Khử trùng lặp đọc tệp
Claude Code theo dõi những tệp nào đã được đọc và thời gian sửa đổi của chúng. Nếu một tệp chưa thay đổi kể từ lần đọc cuối cùng, nó sẽ bỏ qua việc đọc và thông báo cho mô hình “tệp không thay đổi kể từ lần đọc cuối cùng.” Điều này tiết kiệm token khi đọc lại trong các phiên làm việc dài.
Cắt ngắn và lấy mẫu đầu ra
Khi một công cụ trả về một đầu ra lớn (ví dụ: hơn 10.000 dòng kết quả grep), Claude Code cắt ngắn nó và thông báo cho mô hình có bao nhiêu kết quả đã bị bỏ qua. Nếu không có điều này, một kết quả công cụ lớn có thể chiếm toàn bộ cửa sổ ngữ cảnh của bạn.
Tự động nén với tái chèn tệp
Trình nén bị rò rỉ không loại bỏ nội dung tệp. Sau khi tóm tắt cuộc hội thoại, nó tái chèn nội dung của các tệp được truy cập gần đây (tối đa 5.000 token mỗi tệp). Điều này có nghĩa là mô hình giữ được kiến thức làm việc về codebase ngay cả sau khi nén.
Những gì chúng ta học được từ vụ rò rỉ
Vụ rò rỉ mã nguồn Claude Code đã xác nhận một số mẫu mà cộng đồng agent AI đã đưa ra giả thuyết:
Vòng lặp cốt lõi rất đơn giản. Toàn bộ mẫu agent nằm gọn trong 30 dòng. Sự phức tạp nằm ở các công cụ và quản lý ngữ cảnh, chứ không phải ở kỹ thuật prompt.
Các công cụ chuyên dụng vượt trội hơn bash. Các công cụ có cấu trúc, được xây dựng có mục đích cung cấp cho mô hình mật độ thông tin tốt hơn trên mỗi token so với việc nối các lệnh bash.
Bộ nhớ cần nhiều lớp. Một chỉ mục luôn được tải, các tệp chủ đề theo yêu cầu và các bản ghi chỉ tìm kiếm (grep-only) giúp cân bằng khả năng gợi nhớ với chi phí ngữ cảnh.
Quản lý ngữ cảnh là sản phẩm thực sự. Tự động nén, tái chèn nguyên tắc dự án và cắt ngắn đầu ra là những gì giúp các phiên lập trình dài trở nên khả thi.
Phần khung (harness) là sản phẩm, không phải mô hình. Mô hình cung cấp trí thông minh. Phần khung cung cấp nhận thức (đọc tệp, tìm kiếm mã), hành động (ghi tệp, thực thi lệnh) và bộ nhớ. Xây dựng một agent lập trình là xây dựng phần khung đó.
Nếu bạn muốn kiểm tra và gỡ lỗi các tương tác API của agent tùy chỉnh của mình, bao gồm các cuộc hội thoại sử dụng công cụ nhiều lượt, các schema yêu cầu phức tạp và xác thực phản hồi, hãy dùng thử Apidog miễn phí. Nó xử lý việc gỡ lỗi API để bạn có thể tập trung vào logic của agent.
Câu hỏi thường gặp
Tôi có thể hợp pháp sử dụng các mẫu từ vụ rò rỉ mã nguồn Claude Code không?
Vụ rò rỉ đã tiết lộ các mẫu kiến trúc, chứ không phải các thuật toán độc quyền. Xây dựng một agent lập trình sử dụng vòng lặp `while` với phân phối công cụ là một mẫu chuẩn được ghi lại trong tài liệu API của Anthropic. Bạn không nên sao chép mã của Anthropic một cách nguyên văn, nhưng việc tái tạo kiến trúc bằng mã của riêng bạn là một thực hành tiêu chuẩn.
Tôi nên sử dụng mô hình nào cho một agent lập trình tự làm (DIY)?
Claude Sonnet 4.6 mang lại sự cân bằng phù hợp giữa tốc độ và khả năng cho các tác vụ lập trình. Claude Opus 4.6 tạo ra kết quả tốt hơn trong các quyết định kiến trúc phức tạp nhưng tốn kém hơn và chạy chậm hơn. Đối với các chỉnh sửa tệp và tìm kiếm đơn giản, Claude Haiku 4.5 hoạt động và tốn ít hơn 90%.
Chi phí để chạy agent lập trình của riêng bạn là bao nhiêu?
Một phiên lập trình điển hình (30-50 lượt) với Claude Sonnet 4.6 tốn 1-5$ phí API. Yếu tố chi phí chính là kích thước cửa sổ ngữ cảnh; nén mạnh mẽ giúp giảm chi phí. Mã nguồn bị rò rỉ của Claude Code cho thấy nó kích hoạt nén khi sử dụng 92% ngữ cảnh để kiểm soát điều này.
Tại sao Claude Code lại sử dụng React cho một ứng dụng terminal?
Ink (React cho terminal) cho phép nhóm tái sử dụng mô hình thành phần và quản lý trạng thái của React cho các tương tác UI phức tạp như hộp thoại cấp phép, đầu ra truyền trực tuyến và hiển thị lệnh gọi công cụ. Đối với một dự án tự làm (DIY), một REPL input() / print() đơn giản là đủ.
Tính năng quan trọng nhất cần xây dựng sau vòng lặp cốt lõi là gì?
Hệ thống cấp phép. Nếu không có nó, mô hình có thể ghi đè tệp và chạy các lệnh tùy ý mà không có sự giám sát của người dùng. Ngay cả một cổng "xác nhận trước khi ghi/thực thi" đơn giản cũng ngăn chặn hầu hết các thiệt hại do vô ý.
Claude Code xử lý lỗi từ các lệnh gọi công cụ như thế nào?
Lỗi công cụ được trả về dưới dạng nội dung văn bản trong tin nhắn tool_result. Mô hình thấy lỗi và quyết định xem có nên thử lại, thử một cách tiếp cận khác hay hỏi người dùng. Không có xử lý lỗi đặc biệt; lý luận của mô hình xử lý việc khôi phục.
Tôi có thể sử dụng cái này với các mô hình khác ngoài Claude không?
Có. Mẫu sử dụng công cụ hoạt động với bất kỳ mô hình nào hỗ trợ gọi hàm: GPT-4, Gemini, Llama và các mô hình khác. Bạn sẽ cần điều chỉnh định dạng lệnh gọi API, nhưng vòng lặp agent, công cụ và hệ thống bộ nhớ đều không phụ thuộc vào mô hình.
Làm cách nào để ngăn agent chạy các lệnh nguy hiểm?
Bắt đầu với một danh sách chặn các mẫu nguy hiểm (rm -rf /, mkfs, v.v.) và yêu cầu phê duyệt rõ ràng cho tất cả các lệnh gọi run_command. Claude Code phân loại mọi hoạt động là rủi ro THẤP, TRUNG BÌNH hoặc CAO và chặn hoặc nhắc nhở dựa trên phân loại đó. Xây dựng tương tự cho các công cụ của bạn.
