cmpnd 0.7.0__tar.gz → 0.7.2__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 (227) hide show
  1. {cmpnd-0.7.0 → cmpnd-0.7.2}/PKG-INFO +2 -1
  2. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/_gepa_patch.py +9 -2
  3. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/exporter_http.py +74 -10
  4. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/optimizers/gepa.py +6 -2
  5. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/optimizers/gepa_callback.py +1 -1
  6. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/optimizers/tracker.py +11 -4
  7. {cmpnd-0.7.0 → cmpnd-0.7.2}/docs/sdk-instrumentation.md +4 -0
  8. {cmpnd-0.7.0 → cmpnd-0.7.2}/pyproject.toml +38 -1
  9. {cmpnd-0.7.0 → cmpnd-0.7.2}/scripts/seed_review_run.py +290 -7
  10. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/committee_task.py +19 -4
  11. cmpnd-0.7.2/tests/stress/_provider.py +276 -0
  12. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_deploy_programs.py +38 -8
  13. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_multi_client_stress.py +15 -3
  14. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_optimize_loop.py +8 -1
  15. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_optimize_loop_wasm.py +71 -24
  16. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_optimize_stress.py +6 -1
  17. cmpnd-0.7.2/tests/stress/test_provider_roles.py +221 -0
  18. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_real_provider_twins.py +19 -2
  19. cmpnd-0.7.2/tests/stress/test_rlm_wasm.py +411 -0
  20. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_clustering/conftest.py +2 -9
  21. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_clustering/test_edge_cases.py +2 -7
  22. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_clustering/test_hash_consistency.py +4 -11
  23. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_clustering/test_type_conversion.py +3 -8
  24. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_deploy.py +50 -0
  25. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_exporter.py +82 -0
  26. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_optimize_terminal_failure.py +78 -0
  27. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_seed_review_run.py +10 -4
  28. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_tracked_gepa.py +5 -1
  29. {cmpnd-0.7.0 → cmpnd-0.7.2}/uv.lock +58 -94
  30. cmpnd-0.7.0/tests/stress/_provider.py +0 -91
  31. {cmpnd-0.7.0 → cmpnd-0.7.2}/.gitignore +0 -0
  32. {cmpnd-0.7.0 → cmpnd-0.7.2}/CLAUDE.md +0 -0
  33. {cmpnd-0.7.0 → cmpnd-0.7.2}/README.md +0 -0
  34. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/__init__.py +0 -0
  35. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/_program_patch.py +0 -0
  36. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/_rlm_patch.py +0 -0
  37. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/callback.py +0 -0
  38. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/__init__.py +0 -0
  39. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/api_client.py +0 -0
  40. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/__init__.py +0 -0
  41. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/admin.py +0 -0
  42. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/auth.py +0 -0
  43. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/datasets.py +0 -0
  44. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/deployments.py +0 -0
  45. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/evals.py +0 -0
  46. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/login.py +0 -0
  47. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/optimizations.py +0 -0
  48. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/org.py +0 -0
  49. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/programs.py +0 -0
  50. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/top.py +0 -0
  51. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/commands/traces.py +0 -0
  52. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/credentials.py +0 -0
  53. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/shell.py +0 -0
  54. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/trace_render.py +0 -0
  55. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/cli/verbs.py +0 -0
  56. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/configuration.py +0 -0
  57. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/context.py +0 -0
  58. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/dataset_sync.py +0 -0
  59. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/datasets.py +0 -0
  60. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/decorators.py +0 -0
  61. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/deployment.py +0 -0
  62. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/eval_handler.py +0 -0
  63. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/execution.py +0 -0
  64. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/exporter.py +0 -0
  65. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/helpers.py +0 -0
  66. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/identity.py +0 -0
  67. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/ir/__init__.py +0 -0
  68. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/ir/decode.py +0 -0
  69. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/ir/encode.py +0 -0
  70. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/ir/hash.py +0 -0
  71. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/ir/program.py +0 -0
  72. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/ir/reflection.py +0 -0
  73. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/ir/types.py +0 -0
  74. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/ir/xxh64.py +0 -0
  75. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/models.py +0 -0
  76. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/optimization.py +0 -0
  77. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/optimizers/__init__.py +0 -0
  78. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/optimizers/resume.py +0 -0
  79. {cmpnd-0.7.0 → cmpnd-0.7.2}/cmpnd/packaging.py +0 -0
  80. {cmpnd-0.7.0 → cmpnd-0.7.2}/docs/2026-04-02-code-review.md +0 -0
  81. {cmpnd-0.7.0 → cmpnd-0.7.2}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  82. {cmpnd-0.7.0 → cmpnd-0.7.2}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  83. {cmpnd-0.7.0 → cmpnd-0.7.2}/examples/local_ollama.py +0 -0
  84. {cmpnd-0.7.0 → cmpnd-0.7.2}/scripts/capture_review_run.py +0 -0
  85. {cmpnd-0.7.0 → cmpnd-0.7.2}/scripts/fixtures/review_run.json.gz +0 -0
  86. {cmpnd-0.7.0 → cmpnd-0.7.2}/scripts/gepa_resume_bug.py +0 -0
  87. {cmpnd-0.7.0 → cmpnd-0.7.2}/stubs/dspy/__init__.pyi +0 -0
  88. {cmpnd-0.7.0 → cmpnd-0.7.2}/stubs/dspy/primitives/__init__.pyi +0 -0
  89. {cmpnd-0.7.0 → cmpnd-0.7.2}/stubs/dspy/primitives/prediction.pyi +0 -0
  90. {cmpnd-0.7.0 → cmpnd-0.7.2}/stubs/dspy/utils/__init__.pyi +0 -0
  91. {cmpnd-0.7.0 → cmpnd-0.7.2}/stubs/dspy/utils/callback.pyi +0 -0
  92. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/__init__.py +0 -0
  93. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/_exporter_helpers.py +0 -0
  94. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/_identity_helpers.py +0 -0
  95. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/__init__.py +0 -0
  96. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/conftest.py +0 -0
  97. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  98. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  99. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/fake_lm.py +0 -0
  100. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/helpers.py +0 -0
  101. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_capture_prompts.py +0 -0
  102. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_composite_module.py +0 -0
  103. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_dataset_auto_creation.py +0 -0
  104. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_deploy_execute.py +0 -0
  105. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_deploy_programs.py +0 -0
  106. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_edge_cases.py +0 -0
  107. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_example_hash.py +0 -0
  108. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_gepa_optimization.py +0 -0
  109. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_lm_accuracy.py +0 -0
  110. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_module_tracing.py +0 -0
  111. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_optimization_lineage.py +0 -0
  112. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_optimization_metric_identity.py +0 -0
  113. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_optimize_failures.py +0 -0
  114. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_optimize_loop.py +0 -0
  115. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_optimize_review_committee.py +0 -0
  116. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  117. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_parallel_export.py +0 -0
  118. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_project_attribution.py +0 -0
  119. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_react_tool_spans.py +0 -0
  120. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_save_load.py +0 -0
  121. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_trace_structure.py +0 -0
  122. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/e2e/test_user_attribution.py +0 -0
  123. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/01_predict_qa.json +0 -0
  124. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  125. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  126. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  127. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  128. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  129. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/07_program_of_thought.json +0 -0
  130. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/08_opaque_module.json +0 -0
  131. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  132. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/10_react.json +0 -0
  133. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/11_refine.json +0 -0
  134. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/12_retrieve.json +0 -0
  135. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/13_knn.json +0 -0
  136. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/14_parallel.json +0 -0
  137. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/identity_vectors/README.md +0 -0
  138. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/integration/__init__.py +0 -0
  139. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/integration/conftest.py +0 -0
  140. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  141. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/integration/test_logs_renders_traces.py +0 -0
  142. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/integration/test_optimizing_progress.py +0 -0
  143. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/integration/test_unhappy_path.py +0 -0
  144. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/__init__.py +0 -0
  145. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/conftest.py +0 -0
  146. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/seed_parity.py +0 -0
  147. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_auth.py +0 -0
  148. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_datasets.py +0 -0
  149. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_deployments.py +0 -0
  150. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_evals.py +0 -0
  151. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_health.py +0 -0
  152. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_modules.py +0 -0
  153. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_optimizations.py +0 -0
  154. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_programs.py +0 -0
  155. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_signatures.py +0 -0
  156. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_trace_streaming.py +0 -0
  157. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/parity/test_traces.py +0 -0
  158. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/__init__.py +0 -0
  159. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/_reliability.py +0 -0
  160. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/mint_bedrock_token.py +0 -0
  161. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_optimize_client_committee.py +0 -0
  162. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_reliability.py +0 -0
  163. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/stress/test_reliability_classify.py +0 -0
  164. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_callback.py +0 -0
  165. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/__init__.py +0 -0
  166. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/conftest.py +0 -0
  167. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_admin.py +0 -0
  168. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_api_client.py +0 -0
  169. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_auth.py +0 -0
  170. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_credentials.py +0 -0
  171. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_datasets.py +0 -0
  172. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_deployments.py +0 -0
  173. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_evals.py +0 -0
  174. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_login.py +0 -0
  175. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_main.py +0 -0
  176. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_optimizations.py +0 -0
  177. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_org.py +0 -0
  178. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_parity.py +0 -0
  179. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_programs.py +0 -0
  180. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_shell.py +0 -0
  181. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_top.py +0 -0
  182. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cli/test_traces.py +0 -0
  183. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_clustering/__init__.py +0 -0
  184. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_cmp551_grouping_identity.py +0 -0
  185. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_configuration.py +0 -0
  186. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_context.py +0 -0
  187. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_context_propagation.py +0 -0
  188. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_dataset_polling.py +0 -0
  189. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_dataset_sync.py +0 -0
  190. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_datasets.py +0 -0
  191. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_decorators.py +0 -0
  192. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_eval.py +0 -0
  193. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_eval_start.py +0 -0
  194. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_exception_paths.py +0 -0
  195. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_execute.py +0 -0
  196. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_exporter_real.py +0 -0
  197. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_fake_lm.py +0 -0
  198. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_gepa_callback.py +0 -0
  199. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_gepa_integration.py +0 -0
  200. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_gepa_resume.py +0 -0
  201. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_identity.py +0 -0
  202. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_import_weight.py +0 -0
  203. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_integration.py +0 -0
  204. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_ir_encoder_properties.py +0 -0
  205. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_ir_golden_vectors.py +0 -0
  206. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_ir_roundtrip.py +0 -0
  207. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_models.py +0 -0
  208. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_optimization_progress.py +0 -0
  209. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_optimization_start.py +0 -0
  210. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_optimize.py +0 -0
  211. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_optimize_lift.py +0 -0
  212. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_packaging.py +0 -0
  213. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_packaging_callable_source.py +0 -0
  214. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_packaging_program_source.py +0 -0
  215. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_program_lineage_persist.py +0 -0
  216. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_project_payload.py +0 -0
  217. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_reflector_properties.py +0 -0
  218. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_reflector_smoke.py +0 -0
  219. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_repro_reactv2_bugs.py +0 -0
  220. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_rlm_patch.py +0 -0
  221. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_schemas.py +0 -0
  222. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_stats_export.py +0 -0
  223. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_trace_render.py +0 -0
  224. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_tracker_enrich.py +0 -0
  225. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_truncation.py +0 -0
  226. {cmpnd-0.7.0 → cmpnd-0.7.2}/tests/test_xxh64.py +0 -0
  227. {cmpnd-0.7.0 → cmpnd-0.7.2}/todo.md +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cmpnd
