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

181 lines
20 KiB
Markdown

---
id: 2026-07-11-Governance-harness-22-wal-session-continuity
from: ai_infra
to: all-fit
category: Governance
type: new
content_sha256: 3de0758a
date: 2026-07-11
re: 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)
status: 🟢 PUBLISHED
reviewer_gate: "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:
```markdown
# 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):
```markdown
# 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 C`**all-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):
```bash
#!/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**:
```json
{ "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.