cmpnd 0.43.1__tar.gz → 0.45.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 (377) hide show
  1. {cmpnd-0.43.1 → cmpnd-0.45.0}/PKG-INFO +1 -1
  2. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/__init__.py +20 -2
  3. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/callback.py +6 -1
  4. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/client.py +48 -17
  5. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/lm_configuration.py +68 -4
  6. cmpnd-0.45.0/cmpnd/named_programs.py +274 -0
  7. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimization.py +17 -0
  8. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/packaging.py +61 -1
  9. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/programs.py +0 -43
  10. {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/client.md +47 -12
  11. {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/sdk-instrumentation.md +21 -0
  12. {cmpnd-0.43.1 → cmpnd-0.45.0}/pyproject.toml +1 -1
  13. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/hosted_optimize_probe.py +58 -0
  14. cmpnd-0.45.0/tests/test_client_program_names.py +817 -0
  15. cmpnd-0.45.0/tests/test_decision_client_spans.py +144 -0
  16. cmpnd-0.45.0/tests/test_system_one.py +106 -0
  17. {cmpnd-0.43.1 → cmpnd-0.45.0}/uv.lock +1 -1
  18. cmpnd-0.43.1/tests/test_client_program_names.py +0 -339
  19. {cmpnd-0.43.1 → cmpnd-0.45.0}/.gitignore +0 -0
  20. {cmpnd-0.43.1 → cmpnd-0.45.0}/AGENTS.md +0 -0
  21. {cmpnd-0.43.1 → cmpnd-0.45.0}/CLAUDE.md +0 -0
  22. {cmpnd-0.43.1 → cmpnd-0.45.0}/README.md +0 -0
  23. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_assessments.py +0 -0
  24. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_authored_import.py +0 -0
  25. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_deployments.py +0 -0
  26. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_exporter_pool.py +0 -0
  27. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_gepa_patch.py +0 -0
  28. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_http.py +0 -0
  29. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_program_patch.py +0 -0
  30. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_rlm_patch.py +0 -0
  31. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_runtime_tools.py +0 -0
  32. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_timespec.py +0 -0
  33. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_traces.py +0 -0
  34. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/_valsets.py +0 -0
  35. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/authored.py +0 -0
  36. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/__init__.py +0 -0
  37. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/api_client.py +0 -0
  38. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/__init__.py +0 -0
  39. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/admin.py +0 -0
  40. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/assessments.py +0 -0
  41. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/auth.py +0 -0
  42. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/datasets.py +0 -0
  43. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/deployments.py +0 -0
  44. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/evals.py +0 -0
  45. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/login.py +0 -0
  46. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/optimizations.py +0 -0
  47. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/org.py +0 -0
  48. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/programs.py +0 -0
  49. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  50. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/signatures.py +0 -0
  51. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  52. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/top.py +0 -0
  53. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/traces.py +0 -0
  54. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/commands/valsets.py +0 -0
  55. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/credentials.py +0 -0
  56. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/deprecation.py +0 -0
  57. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/pagination.py +0 -0
  58. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/shell.py +0 -0
  59. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/trace_render.py +0 -0
  60. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/tree_help.py +0 -0
  61. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/cli/verbs.py +0 -0
  62. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/configuration.py +0 -0
  63. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/context.py +0 -0
  64. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/dataset_sync.py +0 -0
  65. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/datasets.py +0 -0
  66. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/decorators.py +0 -0
  67. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/deployment.py +0 -0
  68. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/deployment_metadata.py +0 -0
  69. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/deployment_runs.py +0 -0
  70. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/eval_handler.py +0 -0
  71. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/execution.py +0 -0
  72. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/exporter.py +0 -0
  73. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/exporter_http.py +0 -0
  74. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/helpers.py +0 -0
  75. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/identity.py +0 -0
  76. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/imaging.py +0 -0
  77. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/__init__.py +0 -0
  78. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/decode.py +0 -0
  79. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/encode.py +0 -0
  80. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/hash.py +0 -0
  81. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/program.py +0 -0
  82. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/reflection.py +0 -0
  83. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/types.py +0 -0
  84. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/ir/xxh64.py +0 -0
  85. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/mcp_servers.py +0 -0
  86. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/models.py +0 -0
  87. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/__init__.py +0 -0
  88. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/gepa.py +0 -0
  89. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  90. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/resume.py +0 -0
  91. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/optimizers/tracker.py +0 -0
  92. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/references.py +0 -0
  93. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/retry.py +0 -0
  94. {cmpnd-0.43.1 → cmpnd-0.45.0}/cmpnd/traces.py +0 -0
  95. {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/2026-04-02-code-review.md +0 -0
  96. {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/cli.md +0 -0
  97. {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/custom-proposer.md +0 -0
  98. {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  99. {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  100. {cmpnd-0.43.1 → cmpnd-0.45.0}/docs/reading-traces.md +0 -0
  101. {cmpnd-0.43.1 → cmpnd-0.45.0}/examples/generalizing_proposer.py +0 -0
  102. {cmpnd-0.43.1 → cmpnd-0.45.0}/examples/local_ollama.py +0 -0
  103. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/LINEAGE.md +0 -0
  104. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/README.md +0 -0
  105. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  106. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  107. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  108. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  109. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  110. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/py.typed +0 -0
  111. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  112. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/retry.py +0 -0
  113. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  114. {cmpnd-0.43.1 → cmpnd-0.45.0}/ingest/pyproject.toml +0 -0
  115. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/audit-sdist.sh +0 -0
  116. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/capture_review_run.py +0 -0
  117. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/check-pip-install.sh +0 -0
  118. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/check_release_artifacts.py +0 -0
  119. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/fixtures/review_run.json.gz +0 -0
  120. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/gepa_resume_bug.py +0 -0
  121. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/release-stress.sh +0 -0
  122. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/require_version_bump.py +0 -0
  123. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/seed_review_run.py +0 -0
  124. {cmpnd-0.43.1 → cmpnd-0.45.0}/scripts/test-in-linux.sh +0 -0
  125. {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/__init__.pyi +0 -0
  126. {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  127. {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  128. {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/utils/__init__.pyi +0 -0
  129. {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/utils/callback.pyi +0 -0
  130. {cmpnd-0.43.1 → cmpnd-0.45.0}/stubs/dspy/utils/dummies.pyi +0 -0
  131. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/__init__.py +0 -0
  132. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_deployment_signature_helpers.py +0 -0
  133. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_exporter_helpers.py +0 -0
  134. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_fake_clock.py +0 -0
  135. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_identity_helpers.py +0 -0
  136. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_resolve_refs.py +0 -0
  137. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/_traced_run.py +0 -0
  138. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/conftest.py +0 -0
  139. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/__init__.py +0 -0
  140. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/committee_task.py +0 -0
  141. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/conftest.py +0 -0
  142. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  143. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  144. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/fake_lm.py +0 -0
  145. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/helpers.py +0 -0
  146. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/target_capabilities.py +0 -0
  147. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_assessments_cli.py +0 -0
  148. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_capture_prompts.py +0 -0
  149. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_composite_module.py +0 -0
  150. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  151. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_deploy_execute.py +0 -0
  152. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_deploy_pandas.py +0 -0
  153. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_deploy_programs.py +0 -0
  154. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_edge_cases.py +0 -0
  155. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_example_hash.py +0 -0
  156. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_gepa_optimization.py +0 -0
  157. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_interpreter_spans.py +0 -0
  158. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_lm_accuracy.py +0 -0
  159. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_module_tracing.py +0 -0
  160. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimization_lineage.py +0 -0
  161. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  162. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimize_failures.py +0 -0
  163. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimize_loop.py +0 -0
  164. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  165. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  166. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_parallel_export.py +0 -0
  167. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_project_attribution.py +0 -0
  168. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_react_tool_spans.py +0 -0
  169. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_save_load.py +0 -0
  170. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_trace_structure.py +0 -0
  171. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_traces_read.py +0 -0
  172. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_user_attribution.py +0 -0
  173. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/e2e/test_valsets_cli.py +0 -0
  174. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  175. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  176. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  177. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  178. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  179. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  180. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  181. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  182. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  183. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/10_react.json +0 -0
  184. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/11_refine.json +0 -0
  185. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/12_retrieve.json +0 -0
  186. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/13_knn.json +0 -0
  187. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/14_parallel.json +0 -0
  188. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/identity_vectors/README.md +0 -0
  189. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/ingest_conformance.py +0 -0
  190. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/__init__.py +0 -0
  191. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/authored_optimize/judge.py +0 -0
  192. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  193. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/conftest.py +0 -0
  194. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  195. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/optimization_run_probe.py +0 -0
  196. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  197. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_logs_renders_traces.py +0 -0
  198. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_optimization_run.py +0 -0
  199. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_optimizing_progress.py +0 -0
  200. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/integration/test_unhappy_path.py +0 -0
  201. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/__init__.py +0 -0
  202. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/__main__.py +0 -0
  203. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/drain.py +0 -0
  204. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/ingest.py +0 -0
  205. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/latency_transport.py +0 -0
  206. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/rlm_shape.py +0 -0
  207. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/rlm_size_report.py +0 -0
  208. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/sweep.py +0 -0
  209. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/test_rlm_shape.py +0 -0
  210. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/load/test_rlm_size_report.py +0 -0
  211. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/__init__.py +0 -0
  212. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/conftest.py +0 -0
  213. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/seed_parity.py +0 -0
  214. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_auth.py +0 -0
  215. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_datasets.py +0 -0
  216. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_deployments.py +0 -0
  217. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_evals.py +0 -0
  218. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_health.py +0 -0
  219. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_modules.py +0 -0
  220. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_optimizations.py +0 -0
  221. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_programs.py +0 -0
  222. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_signatures.py +0 -0
  223. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_trace_streaming.py +0 -0
  224. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/parity/test_traces.py +0 -0
  225. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/span_reference_fixture.py +0 -0
  226. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/__init__.py +0 -0
  227. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/_provider.py +0 -0
  228. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/_readiness.py +0 -0
  229. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/_reliability.py +0 -0
  230. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/_result.py +0 -0
  231. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/mint_bedrock_token.py +0 -0
  232. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_deploy_programs.py +0 -0
  233. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_multi_client_stress.py +0 -0
  234. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_optimize_client_committee.py +0 -0
  235. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_optimize_loop.py +0 -0
  236. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_optimize_loop_wasm.py +0 -0
  237. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_optimize_stress.py +0 -0
  238. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_provider_roles.py +0 -0
  239. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_readiness.py +0 -0
  240. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_real_provider_twins.py +0 -0
  241. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_reliability.py +0 -0
  242. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_reliability_classify.py +0 -0
  243. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_rlm_wasm.py +0 -0
  244. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/stress/test_trace_search.py +0 -0
  245. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_adapter_reentrancy.py +0 -0
  246. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_assessments_api.py +0 -0
  247. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored.py +0 -0
  248. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored_bundle_collisions.py +0 -0
  249. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored_metric_contract.py +0 -0
  250. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored_named_models.py +0 -0
  251. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_authored_optimize.py +0 -0
  252. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_callback.py +0 -0
  253. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_candidate_lineage.py +0 -0
  254. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/__init__.py +0 -0
  255. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/conftest.py +0 -0
  256. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_admin.py +0 -0
  257. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_api_client.py +0 -0
  258. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_assessments.py +0 -0
  259. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_auth.py +0 -0
  260. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_credentials.py +0 -0
  261. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_datasets.py +0 -0
  262. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_deployment_runs.py +0 -0
  263. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_deployments.py +0 -0
  264. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_evals.py +0 -0
  265. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_login.py +0 -0
  266. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_main.py +0 -0
  267. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_optimizations.py +0 -0
  268. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_org.py +0 -0
  269. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_pagination.py +0 -0
  270. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_parity.py +0 -0
  271. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_programs.py +0 -0
  272. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_provider_credentials.py +0 -0
  273. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_schema_sample.py +0 -0
  274. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_shell.py +0 -0
  275. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_signatures.py +0 -0
  276. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_tool_servers.py +0 -0
  277. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_top.py +0 -0
  278. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_traces.py +0 -0
  279. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  280. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_tree_help.py +0 -0
  281. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cli/test_valsets.py +0 -0
  282. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_client.py +0 -0
  283. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/__init__.py +0 -0
  284. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/conftest.py +0 -0
  285. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/test_edge_cases.py +0 -0
  286. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  287. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_clustering/test_type_conversion.py +0 -0
  288. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_cmp551_grouping_identity.py +0 -0
  289. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_configuration.py +0 -0
  290. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_context.py +0 -0
  291. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_context_propagation.py +0 -0
  292. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_dataset_polling.py +0 -0
  293. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_dataset_sync.py +0 -0
  294. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_datasets.py +0 -0
  295. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_decorators.py +0 -0
  296. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deploy.py +0 -0
  297. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  298. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deploy_program_id.py +0 -0
  299. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deployment_metadata.py +0 -0
  300. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deployment_runs.py +0 -0
  301. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_deployments_read.py +0 -0
  302. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_eval.py +0 -0
  303. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_eval_example_attribution.py +0 -0
  304. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_eval_start.py +0 -0
  305. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_eval_trace_grouping.py +0 -0
  306. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exception_paths.py +0 -0
  307. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_execute.py +0 -0
  308. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_execute_run_identity.py +0 -0
  309. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exporter.py +0 -0
  310. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exporter_program_names.py +0 -0
  311. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exporter_queue.py +0 -0
  312. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_exporter_real.py +0 -0
  313. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_fake_lm.py +0 -0
  314. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_gepa_callback.py +0 -0
  315. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_gepa_integration.py +0 -0
  316. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_gepa_output_normalization.py +0 -0
  317. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_gepa_resume.py +0 -0
  318. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_identity.py +0 -0
  319. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_imaging.py +0 -0
  320. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_import_weight.py +0 -0
  321. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ingest_conformance.py +0 -0
  322. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ingest_second_producer.py +0 -0
  323. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_integration.py +0 -0
  324. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_interpreter_callback.py +0 -0
  325. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ir_encoder_properties.py +0 -0
  326. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ir_golden_vectors.py +0 -0
  327. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_ir_roundtrip.py +0 -0
  328. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_lm15_native_calls.py +0 -0
  329. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_lm_configuration.py +0 -0
  330. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_lm_usage_shapes.py +0 -0
  331. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_mcp_tools.py +0 -0
  332. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_models.py +0 -0
  333. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_multi_client_stress_readiness.py +0 -0
  334. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimization_progress.py +0 -0
  335. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimization_start.py +0 -0
  336. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize.py +0 -0
  337. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_budget.py +0 -0
  338. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_lift.py +0 -0
  339. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_lms.py +0 -0
  340. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_proposer.py +0 -0
  341. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_reflection_lm.py +0 -0
  342. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_optimize_terminal_failure.py +0 -0
  343. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_packaging.py +0 -0
  344. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_packaging_callable_source.py +0 -0
  345. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_packaging_mcp_tools.py +0 -0
  346. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_packaging_program_source.py +0 -0
  347. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_poll_until.py +0 -0
  348. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_program_identity.py +0 -0
  349. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_program_lineage_persist.py +0 -0
  350. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_programs_create.py +0 -0
  351. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_project_payload.py +0 -0
  352. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_real_provider_twins_readiness.py +0 -0
  353. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_reflector_properties.py +0 -0
  354. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_reflector_smoke.py +0 -0
  355. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_release_stress_script.py +0 -0
  356. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_repro_reactv2_bugs.py +0 -0
  357. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_require_version_bump.py +0 -0
  358. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_retry.py +0 -0
  359. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_reversible_failures.py +0 -0
  360. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_rlm_patch.py +0 -0
  361. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_run_attribution_paths.py +0 -0
  362. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_runtime_tools.py +0 -0
  363. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_schemas.py +0 -0
  364. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_seed_review_run.py +0 -0
  365. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_signature_inputs.py +0 -0
  366. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_span_references.py +0 -0
  367. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_span_sent_once.py +0 -0
  368. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_trace_context_concurrency.py +0 -0
  369. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_trace_fact_wire_contract.py +0 -0
  370. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_trace_facts.py +0 -0
  371. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_trace_render.py +0 -0
  372. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_traces_read.py +0 -0
  373. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_tracked_gepa.py +0 -0
  374. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_tracker_enrich.py +0 -0
  375. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_truncation.py +0 -0
  376. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_valsets_api.py +0 -0
  377. {cmpnd-0.43.1 → cmpnd-0.45.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.43.1
3
+ Version: 0.45.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>
@@ -13,6 +13,7 @@ Usage:
13
13
  """
14
14
 
15
15
  import importlib.metadata
16
+ from typing import TYPE_CHECKING
16
17
 
17
18
  from . import authored, programs, traces
18
19
  from ._http import ApiError
@@ -45,7 +46,7 @@ from .deployment_runs import (
45
46
  )
46
47
  from .execution import DeployedProgram, RetryableProviderError, deployment_run_id, execute
47
48
  from .exporter import flush_exporter, shutdown_exporter
48
- from .lm_configuration import configure_lm
49
+ from .lm_configuration import SystemOne, configure_lm
49
50
  from .mcp_servers import (
50
51
  Bearer,
51
52
  McpToolReference,
@@ -55,12 +56,15 @@ from .mcp_servers import (
55
56
  mcp_tools,
56
57
  )
57
58
  from .models import SpanBuilder, SpanType, TraceBuilder, TraceStatus
59
+
60
+ if TYPE_CHECKING:
61
+ # Loaded on first use by __getattr__ below, because their module imports dspy.
62
+ from .named_programs import Program, program
58
63
  from .optimization import (
59
64
  OptimizeError,
60
65
  OptimizeFailed,
61
66
  optimize,
62
67
  )
63
- from .programs import Program
64
68
 
65
69
  try:
66
70
  __version__ = importlib.metadata.version("cmpnd")
@@ -76,6 +80,7 @@ __all__ = [
76
80
  # Configuration
77
81
  "configure",
78
82
  "configure_lm",
83
+ "SystemOne",
79
84
  "get_config",
80
85
  "require_config",
81
86
  # Main callback
@@ -103,6 +108,7 @@ __all__ = [
103
108
  "programs",
104
109
  # A program declared by name
105
110
  "Program",
111
+ "program",
106
112
  "authored",
107
113
  "ApiError",
108
114
  # Types
@@ -253,3 +259,15 @@ def de_instrument() -> None:
253
259
  def _mcp_tool(declaration): # pyright: ignore[reportUnusedFunction] -- generated bundles call this hook
254
260
  """Keep generated declarations importable outside a worker without granting access."""
255
261
  return declaration
262
+
263
+
264
+ def __getattr__(name: str): # noqa: ANN202 — PEP 562 module attribute hook
265
+ """Load ``Program`` and ``program`` on first use, because their module imports dspy.
266
+
267
+ ``import cmpnd`` stays free of dspy until a caller asks for one of them.
268
+ """
269
+ if name in ("Program", "program"):
270
+ from . import named_programs
271
+
272
+ return getattr(named_programs, name)
273
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
@@ -383,6 +383,10 @@ def _lm15_cache_hit(entry: Any, response: Any) -> bool | None:
383
383
 
384
384
  def _usage_dialect(instance: Any, usage: Any) -> str | None:
385
385
  """Name the response surface only when its shape establishes one."""
386
+ # A decision client (DSPy's `TypeSafe`) speaks System One. Its usage names chat's token
387
+ # fields, so the shape alone would misname it. DSPy dispatches on the same flag.
388
+ if getattr(instance, "supports_decision_requests", False) is True:
389
+ return "system_one"
386
390
  model_type = _text_field(instance, "model_type")
387
391
  if model_type == "responses":
388
392
  return "responses"
@@ -1489,7 +1493,8 @@ class _LMMixin(_CallbackBase):
1489
1493
  observation.usage_dialect = _usage_dialect(instance, usage)
1490
1494
  if observation.usage_dialect in {"chat_completions", "responses"}:
1491
1495
  observation.prompt_inclusion = "includes_cache_reads"
1492
- elif observation.usage_dialect == "converse":
1496
+ elif observation.usage_dialect in {"converse", "system_one"}:
1497
+ # System One serves nothing from a cache, so its prompt is all fresh input.
1493
1498
  observation.prompt_inclusion = "excludes_cache_reads"
1494
1499
 
1495
1500
  if usage_reported and usage is not None:
@@ -29,7 +29,7 @@ import dataclasses
29
29
  import os
30
30
  from typing import Any, Literal
31
31
 
32
- from . import _assessments, _deployments, _exporter_pool, _traces, _valsets, callback, configuration, programs
32
+ from . import _assessments, _deployments, _exporter_pool, _traces, _valsets, callback, configuration
33
33
  from .deployment_metadata import DeploymentSignatureResponse
34
34
 
35
35
 
@@ -106,28 +106,44 @@ class Client:
106
106
 
107
107
  # -- scoped runs ------------------------------------------------------
108
108
 
109
+ @staticmethod
110
+ def _as_program(obj: Any) -> Any:
111
+ """``obj`` when it is a ``cmpnd.Program``, else None."""
112
+ from . import named_programs
113
+
114
+ return obj if isinstance(obj, named_programs.Program) else None
115
+
109
116
  def _declared(self, program: Any) -> dict[str, Any]:
110
117
  """The ``dspy.context`` settings that run ``program`` under its declared name.
111
118
 
112
- A ``cmpnd.Program`` runs on its own LM and declares its name. Any other module
113
- declares no name, which also clears a name an enclosing call declared.
119
+ A ``cmpnd.Program`` runs on its own LM, which an enclosing program's LM would
120
+ override, and declares its name. Any other module declares no name and no
121
+ program LM, which also clears the ones an enclosing call declared.
114
122
  """
115
- if isinstance(program, programs.Program):
116
- return {"lm": program.lm, callback.DECLARED_PROGRAM_SETTING: program.name}
117
- return {callback.DECLARED_PROGRAM_SETTING: None}
123
+ from . import named_programs
124
+
125
+ named = self._as_program(program)
126
+ if named is not None:
127
+ return {
128
+ "lm": named.lm,
129
+ named_programs.PROGRAM_LM_SETTING: named.lm,
130
+ callback.DECLARED_PROGRAM_SETTING: named.name,
131
+ }
132
+ return {named_programs.PROGRAM_LM_SETTING: None, callback.DECLARED_PROGRAM_SETTING: None}
118
133
 
119
134
  def run_program(self, program: Any, **inputs: Any) -> Any:
120
135
  """Run ``program(**inputs)`` traced on this client's account.
121
136
 
122
137
  ``program`` is a ``cmpnd.Program`` or a plain DSPy module. A ``Program`` runs
123
- on its own LM, and its traces go to the program with its name.
138
+ on its own LM, and its traces go to the program with its name. A program adds
139
+ no span of its own, so a program and its module run plain record the same
140
+ structure.
124
141
  """
125
142
  self._check_active()
126
143
  import dspy
127
144
 
128
- module = program.module if isinstance(program, programs.Program) else program
129
145
  with dspy.context(callbacks=self._scoped_callbacks(), **self._declared(program)):
130
- return module(**inputs)
146
+ return program(**inputs)
131
147
 
132
148
  def run_optimization(
133
149
  self,
@@ -150,14 +166,22 @@ class Client:
150
166
  suppresses the global class patch so the run is recorded once, on the
151
167
  client's account, not the global one.
152
168
 
153
- ``student`` is a ``cmpnd.Program`` or a plain DSPy module. A ``Program`` is
154
- optimized on its own LM, its rollouts and run record go to the program with
155
- its name, and the result is a ``Program`` with the same name and LM.
169
+ ``student`` is a ``cmpnd.Program`` or a plain DSPy module. The optimizer
170
+ works on a ``Program``'s module, on the program's LM, and its rollouts and run
171
+ record go to the program with its name. The result is a copy of the student,
172
+ of the same class and with the same name and LM, holding the optimized module.
156
173
  """
157
174
  self._check_active()
175
+ import copy
176
+
158
177
  import dspy
159
178
 
160
- module = student.module if isinstance(student, programs.Program) else student
179
+ from . import named_programs
180
+
181
+ named = self._as_program(student)
182
+ whole = named is not None and named_programs.runs_itself(named)
183
+ module = named.module if named is not None and not whole else student
184
+ result: Any = None
161
185
  with dspy.context(callbacks=self._scoped_callbacks(), **self._declared(student)):
162
186
  try:
163
187
  if isinstance(optimizer, dspy.GEPA):
@@ -167,15 +191,22 @@ class Client:
167
191
  trainset=trainset,
168
192
  valset=valset,
169
193
  kwargs=kwargs,
170
- declared_name=student.name if isinstance(student, programs.Program) else None,
194
+ declared_name=named.name if named is not None else None,
171
195
  )
172
196
  else:
173
197
  result = optimizer.compile(module, trainset=trainset, valset=valset, **kwargs)
174
198
  finally:
175
199
  self._callback._clear_optimization_trace_state()
176
- if isinstance(student, programs.Program):
177
- return programs.Program(student.name, module=result, lm=student.lm)
178
- return result
200
+ if named is None:
201
+ return result
202
+ if whole and isinstance(result, type(named)):
203
+ # The optimizer worked on the program itself and returned a copy of it, which
204
+ # deep-copied the LM; the copy gets the student's back.
205
+ result.lm = named.lm
206
+ return result
207
+ optimized = copy.copy(named)
208
+ optimized.module = result
209
+ return optimized
179
210
 
180
211
  # -- data surface (flat verb-object over the client's credentials) -----
181
212
  #
@@ -9,11 +9,25 @@ class LMConfigurationError(ValueError):
9
9
  """A hosted binding declaration cannot be resolved consistently."""
10
10
 
11
11
 
12
- class LMCapabilities(TypedDict):
13
- """Explicit adapter hints for a hosted model, independent of its provider."""
12
+ class LMCapabilities(TypedDict, total=False):
13
+ """Explicit adapter hints for a hosted model, independent of its provider.
14
+
15
+ ``dialect`` is present only for a System One binding, which :class:`SystemOne`
16
+ writes. Absent, the binding is a chat model.
17
+ """
14
18
 
15
19
  supported_params: list[str]
16
20
  supports_response_schema: bool
21
+ dialect: str
22
+
23
+
24
+ # The wire protocol a binding's model speaks, named for the protocol and never for a
25
+ # provider or a client library: a chat model answers chat completions, and a System One
26
+ # model (TypeSafe's System One protocol, which other providers serve too) answers a set
27
+ # of questions about a state.
28
+ CHAT_COMPLETIONS = "chat_completions"
29
+ SYSTEM_ONE = "system_one"
30
+ _DIALECTS = frozenset({CHAT_COMPLETIONS, SYSTEM_ONE})
17
31
 
18
32
 
19
33
  def _validate_lm_capabilities(value: object) -> dict[str, LMCapabilities]:
@@ -27,7 +41,7 @@ def _validate_lm_capabilities(value: object) -> dict[str, LMCapabilities]:
27
41
  )
28
42
  if not isinstance(declaration, dict):
29
43
  raise LMConfigurationError(f"lm_capabilities[{binding!r}] must be a capability declaration")
30
- unknown = declaration.keys() - {"supported_params", "supports_response_schema"}
44
+ unknown = declaration.keys() - {"supported_params", "supports_response_schema", "dialect"}
31
45
  if unknown:
32
46
  raise LMConfigurationError(f"lm_capabilities[{binding!r}] has unknown fields: {sorted(unknown, key=str)}")
33
47
  params = declaration.get("supported_params", [])
@@ -38,11 +52,24 @@ def _validate_lm_capabilities(value: object) -> dict[str, LMCapabilities]:
38
52
  schema = declaration.get("supports_response_schema", False)
39
53
  if not isinstance(schema, bool):
40
54
  raise LMConfigurationError(f"lm_capabilities[{binding!r}].supports_response_schema must be a boolean")
55
+ dialect = declaration.get("dialect", CHAT_COMPLETIONS)
56
+ if dialect not in _DIALECTS:
57
+ raise LMConfigurationError(
58
+ f"lm_capabilities[{binding!r}].dialect must be one of {sorted(_DIALECTS)} (got {dialect!r})"
59
+ )
60
+ if dialect == SYSTEM_ONE and (params or schema):
61
+ raise LMConfigurationError(
62
+ f"lm_capabilities[{binding!r}] is a System One binding, which takes no sampling "
63
+ "parameters and no response schema"
64
+ )
41
65
  if schema and "response_format" not in params:
42
66
  raise LMConfigurationError(
43
67
  f"lm_capabilities[{binding!r}] requires response_format in supported_params for schema output"
44
68
  )
45
- result[binding] = {"supported_params": list(dict.fromkeys(params)), "supports_response_schema": schema}
69
+ hints: LMCapabilities = {"supported_params": list(dict.fromkeys(params)), "supports_response_schema": schema}
70
+ if dialect == SYSTEM_ONE:
71
+ hints["dialect"] = SYSTEM_ONE
72
+ result[binding] = hints
46
73
  return result
47
74
 
48
75
 
@@ -122,6 +149,11 @@ def configure_lm(
122
149
  )
123
150
  _merge(_names, _name(lm), hints)
124
151
  return
152
+ _declare(lm, hints)
153
+
154
+
155
+ def _declare(lm: Any, hints: dict[str, Any]) -> None:
156
+ """Attach a declaration to one LM object, shared by every binding made from it."""
125
157
  model = _model(lm)
126
158
  declared = _object_declaration(lm)
127
159
  if declared is not None and declared != (model, hints):
@@ -131,6 +163,38 @@ def configure_lm(
131
163
  _objects.append(lm)
132
164
 
133
165
 
166
+ class SystemOne:
167
+ """A System One decision model, for a run's ``lms``.
168
+
169
+ A System One model answers a set of questions about a state instead of continuing a
170
+ conversation: TypeSafe's Jev, or another endpoint serving the same schema. A hosted
171
+ judge built on ``dspy.experimental.TypeSafe`` runs against the run's System One
172
+ binding without naming a key or a URL::
173
+
174
+ cmpnd.optimize(..., lms={"judge": cmpnd.SystemOne("typesafe/jev-latest")})
175
+
176
+ ``model`` is a platform model id: the prefix names the organization's sealed
177
+ credential, and the rest is the model at that credential's endpoint. Writing the
178
+ binding this way is the only thing that makes it a System One binding; a model name
179
+ on its own never does.
180
+ """
181
+
182
+ def __init__(self, model: object):
183
+ if not isinstance(model, str) or not model.strip():
184
+ raise ValueError(f"cmpnd.SystemOne needs a model id such as 'typesafe/jev-latest' (got {model!r})")
185
+ credential, _, name = model.strip().partition("/")
186
+ if not credential or not name:
187
+ raise ValueError(
188
+ f"cmpnd.SystemOne({model!r}) names no credential: write the model as "
189
+ "'<credential>/<model>', for example 'typesafe/jev-latest'"
190
+ )
191
+ self.model = model.strip()
192
+ _declare(self, {"supported_params": [], "supports_response_schema": False, "dialect": SYSTEM_ONE})
193
+
194
+ def __repr__(self) -> str:
195
+ return f"cmpnd.SystemOne({self.model!r})"
196
+
197
+
134
198
  def restore(declarations: object) -> None:
135
199
  """Load model-pinned declarations from a packaged artifact."""
136
200
  if not isinstance(declarations, dict):
@@ -0,0 +1,274 @@
1
+ """Programs declared by name: ``cmpnd.Program`` and the ``@cmpnd.program`` decorator.
2
+
3
+ A ``cmpnd.Program`` is a DSPy module with two more things: the name of the program it
4
+ is, and the LM it runs on. It holds the module that does the work as a submodule, so
5
+ it nests inside a larger DSPy program like any other module. The name is the
6
+ program's handle: traces from a ``cmpnd.Client`` run of it go to the program with that
7
+ name in the caller's organization, which the platform creates on first use.
8
+
9
+ ``@cmpnd.program(name)`` is a shorter way to define one. Above a ``dspy.Module`` class,
10
+ it returns a ``cmpnd.Program`` subclass of the same name, so that
11
+
12
+ @cmpnd.program("capital-qa")
13
+ class CapitalQA(dspy.Module): ...
14
+
15
+ qa = CapitalQA(lm=lm)
16
+
17
+ builds the same program as ``cmpnd.Program("capital-qa", module=<the class as
18
+ written>(), lm=lm)``.
19
+
20
+ When programs nest, the outermost program's LM wins, so a caller can always choose the
21
+ model from outside. Each program records the LM it applied in a DSPy settings key, and
22
+ a program inside applies its own LM only when the key is unset.
23
+
24
+ A program adds no span to a trace. Calling one calls its module directly, past the
25
+ ``dspy.Module.__call__`` that traces a module, so a program, a program that wraps
26
+ another, and a program nested in a larger module all trace as the module they hold.
27
+
28
+ This module imports dspy, so ``cmpnd`` loads it only when a caller first uses
29
+ ``cmpnd.Program`` or ``cmpnd.program``.
30
+ """
31
+
32
+ from __future__ import annotations
33
+
34
+ import contextlib
35
+ import inspect
36
+ import weakref
37
+ from collections.abc import Callable
38
+ from typing import Any
39
+
40
+ import dspy
41
+ from dspy.clients import base_lm # pyright: ignore[reportMissingTypeStubs] # dspy re-exports BaseLM untyped
42
+
43
+ from . import programs
44
+
45
+ # The DSPy settings key that holds the LM the outermost running program applied.
46
+ PROGRAM_LM_SETTING = "cmpnd_program_lm"
47
+
48
+ # The source of each class @cmpnd.program decorated, read when it was decorated. The
49
+ # class's qualified name changes then (see `program`), and before Python 3.13
50
+ # `inspect.getsource` finds a class by that name.
51
+ _DECLARED_SOURCE: weakref.WeakKeyDictionary[type, str] = weakref.WeakKeyDictionary()
52
+
53
+
54
+ def _check_lm(lm: Any, usage: str) -> None:
55
+ if not isinstance(lm, base_lm.BaseLM):
56
+ raise TypeError(f"A program needs an LM such as dspy.LM(...), not {type(lm).__name__}: {usage}")
57
+
58
+
59
+ def _check_module(module: object) -> None:
60
+ if not isinstance(module, dspy.Module):
61
+ raise TypeError(f"A Program needs a DSPy module, not {type(module).__name__}")
62
+
63
+
64
+ def _without_self(signature: inspect.Signature) -> inspect.Signature:
65
+ return signature.replace(
66
+ parameters=list(signature.parameters.values())[1:], return_annotation=inspect.Signature.empty
67
+ )
68
+
69
+
70
+ _ModuleMeta: Any = type(dspy.Module) # DSPy's metaclass, which every dspy.Module uses
71
+
72
+
73
+ class _ProgramMeta(_ModuleMeta):
74
+ """DSPy's metaclass, with each program class's constructor arguments for help().
75
+
76
+ DSPy's metaclass defines ``__call__``, which hides a class's ``__init__`` from
77
+ ``inspect.signature``. A property here is read by ``inspect`` and not listed by
78
+ ``help()``.
79
+ """
80
+
81
+ @property
82
+ def __signature__(cls) -> inspect.Signature:
83
+ return _without_self(inspect.signature(cls.__init__))
84
+
85
+
86
+ class Program(dspy.Module, metaclass=_ProgramMeta):
87
+ """A DSPy module with a program name and the LM it runs on.
88
+
89
+ A program holds the DSPy module that does the work. Run it through a
90
+ ``cmpnd.Client``, and its traces go to the program with its name in your
91
+ organization:
92
+
93
+ >>> qa = cmpnd.Program("capital-qa", module=dspy.Predict("question -> answer"), lm=dspy.LM("openai/gpt-5-mini"))
94
+ >>> with cmpnd.Client() as client:
95
+ ... client.run_program(qa, question="What is the capital of France?")
96
+
97
+ ``@cmpnd.program(name)`` is a shorter way to define a program on a class you
98
+ write. Use this constructor when one class serves several programs, when you
99
+ cannot edit the class, or when its constructor already takes ``lm``.
100
+
101
+ Attributes:
102
+ name: The program's name: 1 to 64 letters, digits, ".", "_" or "-", starting
103
+ with a letter or digit and not with "p_". Programs in one organization
104
+ that share a name share a history.
105
+ module: The DSPy module that does the work. It is a submodule of the program,
106
+ so ``named_predictors()`` and optimizers find its predictors, also when
107
+ the program is inside a larger DSPy module.
108
+ lm: The LM the program runs on. When programs nest, the outermost program's
109
+ LM wins, so a caller can choose the model from outside.
110
+
111
+ Declaring a program makes no network call. The platform creates the program
112
+ the first time a client sends one of its traces. Calling a program directly
113
+ runs it on its LM and records nothing under its name.
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ name: str,
119
+ *,
120
+ module: dspy.Module,
121
+ lm: dspy.BaseLM, # pyright: ignore[reportAttributeAccessIssue] # dspy re-exports BaseLM untyped
122
+ ) -> None:
123
+ """Declare a program named ``name`` that runs ``module`` on ``lm``."""
124
+ super().__init__()
125
+ self.name = programs.validate_program_name(name)
126
+ _check_module(module)
127
+ _check_lm(lm, "cmpnd.Program(name, module=..., lm=dspy.LM(...))")
128
+ self.module: Any = module
129
+ self.lm = lm
130
+
131
+ def _lm_context(self) -> Any:
132
+ """A context that applies this program's LM, unless an enclosing program applied one."""
133
+ if dspy.settings.get(PROGRAM_LM_SETTING) is not None:
134
+ return contextlib.nullcontext()
135
+ return dspy.context(lm=self.lm, **{PROGRAM_LM_SETTING: self.lm})
136
+
137
+ def __call__(self, *args: Any, **inputs: Any) -> dspy.Prediction:
138
+ """Run the module on this program's LM, and return its prediction.
139
+
140
+ The program makes no span of its own. ``dspy.Module.__call__`` makes a span for
141
+ each module it runs, and this method calls the module directly instead, so
142
+ wrapped and nested programs add no layers to a trace. A subclass that overrides
143
+ ``forward`` goes through ``dspy.Module.__call__`` and traces as itself.
144
+ """
145
+ with self._lm_context():
146
+ if runs_itself(self):
147
+ return super().__call__(*args, **inputs)
148
+ return self.module(*args, **inputs)
149
+
150
+ async def acall(self, *args: Any, **inputs: Any) -> dspy.Prediction:
151
+ """Run the module asynchronously, with no span for the program itself, as ``__call__`` does."""
152
+ with self._lm_context():
153
+ if runs_itself(self):
154
+ return await getattr(super(), "acall")(*args, **inputs) # dspy's Module.acall is untyped
155
+ return await self.module.acall(*args, **inputs)
156
+
157
+ def forward(self, *args: Any, **inputs: Any) -> dspy.Prediction:
158
+ """Run the module on this program's LM. A subclass may override this to add behavior."""
159
+ with self._lm_context():
160
+ return self.module(*args, **inputs)
161
+
162
+ async def aforward(self, *args: Any, **inputs: Any) -> dspy.Prediction:
163
+ """Run the module asynchronously on this program's LM, as ``forward`` does."""
164
+ with self._lm_context():
165
+ return await self.module.acall(*args, **inputs)
166
+
167
+ def __repr__(self) -> str:
168
+ return f"{type(self).__name__}({self.name!r}, lm={getattr(self.lm, 'model', self.lm)!r})"
169
+
170
+
171
+ def _with_lm(signature: inspect.Signature) -> inspect.Signature:
172
+ """``signature`` with a required keyword ``lm`` added before any ``**kwargs``."""
173
+ parameters = list(signature.parameters.values())
174
+ at = next((i for i, p in enumerate(parameters) if p.kind is inspect.Parameter.VAR_KEYWORD), len(parameters))
175
+ parameters.insert(at, inspect.Parameter("lm", inspect.Parameter.KEYWORD_ONLY, annotation="dspy.BaseLM"))
176
+ return signature.replace(parameters=parameters)
177
+
178
+
179
+ def runs_itself(program: Program) -> bool:
180
+ """Whether running ``program`` means running its own code, not only its module.
181
+
182
+ A subclass that overrides ``forward`` or ``aforward`` adds behavior around its
183
+ module, so calling it runs that code as a traced module, and a client optimizes the
184
+ program itself. Any other program only runs its module, so its trace's root and
185
+ structure match the module run plain.
186
+ """
187
+ cls = type(program)
188
+ return cls.forward is not Program.forward or cls.aforward is not Program.aforward
189
+
190
+
191
+ def program(name: str) -> Callable[[type[dspy.Module]], type[Program]]:
192
+ """Declare a program on a DSPy module class, where the class is defined.
193
+
194
+ ``@cmpnd.program(name)`` is a shorter way to write the ``cmpnd.Program``
195
+ constructor. Above a ``dspy.Module`` class, it returns a ``cmpnd.Program``
196
+ subclass of the same name. That subclass takes the class's own constructor
197
+ arguments and a required keyword ``lm``, and builds the class as written as the
198
+ program's ``module``:
199
+
200
+ >>> @cmpnd.program("capital-qa")
201
+ ... class CapitalQA(dspy.Module):
202
+ ... def __init__(self):
203
+ ... super().__init__()
204
+ ... self.answer = dspy.Predict("question -> answer")
205
+ ...
206
+ ... def forward(self, question):
207
+ ... return self.answer(question=question)
208
+ >>> qa = CapitalQA(lm=dspy.LM("openai/gpt-5-mini"))
209
+ >>> with cmpnd.Client() as client:
210
+ ... client.run_program(qa, question="What is the capital of France?")
211
+
212
+ ``qa`` is the same program as ``cmpnd.Program("capital-qa", module=<CapitalQA as
213
+ written>(), lm=...)``, and the class's attributes are on ``qa.module``.
214
+
215
+ A decorated class has one name. Use the ``cmpnd.Program`` constructor when one
216
+ class serves several programs, when you cannot edit the class, or when its
217
+ constructor already takes ``lm``. ``help(cmpnd.Program)`` gives the name rules
218
+ and how a program runs.
219
+ """
220
+ programs.validate_program_name(name)
221
+
222
+ def decorate(module_class: Any) -> Any:
223
+ if not (isinstance(module_class, type) and issubclass(module_class, dspy.Module)):
224
+ raise TypeError("@cmpnd.program() decorates a dspy.Module class")
225
+ if issubclass(module_class, Program):
226
+ raise TypeError(f"{module_class.__name__} is already a program; subclass it instead of decorating it")
227
+ if "lm" in inspect.signature(module_class.__init__).parameters:
228
+ raise TypeError(
229
+ f"{module_class.__name__}.__init__ already takes lm, so it cannot be decorated. "
230
+ "Wrap an instance instead: cmpnd.Program(name, module=..., lm=...)"
231
+ )
232
+ try:
233
+ _DECLARED_SOURCE[module_class] = inspect.getsource(module_class)
234
+ except (OSError, TypeError):
235
+ pass # no source to read, as in a bare REPL; a deploy then fails as it would undecorated
236
+
237
+ def __init__(self: Any, *args: Any, lm: Any = None, **kwargs: Any) -> None:
238
+ # lm has a default only so that a missing LM gets the message below.
239
+ _check_lm(lm, f"{module_class.__name__}(..., lm=dspy.LM(...))")
240
+ Program.__init__(self, name, module=module_class(*args, **kwargs), lm=lm)
241
+
242
+ qualname = module_class.__qualname__
243
+ signature = _with_lm(inspect.signature(module_class.__init__))
244
+ __init__.__qualname__ = f"{qualname}.__init__"
245
+ __init__.__module__ = module_class.__module__
246
+ __init__.__doc__ = f"Build a {module_class.__name__} as the module of a program named {name!r}."
247
+ setattr(__init__, "__signature__", signature)
248
+ doc = f"A cmpnd.Program named {name!r}, which runs {module_class.__name__} as written."
249
+ if module_class.__doc__:
250
+ doc += "\n\n" + inspect.cleandoc(module_class.__doc__)
251
+ decorated = _ProgramMeta(
252
+ module_class.__name__,
253
+ (Program,),
254
+ {
255
+ "__init__": __init__,
256
+ "__module__": module_class.__module__,
257
+ "__qualname__": qualname,
258
+ "__doc__": doc,
259
+ "_cmpnd_module": module_class,
260
+ },
261
+ )
262
+ # The decorated class takes the class's name in its module, so pickle would find
263
+ # it, not the class as written, when it looks a module instance's class up by
264
+ # name. The class as written moves under the decorated one, where pickle finds it.
265
+ # The attribute's single leading underscore keeps it out of help().
266
+ module_class.__qualname__ = f"{qualname}._cmpnd_module"
267
+ return decorated
268
+
269
+ return decorate
270
+
271
+
272
+ def declared_source(cls: type) -> str | None:
273
+ """The source of a class ``@cmpnd.program`` decorated, read before it was renamed."""
274
+ return _DECLARED_SOURCE.get(cls)
@@ -462,6 +462,23 @@ def _lms_config(config: dict[str, Any], lms: Any) -> None:
462
462
  if isinstance(lm, str):
463
463
  declared[name] = {"model": lm}
464
464
  continue
465
+ from .lm_configuration import SystemOne
466
+
467
+ if isinstance(lm, SystemOne):
468
+ # A System One model takes no sampling, and its declaration (which says it
469
+ # is one) travels in the bundle rather than on the wire.
470
+ declared[name] = {"model": lm.model}
471
+ continue
472
+ if getattr(lm, "supports_decision_requests", False):
473
+ # dspy.experimental.TypeSafe, or a client like it: it carries a key and a URL
474
+ # rather than the name of a sealed credential, so the platform has nothing to
475
+ # resolve. Duck-typed so this module never imports dspy at scope.
476
+ raise ValueError(
477
+ f"{subject} is a {type(lm).__name__} client, which names a key and a URL rather "
478
+ f"than a credential. Bind the model with cmpnd.SystemOne, for example "
479
+ f"lms={{{name!r}: cmpnd.SystemOne('typesafe/jev-latest')}}; a judge that builds "
480
+ f"its own {type(lm).__name__} runs against that binding when hosted"
481
+ )
465
482
  model = _lm_model(lm, subject)
466
483
  _refuse_credential_on(lm, model, subject)
467
484
  entry: dict[str, Any] = {"model": model}