cmpnd 0.63.0__tar.gz → 0.64.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 (395) hide show
  1. {cmpnd-0.63.0 → cmpnd-0.64.0}/PKG-INFO +40 -46
  2. {cmpnd-0.63.0 → cmpnd-0.64.0}/README.md +39 -45
  3. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_assessments.py +6 -5
  4. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_traces.py +11 -14
  5. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_valsets.py +9 -7
  6. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/callback.py +25 -0
  7. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/__init__.py +0 -2
  8. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/assessments.py +11 -2
  9. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/evals.py +1 -1
  10. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/programs.py +59 -16
  11. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/top.py +19 -46
  12. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/traces.py +8 -14
  13. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/valsets.py +6 -25
  14. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/verbs.py +5 -5
  15. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/client.py +32 -9
  16. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/programs.py +22 -1
  17. {cmpnd-0.63.0 → cmpnd-0.64.0}/docs/cli.md +59 -52
  18. {cmpnd-0.63.0 → cmpnd-0.64.0}/docs/client.md +18 -10
  19. {cmpnd-0.63.0 → cmpnd-0.64.0}/docs/reading-traces.md +2 -3
  20. {cmpnd-0.63.0 → cmpnd-0.64.0}/docs/sdk-instrumentation.md +16 -3
  21. {cmpnd-0.63.0 → cmpnd-0.64.0}/pyproject.toml +1 -1
  22. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_traces_read.py +1 -1
  23. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_valsets_cli.py +11 -10
  24. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_assessments_api.py +15 -5
  25. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_assessments.py +34 -7
  26. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_evals.py +1 -1
  27. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_parity.py +0 -4
  28. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_programs.py +90 -4
  29. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_schema_sample.py +12 -8
  30. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_shell.py +7 -0
  31. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_top.py +77 -7
  32. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_traces.py +0 -4
  33. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_traces_program_and_module.py +4 -5
  34. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_traces_search_wire.py +28 -16
  35. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_valsets.py +18 -14
  36. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_client.py +12 -2
  37. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_client_program_names.py +142 -0
  38. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_traces_read.py +0 -2
  39. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_valsets_api.py +34 -8
  40. {cmpnd-0.63.0 → cmpnd-0.64.0}/uv.lock +1 -1
  41. cmpnd-0.63.0/cmpnd/cli/commands/signatures.py +0 -164
  42. cmpnd-0.63.0/tests/test_cli/test_signatures.py +0 -225
  43. {cmpnd-0.63.0 → cmpnd-0.64.0}/.gitignore +0 -0
  44. {cmpnd-0.63.0 → cmpnd-0.64.0}/AGENTS.md +0 -0
  45. {cmpnd-0.63.0 → cmpnd-0.64.0}/CLAUDE.md +0 -0
  46. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/__init__.py +0 -0
  47. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_authored_import.py +0 -0
  48. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_deployments.py +0 -0
  49. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_exporter_pool.py +0 -0
  50. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_gepa_patch.py +0 -0
  51. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_http.py +0 -0
  52. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_program_patch.py +0 -0
  53. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_rlm_patch.py +0 -0
  54. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_runtime_tools.py +0 -0
  55. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/_timespec.py +0 -0
  56. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/authored.py +0 -0
  57. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/api_client.py +0 -0
  58. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/__init__.py +0 -0
  59. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/admin.py +0 -0
  60. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/auth.py +0 -0
  61. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/datasets.py +0 -0
  62. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/deployments.py +0 -0
  63. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/login.py +0 -0
  64. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/optimizations.py +0 -0
  65. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/org.py +0 -0
  66. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  67. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  68. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/credentials.py +0 -0
  69. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/deprecation.py +0 -0
  70. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/pagination.py +0 -0
  71. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/shell.py +0 -0
  72. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/trace_render.py +0 -0
  73. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/cli/tree_help.py +0 -0
  74. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/configuration.py +0 -0
  75. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/context.py +0 -0
  76. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/credentials.py +0 -0
  77. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/dataset_sync.py +0 -0
  78. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/datasets.py +0 -0
  79. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/decorators.py +0 -0
  80. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/deployment.py +0 -0
  81. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/deployment_metadata.py +0 -0
  82. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/deployment_runs.py +0 -0
  83. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/eval_handler.py +0 -0
  84. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/execution.py +0 -0
  85. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/exporter.py +0 -0
  86. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/exporter_http.py +0 -0
  87. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/helpers.py +0 -0
  88. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/identity.py +0 -0
  89. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/imaging.py +0 -0
  90. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/ir/__init__.py +0 -0
  91. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/ir/decode.py +0 -0
  92. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/ir/encode.py +0 -0
  93. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/ir/hash.py +0 -0
  94. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/ir/program.py +0 -0
  95. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/ir/reflection.py +0 -0
  96. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/ir/types.py +0 -0
  97. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/ir/xxh64.py +0 -0
  98. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/lm_configuration.py +0 -0
  99. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/mcp_servers.py +0 -0
  100. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/models.py +0 -0
  101. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/named_programs.py +0 -0
  102. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/optimization.py +0 -0
  103. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/optimizers/__init__.py +0 -0
  104. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/optimizers/gepa.py +0 -0
  105. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  106. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/optimizers/resume.py +0 -0
  107. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/optimizers/tracker.py +0 -0
  108. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/packaging.py +0 -0
  109. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/references.py +0 -0
  110. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/retry.py +0 -0
  111. {cmpnd-0.63.0 → cmpnd-0.64.0}/cmpnd/traces.py +0 -0
  112. {cmpnd-0.63.0 → cmpnd-0.64.0}/docs/2026-04-02-code-review.md +0 -0
  113. {cmpnd-0.63.0 → cmpnd-0.64.0}/docs/custom-proposer.md +0 -0
  114. {cmpnd-0.63.0 → cmpnd-0.64.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  115. {cmpnd-0.63.0 → cmpnd-0.64.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  116. {cmpnd-0.63.0 → cmpnd-0.64.0}/examples/generalizing_proposer.py +0 -0
  117. {cmpnd-0.63.0 → cmpnd-0.64.0}/examples/local_ollama.py +0 -0
  118. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/LINEAGE.md +0 -0
  119. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/README.md +0 -0
  120. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  121. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  122. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  123. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  124. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  125. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  126. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/py.typed +0 -0
  127. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  128. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/retry.py +0 -0
  129. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  130. {cmpnd-0.63.0 → cmpnd-0.64.0}/ingest/pyproject.toml +0 -0
  131. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/audit-sdist.sh +0 -0
  132. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/capture_review_run.py +0 -0
  133. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/check-pip-install.sh +0 -0
  134. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/check_release_artifacts.py +0 -0
  135. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/fixtures/review_run.json.gz +0 -0
  136. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/gepa_resume_bug.py +0 -0
  137. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/install-test-env.sh +0 -0
  138. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/release-stress.sh +0 -0
  139. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/require_version_bump.py +0 -0
  140. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/seed_review_run.py +0 -0
  141. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/selfhost_compat.py +0 -0
  142. {cmpnd-0.63.0 → cmpnd-0.64.0}/scripts/test-in-linux.sh +0 -0
  143. {cmpnd-0.63.0 → cmpnd-0.64.0}/stubs/dspy/__init__.pyi +0 -0
  144. {cmpnd-0.63.0 → cmpnd-0.64.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  145. {cmpnd-0.63.0 → cmpnd-0.64.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  146. {cmpnd-0.63.0 → cmpnd-0.64.0}/stubs/dspy/utils/__init__.pyi +0 -0
  147. {cmpnd-0.63.0 → cmpnd-0.64.0}/stubs/dspy/utils/callback.pyi +0 -0
  148. {cmpnd-0.63.0 → cmpnd-0.64.0}/stubs/dspy/utils/dummies.pyi +0 -0
  149. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/__init__.py +0 -0
  150. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/_credential_route.py +0 -0
  151. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/_deployment_signature_helpers.py +0 -0
  152. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/_exporter_helpers.py +0 -0
  153. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/_fake_clock.py +0 -0
  154. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/_identity_helpers.py +0 -0
  155. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/_resolve_refs.py +0 -0
  156. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/_traced_run.py +0 -0
  157. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/conftest.py +0 -0
  158. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/__init__.py +0 -0
  159. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/committee_task.py +0 -0
  160. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/conftest.py +0 -0
  161. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  162. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  163. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/fake_lm.py +0 -0
  164. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/helpers.py +0 -0
  165. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/target_capabilities.py +0 -0
  166. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_assessments_cli.py +0 -0
  167. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_capture_prompts.py +0 -0
  168. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_composite_module.py +0 -0
  169. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  170. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_deploy_execute.py +0 -0
  171. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_deploy_pandas.py +0 -0
  172. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_deploy_programs.py +0 -0
  173. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_edge_cases.py +0 -0
  174. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_example_hash.py +0 -0
  175. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_gepa_optimization.py +0 -0
  176. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_interpreter_spans.py +0 -0
  177. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_lm_accuracy.py +0 -0
  178. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_module_tracing.py +0 -0
  179. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_optimization_lineage.py +0 -0
  180. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  181. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
  182. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
  183. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_failures.py +0 -0
  184. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_loop.py +0 -0
  185. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  186. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  187. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_parallel_export.py +0 -0
  188. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_project_attribution.py +0 -0
  189. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_react_tool_spans.py +0 -0
  190. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_save_load.py +0 -0
  191. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_trace_structure.py +0 -0
  192. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/e2e/test_user_attribution.py +0 -0
  193. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  194. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  195. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  196. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  197. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  198. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  199. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  200. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  201. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  202. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/10_react.json +0 -0
  203. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/11_refine.json +0 -0
  204. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/12_retrieve.json +0 -0
  205. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/13_knn.json +0 -0
  206. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/14_parallel.json +0 -0
  207. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/identity_vectors/README.md +0 -0
  208. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/ingest_conformance.py +0 -0
  209. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/__init__.py +0 -0
  210. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/authored_optimize/judge.py +0 -0
  211. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  212. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/conftest.py +0 -0
  213. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  214. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/hosted_optimize_probe.py +0 -0
  215. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/optimization_run_probe.py +0 -0
  216. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  217. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/test_logs_renders_traces.py +0 -0
  218. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/test_optimization_run.py +0 -0
  219. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/test_optimizing_progress.py +0 -0
  220. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/integration/test_unhappy_path.py +0 -0
  221. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/__init__.py +0 -0
  222. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/__main__.py +0 -0
  223. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/drain.py +0 -0
  224. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/ingest.py +0 -0
  225. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/latency_transport.py +0 -0
  226. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/rlm_shape.py +0 -0
  227. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/rlm_size_report.py +0 -0
  228. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/sweep.py +0 -0
  229. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/test_rlm_shape.py +0 -0
  230. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/load/test_rlm_size_report.py +0 -0
  231. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/__init__.py +0 -0
  232. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/conftest.py +0 -0
  233. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/seed_parity.py +0 -0
  234. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_auth.py +0 -0
  235. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_datasets.py +0 -0
  236. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_deployments.py +0 -0
  237. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_evals.py +0 -0
  238. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_health.py +0 -0
  239. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_modules.py +0 -0
  240. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_optimizations.py +0 -0
  241. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_programs.py +0 -0
  242. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_signatures.py +0 -0
  243. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_trace_streaming.py +0 -0
  244. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/parity/test_traces.py +0 -0
  245. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/span_reference_fixture.py +0 -0
  246. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/__init__.py +0 -0
  247. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/_provider.py +0 -0
  248. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/_readiness.py +0 -0
  249. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/_reliability.py +0 -0
  250. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/_result.py +0 -0
  251. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/mint_bedrock_token.py +0 -0
  252. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_bedrock_converse.py +0 -0
  253. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_deploy_programs.py +0 -0
  254. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_multi_client_stress.py +0 -0
  255. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_optimize_client_committee.py +0 -0
  256. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_optimize_loop.py +0 -0
  257. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
  258. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_optimize_stress.py +0 -0
  259. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_provider_roles.py +0 -0
  260. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_readiness.py +0 -0
  261. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_real_provider_twins.py +0 -0
  262. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_reliability.py +0 -0
  263. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_reliability_classify.py +0 -0
  264. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_rlm_wasm.py +0 -0
  265. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/stress/test_trace_search.py +0 -0
  266. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_adapter_reentrancy.py +0 -0
  267. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_authored.py +0 -0
  268. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_authored_binding_credentials.py +0 -0
  269. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_authored_bundle_collisions.py +0 -0
  270. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_authored_metric_contract.py +0 -0
  271. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_authored_named_models.py +0 -0
  272. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_authored_optimize.py +0 -0
  273. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_callback.py +0 -0
  274. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_candidate_lineage.py +0 -0
  275. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/__init__.py +0 -0
  276. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/conftest.py +0 -0
  277. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_admin.py +0 -0
  278. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_api_client.py +0 -0
  279. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_auth.py +0 -0
  280. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_credentials.py +0 -0
  281. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_datasets.py +0 -0
  282. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_deployment_runs.py +0 -0
  283. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_deployments.py +0 -0
  284. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_login.py +0 -0
  285. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_main.py +0 -0
  286. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_optimizations.py +0 -0
  287. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_org.py +0 -0
  288. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_pagination.py +0 -0
  289. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_provider_credentials.py +0 -0
  290. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_tool_servers.py +0 -0
  291. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cli/test_tree_help.py +0 -0
  292. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_client_floor.py +0 -0
  293. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_clustering/__init__.py +0 -0
  294. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_clustering/conftest.py +0 -0
  295. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_clustering/test_edge_cases.py +0 -0
  296. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  297. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_clustering/test_type_conversion.py +0 -0
  298. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_cmp551_grouping_identity.py +0 -0
  299. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_configuration.py +0 -0
  300. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_context.py +0 -0
  301. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_context_propagation.py +0 -0
  302. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_credential_kwargs.py +0 -0
  303. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_credential_selection.py +0 -0
  304. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_credential_values.py +0 -0
  305. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_dataset_polling.py +0 -0
  306. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_dataset_sync.py +0 -0
  307. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_datasets.py +0 -0
  308. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_decision_client_spans.py +0 -0
  309. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_decorators.py +0 -0
  310. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_deploy.py +0 -0
  311. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  312. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_deploy_program_id.py +0 -0
  313. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_deployment_metadata.py +0 -0
  314. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_deployment_runs.py +0 -0
  315. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_deployments_read.py +0 -0
  316. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_eval.py +0 -0
  317. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_eval_example_attribution.py +0 -0
  318. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_eval_start.py +0 -0
  319. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_eval_trace_grouping.py +0 -0
  320. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_exception_paths.py +0 -0
  321. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_execute.py +0 -0
  322. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_execute_run_identity.py +0 -0
  323. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_exporter.py +0 -0
  324. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_exporter_program_names.py +0 -0
  325. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_exporter_queue.py +0 -0
  326. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_exporter_real.py +0 -0
  327. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_fake_lm.py +0 -0
  328. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_gepa_callback.py +0 -0
  329. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_gepa_integration.py +0 -0
  330. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_gepa_output_normalization.py +0 -0
  331. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_gepa_resume.py +0 -0
  332. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_identity.py +0 -0
  333. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_imaging.py +0 -0
  334. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_import_weight.py +0 -0
  335. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_ingest_conformance.py +0 -0
  336. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_ingest_second_producer.py +0 -0
  337. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_integration.py +0 -0
  338. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_interpreter_callback.py +0 -0
  339. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_ir_encoder_properties.py +0 -0
  340. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_ir_golden_vectors.py +0 -0
  341. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_ir_roundtrip.py +0 -0
  342. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_lm15_native_calls.py +0 -0
  343. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_lm_configuration.py +0 -0
  344. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_lm_usage_shapes.py +0 -0
  345. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_mcp_tools.py +0 -0
  346. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_models.py +0 -0
  347. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_multi_client_stress_readiness.py +0 -0
  348. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimization_progress.py +0 -0
  349. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimization_start.py +0 -0
  350. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimize.py +0 -0
  351. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimize_budget.py +0 -0
  352. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimize_lift.py +0 -0
  353. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimize_lms.py +0 -0
  354. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimize_proposer.py +0 -0
  355. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimize_reflection_lm.py +0 -0
  356. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_optimize_terminal_failure.py +0 -0
  357. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_packaging.py +0 -0
  358. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_packaging_callable_source.py +0 -0
  359. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_packaging_mcp_tools.py +0 -0
  360. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_packaging_program_source.py +0 -0
  361. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_poll_until.py +0 -0
  362. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_program_id_deprecation.py +0 -0
  363. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_program_identity.py +0 -0
  364. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_program_lineage_persist.py +0 -0
  365. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_program_names_in_deploy.py +0 -0
  366. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_programs_create.py +0 -0
  367. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_project_payload.py +0 -0
  368. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_real_provider_twins_readiness.py +0 -0
  369. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_reflector_properties.py +0 -0
  370. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_reflector_smoke.py +0 -0
  371. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_release_stress_script.py +0 -0
  372. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_replaced_names.py +0 -0
  373. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_repro_reactv2_bugs.py +0 -0
  374. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_require_version_bump.py +0 -0
  375. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_retry.py +0 -0
  376. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_reversible_failures.py +0 -0
  377. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_rlm_patch.py +0 -0
  378. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_run_attribution_paths.py +0 -0
  379. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_runtime_tools.py +0 -0
  380. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_schemas.py +0 -0
  381. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_seal_lm_credential.py +0 -0
  382. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_seed_review_run.py +0 -0
  383. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_signature_inputs.py +0 -0
  384. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_span_references.py +0 -0
  385. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_span_sent_once.py +0 -0
  386. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_system_one.py +0 -0
  387. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_trace_context_concurrency.py +0 -0
  388. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_trace_fact_wire_contract.py +0 -0
  389. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_trace_facts.py +0 -0
  390. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_trace_render.py +0 -0
  391. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_tracked_gepa.py +0 -0
  392. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_tracker_enrich.py +0 -0
  393. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_truncation.py +0 -0
  394. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_upload_auth_headers.py +0 -0
  395. {cmpnd-0.63.0 → cmpnd-0.64.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.63.0
3
+ Version: 0.64.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>
@@ -51,9 +51,9 @@ Description-Content-Type: text/markdown
51
51
 
52
52
  # cmpnd
53
53
 
54
- The `cmpnd` package traces DSPy programs and deploys and optimizes them on the
55
- cmpnd platform. One call turns on tracing, and a client deploys, runs and
56
- optimizes named programs.
54
+ The `cmpnd` package traces, deploys and optimizes DSPy programs on the cmpnd
55
+ platform. You give a program a name, and a client records its traces,
56
+ deployments and optimization runs under that name.
57
57
 
58
58
  ## Installation
59
59
 
@@ -66,46 +66,15 @@ uv add cmpnd # or: pip install cmpnd
66
66
  The `cmpnd` library works with the cmpnd service at
67
67
  [platform.cmpnd.ai](https://platform.cmpnd.ai). Create an API key on the
68
68
  platform under Settings → API keys, and pass it as `api_key=` to
69
- `cmpnd.configure()` or `cmpnd.Client()`. For a self-hosted cmpnd install, also
69
+ `cmpnd.Client()` or `cmpnd.configure()`. For a self-hosted cmpnd install, also
70
70
  pass the install's URL as `endpoint=`.
71
71
 
72
72
  ## Quick start
73
73
 
74
- `cmpnd.auto_instrument()` records a trace of every DSPy call in the process.
75
- Your code needs no other change. Use it to sketch a [DSPy program](https://dspy.ai/) and see what it
76
- does.
77
-
78
- ```python
79
- import cmpnd
80
- import dspy
81
-
82
- cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
83
- cmpnd.auto_instrument()
84
-
85
- dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
86
-
87
- cot = dspy.ChainOfThought("question -> answer")
88
- result = cot(question="What is DSPy?") # the trace goes to cmpnd
89
-
90
- cmpnd.trace_id(result) # "a1b2c3d4-...", or None outside instrumentation
91
- ```
92
-
93
- `cmpnd.trace_id()` links a result to its platform trace, for example to attach
94
- feedback later. Traces recorded this way have no program name. The platform
95
- groups them by the structure of the program's modules. The API reference
96
- below lists the other options of `cmpnd.configure()`.
97
-
98
- ## Program names
99
-
100
- When you’re ready to name programs, evolve them, and share them with your team, switch
101
- to `cmpnd.Client`. A client replaces `configure()` and `auto_instrument()` and offers
102
- more features. A client records only the programs you run through it, and each of
103
- those programs has a program name. A client takes most of the same arguments as `configure()`.
104
-
105
- A program name is the name your organization gives a program, such as `question-answerer`.
106
- Traces, deployments and optimization runs under one program name go to one
107
- program. The platform makes the program the first time your organization uses
108
- the program name.
74
+ A program name is the name your organization gives a program, such as
75
+ `question-answerer`. Traces, deployments and optimization runs under one
76
+ program name go to one program. The platform makes the program the first time
77
+ your organization uses the program name.
109
78
 
110
79
  `@cmpnd.program(name)` above a `dspy.Module` class gives the class a program
111
80
  name. Building the class takes its own arguments and a required `lm=`. The
@@ -128,17 +97,23 @@ class QA(dspy.Predict):
128
97
  qa1 = QA(lm=dspy.LM("openai/gpt-6-luna"))
129
98
  ```
130
99
 
131
- A client runs the program and records its trace under the program name:
100
+ A `cmpnd.Client` runs the program and records its trace under the program
101
+ name:
132
102
 
133
103
  ```python
134
104
  gepa = dspy.GEPA(metric=metric, reflection_lm=dspy.LM("openai/gpt-6-luna"), auto="light")
135
105
 
136
106
  with cmpnd.Client(api_key="ck_xxx", project_tags="my-project") as sdk:
137
- # the trace will go to the program "question-answerer"
107
+ # the trace goes to the program "question-answerer"
138
108
  result = sdk.run_program(qa1, question="What is DSPy?")
139
109
  qa2 = sdk.optimize_program(qa1, gepa, trainset=train, valset=val)
110
+
111
+ cmpnd.trace_id(result) # "a1b2c3d4-...", the trace of this run
140
112
  ```
141
113
 
114
+ `cmpnd.trace_id()` links a result to its platform trace, for example to attach
115
+ feedback later. A client records only the programs you run through it.
116
+
142
117
  A client is active from construction. The `with` form flushes the client's
143
118
  traces and closes it on exit. Without `with`, the client stays active until
144
119
  you call `close()` or the process exits, and its traces go out at exit:
@@ -167,8 +142,26 @@ qa3 = cmpnd.Program(
167
142
  - `optimize_program` runs the optimizer in your process and returns a copy of
168
143
  the program that holds the optimized module. The copy keeps the program name
169
144
  and the LM.
170
- - Calling a `cmpnd.Program` directly while `auto_instrument()` is on raises a
171
- `TypeError` that points to `cmpnd.Client`.
145
+
146
+ ## Sketching with `auto_instrument()`
147
+
148
+ `cmpnd.auto_instrument()` records a trace of every DSPy call in the process,
149
+ with no other change to your code. Use it to sketch a
150
+ [DSPy program](https://dspy.ai/) and see what it does:
151
+
152
+ ```python
153
+ cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
154
+ cmpnd.auto_instrument()
155
+
156
+ dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
157
+ dspy.ChainOfThought("question -> answer")(question="What is DSPy?")
158
+ ```
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
163
+ on raises a `TypeError` that points to the client. The API reference below
164
+ lists the options of `cmpnd.configure()`.
172
165
 
173
166
  ## Deployed programs
174
167
 
@@ -531,7 +524,7 @@ describes one:
531
524
 
532
525
  ```
533
526
  usage: cmpnd [-h] [--version] [--api-key API_KEY] [--endpoint ENDPOINT]
534
- {auth,login,logout,org,credentials,tool-servers,optimizations,programs,signatures,traces,evals,datasets,deployments,ps,status,logs,inspect,schema,sample,run,deploy,optimize}
527
+ {auth,login,logout,org,credentials,tool-servers,optimizations,programs,traces,evals,datasets,deployments,ps,status,logs,inspect,schema,sample,run,deploy,optimize}
535
528
  ...
536
529
 
537
530
  CMPND platform CLI — deploy, run, and inspect DSPy programs.
@@ -625,7 +618,8 @@ captured input and output size limits, 65,536 bytes each by default.
625
618
 
626
619
  ### `cmpnd.auto_instrument()`
627
620
 
628
- Registers the callback with DSPy, so every DSPy call records a trace.
621
+ Registers the callback with DSPy, so every DSPy call records a trace. The
622
+ traces carry no program and land in Unattributed.
629
623
 
630
624
  ### `cmpnd.Client()`
631
625
 
@@ -1,8 +1,8 @@
1
1
  # cmpnd
2
2
 
3
- The `cmpnd` package traces DSPy programs and deploys and optimizes them on the
4
- cmpnd platform. One call turns on tracing, and a client deploys, runs and
5
- optimizes named programs.
3
+ The `cmpnd` package traces, deploys and optimizes DSPy programs on the cmpnd
4
+ platform. You give a program a name, and a client records its traces,
5
+ deployments and optimization runs under that name.
6
6
 
7
7
  ## Installation
8
8
 
@@ -15,46 +15,15 @@ uv add cmpnd # or: pip install cmpnd
15
15
  The `cmpnd` library works with the cmpnd service at
16
16
  [platform.cmpnd.ai](https://platform.cmpnd.ai). Create an API key on the
17
17
  platform under Settings → API keys, and pass it as `api_key=` to
18
- `cmpnd.configure()` or `cmpnd.Client()`. For a self-hosted cmpnd install, also
18
+ `cmpnd.Client()` or `cmpnd.configure()`. For a self-hosted cmpnd install, also
19
19
  pass the install's URL as `endpoint=`.
20
20
 
21
21
  ## Quick start
22
22
 
23
- `cmpnd.auto_instrument()` records a trace of every DSPy call in the process.
24
- Your code needs no other change. Use it to sketch a [DSPy program](https://dspy.ai/) and see what it
25
- does.
26
-
27
- ```python
28
- import cmpnd
29
- import dspy
30
-
31
- cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
32
- cmpnd.auto_instrument()
33
-
34
- dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
35
-
36
- cot = dspy.ChainOfThought("question -> answer")
37
- result = cot(question="What is DSPy?") # the trace goes to cmpnd
38
-
39
- cmpnd.trace_id(result) # "a1b2c3d4-...", or None outside instrumentation
40
- ```
41
-
42
- `cmpnd.trace_id()` links a result to its platform trace, for example to attach
43
- feedback later. Traces recorded this way have no program name. The platform
44
- groups them by the structure of the program's modules. The API reference
45
- below lists the other options of `cmpnd.configure()`.
46
-
47
- ## Program names
48
-
49
- When you’re ready to name programs, evolve them, and share them with your team, switch
50
- to `cmpnd.Client`. A client replaces `configure()` and `auto_instrument()` and offers
51
- more features. A client records only the programs you run through it, and each of
52
- those programs has a program name. A client takes most of the same arguments as `configure()`.
53
-
54
- A program name is the name your organization gives a program, such as `question-answerer`.
55
- Traces, deployments and optimization runs under one program name go to one
56
- program. The platform makes the program the first time your organization uses
57
- the program name.
23
+ A program name is the name your organization gives a program, such as
24
+ `question-answerer`. Traces, deployments and optimization runs under one
25
+ program name go to one program. The platform makes the program the first time
26
+ your organization uses the program name.
58
27
 
59
28
  `@cmpnd.program(name)` above a `dspy.Module` class gives the class a program
60
29
  name. Building the class takes its own arguments and a required `lm=`. The
@@ -77,17 +46,23 @@ class QA(dspy.Predict):
77
46
  qa1 = QA(lm=dspy.LM("openai/gpt-6-luna"))
78
47
  ```
79
48
 
80
- A client runs the program and records its trace under the program name:
49
+ A `cmpnd.Client` runs the program and records its trace under the program
50
+ name:
81
51
 
82
52
  ```python
83
53
  gepa = dspy.GEPA(metric=metric, reflection_lm=dspy.LM("openai/gpt-6-luna"), auto="light")
84
54
 
85
55
  with cmpnd.Client(api_key="ck_xxx", project_tags="my-project") as sdk:
86
- # the trace will go to the program "question-answerer"
56
+ # the trace goes to the program "question-answerer"
87
57
  result = sdk.run_program(qa1, question="What is DSPy?")
88
58
  qa2 = sdk.optimize_program(qa1, gepa, trainset=train, valset=val)
59
+
60
+ cmpnd.trace_id(result) # "a1b2c3d4-...", the trace of this run
89
61
  ```
90
62
 
63
+ `cmpnd.trace_id()` links a result to its platform trace, for example to attach
64
+ feedback later. A client records only the programs you run through it.
65
+
91
66
  A client is active from construction. The `with` form flushes the client's
92
67
  traces and closes it on exit. Without `with`, the client stays active until
93
68
  you call `close()` or the process exits, and its traces go out at exit:
@@ -116,8 +91,26 @@ qa3 = cmpnd.Program(
116
91
  - `optimize_program` runs the optimizer in your process and returns a copy of
117
92
  the program that holds the optimized module. The copy keeps the program name
118
93
  and the LM.
119
- - Calling a `cmpnd.Program` directly while `auto_instrument()` is on raises a
120
- `TypeError` that points to `cmpnd.Client`.
94
+
95
+ ## Sketching with `auto_instrument()`
96
+
97
+ `cmpnd.auto_instrument()` records a trace of every DSPy call in the process,
98
+ with no other change to your code. Use it to sketch a
99
+ [DSPy program](https://dspy.ai/) and see what it does:
100
+
101
+ ```python
102
+ cmpnd.configure(api_key="ck_xxx", project_tags="my-project")
103
+ cmpnd.auto_instrument()
104
+
105
+ dspy.configure(lm=dspy.LM("openai/gpt-6-luna"))
106
+ dspy.ChainOfThought("question -> answer")(question="What is DSPy?")
107
+ ```
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
112
+ on raises a `TypeError` that points to the client. The API reference below
113
+ lists the options of `cmpnd.configure()`.
121
114
 
122
115
  ## Deployed programs
123
116
 
@@ -480,7 +473,7 @@ describes one:
480
473
 
481
474
  ```
482
475
  usage: cmpnd [-h] [--version] [--api-key API_KEY] [--endpoint ENDPOINT]
483
- {auth,login,logout,org,credentials,tool-servers,optimizations,programs,signatures,traces,evals,datasets,deployments,ps,status,logs,inspect,schema,sample,run,deploy,optimize}
476
+ {auth,login,logout,org,credentials,tool-servers,optimizations,programs,traces,evals,datasets,deployments,ps,status,logs,inspect,schema,sample,run,deploy,optimize}
484
477
  ...
485
478
 
486
479
  CMPND platform CLI — deploy, run, and inspect DSPy programs.
@@ -574,7 +567,8 @@ captured input and output size limits, 65,536 bytes each by default.
574
567
 
575
568
  ### `cmpnd.auto_instrument()`
576
569
 
577
- Registers the callback with DSPy, so every DSPy call records a trace.
570
+ Registers the callback with DSPy, so every DSPy call records a trace. The
571
+ traces carry no program and land in Unattributed.
578
572
 
579
573
  ### `cmpnd.Client()`
580
574
 
@@ -13,7 +13,7 @@ import os
13
13
  from collections.abc import Sequence
14
14
  from typing import Any, Literal
15
15
 
16
- from . import _http, configuration
16
+ from . import _http, configuration, programs
17
17
 
18
18
  DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
19
19
 
@@ -58,22 +58,23 @@ def list(
58
58
  ) -> dict[str, Any]:
59
59
  """List assessments by program or by trace, newest first.
60
60
 
61
- Pass exactly one of ``program`` (every assessment of that program) or
62
- ``trace_id`` (only that trace's assessments). Returns
61
+ Pass exactly one of ``program`` (every assessment of that program, named by its
62
+ name or ``p_`` id) or ``trace_id`` (only that trace's assessments). Returns
63
63
  ``{"assessments": [...], "total": N, "limit", "offset", "has_more"}``.
64
64
  ``limit`` (max rows) and ``offset`` (skip this many) page the full set — the
65
65
  server clamps ``limit`` to at most 200.
66
66
  """
67
67
  if program is not None and trace_id is not None:
68
68
  raise ValueError("pass exactly one of program or trace_id")
69
+ client = _client(api_key, endpoint)
69
70
  if trace_id is not None:
70
71
  scope: dict[str, str] = {"trace_id": trace_id}
71
72
  elif program is not None:
72
- scope = {"program": program}
73
+ scope = {"program": programs.program_id_for(client, program)}
73
74
  else:
74
75
  raise ValueError("pass exactly one of program or trace_id")
75
76
  params = {**scope, "limit": str(limit), "offset": str(offset)}
76
- return _client(api_key, endpoint).get("/api/v1/assessments", params=params)
77
+ return client.get("/api/v1/assessments", params=params)
77
78
 
78
79
 
79
80
  def get(assessment_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
@@ -4,7 +4,7 @@ The implementation of ``list`` and ``show`` over the shared ``_http`` transport.
4
4
  :class:`cmpnd.Client`'s ``list_traces`` / ``get_trace`` call these directly; the
5
5
  deprecated :mod:`cmpnd.traces` shim wraps them with a warning. The filter and
6
6
  verb names match the ``cmpnd traces`` CLI vocabulary — ``list`` filtered by
7
- ``query`` / ``program`` / ``module`` / ``hash`` / ``deployment`` / ``since`` / ``until`` /
7
+ ``query`` / ``program`` / ``module`` / ``deployment`` / ``since`` / ``until`` /
8
8
  ``all_history``, and ``show`` for one trace — so the surfaces do not drift.
9
9
  """
10
10
 
@@ -29,7 +29,6 @@ def list(
29
29
  model: str | None = None,
30
30
  program: str | None = None,
31
31
  module: str | None = None,
32
- hash: str | None = None,
33
32
  deployment: str | None = None,
34
33
  metadata: dict[str, str] | None = None,
35
34
  tags: list[str] | None = None,
@@ -49,11 +48,10 @@ def list(
49
48
  ``ignored_fields`` naming any request field an older server did not understand.
50
49
 
51
50
  The filters mirror ``cmpnd traces list``: ``query`` searches indexed span text;
52
- ``program`` names the program by name, and ``hash`` by its program id (``p_…``)
53
- or a legacy signature hash; ``module`` is the root module's class, matched exactly.
54
- No index serves the class, so without ``program`` or a program id in ``hash`` it
55
- reads every trace in the time range and slows as the range grows. Pair it with a program or a shorter
56
- ``since``;
51
+ ``program`` names the program by its name or its program id (``p_…``);
52
+ ``module`` is the root module's class, matched exactly. No index serves the
53
+ class, so without ``program`` it reads every trace in the time range and slows
54
+ as the range grows. Pair it with a program or a shorter ``since``;
57
55
  ``deployment`` is a deployment id; ``metadata`` is matched by containment (every
58
56
  pair must be present); ``tags`` matches any of the given tags. ``since`` /
59
57
  ``until`` accept an ISO 8601 timestamp, a bare date (``YYYY-MM-DD``), or a
@@ -71,15 +69,14 @@ def list(
71
69
  ("status", status),
72
70
  ("q", query),
73
71
  ("model", model),
74
- ("program_name", program),
75
72
  ("module_class", module),
76
73
  ("deployment_id", deployment),
77
74
  ("tags", tags),
78
75
  ):
79
76
  if val is not None:
80
77
  body[wire_key] = val
81
- if hash is not None:
82
- body.update(program_filter(hash))
78
+ if program is not None:
79
+ body.update(program_filter(program))
83
80
  if metadata is not None:
84
81
  body["metadata"] = dict(metadata)
85
82
  if since is not None:
@@ -92,14 +89,14 @@ def list(
92
89
  def program_filter(value: str) -> dict[str, str]:
93
90
  """The search-body key for one program filter value.
94
91
 
95
- A program id (``p_…``) goes in ``program_id`` and a legacy signature hash in
96
- ``program_signature_hash``, which the route decodes as a typed hash and would
97
- refuse an id in. One helper for the CLI and the SDK, so the two cannot split.
92
+ A program id (``p_…``) goes in ``program_id`` and anything else is a program
93
+ name, since no name starts with ``p_``. One helper for the CLI and the SDK,
94
+ so the two cannot split.
98
95
  """
99
96
  text = value.strip()
100
97
  if identity.is_program_id(text):
101
98
  return {"program_id": text}
102
- return {"program_signature_hash": value}
99
+ return {"program_name": value}
103
100
 
104
101
 
105
102
  def show(trace_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
@@ -15,7 +15,7 @@ import datetime
15
15
  import os
16
16
  from typing import Any
17
17
 
18
- from . import _http, configuration
18
+ from . import _http, configuration, programs
19
19
 
20
20
  DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
21
21
 
@@ -30,22 +30,24 @@ def default_name() -> str:
30
30
  def create(
31
31
  program: str, *, name: str | None = None, api_key: str | None = None, endpoint: str | None = None
32
32
  ) -> dict[str, Any]:
33
- """Create a program-owned valset. ``name`` is auto-assigned when omitted."""
34
- payload = {"program": program, "name": name or default_name()}
35
- return _client(api_key, endpoint).post("/api/v1/valsets", json=payload)
33
+ """Create a valset owned by ``program``, a program name or ``p_`` id. ``name`` is auto-assigned when omitted."""
34
+ client = _client(api_key, endpoint)
35
+ payload = {"program": programs.program_id_for(client, program), "name": name or default_name()}
36
+ return client.post("/api/v1/valsets", json=payload)
36
37
 
37
38
 
38
39
  def list(
39
40
  program: str, *, limit: int = 50, offset: int = 0, api_key: str | None = None, endpoint: str | None = None
40
41
  ) -> dict[str, Any]:
41
- """List a program's valsets, newest first.
42
+ """List the valsets of ``program``, a program name or ``p_`` id, newest first.
42
43
 
43
44
  Returns ``{"valsets": [...], "total": N, "limit", "offset", "has_more"}``.
44
45
  ``limit`` / ``offset`` page the full set — the server clamps ``limit`` to at
45
46
  most 200.
46
47
  """
47
- params = {"program": program, "limit": str(limit), "offset": str(offset)}
48
- return _client(api_key, endpoint).get("/api/v1/valsets", params=params)
48
+ client = _client(api_key, endpoint)
49
+ params = {"program": programs.program_id_for(client, program), "limit": str(limit), "offset": str(offset)}
50
+ return client.get("/api/v1/valsets", params=params)
49
51
 
50
52
 
51
53
  def get(valset_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
@@ -39,6 +39,27 @@ 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
45
+
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:
54
+ return
55
+ _warned_unattributed = True
56
+ 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."
60
+ )
61
+
62
+
42
63
  def trace_id(prediction: Any) -> str | None:
43
64
  """Return the cmpnd trace id for a prediction returned by an instrumented program.
44
65
 
@@ -1193,6 +1214,10 @@ class _ModuleMixin(_CallbackBase):
1193
1214
  module_class=instance.__class__.__name__,
1194
1215
  )
1195
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)
1196
1221
 
1197
1222
  # Attach the run ids this trace starts under, from this callback's own
1198
1223
  # state right now — not at finalize (on_module_end), where the
@@ -24,7 +24,6 @@ from .commands import (
24
24
  org,
25
25
  programs,
26
26
  provider_credentials,
27
- signatures,
28
27
  tool_servers,
29
28
  top,
30
29
  traces,
@@ -164,7 +163,6 @@ def main() -> None:
164
163
  # gates each read on scope, so a caller without it gets a 403, not a
165
164
  # hidden command.
166
165
  programs.register(subparsers)
167
- signatures.register(subparsers)
168
166
  traces.register(subparsers)
169
167
  evals.register(subparsers)
170
168
  datasets.register(subparsers)
@@ -56,7 +56,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
56
56
  scope.add_argument(
57
57
  "program",
58
58
  nargs="?",
59
- help="Program id (p_…), or a legacy signature hash (16-hex, as `cmpnd signatures list` shows)",
59
+ metavar="PROGRAM",
60
+ help="Program name or p_ id",
60
61
  )
61
62
  scope.add_argument("--trace", dest="trace_id", metavar="TRACE_ID", help="A single trace's assessments")
62
63
  pagination.add_args(ls, with_page_aliases=False, default_limit=50)
@@ -88,7 +89,15 @@ def handle_create(client: api_client.ApiClient, args: Any) -> Any:
88
89
 
89
90
 
90
91
  def handle_list(client: api_client.ApiClient, args: Any) -> Any:
91
- scope = {"trace_id": args.trace_id} if args.trace_id else {"program": args.program}
92
+ if args.trace_id:
93
+ scope = {"trace_id": args.trace_id}
94
+ else:
95
+ from . import programs
96
+
97
+ program_id, _ = programs.resolve(
98
+ client, args.program, old_server_remedy="Upgrade the server to name a program."
99
+ )
100
+ scope = {"program": program_id}
92
101
  params = {**scope, **pagination.wire(args, "limit_offset", as_str=True)}
93
102
  return client.get("/api/v1/assessments", params=params)
94
103
 
@@ -50,7 +50,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
50
50
  # `evals compare` is deliberately absent. The route requires a
51
51
  # `dataset_hash`, and no eval read returns one: neither the list nor the
52
52
  # detail projection carries it, and nor does the compare response itself.
53
- # The only surface that emits one is `signatures evals`, so the command
53
+ # The only surface that emits one is `programs evals`, so the command
54
54
  # would ship with a required argument its own noun cannot produce. It goes
55
55
  # in once the projection carries the hash.
56
56
 
@@ -13,8 +13,8 @@ from .. import api_client, pagination
13
13
  from . import provider_credentials
14
14
 
15
15
 
16
- # `show`, `traces`, `stats` and `usage` take a program name or a p_ id, as the
17
- # Trash commands do. The `programs optimized` group and `select` take a compiled
16
+ # `show`, `traces`, `stats`, `usage`, `examples`, `evals` and `history` take a
17
+ # program name or a p_ id, as the Trash commands do. The `programs optimized` group and `select` take a compiled
18
18
  # program hash, the value `programs optimized list` prints as `program_hash`.
19
19
  # The routes resolve that hash against optimization runs, so a program
20
20
  # signature hash 404s there. A compiled hash and a program name have the same
@@ -129,7 +129,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
129
129
  help="List a program's traces, newest first, over the widest window trace search reads",
130
130
  description=(
131
131
  "List PROGRAM's traces, newest first, through trace search over its widest window, 365 days. "
132
- "`cmpnd traces list --hash ID --since DATE` sets the window itself. --limit is at most 500."
132
+ "`cmpnd traces list --program PROGRAM --since DATE` sets the window itself. --limit is at most 500."
133
133
  ),
134
134
  )
135
135
  tr.add_argument("program", metavar="PROGRAM", help="Program name or p_ id")
@@ -145,6 +145,23 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
145
145
  _add_usage_window(usage)
146
146
  usage.set_defaults(func=handle_usage)
147
147
 
148
+ examples = sub.add_parser("examples", help="List a program's recorded input/output examples, newest first")
149
+ examples.add_argument("program", metavar="PROGRAM", help="Program name or p_ id")
150
+ pagination.add_args(examples, with_page_aliases=False, default_limit=25)
151
+ examples.set_defaults(func=handle_examples)
152
+
153
+ evals = sub.add_parser("evals", help="List the eval runs that touched a program, newest first")
154
+ evals.add_argument("program", metavar="PROGRAM", help="Program name or p_ id")
155
+ # This route caps rows and has no offset of any spelling, so it gets a bare
156
+ # --limit rather than the shared pagination pair: an --offset the server
157
+ # drops would read as a page the caller never received.
158
+ evals.add_argument("--limit", type=int, default=10, help="Max eval runs, newest first (default %(default)s)")
159
+ evals.set_defaults(func=handle_evals)
160
+
161
+ history = sub.add_parser("history", help="Show a program's distinct prompt instructions over time")
162
+ history.add_argument("program", metavar="PROGRAM", help="Program name or p_ id")
163
+ history.set_defaults(func=handle_history)
164
+
148
165
  # The registry is the programs an SDK client registered, as distinct from
149
166
  # the optimized programs `programs list` aggregates out of runs — different
150
167
  # records, so they nest rather than share a flat verb.
@@ -310,15 +327,20 @@ _RECORDED_PROGRAMS_CAP = 200
310
327
  def _list_recorded(client: api_client.ApiClient, params: dict[str, str]) -> dict[str, Any]:
311
328
  """List the programs a server from before the program list recorded traces for, paged here.
312
329
 
313
- GET /api/v1/modules answers them in one array, by name and legacy hash, up to its cap. A program with no
314
- traces yet is not in it. A full array may be missing programs past the cap, so the answer then has no total
315
- and says it is incomplete.
330
+ GET /api/v1/modules answers them in one array, up to its cap. A program with no traces yet is not in it. A
331
+ full array may be missing programs past the cap, so the answer then has no total and says it is incomplete.
332
+ Each row keeps what the server recorded except its signature hash, which names no program and which no
333
+ command takes.
316
334
  """
317
- rows = client.get("/api/v1/modules")
318
- if not isinstance(rows, list):
335
+ answered = client.get("/api/v1/modules")
336
+ if not isinstance(answered, list):
319
337
  raise api_client.CliError(
320
338
  "this cmpnd server answered neither the program list nor the list of recorded programs"
321
339
  )
340
+ rows = [
341
+ {key: value for key, value in row.items() if key != "program_signature_hash"} if isinstance(row, dict) else row
342
+ for row in answered
343
+ ]
322
344
  complete = len(rows) < _RECORDED_PROGRAMS_CAP
323
345
  note = "note: this cmpnd server predates the program list, so this lists the programs it recorded traces for."
324
346
  if not complete:
@@ -376,9 +398,15 @@ def _trash_remedy() -> str:
376
398
  return "Manage its Trash in the web app, at /ui/account/trash, or upgrade the server."
377
399
 
378
400
 
401
+ # The reads that have a `programs optimized` counterpart taking a compiled program hash.
402
+ _OPTIMIZED_VERBS = frozenset({"show", "traces", "stats", "usage"})
403
+
404
+
379
405
  def _read_remedy(verb: str) -> str:
380
406
  from . import top
381
407
 
408
+ if verb not in _OPTIMIZED_VERBS:
409
+ return f"Upgrade the server to read a program's {verb} by its name or p_ id."
382
410
  return (
383
411
  f"`{top.hint_programs('optimized', verb, 'HASH')}` reads a compiled program hash there, and "
384
412
  f"`{top.hint_programs('list')}` lists the programs it recorded traces for."
@@ -389,17 +417,15 @@ def _resolve_for_read(client: api_client.ApiClient, ref: str, verb: str, *, tras
389
417
  """Return the p_ id of the program ``ref`` names, for a read of its activity.
390
418
 
391
419
  A program in the Trash has no activity to read, so it is refused unless
392
- ``trashed_ok``. An unknown name may be a compiled program hash, which the
393
- ``programs optimized`` command of the same verb takes.
420
+ ``trashed_ok``. For a verb with a ``programs optimized`` counterpart, an
421
+ unknown name may be a compiled program hash, which that command takes.
394
422
  """
395
423
  from . import top
396
424
 
397
- program_id, trashed = resolve(
398
- client,
399
- ref,
400
- unknown_hint=f"A compiled program hash goes to `{top.hint_programs('optimized', verb, ref)}`.",
401
- old_server_remedy=_read_remedy(verb),
402
- )
425
+ unknown_hint = ""
426
+ if verb in _OPTIMIZED_VERBS:
427
+ unknown_hint = f"A compiled program hash goes to `{top.hint_programs('optimized', verb, ref)}`."
428
+ program_id, trashed = resolve(client, ref, unknown_hint=unknown_hint, old_server_remedy=_read_remedy(verb))
403
429
  if trashed and not trashed_ok:
404
430
  raise api_client.CliError(
405
431
  f"{ref} is in the Trash, so it has no activity to read. "
@@ -451,6 +477,23 @@ def handle_usage(client: api_client.ApiClient, args: Any) -> Any:
451
477
  return _read(client, "usage", f"/api/v1/programs/{program_id}/usage-history", _usage_params(args))
452
478
 
453
479
 
480
+ # The per-program record reads live under /api/v1/signatures/{program}, which takes the program's p_ id.
481
+ def handle_examples(client: api_client.ApiClient, args: Any) -> Any:
482
+ params = pagination.wire(args, "per_page", as_str=True)
483
+ program_id = _resolve_for_read(client, args.program, "examples")
484
+ return _read(client, "examples", f"/api/v1/signatures/{program_id}/examples", params)
485
+
486
+
487
+ def handle_evals(client: api_client.ApiClient, args: Any) -> Any:
488
+ program_id = _resolve_for_read(client, args.program, "evals")
489
+ return _read(client, "evals", f"/api/v1/signatures/{program_id}/evals", {"limit": str(args.limit)})
490
+
491
+
492
+ def handle_history(client: api_client.ApiClient, args: Any) -> Any:
493
+ program_id = _resolve_for_read(client, args.program, "history")
494
+ return _read(client, "history", f"/api/v1/signatures/{program_id}/history")
495
+
496
+
454
497
  def handle_optimized_show(client: api_client.ApiClient, args: Any) -> Any:
455
498
  return client.get(f"/api/v1/programs/{args.hash}")
456
499