Architecture
Curriculum OS - 학습 경로 합성
학습 커리큘럼을 outcome 그래프 + prerequisite DAG로 끌어올려 학습 경로를 합성하는 레이어.
Curriculum OS
학습 커리큘럼을 단순 카테고리 트리에서 outcome 그래프로 끌어올린 레이어다. 사용자/teacher가 도메인(목적)을 지정하면 prerequisite을 따라 레슨 순서를 짜고, 커버되지 않는 outcome은 gap으로 리포트한다. Skill OS가 "도구 선택"이라면, Curriculum OS는 "학습 경로 합성"이다.
5계층 구조 (Phase 1~3 완성형)
taxonomy (SSOT YAML - outcomes/domains/capabilityClaims/taskFamilies/lessonOutcomes)
↓
lessonGraph (outcome 매핑 + prerequisite DAG + section/role 메타)
↓
engine (composer + reviewScheduler + outcomeCredit + outcomeMastery + analyticsTimeline + learnerStateBridge)
↓
{REST API, teacher tools}
↓
UI surfaces (MasterPlanPanel + MasteryPanel + TodayReviewsCard + AnalyticsPanel)
SSOT - curricula/python/_taxonomy.yml
세 가지 vocabulary를 한 파일에 둔다.
- outcomes: 학생이 한 레슨을 마치고 얻는 단위 능력. ID는
<도메인 prefix>.<능력명>lowerCamelCase. 예:python.variables,pandas.aggregate,automation.browser.session. - domains: 학습자가 실제로 달성하려는 실무 목표. 승격 경로는 versioned
capabilityClaims와 required TaskFamily를 함께 가진다. - taskFamilies: 같은 underlying rule을 acquisition, transfer, retrieval, application의 다른 표면 조건으로 관찰하는 평가 가족. outcome evidence slice, fixture, CheckSpec version, checker corpus와 artifact contract를 소유한다.
- lessonOutcomes:
<category>/<contentId>키별 backfill. 레슨 YAML 메타에outcomes/prerequisites가 있으면 그 쪽이 우선, 없으면 여기 값으로 채운다. - sectionOutcomes (Phase 2b): 같은 항목 안의 dict -
{sectionId: [outcome, ...]}. 다중 outcome lesson 에서 어느 섹션이 어느 outcome 을 검증하는지 매핑한다. 미지정 시 lesson outcomes 전체로 fallback. - lessonRole (Phase 2d):
concept | practice | project. project 는 deliverable-driven plan 합성 시 우측 칼럼에 배치되는 마지막 lesson.
새 레슨을 만들 때 권장 순서:
- 필요한 outcome/domain이 없으면
_taxonomy.yml에 먼저 추가 - 레슨 YAML
meta에outcomes/prerequisites/estimatedMinutes적기 (권장) 또는lessonOutcomes에 등록 tests/curriculum/testCurriculumOs.py가 그래프 무결성을 검증
planComposer 알고리즘
결정적(deterministic). 같은 입력이면 같은 plan을 낸다.
- 목표 outcome 해석 -
goal.domain.targetOutcomes ∪ goal.outcomes - projectIntent 매칭 (Phase 2d) - 한국어/영어 키워드를 통해 카테고리 boost, 매칭된 project lesson 들의 outcomes 도 target 에 추가.
- skipMasteredOutcomes 필터 (Phase 1) -
MASTERY_THRESHOLD이상 outcome 은 target/expansion 모두 차단. - Backward expansion - 각 target outcome에 대해 best lesson을 선택하고, 그 레슨의 prerequisite outcomes를 큐에 다시 넣는 BFS. 카테고리 우선순위 + sortKey 휴리스틱.
- project lesson 강제 포함 (Phase 2d) - projectIntent 매칭 시 backward expansion 이 입문 lesson 으로 같은 outcome 을 cover 했더라도 deliverable lesson 까지 도달.
- 위상 정렬 - 선택된 레슨 사이의 prerequisite 의존을 그래프로 만들고 Kahn's algorithm으로 정렬. 사이클이 있으면 sortKey fallback.
- 완료 필터 -
progressTracker에서completedAt이 있는 레슨 제외 (옵션). - deliverableOnly 분리 (Phase 2d) - mastery>=0.6 인 outcome 만으로 구성된 concept lesson 은
droppedSteps로. - maxMinutes cropping (Phase 2a) - prerequisite 순서 유지하며 시간 예산 초과 step 을
droppedSteps로. - 3단 분리 (Phase 2d) -
lessonRole기준으로conceptSteps / practiceSteps / projectSteps합성. - Gap 리포트 - provider 레슨이 없는 outcome은
PlanGap으로 분리 반환 (gaps), 학습자 mastery 가 낮으면dynamicGaps.
PRIMARY_CATEGORY_ORDER에 카테고리 우선순위가 명시되어 있다 (기초가 앞).
Legacy 진도 진단과 현재 권위
outcomeCredit.py, outcomeMastery.py, reviewScheduler.py, 수동 validation과 unified blend는 기존 화면과 진단 호환을 위해 남아 있다. 이 경로는 현재 capability assurance, application, golden publication을 바꾸지 못한다. 현재 단계 변경 권위는 AttemptObserved -> canonical LearningEvent -> MasteryPolicy@2 -> CapabilityProjection 한 경로다.
새 credit은 explicit assessmentRole: assurance | application과 promoted TaskFamily identity가 모두 있는 strong check만 쓴다. unmarked legacy assessment는 formative 실행과 feedback에는 쓰지만 새 assurance credit을 만들지 않는다.
Phase 3 - Analytics & Bridge
학습 패턴을 시간축으로 + 두 mastery source 통합:
analyticsTimeline.py: 일별DailySnapshot(masteredCount, lessons/sections/credits today, hintHistogram, domainsTouched). JSONL append-only, idempotent per day.learnerStateBridge.py:outcomeMastery(progress 합성) 와learnerState(EMA SQLite) 를 0.6/0.4 blend 한UnifiedOutcomeMastery. 두 저장소는 합치지 않음 - 합성 view 만./api/curriculum/analytics//analytics/summary//mastery/unified엔드포인트.
API 계약
| Endpoint | Method | Payload | 반환 |
|---|---|---|---|
/api/curriculum/taxonomy |
GET | - | {outcomes, domains} |
/api/curriculum/master-plan |
POST | {domain?, outcomes?, excludeCompleted?, excludeKeys?, skipMasteredOutcomes?, maxMinutes?, projectIntent?, deliverableOnly?} |
MasterPlan (steps, gaps, droppedSteps, conceptSteps/practiceSteps/projectSteps, projectMatches, totalMinutes, summary) |
/api/curriculum/gaps |
GET | ?domain= (옵션) |
{gaps: [{domainId, domainLabel, missing}]} |
/api/curriculum/mastery |
GET | - | MasteryReport (outcomes, domains, masteredOutcomeCount, totalOutcomeCount) |
/api/curriculum/capabilities/{domainId} |
GET | - | current-version 보증, 적용과 evidence receipt |
/api/curriculum/artifacts/{contentHash} |
GET | - | 검증 후 보존한 Local artifact bytes |
/api/curriculum/mastery/unified |
GET | - | UnifiedMasteryReport (progress + learnerState blend) |
/api/curriculum/outcomes/validate |
POST | {outcomeId, validated} |
toggle 결과 |
/api/curriculum/check |
POST | {... category?, contentId?, sectionId?} |
CheckResult + creditedOutcomes + autoValidatedOutcomes |
/api/curriculum/reviews |
GET | - | {reviews, totalDue} |
/api/curriculum/reviews/{cat}/{id} |
POST | {success} |
updated ReviewState |
/api/curriculum/analytics |
GET | ?days=30 |
{snapshots, totalSnapshots} |
/api/curriculum/analytics/summary |
GET | - | 30 일 집계 |
unknown domain/outcome ID는 400 + curriculum_unknown_domain / curriculum_unknown_outcome.
Teacher 도구
teacher-tool-loop에 등록된 7개:
| 도구 | 용도 |
|---|---|
list-curriculum-domains |
도메인/outcome 카탈로그 |
resolve-learning-goal |
자연어 → 도메인 후보 랭킹 |
search-curricula |
키워드/카테고리/outcome 기준 레슨 검색 |
compose-master-plan |
도메인/outcome → 순서대로 정렬된 레슨 plan |
inspect-curriculum |
특정 레슨의 meta/intro 조회 |
list-curriculum-gaps |
도메인별 미충족 outcome 리포트 |
propose-curriculum-draft |
갭을 채울 새 강의 초안만 반환 (사람이 검토·작성) |
bulk generation 금지:
propose-curriculum-draft는 outline + 메타데이터만 돌려준다. 실제 강의 YAML은 사람이 작성한다 (CLAUDE.md,feedback_curriculum_no_bulk_generation).
프론트엔드
세 가지 surface 가 등록되어 있다.
SurfaceMode = "plan"은 legacy 진도 진단과 경로 합성을 제공한다. capability 성취는 curriculum home의 검증된 기본 경로 카드가 canonical projection으로 표시한다.masterPlanPanel.tsx:- 도메인 칩 선택 → 자동 plan 합성
- 시간 예산 / 이미 익힌 능력 건너뛰기 토글
- projectIntent 텍스트 입력 → deliverableOnly 자동 활성 + 매칭 키워드 chip + 단일↔3단 뷰 토글
- 3단 뷰 (
TieredPlanBody): 개념(zinc) → 실습(sky) → 프로젝트(emerald) 칼럼 TodayReviewsCard: 오늘 due review 리스트 + 통과/실패 버튼MasteryPanel: outcome 별 progress + credit count + 자동/수동 검증 토글- step 클릭 →
selectCurriculumCategory/Content→ 해당 레슨 - gap "초안 요청" 버튼 → chat surface + propose-curriculum-draft 자동 입력
SurfaceMode = "analytics"-analyticsPanel.tsx:- mastered/30일 lessons/sections/credits stat 카드 4 개
- SVG mastery 추세 라인 차트
- 힌트 분포 바 차트
- 최근 활동 도메인 chip
- 학습 셀 실행 뒤
curriculumSurface.tsx가 결과를 자동 평가하고, 통과·실패 근거와 필요한 다음 수정을 셀 바로 아래에 표시한다. 별도 검증 버튼, hint reveal, prediction diff, 수동 outcome credit 카드는 사용하지 않는다.
무결성 게이트
tests/curriculum/testCurriculumOs.py가 그래프 무결성을 검사한다. 아래는 절대 수치가 아니라 불변식이며, 케이스는 카탈로그가 커져도 그대로 유지된다:
- 모든 domain.targetOutcomes가 outcome 카탈로그에 존재
- 모든 lessonOutcomes의 outcomes/prerequisites가 카탈로그에 존재
- 레슨 메타의 outcomes가 taxonomy backfill보다 우선
- 합성기가 prerequisite 순서를 지킴
- 완료된 레슨이 plan에서 제외됨
- gap이 정확히 리포트됨
- 모든 레슨이 plan 그래프에서 보임 (orphan 0개)
- 모든 outcome이 어떤 레슨에서 제공됨 (uncovered 0개)
- 모든 도메인이 비어있지 않은 plan을 만듦 (gap 0개)
- 같은 입력은 같은 plan을 낸다 (결정성 스냅샷)
- API 엔드포인트 통합 테스트 (FastAPI TestClient)
tests/curriculum/auditCurriculumWeakness.py가 영구 게이트로 등록되어 있다 (tests/run.py gate curriculum-weakness-audit):
- 정적 신호:
orphanInPlan,noExercise(intro00_*제외),exerciseWithoutCheck,proseOnlyCheck,noHint,shortGoal,sectionIdMissing(Phase 2b) - 카테고리 신호:
categoryWithoutProject(Phase 2d). 면제 카테고리는PROJECT_EXEMPT_CATEGORIES가 소유하며 현재builtins,excel,practical,devTools,resilience다. 나머지 카테고리는lessonRole: project레슨을 최소 1개 가져야 한다. - 각 신호별 임계치를 넘으면 게이트 실패. 대부분 0이고
proseOnlyCheck만 현재 부채를 고정한 ratchet이다. - 리포트:
output/test-runner/curriculum-weakness-audit/curriculum-weakness-report.json
검증 실태와 수렴 방향
exercise를 가진 468개 레슨의 실제 검증 상태다. 숫자보다 중요한 것은 세 상태가 서로 다른 문제라는 점이다.
| 상태 | 레슨 | 뜻 |
|---|---|---|
산문만 (proseOnlyCheck) |
236 | {noError: "...", resultCheck: "..."}처럼 type도 kind도 없다. 사람이 읽는 설명문이고 어떤 엔진도 실행하지 않는다 |
| 약한 실행 검사 | 225 | type: noError뿐. 예외만 안 나면 통과하고 정답 여부는 보지 않는다 |
| 정답 판정 검사 | 7 | outputExact, contains, 또는 CheckSpec v2 |
체크 어휘가 두 런타임으로 갈라져 있는 것이 현재 가장 큰 부채다. src/codaro/curriculum/exerciseCheck.py는 output/variable/contains/noError를 실행하고, editor/src/lib/learningAttemptCheck.ts는 CheckSpec v2와 outputExact를 실행한다. 두 집합은 겹치지 않는다. learningAttemptCheck.ts의 evidence 등급(none/practice/strong)이 학습자에게 실제로 보이는 계약이므로, 수렴 목표는 CheckSpec v2 하나이며 나머지는 마이그레이션 대상이다.
작업 순서는 지표 정직화, 계약 수렴, 커버리지 상승이다. 커버리지 상승은 레슨 콘텐츠 작업이라 사람이 직접 쓴다. 앞의 두 단계가 끝나야 어느 레슨에 어떤 검사를 써야 하는지가 기계적으로 정해진다.
확장 가이드
새 도메인 추가:
_taxonomy.yml의domains:에 항목 추가 - id, label, description, targetOutcomes- targetOutcomes에 들어가는 outcome이 모두 존재하는지 확인
- 해당 outcome을 제공하는 레슨이 없으면 gap으로 리포트됨 -
list-curriculum-gaps로 확인 - 갭을 채우려면 새 강의를 직접 작성 (생성기 스크립트 금지)
새 outcome 추가:
outcomes:에 항목 추가- 어느 레슨이 이 outcome을 제공하는지
lessonOutcomes에 매핑 - 또는 그 레슨 YAML의meta.outcomes에 등록 - 이 outcome을 prerequisite으로 갖는 다른 레슨이 있다면 cycle이 생기지 않게 확인
캐시는 lazy - 첫 호출 시 로드, 이후 메모리 보존. 핫 리로드는 CurriculumOsCache.invalidate().