Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 7 — Con người trong vòng lặp: phê duyệt, timeout và quy trình chạy dài

Đã đọc 0/10 mục · còn khoảng 4 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Nhiều quy trình thật không chạy trong vài giây — chúng chờ con người. Đơn trên hạn mức cần quản lý phê duyệt; quản lý có thể duyệt sau 2 tiếng, ngày mai, hoặc quên luôn. Orchestrator phải xử lý bước "chờ người" như một trạng thái bền, có timeout, có leo thang (escalation) — chứ không phải một lời gọi hàm treo mãi.

Phê duyệt là một trạng thái, không phải một cuộc gọi

Đừng "block" chờ phản hồi. Hãy đưa quy trình vào trạng thái AWAITING_APPROVAL, lưu bền, rồi giải phóng worker. Khi người bấm duyệt, một sự kiện (webhook/callback) đánh thức quy trình đi tiếp.

INVOICED --amount > limit?--> AWAITING_APPROVAL --approved--> PAID
                                     |--rejected--> FAILED (-> compensate)
                                     |--timeout---> ESCALATED / FAILED

Cơ chế đánh thức (resume token)

Sinh một token gắn với đơn khi vào trạng thái chờ. Gửi token trong link phê duyệt. Callback mang token về, orchestrator tra ra đúng đơn và tiếp tục.

def request_approval(row):
    row.state = "AWAITING_APPROVAL"
    row.step_data["approval_token"] = new_token()
    row.step_data["deadline"] = now() + hours(24)
    db.save(row)
    notify_manager(link=f"/approve/{row.step_data['approval_token']}")

def on_approval_callback(token, decision):
    row = db.find_by_token(token)
    if row.state != "AWAITING_APPROVAL":
        return  # đã xử lý (double-click/late) -> idempotent, bỏ qua
    if decision == "approve":
        transition(row, "AWAITING_APPROVAL", "PAID", pay_adapter.do)
    else:
        start_saga(row)  # từ chối -> bù trừ

Lưu ý kiểm tra row.state lần nữa: người dùng bấm hai lần, hoặc callback tới trễ sau khi đã timeout — phải idempotent.

Timeout và leo thang

Bước chờ người phải có deadline, nếu không đơn treo vĩnh viễn. Một job định kỳ quét các đơn quá hạn:

def sweep_timeouts():
    for row in db.where("state='AWAITING_APPROVAL' AND deadline < now()"):
        if row.escalation_level == 0:
            notify_manager_boss(row); row.escalation_level = 1
            row.step_data["deadline"] = now() + hours(24)
        else:
            row.state = "FAILED"; start_saga(row)
        db.save(row)

Leo thang: nhắc lần một, đổi người duyệt lần hai, cuối cùng tự hủy và bù trừ.

AI agent hỗ trợ, con người quyết định

Trong quy trình có phê duyệt, AI agent nên chuẩn bị quyết định cho người: tóm tắt đơn, nêu rủi ro, gợi ý approve/reject kèm lý do — nhưng hành động không hoàn tác (chi tiền) chỉ chạy sau khi người xác nhận. Ranh giới rõ ràng này vừa tăng tốc, vừa giữ trách nhiệm cho con người.

Checklist quy trình chờ người

  • [ ] Trạng thái chờ được lưu bền, worker được giải phóng.
  • [ ] Có resume token/callback idempotent để đánh thức.
  • [ ] Mọi trạng thái chờ có deadline.
  • [ ] Job quét timeout + leo thang nhiều cấp.
  • [ ] Từ chối/hết hạn nối vào saga bù trừ.
  • [ ] AI chỉ đề xuất; side-effect nặng chờ người xác nhận.

Các bước triển khai

  • Thêm trạng thái AWAITING_APPROVAL (và ESCALATED nếu cần) vào state machine.
  • Sinh token + deadline khi vào trạng thái chờ; gửi thông báo kèm link.
  • Viết callback idempotent kiểm tra state trước khi chuyển.
  • Tạo cron quét deadline, leo thang, và fallback FAILED→saga.
  • Cho AI agent tạo bản tóm tắt quyết định đính kèm yêu cầu duyệt.

Xử lý callback tới muộn hoặc trùng

Quy trình chạy dài sinh ra những tình huống thời gian oái oăm: manager bấm duyệt đúng lúc job timeout vừa chuyển đơn sang ESCALATED; hoặc một webhook bị broker gửi lại hai lần. Cách phòng thủ đồng nhất là: mọi callback trước khi hành động phải đọc lại state trong một transaction có khóa và chỉ tiếp tục nếu state đúng như kỳ vọng. Nếu state đã đổi, callback trở thành no-op êm ái. Đừng bao giờ tin rằng "vì tôi vừa đặt trạng thái chờ nên chắc chắn nó vẫn đang chờ" — giữa hai thời điểm đó bất kỳ điều gì cũng có thể xảy ra.

def on_approval_callback(token, decision):
    with db.transaction():
        row = db.lock_by_token(token)
        if row is None or row.state != "AWAITING_APPROVAL":
            return  # trễ / trùng / đã escalate -> bỏ qua an toàn
        apply_decision(row, decision)

Thiết kế thông báo để giảm ma sát

Một quy trình phê duyệt chỉ nhanh khi người duyệt hành động nhanh. Đính kèm đủ bối cảnh ngay trong thông báo (tóm tắt đơn, số tiền, rủi ro AI nêu, hai nút Duyệt/Từ chối) để manager quyết định mà không cần mở nhiều tab. Mỗi lần họ phải đăng nhập, tìm đơn, đọc rải rác là một lần quy trình chậm lại. AI agent giúp nhiều nhất ở đây: biến dữ liệu thô thành một bản tóm tắt quyết định gọn, có khuyến nghị kèm lý do.

Sai lầm thường gặp

  • Block worker chờ người: một quản lý đi họp là kẹt cả pool worker.
  • Không có deadline: đơn AWAITING_APPROVAL nằm im mãi mãi.
  • Callback không idempotent: bấm duyệt hai lần → charge hai lần.
  • Để AI tự chi tiền dựa trên suy luận, không qua người — rủi ro pháp lý và tài chính.
  • Quên nối nhánh từ chối vào bù trừ: hàng vẫn bị giữ dù đơn đã bị bác.
Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi