Skip to content

Delphi: real-model end-to-end for the comment-embeddings table - #3049

Merged
colinmegill merged 1 commit into
edgefrom
embeddings-e2e
Oct 13, 2026
Merged

colinmegill merged 1 commit into
edgefrom
embeddings-e2e

Conversation

@colinmegill

Copy link
Copy Markdown
Member

What this adds

An automated end-to-end proof for the comment-embeddings table (DELPHI_EMBEDDINGS_TABLE, default off), plus one hardening fix in the queue embedding module.

The embeddings-table read/write code and migration 000038 are merged, but there was no automated end-to-end exercising the daemon glue (stage_embeddings, hydrate_set/hydrate_cluster/hydrate_embed_prior) together with the Python flag-on graph_embed/graph_cluster path and the real sentence-transformers/all-MiniLM-L6-v2 model. The flag should not be turned on in production until that passes. This adds it.

The proof

scripts/test-embeddings-table.sh (new workflow Embeddings table) runs a real Delphi job graph (graph_embed → graph_cluster → graph_topics → graph_narrative) through the polis-jobs daemon with the flag on and the real MiniLM model, against Postgres with every migration applied. It is wired into CI the same way the existing graph proofs run (a script invoked by a workflow), reusing the worker image that bakes the model weights at build time so the stage loads them offline (local_files_only).

