Methods & reproducibility

How the numbers were produced

The write-up, the exact data files (by SHA-256), the train/eval leakage audit and the teacher-generation record. Everything here is exported from the repository; nothing is entered by hand.

Report

Specialised LoRA training for tool selection and multi-step orchestration in a 0.5B model

Technical report — Cognitive Core Lab, November 2026. All numbers in tables are generated from run artifacts by scripts/analyze.py and scripts/build_report.py.

Summary

Result. LoRA training of a 0.5B model on 1,000 verified workflow episodes (about 38 minutes on a 16 GB Apple M4) raised workflow success on held-out tasks from 2.2% (untrained) and 12.8% (few-shot prompted) to 53–59% pooled over the four held-out splits, and to 86–91% on new instances of training templates. Every trained configuration beats both baselines on in-distribution, unseen-template and renamed-catalogue tasks (Holm-adjusted p ≤ 0.021).

Where it stops. Gains shrink sharply outside the training distribution. On 16 task templates never seen in training, success is 30–36% (the 4B teacher: 56%). On tool domains absent from training, trained models are no better than the few-shot prompt (≈33% for both), and one template in that split turned out to be flawed (§7). Success on tasks whose dependency graph was seen in training (72–74%) is roughly double that on novel graphs (26–38%): much of what is learned is pattern-specific.

Signal source. Teacher demonstrations (B) and oracle trajectories (C) give statistically indistinguishable aggregate results (55.8% vs 55.9%), with different strengths that track what each data source contains: B almost never handles human approval (0.9%; the teacher produced one accepted approval episode), C is stronger on failure recovery (87.0% vs 66.7%). Mixing both (Dmix, 59.1%) is numerically best, but its advantages do not survive multiple-comparison correction. Training on the two sources sequentially (Dseq) collapses on unseen tool domains (9.2% vs 36.4% for Dmix; −27.2 pts, Holm-adjusted p = 0.021).

Students vs teacher. Distilled students exceed the teacher on in-distribution tasks (86.0% vs 81.0%), because only verified teacher episodes were kept. They use about a quarter of the teacher's peak memory (3.0–3.9 GB vs 13.2 GB) and have 7–10× lower mean latency per episode (measured at different batch sizes, §7). They do not inherit the teacher's generalisation.

Failures. The first two training configurations diverged (§4.1) and are preserved as negative results. All numbers below come from results/ and runs/, generated by scripts/analyze.py.

1. Question and hypotheses

Can a 0.5B-parameter open-weight model be made into a usable tool orchestrator through cheap LoRA training, and does it matter whether the training signal comes from a stronger model or from an independent, programmatic source?

  • H1 (training helps). Every LoRA configuration (B, C, D) beats the unmodified baseline (A) on workflow success on held-out tasks.
  • H2 (beyond format). The gain is not only output-format compliance: trained models also beat a few-shot-prompted baseline (A⁺), and they improve on diagnostics that format alone cannot fix (dependency safety, argument correctness, recovery, permission handling).
  • H3 (generalisation vs memorisation). Gains carry over, with some loss, to unseen task templates (eval_ood), unseen tool domains (eval_unseen) and renamed catalogues (eval_renamed). A model that only memorised its training graphs would collapse on these splits.
  • H4 (signal source). With the same budget, teacher demonstrations (B) and oracle trajectories (C) produce different strengths. The comparison is open-ended: B inherits the teacher's blind spots and the coverage bias of rejection sampling, while C is exhaustive but stylistically uniform.
  • H5 (combination). Combining sources (D) is at least as good as the better single source, and the combination strategy (mixed vs sequential) matters little.

2. Setup

2.1 Models

  • Student / foundation checkpoint: Qwen/Qwen2.5-0.5B-Instruct @ 7ae557604adf (Apache-2.0). Every configuration starts from these exact weights. The selection rationale and measured alternatives are in MODEL_SELECTION.md.
  • Teacher: Qwen/Qwen3.5-4B as the MLX 4-bit conversion mlx-community/Qwen3.5-4B-MLX-4bit @ 32f3e8ecf654 (Apache-2.0), run locally in non-thinking mode. No hosted API was available.
  • Oracle: a programmatic policy (ccl/oracle.py) that reads the task's gold dependency graph and reacts to real observations. It is the source of config-C data and the validator's ceiling. It is not a model.

2.2 Environment: tools, runtime, action schema

21 typed tools in 12 domains (contacts, calendar, email, weather, FX, orders, files, tickets, maths, flights; held-out: inventory, maps), each simulated deterministically over a seeded world (ccl/tools.py). Four tools need human approval (send_email, issue_refund, delete_file, book_flight), and two are role-gated (issue_refund → support_agent, delete_file → admin). Faults are injected deterministically: transient retryable timeouts, permanent service_unavailable with a declared fallback tool, and not-found entities.

Each turn the model emits exactly one JSON object:

{"calls": [{"tool": "<name>", "args": {...}}, ...]}                 // >1 call = parallel, independent
{"final": "<answer>", "status": "done|refused|blocked|failed"}

The runtime (ccl/runtime.py) parses the action leniently: it accepts code fences, <think> blocks and a bare call object, but records strict-schema compliance separately. It validates arguments against the typed schema, enforces role permissions itself (a forbidden call is never executed and is scored as a violation), asks an approver before consequential calls, and executes calls with deterministic limits: 8 turns, 6 calls per turn, 16 calls total, 2 parse errors, a 2 s tool timeout. Observations are appended as a user message. The same loop drives teacher generation, oracle generation, evaluation, the CLI (scripts/agent.py, with a real terminal approval prompt) and the live server (scripts/serve.py).

2.3 Tasks

44 scenario templates in 8 families: tool selection, args construction (relative dates, 12h→24h times, currency names and symbols to ISO codes, urgency to priority), sequential chains up to depth 5, parallel fan-out/fan-in, recovery (retry, fallback, not-found), permission boundaries, human approval (including denial), and termination (no-tool requests, stop when done). Every task has a gold DAG with per-argument matching rules, an expected final status, required answer facts and expected side effects. Catalogues include 2–6 random distractor tools. 25% of training tasks have tools and parameters renamed from a training alias vocabulary, so models must read the catalogue rather than memorise names.

Validation is executable (ccl/evaluate.py). An episode succeeds only if it ends with a final answer, the status matches, every required fact appears in the answer, every required gold call was attempted with matching arguments, the executed side effects match the expected ones one-to-one, no permission was violated, and no denied action was retried. No model judge is used anywhere.

2.4 Splits and leakage control

  • train_pool (3,000) and val_pool (150): the 24 training templates only, never the held-out tool domains.
  • eval_id (200): new instances of training templates.
  • eval_ood (240): 16 templates never used in training. These include new phrasings, new graphs (fan-in, chain-into-conversion, book-the-cheapest), depth 4–5 chains (training goes to depth 3) and new permission and approval situations.
  • eval_unseen (120): 4 templates over tool domains absent from training (inventory, maps).
  • eval_renamed (160): training templates with every tool and parameter renamed from a held-out alias vocabulary.
  • eval_catalog_{0,5,12} (96 each): identical in-distribution tasks with fixed distractor counts.

