cmpnd 0.72.0__tar.gz → 0.74.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 (403) hide show
  1. {cmpnd-0.72.0 → cmpnd-0.74.0}/PKG-INFO +9 -6
  2. {cmpnd-0.72.0 → cmpnd-0.74.0}/README.md +8 -5
  3. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_gepa_patch.py +12 -2
  4. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/callback.py +36 -21
  5. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/admin.py +18 -0
  6. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/verbs.py +2 -1
  7. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/context.py +28 -0
  8. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/eval_handler.py +22 -9
  9. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/models.py +9 -2
  10. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/optimizers/gepa.py +9 -1
  11. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/optimizers/tracker.py +31 -1
  12. cmpnd-0.74.0/cmpnd/program_naming.py +86 -0
  13. {cmpnd-0.72.0 → cmpnd-0.74.0}/docs/cli.md +17 -0
  14. {cmpnd-0.72.0 → cmpnd-0.74.0}/docs/client.md +6 -3
  15. {cmpnd-0.72.0 → cmpnd-0.74.0}/docs/sdk-instrumentation.md +64 -18
  16. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/exporter_http.py +74 -45
  17. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/program_names.py +26 -7
  18. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/protocols.py +5 -4
  19. {cmpnd-0.72.0 → cmpnd-0.74.0}/pyproject.toml +1 -1
  20. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_admin.py +18 -0
  21. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_client_program_names.py +130 -26
  22. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_eval_program_id.py +74 -10
  23. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_exporter_program_names.py +116 -15
  24. cmpnd-0.74.0/tests/test_optimization_program_names.py +363 -0
  25. cmpnd-0.74.0/tests/test_program_naming.py +166 -0
  26. {cmpnd-0.72.0 → cmpnd-0.74.0}/uv.lock +1 -1
  27. {cmpnd-0.72.0 → cmpnd-0.74.0}/.gitignore +0 -0
  28. {cmpnd-0.72.0 → cmpnd-0.74.0}/AGENTS.md +0 -0
  29. {cmpnd-0.72.0 → cmpnd-0.74.0}/CLAUDE.md +0 -0
  30. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/__init__.py +0 -0
  31. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_assessments.py +0 -0
  32. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_authored_import.py +0 -0
  33. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_deployments.py +0 -0
  34. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_exporter_pool.py +0 -0
  35. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_http.py +0 -0
  36. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_program_patch.py +0 -0
  37. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_rlm_patch.py +0 -0
  38. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_runtime_tools.py +0 -0
  39. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_timespec.py +0 -0
  40. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_traces.py +0 -0
  41. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/_valsets.py +0 -0
  42. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/authored.py +0 -0
  43. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/__init__.py +0 -0
  44. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/api_client.py +0 -0
  45. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/__init__.py +0 -0
  46. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/assessments.py +0 -0
  47. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/auth.py +0 -0
  48. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/datasets.py +0 -0
  49. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/deployments.py +0 -0
  50. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/evals.py +0 -0
  51. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/login.py +0 -0
  52. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/optimizations.py +0 -0
  53. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/org.py +0 -0
  54. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/programs.py +0 -0
  55. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  56. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  57. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/top.py +0 -0
  58. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/traces.py +0 -0
  59. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/commands/valsets.py +0 -0
  60. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/credentials.py +0 -0
  61. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/deprecation.py +0 -0
  62. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/pagination.py +0 -0
  63. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/shell.py +0 -0
  64. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/trace_render.py +0 -0
  65. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/tree_help.py +0 -0
  66. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/cli/views.py +0 -0
  67. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/client.py +0 -0
  68. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/configuration.py +0 -0
  69. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/credentials.py +0 -0
  70. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/dataset_sync.py +0 -0
  71. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/datasets.py +0 -0
  72. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/decorators.py +0 -0
  73. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/deployment.py +0 -0
  74. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/deployment_metadata.py +0 -0
  75. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/deployment_runs.py +0 -0
  76. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/execution.py +0 -0
  77. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/exporter.py +0 -0
  78. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/exporter_http.py +0 -0
  79. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/helpers.py +0 -0
  80. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/identity.py +0 -0
  81. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/imaging.py +0 -0
  82. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/ir/__init__.py +0 -0
  83. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/ir/decode.py +0 -0
  84. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/ir/encode.py +0 -0
  85. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/ir/hash.py +0 -0
  86. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/ir/program.py +0 -0
  87. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/ir/reflection.py +0 -0
  88. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/ir/types.py +0 -0
  89. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/ir/xxh64.py +0 -0
  90. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/lm_configuration.py +0 -0
  91. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/mcp_servers.py +0 -0
  92. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/named_programs.py +0 -0
  93. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/optimization.py +0 -0
  94. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/optimizers/__init__.py +0 -0
  95. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  96. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/optimizers/resume.py +0 -0
  97. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/packaging.py +0 -0
  98. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/programs.py +0 -0
  99. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/references.py +0 -0
  100. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/retry.py +0 -0
  101. {cmpnd-0.72.0 → cmpnd-0.74.0}/cmpnd/traces.py +0 -0
  102. {cmpnd-0.72.0 → cmpnd-0.74.0}/docs/2026-04-02-code-review.md +0 -0
  103. {cmpnd-0.72.0 → cmpnd-0.74.0}/docs/custom-proposer.md +0 -0
  104. {cmpnd-0.72.0 → cmpnd-0.74.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  105. {cmpnd-0.72.0 → cmpnd-0.74.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  106. {cmpnd-0.72.0 → cmpnd-0.74.0}/docs/reading-traces.md +0 -0
  107. {cmpnd-0.72.0 → cmpnd-0.74.0}/examples/generalizing_proposer.py +0 -0
  108. {cmpnd-0.72.0 → cmpnd-0.74.0}/examples/local_ollama.py +0 -0
  109. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/LINEAGE.md +0 -0
  110. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/README.md +0 -0
  111. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  112. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  113. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  114. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/py.typed +0 -0
  115. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  116. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/retry.py +0 -0
  117. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  118. {cmpnd-0.72.0 → cmpnd-0.74.0}/ingest/pyproject.toml +0 -0
  119. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/audit-sdist.sh +0 -0
  120. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/capture_review_run.py +0 -0
  121. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/check-pip-install.sh +0 -0
  122. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/check_release_artifacts.py +0 -0
  123. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/fixtures/review_run.json.gz +0 -0
  124. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/gepa_resume_bug.py +0 -0
  125. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/install-test-env.sh +0 -0
  126. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/release-gate.sh +0 -0
  127. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/release-real-models.sh +0 -0
  128. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/require_version_bump.py +0 -0
  129. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/seed_review_run.py +0 -0
  130. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/selfhost_compat.py +0 -0
  131. {cmpnd-0.72.0 → cmpnd-0.74.0}/scripts/test-in-linux.sh +0 -0
  132. {cmpnd-0.72.0 → cmpnd-0.74.0}/stubs/dspy/__init__.pyi +0 -0
  133. {cmpnd-0.72.0 → cmpnd-0.74.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  134. {cmpnd-0.72.0 → cmpnd-0.74.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  135. {cmpnd-0.72.0 → cmpnd-0.74.0}/stubs/dspy/utils/__init__.pyi +0 -0
  136. {cmpnd-0.72.0 → cmpnd-0.74.0}/stubs/dspy/utils/callback.pyi +0 -0
  137. {cmpnd-0.72.0 → cmpnd-0.74.0}/stubs/dspy/utils/dummies.pyi +0 -0
  138. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/__init__.py +0 -0
  139. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/_credential_route.py +0 -0
  140. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/_deployment_signature_helpers.py +0 -0
  141. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/_exporter_helpers.py +0 -0
  142. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/_fake_clock.py +0 -0
  143. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/_identity_helpers.py +0 -0
  144. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/_resolve_refs.py +0 -0
  145. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/_traced_run.py +0 -0
  146. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/conftest.py +0 -0
  147. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/__init__.py +0 -0
  148. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/committee_task.py +0 -0
  149. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/conftest.py +0 -0
  150. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/continuation_task.py +0 -0
  151. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  152. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  153. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/fake_lm.py +0 -0
  154. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/helpers.py +0 -0
  155. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/target_capabilities.py +0 -0
  156. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_assessments_cli.py +0 -0
  157. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_capture_prompts.py +0 -0
  158. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_composite_module.py +0 -0
  159. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  160. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_deploy_execute.py +0 -0
  161. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_deploy_pandas.py +0 -0
  162. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_deploy_programs.py +0 -0
  163. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_edge_cases.py +0 -0
  164. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_example_hash.py +0 -0
  165. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_gepa_optimization.py +0 -0
  166. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_interpreter_spans.py +0 -0
  167. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_lm_accuracy.py +0 -0
  168. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_module_tracing.py +0 -0
  169. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimization_lineage.py +0 -0
  170. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  171. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
  172. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimize_continuation.py +0 -0
  173. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
  174. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimize_failures.py +0 -0
  175. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimize_loop.py +0 -0
  176. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  177. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  178. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_parallel_export.py +0 -0
  179. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_project_attribution.py +0 -0
  180. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_react_tool_spans.py +0 -0
  181. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_save_load.py +0 -0
  182. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_trace_structure.py +0 -0
  183. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_traces_read.py +0 -0
  184. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_user_attribution.py +0 -0
  185. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/e2e/test_valsets_cli.py +0 -0
  186. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  187. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  188. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  189. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  190. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  191. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  192. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  193. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  194. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  195. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/10_react.json +0 -0
  196. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/11_refine.json +0 -0
  197. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/12_retrieve.json +0 -0
  198. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/13_knn.json +0 -0
  199. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/14_parallel.json +0 -0
  200. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/identity_vectors/README.md +0 -0
  201. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/ingest_conformance.py +0 -0
  202. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/__init__.py +0 -0
  203. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/authored_optimize/judge.py +0 -0
  204. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  205. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/conftest.py +0 -0
  206. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  207. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/hosted_optimize_probe.py +0 -0
  208. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/optimization_run_probe.py +0 -0
  209. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  210. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/test_logs_renders_traces.py +0 -0
  211. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/test_optimization_run.py +0 -0
  212. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/test_optimizing_progress.py +0 -0
  213. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/integration/test_unhappy_path.py +0 -0
  214. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/__init__.py +0 -0
  215. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/__main__.py +0 -0
  216. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/drain.py +0 -0
  217. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/ingest.py +0 -0
  218. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/latency_transport.py +0 -0
  219. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/rlm_shape.py +0 -0
  220. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/rlm_size_report.py +0 -0
  221. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/sweep.py +0 -0
  222. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/test_rlm_shape.py +0 -0
  223. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/load/test_rlm_size_report.py +0 -0
  224. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/__init__.py +0 -0
  225. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/conftest.py +0 -0
  226. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/seed_parity.py +0 -0
  227. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_auth.py +0 -0
  228. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_datasets.py +0 -0
  229. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_deployments.py +0 -0
  230. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_evals.py +0 -0
  231. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_health.py +0 -0
  232. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_modules.py +0 -0
  233. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_optimizations.py +0 -0
  234. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_programs.py +0 -0
  235. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_signatures.py +0 -0
  236. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_trace_streaming.py +0 -0
  237. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/parity/test_traces.py +0 -0
  238. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/__init__.py +0 -0
  239. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/_provider.py +0 -0
  240. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/_readiness.py +0 -0
  241. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/_reliability.py +0 -0
  242. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/_result.py +0 -0
  243. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/mint_bedrock_token.py +0 -0
  244. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_bedrock_converse.py +0 -0
  245. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_deploy_programs.py +0 -0
  246. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_multi_client_stress.py +0 -0
  247. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_optimize_client_committee.py +0 -0
  248. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_optimize_continuation.py +0 -0
  249. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_optimize_loop.py +0 -0
  250. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_optimize_loop_wasm.py +0 -0
  251. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_optimize_stress.py +0 -0
  252. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_provider_roles.py +0 -0
  253. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_readiness.py +0 -0
  254. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_real_provider_twins.py +0 -0
  255. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_reliability.py +0 -0
  256. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_reliability_classify.py +0 -0
  257. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_rlm_wasm.py +0 -0
  258. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/real_models/test_trace_search.py +0 -0
  259. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/span_reference_fixture.py +0 -0
  260. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_adapter_reentrancy.py +0 -0
  261. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_assessments_api.py +0 -0
  262. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_authored.py +0 -0
  263. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_authored_binding_credentials.py +0 -0
  264. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_authored_bundle_collisions.py +0 -0
  265. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_authored_metric_contract.py +0 -0
  266. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_authored_named_models.py +0 -0
  267. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_authored_optimize.py +0 -0
  268. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_callback.py +0 -0
  269. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_candidate_lineage.py +0 -0
  270. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/__init__.py +0 -0
  271. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/conftest.py +0 -0
  272. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_api_client.py +0 -0
  273. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_assessments.py +0 -0
  274. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_auth.py +0 -0
  275. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_credentials.py +0 -0
  276. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_datasets.py +0 -0
  277. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_deployment_runs.py +0 -0
  278. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_deployments.py +0 -0
  279. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_evals.py +0 -0
  280. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_login.py +0 -0
  281. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_main.py +0 -0
  282. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_optimizations.py +0 -0
  283. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_org.py +0 -0
  284. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_pagination.py +0 -0
  285. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_parity.py +0 -0
  286. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_programs.py +0 -0
  287. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_provider_credentials.py +0 -0
  288. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_schema_sample.py +0 -0
  289. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_shell.py +0 -0
  290. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_tool_servers.py +0 -0
  291. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_top.py +0 -0
  292. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_traces.py +0 -0
  293. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_traces_program_and_module.py +0 -0
  294. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  295. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_tree_help.py +0 -0
  296. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cli/test_valsets.py +0 -0
  297. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_client.py +0 -0
  298. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_client_floor.py +0 -0
  299. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_clustering/__init__.py +0 -0
  300. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_clustering/conftest.py +0 -0
  301. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_clustering/test_edge_cases.py +0 -0
  302. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  303. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_clustering/test_type_conversion.py +0 -0
  304. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_cmp551_grouping_identity.py +0 -0
  305. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_configuration.py +0 -0
  306. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_context.py +0 -0
  307. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_context_propagation.py +0 -0
  308. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_credential_kwargs.py +0 -0
  309. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_credential_selection.py +0 -0
  310. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_credential_values.py +0 -0
  311. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_dataset_polling.py +0 -0
  312. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_dataset_sync.py +0 -0
  313. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_datasets.py +0 -0
  314. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_decision_client_spans.py +0 -0
  315. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_decorators.py +0 -0
  316. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_deploy.py +0 -0
  317. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  318. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_deploy_program_id.py +0 -0
  319. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_deployment_metadata.py +0 -0
  320. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_deployment_runs.py +0 -0
  321. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_deployments_read.py +0 -0
  322. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_eval.py +0 -0
  323. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_eval_example_attribution.py +0 -0
  324. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_eval_start.py +0 -0
  325. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_eval_trace_grouping.py +0 -0
  326. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_exception_paths.py +0 -0
  327. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_execute.py +0 -0
  328. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_execute_run_identity.py +0 -0
  329. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_exporter.py +0 -0
  330. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_exporter_queue.py +0 -0
  331. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_exporter_real.py +0 -0
  332. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_fake_lm.py +0 -0
  333. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_gepa_callback.py +0 -0
  334. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_gepa_integration.py +0 -0
  335. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_gepa_output_normalization.py +0 -0
  336. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_gepa_resume.py +0 -0
  337. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_identity.py +0 -0
  338. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_imaging.py +0 -0
  339. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_import_weight.py +0 -0
  340. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_ingest_conformance.py +0 -0
  341. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_ingest_second_producer.py +0 -0
  342. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_integration.py +0 -0
  343. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_interpreter_callback.py +0 -0
  344. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_ir_encoder_properties.py +0 -0
  345. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_ir_golden_vectors.py +0 -0
  346. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_ir_roundtrip.py +0 -0
  347. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_lm15_native_calls.py +0 -0
  348. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_lm_configuration.py +0 -0
  349. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_lm_usage_shapes.py +0 -0
  350. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_mcp_tools.py +0 -0
  351. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_models.py +0 -0
  352. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_multi_client_stress_readiness.py +0 -0
  353. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimization_progress.py +0 -0
  354. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimization_start.py +0 -0
  355. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimize.py +0 -0
  356. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimize_budget.py +0 -0
  357. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimize_lift.py +0 -0
  358. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimize_lms.py +0 -0
  359. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimize_proposer.py +0 -0
  360. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimize_reflection_lm.py +0 -0
  361. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_optimize_terminal_failure.py +0 -0
  362. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_packaging.py +0 -0
  363. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_packaging_callable_source.py +0 -0
  364. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_packaging_mcp_tools.py +0 -0
  365. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_packaging_program_source.py +0 -0
  366. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_poll_until.py +0 -0
  367. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_program_id_deprecation.py +0 -0
  368. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_program_identity.py +0 -0
  369. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_program_lineage_persist.py +0 -0
  370. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_program_names_in_deploy.py +0 -0
  371. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_programs_create.py +0 -0
  372. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_project_payload.py +0 -0
  373. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_real_provider_twins_readiness.py +0 -0
  374. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_reflector_properties.py +0 -0
  375. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_reflector_smoke.py +0 -0
  376. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_release_gate_script.py +0 -0
  377. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_release_real_models_script.py +0 -0
  378. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_replaced_names.py +0 -0
  379. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_repro_reactv2_bugs.py +0 -0
  380. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_require_version_bump.py +0 -0
  381. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_retry.py +0 -0
  382. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_reversible_failures.py +0 -0
  383. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_rlm_patch.py +0 -0
  384. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_run_attribution_paths.py +0 -0
  385. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_runtime_tools.py +0 -0
  386. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_schemas.py +0 -0
  387. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_seal_lm_credential.py +0 -0
  388. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_seed_review_run.py +0 -0
  389. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_signature_inputs.py +0 -0
  390. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_span_references.py +0 -0
  391. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_span_sent_once.py +0 -0
  392. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_system_one.py +0 -0
  393. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_trace_context_concurrency.py +0 -0
  394. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_trace_fact_wire_contract.py +0 -0
  395. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_trace_facts.py +0 -0
  396. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_trace_render.py +0 -0
  397. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_traces_read.py +0 -0
  398. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_tracked_gepa.py +0 -0
  399. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_tracker_enrich.py +0 -0
  400. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_truncation.py +0 -0
  401. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_upload_auth_headers.py +0 -0
  402. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_valsets_api.py +0 -0
  403. {cmpnd-0.72.0 → cmpnd-0.74.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.72.0
3
+ Version: 0.74.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>
@@ -157,9 +157,12 @@ dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
157
157
  dspy.ChainOfThought("question -> answer")(question="What is DSPy?")
158
158
  ```
159
159
 
160
- These traces carry no program, so the platform files them under Unattributed.
161
- The first one in a process logs a warning that points to `@cmpnd.program` and
162
- `cmpnd.Client`. Calling a `cmpnd.Program` directly while `auto_instrument()` is
160
+ Each program shape gets a program of its own, under a name the SDK generates
161
+ from its class and signature, such as `ChainOfThought-17e1f4089a24`. These
162
+ programs do not count toward your plan's program limit. The first such trace in
163
+ a process logs a warning that names the generated name and shows how to give the
164
+ program a real one: `@cmpnd.program(name)` or `cmpnd.Program(name, module=..., lm=...)`,
165
+ run through `cmpnd.Client`. Calling a `cmpnd.Program` directly while `auto_instrument()` is
163
166
  on raises a `TypeError` that points to the client. The API reference below
164
167
  lists the options of `cmpnd.configure()`.
165
168
 
@@ -640,8 +643,8 @@ captured input and output size limits, 65,536 bytes each by default.
640
643
 
641
644
  ### `cmpnd.auto_instrument()`
642
645
 
643
- Registers the callback with DSPy, so every DSPy call records a trace. The
644
- traces carry no program and land in Unattributed.
646
+ Registers the callback with DSPy, so every DSPy call records a trace. Each
647
+ trace goes to a program under a name the SDK generates for its root module.
645
648
 
646
649
  ### `cmpnd.Client()`
647
650
 
@@ -106,9 +106,12 @@ dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
106
106
  dspy.ChainOfThought("question -> answer")(question="What is DSPy?")
107
107
  ```
108
108
 
109
- These traces carry no program, so the platform files them under Unattributed.
110
- The first one in a process logs a warning that points to `@cmpnd.program` and
111
- `cmpnd.Client`. Calling a `cmpnd.Program` directly while `auto_instrument()` is
109
+ Each program shape gets a program of its own, under a name the SDK generates
110
+ from its class and signature, such as `ChainOfThought-17e1f4089a24`. These
111
+ programs do not count toward your plan's program limit. The first such trace in
112
+ a process logs a warning that names the generated name and shows how to give the
113
+ program a real one: `@cmpnd.program(name)` or `cmpnd.Program(name, module=..., lm=...)`,
114
+ run through `cmpnd.Client`. Calling a `cmpnd.Program` directly while `auto_instrument()` is
112
115
  on raises a `TypeError` that points to the client. The API reference below
113
116
  lists the options of `cmpnd.configure()`.
114
117
 
@@ -589,8 +592,8 @@ captured input and output size limits, 65,536 bytes each by default.
589
592
 
590
593
  ### `cmpnd.auto_instrument()`
591
594
 
592
- Registers the callback with DSPy, so every DSPy call records a trace. The
593
- traces carry no program and land in Unattributed.
595
+ Registers the callback with DSPy, so every DSPy call records a trace. Each
596
+ trace goes to a program under a name the SDK generates for its root module.
594
597
 
595
598
  ### `cmpnd.Client()`
596
599
 
@@ -58,13 +58,19 @@ def _run_tracked_compile(
58
58
 
59
59
  ``client_config`` scopes the run record to a client: the record takes that
60
60
  config's project tags and program id as they are, absent ones included, so a
61
- client that names no program sends none rather than the global config's. The
62
- global path passes no client config and keeps the tracker's global snapshot.
61
+ client that names no program sends none rather than the global config's. A client
62
+ always runs a named program, so its run never takes a generated name, even when the
63
+ name did not resolve. The global path passes no client config and keeps the
64
+ tracker's global snapshot. With no program id there, the run takes the name the SDK
65
+ generates for the student, the name its rollouts' traces carry, unless the call
66
+ declares a name.
63
67
  ``has_callbacks`` is passed by the patch (resolved once at patch time); a
64
68
  client leaves it None and it is resolved here.
65
69
  """
66
70
  import dspy
67
71
 
72
+ from . import callback as dspy_callback
73
+ from . import program_naming
68
74
  from .optimizers import gepa_callback, tracker
69
75
 
70
76
  if has_callbacks is None:
@@ -127,6 +133,10 @@ def _run_tracked_compile(
127
133
  max_metric_calls=max_metric_calls,
128
134
  )
129
135
 
136
+ # `enrich_from_program` hashed the student, so its generated name reuses that hash.
137
+ if client_config is None and trk.program_id is None and dspy_callback._declared_program_name() is None:
138
+ trk.generated_program_name = program_naming.generated_program_name(student, trk.program_signature_hash)
139
+
130
140
  restore_attribution = callback.start_trace_attribution()
131
141
 
132
142
  try:
@@ -9,7 +9,7 @@ from datetime import datetime, timezone
9
9
  from typing import Any
10
10
  from uuid import UUID, uuid4
11
11
 
12
- from . import configuration, context, eval_handler, exporter, helpers, identity, models, references
12
+ from . import configuration, context, eval_handler, exporter, helpers, identity, models, program_naming, references
13
13
 
14
14
  logger = logging.getLogger(__name__)
15
15
 
@@ -39,24 +39,23 @@ def _declared_program_name() -> str | None:
39
39
  return name if isinstance(name, str) else None
40
40
 
41
41
 
42
- # Set once the process has warned that a trace names no program, so the warning
43
- # appears once per process, whichever callback records the trace.
44
- _warned_unattributed = False
42
+ # Set once the process has warned that a program has no name, so the warning appears
43
+ # once per process, whichever callback records the trace.
44
+ _warned_unnamed = False
45
45
 
46
46
 
47
- def _warn_once_if_unattributed(trace: models.TraceBuilder) -> None:
48
- """Warn the first time a trace in this process names neither a program name nor a program id.
49
-
50
- Such a trace belongs to no program, and the platform files it under Unattributed.
51
- """
52
- global _warned_unattributed
53
- if _warned_unattributed or trace.declared_name is not None or trace.program_id is not None:
47
+ def _warn_once_about_generated_name(trace: models.TraceBuilder) -> None:
48
+ """Warn the first time a trace in this process goes to a program under a generated name."""
49
+ global _warned_unnamed
50
+ if _warned_unnamed or trace.generated_name is None:
54
51
  return
55
- _warned_unattributed = True
52
+ _warned_unnamed = True
56
53
  logger.warning(
57
- "cmpnd: this trace names no program, so the platform files it under Unattributed. Give the program a "
58
- "name with @cmpnd.program(name) and run it through cmpnd.Client, and its traces go to that program. "
59
- "This warning appears once per process."
54
+ "cmpnd: this program has no name, so its traces go to the program named %r, a name cmpnd generated from "
55
+ "its class and its signature. Name the program with @cmpnd.program(name) or "
56
+ "cmpnd.Program(name, module=..., lm=...) and run it through a cmpnd.Client, and its traces go to the program "
57
+ "with that name. This warning appears once per process.",
58
+ trace.generated_name,
60
59
  )
61
60
 
62
61
 
@@ -711,6 +710,7 @@ class _CallbackBase:
711
710
  A trace from a call that declares a program name carries the name and no id,
712
711
  and the exporter resolves the name. The declared name wins over a program id the
713
712
  client's config read from ``CMPND_PROGRAM_ID``, and the callback warns once that it did.
713
+ A trace that gets neither gets a generated name in `_create_trace`.
714
714
  """
715
715
  builder.project_tags = self._resolve_project_tags()
716
716
  if isinstance(builder, models.TraceBuilder):
@@ -1214,10 +1214,6 @@ class _ModuleMixin(_CallbackBase):
1214
1214
  module_class=instance.__class__.__name__,
1215
1215
  )
1216
1216
  self._stamp(trace)
1217
- # A root module call is where a program runs, so it is where an unnamed one
1218
- # is caught. GEPA's own LM calls (_emit_optimization_lm_trace) belong to the
1219
- # optimization run, which names the program, so they never warn.
1220
- _warn_once_if_unattributed(trace)
1221
1217
 
1222
1218
  # Attach the run ids this trace starts under, from this callback's own
1223
1219
  # state right now — not at finalize (on_module_end), where the
@@ -1261,6 +1257,9 @@ class _ModuleMixin(_CallbackBase):
1261
1257
  elif hasattr(instance, "predict") and hasattr(instance.predict, "signature"):
1262
1258
  signature = instance.predict.signature
1263
1259
 
1260
+ # The root module's signature hash, once a branch below computes it. An unnamed
1261
+ # program's generated name reuses it rather than reflecting the module again.
1262
+ program_hash: int | None = None
1264
1263
  if signature:
1265
1264
  # Single-signature module (Predict, ChainOfThought, etc.)
1266
1265
  sig_name = getattr(signature, "__name__", None)
@@ -1288,7 +1287,8 @@ class _ModuleMixin(_CallbackBase):
1288
1287
  identity.get_field_schema(field_info.annotation) for field_info in output_fields.values()
1289
1288
  ]
1290
1289
 
1291
- trace.program_signature_hash = identity.compute_program_hash(instance) or 0
1290
+ program_hash = identity.compute_program_hash(instance)
1291
+ trace.program_signature_hash = program_hash or 0
1292
1292
  trace.signature_summary = identity.build_signature_summary(signature)
1293
1293
  else:
1294
1294
  # Multi-child module: collect Predict children via DSPy's own walker
@@ -1303,7 +1303,8 @@ class _ModuleMixin(_CallbackBase):
1303
1303
 
1304
1304
  if children:
1305
1305
  trace.signature_name = instance.__class__.__name__
1306
- trace.program_signature_hash = identity.compute_program_hash(instance) or 0
1306
+ program_hash = identity.compute_program_hash(instance)
1307
+ trace.program_signature_hash = program_hash or 0
1307
1308
 
1308
1309
  # Build component_signatures for grouped field display
1309
1310
  components = []
@@ -1332,6 +1333,16 @@ class _ModuleMixin(_CallbackBase):
1332
1333
  components.append(comp)
1333
1334
  trace.component_signatures = components
1334
1335
 
1336
+ # A trace that declares neither an id nor a name takes the program of the
1337
+ # optimization run it belongs to, so a run's judge modules land in the program it
1338
+ # optimizes. Otherwise a root module call is where a program runs, so it is where
1339
+ # an unnamed one gets its generated name. A span never names a program.
1340
+ if trace.program_id is None and trace.declared_name is None:
1341
+ trace.program_id = context.optimization_program_id(trace.optimization_run_id)
1342
+ if trace.program_id is None and trace.declared_name is None:
1343
+ trace.generated_name = program_naming.generated_program_name(instance, program_hash)
1344
+ _warn_once_about_generated_name(trace)
1345
+
1335
1346
  # Detect if this is a compiled program (from optimization)
1336
1347
  if hasattr(instance, "_optimization_run_id"):
1337
1348
  trace.compiled_from_run_id = str(instance._optimization_run_id)
@@ -1661,6 +1672,10 @@ class _LMMixin(_CallbackBase):
1661
1672
  module_class="GEPA",
1662
1673
  )
1663
1674
  self._stamp(trace)
1675
+ # The call has no root module to name a program after, so a trace that declares
1676
+ # neither an id nor a name takes the program the run optimizes.
1677
+ if trace.program_id is None and trace.declared_name is None:
1678
+ trace.program_id = context.optimization_program_id(run_id)
1664
1679
  # A metric-phase call can start while DSPy's Evaluate span is still on the
1665
1680
  # context stack, so on_lm_start stamped this span with a parent_span_id for
1666
1681
  # a span that isn't in this standalone one-span trace. This span IS the
@@ -121,6 +121,12 @@ def _register_org(sub: _SubParsersAction[ArgumentParser]) -> None:
121
121
  elect_owner.add_argument("user_id", help="User id to make owner")
122
122
  elect_owner.set_defaults(func=handle_org_elect_owner)
123
123
 
124
+ usage = osub.add_parser(
125
+ "usage", help="Show the org's limits in use and its activity over 24 hours, 7 days and 30 days"
126
+ )
127
+ _org_id_arg(usage)
128
+ usage.set_defaults(func=handle_org_usage)
129
+
124
130
  plan = osub.add_parser("plan", help="An org's plan and its limits")
125
131
  plsub = plan.add_subparsers(dest="org_plan_action")
126
132
  plan_show = plsub.add_parser("show", help="Show the org's plan and every limit as it resolves for the org")
@@ -162,6 +168,10 @@ def _register_user(sub: _SubParsersAction[ArgumentParser]) -> None:
162
168
  _user_id_arg(show)
163
169
  show.set_defaults(func=handle_user_show)
164
170
 
171
+ activity = usub.add_parser("activity", help="Show a user's milestones, memberships and recorded changes")
172
+ _user_id_arg(activity)
173
+ activity.set_defaults(func=handle_user_activity)
174
+
165
175
  deprecation.alias(usub, "get", "show", func=handle_user_show, configure=_user_id_arg)
166
176
 
167
177
  create = usub.add_parser("create", help="Provision a user")
@@ -349,6 +359,10 @@ def handle_org_elect_owner(client: api_client.ApiClient, args: Any) -> Any:
349
359
  return {"elected": args.user_id, "github_login": args.github_login}
350
360
 
351
361
 
362
+ def handle_org_usage(client: api_client.ApiClient, args: Any) -> Any:
363
+ return client.get(f"/api/v1/admin/organizations/{args.org_id}/usage")
364
+
365
+
352
366
  def handle_org_plan_show(client: api_client.ApiClient, args: Any) -> Any:
353
367
  return client.get(f"/api/v1/admin/organizations/{args.org_id}/plan")
354
368
 
@@ -424,6 +438,10 @@ def handle_user_show(client: api_client.ApiClient, args: Any) -> Any:
424
438
  return client.get(f"/api/v1/admin/users/{args.id}")
425
439
 
426
440
 
441
+ def handle_user_activity(client: api_client.ApiClient, args: Any) -> Any:
442
+ return client.get(f"/api/v1/admin/users/{args.id}/activity")
443
+
444
+
427
445
  def handle_user_create(client: api_client.ApiClient, args: Any) -> Any:
428
446
  # email/name/org_id/is_admin are always sent; password is only included
429
447
  # when supplied so the server keeps its own default (e.g. no local login).
@@ -192,7 +192,8 @@ RELATION_NOUNS: dict[str, str] = {
192
192
  "registry": "the registered programs, as distinct from the organization's programs",
193
193
  "optimized": "the compiled programs optimization runs produced, as distinct from the organization's programs",
194
194
  "stats": "a program's execution statistics",
195
- "usage": "a program's usage over time",
195
+ "usage": "a program's usage over time, or an organization's limits in use and recent activity",
196
+ "activity": "a user's account milestones, memberships and recorded changes",
196
197
  "openapi": "the OpenAPI document a deployment serves",
197
198
  "history": "a program's distinct prompt instructions over time",
198
199
  "evals": "the eval runs recorded against a program",
@@ -79,6 +79,33 @@ def set_current_optimization_run_id(optimization_run_id: str | None) -> None:
79
79
  _current_optimization_run_id.set(optimization_run_id)
80
80
 
81
81
 
82
+ # The program each optimization run this process started optimizes, by run id. A trace
83
+ # linked to a run that names no program of its own takes its run's, so everything a run
84
+ # emits is reachable through that program: GEPA's reflection and judge calls, a judge
85
+ # module's trace, the rollouts. Keyed by run id rather than held in a context variable
86
+ # because the run id reaches a trace two ways (the callback's field and a context
87
+ # variable, for evaluator threads), and whichever way a trace learns its run, this
88
+ # answers for it. One entry per run, kept for the life of the process: a trace may still
89
+ # be built after its run's record settles, and the map holds two short strings a run.
90
+ _optimization_program_ids: dict[str, str] = {}
91
+
92
+
93
+ def set_optimization_program_id(optimization_run_id: str, program_id: str) -> None:
94
+ """Record the program an optimization run optimizes, once the run has both ids."""
95
+ _optimization_program_ids[optimization_run_id] = program_id
96
+
97
+
98
+ def optimization_program_id(optimization_run_id: str | None) -> str | None:
99
+ """The program the optimization run ``optimization_run_id`` optimizes, or None.
100
+
101
+ None for no run, and for a run that names no program, as one whose generated name
102
+ serve could not answer for does.
103
+ """
104
+ if optimization_run_id is None:
105
+ return None
106
+ return _optimization_program_ids.get(optimization_run_id)
107
+
108
+
82
109
  def create_child_span(
83
110
  name: str, span_type: models.SpanType, *, root: bool = False, **kwargs: Any
84
111
  ) -> models.SpanBuilder:
@@ -121,3 +148,4 @@ def clear_context() -> None:
121
148
  _span_stack.set(())
122
149
  _current_eval_run.set(None)
123
150
  _current_optimization_run_id.set(None)
151
+ _optimization_program_ids.clear()
@@ -10,7 +10,7 @@ import logging
10
10
  from datetime import datetime, timezone
11
11
  from typing import Any
12
12
 
13
- from . import context, helpers, identity, models
13
+ from . import context, helpers, identity, models, program_naming
14
14
 
15
15
  logger = logging.getLogger(__name__)
16
16
 
@@ -177,7 +177,9 @@ class EvalHandler:
177
177
  eval_run._dataset_exists = None # Will include examples as fallback
178
178
 
179
179
  exp = self._callback._current_exporter()
180
- eval_run.program_id = self._program_id(exp)
180
+ eval_run.program_id = self._program_id(
181
+ exp, program, eval_run.program_signature_hash, eval_run.optimization_run_id
182
+ )
181
183
 
182
184
  logger.info(
183
185
  f"EvalRun created: program_id={eval_run.program_id}, "
@@ -214,20 +216,31 @@ class EvalHandler:
214
216
  self._callback._call_id_to_span[call_id] = span
215
217
  context.push_span(span)
216
218
 
217
- def _program_id(self, exp: Any) -> str | None:
219
+ def _program_id(
220
+ self, exp: Any, program: Any, program_hash: int | None, optimization_run_id: str | None
221
+ ) -> str | None:
218
222
  """The program id the eval run carries, resolved the way a trace's is.
219
223
 
220
- A program name the call declares wins over the callback's program id. The
221
- exporter resolves the name now, on the caller's thread, because the run's
222
- start is sent synchronously and must carry the id. The exporter keeps the
223
- answer for the process, so a name its traces already resolved costs no
224
- request. A name the exporter cannot resolve leaves the run without an id.
224
+ A program name the call declares wins over the callback's program id. A run
225
+ that has neither takes the program of the optimization run it belongs to, else
226
+ the name the SDK generates for the evaluated program, the name its traces carry.
227
+ The exporter resolves either name now, on the caller's thread, because the run's
228
+ start is sent synchronously and must carry the id. The exporter keeps the answer
229
+ for the process, so a name its traces already resolved costs no request. A name
230
+ the exporter cannot resolve leaves the run without an id. ``program_hash`` is the
231
+ evaluated program's signature hash, already computed for the run, so naming the
232
+ program does not reflect it again.
225
233
  """
226
234
  from . import callback
227
235
 
228
236
  name = callback._declared_program_name()
229
237
  if name is None:
230
- return self._callback._resolve_program_id()
238
+ program_id = self._callback._resolve_program_id() or context.optimization_program_id(optimization_run_id)
239
+ if program_id is not None or program is None:
240
+ return program_id
241
+ return program_naming.resolve_generated_name(
242
+ exp, program_naming.generated_program_name(program, program_hash)
243
+ )
231
244
  # Read with getattr because the member is optional: an in-memory collector
232
245
  # that resolves no names has none.
233
246
  resolve = getattr(exp, "resolve_program_name", None)
@@ -967,6 +967,10 @@ class TraceBuilder:
967
967
  # exporter resolves it to a program id and writes that into ``program_id`` on the
968
968
  # bodies it sends, unless the trace already carries an id.
969
969
  declared_name: str | None = None
970
+ # The name the SDK generated for the trace's program, when the trace declares neither
971
+ # an id nor a name (`program_naming`). The exporter resolves it as it resolves a
972
+ # declared name, and tells serve that the SDK generated it.
973
+ generated_name: str | None = None
970
974
 
971
975
  # Optimization lineage - tracks which optimization produced this program
972
976
  compiled_from_run_id: str | None = None
@@ -1275,14 +1279,17 @@ class EvalRunBuilder:
1275
1279
 
1276
1280
  # Eval Identity (defines comparability)
1277
1281
  # The program the run evaluates: the id serve gave the program name the call
1278
- # declared, or the client's program id. None when the run names no program.
1282
+ # declared, else the client's program id, else the id serve gave the name the SDK
1283
+ # generated for the evaluated module. None when the run has no program.
1279
1284
  program_id: str | None = None
1280
1285
  # The evaluated module's class, such as "ReAct".
1281
1286
  module_class: str | None = None
1282
1287
  metric_name: str | None = None
1283
1288
  dataset_hash: str | None = None
1284
1289
  dataset_size: int = 0
1285
- program_signature_hash: int = 0 # xxhash64 of signature - program identity
1290
+ # The evaluated module's signature hash (legacy program key). None when the run has
1291
+ # no module, and then sent as null.
1292
+ program_signature_hash: int | None = None
1286
1293
  signature_summary: str | None = None # Human-readable: "input1, input2 -> output1"
1287
1294
 
1288
1295
  # Model Configuration
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import logging
6
6
  from typing import TYPE_CHECKING, Any
7
7
 
8
- from .. import _gepa_patch, identity
8
+ from .. import _gepa_patch, callback, identity, program_naming
9
9
  from . import gepa_callback, tracker
10
10
 
11
11
  if TYPE_CHECKING:
@@ -166,6 +166,14 @@ class TrackedGEPA:
166
166
  # program's `dspy.Flex` components itself.
167
167
  self._tracker.component_types = tracker.code_component_types(student)
168
168
 
169
+ # A run that declares neither a program id nor a program name takes the name the
170
+ # SDK generates for the student, the name its rollouts' traces carry, and
171
+ # resolves it when the run starts.
172
+ if self._tracker.program_id is None and callback._declared_program_name() is None:
173
+ self._tracker.generated_program_name = program_naming.generated_program_name(
174
+ student, program_signature_hash
175
+ )
176
+
169
177
  # Store both sets for dataset payload building
170
178
  self._tracker._trainset = list(trainset)
171
179
  self._tracker._valset = list(effective_valset)
@@ -11,7 +11,7 @@ from datetime import datetime, timezone
11
11
  from typing import TYPE_CHECKING, Any
12
12
  from uuid import UUID
13
13
 
14
- from .. import configuration, identity, models
14
+ from .. import configuration, context, identity, models, program_naming
15
15
 
16
16
  if TYPE_CHECKING:
17
17
  from .. import exporter_http
@@ -287,6 +287,10 @@ class OptimizationTracker:
287
287
  # The program the run optimizes, snapshotted like project_tags. None
288
288
  # declares none, and the key is then left off the run upsert.
289
289
  self.program_id: str | None = configuration._current_program_id()
290
+ # The name the SDK generated for the program, for a run that declares neither a
291
+ # program id nor a program name (`program_naming`). The run resolves it when it
292
+ # is first sent, on the caller's thread, and carries the id serve gives.
293
+ self.generated_program_name: str | None = None
290
294
  # A client's optimize_program sets this to its own pooled transport, so the
291
295
  # run record is published on the client's account (its endpoint + key)
292
296
  # rather than the process-global exporter. None means the global exporter.
@@ -842,6 +846,7 @@ class OptimizationTracker:
842
846
  if not exp:
843
847
  logger.debug("No exporter available, optimization start not exported")
844
848
  return
849
+ self._resolve_generated_program(exp)
845
850
 
846
851
  # A GEPA job resumed from the same log_dir is one continuous
847
852
  # run. If a prior run left a checkpoint + our run_id sidecar, re-adopt
@@ -853,6 +858,7 @@ class OptimizationTracker:
853
858
  self.optimization_run_id, prior_start = readopted
854
859
  if prior_start is not None:
855
860
  self.start_time = prior_start
861
+ self._publish_program()
856
862
  logger.info(f"Resuming optimization run: run_id={self.optimization_run_id} (log_dir={log_dir})")
857
863
  return
858
864
 
@@ -861,6 +867,7 @@ class OptimizationTracker:
861
867
  assigned = exp.export_optimization_start(self)
862
868
  if assigned:
863
869
  self.optimization_run_id = assigned
870
+ self._publish_program()
864
871
  # Persist the id so a later resume of the same log_dir re-adopts it.
865
872
  if log_dir and self.optimization_run_id:
866
873
  resume.write_run_id(log_dir, self.optimization_run_id, self.program_signature_hash, self.start_time)
@@ -1089,6 +1096,28 @@ class OptimizationTracker:
1089
1096
  return
1090
1097
  self._export_update()
1091
1098
 
1099
+ def _resolve_generated_program(self, exp: Any) -> None:
1100
+ """Give a run that names no program the id of its generated name, before it is sent.
1101
+
1102
+ Resolved now, on the caller's thread, because the run's first record goes out
1103
+ synchronously and must carry its program id. The exporter keeps the answer for
1104
+ the process, so the run's rollouts, whose traces carry the same name, cost no
1105
+ second request. A name serve refuses or cannot answer leaves the run without an
1106
+ id.
1107
+ """
1108
+ if self.program_id is None and self.generated_program_name is not None:
1109
+ self.program_id = program_naming.resolve_generated_name(exp, self.generated_program_name)
1110
+
1111
+ def _publish_program(self) -> None:
1112
+ """Let the traces this run emits find its program by its run id.
1113
+
1114
+ A trace with no program of its own, such as a reflection or judge call, or a
1115
+ judge module's trace, takes the run's program (`context.optimization_program_id`).
1116
+ The id is the one this run resolved, so publishing it costs no request.
1117
+ """
1118
+ if self.optimization_run_id and self.program_id is not None:
1119
+ context.set_optimization_program_id(self.optimization_run_id, self.program_id)
1120
+
1092
1121
  def _export_update(self) -> None:
1093
1122
  """Update existing optimization run record."""
1094
1123
  exp = self._active_exporter()
@@ -1113,6 +1142,7 @@ class OptimizationTracker:
1113
1142
  if not exp:
1114
1143
  logger.warning("No exporter available, optimization run not exported")
1115
1144
  return
1145
+ self._resolve_generated_program(exp)
1116
1146
  # Guarded exactly as start() guards it: the exporters that never reach
1117
1147
  # serve return None here, so an unconditional assignment would write None
1118
1148
  # over a real id the moment one of them meets a run that already has one.
@@ -0,0 +1,86 @@
1
+ """The names the SDK gives programs their users never named.
2
+
3
+ A trace, an eval run or an optimization run takes its program from the id its config
4
+ declares, else the name its call declares, else a name this module generates for its
5
+ root module. The SDK resolves a generated name to a program id through the same route
6
+ as a declared one, ``PUT /api/v1/programs/by-name/{name}``, and says that it generated
7
+ the name, so the program does not count toward the plan's program limit.
8
+
9
+ Serve gives a generated name no meaning. It stores and resolves it like any other
10
+ program name, and never parses it. So the derivation belongs to the SDK alone, and
11
+ ``generated_program_name`` is the only function that knows it. A later SDK may derive
12
+ names some other way, such as from where the program was constructed, without any
13
+ change to serve. Programs then fork once, because the new name names no program yet.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import re
19
+ import unicodedata
20
+ from typing import Any
21
+
22
+ from . import identity
23
+
24
+ # A program name is at most 64 characters. The suffix takes 13 of them, "-" and twelve
25
+ # hex digits, and the class takes the rest.
26
+ _MAX_NAME_LENGTH = 64
27
+ _SUFFIX_DIGITS = 12
28
+ _MAX_CLASS_LENGTH = _MAX_NAME_LENGTH - 1 - _SUFFIX_DIGITS
29
+ # A class whose name leaves nothing a program name may hold.
30
+ _FALLBACK_CLASS = "Module"
31
+
32
+ _NOT_IN_A_NAME = re.compile(r"[^A-Za-z0-9._-]+")
33
+ _NAME_PUNCTUATION = "._-"
34
+ _RESERVED_PREFIX = "p_"
35
+
36
+
37
+ def generated_program_name(module: Any, signature_hash: int | None = None) -> str:
38
+ """The name the SDK gives the program ``module`` runs as, when its user named none.
39
+
40
+ The name is the module's class and twelve hex digits of its signature hash, such as
41
+ ``ChainOfThought-17e1f4089a24``. Programs of one shape get one name in every process,
42
+ so their traces land in one program, and programs of different shapes get different
43
+ names. An optimizer's new instructions and demos keep the shape, so they keep the
44
+ name. The name always fits the program-name syntax, whatever the class is called.
45
+
46
+ A caller that already holds ``identity.compute_program_hash(module)`` passes it as
47
+ ``signature_hash``, so the module is reflected once. Without it, this function
48
+ computes the hash.
49
+ """
50
+ if signature_hash is None:
51
+ signature_hash = identity.compute_program_hash(module)
52
+ signature_hash = signature_hash or 0
53
+ suffix = f"{signature_hash & 0xFFFF_FFFF_FFFF_FFFF:016x}"[:_SUFFIX_DIGITS]
54
+ return f"{_class_part(module.__class__.__name__)}-{suffix}"
55
+
56
+
57
+ def _class_part(class_name: str) -> str:
58
+ """``class_name`` as the start of a program name: ASCII, no reserved prefix, short enough."""
59
+ ascii_name = unicodedata.normalize("NFKD", class_name).encode("ascii", "ignore").decode("ascii")
60
+ part = _NOT_IN_A_NAME.sub("_", ascii_name)
61
+ # A name starts with a letter or digit and never with "p_", in any case.
62
+ while True:
63
+ part = part.lstrip(_NAME_PUNCTUATION)
64
+ if not part.lower().startswith(_RESERVED_PREFIX):
65
+ break
66
+ part = part[len(_RESERVED_PREFIX) :]
67
+ part = part[:_MAX_CLASS_LENGTH].rstrip(_NAME_PUNCTUATION)
68
+ return part or _FALLBACK_CLASS
69
+
70
+
71
+ def resolve_generated_name(exporter: Any, name: str) -> str | None:
72
+ """The program id for a generated name, resolved now on the caller's thread.
73
+
74
+ For a run record that is sent synchronously and must carry its program id then, as
75
+ an eval run's start and an optimization run's start are. The exporter keeps the
76
+ answer for the process, so a name its traces already resolved costs no request.
77
+ Returns None when the exporter resolves no names, as a worker's in-memory collector
78
+ does, or when serve refused the name or could not be reached.
79
+ """
80
+ # Read with getattr because the member is optional: an exporter that resolves no
81
+ # names has none.
82
+ resolve = getattr(exporter, "resolve_program_name", None)
83
+ if not callable(resolve):
84
+ return None
85
+ program_id = resolve(name, generated=True)
86
+ return program_id if isinstance(program_id, str) else None
@@ -890,6 +890,23 @@ and a value of `none` lifts the plan's limit for that org.
890
890
  `cmpnd admin org overrides delete ORG_ID NAME` deletes the override, so the org
891
891
  follows its plan again.
892
892
 
893
+ `cmpnd admin org usage ORG_ID` shows what each limit bounds now, in `in_use`,
894
+ and the org's activity in the last 24 hours, 7 days and 30 days, in `windows`.
895
+ Each window counts traces, model calls and tokens, deployment runs by status,
896
+ and hosted optimizations started and cancelled with their compute. `since` is
897
+ the start of the window's first hour. `traces` or `model_usage` is `null` when
898
+ the server has no published count for the window yet.
899
+
900
+ `cmpnd admin user activity USER_ID` shows a user's milestones, every org they
901
+ belong to, and the newest 50 authority changes recorded about them, such as
902
+ approvals, ownership grants, invites and leaves. A milestone that has not
903
+ happened is `null`. `first_approved_at` is the first approval still on record: an admin-created
904
+ user, an invite redemption and an approved founder write no approval event, so
905
+ their date survives only while a membership keeps it. `oldest_key_at` and
906
+ `last_key_used_at` cover only the keys
907
+ the user still holds, because leaving an org deletes the user's keys there.
908
+ Sign-ins are not recorded, and deployment keys do not count as the user's keys.
909
+
893
910
  `cmpnd admin plans list` shows every plan's limits and feature flags.
894
911
  `cmpnd admin plans limits update PLAN NAME VALUE` and `limits delete PLAN NAME`
895
912
  change a plan's default for every org on it. `cmpnd admin plans flags update
@@ -122,9 +122,12 @@ qa = CapitalQA(lm=dspy.LM("openai/gpt-5-mini"))
122
122
  ## Sketch mode
123
123
 
124
124
  `auto_instrument()` is sketch mode. It traces every DSPy call and declares no
125
- program names. Its traces belong to no program, so the platform files them under
126
- Unattributed, and the first one in a process logs a warning that points to
127
- `@cmpnd.program(name)` and `cmpnd.Client`. A named program refuses sketch mode: calling a `cmpnd.Program`
125
+ program names. Each trace goes to a program under a name the SDK generates from
126
+ its root module's class and signature, such as `ChainOfThought-17e1f4089a24`, so
127
+ each program shape lands in a program of its own. Such a program does not count
128
+ toward the plan's program limit. The first such trace in a process logs a warning
129
+ that names the generated name and points to `@cmpnd.program(name)`,
130
+ `cmpnd.Program(name, module=..., lm=...)` and `cmpnd.Client`. A named program refuses sketch mode: calling a `cmpnd.Program`
128
131
  directly while `auto_instrument()` is on raises a `TypeError` that points to
129
132
  `cmpnd.Client`. The refusal applies at any depth, so a program nested inside a
130
133
  plain module called under `auto_instrument()` raises it too. A program run