Loop Engineering Overview

Deep Dive: Work Intake & Scheduling cho Agent Loops

Từ tín hiệu đánh thức tới một work item có identity, policy, lease và no-work behavior rõ ràng.
Báo cáo cha: ← OverviewTopic: Work Intake & SchedulingNguồn chính: Taxonomy · CloudEvents · GitHub Actions · TemporalNgày: 2026-07-20

Trigger đánh thức runtime; intake cấp quyền cho công việc Thesis

Một cron tick, webhook hay nút Run chỉ nói rằng hệ thống nên thức dậy. Nó không chứng minh event còn mới, item chưa được xử lý, dependency đã sẵn sàng hay actor được phép chạm workspace. Khi trigger bị dùng thẳng như work order, mọi lỗi delivery—duplicate, reorder, delay—đều chảy vào reasoning loop và bị hiểu nhầm thành vấn đề của model.

Intake là admission-control boundary. Nó chuẩn hóa tín hiệu, xác định stable work identity, kiểm freshness và eligibility, áp policy, lấy lease và chỉ sau đó mới tạo context cho agent. Đây là nơi intent của nguồn phát được biến thành một đơn vị công việc có thể dedupe, checkpoint, budget và audit.

Tách hai lớp còn tạo một thuộc tính quan trọng: cùng một intake policy có thể được gọi từ schedule, event hoặc manual recovery mà không thay đổi semantics của work item. Trigger trở thành transport; contract trở thành source of truth.

No-work cũng là outcome hợp lệ. Một scheduled scan không thấy item cần ghi receipt và exit sạch, thay vì gọi model để “tìm thêm việc”. Khả năng im lặng đúng lúc là kiểm soát chống invented work, cost leak và notification noise.

Design invariant: không có model call nào xảy ra trước khi hệ thống biết work key, input version, policy decision, concurrency key và budget owner của item.
Các thẻ Loop Contract, trong đó trigger và intake đứng trước workspace, verification và state
Trigger và intake là hai quyết định riêng trong Loop Contract; gộp chúng làm mất admission boundary. ↗ Loop Engineering Taxonomy

Control-flow model: normalize trước, reason sau Mental model

Pipeline intake tốt giảm entropy theo từng bước. Envelope delivery được chuẩn hóa; identity được ánh xạ sang domain; policy loại item không hợp lệ; dedupe loại bản sao; scheduler kiểm capacity; lease tạo ownership tạm thời. Chỉ payload đã qua chuỗi này mới trở thành prompt context.

Một intake pipeline tách delivery semantics khỏi domain semantics và execution capacity.

flowchart LR
  T["Schedule / event / manual"] --> N["Normalize envelope"]
  N --> I["Resolve work key + version"]
  I --> F{"Fresh and eligible?"}
  F -- No --> R["Reject / defer receipt"]
  F -- Yes --> D{"Seen or active?"}
  D -- Yes --> R
  D -- No --> C{"Capacity available?"}
  C -- No --> Q["Queue with backpressure"]
  C -- Yes --> L["Acquire lease"]
  L --> A["Agent act"]
  A --> P["Persist + release / renew"]

Hai identity cùng tồn tại. Event ID nhận diện lần delivery; work key nhận diện công việc domain, ví dụ repo/pr/check-name/input-sha. Dedupe chỉ theo event ID sẽ bỏ sót hai webhook khác nhau cho cùng commit; dedupe chỉ theo PR sẽ nuốt mất phiên bản input mới.

Scheduler cũng không nên quyết định business priority một mình. Nó thực thi fairness, concurrency và rate limits trên priority đã được intake policy gán. Nếu trộn hai vai trò, việc đổi queue implementation có thể vô tình đổi policy.

Bốn kỹ thuật thiết kế intake Design

1. Định nghĩa trigger semantics như một transport contract

I.1
TAXONOMY.md · GitHub Actions workflow syntax
At-least-once delivery là trạng thái bình thường. Trigger contract phải nói rõ duplicate, ordering, delay và replay thay vì giả định mỗi tín hiệu tương ứng đúng một run.

Bốn trigger family trong taxonomy phục vụ bốn nhu cầu. Schedule bảo đảm cadence nhưng không biết có thay đổi; event có latency thấp nhưng phụ thuộc delivery; goal trigger chạy tới condition thay vì theo thời gian; manual trigger phù hợp recovery và exceptional work. Một loop có thể hỗ trợ nhiều family nhưng chúng phải hội tụ vào cùng intake.

Với mỗi trigger, contract cần trả lời timezone, jitter, missed tick, catch-up, replay window và payload authority. Scheduled run sau downtime có backfill mọi khoảng đã mất hay chỉ chạy latest? Webhook retry trong bao lâu? Manual run có bypass policy không? Những lựa chọn này thay đổi workload và blast radius.

