BTC
$96,000
5.73%
ETH
$3,521.91
3.97%
HTX
$0.{5}2273
5.23%
SOL
$198.17
3.05%
BNB
$710
3.05%
lang
简体中文
繁體中文
English
Tiếng Việt
한국어
日本語
ภาษาไทย
Türkçe
Trang chủ
AI AI
Tin nhanh
Bài viết
Sự kiện
BlockBeats Pro
Thêm
Thông tin tài chính
Chuyên đề
Hệ sinh thái chuỗi khối
Mục nhập
Podcast
Data
OPRR
Partnered With:

Viết Prompt đã lỗi thời? Lập trình AI đang chuyển sang Loop Engineering

Đọc bài viết này mất 32 phút
Một bài viết giải thích về Loop Engineering đang bùng nổ gần đây
Tiêu đề gốc: Loop Engineering.
Tác giả gốc: Addy Osmani
Biên dịch: Peggy, BlockBeats


Lời tòa soạn: Cách sử dụng AI Coding Agent đang chuyển từ "con người viết Prompt thủ công, từng bước đẩy nhiệm vụ" sang "con người thiết kế vòng lặp, để hệ thống liên tục điều phối Agent". Điều cốt lõi mà Addy Osmani gọi là Loop Engineering (Kỹ thuật vòng lặp) là xây dựng một quy trình làm việc có thể tự động phát hiện nhiệm vụ, phân công nhiệm vụ, kiểm tra kết quả, ghi lại tiến độ và quyết định bước tiếp theo.


Vòng lặp này về cơ bản bao gồm năm mô-đun: Automations (phát hiện và phân loại nhiệm vụ theo lịch trình), Worktrees (cách ly nhiều môi trường phát triển song song), Skills (lưu trữ kiến thức dự án và thói quen nhóm), Plugins/Connectors (kết nối các công cụ thực tế như GitHub, Linear, Slack, cơ sở dữ liệu), Sub-agents (tách biệt người thực thi và người kiểm tra), cùng với một lớp ghi nhớ bên ngoài, như tệp Markdown hoặc bảng Linear, để lưu trữ trạng thái và tiến độ.


Bài viết nhấn mạnh rằng ý nghĩa của Loop Engineering không chỉ là "để AI chạy thêm vài vòng", mà là đặt khả năng phán đoán của kỹ sư vào thiết kế hệ thống. Vòng lặp có thể khuếch đại đáng kể đòn bẩy công việc của nhà phát triển, nhưng sẽ không thay thế việc xác minh, hiểu biết và phán đoán. Rủi ro thực sự không nằm ở việc sử dụng vòng lặp, mà là biến vòng lặp thành cái cớ để trốn tránh việc hiểu code và hệ thống. Trong tương lai, khả năng quan trọng khi cộng tác lập trình với AI có lẽ không chỉ là viết một Prompt tốt, mà là thiết kế một quy trình làm việc Agent đáng tin cậy, có thể xác minh và vận hành bền vững.


Dưới đây là nội dung gốc:


Loop engineering (Kỹ thuật vòng lặp) đang thay thế vai trò của bạn như một "người viết prompt cho Agent". Bạn thiết kế một hệ thống, để hệ thống đó thay bạn prompt Agent. Loop (vòng lặp) ở đây có thể hiểu là một mục tiêu đệ quy: bạn xác định một mục đích, AI sẽ liên tục lặp lại cho đến khi hoàn thành nhiệm vụ. Nó về cơ bản bao gồm năm thành phần, và cả Claude Code lẫn Codex hiện đều đã có đủ năm thành phần này.


Tôi tin rằng, đây có thể là cách chúng ta cộng tác với Coding Agent trong tương lai. Tuy nhiên, mọi thứ vẫn đang ở giai đoạn đầu, và tôi cũng giữ thái độ hoài nghi. Bạn chắc chắn cần thận trọng với chi phí token, vì sự khác biệt về chi phí giữa các chế độ sử dụng có thể rất lớn, đặc biệt phụ thuộc vào việc bạn "dư dả token" hay "thiếu hụt token". Bạn cũng cần một cơ chế nào đó để đảm bảo chất lượng không bị suy giảm. Những lo ngại về "rác thải AI" (slop) cũng là hợp lý. Dù vậy, hãy cùng xem xét vấn đề này thực sự là gì.