3
- Version: 0.7.0
3
+ Version: 0.7.2
4
4
  Summary: DSPy observability and deployment SDK for cmpnd
5
5
  Project-URL: Homepage, https://cmpnd.ai
6
6
  Author-email: cmpnd <hello@cmpnd.ai>
@@ -27,6 +27,7 @@ Requires-Dist: basedpyright>=1.39; extra == 'dev'
27
27
  Requires-Dist: boto3>=1.34; extra == 'dev'
28
28
  Requires-Dist: hypothesis>=6.100; extra == 'dev'
29
29
  Requires-Dist: jsonschema>=4.0; extra == 'dev'
30
+ Requires-Dist: numpy>=1.26; extra == 'dev'
30
31
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
31
32
  Requires-Dist: pytest-rerunfailures>=14.0; extra == 'dev'
32
33
  Requires-Dist: pytest-xdist>=3.0; extra == 'dev'
@@ -64,7 +64,7 @@ def patch_gepa() -> None:
64
64
  # This ensures each optimization gets its own run_id, even when
65
65
  # the same GEPA instance is reused (e.g., optimizing an already-optimized program).
66
66
  trk = tracker.OptimizationTracker(
67
- optimizer_type="GEPA",
67
+ optimizer_type="gepa",
68
68
  program_name="",
69
69
  )
