Skip to content

Integrate v0.10 Optimizers 0.2.22 with preserved production history - #55

Merged
JoshuaPurtell merged 56 commits into
devfrom
codex/v010-optimizers-integrated
Sep 9, 2026
Merged

JoshuaPurtell merged 56 commits into
devfrom
codex/v010-optimizers-integrated

Conversation

@JoshuaPurtell

Copy link
Copy Markdown
Contributor

Summary

Supersedes the inherited-stack review in #54. This branch starts from the existing production 0.2.16 baseline (already descended from dev) and ports v0.10 into nine explicit implementation groups, plus CI and whitespace-only follow-ups. It preserves the production changelog rather than overwriting main.

Integration groups

  1. Event contracts and admitted config authority.
  2. OpenRouter GEPA execution and conservative cost accounting.
  3. Standalone SFT/CISPO/checkpoints.
  4. GEPA container preflight.
  5. Container-first RL lifecycle and retained evidence.
  6. Runtime conformance/recovery adapters.
  7. Durable checkpoint authority, including the source merge resolution.
  8. Release test gates and crash recovery.
  9. Stable packaging, eval isolation and final GEPA corrections.

Verification

At 6097962, all files match candidate fe571fe exactly except the restored five-line production changelog. Subsequent changes allow build-only CI on this branch and normalize six inherited documentation files; runtime, tests and dependency bytes remain unchanged. Full branch diff whitespace check passes.

Candidate CI passed, including fresh public PyPI dependency installation on macOS arm64 and Linux x86_64: https://github.com/synth-laboratories/optimizers/actions/runs/34413873850 . Integrated-head validation must finish before merge; the new push runs the same suite and both wheel builds.

Boundaries and next promotion

  • Base is dev. This PR does not deploy production or publish PyPI. Promote dev to staging to main after exact gates.
  • TBLite is eval-only, not a production dependency.
  • Live provider-backed E2E acceptance remains explicitly deferred by the release owner; offline evidence is not live acceptance.
  • The 228-signature type-debt baseline stays explicit.
  • Primary-worktree uncommitted MAPO/research changes and the duplicate cache patch were not imported.
  • Cookbook PR 15 is separate; its hosted CI is disabled pending release-owner authorization. Workshop must re-pin after package publication.
  • No stable 0.2.22 tag or publication has been created.

JoshuaPurtell and others added 30 commits August 14, 2026 15:40
…se-workflow-main-20260814

Add trusted PyPI release workflow
release: promote optimizers 0.2.13
release: promote optimizers 0.2.14
…ncile

release: reconcile and promote v0.5 to main
Baseline at this commit: 51 passed. Records the audited blockers (ModelRoute
https-only, SftConfig backend enum, missing mlx-lora policy kind) and the
containers pin coupling.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four narrow changes let `eval` measure a locally-served policy without giving
a candidate any new say in where inference goes.

O1 `ModelRoute` accepts a local route. `https://` stays unrestricted; `http://`
is permitted only for a host that cannot be anywhere but this machine
(127.0.0.1, localhost, ::1, host.docker.internal). Parsed with `urlsplit`,
mirroring `_validate_remote_checkpoint_endpoint` in the containers Banking77
runtime but without its environment-variable allowlist — the recipe catalog is
already the allowlist, and a second one read from the environment would be an
escape hatch around it. Userinfo, query strings, and fragments are refused, and
the path must name one of the two first-class API families. Private ranges stay
refused: "it is on my LAN" is not the claim "it cannot leave this machine".

O2 needed no schema change. 0.0 was already a legal rate, so a zero-rate route
with `price_source = "local-compute"` and a local bearer token in `secret` is
expressible today; the tests pin that shape, and that `budget.max_llm_calls`
remains the ceiling that actually bounds a run once `max_usd` is vacuous.

O3 adds the `mlx-lora.v1` policy kind. The adapter bytes live in the candidate,
so `artifact_digest` is the digest of the adapter and base-vs-checkpoint-vs-final
is one content-addressed set scored on shared seeds. `policy.json` declares
`adapter` explicitly rather than letting it be inferred from which files are
present, because a checkpoint whose adapter failed to copy would otherwise be
scored as its own baseline and report a lift of zero.

O4 registers the candidate with a snapshot registrar before each trial and
writes the returned id into `trial.json`. The registrar is injected and has no
default: a run with adapter candidates and no registrar fails before it starts a
container, and a run without one never opens a socket. An id that drifts inside
one run is refused — that means the service handed back a mutable name.

