cmpnd 0.61.0__tar.gz → 0.63.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 (398) hide show
  1. {cmpnd-0.61.0 → cmpnd-0.63.0}/PKG-INFO +1 -1
  2. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_traces.py +7 -2
  3. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/callback.py +2 -2
  4. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/org.py +71 -4
  5. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/top.py +19 -6
  6. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/traces.py +40 -1
  7. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/shell.py +6 -4
  8. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/trace_render.py +29 -2
  9. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/models.py +9 -3
  10. {cmpnd-0.61.0 → cmpnd-0.63.0}/docs/cli.md +34 -7
  11. {cmpnd-0.61.0 → cmpnd-0.63.0}/docs/reading-traces.md +3 -2
  12. {cmpnd-0.61.0 → cmpnd-0.63.0}/docs/sdk-instrumentation.md +7 -0
  13. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/trace_facts.py +8 -3
  14. {cmpnd-0.61.0 → cmpnd-0.63.0}/pyproject.toml +1 -1
  15. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/capture_review_run.py +5 -5
  16. cmpnd-0.63.0/scripts/fixtures/review_run.json.gz +0 -0
  17. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/seed_review_run.py +5 -5
  18. cmpnd-0.63.0/tests/e2e/target_capabilities.py +58 -0
  19. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_composite_module.py +1 -1
  20. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_deploy_execute.py +0 -3
  21. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_deploy_pandas.py +6 -0
  22. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_deploy_programs.py +5 -0
  23. cmpnd-0.63.0/tests/e2e/test_optimize_authored_metric.py +78 -0
  24. cmpnd-0.63.0/tests/e2e/test_optimize_custom_proposer.py +158 -0
  25. cmpnd-0.63.0/tests/e2e/test_optimize_failures.py +69 -0
  26. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_server_owned_completion.py +7 -1
  27. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_parallel_export.py +2 -2
  28. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_project_attribution.py +1 -1
  29. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/rlm_shape.py +1 -1
  30. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_trace_streaming.py +1 -1
  31. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_traces.py +4 -4
  32. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/span_reference_fixture.py +1 -1
  33. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/_provider.py +19 -25
  34. cmpnd-0.63.0/tests/stress/test_bedrock_converse.py +127 -0
  35. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_multi_client_stress.py +15 -5
  36. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_callback.py +4 -4
  37. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_org.py +160 -4
  38. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_shell.py +3 -1
  39. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_traces.py +2 -2
  40. cmpnd-0.63.0/tests/test_cli/test_traces_program_and_module.py +274 -0
  41. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_traces_search_wire.py +4 -1
  42. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_client.py +2 -2
  43. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_client_program_names.py +2 -2
  44. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_context_propagation.py +1 -1
  45. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_eval_example_attribution.py +6 -6
  46. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_eval_trace_grouping.py +3 -3
  47. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_exporter.py +3 -3
  48. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_exporter_program_names.py +1 -1
  49. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_exporter_queue.py +1 -1
  50. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_exporter_real.py +3 -3
  51. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_ingest_conformance.py +1 -1
  52. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_integration.py +1 -1
  53. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_models.py +6 -6
  54. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_multi_client_stress_readiness.py +6 -5
  55. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_program_identity.py +1 -1
  56. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_project_payload.py +7 -7
  57. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_repro_reactv2_bugs.py +1 -1
  58. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_run_attribution_paths.py +1 -1
  59. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_schemas.py +1 -1
  60. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_trace_fact_wire_contract.py +1 -1
  61. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_trace_facts.py +8 -8
  62. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_trace_render.py +3 -2
  63. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_truncation.py +2 -2
  64. {cmpnd-0.61.0 → cmpnd-0.63.0}/uv.lock +1 -1
  65. cmpnd-0.61.0/scripts/fixtures/review_run.json.gz +0 -0
  66. cmpnd-0.61.0/tests/e2e/target_capabilities.py +0 -70
  67. cmpnd-0.61.0/tests/e2e/test_optimize_failures.py +0 -136
  68. {cmpnd-0.61.0 → cmpnd-0.63.0}/.gitignore +0 -0
  69. {cmpnd-0.61.0 → cmpnd-0.63.0}/AGENTS.md +0 -0
  70. {cmpnd-0.61.0 → cmpnd-0.63.0}/CLAUDE.md +0 -0
  71. {cmpnd-0.61.0 → cmpnd-0.63.0}/README.md +0 -0
  72. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/__init__.py +0 -0
  73. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_assessments.py +0 -0
  74. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_authored_import.py +0 -0
  75. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_deployments.py +0 -0
  76. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_exporter_pool.py +0 -0
  77. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_gepa_patch.py +0 -0
  78. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_http.py +0 -0
  79. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_program_patch.py +0 -0
  80. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_rlm_patch.py +0 -0
  81. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_runtime_tools.py +0 -0
  82. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_timespec.py +0 -0
  83. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/_valsets.py +0 -0
  84. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/authored.py +0 -0
  85. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/__init__.py +0 -0
  86. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/api_client.py +0 -0
  87. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/__init__.py +0 -0
  88. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/admin.py +0 -0
  89. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/assessments.py +0 -0
  90. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/auth.py +0 -0
  91. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/datasets.py +0 -0
  92. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/deployments.py +0 -0
  93. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/evals.py +0 -0
  94. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/login.py +0 -0
  95. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/optimizations.py +0 -0
  96. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/programs.py +0 -0
  97. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  98. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/signatures.py +0 -0
  99. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  100. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/commands/valsets.py +0 -0
  101. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/credentials.py +0 -0
  102. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/deprecation.py +0 -0
  103. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/pagination.py +0 -0
  104. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/tree_help.py +0 -0
  105. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/cli/verbs.py +0 -0
  106. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/client.py +0 -0
  107. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/configuration.py +0 -0
  108. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/context.py +0 -0
  109. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/credentials.py +0 -0
  110. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/dataset_sync.py +0 -0
  111. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/datasets.py +0 -0
  112. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/decorators.py +0 -0
  113. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/deployment.py +0 -0
  114. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/deployment_metadata.py +0 -0
  115. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/deployment_runs.py +0 -0
  116. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/eval_handler.py +0 -0
  117. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/execution.py +0 -0
  118. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/exporter.py +0 -0
  119. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/exporter_http.py +0 -0
  120. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/helpers.py +0 -0
  121. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/identity.py +0 -0
  122. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/imaging.py +0 -0
  123. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/ir/__init__.py +0 -0
  124. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/ir/decode.py +0 -0
  125. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/ir/encode.py +0 -0
  126. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/ir/hash.py +0 -0
  127. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/ir/program.py +0 -0
  128. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/ir/reflection.py +0 -0
  129. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/ir/types.py +0 -0
  130. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/ir/xxh64.py +0 -0
  131. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/lm_configuration.py +0 -0
  132. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/mcp_servers.py +0 -0
  133. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/named_programs.py +0 -0
  134. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/optimization.py +0 -0
  135. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/optimizers/__init__.py +0 -0
  136. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/optimizers/gepa.py +0 -0
  137. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  138. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/optimizers/resume.py +0 -0
  139. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/optimizers/tracker.py +0 -0
  140. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/packaging.py +0 -0
  141. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/programs.py +0 -0
  142. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/references.py +0 -0
  143. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/retry.py +0 -0
  144. {cmpnd-0.61.0 → cmpnd-0.63.0}/cmpnd/traces.py +0 -0
  145. {cmpnd-0.61.0 → cmpnd-0.63.0}/docs/2026-04-02-code-review.md +0 -0
  146. {cmpnd-0.61.0 → cmpnd-0.63.0}/docs/client.md +0 -0
  147. {cmpnd-0.61.0 → cmpnd-0.63.0}/docs/custom-proposer.md +0 -0
  148. {cmpnd-0.61.0 → cmpnd-0.63.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  149. {cmpnd-0.61.0 → cmpnd-0.63.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  150. {cmpnd-0.61.0 → cmpnd-0.63.0}/examples/generalizing_proposer.py +0 -0
  151. {cmpnd-0.61.0 → cmpnd-0.63.0}/examples/local_ollama.py +0 -0
  152. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/LINEAGE.md +0 -0
  153. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/README.md +0 -0
  154. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  155. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  156. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  157. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  158. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  159. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  160. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/py.typed +0 -0
  161. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  162. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/cmpnd_ingest/retry.py +0 -0
  163. {cmpnd-0.61.0 → cmpnd-0.63.0}/ingest/pyproject.toml +0 -0
  164. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/audit-sdist.sh +0 -0
  165. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/check-pip-install.sh +0 -0
  166. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/check_release_artifacts.py +0 -0
  167. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/gepa_resume_bug.py +0 -0
  168. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/install-test-env.sh +0 -0
  169. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/release-stress.sh +0 -0
  170. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/require_version_bump.py +0 -0
  171. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/selfhost_compat.py +0 -0
  172. {cmpnd-0.61.0 → cmpnd-0.63.0}/scripts/test-in-linux.sh +0 -0
  173. {cmpnd-0.61.0 → cmpnd-0.63.0}/stubs/dspy/__init__.pyi +0 -0
  174. {cmpnd-0.61.0 → cmpnd-0.63.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  175. {cmpnd-0.61.0 → cmpnd-0.63.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  176. {cmpnd-0.61.0 → cmpnd-0.63.0}/stubs/dspy/utils/__init__.pyi +0 -0
  177. {cmpnd-0.61.0 → cmpnd-0.63.0}/stubs/dspy/utils/callback.pyi +0 -0
  178. {cmpnd-0.61.0 → cmpnd-0.63.0}/stubs/dspy/utils/dummies.pyi +0 -0
  179. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/__init__.py +0 -0
  180. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/_credential_route.py +0 -0
  181. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/_deployment_signature_helpers.py +0 -0
  182. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/_exporter_helpers.py +0 -0
  183. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/_fake_clock.py +0 -0
  184. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/_identity_helpers.py +0 -0
  185. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/_resolve_refs.py +0 -0
  186. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/_traced_run.py +0 -0
  187. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/conftest.py +0 -0
  188. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/__init__.py +0 -0
  189. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/committee_task.py +0 -0
  190. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/conftest.py +0 -0
  191. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  192. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  193. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/fake_lm.py +0 -0
  194. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/helpers.py +0 -0
  195. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_assessments_cli.py +0 -0
  196. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_capture_prompts.py +0 -0
  197. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  198. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_edge_cases.py +0 -0
  199. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_example_hash.py +0 -0
  200. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_gepa_optimization.py +0 -0
  201. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_interpreter_spans.py +0 -0
  202. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_lm_accuracy.py +0 -0
  203. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_module_tracing.py +0 -0
  204. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_optimization_lineage.py +0 -0
  205. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  206. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_loop.py +0 -0
  207. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  208. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_react_tool_spans.py +0 -0
  209. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_save_load.py +0 -0
  210. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_trace_structure.py +0 -0
  211. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_traces_read.py +0 -0
  212. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_user_attribution.py +0 -0
  213. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/e2e/test_valsets_cli.py +0 -0
  214. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  215. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  216. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  217. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  218. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  219. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  220. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  221. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  222. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  223. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/10_react.json +0 -0
  224. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/11_refine.json +0 -0
  225. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/12_retrieve.json +0 -0
  226. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/13_knn.json +0 -0
  227. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/14_parallel.json +0 -0
  228. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/identity_vectors/README.md +0 -0
  229. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/ingest_conformance.py +0 -0
  230. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/__init__.py +0 -0
  231. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/authored_optimize/judge.py +0 -0
  232. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  233. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/conftest.py +0 -0
  234. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  235. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/hosted_optimize_probe.py +0 -0
  236. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/optimization_run_probe.py +0 -0
  237. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  238. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/test_logs_renders_traces.py +0 -0
  239. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/test_optimization_run.py +0 -0
  240. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/test_optimizing_progress.py +0 -0
  241. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/integration/test_unhappy_path.py +0 -0
  242. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/__init__.py +0 -0
  243. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/__main__.py +0 -0
  244. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/drain.py +0 -0
  245. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/ingest.py +0 -0
  246. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/latency_transport.py +0 -0
  247. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/rlm_size_report.py +0 -0
  248. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/sweep.py +0 -0
  249. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/test_rlm_shape.py +0 -0
  250. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/load/test_rlm_size_report.py +0 -0
  251. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/__init__.py +0 -0
  252. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/conftest.py +0 -0
  253. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/seed_parity.py +0 -0
  254. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_auth.py +0 -0
  255. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_datasets.py +0 -0
  256. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_deployments.py +0 -0
  257. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_evals.py +0 -0
  258. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_health.py +0 -0
  259. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_modules.py +0 -0
  260. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_optimizations.py +0 -0
  261. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_programs.py +0 -0
  262. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/parity/test_signatures.py +0 -0
  263. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/__init__.py +0 -0
  264. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/_readiness.py +0 -0
  265. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/_reliability.py +0 -0
  266. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/_result.py +0 -0
  267. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/mint_bedrock_token.py +0 -0
  268. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_deploy_programs.py +0 -0
  269. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_optimize_client_committee.py +0 -0
  270. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_optimize_loop.py +0 -0
  271. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
  272. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_optimize_stress.py +0 -0
  273. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_provider_roles.py +0 -0
  274. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_readiness.py +0 -0
  275. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_real_provider_twins.py +0 -0
  276. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_reliability.py +0 -0
  277. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_reliability_classify.py +0 -0
  278. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_rlm_wasm.py +0 -0
  279. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/stress/test_trace_search.py +0 -0
  280. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_adapter_reentrancy.py +0 -0
  281. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_assessments_api.py +0 -0
  282. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_authored.py +0 -0
  283. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_authored_binding_credentials.py +0 -0
  284. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_authored_bundle_collisions.py +0 -0
  285. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_authored_metric_contract.py +0 -0
  286. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_authored_named_models.py +0 -0
  287. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_authored_optimize.py +0 -0
  288. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_candidate_lineage.py +0 -0
  289. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/__init__.py +0 -0
  290. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/conftest.py +0 -0
  291. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_admin.py +0 -0
  292. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_api_client.py +0 -0
  293. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_assessments.py +0 -0
  294. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_auth.py +0 -0
  295. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_credentials.py +0 -0
  296. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_datasets.py +0 -0
  297. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_deployment_runs.py +0 -0
  298. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_deployments.py +0 -0
  299. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_evals.py +0 -0
  300. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_login.py +0 -0
  301. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_main.py +0 -0
  302. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_optimizations.py +0 -0
  303. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_pagination.py +0 -0
  304. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_parity.py +0 -0
  305. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_programs.py +0 -0
  306. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_provider_credentials.py +0 -0
  307. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_schema_sample.py +0 -0
  308. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_signatures.py +0 -0
  309. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_tool_servers.py +0 -0
  310. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_top.py +0 -0
  311. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_tree_help.py +0 -0
  312. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cli/test_valsets.py +0 -0
  313. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_client_floor.py +0 -0
  314. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_clustering/__init__.py +0 -0
  315. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_clustering/conftest.py +0 -0
  316. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_clustering/test_edge_cases.py +0 -0
  317. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  318. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_clustering/test_type_conversion.py +0 -0
  319. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_cmp551_grouping_identity.py +0 -0
  320. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_configuration.py +0 -0
  321. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_context.py +0 -0
  322. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_credential_kwargs.py +0 -0
  323. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_credential_selection.py +0 -0
  324. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_credential_values.py +0 -0
  325. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_dataset_polling.py +0 -0
  326. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_dataset_sync.py +0 -0
  327. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_datasets.py +0 -0
  328. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_decision_client_spans.py +0 -0
  329. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_decorators.py +0 -0
  330. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_deploy.py +0 -0
  331. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  332. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_deploy_program_id.py +0 -0
  333. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_deployment_metadata.py +0 -0
  334. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_deployment_runs.py +0 -0
  335. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_deployments_read.py +0 -0
  336. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_eval.py +0 -0
  337. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_eval_start.py +0 -0
  338. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_exception_paths.py +0 -0
  339. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_execute.py +0 -0
  340. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_execute_run_identity.py +0 -0
  341. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_fake_lm.py +0 -0
  342. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_gepa_callback.py +0 -0
  343. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_gepa_integration.py +0 -0
  344. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_gepa_output_normalization.py +0 -0
  345. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_gepa_resume.py +0 -0
  346. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_identity.py +0 -0
  347. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_imaging.py +0 -0
  348. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_import_weight.py +0 -0
  349. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_ingest_second_producer.py +0 -0
  350. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_interpreter_callback.py +0 -0
  351. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_ir_encoder_properties.py +0 -0
  352. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_ir_golden_vectors.py +0 -0
  353. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_ir_roundtrip.py +0 -0
  354. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_lm15_native_calls.py +0 -0
  355. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_lm_configuration.py +0 -0
  356. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_lm_usage_shapes.py +0 -0
  357. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_mcp_tools.py +0 -0
  358. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimization_progress.py +0 -0
  359. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimization_start.py +0 -0
  360. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimize.py +0 -0
  361. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimize_budget.py +0 -0
  362. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimize_lift.py +0 -0
  363. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimize_lms.py +0 -0
  364. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimize_proposer.py +0 -0
  365. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimize_reflection_lm.py +0 -0
  366. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_optimize_terminal_failure.py +0 -0
  367. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_packaging.py +0 -0
  368. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_packaging_callable_source.py +0 -0
  369. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_packaging_mcp_tools.py +0 -0
  370. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_packaging_program_source.py +0 -0
  371. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_poll_until.py +0 -0
  372. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_program_id_deprecation.py +0 -0
  373. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_program_lineage_persist.py +0 -0
  374. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_program_names_in_deploy.py +0 -0
  375. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_programs_create.py +0 -0
  376. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_real_provider_twins_readiness.py +0 -0
  377. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_reflector_properties.py +0 -0
  378. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_reflector_smoke.py +0 -0
  379. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_release_stress_script.py +0 -0
  380. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_replaced_names.py +0 -0
  381. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_require_version_bump.py +0 -0
  382. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_retry.py +0 -0
  383. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_reversible_failures.py +0 -0
  384. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_rlm_patch.py +0 -0
  385. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_runtime_tools.py +0 -0
  386. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_seal_lm_credential.py +0 -0
  387. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_seed_review_run.py +0 -0
  388. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_signature_inputs.py +0 -0
  389. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_span_references.py +0 -0
  390. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_span_sent_once.py +0 -0
  391. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_system_one.py +0 -0
  392. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_trace_context_concurrency.py +0 -0
  393. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_traces_read.py +0 -0
  394. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_tracked_gepa.py +0 -0
  395. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_tracker_enrich.py +0 -0
  396. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_upload_auth_headers.py +0 -0
  397. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_valsets_api.py +0 -0
  398. {cmpnd-0.61.0 → cmpnd-0.63.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.61.0
3
+ Version: 0.63.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>
@@ -4,7 +4,7 @@ The implementation of ``list`` and ``show`` over the shared ``_http`` transport.
4
4
  :class:`cmpnd.Client`'s ``list_traces`` / ``get_trace`` call these directly; the
5
5
  deprecated :mod:`cmpnd.traces` shim wraps them with a warning. The filter and
6
6
  verb names match the ``cmpnd traces`` CLI vocabulary — ``list`` filtered by
7
- ``query`` / ``program`` / ``hash`` / ``deployment`` / ``since`` / ``until`` /
7
+ ``query`` / ``program`` / ``module`` / ``hash`` / ``deployment`` / ``since`` / ``until`` /
8
8
  ``all_history``, and ``show`` for one trace — so the surfaces do not drift.
9
9
  """
10
10
 
@@ -28,6 +28,7 @@ def list(
28
28
  query: str | None = None,
29
29
  model: str | None = None,
30
30
  program: str | None = None,
31
+ module: str | None = None,
31
32
  hash: str | None = None,
32
33
  deployment: str | None = None,
33
34
  metadata: dict[str, str] | None = None,
@@ -49,7 +50,10 @@ def list(
49
50
 
50
51
  The filters mirror ``cmpnd traces list``: ``query`` searches indexed span text;
51
52
  ``program`` names the program by name, and ``hash`` by its program id (``p_…``)
52
- or a legacy signature hash;
53
+ or a legacy signature hash; ``module`` is the root module's class, matched exactly.
54
+ No index serves the class, so without ``program`` or a program id in ``hash`` it
55
+ reads every trace in the time range and slows as the range grows. Pair it with a program or a shorter
56
+ ``since``;
53
57
  ``deployment`` is a deployment id; ``metadata`` is matched by containment (every
54
58
  pair must be present); ``tags`` matches any of the given tags. ``since`` /
55
59
  ``until`` accept an ISO 8601 timestamp, a bare date (``YYYY-MM-DD``), or a
@@ -68,6 +72,7 @@ def list(
68
72
  ("q", query),
69
73
  ("model", model),
70
74
  ("program_name", program),
75
+ ("module_class", module),
71
76
  ("deployment_id", deployment),
72
77
  ("tags", tags),
73
78
  ):
@@ -1190,7 +1190,7 @@ class _ModuleMixin(_CallbackBase):
1190
1190
  trace = models.TraceBuilder(
1191
1191
  trace_id=uuid4(),
1192
1192
  start_time=datetime.now(timezone.utc),
1193
- program_name=instance.__class__.__name__,
1193
+ module_class=instance.__class__.__name__,
1194
1194
  )
1195
1195
  self._stamp(trace)
1196
1196
 
@@ -1633,7 +1633,7 @@ class _LMMixin(_CallbackBase):
1633
1633
  trace_id=span.trace_id or uuid4(),
1634
1634
  start_time=span.start_time,
1635
1635
  end_time=span.end_time,
1636
- program_name="GEPA",
1636
+ module_class="GEPA",
1637
1637
  )
1638
1638
  self._stamp(trace)
1639
1639
  # A metric-phase call can start while DSPy's Evaluate span is still on the
@@ -29,17 +29,21 @@ adds one) and `switch` picks which stored key later commands use, the way the
29
29
  web app switches a session's active org. `list` reads
30
30
  /api/v1/auth/organizations and marks the orgs you hold a key for and the
31
31
  active one. `switch` needs no network call when the org's key is already
32
- stored. `cmpnd org leave` removes your own membership and drops that org's
33
- stored key.
32
+ stored. When none is stored for an org you are an approved member of, it
33
+ offers to mint one with the current key's scopes (`--mint` skips the
34
+ question), stores it and switches to it. `cmpnd org leave` removes your own
35
+ membership and drops that org's stored key.
34
36
 
35
37
  Always registered (no env gate); the server is the authority.
36
38
  """
37
39
 
38
40
  from __future__ import annotations
39
41
 
42
+ import socket
40
43
  import sys
41
44
  import urllib.parse
42
45
  from argparse import ArgumentParser, _SubParsersAction
46
+ from datetime import datetime, timezone
43
47
  from typing import Any
44
48
 
45
49
  from .. import api_client, credentials
@@ -91,6 +95,11 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
91
95
 
92
96
  switch = sub.add_parser("switch", help="Use the stored key for another of your organizations")
93
97
  switch.add_argument("org", help="Organization slug or id (from `org list`)")
98
+ switch.add_argument(
99
+ "--mint",
100
+ action="store_true",
101
+ help="When no key is stored for the org, mint one with the current key's scopes without asking",
102
+ )
94
103
  switch.set_defaults(func=handle_switch, _skip_resolve_config=True)
95
104
 
96
105
  leave = sub.add_parser("leave", help="Leave one of your organizations and drop its stored key")
@@ -214,12 +223,70 @@ def handle_switch(_unused_client: api_client.ApiClient | None, args: Any) -> Non
214
223
  api_key, _ = api_client.resolve_config(args)
215
224
  except api_client.CliError:
216
225
  raise _no_key_stored(f"'{target}'", endpoint, target) from None
217
- for row in _memberships(api_client.ApiClient(api_key, endpoint)):
226
+ client = api_client.ApiClient(api_key, endpoint)
227
+ for row in _memberships(client):
218
228
  if target in (row.get("id"), row.get("slug")):
219
- raise _no_key_stored(str(row.get("name") or target), endpoint, target)
229
+ _mint_and_switch(client, row, target, ask=not getattr(args, "mint", False))
230
+ return
220
231
  raise api_client.CliError(f"You are not a member of an organization '{target}' on {endpoint}.")
221
232
 
222
233
 
234
+ def _mint_and_switch(client: api_client.ApiClient, row: dict[str, Any], target: str, *, ask: bool) -> None:
235
+ """Mint a key for a member org that has none stored, store it and switch.
236
+
237
+ The request names no scopes, so serve grants the calling key's own
238
+ non-admin scopes and the new key is never broader than the one in use.
239
+ serve refuses with 400 or 403 when the calling key cannot mint, and the
240
+ browser login is then the way to get the key.
241
+ """
242
+ endpoint = client._endpoint
243
+ label = str(row.get("name") or row.get("slug") or target)
244
+ if row.get("approved") is False:
245
+ raise api_client.CliError(
246
+ f"Your membership in {label} is not approved yet, so no key can be minted for it. "
247
+ "An owner of the org approves it."
248
+ )
249
+ if ask:
250
+ if not sys.stdin.isatty():
251
+ raise api_client.CliError(
252
+ f"No key is stored for {label} on {endpoint}. Run {top.hint_org_switch(target, mint=True)} "
253
+ f"to mint one with the current key's scopes, or {top.hint_login(target)}."
254
+ )
255
+ try:
256
+ answer = input(f"No key is stored for {label}. Mint one with the current key's scopes? [Y/n] ")
257
+ except EOFError:
258
+ # Ctrl-D at the prompt declines.
259
+ print(file=sys.stderr)
260
+ answer = "n"
261
+ if answer.strip().lower() not in ("", "y", "yes"):
262
+ raise _no_key_stored(label, endpoint, target)
263
+
264
+ hostname = socket.gethostname() or "unknown"
265
+ name = f"CLI: {hostname} {datetime.now(timezone.utc).strftime('%Y-%m-%d')}"
266
+ try:
267
+ created = client.post("/api/v1/org/keys", json={"name": name, "org_id": row.get("id") or target})
268
+ except api_client.CliError as exc:
269
+ if exc.status_code in (400, 403):
270
+ raise api_client.CliError(
271
+ f"The current key could not mint a key for {label}: {exc}. Run {top.hint_login(target)} to store one."
272
+ ) from exc
273
+ raise
274
+ token = created.get("token") if isinstance(created, dict) else None
275
+ org_id = created.get("org_id") if isinstance(created, dict) else None
276
+ if not isinstance(token, str) or not token or not isinstance(org_id, str) or not org_id:
277
+ raise api_client.CliError(f"The response did not include a key for {label}. Nothing was stored.")
278
+ name_out = created.get("org_name")
279
+ slug_out = created.get("org_slug")
280
+ credentials.save(
281
+ endpoint,
282
+ token,
283
+ org_id=org_id,
284
+ org_name=name_out if isinstance(name_out, str) else None,
285
+ org_slug=slug_out if isinstance(slug_out, str) else None,
286
+ )
287
+ print(f"Minted a key for {name_out or slug_out or org_id} ({org_id}) and switched to it on {endpoint}")
288
+
289
+
223
290
  def _say_active_moved(endpoint: str, moved_to: str | None) -> None:
224
291
  if moved_to:
225
292
  k = {k.org_id: k for k in credentials.stored_keys(endpoint)}.get(moved_to)
@@ -18,7 +18,7 @@ from typing import Any, NoReturn
18
18
 
19
19
  from ... import _deployments, deployment_runs
20
20
  from ...deployment_metadata import DeploymentMetadataError
21
- from .. import api_client, pagination
21
+ from .. import api_client, pagination, trace_render
22
22
  from . import deployments as deployment_commands
23
23
  from . import signatures
24
24
 
@@ -180,8 +180,8 @@ def hint_login(org: str | None = None) -> str:
180
180
  return f"`{cmd}` in a terminal, then restart the shell" if _SHELL_MODE else f"`{cmd}`"
181
181
 
182
182
 
183
- def hint_org_switch(org: str) -> str:
184
- cmd = f"cmpnd org switch {org}"
183
+ def hint_org_switch(org: str, *, mint: bool = False) -> str:
184
+ cmd = f"cmpnd org switch {org}" + (" --mint" if mint else "")
185
185
  # Same reason as hint_login: the shell picks its key once, at startup.
186
186
  # Both are public, unlike the other hint helpers, because only org.py
187
187
  # calls them.
@@ -338,8 +338,20 @@ def render_deployments_table(deployments: list[dict[str, Any]]) -> str:
338
338
  return "\n".join(lines)
339
339
 
340
340
 
341
+ def trace_program_cells(trace: dict[str, Any]) -> tuple[str, str]:
342
+ """The PROGRAM and MODULE cells for one trace record.
343
+
344
+ PROGRAM is the name the user gave the program, or a dash when it has none.
345
+ MODULE is the root module's class, or a dash when the trace carries no
346
+ class. `trace_render.program_and_module` reads an older server's shape,
347
+ where `program_name` holds the class.
348
+ """
349
+ name, module = trace_render.program_and_module(trace)
350
+ return name or "—", module or "—"
351
+
352
+
341
353
  def render_traces_table(traces: list[dict[str, Any]]) -> str:
342
- """Return a colored TRACE / STATUS / PROGRAM / AGE table as a string.
354
+ """Return a colored TRACE / STATUS / PROGRAM / MODULE / AGE table as a string.
343
355
 
344
356
  Reused by the shell when typing `d.traces` at the REPL (via
345
357
  `Trace._render_table`). Mirrors `render_deployments_table` so the
@@ -353,13 +365,13 @@ def render_traces_table(traces: list[dict[str, Any]]) -> str:
353
365
  (
354
366
  id_render(t["trace_id"]),
355
367
  t.get("status", "—"),
356
- _truncate(t.get("program_name") or _hash_fallback(t.get("program_signature_hash")), 28),
368
+ *(_truncate(cell, 28) for cell in trace_program_cells(t)),
357
369
  _age(t.get("start_time")),
358
370
  )
359
371
  for t in traces
360
372
  ]
361
373
 
362
- headers = ("TRACE", "STATUS", "PROGRAM", "AGE")
374
+ headers = ("TRACE", "STATUS", "PROGRAM", "MODULE", "AGE")
363
375
  widths = [max(len(h), max((len(r[i]) for r in rows), default=0)) for i, h in enumerate(headers)]
364
376
 
365
377
  header_line = " ".join(h.ljust(widths[i]) for i, h in enumerate(headers))
@@ -370,6 +382,7 @@ def render_traces_table(traces: list[dict[str, Any]]) -> str:
370
382
  _trace_status(r[1]).ljust(widths[1] + (len(_trace_status(r[1])) - len(r[1]))),
371
383
  r[2].ljust(widths[2]),
372
384
  r[3].ljust(widths[3]),
385
+ r[4].ljust(widths[4]),
373
386
  ]
374
387
  lines.append(" ".join(cells))
375
388
  return "\n".join(lines)
@@ -28,6 +28,16 @@ def _metadata_pair(value: str) -> tuple[str, str]:
28
28
  return key, val
29
29
 
30
30
 
31
+ # The wait after which a class search with no program filter gets a hint.
32
+ MODULE_SLOW_HINT_SECONDS = 5.0
33
+
34
+ # The search-body keys that name a program, whichever spelling the flag took.
35
+ _PROGRAM_FILTER_KEYS = ("program_name", "program_id", "program_signature_hash")
36
+
37
+ # The clock the hint measures with, a seam for tests.
38
+ _monotonic = time.monotonic
39
+
40
+
31
41
  def _add_list_args(p: ArgumentParser) -> None:
32
42
  p.add_argument("--status", help="Filter by status (ok, error, incomplete, in_progress)")
33
43
  p.add_argument(
@@ -49,7 +59,20 @@ def _add_list_args(p: ArgumentParser) -> None:
49
59
  help="Filter by a metadata pair your program recorded; repeat for all-of. "
50
60
  "This is how you find a trace by your own identifier",
51
61
  )
52
- p.add_argument("--program", dest="program_name", help="Filter by program name")
62
+ p.add_argument(
63
+ "--program",
64
+ dest="program_name",
65
+ metavar="NAME",
66
+ help="Filter by the program's name, the one given with cmpnd.Program or `cmpnd programs create`",
67
+ )
68
+ p.add_argument(
69
+ "--module",
70
+ dest="module_class",
71
+ metavar="CLASS",
72
+ help="Filter by the root module's class (exact, e.g. ReAct). No index serves the class: "
73
+ "without --program or --hash it reads every trace in the time range, so it gets slower "
74
+ "as the range grows. Narrow it with --program, --hash or --since",
75
+ )
53
76
  p.add_argument(
54
77
  "--hash",
55
78
  dest="program_signature_hash",
@@ -189,6 +212,7 @@ def handle_list(client: api_client.ApiClient, args: Any) -> Any:
189
212
  ("q", "q"),
190
213
  ("model", "model"),
191
214
  ("program_name", "program_name"),
215
+ ("module_class", "module_class"),
192
216
  ("deployment_id", "deployment_id"),
193
217
  ("tags", "tags"),
194
218
  ("start_time_from", "start_time_from"),
@@ -211,7 +235,22 @@ def handle_list(client: api_client.ApiClient, args: Any) -> Any:
211
235
  body["start_time_from"] = _timespec.resolve_search_time(str(body["start_time_from"]))
212
236
  if "start_time_to" in body:
213
237
  body["start_time_to"] = _timespec.resolve_search_time(str(body["start_time_to"]), end_of_day=True)
238
+ started = _monotonic()
214
239
  resp = client.post("/api/v1/traces/search", json=body)
240
+ # No index serves the class, so a class search with no program filter reads
241
+ # every trace in the range. Say what narrows it once the wait is long enough
242
+ # to notice, on stderr so stdout stays the response.
243
+ if (
244
+ "module_class" in body
245
+ and not any(key in body for key in _PROGRAM_FILTER_KEYS)
246
+ and _monotonic() - started > MODULE_SLOW_HINT_SECONDS
247
+ ):
248
+ print(
249
+ "note: --module reads every trace in the time range when no program is given. "
250
+ "Consider filtering by program (--program or --hash) or a shorter range (--since) "
251
+ "for faster results.",
252
+ file=sys.stderr,
253
+ )
215
254
 
216
255
  # A window the caller did not ask for has to be visible: read as an org
217
256
  # figure, a windowed total is simply wrong, and nothing in the numbers says
@@ -32,7 +32,7 @@ from pathlib import Path
32
32
  from types import SimpleNamespace
33
33
  from typing import Any, Callable
34
34
 
35
- from . import api_client, commands, credentials, verbs
35
+ from . import api_client, commands, credentials, trace_render, verbs
36
36
 
37
37
  # --------------------------------------------------------------------------
38
38
  # Config + dismissible-tips marker
@@ -259,13 +259,15 @@ class Trace(Entity):
259
259
  super().__init__(client, record)
260
260
  self.status_str = record.get("status")
261
261
  self.start_time = record.get("start_time")
262
- self.program_name = record.get("program_name")
262
+ # The program's own name (None when it has none) and the root module's
263
+ # class, read the same way for a server that predates module_class.
264
+ self.program_name, self.module_class = trace_render.program_and_module(record)
263
265
 
264
266
  def __repr__(self) -> str:
265
267
  short = self.id.replace("-", "")[:12]
266
- program = self.program_name or commands.top._hash_fallback(self._record.get("program_signature_hash"))
268
+ program, module = commands.top.trace_program_cells(self._record)
267
269
  age = commands.top._age(self.start_time)
268
- return f"<Trace {short} status={self.status_str} program={program!r} age={age}>"
270
+ return f"<Trace {short} status={self.status_str} program={program!r} module={module!r} age={age}>"
269
271
 
270
272
  def inspect(self) -> dict[str, Any]:
271
273
  """Return the full trace record (with spans)."""
@@ -267,8 +267,35 @@ def _int(v: Any) -> int:
267
267
  return 0
268
268
 
269
269
 
270
+ def program_and_module(trace: dict[str, Any]) -> tuple[str | None, str | None]:
271
+ """The program's name and the root module's class a trace record carries.
272
+
273
+ A server that sends ``module_class`` puts the program's own name in
274
+ ``program_name``, empty when the program has none. An older server sends
275
+ no ``module_class``, and its ``program_name`` holds the class, so that
276
+ value is read as the class and the program counts as unnamed.
277
+ """
278
+ if "module_class" in trace:
279
+ return trace.get("program_name") or None, trace.get("module_class") or None
280
+ return None, trace.get("program_name") or None
281
+
282
+
283
+ def trace_label(trace: dict[str, Any]) -> str:
284
+ """One label for a trace: the program's name, else its id, else its class, else its signature.
285
+
286
+ The id shows only for a program with no name: a name already identifies it.
287
+ """
288
+ name, module = program_and_module(trace)
289
+ return name or trace.get("program_id") or module or trace.get("signature_name") or "trace"
290
+
291
+
270
292
  def _header(trace: dict[str, Any], span_total: int, color: bool, arrivals: bool = False) -> str:
271
- program = trace.get("program_name") or trace.get("signature_name") or "trace"
293
+ _, module = program_and_module(trace)
294
+ label = trace_label(trace)
295
+ program = _paint(label, "bold", color)
296
+ if module and label != module:
297
+ # The label names the program; the class beside it says what ran.
298
+ program += " " + _paint(module, "dim", color)
272
299
  tid = str(trace.get("trace_id") or "")
273
300
  status = trace.get("status")
274
301
  meta = [
@@ -278,7 +305,7 @@ def _header(trace: dict[str, Any], span_total: int, color: bool, arrivals: bool
278
305
  ]
279
306
  if _int(trace.get("total_tokens")) > 0:
280
307
  meta.append(f"{_int(trace.get('total_tokens'))} tokens")
281
- line1 = (_paint(str(program), "bold", color) + " " + _paint(tid, "grey", color)).rstrip()
308
+ line1 = (program + " " + _paint(tid, "grey", color)).rstrip()
282
309
  out = line1 + "\n" + " · ".join(meta)
283
310
  if arrivals:
284
311
  # Absolute landing time on the header (the one place there is room for
@@ -942,7 +942,9 @@ class TraceBuilder:
942
942
  status: TraceStatus = TraceStatus.UNSET
943
943
  error_message: str | None = None
944
944
 
945
- program_name: str | None = None
945
+ # The root DSPy module's class. The program's own name is `declared_name`,
946
+ # which the transport resolves to `program_id`.
947
+ module_class: str | None = None
946
948
  program_version: str | None = None
947
949
 
948
950
  # Signature info - identifies the program by its inputs/outputs
@@ -1071,7 +1073,9 @@ class TraceBuilder:
1071
1073
  "end_time": self.end_time.isoformat() if self.end_time else None,
1072
1074
  "status": self.status.value,
1073
1075
  "error_message": _strip_unstorable_opt(self.error_message),
1074
- "program_name": self.program_name,
1076
+ "module_class": self.module_class,
1077
+ # Also under the key a serve from before module_class reads (StartedFact.creation_dict).
1078
+ "program_name": self.module_class,
1075
1079
  "program_version": self.program_version,
1076
1080
  "signature_name": self.signature_name,
1077
1081
  "signature_input_fields": self.signature_input_fields,
@@ -1112,7 +1116,9 @@ class TraceBuilder:
1112
1116
  return self._with_facts(
1113
1117
  {
1114
1118
  "start_time": self.start_time.isoformat() if self.start_time else None,
1115
- "program_name": self.program_name,
1119
+ "module_class": self.module_class,
1120
+ # Also under the key a serve from before module_class reads (StartedFact.creation_dict).
1121
+ "program_name": self.module_class,
1116
1122
  "program_version": self.program_version,
1117
1123
  "signature_name": self.signature_name,
1118
1124
  "signature_input_fields": self.signature_input_fields,
@@ -291,8 +291,9 @@ caller reads it. To find where a program is slow, list recent traces, then
291
291
  read each one's spans and durations.
292
292
 
293
293
  - `cmpnd traces list [-q TEXT] [--model] [--metadata k=v …] [--status]
294
- [--program] [--hash ID-OR-HASH] [--deployment] [--tag …] [--since]
295
- [--until] [--all] [--limit N] [--offset N]` lists traces, newest first.
294
+ [--program NAME] [--module CLASS] [--hash ID-OR-HASH] [--deployment]
295
+ [--tag …] [--since] [--until] [--all] [--limit N] [--offset N]` lists
296
+ traces, newest first.
296
297
  - `--limit N` returns the N most recent, and `--offset N` skips N rows.
297
298
  - `--since` and `--until` bound the start time. Each takes ISO 8601, a date
298
299
  (`YYYY-MM-DD`, UTC, the start of the day for `--since` and the end for
@@ -305,6 +306,17 @@ read each one's spans and durations.
305
306
  must appear.
306
307
  - `--metadata` repeats and matches metadata you attached at trace time, so
307
308
  you can find a trace by your own identifier.
309
+ - `--program` takes the program's name, the one you gave with
310
+ `cmpnd.Program` or `cmpnd programs create`. The CLI sends it as the search
311
+ body's `program_name`. A name no program answers to is an error that names
312
+ it, not an empty list.
313
+ - `--module` takes the root module's class, such as `ReAct`, and matches it
314
+ exactly. The CLI sends it as `module_class`. No index serves the class.
315
+ Without `--program` or a program id in `--hash` it reads every trace in
316
+ the time range, so it slows as the range grows. When such a search takes
317
+ longer than five seconds, the CLI prints a line on stderr that suggests a
318
+ program filter or a shorter range. Stdout is unchanged. A search that
319
+ times out answers 503 with the same advice.
308
320
  - `--hash` takes a program id or a legacy signature hash. The CLI sends an
309
321
  id as the search body's `program_id` and a hash as
310
322
  `program_signature_hash`. `cmpnd.Client().list_traces(hash=...)` splits
@@ -312,8 +324,10 @@ read each one's spans and durations.
312
324
  - `--deployment` takes a deployment ID (`d_...`).
313
325
  - `cmpnd traces show <id> [--tree|--timeline|--notebook] [--arrivals]` prints
314
326
  one trace. The default is raw JSON with each span's duration and its
315
- `created_at`. `--tree` and `--timeline` render human views, and
316
- `--notebook` prints an RLM trace as a Jupyter notebook. With `--tree` or
327
+ `created_at`. `--tree` and `--timeline` render human views headed by the
328
+ program's name and its root module's class. A program with no name is headed
329
+ by its `p_` id instead, and a trace that names no program by its class
330
+ alone. `--notebook` prints an RLM trace as a Jupyter notebook. With `--tree` or
317
331
  `--timeline`, `--arrivals` shows when the server stored each span and how
318
332
  far that is from the start time the client reported, which exposes a skewed
319
333
  client clock.
@@ -322,6 +336,14 @@ read each one's spans and durations.
322
336
  - `cmpnd traces update <id> --metadata k=v …` merges metadata onto a trace,
323
337
  and `cmpnd traces delete <id>` deletes a trace and its spans.
324
338
 
339
+ Each trace row and each `traces show` trace names what ran in three fields.
340
+ `program_name` is the program's name, empty when the program has none or the
341
+ trace names no program. `module_class` is the root DSPy module's class, and
342
+ `program_id` is the program's `p_` id. A server that predates `module_class`
343
+ sends the class in `program_name`. The shell's traces table and the `--tree`
344
+ and `--timeline` headers then show that value as the class and leave the
345
+ program unnamed.
346
+
325
347
  `-q` matches text that serve indexes on each span when it stores the span.
326
348
  Spans stored before the index existed match nothing until a backfill indexes
327
349
  them. An empty result says so, because the list alone cannot tell "nothing
@@ -416,9 +438,14 @@ mode 0600. An active pointer names the org whose key commands use.
416
438
  is stored, and `active` marks the org of the key in use. When `--api-key` or
417
439
  `CMPND_API_KEY` is set, `active` marks that key's org.
418
440
  - `cmpnd org switch <slug-or-id>` makes another org's stored key active, with
419
- no network call. When no key is stored for that org, it says so and names
420
- the `cmpnd login --org` command to run. It refuses while `--api-key` or
421
- `CMPND_API_KEY` is set, because either one overrides the stored keys.
441
+ no network call. When no key is stored for an org where you are an approved
442
+ member, it asks whether to mint one with the current key's scopes, stores
443
+ it and switches to it. `--mint` mints without asking, and without a
444
+ terminal to ask in it mints only with `--mint`. The request names no
445
+ scopes, so serve grants the current key's non-admin scopes. When serve
446
+ refuses the mint, the error names the `cmpnd login --org` command to run.
447
+ `switch` refuses while `--api-key` or `CMPND_API_KEY` is set, because
448
+ either one overrides the stored keys.
422
449
  - `cmpnd org leave <slug-or-id>` removes your membership in that org and drops
423
450
  its stored key. If that key was active, the CLI switches to another stored
424
451
  key, preferring your new default org's, or clears the pointer, and says
@@ -44,8 +44,9 @@ offers only `list`.
44
44
  | `status` | `--status` | `ok`, `error`, `incomplete` or `in_progress` |
45
45
  | `query` | `--query` / `-q` | Full-text search over indexed span text: prompts, completions, tool calls and errors |
46
46
  | `model` | `--model` | Exact model name, such as `openai/gpt-6-luna` |
47
- | `program` | `--program` | Program name |
48
- | `hash` | `--hash` | Program signature hash, as a string |
47
+ | `program` | `--program` | The program's name. A name no program answers to raises an error that names it. |
48
+ | `module` | `--module` | The root module's class, matched exactly. No index serves it, so without `program` or a program id in `hash` it reads every trace in the time range. Pair it with a program or a shorter `since`. |
49
+ | `hash` | `--hash` | A program id (`p_…`), or a legacy signature hash as a string |
49
50
  | `deployment` | `--deployment` | Deployment ID (`d_…`) |
50
51
  | `metadata` | `--metadata` | A `dict` matched by containment. Every pair must be on the trace. |
51
52
  | `tags` | `--tag` | A `list`. A trace matches if it has any of the project tags. |
@@ -673,6 +673,13 @@ Its first payload carries the optimization id, plus the active eval id for a
673
673
  metric call. A reflection call outside evaluation has no eval id. Manual
674
674
  decorators and `start_span` create spans rather than another automatic trace.
675
675
 
676
+ A trace's creation fields name the root module's class in `module_class`, at
677
+ the top level and in `started_fact`, and again under `program_name`, the key a
678
+ serve from before `module_class` reads and digests a start from. A standalone
679
+ optimizer LM trace sends `GEPA` there. No trace body carries the program's own name: a declared name
680
+ travels as the program id it resolves to, as "A declared program name resolves
681
+ to an id in the exporter" describes.
682
+
676
683
  The callback seals `started_fact` after it sets the root type, before the
677
684
  module executes. It keeps that snapshot even when start emission is disabled.
678
685
  The completion callback seals one independently identified `completion_fact`
@@ -80,7 +80,7 @@ class StartedFact:
80
80
  """Creation fields that later builder mutation cannot change."""
81
81
 
82
82
  start_time: str | None
83
- program_name: str | None
83
+ module_class: str | None
84
84
  program_version: str | None
85
85
  program_signature_hash: str
86
86
  signature_name: str | None
@@ -104,7 +104,7 @@ class StartedFact:
104
104
  def from_payload(cls, payload: dict[str, Any]) -> StartedFact:
105
105
  return cls(
106
106
  start_time=payload.get("start_time"),
107
- program_name=payload.get("program_name"),
107
+ module_class=payload.get("module_class"),
108
108
  program_version=payload.get("program_version"),
109
109
  program_signature_hash=str(payload["program_signature_hash"]),
110
110
  signature_name=payload.get("signature_name"),
@@ -125,7 +125,12 @@ class StartedFact:
125
125
  def creation_dict(self) -> dict[str, Any]:
126
126
  payload = {
127
127
  "start_time": self.start_time,
128
- "program_name": self.program_name,
128
+ "module_class": self.module_class,
129
+ # A serve from before module_class reads the class only under
130
+ # program_name, and digests a start from that key. Sending both keeps
131
+ # a start it accepted matching its replay to a later serve. Dropped
132
+ # once no such serve runs.
133
+ "program_name": self.module_class,
129
134
  "program_version": self.program_version,
130
135
  "program_signature_hash": self.program_signature_hash,
131
136
  "signature_name": self.signature_name,
@@ -5,7 +5,7 @@ name = "cmpnd"
5
5
  # it's new) — no git tags. Bump with `uv version --bump {minor,patch}` or by
6
6
  # hand. Runtime `cmpnd.__version__` reads installed metadata, so it tracks
7
7
  # this automatically. See docs/plans/2026-07-02-sdk-pypi-tag-free-publish.md.
8
- version = "0.61.0"
8
+ version = "0.63.0"
9
9
  description = "DSPy observability and deployment SDK for cmpnd"
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
@@ -10,7 +10,7 @@ GEPA optimization the stress suite runs
10
10
  a weak Bedrock student + a Bedrock reflection LM over the vendored committee
11
11
  subset. Capturing a *real* run (rather than fabricating payloads) keeps the seed
12
12
  faithful — real prompts, token counts, span trees, and now the GEPA optimizer
13
- traces (program_name "GEPA", `gepa.phase` reflection/metric — CMP-485).
13
+ traces (module_class "GEPA", `gepa.phase` reflection/metric).
14
14
 
15
15
  Only the four kinds `seed_review_run.py` replays are kept — `optimization` (the
16
16
  completed run header), `traces_batch`, `spans`, `stats`; the optimization *start*
@@ -146,12 +146,12 @@ def main() -> int:
146
146
  kinds = [it["kind"] for it in captured]
147
147
  n_opt = kinds.count("optimization")
148
148
  trace_payloads = [t for it in captured if it["kind"] == "traces_batch" for t in it["payload"]]
149
- programs = {t.get("program_name") for t in trace_payloads}
149
+ classes = {t.get("module_class") for t in trace_payloads}
150
150
  if n_opt != 1:
151
151
  print(f"expected exactly 1 completed optimization, captured {n_opt}; not writing", file=sys.stderr)
152
152
  return 1
153
- if "GEPA" not in programs:
154
- print(f"no GEPA optimizer traces captured (programs={programs}); not writing", file=sys.stderr)
153
+ if "GEPA" not in classes:
154
+ print(f"no GEPA optimizer traces captured (module classes={classes}); not writing", file=sys.stderr)
155
155
  return 1
156
156
 
157
157
  _FIXTURE.parent.mkdir(parents=True, exist_ok=True)
@@ -160,7 +160,7 @@ def main() -> int:
160
160
  from collections import Counter
161
161
 
162
162
  print(f"wrote {_FIXTURE} — {len(captured)} items {dict(Counter(kinds))}")
163
- print(f" trace program_names: {dict(Counter(t.get('program_name') for t in trace_payloads))}")
163
+ print(f" trace module classes: {dict(Counter(t.get('module_class') for t in trace_payloads))}")
164
164
  return 0
165
165
 
166
166
 
@@ -258,7 +258,7 @@ _TOOL_KIND_DEMO_FIXTURE = (
258
258
  # A dspy.ReAct(ExtractCommittee) run (email_body->committee) that reasons, calls
259
259
  # a `lookup_disclaimer` tool to pull the "Paid for by" line, then extracts the
260
260
  # committee (CMP-634 golden-path). Flat-replay format like the two fixtures
261
- # above; program_name "ReAct" so the sidebar/Type badge reads ReAct. Loaded
261
+ # above; module_class "ReAct" so the sidebar/Type badge reads ReAct. Loaded
262
262
  # lazily so a missing file degrades to skipping that seed.
263
263
  _REACT_COMMITTEE_FIXTURE = (
264
264
  pathlib.Path(__file__).resolve().parents[2] / "serve" / "scripts" / "seed_fixtures" / "react_committee.json"
@@ -390,7 +390,7 @@ def _notebook_trace(now: datetime.datetime) -> dict:
390
390
  "end_time": at(total_ns),
391
391
  "duration_ns": total_ns,
392
392
  "status": "ok",
393
- "program_name": "RLM", # DSPy module class → programs.module_class → sidebar badge
393
+ "module_class": "RLM", # DSPy module class → programs.module_class → sidebar badge
394
394
  "program_version": "0.1.0",
395
395
  "program_signature_hash": fx["program_signature_hash"],
396
396
  "signature_name": fx["signature_name"],
@@ -540,7 +540,7 @@ def _notebook_trace_in_progress(now: datetime.datetime) -> dict:
540
540
  "trace_id": trace_id,
541
541
  "start_time": at(0),
542
542
  "status": "in_progress",
543
- "program_name": "RLM",
543
+ "module_class": "RLM",
544
544
  "program_version": "0.1.0",
545
545
  "program_signature_hash": fx["program_signature_hash"],
546
546
  "signature_name": fx["signature_name"],
@@ -559,7 +559,7 @@ def _notebook_trace_in_progress(now: datetime.datetime) -> dict:
559
559
  def _replay_flat_fixture(
560
560
  fixture_path: pathlib.Path,
561
561
  now: datetime.datetime,
562
- program_name: str,
562
+ module_class: str,
563
563
  project_tags: list[str],
564
564
  request_preview: str,
565
565
  response_preview: str,
@@ -609,7 +609,7 @@ def _replay_flat_fixture(
609
609
  "end_time": at(total_ns),
610
610
  "duration_ns": total_ns,
611
611
  "status": "ok",
612
- "program_name": program_name, # DSPy module class → programs.module_class → sidebar badge
612
+ "module_class": module_class, # DSPy module class → programs.module_class → sidebar badge
613
613
  "program_version": "0.1.0",
614
614
  "program_signature_hash": root["program_signature_hash"],
615
615
  "signature_name": root["signature_name"],