# Quy Trình 02A — Điều Phối Translator (V4)

> **Dành cho Architect** — thiết lập & giám sát cron job điều phối translator
> Cập nhật: 2026-07-23 | Phiên bản: **4.0**
> Hỗ trợ 2 chế độ: **V3 (Vinaya)** — kệ/giảng giải + merge song ngữ | **V4 (Văn xuôi)** — thuần Việt, không merge

---

## I. NGUYÊN LÝ CỐT LÕI

### Cấu trúc chung

```
┌─────────────────────────────────────────────────────┐
│           TRANSLATOR PIPELINE 2-PHASE                │
│                                                      │
│  PHASE 1 (1 LLM call): exec python                   │
│    • Chọn batch pending từ progress.json             │
│    • Đọc source + SQLite glossary                    │
│    • Đọc context-summary batch trước                 │
│                                                      │
│  PHASE 2 (1 LLM call): GỌI TẤT CẢ TOOL CÙNG LÚC     │
│    • write → vi-only/{BATCH}-vi.md                   │
│    • write → context-summary/{BATCH}-context-...     │
│    • [V3 only] exec → merge-bilingual.py             │
│    • exec → update progress.json                     │
│    ⚠️ KHÔNG text report riêng!                       │
│                                                      │
│  🌉 context-summary nối câu đứt giữa batch           │
└─────────────────────────────────────────────────────┘
```

### Hai chế độ

| | V3 (Vinaya) | V4 (Văn xuôi) |
|---|---|---|
| **Dự án mẫu** | 002-vinaya | 014-alahan-srilanka |
| **Nội dung** | Kệ + giảng giải | Văn xuôi liên tục |
| **Separator** | `---` giữa block | `## PAGE X` markers |
| **Merge** | Có (merge-bilingual.py) | Không |
| **Output** | Việt only + song ngữ | Chỉ Việt only |
| **Tools Phase 2** | 4 (vi + ctx + merge + progress) | 3 (vi + ctx + progress) |

### Prompt tối ưu

> ⚠️ **KHÔNG nhúng quy tắc dịch vào prompt** — chỉ reference guide path.
> Mọi quy tắc format, thuật ngữ, văn phong đã có trong guide riêng của dự án.
> Prompt chỉ chứa: guide references + Phase 1 script + Phase 2 tool calls.

---

## II. THIẾT LẬP CHUNG

### 📋 Progress JSON

```json
{
  "batches": [
    {"name": "batch-01", "source": "source/batch-01.md", "status": "pending"},
    {"name": "batch-02", "source": "source/batch-02.md", "status": "pending"}
  ]
}
```

> Tạo bằng script scan thư mục source:
> ```python
> import json, os, re
> files = sorted(os.listdir('source/'), key=lambda x: int(re.search(r'(\d+)', x).group(1)))
> batches = [{'name': f.replace('.md',''), 'source': f'source/{f}', 'status': 'pending'} for f in files]
> ```

### 🔧 CLI chuẩn

```bash
openclaw cron add \
  --name <project>-translator \
  --every 15m \
  --session isolated \
  --agent translator \
  --model openrouter/google/gemini-3-flash-preview \
  --light-context true \
  --best-effort-deliver \
  --message '...' \
  --timeout-seconds 600 \
  --announce --channel telegram --to <chat_id>
```

> ⚠️ **BẮT BUỘC:** `--best-effort-deliver` — tránh delivery retry loop làm kẹt session cron.

---

## III. CHẾ ĐỘ V3 — VINAYA (Kệ + Giảng giải + Merge)

> Dành cho sách Luật tạng: có kệ (verse) + giảng giải (commentary), ngăn cách bằng `---`.
> Output: vi-only + song ngữ merge.

### Cron prompt mẫu (Vinaya)

