embedflow 0.6.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. {embedflow-0.6.0 → embedflow-0.7.0}/CHANGELOG.md +12 -0
  2. {embedflow-0.6.0 → embedflow-0.7.0}/CITATION.cff +1 -1
  3. {embedflow-0.6.0 → embedflow-0.7.0}/PKG-INFO +22 -2
  4. {embedflow-0.6.0 → embedflow-0.7.0}/README.md +20 -1
  5. {embedflow-0.6.0 → embedflow-0.7.0}/README_PYPI.md +21 -1
  6. {embedflow-0.6.0 → embedflow-0.7.0}/docs/api.md +8 -0
  7. {embedflow-0.6.0 → embedflow-0.7.0}/docs/cli.md +7 -0
  8. {embedflow-0.6.0 → embedflow-0.7.0}/docs/configuration.md +47 -0
  9. {embedflow-0.6.0 → embedflow-0.7.0}/docs/limitations.md +1 -1
  10. {embedflow-0.6.0 → embedflow-0.7.0}/docs/releasing.md +8 -8
  11. embedflow-0.7.0/docs/shadow-mode.md +104 -0
  12. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/__init__.py +3 -3
  13. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/cache/persistent_cache.py +23 -0
  14. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/cli.py +140 -10
  15. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/config.py +169 -0
  16. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/facade.py +50 -4
  17. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/materializer.py +65 -2
  18. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/runtime.py +60 -2
  19. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/serving/api.py +26 -3
  20. embedflow-0.7.0/embedflow/serving/engine.py +630 -0
  21. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/serving/schemas.py +1 -0
  22. embedflow-0.7.0/embedflow/shadow/__init__.py +9 -0
  23. embedflow-0.7.0/embedflow/shadow/models.py +60 -0
  24. embedflow-0.7.0/embedflow/shadow/report.py +48 -0
  25. embedflow-0.7.0/embedflow/shadow/runner.py +328 -0
  26. embedflow-0.7.0/embedflow/shadow/telemetry.py +773 -0
  27. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/PKG-INFO +22 -2
  28. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/SOURCES.txt +10 -0
  29. embedflow-0.7.0/examples/shadow/README.md +13 -0
  30. embedflow-0.7.0/examples/shadow/embedflow.yaml.example +25 -0
  31. embedflow-0.7.0/examples/shadow/run_demo.py +95 -0
  32. embedflow-0.7.0/examples/shadow/run_demo.sh +10 -0
  33. {embedflow-0.6.0 → embedflow-0.7.0}/pyproject.toml +1 -1
  34. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/release_gate.py +16 -4
  35. embedflow-0.6.0/embedflow/serving/engine.py +0 -228
  36. {embedflow-0.6.0 → embedflow-0.7.0}/CONTRIBUTING.md +0 -0
  37. {embedflow-0.6.0 → embedflow-0.7.0}/LICENSE +0 -0
  38. {embedflow-0.6.0 → embedflow-0.7.0}/MANIFEST.in +0 -0
  39. {embedflow-0.6.0 → embedflow-0.7.0}/SECURITY.md +0 -0
  40. {embedflow-0.6.0 → embedflow-0.7.0}/docs/assets/README.md +0 -0
  41. {embedflow-0.6.0 → embedflow-0.7.0}/docs/assets/candidate-gap-example.svg +0 -0
  42. {embedflow-0.6.0 → embedflow-0.7.0}/docs/assets/dashboard-screenshot.md +0 -0
  43. {embedflow-0.6.0 → embedflow-0.7.0}/docs/assets/terminal-demo.txt +0 -0
  44. {embedflow-0.6.0 → embedflow-0.7.0}/docs/concepts.md +0 -0
  45. {embedflow-0.6.0 → embedflow-0.7.0}/docs/contributing-benchmarks.md +0 -0
  46. {embedflow-0.6.0 → embedflow-0.7.0}/docs/economics.md +0 -0
  47. {embedflow-0.6.0 → embedflow-0.7.0}/docs/installation.md +0 -0
  48. {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/faiss.md +0 -0
  49. {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/milvus.md +0 -0
  50. {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/pgvector.md +0 -0
  51. {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/pinecone.md +0 -0
  52. {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/qdrant.md +0 -0
  53. {embedflow-0.6.0 → embedflow-0.7.0}/docs/integrations/weaviate.md +0 -0
  54. {embedflow-0.6.0 → embedflow-0.7.0}/docs/methodology.md +0 -0
  55. {embedflow-0.6.0 → embedflow-0.7.0}/docs/planner.md +0 -0
  56. {embedflow-0.6.0 → embedflow-0.7.0}/docs/quickstart.md +0 -0
  57. {embedflow-0.6.0 → embedflow-0.7.0}/docs/registry.md +0 -0
  58. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/__main__.py +0 -0
  59. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/analysis.py +0 -0
  60. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/api.py +0 -0
  61. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/cache/__init__.py +0 -0
  62. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/cache/base.py +0 -0
  63. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/__init__.py +0 -0
  64. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/candidate_gap.py +0 -0
  65. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/containment.py +0 -0
  66. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/evaluate.py +0 -0
  67. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/metrics.py +0 -0
  68. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/migration_depth.py +0 -0
  69. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/probe.py +0 -0
  70. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/report.py +0 -0
  71. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/compatibility/t2.py +0 -0
  72. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/__init__.py +0 -0
  73. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/__init__.py +0 -0
  74. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/benchmark_profiles.jsonl +0 -0
  75. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/checksums.sha256 +0 -0
  76. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/migrations.jsonl +0 -0
  77. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/registry_manifest.json +0 -0
  78. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/research_summaries.json +0 -0
  79. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/data/registry/schema_version.json +0 -0
  80. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/frozen/T2_V1_FROZEN_SPEC.md +0 -0
  81. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/frozen/T2_V1_FROZEN_SPEC.sha256 +0 -0
  82. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/__init__.py +0 -0
  83. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/base.py +0 -0
  84. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/faiss_backend.py +0 -0
  85. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/milvus_backend.py +0 -0
  86. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/pgvector_backend.py +0 -0
  87. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/pinecone_backend.py +0 -0
  88. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/qdrant_backend.py +0 -0
  89. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/indexes/weaviate_backend.py +0 -0
  90. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/metrics/__init__.py +0 -0
  91. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/metrics/latency.py +0 -0
  92. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/__init__.py +0 -0
  93. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/compatibility.py +0 -0
  94. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/planner.py +0 -0
  95. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/migration/state.py +0 -0
  96. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/models/__init__.py +0 -0
  97. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/models/base.py +0 -0
  98. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/models/huggingface.py +0 -0
  99. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/__init__.py +0 -0
  100. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/economics.py +0 -0
  101. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/models.py +0 -0
  102. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/planner.py +0 -0
  103. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/planner/rendering.py +0 -0
  104. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/registry/__init__.py +0 -0
  105. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/registry/loader.py +0 -0
  106. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/registry/matcher.py +0 -0
  107. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/registry/schema.py +0 -0
  108. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/serving/__init__.py +0 -0
  109. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow/serving/factory.py +0 -0
  110. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/dependency_links.txt +0 -0
  111. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/entry_points.txt +0 -0
  112. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/requires.txt +0 -0
  113. {embedflow-0.6.0 → embedflow-0.7.0}/embedflow.egg-info/top_level.txt +0 -0
  114. {embedflow-0.6.0 → embedflow-0.7.0}/examples/faiss/README.md +0 -0
  115. {embedflow-0.6.0 → embedflow-0.7.0}/examples/faiss/documents.jsonl +0 -0
  116. {embedflow-0.6.0 → embedflow-0.7.0}/examples/faiss/embedflow.yaml +0 -0
  117. {embedflow-0.6.0 → embedflow-0.7.0}/examples/faiss/queries.jsonl +0 -0
  118. {embedflow-0.6.0 → embedflow-0.7.0}/examples/milvus/README.md +0 -0
  119. {embedflow-0.6.0 → embedflow-0.7.0}/examples/milvus/compose.yaml +0 -0
  120. {embedflow-0.6.0 → embedflow-0.7.0}/examples/milvus/embedflow.yaml.example +0 -0
  121. {embedflow-0.6.0 → embedflow-0.7.0}/examples/milvus/run_demo.sh +0 -0
  122. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/README.md +0 -0
  123. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/build_index.py +0 -0
  124. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/compose.yaml +0 -0
  125. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/embedflow.yaml +0 -0
  126. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/init.sql +0 -0
  127. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/queries.jsonl +0 -0
  128. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pgvector/run_demo.sh +0 -0
  129. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pinecone/README.md +0 -0
  130. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pinecone/embedflow.yaml.example +0 -0
  131. {embedflow-0.6.0 → embedflow-0.7.0}/examples/pinecone/run_smoke.sh +0 -0
  132. {embedflow-0.6.0 → embedflow-0.7.0}/examples/planner/README.md +0 -0
  133. {embedflow-0.6.0 → embedflow-0.7.0}/examples/planner/run_demo.sh +0 -0
  134. {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/README.md +0 -0
  135. {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/build_index.py +0 -0
  136. {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/documents.jsonl +0 -0
  137. {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/embedflow.yaml +0 -0
  138. {embedflow-0.6.0 → embedflow-0.7.0}/examples/qdrant/queries.jsonl +0 -0
  139. {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/README.md +0 -0
  140. {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/documents.jsonl +0 -0
  141. {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/embedflow.yaml +0 -0
  142. {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/qrels.json +0 -0
  143. {embedflow-0.6.0 → embedflow-0.7.0}/examples/research_analysis/queries.jsonl +0 -0
  144. {embedflow-0.6.0 → embedflow-0.7.0}/examples/weaviate/README.md +0 -0
  145. {embedflow-0.6.0 → embedflow-0.7.0}/examples/weaviate/compose.yaml +0 -0
  146. {embedflow-0.6.0 → embedflow-0.7.0}/examples/weaviate/embedflow.yaml.example +0 -0
  147. {embedflow-0.6.0 → embedflow-0.7.0}/examples/weaviate/run_demo.sh +0 -0
  148. {embedflow-0.6.0 → embedflow-0.7.0}/frozen/T2_V1_FROZEN_SPEC.md +0 -0
  149. {embedflow-0.6.0 → embedflow-0.7.0}/frozen/T2_V1_FROZEN_SPEC.sha256 +0 -0
  150. {embedflow-0.6.0 → embedflow-0.7.0}/requirements-dev.txt +0 -0
  151. {embedflow-0.6.0 → embedflow-0.7.0}/requirements.txt +0 -0
  152. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/milvus_fixture.py +0 -0
  153. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/pinecone_smoke.py +0 -0
  154. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/real_qdrant_smoke.py +0 -0
  155. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/run_demo.sh +0 -0
  156. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/run_tests.sh +0 -0
  157. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/validate_milvus.py +0 -0
  158. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/validate_pgvector_10k.py +0 -0
  159. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/validate_weaviate.py +0 -0
  160. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/weaviate_fixture.py +0 -0
  161. {embedflow-0.6.0 → embedflow-0.7.0}/scripts/weaviate_smoke.py +0 -0
  162. {embedflow-0.6.0 → embedflow-0.7.0}/setup.cfg +0 -0
  163. {embedflow-0.6.0 → embedflow-0.7.0}/src/__init__.py +0 -0
  164. {embedflow-0.6.0 → embedflow-0.7.0}/src/embed.py +0 -0
  165. {embedflow-0.6.0 → embedflow-0.7.0}/src/probe_features.py +0 -0
  166. {embedflow-0.6.0 → embedflow-0.7.0}/src/storage.py +0 -0
  167. {embedflow-0.6.0 → embedflow-0.7.0}/src/t2_v1.py +0 -0
  168. {embedflow-0.6.0 → embedflow-0.7.0}/src/utils.py +0 -0
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.7.0 — Source-authoritative Shadow Mode
4
+
5
+ - Added bounded, deterministic, source-authoritative Shadow Mode for observing
6
+ target reranking on sampled traffic without delaying or changing source
7
+ responses.
8
+ - Added failure/timeout isolation, queue backpressure, optional asynchronous
9
+ target-cache materialization, target-coverage and ranking diagnostics, and
10
+ privacy-conscious SQLite telemetry.
11
+ - Added `embedflow shadow report`, API/status integration, documentation, and a
12
+ deterministic offline demonstration. Shadow reports remain operational
13
+ diagnostics and do not claim qrel-based retrieval quality.
14
+
3
15
  ## v0.6.0 — Migration planner
4
16
 
5
17
  - Added an advisory `embedflow plan` command and Python API that combine
@@ -2,7 +2,7 @@ cff-version: 1.2.0
2
2
  title: "EmbedFlow: Upgrading Legacy Embeddings Without Full Upfront Re-Embedding"
3
3
  message: "If EmbedFlow contributes to your work, please cite this software release."
4
4
  type: software
5
- version: 0.6.0
5
+ version: 0.7.0
6
6
  date-released: 2026-09-13
7
7
  repository-code: "https://github.com/arnsri33/embedflow"
8
8
  url: "https://github.com/arnsri33/embedflow"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: embedflow
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Progressive embedding-model migration over existing vector indexes.
5
5
  Author: Arnav Srivastav
6
6
  License-Expression: AGPL-3.0-only
@@ -204,6 +204,26 @@ behavior, candidate K, cache/economics projections, and staged rollout
204
204
  guidance. It never routes traffic or mutates the source index; `SAFE` is not a
205
205
  qrels-based retrieval-quality guarantee. See the [planner guide](https://github.com/arnsri33/embedflow/blob/main/docs/planner.md).
206
206
 
207
+ ### Source-authoritative Shadow Mode
208
+
209
+ Run the target migration path beside sampled traffic while always returning the
210
+ source result:
211
+
212
+ ```yaml
213
+ runtime: {mode: shadow}
214
+ shadow: {enabled: true, sample_rate: 0.10, candidate_k: 100, materialize: true}
215
+ ```
216
+
217
+ ```bash
218
+ embedflow serve --config ./embedflow.yaml
219
+ embedflow shadow report --config ./embedflow.yaml --since 24h --format json
220
+ ```
221
+
222
+ Shadow work is asynchronous, bounded, privacy-conscious, and failure isolated.
223
+ Reports contain operational and ranking-disagreement diagnostics—not qrel-based
224
+ quality guarantees—and recommendations never route canary traffic. See the
225
+ [Shadow Mode guide](https://github.com/arnsri33/embedflow/blob/main/docs/shadow-mode.md).
226
+
207
227
  ## Research
208
228
 
209
229
  For candidate depth `K`, EmbedFlow measures:
@@ -258,7 +278,7 @@ cover the remaining commands and endpoints.
258
278
 
259
279
  ## Status
260
280
 
261
- EmbedFlow v0.6.0 is a pre-1.0 release for research and early real-world
281
+ EmbedFlow v0.7.0 is a pre-1.0 release for research and early real-world
262
282
  testing. T2-v1 is an empirical finite-tail diagnostic, partial rankings can
263
283
  differ from fully warm target reranking, and ANN fidelity needs a reference
264
284
  comparison to audit.
@@ -156,6 +156,24 @@ embedflow plan --config ./embedflow.yaml --queries ./probe_queries.jsonl --forma
156
156
  `SAFE` is an empirical finite-tail signal, not a retrieval-quality guarantee.
157
157
  See [`docs/planner.md`](https://github.com/arnsri33/embedflow/blob/main/docs/planner.md).
158
158
 
159
+ Observe the reviewed migration path on real traffic without changing the
160
+ source result:
161
+
162
+ ```yaml
163
+ runtime: {mode: shadow}
164
+ shadow: {enabled: true, sample_rate: 0.10, candidate_k: 100, materialize: true}
165
+ ```
166
+
167
+ ```bash
168
+ embedflow serve --config ./embedflow.yaml
169
+ embedflow shadow report --config ./embedflow.yaml --since 24h
170
+ ```
171
+
172
+ Shadow Mode is source-authoritative, bounded, and advisory. It records cache,
173
+ coverage, latency, and ranking-disagreement diagnostics; it does not claim
174
+ retrieval-quality preservation without qrels and never routes canary traffic.
175
+ See [`docs/shadow-mode.md`](https://github.com/arnsri33/embedflow/blob/main/docs/shadow-mode.md).
176
+
159
177
  Search responses expose `COLD`, `PARTIAL`, or `WARM`, cache hits and misses,
160
178
  synchronous work, queued work, and stage timings. Once the candidate vectors
161
179
  are warm, target scoring over that candidate set is deterministic.
@@ -263,13 +281,14 @@ OpenAPI documentation; see
263
281
  - [CLI reference](https://github.com/arnsri33/embedflow/blob/main/docs/cli.md)
264
282
  - [API](https://github.com/arnsri33/embedflow/blob/main/docs/api.md)
265
283
  - [Economics](https://github.com/arnsri33/embedflow/blob/main/docs/economics.md)
284
+ - [Shadow Mode](https://github.com/arnsri33/embedflow/blob/main/docs/shadow-mode.md)
266
285
  - [Limitations](https://github.com/arnsri33/embedflow/blob/main/docs/limitations.md)
267
286
  - [Contributing](https://github.com/arnsri33/embedflow/blob/main/CONTRIBUTING.md)
268
287
  - [Security](https://github.com/arnsri33/embedflow/blob/main/SECURITY.md)
269
288
 
270
289
  ## Status
271
290
 
272
- EmbedFlow v0.6.0 is a pre-1.0 release for research and early real-world
291
+ EmbedFlow v0.7.0 is a pre-1.0 release for research and early real-world
273
292
  testing.
274
293
 
275
294
  - T2-v1 reports an empirical finite-tail diagnostic.
@@ -138,6 +138,26 @@ behavior, candidate K, cache/economics projections, and staged rollout
138
138
  guidance. It never routes traffic or mutates the source index; `SAFE` is not a
139
139
  qrels-based retrieval-quality guarantee. See the [planner guide](https://github.com/arnsri33/embedflow/blob/main/docs/planner.md).
140
140
 
141
+ ### Source-authoritative Shadow Mode
142
+
143
+ Run the target migration path beside sampled traffic while always returning the
144
+ source result:
145
+
146
+ ```yaml
147
+ runtime: {mode: shadow}
148
+ shadow: {enabled: true, sample_rate: 0.10, candidate_k: 100, materialize: true}
149
+ ```
150
+
151
+ ```bash
152
+ embedflow serve --config ./embedflow.yaml
153
+ embedflow shadow report --config ./embedflow.yaml --since 24h --format json
154
+ ```
155
+
156
+ Shadow work is asynchronous, bounded, privacy-conscious, and failure isolated.
157
+ Reports contain operational and ranking-disagreement diagnostics—not qrel-based
158
+ quality guarantees—and recommendations never route canary traffic. See the
159
+ [Shadow Mode guide](https://github.com/arnsri33/embedflow/blob/main/docs/shadow-mode.md).
160
+
141
161
  ## Research
142
162
 
143
163
  For candidate depth `K`, EmbedFlow measures:
@@ -192,7 +212,7 @@ cover the remaining commands and endpoints.
192
212
 
193
213
  ## Status
194
214
 
195
- EmbedFlow v0.6.0 is a pre-1.0 release for research and early real-world
215
+ EmbedFlow v0.7.0 is a pre-1.0 release for research and early real-world
196
216
  testing. T2-v1 is an empirical finite-tail diagnostic, partial rankings can
197
217
  differ from fully warm target reranking, and ANN fidelity needs a reference
198
218
  comparison to audit.
@@ -21,6 +21,7 @@ Interactive OpenAPI documentation is available at
21
21
  | GET | `/metrics` | Aggregated latency and queue metrics |
22
22
  | GET | `/plan` | Current migration plan |
23
23
  | GET | `/economics` | Configured economics estimate |
24
+ | GET | `/shadow/report` | Bounded Shadow Mode diagnostics |
24
25
 
25
26
  ## Search
26
27
 
@@ -48,6 +49,13 @@ The response includes the result list and migration fields such as:
48
49
  request. A partial response scores the available target vectors; it can differ
49
50
  from the fully warm ranking.
50
51
 
52
+ When `runtime.mode=shadow`, `/search` returns the source-only result and marks
53
+ `migration.source_authoritative=true`; target work is scheduled in the
54
+ background. `/shadow/report`, `/status`, and `/metrics` expose aggregate shadow
55
+ observations without raw query/document text or vectors. Shadow failures and
56
+ timeouts are isolated from the response. Ranking overlap is diagnostic and is
57
+ not a qrels-based quality claim.
58
+
51
59
  The advisory migration planner is exposed through the Python API and the
52
60
  `embedflow plan` CLI. It is intentionally not a synchronous FastAPI endpoint:
53
61
  probe analysis may load models and perform bounded candidate work, so operators
@@ -10,6 +10,7 @@ embedflow init --config ./embedflow.yaml
10
10
  embedflow analyze --config ./embedflow.yaml --output-dir ./analysis
11
11
  embedflow evaluate --config ./experiment.yaml --output-dir ./results
12
12
  embedflow plan --config ./embedflow.yaml --queries ./probe_queries.jsonl
13
+ embedflow shadow report --config ./embedflow.yaml --since 24h
13
14
  ```
14
15
 
15
16
  `analyze` is the no-target-index workflow. It uses probe queries and frozen
@@ -42,6 +43,12 @@ materialization throughput. `audit-index` checks the source index against an
42
43
  exact/reference configuration where supported. `prewarm` schedules target
43
44
  document work; it does not change source-index results.
44
45
 
46
+ Use `embedflow serve --mode shadow` to opt into source-authoritative Shadow
47
+ Mode for one process. `embedflow shadow report` reads the bounded telemetry
48
+ store and supports `--format text|json|yaml`, `--since`, `--output`, and
49
+ `--quiet`. A valid `DEFER`/`EXPAND_K` report is still an analytical success;
50
+ only invalid configuration or initialization returns a non-zero exit code.
51
+
45
52
  ## Registry and profiles
46
53
 
47
54
  ```bash
@@ -77,9 +77,38 @@ planner:
77
77
  corpus_name: null
78
78
  corpus_fingerprint: null
79
79
 
80
+ # Source-authoritative observation mode. ``runtime.mode: migration`` (the
81
+ # default) leaves Shadow Mode inactive. ``mode: shadow`` is an explicit opt-in.
82
+ runtime:
83
+ mode: migration # migration, normal, source, or shadow
84
+
85
+ shadow:
86
+ enabled: true
87
+ sample_rate: 0.10
88
+ sample_seed: 42
89
+ candidate_k: 100
90
+ materialize: true
91
+ max_inflight: 32
92
+ queue_capacity: 1000
93
+ timeout_ms: 10000
94
+ shutdown_grace_ms: 1000
95
+ telemetry:
96
+ enabled: true
97
+ path: ./.embedflow/shadow.sqlite3
98
+ retain_query_records: false
99
+ retain_query_text: false
100
+ max_records: 10000
101
+ retention_days: null
102
+ report_k: 10
103
+ min_target_coverage_for_ranking: 1.0
104
+
80
105
  state_path: ./embedflow_state.json
81
106
  ```
82
107
 
108
+ Shadow Mode always returns the source-authoritative result before target work
109
+ finishes. See [`shadow-mode.md`](shadow-mode.md) for queue, timeout, privacy,
110
+ materialization, and report semantics.
111
+
83
112
  ## Model contracts
84
113
 
85
114
  Model configuration can include `revision`, `dimension`, `max_length`,
@@ -164,6 +193,24 @@ EMBEDFLOW_PLANNER_LATENCY_BUDGET_MS
164
193
  EMBEDFLOW_PLANNER_ACCESS_TRACE
165
194
  EMBEDFLOW_PLANNER_CORPUS_NAME
166
195
  EMBEDFLOW_PLANNER_CORPUS_FINGERPRINT
196
+ EMBEDFLOW_RUNTIME_MODE
197
+ EMBEDFLOW_SHADOW_ENABLED
198
+ EMBEDFLOW_SHADOW_SAMPLE_RATE
199
+ EMBEDFLOW_SHADOW_SAMPLE_SEED
200
+ EMBEDFLOW_SHADOW_CANDIDATE_K
201
+ EMBEDFLOW_SHADOW_MATERIALIZE
202
+ EMBEDFLOW_SHADOW_MAX_INFLIGHT
203
+ EMBEDFLOW_SHADOW_QUEUE_CAPACITY
204
+ EMBEDFLOW_SHADOW_TIMEOUT_MS
205
+ EMBEDFLOW_SHADOW_SHUTDOWN_GRACE_MS
206
+ EMBEDFLOW_SHADOW_TELEMETRY_ENABLED
207
+ EMBEDFLOW_SHADOW_TELEMETRY_PATH
208
+ EMBEDFLOW_SHADOW_RETAIN_QUERY_RECORDS
209
+ EMBEDFLOW_SHADOW_RETAIN_QUERY_TEXT
210
+ EMBEDFLOW_SHADOW_MAX_RECORDS
211
+ EMBEDFLOW_SHADOW_REPORT_K
212
+ EMBEDFLOW_SHADOW_RETENTION_DAYS
213
+ EMBEDFLOW_SHADOW_MIN_TARGET_COVERAGE
167
214
  ```
168
215
 
169
216
  ## Input files
@@ -1,6 +1,6 @@
1
1
  # Limitations and release scope
2
2
 
3
- EmbedFlow v0.6.0 is a pre-1.0 release for research and early real-world
3
+ EmbedFlow v0.7.0 is a pre-1.0 release for research and early real-world
4
4
  testing. The serving path is designed to make migration experiments concrete;
5
5
  production rollout still requires application-specific validation.
6
6
 
@@ -18,8 +18,8 @@ python -m twine check dist/*
18
18
  Inspect both archives before uploading:
19
19
 
20
20
  ```bash
21
- unzip -l dist/embedflow-0.6.0-py3-none-any.whl
22
- tar -tzf dist/embedflow-0.6.0.tar.gz
21
+ unzip -l dist/embedflow-0.7.0-py3-none-any.whl
22
+ tar -tzf dist/embedflow-0.7.0.tar.gz
23
23
  sha256sum dist/*
24
24
  ```
25
25
 
@@ -32,7 +32,7 @@ Test the wheel outside the source tree:
32
32
  ```bash
33
33
  python -m venv /tmp/embedflow-wheel-test
34
34
  /tmp/embedflow-wheel-test/bin/python -m pip install --upgrade pip
35
- /tmp/embedflow-wheel-test/bin/python -m pip install dist/embedflow-0.6.0-py3-none-any.whl
35
+ /tmp/embedflow-wheel-test/bin/python -m pip install dist/embedflow-0.7.0-py3-none-any.whl
36
36
  cd /tmp
37
37
  /tmp/embedflow-wheel-test/bin/python -c "import embedflow; print(embedflow.__version__)"
38
38
  /tmp/embedflow-wheel-test/bin/embedflow --help
@@ -65,7 +65,7 @@ python -m venv /tmp/embedflow-testpypi
65
65
  /tmp/embedflow-testpypi/bin/python -m pip install \
66
66
  --index-url https://test.pypi.org/simple/ \
67
67
  --extra-index-url https://pypi.org/simple/ \
68
- embedflow==0.6.0
68
+ embedflow==0.7.0
69
69
  cd /tmp
70
70
  /tmp/embedflow-testpypi/bin/python -c "import embedflow; print(embedflow.__version__)"
71
71
  /tmp/embedflow-testpypi/bin/embedflow --help
@@ -79,11 +79,11 @@ Test optional integrations in a second clean environment:
79
79
  /tmp/embedflow-testpypi/bin/python -m pip install \
80
80
  --index-url https://test.pypi.org/simple/ \
81
81
  --extra-index-url https://pypi.org/simple/ \
82
- "embedflow[faiss,dashboard,pinecone,milvus,weaviate]==0.6.0"
82
+ "embedflow[faiss,dashboard,pinecone,milvus,weaviate]==0.7.0"
83
83
  ```
84
84
 
85
- If the same filename already exists on TestPyPI, use a pre-release such as
86
- `0.6.0rc1` for the TestPyPI-only trial. Keep production `0.6.0` unchanged.
85
+ If the same filename already exists on TestPyPI, use a pre-release for the
86
+ TestPyPI-only trial. Never overwrite the production version.
87
87
 
88
88
  ## Trusted Publishing configuration
89
89
 
@@ -115,7 +115,7 @@ above keeps the test step explicit.
115
115
  3. Run the final release gate and review the generated report.
116
116
  4. Configure the PyPI pending publisher and protected `pypi` environment.
117
117
  5. Create a Git tag and GitHub Release for the exact package version, for
118
- example `v0.6.0`.
118
+ example `v0.7.0`.
119
119
  6. Approve the `pypi` environment when the release workflow is ready.
120
120
  7. Verify the files and metadata on PyPI.
121
121
  8. Install from production PyPI in a directory outside this checkout.
@@ -0,0 +1,104 @@
1
+ # Shadow Mode
2
+
3
+ Shadow Mode runs the configured target migration path beside real traffic while
4
+ the existing source result remains authoritative. It is an observation and
5
+ cache-warming tool, not a traffic router:
6
+
7
+ ```yaml
8
+ runtime:
9
+ mode: shadow
10
+
11
+ shadow:
12
+ enabled: true
13
+ sample_rate: 0.10
14
+ sample_seed: 42
15
+ candidate_k: 100
16
+ materialize: true
17
+ max_inflight: 32
18
+ queue_capacity: 1000
19
+ timeout_ms: 10000
20
+ telemetry:
21
+ enabled: true
22
+ path: ./.embedflow/shadow.sqlite3
23
+ retain_query_records: false
24
+ retain_query_text: false
25
+ ```
26
+
27
+ Install the optional backend/model extras required by the selected
28
+ configuration, then start the ordinary service:
29
+
30
+ ```bash
31
+ pip install "embedflow[dashboard]"
32
+ embedflow serve --config embedflow.yaml
33
+ # or override the file for one process:
34
+ embedflow serve --config embedflow.yaml --mode shadow
35
+ ```
36
+
37
+ The request path performs source encoding, source ANN retrieval, and document
38
+ resolution exactly as source-only serving. It returns that result without
39
+ waiting for target encoding, cache misses, reranking, telemetry, or the
40
+ materialization worker. Shadow jobs use a bounded queue and worker pool. A full
41
+ queue drops only shadow work; a target/model/cache/telemetry failure or timeout
42
+ is recorded and cannot change the primary response.
43
+
44
+ If the target model cannot be loaded during startup, source/shadow serving
45
+ still opens with target work marked unavailable; sampled requests record the
46
+ isolated target-encoding failure. Normal migration mode remains fail-fast for
47
+ the same startup error.
48
+
49
+ `candidate_k` is explicit. The planner may suggest a value, but Shadow Mode
50
+ does not silently select one. `materialize: false` reads already cached target
51
+ vectors without changing cache or queue state. With `materialize: true`, cold
52
+ candidate IDs are deduplicated in the existing persistent materialization queue
53
+ and warmed asynchronously; synchronous target misses remain zero in Shadow
54
+ Mode. A comparison with incomplete target vectors is reported as `partial`,
55
+ with target coverage shown separately.
56
+
57
+ ## Reports
58
+
59
+ Reports use the privacy-preserving SQLite telemetry store (by default beside
60
+ the configured cache):
61
+
62
+ ```bash
63
+ embedflow shadow report --config embedflow.yaml --since 24h
64
+ embedflow shadow report --config embedflow.yaml --since 24h --format json --output shadow-report.json
65
+ ```
66
+
67
+ Use `/shadow/report` or the `shadow` section of `/status` and `/metrics` in the
68
+ FastAPI service. JSON/YAML stdout contains only the requested artifact; progress
69
+ and errors belong on stderr. `--since` accepts seconds or `s`, `m`, `h`, `d`,
70
+ and `w` suffixes.
71
+
72
+ Reports contain sampled/completed/partial/failed/timed-out/dropped counts,
73
+ cache and materialization accounting, source and shadow latency distributions,
74
+ target coverage, top-1 agreement, and top-k overlap. Shadow latency is not
75
+ user-facing latency because it is off the primary critical path. Ranking
76
+ disagreement and overlap are diagnostics, not recall, nDCG, or quality loss;
77
+ without qrels/native-target evaluation no quality guarantee is made.
78
+
79
+ T2-v1 remains the frozen window-level diagnostic. EmbedFlow does not label an
80
+ individual production query `T2 SAFE`. A report recommendation is operational
81
+ guidance only:
82
+
83
+ - `CONTINUE_SHADOW` means evidence or coverage is still limited.
84
+ - `EXPAND_K` means the accumulated T2 window requests a larger candidate pool.
85
+ - `INVESTIGATE` means failures, timeouts, or uncertain T2 behavior need review.
86
+ - `READY_FOR_CANARY_EVALUATION` means an operator may consider a separately
87
+ designed canary; it never routes traffic automatically.
88
+
89
+ Raw query text, candidate text, source vectors, target vectors, and credentials
90
+ are not persisted in telemetry. Query IDs may be retained only when explicitly
91
+ enabled, and retention is bounded by `max_records`. The telemetry database is
92
+ segmented by source/target contract and candidate-K fingerprint so unrelated
93
+ migrations are not mixed.
94
+
95
+ On shutdown EmbedFlow stops accepting new shadow work, drops queued jobs when
96
+ necessary, and waits only the configured bounded grace period. A corrupted or
97
+ unwritable telemetry file degrades observability; it does not take source
98
+ serving down. Source indexes are read-only during Shadow Mode. The planner and
99
+ Shadow Mode are separate: generate a plan first, then copy its recommended K
100
+ explicitly into a reviewed Shadow configuration.
101
+
102
+ Shadow Mode is advisory and experimental operational instrumentation. It does
103
+ not provide autonomous rollout, rollback, qrel evaluation, or ANN-fidelity
104
+ proof.
@@ -1,8 +1,8 @@
1
1
  """EmbedFlow: progressive embedding-model migration for existing indexes."""
2
2
 
3
- __version__ = "0.6.0"
3
+ __version__ = "0.7.0"
4
4
 
5
- from .config import EmbedFlowConfig, PlannerConfig, load_config
5
+ from .config import EmbedFlowConfig, PlannerConfig, RuntimeConfig, ShadowConfig, ShadowTelemetryConfig, load_config
6
6
 
7
7
 
8
8
  def plan(*args, **kwargs):
@@ -28,4 +28,4 @@ def analyze_migration(*args, **kwargs):
28
28
  return _analyze_migration(*args, **kwargs)
29
29
 
30
30
 
31
- __all__ = ["EmbedFlowConfig", "PlannerConfig", "load_config", "migrate", "plan", "analyze_migration", "__version__"]
31
+ __all__ = ["EmbedFlowConfig", "PlannerConfig", "RuntimeConfig", "ShadowConfig", "ShadowTelemetryConfig", "load_config", "migrate", "plan", "analyze_migration", "__version__"]
@@ -141,6 +141,29 @@ class SQLiteVectorCache(TargetVectorCache):
141
141
  self._db.commit()
142
142
  return values
143
143
 
144
+ def peek(self, document_ids: list[str]) -> dict[str, np.ndarray]:
145
+ """Read cached vectors without changing hit/miss telemetry.
146
+
147
+ Shadow analysis with ``materialize=false`` must not make ordinary
148
+ serving cache statistics look like migration traffic. This method is
149
+ intentionally a read-only fast path; callers that need accounting
150
+ should continue to use :meth:`get`.
151
+ """
152
+ ids = [str(x) for x in document_ids]
153
+ if not ids:
154
+ return {}
155
+ with self._lock:
156
+ self._ensure_open()
157
+ rows: list[tuple[Any, ...]] = []
158
+ for start in range(0, len(ids), self._LOOKUP_BATCH_SIZE):
159
+ chunk = ids[start:start + self._LOOKUP_BATCH_SIZE]
160
+ placeholders = ",".join("?" for _ in chunk)
161
+ rows.extend(self._db.execute(
162
+ f"SELECT document_id,model_fingerprint,dimension,dtype,vector,checksum,created_at,accessed_at "
163
+ f"FROM target_vectors WHERE model_fingerprint=? AND document_id IN ({placeholders})",
164
+ [self.model_fingerprint, *chunk]).fetchall())
165
+ return {str(row[0]): self._decode(row) for row in rows}
166
+
144
167
  def put(self, document_ids: list[str], vectors: np.ndarray) -> None:
145
168
  ids = [str(x) for x in document_ids]
146
169
  values = np.asarray(vectors, dtype=self.dtype)