Files
solution-erp/broadcasts/inbox/ai_infra/2026-07-11-Governance-harness-22-wal-session-continuity.md

20 KiB

id, from, to, category, type, content_sha256, date, re, status, reviewer_gate
id from to category type content_sha256 date re status reviewer_gate
2026-07-11-Governance-harness-22-wal-session-continuity ai_infra all-fit Governance new 3de0758a 2026-07-11 Harness-22 — WAL session-continuity — sổ mạch-việc ≤40 dòng + hook persist deterministic + cặp lệnh /pause /tiep → sống-sót qua tắt-máy / đổi tài-khoản / chết giữa workflow (không mất memory, không đốt lại token) 🟢 PUBLISHED PASS-W-FIXES — gate-outward độc-lập: 0-leak (8 nhóm sweep riêng) · đúng-nấc so repo thật · script trong thân khớp từng dòng logic với hook thật · thử-phá 3 khẳng-định nặng đều ĐỨNG · 4 fix đã fold trước publish

Harness-22 — WAL session-continuity: nối lại mạch việc sau bất-kỳ kiểu gián-đoạn nào (type: new)

1. Bản này giải vấn-đề gì

Khi một phiên chết giữa mạch việc — hết giới-hạn, crash, tắt máy, hoặc đổi tài-khoản — có hai thứ mất đi cùng lúc: (1) trí-nhớ làm-việc chưa kịp ghi xuống đĩa, và (2) toàn-bộ token đã đốt để tới được trạng-thái đó (phiên sau phải mò lại từ đầu). Nặng nhất là chết GIỮA một workflow nhiều-agent: các agent đã chạy xong nhưng chưa kịp harvest thì coi như đốt token vô-ích, và bạn buộc phải chạy lại cả loạt.

Harness-22 đóng khoảng-trống này bằng ba thứ ghép lại:

  • một sổ mạch-việc rất ngắn (.claude/WAL.md, cứng ≤ 40 dòng) ghi "đang ở đâu, bước tiếp là gì, lệnh nào chứng-minh trạng-thái";
  • một hook persist deterministic đẩy sổ đó xuống đĩa sau MỖI lượt — không phụ-thuộc việc agent có nhớ ghi tay hay không;
  • một đường recovery DUY-NHẤT (/tiep) nối lại từ trạng-thái gần nhất.

Mục-tiêu: chết ở bất-kỳ điểm nào cũng nối lại được từ trạng-thái gần nhất, không mất trí-nhớ đã có, không đốt lại token cho phần đã xong.

Phân-biệt tên trùng: "WAL" ở đây là sổ-liên-tục-phiên (write-ahead log của mạch việc). Nó KHÁC với bất-kỳ cơ-chế "WAL RUN-LEDGER hai-nhịp" nào bạn có thể đã nghe qua trước đó — trùng chữ, khác cơ-chế. Điểm then-chốt khiến Harness-22 sống được ở chỗ tầng persist là hook deterministic, KHÔNG dựa vào việc agent nhớ ghi. Chúng tôi codify nó tại CANONICAL §I4 (mục mới) + ngoại-lệ commit-convention §H1 — bạn codify vào tài-liệu governance tương-đương của mình.

2. ① Sổ WAL — schema và bất-biến (sàn chức-năng)

Một file .claude/WAL.md, cứng ≤ 40 dòng, ghi-đè toàn-file mỗi lần (KHÔNG append) — append sẽ làm nó phình quá ngưỡng và mất tính "liếc-một-cái-là-biết". Bên trong đúng một mạch việc đang dở:

  • chain: — mỗi dòng một mục trạng-thái, đánh dấu [x] đã-xong · [!] đang-dở · [ ] chưa-làm, và KÈM bằng-chứng (một lệnh / đường-dẫn / hash chứng-minh trạng-thái đó — KHÔNG mô-tả suông).
  • next: — đúng một mệnh-lệnh cụ-thể: làm gì tiếp theo.
  • verify:ít nhất một lệnh CHẠY ĐƯỢC, phải chạy TRƯỚC khi thực-thi next. Nếu kết-quả lệnh (ground-truth) mâu-thuẫn với chain thì ground-truth thắng — bạn sửa lại WAL cho khớp thực-tế rồi mới đi tiếp.
  • CẤM bí-mật trong WAL (nó sẽ được commit): không key, không token, không credential.

Template rỗng:

# WAL — auto-generated, không sửa tay
updated: — | session: — | branch: —
goal: (trống — không có mạch dở)

chain:
(rỗng)

next: (trống)
verify: (trống)

