Kiến trúc NanoClaw: Nghệ thuật tối giản thay thế hàng trăm nghìn dòng mã

07 tháng 4, 2026·9 phút đọc

NanoClaw đã thay thế một khung trợ lý AI khổng lồ 500.000 dòng mã bằng một codebase nhỏ gọn chỉ vỏn vẹn 8.000 dòng TypeScript. Bài viết phân tích sáu quyết định kiến trúc độc đáo như mô hình "Phantom Token", sử dụng container để ủy quyền và cơ chế tin nhắn hai con trỏ, chứng minh rằng sự hiểu biết sâu sắc về các ràng buộc hệ thống giúp loại bỏ hoàn toàn sự phức tạp không cần thiết.

Kiến trúc NanoClaw: Nghệ thuật tối giản thay thế hàng trăm nghìn dòng mã

Hầu hết các cơ sở mã (codebase) đều phát triển theo hướng thêm thắt nhiều thứ: thêm các lớp trừu tượng, thêm phụ thuộc và thêm file cấu hình. NanoClaw lại làm điều ngược lại — nó thay thế một khung trợ lý AI nặng nề 500.000 dòng bằng khoảng 8.000 dòng TypeScript và chỉ sáu thư viện phụ thuộc cho môi trường sản xuất.

Điều này không phải là việc khoe khoang về số lượng dòng mã, mà là kết quả của sáu quyết định kiến trúc thực sự thú vị. Đây là những mẫu thiết kế mà tôi ước rằng nhiều nhóm phát triển đã áp dụng nhiều hơn trong những năm qua.

Sau khi phân tích toàn bộ codebase để hiểu cách thức hoạt động, dưới đây là những gì tôi tìm thấy — và lý do tại sao các mẫu này lại quan trọng vượt ra ngoài phạm vi một trợ lý AI cá nhân.

Proxy chứng thực (Credential Proxy)

Đây là điểm thực sự khiến tôi ấn tượng. NanoClaw chạy các tác nhân AI (AI agents) bên trong các container Linux. Những tác nhân này cần API keys để giao tiếp với Anthropic. Cách tiếp cận hiển nhiên là truyền key dưới dạng biến môi trường (environment variable). Đó là điều hầu hết chúng ta thường làm: nhét nó vào env var, có thể lấy từ trình quản lý bí mật (secrets manager) khi khởi động, và tiếp tục công việc.

Nhưng NanoClaw không làm thế. Thay vào đó, mỗi container nhận được một API key giả (placeholder) và một base URL trỏ đến một proxy localhost trên cổng 3001. Proxy này nằm trên máy chủ (host), chặn mọi yêu cầu API đi ra, loại bỏ key giả và tiêm thông tin xác thực thực trước khi chuyển tiếp lên trên.

Tác nhân hoàn toàn không thể làm rò rỉ key thực — vì nó chưa bao giờ sở hữu nó. Ngay cả khi cuộc tấn công tiêm prompt (prompt injection) đánh lừa tác nhân đổ biến môi trường ra ngoài, kẻ tấn công cũng chỉ nhận được ANTHROPIC_API_KEY=placeholder. Không có gì để đánh cắp cả.

Mô hình này hiện có một tên gọi chính xác — Phantom Token Pattern. Nó đang được áp dụng như một sidecar proxy trong Kubernetes cho các triển khai tác nhân AI trong môi trường sản xuất. NanoClaw đã đi đến thiết kế này một cách độc lập, điều đó cho thấy giải pháp này tự nhiên đến thế nào khi bạn định hình vấn đề một cách đúng đắn.

Proxy cũng xử lý hai chế độ xác thực (API key và OAuth) và liên kết khác nhau tùy theo nền tảng — 127.0.0.1 trên macOS/WSL2, địa chỉ IP cầu docker0 trên Linux. Các bí mật được tải vào một đối tượng thuần túy, không bao giờ vào process.env, để chúng không thể rò rỉ sang các tiến trình con.

Cách ly container làm phương tiện ủy quyền

Tại Vend và Xero, tôi đã dành nhiều thời gian để suy nghĩ về ủy quyền (authorization). Kiểm soát truy cập dựa trên vai trò (role-based access), kiểm tra quyền hạn, middleware quyết định người dùng được phép làm gì. Nó hoạt động, nhưng là nguồn liên tục phát sinh các lỗi — chỉ cần bỏ sót một lần kiểm tra là bạn sẽ có lỗ hổng leo thang đặc quyền.

