diff --git a/docs/src/js/classes/VectorQuery.md b/docs/src/js/classes/VectorQuery.md
index e4cc0f1cc..2eef209bf 100644
--- a/docs/src/js/classes/VectorQuery.md
+++ b/docs/src/js/classes/VectorQuery.md
@@ -686,6 +686,11 @@ searchEffort(searchEffort): VectorQuery
Set the initial IVF search effort to a finite value in [0, 1].
+Efforts 0.1 and 0.25 target empirical mean partition-routing recall of
+80% and 90% for L2, cosine and dot through k=100000. The probe budget
+adapts to the metric and k. Hamming and larger k remain uncalibrated;
+100000 is not an API limit. Remote queries also require server support.
+
The default, null, uses Auto (internally 0.5). Zero starts at the caller
minimum; one searches all partitions within the caller maximum. Later
probing may continue when too few candidates are found.
diff --git a/docs/web/search/vector-search.mdx b/docs/web/search/vector-search.mdx
index ed8e686b4..3fb0a2fe8 100644
--- a/docs/web/search/vector-search.mdx
+++ b/docs/web/search/vector-search.mdx
@@ -268,6 +268,24 @@ Use `search_effort` to tune how much work a search does. It accepts values from
too low, or decrease it when you can trade some recall for lower latency.
`search_effort(0.95)` does **not** mean 95% recall.
+SDK builds using Lance 15.0.0-beta.11 or later provide these calibrated tiers:
+
+| Effort | Empirical mean partition-routing target |
+| :--- | :--- |
+| `0.1` | 80% |
+| `0.25` | 90% |
+| `0.5` (default) | Approximately 95%; existing Auto behavior |
+
+The probe budget adapts to the distance metric and requested `k`, so you can
+keep the same effort when changing the result count. The lower tiers were
+calibrated for `l2`, `cosine` and `dot` through `k=100000`. This is the tested
+range, not a limit on `k`; Hamming and larger requests remain uncalibrated.
+
+These are average routing objectives, not guarantees for each query or final
+Recall@k. Quantization, filtering and explicit probe bounds can lower final
+recall. Remote queries require a server with the corresponding Lance version;
+upgrading only the client does not change server-side routing.
+
{PyVectorIndexSearchEffort}
diff --git a/nodejs/lancedb/query.ts b/nodejs/lancedb/query.ts
index 8f16defbf..cbd927925 100644
--- a/nodejs/lancedb/query.ts
+++ b/nodejs/lancedb/query.ts
@@ -854,6 +854,11 @@ export class VectorQuery extends StandardQueryBase {
/**
* Set the initial IVF search effort to a finite value in [0, 1].
*
+ * Efforts 0.1 and 0.25 target empirical mean partition-routing recall of
+ * 80% and 90% for L2, cosine and dot through k=100000. The probe budget
+ * adapts to the metric and k. Hamming and larger k remain uncalibrated;
+ * 100000 is not an API limit. Remote queries also require server support.
+ *
* The default, null, uses Auto (internally 0.5). Zero starts at the caller
* minimum; one searches all partitions within the caller maximum. Later
* probing may continue when too few candidates are found.
diff --git a/python/pyproject.toml b/python/pyproject.toml
index 1655dedcb..3a19fd826 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -64,7 +64,7 @@ tests = [
"polars>=0.19, <=1.32.3",
"pyarrow>=16",
"pyarrow-stubs>=16.0",
- "pylance==15.0.0b10",
+ "pylance==15.0.0b11",
"requests>=2.31.0",
"datafusion>=54,<55",
"opentelemetry-sdk>=1.30.0",
diff --git a/python/python/lancedb/query.py b/python/python/lancedb/query.py
index ce313c610..08ea3b9bc 100644
--- a/python/python/lancedb/query.py
+++ b/python/python/lancedb/query.py
@@ -2060,6 +2060,11 @@ class LanceVectorQueryBuilder(LanceQueryBuilder):
def search_effort(self, search_effort: Optional[float]) -> LanceVectorQueryBuilder:
"""Set the initial IVF search effort to a finite value in [0, 1].
+ Efforts 0.1 and 0.25 target empirical mean partition-routing recall of
+ 80% and 90% for L2, cosine and dot through k=100000. The probe budget
+ adapts to the metric and k. Hamming and larger k remain uncalibrated;
+ 100000 is not an API limit. Remote queries also require server support.
+
None (the default) uses Auto, internally 0.5. Zero starts at the caller
minimum; one searches all partitions within the caller maximum. Later
probing may continue when too few candidates are found. Any explicit
@@ -3019,6 +3024,11 @@ class LanceHybridQueryBuilder(LanceQueryBuilder):
def search_effort(self, search_effort: Optional[float]) -> LanceHybridQueryBuilder:
"""Set the initial IVF search effort to a finite value in [0, 1].
+ Efforts 0.1 and 0.25 target empirical mean partition-routing recall of
+ 80% and 90% for L2, cosine and dot through k=100000. The probe budget
+ adapts to the metric and k. Hamming and larger k remain uncalibrated;
+ 100000 is not an API limit. Remote queries also require server support.
+
None (the default) uses Auto, internally 0.5. Zero starts at the caller
minimum; one searches all partitions within the caller maximum. Later
probing may continue when too few candidates are found. Any explicit
@@ -4426,6 +4436,11 @@ class AsyncVectorQueryBase:
def search_effort(self, search_effort: Optional[float]) -> Self:
"""Set the initial IVF search effort to a finite value in [0, 1].
+ Efforts 0.1 and 0.25 target empirical mean partition-routing recall of
+ 80% and 90% for L2, cosine and dot through k=100000. The probe budget
+ adapts to the metric and k. Hamming and larger k remain uncalibrated;
+ 100000 is not an API limit. Remote queries also require server support.
+
None preserves Auto (internally 0.5) and fixed nprobes. Any explicit
effort, including 0.5, overrides nprobes in either call order; explicit
minimum and maximum bounds remain effective. Zero starts at the caller
diff --git a/python/uv.lock b/python/uv.lock
index 6abf904a4..d8a1c118a 100644
--- a/python/uv.lock
+++ b/python/uv.lock
@@ -2147,7 +2147,7 @@ requires-dist = [
{ name = "pyarrow-stubs", marker = "extra == 'tests'", specifier = ">=16.0" },
{ name = "pydantic", specifier = ">=2.7.4,<3" },
{ name = "pylance", marker = "extra == 'pylance'", specifier = ">=15.0.0b3" },
- { name = "pylance", marker = "extra == 'tests'", specifier = "==15.0.0b10" },
+ { name = "pylance", marker = "extra == 'tests'", specifier = "==15.0.0b11" },
{ name = "pyright", marker = "extra == 'dev'", specifier = ">=1.1.350" },
{ name = "pytest", marker = "extra == 'tests'", specifier = ">=7.0" },
{ name = "pytest-asyncio", marker = "extra == 'tests'", specifier = ">=0.21" },
@@ -4301,7 +4301,7 @@ crypto = [
[[package]]
name = "pylance"
-version = "15.0.0b10"
+version = "15.0.0b11"
source = { registry = "https://pypi.fury.io/lance-format/" }
dependencies = [
{ name = "lance-namespace" },
@@ -4312,12 +4312,12 @@ dependencies = [
{ name = "pyarrow", version = "26.0.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" },
]
wheels = [
- { url = "https://pypi.fury.io/lance-format/-/ver_1aEUT6/pylance-15.0.0b10-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:fe34d42c5d58008a65f41acb659474c43848104414a2af94461454377c9f5276" },
- { url = "https://pypi.fury.io/lance-format/-/ver_1YU4YL/pylance-15.0.0b10-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1042942b4df0ea42d263bf2e12ac2fa04e24ea49482f55349687df439686f614" },
- { url = "https://pypi.fury.io/lance-format/-/ver_LnRBy/pylance-15.0.0b10-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0954e2a3855bc161a9ffebfcdf7f7086da3fa71ef1f3a92bd38212076963b08" },
- { url = "https://pypi.fury.io/lance-format/-/ver_pjVe1/pylance-15.0.0b10-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb6bc476f79d4c1b786377ae8366afc1718f54a9314141e65c57ad0f47906df6" },
- { url = "https://pypi.fury.io/lance-format/-/ver_RENd/pylance-15.0.0b10-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4dc325458b5b959bc644487c2ade6cdbcc89c4bc14ee7bb7487d9e145426207c" },
- { url = "https://pypi.fury.io/lance-format/-/ver_1I5wzV/pylance-15.0.0b10-cp310-abi3-win_amd64.whl", hash = "sha256:10226cf5c224c0a47d18d7a323ce64473026acd7a4f08e7c6a59e787ee93a73c" },
+ { url = "https://pypi.fury.io/lance-format/-/ver_oRP5O/pylance-15.0.0b11-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:54524c1bd04065516aeb97c5d06e1f6c88d1b1d5191d40077b104c5879893ea2" },
+ { url = "https://pypi.fury.io/lance-format/-/ver_1LdITz/pylance-15.0.0b11-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f6b2496b4b20787ff7780907f3017cf7b17c1f2bd1fa8cb7ed4d0a450445dfc" },
+ { url = "https://pypi.fury.io/lance-format/-/ver_1ONuyB/pylance-15.0.0b11-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:903283401e222dea532a36b91ffa60e7eaaf4d1fc08602cb29eef4ac2d04a8ff" },
+ { url = "https://pypi.fury.io/lance-format/-/ver_2bWLjt/pylance-15.0.0b11-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ce8bb0242b1647ce0b9355a48339b8fe8415f3556209214d0b490afb6ee60b38" },
+ { url = "https://pypi.fury.io/lance-format/-/ver_168GWI/pylance-15.0.0b11-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:387ad2b455ab5b00476d7a60ce96e6014239df239691ac219f0025a29ee19f6e" },
+ { url = "https://pypi.fury.io/lance-format/-/ver_aU9I7/pylance-15.0.0b11-cp310-abi3-win_amd64.whl", hash = "sha256:7022c2afcdaf6f9e9444db5addc72033ad20ad301930b38238e246687628cd75" },
]
[[package]]
diff --git a/rust/lancedb/src/query.rs b/rust/lancedb/src/query.rs
index 866bfbc0d..ad2dbdcbf 100644
--- a/rust/lancedb/src/query.rs
+++ b/rust/lancedb/src/query.rs
@@ -1329,7 +1329,11 @@ impl VectorQuery {
///
/// None (the default) resolves to 0.5, preserving Auto probing. At 0 the
/// search starts at the caller minimum; at 1 it searches all partitions
- /// within the caller maximum. Intermediate values interpolate around Auto.
+ /// within the caller maximum. Efforts 0.1 and 0.25 target empirical mean
+ /// partition-routing recall of 80% and 90%, respectively, for L2, cosine
+ /// and dot with k up to 100,000. The budget adapts to the metric and k;
+ /// this is a calibration boundary, not a limit on k. Hamming and larger k
+ /// retain uncalibrated behavior. Other values interpolate between tiers.
/// Later probing may continue when too few candidates are found.
/// Any explicit value, including 0.5, overrides [`Self::nprobes`] regardless
/// of setter order. Explicit minimum and maximum bounds still apply.
diff --git a/rust/lancedb/src/remote/table.rs b/rust/lancedb/src/remote/table.rs
index 895cc9f12..abba7ff94 100644
--- a/rust/lancedb/src/remote/table.rs
+++ b/rust/lancedb/src/remote/table.rs
@@ -7374,7 +7374,8 @@ mod tests {
#[rstest]
#[case::unset(None)]
#[case::minimum(Some(0.0))]
- #[case::low(Some(0.25))]
+ #[case::recall80(Some(0.1))]
+ #[case::recall90(Some(0.25))]
#[case::auto(Some(0.5))]
#[case::high(Some(0.75))]
#[case::maximum(Some(1.0))]