O5 adds `eval.mlx.local-policy.smoke.v1` for GSM8K. `network = "bridge"` is
mandatory (a `none` network cannot reach a host proxy) and `required_artifacts`
is `["trace"]`. No `image_digest` is set, so the recipe is honestly unavailable
until the target is published and pinned rather than carrying a fake one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The pinned rev's banking77.py (346 lines) has no _sample_responses and no
_validate_responses_endpoint; containers main (a2a316b) has both. D9 does not
hold on this side until the pin is bumped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous pin predated the Banking77 responses lane entirely, so D9's
responses-first-class claim could not hold on this side. No API drift: 88 passed
before and after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Was pinned to a2a316b2, which predates both the hosted rollout contract and the
MLX lane — `synth_containers.platform.local_provider` does not exist there, so
the pin disagreed with the local-MLX eval this branch ships.

Repinned to 5453731dabc078fc4aae700015f7ecd2ae95a969, the immutable SHA of
containers v0.6/implementation on origin, and relocked. Verified by a
--no-cache install into a fresh venv resolving from origin alone: the module
imports and host.docker.internal is still outside the blanket-loopback set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
release: land v0.6 local MLX optimizer lane
An optimizer run an experiment dispatched has to be findable from the
experiment side, and the arm a rollout belongs to has to be readable from
the rollout side. One small record carried unchanged through the run
request, the resolved config, the manifests, and the run registry makes
both directions work without any of those growing a private notion of
what an arm is.

Two deliberate omissions in the envelope.

There is no run id. The service still mints its own and reports it back;
a caller supplying one would be asserting authority over a namespace it
does not own, and resume would then have two candidate truths about which
run a trial is. deny_unknown_fields refuses one that tries.

There is no digest. The producer computes one from its own canonical
encoding and compares it against the envelope it gets back. Recomputing
it here would mean two languages agreeing byte for byte on JSON
canonicalisation forever, for a field nothing on this side reads.

Optional fields are skipped rather than nulled, because the envelope has
to survive being written into an executor's own config format and read
back byte-identical, and TOML has no null.

Surfaces:

  POST /runs        correlation, validated before the contract handshake,
                    while no run record exists yet
  RunConfig         carried, never read
  GET /runs/{id}    echoed, next to the run id the service minted
  result_manifest   success and failure alike -- a failed trial still has
                    to join to its arm
  run_registry      so "every run belonging to trial X" needs no second
                    index

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…dy exist

An ablation is only worth anything when the only thing that differed is
the thing you meant to change. This is the thin layer that makes that
guarantee cheap: you declare what varies, and it proves nothing else did.

It does three things -- assign, adapt, reduce -- and refuses the fourth.
eval, the matrix runner, and GEPA already own execution, queueing, and
evidence; the moment this layer owns them too there are two stories about
what ran.

A spec says what varies and what must not. It never says how to run
anything: the image, the seed schedule, the metrics, and the ceilings all
come from the executor's own trusted configuration, and the spec may only
select among what is already declared there. That asymmetry is what makes
an ablation cheap to write and still impossible to accidentally turn into
an unfair comparison.

Three rules do most of the reducer's work:

  a failed trial is missing evidence, never a zero;
  missingness is harmless only when symmetric, so it is measured per arm
    and the difference gates the claim;
  the order trials actually ran in is evidence, and nominal
    counterbalancing is not.

headline_claim_allowed needs the declared sample, the confidence
interval, completion, missingness, image identity, isolation, and start
order all to pass. An interval containing zero closes the gate rather
than annotating it: a null result is a real finding the report states
plainly, but it cannot carry a directional headline.

Two adapters ship because their executors do.

eval.runtime drives one eval run per cell of the matrix, which buys an
exact dispatch order to counterbalance and one separately-resumable
sealed receipt per trial -- neither of which a single batched run gives.
Every override it sends narrows the trusted recipe.

gepa.cli renders one config per trial through the ordinary GepaRun path.
Its factor catalog is curated rather than reflected: GEPA's TOML sections
are extra="ignore", so a schema walk would offer knobs the engine
silently drops. max_generations, minibatch_size, and the budget caps are
excluded on purpose -- varying how much work an arm does voids any
wall-clock or cost comparison between arms.

evals.matrix and gepa.service register from their own repositories via
register_adapter, so this layer takes no dependency on every executor it
can drive.

Also cuts 0.2.15, so Workshop can pin an Eval runtime carrying the
2ed30aa admission-readiness hardening.

Verified against the real eval-fixture-target container: A/A over three
blocks with a delta of exactly zero (no cache, container, or seed leak),
then a two-arm four-block comparison walked end to end from outcome row
through sealed correlation, narrowed seeds, the trial manifest the
container read, job_result, and the digested trace artifact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nder