70
70
  callback = gepa_callback.CmpndGEPACallback(trk, defer_finalize=True)
@@ -157,7 +157,14 @@ def patch_gepa() -> None:
157
157
 
158
158
  return result
159
159
 
160
- except Exception as e:
160
+ # `BaseException`, because the invariant does not get to have an exception: a
161
+ # run that ends by ANY route must transition the row start() sent, or the
162
+ # program page shows a perpetual in-progress optimization. A backend that
163
+ # rejected a model request raises a failure that deliberately does NOT derive
164
+ # from `Exception` — that is the only way past gepa's and dspy's own blanket
165
+ # `except Exception` clauses — so narrowing this one reopens exactly the bug
166
+ # this block exists for. Re-raised either way, so widening swallows nothing.
167
+ except BaseException as e:
161
168
  if callback is not None: # pyright: ignore[reportUnnecessaryComparison]
162
169
  # Flush a terminal 'failed', not just in-memory state — else the
163
170
  # running row start() sent lingers forever.
@@ -55,6 +55,28 @@ def _send_json(client: httpx.Client, method: str, path: str, payload: Any) -> ht
55
55
  return client.request(method, path, content=body, headers=_GZIP_HEADERS)
56
56
 
57
57
 
58
+ # Gateway/overload statuses a data POST should retry rather than count as a hard
59
+ # export error: the backend (or the proxy in front of it) is transiently busy,
60
+ # not rejecting the payload. A retry is safe because serve dedups on ingest —
61
+ # traces by (org_id, trace_id, start_time) and spans by the spans_dedup index —
62
+ # so a re-POST of a request that actually landed can't double-insert.
63
+ _RETRYABLE_STATUS = frozenset({502, 503, 504})
64
+
65
+
66
+ def _is_retryable_export_error(exc: BaseException) -> bool:
67
+ """Whether a failed data send is a transient delivery blip worth retrying.
68
+
69
+ True for gateway/overload statuses (502/503/504) and httpx transport/timeout
70
+ errors (connect/read/pool timeouts, connection refused/reset). False for
71
+ everything else — 4xx (client error, a retry won't help), 429 (backpressure,
72
+ the caller records it as *dropped* not errored), and 500 (the platform's to
73
+ answer for, mirroring the is_external_outage boundary). Non-httpx exceptions
74
+ are not retried (a bug in our own build path shouldn't loop)."""
75
+ if isinstance(exc, httpx.HTTPStatusError):
76
+ return exc.response.status_code in _RETRYABLE_STATUS
77
+ return isinstance(exc, (httpx.TimeoutException, httpx.TransportError))
78
+
79
+
58
80
  @dataclass
