cmpnd 0.83.0__tar.gz → 0.85.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 (414) hide show
  1. {cmpnd-0.83.0 → cmpnd-0.85.0}/AGENTS.md +7 -1
  2. {cmpnd-0.83.0 → cmpnd-0.85.0}/PKG-INFO +2 -2
  3. {cmpnd-0.83.0 → cmpnd-0.85.0}/README.md +1 -1
  4. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/callback.py +228 -110
  5. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/configuration.py +3 -3
  6. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/context.py +41 -2
  7. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/helpers.py +6 -4
  8. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/ir/reflection.py +4 -1
  9. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/models.py +166 -6
  10. cmpnd-0.85.0/cmpnd/references.py +180 -0
  11. {cmpnd-0.83.0 → cmpnd-0.85.0}/docs/client.md +2 -2
  12. {cmpnd-0.83.0 → cmpnd-0.85.0}/docs/sdk-instrumentation.md +33 -8
  13. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/README.md +20 -3
  14. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/bodies.py +14 -7
  15. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/exporter_http.py +84 -47
  16. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/program_names.py +4 -29
  17. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/protocols.py +19 -1
  18. cmpnd-0.85.0/ingest/cmpnd_ingest/span_context.py +63 -0
  19. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/trace_facts.py +4 -0
  20. {cmpnd-0.83.0 → cmpnd-0.85.0}/pyproject.toml +1 -1
  21. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/test-in-linux.sh +18 -4
  22. cmpnd-0.85.0/tests/lm15_prompt_fixture.py +350 -0
  23. cmpnd-0.85.0/tests/lm15_prompts/four_turn_predict.json +851 -0
  24. cmpnd-0.85.0/tests/lm15_prompts/rlm_six_turns.json +1726 -0
  25. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/rlm_shape.py +4 -0
  26. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/test_rlm_shape.py +3 -1
  27. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/span_reference_fixture.py +9 -1
  28. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_callback.py +6 -2
  29. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_context.py +20 -0
  30. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_credential_kwargs.py +28 -1
  31. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_credential_selection.py +1 -1
  32. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_exporter_program_names.py +7 -4
  33. cmpnd-0.85.0/tests/test_lm15_prompt_fixtures.py +94 -0
  34. cmpnd-0.85.0/tests/test_lm15_prompt_references.py +309 -0
  35. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_program_identity.py +2 -2
  36. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_program_naming.py +39 -0
  37. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_reflector_smoke.py +2 -0
  38. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_span_references.py +18 -0
  39. cmpnd-0.85.0/tests/test_span_submission.py +492 -0
  40. cmpnd-0.85.0/tests/test_span_submission_wire_contract.py +190 -0
  41. {cmpnd-0.83.0 → cmpnd-0.85.0}/uv.lock +1 -1
  42. cmpnd-0.83.0/cmpnd/references.py +0 -80
  43. {cmpnd-0.83.0 → cmpnd-0.85.0}/.gitignore +0 -0
  44. {cmpnd-0.83.0 → cmpnd-0.85.0}/CLAUDE.md +0 -0
  45. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/__init__.py +0 -0
  46. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_assessments.py +0 -0
  47. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_authored_import.py +0 -0
  48. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_deployments.py +0 -0
  49. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_exporter_pool.py +0 -0
  50. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_gepa_patch.py +0 -0
  51. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_http.py +0 -0
  52. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_program_patch.py +0 -0
  53. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_rlm_patch.py +0 -0
  54. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_runtime_tools.py +0 -0
  55. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_timespec.py +0 -0
  56. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_traces.py +0 -0
  57. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/_valsets.py +0 -0
  58. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/authored.py +0 -0
  59. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/__init__.py +0 -0
  60. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/api_client.py +0 -0
  61. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/__init__.py +0 -0
  62. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/admin.py +0 -0
  63. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/assessments.py +0 -0
  64. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/auth.py +0 -0
  65. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/datasets.py +0 -0
  66. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/deployments.py +0 -0
  67. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/evals.py +0 -0
  68. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/login.py +0 -0
  69. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/optimizations.py +0 -0
  70. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/org.py +0 -0
  71. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/programs.py +0 -0
  72. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/provider_credentials.py +0 -0
  73. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/tool_servers.py +0 -0
  74. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/top.py +0 -0
  75. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/traces.py +0 -0
  76. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/commands/valsets.py +0 -0
  77. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/credentials.py +0 -0
  78. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/deprecation.py +0 -0
  79. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/pagination.py +0 -0
  80. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/shell.py +0 -0
  81. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/trace_render.py +0 -0
  82. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/tree_help.py +0 -0
  83. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/verbs.py +0 -0
  84. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/cli/views.py +0 -0
  85. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/client.py +0 -0
  86. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/credentials.py +0 -0
  87. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/dataset_sync.py +0 -0
  88. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/datasets.py +0 -0
  89. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/decorators.py +0 -0
  90. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/deployment.py +0 -0
  91. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/deployment_metadata.py +0 -0
  92. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/deployment_runs.py +0 -0
  93. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/eval_handler.py +0 -0
  94. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/execution.py +0 -0
  95. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/exporter.py +0 -0
  96. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/exporter_http.py +0 -0
  97. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/identity.py +0 -0
  98. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/imaging.py +0 -0
  99. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/ir/__init__.py +0 -0
  100. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/ir/decode.py +0 -0
  101. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/ir/encode.py +0 -0
  102. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/ir/hash.py +0 -0
  103. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/ir/program.py +0 -0
  104. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/ir/types.py +0 -0
  105. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/ir/xxh64.py +0 -0
  106. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/lm_configuration.py +0 -0
  107. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/mcp_servers.py +0 -0
  108. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/named_programs.py +0 -0
  109. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/optimization.py +0 -0
  110. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/optimizers/__init__.py +0 -0
  111. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/optimizers/gepa.py +0 -0
  112. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/optimizers/gepa_callback.py +0 -0
  113. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/optimizers/resume.py +0 -0
  114. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/optimizers/tracker.py +0 -0
  115. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/packaging.py +0 -0
  116. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/program_naming.py +0 -0
  117. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/programs.py +0 -0
  118. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/retry.py +0 -0
  119. {cmpnd-0.83.0 → cmpnd-0.85.0}/cmpnd/traces.py +0 -0
  120. {cmpnd-0.83.0 → cmpnd-0.85.0}/docs/2026-04-02-code-review.md +0 -0
  121. {cmpnd-0.83.0 → cmpnd-0.85.0}/docs/cli.md +0 -0
  122. {cmpnd-0.83.0 → cmpnd-0.85.0}/docs/custom-proposer.md +0 -0
  123. {cmpnd-0.83.0 → cmpnd-0.85.0}/docs/plans/2026-04-05-train-val-dataset-capture-design.md +0 -0
  124. {cmpnd-0.83.0 → cmpnd-0.85.0}/docs/plans/2026-04-05-train-val-dataset-capture.md +0 -0
  125. {cmpnd-0.83.0 → cmpnd-0.85.0}/docs/reading-traces.md +0 -0
  126. {cmpnd-0.83.0 → cmpnd-0.85.0}/examples/generalizing_proposer.py +0 -0
  127. {cmpnd-0.83.0 → cmpnd-0.85.0}/examples/local_ollama.py +0 -0
  128. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/LINEAGE.md +0 -0
  129. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/__init__.py +0 -0
  130. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/client_floor.py +0 -0
  131. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/py.typed +0 -0
  132. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/request_log.py +0 -0
  133. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/cmpnd_ingest/retry.py +0 -0
  134. {cmpnd-0.83.0 → cmpnd-0.85.0}/ingest/pyproject.toml +0 -0
  135. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/audit-sdist.sh +0 -0
  136. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/capture_review_run.py +0 -0
  137. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/check-pip-install.sh +0 -0
  138. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/check_release_artifacts.py +0 -0
  139. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/fixtures/review_run.json.gz +0 -0
  140. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/gepa_resume_bug.py +0 -0
  141. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/install-test-env.sh +0 -0
  142. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/release-gate.sh +0 -0
  143. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/release-real-models.sh +0 -0
  144. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/require_version_bump.py +0 -0
  145. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/seed_review_run.py +0 -0
  146. {cmpnd-0.83.0 → cmpnd-0.85.0}/scripts/selfhost_compat.py +0 -0
  147. {cmpnd-0.83.0 → cmpnd-0.85.0}/stubs/dspy/__init__.pyi +0 -0
  148. {cmpnd-0.83.0 → cmpnd-0.85.0}/stubs/dspy/primitives/__init__.pyi +0 -0
  149. {cmpnd-0.83.0 → cmpnd-0.85.0}/stubs/dspy/primitives/prediction.pyi +0 -0
  150. {cmpnd-0.83.0 → cmpnd-0.85.0}/stubs/dspy/utils/__init__.pyi +0 -0
  151. {cmpnd-0.83.0 → cmpnd-0.85.0}/stubs/dspy/utils/callback.pyi +0 -0
  152. {cmpnd-0.83.0 → cmpnd-0.85.0}/stubs/dspy/utils/dummies.pyi +0 -0
  153. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/__init__.py +0 -0
  154. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/_credential_route.py +0 -0
  155. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/_deployment_signature_helpers.py +0 -0
  156. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/_exporter_helpers.py +0 -0
  157. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/_fake_clock.py +0 -0
  158. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/_identity_helpers.py +0 -0
  159. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/_resolve_refs.py +0 -0
  160. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/_traced_run.py +0 -0
  161. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/conftest.py +0 -0
  162. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/__init__.py +0 -0
  163. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/committee_task.py +0 -0
  164. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/conftest.py +0 -0
  165. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/continuation_task.py +0 -0
  166. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/data/committee_train.ndjson.gz +0 -0
  167. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/data/committee_val.ndjson.gz +0 -0
  168. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/fake_lm.py +0 -0
  169. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/helpers.py +0 -0
  170. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/target_capabilities.py +0 -0
  171. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_assessments_cli.py +0 -0
  172. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_capture_prompts.py +0 -0
  173. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_composite_module.py +0 -0
  174. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_dataset_auto_creation.py +0 -0
  175. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_deploy_execute.py +0 -0
  176. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_deploy_pandas.py +0 -0
  177. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_deploy_programs.py +0 -0
  178. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_edge_cases.py +0 -0
  179. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_example_hash.py +0 -0
  180. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_gepa_optimization.py +0 -0
  181. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_interpreter_spans.py +0 -0
  182. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_lm_accuracy.py +0 -0
  183. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_module_tracing.py +0 -0
  184. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimization_lineage.py +0 -0
  185. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimization_metric_identity.py +0 -0
  186. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_authored_metric.py +0 -0
  187. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_continuation.py +0 -0
  188. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_custom_proposer.py +0 -0
  189. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_failures.py +0 -0
  190. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_loop.py +0 -0
  191. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_review_committee.py +0 -0
  192. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_optimize_server_owned_completion.py +0 -0
  193. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_parallel_export.py +0 -0
  194. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_project_attribution.py +0 -0
  195. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_react_tool_spans.py +0 -0
  196. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_save_load.py +0 -0
  197. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_trace_structure.py +0 -0
  198. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_traces_read.py +0 -0
  199. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_user_attribution.py +0 -0
  200. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/e2e/test_valsets_cli.py +0 -0
  201. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/01_predict_qa.json +0 -0
  202. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/02_predict_renamed_field.json +0 -0
  203. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/03_chain_of_thought_qa.json +0 -0
  204. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/04_predict_richer_types.json +0 -0
  205. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/05_composite_two_predicts.json +0 -0
  206. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/06_multichaincomparison.json +0 -0
  207. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/07_program_of_thought.json +0 -0
  208. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/08_opaque_module.json +0 -0
  209. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/09_predict_pydantic_field.json +0 -0
  210. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/10_react.json +0 -0
  211. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/11_refine.json +0 -0
  212. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/12_retrieve.json +0 -0
  213. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/13_knn.json +0 -0
  214. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/14_parallel.json +0 -0
  215. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/identity_vectors/README.md +0 -0
  216. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/ingest_conformance.py +0 -0
  217. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/__init__.py +0 -0
  218. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/authored_optimize/judge.py +0 -0
  219. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/authored_optimize/optimize_hosted.py +0 -0
  220. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/conftest.py +0 -0
  221. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/hosted_mcp_tool_optimize_probe.py +0 -0
  222. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/hosted_optimize_probe.py +0 -0
  223. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/optimization_run_probe.py +0 -0
  224. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/test_cli_deploy_smoketest.py +0 -0
  225. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/test_logs_renders_traces.py +0 -0
  226. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/test_optimization_run.py +0 -0
  227. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/test_optimizing_progress.py +0 -0
  228. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/integration/test_unhappy_path.py +0 -0
  229. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/__init__.py +0 -0
  230. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/__main__.py +0 -0
  231. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/drain.py +0 -0
  232. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/ingest.py +0 -0
  233. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/latency_transport.py +0 -0
  234. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/rlm_size_report.py +0 -0
  235. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/sweep.py +0 -0
  236. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/load/test_rlm_size_report.py +0 -0
  237. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/__init__.py +0 -0
  238. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/conftest.py +0 -0
  239. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/seed_parity.py +0 -0
  240. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_auth.py +0 -0
  241. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_datasets.py +0 -0
  242. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_deployments.py +0 -0
  243. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_evals.py +0 -0
  244. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_health.py +0 -0
  245. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_modules.py +0 -0
  246. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_optimizations.py +0 -0
  247. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_programs.py +0 -0
  248. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_signatures.py +0 -0
  249. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_trace_streaming.py +0 -0
  250. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/parity/test_traces.py +0 -0
  251. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/__init__.py +0 -0
  252. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/_provider.py +0 -0
  253. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/_readiness.py +0 -0
  254. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/_reliability.py +0 -0
  255. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/_result.py +0 -0
  256. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/mint_bedrock_token.py +0 -0
  257. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_bedrock_converse.py +0 -0
  258. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_deploy_programs.py +0 -0
  259. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_multi_client_stress.py +0 -0
  260. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_client_committee.py +0 -0
  261. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_continuation.py +0 -0
  262. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_loop.py +0 -0
  263. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_loop_wasm.py +0 -0
  264. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_optimize_stress.py +0 -0
  265. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_provider_roles.py +0 -0
  266. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_readiness.py +0 -0
  267. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_real_provider_twins.py +0 -0
  268. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_reliability.py +0 -0
  269. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_reliability_classify.py +0 -0
  270. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_rlm_wasm.py +0 -0
  271. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/real_models/test_trace_search.py +0 -0
  272. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_adapter_reentrancy.py +0 -0
  273. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_assessments_api.py +0 -0
  274. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_authored.py +0 -0
  275. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_authored_binding_credentials.py +0 -0
  276. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_authored_bundle_collisions.py +0 -0
  277. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_authored_metric_contract.py +0 -0
  278. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_authored_named_models.py +0 -0
  279. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_authored_optimize.py +0 -0
  280. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_candidate_lineage.py +0 -0
  281. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/__init__.py +0 -0
  282. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/conftest.py +0 -0
  283. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_admin.py +0 -0
  284. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_api_client.py +0 -0
  285. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_assessments.py +0 -0
  286. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_auth.py +0 -0
  287. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_credentials.py +0 -0
  288. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_datasets.py +0 -0
  289. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_deployment_runs.py +0 -0
  290. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_deployments.py +0 -0
  291. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_evals.py +0 -0
  292. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_login.py +0 -0
  293. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_main.py +0 -0
  294. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_optimizations.py +0 -0
  295. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_org.py +0 -0
  296. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_pagination.py +0 -0
  297. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_parity.py +0 -0
  298. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_programs.py +0 -0
  299. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_provider_credentials.py +0 -0
  300. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_schema_sample.py +0 -0
  301. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_shell.py +0 -0
  302. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_tool_servers.py +0 -0
  303. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_top.py +0 -0
  304. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_traces.py +0 -0
  305. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_traces_program_and_module.py +0 -0
  306. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_traces_search_wire.py +0 -0
  307. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_tree_help.py +0 -0
  308. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cli/test_valsets.py +0 -0
  309. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_client.py +0 -0
  310. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_client_floor.py +0 -0
  311. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_client_program_names.py +0 -0
  312. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_clustering/__init__.py +0 -0
  313. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_clustering/conftest.py +0 -0
  314. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_clustering/test_edge_cases.py +0 -0
  315. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_clustering/test_hash_consistency.py +0 -0
  316. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_clustering/test_type_conversion.py +0 -0
  317. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_cmp551_grouping_identity.py +0 -0
  318. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_configuration.py +0 -0
  319. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_context_propagation.py +0 -0
  320. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_credential_values.py +0 -0
  321. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_dataset_polling.py +0 -0
  322. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_dataset_sync.py +0 -0
  323. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_datasets.py +0 -0
  324. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_decision_client_spans.py +0 -0
  325. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_decorators.py +0 -0
  326. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_deploy.py +0 -0
  327. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_deploy_mcp_tool_references.py +0 -0
  328. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_deploy_program_id.py +0 -0
  329. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_deployment_metadata.py +0 -0
  330. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_deployment_runs.py +0 -0
  331. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_deployments_read.py +0 -0
  332. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_eval.py +0 -0
  333. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_eval_example_attribution.py +0 -0
  334. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_eval_program_id.py +0 -0
  335. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_eval_start.py +0 -0
  336. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_eval_trace_grouping.py +0 -0
  337. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_exception_paths.py +0 -0
  338. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_execute.py +0 -0
  339. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_execute_run_identity.py +0 -0
  340. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_exporter.py +0 -0
  341. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_exporter_queue.py +0 -0
  342. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_exporter_real.py +0 -0
  343. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_fake_lm.py +0 -0
  344. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_gepa_callback.py +0 -0
  345. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_gepa_integration.py +0 -0
  346. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_gepa_output_normalization.py +0 -0
  347. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_gepa_resume.py +0 -0
  348. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_identity.py +0 -0
  349. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_imaging.py +0 -0
  350. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_import_weight.py +0 -0
  351. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_ingest_conformance.py +0 -0
  352. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_ingest_second_producer.py +0 -0
  353. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_integration.py +0 -0
  354. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_interpreter_callback.py +0 -0
  355. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_ir_encoder_properties.py +0 -0
  356. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_ir_golden_vectors.py +0 -0
  357. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_ir_roundtrip.py +0 -0
  358. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_lm15_native_calls.py +0 -0
  359. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_lm_configuration.py +0 -0
  360. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_lm_usage_shapes.py +0 -0
  361. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_mcp_tools.py +0 -0
  362. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_models.py +0 -0
  363. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_multi_client_stress_readiness.py +0 -0
  364. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_no_signature_hash_on_the_wire.py +0 -0
  365. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimization_phase_callback.py +0 -0
  366. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimization_program_names.py +0 -0
  367. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimization_progress.py +0 -0
  368. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimization_start.py +0 -0
  369. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimize.py +0 -0
  370. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimize_budget.py +0 -0
  371. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimize_lift.py +0 -0
  372. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimize_lms.py +0 -0
  373. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimize_proposer.py +0 -0
  374. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimize_reflection_lm.py +0 -0
  375. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_optimize_terminal_failure.py +0 -0
  376. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_packaging.py +0 -0
  377. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_packaging_callable_source.py +0 -0
  378. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_packaging_mcp_tools.py +0 -0
  379. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_packaging_program_source.py +0 -0
  380. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_poll_until.py +0 -0
  381. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_program_id_deprecation.py +0 -0
  382. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_program_lineage_persist.py +0 -0
  383. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_program_names_in_deploy.py +0 -0
  384. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_programs_create.py +0 -0
  385. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_project_payload.py +0 -0
  386. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_real_provider_twins_readiness.py +0 -0
  387. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_reflector_properties.py +0 -0
  388. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_release_gate_script.py +0 -0
  389. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_release_real_models_script.py +0 -0
  390. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_replaced_names.py +0 -0
  391. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_repro_reactv2_bugs.py +0 -0
  392. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_require_version_bump.py +0 -0
  393. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_retry.py +0 -0
  394. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_reversible_failures.py +0 -0
  395. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_rlm_patch.py +0 -0
  396. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_run_attribution_paths.py +0 -0
  397. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_runtime_tools.py +0 -0
  398. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_schemas.py +0 -0
  399. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_seal_lm_credential.py +0 -0
  400. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_seed_review_run.py +0 -0
  401. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_signature_inputs.py +0 -0
  402. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_span_sent_once.py +0 -0
  403. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_system_one.py +0 -0
  404. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_trace_context_concurrency.py +0 -0
  405. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_trace_fact_wire_contract.py +0 -0
  406. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_trace_facts.py +0 -0
  407. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_trace_render.py +0 -0
  408. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_traces_read.py +0 -0
  409. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_tracked_gepa.py +0 -0
  410. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_tracker_enrich.py +0 -0
  411. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_truncation.py +0 -0
  412. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_upload_auth_headers.py +0 -0
  413. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_valsets_api.py +0 -0
  414. {cmpnd-0.83.0 → cmpnd-0.85.0}/tests/test_xxh64.py +0 -0