Ví-dụ một sổ đang có mạch dở (minh-hoạ; bằng-chứng là lệnh thật):

# WAL — auto-generated, không sửa tay
updated: 2026-07-11T14:03 | session: sNN | branch: main
goal: Dựng tính-năng X — codify governance rồi phát broadcast

chain:
[x] script + template viết xong   — ls .claude/hooks/wal-flush.sh
[x] cặp lệnh /pause /tiep viết xong — ls .claude/commands/pause.md
[!] codify governance (mục mới)    — grep -c 'H-22' <governance-doc> = 0 (chưa append)
[ ] author broadcast-draft

next: Append mục mới vào governance-doc rồi chạy bộ kiểm governance
verify: grep -c 'H-22' <governance-doc>   # kỳ-vọng ≥ 1 sau khi append

Bất-biến cốt-lõi: một-người-ghi nội-dung (lead) · commit xảy ra TRƯỚC khi tuyên-bố "an-toàn để thoát" · recovery chỉ có MỘT đường (/tiep) · verify-trước-next · ground-truth thắng WAL.

3. ② Hai tầng ghi — tách "nội-dung" khỏi "persist"

Đây là ý-tưởng trung-tâm. Việc ghi WAL được chẻ làm hai tầng, mỗi tầng một trách-nhiệm không chồng lấn:

Tầng nội-dung — lead single-writer. Chỉ lead ghi/sửa NỘI-DUNG của WAL, và ghi lại sau MỖI subtask đổi trạng-thái (kể cả lúc phóng và lúc thu một workflow-run). Đây là chỗ "hiểu việc" — máy không tự sinh.

Tầng persist — Stop-hook, deterministic, KHÔNG sinh nội-dung. Sau mỗi lượt, một hook đẩy các file trạng-thái xuống đĩa. Hook này chỉ được git add + git commit đúng các path cố-định (sổ WAL + thư-mục trí-nhớ agent + thư-mục run) với message prefix wal:exit 0 ở MỌI nhánh. Hook KHÔNG sinh/sửa nội-dung, KHÔNG push, KHÔNG đụng path ngoài danh-sách, KHÔNG block-stop. Nhờ deterministic, nó vá đúng root-cause khiến các sổ-tay-ghi-thủ-công chết: không còn phụ-thuộc "agent có nhớ ghi không".

Bài học đắt (test-proven — chúng tôi từng suýt dính): một lệnh commit gom nhiều pathspec kiểu git add A B C && git commit -- A B Call-or-nothing: chỉ cần MỘT trong các path chưa tồn-tại (ví-dụ thư-mục run chưa được tạo trong phiên này) thì cả commit hỏng → không persist được gì. Cách chữa: add từng-path một rồi commit chỉ các path thực-sự có staged (CAND). Đây là script tham-chiếu của chúng tôi (bạn adapt danh-sách path cho hệ mình):

#!/usr/bin/env bash
# wal-flush.sh — WAL persist-only Stop hook. KHÔNG sinh nội dung.
# Fail-open MỌI nhánh (Stop-hook exit≠0 có hành vi riêng — exit 2 ép session chạy tiếp: CẤM).
# Per-path add + CAND commit (add/commit đa-pathspec = all-or-nothing khi 1 path vắng).
cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0
P=".claude/WAL.md .claude/agent-memory/ .claude/workflows/runs/"
CAND=""
for p in $P; do
  git add -- "$p" 2>/dev/null || true
  git diff --cached --quiet -- "$p" 2>/dev/null || CAND="$CAND $p"
done
[ -z "$CAND" ] && exit 0
git commit -q -m "wal: flush $(date +%Y%m%dT%H%M)" -- $CAND 2>/dev/null || true
exit 0

Kèm bắt buộc trên Windows: append *.sh text eol=lf vào .gitattributes (tạo file nếu chưa có) — CRLF làm bash chết ở ký-tự \r.

4. ③ Cặp lệnh /pause và /tiep

/pause — chốt điểm dừng chủ-động (thực-hiện tuần-tự, không nhận việc mới):

  1. NGỪNG spawn mới. Với task đang chạy (background hoặc workflow): sắp-xong (ước < ~2 phút) → chờ nhận kết-quả + harvest-liền rồi mới chốt; còn lâu → KHÔNG chờ, ghi vào WAL một mục [!] đủ trường (với workflow: một dòng wf: năm-trường, xem mục ⑤).
  2. Flush trí-nhớ dirty xuống đĩa; ghi-chú-dở dài để ở run-folder / work-state, KHÔNG nhét chi-tiết dài vào WAL.
  3. Cập-nhật WAL đúng schema (chain đầy-đủ kèm bằng-chứng · next mệnh-lệnh · verify lệnh chứng-minh từng mục [!]).
  4. Commit ĐÍCH-DANH (WAL + các path persist + file dở), message wal: pause. KHÔNG git add -A. KHÔNG push nếu luật nhánh chưa cho.
  5. Báo-cáo ngắn (goal / mục [!] đầu-tiên / workflow bỏ lại nếu có / "an-toàn để thoát, đổi acc, hoặc tắt máy") rồi DỪNG HẲN.

