Sau lần sửa thứ năm mà bug vẫn còn đó, câu hỏi không còn là "sửa gì tiếp" mà là "mình có đang sửa nhầm nền móng không?"
Cái Bẫy Sunk-Cost Trong Phiên Làm Việc Agentic
Sunk-cost fallacy (ngụy biện chi phí chìm) là xu hướng tâm lý tiếp tục đầu tư vào một việc chỉ vì đã đầu tư quá nhiều vào nó, bất kể việc tiếp tục có còn hợp lý hay không. Trong agentic session (phiên làm việc với AI agent), cái bẫy này xuất hiện dưới một hình hài đặc biệt nguy hiểm vì tốc độ.
Khi bạn tự tay code, việc "đầu tư" 30 phút vào một hướng tiếp cận sai tạo ra cảm giác mất mát rõ ràng, dễ nhận biết — bạn nhớ mình đã viết bao nhiêu dòng. Nhưng trong agentic workflow, bạn có thể đã trải qua 15 vòng lặp prompt-response với AI agent, mỗi vòng "chỉ" mất 1-2 phút, nhưng cộng dồn lại là một giờ đồng hồ và một core logic đã bị vá đi vá lại tới mức không ai — kể cả AI — còn nắm được toàn bộ bức tranh nữa.
Điều khiến sunk-cost trong agentic session nguy hiểm hơn bình thường: mỗi lần AI "sửa" một vấn đề, nó thường tạo cảm giác tiến bộ (một bug được fix, output trông "gần đúng hơn"), nhưng đồng thời có thể đang chồng thêm phức tạp lên một nền tảng vốn dĩ sai từ gốc. Bạn cảm thấy mình đang tiến gần hơn tới đích, trong khi thực tế đang lún sâu hơn vào một kiến trúc không thể sửa được bằng patch nhỏ.
Nhận diện được cái bẫy này là bước đầu tiên. Bước tiếp theo là có một khung quyết định rõ ràng, không dựa vào cảm tính, để biết khi nào nên tiếp tục và khi nào nên dừng lại, bỏ đi, làm lại từ đầu.
Mẹo: Sau mỗi 3 vòng lặp sửa lỗi liên tiếp không thành công, dừng lại và tự hỏi thành tiếng: "Nếu tôi bắt đầu lại từ đầu ngay bây giờ với những gì tôi đã học được, tôi có làm nhanh hơn không?" Câu hỏi này phá vỡ quán tính tâm lý "cứ sửa tiếp".
Tín Hiệu Cho Thấy Việc Iterate Tiếp Là Vô Ích
Có những dấu hiệu khá rõ ràng cho thấy việc tiếp tục yêu cầu AI sửa từng lỗi nhỏ sẽ không dẫn tới kết quả tốt, và bạn nên cân nhắc restart:
AI sửa lỗi này lại tạo ra lỗi khác ở chỗ khác — vòng lặp "whack-a-mole". Nếu bạn yêu cầu fix bug A, AI fix được A nhưng lại vô tình phá vỡ behavior B đã từng đúng, rồi bạn yêu cầu fix B, nó lại phá C — đây là dấu hiệu rõ ràng nhất của một kiến trúc nền tảng có vấn đề, không phải lỗi cục bộ có thể patch.
Số lượng "patch" chồng lên nhau vượt quá độ phức tạp của bài toán gốc. Nếu bài toán ban đầu đơn giản (ví dụ một hàm validate) nhưng sau 5-6 lần sửa, hàm đó đã phình to với hàng chục điều kiện đặc biệt (special case) chồng chéo, đó là tín hiệu bạn đang vá một cách tiếp cận sai thay vì sửa lỗi thật.
AI bắt đầu mâu thuẫn với chính giải thích trước đó của nó. Nếu ở vòng 3 AI giải thích logic theo cách A, nhưng ở vòng 6 nó giải thích lại theo cách B mâu thuẫn với A cho cùng một đoạn code — điều này cho thấy ngay cả AI cũng đã "mất dấu" hiểu biết nhất quán về code nó đang sửa (context đã bị pha loãng hoặc mâu thuẫn qua quá nhiều vòng).
Bạn không còn tự tin giải thích được toàn bộ code hiện tại. Nếu sau nhiều vòng sửa, bạn — người review — không còn nắm được bức tranh tổng thể của code nữa, đây là tín hiệu ownership (quyền sở hữu) đã bị xói mòn, một rủi ro nghiêm trọng độc lập với việc bug có được fix hay không.
Mỗi lần sửa đều cần một prompt dài hơn, chi tiết hơn, với nhiều ràng buộc hơn để "ép" AI không phá cái đã đúng. Prompt ngày càng phình to để liệt kê "đừng làm A, đừng phá B, giữ nguyên C" là dấu hiệu bạn đang cố gắng bù đắp cho một nền tảng thiết kế không vững, bằng ngày càng nhiều ràng buộc thủ công.
Root cause của lỗi đầu tiên vẫn chưa được xác định rõ sau 2-3 lần hỏi. Nếu bạn hỏi "tại sao lỗi này xảy ra" và nhận được câu trả lời mơ hồ hoặc khác nhau mỗi lần, đây là tín hiệu vấn đề nằm sâu hơn những gì một vài patch có thể giải quyết.
Mẹo: Giữ một bộ đếm đơn giản: mỗi khi một fix của AI vô tình phá vỡ một behavior đã đúng trước đó, cộng 1 điểm. Chạm ngưỡng 2-3 điểm trong cùng một phiên là tín hiệu nên nghiêm túc cân nhắc restart.
Tín Hiệu Cho Thấy Một Fix Có Chủ Đích Sẽ Hiệu Quả
Ngược lại, không phải mọi vấn đề đều cần restart — nhiều tình huống chỉ cần một fix có chủ đích, cụ thể là đủ. Nhận diện đúng những tín hiệu này giúp bạn tránh lãng phí thời gian restart không cần thiết:
Root cause đã được xác định rõ ràng và cụ thể. Nếu bạn (hoặc AI, sau khi được verify) có thể chỉ ra chính xác: "lỗi nằm ở dòng X, do so sánh dùng < thay vì <=" — đây là fix cục bộ, có phạm vi rõ ràng, không cần restart.
Lỗi chỉ ảnh hưởng một phần nhỏ, tách biệt của hệ thống. Nếu bug nằm trong một hàm thuần túy (pure function), không có side effect lan rộng, sửa tại chỗ là hợp lý.
Kiến trúc tổng thể vẫn đúng, chỉ có chi tiết implementation sai. Ví dụ: cấu trúc chương trình, luồng dữ liệu, tên hàm/module đều hợp lý — chỉ có một vài dòng logic tính toán sai. Đây là tình huống lý tưởng cho targeted fix (fix có chủ đích, phạm vi hẹp).
Bạn (người review) vẫn giữ được sự hiểu biết đầy đủ về toàn bộ code. Nếu bạn vẫn tự tin giải thích được mọi phần của code hiện tại, ownership chưa bị xói mòn — tiếp tục sửa nhỏ là an toàn.
Chỉ có một điểm lỗi, không phải nhiều điểm lỗi tương tác với nhau. Nếu bug xuất phát từ một nguyên nhân duy nhất, dù triệu chứng biểu hiện ở nhiều nơi, một fix tại nguồn gốc sẽ giải quyết tất cả — khác với tình huống nhiều nguyên nhân độc lập chồng chéo.
Khung quyết định thực tế: hỏi "Tôi có thể mô tả chính xác, bằng một câu, fix cần làm là gì không?" Nếu câu trả lời rõ ràng và cụ thể (không phải "cứ thử sửa xem sao"), đó là tín hiệu targeted fix sẽ hiệu quả.
Mẹo: Trước khi yêu cầu AI sửa, tự viết ra một câu duy nhất mô tả chính xác root cause và fix cần làm. Nếu bạn không viết nổi câu đó một cách rõ ràng, đó chính là tín hiệu bạn chưa thực sự hiểu vấn đề đủ để làm targeted fix — có thể cần điều tra sâu hơn hoặc cân nhắc restart.
Mô Hình Tư Duy "Ngân Sách Restart"
Để tránh vừa rơi vào sunk-cost fallacy (không bao giờ dám restart) vừa tránh restart quá sớm gây lãng phí công sức đã có, hãy áp dụng mô hình "ngân sách restart" (restart budget) — một cách tư duy có cấu trúc để ra quyết định.
Ý tưởng cốt lõi: trước khi bắt đầu một task với AI agent, tự đặt ra một "ngân sách" số vòng lặp sửa lỗi hợp lý cho task đó — dựa trên độ phức tạp ước tính. Ví dụ: task đơn giản (một hàm validate) — ngân sách 2-3 vòng sửa. Task vừa (một API endpoint với vài business rule) — ngân sách 4-5 vòng. Task phức tạp (tích hợp nhiều service, luồng nghiệp vụ nhiều bước) — ngân sách 6-8 vòng.
Khi ngân sách cạn mà vấn đề cốt lõi chưa được giải quyết, đó là tín hiệu bắt buộc phải dừng lại và đánh giá lại — không nhất thiết phải restart ngay, nhưng phải chuyển từ "tiếp tục sửa theo quán tính" sang "chủ động phân tích tại sao ngân sách đã cạn".
Ngân sách không phải là con số cứng nhắc, mà là điểm dừng bắt buộc để ra quyết định có ý thức. Tại điểm dừng đó, bạn xem xét lại các tín hiệu đã liệt kê ở hai phần trên: nếu đa số tín hiệu chỉ ra "iterate vô ích", restart. Nếu tín hiệu chỉ ra "targeted fix sẽ hiệu quả" nhưng chỉ cần thêm 1-2 vòng nữa, có thể gia hạn ngân sách một cách có ý thức (không phải quán tính) — với điều kiện bạn xác định rõ đây là quyết định chủ động, không phải "cứ thử thêm lần nữa xem sao".
Ghi lại ngân sách và số vòng thực tế đã dùng — theo thời gian, dữ liệu này giúp bạn hiệu chỉnh ước tính ngân sách chính xác hơn cho các task tương lai, và cũng giúp bạn nhận diện pattern: loại task nào thường xuyên vượt ngân sách (thường là dấu hiệu bạn cần spec rõ ràng hơn từ đầu, một kỹ năng đã học ở module về spec-driven development).
Mẹo: Viết ngân sách restart ước tính ngay trong ghi chú đầu phiên làm việc (ví dụ "task này: max 4 vòng sửa") trước khi bắt đầu — cam kết trước giúp bạn tránh việc tự thương lượng "thêm một lần nữa thôi" vô thời hạn khi đang ở giữa phiên.
Cách Restart Sạch Sẽ Mà Không Mất Đi Context Tốt
Quyết định restart không có nghĩa là vứt bỏ toàn bộ giá trị đã tạo ra trong phiên cũ. Một restart sạch sẽ (clean restart) biết cách giữ lại phần có giá trị và bỏ đi phần gây nhiễu.
Phân biệt rõ giữa "context tốt" và "code tốt". Code từ phiên cũ có thể phải bỏ hoàn toàn, nhưng những gì bạn học được — root cause thật sự của vấn đề, những cách tiếp cận đã thử và thất bại, những ràng buộc nghiệp vụ mà bạn phát hiện ra qua quá trình debug — đó là context có giá trị cần giữ lại.
Viết một "distilled spec" (đặc tả chắt lọc) trước khi bắt đầu phiên mới. Đây là bước quan trọng nhất của một restart sạch: tổng hợp lại toàn bộ hiểu biết đã tích lũy được thành một đặc tả rõ ràng, súc tích — bao gồm cả những "bẫy" đã phát hiện ra (ví dụ: "chú ý, coupon percent phải clamp ở 50% TRƯỚC khi áp dụng giảm giá VIP, đây là lỗi đã gặp ở lần thử trước").
Bắt đầu session hoàn toàn mới, không kế thừa lịch sử hội thoại cũ. Lịch sử hội thoại cũ, dù có vẻ chứa nhiều thông tin, thường mang theo cả những giả định sai và cách tiếp cận đã thất bại — những thứ này có thể vô tình "nhiễm" vào cách AI tiếp cận lại vấn đề nếu bạn tiếp tục trong cùng context.
Không xóa hoàn toàn code cũ ngay lập tức — giữ lại như tài liệu tham khảo. Lưu code cũ ở một branch/file riêng, có thể hữu ích để so sánh sau này, hoặc để trích ra một vài đoạn logic nhỏ vẫn đúng (ví dụ một helper function không liên quan tới phần lỗi).
Đặt tiêu chí thành công rõ ràng cho phiên mới trước khi bắt đầu, dựa trên chính những boundary case và test case đã phát hiện ra vấn đề ở phiên cũ — biến thất bại cũ thành bộ test bảo vệ cho lần thử mới.
Mẹo: Dành 10-15 phút viết distilled spec trước khi restart — thời gian này luôn đáng giá hơn nhiều so với việc lao ngay vào phiên mới mà không tổng hợp bài học từ phiên cũ, dẫn tới nguy cơ lặp lại đúng những sai lầm đã mắc.
Giao Tiếp Trong Team Về Việc Restart Task AI
Restart một task AI không nên là chuyện âm thầm, đặc biệt trong môi trường team — vì nó ảnh hưởng tới ước lượng thời gian, tới việc học hỏi chung, và tới văn hóa làm việc với AI của cả nhóm.
Coi restart là thông tin hữu ích, không phải thất bại cần giấu. Một kỹ sư báo cáo "tôi đã restart task này sau 5 vòng sửa vì phát hiện kiến trúc ban đầu sai hướng" đang cung cấp thông tin giá trị cho team — không phải thú nhận yếu kém.
Cập nhật ước lượng thời gian khi restart xảy ra. Nếu task được estimate 2 giờ nhưng đã restart một lần, cần cập nhật lại kỳ vọng với stakeholder/PM thay vì cố gắng "bù" thời gian đã mất bằng cách làm ẩu ở phiên mới.
Chia sẻ distilled spec với team nếu vấn đề có tính lặp lại. Nếu root cause khiến bạn phải restart là một dạng vấn đề có thể xảy ra lại (ví dụ một pattern AI hay hiểu sai), ghi lại và chia sẻ để đồng nghiệp không mất thời gian đi qua đúng con đường đó.
Đưa "số lần restart" vào retro nếu nó xảy ra thường xuyên. Nếu một loại task nào đó liên tục cần restart, đây là tín hiệu quy trình (ví dụ chất lượng spec đầu vào, hoặc cách chia nhỏ task) cần được cải thiện ở cấp độ team, không chỉ cấp độ cá nhân.
Mẹo: Thêm một dòng ngắn vào PR description hoặc standup update khi có restart xảy ra: "Đã restart 1 lần do [lý do ngắn gọn]" — thói quen nhỏ này xây dựng văn hóa minh bạch và giúp team học nhanh hơn từ kinh nghiệm của nhau.
Thực Hành: Ra Quyết Định Iterate hay Restart
Bước 1: Tạo một tình huống để đánh giá
Yêu cầu AI implement một tính năng có độ phức tạp vừa phải nhưng với spec cố tình để một chút mơ hồ, ví dụ:
Viết hệ thống tính điểm thưởng (loyalty points):
- 1 điểm cho mỗi 10,000đ chi tiêu.
- Nhân đôi điểm vào cuối tuần.
- Điểm hết hạn sau 12 tháng.
(Cố tình không nói rõ: "cuối tuần" tính theo giờ nào, điểm hết hạn tính theo ngày mua hay theo tháng dương lịch.)
Bước 2: Chẩn đoán output dựa trên spec được tiết lộ dần
Sau khi có implementation đầu tiên, "tiết lộ" thêm chi tiết spec (giống một khách hàng thực tế bổ sung yêu cầu dần dần): "cuối tuần tính theo giờ Việt Nam, từ 00:00 thứ Bảy tới 23:59 Chủ Nhật." Yêu cầu AI sửa. Lặp lại với chi tiết tiếp theo về việc hết hạn điểm.
Bước 3: Áp dụng ngân sách restart
Trước khi bắt đầu bước 2, tự đặt ngân sách (ví dụ 3 vòng sửa cho task này). Theo dõi xem tới vòng thứ mấy thì các tín hiệu "iterate vô ích" (đã liệt kê ở phần trên) bắt đầu xuất hiện — ví dụ AI bắt đầu phá vỡ logic nhân đôi điểm cuối tuần khi sửa logic hết hạn điểm.
Bước 4: Nếu restart — viết distilled spec
Tổng hợp toàn bộ chi tiết đã làm rõ qua các vòng thành một spec đầy đủ, rõ ràng, viết sẵn cả các "bẫy" đã phát hiện (ví dụ: "chú ý: logic nhân đôi cuối tuần và logic hết hạn điểm có tương tác với nhau khi giao dịch xảy ra đúng lúc giao thời — cần xử lý rõ thứ tự.")
Bước 5: So sánh hai implementation
Yêu cầu AI implement lại từ đầu, session mới, dựa trên distilled spec. So sánh: implementation mới có sạch hơn, dễ hiểu hơn, ít special-case chồng chéo hơn implementation cũ đã qua nhiều vòng patch không?
Bước 6: Ghi lại quyết định restart
Viết một đoạn ngắn (dùng làm tài liệu cá nhân hoặc chia sẻ team): task này, số vòng đã dùng trước khi restart, tín hiệu nào dẫn tới quyết định restart, và kết quả so sánh giữa hai implementation.
Bài thực hành này giúp bạn cảm nhận trực tiếp sự khác biệt giữa một codebase được "vá" qua nhiều vòng và một codebase được viết lại từ một spec đã được làm rõ — đây chính là bài học cốt lõi của cả chủ đề.
Mẹo: Lặp lại bài thực hành này với các mức độ phức tạp task khác nhau để tự hiệu chỉnh "ngân sách restart" của riêng bạn — con số phù hợp sẽ khác nhau tùy vào loại task và tùy vào chính AI assistant bạn đang dùng.
Những điểm chính
- Sunk-cost fallacy trong agentic session nguy hiểm hơn bình thường vì tốc độ vòng lặp nhanh khiến bạn khó nhận ra mình đã đầu tư bao nhiêu vào một hướng sai.
- Tín hiệu nên restart: vòng lặp whack-a-mole (sửa chỗ này hỏng chỗ khác), patch chồng chất vượt độ phức tạp gốc, AI mâu thuẫn với chính giải thích trước, mất ownership hiểu biết toàn bộ code.
- Tín hiệu targeted fix sẽ hiệu quả: root cause rõ ràng cụ thể, phạm vi ảnh hưởng hẹp, kiến trúc tổng thể vẫn đúng, bạn vẫn giữ hiểu biết đầy đủ về code.
- Mô hình "ngân sách restart": đặt trước số vòng sửa hợp lý theo độ phức tạp task, và coi việc cạn ngân sách là điểm dừng bắt buộc để ra quyết định có ý thức, không phải tiếp tục theo quán tính.
- Restart sạch giữ lại "context tốt" (bài học, root cause, bẫy đã phát hiện) qua một distilled spec, nhưng bắt đầu code và session hoàn toàn mới.
- Restart nên được giao tiếp minh bạch trong team — là thông tin hữu ích cho ước lượng thời gian và cải thiện quy trình chung, không phải chuyện cần giấu.