@steipete gần đây đã nói: "Bạn không nên viết prompt cho coding agent nữa. Bạn nên thiết kế các vòng lặp, để những vòng lặp đó prompt agent của bạn." Tương tự, @bcherny, người phụ trách Claude Code của Anthropic, cũng nói: "Tôi không còn prompt Claude nữa. Tôi có một loạt vòng lặp đang chạy, chúng sẽ prompt Claude và tự quyết định bước tiếp theo. Công việc của tôi là viết các vòng lặp."


Vậy, điều này thực sự có nghĩa là gì?


Trong khoảng hai năm qua, cách cơ bản để bạn muốn coding agent làm điều gì đó là viết một prompt tốt và cung cấp đủ ngữ cảnh. Bạn nhập một câu, đọc kết quả trả về, rồi nhập câu tiếp theo. Agent là một công cụ, và bạn luôn cầm công cụ đó, thúc đẩy nó qua từng vòng. Giai đoạn này về cơ bản đã kết thúc, ít nhất một số người cho rằng nó sắp kết thúc.


Bây giờ, bạn xây dựng một hệ thống nhỏ: nó tự phát hiện công việc, phân công nhiệm vụ, kiểm tra kết quả, ghi lại hoàn thành, và sau đó quyết định bước tiếp theo cần làm. Nghĩa là, bạn để hệ thống này điều khiển agent, thay vì tự bạn từng lần một prompt nó. Tôi đã từng viết về "họ hàng gần" của nó – agent harness engineering (kỹ thuật xây dựng khung chạy cho agent), tức là thiết lập môi trường chạy cho một agent đơn lẻ; và factory model (mô hình nhà máy), tức là hệ thống xây dựng phần mềm. Loop engineering nằm ở lớp trên harness. Nó giống harness, nhưng chạy theo bộ hẹn giờ, tạo ra các trợ lý nhỏ, và tự nuôi dưỡng chính nó.


Điều làm tôi ngạc nhiên là vấn đề này không còn chỉ ở "cấp độ công cụ" nữa. Một năm trước, nếu bạn muốn một vòng lặp, bạn phải viết cả đống script bash, và sau đó bảo trì đống script đó mãi mãi. Đó là thứ của riêng bạn, và chỉ thuộc về bạn. Bây giờ, những thành phần này đã được tích hợp trực tiếp vào sản phẩm. Các khả năng mà Steinberger liệt kê gần như có thể ánh xạ một-một vào ứng dụng Codex, và cũng gần như tương tự với Claude Code. Một khi bạn nhận ra hình thái của chúng giống nhau, bạn sẽ không còn băn khoăn nên dùng công cụ nào nữa, mà sẽ thiết kế một vòng lặp: dù bạn ngồi trong công cụ nào, nó vẫn có thể tiếp tục chạy.


Năm thành phần, và một số giải thích


Một vòng lặp cần năm thứ, cộng với một nơi để ghi nhớ thông tin. Tôi sẽ liệt kê trước, rồi giải thích từng cái.


Thứ nhất, Automations (tác vụ tự động hóa): được kích hoạt theo lịch trình, tự động phát hiện và phân luồng.


Thứ hai, Worktrees (Cây làm việc): Giúp hai tác nhân làm việc song song không làm lộn xộn tệp tin của nhau.


Thứ ba, Skills (Kỹ năng): Ghi lại kiến thức dự án để tránh tác nhân phải phỏng đoán mỗi lần.


Thứ tư, Plugins and connectors (Plugin và bộ kết nối): Cho phép tác nhân kết nối với các công cụ bạn đã sử dụng.


Thứ năm, Sub-agents (Tác nhân phụ): Một tác nhân chịu trách nhiệm đề xuất giải pháp, tác nhân kia kiểm tra giải pháp.


Và thứ sáu: memory (bộ nhớ). Nó có thể là một tệp Markdown, một bảng Linear, hoặc bất kỳ nơi nào độc lập với một cuộc trò chuyện cụ thể, lưu trữ "những việc đã hoàn thành" và "những việc cần làm tiếp theo". Nghe có vẻ đơn giản đến mức không quan trọng, nhưng đây là cùng một kỹ thuật mà mọi tác nhân chạy dài hạn đều dựa vào. Tôi cũng đã viết chi tiết trong long-running agents: mô hình quên mọi thứ giữa các lần chạy, vì vậy bộ nhớ phải được lưu trên đĩa, không phải trong ngữ cảnh. Tác nhân sẽ quên, nhưng kho mã nguồn thì không.