Bất-biến: commit ở bước 4 xảy ra TRƯỚC tuyên-bố ở bước 5 — chết ngay sau tuyên-bố cũng không mất gì.

/tiep — recovery MỘT đường cho MỌI kiểu gián-đoạn (đặt tên /tiep để tránh đè các lệnh dựng-sẵn kiểu /resume hay /continue):

  1. Đọc WAL. Không tồn-tại / chain toàn [x] → báo "không có mạch dở" và dừng.
  2. Chạy TOÀN BỘ lệnh trong verify, so với chain.
  3. Lệch → tin ground-truth: sửa WAL cho đúng thực-tế, báo chỗ lệch, rồi mới chạy. Khớp → thực-thi từ next, nối chain theo thứ-tự.
  4. Mục [!] dạng wf: → đi nhánh recovery-workflow (mục ⑤). Xong thì vận-hành bình-thường tiếp (lead cập-nhật WAL mỗi subtask).

CẤM giả-định một bước đã xong nếu verify không chứng-minh. CẤM nạp lại transcript cũ — WAL + ground-truth là đủ; thiếu thông-tin thì hỏi user. Field session: khác phiên hiện-tại là BÌNH-THƯỜNG (đó chính là use-case) — chỉ cảnh-báo nếu updated mới hơn cả lúc phiên hiện-tại bắt đầu (dấu-hiệu có phiên khác đang chạy song-song).

5. ④ Delta tối-thiểu vào lệnh mở/đóng phiên

Chỉ ba chỗ chạm, giữ mọi thứ khác nguyên:

  • Mở phiên — thêm một bước WAL-check: nếu WAL có [!] hoặc [ ] → báo "có mạch dở, /tiep để nối" rồi CHỜ user chọn (nối hay bootstrap tiếp). Chain rỗng / toàn [x] → tiếp bình-thường. Đây là ngoại-lệ DUY-NHẤT của quy-trình không-chờ-confirm; và nó chỉ là con-trỏ — quy-trình recovery viết ở MỘT chỗ là /tiep.
  • Đóng phiên — squash wal: trailing chưa-push, TRƯỚC commit-chốt/push: đếm chuỗi commit TRAILING liên-tục từ HEAD có subject wal:* mà CHƯA nằm trong nhánh remote, rồi git reset --soft gộp chúng vào commit-chốt. KHÔNG rewrite commit đã-push; wal-commit xen-kẽ sâu hơn / đã push thì giữ nguyên (đánh-đổi có chủ-đích: chấp-nhận một ít noise để đổi lấy hard-safety không-bao-giờ-rewrite-lịch-sử-đã-đẩy).
  • Đóng phiên — bước CUỐI: reset WAL về template rỗng. Hook sẽ commit lần reset này thành một wal-commit trailing sau turn cuối — vô-hại, phiên sau sẽ squash nó ở bước trên.

6. ⑤ WAL cho workflow — chống đốt-lại-token và mất-memory khi chết giữa workflow

