Skip to content
Markdown

Autodata: an agentic data scientist for synthetic data

Scope: an agent that acts as a data scientist, autonomously generating and refining synthetic training and evaluation datasets, and a meta-optimization loop that improves the generating agent itself over rounds. Proposed in "Autodata: An agentic data scientist to create high quality synthetic data" (Kulikov et al., 2026), with a concrete instantiation called Agentic Self-Instruct. This is the self-improving-generation angle on synthetic data generation: where that page covers one-shot generators plus filtering, this page covers an agent that runs an inner accept-or-refine loop per example and an outer loop that rewrites its own scaffold. Do not duplicate the sibling page; cross-link it.

The core dynamics of both loops are modelled in a runnable numpy block below (validated: filtered meta-optimization improves data quality monotonically to a ceiling, an already-optimal generator shows no spurious gain, an unfiltered loop collapses, and a biased filter propagates its bias). The agent-orchestration snippet is a clearly-labelled reference template, not executed. This is a single recent (2026) paper; treat exact numbers and model choices as version-specific and validate on your own setting.

What it is

Autodata frames dataset construction as an agent task: an LLM agent, given source documents and an inference budget, produces examples, inspects the results, and iterates until a quality bar is met, the same way a human data scientist would. The paper's practical instantiation, Agentic Self-Instruct, uses a main orchestrator that coordinates four subagents:1

  • Challenger: drafts a training example (a context, a question, and a grading rubric) from a source document.
  • Weak solver: a smaller model expected to generally fail the example.
  • Strong solver: a larger model expected to generally succeed.
  • Judge / verifier: scores both solvers' attempts and returns targeted feedback.

The inner loop runs per document: the challenger drafts an example, both solvers attempt it (typically several rollouts each), and the judge measures the separation gap. For verifiable tasks the acceptance rule is that a majority vote over the strong solver is correct while a majority vote over the weak solver is wrong; for non-verifiable tasks it is a rubric-scored quality gap. If the criterion is not met, the agent revises the challenger prompt and retries, up to a budget. A good example is therefore defined operationally: one that a strong solver passes and a weak solver fails, which is exactly the difficulty band from which RL learns the most.

The outer loop is meta-optimization: the agent's scaffold (its prompts) is treated as evolvable code. Each round selects a parent scaffold by Boltzmann sampling, evaluates it on training documents, has an analyzer LLM diagnose systematic failure patterns from the solver transcripts, has a code-editing LLM produce a prompt diff, re-evaluates the mutant on held-out documents, and keeps the mutant only if its validation score beats the parent's. The reported headline is that this converts extra inference-time compute into higher training-data quality.1

Why use it

  • Quality is defined by a verifiable signal, not by the generator's confidence. The weak-versus-strong separation gap is a checkable target, so acceptance is grounded in solver behaviour rather than the generator asserting its own output is good. This is the difference from a one-shot Self-Instruct pass.
  • Difficulty is targeted, not accidental. The inner loop steers examples into the band where a strong solver succeeds and a weak one fails, which is the learnable region for downstream RL; the reported CS separation gap widened from 0.019 (a standard chain-of-thought Self-Instruct baseline) to 0.314.1
  • The generator improves itself. Meta-optimization is a validation-gated hill-climb on the scaffold, so accepted changes cannot regress the held-out score; the reported CS validation pass rate rose from about 62.1% to 79.6% over 126 accepted mutations of 233 proposed.1
  • Compute becomes data quality. Extra rounds and larger solver ensembles buy better examples, a knob you can turn without new human labels.

When to use it (and when not)

  • Use it when you have source documents to ground generation, a weak/strong solver pair that produces a measurable difficulty gap, and enough inference budget to run multiple accept-or-refine rounds per example (the reported CS runs averaged 6.59 rounds versus 1.00 for the one-shot baseline).1
  • Use it when downstream training is RL or rejection sampling that benefits from examples calibrated to a specific difficulty band, rather than a flat instruction set.
  • Prefer plain synthetic data generation (one-shot Self-Instruct, Evol-Instruct, Magpie) when you need volume cheaply and a coarse quality filter is enough; the agentic loop costs many more forward passes per accepted example.
  • Do not skip the filter. The whole method rests on the acceptance gate. An agentic loop with the gate removed is just recursive self-training and collapses the same way (see the runnable model and Failure modes).
  • Evidence caveat. The results are from one 2026 paper on three domains (CS research, legal reasoning, math/scientific reasoning) with a specific model stack. The mechanism is general; the exact gains and model choices are not established to transfer. Validate on your task.