```bash
openclaw cron add \
  --name vinaya-translator \
  --every 15m \
  --session isolated \
  --agent translator \
  --model openrouter/google/gemini-3-flash-preview \
  --light-context true \
  --best-effort-deliver \
  --message 'Bạn là Translator Agent dịch Vinaya Myanmar → Việt.

Đọc guide:
1. /home/tuan-nguyen/.openclaw/workspace/obsidian/huong-dan/Translator-Myanmar-Vi.md
2. <PROJECT>/guide/TRANSLATOR-GUIDE-VINAYA.md

⚠️ Format Việt only: **XX. Kệ** + giảng giải + --- (giữ đúng số lượng separator)

[PHASE 1 SCRIPT — xem Mục VI bên dưới]

═══════════════════
PHASE 2: 1 RESPONSE — GỌI TẤT CẢ TOOL CÙNG LÚC
═══════════════════

📢 OUTPUT CHỈ ĐÚNG 1 DÒNG, không bullet, không danh sách:
   "BATCH: {tên batch} đã hoàn tất."

🚫 TUYỆT ĐỐI KHÔNG thêm text, bullet points.
🚫 TUYỆT ĐỐI KHÔNG gọi sessions_send hoặc bất kỳ messaging tool nào. Cron tự announce.

Nếu Phase 1 in "ALL_DONE":
  → GỌI TOOL cron action="remove", jobId="<JOB_ID>"
  → ⚠️ Thay <JOB_ID> bằng Job ID thật của cron này (lấy lúc tạo cron)
  → Toàn bộ batch đã xong, cron tự hủy.
Nếu Phase 1 in "SKIP:" / "STUCK:" → KHÔNG làm gì.

Khi Phase 1 in "BATCH:{tên}" và "SOURCE:{đường_dẫn}":
   → Đọc SOURCE (giữa ---SOURCE_START--- và ---SOURCE_END---)
   → Đọc GLOSSARY đã load

   🔗 CÂU ĐỨT TỪ BATCH TRƯỚC:
     Nếu "TRUNCATED_PREV:{name}" (có câu đứt) → GHÉP câu đứt VÀO ĐẦU SOURCE trước khi dịch
     Nếu "TRUNCATED_PREV:NONE" → dịch bình thường

   → Dịch TOÀN BỘ sang Việt theo guide, giữ đúng số lượng --- separators

   🔗 CÂU ĐỨT CUỐI BATCH:
     Nếu câu cuối source bị đứt (không có dấu kết thúc ။):
       → KHÔNG DỊCH câu đó, LƯU nguyên văn Myanmar vào context-summary
     Nếu câu hoàn chỉnh → dịch bình thường

   → GỌI ĐỒNG THỜI 4 TOOL (thay BATCH bằng tên thật):

   [1] write → vi-only/{BATCH}-vi.md (Việt only, giữ --- separators)

   [2] write → context-summary/{BATCH}-context-summary.md:
     # Tóm tắt Bối cảnh
     > Sau batch: {BATCH}
     ## Nội dung đã dịch đến nay
     [≤200 từ]
     ## Batch vừa hoàn thành
     - Nội dung chính: [1-2 câu]
     - Số block: X
     ## Câu đứt chuyển batch (nếu có)
     [Câu Myanmar bị đứt + [CÒN TIẾP] | hoặc "Không có"]

   [3] exec → merge-bilingual.py source + vi → merged

   [4] exec → update progress.json' \
  --timeout-seconds 600 \
  --announce --channel telegram --to 412242443
```

---

## IV. CHẾ ĐỘ V4 — VĂN XUÔI (Thuần Việt, không Merge)

> Dành cho sách văn xuôi: tiểu sử, lịch sử, không có kệ. Dùng `## PAGE X` markers.
> Output: **chỉ Việt only**, không merge song ngữ.

### Cron prompt mẫu (Văn xuôi)

