cmpnd 0.76.0__tar.gz → 0.78.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 (405) hide show
  1. {cmpnd-0.76.0 → cmpnd-0.78.0}/PKG-INFO +1 -1
  2. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_gepa_patch.py +45 -0
  3. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/callback.py +38 -5
  4. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/context.py +24 -0
  5. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/identity.py +10 -9
  6. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/optimizers/gepa.py +4 -0
  7. {cmpnd-0.76.0 → cmpnd-0.78.0}/docs/sdk-instrumentation.md +38 -3
  8. {cmpnd-0.76.0 → cmpnd-0.78.0}/pyproject.toml +2 -2
  9. cmpnd-0.78.0/tests/test_optimization_phase_callback.py +196 -0
  10. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_run_attribution_paths.py +101 -0
  11. {cmpnd-0.76.0 → cmpnd-0.78.0}/uv.lock +1 -1
  12. {cmpnd-0.76.0 → cmpnd-0.78.0}/.gitignore +0 -0
  13. {cmpnd-0.76.0 → cmpnd-0.78.0}/AGENTS.md +0 -0
  14. {cmpnd-0.76.0 → cmpnd-0.78.0}/CLAUDE.md +0 -0
  15. {cmpnd-0.76.0 → cmpnd-0.78.0}/README.md +0 -0
  16. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/__init__.py +0 -0
  17. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_assessments.py +0 -0
  18. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_authored_import.py +0 -0
  19. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_deployments.py +0 -0
  20. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_exporter_pool.py +0 -0
  21. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_http.py +0 -0
  22. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_program_patch.py +0 -0
  23. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_rlm_patch.py +0 -0
  24. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_runtime_tools.py +0 -0
  25. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_timespec.py +0 -0
  26. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_traces.py +0 -0
  27. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/_valsets.py +0 -0
  28. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/authored.py +0 -0
  29. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/__init__.py +0 -0
  30. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/api_client.py +0 -0
  31. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/__init__.py +0 -0
  32. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/admin.py +0 -0
  33. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/assessments.py +0 -0
  34. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/auth.py +0 -0
  35. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/datasets.py +0 -0
  36. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/deployments.py +0 -0
  37. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/evals.py +0 -0
  38. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/login.py +0 -0
  39. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/optimizations.py +0 -0
  40. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/org.py +0 -0
  41. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/programs.py +0 -0
  42. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  43. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  44. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/top.py +0 -0
  45. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/traces.py +0 -0
  46. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/commands/valsets.py +0 -0
  47. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/credentials.py +0 -0
  48. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/deprecation.py +0 -0
  49. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/pagination.py +0 -0
  50. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/shell.py +0 -0
  51. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/trace_render.py +0 -0
  52. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/tree_help.py +0 -0
  53. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/verbs.py +0 -0
  54. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/cli/views.py +0 -0
  55. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/client.py +0 -0
  56. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/configuration.py +0 -0
  57. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/credentials.py +0 -0
  58. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/dataset_sync.py +0 -0
  59. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/datasets.py +0 -0
  60. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/decorators.py +0 -0
  61. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/deployment.py +0 -0
  62. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/deployment_metadata.py +0 -0
  63. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/deployment_runs.py +0 -0
  64. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/eval_handler.py +0 -0
  65. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/execution.py +0 -0
  66. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/exporter.py +0 -0
  67. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/exporter_http.py +0 -0
  68. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/helpers.py +0 -0
  69. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/imaging.py +0 -0
  70. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/ir/__init__.py +0 -0
  71. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/ir/decode.py +0 -0
  72. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/ir/encode.py +0 -0
  73. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/ir/hash.py +0 -0
  74. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/ir/program.py +0 -0
  75. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/ir/reflection.py +0 -0
  76. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/ir/types.py +0 -0
  77. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/ir/xxh64.py +0 -0
  78. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/lm_configuration.py +0 -0
  79. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/mcp_servers.py +0 -0
  80. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/models.py +0 -0
  81. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/named_programs.py +0 -0
  82. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/optimization.py +0 -0
  83. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/optimizers/__init__.py +0 -0
  84. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  85. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/optimizers/resume.py +0 -0
  86. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/optimizers/tracker.py +0 -0
  87. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/packaging.py +0 -0
  88. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/program_naming.py +0 -0
  89. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/programs.py +0 -0
  90. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/references.py +0 -0
  91. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/retry.py +0 -0
  92. {cmpnd-0.76.0 → cmpnd-0.78.0}/cmpnd/traces.py +0 -0
  93. {cmpnd-0.76.0 → cmpnd-0.78.0}/docs/2026-04-02-code-review.md +0 -0
  94. {cmpnd-0.76.0 → cmpnd-0.78.0}/docs/cli.md +0 -0
  95. {cmpnd-0.76.0 → cmpnd-0.78.0}/docs/client.md +0 -0
  96. {cmpnd-0.76.0 → cmpnd-0.78.0}/docs/custom-proposer.md +0 -0
  97. {cmpnd-0.76.0 → cmpnd-0.78.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  98. {cmpnd-0.76.0 → cmpnd-0.78.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  99. {cmpnd-0.76.0 → cmpnd-0.78.0}/docs/reading-traces.md +0 -0
  100. {cmpnd-0.76.0 → cmpnd-0.78.0}/examples/generalizing_proposer.py +0 -0
  101. {cmpnd-0.76.0 → cmpnd-0.78.0}/examples/local_ollama.py +0 -0
  102. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/LINEAGE.md +0 -0
  103. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/README.md +0 -0
  104. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  105. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  106. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  107. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  108. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  109. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  110. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/py.typed +0 -0
  111. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  112. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/retry.py +0 -0
  113. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  114. {cmpnd-0.76.0 → cmpnd-0.78.0}/ingest/pyproject.toml +0 -0
  115. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/audit-sdist.sh +0 -0
  116. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/capture_review_run.py +0 -0
  117. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/check-pip-install.sh +0 -0
  118. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/check_release_artifacts.py +0 -0
  119. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/fixtures/review_run.json.gz +0 -0
  120. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/gepa_resume_bug.py +0 -0
  121. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/install-test-env.sh +0 -0
  122. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/release-gate.sh +0 -0
  123. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/release-real-models.sh +0 -0
  124. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/require_version_bump.py +0 -0
  125. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/seed_review_run.py +0 -0
  126. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/selfhost_compat.py +0 -0
  127. {cmpnd-0.76.0 → cmpnd-0.78.0}/scripts/test-in-linux.sh +0 -0
  128. {cmpnd-0.76.0 → cmpnd-0.78.0}/stubs/dspy/__init__.pyi +0 -0
  129. {cmpnd-0.76.0 → cmpnd-0.78.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  130. {cmpnd-0.76.0 → cmpnd-0.78.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  131. {cmpnd-0.76.0 → cmpnd-0.78.0}/stubs/dspy/utils/__init__.pyi +0 -0
  132. {cmpnd-0.76.0 → cmpnd-0.78.0}/stubs/dspy/utils/callback.pyi +0 -0
  133. {cmpnd-0.76.0 → cmpnd-0.78.0}/stubs/dspy/utils/dummies.pyi +0 -0
  134. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/__init__.py +0 -0
  135. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/_credential_route.py +0 -0
  136. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/_deployment_signature_helpers.py +0 -0
  137. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/_exporter_helpers.py +0 -0
  138. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/_fake_clock.py +0 -0
  139. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/_identity_helpers.py +0 -0
  140. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/_resolve_refs.py +0 -0
  141. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/_traced_run.py +0 -0
  142. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/conftest.py +0 -0
  143. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/__init__.py +0 -0
  144. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/committee_task.py +0 -0
  145. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/conftest.py +0 -0
  146. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/continuation_task.py +0 -0
  147. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  148. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  149. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/fake_lm.py +0 -0
  150. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/helpers.py +0 -0
  151. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/target_capabilities.py +0 -0
  152. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_assessments_cli.py +0 -0
  153. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_capture_prompts.py +0 -0
  154. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_composite_module.py +0 -0
  155. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  156. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_deploy_execute.py +0 -0
  157. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_deploy_pandas.py +0 -0
  158. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_deploy_programs.py +0 -0
  159. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_edge_cases.py +0 -0
  160. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_example_hash.py +0 -0
  161. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_gepa_optimization.py +0 -0
  162. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_interpreter_spans.py +0 -0
  163. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_lm_accuracy.py +0 -0
  164. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_module_tracing.py +0 -0
  165. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimization_lineage.py +0 -0
  166. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  167. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
  168. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimize_continuation.py +0 -0
  169. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
  170. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimize_failures.py +0 -0
  171. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimize_loop.py +0 -0
  172. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  173. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  174. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_parallel_export.py +0 -0
  175. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_project_attribution.py +0 -0
  176. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_react_tool_spans.py +0 -0
  177. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_save_load.py +0 -0
  178. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_trace_structure.py +0 -0
  179. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_traces_read.py +0 -0
  180. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_user_attribution.py +0 -0
  181. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/e2e/test_valsets_cli.py +0 -0
  182. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  183. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  184. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  185. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  186. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  187. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  188. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  189. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  190. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  191. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/10_react.json +0 -0
  192. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/11_refine.json +0 -0
  193. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/12_retrieve.json +0 -0
  194. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/13_knn.json +0 -0
  195. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/14_parallel.json +0 -0
  196. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/identity_vectors/README.md +0 -0
  197. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/ingest_conformance.py +0 -0
  198. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/__init__.py +0 -0
  199. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/authored_optimize/judge.py +0 -0
  200. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  201. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/conftest.py +0 -0
  202. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  203. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/hosted_optimize_probe.py +0 -0
  204. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/optimization_run_probe.py +0 -0
  205. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  206. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/test_logs_renders_traces.py +0 -0
  207. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/test_optimization_run.py +0 -0
  208. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/test_optimizing_progress.py +0 -0
  209. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/integration/test_unhappy_path.py +0 -0
  210. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/__init__.py +0 -0
  211. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/__main__.py +0 -0
  212. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/drain.py +0 -0
  213. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/ingest.py +0 -0
  214. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/latency_transport.py +0 -0
  215. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/rlm_shape.py +0 -0
  216. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/rlm_size_report.py +0 -0
  217. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/sweep.py +0 -0
  218. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/test_rlm_shape.py +0 -0
  219. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/load/test_rlm_size_report.py +0 -0
  220. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/__init__.py +0 -0
  221. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/conftest.py +0 -0
  222. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/seed_parity.py +0 -0
  223. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_auth.py +0 -0
  224. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_datasets.py +0 -0
  225. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_deployments.py +0 -0
  226. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_evals.py +0 -0
  227. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_health.py +0 -0
  228. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_modules.py +0 -0
  229. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_optimizations.py +0 -0
  230. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_programs.py +0 -0
  231. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_signatures.py +0 -0
  232. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_trace_streaming.py +0 -0
  233. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/parity/test_traces.py +0 -0
  234. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/__init__.py +0 -0
  235. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/_provider.py +0 -0
  236. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/_readiness.py +0 -0
  237. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/_reliability.py +0 -0
  238. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/_result.py +0 -0
  239. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/mint_bedrock_token.py +0 -0
  240. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_bedrock_converse.py +0 -0
  241. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_deploy_programs.py +0 -0
  242. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_multi_client_stress.py +0 -0
  243. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_optimize_client_committee.py +0 -0
  244. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_optimize_continuation.py +0 -0
  245. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_optimize_loop.py +0 -0
  246. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_optimize_loop_wasm.py +0 -0
  247. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_optimize_stress.py +0 -0
  248. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_provider_roles.py +0 -0
  249. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_readiness.py +0 -0
  250. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_real_provider_twins.py +0 -0
  251. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_reliability.py +0 -0
  252. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_reliability_classify.py +0 -0
  253. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_rlm_wasm.py +0 -0
  254. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/real_models/test_trace_search.py +0 -0
  255. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/span_reference_fixture.py +0 -0
  256. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_adapter_reentrancy.py +0 -0
  257. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_assessments_api.py +0 -0
  258. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_authored.py +0 -0
  259. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_authored_binding_credentials.py +0 -0
  260. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_authored_bundle_collisions.py +0 -0
  261. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_authored_metric_contract.py +0 -0
  262. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_authored_named_models.py +0 -0
  263. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_authored_optimize.py +0 -0
  264. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_callback.py +0 -0
  265. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_candidate_lineage.py +0 -0
  266. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/__init__.py +0 -0
  267. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/conftest.py +0 -0
  268. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_admin.py +0 -0
  269. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_api_client.py +0 -0
  270. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_assessments.py +0 -0
  271. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_auth.py +0 -0
  272. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_credentials.py +0 -0
  273. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_datasets.py +0 -0
  274. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_deployment_runs.py +0 -0
  275. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_deployments.py +0 -0
  276. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_evals.py +0 -0
  277. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_login.py +0 -0
  278. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_main.py +0 -0
  279. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_optimizations.py +0 -0
  280. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_org.py +0 -0
  281. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_pagination.py +0 -0
  282. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_parity.py +0 -0
  283. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_programs.py +0 -0
  284. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_provider_credentials.py +0 -0
  285. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_schema_sample.py +0 -0
  286. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_shell.py +0 -0
  287. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_tool_servers.py +0 -0
  288. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_top.py +0 -0
  289. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_traces.py +0 -0
  290. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_traces_program_and_module.py +0 -0
  291. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  292. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_tree_help.py +0 -0
  293. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cli/test_valsets.py +0 -0
  294. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_client.py +0 -0
  295. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_client_floor.py +0 -0
  296. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_client_program_names.py +0 -0
  297. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_clustering/__init__.py +0 -0
  298. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_clustering/conftest.py +0 -0
  299. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_clustering/test_edge_cases.py +0 -0
  300. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  301. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_clustering/test_type_conversion.py +0 -0
  302. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_cmp551_grouping_identity.py +0 -0
  303. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_configuration.py +0 -0
  304. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_context.py +0 -0
  305. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_context_propagation.py +0 -0
  306. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_credential_kwargs.py +0 -0
  307. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_credential_selection.py +0 -0
  308. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_credential_values.py +0 -0
  309. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_dataset_polling.py +0 -0
  310. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_dataset_sync.py +0 -0
  311. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_datasets.py +0 -0
  312. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_decision_client_spans.py +0 -0
  313. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_decorators.py +0 -0
  314. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_deploy.py +0 -0
  315. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  316. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_deploy_program_id.py +0 -0
  317. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_deployment_metadata.py +0 -0
  318. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_deployment_runs.py +0 -0
  319. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_deployments_read.py +0 -0
  320. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_eval.py +0 -0
  321. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_eval_example_attribution.py +0 -0
  322. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_eval_program_id.py +0 -0
  323. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_eval_start.py +0 -0
  324. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_eval_trace_grouping.py +0 -0
  325. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_exception_paths.py +0 -0
  326. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_execute.py +0 -0
  327. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_execute_run_identity.py +0 -0
  328. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_exporter.py +0 -0
  329. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_exporter_program_names.py +0 -0
  330. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_exporter_queue.py +0 -0
  331. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_exporter_real.py +0 -0
  332. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_fake_lm.py +0 -0
  333. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_gepa_callback.py +0 -0
  334. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_gepa_integration.py +0 -0
  335. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_gepa_output_normalization.py +0 -0
  336. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_gepa_resume.py +0 -0
  337. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_identity.py +0 -0
  338. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_imaging.py +0 -0
  339. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_import_weight.py +0 -0
  340. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_ingest_conformance.py +0 -0
  341. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_ingest_second_producer.py +0 -0
  342. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_integration.py +0 -0
  343. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_interpreter_callback.py +0 -0
  344. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_ir_encoder_properties.py +0 -0
  345. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_ir_golden_vectors.py +0 -0
  346. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_ir_roundtrip.py +0 -0
  347. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_lm15_native_calls.py +0 -0
  348. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_lm_configuration.py +0 -0
  349. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_lm_usage_shapes.py +0 -0
  350. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_mcp_tools.py +0 -0
  351. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_models.py +0 -0
  352. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_multi_client_stress_readiness.py +0 -0
  353. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_no_signature_hash_on_the_wire.py +0 -0
  354. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimization_program_names.py +0 -0
  355. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimization_progress.py +0 -0
  356. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimization_start.py +0 -0
  357. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimize.py +0 -0
  358. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimize_budget.py +0 -0
  359. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimize_lift.py +0 -0
  360. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimize_lms.py +0 -0
  361. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimize_proposer.py +0 -0
  362. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimize_reflection_lm.py +0 -0
  363. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_optimize_terminal_failure.py +0 -0
  364. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_packaging.py +0 -0
  365. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_packaging_callable_source.py +0 -0
  366. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_packaging_mcp_tools.py +0 -0
  367. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_packaging_program_source.py +0 -0
  368. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_poll_until.py +0 -0
  369. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_program_id_deprecation.py +0 -0
  370. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_program_identity.py +0 -0
  371. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_program_lineage_persist.py +0 -0
  372. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_program_names_in_deploy.py +0 -0
  373. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_program_naming.py +0 -0
  374. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_programs_create.py +0 -0
  375. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_project_payload.py +0 -0
  376. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_real_provider_twins_readiness.py +0 -0
  377. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_reflector_properties.py +0 -0
  378. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_reflector_smoke.py +0 -0
  379. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_release_gate_script.py +0 -0
  380. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_release_real_models_script.py +0 -0
  381. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_replaced_names.py +0 -0
  382. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_repro_reactv2_bugs.py +0 -0
  383. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_require_version_bump.py +0 -0
  384. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_retry.py +0 -0
  385. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_reversible_failures.py +0 -0
  386. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_rlm_patch.py +0 -0
  387. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_runtime_tools.py +0 -0
  388. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_schemas.py +0 -0
  389. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_seal_lm_credential.py +0 -0
  390. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_seed_review_run.py +0 -0
  391. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_signature_inputs.py +0 -0
  392. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_span_references.py +0 -0
  393. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_span_sent_once.py +0 -0
  394. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_system_one.py +0 -0
  395. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_trace_context_concurrency.py +0 -0
  396. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_trace_fact_wire_contract.py +0 -0
  397. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_trace_facts.py +0 -0
  398. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_trace_render.py +0 -0
  399. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_traces_read.py +0 -0
  400. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_tracked_gepa.py +0 -0
  401. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_tracker_enrich.py +0 -0
  402. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_truncation.py +0 -0
  403. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_upload_auth_headers.py +0 -0
  404. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_valsets_api.py +0 -0
  405. {cmpnd-0.76.0 → cmpnd-0.78.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.76.0
3
+ Version: 0.78.0
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>
@@ -9,6 +9,10 @@ module-style (``from . import identity``); basedpyright can't resolve
9
9
  """
10
10
 
11
11
  import contextvars
12
+ import functools
13
+ from collections.abc import Callable
14
+
15
+ from . import context
12
16
 
13
17
  _gepa_patched = False
14
18
 
@@ -39,6 +43,43 @@ def _gepa_supports_callbacks() -> bool:
39
43
  return False
40
44
 
41
45
 
46
+ # Marks a metric wrapper _judge_metric_calls installed, so a compile nested in another
47
+ # (TrackedGEPA under auto_instrument) leaves the outer wrapper in place. An attribute
48
+ # on our own wrapper, read with getattr, because the user's metric may be any
49
+ # callable: a dataclass instance is unhashable, so no set or dict can hold it.
50
+ _JUDGED_METRIC_MARK = "_cmpnd_judges_metric_calls"
51
+
52
+
53
+ def _judge_metric_calls(gepa) -> Callable[[], None]:
54
+ """Run each of ``gepa``'s metric calls as the metric phase; return the undo.
55
+
56
+ dspy.GEPA reads its metric from ``metric_fn`` once its compile starts, so
57
+ swapping in a wrapper for the compile's span reaches every metric call, those
58
+ DSPy Evaluate makes and those GEPA's feedback makes. A trace a grading module
59
+ roots inside a call is then tagged ``optimization_phase: metric``, as a hosted
60
+ run's judge traces are, while the rollout the metric grades ran before it and
61
+ carries no phase. The wrapper keeps the metric's signature (``functools.wraps``)
62
+ for DSPy's checks of it, and the undo restores the optimizer's own metric.
63
+ """
64
+ metric = getattr(gepa, "metric_fn", None)
65
+ if not callable(metric) or getattr(metric, _JUDGED_METRIC_MARK, False) is True:
66
+ return lambda: None
67
+
68
+ @functools.wraps(metric)
69
+ def judged_metric(*args, **kwargs):
70
+ with context.optimization_phase("metric"):
71
+ return metric(*args, **kwargs)
72
+
73
+ setattr(judged_metric, _JUDGED_METRIC_MARK, True)
74
+ gepa.metric_fn = judged_metric
75
+
76
+ def restore() -> None:
77
+ if getattr(gepa, "metric_fn", None) is judged_metric:
78
+ gepa.metric_fn = metric
79
+
80
+ return restore
81
+
82
+
42
83
  def _run_tracked_compile(
43
84
  gepa,
44
85
  student,
@@ -137,8 +178,10 @@ def _run_tracked_compile(
137
178
  trk.generated_program_name = program_naming.generated_program_name(student)
138
179
 
139
180
  restore_attribution = callback.start_trace_attribution()
181
+ restore_metric = None
140
182
 
141
183
  try:
184
+ restore_metric = _judge_metric_calls(gepa)
142
185
  result = run_original()
143
186
 
144
187
  if not has_callbacks:
@@ -164,6 +207,8 @@ def _run_tracked_compile(
164
207
  raise
165
208
 
166
209
  finally:
210
+ if restore_metric is not None:
211
+ restore_metric()
167
212
  restore_attribution()
168
213
 
169
214
 
@@ -517,6 +517,11 @@ def _finish_reason(response: Any) -> str | None:
517
517
  return "stop"
518
518
 
519
519
 
520
+ # The `optimization.role` an LM call in each GEPA phase is priced under. A metric scores a
521
+ # rollout, so its calls are rollout spend; reflection proposes the next candidate.
522
+ _OPTIMIZATION_PHASE_ROLES = {"metric": "rollout", "reflection": "reflection"}
523
+
524
+
520
525
  class _CallbackBase:
521
526
  """Shared state and span-tracking primitives for the CmpndCallback mixins."""
522
527
 
@@ -532,6 +537,7 @@ class _CallbackBase:
532
537
  max_output_field_size: int | None = None,
533
538
  stream_trace_starts: bool | None = None,
534
539
  own_config: configuration.Config | None = None,
540
+ optimization_phase: str | None = None,
535
541
  ) -> None:
536
542
  # A client passes its own config here; the auto_instrument callback leaves
537
543
  # it None and reads the process-global config live, so a reconfigure()
@@ -541,6 +547,19 @@ class _CallbackBase:
541
547
  # later cmpnd.configure(). That fixedness is what keeps a client
542
548
  # orthogonal to the global config.
543
549
  self._own_config = own_config
550
+ # The optimization phase this callback traces for a run it does not own: an
551
+ # executor that scores a hosted rollout with the author's metric ("metric") or runs
552
+ # the author's GEPA proposer ("reflection"). Such a process knows the program the
553
+ # run optimizes and not the run's id, which the ingest key it pushes with decides.
554
+ # With a phase set, an LM call outside any trace becomes a standalone GEPA trace as
555
+ # a local optimization's judge and reflection calls do, and a trace a module opens
556
+ # is tagged with the phase. None is every other caller.
557
+ if optimization_phase not in (None, *_OPTIMIZATION_PHASE_ROLES):
558
+ raise ValueError(
559
+ f"optimization_phase must be one of {sorted(_OPTIMIZATION_PHASE_ROLES)} or None, "
560
+ f"got {optimization_phase!r}"
561
+ )
562
+ self._optimization_phase = optimization_phase
544
563
  # Set once this callback has warned that a declared name replaced the program
545
564
  # id its config read from the environment, so the warning appears once per client.
546
565
  self._warned_name_over_id = False
@@ -1348,6 +1367,12 @@ class _ModuleMixin(_CallbackBase):
1348
1367
  # Apply default tags
1349
1368
  if config and config.default_tags:
1350
1369
  trace.tags.update(config.default_tags)
1370
+ # A trace opened while this callback traces a phase, or while a local run
1371
+ # calls its metric, is the metric's or the proposer's, never a rollout, even
1372
+ # when its module is the program's own class.
1373
+ phase = self._optimization_phase or context.get_current_optimization_phase()
1374
+ if phase is not None:
1375
+ trace.tags["optimization_phase"] = phase
1351
1376
 
1352
1377
  return trace
1353
1378
 
@@ -1401,7 +1426,9 @@ class _LMMixin(_CallbackBase):
1401
1426
  span.model_name = getattr(instance, "model", None)
1402
1427
  span.model_provider = helpers.infer_provider(instance)
1403
1428
 
1404
- if self._active_optimization_run_id is not None or context.get_current_optimization_run_id() is not None:
1429
+ if self._optimization_phase is not None:
1430
+ span.attributes["optimization.role"] = _OPTIMIZATION_PHASE_ROLES[self._optimization_phase]
1431
+ elif self._active_optimization_run_id is not None or context.get_current_optimization_run_id() is not None:
1405
1432
  span.attributes["optimization.role"] = "rollout"
1406
1433
 
1407
1434
  # The usage fields read one named value each out of the raw kwargs. The
@@ -1603,7 +1630,9 @@ class _LMMixin(_CallbackBase):
1603
1630
  # must NOT also be added to _orphaned_lm_usage / untraced_lm_usage — that
1604
1631
  # would double-count them in the optimization total.
1605
1632
  current_trace = context.get_current_trace()
1606
- if current_trace is None and self._active_optimization_run_id is not None:
1633
+ if current_trace is None and (
1634
+ self._active_optimization_run_id is not None or self._optimization_phase is not None
1635
+ ):
1607
1636
  self._emit_optimization_lm_trace(span)
1608
1637
  return # handled as a standalone GEPA-process trace
1609
1638
 
@@ -1644,14 +1673,18 @@ class _LMMixin(_CallbackBase):
1644
1673
  carries the distinction. Each becomes a standalone one-span
1645
1674
  trace so the prompt, response, tokens and cost are visible alongside the
1646
1675
  student traces instead of being reduced to an aggregate token count.
1676
+
1677
+ A callback built with an ``optimization_phase`` takes its phase from that
1678
+ instead, and may have no run id: it traces the metric or the proposer of a
1679
+ run whose id the ingest key the trace is pushed with decides.
1647
1680
  """
1648
1681
  run_id = self._active_optimization_run_id
1649
- if not run_id:
1682
+ if not run_id and self._optimization_phase is None:
1650
1683
  return
1651
1684
 
1652
- phase = "metric" if self._active_eval_run is not None else "reflection"
1685
+ phase = self._optimization_phase or ("metric" if self._active_eval_run is not None else "reflection")
1653
1686
  span.attributes["gepa.phase"] = phase
1654
- span.attributes["optimization.role"] = "reflection" if phase == "reflection" else "rollout"
1687
+ span.attributes["optimization.role"] = _OPTIMIZATION_PHASE_ROLES[phase]
1655
1688
 
1656
1689
  trace = models.TraceBuilder(
1657
1690
  trace_id=span.trace_id or uuid4(),
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ from collections.abc import Generator
6
+ from contextlib import contextmanager
5
7
  from contextvars import ContextVar
6
8
  from datetime import datetime, timezone
7
9
  from typing import TYPE_CHECKING, Any
@@ -27,6 +29,12 @@ _current_eval_run: ContextVar[models.EvalRunBuilder | None] = ContextVar("cmpnd_
27
29
  # evals). Prefixed text id (`op_…`), minted via cmpnd.identity.
28
30
  _current_optimization_run_id: ContextVar[str | None] = ContextVar("cmpnd_current_optimization_run_id", default=None)
29
31
 
32
+ # The optimization phase the code running now serves, when that code is an
33
+ # optimizer's metric rather than the program it optimizes. A local GEPA run sets it
34
+ # around each metric call, so a trace the metric's grading module roots is tagged as
35
+ # the metric's, as a hosted run's kernel tags it through its callback's own phase.
36
+ _current_optimization_phase: ContextVar[str | None] = ContextVar("cmpnd_current_optimization_phase", default=None)
37
+
30
38
 
31
39
  def get_current_trace() -> models.TraceBuilder | None:
32
40
  """Get the current trace."""
@@ -79,6 +87,21 @@ def set_current_optimization_run_id(optimization_run_id: str | None) -> None:
79
87
  _current_optimization_run_id.set(optimization_run_id)
80
88
 
81
89
 
90
+ def get_current_optimization_phase() -> str | None:
91
+ """The optimization phase the running code serves, or None outside a metric call."""
92
+ return _current_optimization_phase.get()
93
+
94
+
95
+ @contextmanager
96
+ def optimization_phase(phase: str) -> Generator[None]:
97
+ """Run the block as the optimization phase ``phase``, restoring the outer phase after."""
98
+ token = _current_optimization_phase.set(phase)
99
+ try:
100
+ yield
101
+ finally:
102
+ _current_optimization_phase.reset(token)
103
+
104
+
82
105
  # The program each optimization run this process started optimizes, by run id. A trace
83
106
  # linked to a run that names no program of its own takes its run's, so everything a run
84
107
  # emits is reachable through that program: GEPA's reflection and judge calls, a judge
@@ -148,4 +171,5 @@ def clear_context() -> None:
148
171
  _span_stack.set(())
149
172
  _current_eval_run.set(None)
150
173
  _current_optimization_run_id.set(None)
174
+ _current_optimization_phase.set(None)
151
175
  _optimization_program_ids.clear()
@@ -1,9 +1,10 @@
1
1
  """Identity and hash computations.
2
2
 
3
- The 64-bit program-identity hash is produced by the structural IR in
3
+ A program's identity is its server-issued program id (``is_program_id``).
4
+ The 64-bit program signature hash is produced by the structural IR in
4
5
  :mod:`cmpnd.ir`. ``compute_program_hash`` reflects a live ``dspy.Module``
5
- through that pipeline; trace, eval, and optimization paths share the
6
- one encoder.
6
+ through that pipeline, and ``program_naming`` uses the hash only to
7
+ generate a name for a program its user never named.
7
8
 
8
9
  Sibling hashes (``compute_signature_hash`` for model-sweep grouping,
9
10
  ``compute_valset_hash``, ``compute_dataset_hash``, ``compute_metric_hash``,
@@ -84,10 +85,9 @@ def _unsigned_to_signed_int64(value: int) -> int: # pyright: ignore[reportUnuse
84
85
  Written as arithmetic rather than as ``ctypes.c_int64(value).value``, which
85
86
  is the identical operation spelled with a C type: mask to 64 bits, then
86
87
  subtract 2^64 from anything at or above the sign bit. ``ctypes`` is not
87
- merely heavy for a two-line cast, it is *absent* where this has to run — the
88
- wasm guest (`worker-wasm/guest/`) imports this module to reproduce the SDK's
89
- ``program_signature_hash``, and its CPython has no ``_ctypes`` because there
90
- is no libffi for wasi, so the module was never compiled. Equivalence with
88
+ merely heavy for a two-line cast, it is *absent* from the worker's wasm
89
+ kernel (`worker-wasm/guest/`), which imports this module, and whose CPython
90
+ has no ``_ctypes`` because there is no libffi for wasi. Equivalence with
91
91
  the ctypes form is asserted over every boundary in ``tests/test_identity.py``.
92
92
 
93
93
  Imported by ``cmpnd.ir.hash`` and exercised by ``tests/test_identity.py``;
@@ -156,8 +156,9 @@ def validate_program_id(value: object, *, source: str = "program_id") -> str:
156
156
  def compute_program_hash(module: Any) -> int | None:
157
157
  """Reflect a live ``dspy.Module`` to IR and hash it.
158
158
 
159
- Returns ``None`` when ``module`` is ``None`` (callers commonly fold
160
- this with ``or 0`` for trace-side DEFAULT-0 columns).
159
+ Returns ``None`` when ``module`` is ``None``. The worker's kernel does not
160
+ bundle :mod:`cmpnd.ir`, so this raises ``ModuleNotFoundError`` there; the
161
+ kernel generates no program names, so it never calls this.
161
162
  """
162
163
  if module is None:
163
164
  return None
@@ -205,9 +205,11 @@ class TrackedGEPA:
205
205
  existing_callbacks = self._gepa.gepa_kwargs.get("callbacks") or []
206
206
  self._gepa.gepa_kwargs["callbacks"] = existing_callbacks + [gcb]
207
207
  restore_attribution = None
208
+ restore_metric = None
208
209
 
209
210
  try:
210
211
  restore_attribution = gcb.start_trace_attribution()
212
+ restore_metric = _gepa_patch._judge_metric_calls(self._gepa)
211
213
  # Run optimization (trainset/valset are keyword-only in GEPA)
212
214
  compile_token = _gepa_patch._tracked_compile_optimizer.set(self._gepa)
213
215
  try:
@@ -246,6 +248,8 @@ class TrackedGEPA:
246
248
  raise
247
249
 
248
250
  finally:
251
+ if restore_metric is not None:
252
+ restore_metric()
249
253
  if restore_attribution is not None:
250
254
  restore_attribution()
251
255
  # Remove our callback from gepa_kwargs
@@ -146,7 +146,7 @@ optimization run name their program by `program_id`.
146
146
  [`CLUSTERING.md`](../../docs/CLUSTERING.md) explains how the hash is computed.
147
147
 
148
148
  - **On a span** — `_capture_module_attributes` in `callback.py` walks `instance.signature.input_fields` / `output_fields` and records names, types, descriptions and schemas. It reflects nothing, because a span never names a program.
149
- - **On a trace** — `_create_trace` in `callback.py` records the root signature, or for a composite module the `dspy.Predict` children's signatures as `component_signatures`. A root module call that names no program reflects the module once, for the name the SDK generates (`program_naming.generated_program_name`). The hash goes into that name and nowhere else.
149
+ - **On a trace** — `_create_trace` in `callback.py` records the root signature, or for a composite module the `dspy.Predict` children's signatures as `component_signatures`. A root module call that names no program reflects the module once, for the name the SDK generates (`program_naming.generated_program_name`). The hash goes into that name and nowhere else. The worker's kernel replaces that function with one that generates no name, so a kernel never reflects a module.
150
150
 
151
151
  `python-sdk/cmpnd/identity.py` is the home of every hash the SDK computes.
152
152
 
@@ -180,6 +180,7 @@ Everything lives in `python-sdk/cmpnd/context.py`:
180
180
  - `_span_stack` — an immutable tuple of `SpanBuilder`s. `push_span` and `pop_span` set a new tuple rather than mutating the old one, because a context copy shares the objects its parent context holds: a mutable stack would be one list shared by every sibling copy, so concurrent runs (GEPA's `DspyAdapter.batch_evaluate`, which runs each candidate in a `copy_context()`, or `asyncio.gather` over `acall`) would read each other's spans as their parent. `get_current_span` returns the innermost. `tests/test_trace_context_concurrency.py` pins this.
181
181
  - `_current_eval_run` (`context.py:24`) — set while an `EvalHandler` is active.
182
182
  - `_current_optimization_run_id` (`context.py:29`) — set while a patched `GEPA.compile` is running, so evals triggered inside it can be linked back.
183
+ - `_current_optimization_phase` — set to `"metric"` for the span of each metric call a tracked GEPA compile makes, so a trace the metric's grading module roots is tagged as the metric's. `context.optimization_phase(phase)` sets it for a block.
183
184
 
184
185
  `create_child_span` at `context.py:83` is the standard constructor: it reads the current trace + span, assigns `trace_id` and `parent_span_id`, and returns an unpushed `SpanBuilder`. The callback uses it for every child span; so does the `start_span` context manager below. Pass `root=True` to have it ignore the stack and leave `parent_span_id` unset, for a span that starts a trace beneath one that does not parent it.
185
186
 
@@ -202,7 +203,41 @@ callback never fires. This prevents an ordinary evaluation after a failure from
202
203
  being queued under the failed run or being claimed as the next optimization's
203
204
  seed evidence.
204
205
 
205
- For tests that need a clean slate, `clear_context()` at `context.py:122` resets all four vars — see the `cmpnd-testing` skill and [`../AGENTS.md`](../AGENTS.md) § "Running Tests & Checks".
206
+ ### A callback that traces one phase of a run it does not own
207
+
208
+ `CmpndCallback(optimization_phase=...)` takes `"metric"` or `"reflection"`. A worker
209
+ kernel builds one around a hosted run's metric and around its custom GEPA proposer.
210
+ That process knows the program the run optimizes, from its config, and not the run's
211
+ id, which serve stamps from the ingest key the traces are pushed with. Such a callback
212
+ makes those calls look like a local optimization's:
213
+
214
+ - An LM call outside any trace becomes the standalone one-span `GEPA` trace that
215
+ `_emit_optimization_lm_trace` writes for a local run. Its span carries `gepa.phase`
216
+ and `optimization.role`, and its trace carries the `optimization_phase` tag. The
217
+ callback takes the phase from its constructor, not from an active eval run, and
218
+ leaves `optimization_run_id` unset.
219
+ - A trace that a dspy module opens is tagged `optimization_phase`, so a program's trace
220
+ list tells a judge module from a rollout of the same class.
221
+ - Every LM span gets `optimization.role`: `rollout` for a metric, `reflection` for a
222
+ proposer.
223
+
224
+ A local run owns its run, so its callback declares no phase. It tags its judge traces
225
+ through context instead. While a tracked GEPA compile runs, through `auto_instrument`,
226
+ `TrackedGEPA` or a client's `optimize_program`, `_gepa_patch._judge_metric_calls`
227
+ swaps the optimizer's `metric_fn` for a wrapper that runs each call under
228
+ `context.optimization_phase("metric")`, and restores the metric when the compile ends.
229
+ `_create_trace` reads that phase when its callback has none. So a judge module the
230
+ metric grades through roots a trace tagged `optimization_phase: metric`, as a hosted
231
+ judge's module trace is. The rollout the metric grades ran before the call and carries
232
+ no tag. The context phase changes only that tag: a direct judge or reflection call is
233
+ still the trace `_emit_optimization_lm_trace` builds, and module spans get no
234
+ `optimization.role`.
235
+
236
+ `tests/test_optimization_phase_callback.py` drives both paths, and the context phase,
237
+ through real DSPy dispatch. `tests/test_run_attribution_paths.py` runs a real GEPA
238
+ search whose metric grades through a module.
239
+
240
+ For tests that need a clean slate, `clear_context()` in `context.py` resets these vars — see the `cmpnd-testing` skill and [`../AGENTS.md`](../AGENTS.md) § "Running Tests & Checks".
206
241
 
207
242
  ### Across a real fork
208
243
 
@@ -1035,7 +1070,7 @@ A new hand-rolled serializer on any of those paths reopens the hole. `tests/test
1035
1070
 
1036
1071
  ## See also
1037
1072
 
1038
- - [`./clustering.md`](../../docs/CLUSTERING.md) — the canonical explainer for `signature_hash`. Start here if you're wondering why two identical-looking modules have different hashes. A hash never decides a trace's program.
1073
+ - [`CLUSTERING.md`](../../docs/CLUSTERING.md) explains how traces and runs group under a program, and how the program signature hash is computed. A hash never decides a trace's program.
1039
1074
  - [`./optimization-tracking.md`](../../docs/optimization-tracking.md) — how `auto_instrument`'s GEPA patch and `cmpnd/optimizers/tracker.py` turn a `GEPA.compile` call into an `OptimizationRun` with linked evals.
1040
1075
  - The `cmpnd-testing` skill + [`../AGENTS.md`](../AGENTS.md) § "Running Tests & Checks" — patterns for writing callback tests against real databases, including `clear_context()` usage.
1041
1076
  - [`serve/DB.md`](../../serve/DB.md) — the Postgres schema these payloads land in.
@@ -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.76.0"
8
+ version = "0.78.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
@@ -41,7 +41,7 @@ dependencies = [
41
41
  # Still required, and still the fast path — but `cmpnd/ir/xxh64.py` no
42
42
  # longer *needs* it: it falls back to a pure-Python xxh64 that the tests
43
43
  # check against this module, so a platform with no wheel and no compiler can
44
- # still compute a program's identity. Don't drop the dependency on that
44
+ # still compute a program's signature hash. Don't drop the dependency on that
45
45
  # basis; the C module is an order of magnitude faster and every ordinary
46
46
  # install should have it.
47
47
  "xxhash>=3.0.0",
@@ -0,0 +1,196 @@
1
+ """A callback that traces one optimization phase on behalf of a run it does not own.
2
+
3
+ A worker kernel scores a hosted rollout with the author's metric and runs the author's
4
+ GEPA proposer, and both may call a model. The kernel knows the program the run
5
+ optimizes but not the run's id: serve stamps that from the run key the traces are
6
+ pushed with. So the kernel builds its callback with ``optimization_phase`` and these
7
+ calls must come out as a local optimization's judge and reflection calls do:
8
+
9
+ * an LM call outside any trace is a standalone one-span ``GEPA`` trace, with
10
+ ``gepa.phase`` and ``optimization.role`` on its span and ``optimization_phase`` in
11
+ its tags;
12
+ * a call made through a dspy module roots a trace at that module, tagged with the phase
13
+ so a program's trace list tells it from a rollout of the same module class;
14
+ * every such trace carries the configured program id, and no run id.
15
+
16
+ A local GEPA run owns its run, so its callback declares no phase. It runs each metric
17
+ call under ``context.optimization_phase("metric")`` instead, and a trace the metric's
18
+ grading module roots there carries the same tag a hosted judge trace does.
19
+
20
+ Driven through real DSPy dispatch (``dspy.Predict`` and ``DummyLM``), because the
21
+ paths under test are which callback hooks DSPy fires and with what trace in scope.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import dataclasses
27
+ from typing import Any
28
+
29
+ import dspy
30
+ import pytest
31
+ from dspy.utils.dummies import DummyLM
32
+
33
+ from cmpnd import configuration, context
34
+ from cmpnd.configuration import configure
35
+ from cmpnd.context import clear_context
36
+ from tests._traced_run import make_callback, run
37
+
38
+ PROGRAM_ID = "p_0123456789Ab"
39
+
40
+
41
+ @pytest.fixture(autouse=True)
42
+ def configured_program():
43
+ """The kernel's config: the SDK configured, holding the deployment's program id."""
44
+ config = configure(api_key="test_key", endpoint="http://localhost:8000")
45
+ with configuration._config_lock:
46
+ configuration._config = dataclasses.replace(config, program_id=PROGRAM_ID)
47
+ yield
48
+ clear_context()
49
+ configuration._config = None
50
+ from cmpnd.exporter import shutdown_exporter
51
+
52
+ shutdown_exporter()
53
+
54
+
55
+ def _lm(answers: list[dict[str, str]]) -> DummyLM:
56
+ return DummyLM(answers, adapter=dspy.ChatAdapter())
57
+
58
+
59
+ def _direct_judge(lm: Any):
60
+ """A metric that asks its model directly, with no dspy module in between."""
61
+
62
+ def judge() -> Any:
63
+ return lm("Is 'four' right for 2+2? Say yes or no.")
64
+
65
+ return judge
66
+
67
+
68
+ class TestADirectCallIsAStandaloneTrace:
69
+ @pytest.mark.parametrize(("phase", "role"), [("metric", "rollout"), ("reflection", "reflection")])
70
+ def test_it_is_one_gepa_span_carrying_the_phase_and_the_program(self, phase: str, role: str):
71
+ lm = _lm([{"answer": "yes"}])
72
+ _, exp = run(_direct_judge(lm), lm, dspy.ChatAdapter(), callback=make_callback(optimization_phase=phase))
73
+
74
+ assert len(exp.traces) == 1, exp.traces
75
+ trace = exp.traces[0]
76
+ assert trace.module_class == "GEPA"
77
+ assert trace.tags.get("optimization_phase") == phase
78
+ assert trace.program_id == PROGRAM_ID
79
+ # Serve stamps the run from the key the trace is pushed with; the kernel claims none.
80
+ assert trace.optimization_run_id is None
81
+ assert len(trace._batch_spans) == 1
82
+ span = trace._batch_spans[0]
83
+ assert span.span_type.value == "lm_call"
84
+ assert span.parent_span_id is None
85
+ assert span.trace_id == trace.trace_id
86
+ wire = span.to_api_dict()
87
+ assert wire["attributes"]["gepa.phase"] == phase
88
+ assert wire["attributes"]["optimization.role"] == role
89
+
90
+ def test_each_call_is_its_own_trace(self):
91
+ lm = _lm([{"answer": "yes"}, {"answer": "no"}])
92
+ judge = _direct_judge(lm)
93
+
94
+ def judge_twice() -> None:
95
+ judge()
96
+ judge()
97
+
98
+ _, exp = run(judge_twice, lm, dspy.ChatAdapter(), callback=make_callback(optimization_phase="metric"))
99
+
100
+ assert len(exp.traces) == 2
101
+ assert len({trace.trace_id for trace in exp.traces}) == 2
102
+
103
+
104
+ class TestACallThroughAModuleRootsItsOwnTrace:
105
+ @pytest.mark.parametrize(("phase", "role"), [("metric", "rollout"), ("reflection", "reflection")])
106
+ def test_the_module_trace_is_tagged_with_the_phase(self, phase: str, role: str):
107
+ lm = _lm([{"verdict": "right"}])
108
+ grader = dspy.Predict("question, answer -> verdict: str")
109
+ _, exp = run(
110
+ grader,
111
+ lm,
112
+ dspy.ChatAdapter(),
113
+ callback=make_callback(optimization_phase=phase),
114
+ question="2+2?",
115
+ answer="four",
116
+ )
117
+
118
+ assert len(exp.traces) == 1, exp.traces
119
+ trace = exp.traces[0]
120
+ assert trace.module_class == "Predict"
121
+ assert trace.tags.get("optimization_phase") == phase
122
+ assert trace.program_id == PROGRAM_ID
123
+ assert trace.optimization_run_id is None
124
+ lm_spans = [span.to_api_dict() for span in exp.spans if span.span_type.value == "lm_call"]
125
+ assert lm_spans, exp.wire()
126
+ assert all(span["attributes"]["optimization.role"] == role for span in lm_spans)
127
+ assert all("gepa.phase" not in span["attributes"] for span in lm_spans)
128
+
129
+
130
+ class TestWithoutAPhase:
131
+ def test_a_program_trace_carries_no_phase(self):
132
+ """A rollout's own callback declares no phase, so its traces stay rollouts."""
133
+ lm = _lm([{"answer": "4"}])
134
+ _, exp = run(dspy.Predict("question -> answer"), lm, dspy.ChatAdapter(), question="2+2?")
135
+
136
+ assert len(exp.traces) == 1
137
+ assert "optimization_phase" not in exp.traces[0].tags
138
+ assert all("optimization.role" not in span.to_api_dict()["attributes"] for span in exp.spans)
139
+
140
+ def test_a_direct_call_outside_an_optimization_is_no_trace(self):
141
+ lm = _lm([{"answer": "yes"}])
142
+ _, exp = run(_direct_judge(lm), lm, dspy.ChatAdapter())
143
+
144
+ assert exp.traces == []
145
+
146
+
147
+ class TestALocalMetricCallsPhase:
148
+ """The phase a local run sets around its metric, read by a callback that declares none."""
149
+
150
+ def test_a_module_the_metric_grades_through_is_tagged_as_the_metric(self):
151
+ lm = _lm([{"verdict": "right"}])
152
+ grader = dspy.Predict("question, answer -> verdict: str")
153
+
154
+ def grade() -> Any:
155
+ with context.optimization_phase("metric"):
156
+ return grader(question="2+2?", answer="four")
157
+
158
+ _, exp = run(grade, lm, dspy.ChatAdapter())
159
+
160
+ assert len(exp.traces) == 1, exp.traces
161
+ assert exp.traces[0].module_class == "Predict"
162
+ assert exp.traces[0].tags.get("optimization_phase") == "metric"
163
+ # Only the trace's tag changes: its spans are built as any module trace's are.
164
+ assert all("optimization.role" not in span.to_api_dict()["attributes"] for span in exp.spans)
165
+
166
+ def test_a_program_traced_after_the_metric_returns_carries_no_phase(self):
167
+ lm = _lm([{"verdict": "right"}, {"answer": "4"}])
168
+ grader = dspy.Predict("question, answer -> verdict: str")
169
+ program = dspy.Predict("question -> answer")
170
+
171
+ def grade_then_run() -> None:
172
+ with context.optimization_phase("metric"):
173
+ grader(question="2+2?", answer="four")
174
+ program(question="2+2?")
175
+
176
+ _, exp = run(grade_then_run, lm, dspy.ChatAdapter())
177
+
178
+ assert [trace.tags.get("optimization_phase") for trace in exp.traces] == ["metric", None]
179
+
180
+ def test_a_direct_call_outside_an_optimization_stays_untraced(self):
181
+ """The phase tags module traces only: a direct call still needs a run to trace."""
182
+ lm = _lm([{"answer": "yes"}])
183
+ judge = _direct_judge(lm)
184
+
185
+ def judge_in_phase() -> None:
186
+ with context.optimization_phase("metric"):
187
+ judge()
188
+
189
+ _, exp = run(judge_in_phase, lm, dspy.ChatAdapter())
190
+
191
+ assert exp.traces == []
192
+
193
+
194
+ def test_an_unknown_phase_is_refused():
195
+ with pytest.raises(ValueError, match="optimization_phase"):
196
+ make_callback(optimization_phase="rollout")