Bộ lập lịch theo thời gian
Sessionization, slot lattice, least-slack-first, bảo toàn phút và evaluation của Planner.
Agent 4 hiện là backend tất định tại src/agents/planner/allocator.py, được gọi qua
POST /api/v1/plan/schedule. Frontend chỉ gửi dữ liệu và hiển thị kết quả qua
frontend/src/lib/api/plan.ts; frontend không còn là nơi sở hữu thuật toán lập lịch.
Planner nhận nguyên thứ hạng của Agent 3 nhưng không tính lại priority_score,
learning_importance hay urgency. Planner không dùng LLM, database, network hoặc
đồng hồ server nên cùng một request luôn có thể replay.
Mọi baseline số học nằm sau hằng số CÓ TÊN ở src/agents/schemas/planner_baselines.py —
một nguồn duy nhất cho cả backend và lớp trình bày:
| Hằng số | Giá trị |
|---|---|
PLANNER_VERSION | "1.1.0-deterministic" |
PLANNER_BASELINE_VERSION | "2026-08-31" |
PACING_MODE | "balanced" |
PLANNER_DEFAULT_HORIZON_DAYS | 14 |
PLANNER_MIN/MAX_HORIZON_DAYS | 1 / 60 |
SESSION_MIN/MAX_MINUTES | 30 / 60 |
SESSION_MINUTES_QUANTUM | 5 |
MAX_SESSIONS_PER_UNIT | 3 |
SESSION_SEQUENCE | ("learn", "practice", "review") |
MIN_DEFERRAL_SHORTFALL_MINUTES | 1 |
IMMINENT_DEADLINE_DAYS | 3 |
WINDOW_MAX_CAPACITY_MINUTES | 180 |
DAILY_MAX_CAPACITY_MINUTES | 540 |
CAPACITY_MINUTES_QUANTUM | 60 |
pacing_mode = "balanced" là hằng số, không phải núm điều chỉnh của request: nó xuất
hiện trong response để đọc được chế độ đã dùng, chứ client không đặt được nó.
1. Input contract
plan_start_date: ngày bắt đầu do client cung cấp;daily_windows: sức chứa sáng/chiều/tối của một ngày mẫu;priority_items: itemranking_status="complete"cópriority_rank;course_deadlines: ngày thi từng môn, có thểnull;horizon_days: ghi đè horizon, hoặc để Planner suy ra.
Sức chứa một window phải là bội của 60, từ 0 đến 180 phút; tổng một ngày không vượt 540 phút.
2. Planning horizon — chân trời lập kế hoạch
Nếu request đặt horizon_days, giá trị được kẹp vào khoảng 1–60 ngày. Nếu không,
Planner lấy ngày thi xa nhất:
Nếu không có deadline hợp lệ:
Planner không đọc date.today(). Mốc duy nhất là plan_start_date trong request.
3. Sessionization — chia learning unit thành buổi học
Gọi là thời lượng tối đa hiệu dụng của một session:
Nếu không có window dương, dùng để vẫn sinh inventory; các session sau đó được hoãn với lý do số học thay vì biến mất.
Khối lượng còn lại được chuẩn hóa:
Nếu bằng 0 hoặc không hợp lệ, baseline dùng 60 phút. Số session:
Thời lượng mỗi session:
Thứ tự session của một learning unit luôn là:
learn → practice → reviewKhông có enrichment 80%. Quỹ trống không tự sinh thêm session ngoài inventory từ
remaining_work_minutes.
4. Slot lattice và least-slack-first
Planner tạo lattice theo thứ tự:
ngày tăng dần × morning → afternoon → eveningVới mỗi môn , required và available trước deadline là:
Trong mỗi slot, Planner chỉ xét session tiếp theo của từng unit và thỏa cả hai hard constraint:
Ứng viên có slack thấp hơn được chọn trước. Fresh-unit, xen kẽ môn, deadline gần, priority rank và ID chỉ là tie-break tất định; chúng không được vượt hard constraint.
5. Bảo toàn phút và deferral
Sau mỗi lần đặt:
Các bất biến bắt buộc:
Mỗi session không đặt được phải có shortfall_minutes ≥ 1 và đúng một reason code:
NO_WINDOW_FITS_MIN_SESSION: session dài hơn window lớn nhất;DEADLINE_BEFORE_FEASIBLE_SLOT: không có slot nào trước deadline;HORIZON_EXCEEDED: deadline ở ngoài horizon nhưng horizon đã hết chỗ;CAPACITY_SHORTFALL: chỗ trống lớn nhất còn lại không chứa được session.
Không còn fixed Top-K. Learning unit chỉ bị hoãn khi tồn tại thiếu hụt số học thật; không bị cắt vì đứng thứ tư trong cùng môn.
6. Ranh giới Agent 3 và Agent 4
| Agent 3 | Agent 4 Planner |
|---|---|
Tạo priority_rank | Đọc rank nguyên văn |
| Mô tả áp lực capacity | Phân session vào ngày/window |
| Không split/defer/drop | Sessionize và defer có reason code |
| Không có ngày/slot lịch học | Sinh ngày, timeframe, duration |
| Tất định, Zero-LLM | Tất định, Zero-LLM |
Ba ranh giới không được xóa: Need ≠ Learning Importance, Priority ≠ Capacity,
Agent 3 ≠ Planner.
7. AI advisory — đã viết, chưa nối dây
advisory trong PlanScheduleResponse luôn là null: asset gate §5.6 còn ĐÓNG.
Mã advisory của Agent 4 đã tồn tại nhưng không nằm trong request path. ADR 11 tuyên bố
việc ngắt; ADR 16 mới là thứ thi hành nó trong code, kèm hai test mục L ở
tests/test_agents/test_planner_advisory.py — một trong hai đọc AST để chứng minh route
không còn gọi seam advisory.
Artifact eval/results/planner_advisory_clause4_v1.md trả lời nửa clause 4 mà không
cần API key: không field nào của advisory đặt được một hard constraint. Nửa còn lại — lợi
ích đo được cho người học — vẫn thiếu, nên gate ĐÓNG.
8. Kết quả evaluation — snapshot v2
Artifact hiện hành:
eval/results/agent4_planner_evaluation_v2.json;eval/results/agent4_planner_evaluation_v2.md.
Cấu hình chạy 2026-08-31: seed 42, 10 golden scenario, 1.000 property case và 200 exact-oracle case. Kết luận P0 hiện là FAIL:
- golden mismatch: 0 trên 10 case;
- toàn bộ 8 invariant trên 1.010 case: 0 lỗi;
- 5.382 quan hệ metamorphic: 0 violation;
- reproducibility: hai run hash giống nhau;
- exact oracle: 30 phút regret, 6 extra session, 7/200 case positive-regret.
Nguyên nhân quan sát được là allocator greedy đôi khi chọn một session dài trước, làm mất cơ hội ghép nhiều session ngắn trong cùng quỹ phút. Đây là kết quả đánh giá, không phải bằng chứng cho phép tự động thay đổi allocator. Cần duyệt lại objective và policy trước khi sửa thuật toán.
PACING_MODE = balanced (ADR 17) sửa việc lịch bị dồn vào những ngày đầu; nó không
nhằm và không làm giảm oracle regret — hai kết luận này không được trộn vào nhau.
Snapshot v1 (2026-08-23, 8 scenario) được giữ bất biến để đối chiếu.
P1 về utilization, fragmentation, inversion, starvation, churn và performance chỉ là số đo mô tả, chưa có threshold.
9. Lệnh kiểm chứng
.venv/bin/python scripts/evaluate_agent4_planner.py \
--seed 42 \
--property-cases 1000 \
--oracle-cases 200 \
--output eval/results/agent4_planner_evaluation_v2.json \
--report eval/results/agent4_planner_evaluation_v2.md \
--evaluated-at "2026-08-31T12:00:00+00:00"CLI trả exit code 0 khi P0 PASS, 1 khi P0 FAIL và 2 khi input/config không hợp lệ.