cmpnd 0.23.2__tar.gz → 0.24.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 (318) hide show
  1. {cmpnd-0.23.2 → cmpnd-0.24.0}/.gitignore +1 -0
  2. cmpnd-0.23.2/CLAUDE.md → cmpnd-0.24.0/AGENTS.md +3 -3
  3. cmpnd-0.24.0/CLAUDE.md +1 -0
  4. {cmpnd-0.23.2 → cmpnd-0.24.0}/PKG-INFO +1 -1
  5. cmpnd-0.24.0/cmpnd/_assessments.py +72 -0
  6. cmpnd-0.23.2/cmpnd/traces.py → cmpnd-0.24.0/cmpnd/_traces.py +8 -19
  7. cmpnd-0.24.0/cmpnd/_valsets.py +107 -0
  8. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/__init__.py +11 -3
  9. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/assessments.py +12 -7
  10. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/valsets.py +31 -15
  11. cmpnd-0.24.0/cmpnd/cli/tree_help.py +98 -0
  12. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/client.py +111 -24
  13. cmpnd-0.24.0/cmpnd/traces.py +35 -0
  14. {cmpnd-0.23.2 → cmpnd-0.24.0}/docs/2026-04-02-code-review.md +1 -1
  15. {cmpnd-0.23.2 → cmpnd-0.24.0}/docs/cli.md +26 -18
  16. {cmpnd-0.23.2 → cmpnd-0.24.0}/docs/client.md +24 -8
  17. {cmpnd-0.23.2 → cmpnd-0.24.0}/docs/reading-traces.md +7 -2
  18. {cmpnd-0.23.2 → cmpnd-0.24.0}/docs/sdk-instrumentation.md +2 -2
  19. {cmpnd-0.23.2 → cmpnd-0.24.0}/pyproject.toml +1 -1
  20. {cmpnd-0.23.2 → cmpnd-0.24.0}/scripts/require_version_bump.py +1 -1
  21. cmpnd-0.24.0/tests/test_assessments_api.py +56 -0
  22. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_assessments.py +22 -14
  23. cmpnd-0.24.0/tests/test_cli/test_tree_help.py +71 -0
  24. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_valsets.py +52 -12
  25. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_client.py +68 -4
  26. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_traces_read.py +46 -26
  27. cmpnd-0.24.0/tests/test_valsets_api.py +98 -0
  28. {cmpnd-0.23.2 → cmpnd-0.24.0}/uv.lock +1 -1
  29. {cmpnd-0.23.2 → cmpnd-0.24.0}/README.md +0 -0
  30. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/__init__.py +0 -0
  31. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/_exporter_pool.py +0 -0
  32. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/_gepa_patch.py +0 -0
  33. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/_http.py +0 -0
  34. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/_program_patch.py +0 -0
  35. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/_rlm_patch.py +0 -0
  36. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/_timespec.py +0 -0
  37. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/callback.py +0 -0
  38. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/api_client.py +0 -0
  39. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/__init__.py +0 -0
  40. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/admin.py +0 -0
  41. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/auth.py +0 -0
  42. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/datasets.py +0 -0
  43. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/deployments.py +0 -0
  44. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/evals.py +0 -0
  45. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/login.py +0 -0
  46. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/optimizations.py +0 -0
  47. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/org.py +0 -0
  48. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/programs.py +0 -0
  49. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  50. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/signatures.py +0 -0
  51. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  52. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/top.py +0 -0
  53. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/commands/traces.py +0 -0
  54. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/credentials.py +0 -0
  55. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/deprecation.py +0 -0
  56. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/pagination.py +0 -0
  57. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/shell.py +0 -0
  58. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/trace_render.py +0 -0
  59. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/cli/verbs.py +0 -0
  60. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/configuration.py +0 -0
  61. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/context.py +0 -0
  62. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/dataset_sync.py +0 -0
  63. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/datasets.py +0 -0
  64. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/decorators.py +0 -0
  65. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/deployment.py +0 -0
  66. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/eval_handler.py +0 -0
  67. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/execution.py +0 -0
  68. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/exporter.py +0 -0
  69. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/exporter_http.py +0 -0
  70. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/helpers.py +0 -0
  71. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/identity.py +0 -0
  72. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/imaging.py +0 -0
  73. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/ir/__init__.py +0 -0
  74. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/ir/decode.py +0 -0
  75. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/ir/encode.py +0 -0
  76. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/ir/hash.py +0 -0
  77. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/ir/program.py +0 -0
  78. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/ir/reflection.py +0 -0
  79. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/ir/types.py +0 -0
  80. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/ir/xxh64.py +0 -0
  81. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/mcp_servers.py +0 -0
  82. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/models.py +0 -0
  83. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/optimization.py +0 -0
  84. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/optimizers/__init__.py +0 -0
  85. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/optimizers/gepa.py +0 -0
  86. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  87. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/optimizers/resume.py +0 -0
  88. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/optimizers/tracker.py +0 -0
  89. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/packaging.py +0 -0
  90. {cmpnd-0.23.2 → cmpnd-0.24.0}/cmpnd/retry.py +0 -0
  91. {cmpnd-0.23.2 → cmpnd-0.24.0}/docs/custom-proposer.md +0 -0
  92. {cmpnd-0.23.2 → cmpnd-0.24.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  93. {cmpnd-0.23.2 → cmpnd-0.24.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  94. {cmpnd-0.23.2 → cmpnd-0.24.0}/examples/generalizing_proposer.py +0 -0
  95. {cmpnd-0.23.2 → cmpnd-0.24.0}/examples/local_ollama.py +0 -0
  96. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/LINEAGE.md +0 -0
  97. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/README.md +0 -0
  98. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  99. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  100. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  101. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  102. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/cmpnd_ingest/py.typed +0 -0
  103. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  104. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/cmpnd_ingest/retry.py +0 -0
  105. {cmpnd-0.23.2 → cmpnd-0.24.0}/ingest/pyproject.toml +0 -0
  106. {cmpnd-0.23.2 → cmpnd-0.24.0}/scripts/audit-sdist.sh +0 -0
  107. {cmpnd-0.23.2 → cmpnd-0.24.0}/scripts/capture_review_run.py +0 -0
  108. {cmpnd-0.23.2 → cmpnd-0.24.0}/scripts/check_release_artifacts.py +0 -0
  109. {cmpnd-0.23.2 → cmpnd-0.24.0}/scripts/fixtures/review_run.json.gz +0 -0
  110. {cmpnd-0.23.2 → cmpnd-0.24.0}/scripts/gepa_resume_bug.py +0 -0
  111. {cmpnd-0.23.2 → cmpnd-0.24.0}/scripts/seed_review_run.py +0 -0
  112. {cmpnd-0.23.2 → cmpnd-0.24.0}/scripts/test-in-linux.sh +0 -0
  113. {cmpnd-0.23.2 → cmpnd-0.24.0}/stubs/dspy/__init__.pyi +0 -0
  114. {cmpnd-0.23.2 → cmpnd-0.24.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  115. {cmpnd-0.23.2 → cmpnd-0.24.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  116. {cmpnd-0.23.2 → cmpnd-0.24.0}/stubs/dspy/utils/__init__.pyi +0 -0
  117. {cmpnd-0.23.2 → cmpnd-0.24.0}/stubs/dspy/utils/callback.pyi +0 -0
  118. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/__init__.py +0 -0
  119. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/_exporter_helpers.py +0 -0
  120. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/_fake_clock.py +0 -0
  121. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/_identity_helpers.py +0 -0
  122. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/conftest.py +0 -0
  123. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/__init__.py +0 -0
  124. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/committee_task.py +0 -0
  125. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/conftest.py +0 -0
  126. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  127. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  128. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/fake_lm.py +0 -0
  129. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/helpers.py +0 -0
  130. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/target_capabilities.py +0 -0
  131. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_assessments_cli.py +0 -0
  132. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_capture_prompts.py +0 -0
  133. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_composite_module.py +0 -0
  134. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  135. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_deploy_execute.py +0 -0
  136. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_deploy_programs.py +0 -0
  137. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_edge_cases.py +0 -0
  138. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_example_hash.py +0 -0
  139. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_gepa_optimization.py +0 -0
  140. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_interpreter_spans.py +0 -0
  141. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_lm_accuracy.py +0 -0
  142. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_module_tracing.py +0 -0
  143. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_optimization_lineage.py +0 -0
  144. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  145. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_optimize_failures.py +0 -0
  146. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_optimize_loop.py +0 -0
  147. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  148. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  149. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_parallel_export.py +0 -0
  150. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_project_attribution.py +0 -0
  151. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_react_tool_spans.py +0 -0
  152. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_save_load.py +0 -0
  153. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_trace_structure.py +0 -0
  154. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_traces_read.py +0 -0
  155. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_user_attribution.py +0 -0
  156. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/e2e/test_valsets_cli.py +0 -0
  157. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  158. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  159. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  160. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  161. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  162. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  163. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  164. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  165. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  166. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/10_react.json +0 -0
  167. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/11_refine.json +0 -0
  168. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/12_retrieve.json +0 -0
  169. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/13_knn.json +0 -0
  170. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/14_parallel.json +0 -0
  171. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/identity_vectors/README.md +0 -0
  172. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/ingest_conformance.py +0 -0
  173. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/__init__.py +0 -0
  174. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/conftest.py +0 -0
  175. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/hosted_optimize_probe.py +0 -0
  176. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/optimization_run_probe.py +0 -0
  177. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  178. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/test_logs_renders_traces.py +0 -0
  179. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/test_optimization_run.py +0 -0
  180. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/test_optimizing_progress.py +0 -0
  181. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/integration/test_unhappy_path.py +0 -0
  182. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/load/__init__.py +0 -0
  183. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/load/__main__.py +0 -0
  184. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/load/drain.py +0 -0
  185. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/load/ingest.py +0 -0
  186. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/load/latency_transport.py +0 -0
  187. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/load/rlm_shape.py +0 -0
  188. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/load/sweep.py +0 -0
  189. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/load/test_rlm_shape.py +0 -0
  190. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/__init__.py +0 -0
  191. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/conftest.py +0 -0
  192. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/seed_parity.py +0 -0
  193. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_auth.py +0 -0
  194. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_datasets.py +0 -0
  195. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_deployments.py +0 -0
  196. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_evals.py +0 -0
  197. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_health.py +0 -0
  198. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_modules.py +0 -0
  199. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_optimizations.py +0 -0
  200. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_programs.py +0 -0
  201. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_signatures.py +0 -0
  202. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_trace_streaming.py +0 -0
  203. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/parity/test_traces.py +0 -0
  204. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/__init__.py +0 -0
  205. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/_provider.py +0 -0
  206. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/_reliability.py +0 -0
  207. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/_result.py +0 -0
  208. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/mint_bedrock_token.py +0 -0
  209. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_deploy_programs.py +0 -0
  210. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_multi_client_stress.py +0 -0
  211. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_optimize_client_committee.py +0 -0
  212. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_optimize_loop.py +0 -0
  213. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
  214. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_optimize_stress.py +0 -0
  215. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_provider_roles.py +0 -0
  216. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_real_provider_twins.py +0 -0
  217. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_reliability.py +0 -0
  218. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_reliability_classify.py +0 -0
  219. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_rlm_wasm.py +0 -0
  220. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/stress/test_trace_search.py +0 -0
  221. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_callback.py +0 -0
  222. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_candidate_lineage.py +0 -0
  223. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/__init__.py +0 -0
  224. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/conftest.py +0 -0
  225. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_admin.py +0 -0
  226. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_api_client.py +0 -0
  227. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_auth.py +0 -0
  228. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_credentials.py +0 -0
  229. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_datasets.py +0 -0
  230. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_deployments.py +0 -0
  231. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_evals.py +0 -0
  232. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_login.py +0 -0
  233. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_main.py +0 -0
  234. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_optimizations.py +0 -0
  235. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_org.py +0 -0
  236. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_pagination.py +0 -0
  237. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_parity.py +0 -0
  238. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_programs.py +0 -0
  239. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_provider_credentials.py +0 -0
  240. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_schema_sample.py +0 -0
  241. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_shell.py +0 -0
  242. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_signatures.py +0 -0
  243. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_tool_servers.py +0 -0
  244. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_top.py +0 -0
  245. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_traces.py +0 -0
  246. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  247. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_clustering/__init__.py +0 -0
  248. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_clustering/conftest.py +0 -0
  249. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_clustering/test_edge_cases.py +0 -0
  250. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  251. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_clustering/test_type_conversion.py +0 -0
  252. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_cmp551_grouping_identity.py +0 -0
  253. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_configuration.py +0 -0
  254. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_context.py +0 -0
  255. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_context_propagation.py +0 -0
  256. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_dataset_polling.py +0 -0
  257. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_dataset_sync.py +0 -0
  258. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_datasets.py +0 -0
  259. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_decorators.py +0 -0
  260. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_deploy.py +0 -0
  261. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  262. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_eval.py +0 -0
  263. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_eval_example_attribution.py +0 -0
  264. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_eval_start.py +0 -0
  265. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_eval_trace_grouping.py +0 -0
  266. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_exception_paths.py +0 -0
  267. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_execute.py +0 -0
  268. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_exporter.py +0 -0
  269. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_exporter_queue.py +0 -0
  270. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_exporter_real.py +0 -0
  271. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_fake_lm.py +0 -0
  272. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_gepa_callback.py +0 -0
  273. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_gepa_integration.py +0 -0
  274. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_gepa_resume.py +0 -0
  275. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_identity.py +0 -0
  276. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_imaging.py +0 -0
  277. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_import_weight.py +0 -0
  278. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_ingest_conformance.py +0 -0
  279. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_ingest_second_producer.py +0 -0
  280. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_integration.py +0 -0
  281. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_interpreter_callback.py +0 -0
  282. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_ir_encoder_properties.py +0 -0
  283. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_ir_golden_vectors.py +0 -0
  284. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_ir_roundtrip.py +0 -0
  285. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_lm_usage_shapes.py +0 -0
  286. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_mcp_tools.py +0 -0
  287. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_models.py +0 -0
  288. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimization_progress.py +0 -0
  289. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimization_start.py +0 -0
  290. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimize.py +0 -0
  291. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimize_budget.py +0 -0
  292. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimize_lift.py +0 -0
  293. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimize_lms.py +0 -0
  294. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimize_proposer.py +0 -0
  295. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimize_reflection_lm.py +0 -0
  296. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_optimize_terminal_failure.py +0 -0
  297. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_packaging.py +0 -0
  298. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_packaging_callable_source.py +0 -0
  299. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_packaging_mcp_tools.py +0 -0
  300. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_packaging_program_source.py +0 -0
  301. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_poll_until.py +0 -0
  302. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_program_lineage_persist.py +0 -0
  303. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_project_payload.py +0 -0
  304. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_reflector_properties.py +0 -0
  305. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_reflector_smoke.py +0 -0
  306. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_repro_reactv2_bugs.py +0 -0
  307. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_require_version_bump.py +0 -0
  308. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_retry.py +0 -0
  309. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_reversible_failures.py +0 -0
  310. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_rlm_patch.py +0 -0
  311. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_schemas.py +0 -0
  312. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_seed_review_run.py +0 -0
  313. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_trace_render.py +0 -0
  314. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_tracked_gepa.py +0 -0
  315. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_tracker_enrich.py +0 -0
  316. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_truncation.py +0 -0
  317. {cmpnd-0.23.2 → cmpnd-0.24.0}/tests/test_xxh64.py +0 -0
  318. {cmpnd-0.23.2 → cmpnd-0.24.0}/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.0/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.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>
@@ -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:
@@ -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
 
@@ -0,0 +1,98 @@
1
+ """Recursive command-tree help for the cmpnd CLI.
2
+
3
+ Argparse only lists a parser's *immediate* subcommands, so a nested group like
4
+ ``cmpnd valsets`` hides ``entries create`` / ``entries list`` / ``entries
5
+ delete`` one level down — the surface is there but undiscoverable without
6
+ drilling in parser by parser. ``TreeHelpParser`` replaces that one-level listing
7
+ with the full descendant set, so ``<command> --help`` shows everything under it
8
+ at once.
9
+
10
+ Each row is the command's **full path** (``valsets entries create``), indented
11
+ two spaces per level. The indent makes the hierarchy scannable; the repeated
12
+ words — unlike ``├─`` glyph decoration — can be copied straight into a shell.
13
+ Depth-first order keeps a group's children directly beneath it.
14
+
15
+ The class propagates for free: ``ArgumentParser.add_subparsers`` defaults its
16
+ ``parser_class`` to ``type(self)``, so making the root parser a
17
+ ``TreeHelpParser`` makes every group and subgroup one too.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import argparse
23
+ from typing import Any
24
+
25
+ _MAX_LABEL = 40
26
+
27
+
28
+ def _subparsers_action(parser: argparse.ArgumentParser) -> argparse._SubParsersAction[Any] | None:
29
+ for action in parser._actions:
30
+ if isinstance(action, argparse._SubParsersAction):
31
+ return action
32
+ return None
33
+
34
+
35
+ def _path(prog: str) -> str:
36
+ """The command path minus the binary name: 'cmpnd valsets create' → 'valsets create'."""
37
+ parts = prog.split(" ", 1)
38
+ return parts[1] if len(parts) > 1 else prog
39
+
40
+
41
+ def _walk(parser: argparse.ArgumentParser, depth: int, out: list[tuple[int, str, str]]) -> None:
42
+ action = _subparsers_action(parser)
43
+ if action is None:
44
+ return
45
+ helps = {ca.dest: (ca.help or "") for ca in action._choices_actions}
46
+ seen: set[int] = set()
47
+ for name, sub in action.choices.items():
48
+ # Aliases point at the same parser object as their canonical name; list
49
+ # each command once, under the name argparse recorded help for.
50
+ if id(sub) in seen or name not in helps:
51
+ continue
52
+ seen.add(id(sub))
53
+ out.append((depth, _path(sub.prog), helps[name]))
54
+ _walk(sub, depth + 1, out)
55
+
56
+
57
+ def format_command_tree(parser: argparse.ArgumentParser) -> str:
58
+ """Render the full nested subcommand set under ``parser`` (empty if none).
59
+
60
+ Each line is the copy-pasteable command path, indented by depth, followed by
61
+ its help.
62
+ """
63
+ out: list[tuple[int, str, str]] = []
64
+ _walk(parser, 0, out)
65
+ if not out:
66
+ return ""
67
+ labels = [" " * depth + path for depth, path, _ in out]
68
+ width = min(max(len(label) for label in labels), _MAX_LABEL)
69
+ lines = ["commands:"]
70
+ for label, (_, _, help_text) in zip(labels, out):
71
+ lines.append(f" {label.ljust(width)} {help_text}".rstrip() if help_text else f" {label}")
72
+ return "\n".join(lines)
73
+
74
+
75
+ class TreeHelpParser(argparse.ArgumentParser):
76
+ """An ``ArgumentParser`` whose ``--help`` lists the full nested command set."""
77
+
78
+ def format_help(self) -> str:
79
+ # Drop the subparsers action from its body group so argparse's own
80
+ # one-level subcommand listing is omitted, while usage (built from
81
+ # self._actions) still shows the command placeholder. The full-path list
82
+ # below replaces the listing with the complete descendant set. The action
83
+ # is restored at its original index, so the mutation never leaks.
84
+ action = _subparsers_action(self)
85
+ removed: tuple[Any, int, argparse.Action] | None = None
86
+ if action is not None:
87
+ for group in self._action_groups:
88
+ if action in group._group_actions:
89
+ idx = group._group_actions.index(action)
90
+ group._group_actions.remove(action)
91
+ removed = (group, idx, action)
92
+ break
93
+ base = super().format_help()
94
+ if removed is not None:
95
+ group, idx, act = removed
96
+ group._group_actions.insert(idx, act)
97
+ tree = format_command_tree(self)
98
+ return f"{base}\n{tree}\n" if tree else base