Skip to content

feat(recursive-loop): serve trained mlxlm/opd adapters via the scenario-bound resolver#1060

Merged
jayscambler merged 3 commits into
mainfrom
feat/recursive-loop-bridge
Jun 9, 2026
Merged

feat(recursive-loop): serve trained mlxlm/opd adapters via the scenario-bound resolver#1060
jayscambler merged 3 commits into
mainfrom
feat/recursive-loop-bridge

Conversation

@jayscambler

Copy link
Copy Markdown
Contributor

What

Closes the last wiring step of the recursive loop for capable models. Until now the loop could only auto-serve from-scratch mlx GPT checkpoints (#1055). This routes an active mlxlm/opd LoRA adapter the harness trained and auto-activated back into the next run's agent role via MLXLMClient (#1058's serving capability).

The two halves wired here:

  1. Publish carries what serving needs. runner._publish_best_model now records base_model and score_conditioned on the published registry record (through completion.metadata). An adapter checkpoint is useless without the base it was trained against, and a score-conditioned model must be re-prompted with the quality prefix at inference.

  2. The resolver routes by backend. scenario_bound_clients gains a pure plan_local_client(record) decision:

    • mlx full checkpoint → MLXClient(checkpoint) (unchanged)
    • mlxlm / opd adapter → MLXLMClient(base, adapter_path, score_conditioned=...)
    • adapter missing its base_model, or unknown backend → None (fall back to the default client rather than serve something broken)

    The active record is resolved capable-backend-first (opdmlxlmmlx), so an instruct fine-tune wins over the from-scratch GPT when both are active for a scenario. An explicit AUTOCONTEXT_MLX_MODEL_PATH still takes precedence.

Tests

CI-safe (JSON-file registry, no MLX load, no training): the routing decision for each backend, the score-conditioned flag carry-through, the unservable-adapter / unknown-backend fallbacks, the resolution preference, and a publish_training_outputplan_local_client round-trip that mirrors exactly what the runner wires up.

Full local sweep green: new bridge suite + test_mlx_agent_resolution + test_scenario_routing + test_model_registry + test_model_router_integration + test_module_size_limits; ruff + mypy clean.

…io-bound resolver

Closes the last wiring step of the recursive loop for capable models. A run can
now use the instruct fine-tune (mlxlm/opd LoRA adapter) a prior run trained and
auto-activated, not just from-scratch mlx GPT checkpoints.

- runner: record base_model + score_conditioned on the published registry record
  (via completion.metadata) so an adapter checkpoint carries the base it needs and
  the quality-prefix flag for score-conditioned serving.
- scenario_bound_clients: add a pure plan_local_client() routing decision (mlx full
  checkpoint -> MLXClient; mlxlm/opd adapter -> MLXLMClient(base, adapter,
  score_conditioned=...)), resolve the active record capable-backend-first, and build
  the planned client. Adapter records missing a base_model fall back rather than crash.
- tests: CI-safe (JSON registry, no MLX load) coverage of the routing decision, the
  resolution preference, and the publish -> route round-trip.
…ner path (review #1060)

[P1] adapter backends (mlxlm/opd/grpo) advertised runtime_types=["checkpoint"] only,
but the resolver queries runtime_type="provider" -> every runner-published adapter fell
back instead of serving. Now that MLXLMClient serves base+adapter, these advertise
["provider", "checkpoint"]. The prior tests used ["provider"] directly and masked it;
they now drive runtime_types from the backend's own supported_runtime_types().

[P2] a default 'autoctx train --backend mlxlm/opd' leaves config.base_model empty (the
subprocess applies the backend default), so the recorded base was empty -> unservable.
Backends now expose default_base_model() (the effective student/base), and the runner
records 'config.base_model or backend.default_base_model()'.

Also extend the bridge to grpo (mlx-lm LoRA, same serving path) so advertising provider
on it is truthful; update the stale opd runtime-types assertion to the corrected contract;
add regression tests for both findings + a full default-CLI publish->resolve->route case.
…ree module (CI)

default_base_model() lazy-imported the autoresearch backend modules, which import mlx at
module top -> ModuleNotFoundError on the no-mlx CI runner (passed locally on Mac). Extract
the default base/student/teacher model ids to a dependency-free model_defaults.py and source
them from there in both backends.py (now mlx-free) and the autoresearch modules (re-export,
back-compat). Single source of truth: the base the runner records == the base the training
subprocess trains against. Verified default_base_model() imports no mlx-scoped module.
@jayscambler jayscambler merged commit 05ca854 into main Jun 9, 2026
17 checks passed
@jayscambler jayscambler deleted the feat/recursive-loop-bridge branch June 9, 2026 19:10
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.

1 participant