```bash
openclaw cron add \
  --name <project>-translator-v2 \
  --every 15m \
  --session isolated \
  --agent translator \
  --model openrouter/google/gemini-3-flash-preview \
  --light-context true \
  --best-effort-deliver \
  --message 'Bạn là Translator Agent dịch sách Phật học Myanmar → Việt.

Đọc guide:
1. /home/tuan-nguyen/.openclaw/workspace/obsidian/huong-dan/Translator-Myanmar-Vi.md
2. <PROJECT>/guide/TRANSLATOR-GUIDE-<PROJECT>.md

⚠️ DỊCH THUẦN VIỆT — KHÔNG song ngữ. Giữ nguyên ## PAGE X markers.

[PHASE 1 SCRIPT — xem Mục VI bên dưới]

═══════════════════
PHASE 2: 1 RESPONSE — GỌI TẤT CẢ TOOL CÙNG LÚC
═══════════════════

📢 OUTPUT CHỈ ĐÚNG 1 DÒNG, không bullet, không danh sách:
   "BATCH: {tên batch} đã hoàn tất."

🚫 TUYỆT ĐỐI KHÔNG thêm text, bullet points.
🚫 TUYỆT ĐỐI KHÔNG gọi sessions_send hoặc bất kỳ messaging tool nào. Cron tự announce.

Nếu Phase 1 in "ALL_DONE":
  → GỌI TOOL cron action="remove", jobId="<JOB_ID>"
  → ⚠️ Thay <JOB_ID> bằng Job ID thật của cron này
  → Toàn bộ batch đã xong, cron tự hủy.
Nếu Phase 1 in "SKIP:" / "STUCK:" → KHÔNG làm gì.

Khi Phase 1 in "BATCH:{tên}" và "SOURCE:{đường_dẫn}":
   → Đọc SOURCE (giữa ---SOURCE_START--- và ---SOURCE_END---)
   → Đọc GLOSSARY đã load

   🔗 CÂU ĐỨT TỪ BATCH TRƯỚC:
     Nếu "TRUNCATED_PREV:{name}" (có câu đứt) → GHÉP câu đứt VÀO ĐẦU SOURCE trước khi dịch
     Nếu "TRUNCATED_PREV:NONE" → dịch bình thường

   → Dịch TOÀN BỘ sang Việt theo guide

   🔗 CÂU ĐỨT CUỐI BATCH:
     Nếu câu cuối source bị đứt (không có dấu kết thúc ။):
       → KHÔNG DỊCH câu đó, LƯU nguyên văn Myanmar vào context-summary
     Nếu câu hoàn chỉnh → dịch bình thường

   → GỌI ĐỒNG THỜI 3 TOOL (thay BATCH bằng tên thật):

   [1] write → vi-only/{BATCH}-vi.md (Việt only, giữ ## PAGE X)

   [2] write → context-summary/{BATCH}-context-summary.md:
     # Tóm tắt Bối cảnh
     > Sau batch: {BATCH}
     ## Nội dung đã dịch đến nay
     [≤200 từ]
     ## Batch vừa hoàn thành
     - Nội dung chính: [1-2 câu]
     - Số trang: X
     ## Câu bị đứt (chờ batch sau nối)
     [Câu Myanmar bị đứt + [CÒN TIẾP] | hoặc "Không có"]

   [3] exec → update progress.json' \
  --timeout-seconds 600 \
  --announce --channel telegram --to 412242443
```

### Cấu trúc thư mục V4

```
<project>/ver-2/Gemini-3-Flash/
├── progress.json
├── vi-only/           ← Chỉ thư mục này + context
└── context-summary/
```

### Guide riêng cho dự án văn xuôi

Mỗi dự án văn xuôi cần 1 file guide trong `<project>/guide/`:
- Format output (giữ `## PAGE X`)
- Thuật ngữ đặc thù của sách
- Quy tắc dịch Pāḷi, tên riêng, xử lý caption ảnh
- Mẫu context-summary

