fix(server): echo persisted recipe_options in the /load response#1706
fix(server): echo persisted recipe_options in the /load response#1706ianbmacdonald wants to merge 2 commits into
Conversation
|
Please ensure tests are passing and then request review from @bitgamma |
7376f63 to
81dd2bd
Compare
|
Rebased onto current Core fix (unchanged intent): load-time resolution re-reads Thread safety: the load-time refresh reads the file into a local (it does not mutate the shared
Tests: added regressions for auto-load recall, |
81dd2bd to
b3adcfb
Compare
|
Rebased onto current The only textual conflict was an import block in Re-validated on real hardware (RX 7900 XT, llama.cpp/vulkan): the |
|
except if modifying the |
b3adcfb to
61fe7f5
Compare
|
@bitgamma you're right that the out-of-band
I've also narrowed the PR accordingly: the load-time disk-refresh from the earlier revision is gone, since — as you note — it only affected manual edits, and |
this behavior is intentional, sending recipe options means sending all of them. Otherwise there is no way to unset them |
A save_options:true load *replaces* the model's stored recipe options with the set sent in the request (omitting a key clears it). That contract is documented but invisible to a client driving the API programmatically: the /load response reports a bare success, so a caller incrementally tuning a recipe cannot tell that an option it did not resend was dropped without a follow-up /models query or reading the API docs. Echo the model's persisted recipe_options in the /load success response (both the per-model and collection branches), mirroring the existing /models representation (model_info_to_json already serializes recipe_options.to_json()). The replace-on-save semantics are unchanged; the resulting persisted state is simply made observable. The echoed set is the persisted one (== /models), not the per-request effective options; this is documented in docs/api/lemonade.md. Adds test_012m_load_response_echoes_recipe_options: asserts the response carries recipe_options and that a subsequent partial save surfaces the cleared key's absence. Validated on ai3 (RX 7900 XT, llamacpp/vulkan): recipe-options suite 6/6 pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-Authored-By: glm-5.2 <noreply@zhipuai.cn> Co-Authored-By: gpt-5.5 <noreply@openai.com>
61fe7f5 to
5e58f75
Compare
|
Thanks @bitgamma — that's a fair call, and you're right. The replace-on-save behavior is documented ( I do think there's a narrower, real problem underneath, and it's not "merge vs replace" — it's discoverability. A client driving the API programmatically (e.g. an agent that loads a model, measures, adjusts an option, and saves to iterate on a recipe) has no way to learn the replace-all rule except by reading that one line in the docs. The API itself never surfaces it: So I've re-pointed this PR away from changing the default and toward making the contract observable: Echo the model's persisted The echoed field is the persisted set (== The diff is down to Does that direction work for you? |
|
I don't think returning persisted options on |
You are talking about a human or an agent? That is a lot of wasted context if you are letting a Qwen3.6 model on a strix halo orchestrate lemonade. the original use case was a project for a client, where crewai (a long time ago) was persisting its own recipes (before we made recipes better) and just could not get it right.. stuck in the trap of trying new options and getting inconsistent results. The PR grew a bit (my fault) but its right back at the narrow scope that makes agents burn calories or fail at optimizing lemonade recipes on their own. |
What & why
save_options: trueonPOST /loadreplaces the model's stored recipe options with the set sent in the request (omitting a key clears it). As @bitgamma pointed out, that replace-on-save behavior is intentional — sending recipe options means sending the complete set, which is how a caller unsets one. Agreed; this PR no longer touches those semantics.The problem it addresses is narrower: that contract is invisible to a client driving the API programmatically. The
/loadresponse reports a baresuccess, so an agent iteratively tuning a recipe (load → measure → adjust → save) can't tell that an option it didn't resend was just dropped — short of a follow-upGET /modelsor reading the API docs.Change
Echo the model's persisted
recipe_optionsin the/loadsuccess response, mirroring whatGET /modelsalready returns (model_info_to_jsonserializes the samerecipe_options.to_json()). Replace-on-save semantics are unchanged — the resulting persisted state is simply made observable.The echoed set is the persisted one (identical to
/models), not the per-request effective options: runtime options sent withoutsave_optionsare applied for that load but are not reflected here. This is documented explicitly.src/cpp/server/server.cpp— echorecipe_optionsin both the per-model and collection/loadbranches (uniform response shape)test/server_endpoints.py—test_012m: asserts the response carriesrecipe_optionsand that a subsequent partial save surfaces the cleared key's absence (real RED→GREEN teeth — an unpatched server returns norecipe_options)docs/api/lemonade.md— corrected the stale/loadresponse example and documented the field + persisted-vs-effective semanticsValidation
Recipe-options endpoint suite 6/6 (
test_010/011/012/012a/012b/012m) on real hardware:.deb), Ubuntu 26.04, kernel 7.0.0-22-genericllamacpp/vulkanRED→GREEN confirmed against a live server: pre-patch
/loadreturns{status, model_name, checkpoint, recipe}(norecipe_options); post-patch it includesrecipe_options, and a partial save visibly drops the omitted key.Note: this supersedes the earlier merge-on-partial-save approach (which would have changed the replace-on-save contract). The branch was reset to a single clean commit on top of
main.