TriggerƯu thếDelivery riskPolicy bắt buộc
ScheduleCadence dự đoán đượcOverlap, missed tick, thundering herdJitter + overlap + catch-up rule
EventPhản ứng nhanhDuplicate, reorder, delayEnvelope + dedupe + freshness
GoalBám conditionPolling vô hạn, stale observationObservation source + stop bound
ManualRecovery và exception rõBypass/privilege escalationActor identity + same admission policy

GitHub Actions cho thấy một workflow có thể được kích hoạt bởi nhiều event và các event đồng thời tạo nhiều run. Điều đó hữu ích như transport behavior, nhưng domain vẫn phải xác định các run có cùng work key hay độc lập.

Ưu điểm
  • Trigger có semantics rõ nên replay và backfill dự đoán được.
  • Nhiều transport dùng chung intake contract.
  • Manual recovery không cần bypass guardrail.
Nhược điểm
  • Cần mô hình hóa clock và delivery edge cases.
  • Backfill có thể tạo burst lớn.
  • Event schema evolution cần compatibility policy.

2. Dùng identity, version và freshness để chống duplicate lẫn stale work

I.2
CloudEvents spec · examples/runnable
Dedupe là domain decision. Event envelope cung cấp provenance; work key và version mới quyết định hai tín hiệu có đại diện cùng một việc hay không.

CloudEvents cung cấp các trường như id, source, type, subjecttime. Chúng đủ để correlation và routing, nhưng không tự định nghĩa business uniqueness. Intake nên lưu cả delivery identity và stable work identity trong receipt.

Freshness không chỉ là tuổi event. Item có thể mới được deliver nhưng trỏ tới commit cũ, ticket đã đóng hoặc policy version hết hiệu lực. Freshness gate phải đọc authoritative source và so input version trước khi lease.

Idempotency key cho side effect nên dẫn xuất từ work key, operation và target version; không dùng attempt number. Nếu crash rồi retry, attempt mới phải nhận ra operation cũ đã commit và chuyển sang reconcile thay vì write lại.

Identity bundle tối thiểu
  • Delivery: event ID, source, type, emitted/received time.
  • Domain: work key, target ID, input version, policy version.
  • Execution: run ID, attempt, lease token, concurrency key.
  • Mutation: operation key, external resource ID, commit status.

Dedupe window hữu hạn cần được chọn theo delivery contract và retention. Window quá ngắn để duplicate cũ lọt vào; window vô hạn có thể chặn work hợp lệ sau khi domain reset. Tombstone và explicit generation thường an toàn hơn một boolean “processed forever”.

Ưu điểm
  • Chống duplicate model calls và external writes.
  • Freshness gate ngăn agent làm đúng trên input cũ.
  • Receipt có provenance đầy đủ để replay.
Nhược điểm
  • Stable key sai gây false merge hoặc false split.
  • Ledger cần retention và schema migration.
  • Reconciliation với external systems làm implementation phức tạp hơn.

3. Chọn queue, scan hay reactive theo source of truth và backpressure

I.3
TAXONOMY.md · Pattern Matrix
Intake mode quyết định nơi backlog sống. Queue tối ưu ownership, scan tối ưu reconciliation, reactive tối ưu latency; production thường cần hybrid.

Queue intake tốt khi producer biết rõ work item và có thể enqueue atomically. Nó cho visibility, retry và priority, nhưng queue có thể lệch khỏi source of truth. Scan intake đọc authoritative state nên chữa missed event tốt, đổi lại tốn query và phải có watermark. Reactive intake nhanh nhưng mang toàn bộ rủi ro delivery của event source.

Hybrid phổ biến là event để tăng tốc, queue để hấp thụ burst và periodic scan để reconcile. Ba path phải tạo cùng work key; nếu mỗi path có identity riêng, “reliability” vô tình nhân ba công việc.

ModeBacklog ở đâuFailure điển hìnhKhi nên dùng
QueueBroker/database queuePoison item, visibility timeout, starvationWork explicit, cần ownership/fairness
ScanAuthoritative sourceStale watermark, expensive full scanMissed-event repair, periodic reconciliation
ReactiveEvent streamDuplicate/reorder/dropLow latency và event source đáng tin
HybridQueue + source of truthIdentity mismatch giữa pathsProduction cần latency lẫn recovery

Backpressure phải tồn tại trước model. Khi capacity hết, intake defer hoặc queue; không spawn thêm agent rồi hy vọng budget tổng vẫn ổn. Concurrency key nên bám resource conflict—repository, customer, deployment target—chứ không chỉ bám tên workflow.

Fairness cần age, priority và per-tenant quota. Priority tuyệt đối dễ làm low-priority starvation; FIFO tuyệt đối có thể để một poison item chặn hàng. Dead-letter route phải kèm evidence và owner, không phải nghĩa địa không ai đọc.

Ưu điểm
  • Mode được chọn theo source of truth thay vì thói quen runtime.
  • Hybrid khôi phục missed events mà vẫn giữ latency thấp.
  • Backpressure bảo vệ cost và downstream systems.
