Salva 是一個自架的 Discovery Intelligence Runtime — 面向 Agent、CLI 和 API 調用的結構化檢索服務。
它不是爬蟲,而是一個事件驅動的智慧 pipeline,每次運行後積累學習能力。
- Event-triggered:由調用觸發,非定時輪詢
- API-first:REST API + MCP + CLI 三端整合
- Agent-native:MCP 是主要 agent 介面(Claude Code / Claude Desktop 直接接入)
- Review-gated compounding:content terms 可沉澱到隔離記憶,但預設不跨 run 讀取;只有同 campaign 已提升的記憶會再次注入
trigger (agent / CLI / API call)
→ Intent 解析 + domain 路由
→ KeywordGraph 擴展(依 ExecutionContext 決定是否注入 memory seeds)
→ Multi-round multi-provider 檢索
→ 提取 → BM25 去重 → 評分
→ content_terms 提取 → 沉澱進 memory
→ 輸出 entities + relations + telemetry
Round N 結果片段
→ _extract_content_terms() [B1: controller.py]
→ telemetry.metadata["content_terms"]
→ content_nodes_json 持久化 [B2: persistence/runs.py]
↓
後續 run(僅 campaign_promoted / campaign_all / global_legacy)
→ seed_from_memory() 讀取允許範圍內的 content_nodes
→ 注入圖中,擴展查詢範圍
預設值是 read_scope=none、write_mode=quarantine。單次呼叫內的多輪
KeywordGraph 仍在記憶體中運作,不需要在專案根目錄建立 cache。
{
"execution": {
"campaign_id": "naturehike-dach-2026",
"continuation_id": "channel-map-r1",
"persistence": "audit",
"memory": {
"read_scope": "campaign_promoted",
"write_mode": "quarantine"
}
}
}- Agent 負責宣告 objective、campaign、continuation 與是否需要舊記憶。
- Salva 負責強制 campaign filter、quarantine/promote 與 no-write 模式。
- 部署平台負責 auth、tenant 權限、filesystem roots、secrets。
完整契約:docs/spec/execution-context.md
配置 apps/mcp/server.py 為 Claude Code MCP extension:
{
"mcpServers": {
"salva": {
"command": "/absolute/path/to/salva/.venv/bin/python",
"args": ["-m", "apps.mcp"]
}
}
}務必用 venv 內的絕對路徑 python,不要用裸 python3——裸 python3 通常不在這個 venv 裡,抓不到 salva_core/mcp 套件,MCP server 會啟動失敗(salva-p3-execute-arms 實測時踩過這個坑)。
可用工具(14 個,apps/mcp/server.py):
| 工具 | 用途 |
|---|---|
salva_discover |
同步 discovery(小任務,≤20 筆) |
salva_job_create |
異步 job(大任務) |
salva_job_status |
輪詢 job 狀態 |
salva_job_cancel |
取消排隊中/執行中的 job |
salva_run_result |
取完整 run 結果(entities + evidence) |
salva_audit |
品質審計(評分拆解、來源分析、逐輪表現) |
salva_pilot |
下一步搜尋建議 |
salva_research_report |
產出結構化研究報告(摘要/coverage map/gap) |
salva_run_diff |
比較兩次 run 的新增/移除/變動實體 |
salva_graph_export |
匯出 run 的實體/關係圖(HIF JSON 或 DOT) |
salva_vocab |
查詢 domain 詞彙 registry |
salva_topology |
探測 query 拓撲並取得推薦路由 |
salva_plugins |
列出可用 enrichment plugin |
salva_providers |
列出可用檢索 provider |
salva_discover(以及 REST /v1/discover)支援選用參數 enable_stability_gating(MCP)/ stability(REST body,見下)——依 domain 歷史 query-family 記憶的 drift/volatility 微調評分,預設關閉,需要該 domain 已有足夠歷史記錄才會生效。詳見 salva_core/stability.py 與 salva_core/schemas.py::StabilityPolicy。
curl -X POST http://localhost:8000/v1/discover \
-H "Content-Type: application/json" \
-d '{
"objective": "find_companies",
"intent": {"market": "US", "industry": "AI hardware"},
"max_results": 10
}'# 創建 job
curl -X POST http://localhost:8000/v1/jobs \
-d '{"discovery": {...}, "wait_for_completion": false}'
# 查狀態
curl http://localhost:8000/v1/jobs/{job_id}salva find --market US --industry "AI hardware"
salva job status <job_id>
salva audit <run_id>| 端點 | 說明 |
|---|---|
POST /v1/discover |
同步 discovery |
POST /v1/jobs |
創建異步 job |
GET /v1/jobs/{job_id} |
Job 狀態 |
GET /v1/jobs/{job_id}/stream |
SSE 事件流 |
GET /v1/runs/{run_id} |
Run 結果 |
GET /v1/query-families |
依 campaign / continuation / status 查詢記憶 |
POST /v1/query-families/{memory_id}/promote |
提升已審核的 query-family memory |
GET /v1/routes |
路由目錄 |
GET /v1/providers |
供應商列表 |
POST /v1/pilot |
下一步建議 |
POST /v1/audits/{run_id} |
品質審計 |
GET /v1/hold/walk |
超圖遍歷 |
GET /v1/usage |
用量統計 |
salva/
├── apps/
│ ├── api/ # REST API (FastAPI)
│ ├── cli/ # CLI (typer)
│ └── mcp/ # MCP Server(9 tools,agent 主要入口)
├── core/
│ ├── controller.py # 協調器 + B1 content term 提取
│ ├── keyword_graph.py # 查詢圖 + B2 memory seed 注入
│ └── domain_vocab.py # 領域詞彙 registry
├── retrieval/ # 供應商適配器(SearXNG / Whoogle / DDG)
├── processing/
│ ├── dedup.py # BM25-hybrid 去重
│ └── scorer.py # 評分(injectable ScorerConfig)
├── enrichment/ # LLM/OSINT 富化(omlx bounded prompts)
├── hold/ # 超圖容器入口
├── experiments/ # 理論驗證實驗(E1–E9)
└── salva_core/
├── persistence/ # SQLite — 分模組
│ ├── db.py # Schema + migration
│ ├── runs.py # Run 記錄(含 content_nodes_json)
│ ├── memory.py # Query family memory + seed 查詢
│ ├── hold.py # n-ary 超邊、canonical entities、routing memory
│ ├── jobs.py # Job 記錄
│ ├── evidence.py # 證據鏈
│ ├── telemetry.py # 遙測
│ └── usage.py # 用量統計
├── relation_ontology.py # FtM 對齊關係類型(7 canonical + multilingual surface forms)
├── vector_backends.py # JinaOmlxVectorBackend(1024d)+ HybridHash fallback
├── schemas.py # Canonical types
├── execution.py # ExecutionContext 標準化與 metadata
└── service.py # 核心服務
# 1. 安裝
pip install -e ".[dev]"
# 2. 設置環境變數
cp .env.example .env # 或直接編輯 .env
# 必填:
# OMLX_BASE_URL=http://localhost:8140 (本地 omlx,Jina embedding + LLM)
# SALVA_SQLITE_PATH=./data/salva.db
# SEARXNG_ENABLED=false (若無本地 SearXNG;要自架見 docs/local-dev-setup.md)
# 3. 啟動 API
python3 -m uvicorn apps.api.main:app --port 8000
# 4. 健康檢查
curl http://localhost:8000/health
# 5. 測試
pytest| Backend | 啟用方式 | 用途 |
|---|---|---|
jina_omlx |
SALVA_SEMANTIC_VECTOR_BACKEND=jina_omlx |
內容語義搜索(1024d Jina v5) |
hybrid_hash |
預設 | 輕量 hash 備用(無 omlx 時自動降級) |
注意: Jina v5 小模型不適合跨字形實體名稱解析(台積電↔TSMC cosine≈0.04)。
跨語言實體合併依賴 canonical_entities + entity_aliases gazetteer(Hold C2)。
Salva 使用 n-ary 超邊表示多方關係(如 §13(d)(3) 集體持股、多方控股協議):
# 一條 acting_in_concert 超邊包含多個參與者
hyperedge: acting_in_concert
├── BlueMountain Capital [group_lead, 5.2%]
├── BM Fund A [group_member, 2.1%]
├── BM Fund B [group_member, 1.8%]
└── Chatham Lodging Trust [target]
evidence: SEC EDGAR SC 13D/A 2013-05-15支援:
- HIF(Hypergraph Interchange Format)lossless round-trip export/import
- Bipartite projection(entity ↔ hyperedge)
- Star projection(entity ↔ entity via shared hyperedge)
| VP | 主張 | 結論 |
|---|---|---|
| VP1 | n-ary 超圖忠實度 | ✅ E1 PASS |
| VP2 | 公開源可得性 | ✅ E2 PASS |
| VP3 | 真實 filing → n-ary 事實 | ✅ E3 PASS |
| VP4 | 路由表自我優化 | ✅ E4 PASS |
| VP5 | 跨語言實體解析 | ✅ gazetteer / |
| VP6 | 跨語義關係合併 | ✅ E6 PASS(7→3 canonical 超邊) |
| VP7 | 語義+二跳 > 關鍵詞 | |
| VP8 | HIF round-trip + 投影 | ✅ E8 PASS(零 diff) |
| VP9 | 持久化複利可量測 | ✅ E9 PASS(seeds 0→46,nodes 34→61) |
詳細結果:experiments/hg_penetration/E*_FINDINGS.md
E1-E9 跑在較舊的環境上;此後 Phase 1(2026-07-03)修好了幾個會直接影響檢索結果
真實性的 bug:_apply_live_probe 把「探測失敗」誤判成「確認無結構」的降級邏輯、
dev extras 沒帶到 ddgs 套件、sqlite-vec 向量後端接上。VP7(語義+二跳)與
VP9(持久化複利)的 E7/E9 結論本身沒有被重新驗證,上面表格的判定維持原樣,
沒有因為這些 bug 修復而改判。
⚠️ E10 是 N=1 的單題 dogfood(2026-06-08),下面的數字已被 2026-07-03 的salva_v218 題固定任務集實驗補充——後者用更大樣本、有人工核驗 ground truth、且比較的是「Claude Code + Haiku 裸搜尋」vs「+Salva」這個貼近實際使用 情境的框架,而不是 E10 當時未指名的 agent/model。E10 保留在此作為歷史記錄, 不代表目前對 Salva 檢索效果的最新判斷——最新結果見下方連結,不要只看 E10 這組數字。
Naturehike DACH 渠道檢索的 live dogfood 顯示:
| Condition | Round 1 | Round 2 | Round 3 | Best pooled recall |
|---|---|---|---|---|
| Agent-only | 5 verified | 11 cumulative | 15 cumulative | 88.2% |
| Salva | 2 verified | 0 snapshot | 0 snapshot | 11.8% |
Pooled recall 的分母是兩條路徑事後驗證候選的聯集,不是預先凍結的外部 ground truth。這也不是等預算 benchmark:Agent raw SERP 未完整保存、耗時不可比、Salva 使用 DDG live provider 且 R2/R3 發生零結果。它是可重現的 dogfood 與失敗模式記錄, 不能被解讀為一般性模型排名。
詳見:experiments/agent_vs_salva/README.md
18 題固定任務集(單一實體 / 跨語言實體 / 多跳關係,各 6 題),ground truth 皆人工 核驗且附來源 URL,Arm A/B 各跑 36 次 Haiku model agent 執行。核心結論:
- 召回率:17 平手、Arm B 贏 1、Arm A 贏 0——不是 Salva 更準,是因為協定允許 agent 在 Salva 沒用時退回裸搜尋,這是真實 production 行為,不是刻意放水的測試設計。
- Salva 自己的評分/篩選層在 61%(11/18)的測試裡回傳零可用實體,即使
retrieval_health100% 都是ok——問題出在QualificationScorer的評分邏輯, 不是檢索或 provider 健康。 - Arm B 總請求數比 Arm A 少 14%(38 vs 44),在同等或更好召回率下——這是獨立於 平手/贏之外的一個真實效率優勢。
完整分析、原始資料與圖表:experiments/salva_v2/ANALYSIS_FINDINGS.md
| 文件 | 用途 |
|---|---|
| CLAUDE.md | 開發者必讀:設計原則與架構邊界 |
| DEVELOPMENT_PROGRESS.md | 本次開發進度報告 |
| docs/archive/TODO.md | 開發任務清單(已封存,72/72 完成) |
| docs/spec/ | 行為契約(正式規範) |
| docs/reports/execution-isolation-update-2026-06-08.md | 隔離架構、風險與對抗審計 |
| docs/dogfood/naturehike-dach-2026-06-08.md | Naturehike DACH 渠道與 dogfood 結果 |
| experiments/EXPERIMENT_PLAN.md | 實驗計畫與驗證狀態 |
400— 驗證失敗或輸入錯誤403— Tenant 權限不足404— 找不到資源429— Quota 超限500— 內部錯誤
採用 Apache License 2.0。允許商用、修改與私有部署,並附帶專利授權;再散布時請保留版權與授權聲明。
Copyright © 2026 Ryan Lee.