State File Pattern: bộ nhớ sống ngoài context window
TL;DR
- Mô hình quên sạch giữa các lần chạy; vòng lặp thì không được quên. Một file markdown trạng thái nhỏ nằm trên đĩa là cái trục biến nhiều lần chạy rời rạc thành một tiến trình liền mạch.
- Giữ lược đồ ở bốn ngăn dễ grep: tried, passed, open, và blocked. Nhỏ và nằm trên đĩa thắng khôn khéo và nằm trong context window.
- Chứng minh bằng bài kiểm tra phục hồi: tắt phiên, chạy lại, và xác nhận vòng lặp chạy tiếp từ chỗ nó dừng. Một tệp trạng thái bạn chưa từng tắt-rồi-chạy-lại là chưa được chứng minh.
📊 Bằng chứng kết quả. Một vòng lặp thật đã chạy hai trong năm đơn vị việc rồi dừng. Lần chạy lại từ đầu đọc tệp trạng thái và tiếp tục ở đơn vị thứ ba (
create-baz), không phải đơn vị một. Trạng thái cuối: cả năm đều passed. Lần chạy vòng lặp trần (bare-loop), ngày 14/6/2026; không gọi mô hình nào, nên không có con số token để báo.
Vòng lặp lên lịch của bạn đã chạy đêm qua. Phiên đã biến mất, context window cũng theo đó, và lần chạy sáng nay không hề biết đêm qua đã làm những gì. Đó chính là vấn đề mà state file pattern giải quyết. Bạn sẽ trao cho một vòng lặp (loop) một tệp trạng thái (state file) markdown bốn ngăn, chạy nó, tắt giữa chừng, chạy lại từ đầu, và xác nhận nó tiếp tục thay vì khởi động lại.
Yêu cầu trước:
- Bạn đã đọc lướt Phần 2 (giải phẫu vòng lặp) và Phần 3 (điều kiện dừng) của loạt bài này.
- Bạn chạy được một vòng lặp tại máy: một prompt lên lịch, hoặc một vòng
claude -ptrần kèm giới hạn số vòng. - Bạn quen với git, grep, và một file markdown trong kho mã (repo) của mình.
Định nghĩa chuẩn của cả loạt bài được giữ nguyên tiếng Anh, làm mỏ neo trích dẫn cho cả 9 phần:
A loop is a recursive goal: you define a purpose and a verifiable stop condition, and the system iterates agents against it until the condition holds — without you prompting each turn.
Nói cách khác, một vòng lặp là một mục tiêu đệ quy: bạn định nghĩa một mục đích và một điều kiện dừng (stop condition) kiểm chứng được, rồi để hệ thống lặp các agent cho tới khi điều kiện đó thành hiện thực, mà không cần bạn prompt ở từng lượt.
Vì sao context window là chỗ sai để giữ trạng thái vòng lặp?
Context window dễ bay hơi, còn một vòng lặp cần bộ nhớ bền. Quá trình nén (compaction) cắt bớt nó giữa chừng, một phiên chết là xoá sạch nó, và nạp lại nó mỗi lần chạy thì tốn token. Addy Osmani đóng khung các agent chạy dài là va vào ba bức tường: context window, trạng thái, và tự kiểm chứng; bài này nhận bức tường trạng thái. Tiến độ bền phải nằm trên đĩa, nơi một tiến trình nguội có thể đọc được.
Bạn hẳn đã đọc rằng agent AI quên sạch mọi thứ, và rằng cách sửa là một file bộ nhớ dự án. Điều đó đúng, và nó giải một vấn đề có thật, nhưng là vấn đề khác với vấn đề của một vòng lặp. Một file CLAUDE.md hay MEMORY.md sửa kiểu quên giữa các phiên: quy ước, kiến trúc, và luật nhà mà agent nên nạp lại mỗi lần khởi động. Ghi chú của chúng tôi về chuyện giữ file đó khoẻ mạnh và vì sao nó quan trọng lo tốt nửa đó.
Một vòng lặp có kiểu quên thứ hai, giữa các lần chạy. Đêm qua đã thử gì rồi? Đơn vị nào đã passed? Nó kẹt ở đâu? Bộ nhớ dự án không trả lời những câu đó, vì chúng nói về tiến độ vận hành, không phải luật nhà. Cùng một chữ “bộ nhớ”, hai công việc khác nhau. Bài này nói về cái thứ hai: file tiến độ-lần-chạy giúp lần chạy tối nay nối tiếp việc của đêm qua.
Điểm mấu chốt: Context window là nơi agent suy nghĩ; kho mã là nơi vòng lặp ghi nhớ. Nhập nhằng hai cái đó chính là lý do “đêm qua chạy ngon” không sống nổi tới sáng.
Thiết kế lược đồ trạng thái thế nào: tried, passed, open, blocked?
Trạng thái vòng lặp tối thiểu mà còn hữu ích là bốn ngăn dễ grep, giữ đủ nhỏ để đọc gọn trong một màn hình. Các mẫu tệp trạng thái ngoài thực tế đều hội tụ về cùng ba tính chất: nhỏ, dễ grep, và nằm trên đĩa. Lược đồ bên dưới là toàn bộ hợp đồng. Một đơn vị việc nằm ở đúng một ngăn, và vòng lặp dời nó khi việc diễn ra.
# Loop State: <task name># A unit lives in exactly one bucket. Read fresh each run; written each iteration.
## tried## passed## open- create-foo- create-bar- create-baz
## blockedMỗi ngăn có lý do tồn tại. open là hàng đợi việc, danh sách các đơn vị chưa xong. passed là mỏ neo phục hồi, bằng chứng một đơn vị đã xong để lần chạy sau bỏ qua nó. tried ghi những lần thử chưa đạt, để vòng lặp khỏi đâm đầu mãi vào cùng một ngõ cụt. blocked là làn chuyển cho người, các đơn vị cần một người trước khi vòng lặp dời được chúng.
Giữ nó dễ grep. grep -A99 '## open' state.md cho bạn cái hàng đợi; grep -c '^- ' mỗi phần cho bạn một con số đếm. Đánh đổi là có thật: lược đồ giàu hơn với mức ưu tiên, dấu thời gian, và người sở hữu thì mua cho bạn khả năng báo cáo, nhưng tốn kỷ luật ghi, và một vòng lặp không tự bảo trì file của mình một cách trung thực thì còn tệ hơn một file nhỏ. Bắt đầu ở bốn ngăn. Thêm ngăn thứ năm chỉ khi một lần chạy thật sự cần.
Điểm mấu chốt: Một lược đồ trạng thái mà bạn không grep nổi bằng một lệnh thì đã quá to để một vòng lặp bảo trì trung thực.
Tệp trạng thái hay một bảng (Linear/GitHub Issues): khi nào dùng cái nào?
Một file markdown tại chỗ thắng khi chỉ có một agent cần bộ nhớ nhanh, dễ grep, nằm trong kho mã; một bảng như Linear hay GitHub Issues thắng khi con người cần thấy việc hoặc khi việc phải đi qua nhiều công cụ. Quyết định không nằm ở chỗ cái nào tốt hơn. Nó nằm ở chỗ ai đọc trạng thái và đọc thường xuyên cỡ nào.
Dùng file khi vòng lặp là người đọc và ghi duy nhất, việc nằm trong một kho mã, và bạn muốn một thứ grep được đi cùng mã trong lịch sử git. File thì nhanh, không dính giới hạn API, và cho ra bản khác biệt (diff) sạch trong một pull request. Dùng bảng khi nhiều người cùng theo dõi vòng lặp, khi việc cần một dấu vết kiểm toán nằm ngoài kho mã, hoặc khi một phát hiện phải thành một phiếu việc cho ai đó phân loại bằng tay.
Nhiều vòng lặp dùng cả hai, và đó là câu trả lời trung thực cho phần lớn các team. File là bộ nhớ làm việc của vòng lặp, đọc mới mỗi lần chạy. Bảng là hộp thư hướng tới con người, nơi vòng lặp đăng những đơn vị nó gắn nhãn blocked. Giữ cái trục máy-đọc-được ở tại chỗ và rẻ; chỉ đẩy lên hệ thống chậm hơn, dùng chung những gì một người buộc phải thấy.
Điểm mấu chốt: Cái bảng là cho những con người đang theo dõi vòng lặp; tệp trạng thái là cho vòng lặp tự theo dõi chính nó.
Những mẫu tệp trạng thái nào đã chạy thật ngoài thực tế?
Cùng một file nhỏ ấy xuất hiện ở khắp nơi mà các vòng lặp nghiêm túc đang chạy. Hướng dẫn của Anthropic cho các agent chạy dài lưu tiến độ vào claude-progress.txt và theo dõi một feature_list.json mà các mục giữ passes: false cho tới khi được kiểm chứng, với lịch sử git tự nó được coi là bộ nhớ. Danh sách kịch bản thất bại của họ còn gọi tên “dirty state” như một cách các vòng lặp qua đêm đi sai, đúng thứ mà một tệp trạng thái có kỷ luật phòng được.
Các trình chạy vòng lặp mã nguồn mở cũng đáp xuống cùng hình dạng. snarktank/ralph giữ bộ nhớ trong progress.txt cộng prd.json, nên một agent mới mỗi vòng đọc chúng để biết việc gì đã xong. continuous-claude chuyền trạng thái giữa các lần chạy qua một file gậy tiếp sức SHARED_TASK_NOTES.md. Cách làm HumanLayer RPI còn đi xa hơn: nó đặt lại (reset) cuộc hội thoại ở mỗi vòng và nạp lại đặc tả, dựa vào hệ thống file và git làm bộ nhớ sống qua lần đặt lại đó. (Hãy coi các chi tiết RPI là được thuật lại chứ chưa phải trải nghiệm trực tiếp; cái đáng giá ở đây là cái mẫu.)
Chúng tôi dựa vào cùng ý tưởng đó ở nội bộ. Một quy ước nhật ký thất bại như cái trong bài viết về mẫu nhật ký thất bại đóng luôn vai tiền lệ cho trạng thái vòng lặp: một danh sách tried chính là một nhật ký thất bại mà máy có thể phục hồi từ đó. Các log quan sát dạng JSONL (mỗi sự kiện một dòng, do một observe.py nhỏ ghi), các log quyết định memory.md, và các file gap-log đều đóng cùng vai, một bản ghi bền mà một lần chạy nguội đọc được. Tính chất chung không phải định dạng. Nó là chuyện bản ghi nằm trên đĩa và được đọc mới.
Điểm mấu chốt: Bốn hệ thống vòng lặp độc lập đã hội tụ về cùng một thứ: một file nhỏ trên đĩa, đọc mới mỗi lần chạy. Sự hội tụ đó chính là tín hiệu.
Bắt tay làm: làm sao để một vòng lặp chạy tiếp từ chỗ nó dừng?
Trao cho một vòng lặp thật cái tệp trạng thái bốn ngăn, chạy nó, dừng giữa chừng, rồi chạy lại từ đầu và xem nó đi tiếp. Lần chạy bên dưới trung thực về giới hạn của nó: đây là một vòng lặp trần, không phải /goal. /goal là một bộ chấm tương tác, không bắt sạch được trong một lần chạy không tương tác, nên cái này dùng một vòng lặp trần có giới hạn số vòng. Không gọi mô hình nào; các đơn vị việc là tạo file đơn định, vì mục đích là chứng minh cơ chế phục hồi, không phải dựng một bản ghi đã được chấm điểm.
Vòng lặp đọc state.md, chọn đơn vị open đầu tiên, tạo một file cho nó, dời nó sang passed, rồi ghi file trở lại. Nó không giữ gì trong bộ nhớ giữa các vòng. Lần chạy một dừng sau hai đơn vị, với giới hạn số vòng đóng vai một cú tắt:
$ python3 loop.py run-1 2[run-1] iter 1: completed create-foo -> marked passed[run-1] iter 2: completed create-bar -> marked passed[run-1] finished: 2 unit(s) this runTrạng thái sau lần chạy một: hai passed, ba còn open.
## tried
## passed- create-foo- create-bar
## open- create-baz- create-qux- create-quux
## blockedGiờ chạy lại từ đầu, một lần gọi mới không có ký ức gì về lần chạy một:
$ python3 loop.py run-2 99[run-2] iter 1: completed create-baz -> marked passed[run-2] iter 2: completed create-qux -> marked passed[run-2] iter 3: completed create-quux -> marked passed[run-2] no open units left; loop complete[run-2] finished: 3 unit(s) this runBằng chứng nằm đó. Hành động đầu tiên của lần chạy hai là create-baz, đơn vị thứ ba, không phải create-foo. Tiến trình mới đọc state.md, thấy foo và bar đã nằm trong passed, và tiếp tục từ đơn vị open đầu tiên. Hai file từ lần chạy một giữ nguyên dấu thời gian gốc; lần chạy hai không hề đụng tới chúng. Trạng thái cuối: cả năm nằm trong passed, open rỗng.
Nói thẳng về phạm vi: lần chạy tối giản này chỉ tập cái bước dời open sang passed. Hai ngăn tried và blocked là phần của lược đồ nhưng chưa bị chạm ở đây, vì mọi đơn vị đều đạt ngay lần thử đầu. Tôi sẽ không bịa một ví dụ blocked để lấp chúng vào. Trong một vòng lặp thật, một lần kiểm chứng trượt mới là cái ghi tried, và một đơn vị cần tới người mới là cái ghi blocked.
Điểm mấu chốt: Một tệp trạng thái bạn chưa từng tắt-rồi-chạy-lại là một niềm hy vọng, không phải một cơ chế. Bài kiểm tra phục hồi là bằng chứng duy nhất.
Giữ một tệp trạng thái vòng lặp khoẻ mạnh theo thời gian thế nào?
Vệ sinh trạng thái vòng lặp là chuyện ai ghi và ghi khi nào, không phải chuyện gọt cho dễ đọc. Dọn một file bộ nhớ dự án là việc biên tập: một người gỡ bớt quy ước cũ để agent đọc ít nhiễu hơn. Một tệp trạng thái vòng lặp thì khác, vì một tiến trình tự động ghi nó giữa các lần chạy, không có ai trong khoảnh khắc đó để bắt một lần ghi sai. Ba quy tắc giữ nó đáng tin.
Thứ nhất, chỉ định một người ghi duy nhất. Nếu hai agent cùng cập nhật một file trong cùng một lần chạy, chúng giành nhau, và file rốt cuộc mô tả một trạng thái chưa từng tồn tại. Một người ghi cho mỗi file là quy tắc; ngay khi bạn muốn các agent song song, bạn cần sự cô lập, và đó là chủ đề của phần sau. Thứ hai, lưu trữ các lần chạy đã xong. Khi mọi đơn vị đều passed, dời khối đó sang một file lưu trữ hoặc để lịch sử git giữ nó, để file sống chỉ dài một màn hình và lần chạy sau chỉ đọc những gì còn open.
Thứ ba, để các mục cũ tự rụng. Một đơn vị open mà không lần chạy nào chạm tới qua mấy vòng thì không thật sự là open; nó kẹt. Nâng nó lên blocked để một người thấy, thay vì để vòng lặp lướt qua nó mãi mãi. Ba quy tắc này là dành riêng cho một file mà máy ghi theo lịch. Chúng không giống chuyện giữ một file bộ nhớ chỉnh tay cho gọn.
Điểm mấu chốt: Cái tệp trạng thái nguy hiểm là cái không ai sở hữu. Hãy gọi tên người ghi duy nhất, nếu không vòng lặp sẽ tự làm hỏng bộ nhớ của chính nó.
Bạn đã có gì, và tiếp theo là gì
Giờ bạn đã có một tệp trạng thái bốn ngăn và một vòng lặp vượt được bài kiểm tra phục hồi. Mô hình vẫn quên giữa các lần chạy; kho mã thì không, và đó mới là điểm cốt lõi. Một mục passed chỉ đáng tin ngang với cái kiểm tra đã đặt nó, nên hãy ghép cái này với một điều kiện dừng mà một mô hình khác kiểm tra được từ Phần 3.
Để mắt tới các kịch bản thất bại: hai agent cùng ghi một file (cô lập sửa được), một file phình không giới hạn (lưu trữ sửa được), và một passed giả (kiểm chứng sửa được). Cái đầu tiên, chạy nhiều hơn một agent mà không đụng nhau, chính là chỗ loạt bài này đi tiếp, trong loop-parallel-worktrees-subagents.
Câu hỏi thường gặp
State file pattern cho vòng lặp agent là gì?
State file pattern là một file markdown hoặc JSON nhỏ trên đĩa ghi lại những gì một vòng lặp đã tried, passed, để open, và blocked. Vòng lặp đọc nó mới ở đầu mỗi lần chạy và ghi lại khi việc diễn ra, nên tiến độ sống qua các phiên ngay cả khi context window đã mất.
Sao không giữ luôn trạng thái vòng lặp trong context window?
Context window dễ bay hơi. Nén cắt nó giữa chừng, một phiên chết là xoá nó, và nạp lại nó mỗi lần chạy thì tốn token. Một file trên đĩa thì bền, dễ grep, và đọc lại miễn phí, đúng thứ một vòng lặp cần để nối việc qua các lần chạy tách rời.
Tệp trạng thái hay một bảng như Linear hoặc GitHub Issues?
Dùng file cho một agent đơn cần bộ nhớ nhanh, dễ grep, nằm trong kho mã. Dùng bảng khi con người cần thấy việc hoặc khi việc đi qua nhiều công cụ và cần dấu vết kiểm toán. Nhiều vòng lặp dùng cả hai: file làm bộ nhớ làm việc, bảng làm hộp thư hướng tới người.
Làm sao tôi biết vòng lặp của mình sẽ thật sự chạy tiếp?
Chạy bài kiểm tra phục hồi. Tắt phiên giữa chừng, chạy lại vòng lặp từ đầu, và xác nhận nó tiếp tục từ các đơn vị passed thay vì khởi động lại từ cái đầu tiên. Một tệp trạng thái bạn chưa tắt-rồi-chạy-lại là chưa được chứng minh, dù lược đồ trông sạch tới đâu.
Đọc tiếp
- Điều kiện dừng: để “xong” có nghĩa. Phần 3, tiền truyện trực tiếp. Một mục
passedchỉ tốt ngang với cái kiểm chứng đã đặt nó. - Agent AI của bạn quên sạch. Đây là cách sửa.. Nửa bộ-nhớ-dự-án của chuyện quên, cái file bộ nhớ dự án mà bài này cố tình không nói về.
- Mẫu nhật ký thất bại trong CLAUDE.md. Một nhật ký thất bại do người biên tập, và là anh em hữu ích với ngăn
triedmáy-phục-hồi-được ở đây.