Multi-Task Skills & Saturation

Lesson 10 · SkillOpt & Agent Harnesses · ~25 minutes

Warm-up: retrieve

From Lesson 9: your nightly cycle shows 7 consecutive REJECTED proposals. What does this mean?
Saturation — the optimizer can't find improvements for these tasks. The answer is more variety: new task types, harder variants, or a broader domain. That's exactly what this lesson covers.
The archive doesn't cause rejections (more history = better recall). And the optimizer isn't "weak" — it's proposing edits that don't help because the skill is already good at these specific tasks. The fix is expanding the task surface.

What you'll learn

Your first skill was trained on one task domain (Lesson 5). But your agent does many things — code review, test writing, refactoring, documentation, debugging. This lesson covers: training across multiple task types, deciding whether to compose separate skills or merge into one, and what to do when a skill plateaus.

One skill or many?

Two architectures for multi-domain skills:

ApproachWhen to useTradeoff
Single unified skill Task types share common patterns (e.g., all involve reading code + producing text) Simpler to deploy; risk of conflicting rules between domains
Separate skills, composed Task types are genuinely different (e.g., code review vs. deployment scripts) Cleaner separation; more files to manage; agent picks which to apply
Start unified, split if conflicts emerge

SkillOpt's task_type field in your data items lets the optimizer learn type-specific rules within a single skill. Start with one skill containing multiple task types. If the gate starts rejecting edits that help one type but hurt another, that's your signal to split.

Step 1 — Add task types to your env

In your dataloader (Lesson 5), each item has a task_type field. Add items from a second domain:

# data/my_env_split/train/tasks.json
[
  {"id": "review_001", "task_type": "code_review", "input": "...", "ground_truth": "..."},
  {"id": "review_002", "task_type": "code_review", "input": "...", "ground_truth": "..."},
  {"id": "test_001", "task_type": "test_generation", "input": "...", "ground_truth": "..."},
  {"id": "test_002", "task_type": "test_generation", "input": "...", "ground_truth": "..."}
]

Your adapter's get_task_types() returns all types present — the optimizer sees them all and can learn type-specific rules.

Step 2 — Train on the combined set

python scripts/train.py --config configs/my_env/default.yaml

Watch the per-type scores in the training log. The optimizer may learn rules that help one type and are neutral for others (good), or rules that help one and hurt another (the gate will reject these).

Step 3 — Diagnose conflicts

If you see many rejections after adding a second task type, check whether:

  1. The rules conflict: "Always include type annotations" helps test generation but hurts commit message formatting. Solution: split into separate skills.
  2. The scoring functions conflict: Different domains need different evaluation. Solution: ensure your _score() function handles each type appropriately.
  3. The data is imbalanced: 90% of one type drowns out the other. Solution: balance your train split or use stratified sampling.

Step 4 — Breaking through plateaus

When a skill saturates (Lesson 9), you have four levers:

LeverHowWhen
Add harder tasks Include items the agent currently fails at The skill is good at easy cases, bad at hard ones
Expand task types Add a new domain to the env (this lesson) You want the skill to cover more of your workflow
Increase edit budget optimizer.learning_rate: 8 (default 4) The optimizer needs larger changes to express new rules
Reset and retrain Start from a fresh seed with updated data The accumulated skill has cruft from obsolete patterns
Increasing learning rate has a cost

A higher edit budget allows bigger changes per proposal — more expressive, but also more risk of harmful edits slipping through. The gate still protects you, but a single rejected proposal wastes more optimizer compute. Increase by 1–2 at a time.

Step 5 — The skill composition pattern

If you do split into multiple skills, deploy them as layers:

# For pi:
~/.pi/skills/
├── 00-base-coding.md      # Universal coding patterns
├── 01-code-review.md      # Review-specific rules
└── 02-test-generation.md  # Test-writing rules

# For Kiro:
.kiro/steering/
├── skillopt-base.md
├── skillopt-review.md
└── skillopt-tests.md

Number-prefix ordering ensures the base skill loads first. Type-specific skills add rules that only apply in their domain. The agent sees all of them — the rules are additive.

The end state

After this course, you have a system that:

  1. Harvests your real agent sessions nightly
  2. Mines recurring tasks and replays them with contrastive dreaming
  3. Proposes skill edits bounded by a textual learning rate
  4. Gates every edit against held-out tasks (no negative transfer)
  5. Stages proposals for your review (or auto-adopts if you trust the gate)
  6. Deploys as zero-overhead system prompt text
  7. Covers multiple task types with a unified or composed skill architecture

Your agent gets better the more you use it. No weight training. No inference overhead. Just validated text edits that encode what works.

You have two task types: code review and test generation. A proposed rule "always assert function return types" helps test generation (+3pp) but hurts code review (-1pp). What does the gate do?
The gate checks aggregate held-out score across all types. If +3pp on tests and -1pp on review nets out to an improvement on the full held-out set, it accepts. If the regression on review outweighs the gain (because review has more test items), it rejects. That's when you know it's time to split into separate skills.
The gate doesn't do per-type evaluation — it checks aggregate held-out performance. Whether this specific edit passes depends on the item counts per type and the magnitude of help vs harm. Persistent conflicts between types are the signal to split into separate skills.
Recommended Reading

SkillOpt paper — §4.2 "Multi-benchmark training" — Microsoft Research. How the optimizer handles multiple task types within one training run, and the per-cell results across 6 benchmarks. ~10 minutes for the multi-env sections.

Questions? If you're not sure whether your domains should be unified or split, or you've hit a plateau you can't break through — describe your task types and I'll help design the architecture.
Prev