Architecture

Two nested loops. The inner Agentic Self-Instruct loop generates and refines one example against the solver gap; the outer meta-optimization loop rewrites the agent that runs the inner loop, keeping a mutation only when it beats the parent on held-out data.

flowchart TB
  subgraph inner["Agentic Self-Instruct (inner loop, per document)"]
    DOC["Source document (paper / statute / problem)"] --> CH["Challenger: draft example + rubric"]
    CH --> WS["Weak solver (small model, N rollouts)"]
    CH --> SS["Strong solver (large model, N rollouts)"]
    WS --> J["Judge / verifier: score both, measure the gap"]
    SS --> J
    J -->|"gap too small: revise challenger prompt, retry"| CH
    J -->|"weak fails AND strong passes: accept"| KEEP["Accepted example"]
  end
  KEEP --> DS["Synthetic dataset"]
  DS --> TRAIN["RL / SFT the target model"]
  subgraph outer["Meta-optimization (outer loop, over the agent itself)"]
    SEL["Select a parent scaffold (Boltzmann sampling)"] --> AN["Analyzer LLM: diagnose failure patterns"]
    AN --> ED["Code-editing LLM: mutate the agent's prompts"]
    ED --> VAL["Re-evaluate on held-out documents"]
    VAL -->|"validation gap beats parent: keep mutant"| SEL
  end
  outer -->|"improved scaffold"| CH

How to use it

At the level that matters for whether the method works, Autodata is two control loops over a quality signal, and that signal plus both loops are small enough to model exactly. The block below is self-contained (numpy only) and pins down the four properties the method depends on: filtered meta-optimization improves data quality monotonically up to a ceiling, an already-optimal generator gains nothing spurious, an unfiltered loop collapses, and a filter that is itself biased bakes that bias into the dataset. Run: python3 autodata_loop.py.

# autodata_loop.py -- core dynamics of Autodata's two loops (numpy + stdlib, runnable).
#
# Faithful minimal model of Agentic Self-Instruct + meta-optimization.
# A generator aims synthetic examples at a difficulty target theta. Autodata's quality
# signal is the WEAK-vs-STRONG separation gap: a good training example is one a strong
# solver passes and a weak solver fails. That gap is UNIMODAL in difficulty (zero when
# both solvers pass, zero when both fail, peaked in a learnable middle band at theta*).
# The INNER loop keeps only separating examples; the OUTER meta-optimization proposes
# generator mutations and ACCEPTS one only if it raises the gap on HELD-OUT validation
# data, which is exactly the paper's validation-gated acceptance rule.
import numpy as np

THETA_STAR = 0.55                     # difficulty that maximizes the true weak/strong gap
_S = 12.0                             # solver sharpness
_C_WEAK, _C_STRONG = 0.35, 0.75      # the weak solver starts failing earlier than the strong one
_VAL_NOISE = np.random.default_rng(0).normal(0.0, 1.0, size=4000)  # frozen held-out draws


def _sig(x):
    return 1.0 / (1.0 + np.exp(-x))


def gap_with_peak(d, peak):
    """Separation gap P(strong solves) - P(weak solves), unimodal with its max at `peak`."""
    shift = peak - THETA_STAR
    p_strong = _sig(_S * ((_C_STRONG + shift) - d))
    p_weak = _sig(_S * ((_C_WEAK + shift) - d))
    return np.clip(p_strong - p_weak, 0.0, 1.0)


def true_gap(d):
    """Ground-truth example quality at difficulty d (what the trained model actually gets)."""
    return gap_with_peak(d, THETA_STAR)