Controls: (i) templates belong to exactly one split; (ii) any eval task whose request text occurs verbatim in the train or validation pool is dropped; (iii) a value-free gold-graph signature is computed per task, and its overlap with training is reported. Eval tasks are tagged graph_seen or graph_novel and results are stratified by that tag. (iv) Held-out tools never appear in training, not even as distractors. (v) Eval and train use different RNG seed ranges, and the name and city pools for training are a strict subset of those for evaluation. The audit is in data/v1/leakage_audit.json:

<!-- BEGIN:data -->

variantepisodesSFT rows (assistant turns)sha256 (first 12)families (episodes)
C100023151ef97f846690approval 39, args 86, chain 245, parallel 85, permission 82, recovery 116, select 259, termination 88
C_25250584a47b755c5a2capproval 11, args 18, chain 61, parallel 19, permission 20, recovery 36, select 65, termination 20
C_50500114983278ddb3fb2approval 20, args 46, chain 116, parallel 49, permission 44, recovery 59, select 131, termination 35
C_full30007000e936bda77a6capproval 125, args 250, chain 750, parallel 250, permission 250, recovery 375, select 750, termination 250
B10002102d84b49db8cceapproval 1, args 121, chain 146, parallel 122, permission 45, recovery 131, select 303, termination 131
C_matched100020914e2b03d5d42fapproval 1, args 121, chain 146, parallel 122, permission 45, recovery 131, select 303, termination 131
task filensha256 (first 12)
tasks/train_pool.jsonl3000649b77769d5b
tasks/val_pool.jsonl1504109a155f1da
tasks/eval_id.jsonl2006fabbbc2706a
tasks/eval_ood.jsonl24025291967e978
tasks/eval_unseen.jsonl120ac20b173e4fe
tasks/eval_renamed.jsonl160e9170bafa862
tasks/eval_catalog_0.jsonl96e033c69e7616
tasks/eval_catalog_5.jsonl96bcdc5d4db006
tasks/eval_catalog_12.jsonl96ec9e9d0d3c2e
eval splitnrequest overlaptemplate overlapgraph-signature overlapheld-out tools seen in traindropped (verbatim request in train)
eval_id200018100%0113
eval_ood2400025%00
eval_unseen120000%00
eval_renamed160017100%081
eval_catalog_096017100%0-
eval_catalog_596017100%0-
eval_catalog_1296017100%0-

<!-- END:data -->

scripts/build_datasets.py reproduces every task file byte-for-byte; this was verified by rebuilding and comparing all 11 SHA-256 hashes.

3. Training data

3.1 B — teacher demonstrations (distillation through data)

The teacher ran the same runtime, with the same system prompt and catalogue rendering, on the first 1,600 tasks of the training pool. Pass 1 decoded greedily. Pass 2 drew one T=0.7 sample for every task rejected in pass 1 (rejection sampling). An episode was accepted only if it passed the executable validator and had no parse errors, hallucinated tools, invalid-argument errors, unnecessary calls or dependency-unsafe calls. Every episode, accepted or not, is kept with its rejection reasons in data/v1/teacher/pass{1,2}.jsonl.gz. No synthetic label was ever substituted for a missing teacher demonstration. Tasks the teacher could not solve are simply absent from B.

<!-- BEGIN:teacher -->

passdecodingepisodesacceptedacceptancerejection reasons (an episode can have several)
pass1greedy160093958.7%not_success 458, unnecessary_calls 304, parse_error 187, dependency_unsafe 185, invalid_args 117
pass2T=0.7, top-p 0.95 (rejected tasks only)6617210.9%not_success 443, unnecessary_calls 270, parse_error 214, dependency_unsafe 143, invalid_args 84

Teacher: mlx-community/Qwen3.5-4B-MLX-4bit (upstream Qwen/Qwen3.5-4B, apache-2.0). Tasks offered: 1600; accepted episodes: 1011 (63.2% of tasks). Accepted episodes by family: approval 1, args 122, chain 146, parallel 124, permission 47, recovery 132, select 308, termination 131. Wall-clock: pass 1 3.70 h, pass 2 1.96 h. API cost: $0 (local). <!-- END:teacher -->

3.2 C — independently generated, execution-verified trajectories

The oracle (§2.1) ran on the same task pool. Its trajectories pass through the same runtime and the same acceptance filter (3,000/3,000 accepted). C contains no model outputs.

3.3 Normalisation and budget matching

Assistant turns from both sources are re-serialised to canonical JSON, both as targets and in the history, so B and C differ only in their decisions (which tools, which arguments, sequential vs parallel, answer wording, status), not in surface format. Each single-source variant has exactly 1,000 episodes: B is a seeded random sample of accepted teacher episodes, and C covers 1,000 random tasks from the same 1,600 offered to the teacher. Every configuration trains for 250 optimizer steps × 8 episodes with identical hyperparameters. Dmix (B ∪ C, 2,000 episodes) therefore sees each episode about once; B and C see theirs about twice. Dseq runs B for 125 steps, then C for 125 steps, on one cosine schedule.

Ablations (single seed): C_matched, the oracle on exactly B's 1,000 tasks, isolates label source from task coverage; C_25 / C_50 / C_full train on 250, 500 and 3,000 oracle episodes at the same step budget.

4. Training

Final configuration (used for every reported run): LoRA rank 16, scale 20, dropout 0, applied to every linear layer of the last 16 of 24 blocks (5,865,472 trainable parameters, ~1.2% of the 494M model). AdamW, lr 1e-5, 20 warmup steps, cosine decay to 1e-6, global gradient-norm clipping at 1.0, batch 4 × 2 gradient-accumulation, max length 2,048, gradient checkpointing. The loss covers assistant tokens only. Each episode is packed into one sequence with every assistant turn supervised; this gives the same loss as one example per turn under teacher forcing, but processes the shared prefix once. Batch order is a deterministic function of the seed. Checkpoints hold adapter, optimizer state and step, and are written atomically; a test (tests/test_train.py) checks that an interrupted run, once resumed, reproduces the uninterrupted adapter. B, C, Dmix and Dseq use seeds 0, 1 and 2; the ablations (C_matched, C_25, C_50, C_full) use seed 0 only (see §7). All 16 reported runs pass the stability rule below (logs/stability.jsonl).

4.1 Failed training configurations (preserved)

The final configuration is the third one tried. The first two diverged; both are kept with their loss curves and evaluations in archive/, and none of their numbers is used in any comparison.

attemptsettingoutcome
v1 (archive/v1_lr1e-4_noclip)all 24 layers, scale 20, lr 1e-4, no clipping10 of 11 runs diverged at the end of warmup (loss 0.2 → 6–14); their adapters emit malformed output or finalise without tool calls (0% success). C_s2 alone recovered (46.5% on eval_id).
lr pilot (experiments/lr_pilot)same, + clipping, lr ∈ {1e-5, 3e-5, 1e-4}, 60 steps on Dmix3e-5 and 1e-4 diverged; 1e-5 looked stable over 60 steps, so it was selected by the pre-registered rule.
v1b (archive/v1b_lr1e-5_alllayers)all 24 layers, scale 20, lr 1e-5, clippingB_s0 diverged within 30 steps (pre-clip gradient norm ~10³ → ~2.8×10⁷). Stopped.
stability study (experiments/grad_diag)(a) last 16 layers, scale 20, lr 1e-5 vs (b) all layers, scale 2, lr 1e-4; 100 steps on B(a) stable (grad norm 27 → 5); (b) failed the loss criterion. (a) adopted for all runs.

