← TinyGPT · docs · devlog · roadmap · speedup
source: docs/prds/pace-planner-v11-training-data.md · view on GitHub ↗

Pace planner v11 — unhappy-path training data PRD

Date: 2026-06-09 Status: SPEC — execution conditional on v10 result missing one or more dimensions of the v11 ship gate Owner: tinygpt repo


Goal

Produce ~450 training rows that teach a Qwen3-0.6B planner to emit three new intent classes:

These rows compose with the existing v10 corpus (404 happy-path rows) to form the v11 training set of ~850 rows total.

Why this dataset has to be built carefully

Past Pace versions over-eagerly emit AX.press for every prompt because:

  1. The training distribution only contains AX.press / AX.setValue happy-path examples
  2. The grammar / schema doesn’t allow non-action outputs
  3. The teacher used for v10 multiplication reflexively produced AX.press regardless of input

Adding 450 rows of unhappy-path examples fixes (1), an action-registry update fixes (2), and the data-generation recipe in this PRD fixes (3).


Action-registry extension (v10 → v10.5)

grammars/v10-actions/registry.json adds three new top-level intent values alongside action:

{
  "intent": {
    "type": "string",
    "enum": ["action", "out_of_scope", "clarify", "confirm_destructive"]
  }
}

Per-intent payload shapes:

IntentPayloadExample
action{name, args} (existing v10 shape){name: "Mail.draft", args: {...}}
out_of_scope{reason} — short user-facing explanation{reason: "Weather requires a cloud query"}
clarify{question, options?} — what to ask the user{question: "Which file do you mean?", options: ["Q4 report", "Q3 report"]}
confirm_destructive{action, target} — planned action + thing being destroyed{action: "Mail.deleteAll", target: "all emails in Inbox"}

Grammar-constrained decoding at inference uses this extended registry. The constraint mathematically prevents the model from emitting an action when it’s supposed to refuse — so even if the model internally guesses, the structural output is forced correct.


Data-generation recipe — three-stage hybrid

Stage 1 — Hand-curated seeds (1.5h human, 0 compute)

50 high-quality examples per class. Hand-written by the engineer.

Why hand-curate seeds and not full corpus? Seeds anchor the teacher’s behavior. With 50 anchor examples per class showing exactly the shape we want, the teacher’s output distribution shifts toward our target. Without anchors, the teacher generates “what it thinks an LLM would emit” — which is biased toward the most common shapes in its pretraining (which is AX.press-shaped, because Mac voice assistants are rare in pretraining).

Per-class seed shape:

Stage 2 — Teacher amplification (3h compute, ~30min human supervision)

Use Qwen3-14B-MLX-4bit with thinking ENABLED. Local, no API cost.

Anti-mistakes from the last multiplier run:

Per-class expansion plan:

ClassHand seedsVariations/seedPre-judge candidatesTarget post-judge
out_of_scope504200~150
clarify504200~150
confirm_destructive504200~150

Stage 3 — Judge filter (~1h compute)

For each candidate, run a critic prompt against the same Qwen3-14B teacher (different sampling temperature):

Critic prompt:
You are checking a Pace planner training example. Pace is a Mac voice
assistant. The training row claims the user input belongs to the
"<intent_class>" category. Rate this example 1-5:

5 = perfect example of the category, schema-valid, would be useful training data
4 = good example, minor issues
3 = ambiguous category fit, could go either way
2 = wrong category but related
1 = bad example, would hurt training

Input prompt: <prompt>
Generated response: <response>
Expected intent: <intent_class>

Return ONLY: SCORE: <1-5>

Keep candidates rated 4 or 5. Reject 1-3.

Expected pass rate ~50-70% per category based on Tulu-3 / Llama-3.1 paper baselines. With 200 candidates × 12-class slot × ~60% pass = ~120-140 per class, slightly under our 150 target. If short, hand-write the remainder (cheap last-mile).


Quality controls


Output

~/.cache/tinygpt/datasets/pace-v11-unhappy.jsonl
  - one JSONL per row, shape: {messages: [{system, user, assistant}]}
  - assistant = {spokenText, intent, payload}
  - ~450 rows total, balanced across the 3 classes

~/.cache/tinygpt/datasets/pace-v11-merged.jsonl
  - v10 (404) + v11-unhappy (~450) = ~850 rows
  - shuffle preserved, no class label leakage

Merge order: existing v10 rows tagged with intent: action for consistency, then concatenated with the new classes. Grammar at training time uses the extended registry so the model learns the full 4-intent vocabulary.


Time + cost

PhaseHumanComputeWall
1. Hand-curate 150 seeds1.5h01.5h
2. Teacher amplification (600 candidates total)30min supervision~2.5h2.5h
3. Judge filter0~30min30min
4. Diversity + schema audit30min~5min30min
5. Merge with v10 corpus5min05min
Total~2.5h human~3h compute~5h wall

Less than the original “4h hand-curate” estimate AND scales to 1000+ rows if v11 needs more.


Failure modes the judge filter cannot catch


Activation

This corpus is built only if v10 cascade results (in flight 2026-06-09) miss the v11 ship gate on dimensions 3, 4, or 6. If v10 happens to clear all six dimensions (unlikely but possible), no v11 needed and this PRD shelves.

If activated, the recipe runs against the same teacher endpoint (Qwen3-14B-MLX-4bit at port 1234) that produced the v10 multiplier — with the four corrections listed in Stage 2.


Why not just hand-curate

Time is comparable (~2.5h vs 4h), but the hybrid:

  1. Scales — if v11 needs 1500 rows instead of 450, the same recipe applies; hand-curation does not scale
  2. Captures phrasing diversity the engineer wouldn’t think of
  3. Doubles as quality control — the judge step is reusable for any future data generation

The seed-anchored approach is roughly how Anthropic, OpenAI, and Tulu generate alignment data. We are not inventing a new methodology; we are correcting last week’s mistake (no thinking, no judge, no anchored seeds) by following the canonical recipe.


What this PRD does NOT cover