`experiment plan` printed "image UNPINNED" for every executor, because it
looked for an image digest that only the eval runtime records. gepa.cli
pins a base config digest, a taskset digest, and a task-pools digest
instead. Show what the adapter actually recorded rather than one
executor's vocabulary applied to all of them.

Also pins, in config.rs, the exact TOML that gepa_cli renders into a trial
config. RunConfig is deny_unknown_fields, so that parse is where a
correlation envelope would be rejected -- before a container starts and
long before anyone notices a manifest that cannot be joined to an arm.
TOML has no null, so the test also asserts the optionals arrive absent
rather than empty; a `candidate_id = null` would not have parsed at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… names

Checked the adapter against a real sealed manifest
(banking77_gepa_async_t50_mb20_h100_735a9c29) rather than the fake one the
tests build, and found two things the fake had hidden.

GEPA's usage block reports `rollout_calls`, not `rollouts`. A spec asking
for `rollouts` as a secondary metric produced an empty comparison with no
error anywhere -- which is the same silent-reduction failure the layer
exists to prevent, one level up. Lifting every numeric scalar that
`best_candidate` and `usage` actually carry removes the whole class.

A successful run's manifest also has no `status` key at all; it is the
serialised GepaRunResult. Only failure manifests carry status. The seal
already fell through to the right branch, but by luck, so the real shape
is now pinned in a test.

Resource metrics (tokens, calls) join cost and wall time as improving
downward. For a reasoning-effort ablation "the harder-thinking arm spent
more" is the expected finding, not a regression -- the direction only
fixes the sign convention.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A sealed failure was skipped on resume forever, so one crashed container
five hours into a twenty-trial matrix permanently poisoned that block and,
under missing_policy = "fail", blocked the claim outright. Restarting the
whole experiment was the only remedy.

`resume --retry-rig-failures` re-dispatches trials whose failure was the
rig's. Only `rig` and `infra` qualify: a `policy` failure is the thing
under test, and a `budget` or `timeout` failure may itself *be* the arm
difference, so retrying either would be selecting for the result you
wanted.

Nothing is hidden. A retry appends a row carrying `attempt = N+1` and the
digest of the row it supersedes; the superseded rows stay in the log,
`retried_trials` lands in the report totals, and the claim verdict names
them in a note. A rig that needed three attempts is a fact about the
comparison, not housekeeping. Two rows for the same attempt are still a
contradiction rather than a retry.

Each retry runs under its own executor identity (`…_r1`). Found by test:
the first implementation re-dispatched correctly and still completed zero
trials, because the eval runner treats an existing job_result.json as
terminal and replayed the very failure the retry existed to clear. Every
executor here resumes its own work from a sealed record, so the attempt
number has to reach the adapter and change the run identity.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Serial dispatch made the planned order the executed order, and made a
twenty-trial matrix a sixty-five minute wait. `[execution]
max_parallel_trials` fans out while still submitting in planned order.

For a *paired* design this is arguably better than serial rather than a
compromise: both arms of a block now meet the same machine in the same
minute, which removes temporal drift instead of adding it. What it does
add is contention, which is why the reducer keeps computing fairness from
recorded dispatch and start times rather than trusting the setting -- the
run this landed for observed a start-order bias of 0.021 against a
declared limit of 0.25.

The knob is declared in the spec and carried in the plan, so a report can
compare intent against what happened. Serial stays the default.

A port per trial is the load-bearing half. A base config names one
container socket, so concurrent trials would have fought over it -- and a
collision would not have looked like an error, it would have looked like
one arm scoring the other arm's rollouts. Ports derive from the trial id,
are asserted distinct across the matrix, and `container.url` joins the
reserved paths a spec cannot make a treatment. The launch command's port
literal is rewritten too, since moving only the URL leaves the trial
talking to nothing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JoshuaPurtell and others added 26 commits August 20, 2026 13:15
feat: add experiment layer and GEPA correlation
drop_does_not_delete_heartbeat_owned_by_another_pid overwrote the
heartbeat with pid=1 while the guard's heartbeat-writer thread was still
live. start_owned_guard writes the heartbeat synchronously and then spawns
a thread whose first iteration writes it again (tmp + rename), so that
rename could land after the test's overwrite and restore the real PID;
Drop then saw its own PID, deleted the file, and the assertion failed
(4/30 under the default parallel runner on d3c9edd).

Setting the stop flag first was not enough (3/20 still failed): a write
already past the flag check still lands. The guard now retains the
writer's JoinHandle and the test-only stop_heartbeat_writer joins it, so
no write can follow it. Production Drop detaches the handle exactly as
before and never joins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…a route that does not exist

