Repository navigation
Resolve the embedding model under NODESPACE_HOME - #3601
Conversation
Pragmatic Code Review — PR #3601Review Type: Initial Review Requirements Check (issue #2781, Part A only; Part B is out of scope on purpose):
Design assessmentThe layering choice is right. nlp-engine sits below the daemon, so it can't call The reversal of the earlier "read the real home on purpose" rule costs less than that old comment implies. Most of the shared store it worried about ("9 GGUFs totalling 39 GB") is chat models. Those still resolve from The tests change process-global env. That's acceptable here: Code Review Findings1. 🟡 Important: the documented eval setup breaks when this merges, and the docs change is deferred
2. 🟢 Suggestion: "authoritative"
3. 🟢 Nit: ambiguous wording in the skill-index hint 4. 🟢 Nit: run-app skill now half-overstates what What's good
RecommendationAPPROVE. This is a clear improvement: it fixes a real isolation bug, with the right layering and tests that guard against regressions. Please open finding 1's docs PR before merging so the eval recipe doesn't break. Findings 2-4 are optional. 🤖 Generated with Claude Code |
mstomar125
left a comment
There was a problem hiding this comment.
Automated pragmatic-code-review: APPROVE (posted as a comment — GitHub blocks self-approval). Full findings in the review comment above.
Address review: response to the pragmatic reviewAll four findings are addressed in bc06eb9, except that the docs patch for finding 1 is prepared but not yet pushed (see below). ✅ 2 🟢: a missing explicit
|
The daemon's resolve_model_path built the default model path from dirs::home_dir(), so a daemon isolated with NODESPACE_HOME still loaded the user's ~/.nodespace/models model and started GPU work. It now resolves models/ under nodespace_dir(), like the database, registry and daemon.toml. NODESPACED_MODEL_PATH still overrides it. EmbeddingConfig::resolve_model_path treated an explicit model_path that did not exist as a hint and fell back to ~/.nodespace/models. An explicit path is now authoritative and missing means NotFound; the home lookup applies only when no path is given. The eval preflight's isolation recipe and skill-index hint, and the run-app skill, now say an isolated daemon loads no embedding model unless NODESPACED_MODEL_PATH names one. Part of #2781 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EmbeddingService::initialize fell into stub mode on any resolve error, so an explicit model_path that vanished before the load produced a service that wrote zero vectors while the daemon logged semantic search as available. A missing explicit path is now a ModelLoadError, which the daemon's load task already turns into load_failed; stub mode stays for callers that give no path. Also rewords EmbeddingConfig::resolve_model_path's doc comment so it no longer describes the daemon, clarifies the eval preflight hint, and notes in the run-app skill that chat models still load from $HOME. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bc06eb9 to
2b88787
Compare
Part of #2781. This PR fixes the bug the issue describes. The bundled
resources/models/question needs a design decision, so it stays open on the issue; my findings are in a comment there.Summary
packages/daemon/src/services/assembly.rs,resolve_model_path): the default model path now comes fromcrate::nodespace_dir(), notdirs::home_dir(). The model therefore followsNODESPACE_HOME, like the database, the registry anddaemon.tomlalready do. Before this change, a daemon isolated withNODESPACE_HOMEstill loaded the user's real~/.nodespace/modelsmodel and started GPU work.NODESPACED_MODEL_PATHstill overrides the path.packages/nlp-engine/src/config.rs,EmbeddingConfig::resolve_model_path): an explicitmodel_paththat doesn't exist now returnsNotFound, andEmbeddingService::initializereports it asModelLoadErrorinstead of falling into stub mode. Before, it fell back silently to~/.nodespace/models. The daemon is the only caller that passes an explicit path. The home lookup still applies when no path is given, which is how the standalone live tests use it.scripts/eval/preflight.tsnow nameNODESPACED_MODEL_PATH..claude/skills/run-app/SKILL.mdnow says the isolated daemon starts with no embedding model and puts nothing on the GPU, and shows how to opt in.local_agent_service.rs): a comment claimed that no other unit test in the crate reads$HOME. That was already loose and is now false, so it now relies on nextest running each test in its own process.This reverses an earlier, deliberate choice. The old doc comment said model resolution read the real home on purpose, so that isolated eval daemons could share the model store. Sharing is now opt-in through
NODESPACED_MODEL_PATH. An isolated eval daemon started without that variable has no embedding model, and preflight's skill-index wait times out. Its error now says what to set.Tests
New tests:
services::assembly::tests::empty_nodespace_home_finds_no_model_even_when_the_user_home_has_oneNODESPACE_HOME;HOMEholds a placeholder model (stands in for the real~/.nodespace/models)Noneservices::assembly::tests::model_under_nodespace_home_is_chosen_over_the_user_homeNODESPACE_HOMEpathconfig::tests::test_missing_explicit_model_path_does_not_fall_back_to_homeHOMEholds a placeholder modelNotFoundconfig::tests::test_existing_explicit_model_path_is_chosenembedding::tests::initialize_fails_when_an_explicit_model_path_is_missing(added in address-review)ModelLoadError, not stub modeEvery test uses a tiny placeholder file and checks path resolution only. Nothing loads a model.
Mutation checks (each mutation applied, the tests run, then the file restored):
dirs::home_dir()modelssubdirectory misspeltmodel_under_nodespace_home…failstest_missing_explicit…failsexists()check negatedtest_existing_explicit…fails (both tests fail)initialize()removedinitialize_fails_when_an_explicit_model_path_is_missingfailsOther checks:
nodespace-daemonandnodespace-nlp-engine, run withHOMEandNODESPACE_HOMEset to scratch directories and model-loading files excluded (golden_*,live_terminal_summary,scenario6_update_node_properties): 576 passed, 21 skipped (round 2, after address-review).cargo clippy -p nodespace-daemon -p nodespace-nlp-engine --all-targets -- -D warnings -D clippy::unused-async: clean.cargo fmt --check: clean.bun run scripts/check-pro-boundary.ts: clean.scripts/eval/preflight.test.ts: 22 passed.Out of scope (follow-ups)
SettingsServiceImpl::with_default_path(settings_service.rs:527) still builds the settingsdaemon.tomlpath from$HOMEand ignoresNODESPACE_HOME. This is the same class of bug: an isolated daemon reads and writes the real settings file, which holds API keys. Meanwhile the local agent reads$NODESPACE_HOME/.nodespace/daemon.toml.packages/agent/src/local_agent/model_manager.rs:788) comes fromHOME. That is the agent lane.Documentation
nodespace_dir()and followNODESPACE_HOME. This PR brings the embedding model in line with that.NODESPACE_HOMEredirects every daemon state path.development/agent-eval.mdshould addNODESPACED_MODEL_PATH=$HOME/.nodespace/models/nomic-embed-text-v1.5.Q8_0.ggufto the isolated daemon command. That docs change is not pushed from here. A ready-to-apply patch for it and for thedaemon-cli-protocol.mdstate-path list is in the address-review comment; push it to nodespace-docsmainwith the merge.🤖 Generated with Claude Code