@@ -137,7 +137,13 @@ still install against the `dspy>=…` floor in `dependencies`.
137
137
  - `scripts/install-test-env.sh <python> <dspy>` builds `.venv` from one group's
138
138
  locked tree, as each `sdk-test` leg in `ci-sdk.yml` does. From the repo root,
139
139
  run `just sdk-test-env 3.13 3.3.0`, then, from `python-sdk/`,
140
- `.venv/bin/python -m pytest tests/ -k "not e2e"`. `uv sync --extra dev` in
140
+ `.venv/bin/python -m pytest tests/ -k "not e2e"`. The root recipe
141
+ `SDK_TEST_PYTHON_VERSION=3.13 SDK_TEST_DSPY_VERSION=3.4.0 just sdk-test-linux`
142
+ runs the same locked environment in Linux. `SDK_TEST_LINUX_IMAGE` selects a
143
+ pinned CI builder when reproducing a job. The helper copies the worker pins
144
+ and truncation vectors and mounts Git metadata read-only for revision tests.
145
+ The revision tests require Git, which the CI builder provides.
146
+ `uv sync --extra dev` in
141
147
  `python-sdk/` returns to the default group.
142
148
  - `--latest` resolves every package fresh, as a user's install today would.
143
149
  With the DSPy version `newest` it pins no DSPy either. The daily
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cmpnd
3
- Version: 0.83.0
3
+ Version: 0.85.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>
@@ -685,7 +685,7 @@ cmpnd.configure(
685
685
  flush_interval_seconds=5.0, # optional: seconds between flushes
686
686
  capture_inputs=True, # optional: record span inputs
687
687
  capture_outputs=True, # optional: record span outputs
688
- default_tags={"env": "prod"}, # optional: tags on every trace
688
+ default_tags={"env": "prod"}, # optional: tags on every trace; a run's own tags override them
689
689
  )
