fix(postgres): embed through get_embedding_function(), not chromadb's default (#413) - #463
Merged
Merged
Conversation
|
Warning Review limit reachedNext included review available in 32 minutes. View limit detailsLimit details: You’ve used the included review currently available. You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Review configuration: ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
jphein
force-pushed
the
fix/413-postgres-embedding-resolver
branch
from
September 11, 2026 01:15
0bd3709 to
2ec1ee3
Compare
… default backends/postgres.py::_embed constructed its own DefaultEmbeddingFunction and never called mempalace.embedding.get_embedding_function(). Every embedding-layer improvement in the project lands inside that resolver, so this backend silently missed all of them. Three defects compounded in one eight-line function: 1. MEMPALACE_EMBEDDING_MODEL was ignored on this backend's write path (_prepare_write_inputs) and query path. The config layer resolves fine -- describe_device() reports the endpoint and get_embedding_function() returns OpenAICompatEmbeddingFunction -- but _embed never asked, so every vector was computed by a local CPU embedder. Measured on two long-running installs: the configured remote embedding server served 9 texts in 18.3 hours of near-continuous mining. 2. The ORT intra_op_num_threads cap from MemPalace#1068 is wired up only inside get_embedding_function() (via _build_ef_class/_MempalaceONNX), so calling DefaultEmbeddingFunction() directly skipped it. Measured on a 48-core host: one such embedder adds ~49 OS threads; daemon CPU averaged 210-265% over 18 hours and was sampled at 2550% mid-mine. 3. In chromadb 1.5.9 DefaultEmbeddingFunction is not ONNXMiniLM_L6_V2 -- its whole __call__ body is `return ONNXMiniLM_L6_V2()(input)`, and .model is a per-*instance* cached_property. The module-global _embedder therefore cached an object holding no model, and every _embed() call rebuilt the entire ONNX session: 0.706s / 0.711s / 0.713s across three identical calls -- perfectly flat, no warm-up ever -- against 1.257s / 0.532s / 0.539s for a genuinely reused instance. At DRAWER_UPSERT_BATCH_SIZE = 1000 that is one session rebuild per 1000 chunks, each spawning and discarding a ~47-thread pool. The docstring's stated rationale -- match the default backend's zero-API local model without a second ML dependency stack -- is fully satisfied by delegating: with no embedding configuration set the resolver still returns a local ONNX MiniLM embedder, adds no dependency, and does not change default behaviour. It diverges only where an operator explicitly configured something else, which is the point of MEMPALACE_EMBEDDING_MODEL. The numpy-to-float conversion is reused from backends/embedding_wrapper rather than re-derived: the postgres vector literal formats with %f and np.float32 scalars would not survive it, and that subtlety should have one home. The module-global _embedder is deleted rather than moved -- get_embedding_function already caches under a lock. Not declaring requires_explicit_embeddings: it only takes effect at palace.get_collection(), and mcp_server._get_collection_postgres() and convo_miner.mine_sessions() both bypass that wrap point, so the capability alone would break the MCP server and the miner. Delegating inside _embed fixes every route, wrapped or not. Routing through the resolver makes one new failure mode reachable: _embed could previously only ever produce EMBEDDING_DIM vectors, and can now produce any width against a vector(384) column. _warn_on_dimension_mismatch names the model, the produced width and the column width once, because pgvector's own error names none of them. Postgres stays the authority on whether the write is legal; this only makes the cause legible. Blast radius: mempalace/backends/postgres.py only. Tests: tests/test_postgres_embedding_resolver.py, 6 cases -- the embedder is constructed once across three embeds (defect 3), DefaultEmbeddingFunction is never constructed directly, the configured embedding function is the one that runs (defect 1), plain Python floats come back, an empty batch never triggers a model load, and the dimension mismatch is named exactly once. Part of #413 Fixes #413 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Entry + the three renderers, plus the README/CLAUDE test count moved 7057 -> 7063 for the 6 new cases. Rebased onto d8564fc; the entry's commit hash tracks the rebased code commit so check-docs's hash-resolution check stays green. The entry also records the same-width embedder-identity gap this change leaves open, tracked in #468. Part of #413 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jphein
force-pushed
the
fix/413-postgres-embedding-resolver
branch
from
September 11, 2026 03:05
d7b5124 to
d6ad8e9
Compare
jphein
added a commit
that referenced
this pull request
Sep 18, 2026
Step 4 did `grep … | head -1` per document, so only the FIRST line mentioning
a PR number contributed to that document's claimed state. The check answered
"does the first mention agree?" rather than "do all mentions agree?", and a
drifted claim appearing after a correct one was invisible.
Reproduced on the real repo before fixing: appending
PR MemPalace#1377 is still open upstream.
to the end of README.md, with MemPalace#1377 MERGED upstream, left check-docs
reporting "✓ all 245 PR references match upstream state".
The control was confirmed reachable BEFORE its silence was trusted: the
appended line is in the instrument's own match set (line 477 of 5 matches),
while `head -1` selects line 30 — which mentions MemPalace#1377 and claims nothing.
A control that is present but never looked at produces "did not fire" for the
wrong reason, and that reads identically to "no drift".
Three defects, now one scan:
- Only the first line was read. Every matching line is now, with the
multi-PR commentary skip applied per LINE rather than zeroing a whole
document's claims.
- No right boundary. The claim scan used (#$n|/$n), so `#45` matched `#452`
and `/45` matched `/459efab`, while the commentary scan beside it used
[^0-9] — the two loops could read different lines and reach a conclusion
neither line supported. One scan, one regex, anchored on a non-digit or
end of line.
- State words matched as substrings. "opencode", "openai-compat" and
"reopened" all contain "open" and were read as a claim of OPEN. Latent
while one line per doc was examined; amplified the moment every line is.
Measured differentially over the whole repo with every PR stubbed MERGED:
7 findings before, 7 after — and five different ones in each direction.
removed (false positives): #45 read off a line about #452
#56 "OpenCode adapter smoke test"
#463 "openai-compat embedding"
MemPalace#1567 ".opencode/opencode.json"
MemPalace#2062 v3.8.0 sync line
found (never examined): #23 "PR #23 is still OPEN but"
#168 "#168 itself stays open"
MemPalace#665 "*Upstream:* [PR MemPalace#665] (OPEN)"
MemPalace#1087 "(OPEN)"
MemPalace#1094 "(open upstream, jp-authored)"
The unchanged total is the trap: a reader checking whether the count moved
would conclude nothing had.
Also removes both shellcheck errors in the file (SC1087 — `$n[` read as
array indexing) and adds none; the two remaining warnings are pre-existing.
Known limit, asserted in a test rather than left implicit: when one clean
line claims the true state and another claims a different one, drift cannot
be distinguished from history, and the PR is skipped. MemPalace#1024 in
FORK_CHANGELOG.md is exactly that shape — "pushed to the open MemPalace#1024 PR
branch (squash-merged upstream)" alongside an authoritative "(MERGED)" — and
is correct documentation of a MERGED PR. It is the only such pair in the
repo, which is why flagging disagreement was rejected.
Tests: 11 new in tests/test_check_docs_pr_state.py, driving the real script
over a fixture tree with a stubbed `gh` (no network, no shared API quota).
Four guards mutation-verified, each mutation asserting its target exists so a
mutation that fails to apply reports loudly instead of as "nothing to guard":
restore head -1 -> 3 tests
remove the word boundary -> 2
remove the commentary skip -> 1
drop the right boundary -> 1
Three of those tests were not evidence when first written and were rebuilt:
the harness sliced output between step headings while findings go to stderr
(so every "no finding" assertion passed vacuously); the commentary test
claimed a state the MERGED branch ignores; and the boundary test used a
number the check never queried. Each now fails under the old behaviour.
check-docs passes on itself. Full suite 7443 passed, 82 skipped.
Part of #516
Fixes #516
jphein
added a commit
that referenced
this pull request
Sep 18, 2026
Step 4 did `grep … | head -1` per document, so only the FIRST line mentioning
a PR number contributed to that document's claimed state. The check answered
"does the first mention agree?" rather than "do all mentions agree?", and a
drifted claim appearing after a correct one was invisible.
Reproduced on the real repo before fixing: appending
PR MemPalace#1377 is still open upstream.
to the end of README.md, with MemPalace#1377 MERGED upstream, left check-docs
reporting "✓ all 245 PR references match upstream state".
The control was confirmed reachable BEFORE its silence was trusted: the
appended line is in the instrument's own match set (line 477 of 5 matches),
while `head -1` selects line 30 — which mentions MemPalace#1377 and claims nothing.
A control that is present but never looked at produces "did not fire" for the
wrong reason, and that reads identically to "no drift".
Three defects, now one scan:
- Only the first line was read. Every matching line is now, with the
multi-PR commentary skip applied per LINE rather than zeroing a whole
document's claims.
- No right boundary. The claim scan used (#$n|/$n), so `#45` matched `#452`
and `/45` matched `/459efab`, while the commentary scan beside it used
[^0-9] — the two loops could read different lines and reach a conclusion
neither line supported. One scan, one regex, anchored on a non-digit or
end of line.
- State words matched as substrings. "opencode", "openai-compat" and
"reopened" all contain "open" and were read as a claim of OPEN. Latent
while one line per doc was examined; amplified the moment every line is.
Measured differentially over the whole repo with every PR stubbed MERGED:
7 findings before, 7 after — and five different ones in each direction.
removed (false positives): #45 read off a line about #452
#56 "OpenCode adapter smoke test"
#463 "openai-compat embedding"
MemPalace#1567 ".opencode/opencode.json"
MemPalace#2062 v3.8.0 sync line
found (never examined): #23 "PR #23 is still OPEN but"
#168 "#168 itself stays open"
MemPalace#665 "*Upstream:* [PR MemPalace#665] (OPEN)"
MemPalace#1087 "(OPEN)"
MemPalace#1094 "(open upstream, jp-authored)"
The unchanged total is the trap: a reader checking whether the count moved
would conclude nothing had.
Also removes both shellcheck errors in the file (SC1087 — `$n[` read as
array indexing) and adds none; the two remaining warnings are pre-existing.
Known limit, asserted in a test rather than left implicit: when one clean
line claims the true state and another claims a different one, drift cannot
be distinguished from history, and the PR is skipped. MemPalace#1024 in
FORK_CHANGELOG.md is exactly that shape — "pushed to the open MemPalace#1024 PR
branch (squash-merged upstream)" alongside an authoritative "(MERGED)" — and
is correct documentation of a MERGED PR. It is the only such pair in the
repo, which is why flagging disagreement was rejected.
Tests: 11 new in tests/test_check_docs_pr_state.py, driving the real script
over a fixture tree with a stubbed `gh` (no network, no shared API quota).
Four guards mutation-verified, each mutation asserting its target exists so a
mutation that fails to apply reports loudly instead of as "nothing to guard":
restore head -1 -> 3 tests
remove the word boundary -> 2
remove the commentary skip -> 1
drop the right boundary -> 1
Three of those tests were not evidence when first written and were rebuilt:
the harness sliced output between step headings while findings go to stderr
(so every "no finding" assertion passed vacuously); the commentary test
claimed a state the MERGED branch ignores; and the boundary test used a
number the check never queried. Each now fails under the old behaviour.
check-docs passes on itself. Full suite 7443 passed, 82 skipped.
Part of #516
Fixes #516
jphein
added a commit
that referenced
this pull request
Sep 18, 2026
) (#520) * fix(check-docs): examine every mention of a PR, not just the first Step 4 did `grep … | head -1` per document, so only the FIRST line mentioning a PR number contributed to that document's claimed state. The check answered "does the first mention agree?" rather than "do all mentions agree?", and a drifted claim appearing after a correct one was invisible. Reproduced on the real repo before fixing: appending PR MemPalace#1377 is still open upstream. to the end of README.md, with MemPalace#1377 MERGED upstream, left check-docs reporting "✓ all 245 PR references match upstream state". The control was confirmed reachable BEFORE its silence was trusted: the appended line is in the instrument's own match set (line 477 of 5 matches), while `head -1` selects line 30 — which mentions MemPalace#1377 and claims nothing. A control that is present but never looked at produces "did not fire" for the wrong reason, and that reads identically to "no drift". Three defects, now one scan: - Only the first line was read. Every matching line is now, with the multi-PR commentary skip applied per LINE rather than zeroing a whole document's claims. - No right boundary. The claim scan used (#$n|/$n), so `#45` matched `#452` and `/45` matched `/459efab`, while the commentary scan beside it used [^0-9] — the two loops could read different lines and reach a conclusion neither line supported. One scan, one regex, anchored on a non-digit or end of line. - State words matched as substrings. "opencode", "openai-compat" and "reopened" all contain "open" and were read as a claim of OPEN. Latent while one line per doc was examined; amplified the moment every line is. Measured differentially over the whole repo with every PR stubbed MERGED: 7 findings before, 7 after — and five different ones in each direction. removed (false positives): #45 read off a line about #452 #56 "OpenCode adapter smoke test" #463 "openai-compat embedding" MemPalace#1567 ".opencode/opencode.json" MemPalace#2062 v3.8.0 sync line found (never examined): #23 "PR #23 is still OPEN but" #168 "#168 itself stays open" MemPalace#665 "*Upstream:* [PR MemPalace#665] (OPEN)" MemPalace#1087 "(OPEN)" MemPalace#1094 "(open upstream, jp-authored)" The unchanged total is the trap: a reader checking whether the count moved would conclude nothing had. Also removes both shellcheck errors in the file (SC1087 — `$n[` read as array indexing) and adds none; the two remaining warnings are pre-existing. Known limit, asserted in a test rather than left implicit: when one clean line claims the true state and another claims a different one, drift cannot be distinguished from history, and the PR is skipped. MemPalace#1024 in FORK_CHANGELOG.md is exactly that shape — "pushed to the open MemPalace#1024 PR branch (squash-merged upstream)" alongside an authoritative "(MERGED)" — and is correct documentation of a MERGED PR. It is the only such pair in the repo, which is why flagging disagreement was rejected. Tests: 11 new in tests/test_check_docs_pr_state.py, driving the real script over a fixture tree with a stubbed `gh` (no network, no shared API quota). Four guards mutation-verified, each mutation asserting its target exists so a mutation that fails to apply reports loudly instead of as "nothing to guard": restore head -1 -> 3 tests remove the word boundary -> 2 remove the commentary skip -> 1 drop the right boundary -> 1 Three of those tests were not evidence when first written and were rebuilt: the harness sliced output between step headings while findings go to stderr (so every "no finding" assertion passed vacuously); the commentary test claimed a state the MERGED branch ignores; and the boundary test used a number the check never queried. Each now fails under the old behaviour. check-docs passes on itself. Full suite 7443 passed, 82 skipped. Part of #516 Fixes #516 * fix(check-docs): a state WORD is not a state CLAIM; /pull/N, not /N Follow-up on the same PR, applying nebula's measurement from the issue thread. The first pass fixed `head -1` and matched state words as whole words; that was not enough, and I could prove it only after running the check with REAL upstream states. Two corrections to my own verification first, because they are why this was nearly missed: * My "no new false positives" run used a stub that returned a state for ONE pr number and nothing for the rest, so every other PR was skipped. "Clean" there proved nothing. With real `gh` the fix ADDED a warning. * The control line nebula cited (FORK_CHANGELOG.md L246) is blank in this tree — the measurement was taken on another branch. Found by content instead: it is L358 here, and L356 is a worse case nebula predicted but could not see. Measured with real states, before this commit: NEW script: 2 warnings (MemPalace#1377, #459) OLD script: 1 warning (MemPalace#1377) Both causes are the same defect from the other end. `head -1` decided WHICH lines are read; this decides WHAT counts as a claim on a line. #459 README.md:297 and FORK_CHANGELOG.md:356 are the heading "purge / prune / mined share one open-and-refuse sequence", linking commit `459efab`. There is no #459 on either line — `/459` matched inside the COMMIT HASH, and "open-and-refuse" supplied a whole-word "open". A right boundary does not help: `/459` is followed by `e`. MemPalace#1377 FORK_CHANGELOG.md:81 was MY OWN changelog entry, quoting the control sentence verbatim. The entry documenting the defect reproduced it — the `self-quoting-retraction` shape from the #503 spec, which is how #511 went green and then warned again once its own entry landed. So, three rules now: * `/pull/$n`, never a bare `/$n`. A commit hash is not a PR reference. * A state word must appear in a CLAIM SHAPE — a parenthesised marker "(OPEN)", or a copula "is/was/stays/remains/now [still] open". Checked against all 13 cases in this repo: every real claim kept (#23 "is still OPEN", #168 "stays open", MemPalace#665/MemPalace#1087 "(OPEN)", MemPalace#1094 "(open upstream"), every prose case dropped ("open-and-refuse", "open the drawers", "opencode", "openai-compat", "reopened"). * The entry for this change does not quote its own control sentence. It states a MERGED claim for MemPalace#1377, which is what MemPalace#1377 is. Result with real `gh` on the repo itself: both the base script and this one report zero PR-state warnings. The base's cleanliness is incidental — its `head -1` happens to land on a line without a claim, and moved there only because #509/#512/this entry changed the changelog. This one is clean for a reason. shellcheck findings 4 -> 2 (both remaining are pre-existing warnings; both former ERRORS are gone). Tests: 5 more (state word without a claim; nebula's "open the drawers" with the PR alone on the line, since the real one is spared only incidentally by a second PR sharing it; a commit hash is not a PR reference; a /pull/ URL still counts; a parenthesised marker is still a claim). 16 total, and two more mutations verified with the target-exists assertion: claim shape -> bare word -> 3 tests /pull/N -> /N -> 2 tests One test asserted something the check cannot do — a PR referenced only by URL is never examined, because the number list is harvested from `#NNNN` alone. Pre-existing and out of scope; the test now says so rather than pretending to cover it. Full suite 7448 passed, 82 skipped. check-docs passes on itself. Part of #516 * docs(fork-changes): renumber to seq 157 after the #511/#522 rebase Rebased onto 4c6a8d0. `--next-seq` re-run after the final fetch says 157; taken from the tool rather than assumed. Generated artefacts re-rendered from main's side, never hand-merged. Part of #516
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
backends/postgres.py::_embednow delegates tomempalace.embedding.get_embedding_function()instead of constructing its ownchromadb.utils.embedding_functions.DefaultEmbeddingFunction().Why — three defects compounding in one eight-line function
Every embedding-layer improvement in the project lands inside that resolver, so this backend silently missed all of them.
1.
MEMPALACE_EMBEDDING_MODELwas ignored on this backend's write path (_prepare_write_inputs) and query path. The config layer resolves correctly —describe_device()reports the endpoint andget_embedding_function()returnsOpenAICompatEmbeddingFunction— but_embednever asked, so every vector was computed by a local CPU embedder. Measured on two long-running installs: the configured remote embedding server served 9 texts in 18.3 hours of near-continuous mining. Same class of failure as MemPalace#2324; the external-embedding-API support added in MemPalace#1559 never reached this backend.2. The ORT
intra_op_num_threadscap from MemPalace#1068 was bypassed. That fix lives in_build_ef_class/_MempalaceONNX, wired up only inside the resolver. Measured on a 48-core host: instantiating one such embedder adds ~49 OS threads; daemon CPU averaged 210-265% over 18 hours and was sampled at 2550% mid-mine.3.
DefaultEmbeddingFunctionrebuilt the ONNX session on every call. In chromadb 1.5.9 it is notONNXMiniLM_L6_V2— it is a separate class inchromadb/api/types.pywhose entire__call__body isreturn ONNXMiniLM_L6_V2()(input), andONNXMiniLM_L6_V2.modelis a per-instancecached_property. So the module-global_embeddercached an object that held no model.At
DRAWER_UPSERT_BATCH_SIZE = 1000that is one full session rebuild per 1000 chunks, each spawning and discarding a ~47-thread pool. Per-batch embed time after delegating: 0.706s → 0.016s.The stated rationale still holds
The old docstring said: reuse Chroma's default local embedding function so the PostgreSQL backend matches the zero-API embedding model already used by the default backend without adding a second ML dependency stack. Delegating satisfies that completely — with no embedding configuration set, the resolver still returns a local ONNX MiniLM embedder. No new dependency, no change to default behaviour. It diverges only where an operator has explicitly configured something else, which is the entire point of
MEMPALACE_EMBEDDING_MODEL.Two deliberate design calls
Not declaring
requires_explicit_embeddings. It is architecturally tidier and the other backends do it, but it only takes effect atpalace.get_collection()— andmcp_server._get_collection_postgres()callsPostgresBackend.get_collectiondirectly, whileconvo_miner.mine_sessions()importsmcp_server._get_collection. Both bypass the wrap point, so the capability alone (with_embedraising) would break the MCP server and the miner. Delegating inside_embedfixes every route, wrapped or not, and cannot break a caller that already worked.Reusing
embedding_wrapper._embed_textsrather than re-deriving the conversion. The postgres vector literal formats with%f, andnp.float32scalars do not survive it — that subtlety is documented at length in_embed_textsand should have exactly one home rather than a second copy that can drift.The new failure mode this change makes reachable, and the guard for it
Before this change
_embedcould only ever produceEMBEDDING_DIM(384) vectors. Routing through the resolver means an operator can now configure a 768-dim model against avector(384)column. pgvector's own error names neither the model nor the configuration that selected it, so_warn_on_dimension_mismatchnames the model, the produced width, and the column width — once, on the batch that discovers it. Postgres stays the authority on whether the write is legal; this adds no second place that can refuse one.(Vector-space safety, per the issue: on 500 real stored documents, chromadb's
DefaultEmbeddingFunctionvs anopenai-compatendpoint serving the sameall-MiniLM-L6-v2gave min cosine 0.999999488, k-NN top-20 id-set overlap mean 0.997 — identical space, no re-embedding required there. That will not hold for every configured endpoint, which is what MemPalace#1561/MemPalace#1724 identity enforcement is for.)Blast radius
mempalace/backends/postgres.pyonly. The module-global_embedderis deleted rather than moved —get_embedding_function()already caches under a lock.Tests
tests/test_postgres_embedding_resolver.py— 6 cases:_embedcalls (defect 3 — this is the 0.706s-flat measurement, expressed structurally)DefaultEmbeddingFunctionis never constructed directlyAll watched failing against
mainfirst, and re-verified red after the harness was finalized (4 of 6 red; the float-conversion and empty-batch cases are existing-behaviour guards).One harness note worth flagging for future test authors: conftest's autouse
_stable_embedding_function_for_testsreplaces bothembedding_wrapper._embed_textsandembedding.get_embedding_functionfor every module outside its opt-out list. Those stubs sit exactly where this module's subject does and would have silently hidden every assertion here (they did, on the first run). The fixture captures the real functions at import time — collection runs before autouse fixtures — and restores them for this module only. No native ONNX is loaded either way: every test supplies its own embedder.Full suite: 6821 passed, 82 skipped, 115 deselected. (5 pre-existing
test_init_filters_sys_path_from_leaked_pythonpathfailures reproduce on an unmodified branch in any git worktree — #454, unrelated.)ruff checkandruff format --checkclean;scripts/check-docs.shgreen after all three renderers. Rebased ontomainafter #453 and #464 landed.Arms #468
Delegating is what makes an embedder swap possible on this backend, and #468 covers the gap that leaves:
postgres.pyis the only vector backend with no stored embedder identity, so a swap to a same-width model after this lands is silent — the dimension guard added here catches a width change, and nothing catches a model change at the same width.Production behaviour is unchanged by this PR:
MEMPALACE_EMBEDDING_MODELis unset in the environment, so the resolver returns the same local ONNX MiniLM embedder the old code constructed directly. The identity work belongs in #468 with MemPalace#1561/MemPalace#1724, not here.Part of #413
Arms #468