def gen_difficulties(theta, sigma, noise):
    """Generator output: example difficulties ~ N(theta, sigma) clipped to [0, 1]."""
    return np.clip(theta + sigma * noise, 0.0, 1.0)


def validation_score(theta, proxy_peak, sigma=0.12):
    """Held-out validation quality of generator(theta), scored by a proxy peaked at proxy_peak.
    Deterministic in theta (frozen held-out draws), matching a fixed held-out document set."""
    d = gen_difficulties(theta, sigma, _VAL_NOISE)
    return float(gap_with_peak(d, proxy_peak).mean())


def meta_optimize(theta0, proxy_peak, rounds=40, steps=(0.08, 0.04, 0.02, 0.01)):
    """Outer loop: each round propose theta +/- step mutants, ACCEPT the best only if it
    beats the parent on validation. Returns the validation-score trajectory and final theta."""
    theta = float(theta0)
    traj = [validation_score(theta, proxy_peak)]
    for _ in range(rounds):
        parent = validation_score(theta, proxy_peak)
        cands = [theta + s for s in steps] + [theta - s for s in steps]
        cands = [c for c in cands if 0.0 <= c <= 1.0]
        best = max(cands, key=lambda c: validation_score(c, proxy_peak))
        if validation_score(best, proxy_peak) > parent:      # validation-gated acceptance
            theta = best
        traj.append(validation_score(theta, proxy_peak))
    return np.array(traj), theta


def collapse_loop(theta0, sigma0, rounds=12, shrink=0.8, batch=64, seed=1):
    """No-filter recursive self-training: refit the generator to its OWN last batch with no
    quality gate. Canonical model-collapse dynamics: variance (diversity) erodes each round."""
    rng = np.random.default_rng(seed)
    theta, sigma = float(theta0), float(sigma0)
    d0 = gen_difficulties(theta, sigma, rng.normal(0.0, 1.0, batch))
    sig_traj, qual_traj = [sigma], [float(true_gap(d0).mean())]
    for _ in range(rounds):
        d = gen_difficulties(theta, sigma, rng.normal(0.0, 1.0, batch))  # sample own output
        theta, sigma = float(d.mean()), float(d.std() * shrink)          # refit; tails erode
        sig_traj.append(sigma)
        qual_traj.append(float(true_gap(d).mean()))
    return np.array(sig_traj), np.array(qual_traj)


# --- Sanity: the quality landscape is unimodal with its true peak at THETA_STAR ---
grid = np.linspace(0.0, 1.0, 1001)
assert abs(grid[np.argmax(true_gap(grid))] - THETA_STAR) < 1e-2, "true gap must peak at THETA_STAR"
CEILING = validation_score(THETA_STAR, THETA_STAR)   # best achievable dataset quality

# (b) WITH filtering + meta-optimization: quality improves MONOTONICALLY up to the ceiling.
traj, theta_end = meta_optimize(theta0=0.15, proxy_peak=THETA_STAR, rounds=40)
assert np.all(np.diff(traj) >= -1e-12), "validation-gated meta-opt must never regress"   # monotone
assert traj[-1] > traj[0] + 0.2, (traj[0], traj[-1])                     # real, large improvement
assert traj[-1] >= CEILING - 1e-3, (traj[-1], CEILING)                   # converges to the ceiling
assert abs(theta_end - THETA_STAR) < 0.02, theta_end                     # recovered the optimum

# (c) BOUNDARY: an already-optimal generator does not improve (no spurious gains).
traj_opt, theta_opt = meta_optimize(theta0=THETA_STAR, proxy_peak=THETA_STAR, rounds=40)
assert np.ptp(traj_opt) < 1e-9, np.ptp(traj_opt)         # flat: nothing beats the optimum
assert theta_opt == THETA_STAR                           # the gate refuses every mutation
assert traj_opt[-1] <= CEILING + 1e-12                   # no gain conjured above the ceiling