Hiện tại, cả hai sản phẩm đều có năm thành phần này.



Tên gọi của chúng có một số điểm khác biệt, nhưng về bản chất, khả năng là giống nhau. Dưới đây tôi sẽ giải thích từng điểm, vì thành thật mà nói, dù một vòng lặp cuối cùng hoạt động ổn định hay âm thầm rò rỉ khắp nơi, tất cả đều nằm ở chi tiết.


Automations: Đây là nhịp đập của vòng lặp


Automations là thứ biến vòng lặp thành một vòng lặp thực sự, chứ không phải một tác vụ một lần bạn chạy thủ công. Trong ứng dụng Codex, bạn có thể tạo một tác vụ tự động trong tab Automations, chọn dự án, prompt mà nó sẽ chạy, tần suất chạy, và liệu nó chạy trong bản checkout cục bộ của bạn hay trong worktree nền. Các kết quả chạy phát hiện vấn đề sẽ được đưa vào Triage inbox (hộp thư phân loại), còn các kết quả không có vấn đề sẽ tự động được lưu trữ, điều này khá tốt. OpenAI nội bộ cũng sử dụng nó cho những việc nhàm chán nhưng cần thiết, như phân loại issue hàng ngày, tổng kết nguyên nhân thất bại CI, viết báo cáo commit ngắn gọn, theo dõi lỗi do ai đó gây ra tuần trước. Các tác vụ tự động cũng có thể gọi skill, vì vậy bạn có thể giữ cho các tác vụ chạy lặp lại dễ bảo trì: kích hoạt $skill-name thay vì dán một bức tường hướng dẫn vào một tác vụ đã lên lịch mà sau này không ai cập nhật.


Claude Code cũng có thể đạt được hiệu quả tương tự, chỉ khác về cách thức: nó thực hiện thông qua lập lịch và hooks. Bạn có thể dùng /loop để chạy một prompt hoặc lệnh theo khoảng thời gian cố định, hoặc thiết lập một cron job, và cũng có thể dùng hooks để kích hoạt các lệnh shell tại một số điểm nhất định trong vòng đời của agent. Nếu bạn muốn nó tiếp tục chạy sau khi đóng laptop, bạn cũng có thể đẩy toàn bộ lên GitHub Actions. Ý tưởng hoàn toàn giống nhau: bạn định nghĩa một tác vụ tự động, gán cho nó một nhịp điệu, và để kết quả đến với bạn thay vì bạn phải tự đi kiểm tra.


Còn có một nguyên thủy trong phiên đáng để biết, gần hơn với trọng tâm thực sự của bài viết này. /loop sẽ lặp lại theo nhịp; /goal thì sẽ thực thi liên tục cho đến khi một điều kiện bạn viết ra thực sự được thỏa mãn. Sau mỗi vòng, một mô hình nhỏ riêng biệt sẽ đánh giá xem tác vụ đã hoàn thành chưa, vì vậy agent viết code không phải là người tự chấm điểm cho mình. Bạn có thể đưa ra một điều kiện như "tất cả các bài kiểm tra trong test/auth đều pass và lint sạch", rồi rời đi. Codex cũng có khả năng tương tự, cũng gọi là /goal. Nó sẽ làm việc liên tục qua nhiều vòng cho đến khi một điều kiện dừng có thể xác minh được thỏa mãn, và hỗ trợ tạm dừng, tiếp tục, và xóa. Cùng một nguyên thủy, cả hai công cụ đều có. Về cơ bản, đây là mô hình lặp đi lặp lại trong suốt bài viết.


Vì vậy, Automations chịu trách nhiệm đưa công việc lên bề mặt. Phần còn lại của loop thì xử lý những công việc đó.


Worktrees: Giúp song song không trở thành hỗn loạn


Một khi bạn chạy nhiều hơn một agent, xung đột file sẽ trở thành điểm thất bại. Hai agent cùng ghi vào một file về cơ bản cũng rắc rối như hai kỹ sư sửa cùng một dòng code mà không giao tiếp với nhau. git worktree có thể giải quyết vấn đề này. Nó là một thư mục làm việc riêng biệt trên một nhánh độc lập, nhưng chia sẻ cùng một lịch sử kho lưu trữ, vì vậy các sửa đổi của một agent về mặt vật lý không thể chạm vào checkout của agent khác.