59
81
  class ExportItem:
60
82
  """Item in the export queue."""
@@ -115,6 +137,14 @@ class BatchExporter:
115
137
  self._start_post_attempts = 3
116
138
  self._start_post_backoff = 0.5
117
139
 
140
+ # Bounded retry for the best-effort data POSTs (traces/spans/stats/…):
141
+ # a transient gateway/overload or transport blip retries with backoff
142
+ # instead of immediately counting a hard export error. Idempotent on the
143
+ # serve side (see _is_retryable_export_error). Instance attrs so tests
144
+ # can zero the backoff. Distinct from the start-post budget above.
145
+ self._data_post_attempts = 3
146
+ self._data_post_backoff = 0.5
147
+
118
148
  # Guard against registering multiple exit handlers on repeated start()
119
149
  self._atexit_registered = False
120
150
 
@@ -440,6 +470,34 @@ class BatchExporter:
440
470
  raise RuntimeError("BackgroundExporter has no HTTP client (not started or already shut down)")
441
471
  return self._client
442
472
 
473
+ def _send_json_retrying(self, client: httpx.Client, method: str, path: str, payload: Any) -> httpx.Response:
474
+ """`_send_json` + `raise_for_status`, retrying transient delivery blips.
475
+
476
+ Retries `_data_post_attempts` times with exponential backoff on a
477
+ retryable failure (502/503/504 or a transport/timeout error — see
478
+ `_is_retryable_export_error`), then re-raises. A non-retryable failure
479
+ (4xx, 429, 500, or a 2xx that somehow raised) propagates on the first
480
+ attempt, so the caller's existing except blocks still record it (429 →
481
+ dropped, everything else → error) exactly as before — the only change
482
+ is that a transient blip now gets a few tries before it counts."""
483
+ last_exc: Exception | None = None
484
+ for attempt in range(self._data_post_attempts):
485
+ try:
486
+ response = _send_json(client, method, path, payload)
487
+ response.raise_for_status()
488
+ return response
489
+ except Exception as e:
490
+ last_exc = e
491
+ if _is_retryable_export_error(e) and attempt + 1 < self._data_post_attempts:
492
+ logger.debug(
493
+ "cmpnd: retrying %s %s after transient error (attempt %d): %s", method, path, attempt + 1, e
494
+ )
495
+ time.sleep(self._data_post_backoff * (2**attempt))
496
+ continue
497
+ raise
498
+ assert last_exc is not None # unreachable: the loop either returns or raises
499
+ raise last_exc
500
+
443
501
  def _flush_batch(self, batch: list[ExportItem]) -> None:
444
502
  """Send a batch of items to the backend with parallel dispatch."""
445
503
  if not batch or not self._client:
@@ -600,9 +658,9 @@ class BatchExporter:
600
658
  total_spans = sum(len(t.get("spans", [])) for t in traces_to_send)
601
659
  try:
602
660
  if len(traces_to_send) == 1:
603
- response = _send_json(client, "POST", "/api/v1/traces", traces_to_send[0])
661
+ response = self._send_json_retrying(client, "POST", "/api/v1/traces", traces_to_send[0])
604
662
  else:
605
- response = _send_json(client, "POST", "/api/v1/traces/batch", traces_to_send)
663
+ response = self._send_json_retrying(client, "POST", "/api/v1/traces/batch", traces_to_send)
606
664
  response.raise_for_status()
607
665
  with self._stats_lock:
608
666
  self._exported_count += len(traces_to_send) + total_spans
@@ -627,7 +685,7 @@ class BatchExporter:
627
685
  client = self._require_client()
628
686
  for trace_id, start_dict in trace_starts:
629
687
  try:
630
- response = _send_json(client, "POST", f"/api/v1/traces/{trace_id}/start", start_dict)
688
+ response = self._send_json_retrying(client, "POST", f"/api/v1/traces/{trace_id}/start", start_dict)
631
689
  response.raise_for_status()
632
690
  with self._stats_lock:
633
691
  self._exported_count += 1
@@ -641,7 +699,9 @@ class BatchExporter:
641
699
  client = self._require_client()
642
700
  for trace_id, span_list in spans_by_trace.items():
643
701
  try:
644
- response = _send_json(client, "POST", f"/api/v1/traces/{trace_id}/spans", {"spans": span_list})
702
+ response = self._send_json_retrying(
703
+ client, "POST", f"/api/v1/traces/{trace_id}/spans", {"spans": span_list}
704
+ )
645
705
  response.raise_for_status()
646
706
  with self._stats_lock:
647
707
  self._exported_count += len(span_list)
@@ -655,7 +715,9 @@ class BatchExporter:
655
715
  client = self._require_client()
656
716
  for trace_id, finalize_dict in trace_finalizes:
657
717
  try:
