cmpnd 0.23.2__tar.gz → 0.24.1__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 (319) hide show
  1. {cmpnd-0.23.2 → cmpnd-0.24.1}/.gitignore +1 -0
  2. cmpnd-0.23.2/CLAUDE.md → cmpnd-0.24.1/AGENTS.md +3 -3
  3. cmpnd-0.24.1/CLAUDE.md +1 -0
  4. {cmpnd-0.23.2 → cmpnd-0.24.1}/PKG-INFO +4 -2
  5. cmpnd-0.24.1/cmpnd/_assessments.py +72 -0
  6. cmpnd-0.23.2/cmpnd/traces.py → cmpnd-0.24.1/cmpnd/_traces.py +8 -19
  7. cmpnd-0.24.1/cmpnd/_valsets.py +107 -0
  8. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/__init__.py +11 -3
  9. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/assessments.py +12 -7
  10. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/top.py +65 -56
  11. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/valsets.py +31 -15
  12. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/shell.py +38 -32
  13. cmpnd-0.24.1/cmpnd/cli/snippet.py +52 -0
  14. cmpnd-0.24.1/cmpnd/cli/tree_help.py +98 -0
  15. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/client.py +111 -24
  16. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/mcp_servers.py +112 -19
  17. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/packaging.py +157 -2
  18. cmpnd-0.24.1/cmpnd/traces.py +35 -0
  19. {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/2026-04-02-code-review.md +1 -1
  20. {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/cli.md +49 -18
  21. {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/client.md +24 -8
  22. {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/reading-traces.md +7 -2
  23. {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/sdk-instrumentation.md +71 -2
  24. {cmpnd-0.23.2 → cmpnd-0.24.1}/pyproject.toml +15 -7
  25. {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/require_version_bump.py +1 -1
  26. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/helpers.py +24 -0
  27. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_assessments_cli.py +4 -1
  28. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_valsets_cli.py +4 -1
  29. cmpnd-0.24.1/tests/test_assessments_api.py +56 -0
  30. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_assessments.py +22 -14
  31. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_main.py +2 -2
  32. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_top.py +137 -0
  33. cmpnd-0.24.1/tests/test_cli/test_tree_help.py +71 -0
  34. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_valsets.py +52 -12
  35. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_client.py +68 -4
  36. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_mcp_tools.py +264 -0
  37. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_packaging_program_source.py +291 -1
  38. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_traces_read.py +46 -26
  39. cmpnd-0.24.1/tests/test_valsets_api.py +98 -0
  40. {cmpnd-0.23.2 → cmpnd-0.24.1}/uv.lock +36 -32
  41. {cmpnd-0.23.2 → cmpnd-0.24.1}/README.md +0 -0
  42. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/__init__.py +0 -0
  43. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_exporter_pool.py +0 -0
  44. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_gepa_patch.py +0 -0
  45. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_http.py +0 -0
  46. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_program_patch.py +0 -0
  47. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_rlm_patch.py +0 -0
  48. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/_timespec.py +0 -0
  49. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/callback.py +0 -0
  50. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/api_client.py +0 -0
  51. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/__init__.py +0 -0
  52. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/admin.py +0 -0
  53. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/auth.py +0 -0
  54. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/datasets.py +0 -0
  55. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/deployments.py +0 -0
  56. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/evals.py +0 -0
  57. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/login.py +0 -0
  58. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/optimizations.py +0 -0
  59. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/org.py +0 -0
  60. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/programs.py +0 -0
  61. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/provider_credentials.py +0 -0
  62. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/signatures.py +0 -0
  63. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/tool_servers.py +0 -0
  64. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/commands/traces.py +0 -0
  65. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/credentials.py +0 -0
  66. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/deprecation.py +0 -0
  67. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/pagination.py +0 -0
  68. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/trace_render.py +0 -0
  69. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/cli/verbs.py +0 -0
  70. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/configuration.py +0 -0
  71. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/context.py +0 -0
  72. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/dataset_sync.py +0 -0
  73. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/datasets.py +0 -0
  74. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/decorators.py +0 -0
  75. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/deployment.py +0 -0
  76. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/eval_handler.py +0 -0
  77. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/execution.py +0 -0
  78. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/exporter.py +0 -0
  79. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/exporter_http.py +0 -0
  80. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/helpers.py +0 -0
  81. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/identity.py +0 -0
  82. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/imaging.py +0 -0
  83. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/__init__.py +0 -0
  84. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/decode.py +0 -0
  85. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/encode.py +0 -0
  86. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/hash.py +0 -0
  87. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/program.py +0 -0
  88. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/reflection.py +0 -0
  89. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/types.py +0 -0
  90. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/ir/xxh64.py +0 -0
  91. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/models.py +0 -0
  92. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimization.py +0 -0
  93. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/__init__.py +0 -0
  94. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/gepa.py +0 -0
  95. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/gepa_callback.py +0 -0
  96. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/resume.py +0 -0
  97. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/optimizers/tracker.py +0 -0
  98. {cmpnd-0.23.2 → cmpnd-0.24.1}/cmpnd/retry.py +0 -0
  99. {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/custom-proposer.md +0 -0
  100. {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  101. {cmpnd-0.23.2 → cmpnd-0.24.1}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  102. {cmpnd-0.23.2 → cmpnd-0.24.1}/examples/generalizing_proposer.py +0 -0
  103. {cmpnd-0.23.2 → cmpnd-0.24.1}/examples/local_ollama.py +0 -0
  104. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/LINEAGE.md +0 -0
  105. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/README.md +0 -0
  106. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/__init__.py +0 -0
  107. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/bodies.py +0 -0
  108. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  109. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/protocols.py +0 -0
  110. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/py.typed +0 -0
  111. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/request_log.py +0 -0
  112. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/cmpnd_ingest/retry.py +0 -0
  113. {cmpnd-0.23.2 → cmpnd-0.24.1}/ingest/pyproject.toml +0 -0
  114. {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/audit-sdist.sh +0 -0
  115. {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/capture_review_run.py +0 -0
  116. {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/check_release_artifacts.py +0 -0
  117. {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/fixtures/review_run.json.gz +0 -0
  118. {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/gepa_resume_bug.py +0 -0
  119. {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/seed_review_run.py +0 -0
  120. {cmpnd-0.23.2 → cmpnd-0.24.1}/scripts/test-in-linux.sh +0 -0
  121. {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/__init__.pyi +0 -0
  122. {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/primitives/__init__.pyi +0 -0
  123. {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/primitives/prediction.pyi +0 -0
  124. {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/utils/__init__.pyi +0 -0
  125. {cmpnd-0.23.2 → cmpnd-0.24.1}/stubs/dspy/utils/callback.pyi +0 -0
  126. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/__init__.py +0 -0
  127. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/_exporter_helpers.py +0 -0
  128. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/_fake_clock.py +0 -0
  129. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/_identity_helpers.py +0 -0
  130. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/conftest.py +0 -0
  131. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/__init__.py +0 -0
  132. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/committee_task.py +0 -0
  133. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/conftest.py +0 -0
  134. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  135. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  136. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/fake_lm.py +0 -0
  137. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/target_capabilities.py +0 -0
  138. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_capture_prompts.py +0 -0
  139. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_composite_module.py +0 -0
  140. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_dataset_auto_creation.py +0 -0
  141. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_deploy_execute.py +0 -0
  142. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_deploy_programs.py +0 -0
  143. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_edge_cases.py +0 -0
  144. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_example_hash.py +0 -0
  145. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_gepa_optimization.py +0 -0
  146. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_interpreter_spans.py +0 -0
  147. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_lm_accuracy.py +0 -0
  148. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_module_tracing.py +0 -0
  149. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimization_lineage.py +0 -0
  150. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimization_metric_identity.py +0 -0
  151. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimize_failures.py +0 -0
  152. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimize_loop.py +0 -0
  153. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimize_review_committee.py +0 -0
  154. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  155. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_parallel_export.py +0 -0
  156. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_project_attribution.py +0 -0
  157. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_react_tool_spans.py +0 -0
  158. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_save_load.py +0 -0
  159. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_trace_structure.py +0 -0
  160. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_traces_read.py +0 -0
  161. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/e2e/test_user_attribution.py +0 -0
  162. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/01_predict_qa.json +0 -0
  163. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  164. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  165. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  166. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  167. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  168. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/07_program_of_thought.json +0 -0
  169. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/08_opaque_module.json +0 -0
  170. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  171. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/10_react.json +0 -0
  172. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/11_refine.json +0 -0
  173. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/12_retrieve.json +0 -0
  174. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/13_knn.json +0 -0
  175. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/14_parallel.json +0 -0
  176. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/identity_vectors/README.md +0 -0
  177. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/ingest_conformance.py +0 -0
  178. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/__init__.py +0 -0
  179. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/conftest.py +0 -0
  180. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/hosted_optimize_probe.py +0 -0
  181. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/optimization_run_probe.py +0 -0
  182. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  183. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_logs_renders_traces.py +0 -0
  184. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_optimization_run.py +0 -0
  185. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_optimizing_progress.py +0 -0
  186. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/integration/test_unhappy_path.py +0 -0
  187. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/__init__.py +0 -0
  188. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/__main__.py +0 -0
  189. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/drain.py +0 -0
  190. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/ingest.py +0 -0
  191. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/latency_transport.py +0 -0
  192. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/rlm_shape.py +0 -0
  193. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/sweep.py +0 -0
  194. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/load/test_rlm_shape.py +0 -0
  195. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/__init__.py +0 -0
  196. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/conftest.py +0 -0
  197. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/seed_parity.py +0 -0
  198. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_auth.py +0 -0
  199. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_datasets.py +0 -0
  200. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_deployments.py +0 -0
  201. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_evals.py +0 -0
  202. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_health.py +0 -0
  203. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_modules.py +0 -0
  204. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_optimizations.py +0 -0
  205. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_programs.py +0 -0
  206. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_signatures.py +0 -0
  207. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_trace_streaming.py +0 -0
  208. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/parity/test_traces.py +0 -0
  209. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/__init__.py +0 -0
  210. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/_provider.py +0 -0
  211. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/_reliability.py +0 -0
  212. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/_result.py +0 -0
  213. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/mint_bedrock_token.py +0 -0
  214. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_deploy_programs.py +0 -0
  215. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_multi_client_stress.py +0 -0
  216. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_optimize_client_committee.py +0 -0
  217. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_optimize_loop.py +0 -0
  218. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_optimize_loop_wasm.py +0 -0
  219. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_optimize_stress.py +0 -0
  220. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_provider_roles.py +0 -0
  221. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_real_provider_twins.py +0 -0
  222. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_reliability.py +0 -0
  223. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_reliability_classify.py +0 -0
  224. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_rlm_wasm.py +0 -0
  225. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/stress/test_trace_search.py +0 -0
  226. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_callback.py +0 -0
  227. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_candidate_lineage.py +0 -0
  228. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/__init__.py +0 -0
  229. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/conftest.py +0 -0
  230. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_admin.py +0 -0
  231. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_api_client.py +0 -0
  232. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_auth.py +0 -0
  233. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_credentials.py +0 -0
  234. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_datasets.py +0 -0
  235. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_deployments.py +0 -0
  236. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_evals.py +0 -0
  237. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_login.py +0 -0
  238. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_optimizations.py +0 -0
  239. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_org.py +0 -0
  240. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_pagination.py +0 -0
  241. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_parity.py +0 -0
  242. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_programs.py +0 -0
  243. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_provider_credentials.py +0 -0
  244. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_schema_sample.py +0 -0
  245. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_shell.py +0 -0
  246. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_signatures.py +0 -0
  247. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_tool_servers.py +0 -0
  248. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_traces.py +0 -0
  249. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cli/test_traces_search_wire.py +0 -0
  250. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/__init__.py +0 -0
  251. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/conftest.py +0 -0
  252. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/test_edge_cases.py +0 -0
  253. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/test_hash_consistency.py +0 -0
  254. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_clustering/test_type_conversion.py +0 -0
  255. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_cmp551_grouping_identity.py +0 -0
  256. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_configuration.py +0 -0
  257. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_context.py +0 -0
  258. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_context_propagation.py +0 -0
  259. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_dataset_polling.py +0 -0
  260. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_dataset_sync.py +0 -0
  261. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_datasets.py +0 -0
  262. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_decorators.py +0 -0
  263. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_deploy.py +0 -0
  264. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_deploy_mcp_tool_references.py +0 -0
  265. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_eval.py +0 -0
  266. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_eval_example_attribution.py +0 -0
  267. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_eval_start.py +0 -0
  268. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_eval_trace_grouping.py +0 -0
  269. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_exception_paths.py +0 -0
  270. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_execute.py +0 -0
  271. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_exporter.py +0 -0
  272. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_exporter_queue.py +0 -0
  273. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_exporter_real.py +0 -0
  274. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_fake_lm.py +0 -0
  275. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_gepa_callback.py +0 -0
  276. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_gepa_integration.py +0 -0
  277. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_gepa_resume.py +0 -0
  278. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_identity.py +0 -0
  279. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_imaging.py +0 -0
  280. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_import_weight.py +0 -0
  281. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ingest_conformance.py +0 -0
  282. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ingest_second_producer.py +0 -0
  283. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_integration.py +0 -0
  284. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_interpreter_callback.py +0 -0
  285. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ir_encoder_properties.py +0 -0
  286. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ir_golden_vectors.py +0 -0
  287. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_ir_roundtrip.py +0 -0
  288. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_lm_usage_shapes.py +0 -0
  289. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_models.py +0 -0
  290. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimization_progress.py +0 -0
  291. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimization_start.py +0 -0
  292. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize.py +0 -0
  293. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_budget.py +0 -0
  294. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_lift.py +0 -0
  295. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_lms.py +0 -0
  296. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_proposer.py +0 -0
  297. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_reflection_lm.py +0 -0
  298. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_optimize_terminal_failure.py +0 -0
  299. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_packaging.py +0 -0
  300. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_packaging_callable_source.py +0 -0
  301. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_packaging_mcp_tools.py +0 -0
  302. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_poll_until.py +0 -0
  303. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_program_lineage_persist.py +0 -0
  304. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_project_payload.py +0 -0
  305. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_reflector_properties.py +0 -0
  306. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_reflector_smoke.py +0 -0
  307. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_repro_reactv2_bugs.py +0 -0
  308. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_require_version_bump.py +0 -0
  309. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_retry.py +0 -0
  310. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_reversible_failures.py +0 -0
  311. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_rlm_patch.py +0 -0
  312. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_schemas.py +0 -0
  313. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_seed_review_run.py +0 -0
  314. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_trace_render.py +0 -0
  315. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_tracked_gepa.py +0 -0
  316. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_tracker_enrich.py +0 -0
  317. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_truncation.py +0 -0
  318. {cmpnd-0.23.2 → cmpnd-0.24.1}/tests/test_xxh64.py +0 -0
  319. {cmpnd-0.23.2 → cmpnd-0.24.1}/todo.md +0 -0
@@ -21,6 +21,7 @@ wheels/
21
21
  # Virtual environments
22
22
  .env
23
23
  .venv
24
+ .venv-linux
24
25
  env/
25
26
  venv/
26
27
  ENV/
@@ -1,6 +1,6 @@
1
- # Claude Code Instructions
1
+ # Agent Instructions
2
2
 
3
- Part of the CMPND monorepo. See `../CLAUDE.md` for cross-cutting rules (hash architecture, null handling, testing philosophy).
3
+ Part of the CMPND monorepo. See `../AGENTS.md` for cross-cutting rules (hash architecture, null handling, testing philosophy).
4
4
 
5
5
  ## This code is released publicly — no private references
6
6
 
@@ -104,7 +104,7 @@ code while the top-level `docs/` rotted: **the docs are part of the diff.**
104
104
  exporter transport, the IR/hash, the optimizer tracker), or changes the wire
105
105
  shape the SDK emits **updates the matching doc in the same commit.** If a
106
106
  change leaves a doc describing the old behavior, the change isn't done.
107
- - The `cmpnd-*` skills under `.claude/skills/` point at these docs rather than
107
+ - The `cmpnd-*` skills under `../.agents/skills/` point at these docs rather than
108
108
  duplicating them — keep the doc authoritative and let the skill be the thin
109
109
  trigger. (Anchor paths are checked by `bin/check-skill-anchors`.)
110
110
  - `docs/plans/*` is append-only design history and is exempt — don't rewrite it.
cmpnd-0.24.1/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.23.2
3
+ Version: 0.24.1
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>
@@ -22,6 +22,7 @@ Requires-Dist: httpx>=0.27.0
22
22
  Requires-Dist: ipython>=8.20
23
23
  Requires-Dist: openai<2
24
24
  Requires-Dist: pydantic>=2.0
25
+ Requires-Dist: typing-extensions>=4.1.0
25
26
  Requires-Dist: xxhash>=3.0.0
26
27
  Provides-Extra: dev
27
28
  Requires-Dist: basedpyright>=1.39; extra == 'dev'
@@ -41,7 +42,8 @@ Requires-Dist: cmpnd-fake-provider; (python_version >= '3.11') and extra == 'fak
41
42
  Provides-Extra: images
42
43
  Requires-Dist: pillow>=10.0; extra == 'images'
43
44
  Provides-Extra: mcp
44
- Requires-Dist: mcp<2,>=1.9; extra == 'mcp'
45
+ Requires-Dist: dspy>=3.3.1; extra == 'mcp'
46
+ Requires-Dist: mcp>=1.9; extra == 'mcp'
45
47
  Description-Content-Type: text/markdown
46
48
 
47
49
  # cmpnd
@@ -0,0 +1,72 @@
1
+ """Assessment HTTP calls behind ``cmpnd.Client``'s assessment methods.
2
+
3
+ Private helpers: the public surface is ``create_assessment`` /
4
+ ``list_assessments`` / ``get_assessment`` on :class:`cmpnd.Client`, which
5
+ delegate here with the client's credentials. Each function reaches one backend
6
+ route over the shared ``_http`` transport. An omitted ``conclusion`` is
7
+ ``"neutral"`` — neither affirming nor disputing the trace.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ from collections.abc import Sequence
14
+ from typing import Any, Literal
15
+
16
+ from . import _http, configuration
17
+
18
+ DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
19
+
20
+ Conclusion = Literal["up", "down", "neutral"]
21
+
22
+
23
+ def create(
24
+ trace_id: str,
25
+ *,
26
+ conclusion: Conclusion = "neutral",
27
+ body: str | None = None,
28
+ annotations: Sequence[tuple[str, str]] | None = None,
29
+ api_key: str | None = None,
30
+ endpoint: str | None = None,
31
+ ) -> dict[str, Any]:
32
+ """Record one judgement of a trace and return the created assessment.
33
+
34
+ ``conclusion`` affirms (``"up"``), disputes (``"down"``), or neither
35
+ (``"neutral"``, the default). ``annotations`` are ``(span_id, note)`` pairs,
36
+ each attaching a note to one span of the trace; the server rejects a span id
37
+ that does not belong to the trace. ``api_key`` / ``endpoint`` override the
38
+ configured credentials for this call.
39
+ """
40
+ payload: dict[str, Any] = {"trace_id": trace_id, "conclusion": conclusion}
41
+ if body is not None:
42
+ payload["body"] = body
43
+ if annotations:
44
+ payload["annotations"] = [{"span_id": span, "note": note} for span, note in annotations]
45
+ return _client(api_key, endpoint).post("/api/v1/assessments", json=payload)
46
+
47
+
48
+ def list(
49
+ program: str, *, limit: int = 50, offset: int = 0, api_key: str | None = None, endpoint: str | None = None
50
+ ) -> dict[str, Any]:
51
+ """List a program's assessments, newest first.
52
+
53
+ Returns ``{"assessments": [...], "total": N, "limit", "offset", "has_more"}``.
54
+ ``limit`` (max rows) and ``offset`` (skip this many) page the full set — the
55
+ server clamps ``limit`` to at most 200.
56
+ """
57
+ params = {"program": program, "limit": str(limit), "offset": str(offset)}
58
+ return _client(api_key, endpoint).get("/api/v1/assessments", params=params)
59
+
60
+
61
+ def get(assessment_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
62
+ """Fetch one assessment with its annotations."""
63
+ return _client(api_key, endpoint).get(f"/api/v1/assessments/{assessment_id}")
64
+
65
+
66
+ def _client(api_key: str | None, endpoint: str | None) -> _http.ApiClient:
67
+ cfg = configuration.get_config()
68
+ resolved_key = api_key or (cfg.api_key if cfg else None) or os.getenv("CMPND_API_KEY")
69
+ if not resolved_key:
70
+ raise _http.ApiError("No API key. Pass api_key=, call cmpnd.configure(api_key=...), or set CMPND_API_KEY.")
71
+ resolved_endpoint = endpoint or (cfg.endpoint if cfg else None) or os.getenv("CMPND_ENDPOINT") or DEFAULT_ENDPOINT
72
+ return _http.ApiClient(resolved_key, resolved_endpoint)
@@ -1,22 +1,11 @@
1
- """Programmatic trace reads for the cmpnd backend.
2
-
3
- ``import cmpnd`` already emits traces; this module reads them back. ``cmpnd.traces``
4
- reaches the same two backend reads the CLI does — ``list`` and ``show`` — over the
5
- same HTTP transport, so the library and the CLI cannot drift. It follows the SDK's
6
- existing shape: module-level verbs over the credentials ``cmpnd.configure()`` set,
7
- like ``cmpnd.deploy`` / ``cmpnd.execute`` / ``cmpnd.load_dataset``.
8
-
9
- >>> import cmpnd
10
- >>> cmpnd.configure(api_key="ct_xxx")
11
- >>> page = cmpnd.traces.list(status="error", since="30d", limit=10)
12
- >>> for row in page["traces"]:
13
- ... detail = cmpnd.traces.show(row["trace_id"])
14
-
15
- The verbs and filter names match the ``cmpnd traces`` CLI vocabulary — ``list`` and
16
- ``show``, filtered by ``query``, ``program``, ``hash``, ``deployment``, ``since`` /
17
- ``until``, ``all_history`` — so a user moves between the two surfaces without
18
- relearning names. (The CLI's ``traces search`` spelling is a deprecated alias of
19
- ``traces list``; this surface offers only ``list``.)
1
+ """Trace-read HTTP calls behind ``cmpnd.Client``'s trace methods.
2
+
3
+ The implementation of ``list`` and ``show`` over the shared ``_http`` transport.
4
+ :class:`cmpnd.Client`'s ``list_traces`` / ``get_trace`` call these directly; the
5
+ deprecated :mod:`cmpnd.traces` shim wraps them with a warning. The filter and
6
+ verb names match the ``cmpnd traces`` CLI vocabulary — ``list`` filtered by
7
+ ``query`` / ``program`` / ``hash`` / ``deployment`` / ``since`` / ``until`` /
8
+ ``all_history``, and ``show`` for one trace — so the surfaces do not drift.
20
9
  """
21
10
 
22
11
  from __future__ import annotations
@@ -0,0 +1,107 @@
1
+ """Validation-set HTTP calls behind ``cmpnd.Client``'s valset methods.
2
+
3
+ Private helpers: the public surface is ``create_valset`` / ``list_valsets`` /
4
+ ``get_valset`` / ``list_valset_entries`` / ``create_valset_entry`` /
5
+ ``delete_valset_entry`` on :class:`cmpnd.Client`, which delegate here with the
6
+ client's credentials. Each function reaches one backend route over the shared
7
+ ``_http`` transport. An entry's input comes from exactly one source — an
8
+ assessment's trace (``from_assessment``) or a JSON ``inputs`` object; the
9
+ expected output is always the caller's.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import datetime
15
+ import os
16
+ from typing import Any
17
+
18
+ from . import _http, configuration
19
+
20
+ DEFAULT_ENDPOINT = "https://platform.cmpnd.ai"
21
+
22
+
23
+ def default_name() -> str:
24
+ """The valset name assigned when the caller omits one — a human-readable
25
+ date label. Names are not required to be unique (the id identifies a
26
+ valset), so a repeated name is fine and needs no collision suffix."""
27
+ return f"valset-{datetime.datetime.now():%Y%m%d-%H%M%S}"
28
+
29
+
30
+ def create(
31
+ program: str, *, name: str | None = None, api_key: str | None = None, endpoint: str | None = None
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)
36
+
37
+
38
+ def list(
39
+ program: str, *, limit: int = 50, offset: int = 0, api_key: str | None = None, endpoint: str | None = None
40
+ ) -> dict[str, Any]:
41
+ """List a program's valsets, newest first.
42
+
43
+ Returns ``{"valsets": [...], "total": N, "limit", "offset", "has_more"}``.
44
+ ``limit`` / ``offset`` page the full set — the server clamps ``limit`` to at
45
+ most 200.
46
+ """
47
+ params = {"program": program, "limit": str(limit), "offset": str(offset)}
48
+ return _client(api_key, endpoint).get("/api/v1/valsets", params=params)
49
+
50
+
51
+ def get(valset_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
52
+ """Fetch one valset with its field shape and current version."""
53
+ return _client(api_key, endpoint).get(f"/api/v1/valsets/{valset_id}")
54
+
55
+
56
+ def list_entries(
57
+ valset_id: str, *, limit: int = 50, offset: int = 0, api_key: str | None = None, endpoint: str | None = None
58
+ ) -> dict[str, Any]:
59
+ """List a valset's active entries.
60
+
61
+ Returns ``{"entries": [...], "total": N, "limit", "offset", "has_more"}``.
62
+ ``limit`` / ``offset`` page the full set — the server clamps ``limit`` to at
63
+ most 200.
64
+ """
65
+ params = {"limit": str(limit), "offset": str(offset)}
66
+ return _client(api_key, endpoint).get(f"/api/v1/valsets/{valset_id}/entries", params=params)
67
+
68
+
69
+ def create_entry(
70
+ valset_id: str,
71
+ *,
72
+ expected_outputs: dict[str, Any],
73
+ inputs: dict[str, Any] | None = None,
74
+ from_assessment: str | None = None,
75
+ api_key: str | None = None,
76
+ endpoint: str | None = None,
77
+ ) -> dict[str, Any]:
78
+ """Add one entry to a valset.
79
+
80
+ The input comes from exactly one source: pass ``from_assessment`` and the
81
+ server derives the inputs from that assessment's trace, or pass ``inputs``
82
+ directly. Supplying both or neither is an error. ``expected_outputs`` is
83
+ always required — the server never infers it.
84
+ """
85
+ if (inputs is None) == (from_assessment is None):
86
+ raise _http.ApiError("pass exactly one of inputs= or from_assessment=")
87
+ payload: dict[str, Any] = {"expected_outputs": expected_outputs}
88
+ if from_assessment is not None:
89
+ payload["from_assessment"] = from_assessment
90
+ else:
91
+ payload["inputs"] = inputs
92
+ return _client(api_key, endpoint).post(f"/api/v1/valsets/{valset_id}/entries", json=payload)
93
+
94
+
95
+ def delete_entry(entry_id: str, *, api_key: str | None = None, endpoint: str | None = None) -> dict[str, Any]:
96
+ """Retire an entry (versioned, not a hard delete)."""
97
+ _client(api_key, endpoint).delete(f"/api/v1/valsets/entries/{entry_id}")
98
+ return {"deleted": entry_id}
99
+
100
+
101
+ def _client(api_key: str | None, endpoint: str | None) -> _http.ApiClient:
102
+ cfg = configuration.get_config()
103
+ resolved_key = api_key or (cfg.api_key if cfg else None) or os.getenv("CMPND_API_KEY")
104
+ if not resolved_key:
105
+ raise _http.ApiError("No API key. Pass api_key=, call cmpnd.configure(api_key=...), or set CMPND_API_KEY.")
106
+ resolved_endpoint = endpoint or (cfg.endpoint if cfg else None) or os.getenv("CMPND_ENDPOINT") or DEFAULT_ENDPOINT
107
+ return _http.ApiClient(resolved_key, resolved_endpoint)
@@ -7,10 +7,11 @@ import importlib.metadata
7
7
  import json
8
8
  import os
9
9
  import sys
10
+ import typing
10
11
 
11
12
  import httpx
12
13
 
13
- from . import api_client, deprecation
14
+ from . import api_client, deprecation, tree_help
14
15
  from .commands import (
15
16
  admin,
16
17
  assessments,
@@ -87,7 +88,7 @@ Auth: set CMPND_API_KEY and (optionally) CMPND_ENDPOINT, or pass --api-key /
87
88
 
88
89
 
89
90
  def main() -> None:
90
- parser = argparse.ArgumentParser(
91
+ parser = tree_help.TreeHelpParser(
91
92
  prog="cmpnd",
92
93
  description="CMPND platform CLI — deploy, invoke, and inspect DSPy programs.",
93
94
  epilog=_EPILOG,
@@ -97,7 +98,14 @@ def main() -> None:
97
98
  parser.add_argument("--api-key", dest="api_key", help="API key (or set CMPND_API_KEY)")
98
99
  parser.add_argument("--endpoint", help="API endpoint (or set CMPND_ENDPOINT)")
99
100
 
100
- subparsers = parser.add_subparsers(dest="resource")
101
+ # The root parser is a TreeHelpParser, so add_subparsers() is typed
102
+ # _SubParsersAction[TreeHelpParser]; the command modules' register() take
103
+ # _SubParsersAction[ArgumentParser], and that generic is invariant. A
104
+ # TreeHelpParser IS an ArgumentParser at runtime, so cast to the base.
105
+ subparsers = typing.cast(
106
+ "argparse._SubParsersAction[argparse.ArgumentParser]",
107
+ parser.add_subparsers(dest="resource"),
108
+ )
101
109
 
102
110
  # Auth subcommand (part of the primary flow, always available)
103
111
  auth.register(subparsers)
@@ -9,7 +9,7 @@ from __future__ import annotations
9
9
  from argparse import ArgumentParser, _SubParsersAction
10
10
  from typing import Any
11
11
 
12
- from .. import api_client
12
+ from .. import api_client, pagination
13
13
 
14
14
 
15
15
  def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
@@ -18,7 +18,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
18
18
 
19
19
  create = sub.add_parser("create", help="Record a judgement of a trace")
20
20
  create.add_argument("trace_id", help="The trace to assess (its SDK trace id)")
21
- conclusion = create.add_mutually_exclusive_group(required=True)
21
+ conclusion = create.add_mutually_exclusive_group()
22
22
  conclusion.add_argument(
23
23
  "--up", dest="conclusion", action="store_const", const="up", help="Affirm the trace's output"
24
24
  )
@@ -26,8 +26,13 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
26
26
  "--down", dest="conclusion", action="store_const", const="down", help="Dispute the trace's output"
27
27
  )
28
28
  conclusion.add_argument(
29
- "--neutral", dest="conclusion", action="store_const", const="neutral", help="Neither affirm nor dispute"
29
+ "--neutral",
30
+ dest="conclusion",
31
+ action="store_const",
32
+ const="neutral",
33
+ help="Neither affirm nor dispute (the default when no conclusion is given)",
30
34
  )
35
+ create.set_defaults(conclusion="neutral")
31
36
  create.add_argument("-m", "--message", dest="body", help="The prose body of the judgement")
32
37
  create.add_argument(
33
38
  "--annotate",
@@ -40,9 +45,8 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
40
45
  create.set_defaults(func=handle_create)
41
46
 
42
47
  ls = sub.add_parser("list", help="List a program's assessments")
43
- ls.add_argument(
44
- "--program", required=True, help="Program signature hash (16-hex, as `cmpnd signatures list` shows)"
45
- )
48
+ ls.add_argument("program", help="Program signature hash (16-hex, as `cmpnd signatures list` shows)")
49
+ pagination.add_args(ls, with_page_aliases=False, default_limit=50)
46
50
  ls.set_defaults(func=handle_list)
47
51
 
48
52
  show = sub.add_parser("show", help="Show one assessment with its annotations")
@@ -60,7 +64,8 @@ def handle_create(client: api_client.ApiClient, args: Any) -> Any:
60
64
 
61
65
 
62
66
  def handle_list(client: api_client.ApiClient, args: Any) -> Any:
63
- return client.get("/api/v1/assessments", params={"program": args.program})
67
+ params = {"program": args.program, **pagination.wire(args, "limit_offset", as_str=True)}
68
+ return client.get("/api/v1/assessments", params=params)
64
69
 
65
70
 
66
71
  def handle_show(client: api_client.ApiClient, args: Any) -> Any:
@@ -15,7 +15,7 @@ from argparse import ArgumentParser, ArgumentTypeError, _SubParsersAction
15
15
  from datetime import datetime, timezone
16
16
  from typing import Any
17
17
 
18
- from .. import api_client, pagination
18
+ from .. import api_client, pagination, snippet
19
19
  from . import signatures
20
20
 
21
21
  # --------------------------------------------------------------------------
@@ -782,30 +782,35 @@ def cmd_deploy(client: api_client.ApiClient, args: Any) -> None:
782
782
 
783
783
  configuration.configure(api_key=client._api_key, endpoint=client._endpoint)
784
784
 
785
- namespace: dict[str, Any] = {"__name__": "__cmpnd_deploy__", "__file__": source_label}
786
- try:
787
- exec(compile(source, source_label, "exec"), namespace)
788
- except Exception as exc:
789
- raise api_client.CliError(f"Snippet raised {type(exc).__name__}: {exc}")
790
-
791
- program = namespace.get("program")
792
- if program is None:
793
- # Fallback: pick the last dspy.Module instance defined.
785
+ # The whole packaging step stays inside the registration, not just the exec:
786
+ # both the annotations DSPy resolves and the class sources the packager
787
+ # inlines are read back out of `sys.modules` after the snippet has run.
788
+ with snippet.as_module("__cmpnd_deploy__", source_label) as module:
789
+ namespace: dict[str, Any] = module.__dict__
794
790
  try:
795
- import dspy
796
- except ImportError:
797
- raise api_client.CliError("Snippet did not bind `program`, and dspy isn't importable to scan for one.")
798
- candidates = [v for v in namespace.values() if isinstance(v, dspy.Module)]
799
- if not candidates:
800
- raise api_client.CliError(
801
- "Snippet did not bind `program` to a dspy.Module instance. Add e.g. `program = dspy.Predict(...)`."
802
- )
803
- program = candidates[-1]
791
+ exec(compile(source, source_label, "exec"), namespace)
792
+ except Exception as exc:
793
+ raise api_client.CliError(f"Snippet raised {type(exc).__name__}: {exc}")
794
+
795
+ program = namespace.get("program")
796
+ if program is None:
797
+ # Fallback: pick the last dspy.Module instance defined.
798
+ try:
799
+ import dspy
800
+ except ImportError:
801
+ raise api_client.CliError("Snippet did not bind `program`, and dspy isn't importable to scan for one.")
802
+ candidates = [v for v in namespace.values() if isinstance(v, dspy.Module)]
803
+ if not candidates:
804
+ raise api_client.CliError(
805
+ "Snippet did not bind `program` to a dspy.Module instance. Add e.g. `program = dspy.Predict(...)`."
806
+ )
807
+ program = candidates[-1]
808
+
809
+ metadata = _parse_deploy_metadata(getattr(args, "metadata", None))
804
810
 
805
- metadata = _parse_deploy_metadata(getattr(args, "metadata", None))
811
+ print(_c("deploying…", "dim"), file=sys.stderr)
812
+ deployed = deployment.deploy(program, metadata=metadata)
806
813
 
807
- print(_c("deploying…", "dim"), file=sys.stderr)
808
- deployed = deployment.deploy(program, metadata=metadata)
809
814
  deployment_id = deployed.deployment_id
810
815
  print(deployment_id)
811
816
 
@@ -882,42 +887,46 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
882
887
 
883
888
  configuration.configure(api_key=client._api_key, endpoint=client._endpoint)
884
889
 
885
- namespace: dict[str, Any] = {"__name__": "__cmpnd_optimize__", "__file__": source_label}
886
- try:
887
- exec(compile(source, source_label, "exec"), namespace)
888
- except Exception as exc:
889
- raise api_client.CliError(f"Snippet raised {type(exc).__name__}: {exc}")
890
+ # Registered for the whole run, not just the exec: optimize deploys the
891
+ # program, and packaging reads the snippet's classes back through
892
+ # `sys.modules`. See `cmpnd.cli.snippet`.
893
+ with snippet.as_module("__cmpnd_optimize__", source_label) as module:
894
+ namespace: dict[str, Any] = module.__dict__
895
+ try:
896
+ exec(compile(source, source_label, "exec"), namespace)
897
+ except Exception as exc:
898
+ raise api_client.CliError(f"Snippet raised {type(exc).__name__}: {exc}")
890
899
 
891
- missing = [name for name in ("program", "trainset", "valset", "metric") if namespace.get(name) is None]
892
- if missing:
893
- raise api_client.CliError(
894
- f"Snippet must bind {missing} — e.g. `program = dspy.Predict('q -> a')`, "
895
- "`trainset = [...]`, `valset = [...]`, "
896
- "`def metric(gold, pred, trace, pred_name, pred_trace): ...` returning "
897
- "`dspy.Prediction(score=, feedback=)`."
898
- )
900
+ missing = [name for name in ("program", "trainset", "valset", "metric") if namespace.get(name) is None]
901
+ if missing:
902
+ raise api_client.CliError(
903
+ f"Snippet must bind {missing} — e.g. `program = dspy.Predict('q -> a')`, "
904
+ "`trainset = [...]`, `valset = [...]`, "
905
+ "`def metric(gold, pred, trace, pred_name, pred_trace): ...` returning "
906
+ "`dspy.Prediction(score=, feedback=)`."
907
+ )
899
908
 
900
- print(_c(f"optimizing with {args.optimizer}…", "dim"), file=sys.stderr)
901
- try:
902
- result = optimization.optimize(
903
- namespace["program"],
904
- optimizer=args.optimizer,
905
- trainset=namespace["trainset"],
906
- valset=namespace["valset"],
907
- metric=namespace["metric"],
908
- deployment_id=getattr(args, "deployment_id", None),
909
- # The flag is named for what a command line can carry — a model name,
910
- # never an LM object — and the parameter for what the Python surface
911
- # accepts. Each layer's name says what that layer takes.
912
- reflection_lm=getattr(args, "reflection_model", None),
913
- max_metric_calls=getattr(args, "max_metric_calls", None),
914
- auto=getattr(args, "auto", None),
915
- max_steps=getattr(args, "max_steps", 10),
916
- patience=getattr(args, "patience", 2),
917
- seed=getattr(args, "seed", 0),
918
- )
919
- except (optimization.OptimizeError, optimization.OptimizeFailed, ValueError) as exc:
920
- raise api_client.CliError(str(exc))
909
+ print(_c(f"optimizing with {args.optimizer}…", "dim"), file=sys.stderr)
910
+ try:
911
+ result = optimization.optimize(
912
+ namespace["program"],
913
+ optimizer=args.optimizer,
914
+ trainset=namespace["trainset"],
915
+ valset=namespace["valset"],
916
+ metric=namespace["metric"],
917
+ deployment_id=getattr(args, "deployment_id", None),
918
+ # The flag is named for what a command line can carry — a model name,
919
+ # never an LM object — and the parameter for what the Python surface
920
+ # accepts. Each layer's name says what that layer takes.
921
+ reflection_lm=getattr(args, "reflection_model", None),
922
+ max_metric_calls=getattr(args, "max_metric_calls", None),
923
+ auto=getattr(args, "auto", None),
924
+ max_steps=getattr(args, "max_steps", 10),
925
+ patience=getattr(args, "patience", 2),
926
+ seed=getattr(args, "seed", 0),
927
+ )
928
+ except (optimization.OptimizeError, optimization.OptimizeFailed, ValueError) as exc:
929
+ raise api_client.CliError(str(exc))
921
930
 
922
931
  if getattr(args, "json", False):
923
932
  print(
@@ -11,7 +11,8 @@ import json
11
11
  from argparse import ArgumentParser, _SubParsersAction
12
12
  from typing import Any
13
13
 
14
- from .. import api_client
14
+ from ... import _valsets
15
+ from .. import api_client, pagination
15
16
 
16
17
 
17
18
  def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
@@ -20,11 +21,15 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
20
21
 
21
22
  create = sub.add_parser("create", help="Create a validation set owned by a program")
22
23
  create.add_argument("program_hash", help="Owning program signature hash (16-hex, as `cmpnd signatures list` shows)")
23
- create.add_argument("--name", required=True, help="Validation-set name (unique within the program)")
24
+ create.add_argument(
25
+ "--name",
26
+ help="Validation-set name — a memory-aid label, need not be unique; auto-assigned from the date when omitted",
27
+ )
24
28
  create.set_defaults(func=handle_create)
25
29
 
26
30
  ls = sub.add_parser("list", help="List a program's validation sets")
27
- ls.add_argument("--program", required=True, help="Program signature hash (16-hex)")
31
+ ls.add_argument("program", help="Program signature hash (16-hex)")
32
+ pagination.add_args(ls, with_page_aliases=False, default_limit=50)
28
33
  ls.set_defaults(func=handle_list)
29
34
 
30
35
  show = sub.add_parser("show", help="Show a validation set")
@@ -36,12 +41,20 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
36
41
 
37
42
  elist = esub.add_parser("list", help="List a valset's active entries")
38
43
  elist.add_argument("valset_id", help="Valset id")
44
+ pagination.add_args(elist, with_page_aliases=False, default_limit=50)
39
45
  elist.set_defaults(func=handle_entries_list)
40
46
 
41
- ecreate = esub.add_parser("create", help="Add an entry from JSON content")
47
+ ecreate = esub.add_parser("create", help="Add an entry from an assessment or from JSON inputs")
42
48
  ecreate.add_argument("valset_id", help="Valset id")
43
- ecreate.add_argument(
44
- "--inputs", required=True, metavar="JSON", help="Inputs object; its keys are the valset's input fields"
49
+ # The entry's input comes from exactly one source: an assessment's trace, or
50
+ # a JSON inputs object supplied directly. The expected output is always the
51
+ # caller's — the server never infers it.
52
+ source = ecreate.add_mutually_exclusive_group(required=True)
53
+ source.add_argument("--inputs", metavar="JSON", help="Inputs object; its keys are the valset's input fields")
54
+ source.add_argument(
55
+ "--from-assessment",
56
+ dest="from_assessment",
57
+ help="Source assessment id; its trace supplies the inputs and records the entry's provenance",
45
58
  )
46
59
  ecreate.add_argument(
47
60
  "--expected-outputs",
@@ -50,9 +63,6 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
50
63
  metavar="JSON",
51
64
  help="Expected-outputs object; its keys are the valset's output fields",
52
65
  )
53
- ecreate.add_argument(
54
- "--from-assessment", dest="from_assessment", help="Source assessment id, recorded as the entry's provenance"
55
- )
56
66
  ecreate.set_defaults(func=handle_entries_create)
57
67
 
58
68
  edelete = esub.add_parser("delete", help="Retire an entry (versioned, not a hard delete)")
@@ -61,11 +71,15 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
61
71
 
62
72
 
63
73
  def handle_create(client: api_client.ApiClient, args: Any) -> Any:
64
- return client.post("/api/v1/valsets", json={"program": args.program_hash, "name": args.name})
74
+ # One generator (cmpnd._valsets.default_name) for both the CLI and the
75
+ # Client, so the collision-avoiding suffix can't drift between them.
76
+ name = args.name or _valsets.default_name()
77
+ return client.post("/api/v1/valsets", json={"program": args.program_hash, "name": name})
65
78
 
66
79
 
67
80
  def handle_list(client: api_client.ApiClient, args: Any) -> Any:
68
- return client.get("/api/v1/valsets", params={"program": args.program})
81
+ params = {"program": args.program, **pagination.wire(args, "limit_offset", as_str=True)}
82
+ return client.get("/api/v1/valsets", params=params)
69
83
 
70
84
 
71
85
  def handle_show(client: api_client.ApiClient, args: Any) -> Any:
@@ -73,15 +87,17 @@ def handle_show(client: api_client.ApiClient, args: Any) -> Any:
73
87
 
74
88
 
75
89
  def handle_entries_list(client: api_client.ApiClient, args: Any) -> Any:
76
- return client.get(f"/api/v1/valsets/{args.valset_id}/entries")
90
+ params = pagination.wire(args, "limit_offset", as_str=True)
91
+ return client.get(f"/api/v1/valsets/{args.valset_id}/entries", params=params)
77
92
 
78
93
 
79
94
  def handle_entries_create(client: api_client.ApiClient, args: Any) -> Any:
80
- inputs = _parse_json_flag("--inputs", args.inputs)
81
- expected = _parse_json_flag("--expected-outputs", args.expected_outputs)
82
- body: dict[str, Any] = {"inputs": inputs, "expected_outputs": expected}
95
+ body: dict[str, Any] = {"expected_outputs": _parse_json_flag("--expected-outputs", args.expected_outputs)}
83
96
  if args.from_assessment:
97
+ # The source assessment's trace supplies the inputs; the server derives them.
84
98
  body["from_assessment"] = args.from_assessment
99
+ else:
100
+ body["inputs"] = _parse_json_flag("--inputs", args.inputs)
85
101
  return client.post(f"/api/v1/valsets/{args.valset_id}/entries", json=body)
86
102
 
87
103