NanoClaw tiếp cận vấn đề hoàn toàn khác. Thay vì kiểm tra tác nhân được phép làm gì, nó kiểm soát tác nhân có thể nhìn thấy gì.

Mỗi container nhận được một tập hợp các gắn kết (mounts) cụ thể. Một container không thuộc nhóm chính chỉ có thể nhìn thấy thư mục của chính nó — nó hoàn toàn không thể truy cập thư mục gốc dự án, dữ liệu của các nhóm khác hay file của máy chủ. Nhóm chính có quyền truy cập chỉ đọc (read-only) vào dự án, nhưng file .env được shadow-mounted (che khuất) đến /dev/null. Ngay cả khi toàn bộ dự án được gắn kết, các bí mật vẫn vô hình.

Tác nhân bên trong container chạy với quyền bypassPermissions — nó có thể dùng Bash, ghi file, làm bất cứ điều gì nó muốn. Nhưng "bất cứ điều gì nó muốn" bị giới hạn bởi những gì hệ điều hành cho phép nó nhìn thấy. Không cần kiểm tra quyền hạn ở cấp độ ứng dụng.

Các gắn kết bổ sung được xác thực dựa trên danh sách chặn (blocklist) như .ssh, .gnupg, .aws, credentials, v.v., nằm ngoài thư mục gốc dự án, khiến nó không thể bị can thiệp từ bên trong bất kỳ container nào. Phân giải đường dẫn symlink ngăn chặn các cuộc tấn công duyệt đường dẫn (path traversal) như tạo /tmp/innocent -> ~/.ssh.

Mô hình bảo mật ở đây chính là cấu trúc hệ thống tập tin (filesystem topology) của hệ điều hành.

Hệ thống hai con trỏ

Xử lý tin nhắn trong NanoClaw sử dụng hai con trỏ độc lập — và sự tương tác giữa chúng là phần tinh tế nhất của toàn bộ hệ thống.

Con trỏ 1 là một dấu mốc toàn cục (global watermark). Vòng lặp thăm dò chính đọc tin nhắn mới từ SQLite mỗi 2 giây. Khi thấy tin nhắn, nó đẩy dấu mốc lên ngay lập tức và lưu lại (persist). Điều này ngăn việc xử lý trùng lặp nếu vòng lặp chạy lại trước khi tác nhân hoàn thành.

Con trỏ 2 là riêng cho từng nhóm (per-group). Khi tin nhắn của một nhóm sắp được xử lý, con trỏ nhóm được đẩy lên một cách lạc quan (optimistically) trước khi tác nhân bắt đầu. Nếu tác nhân thất bại:

  • Nếu chưa có gì được gửi đến người dùng — quay lùi con trỏ, thử lại sau.
  • Nếu phản hồi đã được gửi đến người dùng — giữ nguyên con trỏ, vì quay lùi sẽ gây ra tin nhắn trùng lặp.

Cơ chế này đảm bảo giao hàng "tối đa một lần" (at-most-once) cho người dùng và xử lý "tối thiểu một lần" (at-least-once) cho tác nhân. Đó là sự đánh đổi đúng đắn cho một hệ thống nhắn tin — người dùng không bao giờ nên thấy tin nhắn trùng lặp, nhưng việc thử lại công việc của tác nhân thì không sao.

Tôi từng xây dựng các nền tảng thanh toán (billing) nơi chúng ta phải suy nghĩ chính xác về các loại bảo đảm giao hàng này. Làm sai trong bối cảnh thanh toán có nghĩa là tính phí khách hàng hai lần. Cách tiếp cận của NanoClaw rất gọn gàng — hai con trỏ, một quy tắc: một khi bạn đã nói chuyện với người dùng, bạn không thể rút lại.

IPC dựa trên tệp tin (File-based IPC)

Không Redis. Không RabbitMQ. Không gRPC. Giao tiếp giữa các tiến trình (IPC) của NanoClaw là các tệp tin JSON trên hệ thống tập tin.

