บทที่ 25 — Workflows
Workflows คือ orchestration tier ที่สี่ ของ thClaws — model
เขียนสคริปต์ JavaScript ที่กระจายงานไปยัง subagent หลายตัว แล้ว JS
engine ในตัวสคริปต์รันแบบ deterministic บนเครื่องของคุณ ต่างจาก
subagent (บทที่ 15), /agent side-channel, หรือ Agent Teams (บทที่
17) ตรงที่ตัวสั่งการคือ code ไม่ใช่ model — ซึ่งหมายความว่ารัน
workflow เดิมซ้ำจะได้รูปทรงงานเหมือนเดิมทุกครั้ง และงานยาว ๆ จะ
เหลือ checkpoint ไว้บนดิสก์
Fan-out, JSON-schema validation, budget token/time ต่อ worker, retry,
KMS-write grant, และ resume ใช้งานได้หมดแล้ว ช่องว่างหลักที่ยังเหลือ
คือ การรันขนานจริง — worker ยังรันทีละตัวแม้อยู่ใน Promise.all
(ดู “ข้อจำกัดปัจจุบัน” ด้านล่าง)
ควรใช้ workflows เมื่อไร
ใช้ workflows กับ งาน bulk ที่อิสระจากกันและต้องการความแน่นอน:
- “rewrite test file 800 ไฟล์ให้ใช้ fixture ใหม่”
- “แปลทุก
.mdใต้kms/bug/เป็นภาษาไทย” - “audit
Cargo.tomlของแต่ละ crate แล้ว flag deps ที่ deprecated”
ใช้ Task tool (บทที่ 15) กับ side-quest ที่ model ตัดสินใจสร้าง
ขึ้นมาเอง กลางเทิร์น — นั่นคือสิ่งที่ subagent ทำต่อไป
ใช้ /agent (บทที่ 15) เมื่อ คุณ รู้ชัดว่าจะให้ specialist ทำ
อะไร และอยากให้ทำงานคู่ขนานกับ session หลัก
ใช้ Agent Teams (บทที่ 17) เมื่อ teammate ต้อง ร่วมมือกัน — แลก message ถกเถียงสมมติฐาน ประสานงานบน task list ร่วมกัน Workflows เป็น stateless fan-out ส่วน team เป็น stateful collaboration
สองทางในการเรียก workflow
Engine เดียวกัน (LLM เขียนสคริปต์ JS แล้ว Boa sandbox รัน) แต่มี ทางเข้า 2 ทาง ขึ้นกับว่าใครเป็นคนตัดสินว่าควรใช้ workflow:
| Trigger | เหมาะกับ | UX |
|---|---|---|
คุณพิมพ์ /workflow run <prompt> (slash command) |
คุณรู้อยู่แล้วว่าควร fan-out และอยากเห็นสคริปต์ก่อนรัน | Author → review → approve/cancel/re-author loop → run |
Model เรียก tool WorkflowRun |
คุณอธิบายงานเป็นภาษาธรรมชาติ (“rewrite all tests ให้ใช้ fixture ใหม่”) แล้วให้ model ตัดสินเองว่าจะ spawn subagent ตัวเดียวหรือ author workflow ขนาน | approval prompt ต่อ call (เหมือน Bash) → author → run, ไม่มี review loop |
เลือก slash command เมื่ออยากเห็น JS ก่อนรัน — ดีกับ pattern ใหม่ หรือเวลาที่กำลังลองรูปแบบงาน เลือกเส้น tool เมื่ออยากได้ผลลัพธ์ เลยและไว้ใจให้ model เลือก orchestration strategy เอง
Model รู้จัก WorkflowRun เพราะอยู่ใน section Collaboration
primitives ของ system prompt คู่กับ Subagent (side-quest ครั้งเดียว)
กับ Agent Teams (collaborator ที่อยู่นาน) — เลือก primitive ที่
เหมาะสมได้เองโดยไม่ต้องบอก ถ้าอยาก nudge ชัด ๆ บอกใน chat ได้เลย
“ใช้ WorkflowRun ทำ …”
ทั้งสองทาง reject nested call: สคริปต์ที่พยายามเรียก WorkflowRun
ในตัวเองจะ fail พร้อม error ชัดเจน — orchestrate ผ่าน
thclaws.subagent(...) ในสคริปต์แทน (เป็น fan-out primitive ตัวเดียว
ที่มี ไม่มี thclaws.parallel)
เริ่มใช้
/workflow run summarize each .rs file under src/ in one line
ลำดับเหตุการณ์:
- Author phase model เขียนสคริปต์ JavaScript ที่ใช้ API
thclaws.*(รายละเอียด API อยู่ใน system prompt ของ model อยู่แล้ว ดังนั้นสคริปต์ที่ได้กลับมารู้ว่ามีอะไรให้ใช้บ้าง) - Review สคริปต์ถูก print พร้อมเลขบรรทัด แล้วถาม:
text [a]pprove · [c]ancel · [r]e-author:-a— รันตามนี้ -c— ยกเลิก -r— ใส่ note บรรทัดเดียวบอกว่าให้แก้อะไร (“ใช้ read tool ไม่ ใช่ bash cat”) แล้ว model เขียนสคริปต์ใหม่ตาม feedback วน จนกว่าจะกดaหรือc - Execute แสดง workflow id (
wf-…) จากนั้นทุก subagent call จะมีบรรทัด progress:text ✓ w0 List every .rs file under src/, recursively. Return o… 2s ✓ w1 Read crates/core/src/agent.rs and write ONE sentence … 3s ✓ w2 Read crates/core/src/repl.rs and write ONE sentence d… 4s … workflow done — 47 workers, total 1m 12s crates/core/src/agent.rs — the streaming agent loop crates/core/src/repl.rs — REPL command parser + rustyline I/O …
ถ้า worker error จะเห็น ✗ wN … และสคริปต์มักจะ catch แล้วทำงาน
ต่อ (แล้วแต่ model เขียน)
เริ่มใช้ผ่าน model
งานเดียวกัน ไม่ต้องพิมพ์ slash command — บอกใน chat เลย:
you > rewrite each test file under tests/ ให้ใช้ TestHarness fixture ใหม่
Model มอง pattern นี้เป็น fan-out แล้วตัดสินใจเรียก WorkflowRun:
[approval] WorkflowRun(prompt: "rewrite each test file under tests/
ให้ใช้ TestHarness fixture ใหม่")
[a]llow once · [A]lways · [d]eny: a
[workflow: author phase…]
[workflow: 32 subagent turn(s), 18432 in / 9621 out tokens]
✓ tests/test_login.rs — migrated to TestHarness::new()
✓ tests/test_signup.rs — migrated; 1 helper renamed
…
เห็น approval prompt 1 ครั้งสำหรับทั้ง call WorkflowRun (เหมือน
Bash) แต่ใน script — แต่ละ Skill / Bash / Edit call ยังต้อง
approval ของตัวเองตามปกติ — WorkflowRun ไม่ bypass per-tool gate
ถ้าอยากเห็น JS script ที่ model เขียน ใช้ slash-command แทน
(/workflow run <prompt> เดียวกัน) — เส้น tool ข้าม review loop
เพื่อความเร็ว
API thclaws.*
สคริปต์ของคุณได้ global ตัวเดียว — thclaws — มี field ต่อไปนี้:
thclaws.subagent({
prompt: string, // จำเป็น — งานของ worker
budget?: { // enforce แล้วทั้งคู่
time?: number | string, // "60s" / "2m" / "1m30s" / 60 (เป็นวินาที)
tokens?: number, // เพดาน input + output ต่อ worker
},
schema?: object, // JSON Schema worker ถูกขอ JSON ที่
// ตรง schema เมื่อสำเร็จจะคืน parsed value
// (ไม่ใช่ text)
retry?: number | { // retry เมื่อ hard error + schema fail
max: number,
backoff?: string, // "exponential" / "linear" / "500ms" / ฯลฯ
},
caps?: { // grant แบบชัดเจน — default DENY ของ KMS write
kms?: { write?: string[] },
},
agent?: string, // รันเป็น named agent (.thclaws/agents/<name>.md)
// ใช้ tools/instructions ของ agent นั้น ดูบทที่ 15
}) → string | parsed_value
สคริปต์ยังได้ thclaws.include(path) — ดึงไฟล์ .js อีกตัว (helper,
prompt string ที่ใช้ร่วม) แบบ relative กับ directory ของสคริปต์ การ
traverse (..), absolute path, และ symlink ที่หลุดออกนอก base dir
ถูกปฏิเสธ
caps.kms.write ควบคุมว่า worker เขียน KMS อะไรได้ นอก workflow
KMS write tool ทำงานปกติ ใน /workflow run worker default = deny
ทั้งหมด ต้องผ่าน caps: { kms: { write: ["scratch", "audit-log"] } }
จึงจะ grant เป็นราย call grant ถูกบันทึก worker_caps ใน
state.jsonl และ ไม่ transitive — Task spawn ของ worker เองจะได้
caps เปล่าใหม่ ถ้าไม่ grant ใหม่อีกครั้ง
Time budget ครอบ worker call ด้วย tokio::time::timeout พอเกินจะ
throw Schema validation รันหลังทุก attempt — ถ้า worker output ไม่
parse เป็น JSON หรือไม่ตรง schema จะ retry ตาม retry.max ด้วย
backoff ที่เลือก ทุก retry บันทึก worker_retry event ให้
/workflow inspect <id> เห็น chain Worker จะ inherit provider, model, system prompt,
tool registry, memory, KMS, และ permission mode จาก session แม่ —
ดังนั้น worker ใช้ Bash, Read, Edit, search KMS, MCP server
ได้หมด การ recurse ของ subagent (worker เรียก Task เอง) ถูกจำกัด
ด้วย DEFAULT_MAX_DEPTH = 3 เหมือนกับ subagent ปกติ
Async syntax ใช้ได้แล้ว — script ที่ใช้ await / async /
Promise.all จะถูก route ผ่าน Boa Module mode thclaws.subagent
ยัง synchronous ภายใน ดังนั้น Promise.all([...]) resolve ได้แต่
worker รันตามลำดับใน source (ทีละตัว) การรันขนานจริงยังไม่มี (ดู
“ข้อจำกัดปัจจุบัน”)
เขียนอะไรในสคริปต์ได้บ้าง
JS control flow: for, while, if/else, try/catch, await,
async function, Promise.all, destructuring, template literal,
array/string method, regex, JSON parsing
เขียนอะไรไม่ได้
eval,Function(ถูกปลดจาก sandbox)fetch,require,process, DOM,console.log
ของที่จะ I/O ต้องผ่าน subagent
ตัวอย่างสั้น ๆ
// Workflow: list .rs files, summarise each
const list = await thclaws.subagent({
prompt: "List every .rs file under src/, recursively. Paths only."
});
const paths = list.split("\n").map(s => s.trim()).filter(Boolean);
const summaries = await Promise.all(
paths.map(p => thclaws.subagent({
prompt: `Read ${p} and write ONE sentence describing what it does.`
}))
);
paths.map((p, i) => `${p} — ${summaries[i]}`).join("\n");
สำหรับ script แบบ sync expression สุดท้าย คือผลลัพธ์ ส่วน script
แบบ async (Module mode) ใช้ expression สุดท้ายที่ auto-wrapper หา
เจอ หรือใส่ globalThis.__wf_result = … เองก็ได้ ถ้าไม่มีทั้งสอง
อย่างจะคืน undefined
State บนดิสก์
ทุกครั้งที่รัน workflow จะเขียน JSONL log ลง:
.thclaws/workflows/wf-<id>/state.jsonl
หนึ่ง event ต่อบรรทัด flush หลังเขียนทุกครั้งเพื่อให้ Ctrl-C ไม่ ทิ้งไฟล์ค้างกลางคัน รูปแบบ event:
{"ts":"…","kind":"start","id":"wf-…","prompt":"…","script_sha":"…","script_chars":234}
{"ts":"…","kind":"worker_start","id":"wf-…","worker":"w0","prompt":"…"}
{"ts":"…","kind":"worker_done","id":"wf-…","worker":"w0","output":"…"}
{"ts":"…","kind":"worker_error","id":"wf-…","worker":"w1","error":"…"}
{"ts":"…","kind":"done","id":"wf-…","result":"…"}
cat, grep, jq ไฟล์ได้ตลอดเวลา — เป็น JSONL ธรรมดา ไม่มี
ฟอร์แมตปิด ไฟล์ script.js (JS ที่ approve แล้ว) ก็ถูกเขียนไว้
ข้างกัน เผื่อ /workflow resume <id> จะ replay จาก source เดิม
Slash command สำหรับจัดการ run (ใช้ได้ทั้ง CLI REPL และ GUI chat tab
ยกเว้น /workflow rm ที่ต้อง y/N confirm แบบ interactive ซึ่ง
chat tab แสดงไม่ได้ — ต้องรันจาก thclaws --cli):
/workflow list หนึ่งบรรทัดต่อ run จากใหม่ไปเก่า
/workflow inspect <id> dump state.jsonl events
/workflow resume <id> รันใหม่ replay worker ที่เสร็จจาก cache
ส่วน fresh spawn เลขต่อไปเรื่อย ๆ
/workflow rm <id> ถาม y/N แล้วลบทั้ง directory
resume match ด้วย prompt ที่ทุก thclaws.subagent call cache
entry จะถูกใช้ก็ต่อเมื่อ prompt ตรง mismatch จะ fall-through ไป
spawn ใหม่ (script อาจถูกแก้หรือ path เปลี่ยน) ถ้ามี cache เหลือ
ตอนจบ script จะรายงาน “diverged” ให้รู้
ถ้า .thclaws/ เขียนไม่ได้ (read-only volume, permission)
workflow ยังรันแต่จะ print:
/workflow run: state.jsonl unavailable — proceeding without checkpoint
audit trail หายไป แต่ run ไม่หาย
รัน script ที่เขียนไว้ล่วงหน้า (ข้าม author phase)
ถ้าคุณมีไฟล์ workflow .js บนดิสก์อยู่แล้ว — ที่เขียนเอง หรือ
script.js ที่เซฟไว้จาก /workflow run ครั้งก่อน — รันตรง ๆ ได้เลย
โดยข้าม author + review phase ทั้งหมด:
- Mid-session:
/workflow exec <path>(aliasfile/script) รัน script จากดิสก์ใน REPL หรือ GUI session ปัจจุบัน - Headless:
thclaws --workflow <file.js>รันจาก command line — เหมาะกับ CI, cron job (บทที่ 19), deploy hook
ทุก /workflow run จะ persist script ที่ approve แล้วไว้ที่
.thclaws/workflows/wf-<id>/script.js ดังนั้น workflow ที่ model
author ครั้งเดียวเอามารันซ้ำแบบ deterministic ได้ด้วย /workflow exec
ชี้ path นั้น (หรือ replay จาก checkpoint ด้วย /workflow resume <id>)
# รันใหม่:
thclaws --workflow ./scripts/audit-crates.js
# Resume จาก id เดิม (หรือ prefix):
thclaws --workflow ./scripts/audit-crates.js --resume wf-18b3fa
# stdout = ค่าสุดท้ายของ script; stderr = id + done summary
# pipe stdout เข้า jq, redirect ลงไฟล์ ฯลฯ ได้:
thclaws --workflow ./scripts/audit-crates.js > result.txt
Exit code = 0 เมื่อสำเร็จ, 1 เมื่อ script fail ทั้ง /workflow
exec และ --workflow จะ auto-approve tool call ทุกตัวที่ worker เรียก
(เหมือน --dangerously-skip-permissions) — ถือว่า script ผ่านการ vet
จาก operator แล้ว ดังนั้นรันเฉพาะ script ที่ไว้ใจได้
thclaws -p "/workflow run <goal>"ไม่ใช่เส้นที่ใช้งานได้จริง: print mode แบบ one-shot ไม่มี surface สำหรับ author/review loop และ host functionthclaws.subagentก็ไม่ถูก wire ในนั้น สำหรับการรัน แบบ non-interactive ให้ pre-author script แล้วใช้--workflow
ข้อจำกัดปัจจุบัน
นี่คือช่องว่างที่รู้อยู่ ไม่ใช่ bug — track ไว้ใน dev-plan/32 (workspace-only):
Promise.allresolve ได้แต่ยังไม่ขนานจริง Boa รัน script ที่ ใช้await/Promise.allใน Module mode แล้ว syntax parse ได้และawait thclaws.subagent(...)คืน text ของ worker ได้ แต่ host function ยัง block JS thread per-call ดังนั้น subagent call ในPromise.allก็ยังรันตามลำดับ wall clock = ผลรวม latency ไม่ใช่ค่า มากที่สุด executor ที่ผูกกับ tokio เพื่อรันขนานจริงยัง pending- ยังไม่มี verification phase primitive
thclaws.verify({...})เฉพาะยังไม่มี — script ที่อยากมีขั้น verify ให้เขียนเป็นthclaws.subagentอีก call - ยังไม่มี GUI worker grid แบบ real-time
/workflow runและ คำสั่งจัดการรันได้จาก chat tab แต่ progress ของ worker stream เป็น text ไม่ใช่ grid สด UI progress แบบ real-time ยังเป็นงาน frontend
เรื่อง cost
ทุก thclaws.subagent call เป็น model turn แยก — ปกติไม่กี่วินาที
และไม่กี่ร้อยถึงไม่กี่พัน token Workflow 200 worker อาจกิน $5–$20
ของ API token ได้ง่าย ๆ ขึ้นกับ model มี 2 guard ใช้งานจริง:
- จำกัด fan-out ก่อนเขียนสคริปต์ ถ้าเป้าหมายไม่มีขอบเขต (“ทุก ไฟล์”) ให้ discovery subagent คืน list ก่อนจะได้เห็น cardinality ก่อน approve สคริปต์
- ดูบรรทัดสรุปปิดท้าย
workflow done — N workers, total Xs, In tokens / Out tokens (≈$Y.YY)พิมพ์ทุกครั้งหลัง/workflow runโมเดลที่เป็น tier-billed หรือไม่รู้ราคาจะแสดง(cost unknown)แทนตัวเลข
ตารางอ้างอิงเร็ว
Subagent (Task) |
/agent |
Agent Teams | Workflow | |
|---|---|---|---|---|
| ตัวสั่งการ | Model | คุณ (one-shot) | Team-lead model | Code |
| จำนวน worker | 1 (blocking) | 1 (concurrent) | 3–5 collaborator | สิบถึงร้อย |
| worker คุยกันเอง | ไม่ได้ | ไม่ได้ | ได้ (mailbox) | ไม่ได้ (stateless) |
| Determinism | Model-driven | Model-driven | Model-driven | Deterministic execution |
| Resume ได้ | ไม่ | ไม่ | จำกัด | ได้ (/workflow resume replay จาก checkpoint) |
| เหมาะกับ | Side-quest กลางเทิร์น | Specialist ทำงานคู่ขนาน | ถกเถียง / ร่วมมือ | Bulk fan-out |
Troubleshooting
“workflow: state.jsonl unavailable — proceeding without checkpoint”
— .thclaws/workflows/ สร้างหรือเขียนไม่ได้ ตรวจ permission ของ
.thclaws/ ใน project root
Script error: ReferenceError: thclaws is not defined — คุณ
น่าจะรันสคริปต์นอก /workflow run global thclaws.* มีอยู่เฉพาะ
ใน workflow sandbox
Workflow ค้างหลังบรรทัด ⠋ wN … — worker ตัวนั้นกำลังใช้เวลา
นาน ถ้าไม่ได้ตั้ง budget: { time } subagent call จะรันไม่จำกัดเวลา
กด Ctrl-C จะหยุดทั้ง run ใส่ time budget ต่อ worker ในสคริปต์เพื่อ
จำกัดเวลาราย worker ได้
Re-author loop ได้สคริปต์เดิมซ้ำ ๆ — model อาจมอง revision
note ของคุณข้าม ลองยกเลิกแล้วรันใหม่โดยเขียน goal ให้ชัดขึ้น แทน
การพึ่ง r-loop