# (d) HONEST FAILURE: a BIASED filter (proxy peaked off-target) propagates its bias.
BIAS_PEAK = 0.30
traj_bias, theta_bias = meta_optimize(theta0=0.55, proxy_peak=BIAS_PEAK, rounds=40)
assert abs(theta_bias - BIAS_PEAK) < 0.03, theta_bias                    # meta-opt chases the bias
proxy_view = validation_score(theta_bias, BIAS_PEAK)                     # looks great to the filter
true_view = validation_score(theta_bias, THETA_STAR)                     # ...but true quality is worse
assert proxy_view > 0.7, proxy_view
assert true_view < CEILING - 0.15, (true_view, CEILING)                  # bias baked into the dataset

# (a) WITHOUT a filter: recursive self-training collapses diversity (model-collapse style).
sig_traj, qual_traj = collapse_loop(theta0=0.35, sigma0=0.15, rounds=12)
assert sig_traj[-1] < 0.2 * sig_traj[0], (sig_traj[0], sig_traj[-1])     # diversity collapses
assert qual_traj[-1] < CEILING - 0.1, (qual_traj[-1], CEILING)           # quality never reaches ceiling

print("Autodata two-loop model: ALL CHECKS PASS")
print(f"  ceiling (best dataset quality)      = {CEILING:.3f}")
print(f"  (b) meta-opt filtered: round0={traj[0]:.3f} -> final={traj[-1]:.3f}  "
      f"theta {0.15:.2f}->{theta_end:.2f} (theta*={THETA_STAR})  monotone={bool(np.all(np.diff(traj) >= -1e-12))}")
print(f"  (c) start-at-optimum: score range over 40 rounds = {np.ptp(traj_opt):.2e}  (no spurious gain)")
print(f"  (d) biased filter (peak={BIAS_PEAK}): theta->{theta_bias:.2f}  "
      f"filter-score={proxy_view:.3f}  TRUE-quality={true_view:.3f}  (ceiling={CEILING:.3f})")
print(f"  (a) no-filter collapse: diversity sigma {sig_traj[0]:.3f}->{sig_traj[-1]:.3f}  "
      f"quality {qual_traj[0]:.3f}->{qual_traj[-1]:.3f}  (stuck below ceiling)")

Run output:

Autodata two-loop model: ALL CHECKS PASS
  ceiling (best dataset quality)      = 0.714
  (b) meta-opt filtered: round0=0.140 -> final=0.714  theta 0.15->0.55 (theta*=0.55)  monotone=True
  (c) start-at-optimum: score range over 40 rounds = 0.00e+00  (no spurious gain)
  (d) biased filter (peak=0.3): theta->0.30  filter-score=0.714  TRUE-quality=0.377  (ceiling=0.714)
  (a) no-filter collapse: diversity sigma 0.150->0.007  quality 0.461->0.376  (stuck below ceiling)

The four results map directly onto the method's claims and its limits. The monotone climb (b) is what the validation gate buys: because acceptance requires beating the parent, the held-out score can only go up, and it converges to the ceiling set by the quality signal. Case (d) is the load-bearing caveat: meta-optimization is only ever as good as the filter it optimizes against, so a filter peaked at the wrong difficulty (or rewarding a spurious feature) drives the generator to a dataset that scores high under the filter and low in reality. Case (a) is the sibling page's model collapse restated: strip the gate and the loop is recursive self-training, whose diversity erodes to almost nothing.

How to develop and integrate it

Agentic Self-Instruct is an orchestration over an inference stack, not a new trainer. The subagents are ordinary LLM calls; the machinery is the accept-or-refine control flow and the transcript that meta-optimization reads. A faithful sketch of the inner loop (reference template, needs an LLM client and is not executed here):

# agentic_self_instruct.py -- inner loop orchestration (REFERENCE TEMPLATE, not executed).
# The quality signal and both loops are validated in numpy above; this shows the wiring.
def make_example(document, llm, weak, strong, max_rounds, n_rollouts):
    prompt = seed_challenger_prompt(document)          # challenger instructions for this doc
    for _ in range(max_rounds):
        example = llm(challenger(prompt, document))    # draft: context + question + rubric
        w = [weak(example) for _ in range(n_rollouts)]     # weak solver, majority vote
        s = [strong(example) for _ in range(n_rollouts)]   # strong solver, majority vote
        verdict = llm(judge(example, w, s))            # score both, measure the gap, advise
        if verdict["strong_majority_correct"] and not verdict["weak_majority_correct"]:
            return example                             # accept: a learnable separation gap
        prompt = llm(revise(prompt, verdict["feedback"]))  # refine the challenger, retry
    return None                                        # budget exhausted, drop this document

