Anti-trend Insurance (v0.68.0)

Five-bet hedge against paradigm shifts. If prompt engineering keeps winning, soup compile keeps you covered. If prompt costs wall out, soup distill-prompt migrates the system prompt into weights. soup compile-tools does the same trick for tool descriptions. soup apple-adapter converts HF and MLX adapters, with Apple FoundationModels (iOS 26+) directions that are accepted but refuse until Apple publishes the adapter format. And soup local-rl builds a personal-LLM feedback flywheel from thumbs-up/down on your own machine.

soup compile — DSPy + GEPA + TextGrad prompt-program compiler

bash
soup compile ./program.py --eval ./examples.jsonl \
  --optimizer mipro --max-iters 500 --output ./compiled_program.py

Closed optimiser allowlist of 5: mipro, gepa, textgrad, copro, bootstrap_fewshot. Hard bound MAX_COMPILE_ITERS=1000.

--eval is a JSON or JSONL file of example objects: a JSON array, a single object, or one object per line. YAML is not parsed. The plan step only checks that the path stays under the working directory, so --plan-only prints a clean plan for a .yaml suite (or for a file that does not exist), and the loader then keeps only lines that parse as JSON objects, which gives zero examples from a YAML file. The output is a compiled Python program (--output, default compiled_program.py), not JSON.

  • validate_program_path requires .py, cwd containment, os.lstat + S_ISLNK rejection via shared paths.enforce_under_cwd_and_no_symlink
  • CompileResult enforces finite-score (no NaN / ±Inf), iterations ≥ 0
  • Module is commands/compile_cmd.py because compile is a Python builtin
  • Live as of v0.71.13 (behind the [compile] extra) — run_compile dispatches DSPy / GEPA / TextGrad; soup compile-tools optimises tool schemas too; --plan-only still renders the plan and exits 0

soup distill-prompt — long prompts → small fine-tunes

bash
soup distill-prompt --traces ./traces.jsonl \
  --teacher llama3.1:70b --student llama3.2:3b \
  --strategy sft --output ./dataset.jsonl

--teacher and --student are model names in the provider you choose, not Hugging Face repo ids: --provider is ollama by default (or vllm or anthropic, with --base-url for the first two), so the example uses Ollama tags. The teacher is called once per trace; the student is called only for the preference strategy, where it supplies the rejected side. A call that fails or returns empty text is skipped without a message, so a wrong model name finishes with Rows: 0, exit 0 and an empty output file. Read the row count in the summary.

--temperature sets the sampling temperature of the teacher and, under preference, the student (default 0.0, allowed 0 to 2, otherwise exit 2). --max-rows N stops once N rows have been written (a positive integer; by default every trace is used). It counts rows written, not model calls, so a prompt that gets no usable reply still costs a call. --plan-only prints the plan and calls no model.

Closed strategy set {sft, preference, kl}. Teacher / student IDs capped at 512 chars; null-byte + empty rejected. Composes with v0.71 cross-tokenizer ULD. Live as of v0.71.13 (behind the [compile] extra) — prepare_distill_dataset calls the teacher once per trace via the v0.20 providers and emits sft/kl messages or preference chosen/rejected pairs.

soup compile-tools — textual gradients over tool descriptions

bash
soup compile-tools ./spec.json --eval ./eval.jsonl --optimizer textgrad

Spec extension allowlist {.json, .yaml, .yml} (case-insensitive on Windows). TextGrad / GEPA optimise tool descriptions so model picks them more accurately. Composes with v0.46 Agent Forge — Agent Forge parses the spec, ToolCompile optimises descriptions.

soup apple-adapter — HF ↔ MLX ↔ Apple FoundationModels

bash
soup apple-adapter ./adapter \
  --direction hf-to-mlx --output ./mlx-adapter --sign

Closed direction allowlist of 4: hf-to-mlx, mlx-to-hf, hf-to-apple, mlx-to-apple. Explicit S_ISDIR + symlink rejection on source. sign must be real bool (bool-as-int defence). Reuses v0.60 Part B signing infrastructure and extends the v0.25 MLX backend. Live as of v0.71.21 — convert_apple_adapter does the PEFT ↔ mlx-lm round trip for hf-to-mlx / mlx-to-hf (real bf16 adapter smoke); the *-to-apple directions stay upstream-gated until Apple's on-device adapter spec settles: they are accepted by the allowlist and then refuse with exit 3 ("no stable public spec yet"), so only hf-to-mlx and mlx-to-hf convert anything.

--sign here uses the unsigned backend: it writes .soup-signature.json with a Merkle root over the output files, so it detects later tampering but carries no ed25519 signature and says nothing about who produced the files, even though the summary prints "Signed: True". For a real signature run soup adapters sign ./mlx-adapter --backend ed25519 --key <key.pem> on the output.

soup local-rl — personal-LLM feedback flywheel (LIVE)

bash
# The DB path must stay under the current working directory: a ~ path is refused.
soup local-rl init --db ./local.db
soup local-rl record --db ./local.db \
  --prompt "summarise my last commit" --response "..." --thumb up
# or let the served model fill the same DB for you:
soup serve --model ./output --record-thumbs ./local.db
soup local-rl status --db ./local.db
soup local-rl harvest --db ./local.db --output ./pairs.jsonl

soup train --config dpo.yaml   # point data.train at ./pairs.jsonl
  • You do not have to call record by hand. soup serve --record-thumbs <db> turns on POST /v1/thumbs and writes into this same database, which is the edge that closes the loop: serve, collect, harvest, train. Path contained under cwd, transformers backend only.
  • POSIX 0o600 SQLite, interactions + thumbs tables (idempotent CREATE TABLE IF NOT EXISTS)
  • Parameterised inserts, 16 KiB prompt + 16 KiB response caps, null-byte rejection
  • harvest walks thumbs by ts ASC, emits one DpoPair{prompt, chosen, rejected} per prompt with both an up and down (last-writes-win dedup), atomic JSONL write via tempfile.mkstemp + os.replace
  • soup local-rl train is live as of v0.71.13 — --once harvests the latest thumbs DPO pairs and runs a real nightly DPO/KTO/ORPO train via a soup train subprocess (a state table skips re-runs with no new feedback or fewer than --min-pairs); without --once it renders an injection-safe systemd .service/.timer + launchd .plist scheduler scaffold

Numbers

+204 tests in v0.68.0 (11,021 → 11,225) across 6 new test files. 2 POSIX-only symlink tests skip on Windows.

See also

Soup is free and Apache-2.0. If it saved you a training run, starring the repo costs nothing and helps most. You can also fund the GPU time behind the work a 4 GB laptop cannot reach.