# Saving and training

Saving a voice and training a model are different things. Saving records which writing and settings a voice uses, so you can load the same voice again; it changes nothing about any model. Training fine-tunes a model on the voice's writing, and it's optional.

## Training is optional

Most voices should start without fine-tuning: real examples, good retrieval, a capable base model, and evaluation. Train only when held-out evals show a repeatable gap that better examples and clearer requests aren't closing.

Start with a base model, well-chosen exemplars and an evaluator to pick the best of several drafts, and [evaluate](https://june.dev/docs/copy/scoring.md#evaluating-a-voice) it against a baseline before deciding it isn't enough. Fine-tuning is worth it when that evaluation shows the voice falls short in ways exemplars can't fix, and you have enough writing to train on: hundreds of samples, not a handful. A tuned model learns the invariant voice; exemplars still supply what fits the situation, so a tuned voice still retrieves and scores like any other. And a tuned model has to be served somewhere, which a base model doesn't.

## Manifests

`voice.manifest()` returns plain JSON. It serializes the voice's identity, settings and provenance, not its code: the corpus, writer model, retriever and evaluators are code. The manifest records what it can check about them, the corpus's fingerprint and the evaluators' names, and you pass the code itself again when you load.

```json
{
  "version": 1,
  "name": "fonzie",
  "corpus": {
    "source": "./fonzie.jsonl",
    "size": 10,
    "fingerprint": "sha256:…"
  },
  "settings": {
    "exemplars": 5,
    "candidates": 3,
    "threshold": 0.75,
    "evaluators": ["stylometry"]
  }
}
```

It has the voice's name, the raw corpus's source, size and fingerprint (for a prepared voice too), its settings, including `instructions` if set and each evaluator's name, and for a trained voice, the `artifact` and the `training` manifest of the fine-tune that made it. Save it next to anything the voice wrote, so you know which voice wrote it.

## Loading a voice

`loadVoice()` rebuilds a voice from its manifest and the code the manifest can't hold. You supply:

- `corpus`: required, and checked against the manifest's fingerprint;
- `model`: required, the writer, or for a trained voice the model that serves its artifact;
- `evaluators`: checked by name against the manifest; `[stylometry()]` when you pass none;
- `retriever`: optional, not checked; `metadataRetriever()` when you pass none.

```ts
import { openai } from "june/sdk/openai";
import { copyCorpus, loadVoice, type VoiceManifest } from "june/copy";

declare const saved: VoiceManifest;

const fonzie = await loadVoice(saved, {
  corpus: copyCorpus("./fonzie.jsonl"),
  model: openai("gpt-5-mini"),
});
```

It refuses a corpus with a different fingerprint, or evaluators with different names (`[stylometry()]` when you pass none), so a loaded voice retrieves from the same writing, with evaluators of the same names, as the one you saved. It can't check the evaluators' own options (a `judge()`'s model, say), the retriever or the writer model, so pass the same ones. Its name, settings, artifact and training come from the manifest.

The manifest records the raw corpus, so a voice loads from `copyCorpus()` whether or not it was prepared. Pass the `PreparedVoice` instead if you have it, to keep retrieving with its inferred labels. For a trained voice, `model` is the model that serves its artifact.

## Fine-tuning a voice

`voice.train()` is a thin layer over the generic [fine-tuning](https://june.dev/docs/training/fine-tuning.md) lifecycle; read that for jobs, trainers and artifacts. It prepares the corpus (unless it's already a `PreparedVoice`), fine-tunes on its splits with `fineTune()`, and returns a voice that writes with the tuned model. A tuned artifact isn't always callable directly, so `serve` turns it into an AI SDK model:

```ts
import { openai } from "june/sdk/openai";
import { copyCorpus, voice } from "june/copy";
import { trl } from "june/trainers";
import type { LanguageModel } from "ai";

declare function servedAdapter(uri: string): LanguageModel;

const { voice: tuned, result } = await voice({
  name: "fonzie",
  corpus: copyCorpus("./fonzie.jsonl"),
  model: openai("gpt-5-mini"),
}).train({
  model: "Qwen/Qwen3-8B",
  trainer: trl(),
  method: { type: "lora", rank: 32 },
  prepare: { teacher: openai("gpt-5") },
  serve: (artifact) =>
    servedAdapter(artifact.type === "adapter" ? artifact.uri : ""),
});

await tuned.write({ type: "tweet", intent: "announce scheduling" });
```

`train()` takes `fineTune()`'s options except `dataset` and `name`, which come from the voice, plus `serve` and, for a raw corpus, `prepare` with a teacher. Without either a prepared corpus or `prepare`, it throws. The tuned voice keeps every setting of the original, and `result` is the fine-tune's result, with its artifact and manifest. The tuned voice's manifest records both.

The training data is the prepared splits: each kept sample's reconstructed brief as the prompt and the person's text, unchanged, as the completion. Test samples aren't trained on, so keep them for evaluating the tuned voice against the untuned one. Decide whether the tuned voice replaces the untuned one with [Comparing candidates](https://june.dev/docs/training/comparing.md), on the same held-out cases.

## Serving and portability

A voice's manifest is portable everywhere: it's JSON, and any process with the same corpus and code can load it. A trained model is a separate thing, and its portability depends on the artifact. A local `adapter` is files you own: serve it with vLLM or similar, move it, keep it. A `provider-model` only exists inside its provider, and `serve` returns that provider's model for its `modelId`. See [artifacts](https://june.dev/docs/training/fine-tuning.md#artifacts).