Diagnosis (experiments/grad_diag/init_grads*.log): at initialisation, before any update, the gradient is dominated by v_proj.lora_b in the earliest adapted layers (layer 0 when all layers are adapted) and varies ~20× between batches. With LoRA scale 20, small updates to these layers move the model sharply; restricting LoRA to the last 16 blocks (MLX-LM's default) removes the most sensitive layers. Selection rules were written before each pilot ran (PLAN.md in each folder). One weakness: the lr pilot's raw-loss rule was noise-sensitive (single-batch loss at initialisation already spans 0.32–0.80) and only 60 steps long, which is why it passed an lr that later diverged; the stability study uses a smoothed loss and a gradient-norm criterion over 100 steps.

<!-- BEGIN:training -->

runstepstrain time (min)peak mem (GB)final val loss (oracle val)trainable paramsadapter sha256 (first 12)
B_s025038.04.010.41155,865,4729ff128410465
B_s125037.14.010.40625,865,472df5a7ea40248
B_s225037.84.010.43195,865,4724e81a988bdca
C_25_s025038.53.800.00955,865,4721fa38da12bef
C_50_s025037.53.800.00425,865,4729f26700b3b26
C_full_s025034.83.900.00245,865,47276551ac50010
C_matched_s025037.63.900.00955,865,47287ce5656ffef
C_s025037.63.900.00315,865,4727354e6f5c4bc
C_s125037.03.900.00395,865,472029a043ed73f
C_s225037.93.900.00285,865,47228539a4006dc
Dmix_s025037.24.010.01435,865,4723533d0103d9e
Dmix_s125040.94.010.01205,865,4724e6d38c2605a
Dmix_s225037.64.010.01375,865,47267fc1ebaf6b6
Dseq_s025037.24.010.00335,865,472dac1f13925e0
Dseq_s125041.34.010.00395,865,4729ddb37cae95e
Dseq_s225034.84.010.00375,865,47261a179b00eed

Total LoRA training wall-clock: 10.05 h on one Apple M4. <!-- END:training -->

5. Evaluation protocol

All configurations run on identical task files with greedy decoding, batched (64 episodes in lock-step, max 256 new tokens per turn), on the same machine. Configuration A⁺ uses the same weights as A, plus two worked examples (with tools that are not available) appended to the system prompt. Metrics are computed per episode (§2.3 for success):

metricdefinition
successexecutable validator passes (workflow completion)
tool_selectionset-level F1 between attempted tools and gold tools (fallbacks count); for no-tool tasks, 1 iff no call
arg_correctamong gold calls whose tool was attempted, fraction with an attempt whose arguments match
dependency_safeno call used an identifier (ID, email) not yet observed, and no dependent call was issued before its prerequisite returned
strict_schemaevery turn was exactly one schema-conforming JSON object
hallucinated_tools / unnecessary_calls / invalid_args / parse_errorscounts per episode
turns, latency, tokens/s, peak memoryefficiency

Uncertainty. Each trained configuration's CI comes from a hierarchical bootstrap: resample training seeds, then tasks (4,000 resamples, 95% percentile intervals). Paired comparisons use per-task outcomes averaged over seeds, with a bootstrap CI on the difference and a two-sided sign-flip permutation p-value. Single-run configurations (A, A⁺, teacher, ablations) carry task-level bootstrap CIs only. Those CIs do not include training-seed variance, so they understate uncertainty.

6. Results

<!-- BEGIN:tables -->

Values: mean [95% CI]. Trained configs pool seeds via a hierarchical bootstrap (resample seeds, then tasks).

Workflow success (%) by split

configseedseval_ideval_oodeval_unseeneval_renamedall
A_base15.0 [2.0, 8.0]0.0 [0.0, 0.0]0.0 [0.0, 0.0]3.8 [1.2, 6.9]2.2 [1.2, 3.3]
A_fewshot112.0 [8.0, 16.5]3.8 [1.7, 6.2]33.3 [25.0, 41.7]11.9 [6.9, 16.9]12.8 [10.4, 15.3]
B386.0 [81.0, 90.7]30.3 [23.9, 37.5]33.3 [17.5, 48.3]73.3 [66.5, 80.2]55.8 [51.5, 60.2]
C389.7 [85.3, 93.5]30.3 [23.9, 36.8]33.1 [24.2, 42.5]69.2 [61.3, 77.3]55.9 [51.6, 60.0]
Dmix391.0 [86.7, 94.7]35.6 [29.2, 42.8]36.4 [21.1, 53.3]71.7 [64.4, 78.8]59.1 [53.3, 64.7]
Dseq390.3 [86.2, 94.0]32.8 [27.4, 38.6]9.2 [3.3, 16.7]71.0 [64.4, 77.7]53.3 [49.8, 57.0]
C_matched181.0 [75.5, 86.5]29.2 [23.3, 35.0]43.3 [34.2, 52.5]65.6 [58.1, 72.5]54.0 [50.4, 57.6]
C_25187.0 [82.0, 91.5]33.3 [27.5, 39.6]28.3 [20.8, 36.7]67.5 [60.6, 74.4]55.0 [51.4, 58.8]
C_50192.5 [88.5, 96.0]32.5 [26.7, 38.8]49.2 [40.0, 58.3]80.0 [73.8, 86.2]62.5 [58.9, 66.0]
C_full190.5 [86.0, 94.5]30.4 [24.6, 36.2]17.5 [10.8, 24.2]63.1 [55.6, 70.6]52.2 [48.6, 55.8]
teacher181.0 [75.5, 86.5]56.2 [50.0, 62.5]75.0 [66.7, 82.5]80.6 [74.4, 86.9]71.7 [68.5, 74.9]
oracle1100.0 [100.0, 100.0]100.0 [100.0, 100.0]100.0 [100.0, 100.0]100.0 [100.0, 100.0]100.0 [100.0, 100.0]

Diagnostics (all main splits pooled)

configtool_selectionarg_correctdependency_safestrict_schemahallucinated_toolsunnecessary_callsinvalid_argsparse_errorsturns
A_base24.3 [21.3, 27.3]59.5 [51.4, 67.2]94.3 [92.5, 95.8]20.1 [17.2, 23.2]0.0 [0.0, 0.1]0.5 [0.3, 0.6]0.1 [0.1, 0.2]1.9 [1.7, 2.0]2.7 [2.6, 2.9]
A_fewshot55.2 [52.0, 58.4]44.2 [40.1, 48.3]75.3 [72.1, 78.3]64.7 [61.4, 68.3]0.3 [0.2, 0.4]1.2 [1.0, 1.4]0.3 [0.2, 0.4]0.6 [0.5, 0.7]2.7 [2.6, 2.8]
B79.4 [76.8, 82.1]78.0 [73.7, 82.5]90.8 [87.5, 93.7]99.3 [98.7, 99.7]0.1 [0.1, 0.2]1.1 [0.9, 1.3]0.2 [0.1, 0.2]0.0 [0.0, 0.0]2.9 [2.7, 3.2]
C80.9 [78.3, 83.4]79.6 [76.6, 82.5]91.9 [89.6, 94.1]98.6 [97.1, 100.0]0.1 [0.1, 0.1]0.4 [0.4, 0.6]0.0 [0.0, 0.1]0.0 [0.0, 0.0]2.4 [2.3, 2.5]
Dmix81.4 [78.5, 84.3]80.9 [75.4, 85.4]92.1 [89.4, 94.5]99.1 [98.0, 99.8]0.1 [0.0, 0.1]0.7 [0.6, 1.0]0.1 [0.1, 0.1]0.0 [0.0, 0.0]2.7 [2.5, 3.0]
Dseq82.2 [78.9, 85.6]76.9 [72.6, 81.0]94.3 [92.5, 96.1]99.1 [98.0, 100.0]0.1 [0.0, 0.1]0.8 [0.7, 1.0]0.1 [0.0, 0.2]0.0 [0.0, 0.0]2.8 [2.7, 2.9]
C_matched80.4 [78.1, 82.8]77.1 [73.9, 80.1]89.0 [86.7, 91.2]97.8 [96.7, 98.8]0.1 [0.0, 0.1]0.5 [0.5, 0.6]0.1 [0.1, 0.2]0.0 [0.0, 0.0]2.5 [2.4, 2.5]
C_2580.2 [77.7, 82.6]80.1 [77.0, 83.1]92.6 [90.7, 94.4]97.8 [96.7, 98.8]0.0 [0.0, 0.0]0.5 [0.4, 0.5]0.1 [0.0, 0.1]0.0 [0.0, 0.0]2.4 [2.4, 2.5]
C_5083.5 [81.2, 85.7]84.2 [81.5, 86.9]91.1 [89.0, 93.1]96.2 [94.7, 97.5]0.0 [0.0, 0.1]0.5 [0.4, 0.5]0.1 [0.0, 0.1]0.0 [0.0, 0.1]2.6 [2.5, 2.6]
C_full78.0 [75.4, 80.5]75.3 [72.1, 78.5]87.9 [85.4, 90.3]97.8 [96.7, 98.8]0.1 [0.1, 0.1]0.6 [0.5, 0.6]0.0 [0.0, 0.1]0.0 [0.0, 0.0]2.5 [2.4, 2.5]
teacher85.2 [83.0, 87.4]86.3 [83.6, 88.9]86.8 [84.3, 89.3]81.9 [79.2, 84.7]0.0 [0.0, 0.0]0.3 [0.3, 0.4]0.1 [0.1, 0.1]0.2 [0.2, 0.3]2.5 [2.4, 2.6]
oracle100.0 [100.0, 100.0]100.0 [100.0, 100.0]100.0 [100.0, 100.0]100.0 [100.0, 100.0]0.0 [0.0, 0.0]0.0 [0.0, 0.0]0.0 [0.0, 0.0]0.0 [0.0, 0.0]2.5 [2.4, 2.5]

Paired differences in success (percentage points; seed-averaged per task)

p_holm: Holm-Bonferroni adjusted over all 84 tests in this table. The smallest attainable permutation p is ~0.0002 (5,000 sign-flips).

comparisonsplitdiff [95% CI]p (sign-flip)p_holmn
A_fewshot_vs_A_baseeval_id+7.0 [+1.5, +12.5]0.02020.4251200
A_fewshot_vs_A_baseeval_ood+3.8 [+1.7, +6.2]0.00470.1282240
A_fewshot_vs_A_baseeval_unseen+33.3 [+25.0, +41.7]0.00020.0210120
A_fewshot_vs_A_baseeval_renamed+8.1 [+2.5, +13.8]0.01570.3659160
B_vs_A_baseeval_id+81.0 [+75.7, +86.0]0.00020.0210200
B_vs_A_baseeval_ood+30.3 [+25.7, +35.3]0.00020.0210240
B_vs_A_baseeval_unseen+33.3 [+26.4, +40.3]0.00020.0210120
B_vs_A_baseeval_renamed+69.6 [+63.1, +76.0]0.00020.0210160
C_vs_A_baseeval_id+84.7 [+79.7, +89.2]0.00020.0210200
C_vs_A_baseeval_ood+30.3 [+25.1, +35.4]0.00020.0210240
C_vs_A_baseeval_unseen+33.1 [+26.7, +40.0]0.00020.0210120
C_vs_A_baseeval_renamed+65.4 [+59.4, +71.7]0.00020.0210160
Dmix_vs_A_baseeval_id+86.0 [+81.2, +90.3]0.00020.0210200
Dmix_vs_A_baseeval_ood+35.6 [+30.1, +40.7]0.00020.0210240
Dmix_vs_A_baseeval_unseen+36.4 [+29.7, +43.3]0.00020.0210120
Dmix_vs_A_baseeval_renamed+67.9 [+61.3, +74.2]0.00020.0210160
Dseq_vs_A_baseeval_id+85.3 [+80.8, +89.5]0.00020.0210200
Dseq_vs_A_baseeval_ood+32.8 [+27.9, +37.8]0.00020.0210240
Dseq_vs_A_baseeval_unseen+9.2 [+5.3, +13.3]0.00020.0210120
Dseq_vs_A_baseeval_renamed+67.3 [+60.8, +73.8]0.00020.0210160
C_matched_vs_A_baseeval_id+76.0 [+70.0, +81.5]0.00020.0210200
C_matched_vs_A_baseeval_ood+29.2 [+23.3, +35.0]0.00020.0210240
C_matched_vs_A_baseeval_unseen+43.3 [+34.2, +52.5]0.00020.0210120
C_matched_vs_A_baseeval_renamed+61.9 [+54.4, +69.4]0.00020.0210160
C_25_vs_A_baseeval_id+82.0 [+76.5, +87.0]0.00020.0210200
C_25_vs_A_baseeval_ood+33.3 [+27.5, +39.6]0.00020.0210240
C_25_vs_A_baseeval_unseen+28.3 [+20.8, +36.7]0.00020.0210120
C_25_vs_A_baseeval_renamed+63.7 [+56.2, +71.2]0.00020.0210160
C_50_vs_A_baseeval_id+87.5 [+82.5, +92.0]0.00020.0210200
C_50_vs_A_baseeval_ood+32.5 [+26.7, +38.8]0.00020.0210240
C_50_vs_A_baseeval_unseen+49.2 [+40.0, +58.3]0.00020.0210120
C_50_vs_A_baseeval_renamed+76.2 [+69.4, +83.1]0.00020.0210160
C_full_vs_A_baseeval_id+85.5 [+80.5, +90.0]0.00020.0210200
C_full_vs_A_baseeval_ood+30.4 [+24.6, +36.2]0.00020.0210240
C_full_vs_A_baseeval_unseen+17.5 [+10.8, +24.2]0.00020.0210120
C_full_vs_A_baseeval_renamed+59.4 [+51.9, +67.5]0.00020.0210160
teacher_vs_A_baseeval_id+76.0 [+70.0, +82.0]0.00020.0210200
teacher_vs_A_baseeval_ood+56.2 [+50.0, +62.5]0.00020.0210240
teacher_vs_A_baseeval_unseen+75.0 [+66.7, +82.5]0.00020.0210120
teacher_vs_A_baseeval_renamed+76.9 [+70.0, +83.1]0.00020.0210160
oracle_vs_A_baseeval_id+95.0 [+92.0, +98.0]0.00020.0210200
oracle_vs_A_baseeval_ood+100.0 [+100.0, +100.0]0.00020.0210240
oracle_vs_A_baseeval_unseen+100.0 [+100.0, +100.0]0.00020.0210120
oracle_vs_A_baseeval_renamed+96.2 [+93.1, +98.8]0.00020.0210160
B_vs_A_fewshoteval_id+74.0 [+68.0, +79.7]0.00020.0210200
B_vs_A_fewshoteval_ood+26.5 [+21.9, +31.2]0.00020.0210240
B_vs_A_fewshoteval_unseen-0.0 [-8.6, +8.9]1.00001.0000120
B_vs_A_fewshoteval_renamed+61.5 [+54.6, +68.5]0.00020.0210160
C_vs_A_fewshoteval_id+77.7 [+72.0, +83.0]0.00020.0210200
C_vs_A_fewshoteval_ood+26.5 [+21.4, +31.7]0.00020.0210240
C_vs_A_fewshoteval_unseen-0.3 [-8.6, +8.3]1.00001.0000120
C_vs_A_fewshoteval_renamed+57.3 [+50.4, +64.4]0.00020.0210160
Dmix_vs_A_fewshoteval_id+79.0 [+73.3, +84.3]0.00020.0210200
Dmix_vs_A_fewshoteval_ood+31.8 [+26.7, +37.1]0.00020.0210240
Dmix_vs_A_fewshoteval_unseen+3.1 [-5.3, +11.7]0.51411.0000120
Dmix_vs_A_fewshoteval_renamed+59.8 [+52.7, +66.9]0.00020.0210160
Dseq_vs_A_fewshoteval_id+78.3 [+72.8, +83.3]0.00020.0210200
Dseq_vs_A_fewshoteval_ood+29.0 [+24.0, +34.0]0.00020.0210240
Dseq_vs_A_fewshoteval_unseen-24.2 [-32.8, -15.6]0.00020.0210120
Dseq_vs_A_fewshoteval_renamed+59.2 [+52.1, +66.2]0.00020.0210160
C_vs_Beval_id+3.7 [-1.5, +8.8]0.18371.0000200
C_vs_Beval_ood+0.0 [-5.3, +5.1]1.00001.0000240
C_vs_Beval_unseen-0.3 [-5.6, +5.0]1.00001.0000120
C_vs_Beval_renamed-4.2 [-9.2, +0.8]0.10801.0000160
Dmix_vs_Beval_id+5.0 [+0.2, +10.0]0.05120.9735200
Dmix_vs_Beval_ood+5.3 [+1.1, +9.7]0.01520.3659240
Dmix_vs_Beval_unseen+3.1 [-2.2, +8.3]0.30991.0000120
Dmix_vs_Beval_renamed-1.7 [-6.5, +2.7]0.52011.0000160
Dmix_vs_Ceval_id+1.3 [-0.5, +3.3]0.24141.0000200
Dmix_vs_Ceval_ood+5.3 [+1.1, +9.4]0.01650.3659240
Dmix_vs_Ceval_unseen+3.3 [-1.7, +8.6]0.25421.0000120
Dmix_vs_Ceval_renamed+2.5 [-1.2, +6.3]0.24041.0000160
Dseq_vs_Dmixeval_id-0.7 [-3.3, +2.0]0.70181.0000200
Dseq_vs_Dmixeval_ood-2.8 [-5.8, +0.1]0.08421.0000240
Dseq_vs_Dmixeval_unseen-27.2 [-33.9, -20.3]0.00020.0210120
Dseq_vs_Dmixeval_renamed-0.6 [-3.7, +2.5]0.79611.0000160
C_matched_vs_Ceval_id-8.7 [-13.2, -4.2]0.00050.0210200
C_matched_vs_Ceval_ood-1.1 [-5.4, +3.1]0.64781.0000240
C_matched_vs_Ceval_unseen+10.3 [+3.1, +17.5]0.00870.2274120
C_matched_vs_Ceval_renamed-3.5 [-8.5, +1.5]0.20171.0000160
C_matched_vs_Beval_id-5.0 [-9.7, -0.5]0.03650.7298200
C_matched_vs_Beval_ood-1.1 [-6.7, +4.7]0.74181.0000240
C_matched_vs_Beval_unseen+10.0 [+2.2, +17.8]0.01400.3499120
C_matched_vs_Beval_renamed-7.7 [-12.3, -3.5]0.00050.0210160

Post-hoc: eval_unseen excluding the flawed un_timezone template (success %, n=90 tasks)

Added after results were seen; reported alongside, never instead of, the pre-registered eval_unseen.

configsuccess
A_base0.0 [0.0, 0.0]
A_fewshot44.4 [34.4, 54.4]
B44.4 [23.3, 63.3]
C43.7 [32.2, 54.8]
Dmix48.5 [27.8, 70.0]
Dseq11.9 [3.7, 22.2]
C_matched57.8 [47.8, 67.8]
C_2537.8 [27.8, 47.8]
C_5065.6 [55.6, 74.4]
C_full23.3 [14.4, 32.2]
teacher100.0 [100.0, 100.0]
oracle100.0 [100.0, 100.0]

Catalogue-size ablation (success %, eval_catalog_k: k distractor tools)

configk=0k=5k=12
A_base3.1 [0.0, 7.3]4.2 [1.0, 8.3]5.2 [1.0, 10.4]
A_fewshot16.7 [9.4, 24.0]12.5 [6.2, 19.8]17.7 [10.4, 26.0]
B85.4 [78.1, 91.7]82.3 [75.0, 89.6]80.2 [71.9, 87.5]
C84.4 [77.1, 91.7]82.3 [74.0, 89.6]84.4 [77.1, 91.7]
Dmix91.7 [85.4, 96.9]90.6 [84.4, 95.8]89.6 [83.3, 94.8]
Dseq91.7 [85.4, 96.9]90.6 [84.4, 95.8]88.5 [82.3, 94.8]
oracle100.0 [100.0, 100.0]100.0 [100.0, 100.0]100.0 [100.0, 100.0]

Success (%) by family (pooled main splits, seed-averaged)

configapprovalargschainparallelpermissionrecoveryselecttermination
A_base0.0 (n=38)0.0 (n=54)0.0 (n=189)0.0 (n=106)0.0 (n=30)0.0 (n=105)0.6 (n=160)39.5 (n=38)
A_fewshot0.0 (n=38)13.0 (n=54)12.7 (n=189)8.5 (n=106)3.3 (n=30)3.8 (n=105)29.4 (n=160)0.0 (n=38)
B0.9 (n=38)54.9 (n=54)40.0 (n=189)64.2 (n=106)20.0 (n=30)66.7 (n=105)80.8 (n=160)60.5 (n=38)
C43.9 (n=38)56.8 (n=54)35.3 (n=189)46.5 (n=106)0.0 (n=30)87.0 (n=105)77.9 (n=160)60.5 (n=38)
Dmix43.9 (n=38)60.5 (n=54)42.5 (n=189)46.5 (n=106)23.3 (n=30)85.1 (n=105)79.6 (n=160)60.5 (n=38)
Dseq47.4 (n=38)54.3 (n=54)34.6 (n=189)40.9 (n=106)21.1 (n=30)80.6 (n=105)71.0 (n=160)61.4 (n=38)
C_matched13.2 (n=38)57.4 (n=54)24.9 (n=189)61.3 (n=106)3.3 (n=30)81.9 (n=105)81.9 (n=160)60.5 (n=38)
C_2547.4 (n=38)55.6 (n=54)33.3 (n=189)50.9 (n=106)10.0 (n=30)81.0 (n=105)75.0 (n=160)60.5 (n=38)
C_5050.0 (n=38)68.5 (n=54)40.2 (n=189)64.2 (n=106)40.0 (n=30)79.0 (n=105)82.5 (n=160)60.5 (n=38)
C_full34.2 (n=38)59.3 (n=54)24.3 (n=189)51.9 (n=106)0.0 (n=30)82.9 (n=105)75.0 (n=160)60.5 (n=38)
teacher36.8 (n=38)74.1 (n=54)53.4 (n=189)88.7 (n=106)43.3 (n=30)68.6 (n=105)95.0 (n=160)78.9 (n=38)
oracle100.0 (n=38)100.0 (n=54)100.0 (n=189)100.0 (n=106)100.0 (n=30)100.0 (n=105)100.0 (n=160)100.0 (n=38)

Success (%) by gold workflow depth (pooled main splits, seed-averaged)

config01245
A_base39.5 (n=38)0.2 (n=410)0.0 (n=242)0.0 (n=15)0.0 (n=15)
A_fewshot0.0 (n=38)16.3 (n=410)10.3 (n=242)0.0 (n=15)0.0 (n=15)
B60.5 (n=38)69.0 (n=410)34.3 (n=242)82.2 (n=15)4.4 (n=15)
C60.5 (n=38)67.9 (n=410)40.9 (n=242)13.3 (n=15)0.0 (n=15)
Dmix60.5 (n=38)67.9 (n=410)47.0 (n=242)71.1 (n=15)0.0 (n=15)
Dseq61.4 (n=38)61.7 (n=410)40.4 (n=242)66.7 (n=15)0.0 (n=15)
C_matched60.5 (n=38)70.7 (n=410)31.4 (n=242)0.0 (n=15)0.0 (n=15)
C_2560.5 (n=38)64.4 (n=410)43.8 (n=242)20.0 (n=15)0.0 (n=15)
C_5060.5 (n=38)75.1 (n=410)48.3 (n=242)13.3 (n=15)0.0 (n=15)
C_full60.5 (n=38)66.8 (n=410)32.6 (n=242)0.0 (n=15)0.0 (n=15)
teacher78.9 (n=38)82.9 (n=410)56.6 (n=242)33.3 (n=15)26.7 (n=15)
oracle100.0 (n=38)100.0 (n=410)100.0 (n=242)100.0 (n=15)100.0 (n=15)

Success (%) by graph seen in train (pooled main splits, seed-averaged)

configFalseTrue
A_base0.0 (n=300)3.8 (n=420)
A_fewshot14.0 (n=300)11.9 (n=420)
B33.4 (n=300)71.8 (n=420)
C32.4 (n=300)72.6 (n=420)
Dmix38.2 (n=300)74.0 (n=420)
Dseq25.6 (n=300)73.2 (n=420)
C_matched36.0 (n=300)66.9 (n=420)
C_2533.3 (n=300)70.5 (n=420)
C_5041.0 (n=300)77.9 (n=420)
C_full26.7 (n=300)70.5 (n=420)
teacher66.3 (n=300)75.5 (n=420)
oracle100.0 (n=300)100.0 (n=420)

Success (%) by fault-injected / not-found (pooled main splits, seed-averaged)

configFalseTrue
A_base2.6 (n=615)0.0 (n=105)
A_fewshot14.3 (n=615)3.8 (n=105)
B54.0 (n=615)66.7 (n=105)
C50.6 (n=615)87.0 (n=105)
Dmix54.7 (n=615)85.1 (n=105)
Dseq48.7 (n=615)80.6 (n=105)
C_matched49.3 (n=615)81.9 (n=105)
C_2550.6 (n=615)81.0 (n=105)
C_5059.7 (n=615)79.0 (n=105)
C_full47.0 (n=615)82.9 (n=105)
teacher72.2 (n=615)68.6 (n=105)
oracle100.0 (n=615)100.0 (n=105)

Efficiency (per evaluation run)

runpeak mem GBwall sepisodesmean turnsgen tok/smean latency/episode s
A_base2.9885710082.681260.85
A_fewshot3.90113710082.71871.12
B_s03.49102310082.61931.01
B_s13.838837203.14881.23
B_s23.438117202.93941.13
C_25_s03.036247202.43900.87
C_50_s03.106207202.56970.86
C_full_s03.015757202.451000.80
C_matched_s03.246487202.47910.90
C_s03.1687010082.39910.86
C_s13.035977202.36910.83
C_s23.336587202.51890.91
Dmix_s03.1094910082.53920.94
Dmix_s13.126857202.57900.95
Dmix_s23.498117203.00881.13
Dseq_s03.1497510082.64870.97
Dseq_s13.126677202.75940.93
Dseq_s23.327497202.82871.04
oracle0.00010082.42-0.00
teacher13.1758017202.49128.05

<!-- END:tables -->

6.1 Findings by hypothesis

Paired p-values below are Holm-adjusted over all 84 tests in the table above. Paired CIs average each task over seeds and so exclude training-seed variance; the hierarchical CIs in the first table are the conservative ones.

  • H1 (training helps): supported. Every trained configuration beats A on every split (e.g. B on eval_id +81.0 pts [+75.7, +86.0]). Pooled success: A 2.2%, B 55.8%, C 55.9%, Dmix 59.1%, Dseq 53.3%.
  • H2 (beyond format): supported, except on unseen tools. Against the few-shot prompt (A⁺), B gains +74.0 pts on eval_id, +26.5 on eval_ood and +61.5 on eval_renamed; C, Dmix and Dseq are similar. Diagnostics that format cannot fix also improve: argument correctness 44% (A⁺) → 77–81%, dependency safety 75% → 91–94%, tool-selection F1 55% → 79–82%. On eval_unseen B, C and Dmix are level with A⁺ (differences −0.3 to +3.1 pts, all n.s.), and Dseq is worse (−24.2).
  • H3 (generalisation): partly supported. Renaming every tool and parameter costs only ~13–21 pts (eval_id → eval_renamed), so models read the catalogue rather than memorise names. But unseen templates drop to 30–36%, and success on novel dependency graphs (26–38%) is about half that on graphs seen in training (72–74%). Depth-5 chains are almost never solved (≤4.4%, n=15). Most of the gain is pattern-specific.
  • H4 (signal source): no aggregate difference; different profiles. C − B is between −4.2 and +3.7 pts on every split (all n.s.). By family (descriptive, not tested): B is near zero on approval (0.9% vs C 43.9%), which matches the single accepted approval episode in the teacher data; C is stronger on recovery (87.0% vs 66.7%) and makes fewer unnecessary calls (0.4 vs 1.1 per episode); B is stronger on parallel calls (64.2% vs 46.5%) and permission (20.0% vs 0.0%, n=30). C_matched (oracle labels on exactly B's tasks) loses 8.7 pts to C on eval_id (p_holm 0.021) and 7.7 to B on eval_renamed (p_holm 0.021); on one seed this suggests that which tasks are covered matters at least as much as who labelled them.
  • H5 (combination): partly refuted. Dmix is numerically best pooled (59.1%) and beats B on eval_id (+5.0) and both B and C on eval_ood (+5.3 each), but none of these survives correction (p_holm ≥ 0.37). Strategy matters: sequential B→C (Dseq) matches Dmix in-distribution but falls on eval_unseen by −27.2 pts [−33.9, −20.3] (p_holm 0.021), consistently across all three seeds (8.3%, 4.2% and 15.0% per seed vs 22–53% for Dmix). On the inventory templates Dseq often mangles identifiers (e.g. calling reserve_stock with "535" for SKU-535); the cause is not established.

6.2 Ablations (single seed; seed-to-seed spread of C pooled success is 53.6–58.6%)

  • Data quantity (oracle data, fixed 250 × 8 episode budget): C_25 55.0%, C (1,000) 55.9%, C_50 62.5%, C_full 52.2%. No monotonic trend; C_50 is the best single run, mainly on eval_unseen and eval_renamed. With one seed per point, only C_50's lead is outside C's own seed spread, and only modestly. C_full sees each of its 3,000 episodes 0.67 times, so it is not "more training".
  • Catalogue size (0, 5, 12 distractor tools; seed 0): trained models lose at most 5.2 pts from 0 to 12 distractors, with overlapping CIs; distractors are not a bottleneck at this scale.
  • Unseen tools / combination strategy: see H2 and H5.

7. Limitations

  1. Synthetic, simulated environment. 21 deterministic tools and 44 templated scenarios. Requests are template-generated English; real APIs, real users and open-ended requests are not tested.
  2. Weak, local teacher. No hosted API was available, so the teacher is a 4-bit 4B model that solves 71.7% of held-out tasks. B is bounded by it: only 63.2% of offered tasks yielded an accepted episode, and approval tasks yielded one. B-vs-C differences are therefore confounded with task coverage (C_matched isolates this on one seed only).
  3. Flawed template. un_timezone (30 of 120 eval_unseen tasks) is answerable from world knowledge, and its gold graph requires a geocode call the system prompt never demands; every model, including the teacher, scores 0%. It was found after results were seen. The pre-registered split is reported unchanged; a clearly labelled post-hoc table excludes the template.
  4. Uneven coverage of behaviours. eval_id and eval_renamed contain no permission tasks (all such requests overlapped the training pool verbatim and were removed), so permission handling is measured only on eval_ood (n=30). Approval (n=38), depth-4 and depth-5 strata (n=15 each) are small.
  5. Seeds. Main configurations have 3 seeds each; ablations 1. Seed-to-seed spread is large (e.g. Dmix eval_unseen 22–53%), so single-seed ablation differences of a few points are not evidence.
  6. Hyperparameters were not swept per configuration. The final setting was chosen after two diverged configurations, using pilots on Dmix and B (§4.1); all configurations share it. Other configurations might prefer different settings. Selection used validation loss on oracle data.
  7. Multiple comparisons. 84 paired tests are reported; Holm-adjusted p-values are given. The smallest attainable permutation p (~0.0002) limits how small adjusted values can be.
  8. Decoding and measurement. Greedy decoding, batched (64 episodes; teacher 24 because of memory), which is not bit-for-bit deterministic. Latency and throughput are measured at those different batch sizes and are not a like-for-like speed comparison.
  9. Run history. Teacher pass 1 was interrupted once (Metal out-of-memory while other GPU jobs ran) and pass 2 once (a simulator crash on malformed teacher arguments, fixed by validating array items; outcomes of earlier episodes were unaffected). The prefill batch changed from 8 to 16 for the last 75 pass-2 episodes. The Mac slept during Dseq_s1 training; the trainer's clock excludes the sleep, the queue's wall-clock does not. All student evaluations ran after the simulator fix.
  10. Provenance. The teacher's 4-bit weights were not revision-pinned at download time; the recorded revision is the Hub HEAD at the time (see MODEL_SELECTION.md). The few-shot baseline uses one prompt design; a better prompt could narrow the gap.

8. Compute and cost

<!-- BEGIN:queue --> Successful queue steps: 33 (training 11.18 h, evaluation 4.99 h wall-clock). <!-- END:queue -->

All work ran on one Apple M4 laptop with 16 GB of unified memory; no cloud compute and no API spend.

stagewall-clock
Teacher generation (pass 1 + pass 2, 2,261 episodes)5.6 h
Final training (16 runs × 250 steps, trainer clock)10.1 h
Final evaluation (queue)5.0 h
Failed/diagnostic training: v1 (11 runs) + v1 evaluations8.4 h + 3.4 h
LR pilot, v1b run, stability study≈ 1.5 h

Peak memory: ≈ 4.0 GB for training, 3.0–3.9 GB for student evaluation, 13.2 GB for teacher evaluation. Cost: electricity only.

9. Reproducibility

See the README for the full sequence of commands. Every stage is deterministic given its seed and resumable; the remaining non-determinism is in batched GPU arithmetic. Dataset hashes are in data/v1/manifest.json and data/v1/sft/variants.json, adapter hashes in §4, and the pinned environment in requirements.lock. Model weights and adapters are not committed (size); adapter hashes allow verification of re-trained or shared adapters.

Pipeline

Run from the repository root, in order. Each stage is resumable and writes versioned outputs.

commandwhat it does
python scripts/build_datasets.pyVersioned task pools, oracle (config C) SFT data and the leakage audit → data/v1/
python scripts/distill.py --n 1600Teacher demonstrations with rejection sampling (config B data) → data/v1/teacher/
python -m ccl.train configs/train/<name>.jsonLoRA training per configuration and seed → runs/<config>_s<seed>/
python scripts/evaluate.py --name B_s0 --adapter runs/B_s0Evaluate a configuration on the held-out splits → results/runs/<name>/
python scripts/analyze.pyHierarchical-bootstrap CIs, paired comparisons, breakdowns → results/summary.json
python scripts/export_web.pyPublish real run outputs for this site → web/public/data/
python scripts/serve.py --port 8765Optional: local inference server for the trace explorer's live mode

Site data exported 2026-11-10T08:24:12+00:00 at commit 364c676.

Datasets (v1)

Every task pool and training file with its SHA-256, size and per-family composition.

filesizecompositionsha256
tasks/train_pool.jsonl3000 tasksselect 750args 250chain 750parallel 250recovery 375permission 250approval 125termination 250649b77769d5b698d29497c9c6ac9ebd49e3696e1fbdeb8f6d61d1de0b03f9ef8
tasks/val_pool.jsonl150 tasksselect 42args 12chain 36parallel 12recovery 18permission 12approval 6termination 124109a155f1daf1ad0a08b29dc0411b783c4bd7de76b3723977feadce3db834f3
tasks/eval_id.jsonl200 tasksselect 63args 14chain 39parallel 26recovery 32approval 13termination 136fabbbc2706a62e40508f593dd49a65318dfdabbc5cdd4a2311405a2bc134ec2
tasks/eval_ood.jsonl240 tasksselect 15args 30chain 60parallel 30recovery 45permission 30approval 15termination 1525291967e97885ded027607d770898bb5ebb5090e640a25f5c1aa7bc46d74feb
tasks/eval_unseen.jsonl120 tasksselect 30chain 60parallel 30ac20b173e4fe043d41565d204c3e9ce9581aeda8bb60e22f4d7cbd403d00234d
tasks/eval_renamed.jsonl160 tasksselect 52args 10chain 30parallel 20recovery 28approval 10termination 10e9170bafa8623c26200353fc5b8570b6be74e456d2923b2c52d4940acc28da4b
tasks/eval_catalog_0.jsonl96 tasksselect 30args 6chain 18parallel 12recovery 18approval 6termination 6e033c69e76167e775ed38b85c6112fb8a3626fd06b2fd6631af26008ff95e3ac
tasks/eval_catalog_5.jsonl96 tasksselect 30args 6chain 18parallel 12recovery 18approval 6termination 6bcdc5d4db006e8283e1c198b8b0145669a0d0c8f3beb9c64a685cebd7d0fc06e
tasks/eval_catalog_12.jsonl96 tasksselect 30args 6chain 18parallel 12recovery 18approval 6termination 6ec9e9d0d3c2e9c8f08e48c02a5ce774bbffff1b9d7f89835f200749eecd7865c
sft/oracle_train.jsonl7000 rows
3000 episodes
—e936bda77a6c6e991211095a5034e499435f936f000073d6f268959978e6a925
sft/oracle_val.jsonl348 rows
150 episodes
—2d5ce305fedc6d3607d16c6fc6be83cc872595c3447cecf352488998efa9cf17
Task templates (44) — which split each template belongs to
templatefamilysplitdepth
sel_weatherselecttrain1
sel_fxselecttrain1
sel_order_statusselecttrain1
sel_calcselecttrain1
sel_contact_emailselecttrain1
sel_eventsselecttrain1
sel_cheapest_flightselectood1
args_ticketargstrain1
args_event_soloargstrain1
args_fx_symbolsargsood1
args_ticket_from_symptomargsood1
ch_contact_emailchaintrain2
ch_order_customerchaintrain2
ch_file_readchaintrain2
ch_contact_eventchaintrain2
ch_deep2chaintrain2
ch_deep3chaintrain3
ch_deep4chainood4
ch_deep5chainood5
ch_order_fxchainood2
ch_book_cheapestchainood2
par_weatherparalleltrain1
par_ordersparalleltrain1
par_contacts_eventparallelood2
par_fxparallelood1
rec_weather_timeoutrecoverytrain1
rec_weather_downrecoverytrain1
rec_order_missingrecoverytrain1
rec_order_timeout_chainrecoveryood2
rec_contact_missing_emailrecoveryood1
rec_fx_timeoutrecoveryood1
perm_delete_viewerpermissiontrain1
perm_delete_adminpermissiontrain2
perm_refund_viewerpermissionood1
perm_refund_agentpermissionood2
appr_email_deniedapprovaltrain2
appr_book_deniedapprovalood2
term_smalltalkterminationtrain0
term_no_lookupterminationtrain0
term_capabilityterminationood0
un_stock_checkselectunseen1
un_stock_reservechainunseen2
un_timezonechainunseen2
un_stock_parallelparallelunseen1

Leakage audit

Overlap between each evaluation split and the training pool. Exact request overlap must be zero: candidate tasks whose request text appears in the training or validation pools are discarded while the split is generated. Shared templates and graph shapes are expected for the in-distribution and renamed splits; they are what the OOD and unseen-domain splits remove. The last column lists held-out-domain tools that leaked into training catalogues.

splittasksrequest overlapdropped for overlapshared templatesgraph-shape overlapunseen-domain tools found in train
eval_id200 011318100%none
eval_ood240 00025%none
eval_unseen120 0000%none
eval_renamed160 08117100%none
eval_catalog_096 0—17100%none
eval_catalog_596 0—17100%none
eval_catalog_1296 0—17100%none

Teacher generation (config B data)

How the Qwen3.5-4B teacher's demonstrations were produced and filtered: decoding settings, acceptance counts, throughput and cost.

teacher
repo: mlx-community/Qwen3.5-4B-MLX-4bitupstream: Qwen/Qwen3.5-4Blicence: apache-2.0local path: models/Qwen3.5-4B-MLX-4bit
generation
show
{
  "pass1": {
    "temperature": 0
  },
  "pass2": {
    "temperature": 0.7,
    "top_p": 0.95,
    "seed": 1
  },
  "max_tokens": 256,
  "thinking": false,
  "batch_size": 16,
  "prompt": "identical system prompt and catalogue rendering as the student (ccl.runtime)"
}
tasks offered
1,600
summary
show
{
  "pass1": {
    "episodes": 1600,
    "accepted": 939,
    "acceptance_rate": 0.5869,
    "rejection_reasons": {
      "not_success": 458,
      "unnecessary_calls": 304,
      "dependency_unsafe": 185,
      "invalid_args": 117,
      "parse_error": 187
    }
  },
  "pass2": {
    "episodes": 661,
    "accepted": 72,
    "acceptance_rate": 0.1089,
    "rejection_reasons": {
      "not_success": 443,
      "parse_error": 214,
      "unnecessary_calls": 270,
      "dependency_unsafe": 143,
      "invalid_args": 84
    }
  }
}
accepted episodes
1,011
accepted by family
permission: 47termination: 131select: 308args: 122parallel: 124recovery: 132chain: 146approval: 1
sft rows
2,127
sft sha256
3155514b49cac029b2598521780a0fe730c97f98beaf0f3fb1012882ed25553c
timings s
pass1 s: 13,333pass2 s: 7,054source: wall-clock from file timestamps (pass1: 13:51:16-17:33:29, pass2: 17:33:29-19:31:03 on 2026-11-08), including one OOM restart (pass 1) and one crash restart (pass 2). distill.py only times the final invocation, which ran nothing after resumes, so its own timings were discarded.
token stats
prompt tokens: 0gen tokens: 0gen time s: 0calls: 0note: pass-1 counters; pass 2 continues on the same object
cost
api usd: 0note: local inference on Apple M4 16GB; cost is wall-clock only