# Quy Trình 02B — Điều Phối Translator Dịch Song Ngữ (Template)

> **Dành cho Architect** — template tái sử dụng cho mọi dự án dịch song ngữ
> Cập nhật: 2026-06-08 | Phiên bản: **1.0** (Việt only + merge tự động + 2-phase)

---

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

```
┌─────────────────────────────────────────────────────────────┐
│           KIẾN TRÚC V1 — TRANSLATOR 2-PHASE (GENERIC)        │
│                                                              │
│  TRANSLATOR AI (2 LLM CALLS)                                 │
│  ├─ PHASE 1 (1 call): exec python → chọn batch + source     │
│  │                    + SQLite glossary lookup               │
│  └─ PHASE 2 (1 call): write+write+exec+exec, TẤT CẢ CÙNG LÚC│
│                       vi-only + context-summary              │
│                       + merge-bilingual.py                   │
│                       + update progress                      │
│                                                              │
│  CHIẾN LƯỢC: Việt only + separator → merge tự động           │
│  lightContext: true → bỏ system prompt files                 │
└─────────────────────────────────────────────────────────────┘
```

### Tại sao Việt only + merge script?

| Trước (song ngữ trực tiếp) | Sau (Việt only + merge) | Lý do |
|-----|------|------|
| Translator tự copy source | Architect merge bằng script | Copy thủ công → sai lệch, thiếu dòng |
| Không verify block count | `assert src.count == out.count` | Bắt lỗi ngay, không để lọt |
| 1 file output | 3 files: vi-only + merged + context | Tách biệt, dễ QA từng bước |
| Format phụ thuộc translator | Merge script kiểm soát | Separator là mốc cứng |

---

## II. KIẾN TRÚC

```
┌──────────────────────────────────────────────────────┐
│                 ARCHITECT (giám sát)                  │
│  • 1 cron job translator-processor (every 15min)      │
│  • 1 cron job translator-monitor (every 30min)        │
│  • QA hậu kiểm: verify separator + format             │
│  • Cập nhật SQLite glossary khi cần                   │
└──────────────────────────────────────────────────────┘

┌──────────────────────────────────────────────────────┐
│       TRANSLATOR AI (2 LLM CALLS)                     │
│  ┌──────────────────────────────────────────┐        │
│  │ PHASE 1: exec python                      │        │
│  │   • Chọn batch pending từ progress JSON   │        │
│  │   • Đọc source                            │        │
│  │   • SQLite glossary lookup                │        │
│  │   → In source + glossary vào output       │        │
│  └──────────────────────────────────────────┘        │
│  ┌──────────────────────────────────────────┐        │
│  │ PHASE 2: ALL TOOLS IN 1 RESPONSE          │        │
│  │   • write → vi-only/{BATCH}-vi.md         │        │
│  │   • write → context-summary/{BATCH}-...   │        │
│  │   • exec → merge-bilingual.py             │        │
│  │   • exec → update progress JSON           │        │
│  │   ⚠️ KHÔNG tạo text report riêng!         │        │
│  └──────────────────────────────────────────┘        │
│  ⚡ 2 LLM CALLS, token tùy model                      │
└──────────────────────────────────────────────────────┘
```

---

## III. CHUẨN BỊ DỰ ÁN

### Bước 1: Xác định biến dự án

| Biến              | Mô tả                  | Ví dụ (Vinaya)                                |
| ----------------- | ---------------------- | --------------------------------------------- |
| `{PROJECT_DIR}`   | Thư mục gốc dự án      | `/home/.../002-vinaya`                        |
| `{OUTPUT_DIR}`    | Thư mục output chính   | `{PROJECT_DIR}/ver-2/Gemini-3-Flash`          |
| `{SOURCE_DIR}`    | Thư mục source         | `{PROJECT_DIR}/split`                         |
| `{GUIDE_CHUNG}`   | Guide dịch tổng quát   | `obsidian/huong-dan/Translator-Myanmar-Vi.md` |
| `{GUIDE_DAC_THU}` | Guide đặc thù dự án    | `{PROJECT_DIR}/TRANSLATOR-GUIDE-XXX.md`       |
| `{GLOSSARY_DB}`   | SQLite glossary path   | `data/myanmar_pali_viet_terms_vec.db`         |
| `{MERGE_SCRIPT}`  | Script merge bilingual | `{OUTPUT_DIR}/merge-bilingual.py`             |
| `{MODEL}`         | Model dịch             | `openrouter/google/gemini-3-flash-preview`    |
| `{AGENT}`         | Agent ID               | `translator`                                  |
| `{DELIVERY}`      | Cron delivery          | `announce,telegram,{CHAT_ID}`                 |

### Bước 2: Tạo merge script