It asserts, with the flag on:

  1. First run writes every comment's embedding to delphi_comment_embeddings and publishes the set with the run's results; the read-back is bit-exact (float32) to what the model produced, and the embeddings are served from the table, not the Delphi_CommentEmbeddings codec family.
  2. Second run, after comments are added, embeds only the new comments — proven against the spool the child actually wrote (the earlier comments carry the first run's write batch and are reused unchanged) — and serves a set containing all of them; the daemon hands the second run the prior served set so it knows what to skip.
  3. Parity: a single flag-on run and a single flag-off run of the same input serve the same results, with the embeddings bit-identical whether they live in the table or the codec family, and the served family set differing only by where the embeddings live.

Hardening

read_spool in the queue embedding module caps its pre-allocation by the remaining byte length, so a crafted header row count cannot drive a huge reservation before any row is read. Covered by a new unit test with a crafted header.

Scope

The flag stays default-off everywhere except this test. No migration, schema, importer, model-choice or server changes.

Notes for review

  • The model is 88 MB on disk; a cold fetch is ~11 s and happens once at worker-image build (not per stage — stages load the baked copy). The dependency image (torch/sentence-transformers) is the same build cost the existing graph real-model proof already pays.
  • MiniLM's float32 output depends on the batch a text is encoded in (~1e-7, last-bit), so the read-back ground truth is encoded in the same grouping the stage used; the parity test compares single-run vs single-run so the batching matches.
  • The clustering stage is not reproducible run to run even on identical input embeddings (the projection is seeded and stable; the clusterer is not), so the cluster-derived families are not expected to match between any two runs, independently of this flag. The proof records this (it is not caused by where the embeddings live) rather than asserting it.

🤖 Generated with Claude Code

https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s

Add a job-graph proof that runs the queue daemon with
DELPHI_EMBEDDINGS_TABLE on and the real all-MiniLM-L6-v2 model against
Postgres with every migration applied, wired into CI the way the existing
graph proofs run (scripts/test-embeddings-table.sh +
.github/workflows/embeddings-table.yml, reusing the worker image that
bakes the model weights at build time so the stage loads them offline).

With the flag on the proof shows:
- a first run writes every comment's embedding to the table and publishes
  the set with the run's results; the read-back is bit-exact (float32) to
  what the model produced;
- a second run after comments are added embeds only the new comments
  (checked against the spool the child actually wrote) and serves a set
  containing all of them, reusing the earlier rows unchanged;
- a single flag-on run and a single flag-off run of the same input serve
  the same results, with the embeddings bit-identical whether they live in
  the table or the codec family.

Also cap read_spool's pre-allocation in the queue embedding module by the
remaining byte length, so a crafted header row count cannot drive a huge
reservation; covered by a new unit test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018NVGBuYCk4EZmiz4csUv9s
@github-actions

Copy link
Copy Markdown

Delphi Coverage Report

File Stmts Miss Cover
polismath/init.py 2 0 100%
polismath/benchmarks/bench_pca.py 128 107 16%
polismath/benchmarks/bench_repness.py 81 65 20%
polismath/benchmarks/bench_update_votes.py 38 28 26%
polismath/benchmarks/benchmark_utils.py 34 0 100%
polismath/components/init.py 1 0 100%
polismath/components/config.py 165 70 58%
polismath/conversation/init.py 2 0 100%
polismath/conversation/conversation.py 1291 264 80%
polismath/conversation/group_vote_counts.py 38 2 95%
polismath/conversation/manager.py 131 42 68%
polismath/database/init.py 1 0 100%
polismath/database/dynamodb.py 392 176 55%
polismath/database/postgres.py 414 95 77%
polismath/delphi_storage/init.py 1 0 100%
polismath/delphi_storage/codec.py 295 22 93%
polismath/delphi_storage/embedding_spool.py 58 6 90%
polismath/delphi_storage/golden_corpus.py 60 9 85%
polismath/delphi_storage/import_report.py 51 3 94%
polismath/delphi_storage/legacy_import.py 171 16 91%
polismath/delphi_storage/postgres.py 49 22 55%
polismath/delphi_storage/production_import.py 429 96 78%
polismath/delphi_storage/resource.py 179 32 82%
polismath/delphi_storage/scan_pacing.py 77 0 100%
polismath/delphi_storage/writer.py 158 17 89%
polismath/empty_output.py 29 2 93%
polismath/engine_adapter.py 317 265 16%
polismath/job_child/init.py 352 51 86%
polismath/job_child/census.py 101 8 92%
polismath/pca_kmeans_rep/init.py 5 0 100%
polismath/pca_kmeans_rep/clusters.py 257 38 85%
polismath/pca_kmeans_rep/corr.py 98 17 83%
polismath/pca_kmeans_rep/group_k_smoother.py 28 0 100%
polismath/pca_kmeans_rep/legacy_kmeans.py 197 7 96%
polismath/pca_kmeans_rep/pca.py 162 19 88%
polismath/pca_kmeans_rep/repness.py 226 5 98%
polismath/poller/init.py 4 0 100%
polismath/poller/admission.py 318 15 95%
polismath/poller/backfill.py 936 32 97%
polismath/poller/capacity.py 485 27 94%
polismath/poller/capacity_queue.py 248 13 95%
polismath/poller/coordinator_bridge.py 317 317 0%
polismath/poller/math_writer.py 113 3 97%
polismath/poller/promotion.py 112 4 96%
polismath/poller/readiness.py 368 13 96%
polismath/poller/rebuild_child.py 133 5 96%
polismath/poller/service.py 665 52 92%
polismath/poller/worker_pool.py 140 0 100%
polismath/queue/executor.py 223 111 50%
polismath/regression/init.py 4 0 100%
polismath/regression/clojure_comparer.py 188 20 89%
polismath/regression/comparer.py 891 640 28%
polismath/regression/datasets.py 135 27 80%
polismath/regression/recorder.py 36 27 25%
polismath/regression/utils.py 130 85 35%
polismath/replay/init.py 2 0 100%
polismath/replay/attribution_capture.py 49 0 100%
polismath/replay/axis_continuity.py 347 24 93%
polismath/replay/certify.py 999 59 94%
polismath/replay/crosslang.py 128 2 98%
polismath/replay/decision_ties.py 55 8 85%
polismath/replay/diagnostics.py 50 7 86%
polismath/replay/driver.py 73 1 99%
polismath/replay/dsn_admission.py 36 1 97%
polismath/replay/equiv_query.py 10 0 100%
polismath/replay/event_ingress.py 95 1 99%
polismath/replay/fixture_bundle.py 815 62 92%
polismath/replay/fixture_config.py 266 11 96%
polismath/replay/fixture_extract.py 285 12 96%
polismath/replay/fixture_generate.py 103 1 99%
polismath/replay/fixture_samples.py 157 14 91%
polismath/replay/fixture_selection.py 119 0 100%
polismath/replay/fixture_survey.py 147 11 93%
polismath/replay/legacy_pca.py 29 17 41%
polismath/replay/polarity.py 319 25 92%
polismath/replay/poller_equiv.py 776 99 87%
polismath/replay/prodclone.py 193 2 99%
polismath/replay/real_data.py 146 10 93%
polismath/replay/schedule.py 205 10 95%
polismath/replay/served_math.py 159 4 97%
polismath/replay/shard_bench.py 189 65 66%
polismath/replay/stagecompare.py 890 161 82%
polismath/replay/stages.py 256 40 84%
polismath/replay/stepcompare.py 86 1 99%
polismath/replay/store.py 120 11 91%
polismath/replay/tie_capture.py 107 19 82%
polismath/replay/types.py 71 6 92%
polismath/run_math_pipeline.py 284 93 67%
polismath/tools/engine_rebuild.py 230 11 95%
polismath/types.py 22 0 100%
polismath/utils/cli_flags.py 15 0 100%
polismath/utils/clj_hash.py 44 0 100%
polismath/utils/env_flags.py 13 0 100%
polismath/utils/general.py 63 39 38%
polismath/utils/output_profile.py 66 2 97%
polismath/utils/serialization.py 10 5 50%
polismath/utils/vote_convention.py 159 5 97%
umap_narrative/501_calculate_comment_extremity.py 113 38 66%
umap_narrative/502_calculate_priorities.py 136 103 24%
umap_narrative/700_datamapplot_for_layer.py 502 502 0%
umap_narrative/701_static_datamapplot_for_layer.py 310 310 0%
umap_narrative/702_consensus_divisive_datamapplot.py 435 275 37%
umap_narrative/801_narrative_report_batch.py 666 455 32%
umap_narrative/803_check_batch_status.py 268 148 45%
umap_narrative/llm_factory_constructor/init.py 2 0 100%
umap_narrative/llm_factory_constructor/model_provider.py 234 107 54%
umap_narrative/narrative_data.py 178 65 63%
umap_narrative/numerical_stages.py 54 25 54%
umap_narrative/polismath_commentgraph/init.py 1 0 100%
umap_narrative/polismath_commentgraph/cli.py 268 268 0%
umap_narrative/polismath_commentgraph/core/init.py 3 3 0%
umap_narrative/polismath_commentgraph/core/clustering.py 108 108 0%
umap_narrative/polismath_commentgraph/core/embedding.py 104 104 0%
umap_narrative/polismath_commentgraph/lambda_handler.py 219 219 0%
umap_narrative/polismath_commentgraph/schemas/init.py 2 0 100%
umap_narrative/polismath_commentgraph/schemas/dynamo_models.py 160 9 94%
umap_narrative/polismath_commentgraph/tests/conftest.py 17 17 0%
umap_narrative/polismath_commentgraph/tests/test_clustering.py 74 74 0%
umap_narrative/polismath_commentgraph/tests/test_embedding.py 55 55 0%
umap_narrative/polismath_commentgraph/tests/test_storage.py 87 87 0%
umap_narrative/polismath_commentgraph/utils/init.py 9 7 22%
umap_narrative/polismath_commentgraph/utils/converter.py 283 237 16%
umap_narrative/polismath_commentgraph/utils/group_data.py 332 289 13%
umap_narrative/polismath_commentgraph/utils/storage.py 581 466 20%
umap_narrative/reset_conversation.py 162 53 67%
umap_narrative/run_pipeline.py 337 207 39%
umap_narrative/topic_naming.py 148 11 93%
Total 25500 7956 69%

@colinmegill
colinmegill merged commit 310b2fc into edge Oct 13, 2026
32 of 33 checks passed
@colinmegill
colinmegill deleted the embeddings-e2e branch October 13, 2026 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant