---
url: https://docs.nemo12.com/reference/workflows.md
description: >-
  Workflow Reference: từng step đang chạy thật trong runtime với run log, khác
  với thiết kế WF-01..WF-20 ở docs/workflows.
---

# Workflow Reference

Phân biệt hai thứ cùng tên "workflow":

| | [`docs/workflows/`](../workflows/index.md) | Trang này |
| --- | --- | --- |
| Là gì | **Thiết kế** — luồng nghiệp vụ mong muốn (WF-01…WF-20) | **Thi hành** — cái đang chạy trong runtime, từng step, có run log |
| Đọc để | Hiểu hệ thống *nên* hoạt động thế nào | Debug "hôm qua chạy gì, bước nào hỏng" |

> ⚠️ **In-process là mặc định**: workflow của API chạy trong request, ghi lại bằng run log ba bảng
> dưới đây. **Ba ngoại lệ** là Cloudflare Workflows (`WorkflowEntrypoint`) thật, chạy ngoài hạ tầng
> run log này (Audit #013 T-10 bắt được trang này chỉ khai một):
>
> | Workflow | Worker · binding · class | Ghi sổ ở đâu | Tra run |
> | --- | --- | --- | --- |
> | **WF-19 LessonForge** (SRC-632) | `nemo12-foundry` · `LESSON_FORGE` · `LessonForge` | `content_runs` + `content_run_trials` | `GET /v1/admin/foundry/runs[/{id}]` |
> | **WF-20 ItemForge** (SRC-638) | `nemo12-foundry` · `ITEM_FORGE` · `ItemForge` | `content_runs` + `content_run_trials` (16 câu/bài theo khe) | như trên |
> | **DictationForge** (SRC-669) | `nemo12-api` · `DICTATION_FORGE` · `DictationForge` | `dictation_practice` (`status` pending → running → ready/failed, `error`) | `GET /v1/dictation/practices?learner_id=` (learner) |
>
> Xem [SDD-027](../architecture/sdd-027-content-foundry/index.md) cho hai cái đầu, `modules/dictation/forge.ts`
> cho cái thứ ba. Xem §5 để biết khi nào một workflow in-process cần đổi sang Cloudflare Workflows.

***

## 1. Hạ tầng run log

Ba bảng, ba câu hỏi khác nhau:

| Bảng | Trả lời |
| --- | --- |
| `workflow_runs` + `workflow_run_steps` | "Luồng nào đã chạy, bước nào hỏng, mất bao lâu?" |
| `engine_runs` | "Engine nào đã chạy, với input nào, ra version nào?" |
| `learner_model_versions` | "Nội dung model tại version N là gì?" |

Luật vàng: **ghi log không bao giờ được làm hỏng nghiệp vụ chính.** Mọi lỗi ghi log bị nuốt và chỉ `console.error("RUNLOG_DEGRADED")`. Learner mất một dòng log còn hơn mất bài làm.

```
startWorkflowRun()  → workflow_runs (status='running')
  recordStep()      → workflow_run_steps (seq tăng dần, succeeded|failed|skipped)
  runEngine()       → engine_runs, tự update status khi xong/lỗi
    saveModelVersion() → learner_model_versions (chỉ khi hash đổi)
finishWorkflowRun() → status='succeeded'|'failed', duration_ms, output_json
```

***

## 2. WF-04 — Evidence → Model Update

Workflow quan trọng nhất của hệ thống. `runModelUpdateWorkflow()` trong `modules/models/service.ts`.

### Trigger

| `trigger` | Từ đâu |
| --- | --- |
| `AssessmentCompleted` | Nộp bài chẩn đoán (`knowledge/routes.ts`), nộp đề thi (`exams/routes.ts`) |
| `GoalChanged` | Khai/sửa đích thi, thêm lịch thi học kỳ (`onboarding/routes.ts`) — chạy dưới mã WF-15 |
| `EXAM_RESCHEDULED` | Đổi ngày thi |
| `admin_recompute` | Admin bấm tính lại (`admin/observability.ts`) |

### 4 step, thứ tự bắt buộc

| # | Step | Engine | Vì sao đúng thứ tự này |
| --- | --- | --- | --- |
| 1 | `goal_engine` | Goal | Ba step sau đều tham chiếu Goal Model |
| 2 | `context_engine` | Context | Cần goal urgent nhất để dựng `active_goal_ref` |
| 3 | `learner_model_engine` | Learner Model | Cần goal version để nhúng `goals_summary` |
| 4 | `readiness_engine` | Readiness | Cần biết goal trỏ tới blueprint nào để chọn target |

Đảo thứ tự sẽ cho ra model tham chiếu version cũ của Goal — không crash, nhưng sai âm thầm. Đây là loại lỗi tệ nhất.

### Xử lý lỗi

Mỗi step bọc try/catch riêng: **một step hỏng không dừng các step sau**. Cuối cùng `status = failed` nếu có bất kỳ step nào hỏng, nhưng những model tính được vẫn được lưu. Suy giảm từng phần tốt hơn mất tất cả.

Hot path gọi qua `runModelsSafely()` — nếu cả workflow nổ thì chỉ log `model_update_degraded`, **học sinh vẫn nộp được bài**.

### Output

`output_json` của run chứa tóm tắt từng step:

```jsonc
{
  "goal_engine":          { "goals": 3, "conflicts": 1 },
  "context_engine":       { "version": 7, "changed": false },
  "learner_model_engine": { "version": 12, "changed": true },
  "readiness_engine":     { "version": 5, "changed": true }
}
```

`changed: false` nghĩa là engine đã chạy nhưng nội dung model không đổi — **không** phải là engine bị bỏ qua.

***

## 3. WF-15 — Declarations Intake

Cùng cỗ máy WF-04 nhưng mang mã và tên riêng để tra cứu tách bạch: model đổi vì **học sinh khai điều mới** (đích thi, lịch thi), không phải vì **làm bài**.

| Điểm gọi | `input` |
| --- | --- |
| Thêm exam target | `{ exam_target_id, kind }` |
| Sửa exam target | `{ exam_target_id, kind }`, trigger `EXAM_RESCHEDULED` nếu đổi ngày |
| Thêm lịch thi học kỳ | `{ semester_exam_id, exam_date }` |

***

## 4. Xem lại một run

| Câu hỏi | Endpoint (role `admin`) |
| --- | --- |
| Gần đây chạy gì? | `GET /v1/admin/workflow-runs` |
| Run này gồm bước nào? | `GET /v1/admin/workflow-runs/{runId}` |
| Engine nào đã chạy? | `GET /v1/admin/engine-runs` |
| Learner này có model gì? | `GET /v1/admin/learners/{learnerId}/models` |
| Lịch sử một model? | `GET /v1/admin/learners/{learnerId}/models/{modelKind}/versions` |
| Nội dung version cụ thể? | `GET /v1/admin/model-versions/{versionId}` |
| Tính lại ngay | `POST /v1/admin/learners/{learnerId}/recompute-models` |

Đây là cách thực hiện REQ-INT-29 (model version inspectable) — xem được **nội dung đầy đủ của từng phiên bản**, không chỉ bản mới nhất. Giao diện: `admin.nemo12.com`.

***

## 5. Workflow đã thiết kế nhưng chưa thi hành

[`docs/workflows/`](../workflows/index.md) mô tả WF-01…WF-19. Chạy thật trên hạ tầng run log:
**WF-04** và **WF-15**; chạy thật trên Cloudflare Workflows: **WF-19** (LessonForge, không dùng run
log — xem cảnh báo đầu trang). Các luồng còn lại hoặc đang là code inline chưa gắn run log, hoặc
chưa xây.

Khi nào cần chuyển sang **Cloudflare Workflows** thật (thay vì in-process):

* Luồng chạy > 30 giây hoặc vượt giới hạn CPU của một request.
* Cần retry có trạng thái qua nhiều bước (crawl đề — SDD-014, generation run — SDD-003).
* Cần chạy nền không có request nào kích hoạt → xem [schedules](schedules.md).

Cho tới lúc đó, in-process + run log là đủ và đơn giản hơn hẳn.

## Trace

* REQ-INT-29 (model version inspectable), REQ-NFR-01 (không làm hỏng nghiệp vụ chính).
* Thiết kế: [SDD-002](../architecture/sdd-002-learner-intelligence/index.md) §14/§15/§19, [SDD-006](../architecture/sdd-006-reliability.md) §2, [workflows/](../workflows/index.md).
* Kiểm chứng: QG-005, QG-009.