Copy và điều chỉnh `merge-bilingual.py` từ dự án Vinaya, hoặc tạo script mới theo logic:
- Split source + translated bằng separator `\n---\n`
- Verify block count khớp
- Merge: `---` + Myanmar + `→` + Việt + `---`

### Bước 3: Tạo progress.json

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

### Bước 4: Tạo guide đặc thù (nếu cần)

Nếu dự án có thuật ngữ/format đặc thù → tạo `TRANSLATOR-GUIDE-{TEN}.md` trong `{PROJECT_DIR}/`.

### Bước 5: Tạo prompt mẫu

File `{OUTPUT_DIR}/translator-prompt.md` — dùng cho spawn thủ công:

```
Dịch batch {BATCH} — 2 API calls

## Turn 1: ĐỌC SONG SONG
- Guide chung: {GUIDE_CHUNG}
- Guide đặc thù: {GUIDE_DAC_THU} (nếu có)
- Source: {SOURCE_PATH}
- SQLite glossary: python3 -c "..." {GLOSSARY_DB}
- Context-summary batch trước: {PREV_CONTEXT_PATH}

## Turn 2: DỊCH + GHI FILE + MERGE
- Output (Việt only): {VI_OUTPUT_PATH}
- Context-summary: {CONTEXT_SUMMARY_PATH}
- Merge script: python3 {MERGE_SCRIPT} {SOURCE_PATH} {VI_OUTPUT_PATH} {MERGED_OUTPUT_PATH}

## Yêu cầu
- Output CHỈ tiếng Việt — KHÔNG có ngôn ngữ nguồn
- Giữ NGUYÊN separator `---`
- Format mỗi block: **XX. Kệ tô đậm.** → dòng trắng → nội dung → `---`
- Verify: assert source.count('\n---\n') == output.count('\n---\n')
- Sau khi ghi Việt only → chạy merge script
```

---

## IV. THIẾT LẬP CRON JOBS

### Cron 1: Translator Processor (mỗi 15 phút)

```bash
openclaw cron add \
  --name {TEN_DU_AN}-translator \
  --every 15m \
  --session isolated \
  --agent {AGENT} \
  --model {MODEL} \
  --light-context true \
  --message 'Bạn là Translator Agent — TỐI ƯU 2 LLM CALLS.

═══════════════════
QUY TẮC DỊCH (theo guide)
═══════════════════

1. Đọc guide chung: {GUIDE_CHUNG}
2. Đọc guide đặc thù: {GUIDE_DAC_THU} (nếu có)

Format Việt only mỗi block:
  **XX. Nội dung tô đậm**
  (dòng trắng)
  Nội dung văn xuôi (KHÔNG tô đậm)
  (dòng trắng)
  ---

Từ khóa:
  - Tô đậm TOÀN BỘ dòng tiêu đề: **XX. text**
  - TUYỆT ĐỐI giữ nguyên separator ---
  - Không header/footer/commentary
  - Không thêm ngôn ngữ nguồn vào output

═══════════════════
PHASE 1: CHẠY SCRIPT NÀY (1 LLM call)
═══════════════════

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

PROJ = "{PROJECT_DIR}"
OUT = "{OUTPUT_DIR}"
SRC = "{SOURCE_DIR}"
tz = timezone(timedelta(hours=7))

# P1: Chọn batch pending
with open(f"{OUT}/progress.json") as f:
    data = json.load(f)

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

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"{OUT}/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")
    # 🔒 Tự disable: tìm ID theo tên → disable
    #    Dùng openclaw cron list --json (có từ CLI), KHÔNG dùng update
    import subprocess, json as _json
    result = subprocess.run(
        ["openclaw", "cron", "list", "--json"],
        capture_output=True, text=True, timeout=15)
    jobs = _json.loads(result.stdout)
    for j in jobs:
        if j.get("name") == "{TEN_DU_AN}-translator":
            subprocess.run(
                ["openclaw", "cron", "disable", j["id"]],
                capture_output=True, timeout=15)
            break
    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"{OUT}/progress.json", "w") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

# P2: Đọc source
with open(f"{PROJ}/{SOURCE}") as f:
    source = f.read()

# P3: SQLite glossary
conn = sqlite3.connect("{GLOSSARY_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 ""))

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

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

⚠️ TUYỆT ĐỐI: 1 RESPONSE. GỌI TẤT CẢ TOOL SONG SONG.

Nếu Phase 1 in "ALL_DONE":
  → Đã tự disable trong script Python bên trên (tìm ID → disable)
  → KHÔNG dịch (tất cả batch đã done)
  → KHÔNG cần gọi thêm tool nào
Nếu Phase 1 in "SKIP" hoặc "STUCK": KHÔNG làm gì.

Khi Phase 1 in "BATCH:{tên_batch}":
   → Đọc SOURCE (---SOURCE_START--- đến ---SOURCE_END---)
   → Đọc GLOSSARY
   → Dịch TOÀN BỘ sang Việt, giữ nguyên separator ---

   → GỌI ĐỒNG THỜI TẤT CẢ TOOL SAU (thay BATCH thật):

   [TOOL 1] write:
     path: {OUTPUT_DIR}/vi-only/{BATCH}-vi.md
     content: toàn bộ bản dịch Việt only (giữ nguyên --- separator)

   [TOOL 2] write:
     path: {OUTPUT_DIR}/context-summary/{BATCH}-context-summary.md
     content: context-summary markdown (≤200 từ)

   [TOOL 3] exec:
     command: python3 {MERGE_SCRIPT} {PROJ}/{SOURCE} {OUTPUT_DIR}/vi-only/{BATCH}-vi.md {OUTPUT_DIR}/{BATCH}-merged.md

   [TOOL 4] exec:
     command: python3 -c "
import json
from datetime import datetime, timezone, timedelta
OUT = '{OUTPUT_DIR}'
BATCH = '{BATCH}'
with open(f'{OUT}/progress.json') as f: data = json.load(f)
for b in data['batches']:
    if b['name'] == BATCH:
        b['status'] = 'done'
        b['completed_at'] = datetime.now(timezone(timedelta(hours=7))).isoformat()
        break
with open(f'{OUT}/progress.json', 'w') as f:
    json.dump(data, f, indent=2, ensure_ascii=False)
print('OK')
"

   → DONE. KHÔNG thêm text!' \
  --timeout-seconds 600 \
  --delivery "{DELIVERY}"
```