690
690
  ```
691
691
 
@@ -634,7 +634,7 @@ cmpnd.configure(
634
634
  flush_interval_seconds=5.0, # optional: seconds between flushes
635
635
  capture_inputs=True, # optional: record span inputs
636
636
  capture_outputs=True, # optional: record span outputs
637
- default_tags={"env": "prod"}, # optional: tags on every trace
637
+ default_tags={"env": "prod"}, # optional: tags on every trace; a run's own tags override them
638
638
  )
639
639
  ```
640
640
 
@@ -538,6 +538,7 @@ class _CallbackBase:
538
538
  stream_trace_starts: bool | None = None,
539
539
  own_config: configuration.Config | None = None,
540
540
  optimization_phase: str | None = None,
541
+ producer_context: dict[str, Any] | None = None,
541
542
  ) -> None:
542
543
  # A client passes its own config here; the auto_instrument callback leaves
543
544
  # it None and reads the process-global config live, so a reconfigure()
@@ -547,6 +548,24 @@ class _CallbackBase:
547
548
  # later cmpnd.configure(). That fixedness is what keeps a client
548
549
  # orthogonal to the global config.
549
550
  self._own_config = own_config
551
+ producer_context = producer_context or {}
552
+ self._producer_references = tuple(
553
+ (name, producer_context[name])
554
+ for name in (
555
+ "program_id",
556
+ "deployment_id",
557
+ "deployment_run_id",
558
+ "optimization_run_id",
559
+ "eval_run_id",
560
+ "compiled_from_run_id",
561
+ )
562
+ if producer_context.get(name) is not None
563
+ )
564
+ self._producer_tags = tuple((producer_context.get("tags") or {}).items())
565
+ self._producer_metadata = tuple((producer_context.get("metadata") or {}).items())
566
+ self._producer_projects = (
567
+ tuple(producer_context["project_tags"]) if "project_tags" in producer_context else None
568
+ )
550
569
  # The optimization phase this callback traces for a run it does not own: an
