| name | add-trainer-feature |
| description | Use this skill when adding a substantial end-to-end feature to ForgeLM's training pipeline — a new alignment method, a new evaluation gate, a new quantization backend, a new distributed scheme. Differs from add-config-field by touching multiple modules and requiring integration tests. Triggered by requests like "add support for algorithm X", "integrate library Y for training", "implement feature from roadmap Phase N". |
Skill: Add a Trainer-Level Feature
Features that span config + model + trainer + tests + docs. Most Phase 10-13 tasks fit this pattern.
When to use
- New alignment method (like adding a 7th trainer type)
- New evaluation pipeline (new safety model, new benchmark suite)
- New PEFT method
- New backend (a future alternative to unsloth/transformers)
- New compliance artifact
Do not use for:
- Bug fixes (scope too small)
- Single config field additions → use
add-config-field
- Documentation-only changes
Required reading before acting
- docs/standards/architecture.md — module boundaries
- docs/standards/testing.md — what tests CI demands
- The phase file describing the feature (e.g., docs/roadmap/completed-phases.md)
- Existing similar feature — read one similar trainer/module end-to-end before writing yours
The scaffold
Every trainer-level feature touches roughly the same list of files. Tick them as you go:
Code
Dependencies
Tests
Config
Docs
Worked example: adding a new trainer type
Say the task is "add IPO (Identity Preference Optimization) trainer." Here's the shape:
1. Config
class TrainingConfig(BaseModel):
trainer_type: Literal["sft", "dpo", "simpo", "kto", "orpo", "grpo", "ipo"] = "sft"
ipo_beta: float = 0.1
2. Trainer wiring
from trl import IPOTrainer
TRAINER_REGISTRY = {
"sft": _run_sft,
...
"ipo": _run_ipo,
}
def _run_ipo(config, model, tokenizer, dataset):
trainer = IPOTrainer(
model=model,
args=_build_training_args(config),
train_dataset=dataset,
beta=config.training.ipo_beta,
...
)
trainer.train()
return trainer
3. Data format detection
if "chosen" in first_row and "rejected" in first_row:
return "preference_pairs"
4. Tests
class TestIPO:
def test_config_accepts_ipo(self):
cfg = ForgeConfig.model_validate(
minimal_config(training={"trainer_type": "ipo", "ipo_beta": 0.1})
)
assert cfg.training.trainer_type == "ipo"
def test_dry_run_with_ipo(self, tmp_path, monkeypatch):
...
5. Docs
configuration.md: add trainer_type: ipo to the enum table + ipo_beta row
guides/alignment.md: add "When to use IPO" section with 2-3 sentences
CHANGELOG.md: ### Added — IPO trainer type (TRL-backed). See docs/guides/alignment.md.
6. Phase tick
Open docs/roadmap/phase-N-*.md where this task sits, change [ ] to [x].
Integration checklist
Before opening the PR, run all of these:
ruff check . && ruff format --check .
pytest tests/ -v
forgelm --config config_template.yaml --dry-run
forgelm --wizard
pip install -e '.[new_feature]'
Pitfalls to avoid
- Fat trainer.py. If the new trainer logic is >100 lines, extract to a new
forgelm/<name>.py module and have trainer.py just dispatch.
- Global state. Tempting for trainer-level features ("let me cache this tokenizer"). Don't. Thread state through the function signature.
- Skipping the optional extra pattern. A single missing
try: import X with a helpful message is the difference between a kind error and an ugly traceback.
- Not testing auto-revert interaction. If the feature can fail an evaluation gate, test that auto-revert correctly fires.
- Not updating wizard.py. If the feature is user-visible,
forgelm --wizard should surface it.
Related skills
add-config-field — when scope is smaller
add-test — focused test writing
sync-bilingual-docs — after touching docs/reference/*