Bạn đã có các mảnh: sàng lọc (B2-3), đặt lịch (B4-5), onboarding (B6). Bài này nối chúng thành một pipeline liền mạch với máy trạng thái, tích hợp ATS và thông báo — để agent chạy như một quy trình thật, không phải các script rời.
Xương sống: máy trạng thái ứng viên (state machine)
Mọi ứng viên đi qua các trạng thái rõ ràng; mỗi chuyển trạng thái do một sự kiện kích hoạt:
NEW → SCREENED → SHORTLISTED → INTERVIEW_SCHEDULED
→ INTERVIEWED → OFFER → HIRED → ONBOARDING → DONE
(bất kỳ đâu) → REJECTED / WITHDRAWN
Lợi ích: bạn luôn biết mỗi người đang ở đâu, agent nào chịu trách nhiệm chuyển tiếp, và không bước nào bị bỏ sót.
Bước 1: Định nghĩa transition + guard
Mỗi chuyển trạng thái có điều kiện (guard) và hành động:
TRANSITIONS = {
("SCREENED", "SHORTLISTED"): {
"guard": lambda c: c.score >= 70 and c.recruiter_approved,
"action": start_scheduling
},
("INTERVIEWED", "OFFER"): {
"guard": lambda c: c.interview_passed, # do người chấm
"action": draft_offer
},
("HIRED", "ONBOARDING"): {
"guard": lambda c: c.offer_signed,
"action": build_and_assign_checklist
}
}
Guard chính là nơi cài human-in-the-loop: recruiter_approved, interview_passed là cờ do người bật.
Bước 2: Orchestrator điều phối
Một orchestrator lắng nghe sự kiện và gọi đúng transition:
def on_event(candidate, event):
key = (candidate.state, target_state(event))
rule = TRANSITIONS.get(key)
if rule and rule["guard"](candidate):
rule["action"](candidate)
candidate.state = key[1]
log_transition(candidate, key, event)
else:
queue_for_human(candidate, event) # không đủ điều kiện → người xử lý
Nguyên tắc: nếu không chắc, đẩy cho người thay vì đoán. Agent tốt biết khi nào nên dừng.
Bước 3: Tích hợp với ATS / nguồn dữ liệu thật
Agent không nên là "ốc đảo". Đồng bộ 2 chiều:
- Nguồn vào: webhook từ ATS/careers page khi có CV mới → tạo candidate NEW
- Ghi ra : mỗi transition cập nhật lại ATS (state, score, note)
- Idempotency: dùng candidate_id ngoài để tránh xử lý trùng khi webhook gửi lại
Idempotency rất quan trọng: webhook thường gửi lại (retry), nếu không chống trùng bạn sẽ chấm điểm/gửi email 2 lần.
Bước 4: Lớp thông báo cho con người
Agent phải cập nhật cho các bên liên quan đúng lúc, đúng người:
NOTIFY_MAP = {
"SHORTLISTED": ("recruiter", "telegram"),
"INTERVIEW_SCHEDULED": ("interviewer", "email+calendar"),
"OFFER": ("hr_manager", "email"),
"ONBOARDING": ("it,team_lead", "ticket")
}
Gắn thông báo vào transition, không rải rác trong code — dễ kiểm soát và tránh spam.
Khung tư duy pipeline bền vững
QUAN SÁT ĐƯỢC : mỗi ứng viên có state + lịch sử transition (audit)
KHÔI PHỤC ĐƯỢC: job lỗi thì retry, không mất dữ liệu (idempotent)
DỪNG ĐÚNG CHỖ : guard không đạt → human queue, không tự đoán
ĐO ĐƯỢC : thời gian mỗi giai đoạn, tỉ lệ rớt từng bước
Xử lý lỗi & khả năng phục hồi
- Mỗi hành động (gửi email, gọi API) bọc try/except, lỗi → ghi log + retry có giới hạn.
- Lỗi vượt ngưỡng retry → chuyển "dead-letter" cho người xem, không im lặng.
- Không để một CV lỗi làm sập cả batch — xử lý độc lập từng ứng viên.
Checklist pipeline end-to-end
- [ ] Máy trạng thái rõ ràng, mọi transition có guard
- [ ] Human-in-the-loop cài trong guard ở các bước hệ trọng
- [ ] Orchestrator đẩy về human khi guard không đạt
- [ ] Tích hợp ATS 2 chiều + idempotency chống trùng
- [ ] Thông báo gắn vào transition, đúng người/kênh
- [ ] Lỗi có retry + dead-letter, không im lặng
- [ ] Có audit log & metric từng giai đoạn
Sai lầm thường gặp
- Không có state machine, dùng cờ boolean rải rác. Nhanh chóng thành mớ bòng bong, không ai biết ứng viên đang ở đâu.
- Quên idempotency. Webhook retry khiến gửi email/chấm điểm nhiều lần, làm phiền ứng viên.
- Một lỗi làm sập cả batch. Không cô lập xử lý từng ứng viên → 1 CV hỏng, 199 CV dừng theo.
- Nuốt lỗi im lặng. Không dead-letter thì ứng viên "biến mất" khỏi pipeline mà không ai hay.
- Agent tự đoán khi thiếu điều kiện. Đúng ra phải đẩy cho người; đoán liều gây quyết định sai với con người.