> 📖 Mẫu: `/home/tuan-nguyen/.openclaw/workspace/014-alahan-srilanka/guide/TRANSLATOR-GUIDE-ALAHAN.md`

---

## V. SQLITE GLOSSARY

**Database:** `/home/tuan-nguyen/.openclaw/workspace/data/myanmar_pali_viet_terms_vec.db`

| Table | Columns |
|-------|---------|
| `terms` | `id, pali, myanmar, vietnamese, desc_vi, desc_pali, category` |

### Load trong Phase 1:
```python
import sqlite3
conn = sqlite3.connect('/home/tuan-nguyen/.openclaw/workspace/data/myanmar_pali_viet_terms_vec.db')
cur = conn.cursor()
cur.execute('SELECT pali, vietnamese, desc_vi, category FROM terms ORDER BY category, id')
glossary = [(r[0], r[1], r[2], r[3]) for r in cur.fetchall()]
```

---

## VI. PHASE 1 SCRIPT CHUẨN (DÙNG CHUNG)

Script này dùng cho cả V3 và V4 — chỉ khác phần kiểm tra output (V3 kiểm tra merged + vi, V4 chỉ kiểm tra vi).

### Cho V3 (Vinaya):

```python
import json, os, sqlite3
from datetime import datetime, timezone, timedelta

PROJ = "/home/tuan-nguyen/.openclaw/workspace-translator/002-vinaya"
VER = "ver-2/Gemini-3-Flash"
tz = timezone(timedelta(hours=7))

with open(f"{PROJ}/{VER}/progress.json") as f:
    data = json.load(f)

vi_dir = f"{PROJ}/{VER}/vi-only"
ctx_dir = f"{PROJ}/{VER}/context-summary"
merged_dir = f"{PROJ}/{VER}"

for b in data["batches"]:
    name = b["name"]
    vi_path = f"{vi_dir}/{name}-vi.md"
    merged_path = f"{merged_dir}/{name}-merged.md"
    ctx_path = f"{ctx_dir}/{name}-context-summary.md"
    if b["status"] == "in_progress":
        if os.path.exists(vi_path) and os.path.exists(merged_path) and os.path.exists(ctx_path):
            b["status"] = "done"
            b["completed_at"] = datetime.now(tz).isoformat()
            print(f"RECOVER:{name}")
        else:
            started = datetime.fromisoformat(b.get("started_at", "2000-01-01T00:00:00+07"))
            if (datetime.now(tz) - started).total_seconds() > 900:
                b["status"] = "pending"
                print(f"STUCK:{name}")
            else:
                print(f"SKIP:{name}")
                exit()
        with open(f"{PROJ}/{VER}/progress.json", "w") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)

target = None
for b in data["batches"]:
    name = b["name"]
    vi_path = f"{vi_dir}/{name}-vi.md"
    merged_path = f"{merged_dir}/{name}-merged.md"
    if b["status"] == "pending":
        target = b
        break
    if b["status"] != "done" and (not os.path.exists(vi_path) or not os.path.exists(merged_path)):
        target = b
        break

if target is None: print("ALL_DONE"); exit()

target["status"] = "in_progress"
target["started_at"] = datetime.now(tz).isoformat()
BATCH = target["name"]; SOURCE = target["source"]
print(f"BATCH:{BATCH}"); print(f"SOURCE:{SOURCE}")

with open(f"{PROJ}/{VER}/progress.json", "w") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

with open(f"{PROJ}/{SOURCE}") as f: source = f.read()

conn = sqlite3.connect("/home/tuan-nguyen/.openclaw/workspace/data/myanmar_pali_viet_terms_vec.db")
cur = conn.cursor()
cur.execute("SELECT pali, vietnamese, desc_vi, category FROM terms ORDER BY category, id")
glossary = [(r[0], r[1], r[2], r[3]) for r in cur.fetchall()]
conn.close()
print(f"GLOSSARY:{len(glossary)} terms loaded")
for pali, vi, desc, cat in glossary:
    print(f"  [{cat}] {pali} → {vi}" + (f" | {desc}" if desc else ""))

prev_idx = next((i-1 for i, b in enumerate(data["batches"]) if b["name"] == BATCH), None)
if prev_idx is not None and prev_idx >= 0:
    prev_name = data["batches"][prev_idx]["name"]
    prev_ctx_path = f"{PROJ}/{VER}/context-summary/{prev_name}-context-summary.md"
    if os.path.exists(prev_ctx_path):
        with open(prev_ctx_path) as f: prev_ctx = f.read()
        if "## Câu" in prev_ctx and "[CÒN TIẾP]" in prev_ctx:
            idx = prev_ctx.index("## Câu")
            print(f"TRUNCATED_PREV:{prev_name}"); print(prev_ctx[idx:].strip())
        else: print("TRUNCATED_PREV:NONE")
    else: print("TRUNCATED_PREV:NONE (no context file)")
else: print("TRUNCATED_PREV:NONE (first batch)")

print("---SOURCE_START---"); print(source); print("---SOURCE_END---")
print(f"SEPARATORS:{source.count(chr(10)+chr(45)*3+chr(10))}")
```