Codex hỗ trợ worktree trực tiếp, vì vậy nhiều luồng có thể xử lý cùng một kho lưu trữ mà không va chạm lẫn nhau. Claude Code cũng có thể đạt được sự cô lập tương tự thông qua git worktree: bạn có thể dùng flag --worktree để mở một phiên trong checkout riêng biệt, hoặc thiết lập isolation: worktree trên subagent để mỗi trợ lý nhỏ nhận được một checkout hoàn toàn mới và tự động dọn dẹp sau khi kết thúc. Tôi đã viết về khía cạnh con người của vấn đề này trong "the orchestration tax": worktrees loại bỏ xung đột ở cấp độ cơ học, nhưng bạn vẫn là giới hạn. Thứ thực sự quyết định bạn có thể chạy bao nhiêu agent cùng lúc không phải là công cụ, mà là băng thông review của bạn.


Skills:Giúp bạn không cần giải thích lại dự án mỗi lần


Skill là một cơ chế giúp bạn không phải giải thích lại cùng một bối cảnh dự án mỗi phiên làm việc như một con cá vàng. Cả hai công cụ đều sử dụng cùng một định dạng: một thư mục chứa tệp SKILL.md lưu hướng dẫn và siêu dữ liệu; ngoài ra có thể có các tập lệnh tùy chọn, tài liệu tham khảo và tệp tài nguyên. Codex sẽ chạy một skill khi bạn gọi nó bằng $ hoặc /skills, và cũng sẽ tự động chạy khi nhiệm vụ của bạn khớp với mô tả của skill đó. Đây cũng là lý do tại sao một mô tả ngắn gọn, đơn giản thường tốt hơn một mô tả thông minh nhưng cầu kỳ. Claude Code cũng làm tương tự, tôi đã viết về mô hình này trong agent skills.


Skills cũng là nơi để ý định không còn tiêu hao bạn mỗi lần nữa. Như tôi đã nói trong intent debt, mỗi khi agent bắt đầu một phiên làm việc, nó đều khởi động nguội, và hễ ý định của bạn có khoảng trống nào, nó sẽ lấp đầy bằng những phỏng đoán tự tin. Skill chính là cách ghi ý định đó ra bên ngoài: quy ước dự án, các bước build, "chúng tôi không làm thế vì đã từng xảy ra sự cố đó" v.v., tất cả được viết một lần vào nơi mà agent đọc mỗi khi chạy. Không có skills, mỗi vòng lặp phải suy luận lại toàn bộ dự án của bạn từ đầu; có skills, nó giống như đang được hưởng lãi kép.


Có một điểm cần phân biệt: skill là định dạng viết, còn plugin là cách phân phối. Khi bạn muốn chia sẻ một skill giữa nhiều kho lưu trữ mã nguồn, hoặc gói gọn vài skill lại với nhau, bạn sẽ đóng gói chúng thành một plugin. Codex làm vậy, Claude Code cũng làm vậy.


Plugins và connectors: Đưa vòng lặp tiếp xúc với công cụ thực tế của bạn


Một vòng lặp chỉ có thể nhìn thấy hệ thống tệp là một vòng lặp rất nhỏ. Connectors được xây dựng dựa trên MCP, cho phép agent đọc issue tracker của bạn, truy vấn cơ sở dữ liệu, gọi staging API, hoặc gửi tin nhắn trong Slack. Cả Codex và Claude Code đều hỗ trợ MCP, vì vậy connector bạn viết cho một công cụ thường cũng có thể dùng được cho công cụ kia. Plugins sẽ đóng gói connectors và skills lại với nhau, giúp đồng đội của bạn cài đặt toàn bộ cấu hình một lần, thay vì phải dựng lại toàn bộ từ trí nhớ.


Đây chính là sự khác biệt giữa "một agent nói với bạn 'đây là cách sửa'" và "một vòng lặp tự mở PR, liên kết Linear ticket, và thông báo lên kênh sau khi CI pass". Connectors quan trọng vì chúng cho phép vòng lặp hành động trong môi trường thực tế của bạn, chứ không chỉ nói "nếu tôi có thể làm, tôi sẽ làm thế".