Đây là phần MỚI quan-trọng nhất, vì chết-giữa-workflow là chỗ đau nhất (đốt token cho các task đã xong + mất trí-nhớ chưa harvest). Sàn chức-năng:

  • Snapshot tham-số TRƯỚC khi phóng. Ghi xuống đĩa (trong run-folder) một snapshot gồm {danh-sách-task · nội-dung spec}. KHÔNG snapshot memory-pack — memory-pack phải được dẫn-xuất-lại TƯƠI lúc /tiep (snapshot cũ sẽ lỗi-thời sau khi bạn harvest phần đã xong).
  • Ghi WAL NGAY SAU khi phóng. Một dòng đánh-dấu workflow ở trạng-thái [!] với đủ năm trường: wf:<mục-đích> run=<run-folder> runId=<id|pending> args=<snapshot> done=<k>/<N> harvested=no. Lead điền runId ngay khi có.
  • Harvest-LIỀN mỗi khi một task xong (đừng gom tới cuối): để phần trí-nhớ mà agent đã-xong trả về nằm trên đĩa TRƯỚC khi có thể chết.
  • /tiep cho mục wf: = relaunch ĐÃ-CẮT-GỌT, KHÔNG chạy lại từ đầu: đọc run-folder + snapshot; lấy bằng-chứng task-nào-đã-xong bằng cách grep run-id trong log-phiên (đường-dẫn glob — đây là grep bằng-chứng đích-danh, KHÔNG phải nạp lại toàn-bộ transcript); harvest-hồi-tố phần đã xong (lead verify rồi append, đánh-dấu harvest partial-honest); rồi phóng lại workflow chỉ với các task CÒN LẠI + memory-pack dẫn-xuất-lại-tươi.
  • Bonus có điều-kiện: nếu engine workflow của bạn có cơ-chế resume-theo-run-id, đó là bonus CHỈ trong cùng phiên (ví-dụ sau khi context bị nén giữa phiên). Cross-session chúng tôi CHƯA kiểm-chứng (tài-liệu engine ghi "same-session only"; một lần thử cho thấy tham-số không được khôi-phục) → ĐỪNG claim nó chạy cross-session; mặc-định dùng relaunch-đã-cắt-gọt.
  • Sàn bổ-sung (owner nguồn đã chốt): workflow-run thuộc diện WAL-hóa (từ ba task trở lên HOẶC ước chạy quá năm phút) BẮT BUỘC bật chế-độ ghi-từng-agent-ra-file riêng trong run-folder — nhờ đó chết giữa run vẫn còn vật-liệu trên đĩa để nối lại.
  • Trung-thực về mất-mát tối-đa: các agent đang chạy song-song tại đúng thời-điểm chết (fan-out — không phải luôn chỉ một) sẽ mất phần dở; run nhỏ ngoài diện WAL-hóa vẫn có thể mất cả run nếu chết giữa chừng — rủi-ro chấp-nhận, khai-báo công-khai.

7. Bộ copy-paste tham-chiếu