Nhược điểm
  • Hybrid cần identity thống nhất.
  • Priority/fairness policy phải được vận hành.
  • Scan và reconciliation tăng load lên source system.

4. Lease, checkpoint và recovery đóng crash window

I.4
Temporal docs · GitHub Actions concurrency
Concurrency control không phải exactly-once. Lease ngăn hai worker active cùng lúc; checkpoint và idempotency xử lý crash giữa action và acknowledgement.

Concurrency group hữu ích để serialize run theo key và có thể cancel run cũ, nhưng cancellation không đảo ngược side effect đã xảy ra. Domain lease cần token/version để stale worker không commit sau khi lease được cấp lại.

Lease có TTL, heartbeat và renewal bound. TTL quá ngắn gây duplicate ownership khi tool call dài; quá dài kéo dài recovery. Worker mất heartbeat phải ngừng mutation ngay cả khi process vẫn sống—đây là fencing, không chỉ monitoring.

Durable execution có thể resume control flow sau crash, nhưng external API không nằm trong transaction của runtime. Pattern an toàn là persist intent, gọi operation bằng idempotency key, persist result; nếu không biết call đã commit, reconcile bằng external ID trước khi retry.

Loop lifecycle với persist nằm trước decision
Persist và decision phải bao quanh material transition; nếu chỉ checkpoint cuối run, crash window vẫn mở. ↗ Loop lifecycle

Recovery policy phân biệt transient failure, poison work và infrastructure outage. Retry storm sau outage có thể nặng hơn outage gốc, nên cần exponential backoff, jitter, global circuit breaker và admission ramp thay vì thả toàn bộ backlog.

Ưu điểm
  • Lease/fencing ngăn stale worker commit.
  • Durable checkpoint giảm restart-from-zero.
  • Reconciliation đóng ambiguous external-write window.
Nhược điểm
  • TTL và heartbeat cần tuning theo workload.
  • Không có distributed transaction cho mọi tool.
  • Recovery burst cần capacity plan riêng.

Failure map: từ delivery tới side effect Runbook

Một symptom “job chạy hai lần” có thể xuất phát từ event duplicate, schedule overlap, lease expiry hoặc crash sau write. Chẩn đoán cần xem identity bundle và causal receipt trước khi sửa retry count.

Triệu chứngEvidence cần xemControl
Cùng item chạy đồng thờiwork key, lease token, concurrency keyAtomic lease + fencing
Item cũ được xử lýevent time, input version, source snapshotFreshness gate + authoritative reread
External write lặpoperation key, external ID, checkpoint orderIdempotency + reconcile
Backlog tăng sau outagearrival/service rate, retry scheduleCircuit breaker + jittered ramp
Không còn việc vẫn tốn tokenno-work branch, model-call timestampPre-model no-work exit

Runbook nên drill ít nhất ba case: duplicate delivery, worker chết giữa write/checkpoint và backlog recovery. Nếu không tái hiện được ba case này, intake contract vẫn là mô tả chứ chưa là control.

Intake contract tối thiểu Recipe

Contract dưới đây thể hiện bốn outcome—accept, defer, reject và no-work—để scheduler không biến mọi tín hiệu thành retry. Mỗi outcome đều tạo receipt và cập nhật watermark hoặc ledger tương ứng.

Admission boundary trước mọi model call

TS
type IntakeDecision =
  | { kind: "accept"; item: WorkItem; lease: Lease }
  | { kind: "defer"; reason: string; retryAfter: Date }
  | { kind: "reject"; reason: string }
  | { kind: "no-work"; watermark: string };

async function intake(signal: Trigger): Promise<IntakeDecision> {
  const event = normalize(signal);            // identity + source + time
  if (!fresh(event) || seen(event.id)) return reject("stale-or-duplicate");
  const item = await resolveStableWorkKey(event);
  if (!eligible(item) || !withinScope(item)) return reject("policy");
  return leaseAtomically(item, concurrencyKey(item));
}
Bản đồ runtime theo persistence, isolation và control needs
Runtime hỗ trợ scheduling không thay thế intake semantics; hãy chọn sau khi contract đã rõ. ↗ Runtime Selection Guide
Definition of ready cho intake
  • Stable work key và input version có thể giải thích.
  • Duplicate, stale, no-work và capacity-exhausted đều có explicit branch.
  • Lease có fencing; side effect có idempotency key và reconcile path.
  • Schedule/event/manual cùng đi qua một admission policy.

Kết luận: intake tốt làm model ít việc hơn Conclusion

Work intake đáng tin không tối đa hóa số run; nó tối đa hóa tỷ lệ run có công việc hợp lệ và đủ điều kiện. Phần lớn duplicate, stale và overload nên bị loại trước prompt.

Khi trigger chỉ còn là transport, scheduler chỉ còn là capacity controller và intake là policy boundary, loop có thể thay runtime mà vẫn giữ semantics. Đó là dấu hiệu contract đã trở thành kiến trúc, không phải tài liệu.