---

### Cron 2: Monitor (mỗi 30 phút)

> ⚠️ **Bài học 2026-06-17:** Monitor dùng model DeepSeek V4 Flash có thể bị timeout ở 120s
> (phase `model-call-started`). Đã fix: tăng timeout → 300s, đơn giản hóa prompt.

```bash
openclaw cron add \
  --name {TEN_DU_AN}-monitor \
  --every 30m \
  --session isolated \
  --agent architect \
  --model deepseek/deepseek-v4-flash \
  --light-context true \
  --message '## Translate Monitor — {TEN_DU_AN}

### 1. Check progress (chạy script này)
```python
import json, os, glob
from collections import Counter

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

c = Counter(b["status"] for b in data["batches"])
done = c.get("done", 0)
in_prog = c.get("in_progress", 0)
pend = c.get("pending", 0)
total = len(data["batches"])

# Count output files (adapt path pattern cho dự án: *-bilingual.md hoặc *-merged.md)
output_files = glob.glob(f"{OUT}/*-bilingual.md") or glob.glob(f"{OUT}/*-merged.md")
ctx_files = glob.glob(f"{OUT}/context-summary/*.md")
print(f"Done={done} InProg={in_prog} Pend={pend} (total={total})")
print(f"Files: output={len(output_files)} context={len(ctx_files)}")

# Detect issues
if in_prog > 0:
    for b in data["batches"]:
        if b["status"] == "in_progress":
            started = b.get("started_at", "")
            print(f"WARNING:in_progress={b[\"name\"]} since {started}")
```

### 2. Actions
- **in_progress > 0:** Report tên batch đang chạy → không cần action (translator đang xử lý)
- **ALL DONE (done == total):** Disable cả 2 cron + report "✅ Pipeline complete"
- **pending > 0 + done < total:** Bình thường — translator cron sẽ pick batch tiếp

### 3. Self-disable khi ALL DONE
When done == total:
  1. Cron: update "{TEN_DU_AN}-translator" enabled=false
  2. Cron: update "{TEN_DU_AN}-monitor" enabled=false
  3. Report "✅ {TEN_DU_AN} pipeline complete — all crons disabled."

### 4. Report ngắn gọn' \
  --timeout-seconds 300 \
  --delivery "{DELIVERY}"
```

> **Lưu ý:** Monitor có thể dùng model rẻ hơn translator vì chỉ chạy script đơn giản.
> Nếu vẫn bị timeout → thử model khác (vd: `deepseek/deepseek-chat` thay vì flash).

---

## V. CẤU TRÚC THƯ MỤC CHUẨN

```
{PROJECT_DIR}/
├── TRANSLATOR-GUIDE-{TEN}.md          # Guide đặc thù dự án (nếu có)
├── {SOURCE_DIR}/                       # Source files đã split
│   └── batch-*.md
│
└── {OUTPUT_DIR}/
    ├── progress.json                   # Tiến độ pipeline
    ├── translator-prompt.md            # Prompt mẫu
    ├── merge-bilingual.py              # Script merge
    │
    ├── vi-only/                        # Translator output (Việt only)
    │   └── batch-*-vi.md
    │
    ├── batch-*-merged.md               # Merge output (song ngữ)
    │
    └── context-summary/                # Context summaries
        └── batch-*-context-summary.md
```

---

## VI. BIẾN THỂ: SONG NGỮ TRỰC TIẾP (KHÔNG MERGE)

> **Áp dụng khi:** Translator đủ mạnh để xuất song ngữ đúng format, sách có cấu trúc đơn giản.
> **Ví dụ:** Sổ Tay Mahāvihāra (2026-06-17)

### Khác biệt với template chuẩn:

| | Template chuẩn (V1) | Biến thể song ngữ trực tiếp |
|---|---|---|
| Output translator | Việt only | **Song ngữ My → Việt** |
| Merge | merge-bilingual.py | **Không cần** |
| Verify | assert separator count | verify-bilingual.py (kiểm tra thêm →) |
| Thư mục | vi-only/ | Không cần |
| LLM calls | 2 (dịch + merge) | **2 (chỉ dịch)** |
| Cost | ~$0.04/batch | ~$0.03/batch (tiết kiệm merge call) |

### Cấu trúc thư mục (biến thể):
```
{PROJECT_DIR}/
├── TRANSLATOR-GUIDE-{TEN}.md
├── {OUTPUT_DIR}/
│   ├── progress.json
│   ├── translator-prompt.md
│   ├── verify-bilingual.py          # ← Thay merge-bilingual.py
│   ├── preprocess-for-translation.py # ← Tách source thành đoạn (nếu cần)
│   ├── split/                        # Source đã tách đoạn
│   ├── {batch}-bilingual.md          # Output song ngữ trực tiếp
│   └── context-summary/
```

### Format output song ngữ (mỗi đoạn):
```
**မြန်မာခေါင်းစဉ်**
မြန်မာစာသား...

→

**Tiêu đề tiếng Việt**
Nội dung tiếng Việt...

---
```

### Điều kiện áp dụng:
- Translator model đủ mạnh để giữ format (Gemini 3 Flash+ hoặc tương đương)
- Sách có cấu trúc đoạn rõ ràng (không quá phức tạp)
- Muốn tiết kiệm 1 bước merge

---

## VII. SPWN THỦ CÔNG (DỰ PHÒNG)

```js
sessions_spawn({
  agentId: "{AGENT}",
  task: "<điền prompt từ translator-prompt.md>",
  model: "{MODEL}",
  mode: "run"
})
```

---

## VIII. ÁP DỤNG CHO DỰ ÁN MỚI

### Checklist triển khai:

- [ ] Tạo thư mục `{OUTPUT_DIR}/vi-only/`, `{OUTPUT_DIR}/context-summary/`
- [ ] Copy `merge-bilingual.py` từ dự án mẫu → điều chỉnh nếu cần
- [ ] Tạo `progress.json` với danh sách batch
- [ ] Tạo `TRANSLATOR-GUIDE-{TEN}.md` nếu dự án có thuật ngữ đặc thù
- [ ] Tạo `translator-prompt.md` (điền placeholders)
- [ ] Điều chỉnh cron prompt: thay `{PLACEHOLDERS}` → giá trị thực
- [ ] Chạy thử 1 batch bằng spawn thủ công → verify
- [ ] Enable cron translator
- [ ] Enable cron monitor
- [ ] Theo dõi cost trong session status

### Ví dụ đã triển khai:
- **Vinaya Saṅkhepa:** `02A-Dieu-Phoi-Translator.md` trong `obsidian/quy-trinh/`
- **Sổ Tay Mahāvihāra:** `011-so-tay-mahavihara/` — Song ngữ trực tiếp (không merge)
- **File dự án:** `002-vinaya/02A-Dieu-Phoi-Translator.md`

---

## IX. TỔNG KẾT

```
TEMPLATE V1 — VIỆT ONLY + SEPARATOR + MERGE TỰ ĐỘNG

  📁 Chuẩn bị:
     progress.json + merge-bilingual.py + guides + thư mục

  🔧 Cron translator (15min):
     Phase 1: exec python → batch + source + glossary
     Phase 2: write vi-only + write context + exec merge + exec progress

  👁️ Cron monitor (30min):
     Check stuck → reset | Check done → self-disable

  💰 Chi phí: tùy model
     Gemini 3 Flash Preview: ~$0.04/batch
     DeepSeek V4 Flash: ~$0.01/batch
```

> 📖 **File liên quan:**
> - `obsidian/huong-dan/Translator-Myanmar-Vi.md` — Guide chung
> - `02A-Dieu-Phoi-Translator.md` — Instance cho dự án Vinaya
> - `01A-Dieu-Phoi-Editor-OCR.md` — Pattern gốc (editor OCR)