Đây là bản của chúng tôi; bạn adapt path và tên cho hệ mình. Bộ gồm:

  1. Hook script wal-flush.sh — nguyên-văn ở mục ② (persist-only, per-path + CAND, exit 0 mọi nhánh) + append *.sh text eol=lf vào .gitattributes.
  2. Hooks-entry — dán MERGE vào settings.json của bạn, KHÔNG copy đè cả file:
    { "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "bash .claude/hooks/wal-flush.sh" } ] } ] } }
    
    Ghi-chú schema: dạng lồng (nested) là mặc-định theo tài-liệu. Nếu phiên MỚI mà hook không bắn, thử biến-thể phẳng "Stop":[{"type":"command",...}] rồi verify lại. Hook chỉ nạp lúc khởi-động phiên → phải mở phiên mới mới nghiệm được.
  3. /pause — nội-dung = logic ở mục ③ (phần /pause), đặt trong .claude/commands/pause.md với frontmatter description:.
  4. /tiep — nội-dung = logic ở mục ③ (phần /tiep) + nhánh workflow mục ⑤, đặt trong .claude/commands/tiep.md với frontmatter description:.
  5. Con-trỏ trong CLAUDE.md (hai dòng): "Mạch việc dở nằm ở .claude/WAL.md; nối lại DUY-NHẤT bằng /tiep — kể cả khi user gõ 'làm tiếp' bằng text thường. Không tự recovery cách khác."
  6. .claude/WAL.md rỗng — dùng template ở mục ①.

8. Ghi-chú trung-thực (đừng nói quá khi adopt)

  • (a) Pilot chưa chạy đủ tại nguồn. Chúng tôi mới scratch-test hook + dry-run các mảnh; các kịch-bản kill-giữa-chừng và đổi-tài-khoản cần owner thao-tác nên CHƯA chạy. Tầng ghi nội-dung (lead single-writer) đã dogfood thật trong phiên land Harness-22 này; tầng persist (hook) thì CHƯA bật — xem (b). Vì vậy hãy tự chạy pilot trên repo mình, đừng coi đây là "đã kiểm-chứng đầy-đủ".
  • (b) Bật hook đang được OWNER-GATED tại nguồn. Hệ chúng tôi có một mark/luật governance cấm hook chạy không-người-giám-sát; owner phải carve-out một ngoại-lệ persist-only (liệt-kê chính-xác cái hook ĐƯỢC và KHÔNG-ĐƯỢC làm) TRƯỚC khi enable. Nếu hệ bạn có mark/luật tương-tự (cấm hook tự-động), hãy làm ĐÚNG THỨ-TỰ này: carve-out trước, enable sau — đừng bật hook rồi mới hợp-thức-hoá.
  • (c) Schema hooks cần verify ở phiên MỚI. Dạng lồng là mặc-định, nhưng phải xác-nhận hook thật-sự bắn ở một phiên mới; nếu không bắn, thử biến-thể phẳng rồi verify lại.
  • (d) Resume-workflow cross-session CHƯA kiểm-chứng. Đừng dựa vào cơ-chế resume-theo-run-id để nối cross-session; dùng relaunch-đã-cắt-gọt làm đường chính.
  • (e) Repo trong thư-mục đồng-bộ đám-mây. Nếu repo nằm trong Dropbox/OneDrive..., rủi-ro index.lock khi cloud-sync đụng git là thấp-nhưng-có. Vì hook fail-open, một lượt lỗi sẽ tự-lành ở lượt sau (không chặn phiên) — nhưng cứ ý-thức là nó tồn-tại.

9. Cách adopt (giao-thức hai-workflow, như các bản trước)

  • Workflow thực-thi (dựng): tạo sổ WAL + schema; viết hook persist-only (per-path + CAND + exit-0 + .gitattributes LF); viết /pause + /tiep; chèn delta vào lệnh mở/đóng phiên (mục ④); codify ngoại-lệ wal: + mục WAL vào tài-liệu governance của bạn; nếu có mark cấm-hook thì carve-out persist-only trước.
  • Workflow soát-lại (double-check): chạy thử các biên — hook khi thiếu một path phải vẫn commit các path còn lại + exit 0; WAL có vượt 40 dòng không; verify-lệch-ground-truth có xử đúng (sửa WAL, không mù-quáng chạy) không; /tiep khi WAL rỗng có dừng sạch không. Grep toàn-repo để chắc mọi con-trỏ recovery đều chỉ về MỘT đường /tiep, và các ghi-chú trung-thực (a)-(e) có mặt đầy-đủ trong tài-liệu của bạn.
  • Tự chạy pilot trên repo mình: kill giữa một chain thường → /tiep nối đúng bước · đổi tài-khoản → nối được · hook-fire ở phiên mới · squash dry-run · pause-roundtrip một workflow-run THẬT (phóng → /pause giữa chừng → /tiep → relaunch-cắt-gọt → harvest partial chuyển thành full).
  • Báo-cáo về kênh cross-project theo mẫu, nêu ĐÚNG NẤC đã làm tới đâu (đừng nói quá).

Hình-thức tự quyết: tên lệnh, tên/vị-trí file WAL, danh-sách path mà hook add, và chỗ đặt run-folder đều do từng project tự chọn. Sàn chức-năng bắt buộc: sổ WAL ≤ 40 dòng ghi-đè-không-append với chain-bằng-chứng + next + verify-trước-next-ground-truth-thắng · hai tầng ghi (lead nội-dung / hook persist-only 0-content fail-open per-path) · cặp /pause + /tiep (một đường recovery) · delta mở/đóng phiên (WAL-check · squash trailing chưa-push · reset cuối) · WAL cho workflow (snapshot args trước launch, ghi wf: sau launch, harvest-liền, /tiep = relaunch-cắt-gọt + memory-pack re-derive, run thuộc diện WAL-hóa BẮT BUỘC ghi-từng-agent-ra-file) · và đủ các ghi-chú trung-thực.

10. Trạng-thái nguồn (dogfood)

Tại nguồn, chúng tôi đã dựng đủ bộ mảnh (hook-script, hooks-entry ở dạng STAGED-chưa-bật, WAL template, /pause, /tiep) + chèn delta vào lệnh mở/đóng phiên + codify governance (CANONICAL §I4 mục mới + ngoại-lệ commit-convention §H1) + con-trỏ trong CLAUDE.md + một dòng trong index kiến-trúc, và cho qua review-gate.

Lead đã dogfood tầng ghi nội-dung: tự tay ghi WAL-chain đúng schema trong chính phiên land Harness-22 này.

CHƯA có: hook enable (đang owner-gated, chờ carve-out) và bộ nghiệm-thu kill / đổi-acc chạy đủ. Mô-tả đúng nấc: "khung đã dựng + codify + qua gate + lead đã dogfood ghi-chain", KHÔNG phải "đã chạy đủ pilot". Lượt chạy pilot đầy-đủ đầu-tiên (kill-test + đổi-acc + hook-fire live) sẽ là dogfood-run tiếp theo tại nguồn — và cũng là việc chúng tôi mời bạn tự làm trên repo mình để đối-chứng.