diff --git a/devflow/index.md b/devflow/index.md
index a8dfdb0..3b41ed3 100644
--- a/devflow/index.md
+++ b/devflow/index.md
@@ -12,6 +12,7 @@
| 日期 | slug | 说明 | 领域 | 关键词 | 关联 OpenSpec | 状态 |
|---|---|---|---|---|---|---|
| 2026-07-27 | rag-chunk-evidence-identity-dedup | chunk 级证据身份、去重、retrieve-k/return-n 与 SearchPort 地基,为 hybrid 铺路。 | RAG/证据身份/去重 | evidenceKey, maxChunksPerDocument, retrieve-k, return-n, KnowledgeSearchPort, document_id chunk-scoped | openspec/changes/archive/2026-07-27-rag-chunk-evidence-identity-dedup | archived |
+| 2026-07-27 | rag-bm25-hybrid-drop-sdk | 真 dense+BM25 hybrid(MilvusClientV2),废弃知识路径旧 SDK 检索/写入。 | RAG/BM25/hybrid | MilvusClientV2, BM25, hybridSearch, RRFRanker, biz_hybrid, drop SDK path | openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk | archived |
| 2026-07-27 | rag-hybrid-search-rrf | Delivery 2:可配置 hybrid 检索与 RRF 多路融合(不绑旧 SDK)。 | RAG/hybrid/RRF | hybrid mode, RRF, KnowledgeSearchPort, filtered+unfiltered fusion, sparse-lite lexical | openspec/changes/archive/2026-07-27-rag-hybrid-search-rrf | archived |
| 2026-07-21 | single-react-tool-invocation-store | 建立统一 ToolBoundary 与 Redis canonical invocation store,集中生命周期、证据状态、TTL、容量和 Run 所有权。 | Harness/Tool boundary/Canonical store | ISS-014, ToolBoundary, canonical invocation, PROJECTING, READY, ERROR, TTL, RESULT_TOO_LARGE | openspec/changes/archive/2026-07-21-single-react-tool-invocation-store | archived |
| 2026-07-21 | single-react-harness-run-context | 建立显式 RunContext、Harness Core、预算、取消、类型化重试和 Tool Store 基础。 | Harness/Run lifecycle/Budget | ISS-014, RunContext, deadline, cancellation, budget, retry, ToolCallKey | openspec/changes/archive/2026-07-21-single-react-harness-run-context | archived |
diff --git a/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/acceptance.md b/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/acceptance.md
new file mode 100644
index 0000000..c708f20
--- /dev/null
+++ b/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/acceptance.md
@@ -0,0 +1,22 @@
+# Acceptance
+
+## Done
+
+- `MilvusHybridKnowledgeStore`: schema BM25 function + dense, hybridSearch+RRFRanker, dense search
+- `VectorSearchService` only routes dense|hybrid to V2 store
+- `VectorIndexService` writes via V2 store
+- Removed knowledge-path `MilvusServiceClient` bean wiring
+- Config: `milvus.collection=biz_hybrid`, `retrieval.search.mode=hybrid`
+
+## Tests
+
+```text
+mvn -Dtest=LookupKnowledgeToolTest,KnowledgeEvidencePostProcessorTest,RagResultProjectorTest,RrfFusionTest,VectorKnowledgeSearchAdapterHybridTest,VectorSearchServiceTest,VectorIndexServiceTest test
+```
+
+EXIT:0
+
+## Ops note
+
+Reindex all knowledge docs into `biz_hybrid` before production hybrid search is meaningful.
+Legacy `biz` collection is unused by knowledge path.
diff --git a/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/brief.md b/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/brief.md
new file mode 100644
index 0000000..b14aa74
--- /dev/null
+++ b/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/brief.md
@@ -0,0 +1,3 @@
+# Brief: rag-bm25-hybrid-drop-sdk
+
+True dense+BM25 hybrid on a single MilvusClientV2 backend. Legacy SDK search/write for knowledge path removed. New collection `biz_hybrid` requires reindex.
diff --git a/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/decisions.md b/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/decisions.md
new file mode 100644
index 0000000..f4751c0
--- /dev/null
+++ b/devflow/projects/2026-07-27-rag-bm25-hybrid-drop-sdk/decisions.md
@@ -0,0 +1,7 @@
+# Decisions
+
+- Single backend: MilvusClientV2 only for knowledge RAG
+- Drop sdk/spring/auto retrieval routing
+- New collection biz_hybrid to avoid mutating legacy biz schema in place
+- Hybrid = dense ANN + BM25 sparse ANN + RRFRanker
+- Dense L2 enrichment for threshold compatibility on hybrid hits
diff --git a/docs/milvus-hybrid-search-integration-checklist.md b/docs/milvus-hybrid-search-integration-checklist.md
index c1ea4be..0686010 100644
--- a/docs/milvus-hybrid-search-integration-checklist.md
+++ b/docs/milvus-hybrid-search-integration-checklist.md
@@ -32,6 +32,26 @@
- 分块去重与 hybrid 同规划、分里程碑交付(先共用地基,再开 hybrid)
```
+## 实现状态(2026-07-27)
+
+| 里程碑 | 状态 | 说明 |
+|---|---|---|
+| 交付 1 chunk 身份/去重/SearchPort | **已完成并归档** | `2026-07-27-rag-chunk-evidence-identity-dedup` |
+| 交付 2a 应用层 multi-path+RRF | **已完成并归档** | `2026-07-27-rag-hybrid-search-rrf`(已被 2b 取代为生产路径) |
+| 交付 2b 真 BM25 hybrid + 废弃 SDK | **已完成并归档** | `2026-07-27-rag-bm25-hybrid-drop-sdk` |
+
+**当前生产知识路径:**
+
+```text
+VectorIndexService / VectorSearchService
+ -> MilvusHybridKnowledgeStore (MilvusClientV2 only)
+ collection: milvus.collection (default biz_hybrid)
+ mode: retrieval.search.mode = dense | hybrid
+ hybrid: dense ANN + BM25 sparse ANN + RRFRanker
+```
+
+**运维必做:** 全量重灌知识库到 `biz_hybrid`;旧 `biz` collection 不再被知识路径使用。
+
---
## 1.1 交付拆分:分块去重 + Hybrid 同规划
diff --git a/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/.committed b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/.committed
new file mode 100644
index 0000000..8a7161d
--- /dev/null
+++ b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/.committed
@@ -0,0 +1,2 @@
+committed: 2026-07-27
+authorized-apply: user-preauthorized
diff --git a/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/.openspec.yaml b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/.openspec.yaml
new file mode 100644
index 0000000..8e7013b
--- /dev/null
+++ b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-07-27
diff --git a/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/design.md b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/design.md
new file mode 100644
index 0000000..6125624
--- /dev/null
+++ b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/design.md
@@ -0,0 +1,57 @@
+# Design: rag-bm25-hybrid-drop-sdk
+
+## Decision: single backend
+
+```text
+KnowledgeSearchPort
+ -> MilvusHybridKnowledgeStore (MilvusClientV2 only)
+Write
+ -> VectorIndexService -> MilvusHybridKnowledgeStore
+```
+
+No `retrieval.vector-store.mode=sdk|spring|auto` for knowledge lookup.
+
+## Schema (`milvus.collection`, default `biz_hybrid`)
+
+| field | type | notes |
+|---|---|---|
+| id | VarChar PK | chunk id |
+| content | VarChar | original chunk body returned to agent |
+| search_text | VarChar + analyzer | BM25 input (title/path/content) |
+| sparse_vector | SparseFloatVector | BM25 function output |
+| vector | FloatVector | dense embedding |
+| metadata | JSON | docId, chunkIndex, category, kb_scope, ... |
+
+Function: `BM25(search_text -> sparse_vector)`
+
+Indexes:
+
+- `vector`: IVF_FLAT / L2 (or COSINE if configured later)
+- `sparse_vector`: SPARSE_INVERTED_INDEX / BM25
+
+## Hybrid query
+
+```text
+AnnSearchReq(vector, FloatVec(queryEmbedding), topK, filter?)
+AnnSearchReq(sparse_vector, EmbeddedText(query), topK, filter?)
+HybridSearchReq + RRFRanker(k)
+```
+
+Dense-compatible score for thresholds: if entity lacks dense distance, map fused score conservatively or re-use dense-only probe. Preferred: keep dense distance when available from a parallel dense search metadata; for hybrid hits use inverse-rank placeholder only in metadata and set score from optional dense sub-hit map.
+
+Implementation approach for threshold stability:
+
+1. Run hybridSearch for ordering/identity
+2. Build map evidenceKey -> dense L2 from a concurrent dense search (same filter/topK)
+3. Attach dense score onto fused hits when present; else maxL2 (low similarity) so weak BM25-only hits don't fake PRECISE
+
+## Migration
+
+- New collection name avoids mutating legacy `biz`
+- Operators re-run knowledge init / document reindex
+- Document in acceptance
+
+## Drop SDK search
+
+- Delete SDK search methods usage from knowledge path
+- `MilvusServiceClient` bean may remain temporarily only if other non-search utilities need it; prefer migrate write/delete fully to V2 and stop creating V1 client if unused
diff --git a/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/proposal.md b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/proposal.md
new file mode 100644
index 0000000..94c5dc9
--- /dev/null
+++ b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/proposal.md
@@ -0,0 +1,34 @@
+# Change: Real BM25 hybrid search and drop legacy SDK path
+
+## Why
+
+Delivery 2 shipped application-layer multi-path + sparse-lite lexical ranking. Product requirement is **true dense + BM25 hybrid retrieval**. Legacy `MilvusServiceClient` search path will be abandoned in this change; keep a single knowledge vector backend based on Milvus Java SDK v2 (`MilvusClientV2`) with BM25 function + `hybridSearch` + RRF.
+
+## What Changes
+
+- New hybrid collection schema: dense float vector + analyzed text + sparse BM25 output
+- Write path inserts text for BM25 auto-sparsification and dense embedding
+- Search path: single backend
+ - dense mode: dense ANN only
+ - hybrid mode: dense ANN + BM25 sparse ANN fused by RRFRanker
+- Remove/disable legacy SDK `search` / `searchSimilarDocumentsWithSdk` knowledge path and mode routing (`sdk|spring|auto`)
+- KnowledgeSearchPort adapter calls the single backend
+- Config: collection name, search mode, rrf-k, path weights optional
+- Tests for store mapping/fusion wiring; no dependency on live Zilliz in unit tests
+
+## Non-goals
+
+- Automatic full production reindex job UI
+- Cross-encoder model rerank
+- Keeping dual Spring-AI + SDK knowledge search modes
+
+## Impact
+
+- **Breaking for existing `biz` collection**: requires new hybrid collection + reindex
+- Agent tool contract unchanged
+- Interface: L2 internal retrieval backend
+
+## Depends on
+
+- Delivery 1 chunk identity
+- Delivery 2 SearchPort (will replace sparse-lite hybrid implementation)
diff --git a/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/specs/rag-bm25-hybrid/spec.md b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/specs/rag-bm25-hybrid/spec.md
new file mode 100644
index 0000000..4f62034
--- /dev/null
+++ b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/specs/rag-bm25-hybrid/spec.md
@@ -0,0 +1,31 @@
+# rag-bm25-hybrid Specification
+
+## ADDED Requirements
+
+### Requirement: Knowledge vector backend SHALL be a single Milvus V2 store
+
+Knowledge indexing and retrieval SHALL use one MilvusClientV2-backed store. Legacy MilvusServiceClient SDK search modes (`sdk`, `auto` fallback to SDK) SHALL NOT be used for `lookup_knowledge`.
+
+#### Scenario: No SDK search mode
+
+- **WHEN** knowledge retrieval executes
+- **THEN** it SHALL NOT call legacy SDK `search` APIs for candidate generation
+
+### Requirement: Hybrid mode SHALL fuse dense ANN and BM25 sparse ANN
+
+When search mode is hybrid, the store SHALL query dense vectors and BM25 sparse vectors and fuse results with RRF (or equivalent ranker) before returning hits.
+
+#### Scenario: Hybrid uses BM25 text query
+
+- **WHEN** hybrid search runs with a text query
+- **THEN** one search leg SHALL use the BM25/sparse field with the raw query text
+- **AND** another leg SHALL use the dense embedding of the query
+
+### Requirement: Write path SHALL populate BM25 input text and dense vectors
+
+Document chunk indexing SHALL write original content, BM25 input text, dense embedding, and metadata required for chunk identity.
+
+#### Scenario: Index writes search_text and vector
+
+- **WHEN** a chunk is indexed
+- **THEN** the store row SHALL include searchable text for BM25 and a dense vector field
diff --git a/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/tasks.md b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/tasks.md
new file mode 100644
index 0000000..c2f4675
--- /dev/null
+++ b/openspec/changes/archive/2026-07-27-rag-bm25-hybrid-drop-sdk/tasks.md
@@ -0,0 +1,10 @@
+# Tasks
+
+- [x] 1. Add MilvusClientV2 factory + hybrid collection bootstrap
+- [x] 2. Implement MilvusHybridKnowledgeStore (insert/delete/dense/hybrid)
+- [x] 3. Point VectorIndexService writes at V2 store
+- [x] 4. Replace VectorSearchService knowledge path with V2 store (drop SDK/spring mode routing)
+- [x] 5. Update VectorKnowledgeSearchAdapter to delegate single backend
+- [x] 6. Config + constants for collection/mode
+- [x] 7. Unit tests (store routing / adapter / no SDK search)
+- [x] 8. Archive + commit
diff --git a/openspec/specs/rag-bm25-hybrid/spec.md b/openspec/specs/rag-bm25-hybrid/spec.md
new file mode 100644
index 0000000..739ec8c
--- /dev/null
+++ b/openspec/specs/rag-bm25-hybrid/spec.md
@@ -0,0 +1,33 @@
+# rag-bm25-hybrid Specification
+
+## Purpose
+TBD - created by archiving change rag-bm25-hybrid-drop-sdk. Update Purpose after archive.
+## Requirements
+### Requirement: Knowledge vector backend SHALL be a single Milvus V2 store
+
+Knowledge indexing and retrieval SHALL use one MilvusClientV2-backed store. Legacy MilvusServiceClient SDK search modes (`sdk`, `auto` fallback to SDK) SHALL NOT be used for `lookup_knowledge`.
+
+#### Scenario: No SDK search mode
+
+- **WHEN** knowledge retrieval executes
+- **THEN** it SHALL NOT call legacy SDK `search` APIs for candidate generation
+
+### Requirement: Hybrid mode SHALL fuse dense ANN and BM25 sparse ANN
+
+When search mode is hybrid, the store SHALL query dense vectors and BM25 sparse vectors and fuse results with RRF (or equivalent ranker) before returning hits.
+
+#### Scenario: Hybrid uses BM25 text query
+
+- **WHEN** hybrid search runs with a text query
+- **THEN** one search leg SHALL use the BM25/sparse field with the raw query text
+- **AND** another leg SHALL use the dense embedding of the query
+
+### Requirement: Write path SHALL populate BM25 input text and dense vectors
+
+Document chunk indexing SHALL write original content, BM25 input text, dense embedding, and metadata required for chunk identity.
+
+#### Scenario: Index writes search_text and vector
+
+- **WHEN** a chunk is indexed
+- **THEN** the store row SHALL include searchable text for BM25 and a dense vector field
+
diff --git a/src/main/java/com/superbiz/agent/config/MilvusConfig.java b/src/main/java/com/superbiz/agent/config/MilvusConfig.java
index e35587c..a196412 100644
--- a/src/main/java/com/superbiz/agent/config/MilvusConfig.java
+++ b/src/main/java/com/superbiz/agent/config/MilvusConfig.java
@@ -1,51 +1,22 @@
package com.superbiz.agent.config;
-import io.milvus.client.MilvusServiceClient;
-import com.superbiz.agent.client.MilvusClientFactory;
+import com.superbiz.agent.service.milvus.MilvusHybridKnowledgeStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import jakarta.annotation.PreDestroy;
-
/**
- * Milvus 配置类
- * 负责创建和管理 MilvusServiceClient Bean
+ * Milvus configuration notes.
+ *
+ *
Knowledge RAG uses {@link MilvusHybridKnowledgeStore} (MilvusClientV2) exclusively.
+ * Legacy {@code MilvusServiceClient} bean is no longer created for the knowledge path.
*/
@Configuration
public class MilvusConfig {
private static final Logger logger = LoggerFactory.getLogger(MilvusConfig.class);
- @Autowired
- private MilvusClientFactory milvusClientFactory;
-
- private MilvusServiceClient milvusClient;
-
- /**
- * 创建 MilvusServiceClient Bean
- *
- * @return MilvusServiceClient 实例
- */
- @Bean
- public MilvusServiceClient milvusServiceClient() {
- logger.info("正在初始化 Milvus 客户端...");
- milvusClient = milvusClientFactory.createClient();
- logger.info("Milvus 客户端初始化完成");
- return milvusClient;
- }
-
- /**
- * 应用关闭时清理资源
- */
- @PreDestroy
- public void cleanup() {
- if (milvusClient != null) {
- logger.info("正在关闭 Milvus 客户端连接...");
- milvusClient.close();
- logger.info("Milvus 客户端连接已关闭");
- }
+ public MilvusConfig() {
+ logger.info("Milvus knowledge path: MilvusClientV2 hybrid store only (legacy SDK search disabled)");
}
}
diff --git a/src/main/java/com/superbiz/agent/constant/MilvusConstants.java b/src/main/java/com/superbiz/agent/constant/MilvusConstants.java
index 9c4475f..a274833 100644
--- a/src/main/java/com/superbiz/agent/constant/MilvusConstants.java
+++ b/src/main/java/com/superbiz/agent/constant/MilvusConstants.java
@@ -8,9 +8,14 @@ public class MilvusConstants {
public static final String MILVUS_DB_NAME = "default";
/**
- * Milvus 集合名称
+ * Legacy collection name (pre hybrid). Knowledge path uses milvus.collection (default biz_hybrid).
*/
public static final String MILVUS_COLLECTION_NAME = "biz";
+
+ /**
+ * Default hybrid dense+BM25 collection.
+ */
+ public static final String MILVUS_HYBRID_COLLECTION_NAME = "biz_hybrid";
/**
* 向量维度(豆包 embedding 模型的维度)
diff --git a/src/main/java/com/superbiz/agent/controller/MilvusCheckController.java b/src/main/java/com/superbiz/agent/controller/MilvusCheckController.java
index 8771db4..458b604 100644
--- a/src/main/java/com/superbiz/agent/controller/MilvusCheckController.java
+++ b/src/main/java/com/superbiz/agent/controller/MilvusCheckController.java
@@ -1,47 +1,36 @@
package com.superbiz.agent.controller;
-import io.milvus.client.MilvusServiceClient;
-import io.milvus.grpc.ShowCollectionsResponse;
-import io.milvus.param.R;
-import io.milvus.param.collection.ShowCollectionsParam;
+import com.superbiz.agent.service.milvus.MilvusHybridKnowledgeStore;
+import io.milvus.v2.service.collection.response.ListCollectionsResp;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.*;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
- * Milvus 测试控制器
- * 用于测试数据库连接和数据读取
+ * Milvus health check using the single V2 knowledge backend.
*/
@RestController
@RequestMapping("/milvus")
public class MilvusCheckController {
@Autowired
- private MilvusServiceClient milvusClient;
+ private MilvusHybridKnowledgeStore knowledgeStore;
- /**
- * 简单的健康检查
- */
@GetMapping("/health")
public ResponseEntity