Sub-agent: Tách người tạo khỏi người kiểm tra


Trong một vòng lặp, thiết kế cấu trúc hữu ích nhất chính là tách biệt "người viết" và "người kiểm tra". Mô hình viết mã quá dễ dàng trở nên dễ dãi khi tự chấm điểm bài làm của chính mình. Một tác nhân khác với hướng dẫn khác, đôi khi sử dụng mô hình khác, có thể phát hiện ra những vấn đề mà tác nhân đầu tiên đã bỏ qua sau khi tự thuyết phục bản thân.


Codex chỉ tạo sub-agent khi bạn yêu cầu, chúng chạy song song, sau đó tổng hợp kết quả thành một câu trả lời. Bạn có thể định nghĩa agent của riêng mình trong .codex/agents/ bằng file TOML: mỗi agent có tên, mô tả, hướng dẫn, và tùy chọn mô hình cùng cường độ suy luận. Do đó, người kiểm tra bảo mật của bạn có thể là một mô hình mạnh với suy luận cường độ cao, trong khi người khám phá có thể là một mô hình nhẹ, nhanh và chỉ đọc. Claude Code cũng đạt được khả năng tương tự thông qua sub-agent và nhóm agent trong .claude/agents/, cho phép nhiều agent chuyển giao công việc cho nhau. Sự phân công phổ biến nhất ở cả hai bên là: một agent khám phá, một agent triển khai, và một agent xác minh theo thông số kỹ thuật.


Tôi đã trình bày quan điểm này hai lần: một lần trong code agent orchestra, và một lần trong adversarial code review. Nó đặc biệt quan trọng trong vòng lặp, vì vòng lặp chạy khi bạn không giám sát, nên một người xác minh (verifier) mà bạn thực sự tin tưởng là lý do duy nhất bạn dám rời đi. Sub-agent thực sự tiêu tốn nhiều token hơn, vì mỗi agent phải thực hiện các lệnh gọi mô hình và công cụ riêng, vì vậy bạn nên sử dụng chúng ở những nơi "ý kiến thứ hai đáng để trả tiền". Về cơ bản, đây cũng là những gì /goal của Claude Code làm ở tầng nền: một mô hình mới quyết định xem vòng lặp đã hoàn thành hay chưa, thay vì để mô hình thực hiện công việc tự quyết định. Nói cách khác, nó áp dụng sự tách biệt giữa "người tạo" và "người kiểm tra" vào chính điều kiện dừng.


Một vòng lặp trông như thế nào


Kết hợp tất cả những điều này lại, một luồng riêng lẻ sẽ biến thành một bảng điều khiển nhỏ. Dưới đây là một cấu trúc tôi thường sử dụng.


Mỗi sáng, một automation chạy trên kho mã. Prompt của nó gọi một skill phân loại, đọc các lỗi CI của ngày hôm trước, các issue đang mở, các commit gần đây, và ghi lại phát hiện vào một file Markdown hoặc bảng Linear. Đối với mỗi vấn đề đáng xử lý, luồng sẽ mở một worktree cách ly, cử một sub-agent phác thảo bản sửa lỗi, sau đó cử sub-agent thứ hai xem xét bản sửa lỗi đó dựa trên skill của dự án và các bài kiểm tra hiện có.


Connectors cho phép loop này tự động mở PR và cập nhật ticket. Bất cứ thứ gì loop không xử lý được sẽ được chuyển vào triage inbox để tôi xử lý. Tệp trạng thái là xương sống của toàn bộ hệ thống: nó ghi nhớ những gì đã thử, những gì đã qua, và những gì vẫn chưa hoàn thành. Do đó, lần chạy vào sáng hôm sau sẽ tiếp tục từ nơi hôm nay dừng lại.


Hãy chú ý đến những gì bạn thực sự đã làm. Bạn chỉ thiết kế một lần. Những bước đó không phải do bạn tự tay từng bước nhắc nhở. Đó là phiên bản thực tế của câu nói của Steinberger. Và cùng một loop có thể chạy trên Codex, cũng có thể chạy trên Claude Code, bởi vì các thành phần cấu trúc vẫn là một bộ thành phần duy nhất.


Loop vẫn sẽ không làm thay bạn những gì