Các container ghi tin nhắn đi ra vào /workspace/ipc/messages/. Máy chủ ghi tin nhắn tiếp theo vào /workspace/ipc/input/. Cả hai bên đều sử dụng cùng một mẫu: ghi vào tệp .tmp, sau đó đổi tên (rename) nó vào vị trí. Hoạt động rename là nguyên tử (atomic) trên các hệ thống tập tin POSIX. Tệp tin hoặc là tồn tại với nội dung đầy đủ hoặc là không tồn tại tại cả.

Ủy quyền được thực thi bằng danh tính thư mục. Mỗi nhóm có thư mục IPC riêng (/data/ipc/{group}/). Máy chủ biết nhóm nào đã viết tệp dựa trên thư mục mà tệp đó xuất hiện trong. Container không thể mạo danh danh tính vì cấu hình mount của nó cố định các thư mục mà nó có thể ghi vào.

Nhóm chính có thể gửi tin nhắn đến bất kỳ nhóm nào khác. Các nhóm không chính chỉ có thể gửi cho chính mình. Điều này không được thực thi bởi logic ứng dụng kiểm tra token — nó được thực thi bởi máy chủ đọc từ một thư mục mà chỉ có một container có thể ghi vào.

Đây là cùng một mẫu ghi nguyên tử được sử dụng trong các triển khai cơ sở dữ liệu an toàn trước sự cố (crash-safe). Ràng buộc chính — tệp tạm thời và tệp đích phải nằm trên cùng một hệ thống tập tin — được đảm bảo vì cả hai đều nằm trong cùng một mount của container.

Thăm dò thay vì sự kiện (Polling over events)

Vòng lặp chính thăm dò SQLite mỗi 2 giây. Bộ giám sát IPC thăm dò hệ thống tệp mỗi 1 giây. Trình lập lịch tác vụ thăm dò mỗi 60 giây.

Trong một hệ thống được thiết kế cho một người dùng, điều này loại bỏ toàn bộ các loại điều kiện tranh chấp (race conditions). Không cần quản lý kết nối WebSocket, không cần đảm bảo thứ tự sự kiện, không có "callback hell". Chỉ đơn giản là: kiểm tra cái mới, xử lý nó, ngủ, lặp lại.

Tôi từng làm việc trong các nhóm quá vội vàng chạy tới các kiến trúc hướng sự kiện (event-driven). Tại Xero, chúng ta triển khai nhiều lần một ngày — nhưng công cụ nội bộ hỗ trợ việc đó? Một nửa trong số đó đã đơn giản hơn nhiều với một vòng lặp thăm dò. Khi bạn biết trần quy mô (scale ceiling) của mình, thăm dò là một tính năng, không phải là giới hạn.

Biên dịch lại thay vì plugin

Mỗi container biên dịch lại TypeScript khi khởi động. Mã nguồn agent-runner được gắn kết từ một thư mục riêng cho từng nhóm, do đó mỗi nhóm có thể có hành vi tác nhân tùy chỉnh. Kết quả biên dịch đi vào /tmp/dist sau đó được chuyển sang chỉ đọc — tác nhân không thể sửa đổi runner của chính mình.

Không có sổ đăng ký plugin. Không có lớp trừu tượng. Không có tiêm phụ thuộc. Bạn muốn hành vi khác nhau cho mỗi nhóm? Đổi file nguồn. Container sẽ tự động nhận diện vào lần khởi động tiếp theo.

Điều này chậm hơn hệ thống plugin, chắc chắn rồi. Khởi động container phải trả thuế biên dịch. Nhưng nó loại bỏ một lớp gián tiếp hoàn toàn — và lớp đó thường là nơi sinh sống của các lỗi (bugs).

Điều gắn kết tất cả lại với nhau

Những lựa chọn này không ngẫu nhiên. Tất cả đều là biểu hiện của cùng một nguyên tắc: hiểu rõ các ràng buộc của bạn và sử dụng chúng để xóa bỏ sự phức tạp.

NanoClaw là hệ thống đơn người dùng. Nó biết điều này. Vì vậy nó dùng thăm dò thay vì đẩy, dùng hệ thống tệp thay vì hàng đợi tin nhắn, và cách ly bằng container thay vì viết middleware ủy quyền.

Câu hỏi thú vị không phải là các mẫu này có hoạt động cho NanoClaw hay không — chúng rõ ràng là hoạt động. Câu hỏi là: ở những đâu trong hệ thống của bạn tồn tại những ràng buộc tương tự? Bởi vì chúng phổ biến hơn bạn nghĩ.

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