Created: 2026-09-01 (Step 1 of the Clinical Training Dataset & Nightmare
Fuel Pipeline, tracker:
.agent/internal/plans/2026-09-01-clinical-nf-pipeline-tracker.md) Purpose:
Single source of truth for the 5-stage training-record phasing model — stage
definitions, quotas, classification, dedup policy, manifest layout, and split
conventions. Steps 7–9 (generation, consolidation) validate against this doc.
Authoritative code:
- Stage model + quotas + splits:
ai/pipelines/orchestration/stage_organizer.py(PIX-4193) - Dedup policy:
ai/pipelines/ingestion_deduplication.py(PIX-4192) - V7 consolidation path (same policy):
ai/pipelines/data_processing/orchestration/consolidate_v7.py - Streaming variants:
s3_stage_organizer.py(S3 → gdrive viaS3Streamer),stage_organizer_streaming.py(two-pass, memory-bounded) - Stage 5 ingestion:
ai/pipelines/dpo_ingestion.py(PIX-4199)
| # | Stage ID | Purpose | Target % (code) | Quality profile (empathy / clinical / safety floors) |
|---|---|---|---|---|
| 1 | stage1_foundation |
General psych/mental-health foundation | 0.35 | 0.70 / 0.30 / 1.0 |
| 2 | stage2_therapeutic_expertise |
Modality-grounded clinical work (CBT, DBT, EMDR, …) | 0.25 | 0.75 / 0.50 / 1.0 |
| 3 | stage3_edge_stress_test |
Edge cases + nightmare fuel (adversarial stress) | 0.20 | 0.60 / 0.40 / 1.0 |
| 4 | stage4_voice_persona |
Voice/persona tone data (YouTube persona distillation) | 0.15 | 0.80 / 0.35 / 1.0 |
| 5 | stage5_safety |
Safety/DPO preference data | 0.05 | 0.65 / 0.45 / 1.0 |
DEFAULT_STAGE_CONFIGS in stage_organizer.py
sets 0.35. The code value is what executes. Quotas are soft (see §4), so the
effective ratio is emergent either way. Reconciliation decision is deferred to
Steps 7–9; this doc records the code value as executable truth.
Historical closure status (PIX-4192/4193/4194/4195/4199, all DONE): Stages 1–4
closed in the MTGC sprint; Stage 5 DPO deferred (partial data only, PAL DPO
148KB). Stage 5 is being revived by the safety_dpo_pairs_10k corpus in this
pipeline run.
Each record is a JSONL line:
{
"messages": [{ "role": "system|user|assistant", "content": "..." }],
"metadata": {
"stage": "stage3_edge_stress_test",
"source": "nightmare_fuel | edge_cases | t1_gold | persona | safety_dpo_pairs_10k | ...",
"topic_tags": ["edge_case", "crisis"],
"therapeutic_modality": "",
"conversation_id": "...",
"crisis_intensity": "extreme|very_high|high|..."
}
}Classification inputs: source (top-level or metadata), metadata.topic_tags,
metadata.therapeutic_modality. Provenance fields added at consolidation (Step
9): source, family (10-family taxonomy), generated_at.
Edge-case preservation bypass: records tagged is_training_edge_case: true pass
quality gates that would otherwise filter high-crisis content (MTGC P0-2).
Keyword-driven, evaluated in strict priority order — first match wins:
- Stage 5 (Safety): source ∈ {safety, crisis_intervention, harm_prevention, suicide_prevention, self_harm, crisis_hotline} or any tag ∈ {safety, crisis, self_harm, suicide, harm_prevention, emergency, crisis_response, mental_health_crisis}
- Stage 4 (Voice/Persona): source ∈ {pixel_voice, voice_persona, dual_persona, persona} or tag ∈ {voice, persona, dual_persona, personality, character, role_play}
- Stage 3 (Edge/Stress): source ∈ {edge_cases, adversarial, stress_test, jailbreak, red_team, safety_test} or tag ∈ {edge_case, adversarial, stress_test, jailbreak, red_team, crisis, boundary_test}
- Stage 2 (Therapeutic):
therapeutic_modality∈ {cbt, dbt, psychodynamic, emdr, act, mbct, ipt, sfbt, gottman, somatic, trauma_informed, attachment_based} or tag ∈ {therapy, counseling, psychotherapy, clinical, diagnosis, treatment_plan, intervention, therapeutic_technique, case_study} - Stage 1 (Foundation): tag ∈ {psychology, mental_health, general, education, self_help, wellness, mindfulness, communication, emotional_intelligence}
- Default → Stage 1 for any unclassified conversational content.
crisis is a Stage 5 tag and a Stage 3 tag. The
priority order means a record tagged crisis lands in Stage 5, not Stage 3. New
pipeline-generated records must be tagged deliberately: crisis-safety content
(correct crisis handling demonstrations) vs adversarial stress content
(edge_case/adversarial tags, no crisis tag) unless it is genuinely a
safety-behavior exemplar.
enforce_quotas treats stage percentages as soft capacity targets, not hard
caps (P0-4 policy):
- Under-target stages keep all their records.
- Over-target overflow is covered by redistributable slack (unused capacity of under-target/missing stages).
- If overflow exceeds slack, records are retained anyway with a logged warning — no record is ever silently discarded.
- Stage ratios in the final output are therefore emergent; the ±2% distribution gate (8-Gate validation) is checked, not enforced by truncation.
Implemented in ai/pipelines/ingestion_deduplication.py (shared by
consolidate_v7.py):
Primary hash (content-based, default):
sha256( lowercase( concat( messages[i].role + messages[i].content for all i ) ) )
- Normalization: role+content pairs concatenated, whole string lowercased,
UTF-8. Empty/missing
messages→ sha256 of empty bytes.
Secondary hash (metadata-based, opt-in --use-secondary-hash):
sha1( conversation_id + stage + source + crisis_intensity )
- Catches near-duplicate content re-shipped under different metadata; never the default gate.
Conflict resolution (stage priority, higher wins):
| Stage | Priority |
|---|---|
stage4_voice_persona |
5 |
stage3_edge_stress_test |
4 |
stage2_therapeutic_expertise |
3 |
stage1_foundation |
2 |
supplementary |
1 |
- Same primary hash seen twice → keep the higher-priority stage's record; count
as
stage_conflicts_resolved. - Equal priority → first-seen wins; the later record counts as
duplicates_removed.
STAGE_PRIORITY has no stage5_safety entry — a Stage 5
record's get_stage_priority() returns the default 1 (ties with
supplementary). Stage 5 records currently lose all collisions. Fix candidate
for Step 9's consolidator work (add stage5_safety: 6, above voice, since
safety exemplars must never be dropped in favor of tone data — or at minimum 5
to break the tie).
Output root (default): ai/training_data_consolidated/final/
ai/training_data_consolidated/final/
├── MASTER_STAGE_1.jsonl # consolidated per-stage records
├── MASTER_STAGE_1_train.jsonl # 80%
├── MASTER_STAGE_1_val.jsonl # 10%
├── MASTER_STAGE_1_test.jsonl # 10%
├── MASTER_STAGE_2.jsonl (+ _train/_val/_test)
├── MASTER_STAGE_3.jsonl (+ _train/_val/_test)
├── MASTER_STAGE_4.jsonl (+ _train/_val/_test)
├── MASTER_STAGE_5.jsonl (+ _train/_val/_test)
└── manifests.json # index: [{stage, target_percentage, actual_count,
# quality_profile, split_counts, manifest_file, output_path}]
Per-stage StageManifest metadata: stage, target_percentage,
actual_count, quality_profile, split_counts {train, val, test},
manifest_file, output_path. Stages with zero records are skipped (no empty
manifests).
Flat gold: ai/data/curated/sft_chatml/train_master_gold.jsonl (189,122
records / 379 MB at 2026-09-01 baseline) is the stage-unaware master. Per
Decision 4, consolidation (Step 9) appends gate-passing records to both the
correct MASTER_STAGE_N.jsonl and the flat gold, with hash dedup on each side.
Stage organizer (this spec's path): split_dataset — 80/10/10
train/val/test, per-stage shuffle with random.Random(seed=42), split AFTER
classification and quota enforcement.
Do not conflate with the release-manifest splitter:
ai/training/data/dataset_splitter_stratified.py uses 70/15/15 (8-axis
stratified, hash-disjoint + source-family-disjoint integrity gates, ±2pp
tolerance) for the DVC-tracked release path (blueprint §7.1). Two different
consumers: stage manifests feed staged SFT curriculum; the stratified splitter
produces release-grade train/val/test JSONLs with DVC pointers. A record's
val/test membership in one path says nothing about the other.
| Module | Read | Write | Use when |
|---|---|---|---|
stage_organizer.py |
local JSONL shards (in-memory) | local FS | small/medium datasets |
stage_organizer_streaming.py |
local JSONL (two-pass) | local FS | datasets too large for memory |
s3_stage_organizer.py |
S3 via S3Streamer (line-by-line, never bulk-download) |
gdrive via rclone | remote staged assets (Step 6/9 constraint) |
Stage 5 path: ai/pipelines/dpo_ingestion.py normalizes HF preference
datasets (mlx-community/Human-Like-DPO, flammenai/character-roleplay-DPO,
PJMixers/unalignment_toxic-dpo-v0.2 → {prompt, chosen, rejected}) and writes
MASTER_STAGE_5.jsonl directly. The safety_dpo_pairs_10k corpus joins this
path in Step 8.
| New data | Stage | Route |
|---|---|---|
| Edge cases + nightmare fuel (+50k) | Stage 3 | generate → cliché gate (Step 5) → dedup → MASTER_STAGE_3.jsonl + gold append |
| Clinically-backed (+25k): T1_GOLD textbook path | Stage 1–2 | classify via modality/tags → Stage 1 or 2 manifests + gold |
Clinically-backed (+25k): JMIR / clinical_redteam |
Stage 3 (adversarial red-team) or Stage 5 (safety exemplars) — per §3 tag rules | same |
safety_dpo_pairs_10k |
Stage 5 | dpo_ingestion.py path |
| Persona distillation output | Stage 4 | voice pipeline → persona records → MASTER_STAGE_4.jsonl + gold |
Every appended record: provenance (source, family, generated_at),
primary-hash dedup against existing stage manifest + gold, append+fsync with
checkpoint (Step 9).
- Stage 1 quota 40% (docs) vs 35% (code) — pick one before consolidation;
if 40% wins, update
DEFAULT_STAGE_CONFIGS, else amend the MTGC docs' historical note (code is authoritative for execution). stage5_safetymissing fromSTAGE_PRIORITY— add before any Stage 5 consolidation so safety records don't lose collisions.crisistag double-booking (Stage 5 vs Stage 3) — define the tagging convention for generated edge/nightmare records in the Step 7 generator config.- 8-Gate validation (coverage, distribution ±2%, hash gate across stages, …) — run at consolidation time, not per-shard.