658
- response = _send_json(client, "POST", f"/api/v1/traces/{trace_id}/finalize", finalize_dict)
718
+ response = self._send_json_retrying(
719
+ client, "POST", f"/api/v1/traces/{trace_id}/finalize", finalize_dict
720
+ )
659
721
  response.raise_for_status()
660
722
  with self._stats_lock:
661
723
  self._exported_count += 1
@@ -668,7 +730,7 @@ class BatchExporter:
668
730
  def _send_stats(self, stats_to_send: list[dict[str, Any]]) -> None:
669
731
  client = self._require_client()
670
732
  try:
671
- response = _send_json(client, "POST", "/api/v1/traces/stats", stats_to_send)
733
+ response = self._send_json_retrying(client, "POST", "/api/v1/traces/stats", stats_to_send)
672
734
  response.raise_for_status()
673
735
  with self._stats_lock:
674
736
  self._exported_count += len(stats_to_send)
@@ -686,7 +748,7 @@ class BatchExporter:
686
748
  eval_run_id = str(eval_run_dict.get("eval_run_id"))
687
749
  num_examples = len(eval_run_dict.get("examples", []))
688
750
  try:
689
- response = _send_json(client, "PATCH", f"/api/v1/evals/{eval_run_id}", eval_run_dict)
751
+ response = self._send_json_retrying(client, "PATCH", f"/api/v1/evals/{eval_run_id}", eval_run_dict)
690
752
  response.raise_for_status()
691
753
  with self._stats_lock:
692
754
  self._exported_count += 1 + num_examples
@@ -703,7 +765,7 @@ class BatchExporter:
703
765
  num_iterations = len(opt_run_dict.get("iterations", []))
704
766
  num_candidates = len(opt_run_dict.get("candidates", []))
705
767
  try:
706
- response = _send_json(client, "POST", "/api/v1/optimizations", opt_run_dict)
768
+ response = self._send_json_retrying(client, "POST", "/api/v1/optimizations", opt_run_dict)
707
769
  response.raise_for_status()
708
770
  with self._stats_lock:
709
771
  self._exported_count += 1
@@ -722,7 +784,9 @@ class BatchExporter:
722
784
  client = self._require_client()
723
785
  for opt_run_id, progress_dict, trk in optimization_progress_to_send:
724
786
  try:
725
- response = _send_json(client, "PATCH", f"/api/v1/optimizations/{opt_run_id}/progress", progress_dict)
787
+ response = self._send_json_retrying(
788
+ client, "PATCH", f"/api/v1/optimizations/{opt_run_id}/progress", progress_dict
789
+ )
726
790
  response.raise_for_status()
727
791
  # Delivery-acknowledged high-water: only advance past these
728
792
  # iteration_points now that the PATCH succeeded (2xx, no
@@ -745,7 +809,7 @@ class BatchExporter:
745
809
  num_iterations = len(opt_run_dict.get("iterations", []))
746
810
  num_candidates = len(opt_run_dict.get("candidates", []))
747
811
  try:
748
- response = _send_json(client, "PUT", f"/api/v1/optimizations/{opt_run_id}", opt_run_dict)
812
+ response = self._send_json_retrying(client, "PUT", f"/api/v1/optimizations/{opt_run_id}", opt_run_dict)
749
813
  response.raise_for_status()
750
814
  with self._stats_lock:
751
815
  self._exported_count += 1
@@ -152,7 +152,7 @@ class TrackedGEPA:
152
152
 
153
153
  # Initialize optimization tracking