Integration seams that matter:

  • Solver pair defines the difficulty axis. The gap is measured between a weak and a strong solver, so the pair is a design choice, not an incidental detail. The reported stack uses a small model as the weak solver (also the model later RL-trained) and a much larger model as the strong solver.1 Pick a pair whose gap is meaningful for your task.
  • Judge and orchestrator are LLM calls. They plug into the same inference serving path as any batch generation. The judge's rubric is versioned exactly like the synthetic-data quality judge.
  • Output is an ordinary dataset. Accepted examples flow into the same curation and training path as any synthetic corpus: dedup, decontaminate against your evals, then hand to SFT or RL. Nothing downstream of acceptance is special.
  • Meta-optimization needs a held-out split. The outer loop's acceptance test must score on documents the mutant did not train its prompts on, or it overfits the scaffold to the training papers. This is the same train/validation discipline as evaluation integrity.

How to run it in production

  • Budget the inner loop. Every accepted example costs multiple challenger drafts plus weak and strong solver rollouts (the reported CS average was 6.59 rounds per example). Cap rounds and rollouts per document and treat the accept rate as a cost metric, because low accept rates mean you are paying for many rejected drafts.
  • Cache and reuse solver rollouts. The strong solver on a large model is the expensive call; batch it through the inference engine and reuse rollouts across judge retries where the example text is unchanged.
  • Gate the dataset before training, not after. Acceptance is the inner gate; still run standard decontamination against every reported benchmark before the data reaches a trainer, since the challenger grounds on documents that may overlap an eval.
  • Log the full transcript. Meta-optimization's analyzer reads solver exchanges to diagnose failure patterns, so the per-example transcript (drafts, rollouts, verdicts) is a first-class artifact, not a debug log. Persist it.
  • Fix seeds and pin models. Solver majority votes depend on sampling; pin the solver models, temperatures, and rollout counts so an accept/reject decision is reproducible and a meta-optimization diff is attributable to the scaffold change, not sampling noise.

How to maintain it

  • Re-validate the scaffold when a solver changes. The accepted scaffold is tuned against a specific weak/strong pair. Swapping either model shifts the difficulty axis, so re-run meta-optimization rather than assuming the old prompts still separate the solvers.
  • Guard the acceptance gate. The gate is the entire safety of the method (case (a) and (d) in the model). Any refactor that weakens it, accepts on the generator's own confidence, or lets the judge score with a biased rubric, reintroduces collapse or bias propagation. Keep the numpy invariants as a regression check on the loop logic.
  • Audit accepted examples by hand. A high separation gap can still hide a spurious shortcut the weak solver misses and the strong solver exploits; sample accepted examples for answer leakage, rubric coverage, and reasoning-versus-recall, the same quality-verifier checks the paper's CS pipeline runs.1
  • Track the accept rate and the gap distribution over rounds. A falling accept rate or a narrowing gap is the early signal that the challenger has drifted or the solver pair has converged; it shows before downstream eval moves.
  • Treat the constants as version-specific. Round budgets, rollout counts, the Boltzmann temperature, and the acceptance thresholds come from one paper on one model stack. Re-tune per domain and scale.

Results

Reported on CS research examples (the domain with the fullest published tables); numbers are from the paper and were extracted from its arXiv HTML, so treat them as reported rather than independently reproduced.1

Data quality, one-shot chain-of-thought Self-Instruct baseline versus Agentic Self-Instruct:

Metric CoT Self-Instruct Agentic Self-Instruct
Weak-solver pass rate 0.677 0.458
Strong-solver pass rate 0.696 0.772
Separation gap (strong minus weak) 0.019 0.314
Avg refinement rounds per example 1.00 6.59

