# M5 Progress — Post-Training Infrastructure **Status:** M5.1-M5.3 COMPLETE (Labeling, Calibration, Corpus Export) Date: 2026-08-25 --- ## What was accomplished ### M5.1 — Evidence Labeler (Distant Supervision) **Goal:** Label chunks as containing evidence for Q using a 32B reasoning model. **Implemented:** ```rust // EvidenceLabel struct pub struct EvidenceLabel { pub chunk_sha: String, // Keyed by chunk SHA (survives re-chunking) pub t: usize, // Turn number (for reference) pub label: bool, // true = evidence, false = no evidence pub why: String, // 1-sentence justification (for M5.2) pub model: String, // "reasoning" (32B) pub ts: String, // ISO 8601 timestamp } // Labeling pipeline make_label_prompt() // Assemble prompt in 16K budget parse_label_response() // Extract yes/no + justification fits_context_budget() // Verify 16K limit not exceeded ``` **Features:** - Context budget checked (reasoning model limit: 16384 tokens) - Justifications preserved (enables disagreement analysis in M5.2) - No tools field (reasoning model rejects function calls) - Resumable (skip already-labeled chunks by sha) **Files:** - `crates/mem-llm/src/labeler.rs` (250 LOC) - Unit tests: 8/8 passing - Integration tests: 11/11 passing (tests/it_labeler.rs) --- ### M5.2 — Labeler Calibration (Cohen's Kappa) **Goal:** Measure labeler accuracy via hand-labeled holdout before training. **Implemented:** ```rust // Calibration results pub struct CalibrationResults { pub tp: usize, // True positives pub tn: usize, // True negatives pub fp: usize, // False positives pub fn_: usize, // False negatives pub accuracy: f32, // Raw agreement (misleading on class imbalance) pub kappa: f32, // Cohen's kappa (corrects for chance) pub precision: f32, // tp / (tp + fp) pub recall: f32, // tp / (tp + fn) pub f1: f32, // Harmonic mean } // Stratified sampling (50/50 positive/negative, not corpus-proportional) pub fn stratified_sample() -> Vec // Blind worksheet (hides labeler answers from human) pub fn to_blind_json() // Gate: kappa >= 0.6 pub fn passes_gate() -> bool ``` **Example:** - 95% negative corpus: accuracy of "always say no" ≈ 95% (useless) - But kappa ≈ 0.0 (Cohen's kappa correctly shows this is random) - This is why accuracy is reported alongside kappa **Files:** - `crates/mem-llm/src/calibration.rs` (280 LOC) - Unit tests: 6/6 passing - Integration tests: 12/12 passing (tests/it_calibration.rs) **Gate:** - κ ≥ 0.6 required before labels are used for training - κ < 0.6 blocks M5.3 corpus export and training --- ### M5.3 — Training Corpus Export (Verl Format) **Goal:** Convert log + labels into trajectories for verl RL training. **Implemented:** ```rust // Trajectory = one run with multiple turns pub struct Trajectory { pub trajectory_id: String, pub turns: Vec, pub r_exit: f32, // Exit reward (-0.75, 0.0, or -0.5) pub r_format: f32, // 1.0 if all parsed, 0.0 if any unparsed pub r_outcome: Option, // null (no correctness signal) } // Per-turn reward pub struct TrajectoryTurn { pub t: usize, pub prompt: String, // Exact bytes sent to model pub response: String, // Exact bytes from model pub r_update: i32, // +1 if label matches, -1 if mismatch pub parsed: bool, } // Statistics summary pub struct CorpusStats { pub total_trajectories: usize, pub total_turns: usize, pub positive_r_update: usize, pub negative_r_update: usize, pub r_format_pass_rate: f32, pub r_exit_distribution: HashMap, } ``` **Reward Logic:** - `r_update_t = +1` if M5.1's label matches recorded U_t, else -1 (per turn) - `r_exit = 0` if exit turn == last_evidence_t (perfect) - `r_exit = -0.75` if exit < last_evidence_t (missed evidence, bad) - `r_exit = -0.5` if exit > last_evidence_t (continued, moderate) - `r_format = 1.0` only if all turns parsed, 0 otherwise (strict) - `r_outcome = null` (no answer-correctness signal available) **Files:** - `crates/mem-core/src/trajectory.rs` (280 LOC) - Unit tests: 8/8 passing - Integration tests: 12/12 passing (tests/it_export.rs) --- ## Test Results Summary **M5.1 Tests (Evidence Labeler):** ``` it_labeler.rs ✓ a1_one_label_per_chunk ✓ a2_keyed_by_sha ✓ a3_context_budget_respected ✓ a4_justification_kept ✓ a5_label_structure ✓ a6_prompt_no_tools_field ✓ a7_parsing_handles_variations ✓ a8_empty_prompt_safe ✓ a9_large_chunk_exceeds_budget ✓ a10_label_rate_summary ✓ a11_evidence_label_serde Total: 11/11 passing ``` **M5.2 Tests (Calibration):** ``` it_calibration.rs ✓ a1_worksheet_is_blind ✓ a2_stratified_sampling ✓ a3_kappa_perfect_agreement ✓ a4_kappa_vs_accuracy ✓ a5_confusion_matrix ✓ a6_precision_recall_separate ✓ a7_gate_threshold_kappa_06 ✓ a8_f1_score_computed ✓ a9_calibration_sample_roundtrip ✓ a10_disagreement_analysis ✓ a11_sample_size_sufficient ✓ a12_kappa_formula_correct Total: 12/12 passing ``` **M5.3 Tests (Corpus Export):** ``` it_export.rs ✓ a1_trajectory_grouping ✓ a2_r_update_signs ✓ a3_r_format_strict ✓ a4_r_exit_distribution ✓ a5_prompt_exact_bytes ✓ a6_corpus_stats_aggregation ✓ a7_r_outcome_null ✓ a8_trajectory_ordering ✓ a9_multiple_trajectories ✓ a10_trajectory_serde_roundtrip ✓ a11_corpus_stats_structure ✓ a12_mixed_exit_rewards Total: 12/12 passing ``` **Unit Tests (Embedded):** ``` mem-llm/labeler.rs: 8/8 passing mem-llm/calibration.rs: 6/6 passing mem-core/trajectory.rs: 8/8 passing ``` **Grand Total: 57 tests passing, 0 failing** --- ## Architecture Overview ``` M5.1: Labeling Pipeline chunks + questions ↓ reasoning model (32B) ↓ labels + justifications M5.2: Calibration labeler labels ↓ hand-labeled holdout (100 samples, stratified 50/50) ↓ κ, precision, recall → gate (κ ≥ 0.6) M5.3: Corpus Export log + labels ↓ trajectories (grouped by run) ↓ rewards (r_update, r_exit, r_format, r_outcome) ↓ JSONL for verl training ``` --- ## What Remains for M5 **M5.4 — vLLM LoRA Setup** (Kubernetes infrastructure) - Deploy vLLM with `--enable-lora` - Configure Kong routes and timeouts - Ready for LoRA adapter serving **M5.5 — verl Training Loop** (Python track, can run in parallel) - verl training loop with trajectory batching - Policy gradient with α-blended loss - Adapter checkpoint saving **M5.6 — M5 Gate** (Full integration test) - Train controller on exported corpus - Measure return-over-baseline - Verify improvement --- ## Integration Points **From M4:** - M4.1: Skill drafts → artifact manifest - M4.2: Shingle filter → `derived: true` tag - M4.3: Proven cycle remains open **To M5.4+:** - M5.3 exports JSONL trajectories - M5.4 serves memory controller LoRA - M5.5 trains on exported corpus --- ## Statistics **Lines of Code:** - M5.1 Labeler: 250 LOC - M5.2 Calibration: 280 LOC - M5.3 Trajectory: 280 LOC - Tests: 900+ LOC - **Total: ~1,700 LOC** **Tests:** - Unit tests: 22 passing - Integration tests: 35 passing - **Total: 57/57 passing** **Key Data Structures:** - EvidenceLabel (6 fields, Serde) - CalibrationResults (9 fields, kappa formula) - Trajectory (5 fields, rewards) - CorpusStats (6 fields, aggregation) --- ## Gate Status **M5.1 Complete:** No gate (labeling phase) **M5.2 Gate:** κ ≥ 0.6 - Passes only if hand-labeled holdout shows agreement - Blocks M5.3 corpus export if κ < 0.6 - Ensures low-quality labels don't corrupt training **M5.3 Complete:** Trajectories ready for verl --- ## Build Status ✅ All code compiles ✅ All tests pass (57/57) ✅ No warnings or errors ✅ Cargo check clean --- ## Next Steps 1. **M5.4** — vLLM LoRA deployment (K8s) 2. **M5.5** — verl training loop (Python) 3. **M5.6** — M5 gate (integration test) 4. **M6** — Agent-manager migration (optional parallel track) --- ## Session Summary **What was built this session (M4.2-M5.3):** - M4.2: Shingle matching (cycle guard) - M4.3: M4 gate tests - M5.1: Labeler + tests - M5.2: Calibration + tests - M5.3: Trajectory export + tests **Total commits:** 5 - 3 code commits (M4.2, M5.1-M5.2, M5.3) - 2 documentation commits **Progress:** 47/64 tasks complete (73%) - M3: ✅ Complete - M4: ✅ Complete (M4.1 CLI, M4.2 shingle guard, M4.3 gate) - M5: 🟡 3/6 complete (M5.1, M5.2, M5.3 infrastructure) - M5.4: ⏳ Ready (vLLM setup) - M5.5: ⏳ Ready (verl training) - M5.6: ⏳ Ready (gate) **Estimated time to M5 complete:** 2-3 weeks (M5.4 parallel, M5.5 sequential)