551
570
  # executor that scores a hosted rollout with the author's metric ("metric") or runs
552
571
  # the author's GEPA proposer ("reflection"). Such a process knows the program the
@@ -621,7 +640,7 @@ class _CallbackBase:
621
640
 
622
641
  # Active optimization run ID - stored on instance for same thread-safety reason.
623
642
  # Prefixed text id (`op_…`), minted via cmpnd.identity.
624
- self._active_optimization_run_id: str | None = None
643
+ self._active_optimization_run_id: str | None = dict(self._producer_references).get("optimization_run_id")
625
644
 
626
645
  # Token usage from LM calls without a trace context (e.g., GEPA reflection calls)
627
646
  # Accumulated during optimization and included in the finalization payload
@@ -668,18 +687,20 @@ class _CallbackBase:
668
687
  # repl_history (see _AdapterMixin._thin_rlm_adapter_inputs).
669
688
  self._rlm_turn_adapter_spans: set[UUID] = set()
670
689
 
671
- # Parent span -> the last adapter ``format`` span under it and its full
672
- # rendered messages, for the LM call that follows to reference. See
673
- # _AdapterMixin._remember_rendered_messages and _LMMixin.on_lm_start.
674
- self._rendered_messages: dict[UUID, tuple[models.SpanBuilder, Any]] = {}
690
+ # Parent span -> the last adapter ``format`` span under it and the full
691
+ # value of each prompt key it stored whole, for the LM calls that follow
692
+ # to reference. See _AdapterMixin._remember_rendered_prompt and
693
+ # _LMMixin._reference_rendered_prompt.
694
+ self._rendered_prompts: dict[UUID, tuple[models.SpanBuilder, dict[str, Any]]] = {}
675
695
 
676
696
  # Open module span -> id() of its module instance, so an adapter span can
677
697
  # name the predictor it renders for. And (trace, predictor) -> the last
678
- # rendering of each message index that serve can rebuild in full, as
679
- # (the format span storing it, its full text). Dropped when the trace
680
- # ends. See _AdapterMixin._extend_previous_rendering.
698
+ # rendering of each prompt text that serve can rebuild in full, by its
699
+ # path in the format span's outputs, as (the format span storing it, its
700
+ # full text). Dropped when the trace ends. See
701
+ # _AdapterMixin._extend_previous_rendering.
681
702
  self._module_instance_by_span: dict[UUID, int] = {}