The 'Make the repository-linked target public' step called
PATCH /orgs/synth-laboratories/packages/container/workshop-craftax-eval-target,
which GitHub's REST API does not expose (packages support get/list/delete/
restore only; visibility is a package-settings action). It 404ed on runs
32094428767 and 32098386835 after the push itself succeeded, failing the
job before the receipt artifact was uploaded.

Upload the receipt first, then verify the package is anonymously
pullable via the ghcr.io token endpoint and a manifest GET on the
published digest. If it is private, fail with the exact one-time
settings action an org owner must take.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-visibility-step

ci(craftax-target): verify anonymous pullability instead of PATCHing a nonexistent route
…hip-flake

test(gepa): stop the heartbeat writer before the foreign-pid drop check
docker/gsm8k-eval-target: an eval.target.v1 container for
eval.mlx.local-policy.smoke.v1. stage.py vendors gsm8k_world.py from a pinned
containers commit and bakes openai/gsm8k @ 740312add88f781978c0658806c59bc2815b9866
into the context through write_snapshot(), which refuses rows that do not
reproduce the recorded split digests. target.py samples the host's
synth-mlx-rl over the recipe-owned route pinned to the trial's
policy_snapshot_id, verifies the served snapshot, scores exact match with
containers' parser, and writes parse_mode into the trace and evidence.

.github/workflows/publish-gsm8k-eval-target.yml mirrors the Craftax publish
workflow (multi-arch push by digest, receipt artifact); the make-public step
is continue-on-error because it is known to 404 on this org.

Tests: tests/test_gsm8k_eval_target.py (11) — rig vs policy failure, snapshot
pin, route/secret refusal, trace parse mode.

Base: origin/v0.7 279eaf5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
workflow_dispatch resolves a workflow by file name on the default branch, so
the file has to exist here before `gh workflow run … --ref v07/l2-gsm8k-catalog`
can run the lane branch's copy (which carries docker/gsm8k-eval-target/).
Same shape as publish-craftax-eval-target.yml; the make-public step is
continue-on-error because it is known to 404 on this org.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…orkflow

ci: GSM8K eval-target publish workflow on the default branch (dispatch prerequisite)
…t (catalog only)

image_digest = sha256:1954fb48382590744643a6716a897234eed8a65899297d72a1313e54c7c7ab5d,
recorded by publish-gsm8k-eval-target run 32403202526 (tag
v0.7-gsm8k-740312ad-containers-9916cd74, linux/amd64 + linux/arm64; the
workflow's receipt artifact is kept under docs/receipts/). No version bump:
the pin ships in the next package cut. Until then the operator path is
`synth-optimizers eval pin --home … --recipe … --digest …` (home.py:write_pin),
documented in docs/local-eval.md with fresh-home `eval doctor` receipts.

Also: stage.py registers the vendored module in sys.modules before exec
(dataclasses under `from __future__ import annotations`), and a local-trial
receipt of the built image against a real synth-mlx-rl.

Known: the GHCR package is not publicly pullable yet (make-public step
"succeeds" without effect, as for the Craftax target); visibility is an org
setting.

Base: origin/v0.7 279eaf5

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
eval: GSM8K eval target + publish workflow; pin eval.mlx.local-policy.smoke.v1 by digest (L2, catalog-only)
…-main

release(v0.7): reconcile main before promotion
release(v0.7): promote v0.7 to main
Port c187e28 onto the production 0.2.16 baseline; preserve its release history and official Containers dependency. Later v0.10 groups supply final validation and packaging.
Port 1e4dfb0 through eaead59 as one provider-execution group. Preserve causal failures, opt-in models, claim races and stateful replay boundaries. Paid validation remains deferred.
Port 842615e through a8913aa: scoped sampler identities, bounded rollout concurrency, paired evaluation and explicit idempotency. Optional eval isolation arrives in the final packaging group; this intermediate commit is not a release.
Port first-parent 96d7bba..128d83f: queueing, conformance, replay, budget accounting, heldout and recovery evidence. No experiments executed. Intermediate whitespace findings remain identical to the source and are addressed by the later stabilization group.
Port 9de4740; resolve the obsolete main Containers pin in favor of the source candidate pin. Final packaging will replace development dependencies. Only changelog history differs from the source checkpoint.
Port cfebb60, merge delta 31e3ca1 against its first parent, and final 0.2.21 pins. Preserve merge-resolution implementation rather than dropping it via a no-merges replay.
Port 19ecb4b through dcf7e8f; preserve both September candidate notes and the production August 21 release record.
Port 7dc4ef2 through fe571fe. Runtime, dependency, tests and workflow bytes match the preserved candidate; retain production 0.2.16 changelog history. TBLite remains eval-only; live provider E2E stays deferred.
@JoshuaPurtell
JoshuaPurtell merged commit 0e3d66e into dev Sep 9, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants