| name | hyp-harness |
| description | Multi-model fan-out harness: given a problem.yaml spec, fans out N parallel workers (kimi CLI or Claude Code CLI) each writing one research task as markdown, runs evaluator-optimizer quality gate, synthesizes all outputs into a final report, renders to dark-theme HTML. Triggers on: harness, kimi harness, claude harness, run harness, fan-out tasks, poc harness, long running task, parallel research, parallel workers, deep research. |
Kimi Harness — Scaffold & Run (v4.12)
Fan-out N workers chạy song song, mỗi worker nhận 1 task từ problem.yaml, output là markdown, cuối cùng synthesis tổng hợp → render HTML. Monitor UI xem live qua dashboard.
Source template: <repo-root>/src/
Repo: repo root — runs ở runs/poc-*/, dashboard ở dashboard/
Dashboard: http://localhost:4322 (hoặc domain riêng qua Cloudflare tunnel)
Cấu trúc hoạt động
problem.yaml ← spec: title, task list, synthesis prompt, evaluator config
↓
harness.py ← ThreadPoolExecutor(PAR=3) fans out workers (kimi hoặc claude)
per task: worker CLI (cwd=ROOT) -p "<prompt>"
Write tool (primary): worker writes tasks/<slug>.md → 27 safety-net fixers
Stream fallback: logs/<slug>-stream.log → F1+F2 TUI fixers + 27 fixers
→ PHASE2_VALIDATE: mmdc validates mermaid blocks locally; kimi called only on errors
→ DIAGRAM_CHECK: static validation → inject issues into eval hint
→ evaluator: kimi judge (READER_RUBRIC for tutorial/reference styles)
→ retry if score < threshold
→ streaming partial render → result.html live preview
↓
synthesis: worker CLI (cwd=ROOT)
→ final.md (or append_tasks: exec summary + all tasks/*.md appended)
→ SYNTH_EVAL: if synthesis.evaluator.enabled → score final.md, retry once if low
↓
render.py → result.html (dark theme, TOC, Mermaid pre-rendered SVG, AntV G2, MathJax)
↓
verify_render() → logs/verify.md (non-fatal)
↓
harness triggers npm run sync-build → Astro SSR dashboard auto-rebuild
progress.log key events: DRIVER START harness_version=vX.Y, BEGIN slug, END slug rc=0 eval=PASS score=7.5, FAIL_REASON slug: <reason>, SYNTH BEGIN/END/ERROR, SYNTH_EVAL verdict=PASS score=X.X, DRIVER COMPLETE
Step 1: Hỏi trước khi scaffold
1. Thư mục đích? (mặc định: <repo-root>/runs/poc-<topic>)
2. Bài toán gì? Mô tả ngắn để viết problem.yaml.
3. Số tasks? (3–20, sweet spot 10–15)
4. Worker backend? kimi (mặc định) hoặc claude
5. report_style? decision (mặc định) / tutorial / reference / historical / narrative
Step 2: Scaffold
2a. Copy source files
REPO=<repo-root>
TOPIC=<topic>
DEST=$REPO/runs/poc-$TOPIC
SRC=$REPO/src
mkdir -p $DEST/{tasks,logs}
cp $SRC/*.py $DEST/
cp $SRC/problem.yaml.template $DEST/problem.yaml
2b. Viết problem.yaml
title: "Tên project"
subtitle: "Mô tả ngắn"
emoji: "🔍"
report_style: decision
tasks:
- slug: "01-topic-a"
title: "Topic A"
prompt: |
Viết báo cáo về topic A. Bao gồm: ...
Độ dài: ít nhất 2000 từ (không giới hạn trên — KHÔNG đếm từ, KHÔNG rút ngắn để khớp số từ).
- slug: "02-topic-b"
title: "Topic B"
prompt: |
...
evaluator:
enabled: true
threshold: 6.5
max_retries: 1
synthesis:
output_filename: "final.md"
append_tasks: false
prompt_template: |
Bạn đang ở {workdir}. Đọc TẤT CẢ tasks/*.md.
Tổng hợp thành {output}. Ghi file bằng Write tool.
In "SYNTHESIS_DONE" khi xong.
Tips:
- MECE decomposition = 80% chất lượng: tasks không overlap, bao phủ hết domain
append_tasks: true khi ≥10 tasks — tránh kimi compress/mất content khi synthesis
report_style ảnh hưởng rubric: tutorial/reference/... dùng READER_RUBRIC (không cần evidence tags)
- prompt_template CHỈ chứa phần topic-specific (tên H1, cấu trúc section, disclaimer, đối tượng độc giả). KHÔNG thêm rule chung kiểu nhãn
[measured], cross-ref tasks/<slug>.md, "Why now/Verify", "Decision:" — wrapper tự inject các rule này THEO style; lặp lại sẽ mâu thuẫn với reader styles
- Heading tổng hợp viết theo
language của report (vi: "Điểm chính", "Bản đồ chương" — không dùng "Reading Guide"/"Chapter Map" trong báo cáo tiếng Việt). KHÔNG yêu cầu section "Mục lục" (TOC do render tự sinh); phần mở đầu tối đa ~1200 từ, mỗi section một vai trò riêng — không tóm tắt cùng nội dung nhiều lần
- Không nhắc kimi dùng FetchURL — DEFAULT_WRAPPER đã cấm, nếu bật sẽ waste steps → FAIL
- Harness tự inject Write-tool instruction qua DEFAULT_WRAPPER
2c. Tuning env vars
| Var | Default | Notes |
|---|
HARNESS_WORKER | kimi | Worker backend: kimi hoặc claude |
HARNESS_PAR | 3 | Parallel workers; sweet spot 2–3 |
HARNESS_TIMEOUT | 900 | Giây per task trước SIGTERM |
HARNESS_SYNTH_TIMEOUT | 300 | Actual = max(env, 200 + task_count × 25) |
HARNESS_WORKER_STEPS | 5 | --max-steps-per-turn kimi workers (ignored for claude) |
HARNESS_SYNTH_STEPS | 200 | --max-steps-per-turn kimi synthesis (ignored for claude) |
HARNESS_PHASE2_TIMEOUT | 120 | Phase 2 mmdc validate per task |
HARNESS_CLAUDE_MODEL | claude-sonnet-4-6 | Model cho claude workers/synthesis |
KIMI | kimi | Override nếu kimi không trong PATH |
CLAUDE | claude | Override nếu claude không trong PATH |
Step 3: Launch
cd $DEST
nohup python3 harness.py > /tmp/harness-$TOPIC.log 2>&1 &
tail -F progress.log | grep -E --line-buffered "BEGIN|END|FAIL|SYNTH|EVAL|DRIVER"
Dashboard auto-rebuild sau DRIVER COMPLETE — không cần làm gì thêm.
Step 4: Cloudflare (nếu muốn public URL mới)
Config: cloudflare/ttyd-config.yml (copy từ ttyd-config.example.yml, symlinked ← ~/.cloudflared/)
cloudflared tunnel route dns <tunnel-id> <subdomain>.<your-domain>
launchctl unload ~/Library/LaunchAgents/com.cloudflared.ttyd.plist
launchctl load ~/Library/LaunchAgents/com.cloudflared.ttyd.plist