682
- self._last_rendering: dict[tuple[str, int], list[tuple[UUID, str] | None]] = {}
703
+ self._last_rendering: dict[tuple[str, int], dict[tuple[str | int, ...], tuple[UUID, str]]] = {}
683
704
 
684
705
  # Ensure exporter is running
685
706
  self._ensure_exporter()
@@ -722,19 +743,18 @@ class _CallbackBase:
722
743
  return configuration._current_program_id()
723
744
 
724
745
  def _stamp(self, builder: Any) -> Any:
725
- """Stamp this callback's project tags, and on a trace its program, onto a
726
- builder it just created. A span's program is its trace's, so a span carries
727
- no program of its own.
746
+ """Stamp this callback's trace context before any spans begin.
728
747
 
729
748
  A trace from a call that declares a program name carries the name and no id,
730
749
  and the exporter resolves the name. The declared name wins over a program id the
731
750
  client's config read from ``CMPND_PROGRAM_ID``, and the callback warns once that it did.
732
751
  A trace that gets neither gets a generated name in `_create_trace`.
733
752
  """
734
- builder.project_tags = self._resolve_project_tags()
735
753
  if isinstance(builder, models.TraceBuilder):
736
- program_id = self._resolve_program_id()
737
- name = _declared_program_name()
754
+ builder.project_tags = list(self._resolve_project_tags() or ())
755
+ producer_program_id = dict(self._producer_references).get("program_id")
756
+ program_id = producer_program_id or self._resolve_program_id()
757
+ name = None if producer_program_id else _declared_program_name()
738
758
  if name is not None:
739
759
  if program_id is not None and not self._warned_name_over_id:
740
760
  self._warned_name_over_id = True
@@ -765,6 +785,18 @@ class _CallbackBase:
765
785
  return _exporter_pool.ensure_exporter_started(self._own_config)
766
786
  return exporter.get_exporter()
767
787
 
788
+ def _apply_producer_context(self, trace: models.TraceBuilder) -> None:
789
+ for name, value in self._producer_references:
790
+ setattr(trace, name, value)
791
+ if dict(self._producer_references).get("program_id") is not None:
792
+ trace.declared_name = trace.generated_name = None
793
+ if self._producer_projects is not None:
794
+ trace.project_tags = list(self._producer_projects)
795
+ # A run's tags and metadata are more specific than the process defaults
796
+ # already on the trace, so they override them.
797
+ trace.tags.update(self._producer_tags)
798
+ trace.metadata.update(self._producer_metadata)
799
+
768
800
  def _ensure_exporter(self) -> Any:
769
801
  """Start this callback's exporter if it is not already running."""
770
802
  if self._own_config is not None:
@@ -780,6 +812,60 @@ class _CallbackBase:
780
812
  span makes ``to_api_dict`` bound the whole body by the same limits rather
781
813
  than by the 64 KiB default.
782
814
  """
815
+ if context.get_current_trace() is None:
816
+ config = self._own_config or configuration.get_config()
817
+ kwargs.setdefault(
818
+ "project_tags",
819
+ list(
820
+ self._producer_projects
821
+ if self._producer_projects is not None
822
+ else self._resolve_project_tags() or ()
823
+ ),
824
+ )
825
+ # Most specific wins: process defaults, then the run's tags, then the span's own.
826
+ kwargs["tags"] = {
827
+ **(config.default_tags if config else {}),
828
+ **dict(self._producer_tags),
829
+ **kwargs.get("tags", {}),
830
+ }
831
+ for key, value in self._producer_references:
832
+ kwargs.setdefault(key, value)
833
+ run_id = self._active_optimization_run_id or context.get_current_optimization_run_id()
834
+ kwargs.setdefault("optimization_run_id", run_id)
835
+ kwargs.setdefault("eval_run_id", self._active_eval_run.eval_run_id if self._active_eval_run else None)
836
+ program_id = (
837
+ kwargs.get("program_id") or self._resolve_program_id() or context.optimization_program_id(run_id)
838
+ )
839
+ declared = None if kwargs.get("program_id") else _declared_program_name()
840
+ if declared is not None:
841
+ program_id = None
842
+ kwargs.setdefault("program_id", program_id)
843
+ from cmpnd_ingest.program_names import ProgramName
844
+ from cmpnd_ingest.span_context import ExportContext, SpanContext
845
+
846
+ kwargs.setdefault(
847
+ "export_context",
848
+ ExportContext(
849
+ ProgramName(declared) if declared else None,
850
+ program_id,
851
+ declared is None or program_id is not None,
852
+ ),
853
+ )
854
+ phase = self._optimization_phase or (
855
+ ("metric" if self._active_eval_run else "reflection") if run_id else None
856
+ )
857
+ if phase is not None:
858
+ kwargs["tags"]["optimization_phase"] = phase
859
+ kwargs.setdefault(
860
+ "span_context",
861
+ SpanContext(
862
+ project_tags=tuple(kwargs["project_tags"]),
863
+ tags=tuple(kwargs["tags"].items()),
864
+ program_id=program_id,
865
+ optimization_run_id=kwargs["optimization_run_id"],
866
+ eval_run_id=kwargs["eval_run_id"],
867
+ ),
868
+ )
783
869
  return context.create_child_span(
784
870
  name,
785
871
  span_type,
@@ -974,7 +1060,7 @@ class _ModuleMixin(_CallbackBase):
974
1060
 
975
1061
  # An adapter rendering this module's prompt is only ever referenced by an
976
1062
  # LM call under this module, which has ended with it.
977
- self._rendered_messages.pop(span.span_id, None)
1063
+ self._rendered_prompts.pop(span.span_id, None)
978
1064
  self._module_instance_by_span.pop(span.span_id, None)
979
1065
 
980
1066
  # RLM repl_history: append trajectory data from PREDICT children
@@ -1129,9 +1215,9 @@ class _ModuleMixin(_CallbackBase):
1129
1215
 
1130
1216
  def _forget_trace_references(self, trace_id: UUID) -> None:
1131
1217
  """Drop the per-trace state reference encoding keeps, once the trace ends."""
1132
- for parent, (fmt_span, _) in list(self._rendered_messages.items()):
1218
+ for parent, (fmt_span, _) in list(self._rendered_prompts.items()):
1133
1219
  if fmt_span.trace_id == trace_id:
1134
- self._rendered_messages.pop(parent, None)
1220
+ self._rendered_prompts.pop(parent, None)
1135
1221
  for key in [k for k in self._last_rendering if k[0] == str(trace_id)]:
1136
1222
  self._last_rendering.pop(key, None)
1137
1223
 
@@ -1220,7 +1306,7 @@ class _ModuleMixin(_CallbackBase):
1220
1306
 
1221
1307
  def _create_trace(self, instance: Any, inputs: dict[str, Any]) -> models.TraceBuilder:
1222
1308
  """Create a new trace for a root module call."""
1223
- config = configuration.get_config()
1309
+ config = self._own_config or configuration.get_config()
1224
1310
 
1225
1311
  trace = models.TraceBuilder(
1226
1312
  trace_id=uuid4(),
@@ -1364,16 +1450,16 @@ class _ModuleMixin(_CallbackBase):
1364
1450
  preview = helpers.create_preview(helpers.clean_inputs(inputs, dspy, self._max_input_field_size))
1365
1451
  trace.request_preview = preview
1366
1452
 
1367
- # Apply default tags
1453
+ # Process default tags first, then the run's own labels over them.
1368
1454
  if config and config.default_tags:
1369
1455
  trace.tags.update(config.default_tags)
1456
+ self._apply_producer_context(trace)
1370
1457
  # A trace opened while this callback traces a phase, or while a local run
1371
1458
  # calls its metric, is the metric's or the proposer's, never a rollout, even
1372
1459
  # when its module is the program's own class.
1373
1460
  phase = self._optimization_phase or context.get_current_optimization_phase()
1374
1461
  if phase is not None:
1375
1462
  trace.tags["optimization_phase"] = phase
1376
-
1377
1463
  return trace
1378
1464
 
1379
1465
  def _extract_usage_from_prediction(self, span: models.SpanBuilder, outputs: Any, dspy: Any) -> None:
@@ -1456,39 +1542,53 @@ class _LMMixin(_CallbackBase):
1456
1542
 
1457
1543
  # Capture prompt/messages if enabled. A credential passed to the call itself, as in
1458
1544
  # `lm(prompt, api_key=...)`, is stripped first: every member of the org reads a span.
1545
+ # The rendered prompt becomes a reference before truncation, so the rest of the
1546
+ # inputs keep the whole budget.
1459
1547
  if self._capture_prompts:
1460
- cleaned_inputs = helpers.clean_inputs(helpers.without_credentials(inputs), dspy, self._max_input_field_size)
1461
- self._reference_rendered_messages(parent_span, inputs, cleaned_inputs)
1462
- span.set_inputs(cleaned_inputs)
1548
+ referenced = self._reference_rendered_prompt(parent_span, helpers.without_credentials(inputs))
1549
+ span.set_inputs(helpers.clean_inputs(referenced, dspy, self._max_input_field_size))
1463
1550
 
1464
1551
  self._call_id_to_span[call_id] = span
1465
1552
  # Store LM instance reference to access history for token usage
1466
1553
  self._call_id_to_instance[call_id] = instance
1467
1554
  context.push_span(span)
1468
1555
 
1469
- def _reference_rendered_messages(
1470
- self, parent_span: models.SpanBuilder | None, inputs: dict[str, Any], cleaned: dict[str, Any]
1471
- ) -> None:
1472
- """Store the LM's messages as a reference to the adapter output they came from.
1556
+ def _reference_rendered_prompt(
1557
+ self, parent_span: models.SpanBuilder | None, inputs: dict[str, Any]
1558
+ ) -> dict[str, Any]:
1559
+ """``inputs`` with the rendered prompt as references to the adapter output it came from.
1473
1560
 
1474
1561
  The adapter's ``format`` span ended just before this call started, under
1475
- the same parent, and its ``outputs.result`` holds the rendered messages.
1476
- When the LM received exactly those, compared on the full values before any
1477
- truncation, ``messages`` becomes a ``_same_as`` reference to them; serve
1478
- resolves it on read. Anything else — messages the adapter changed after
1479
- rendering, or an LM call no adapter rendered for, such as an RLM
1480
- ``llm_query`` — keeps a full copy, so a mismatch costs bytes, never
1481
- correctness.
1562
+ the same parent, and stored the rendered prompt (``references.prompt_keys``).
1563
+ In the list shape, ``messages`` equal to the stored ``result`` becomes a
1564
+ ``_same_as`` reference to it. In the typed shape, each of ``prompt.system``
1565
+ and ``prompt.messages`` equal to the stored key becomes a reference to that
1566
+ key, and the rest of the request stays inline. Values are compared in full,
1567
+ before any truncation, and serve resolves the references on read.
1568
+ Anything else, such as messages the adapter changed after rendering or an
1569
+ LM call no adapter rendered for, like an RLM ``llm_query``, keeps a full
1570
+ copy, so a mismatch costs bytes, never correctness. Each LM call the
1571
+ adapter makes from one rendering gets the references.
1482
1572
  """
