cmpnd 0.64.0__tar.gz → 0.66.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 (396) hide show
  1. {cmpnd-0.64.0 → cmpnd-0.66.0}/PKG-INFO +34 -6
  2. {cmpnd-0.64.0 → cmpnd-0.66.0}/README.md +33 -5
  3. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/optimizations.py +142 -2
  4. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/top.py +18 -26
  5. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/shell.py +0 -4
  6. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/verbs.py +1 -0
  7. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/client.py +109 -20
  8. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/execution.py +4 -0
  9. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/optimization.py +210 -116
  10. {cmpnd-0.64.0 → cmpnd-0.66.0}/docs/cli.md +43 -3
  11. {cmpnd-0.64.0 → cmpnd-0.66.0}/pyproject.toml +1 -1
  12. cmpnd-0.66.0/tests/e2e/continuation_task.py +72 -0
  13. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_deploy_pandas.py +0 -1
  14. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_optimize_authored_metric.py +0 -2
  15. cmpnd-0.66.0/tests/e2e/test_optimize_continuation.py +150 -0
  16. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_optimize_custom_proposer.py +0 -1
  17. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_optimize_failures.py +0 -2
  18. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_optimize_loop.py +0 -3
  19. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -1
  20. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/hosted_optimize_probe.py +0 -1
  21. cmpnd-0.66.0/tests/stress/test_optimize_continuation.py +170 -0
  22. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_optimize_loop.py +1 -2
  23. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_optimize_loop_wasm.py +0 -1
  24. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_optimize_stress.py +0 -1
  25. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_authored_binding_credentials.py +3 -3
  26. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_authored_metric_contract.py +5 -5
  27. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_authored_named_models.py +0 -2
  28. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_authored_optimize.py +3 -3
  29. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_optimizations.py +140 -0
  30. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_top.py +4 -3
  31. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_credential_selection.py +6 -6
  32. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_deploy_program_id.py +11 -14
  33. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_lm_configuration.py +1 -1
  34. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimize.py +307 -81
  35. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimize_budget.py +3 -4
  36. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimize_proposer.py +0 -1
  37. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_program_names_in_deploy.py +3 -1
  38. {cmpnd-0.64.0 → cmpnd-0.66.0}/uv.lock +1 -1
  39. {cmpnd-0.64.0 → cmpnd-0.66.0}/.gitignore +0 -0
  40. {cmpnd-0.64.0 → cmpnd-0.66.0}/AGENTS.md +0 -0
  41. {cmpnd-0.64.0 → cmpnd-0.66.0}/CLAUDE.md +0 -0
  42. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/__init__.py +0 -0
  43. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_assessments.py +0 -0
  44. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_authored_import.py +0 -0
  45. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_deployments.py +0 -0
  46. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_exporter_pool.py +0 -0
  47. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_gepa_patch.py +0 -0
  48. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_http.py +0 -0
  49. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_program_patch.py +0 -0
  50. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_rlm_patch.py +0 -0
  51. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_runtime_tools.py +0 -0
  52. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_timespec.py +0 -0
  53. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_traces.py +0 -0
  54. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/_valsets.py +0 -0
  55. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/authored.py +0 -0
  56. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/callback.py +0 -0
  57. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/__init__.py +0 -0
  58. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/api_client.py +0 -0
  59. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/__init__.py +0 -0
  60. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/admin.py +0 -0
  61. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/assessments.py +0 -0
  62. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/auth.py +0 -0
  63. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/datasets.py +0 -0
  64. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/deployments.py +0 -0
  65. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/evals.py +0 -0
  66. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/login.py +0 -0
  67. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/org.py +0 -0
  68. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/programs.py +0 -0
  69. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  70. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  71. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/traces.py +0 -0
  72. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/commands/valsets.py +0 -0
  73. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/credentials.py +0 -0
  74. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/deprecation.py +0 -0
  75. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/pagination.py +0 -0
  76. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/trace_render.py +0 -0
  77. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/cli/tree_help.py +0 -0
  78. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/configuration.py +0 -0
  79. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/context.py +0 -0
  80. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/credentials.py +0 -0
  81. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/dataset_sync.py +0 -0
  82. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/datasets.py +0 -0
  83. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/decorators.py +0 -0
  84. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/deployment.py +0 -0
  85. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/deployment_metadata.py +0 -0
  86. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/deployment_runs.py +0 -0
  87. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/eval_handler.py +0 -0
  88. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/exporter.py +0 -0
  89. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/exporter_http.py +0 -0
  90. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/helpers.py +0 -0
  91. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/identity.py +0 -0
  92. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/imaging.py +0 -0
  93. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/ir/__init__.py +0 -0
  94. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/ir/decode.py +0 -0
  95. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/ir/encode.py +0 -0
  96. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/ir/hash.py +0 -0
  97. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/ir/program.py +0 -0
  98. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/ir/reflection.py +0 -0
  99. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/ir/types.py +0 -0
  100. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/ir/xxh64.py +0 -0
  101. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/lm_configuration.py +0 -0
  102. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/mcp_servers.py +0 -0
  103. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/models.py +0 -0
  104. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/named_programs.py +0 -0
  105. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/optimizers/__init__.py +0 -0
  106. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/optimizers/gepa.py +0 -0
  107. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  108. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/optimizers/resume.py +0 -0
  109. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/optimizers/tracker.py +0 -0
  110. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/packaging.py +0 -0
  111. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/programs.py +0 -0
  112. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/references.py +0 -0
  113. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/retry.py +0 -0
  114. {cmpnd-0.64.0 → cmpnd-0.66.0}/cmpnd/traces.py +0 -0
  115. {cmpnd-0.64.0 → cmpnd-0.66.0}/docs/2026-04-02-code-review.md +0 -0
  116. {cmpnd-0.64.0 → cmpnd-0.66.0}/docs/client.md +0 -0
  117. {cmpnd-0.64.0 → cmpnd-0.66.0}/docs/custom-proposer.md +0 -0
  118. {cmpnd-0.64.0 → cmpnd-0.66.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  119. {cmpnd-0.64.0 → cmpnd-0.66.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  120. {cmpnd-0.64.0 → cmpnd-0.66.0}/docs/reading-traces.md +0 -0
  121. {cmpnd-0.64.0 → cmpnd-0.66.0}/docs/sdk-instrumentation.md +0 -0
  122. {cmpnd-0.64.0 → cmpnd-0.66.0}/examples/generalizing_proposer.py +0 -0
  123. {cmpnd-0.64.0 → cmpnd-0.66.0}/examples/local_ollama.py +0 -0
  124. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/LINEAGE.md +0 -0
  125. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/README.md +0 -0
  126. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  127. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/bodies.py +0 -0
  128. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  129. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/exporter_http.py +0 -0
  130. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/program_names.py +0 -0
  131. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/protocols.py +0 -0
  132. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/py.typed +0 -0
  133. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  134. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/retry.py +0 -0
  135. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/cmpnd_ingest/trace_facts.py +0 -0
  136. {cmpnd-0.64.0 → cmpnd-0.66.0}/ingest/pyproject.toml +0 -0
  137. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/audit-sdist.sh +0 -0
  138. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/capture_review_run.py +0 -0
  139. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/check-pip-install.sh +0 -0
  140. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/check_release_artifacts.py +0 -0
  141. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/fixtures/review_run.json.gz +0 -0
  142. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/gepa_resume_bug.py +0 -0
  143. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/install-test-env.sh +0 -0
  144. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/release-stress.sh +0 -0
  145. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/require_version_bump.py +0 -0
  146. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/seed_review_run.py +0 -0
  147. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/selfhost_compat.py +0 -0
  148. {cmpnd-0.64.0 → cmpnd-0.66.0}/scripts/test-in-linux.sh +0 -0
  149. {cmpnd-0.64.0 → cmpnd-0.66.0}/stubs/dspy/__init__.pyi +0 -0
  150. {cmpnd-0.64.0 → cmpnd-0.66.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  151. {cmpnd-0.64.0 → cmpnd-0.66.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  152. {cmpnd-0.64.0 → cmpnd-0.66.0}/stubs/dspy/utils/__init__.pyi +0 -0
  153. {cmpnd-0.64.0 → cmpnd-0.66.0}/stubs/dspy/utils/callback.pyi +0 -0
  154. {cmpnd-0.64.0 → cmpnd-0.66.0}/stubs/dspy/utils/dummies.pyi +0 -0
  155. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/__init__.py +0 -0
  156. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/_credential_route.py +0 -0
  157. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/_deployment_signature_helpers.py +0 -0
  158. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/_exporter_helpers.py +0 -0
  159. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/_fake_clock.py +0 -0
  160. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/_identity_helpers.py +0 -0
  161. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/_resolve_refs.py +0 -0
  162. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/_traced_run.py +0 -0
  163. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/conftest.py +0 -0
  164. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/__init__.py +0 -0
  165. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/committee_task.py +0 -0
  166. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/conftest.py +0 -0
  167. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  168. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  169. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/fake_lm.py +0 -0
  170. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/helpers.py +0 -0
  171. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/target_capabilities.py +0 -0
  172. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_assessments_cli.py +0 -0
  173. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_capture_prompts.py +0 -0
  174. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_composite_module.py +0 -0
  175. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  176. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_deploy_execute.py +0 -0
  177. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_deploy_programs.py +0 -0
  178. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_edge_cases.py +0 -0
  179. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_example_hash.py +0 -0
  180. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_gepa_optimization.py +0 -0
  181. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_interpreter_spans.py +0 -0
  182. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_lm_accuracy.py +0 -0
  183. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_module_tracing.py +0 -0
  184. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_optimization_lineage.py +0 -0
  185. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  186. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  187. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  188. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_parallel_export.py +0 -0
  189. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_project_attribution.py +0 -0
  190. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_react_tool_spans.py +0 -0
  191. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_save_load.py +0 -0
  192. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_trace_structure.py +0 -0
  193. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_traces_read.py +0 -0
  194. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_user_attribution.py +0 -0
  195. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/e2e/test_valsets_cli.py +0 -0
  196. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  197. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  198. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  199. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  200. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  201. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  202. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  203. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  204. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  205. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/10_react.json +0 -0
  206. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/11_refine.json +0 -0
  207. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/12_retrieve.json +0 -0
  208. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/13_knn.json +0 -0
  209. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/14_parallel.json +0 -0
  210. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/identity_vectors/README.md +0 -0
  211. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/ingest_conformance.py +0 -0
  212. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/__init__.py +0 -0
  213. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/authored_optimize/judge.py +0 -0
  214. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  215. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/conftest.py +0 -0
  216. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/optimization_run_probe.py +0 -0
  217. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  218. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/test_logs_renders_traces.py +0 -0
  219. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/test_optimization_run.py +0 -0
  220. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/test_optimizing_progress.py +0 -0
  221. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/integration/test_unhappy_path.py +0 -0
  222. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/__init__.py +0 -0
  223. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/__main__.py +0 -0
  224. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/drain.py +0 -0
  225. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/ingest.py +0 -0
  226. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/latency_transport.py +0 -0
  227. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/rlm_shape.py +0 -0
  228. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/rlm_size_report.py +0 -0
  229. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/sweep.py +0 -0
  230. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/test_rlm_shape.py +0 -0
  231. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/load/test_rlm_size_report.py +0 -0
  232. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/__init__.py +0 -0
  233. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/conftest.py +0 -0
  234. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/seed_parity.py +0 -0
  235. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_auth.py +0 -0
  236. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_datasets.py +0 -0
  237. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_deployments.py +0 -0
  238. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_evals.py +0 -0
  239. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_health.py +0 -0
  240. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_modules.py +0 -0
  241. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_optimizations.py +0 -0
  242. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_programs.py +0 -0
  243. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_signatures.py +0 -0
  244. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_trace_streaming.py +0 -0
  245. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/parity/test_traces.py +0 -0
  246. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/span_reference_fixture.py +0 -0
  247. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/__init__.py +0 -0
  248. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/_provider.py +0 -0
  249. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/_readiness.py +0 -0
  250. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/_reliability.py +0 -0
  251. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/_result.py +0 -0
  252. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/mint_bedrock_token.py +0 -0
  253. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_bedrock_converse.py +0 -0
  254. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_deploy_programs.py +0 -0
  255. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_multi_client_stress.py +0 -0
  256. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_optimize_client_committee.py +0 -0
  257. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_provider_roles.py +0 -0
  258. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_readiness.py +0 -0
  259. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_real_provider_twins.py +0 -0
  260. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_reliability.py +0 -0
  261. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_reliability_classify.py +0 -0
  262. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_rlm_wasm.py +0 -0
  263. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/stress/test_trace_search.py +0 -0
  264. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_adapter_reentrancy.py +0 -0
  265. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_assessments_api.py +0 -0
  266. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_authored.py +0 -0
  267. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_authored_bundle_collisions.py +0 -0
  268. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_callback.py +0 -0
  269. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_candidate_lineage.py +0 -0
  270. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/__init__.py +0 -0
  271. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/conftest.py +0 -0
  272. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_admin.py +0 -0
  273. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_api_client.py +0 -0
  274. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_assessments.py +0 -0
  275. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_auth.py +0 -0
  276. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_credentials.py +0 -0
  277. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_datasets.py +0 -0
  278. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_deployment_runs.py +0 -0
  279. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_deployments.py +0 -0
  280. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_evals.py +0 -0
  281. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_login.py +0 -0
  282. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_main.py +0 -0
  283. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_org.py +0 -0
  284. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_pagination.py +0 -0
  285. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_parity.py +0 -0
  286. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_programs.py +0 -0
  287. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_provider_credentials.py +0 -0
  288. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_schema_sample.py +0 -0
  289. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_shell.py +0 -0
  290. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_tool_servers.py +0 -0
  291. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_traces.py +0 -0
  292. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_traces_program_and_module.py +0 -0
  293. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  294. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_tree_help.py +0 -0
  295. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cli/test_valsets.py +0 -0
  296. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_client.py +0 -0
  297. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_client_floor.py +0 -0
  298. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_client_program_names.py +0 -0
  299. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_clustering/__init__.py +0 -0
  300. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_clustering/conftest.py +0 -0
  301. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_clustering/test_edge_cases.py +0 -0
  302. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  303. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_clustering/test_type_conversion.py +0 -0
  304. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_cmp551_grouping_identity.py +0 -0
  305. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_configuration.py +0 -0
  306. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_context.py +0 -0
  307. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_context_propagation.py +0 -0
  308. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_credential_kwargs.py +0 -0
  309. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_credential_values.py +0 -0
  310. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_dataset_polling.py +0 -0
  311. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_dataset_sync.py +0 -0
  312. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_datasets.py +0 -0
  313. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_decision_client_spans.py +0 -0
  314. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_decorators.py +0 -0
  315. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_deploy.py +0 -0
  316. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  317. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_deployment_metadata.py +0 -0
  318. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_deployment_runs.py +0 -0
  319. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_deployments_read.py +0 -0
  320. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_eval.py +0 -0
  321. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_eval_example_attribution.py +0 -0
  322. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_eval_start.py +0 -0
  323. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_eval_trace_grouping.py +0 -0
  324. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_exception_paths.py +0 -0
  325. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_execute.py +0 -0
  326. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_execute_run_identity.py +0 -0
  327. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_exporter.py +0 -0
  328. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_exporter_program_names.py +0 -0
  329. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_exporter_queue.py +0 -0
  330. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_exporter_real.py +0 -0
  331. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_fake_lm.py +0 -0
  332. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_gepa_callback.py +0 -0
  333. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_gepa_integration.py +0 -0
  334. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_gepa_output_normalization.py +0 -0
  335. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_gepa_resume.py +0 -0
  336. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_identity.py +0 -0
  337. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_imaging.py +0 -0
  338. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_import_weight.py +0 -0
  339. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_ingest_conformance.py +0 -0
  340. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_ingest_second_producer.py +0 -0
  341. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_integration.py +0 -0
  342. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_interpreter_callback.py +0 -0
  343. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_ir_encoder_properties.py +0 -0
  344. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_ir_golden_vectors.py +0 -0
  345. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_ir_roundtrip.py +0 -0
  346. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_lm15_native_calls.py +0 -0
  347. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_lm_usage_shapes.py +0 -0
  348. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_mcp_tools.py +0 -0
  349. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_models.py +0 -0
  350. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_multi_client_stress_readiness.py +0 -0
  351. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimization_progress.py +0 -0
  352. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimization_start.py +0 -0
  353. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimize_lift.py +0 -0
  354. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimize_lms.py +0 -0
  355. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimize_reflection_lm.py +0 -0
  356. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_optimize_terminal_failure.py +0 -0
  357. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_packaging.py +0 -0
  358. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_packaging_callable_source.py +0 -0
  359. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_packaging_mcp_tools.py +0 -0
  360. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_packaging_program_source.py +0 -0
  361. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_poll_until.py +0 -0
  362. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_program_id_deprecation.py +0 -0
  363. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_program_identity.py +0 -0
  364. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_program_lineage_persist.py +0 -0
  365. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_programs_create.py +0 -0
  366. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_project_payload.py +0 -0
  367. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_real_provider_twins_readiness.py +0 -0
  368. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_reflector_properties.py +0 -0
  369. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_reflector_smoke.py +0 -0
  370. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_release_stress_script.py +0 -0
  371. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_replaced_names.py +0 -0
  372. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_repro_reactv2_bugs.py +0 -0
  373. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_require_version_bump.py +0 -0
  374. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_retry.py +0 -0
  375. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_reversible_failures.py +0 -0
  376. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_rlm_patch.py +0 -0
  377. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_run_attribution_paths.py +0 -0
  378. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_runtime_tools.py +0 -0
  379. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_schemas.py +0 -0
  380. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_seal_lm_credential.py +0 -0
  381. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_seed_review_run.py +0 -0
  382. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_signature_inputs.py +0 -0
  383. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_span_references.py +0 -0
  384. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_span_sent_once.py +0 -0
  385. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_system_one.py +0 -0
  386. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_trace_context_concurrency.py +0 -0
  387. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_trace_fact_wire_contract.py +0 -0
  388. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_trace_facts.py +0 -0
  389. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_trace_render.py +0 -0
  390. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_traces_read.py +0 -0
  391. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_tracked_gepa.py +0 -0
  392. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_tracker_enrich.py +0 -0
  393. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_truncation.py +0 -0
  394. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_upload_auth_headers.py +0 -0
  395. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_valsets_api.py +0 -0
  396. {cmpnd-0.64.0 → cmpnd-0.66.0}/tests/test_xxh64.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.64.0
3
+ Version: 0.66.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>
@@ -497,14 +497,12 @@ sdk.optimize_deployment(
497
497
  reflection_lm=None, # required: a dspy.LM, a bare model name or a cmpnd.HostedLM
498
498
  lms=None, # optional: name -> dspy.LM, cmpnd.HostedLM or cmpnd.SystemOne
499
499
  credential=None, # optional: the cmpnd.Credential every other binding spends
500
- max_metric_calls=None, # budget: the metric-call ceiling for one step
500
+ max_metric_calls=None, # budget: the metric-call ceiling for the run
501
501
  auto=None, # budget: "light", "medium" or "heavy"
502
502
  may_author_code=None, # optional: False keeps each dspy.Flex at its deployed source
503
- max_steps=10, # optional: the most optimize steps
504
- patience=2, # optional: stop after this many steps without improvement
505
- seed=0, # optional: the starting seed, advanced each step
503
+ seed=0, # optional: the search's random seed
506
504
  poll_interval=3.0, # optional: seconds between status checks
507
- timeout=600.0, # optional: seconds to wait for each step
505
+ timeout=600.0, # optional: seconds to wait for the run
508
506
  )
509
507
  ```
510
508
 
@@ -516,6 +514,30 @@ budget is missing.
516
514
  initiation with a 4xx, such as a 409 for missing data or a metric mismatch. It
517
515
  raises `cmpnd.OptimizeFailed` when a step reaches `failed`.
518
516
 
517
+ The result's `optimization_run_id` names the run that produced it.
518
+ `continue_optimization()` starts a new run from one of that run's candidates,
519
+ reusing its data, with a budget of its own:
520
+
521
+ ```python
522
+ better = sdk.continue_optimization(best, max_metric_calls=120) # from the best candidate
523
+
524
+ for candidate in sdk.list_candidates(best.optimization_run_id): # read each one first
525
+ print(candidate["candidate_id"], candidate["aggregate_score"], candidate["instructions"])
526
+ other = sdk.continue_optimization(best, candidate="ca_...", auto="light")
527
+ ```
528
+
529
+ `model=` runs the candidate on another student model, and `credential=` names
530
+ the credential that model spends. A loop over models is a model sweep:
531
+
532
+ ```python
533
+ for model in ["openai/gpt-4o-mini", "openai/gpt-4.1-nano"]:
534
+ swept = sdk.continue_optimization(best, model=model, auto="light")
535
+ print(model, swept.score)
536
+ ```
537
+
538
+ A refusal raises `cmpnd.OptimizeError`, whose `error_code` says why, such as
539
+ `not_completed`, `unknown_candidate` or `no_credential`.
540
+
519
541
  ## Command line
520
542
 
521
543
  The package installs the `cmpnd` command. `cmpnd login` saves your API key for
@@ -639,6 +661,12 @@ global config. Use it as a context manager or as a plain object.
639
661
  - `optimize_deployment(deployment, ...)` optimizes a deployment on the platform
640
662
  and returns the best `DeployedProgram`, whose `.score` is the best score
641
663
  found. It raises `cmpnd.OptimizeError` or `cmpnd.OptimizeFailed` on failure.
664
+ - `list_candidates(run_id)` returns a run's candidates as dicts, each with its
665
+ `ca_` id, score, parent and instructions.
666
+ - `continue_optimization(run, candidate=None, model=None, credential=None, ...)`
667
+ runs a new optimization from one candidate of a completed run, with that
668
+ run's data and a new budget, optionally on another student model, and returns
669
+ its best `DeployedProgram`.
642
670
  - `list_traces(**filters)` and `get_trace(trace_id)` read traces with the
643
671
  client's credentials. They flush the client's traces first.
644
672
  - `close()` flushes the client's traces and closes the client. A closed
@@ -446,14 +446,12 @@ sdk.optimize_deployment(
446
446
  reflection_lm=None, # required: a dspy.LM, a bare model name or a cmpnd.HostedLM
447
447
  lms=None, # optional: name -> dspy.LM, cmpnd.HostedLM or cmpnd.SystemOne
448
448
  credential=None, # optional: the cmpnd.Credential every other binding spends
449
- max_metric_calls=None, # budget: the metric-call ceiling for one step
449
+ max_metric_calls=None, # budget: the metric-call ceiling for the run
450
450
  auto=None, # budget: "light", "medium" or "heavy"
451
451
  may_author_code=None, # optional: False keeps each dspy.Flex at its deployed source
452
- max_steps=10, # optional: the most optimize steps
453
- patience=2, # optional: stop after this many steps without improvement
454
- seed=0, # optional: the starting seed, advanced each step
452
+ seed=0, # optional: the search's random seed
455
453
  poll_interval=3.0, # optional: seconds between status checks
456
- timeout=600.0, # optional: seconds to wait for each step
454
+ timeout=600.0, # optional: seconds to wait for the run
457
455
  )
458
456
  ```
459
457
 
@@ -465,6 +463,30 @@ budget is missing.
465
463
  initiation with a 4xx, such as a 409 for missing data or a metric mismatch. It
466
464
  raises `cmpnd.OptimizeFailed` when a step reaches `failed`.
467
465
 
466
+ The result's `optimization_run_id` names the run that produced it.
467
+ `continue_optimization()` starts a new run from one of that run's candidates,
468
+ reusing its data, with a budget of its own:
469
+
470
+ ```python
471
+ better = sdk.continue_optimization(best, max_metric_calls=120) # from the best candidate
472
+
473
+ for candidate in sdk.list_candidates(best.optimization_run_id): # read each one first
474
+ print(candidate["candidate_id"], candidate["aggregate_score"], candidate["instructions"])
475
+ other = sdk.continue_optimization(best, candidate="ca_...", auto="light")
476
+ ```
477
+
478
+ `model=` runs the candidate on another student model, and `credential=` names
479
+ the credential that model spends. A loop over models is a model sweep:
480
+
481
+ ```python
482
+ for model in ["openai/gpt-4o-mini", "openai/gpt-4.1-nano"]:
483
+ swept = sdk.continue_optimization(best, model=model, auto="light")
484
+ print(model, swept.score)
485
+ ```
486
+
487
+ A refusal raises `cmpnd.OptimizeError`, whose `error_code` says why, such as
488
+ `not_completed`, `unknown_candidate` or `no_credential`.
489
+
468
490
  ## Command line
469
491
 
470
492
  The package installs the `cmpnd` command. `cmpnd login` saves your API key for
@@ -588,6 +610,12 @@ global config. Use it as a context manager or as a plain object.
588
610
  - `optimize_deployment(deployment, ...)` optimizes a deployment on the platform
589
611
  and returns the best `DeployedProgram`, whose `.score` is the best score
590
612
  found. It raises `cmpnd.OptimizeError` or `cmpnd.OptimizeFailed` on failure.
613
+ - `list_candidates(run_id)` returns a run's candidates as dicts, each with its
614
+ `ca_` id, score, parent and instructions.
615
+ - `continue_optimization(run, candidate=None, model=None, credential=None, ...)`
616
+ runs a new optimization from one candidate of a completed run, with that
617
+ run's data and a new budget, optionally on another student model, and returns
618
+ its best `DeployedProgram`.
591
619
  - `list_traces(**filters)` and `get_trace(trace_id)` read traces with the
592
620
  client's credentials. They flush the client's traces first.
593
621
  - `close()` flushes the client's traces and closes the client. A closed
@@ -2,10 +2,12 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import sys
5
6
  from argparse import ArgumentParser, _SubParsersAction
6
7
  from typing import Any
7
8
 
8
9
  from .. import api_client, deprecation, pagination
10
+ from . import top
9
11
 
10
12
 
11
13
  def _add_list_args(p: ArgumentParser) -> None:
@@ -88,6 +90,55 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
88
90
  cancel.add_argument("id", help="Optimization run ID (op_...)")
89
91
  cancel.set_defaults(func=handle_cancel)
90
92
 
93
+ cont = sub.add_parser(
94
+ "continue",
95
+ help="Run a new optimization from one candidate of a completed run. It reuses that run's data, models "
96
+ "and credentials, and takes a new budget. --model runs the candidate on another student model",
97
+ )
98
+ cont.add_argument("id", help="Completed optimization run ID (op_...)")
99
+ cont.add_argument(
100
+ "--candidate",
101
+ metavar="CANDIDATE_ID",
102
+ help="The ca_ id of the candidate to start from, as `cmpnd optimizations candidates ID` lists them "
103
+ "(default: the run's best)",
104
+ )
105
+ cont.add_argument(
106
+ "--model",
107
+ default=None,
108
+ help="The student model the new run spends, as a deploy records it, for example openai/gpt-4o-mini "
109
+ "(default: the completed run's). Only the program's own model changes",
110
+ )
111
+ cont.add_argument(
112
+ "--credential",
113
+ metavar="NAME",
114
+ default=None,
115
+ help="The provider credential the student model spends (default: the completed run's). "
116
+ "`cmpnd credentials list` shows the names",
117
+ )
118
+ # The budget flags are `cmpnd optimize`'s, and the SDK refuses a run with none or two,
119
+ # so both commands answer a missing budget the same way.
120
+ cont.add_argument(
121
+ "--max-metric-calls",
122
+ dest="max_metric_calls",
123
+ type=int,
124
+ default=None,
125
+ help="Budget for the search, as metric calls (one per rollout). Required unless --auto is given",
126
+ )
127
+ cont.add_argument(
128
+ "--auto",
129
+ choices=["light", "medium", "heavy"],
130
+ default=None,
131
+ help="Budget as a t-shirt size, sized from the program and valset (alternative to --max-metric-calls)",
132
+ )
133
+ cont.add_argument(
134
+ "--async",
135
+ dest="asynchronous",
136
+ action="store_true",
137
+ help="Return the queued run at once instead of waiting for it to finish",
138
+ )
139
+ cont.add_argument("--json", action="store_true", help="Emit the result summary as JSON")
140
+ cont.set_defaults(func=handle_continue)
141
+
91
142
  delete = sub.add_parser("delete", help="Delete an optimization run and everything recorded under it")
92
143
  delete.add_argument("id", help="Optimization run ID (op_...)")
93
144
  delete.set_defaults(func=handle_delete)
@@ -124,8 +175,11 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
124
175
  iters.add_argument("id", help="Optimization run ID (UUID)")
125
176
  iters.set_defaults(func=handle_iterations)
126
177
 
127
- cands = sub.add_parser("candidates", help="List candidates for an optimization run")
128
- cands.add_argument("id", help="Optimization run ID (UUID)")
178
+ cands = sub.add_parser(
179
+ "candidates",
180
+ help="List a run's candidates: each one's ca_ id, score and parent, to pick one to continue from",
181
+ )
182
+ cands.add_argument("id", help="Optimization run ID (op_...)")
129
183
  cands.add_argument(
130
184
  "--full",
131
185
  action="store_true",
@@ -213,6 +267,92 @@ def handle_cancel(client: api_client.ApiClient, args: Any) -> Any:
213
267
  return client.post(f"/api/v1/optimizations/{args.id}/cancel")
214
268
 
215
269
 
270
+ def handle_continue(client: api_client.ApiClient, args: Any) -> Any:
271
+ """Run a new optimization from one candidate of a completed run, as `cmpnd optimize` runs one.
272
+
273
+ The new run reuses the completed run's data and configuration and takes a new budget.
274
+ `--model` and `--credential` run the candidate on another student model. The command
275
+ names the candidate it starts from, waits for the run, and prints the best program as
276
+ `cmpnd optimize` does. `--async` returns the queued run instead.
277
+ """
278
+ from ... import optimization
279
+
280
+ credential_name = args.credential
281
+ if credential_name is not None:
282
+ credential_name = top._CredentialLookup(client)(f"--credential {credential_name}", credential_name).name
283
+ cfg = top.sdk_config(client)
284
+ try:
285
+ started = optimization.start_continuation(
286
+ args.id,
287
+ cfg=cfg,
288
+ candidate=args.candidate,
289
+ model=args.model,
290
+ credential_name=credential_name,
291
+ max_metric_calls=args.max_metric_calls,
292
+ auto=args.auto,
293
+ )
294
+ except (optimization.OptimizeError, ValueError) as exc:
295
+ raise api_client.CliError(str(exc)) from exc
296
+ if args.asynchronous:
297
+ return started
298
+
299
+ texts = _instruction_texts(started.get("candidate_instructions"))
300
+ score = started.get("candidate_score")
301
+ print(
302
+ top._c(
303
+ f"continuing {args.id} from {started.get('continued_from_candidate_id')} "
304
+ f"(score {score:.3f}): {_first_line(texts)}"
305
+ if isinstance(score, (int, float))
306
+ else f"continuing {args.id} from {started.get('continued_from_candidate_id')}",
307
+ "dim",
308
+ ),
309
+ file=sys.stderr,
310
+ )
311
+ if args.model is not None or args.credential is not None:
312
+ print(
313
+ top._c(f"on {started.get('model')} with the credential {started.get('credential_name')}", "dim"),
314
+ file=sys.stderr,
315
+ )
316
+ print(top._c(f"optimizing with {started.get('optimizer')}…", "dim"), file=sys.stderr)
317
+ try:
318
+ result = optimization.wait_for_continuation(started, cfg=cfg)
319
+ except (optimization.OptimizeError, optimization.OptimizeFailed) as exc:
320
+ raise api_client.CliError(str(exc)) from exc
321
+ top.print_optimize_result(
322
+ result,
323
+ as_json=args.json,
324
+ extra={
325
+ "optimization_id": started.get("optimization_id"),
326
+ "continued_from_run_id": started.get("continued_from_run_id"),
327
+ "continued_from_candidate_id": started.get("continued_from_candidate_id"),
328
+ **{key: started[key] for key in ("model", "credential_name") if key in started},
329
+ },
330
+ )
331
+ return None
332
+
333
+
334
+ def _instruction_texts(instructions: Any) -> list[tuple[str, str]]:
335
+ """A candidate's instructions as (component, text) pairs, in the order serve sent them."""
336
+ if not isinstance(instructions, dict):
337
+ return []
338
+ return [(str(name), text if isinstance(text, str) else str(text)) for name, text in instructions.items()]
339
+
340
+
341
+ def _first_line(texts: list[tuple[str, str]], width: int = 72) -> str:
342
+ """The first line of the first component's text, naming the component when there are several."""
343
+ if not texts:
344
+ return "-"
345
+ name, text = texts[0]
346
+ line = next((ln.strip() for ln in text.splitlines() if ln.strip()), "")
347
+ if len(texts) > 1:
348
+ line = f"{name}: {line}"
349
+ if len(line) > width:
350
+ line = line[: width - 1] + "…"
351
+ if len(texts) > 1:
352
+ line += f" (+{len(texts) - 1} more)"
353
+ return line
354
+
355
+
216
356
  def handle_delete(client: api_client.ApiClient, args: Any) -> Any:
217
357
  # 204 No Content → delete() returns None; synthesize a confirmation.
218
358
  client.delete(f"/api/v1/optimizations/{args.id}")
@@ -1028,7 +1028,7 @@ def _optimize_credentials(
1028
1028
  ``cmpnd.HostedLM`` when ``--reflection-credential`` names its credential) and the
1029
1029
  credential per binding. :class:`_CredentialLookup` turns each name into a
1030
1030
  ``cmpnd.Credential``, so a mistyped one fails before the import and the deploy a run
1031
- makes ahead of its first step.
1031
+ makes ahead of the optimization.
1032
1032
  """
1033
1033
  from ...lm_configuration import HostedLM
1034
1034
 
@@ -1133,29 +1133,30 @@ def cmd_optimize(client: api_client.ApiClient, args: Any) -> Any:
1133
1133
  lm_credentials=lm_credentials,
1134
1134
  max_metric_calls=getattr(args, "max_metric_calls", None),
1135
1135
  auto=getattr(args, "auto", None),
1136
- max_steps=getattr(args, "max_steps", 10),
1137
- patience=getattr(args, "patience", 2),
1138
1136
  seed=getattr(args, "seed", 0),
1139
1137
  )
1140
1138
  except (optimization.OptimizeError, optimization.OptimizeFailed, ValueError) as exc:
1141
1139
  raise api_client.CliError(str(exc))
1142
1140
 
1143
- if getattr(args, "json", False):
1144
- print(
1145
- json.dumps(
1146
- {
1147
- "best_deployment_id": result.deployment_id,
1148
- "best_score": result.score,
1149
- },
1150
- indent=2,
1151
- default=str,
1152
- )
1153
- )
1141
+ print_optimize_result(result, as_json=getattr(args, "json", False))
1142
+ return result
1143
+
1144
+
1145
+ def print_optimize_result(result: Any, *, as_json: bool, extra: dict[str, Any] | None = None) -> None:
1146
+ """Print a finished optimization's best program, as every command that runs one does.
1147
+
1148
+ The best deployment's id goes to stdout and its score to stderr, so a script can
1149
+ capture the id alone. ``--json`` prints both, and ``extra`` adds fields a command
1150
+ knows about the run.
1151
+ """
1152
+ if as_json:
1153
+ summary = {"best_deployment_id": result.deployment_id, "best_score": result.score}
1154
+ summary.update(extra or {})
1155
+ print(json.dumps(summary, indent=2, default=str))
1154
1156
  else:
1155
1157
  print(result.deployment_id)
1156
1158
  score = "n/a" if result.score is None else f"{result.score}"
1157
1159
  print(_c(f"best score: {score}", "dim"), file=sys.stderr)
1158
- return result
1159
1160
 
1160
1161
 
1161
1162
  def cmd_run(client: api_client.ApiClient, args: Any) -> None:
@@ -1563,7 +1564,7 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
1563
1564
  dest="max_metric_calls",
1564
1565
  type=int,
1565
1566
  default=None,
1566
- help="Budget for each step's search, as metric calls (one per rollout). Required unless --auto is given",
1567
+ help="Budget for the search, as metric calls (one per rollout). Required unless --auto is given",
1567
1568
  )
1568
1569
  opt.add_argument(
1569
1570
  "--auto",
@@ -1571,15 +1572,6 @@ def register(subparsers: _SubParsersAction[ArgumentParser]) -> None:
1571
1572
  default=None,
1572
1573
  help="Budget as a t-shirt size, sized from the program and valset (alternative to --max-metric-calls)",
1573
1574
  )
1574
- opt.add_argument(
1575
- "--max-steps",
1576
- dest="max_steps",
1577
- type=int,
1578
- default=10,
1579
- help="How many times to re-optimize the winner, each step a fresh search "
1580
- "with an advanced seed (default 10). Bounds the LOOP, not one search",
1581
- )
1582
- opt.add_argument("--patience", type=int, default=2, help="Stop after this many consecutive non-improving steps")
1583
- opt.add_argument("--seed", type=int, default=0, help="Starting seed; advanced each step")
1575
+ opt.add_argument("--seed", type=int, default=0, help="The search's random seed (default 0)")
1584
1576
  opt.add_argument("--json", action="store_true", help="Emit the result summary as JSON")
1585
1577
  opt.set_defaults(func=_handler("optimize"))
@@ -537,8 +537,6 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
537
537
  reflection_model: str | None = None,
538
538
  source_root: str | None = None,
539
539
  include: list[str] | None = None,
540
- max_steps: int = 10,
541
- patience: int = 2,
542
540
  seed: int = 0,
543
541
  # Deprecated program id input: refused, and goes when the deprecated inputs are stripped out.
544
542
  program_id: str | None = None,
@@ -568,8 +566,6 @@ def _make_namespace(client: api_client.ApiClient, endpoint: str) -> dict[str, An
568
566
  reflection_model=reflection_model,
569
567
  source_root=source_root,
570
568
  include=list(include or []),
571
- max_steps=max_steps,
572
- patience=patience,
573
569
  seed=seed,
574
570
  ),
575
571
  )
@@ -154,6 +154,7 @@ DOMAIN_VERBS: dict[str, str] = {
154
154
  "wait": "polls one accepted run until it settles or the local budget expires",
155
155
  "cancel": "settles an open run as cancelled and keeps its record; delete would read as removing the run",
156
156
  "follow": "tails a record until it settles — a list over time rather than a snapshot",
157
+ "continue": "starts a run from one candidate of a completed run; create would hide that it inherits its data",
157
158
  "select": "marks one candidate as the program's chosen version",
158
159
  "refresh": "re-reads a record from the system of record it mirrors; nothing the caller supplies is written",
159
160
  "allow": "grants permission to call a tool — a person's decision, never inferred from what code names",
@@ -507,17 +507,14 @@ class Client:
507
507
  max_metric_calls: int | None = None,
508
508
  auto: str | None = None,
509
509
  may_author_code: bool | None = None,
510
- max_steps: int = 10,
511
- patience: int = 2,
512
510
  seed: int = 0,
513
511
  poll_interval: float = 3.0,
514
512
  timeout: float = 600.0,
515
513
  ) -> Any:
516
514
  """Optimize a deployment on the platform, on this client's account, and return the best deployment.
517
515
 
518
- Runs a client-driven loop over the per-step optimize API: upload the
519
- optimization bundle once, then re-optimize the deployment with an advancing
520
- seed until improvement plateaus. Deploy the program first with
516
+ Runs one optimization: upload the optimization bundle, wait for the run to
517
+ settle, and return its result deployment. Deploy the program first with
521
518
  ``deploy_program``. The optimization belongs to the deployment's program, and
522
519
  the program's model comes from the deployment.
523
520
 
@@ -676,28 +673,23 @@ class Client:
676
673
  optimizes every ordinary predictor and leaves each ``dspy.Flex`` at
677
674
  the source you deployed. A program with no ``dspy.Flex`` in it is
678
675
  unaffected either way.
679
- max_steps: How many times to re-optimize the winner, each step a fresh
680
- ``optimize`` call with an advanced seed (default 10). Distinct from
681
- the budget above, which bounds ONE step's search: total spend is
682
- roughly the budget times the steps actually taken.
683
- patience: Stop after this many consecutive non-improving steps (default 2).
684
- seed: Starting seed; advanced by 1 each step (default 0).
676
+ seed: The search's random seed (default 0).
685
677
  poll_interval: Seconds between status polls (default 3.0).
686
- timeout: Per-step polling timeout in seconds (default 600). Also the
687
- outer bound on riding through transient answers: a step's backoff
688
- waits are clipped by it rather than extending the step past it.
678
+ timeout: Polling timeout in seconds (default 600). Also the outer bound
679
+ on riding through transient answers: the run's backoff waits are
680
+ clipped by it rather than extending the wait past it.
689
681
 
690
682
  Returns:
691
- The best ``cmpnd.DeployedProgram``. Run it with ``run_deployment``. Its
692
- ``.score`` is the best score found (``None`` if the server returned no
683
+ The run's result ``cmpnd.DeployedProgram``. Run it with ``run_deployment``.
684
+ Its ``.score`` is the run's best score (``None`` if the server returned no
693
685
  scores).
694
686
 
695
687
  Raises:
696
688
  ValueError: A retired optimizer, an unknown one, a metric that is not a
697
689
  5-arg feedback metric (or whose signature cannot be introspected), or a
698
690
  proposer the sandbox could not call or whose source cannot be recovered.
699
- OptimizeError: A 4xx initiating a step (e.g. 409 no-data / mismatch).
700
- OptimizeFailed: A step transitioned to ``status="failed"``.
691
+ OptimizeError: A 4xx initiating the run (e.g. 409 mismatch).
692
+ OptimizeFailed: The run transitioned to ``status="failed"``.
701
693
  TypeError: A string for ``credential``. Look a credential up with
702
694
  ``cmpnd.credentials.get``.
703
695
  """
@@ -719,13 +711,94 @@ class Client:
719
711
  max_metric_calls=max_metric_calls,
720
712
  auto=auto,
721
713
  may_author_code=may_author_code,
722
- max_steps=max_steps,
723
- patience=patience,
724
714
  seed=seed,
725
715
  poll_interval=poll_interval,
726
716
  timeout=timeout,
727
717
  )
728
718
 
719
+ def continue_optimization(
720
+ self,
721
+ run: Any,
722
+ *,
723
+ candidate: str | None = None,
724
+ model: str | None = None,
725
+ credential: credentials.Credential | None = None,
726
+ max_metric_calls: int | None = None,
727
+ auto: str | None = None,
728
+ poll_interval: float = 3.0,
729
+ timeout: float = 600.0,
730
+ ) -> Any:
731
+ """Run a new optimization from one candidate of a completed one, and return its best program.
732
+
733
+ The new run is a fresh search that starts from the chosen candidate, with its own
734
+ budget. It reuses the completed run's training and validation data and its
735
+ configuration, so nothing is uploaded::
736
+
737
+ best = client.optimize_deployment(deployed, ..., max_metric_calls=120)
738
+ better = client.continue_optimization(best, max_metric_calls=120)
739
+
740
+ With ``model``, the new run is a model sweep: the candidate runs and optimizes on
741
+ that student model instead of the completed run's. A loop over models compares
742
+ them on one task::
743
+
744
+ for model in ["openai/gpt-4o-mini", "openai/gpt-4.1-nano"]:
745
+ swept = client.continue_optimization(best, model=model, auto="light")
746
+ print(model, swept.score)
747
+
748
+ Args:
749
+ run: The completed run's ``op_`` id, or the ``DeployedProgram`` an
750
+ optimization returned.
751
+ candidate: The ``ca_`` id of the candidate to start from, as
752
+ ``list_candidates`` and ``cmpnd optimizations candidates`` list it. The
753
+ default is the run's best.
754
+ model: The student model the new run spends, as a deploy records it, for
755
+ example ``"openai/gpt-4o-mini"``. The default is the completed run's.
756
+ Only the program's own model changes: a predictor with its own LM, the
757
+ run's ``lms`` and the reflection model stay as the completed run had
758
+ them.
759
+ credential: The credential the student model spends, from
760
+ ``cmpnd.credentials.get(NAME)``. The default is the completed run's.
761
+ max_metric_calls: The budget as a ceiling on metric calls. Pass this or
762
+ ``auto``, exactly one.
763
+ auto: The budget as ``"light"``, ``"medium"`` or ``"heavy"``.
764
+ poll_interval: Seconds between status polls (default 3.0).
765
+ timeout: Polling timeout in seconds (default 600).
766
+
767
+ Returns:
768
+ The new run's best program, a ``cmpnd.DeployedProgram``, as
769
+ ``optimize_deployment`` returns.
770
+
771
+ Raises:
772
+ ValueError: No budget or two budgets, or a ``DeployedProgram`` that no
773
+ optimization produced.
774
+ TypeError: ``credential`` is a string. Look a credential up with
775
+ ``cmpnd.credentials.get``.
776
+ OptimizeError: The platform refused the continuation. Its ``error_code``
777
+ says why, for example ``not_completed``, ``unknown_candidate`` or
778
+ ``no_credential``.
779
+ OptimizeFailed: The new run transitioned to ``status="failed"``.
780
+ """
781
+ self._check_active()
782
+ credential_name = credentials.name_of(credential, "cmpnd.Client.continue_optimization(credential=)")
783
+ from . import execution, optimization
784
+
785
+ run_id = run.optimization_run_id if isinstance(run, execution.DeployedProgram) else run
786
+ if not run_id:
787
+ raise ValueError(
788
+ f"deployment {run} is not the result of an optimization this client ran; pass the run's op_ id"
789
+ )
790
+ return optimization.continue_optimization(
791
+ run_id,
792
+ cfg=self._config,
793
+ candidate=candidate,
794
+ model=model,
795
+ credential_name=credential_name,
796
+ max_metric_calls=max_metric_calls,
797
+ auto=auto,
798
+ poll_interval=poll_interval,
799
+ timeout=timeout,
800
+ )
801
+
729
802
  # -- data surface (flat verb-object over the client's credentials) -----
730
803
  #
731
804
  # Flat methods (create_valset_entry, list_assessments, get_trace, …), so the
@@ -747,6 +820,22 @@ class Client:
747
820
  _exporter_pool.flush(self._config)
748
821
  return programs.program_id_for(_http.ApiClient(self._config.api_key, self._config.endpoint), program)
749
822
 
823
+ def list_candidates(self, run_id: str) -> list[dict[str, Any]]:
824
+ """The candidates of an optimization run, to choose one to continue from.
825
+
826
+ Each carries its ``candidate_id`` (``ca_…``), ``aggregate_score``,
827
+ ``parent_candidate_id`` and ``instructions``, so a caller picks by what a
828
+ candidate is::
829
+
830
+ candidates = client.list_candidates("op_…")
831
+ chosen = max(candidates, key=lambda c: c["aggregate_score"])
832
+ client.continue_optimization("op_…", candidate=chosen["candidate_id"], max_metric_calls=120)
833
+ """
834
+ self._check_active()
835
+ from . import optimization
836
+
837
+ return optimization.list_candidates(run_id, cfg=self._config)
838
+
750
839
  def get_deployment_signature(self, deployment_id: str) -> DeploymentSignatureResponse:
751
840
  """Fetch one deployment's persisted artifact metadata."""
752
841
  self._check_active()
@@ -114,8 +114,12 @@ class DeployedProgram:
114
114
  endpoint: str | None = None,
115
115
  score: float | None = None,
116
116
  deployment_signature: DeploymentSignatureResponse | None = None,
117
+ optimization_run_id: str | None = None,
117
118
  ) -> None:
118
119
  self.deployment_id = deployment_id
120
+ #: The optimization run that produced this program, when it is a run's result.
121
+ #: ``cmpnd.Client.continue_optimization`` takes such a program in place of a run id.
122
+ self.optimization_run_id = optimization_run_id
119
123
  self.deployment_signature = deployment_signature
120
124
  self.artifact_metadata: DeploymentArtifactMetadata | None = None
121
125
  self.metadata_unavailable_reason: str | None = None