154
154
  self._tracker = tracker.OptimizationTracker(
155
- optimizer_type="GEPA",
155
+ optimizer_type="gepa",
156
156
  program_name=student.__class__.__name__,
157
157
  config=config,
158
158
  model_name=model_name,
@@ -218,7 +218,11 @@ class TrackedGEPA:
218
218
 
219
219
  return result
220
220
 
221
- except Exception as e:
221
+ # `BaseException` for the reason `_gepa_patch._patched_compile` documents: the
222
+ # row must transition however the run ends, and a rejected model request
223
+ # reaches here as something that is not an `Exception`, by design. Re-raised
224
+ # either way.
225
+ except BaseException as e:
222
226
  if self._tracker:
223
227
  # Flush a terminal 'failed', not just in-memory state — else the
224
228
  # running row start() sent lingers forever.
@@ -5,7 +5,7 @@ monkey-patching or wrapper classes. Replaces the monkey-patched
5
5
  InstrumentedDspyAdapter approach used by TrackedGEPA.
6
6
 
7
7
  Usage:
8
- tracker = OptimizationTracker(optimizer_type="GEPA", ...)
8
+ tracker = OptimizationTracker(optimizer_type="gepa", ...)
9
9
  callback = CmpndGEPACallback(tracker)
10
10
  gepa = dspy.GEPA(gepa_kwargs={"callbacks": [callback]})
11
11
  """
@@ -199,7 +199,7 @@ class OptimizationTracker:
199
199
 
200
200
  Usage:
201
201
  tracker = OptimizationTracker(
202
- optimizer_type="GEPA",
202
+ optimizer_type="gepa",
203
203
  program_name="MyProgram",
204
204
  config={"auto": "light", "seed": 42}
205
205
  )
@@ -634,14 +634,21 @@ class OptimizationTracker:
634
634
  except Exception as e:
635
635
  logger.warning(f"Failed to capture compiled program state: {e}")
636
636
 
637
- def set_error(self, exception: Exception) -> None:
638
- """Mark the optimization run as failed (in-memory only)."""
637
+ def set_error(self, exception: BaseException) -> None:
638
+ """Mark the optimization run as failed (in-memory only).
639
+
640
+ ``BaseException`` and not ``Exception``: a backend that rejected a model request
641
+ reports it as a failure that deliberately does not derive from ``Exception``,
642
+ since that is the only thing gepa's and dspy's blanket ``except Exception``
643
+ clauses do not swallow. A run that ended is a run that has to be recorded as
644
+ ended, whichever base class carried the reason.
645
+ """
639
646
  self.status = "failed"
640
647
  self.error_message = str(exception)
641
648
  self.end_time = datetime.now(timezone.utc)
642
649
  logger.error(f"Optimization failed: {exception}")
643
650
 
644
- def finalize_error(self, exception: Exception) -> None:
651
+ def finalize_error(self, exception: BaseException) -> None:
645
652
  """Mark the run failed AND flush the terminal 'failed' to the backend.
646
653
 
647
654
  The success path is finalize(); on an optimizer exception the run must
@@ -154,6 +154,10 @@ Override any of these by passing kwargs to `cmpnd.configure(...)`.
154
154
  - `_flush_batch` at `exporter_http.py:443` sorts items by type (`trace`, `span`, `trace_start`, `trace_finalize`, `trace_stats`, `eval_run`, `optimization_run`, various optimization progress records), buffers loose spans keyed by `trace_id` so they can be attached to their parent trace when it arrives, and submits each type's `_send_*` call to the thread pool in parallel.
155
155
  - `shutdown()` at `exporter_http.py:163` sets the shutdown event, joins the worker thread, drains any leftover queue items, then shuts down the executor and HTTP client. It is safe to call multiple times.
156
156
 
157
+ ### Delivery errors, dropping, and transient retry
158
+
159
+ Each `_send_*` method routes a failed delivery to one of two counters: a `429` (server buffer full) increments `_dropped_count` and is *not* retried (it's backpressure — the caller intentionally sheds the load), while any other non-2xx or transport failure increments `_error_count`. The data POSTs go through `_send_json_retrying` (`exporter_http.py`), which retries a **transient** failure — a `502`/`503`/`504` gateway/overload status, or an httpx transport/timeout error — up to `_data_post_attempts` (3) times with exponential backoff before it counts, so a momentary blip against a busy backend doesn't register as a hard error. It is *not* retried on `4xx` (client error), `429` (dropped, above), or `500` (the platform's to answer for — the same boundary as the stress harness's `is_external_outage`). Retrying is safe because serve dedups ingest — traces on `(org_id, trace_id, start_time)`, spans on the `spans_dedup` index — so a re-POST of a request that actually landed can't double-insert. The blocking run-*start* POSTs keep their own separate retry budget (`_start_post_attempts`), since they must return a server-assigned id synchronously.
160
+
157
161
  ### Loose spans vs bundled traces
158
162
 
159
163
  A span can arrive at `_flush_batch` before its parent trace has been exported — this happens whenever the exporter ticks in the middle of a DSPy call. The exporter holds those loose spans in `self._pending_spans` (`exporter_http.py:122`) keyed by `trace_id`, and when the trace finally arrives it attaches the buffered spans before shipping. Spans for which the trace never arrives get flushed as a bare spans batch via `/api/v1/traces/:id/spans` — see `_send_spans` at `exporter_http.py:637`.
@@ -5,7 +5,7 @@ name = "cmpnd"
5
5
  # it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
6
6
  # hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
7
7
  # this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
8
- version = "0.7.0"
8
+ version = "0.7.2"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.10"
@@ -58,6 +58,10 @@ dev = [
58
58
  # litellm's bedrock/ provider imports boto3 in-process, so the client-side
59
59
  # Bedrock stress tests (committee twins) need it in the test env.
60
60
  "boto3>=1.34",
61
+ # test_reflector_smoke.py builds numpy fixtures. dspy <=3.2.x required numpy
62
+ # unconditionally so it rode in transitively; dspy 3.3.0 moved numpy behind a
63
+ # `numpy` extra, so declare it directly as the test dependency it always was.
64
+ "numpy>=1.26",
61
65
  ]
62
66
  # Deps for `deploy/deploy/stub/`, the local FastAPI server that emulates
63
67
  # the AWS deploy stack. The stub itself ships in the cmpnd-deploy
@@ -92,6 +96,39 @@ build-backend = "hatchling.build"
92
96
  [tool.hatch.build.targets.wheel]
93
97
  packages = ["cmpnd"]
94
98
 
99
+ [tool.pytest.ini_options]
100
+ # Deliberately minimal: `markers` and `--strict-markers`, nothing else. Setting
101
+ # `testpaths` or a broader `addopts` here would change how every existing
102
+ # invocation resolves (CI lanes, `uv run pytest tests/ -k "not e2e"`, a bare
103
+ # `pytest` from a subdirectory), and this file's job is only to make the marker
104
+ # vocabulary explicit.
105
+ #
106
+ # --strict-markers turns a typo'd marker into an error instead of a silent
107
+ # no-op. That matters most for `forall_workers`: it SELECTS the merge-queue's
108
+ # wasm lane, so a misspelling there would quietly select nothing and report a
109
+ # green lane that ran zero tests. `asyncio` and `flaky` are registered by
110
+ # pytest-asyncio / pytest-rerunfailures, so they are not listed here.
111
+ addopts = "--strict-markers"
112
+ markers = [
113
+ # Tests that must hold for EVERY worker backend — i.e. that provision,
114
+ # execute or optimize through a data plane, and so can distinguish the
115
+ # Lambda plane from the wasm plane from the on-prem worker. This is the
116
+ # selector for the per-plane stress lanes: the set worth running against a
117
+ # second worker is exactly the set claimed to hold for all of them.
118
+ #
119
+ # A stress test that deploys or executes gets this marker. Its absence on
120
+ # such a test is a bug; its PRESENCE on a test no worker can affect (one
121
+ # that only exercises client-side tracing, or serve's own health) is worse,
122
+ # because it costs a second real-provider run to prove nothing.
123
+ # See docs/plans/2026-07-29-wasm-stress-lane.md.
124
+ "forall_workers: must hold for every worker backend (reaches a data plane)",
125
+ # Pre-existing vocabulary, registered here because --strict-markers now
126
+ # requires it — not introduced by this block.
127
+ "parity: CLI dual-front-end parity (argparse vs. interactive shell)",
128
+ "e2e: needs a live platform; excluded from the fast lane via -k 'not e2e'",
129
+ "integration: crosses a component boundary (SDK ↔ serve ↔ worker)",
130
+ ]
131
+
95
132
  [tool.ruff]
96
133
  line-length = 120
97
134
  target-version = "py310"