1483
- if parent_span is None or "messages" not in cleaned:
1484
- return
1485
- remembered = self._rendered_messages.pop(parent_span.span_id, None)
1573
+ remembered = self._rendered_prompts.get(parent_span.span_id) if parent_span is not None else None
1486
1574
  if remembered is None:
1487
- return
1575
+ return inputs
1488
1576
  fmt_span, rendered = remembered
1489
- if models._safe_serialize(inputs.get("messages")) != rendered:
1490
- return
1491
- cleaned["messages"] = references.same_as(str(fmt_span.span_id), "outputs", ["result"])
1577
+ fmt_id = str(fmt_span.span_id)
1578
+ if "result" in rendered:
1579
+ if "messages" in inputs and models._safe_serialize(inputs["messages"]) == rendered["result"]:
1580
+ return {**inputs, "messages": references.same_as(fmt_id, "outputs", ["result"])}
1581
+ return inputs
1582
+ if inputs.get("prompt") is None:
1583
+ return inputs
1584
+ prompt = models._safe_serialize(inputs["prompt"])
1585
+ if not isinstance(prompt, dict):
1586
+ return inputs
1587
+ referenced = dict(prompt)
1588
+ for key in references.TYPED_PROMPT_KEYS:
1589
+ if key in rendered and key in prompt and prompt[key] == rendered[key]:
1590
+ referenced[key] = references.same_as(fmt_id, "outputs", [key])
1591
+ return inputs if referenced == prompt else {**inputs, "prompt": referenced}
1492
1592
 