### Cho V4 (Văn xuôi):

```python
import json, os, sqlite3
from datetime import datetime, timezone, timedelta

PROJ = "/home/tuan-nguyen/.openclaw/workspace/014-alahan-srilanka"
VER = "ver-2/Gemini-3-Flash"
tz = timezone(timedelta(hours=7))

with open(f"{PROJ}/{VER}/progress.json") as f:
    data = json.load(f)

vi_dir = f"{PROJ}/{VER}/vi-only"
ctx_dir = f"{PROJ}/{VER}/context-summary"

for b in data["batches"]:
    name = b["name"]
    vi_path = f"{vi_dir}/{name}-vi.md"
    ctx_path = f"{ctx_dir}/{name}-context-summary.md"
    if b["status"] == "in_progress":
        if os.path.exists(vi_path) and os.path.exists(ctx_path):
            b["status"] = "done"
            b["completed_at"] = datetime.now(tz).isoformat()
            print(f"RECOVER:{name}")
        else:
            started = datetime.fromisoformat(b.get("started_at", "2000-01-01T00:00:00+07"))
            if (datetime.now(tz) - started).total_seconds() > 900:
                b["status"] = "pending"
                print(f"STUCK:{name}")
            else:
                print(f"SKIP:{name}")
                exit()
        with open(f"{PROJ}/{VER}/progress.json", "w") as f:
            json.dump(data, f, indent=2, ensure_ascii=False)

target = None
for b in data["batches"]:
    name = b["name"]
    vi_path = f"{vi_dir}/{name}-vi.md"
    if b["status"] == "pending":
        target = b; break
    if b["status"] != "done" and not os.path.exists(vi_path):
        target = b; break

if target is None: print("ALL_DONE"); exit()
    exit()

target["status"] = "in_progress"
target["started_at"] = datetime.now(tz).isoformat()
BATCH = target["name"]; SOURCE = target["source"]
print(f"BATCH:{BATCH}"); print(f"SOURCE:{SOURCE}")

with open(f"{PROJ}/{VER}/progress.json", "w") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

with open(f"{PROJ}/{SOURCE}") as f: source = f.read()

conn = sqlite3.connect("/home/tuan-nguyen/.openclaw/workspace/data/myanmar_pali_viet_terms_vec.db")
cur = conn.cursor()
cur.execute("SELECT pali, vietnamese, desc_vi, category FROM terms ORDER BY category, id")
glossary = [(r[0], r[1], r[2], r[3]) for r in cur.fetchall()]
conn.close()
print(f"GLOSSARY:{len(glossary)} terms loaded")
for pali, vi, desc, cat in glossary:
    print(f"  [{cat}] {pali} → {vi}" + (f" | {desc}" if desc else ""))

prev_idx = next((i-1 for i, b in enumerate(data["batches"]) if b["name"] == BATCH), None)
if prev_idx is not None and prev_idx >= 0:
    prev_name = data["batches"][prev_idx]["name"]
    prev_ctx_path = f"{PROJ}/{VER}/context-summary/{prev_name}-context-summary.md"
    if os.path.exists(prev_ctx_path):
        with open(prev_ctx_path) as f: prev_ctx = f.read()
        if "## Câu bị đứt" in prev_ctx and "[CÒN TIẾP]" in prev_ctx:
            idx = prev_ctx.index("## Câu bị đứt")
            print(f"TRUNCATED_PREV:{prev_name}"); print(prev_ctx[idx:].strip())
        else: print("TRUNCATED_PREV:NONE")
    else: print("TRUNCATED_PREV:NONE (no context file)")
else: print("TRUNCATED_PREV:NONE (first batch)")

print("---SOURCE_START---"); print(source); print("---SOURCE_END---")
print(f"PAGE_COUNT:{source.count('## PAGE')}")
```

