Evals and training
Evaluate first. Train only when the evidence says you should.
june/train gives you the pieces to build datasets, run repeatable evals, compare a candidate with what's active, and, when prompting, retrieval or examples are no longer enough, orchestrate a fine-tune. Most improvements never need the last step: a better prompt or a better retrieval set is a candidate too, and it's judged on the same evals.
dataset → eval → compare → (fine-tune → eval again) → a person promotes
- Dataset. Collect examples of the behavior, from files or from chat feedback, and split off a test set before anything else.
- Eval. Turn the behavior into cases and graders, and run what's active today: the baseline.
- Compare. Run a candidate on the same cases and line it up with the baseline case by case.
- Fine-tune, only if needed. When cheaper candidates plateau, train one, then evaluate it on the same held-out cases.
- Promote. A person reads the comparison and decides. June gives you the evidence and lineage as plain values to keep; it doesn't store eval runs or promote anything itself.
Use as much as you need
june/train doesn't depend on june/copy, june/chat or a workflow, and each layer works without the next:
- Evals only.
evalSuite(),evaluate()andcompareEvaluations()score any model, agent or function. No dataset or trainer needed. - Datasets and evals.
dataset()loads, cleans and splits examples, andcasesFrom()turns a held-out split into eval cases. - Fine-tuning.
fineTune()adds a trainer:trl(),vertex()or your own. - In workflows.
<FineTune>and<Evaluate>run the same calls as traced, cancelable workflow steps.
june/copy and learning from chat are built on these primitives, and the Copy Agent is a complete application that uses them: it turns chat feedback into evidence and evaluates candidates against a golden benchmark, and a person promotes a candidate only after seeing that evaluation.
Architecture
June orchestrates training; it isn't an ML framework. It owns the data, the job lifecycle, the evals and the record of what was trained. The weights, GPUs, optimization and provider APIs belong to a trainer. June doesn't decide when to train, tune anything on its own, or promise that a fine-tune behaves the same on every trainer.
june/train june/trainers
┌───────────────────────────────────────┐ ┌──────────────────────────┐
│ dataset() ──split──▶ train / test │ │ trl() → Python + TRL │
│ │ Trainer│ vertex() → Vertex AI │
│ fineTune() ── start/status/cancel ────┼───────▶│ yours → anything │
│ │ │ └──────────────────────────┘
│ ▼ │
│ Job ── ref (JSON) ── survives restarts│
│ │ │
│ ▼ │
│ artifact + manifest │
│ │
│ evalSuite() ─▶ evaluate() ─▶ compare │
│ │
│ <FineTune> / <Evaluate> in workflows │
└───────────────────────────────────────┘
▲
│ built on
┌───────────────────────────────────────┐
│ june/copy: corpus → prepareVoice() │
│ → voice().write() │
└───────────────────────────────────────┘
| June owns | Trainers and providers own |
|---|---|
| canonical datasets, fingerprints and splits | converting examples to their format |
| checking a trainer supports a method | running the training, on whatever hardware |
| starting, polling and canceling jobs | the job's real state and its progress |
| a JSON manifest of what was trained, from what | the weights, endpoints and provider provenance |
| eval suites, runs and comparisons | serving a tuned model |
| copy corpora, reverse instructions, voices |
Local or managed
The same fineTune() call targets either. Only the trainer changes:
trl() (local) | vertex() (managed) | |
|---|---|---|
| Runs | a Python process on your machine | Google's infrastructure |
| Models | open-weight models from Hugging Face | Gemini models Vertex can tune |
| Methods | sft, lora, qlora | sft |
| Artifact | adapter or checkpoint files you own | a provider-model endpoint inside Vertex |
| Data | stays on your machine | uploaded to your Cloud Storage bucket |
| Config | epochs, learningRate, batchSize, maxSequenceLength, seed, LoRA options | epochs; the rest through providerOptions.vertex |
| Examples | completion or conversation, one shape per dataset | completion and conversation, text only |
| Needs | uv, and a GPU for anything but small models | a Google Cloud project, a bucket and an access token |
The call is portable; the results aren't. The same dataset and method on two trainers produce different artifacts, trained differently, and need to be served and evaluated separately. Compare them on the same eval suite rather than assuming one stands in for the other.
Artifacts
A fine-tune produces an artifact, which isn't always a model you can call:
adapter: weights on top of the base model, such as a LoRA. Serve it with a LoRA-capable server.checkpoint: a full set of weights.provider-model: a model that only exists inside a provider, called by itsmodelId.
Every fine-tune also returns a manifest: plain JSON naming the trainer, base model, method, config and the fingerprints of the data it trained on. Store it next to the artifact and next to the comparison that justified promoting it.
Guides
Read them in order the first time. The first three are all most agents need:
- Datasets: splits that keep test data out of training, fingerprints, example shapes, transforms
- Evals: the smallest useful eval, deterministic and model graders, results
- Comparing candidates: promotion rules, golden benchmarks and evolving evidence
- Fine-tuning: when to fine-tune and when not to,
fineTune(), artifacts, manifests, serving - Jobs: work a backend owns, which outlives your process
- Training in workflows: when
<FineTune>and<Evaluate>are worth it over plain calls - Local training: hardware, TRL and PEFT with uv
- Managed training: Vertex AI, its data and credential boundary, swapping trainers
- Custom trainers: when to write one, and the smallest contract
Related:
- Learning from chat: feedback into datasets and eval cases
- Copy and voice: from a person's writing to a voice, its drafts and their edits, with guides to corpora, preparation, writing, retrieval, scoring, saving and training and learning from edits
- Chat: sessions, turns, responders, storage, context and feedback
- Tools:
<Bash />and where it runs - Security and privacy
- Compatibility: what's stable
Examples
Each one runs; the first four need nothing but Bun, and June's tests run them.
| Example | |
|---|---|
quickstart.ts | dataset, baseline eval, a custom trainer, tuned eval, comparison |
compare.ts | two candidates on a golden benchmark and held-out evidence |
voice.ts | prepare a corpus, train a voice, write with it |
workflow.tsx | <FineTune> then <Evaluate> under june run |
dataset.ts | load, filter, dedupe and split a JSONL dataset |
local-trl.ts | LoRA-tune a small model with TRL (needs uv) |
managed-vertex.ts | tune Gemini on Vertex AI (needs a Google Cloud project) |
Imports
import { dataset, evaluate, fineTune, FineTune } from "june/train";
import { trl, vertex } from "june/trainers";
import { copyCorpus, prepareVoice, voice } from "june/copy";
All three are subpaths of the june package, with no npm dependencies beyond June's own. Python's ML packages are only ever installed in the TRL runner's isolated environment.