1493
1593
  def on_lm_end(
1494
1594
  self,
@@ -1631,7 +1731,8 @@ class _LMMixin(_CallbackBase):
1631
1731
  # would double-count them in the optimization total.
1632
1732
  current_trace = context.get_current_trace()
1633
1733
  if current_trace is None and (
1634
- self._active_optimization_run_id is not None or self._optimization_phase is not None
1734
+ (span.span_context is not None and span.span_context.optimization_run_id is not None)
1735
+ or self._optimization_phase is not None
1635
1736
  ):
1636
1737
  self._emit_optimization_lm_trace(span)
1637
1738
  return # handled as a standalone GEPA-process trace
@@ -1678,11 +1779,12 @@ class _LMMixin(_CallbackBase):
1678
1779
  instead, and may have no run id: it traces the metric or the proposer of a
1679
1780
  run whose id the ingest key the trace is pushed with decides.
1680
1781
  """
1681
- run_id = self._active_optimization_run_id
1782
+ assert span.span_context is not None
1783
+ run_id = span.span_context.optimization_run_id
1682
1784
  if not run_id and self._optimization_phase is None:
1683
1785
  return
1684
1786
 
1685
- phase = self._optimization_phase or ("metric" if self._active_eval_run is not None else "reflection")
1787
+ phase = dict(span.span_context.tags).get("optimization_phase", "reflection")
1686
1788
  span.attributes["gepa.phase"] = phase
1687
1789
  span.attributes["optimization.role"] = _OPTIMIZATION_PHASE_ROLES[phase]
1688
1790
 
@@ -1692,11 +1794,14 @@ class _LMMixin(_CallbackBase):
1692
1794
  end_time=span.end_time,
1693
1795
  module_class="GEPA",
1694
1796
  )
1695
- self._stamp(trace)
1696
- # The call has no root module to name a program after, so a trace that declares
1697
- # neither an id nor a name takes the program the run optimizes.
1698
- if trace.program_id is None and trace.declared_name is None:
1699
- trace.program_id = context.optimization_program_id(run_id)
1797
+ trace.program_id = span.span_context.program_id
1798
+ trace.deployment_id = span.span_context.deployment_id
1799
+ trace.deployment_run_id = span.span_context.deployment_run_id
1800
+ trace.compiled_from_run_id = span.span_context.compiled_from_run_id
1801
+ trace.project_tags = list(span.span_context.project_tags)
1802
+ trace.tags = dict(span.span_context.tags)
1803
+ trace.metadata = dict(self._producer_metadata)
1804
+ trace._export_context = span.export_context
1700
1805
  # A metric-phase call can start while DSPy's Evaluate span is still on the
1701
1806
  # context stack, so on_lm_start stamped this span with a parent_span_id for
1702
1807
  # a span that isn't in this standalone one-span trace. This span IS the
@@ -1707,8 +1812,7 @@ class _LMMixin(_CallbackBase):
1707
1812
  trace.signature_name = span.model_name or phase
1708
1813
  trace.status = span.status
1709
1814
  trace.optimization_run_id = run_id
1710
- if self._active_eval_run is not None:
1711
- trace.eval_run_id = self._active_eval_run.eval_run_id
1815
+ trace.eval_run_id = span.span_context.eval_run_id
1712
1816
  trace.tags["optimization_phase"] = phase
1713
1817
 
1714
1818
  # Single-span trace: roll the span's usage up onto the trace so the
@@ -1874,12 +1978,16 @@ class _AdapterMixin(_CallbackBase):
1874
1978
  if self._capture_outputs and outputs:
1875
1979
  rendered = models._safe_serialize(outputs)
1876
1980
  if span.span_type == models.SpanType.ADAPTER_FORMAT:
1877
- # Encode against the previous rendering first, then truncate:
1878
- # only a suffix that alone overflows the budget is cut.
1879
- encoded = self._extend_previous_rendering(span, rendered)
1880
- span.set_outputs(helpers.truncate_outputs(encoded, self._max_output_field_size))
1881
- self._record_rendering(span, rendered)
1882
- self._remember_rendered_messages(span, rendered)
1981
+ # The outputs as stored: a typed Prompt as its own dict, a list
1982
+ # of messages under "result". Encode against the previous
1983
+ # rendering first, then truncate: only a suffix that alone
1984
+ # overflows the budget is cut.
1985
+ body = rendered if isinstance(rendered, dict) else {"result": rendered}
1986
+ encoded = self._extend_previous_rendering(span, body)
1987
+ stored = encoded if isinstance(rendered, dict) else encoded["result"]
1988
+ span.set_outputs(helpers.truncate_outputs(stored, self._max_output_field_size))
1989
+ self._record_rendering(span, body)
1990
+ self._remember_rendered_prompt(span, body)
1883
1991
  else:
1884
1992
  span.set_outputs(helpers.truncate_outputs(rendered, self._max_output_field_size))
1885
1993
 
@@ -1904,78 +2012,81 @@ class _AdapterMixin(_CallbackBase):
1904
2012
  predictor = self._module_instance_by_span.get(span.parent_span_id)
1905
2013
  return None if predictor is None else (str(span.trace_id), predictor)
1906
2014
 
1907
- def _extend_previous_rendering(self, span: models.SpanBuilder, rendered: Any) -> Any:
1908
- """``rendered`` with each message that extends the predictor's last one encoded.
2015
+ def _extend_previous_rendering(self, span: models.SpanBuilder, body: dict[str, Any]) -> dict[str, Any]:
2016
+ """``body`` with each prompt text that extends the predictor's last one encoded.
1909
2017
 
1910
2018
  A predictor called once per turn — an RLM's action step, a ReAct step —
1911
- renders the previous turn's prompt plus what the turn added. For each
1912
- message whose content begins with the same-index message of the last
1913
- rendering this predictor stored in this trace, the content becomes an
1914
- ``_extends`` envelope over that stored message. Matching compares the full
1915
- rendered text and needs no knowledge of the module. The base may itself be
1916
- an extension, and serve resolves the chain.
2019
+ renders the previous turn's prompt plus what the turn added. For each text
2020
+ of the prompt (``references.prompt_text_leaves``) that begins with the text
2021
+ at the same path in the last rendering this predictor stored in this trace,
2022
+ the text becomes an ``_extends`` envelope over that stored text. In the
2023
+ typed shape this covers the system prompt, which is the same on every turn,
2024
+ as well as each message's text parts. Matching compares the full rendered
2025
+ text and needs no knowledge of the module. The base may itself be an
2026
+ extension, and serve resolves the chain.
1917
2027
  """
1918
2028
  key = self._rendering_key(span)
1919
2029
  previous = self._last_rendering.get(key) if key else None
1920
- if not previous or not isinstance(rendered, list):
1921
- return rendered
1922
- encoded = list(rendered)
1923
- for i, message in enumerate(rendered):
1924
- base = previous[i] if i < len(previous) else None
1925
- content = message.get("content") if isinstance(message, dict) else None
1926
- if base is None or not isinstance(content, str):
2030
+ if not previous:
2031
+ return body
2032
+ encoded = body
2033
+ for path, text in references.prompt_text_leaves(body):
2034
+ base = previous.get(path)
2035
+ if base is None:
1927
2036
  continue
1928
- base_span_id, base_content = base
1929
- env = references.extends(
1930
- base_content, content, references.ref(str(base_span_id), "outputs", ["result", i, "content"])
1931
- )
2037
+ base_span_id, base_text = base
2038
+ env = references.extends(base_text, text, references.ref(str(base_span_id), "outputs", list(path)))
1932
2039
  if env is not None:
1933
- encoded[i] = {**message, "content": env}
2040
+ encoded = references.with_value_at(encoded, path, env)
1934
2041
  return encoded
1935
2042
 
1936
- def _record_rendering(self, span: models.SpanBuilder, rendered: Any) -> None:
2043
+ def _record_rendering(self, span: models.SpanBuilder, body: dict[str, Any]) -> None:
1937
2044
  """Keep this rendering as the base for the predictor's next one.
1938
2045
 
1939
- A message becomes the next base only where its stored form resolves to the
2046
+ A text becomes the next base only where its stored form resolves to the
1940
2047
  full text: a whole string, or an extension whose suffix survived
1941
- truncation. Where it was cut, the previous base stays, so the next turn
1942
- extends a message serve can still rebuild.
2048
+ truncation. Where it was cut, the previous base at that path stays, so the
2049
+ next turn extends a text serve can still rebuild.
1943
2050
  """
1944
2051
  key = self._rendering_key(span)
1945
- stored = span.outputs.get("result") if isinstance(span.outputs, dict) else None
1946
- if key is None or not isinstance(rendered, list):
2052
+ if key is None or not references.prompt_keys(body):
1947
2053
  return
1948
- previous = self._last_rendering.get(key) or []
1949
- current: list[tuple[UUID, str] | None] = []
1950
- for i, message in enumerate(rendered):
1951
- content = message.get("content") if isinstance(message, dict) else None
1952
- kept = stored[i] if isinstance(stored, list) and len(stored) == len(rendered) else None
1953
- kept_content = kept.get("content") if isinstance(kept, dict) else None
1954
- resolvable = kept_content == content or (
1955
- isinstance(kept_content, dict)
1956
- and set(kept_content) == {"_extends", "_prefix_chars", "_suffix"}
1957
- and isinstance(kept_content["_suffix"], str)
2054
+ previous = self._last_rendering.get(key) or {}
2055
+ current: dict[tuple[str | int, ...], tuple[UUID, str]] = {}
2056
+ for path, text in references.prompt_text_leaves(body):
2057
+ kept = references.value_at(span.outputs, path)
2058
+ resolvable = kept == text or (
2059
+ isinstance(kept, dict)
2060
+ and set(kept) == {"_extends", "_prefix_chars", "_suffix"}
2061
+ and isinstance(kept["_suffix"], str)
1958
2062
  )
1959
- if isinstance(content, str) and resolvable:
1960
- current.append((span.span_id, content))
1961
- else:
1962
- current.append(previous[i] if i < len(previous) else None)
2063
+ if resolvable:
2064
+ current[path] = (span.span_id, text)
2065
+ elif path in previous:
2066
+ current[path] = previous[path]
1963
2067
  self._last_rendering[key] = current
1964
2068
 
1965
- def _remember_rendered_messages(self, span: models.SpanBuilder, rendered: Any) -> None:
1966
- """Keep a ``format`` span's full rendered messages for the LM call that follows.
2069
+ def _remember_rendered_prompt(self, span: models.SpanBuilder, body: dict[str, Any]) -> None:
2070
+ """Keep a ``format`` span's full rendered prompt for the LM calls that follow.
1967
2071
 
1968
- The adapter hands exactly these messages to the LM, which starts after
1969
- this span ended, as its sibling under the same parent. ``on_lm_start``
1970
- compares its messages with them and writes a reference when they are
1971
- equal. Kept only when the stored output still holds the list at
1972
- ``outputs.result``: a list truncated whole into an envelope cannot be
1973
- walked into, and the LM span then keeps its own copy.
2072
+ The adapter hands exactly this prompt to the LM, which starts after this
2073
+ span ended, as its sibling under the same parent.
2074
+ ``_reference_rendered_prompt`` compares the LM's prompt with it and writes
2075
+ references where they are equal. A prompt key is kept only when its stored
2076
+ value resolves in full: a list truncated into an envelope cannot be walked
2077
+ into, and the LM span then keeps its own copy of that key.
1974
2078
  """
1975
- stored = span.outputs.get("result") if isinstance(span.outputs, dict) else None
1976
- if span.parent_span_id is None or not isinstance(rendered, list) or not isinstance(stored, list):
2079
+ if span.parent_span_id is None:
1977
2080
  return
1978
- self._rendered_messages[span.parent_span_id] = (span, rendered)
2081
+ kept = {}
2082
+ for key in references.prompt_keys(body):
2083
+ stored = references.value_at(span.outputs, (key,))
2084
+ if stored is not None and references.resolves_in_full(stored):
2085
+ kept[key] = body[key]
2086
+ if kept:
2087
+ self._rendered_prompts[span.parent_span_id] = (span, kept)
2088
+ else:
2089
+ self._rendered_prompts.pop(span.parent_span_id, None)
1979
2090
 
1980
2091
 
1981
2092
  class _ToolMixin(_CallbackBase):
@@ -2169,6 +2280,11 @@ class _InterpreterMixin(_CallbackBase):
2169
2280
  "parent_span": context.get_current_span(),
2170
2281
  "start_time": datetime.now(timezone.utc),
2171
2282
  }
2283
+ trace = context.get_current_trace()
2284
+ parent = frame["parent_span"]
2285
+ frame["trace_id"] = trace.trace_id if trace else parent.trace_id
2286
+ frame["span_context"] = trace.span_context() if trace else parent.span_context
2287
+ frame["export_context"] = trace.export_context if trace else parent.export_context
2172
2288
  self._interpreter_tool_frames[call_id] = frame
2173
2289
  # Publish this frame as the one a nested on_tool_start marks, in this
2174
2290
  # context only. The token restores the prior frame (nested tool calls) on
@@ -2209,13 +2325,15 @@ class _InterpreterMixin(_CallbackBase):
2209
2325
  parent = frame["parent_span"]
2210
2326
  span = models.SpanBuilder(
2211
2327
  span_id=uuid4(),
2212
- trace_id=parent.trace_id if parent else None,
2328
+ trace_id=frame["trace_id"],
2213
2329
  name=f"Tool.{frame['tool_name']}",
2214
2330
  span_type=models.SpanType.TOOL,
2215
2331
  parent_span_id=parent.span_id if parent else None,
2216
2332
  start_time=frame["start_time"],
2217
2333
  max_input_size=self._max_input_field_size,
2218
2334
  max_output_size=self._max_output_field_size,
2335
+ span_context=frame["span_context"],
2336
+ export_context=frame["export_context"],
2219
2337
  )
2220
2338
  span.attributes["tool.name"] = str(frame["tool_name"])
2221
2339
  span.attributes["tool.synthesized"] = "true"
@@ -257,11 +257,11 @@ def require_config() -> Config:
257
257
  def _current_project_tags() -> list[str] | None: # pyright: ignore[reportUnusedFunction] # used cross-module as a dataclass default_factory
258
258
  """Read the currently-configured project tags, or None if unconfigured.
259
259
 
260
- Called at payload serialization time so that a configure(project_tags=...)
261
- call after a TraceBuilder is constructed still gets the fresh value.
260
+ Builders copy these values when execution begins, so later configuration
261
+ changes affect only future traces and spans.
262
262
  """
263
263
  cfg = get_config()
264
- return cfg.project_tags if cfg else None
264
+ return list(cfg.project_tags) if cfg and cfg.project_tags is not None else None
265
265
 
266
266
 
267
267
  def _current_program_id() -> str | None: # pyright: ignore[reportUnusedFunction] # used cross-module as a dataclass default_factory