---

## VII. PHASE 2 — TOOL CALLS (MẪU)

### 🌉 Bridge: Xử lý câu đứt giữa batch

```
🔗 CÂU ĐỨT TỪ BATCH TRƯỚC:
  Nếu Phase 1 in "TRUNCATED_PREV:{name}" → GHÉP câu đứt vào ĐẦU SOURCE trước dịch
  Nếu "TRUNCATED_PREV:NONE" → dịch bình thường

🔗 CÂU ĐỨT CUỐI BATCH:
  Nếu câu cuối source bị đứt (không có dấu kết thúc ။):
    → KHÔNG DỊCH câu đó, LƯU nguyên văn Myanmar vào context-summary mục "Câu đứt"
  Nếu câu hoàn chỉnh → dịch bình thường, context-summary ghi "Không có"
```

### V3 (Vinaya — 4 tools):

```
[TOOL 1] write → vi-only/{BATCH}-vi.md (Việt only + --- separators)
[TOOL 2] write → context-summary/{BATCH}-context-summary.md
          (có mục ## Câu đứt chuyển batch (nếu có): [Myanmar] + [CÒN TIẾP])
[TOOL 3] exec → merge-bilingual.py source + vi → merged
[TOOL 4] exec → update progress.json
```

### V4 (Văn xuôi — 3 tools):

```
[TOOL 1] write → vi-only/{BATCH}-vi.md (Việt only, giữ ## PAGE X)
[TOOL 2] write → context-summary/{BATCH}-context-summary.md
          (có mục ## Câu bị đứt (chờ batch sau nối): [Myanmar] + [CÒN TIẾP])
[TOOL 3] exec → update progress.json
```

> ⚠️ Chung: GỌI TẤT CẢ TOOL CÙNG 1 RESPONSE.
> 🚫 **TUYỆT ĐỐI KHÔNG** gọi `sessions_send` hoặc bất kỳ messaging tool nào. Cron tự announce.

---

## VIII. SPAWN THỦ CÔNG (DỰ PHÒNG)

Khi cron bị kẹt hoặc cần chạy batch riêng:

```js
sessions_spawn({
  agentId: "translator",
  task: "Đọc guide... Dịch batch X. Source: <path>. Context trước: <path>.\nGọi ĐỒNG THỜI: write vi-only + write context-summary + exec update progress.",
  model: "openrouter/google/gemini-3-flash-preview",
  mode: "run"
})
```

---

## IX. XỬ LÝ SỰ CỐ

### 1. Cron announce lặp cùng 1 batch >2 lần

**Triệu chứng:** Cùng batch re-announce liên tục, cron không chạy batch mới.
**Nguyên nhân:** Delivery retry loop → session bị kẹt.