Downstream RL of a small (reported 4B) model, evaluated on the harder agentic-style test split (higher is better):

Training data mean@3 best@3
No RL (base) 0.366 0.484
RL on CoT Self-Instruct data 0.500 0.631
RL on Agentic Self-Instruct data 0.632 0.768

Meta-optimization of the CS agent reportedly raised the held-out validation pass rate (the fraction of generated pairs that separate the weak and strong solvers) from about 62.1% to 79.6% over 126 accepted mutations out of 233 proposed.1 The paper further reports that on legal reasoning the small model trained on Agentic data beat both the CoT-trained model and a much larger baseline on two independent graders, and that on math/scientific reasoning training on Agentic data gave the largest overall gain (reported +3.20 avg@8) over training on CoT data (+2.42) or the combined set (+2.70). These cross-domain figures are reported here at lower confidence; verify against the paper before quoting.

Failure modes

  • Gate removed becomes model collapse. Without the acceptance filter, the loop is recursive self-training: diversity erodes and quality stalls below the ceiling, exactly as case (a) in the runnable model and the Curse of Recursion result.2
  • Biased filter, biased dataset. Meta-optimization optimizes whatever the judge measures, so a judge with a wrong rubric or a shortcut it fails to penalize yields data that scores high under the filter and low in reality, as case (d) shows. The filter is the ceiling.
  • Spurious separation. A large weak/strong gap can come from a shortcut the strong solver exploits rather than genuine difficulty; hand-audit accepted examples for answer leakage and reasoning-versus-recall.
  • Solver-pair drift. The scaffold is tuned to one weak/strong pair; changing either model without re-optimizing can leave examples that no longer separate the solvers.
  • Cost blowup. Many rounds and rollouts per accepted example make this far more expensive per datum than one-shot synthetic generation; a low accept rate silently multiplies the bill.
  • Over-generalizing the evidence. Results are from one 2026 paper on three domains and a specific model stack; the exact gains and model choices are not established to transfer.

References

  • Autodata: An agentic data scientist to create high quality synthetic data (Kulikov et al., 2026): https://arxiv.org/abs/2606.25996
  • Self-Instruct (the one-shot instruction-synthesis baseline this iterates on): https://arxiv.org/abs/2212.10560
  • The Curse of Recursion / model collapse (why the acceptance gate is load-bearing): https://arxiv.org/abs/2305.17493

Related: Synthetic data generation · Training-data curation · Rejection sampling and best-of-N · Reasoning distillation (SFT) · Reward design for RL post-training · Evaluation integrity · Fine-tuning and post-training · Glossary


  1. Kulikov, Whitehouse, Wu, Nie, Saha, Helenowski, Yuan, Golovneva, Lanchantin, Bachrach, Foerster, Li, Fang, Sukhbaatar, Weston, "Autodata: An agentic data scientist to create high quality synthetic data" (arXiv 2606.25996, v1 2026-06-24, v3 2026-07-04). Agentic Self-Instruct orchestrates a challenger, weak solver, strong solver, and judge; the acceptance rule for verifiable tasks is "majority vote over the strong solver is correct, while majority vote over the weak solver is wrong". Meta-optimization selects a parent scaffold by Boltzmann sampling, has an analyzer LLM diagnose failure patterns and a code-editing LLM mutate the prompts, and keeps a mutant only if it beats the parent on held-out validation. Reported CS figures: separation gap 0.019 (CoT) vs 0.314 (Agentic); downstream RL on the agentic test split 0.366 (no RL) / 0.500 (RL on CoT) / 0.632 (RL on Agentic) mean@3; meta-optimization validation pass rate ~62.1% to 79.6% over 126 accepted of 233. Numbers extracted from the arXiv HTML; reported here, not independently reproduced. https://arxiv.org/abs/2606.25996 

  2. Shumailov et al., The Curse of Recursion: training on unfiltered model-generated content causes irreversible model collapse as the tails of the distribution disappear; the analog of removing Autodata's acceptance gate. https://arxiv.org/abs/2305.17493