Status
Accepted
Date
2026-05-02
Context
crates/codelens-mcp/Cargo.toml declared default = ["semantic"], so a
user running
cargo install codelens-mcp
implicitly opted into codelens-engine/semantic, which pulls in
fastembed, ort (ONNX Runtime), and sqlite-vec. The semantic stack
needs an ~80 MB sidecar model directory to be useful; crates.io does
not ship the model (the model is gitignored under
crates/codelens-engine/models/ and only included in GitHub Release
tarballs).
The README itself acknowledges the resulting cliff:
Semantic search additionally needs a sidecar model directory (~80 MB ONNX) — GitHub Release tarballs bundle it automatically, but users installing via
cargo install codelens-mcpmust pointCODELENS_MODEL_DIRat a separately-fetched model payload.
In practice this means:
cargo installusers pay the heavy ONNX/fastembed/ort dependency graph at compile time — even before they discover the model is missing.- The first
semantic_searchcall returns aFeatureUnavailableerror pointing atindex_embeddings, which itself fails because the model directory is empty. There is no way for a fresh user to reach a green semantic surface fromcargo installalone. - The bench-data figure in the README ("67-87% token saving") is measured with semantic on. With semantic off — which is what most cargo-install-only users actually run after the first failure — the retrieval surface degrades to BM25-only. That fact is not surfaced.
The 2026-05-02 architecture audit (ADR-0011 §Neutral / Deferred) flagged this onboarding cliff as a separate decision. ADR-0012 makes that decision.
Existing release-channel feature policy
At decision time, the user-facing release channels already had different feature policies, and the inconsistency was part of the problem. For the current channel inventory, use release distribution.
| Channel | Feature flags today |
|---|---|
cargo install codelens-mcp (crates.io) |
default = ["semantic"] — semantic on, but no model shipped |
| GitHub Release tarball (linux-x86_64, windows-x86_64) | --features "http" — semantic OFF |
| GitHub Release tarball (darwin-arm64) | --features "http,coreml" — semantic on (via coreml → semantic) + model bundled |
install.sh |
downloads the GitHub Release tarball, inherits per-target feature set |
| Homebrew tap | external repo mupozg823/tap, not in this repo's scope |
The contradiction is concentrated on the crates.io path: it claims semantic but cannot deliver it, while the GitHub Release linux/win builds already ship semantic-off and that is not a problem because those users selected the "tarball" path knowingly.
Decision
Flip the crates.io default to []. Three sub-decisions follow.
1. Cargo.toml [features].default = []
crates/codelens-mcp/Cargo.toml:
[features]
default = []
semantic = ["codelens-engine/semantic"]
coreml = ["semantic", "codelens-engine/coreml"]
…
Effect:
cargo install codelens-mcpno longer compilesfastembed,ort,sqlite-vec; the binary is materially smaller and faster to install.- Existing semantic users opt in explicitly:
cargo install codelens-mcp --features semantic(model dir still has to be supplied separately, as today). - All
#[cfg(feature = "semantic")]gates already exist in the codebase (PR #125 audit); the dispatcher already returnsFeatureUnavailablewith a hint when semantic tools are called and the feature is off. No additional dispatch changes are required.
2. Startup banner makes the trade-off explicit
When the binary boots without feature = "semantic", the stderr banner
now says so once:
codelens-mcp 1.x.x (semantic off)
Hybrid retrieval is disabled. To enable it:
cargo install codelens-mcp --features semantic
or download a GitHub Release tarball (semantic + model bundled).
BM25, AST, and call-graph tools work with no extra setup.
This avoids a silent "semantic_search returns 0 results" failure mode.
The banner is suppressed in stdio mode so JSON-RPC stdout stays clean —
banner goes to stderr per the existing init_tracing policy.
3. README repositions the bench claim
The "67-87% saving" line is genuine but it is measured with semantic on
and the bundled model. The README is updated so the headline number is
qualified ("with --features semantic"), and the install section gets
two parallel quick-paths:
- "Quick install (BM25 + AST only)" →
cargo install codelens-mcp - "Quick install (full hybrid retrieval)" →
cargo install codelens-mcp --features semantic+ a one-lineCODELENS_MODEL_DIRsetup, or the GitHub Release tarball.
Out-of-scope sub-decisions (deferred)
The following were considered and not taken in this ADR; each gets its own decision when telemetry from this change accumulates:
- Auto-download the model on first semantic call. Tempting, but it introduces an outbound supply-chain dependency the binary did not previously have. Defer until model hosting is decided.
- Make
coremlan additive layer rather thancoreml = ["semantic", …]. The current shape (coreml implies semantic) is fine for the macOS GitHub Release path, but if we ever ship a coreml-only-no-onnx variant we will need to revisit. Out of scope. - A
CODELENS_AUTO_FETCH_MODELopt-in. Same supply-chain note. - Adjust
default-membersin the workspaceCargo.toml. Workspace default-members affectscargo buildfrom the repo root; that is developer-only ergonomics and unrelated to the install-path UX. No change.
Consequences
Positive
- Onboarding cliff removed: a fresh
cargo install codelens-mcpsucceeds, the binary boots, the banner explains what semantic adds, and BM25 / AST / call-graph / mutation tools all work without a model directory. - Compile time and binary size on the crates.io path drop noticeably —
fastembed,ort,sqlite-vecare heavy. - Honest README: the headline bench number now carries the feature qualifier, so the marketing claim and the actual default install match.
- A new telemetry counter pair (
semantic_engaged_calls/semantic_disabled_calls) gives Track 2/3 of the deferred-items roadmap real install-path data instead of a guess.
Negative
- Existing users who run
cargo install --force codelens-mcpafter this change will silently lose semantic, because they will not pass--features semantic. Mitigations: - Bump the published version to
1.10.0(minor — feature default is a behaviour change, not a SemVer-breaking API change, but it is non-trivial enough to deserve a minor bump). - CHANGELOG.md entry under
1.10.0calling out the flag explicitly. - The startup banner makes the change visible on the first run.
- Two parallel quick-install paths in the README slightly raise reader load. Mitigation: one default path is shown first, the semantic path is collapsed into a "Need NL retrieval?" expand block.
Neutral
- GitHub Release tarballs are unchanged. The linux/win builds were
already semantic-off, the darwin-arm64 build is still semantic-on
via the
coremlfeature, and the bundled model still ships. install.shis unchanged — it consumes the Release tarball.- The Homebrew tap (external repo) is unchanged in this PR; if its
formula passes through
cargo installit should be updated to--features semanticseparately. Tracked as a follow-up note in the PR description, not in this ADR. - The CI matrix already has a
--no-default-featureslane (PR #125); it stays as the canonical "semantic-off" coverage.
Cross-reference
- ADR-0011 — control-plane sprawl resolution; this is the first follow-up from §Neutral / Deferred.
- ADR-0001 — runtime boundaries; the semantic backend is correctly
isolated behind
codelens-engine/semantic, so the flip is local to one Cargo.toml line plus banner/README. - README — Quick Install section reorganisation lands in the same PR as this ADR.