--- name: omp-per-model-sampler-config description: "Set per-model sampler params and preserve_thinking in OMP's models.yml via compat.extraBody, and verify the exact wire body with an echo-capture profile." --- # OMP Per-Model Sampler Config (`compat.extraBody`) `~/.omp/agent/models.yml` has **no per-model sampler keys**. Sampling is global (`config.yml`: `temperature`, `topP`, `topK`, `minP`, `presencePenalty`, `repetitionPenalty`; default `-1` = **not sent**). The per-model hook is `compat.extraBody`. ## Why it works `applyOpenAIExtraBody` runs **last** in the openai-completions builder — after `applyChatCompletionsCompatPolicy` — via `Object.assign(params, extraBody)`. So extraBody overrides global settings and compat-policy output. ## Recipe Use **wire names**, not OMP camelCase: ```yaml - id: frank-large compat: reasoningEffortMap: max: xhigh extraBody: temperature: 1.0 top_p: 0.95 top_k: 20 min_p: 0.0 presence_penalty: 0.0 repetition_penalty: 1.0 preserve_thinking: true ``` ## Two traps 1. **`preserve_thinking` is not user-settable.** `qwenPreserveThinking` is auto-derived in `buildOpenAICompat` from the *auto-detected* `thinkingFormat` + local baseUrl, **before** `applyCompatOverrides`. Setting `compat.thinkingFormat: qwen` in models.yml does **not** enable it. Custom-named models (`frank-large`) match no Qwen id pattern → use `extraBody: {preserve_thinking: true}`. 2. **Never put `chat_template_kwargs` in `extraBody`.** `Object.assign` is shallow — it replaces the whole object and silently drops the `reasoning_effort` kwarg the compat policy set. Top-level `preserve_thinking` is honoured by halogen; use that. ## Gotcha: silent greedy decode halogen-flash-server has **no server-side sampler defaults** — `temperature` absent or `0` = greedy. If OMP sends nothing, the endpoint decodes greedy while looking configured. ## Verify the exact wire body (echo-capture) Don't trust the YAML — capture what OMP actually emits. 1. Echo server on `127.0.0.1:18099` that dumps the JSON body to a file and returns a canned `chat.completion` (include `reasoning_content` so thinking paths exercise). 2. Copy the **real** models.yml into a throwaway profile with only the baseUrl swapped: ```bash sed 's|http://:7770/v1|http://127.0.0.1:18099/v1|' \ ~/.omp/agent/models.yml > ~/.omp/profiles/wiretest/agent/models.yml ``` 3. Run and inspect: ```bash omp --profile wiretest --model "/" -p "hi" \ --no-tools --no-session --no-extensions --no-skills --no-rules jq 'del(.messages, .tools)' last-body.json ``` Confirm every intended field is present with the right value. ## Verify a field is *honoured*, not just accepted Unknown fields often return 200 anyway. Prove behavior with a **prompt-token delta**: replay an assistant turn carrying a long `reasoning_content`, then compare `usage.prompt_tokens` with the flag true vs false. A large delta means the field changed template rendering; zero delta means it was ignored. ## Cleanup Trash the probe profile and echo server (`profiles/.AGENTTRASH`), never `rm`.