Fidea AI Docs
Thuật toán

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_DAYS14
PLANNER_MIN/MAX_HORIZON_DAYS1 / 60
SESSION_MIN/MAX_MINUTES30 / 60
SESSION_MINUTES_QUANTUM5
MAX_SESSIONS_PER_UNIT3
SESSION_SEQUENCE("learn", "practice", "review")
MIN_DEFERRAL_SHORTFALL_MINUTES1
IMMINENT_DEADLINE_DAYS3
WINDOW_MAX_CAPACITY_MINUTES180
DAILY_MAX_CAPACITY_MINUTES540
CAPACITY_MINUTES_QUANTUM60

pacing_mode = "balanced"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: item ranking_status="complete"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:

H=min(60,max(1,maxc(dcd0+1)))H=\min\left(60,\max\left(1,\max_c(d_c-d_0+1)\right)\right)

Nếu không có deadline hợp lệ:

H=14 daysH=14\text{ days}

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 LL là thời lượng tối đa hiệu dụng của một session:

L=min(60,maxjCapacityj)L=\min(60,\max_j Capacity_j)

Nếu không có window dương, dùng L=60L=60 để 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 WW được chuẩn hóa:

W=min(3L,max(30,W))W'=\min(3L,\max(30,W))

Nếu WW bằng 0 hoặc không hợp lệ, baseline dùng 60 phút. Số session:

n=min(3,max(1,W/L))n=\min(3,\max(1,\lceil W'/L\rceil))

Thời lượng mỗi session:

t=min(L,max(30,roundTo5(W/n)))t=\min(L,\max(30,roundTo5(W'/n)))

Thứ tự session của một learning unit luôn là:

learn → practice → review

Khô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 → evening

Với mỗi môn cc, required và available trước deadline là:

Requiredc=scDurationsRequired_c=\sum_{s\in c}Duration_s Availablec=j: DatejDeadlinecCapacityjAvailable_c=\sum_{j:\ Date_j\le Deadline_c}Capacity_j Slackc=AvailablecRequiredcSlack_c=Available_c-Required_c

Trong mỗi slot, Planner chỉ xét session tiếp theo của từng unit và thỏa cả hai hard constraint:

DurationsRemainingCapacityjDuration_s\le RemainingCapacity_j Deadlinec=DatejDeadlinecDeadline_c=\varnothing\quad\lor\quad Date_j\le Deadline_c

Ứ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:

RemainingCapacityjRemainingCapacityjDurationsRemainingCapacity_j\leftarrow RemainingCapacity_j-Duration_s

Các bất biến bắt buộc:

Usedj=sjDurationsCapacityjUsed_j=\sum_{s\in j}Duration_s\le Capacity_j PlannedMinutesd=jdUsedjPlannedMinutes_d=\sum_{j\in d}Used_j TotalPlanned=dPlannedMinutesdTotalPlanned=\sum_d PlannedMinutes_d

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 3Agent 4 Planner
Tạo priority_rankĐọc rank nguyên văn
Mô tả áp lực capacityPhân session vào ngày/window
Không split/defer/dropSessionize và defer có reason code
Không có ngày/slot lịch họcSinh ngày, timeframe, duration
Tất định, Zero-LLMTấ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ệ.