**Cách fix:**
```bash
# Xóa cron cũ + tạo lại với --best-effort-deliver
openclaw cron rm <old-id>
openclaw cron add --best-effort-deliver ... (tạo mới)
```

### 2. Session claim error

**Triệu chứng:** `CronSessionLifecycleClaimError: Session changed while starting work`
**Nguyên nhân:** Run cũ chưa release session.

**Cách fix:**
```bash
openclaw cron disable <id> && sleep 2 && openclaw cron enable <id>
openclaw cron run <id>  # force-run
```

### 3. Token spike — sessions_send loop

**Triệu chứng:** 1 run đột ngột tiêu thụ 3-5M tokens (bình thường ~60K).
**Nguyên nhân:** Translator gọi `sessions_send` lặp ~29 lần để tự deliver kết quả, mỗi lần fail → retry → đốt token.

**Cách fix:** Thêm dòng cấm vào Phase 2 prompt:
```
🚫 TUYỆT ĐỐI KHÔNG gọi sessions_send hoặc bất kỳ messaging tool nào. Cron tự announce.
```

> ⚠️ Tần suất: ~2/100 run với Gemini 3 Flash. Thiệt hại ~$2-4/run.

### 4. Batch stuck in_progress

Script Phase 1 tự detect batch in_progress >15 phút → reset về pending. Không cần can thiệp thủ công.

---

## X. GIÁM SÁT CHI PHÍ

| Dự án | Batch | Token/batch | Model | Cost/batch | Tổng |
|-------|-------|------------|-------|-----------|------|
| Vinaya V3 | 22 | ~55K | Gemini 3 Flash | ~$0.04 | ~$0.84 |
| Alahan V4 | 221 | ~50K | Gemini 3 Flash | ~$0.04 | ~$8.84 |

**Giá OpenRouter Gemini 3 Flash Preview:** $0.50/M input | $3.00/M output

---

## XI. TỔNG KẾT

```
V4 — 2-PHASE PIPELINE + 2 CHẾ ĐỘ

  ⚙️ CHUNG                          📋 V3 (Vinaya)            📖 V4 (Văn xuôi)
  ┌────────────────────┐            ┌─────────────────┐      ┌──────────────────┐
  │ PHASE 1: Python    │            │ Kệ + giảng giải │      │ Văn xuôi liên tục│
  │  • progress scan   │            │ --- separators  │      │ ## PAGE X markers│
  │  • glossary load   │            │ 4 tools Phase 2 │      │ 3 tools Phase 2  │
  │  • context bridge  │            │ Có merge script │      │ Thuần Việt       │
  ├────────────────────┤            └─────────────────┘      └──────────────────┘
  │ PHASE 2: 1 RESP.   │
  │  • write vi-only   │
  │  • write ctx-sum   │
  │  • exec update     │
  │ ⚠️ NO text report! │
  │ ~2 LLM calls/batch │
  └────────────────────┘
```

> **Bài học từ Alahan (2026-07-23):**
> 1. `--best-effort-deliver` **bắt buộc** để tránh delivery retry loop
> 2. Prompt **chỉ chứa operational logic** — mọi quy tắc dịch để trong guide
> 3. Dự án văn xuôi cần guide riêng với thuật ngữ đặc thù
> 4. Khi cron kẹt → xóa + tạo lại, không cố sửa
> 5. Model: `openrouter/google/gemini-3-flash-preview` (qua OpenRouter)
> 6. **Cấm `sessions_send`** trong Phase 2 — Gemini 3 Flash đôi khi hallucinate gọi messaging tool loop (29 lần/run, ~$2-4/run)

> 📖 **File liên quan:**
> - `ver-2/Gemini-3-Flash/merge-bilingual.py` — Script merge (V3 only)
> - `guide/TRANSLATOR-GUIDE-*.md` — Guide đặc thù từng dự án
> - `obsidian/huong-dan/Translator-Myanmar-Vi.md` — Guide chung Myanmar→Việt