Loop thay đổi cách làm việc, nhưng không loại bỏ bạn khỏi công việc. Trên thực tế, khi loop trở nên mạnh hơn, ba vấn đề sẽ trở nên gay gắt hơn, chứ không dễ dàng hơn.


Việc xác minh vẫn phụ thuộc vào bạn. Một loop chạy không có người giám sát, cũng có thể đang mắc lỗi mà không ai hay. Lý do bạn tách verifier sub-agent ra khỏi maker là để câu nói "hoàn thành" của loop có phần nào đó ý nghĩa. Dù vậy, "hoàn thành" vẫn chỉ là một tuyên bố, chứ không phải bằng chứng. Tôi luôn lặp lại một câu trong code review in the age of AI: trách nhiệm của bạn là bàn giao mã mà bạn xác nhận là có hiệu lực.


Nếu bạn buông lơi, sự hiểu biết của chính bạn vẫn sẽ suy thoái. Loop càng nhanh chóng bàn giao mã mà bạn không tự viết, thì khoảng cách giữa những gì bạn thực sự hiểu và những gì thực sự tồn tại trong hệ thống càng lớn. Đây chính là comprehension debt (nợ hiểu biết). Nếu bạn không đọc những gì loop tạo ra, một loop trơn tru sẽ chỉ làm khoản nợ này tăng nhanh hơn.


Và đúng vậy, tư thế thoải mái nhất rất có thể cũng là tư thế nguy hiểm nhất. Khi loop có thể tự chạy, bạn dễ dàng ngừng hình thành phán đoán của riêng mình, chỉ chấp nhận bất cứ thứ gì nó trả về. Tôi gọi đây là cognitive surrender (đầu hàng nhận thức). Nếu bạn thiết kế loop với khả năng phán đoán, nó là liều thuốc giải; nếu bạn thiết kế loop chỉ để trốn tránh suy nghĩ, nó là chất xúc tác. Cùng một hành động, nhưng mang lại kết quả hoàn toàn trái ngược.


Xây dựng loop, nhưng vẫn làm kỹ sư


Tôi cho rằng, điều này báo hiệu hướng tiến hóa trong công việc tương lai của chúng ta. Dù nói vậy, nếu tôi không tự mình xem xét mã, hoặc hoàn toàn phụ thuộc vào loop tự động để sửa mã, chất lượng sản phẩm của tôi sẽ bị tổn hại. Tôi rất có thể rơi vào một vòng xoáy đi xuống: liên tục tự đào mình vào những hố sâu hơn.


Vì vậy, tất nhiên bạn có thể tự xây dựng vòng lặp (loop) của riêng mình, nhưng đừng quên rằng việc nhắc trực tiếp tác nhân thông minh (agent) của bạn vẫn hiệu quả. Điều quan trọng là tìm ra sự cân bằng phù hợp.


Kết quả của vòng lặp cũng khác nhau tùy vào từng người. Hai người có thể xây dựng cùng một vòng lặp hoàn hảo, nhưng lại thu được kết quả trái ngược. Một người dùng nó để tăng tốc công việc mà họ hiểu sâu sắc; người kia dùng nó để trốn tránh việc hiểu bản chất công việc. Vòng lặp không biết sự khác biệt giữa hai điều này. Bạn thì biết.


Đó là lý do tại sao thiết kế vòng lặp (loop design) khó hơn, chứ không dễ hơn, so với kỹ thuật nhắc (prompt engineering). Ý của Cherny không phải là công việc trở nên nhẹ nhàng hơn, mà là điểm đòn bẩy đã thay đổi.


Hãy xây dựng vòng lặp. Nhưng hãy xây dựng nó như một người vẫn có ý định làm kỹ sư, chứ không phải như một người chỉ biết nhấn nút "Bắt đầu".


[Liên kết gốc]



Chào mừng bạn tham gia cộng đồng chính thức của BlockBeats:

Nhóm Telegram đăng ký: https://t.me/theblockbeats

Nhóm Telegram thảo luận: https://t.me/BlockBeats_App

Tài khoản Twitter chính thức: https://twitter.com/BlockBeatsAsia

举报 Báo lỗi/Báo cáo
Chọn thư viện
Thêm mới thư viện
Hủy
Hoàn thành
Thêm mới thư viện
Chỉ mình tôi có thể nhìn thấy
Công khai
Lưu
Báo lỗi/Báo cáo
Gửi