agentevolve-optimizer 0.5.0__py3-none-any.whl

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. agent_evolve/__init__.py +722 -0
  2. agent_evolve/agentic.py +2800 -0
  3. agent_evolve/api.py +767 -0
  4. agent_evolve/application/__init__.py +1580 -0
  5. agent_evolve/application/action_allocation.py +744 -0
  6. agent_evolve/application/action_allocation_frame.py +347 -0
  7. agent_evolve/application/action_allocation_frame_commit.py +185 -0
  8. agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
  9. agent_evolve/application/action_allocation_frame_v3.py +338 -0
  10. agent_evolve/application/action_archive_value.py +497 -0
  11. agent_evolve/application/action_evidence_consistency.py +455 -0
  12. agent_evolve/application/action_forecast_partitioning.py +1471 -0
  13. agent_evolve/application/action_metric_projection.py +211 -0
  14. agent_evolve/application/action_role_value.py +680 -0
  15. agent_evolve/application/action_score_authorities.py +363 -0
  16. agent_evolve/application/action_structural_signature.py +116 -0
  17. agent_evolve/application/action_target_realization.py +402 -0
  18. agent_evolve/application/agentic_evolution.py +7734 -0
  19. agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
  20. agent_evolve/application/anchor_residual_identification.py +463 -0
  21. agent_evolve/application/archive_conditioned_action_target.py +208 -0
  22. agent_evolve/application/artifact_journal.py +246 -0
  23. agent_evolve/application/artifact_replay.py +347 -0
  24. agent_evolve/application/budgeted_optimizer.py +1828 -0
  25. agent_evolve/application/calibrated_campaign.py +485 -0
  26. agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
  27. agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
  28. agent_evolve/application/campaign_capacity_recourse.py +254 -0
  29. agent_evolve/application/campaign_contextual_outcomes.py +119 -0
  30. agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
  31. agent_evolve/application/campaign_evidence_registry.py +262 -0
  32. agent_evolve/application/campaign_execution.py +2537 -0
  33. agent_evolve/application/campaign_generation_audit.py +942 -0
  34. agent_evolve/application/campaign_learning.py +1812 -0
  35. agent_evolve/application/campaign_learning_runtime.py +1977 -0
  36. agent_evolve/application/campaign_search_phase.py +227 -0
  37. agent_evolve/application/campaign_selector_context_extension.py +220 -0
  38. agent_evolve/application/campaign_variation_envelope.py +649 -0
  39. agent_evolve/application/campaign_variation_trace.py +451 -0
  40. agent_evolve/application/candidate_archive_consequence.py +128 -0
  41. agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
  42. agent_evolve/application/composite_outcome_updater.py +145 -0
  43. agent_evolve/application/composition_portfolio_selection.py +363 -0
  44. agent_evolve/application/concurrent_stage.py +144 -0
  45. agent_evolve/application/contextual_action_allocation.py +181 -0
  46. agent_evolve/application/contextual_campaign_outcomes.py +267 -0
  47. agent_evolve/application/contextual_campaign_planning.py +1366 -0
  48. agent_evolve/application/contextual_delayed_credit.py +651 -0
  49. agent_evolve/application/contextual_search_controller.py +2374 -0
  50. agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
  51. agent_evolve/application/decision_metric_projection.py +112 -0
  52. agent_evolve/application/derived_action_semantics.py +129 -0
  53. agent_evolve/application/detailed_evaluation.py +449 -0
  54. agent_evolve/application/earned_lineage.py +1011 -0
  55. agent_evolve/application/effective_choice_audit.py +484 -0
  56. agent_evolve/application/empirical_consequence_calibration.py +908 -0
  57. agent_evolve/application/evaluation_accounting.py +325 -0
  58. agent_evolve/application/evaluation_cache.py +199 -0
  59. agent_evolve/application/evaluation_escrow.py +547 -0
  60. agent_evolve/application/evaluation_recourse.py +253 -0
  61. agent_evolve/application/event_recorder.py +151 -0
  62. agent_evolve/application/evolution_campaign.py +1840 -0
  63. agent_evolve/application/executable_hypothesis.py +323 -0
  64. agent_evolve/application/factorial_branch_pilot.py +772 -0
  65. agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
  66. agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
  67. agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
  68. agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
  69. agent_evolve/application/finite_action_selection.py +188 -0
  70. agent_evolve/application/finite_action_set.py +306 -0
  71. agent_evolve/application/finite_action_transition.py +537 -0
  72. agent_evolve/application/finite_variation_eligibility.py +296 -0
  73. agent_evolve/application/forecast_geometry_portfolio.py +799 -0
  74. agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
  75. agent_evolve/application/front_proximity_admission.py +311 -0
  76. agent_evolve/application/front_proximity_parent_basis.py +458 -0
  77. agent_evolve/application/frozen_hurdle_score.py +659 -0
  78. agent_evolve/application/g3_causal_screen.py +2257 -0
  79. agent_evolve/application/g3_causal_validation.py +1046 -0
  80. agent_evolve/application/g3_postseal_curation.py +818 -0
  81. agent_evolve/application/gated_agentic_generator.py +205 -0
  82. agent_evolve/application/generation_feedback.py +293 -0
  83. agent_evolve/application/generative_proposal_journal.py +185 -0
  84. agent_evolve/application/geometry_conditional_elasticity.py +453 -0
  85. agent_evolve/application/global_wave_action_allocation.py +1151 -0
  86. agent_evolve/application/head_mass_conditional_seat.py +268 -0
  87. agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
  88. agent_evolve/application/identifiable_reflection_learning.py +395 -0
  89. agent_evolve/application/identifiable_reflection_request.py +364 -0
  90. agent_evolve/application/in_memory_residual_archive.py +341 -0
  91. agent_evolve/application/insight_memory.py +1804 -0
  92. agent_evolve/application/live_runtime_manifest.py +758 -0
  93. agent_evolve/application/llm_task_queue.py +769 -0
  94. agent_evolve/application/matched_finite_action_block.py +409 -0
  95. agent_evolve/application/materialized_action_broker.py +2328 -0
  96. agent_evolve/application/materialized_action_constraints.py +83 -0
  97. agent_evolve/application/materialized_variation.py +211 -0
  98. agent_evolve/application/multi_option_evolution.py +1536 -0
  99. agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
  100. agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
  101. agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
  102. agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
  103. agent_evolve/application/outcome_relation.py +193 -0
  104. agent_evolve/application/paired_allocation_comparison.py +241 -0
  105. agent_evolve/application/paired_block_schedule.py +127 -0
  106. agent_evolve/application/parent_measurement.py +226 -0
  107. agent_evolve/application/pareto_archive.py +811 -0
  108. agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
  109. agent_evolve/application/portfolio_evolution.py +2950 -0
  110. agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
  111. agent_evolve/application/portfolio_memory_attribution.py +581 -0
  112. agent_evolve/application/portfolio_memory_dose.py +788 -0
  113. agent_evolve/application/portfolio_memory_matched_control.py +938 -0
  114. agent_evolve/application/portfolio_memory_transfer.py +297 -0
  115. agent_evolve/application/portfolio_optimization_memory.py +363 -0
  116. agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
  117. agent_evolve/application/portfolio_projection.py +335 -0
  118. agent_evolve/application/portfolio_recombination.py +2032 -0
  119. agent_evolve/application/post_evolution_reflection.py +834 -0
  120. agent_evolve/application/postcommit_rank_authority.py +245 -0
  121. agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
  122. agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
  123. agent_evolve/application/prequential_residual_exploration.py +343 -0
  124. agent_evolve/application/prequential_score_portfolio.py +954 -0
  125. agent_evolve/application/projections.py +292 -0
  126. agent_evolve/application/protected_action_committee.py +1027 -0
  127. agent_evolve/application/protected_branch_pilot.py +376 -0
  128. agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
  129. agent_evolve/application/provider_replay.py +910 -0
  130. agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
  131. agent_evolve/application/recombination_residual_expert.py +403 -0
  132. agent_evolve/application/reflection_workflow.py +571 -0
  133. agent_evolve/application/region_conditional_credit.py +911 -0
  134. agent_evolve/application/residual_campaign_runtime.py +531 -0
  135. agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
  136. agent_evolve/application/residual_headroom_ledger.py +1544 -0
  137. agent_evolve/application/residual_learning_transaction.py +396 -0
  138. agent_evolve/application/residual_portfolio_evolution.py +1228 -0
  139. agent_evolve/application/residual_reachability.py +749 -0
  140. agent_evolve/application/residual_stage_credit.py +499 -0
  141. agent_evolve/application/same_prefix_paired_audit.py +1580 -0
  142. agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
  143. agent_evolve/application/sequential_lineage_allocation.py +1017 -0
  144. agent_evolve/application/sequential_market_replay.py +1395 -0
  145. agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
  146. agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
  147. agent_evolve/application/single_score_action_allocation.py +299 -0
  148. agent_evolve/application/source_exposure_allocation.py +906 -0
  149. agent_evolve/application/staged_memory.py +210 -0
  150. agent_evolve/application/stratified_cold_start_allocation.py +732 -0
  151. agent_evolve/application/support_guarded_hurdle_score.py +549 -0
  152. agent_evolve/application/target_conditioned_action_forecast.py +595 -0
  153. agent_evolve/application/target_conditioned_campaign.py +566 -0
  154. agent_evolve/application/treatment_assignment.py +201 -0
  155. agent_evolve/application/trusted_objective_evidence.py +217 -0
  156. agent_evolve/application/two_stage_action_evolution.py +1131 -0
  157. agent_evolve/application/v8lite_allocation_policy.py +1083 -0
  158. agent_evolve/application/v9_candidate_policy.py +1303 -0
  159. agent_evolve/bootstrap.py +108 -0
  160. agent_evolve/campaign_presets.py +517 -0
  161. agent_evolve/campaign_profiles.py +452 -0
  162. agent_evolve/campaign_variation_topology.py +288 -0
  163. agent_evolve/campaign_workload.py +950 -0
  164. agent_evolve/cli.py +797 -0
  165. agent_evolve/contract.py +241 -0
  166. agent_evolve/core/__init__.py +91 -0
  167. agent_evolve/core/action_semantics.py +411 -0
  168. agent_evolve/core/authored.py +105 -0
  169. agent_evolve/core/formatting.py +286 -0
  170. agent_evolve/core/optimization_semantics.py +324 -0
  171. agent_evolve/core/problem.py +167 -0
  172. agent_evolve/core/results.py +323 -0
  173. agent_evolve/core/stats.py +70 -0
  174. agent_evolve/core/telemetry.py +100 -0
  175. agent_evolve/domain/__init__.py +89 -0
  176. agent_evolve/domain/artifact.py +162 -0
  177. agent_evolve/domain/durable_text.py +68 -0
  178. agent_evolve/domain/event.py +1454 -0
  179. agent_evolve/domain/finite_action_set.py +426 -0
  180. agent_evolve/domain/finite_variation.py +526 -0
  181. agent_evolve/domain/generative_emission.py +559 -0
  182. agent_evolve/domain/ids.py +163 -0
  183. agent_evolve/domain/inline_text.py +106 -0
  184. agent_evolve/domain/insight.py +27 -0
  185. agent_evolve/domain/lineage.py +737 -0
  186. agent_evolve/domain/llm_task_queue.py +960 -0
  187. agent_evolve/domain/outcome.py +96 -0
  188. agent_evolve/domain/patch.py +854 -0
  189. agent_evolve/domain/typed_json.py +542 -0
  190. agent_evolve/domain/variation_space.py +158 -0
  191. agent_evolve/driver.py +1014 -0
  192. agent_evolve/harness/__init__.py +29 -0
  193. agent_evolve/harness/base.py +242 -0
  194. agent_evolve/harness/directives.py +163 -0
  195. agent_evolve/harness/generative_seal.py +479 -0
  196. agent_evolve/harness/registry.py +41 -0
  197. agent_evolve/infrastructure/__init__.py +39 -0
  198. agent_evolve/infrastructure/artifacts/__init__.py +6 -0
  199. agent_evolve/infrastructure/artifacts/_verification.py +67 -0
  200. agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
  201. agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
  202. agent_evolve/infrastructure/asyncio_runtime.py +109 -0
  203. agent_evolve/infrastructure/authored_runtime.py +188 -0
  204. agent_evolve/infrastructure/authored_worker.py +171 -0
  205. agent_evolve/infrastructure/clock.py +53 -0
  206. agent_evolve/infrastructure/events/__init__.py +6 -0
  207. agent_evolve/infrastructure/events/_validation.py +89 -0
  208. agent_evolve/infrastructure/events/in_memory.py +56 -0
  209. agent_evolve/infrastructure/events/jsonl.py +193 -0
  210. agent_evolve/infrastructure/exception_provenance.py +215 -0
  211. agent_evolve/infrastructure/ids.py +118 -0
  212. agent_evolve/infrastructure/lineage_codec.py +1836 -0
  213. agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
  214. agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
  215. agent_evolve/infrastructure/resource_lease.py +370 -0
  216. agent_evolve/infrastructure/sanitization/__init__.py +8 -0
  217. agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
  218. agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
  219. agent_evolve/infrastructure/stream_liveness.py +383 -0
  220. agent_evolve/infrastructure/subprocess_boundary.py +136 -0
  221. agent_evolve/integrations/__init__.py +1 -0
  222. agent_evolve/integrations/botorch/__init__.py +28 -0
  223. agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
  224. agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
  225. agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
  226. agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
  227. agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
  228. agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
  229. agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
  230. agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
  231. agent_evolve/integrations/completion.py +242 -0
  232. agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
  233. agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
  234. agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
  235. agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
  236. agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
  237. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
  238. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
  239. agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
  240. agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
  241. agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
  242. agent_evolve/integrations/pydantic_ai/harness.py +159 -0
  243. agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
  244. agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
  245. agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
  246. agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
  247. agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
  248. agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
  249. agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
  250. agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
  251. agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
  252. agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
  253. agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
  254. agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
  255. agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
  256. agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
  257. agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
  258. agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
  259. agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
  260. agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
  261. agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
  262. agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
  263. agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
  264. agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
  265. agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
  266. agent_evolve/integrations/pymoo_adapter.py +242 -0
  267. agent_evolve/policies/__init__.py +17 -0
  268. agent_evolve/policies/check.py +469 -0
  269. agent_evolve/policies/emit_scaffold.py +451 -0
  270. agent_evolve/policies/feedback/__init__.py +37 -0
  271. agent_evolve/policies/feedback/held_out_asn.py +1325 -0
  272. agent_evolve/policies/genetic.py +607 -0
  273. agent_evolve/policies/llm_backoff.py +183 -0
  274. agent_evolve/policies/llm_chooser.py +226 -0
  275. agent_evolve/policies/llm_generator.py +1760 -0
  276. agent_evolve/policies/llm_init.py +267 -0
  277. agent_evolve/policies/llm_operator.py +109 -0
  278. agent_evolve/policies/llm_prior.py +194 -0
  279. agent_evolve/policies/llm_surrogate.py +334 -0
  280. agent_evolve/policies/measurement_evidence.py +704 -0
  281. agent_evolve/policies/memory/__init__.py +223 -0
  282. agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
  283. agent_evolve/policies/memory/compatibility_matching.py +593 -0
  284. agent_evolve/policies/memory/global_falsification.py +1841 -0
  285. agent_evolve/policies/memory/prompt_shape.py +503 -0
  286. agent_evolve/policies/memory/randomized_subset.py +714 -0
  287. agent_evolve/policies/memory/staged_causal.py +1270 -0
  288. agent_evolve/policies/memory/treatment_compliance.py +759 -0
  289. agent_evolve/policies/objective_resolution/__init__.py +17 -0
  290. agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
  291. agent_evolve/policies/operator_portfolio.py +407 -0
  292. agent_evolve/policies/reguidance.py +1133 -0
  293. agent_evolve/policies/reward/__init__.py +83 -0
  294. agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
  295. agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
  296. agent_evolve/policies/reward/affine_hypervolume.py +490 -0
  297. agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
  298. agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
  299. agent_evolve/policies/reward/frozen_archive.py +360 -0
  300. agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
  301. agent_evolve/policies/search_state.py +208 -0
  302. agent_evolve/policies/selection/__init__.py +345 -0
  303. agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
  304. agent_evolve/policies/selection/affine_frontier_context.py +330 -0
  305. agent_evolve/policies/selection/affine_frontier_target.py +473 -0
  306. agent_evolve/policies/selection/archive_elite.py +1346 -0
  307. agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
  308. agent_evolve/policies/selection/calibrated_slate.py +1394 -0
  309. agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
  310. agent_evolve/policies/selection/common_candidate_pool.py +685 -0
  311. agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
  312. agent_evolve/policies/selection/disjoint_pairs.py +479 -0
  313. agent_evolve/policies/selection/elite_explorer.py +719 -0
  314. agent_evolve/policies/selection/finite_action.py +187 -0
  315. agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
  316. agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
  317. agent_evolve/policies/selection/forecast_calibration.py +922 -0
  318. agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
  319. agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
  320. agent_evolve/policies/selection/full_support_slate.py +91 -0
  321. agent_evolve/policies/selection/meaningful_direction.py +240 -0
  322. agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
  323. agent_evolve/policies/selection/model_anchored_slate.py +826 -0
  324. agent_evolve/policies/selection/phenotype_recourse.py +979 -0
  325. agent_evolve/policies/selection/proposal_support.py +368 -0
  326. agent_evolve/policies/selection/random_portfolio.py +254 -0
  327. agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
  328. agent_evolve/policies/selection/residual_frontier.py +463 -0
  329. agent_evolve/policies/selection/residual_frontier_target.py +605 -0
  330. agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
  331. agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
  332. agent_evolve/policies/selection/target_conditioned_features.py +812 -0
  333. agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
  334. agent_evolve/policies/selection/task_keyed_palette.py +906 -0
  335. agent_evolve/policies/semantics.py +147 -0
  336. agent_evolve/policies/structure.py +362 -0
  337. agent_evolve/policies/structured_output_budget.py +62 -0
  338. agent_evolve/policies/surrogate.py +696 -0
  339. agent_evolve/policies/variation/__init__.py +1 -0
  340. agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
  341. agent_evolve/policies/variation/crossover_inheritance.py +575 -0
  342. agent_evolve/policies/variation/disjoint_recombination.py +611 -0
  343. agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
  344. agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
  345. agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
  346. agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
  347. agent_evolve/policies/variation/typed_patch.py +1981 -0
  348. agent_evolve/policies/weighted_prior.py +394 -0
  349. agent_evolve/ports/__init__.py +383 -0
  350. agent_evolve/ports/action_allocation.py +733 -0
  351. agent_evolve/ports/action_allocation_frame.py +1153 -0
  352. agent_evolve/ports/action_allocation_frame_commit.py +294 -0
  353. agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
  354. agent_evolve/ports/action_allocation_frame_v3.py +995 -0
  355. agent_evolve/ports/action_forecast.py +1568 -0
  356. agent_evolve/ports/action_metric_projection.py +165 -0
  357. agent_evolve/ports/agentic_generator.py +1561 -0
  358. agent_evolve/ports/archive_context.py +136 -0
  359. agent_evolve/ports/artifact_sanitizer.py +44 -0
  360. agent_evolve/ports/artifact_store.py +225 -0
  361. agent_evolve/ports/clock.py +13 -0
  362. agent_evolve/ports/contextual_search_allocation.py +827 -0
  363. agent_evolve/ports/decision_metric_projection.py +258 -0
  364. agent_evolve/ports/event_store.py +55 -0
  365. agent_evolve/ports/executable_hypothesis.py +557 -0
  366. agent_evolve/ports/finite_acquisition.py +377 -0
  367. agent_evolve/ports/finite_acquisition_batch.py +296 -0
  368. agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
  369. agent_evolve/ports/finite_acquisition_json.py +247 -0
  370. agent_evolve/ports/finite_acquisition_space.py +168 -0
  371. agent_evolve/ports/finite_action_selection.py +348 -0
  372. agent_evolve/ports/finite_action_set.py +256 -0
  373. agent_evolve/ports/frontier_target.py +396 -0
  374. agent_evolve/ports/generation_failure.py +43 -0
  375. agent_evolve/ports/hard_feasibility.py +233 -0
  376. agent_evolve/ports/id_factory.py +34 -0
  377. agent_evolve/ports/llm_task_queue.py +93 -0
  378. agent_evolve/ports/objective_resolution.py +419 -0
  379. agent_evolve/ports/paired_allocation_comparison.py +401 -0
  380. agent_evolve/ports/paired_block_schedule.py +475 -0
  381. agent_evolve/ports/parent_measurement.py +336 -0
  382. agent_evolve/ports/portfolio_memory_dose.py +643 -0
  383. agent_evolve/ports/portfolio_selection.py +3169 -0
  384. agent_evolve/ports/postcommit_rank_authority.py +467 -0
  385. agent_evolve/ports/presented_action_evidence.py +794 -0
  386. agent_evolve/ports/resource_lease.py +162 -0
  387. agent_evolve/ports/structured_generator.py +734 -0
  388. agent_evolve/ports/structured_output_budget.py +120 -0
  389. agent_evolve/ports/subprocess_boundary.py +138 -0
  390. agent_evolve/ports/treatment_assignment.py +466 -0
  391. agent_evolve/ports/variation_catalog.py +76 -0
  392. agent_evolve/ports/variation_source.py +226 -0
  393. agent_evolve/proposal_mode.py +157 -0
  394. agent_evolve/proposers/__init__.py +10 -0
  395. agent_evolve/proposers/random_proposer.py +188 -0
  396. agent_evolve/provider_accounting.py +163 -0
  397. agent_evolve/py.typed +0 -0
  398. agent_evolve/reference_method.py +1570 -0
  399. agent_evolve/session/__init__.py +11 -0
  400. agent_evolve/session/authorship.py +864 -0
  401. agent_evolve/session/evaluate.py +236 -0
  402. agent_evolve/session/fidelity.py +237 -0
  403. agent_evolve/session/genetic_loop.py +742 -0
  404. agent_evolve/session/loop.py +803 -0
  405. agent_evolve/session/screening.py +671 -0
  406. agent_evolve/settings.py +376 -0
  407. agent_evolve/workload_kit.py +368 -0
  408. agent_evolve/workload_prompt.py +398 -0
  409. agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
  410. agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
  411. agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
  412. agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
  413. agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
  414. agentevolve_optimizer-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1325 @@
1
+ """Injectable six-call outcome-reflection and held-out A/S/N policy.
2
+
3
+ This experiment policy restores one concrete reflection-to-action step without
4
+ making a two-card A/S/N design a universal AgentEvolve assumption. One
5
+ interceptor converts two sealed diagnostic outcomes into exactly two
6
+ quarantined cards. A small mailbox adapter then binds the higher-score card, its
7
+ score-swapped counterpart, and a neutral sham card to three otherwise matched
8
+ held-out invocation plans.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ import hashlib
15
+ import json
16
+ from dataclasses import dataclass, replace
17
+ from enum import Enum
18
+ from typing import Protocol
19
+
20
+ from agent_evolve.application.agentic_evolution import (
21
+ InvocationOutcome,
22
+ InvocationPlan,
23
+ )
24
+ from agent_evolve.application.budgeted_optimizer import OptimizerState
25
+ from agent_evolve.application.generation_feedback import (
26
+ GenerationFeedbackContext,
27
+ GenerationFeedbackReservation,
28
+ GenerationFeedbackResult,
29
+ validate_generation_feedback_receipt,
30
+ )
31
+ from agent_evolve.application.insight_memory import (
32
+ InsightLifecycleState,
33
+ InsightMemoryBank,
34
+ InsightMemoryEntry,
35
+ InsightOrigin,
36
+ QuarantineAssignmentStructuralError,
37
+ )
38
+ from agent_evolve.domain.ids import CandidateId
39
+ from agent_evolve.domain.insight import InsightRef
40
+ from agent_evolve.domain.patch import ArrayIndex, JsonPath, ObjectKey
41
+ from agent_evolve.ports.agentic_generator import (
42
+ InsightDraft,
43
+ MetricEffectDirection,
44
+ MetricEffectPrediction,
45
+ ReflectionInsightContract,
46
+ validate_reflection_insight_draft,
47
+ )
48
+ from agent_evolve.ports.generation_failure import (
49
+ GenerationFailureDisposition,
50
+ classify_generation_failure,
51
+ )
52
+ from agent_evolve.policies.memory.treatment_compliance import (
53
+ InsightTreatmentRequirement,
54
+ TreatmentActionBinding,
55
+ TreatmentAssignmentRole,
56
+ TreatmentClaimMode,
57
+ TreatmentInsightEvidence,
58
+ )
59
+
60
+
61
+ REFLECTIVE_FEEDBACK_POLICY_ID = "v7_reflective_feedback"
62
+ REFLECTIVE_FEEDBACK_POLICY_VERSION = 2
63
+ _METADATA_SCHEMA = "v7-reflected-card-batch-v2"
64
+ _REFLECTION_CONTRAST_DOMAIN = b"agent-evolve:reflection-contrast:v1\x00"
65
+ HELD_OUT_SELECTOR_POLICY_ID = "held_out_asn_origin_score_swap"
66
+ HELD_OUT_SELECTOR_POLICY_VERSION = 1
67
+ _ASSIGNMENT_COMMITMENT_DOMAIN = (
68
+ b"agent-evolve:held-out-asn-assignment-commitment:v1\x00"
69
+ )
70
+
71
+
72
+ class ReflectiveFeedbackContractError(ValueError):
73
+ """Diagnostic evidence or reflected cards violated the frozen contract."""
74
+
75
+
76
+ class HeldOutAssignmentUnavailableReason(str, Enum):
77
+ """Closed, experiment-safe reasons for omitting the held-out A/S/N wave."""
78
+
79
+ REFLECTED_CARD_BATCH_UNAVAILABLE = "reflected_card_batch_unavailable"
80
+ EQUAL_ORIGIN_SCORES = "equal_origin_scores"
81
+ STRUCTURALLY_INAPPLICABLE_ASSIGNMENT = "structurally_inapplicable_assignment"
82
+
83
+
84
+ class HeldOutAssignmentUnavailable(ValueError):
85
+ """The A/S/N block cannot be constructed for one closed safe reason."""
86
+
87
+ def __init__(
88
+ self,
89
+ reason: HeldOutAssignmentUnavailableReason,
90
+ detail: str,
91
+ ) -> None:
92
+ if type(reason) is not HeldOutAssignmentUnavailableReason:
93
+ raise TypeError(
94
+ "reason must be an exact HeldOutAssignmentUnavailableReason"
95
+ )
96
+ if type(detail) is not str or not detail.strip() or detail != detail.strip():
97
+ raise ValueError("detail must be non-empty canonical text")
98
+ self.reason = reason
99
+ self.detail = detail
100
+ super().__init__(f"{reason.value}: {detail}")
101
+
102
+
103
+ class OutcomeReflector(Protocol):
104
+ """Narrow engine capability consumed by the feedback interceptor."""
105
+
106
+ async def reflect(
107
+ self,
108
+ outcomes: tuple[InvocationOutcome, ...],
109
+ *,
110
+ label: str,
111
+ max_insights: int,
112
+ insight_contract: ReflectionInsightContract | None = None,
113
+ ) -> tuple[InsightMemoryEntry, ...]: ...
114
+
115
+ def identify_phenotype(self, configuration): ...
116
+
117
+
118
+ def reflection_contrast_id(outcome: InvocationOutcome) -> str:
119
+ """Reproduce the engine-owned contrast identity for one-parent variation."""
120
+
121
+ if type(outcome) is not InvocationOutcome:
122
+ raise TypeError("outcome must be an exact InvocationOutcome")
123
+ if len(outcome.prepared.plan.parents) != 1:
124
+ raise ReflectiveFeedbackContractError(
125
+ "v7 diagnostic reflection requires exactly one parent per outcome"
126
+ )
127
+ parent = outcome.prepared.plan.parents[0]
128
+ return hashlib.sha256(
129
+ _REFLECTION_CONTRAST_DOMAIN
130
+ + outcome.prepared.operator_invocation_id.value.encode("ascii")
131
+ + b"\x00"
132
+ + parent.candidate_id.value.encode("ascii")
133
+ ).hexdigest()
134
+
135
+
136
+ def _ternary_reward(outcome: InvocationOutcome) -> int:
137
+ reward = outcome.reward
138
+ if reward not in {-1.0, 0.0, 1.0}:
139
+ raise ReflectiveFeedbackContractError(
140
+ "diagnostic reward must be exactly one of -1, 0, +1"
141
+ )
142
+ return int(reward)
143
+
144
+
145
+ def _successful_diagnostic_outcomes(
146
+ outcomes: tuple[InvocationOutcome, ...],
147
+ ) -> tuple[InvocationOutcome, InvocationOutcome]:
148
+ if type(outcomes) is not tuple or any(
149
+ type(outcome) is not InvocationOutcome for outcome in outcomes
150
+ ):
151
+ raise TypeError("outcomes must contain exact InvocationOutcome values")
152
+ if len(outcomes) != 2:
153
+ raise ReflectiveFeedbackContractError(
154
+ "v7 diagnostic reflection requires exactly two outcomes"
155
+ )
156
+ generations = {outcome.prepared.plan.generation for outcome in outcomes}
157
+ if generations != {1}:
158
+ raise ReflectiveFeedbackContractError(
159
+ "v7 diagnostic reflection is restricted to generation one"
160
+ )
161
+ configuration_hashes: set[str] = set()
162
+ parent_ids: set[CandidateId] = set()
163
+ for outcome in outcomes:
164
+ candidate = outcome.candidate
165
+ if (
166
+ outcome.failure_stage is not None
167
+ or candidate is None
168
+ or not candidate.valid
169
+ or not candidate.operator_compliant
170
+ or not candidate.evidence_compliant
171
+ ):
172
+ raise ReflectiveFeedbackContractError(
173
+ "every diagnostic outcome must be a compliant successful candidate"
174
+ )
175
+ if len(outcome.prepared.plan.parents) != 1:
176
+ raise ReflectiveFeedbackContractError(
177
+ "every diagnostic outcome must have exactly one parent"
178
+ )
179
+ parent_ids.add(outcome.prepared.plan.parents[0].candidate_id)
180
+ configuration_hashes.add(candidate.occurrence.configuration_hash)
181
+ _ternary_reward(outcome)
182
+ if len(parent_ids) != 1:
183
+ raise ReflectiveFeedbackContractError(
184
+ "both diagnostic outcomes must share one frozen parent"
185
+ )
186
+ if len(configuration_hashes) != 2:
187
+ raise ReflectiveFeedbackContractError(
188
+ "diagnostic candidates must have distinct exact configurations"
189
+ )
190
+ return outcomes
191
+
192
+
193
+ @dataclass(frozen=True, slots=True, order=True)
194
+ class ReflectedCard:
195
+ reference: InsightRef
196
+ origin_contrast_id: str
197
+ origin_transfer_score: int
198
+
199
+ def __post_init__(self) -> None:
200
+ if type(self.reference) is not InsightRef:
201
+ raise TypeError("reference must be an exact InsightRef")
202
+ InsightRef.__post_init__(self.reference)
203
+ if (
204
+ type(self.origin_contrast_id) is not str
205
+ or len(self.origin_contrast_id) != 64
206
+ or any(
207
+ character not in "0123456789abcdef"
208
+ for character in self.origin_contrast_id
209
+ )
210
+ ):
211
+ raise ValueError("origin_contrast_id must be a lowercase SHA-256 digest")
212
+ if type(self.origin_transfer_score) is not int or (
213
+ self.origin_transfer_score not in {-1, 0, 1}
214
+ ):
215
+ raise ValueError("origin_transfer_score must be exactly -1, 0, or +1")
216
+
217
+
218
+ @dataclass(frozen=True, slots=True)
219
+ class ReflectedCardBatch:
220
+ source_generation: int
221
+ diagnostic_parent_id: CandidateId
222
+ cards: tuple[ReflectedCard, ReflectedCard]
223
+ reflection_logical_calls: int = 1
224
+
225
+ def __post_init__(self) -> None:
226
+ if type(self.source_generation) is not int or self.source_generation != 1:
227
+ raise ValueError("source_generation must be exactly one")
228
+ if type(self.diagnostic_parent_id) is not CandidateId:
229
+ raise TypeError("diagnostic_parent_id must be an exact CandidateId")
230
+ CandidateId.__post_init__(self.diagnostic_parent_id)
231
+ if (
232
+ type(self.cards) is not tuple
233
+ or len(self.cards) != 2
234
+ or any(type(card) is not ReflectedCard for card in self.cards)
235
+ ):
236
+ raise TypeError("cards must contain exactly two ReflectedCard values")
237
+ for card in self.cards:
238
+ ReflectedCard.__post_init__(card)
239
+ canonical = tuple(sorted(self.cards, key=lambda card: card.origin_contrast_id))
240
+ if self.cards != canonical:
241
+ raise ValueError("cards must be ordered by origin contrast identity")
242
+ if len({card.reference for card in self.cards}) != 2:
243
+ raise ValueError("reflected cards must use distinct insight references")
244
+ if len({card.origin_contrast_id for card in self.cards}) != 2:
245
+ raise ValueError("reflected cards must cite distinct origin contrasts")
246
+ if (
247
+ type(self.reflection_logical_calls) is not int
248
+ or self.reflection_logical_calls <= 0
249
+ ):
250
+ raise ValueError("reflection_logical_calls must be positive")
251
+
252
+ @property
253
+ def feedback_metadata(self) -> tuple[tuple[str, str], ...]:
254
+ rows: list[tuple[str, str]] = [
255
+ ("card_count", "2"),
256
+ ("diagnostic_parent_id", self.diagnostic_parent_id.value),
257
+ ("schema", _METADATA_SCHEMA),
258
+ ("source_generation", str(self.source_generation)),
259
+ ("status", "ready"),
260
+ ]
261
+ for index, card in enumerate(self.cards):
262
+ prefix = f"card.{index}"
263
+ rows.extend(
264
+ (
265
+ (f"{prefix}.insight_id", card.reference.insight_id.value),
266
+ (f"{prefix}.insight_version", str(card.reference.version)),
267
+ (f"{prefix}.origin_contrast_id", card.origin_contrast_id),
268
+ (
269
+ f"{prefix}.origin_transfer_score",
270
+ str(card.origin_transfer_score),
271
+ ),
272
+ )
273
+ )
274
+ return tuple(sorted(rows))
275
+
276
+
277
+ def _entry_for_reference(
278
+ memory: InsightMemoryBank,
279
+ reference: InsightRef,
280
+ ) -> InsightMemoryEntry:
281
+ matches = tuple(entry for entry in memory.entries if entry.reference == reference)
282
+ if len(matches) != 1:
283
+ raise ValueError(
284
+ "assignment references an insight absent from the bound memory bank"
285
+ )
286
+ return matches[0]
287
+
288
+
289
+ def build_reflected_card_batch(
290
+ *,
291
+ outcomes: tuple[InvocationOutcome, ...],
292
+ entries: tuple[InsightMemoryEntry, ...],
293
+ insight_contract: ReflectionInsightContract | None = None,
294
+ reflection_logical_calls: int = 1,
295
+ ) -> ReflectedCardBatch:
296
+ """Validate one-to-one exact citations and bind engine-derived scores."""
297
+
298
+ first, second = _successful_diagnostic_outcomes(outcomes)
299
+ ordered_outcomes = (first, second)
300
+ contrast_to_outcome = {
301
+ reflection_contrast_id(outcome): outcome for outcome in ordered_outcomes
302
+ }
303
+ expected_contrasts = set(contrast_to_outcome)
304
+ if type(entries) is not tuple or any(
305
+ type(entry) is not InsightMemoryEntry for entry in entries
306
+ ):
307
+ raise TypeError("entries must contain exact InsightMemoryEntry values")
308
+ if len(entries) != 2:
309
+ raise ReflectiveFeedbackContractError(
310
+ "reflection must yield exactly two accepted insight entries"
311
+ )
312
+ if type(reflection_logical_calls) is not int or reflection_logical_calls <= 0:
313
+ raise ValueError("reflection_logical_calls must be positive")
314
+
315
+ available_sets = tuple(
316
+ set(entry.evidence_lineage.available_contrast_ids)
317
+ for entry in entries
318
+ if entry.evidence_lineage is not None
319
+ )
320
+ full_batch_lineage = len(available_sets) == len(entries) and all(
321
+ available == expected_contrasts for available in available_sets
322
+ )
323
+ singleton_lineage = len(available_sets) == len(entries) and all(
324
+ len(available) == 1 for available in available_sets
325
+ )
326
+ if full_batch_lineage == singleton_lineage:
327
+ raise ReflectiveFeedbackContractError(
328
+ "reflected cards must use one consistent full-batch or singleton lineage mode"
329
+ )
330
+
331
+ cards: list[ReflectedCard] = []
332
+ cited: set[str] = set()
333
+ for entry in entries:
334
+ if insight_contract is not None:
335
+ try:
336
+ validate_reflection_insight_draft(
337
+ entry.draft,
338
+ insight_contract,
339
+ )
340
+ except (TypeError, ValueError) as exc:
341
+ raise ReflectiveFeedbackContractError(
342
+ "reflected card violates the actionable insight contract"
343
+ ) from exc
344
+ if (
345
+ entry.origin is not InsightOrigin.REFLECTION
346
+ or entry.lifecycle_state is not InsightLifecycleState.QUARANTINED
347
+ or entry.retrievable
348
+ or entry.evidence_lineage is None
349
+ ):
350
+ raise ReflectiveFeedbackContractError(
351
+ "every reflected card must be a non-retrievable quarantine entry"
352
+ )
353
+ lineage = entry.evidence_lineage
354
+ if len(lineage.cited_contrast_ids) != 1:
355
+ raise ReflectiveFeedbackContractError(
356
+ "each reflected card must cite exactly one full origin contrast"
357
+ )
358
+ contrast_id = lineage.cited_contrast_ids[0]
359
+ if singleton_lineage and lineage.available_contrast_ids != (contrast_id,):
360
+ raise ReflectiveFeedbackContractError(
361
+ "singleton reflected-card lineage differs from its exact citation"
362
+ )
363
+ if contrast_id not in expected_contrasts or contrast_id in cited:
364
+ raise ReflectiveFeedbackContractError(
365
+ "reflected cards must form a one-to-one exact contrast assignment"
366
+ )
367
+ if entry.draft.evidence_contrast_ids != (contrast_id,):
368
+ raise ReflectiveFeedbackContractError(
369
+ "reflected draft citation differs from its evidence lineage"
370
+ )
371
+ outcome = contrast_to_outcome[contrast_id]
372
+ candidate = outcome.candidate
373
+ assert candidate is not None
374
+ expected_operator_ids = (outcome.prepared.operator_invocation_id,)
375
+ expected_candidate_ids = tuple(
376
+ sorted(
377
+ (
378
+ outcome.prepared.plan.parents[0].candidate_id,
379
+ candidate.candidate_id,
380
+ )
381
+ )
382
+ )
383
+ if lineage.source_operator_invocation_ids != expected_operator_ids or (
384
+ lineage.source_candidate_ids != expected_candidate_ids
385
+ ):
386
+ raise ReflectiveFeedbackContractError(
387
+ "reflected evidence lineage differs from its cited contrast"
388
+ )
389
+ cited.add(contrast_id)
390
+ cards.append(
391
+ ReflectedCard(
392
+ reference=entry.reference,
393
+ origin_contrast_id=contrast_id,
394
+ origin_transfer_score=_ternary_reward(outcome),
395
+ )
396
+ )
397
+ if cited != expected_contrasts:
398
+ raise ReflectiveFeedbackContractError(
399
+ "reflection omitted a diagnostic origin contrast"
400
+ )
401
+ return ReflectedCardBatch(
402
+ source_generation=1,
403
+ diagnostic_parent_id=ordered_outcomes[0].prepared.plan.parents[0].candidate_id,
404
+ cards=tuple(sorted(cards, key=lambda card: card.origin_contrast_id)), # type: ignore[arg-type]
405
+ reflection_logical_calls=reflection_logical_calls,
406
+ )
407
+
408
+
409
+ class ReflectedCardMailbox:
410
+ """Write-once typed handoff from feedback to the next planner call."""
411
+
412
+ def __init__(self) -> None:
413
+ self._batches: dict[int, ReflectedCardBatch] = {}
414
+
415
+ def publish(self, batch: ReflectedCardBatch) -> None:
416
+ if type(batch) is not ReflectedCardBatch:
417
+ raise TypeError("batch must be an exact ReflectedCardBatch")
418
+ ReflectedCardBatch.__post_init__(batch)
419
+ if batch.source_generation in self._batches:
420
+ raise ReflectiveFeedbackContractError(
421
+ "a reflected card batch was already published for this generation"
422
+ )
423
+ self._batches[batch.source_generation] = batch
424
+
425
+ def read_verified(
426
+ self,
427
+ *,
428
+ state: OptimizerState,
429
+ source_generation: int = 1,
430
+ ) -> ReflectedCardBatch:
431
+ """Read only if the next planner state authenticates the same metadata."""
432
+
433
+ if type(state) is not OptimizerState:
434
+ raise TypeError("state must be an exact OptimizerState")
435
+ if type(source_generation) is not int or source_generation != 1:
436
+ raise ValueError("source_generation must be exactly one")
437
+ receipts = tuple(
438
+ receipt
439
+ for receipt in state.feedback_receipts
440
+ if receipt.generation == source_generation
441
+ )
442
+ if len(receipts) != 1:
443
+ raise ReflectiveFeedbackContractError(
444
+ "planner state lacks one exact source feedback receipt"
445
+ )
446
+ receipt = receipts[0]
447
+ validate_generation_feedback_receipt(receipt)
448
+ if (
449
+ receipt.policy_id != REFLECTIVE_FEEDBACK_POLICY_ID
450
+ or receipt.policy_version != REFLECTIVE_FEEDBACK_POLICY_VERSION
451
+ ):
452
+ raise ReflectiveFeedbackContractError(
453
+ "planner feedback receipt names a different feedback policy"
454
+ )
455
+ try:
456
+ batch = self._batches[source_generation]
457
+ except KeyError as exc:
458
+ metadata = dict(receipt.result_metadata)
459
+ status = metadata.get("status")
460
+ reason = metadata.get("reason")
461
+ expected_calls = {
462
+ "diagnostic_rejected": 0,
463
+ "reflection_failed": receipt.reserved_logical_llm_calls,
464
+ "reflection_rejected": receipt.reserved_logical_llm_calls,
465
+ }.get(status)
466
+ if (
467
+ expected_calls is None
468
+ or type(reason) is not str
469
+ or not reason
470
+ or receipt.used_logical_llm_calls != expected_calls
471
+ or receipt.result_metadata != _status_metadata(status, reason)
472
+ ):
473
+ raise ReflectiveFeedbackContractError(
474
+ "a missing card batch lacks one authenticated unavailable status"
475
+ ) from exc
476
+ raise HeldOutAssignmentUnavailable(
477
+ HeldOutAssignmentUnavailableReason.REFLECTED_CARD_BATCH_UNAVAILABLE,
478
+ f"no accepted reflected card batch is available after {status}",
479
+ ) from exc
480
+ if (
481
+ receipt.used_logical_llm_calls != batch.reflection_logical_calls
482
+ or receipt.result_metadata != batch.feedback_metadata
483
+ ):
484
+ raise ReflectiveFeedbackContractError(
485
+ "planner feedback receipt differs from the typed card mailbox"
486
+ )
487
+ return batch
488
+
489
+
490
+ def _status_metadata(status: str, reason: str) -> tuple[tuple[str, str], ...]:
491
+ return tuple(
492
+ sorted(
493
+ (
494
+ ("card_count", "0"),
495
+ ("reason", reason),
496
+ ("schema", _METADATA_SCHEMA),
497
+ ("source_generation", "1"),
498
+ ("status", status),
499
+ )
500
+ )
501
+ )
502
+
503
+
504
+ @dataclass(slots=True)
505
+ class G1ReflectionFeedbackInterceptor:
506
+ """Spend the reserved reflection calls after a valid sealed G1 block."""
507
+
508
+ engine: OutcomeReflector
509
+ mailbox: ReflectedCardMailbox
510
+ diagnostic_slot_ids: tuple[str, str] = ("D-S", "D-T")
511
+ reflection_label: str = "v7_g1_outcome_reflection"
512
+ required_metric_ids: tuple[str, ...] = ()
513
+ allowed_option_families: tuple[str, ...] = ()
514
+ allowed_option_ids: tuple[str, ...] = ()
515
+ reflection_logical_calls: int = 1
516
+
517
+ def __post_init__(self) -> None:
518
+ if not callable(getattr(self.engine, "reflect", None)):
519
+ raise TypeError("engine must provide async reflect")
520
+ if not callable(getattr(self.engine, "identify_phenotype", None)):
521
+ raise TypeError("engine must provide phenotype identity")
522
+ if type(self.mailbox) is not ReflectedCardMailbox:
523
+ raise TypeError("mailbox must be an exact ReflectedCardMailbox")
524
+ if (
525
+ type(self.diagnostic_slot_ids) is not tuple
526
+ or len(self.diagnostic_slot_ids) != 2
527
+ or any(
528
+ type(value) is not str or not value
529
+ for value in self.diagnostic_slot_ids
530
+ )
531
+ or len(set(self.diagnostic_slot_ids)) != 2
532
+ ):
533
+ raise ValueError("diagnostic_slot_ids must contain two distinct IDs")
534
+ if type(self.reflection_label) is not str or not self.reflection_label:
535
+ raise ValueError("reflection_label must be non-empty")
536
+ if bool(self.required_metric_ids) != bool(self.allowed_option_families):
537
+ raise ValueError(
538
+ "advanced reflection requires metrics and option families together"
539
+ )
540
+ if self.required_metric_ids:
541
+ ReflectionInsightContract(
542
+ required_metric_ids=self.required_metric_ids,
543
+ allowed_option_families=self.allowed_option_families,
544
+ allowed_option_ids=self.allowed_option_ids,
545
+ )
546
+ elif self.allowed_option_ids:
547
+ raise ValueError("exact option IDs require an advanced reflection contract")
548
+ if (
549
+ type(self.reflection_logical_calls) is not int
550
+ or self.reflection_logical_calls <= 0
551
+ ):
552
+ raise ValueError("reflection_logical_calls must be positive")
553
+
554
+ @property
555
+ def insight_contract(self) -> ReflectionInsightContract | None:
556
+ if not self.required_metric_ids:
557
+ return None
558
+ return ReflectionInsightContract(
559
+ required_metric_ids=self.required_metric_ids,
560
+ allowed_option_families=self.allowed_option_families,
561
+ allowed_option_ids=self.allowed_option_ids,
562
+ )
563
+
564
+ def reserve(
565
+ self,
566
+ *,
567
+ state: OptimizerState,
568
+ plan,
569
+ ) -> GenerationFeedbackReservation:
570
+ del state
571
+ generation = plan.generation
572
+ return GenerationFeedbackReservation(
573
+ policy_id=REFLECTIVE_FEEDBACK_POLICY_ID,
574
+ policy_version=REFLECTIVE_FEEDBACK_POLICY_VERSION,
575
+ logical_llm_calls=(
576
+ self.reflection_logical_calls if generation == 1 else 0
577
+ ),
578
+ metadata=(("scheduled_generation", "1"),),
579
+ )
580
+
581
+ async def after_generation(
582
+ self,
583
+ context: GenerationFeedbackContext,
584
+ ) -> GenerationFeedbackResult:
585
+ if context.plan.generation != 1:
586
+ return GenerationFeedbackResult(
587
+ logical_llm_calls_used=0,
588
+ metadata=_status_metadata("not_scheduled", "generation_is_not_one"),
589
+ )
590
+ results_by_slot = {
591
+ result.slot.slot_id: result.outcome
592
+ for result in context.generation_receipt.slot_results
593
+ }
594
+ if set(results_by_slot) != set(self.diagnostic_slot_ids):
595
+ return GenerationFeedbackResult(
596
+ logical_llm_calls_used=0,
597
+ metadata=_status_metadata(
598
+ "diagnostic_rejected",
599
+ "diagnostic_slot_ids_differ",
600
+ ),
601
+ )
602
+ outcomes = tuple(
603
+ results_by_slot[slot_id] for slot_id in self.diagnostic_slot_ids
604
+ )
605
+ try:
606
+ _successful_diagnostic_outcomes(outcomes) # type: ignore[arg-type]
607
+ except (TypeError, ReflectiveFeedbackContractError) as exc:
608
+ return GenerationFeedbackResult(
609
+ logical_llm_calls_used=0,
610
+ metadata=_status_metadata(
611
+ "diagnostic_rejected",
612
+ type(exc).__name__,
613
+ ),
614
+ )
615
+ phenotype_ids = {
616
+ self.engine.identify_phenotype(
617
+ outcome.candidate.configuration
618
+ ).identity_sha256
619
+ for outcome in outcomes
620
+ if outcome.candidate is not None
621
+ }
622
+ if len(phenotype_ids) != 2:
623
+ return GenerationFeedbackResult(
624
+ logical_llm_calls_used=0,
625
+ metadata=_status_metadata(
626
+ "diagnostic_rejected",
627
+ "diagnostic_phenotype_collision",
628
+ ),
629
+ )
630
+ try:
631
+ contract = self.insight_contract
632
+ if contract is None:
633
+ entries = await self.engine.reflect(
634
+ outcomes, # type: ignore[arg-type]
635
+ label=self.reflection_label,
636
+ max_insights=2,
637
+ min_insights=2,
638
+ )
639
+ else:
640
+ entries = await self.engine.reflect(
641
+ outcomes, # type: ignore[arg-type]
642
+ label=self.reflection_label,
643
+ max_insights=2,
644
+ min_insights=2,
645
+ insight_contract=contract,
646
+ )
647
+ except asyncio.CancelledError:
648
+ raise
649
+ except Exception as exc:
650
+ if (
651
+ classify_generation_failure(exc)
652
+ is not GenerationFailureDisposition.MODEL_OR_SCHEMA_FAILURE
653
+ ):
654
+ # Credential, source-integrity, queue publication, provider,
655
+ # and programming failures are fatal experiment failures. An
656
+ # untyped exception must never masquerade as a clean no-card
657
+ # condition.
658
+ raise
659
+ return GenerationFeedbackResult(
660
+ logical_llm_calls_used=self.reflection_logical_calls,
661
+ metadata=_status_metadata(
662
+ "reflection_failed",
663
+ GenerationFailureDisposition.MODEL_OR_SCHEMA_FAILURE.value,
664
+ ),
665
+ )
666
+ try:
667
+ batch = build_reflected_card_batch(
668
+ outcomes=outcomes, # type: ignore[arg-type]
669
+ entries=entries,
670
+ insight_contract=self.insight_contract,
671
+ reflection_logical_calls=self.reflection_logical_calls,
672
+ )
673
+ except (TypeError, ReflectiveFeedbackContractError) as exc:
674
+ return GenerationFeedbackResult(
675
+ logical_llm_calls_used=self.reflection_logical_calls,
676
+ metadata=_status_metadata(
677
+ "reflection_rejected",
678
+ type(exc).__name__,
679
+ ),
680
+ )
681
+ self.mailbox.publish(batch)
682
+ return GenerationFeedbackResult(
683
+ logical_llm_calls_used=self.reflection_logical_calls,
684
+ metadata=batch.feedback_metadata,
685
+ )
686
+
687
+
688
+ class HeldOutArm(str, Enum):
689
+ ADAPTIVE = "adaptive"
690
+ SCORE_SWAPPED = "score_swapped"
691
+ SHAM = "sham"
692
+
693
+
694
+ @dataclass(frozen=True, slots=True)
695
+ class HeldOutArmAssignment:
696
+ arm: HeldOutArm
697
+ reference: InsightRef
698
+ origin_transfer_score: int | None
699
+ assigned_selection_score: int | None
700
+
701
+
702
+ @dataclass(frozen=True, slots=True)
703
+ class HeldOutASNAssignments:
704
+ adaptive: HeldOutArmAssignment
705
+ score_swapped: HeldOutArmAssignment
706
+ sham: HeldOutArmAssignment
707
+
708
+ def __post_init__(self) -> None:
709
+ if self.adaptive.arm is not HeldOutArm.ADAPTIVE:
710
+ raise ValueError("adaptive assignment has the wrong arm")
711
+ if self.score_swapped.arm is not HeldOutArm.SCORE_SWAPPED:
712
+ raise ValueError("score-swapped assignment has the wrong arm")
713
+ if self.sham.arm is not HeldOutArm.SHAM:
714
+ raise ValueError("sham assignment has the wrong arm")
715
+ if self.adaptive.reference == self.score_swapped.reference:
716
+ raise ValueError(
717
+ "adaptive and score-swapped arms must select different cards"
718
+ )
719
+
720
+
721
+ @dataclass(frozen=True, slots=True)
722
+ class HeldOutScoreMapEntry:
723
+ """One card's immutable origin and assigned score in a selector map."""
724
+
725
+ reference: InsightRef
726
+ origin_contrast_id: str
727
+ origin_transfer_score: int
728
+ assigned_selection_score: int
729
+
730
+ def __post_init__(self) -> None:
731
+ ReflectedCard(
732
+ reference=self.reference,
733
+ origin_contrast_id=self.origin_contrast_id,
734
+ origin_transfer_score=self.origin_transfer_score,
735
+ )
736
+ if type(self.assigned_selection_score) is not int or (
737
+ self.assigned_selection_score not in {-1, 0, 1}
738
+ ):
739
+ raise ValueError(
740
+ "assigned_selection_score must be exactly -1, 0, or +1"
741
+ )
742
+
743
+ def to_record(self) -> dict[str, object]:
744
+ self.__post_init__()
745
+ return {
746
+ "insight_id": self.reference.insight_id.value,
747
+ "insight_version": self.reference.version,
748
+ "origin_contrast_id": self.origin_contrast_id,
749
+ "origin_transfer_score": self.origin_transfer_score,
750
+ "assigned_selection_score": self.assigned_selection_score,
751
+ }
752
+
753
+
754
+ def _reference_record(reference: InsightRef) -> dict[str, object]:
755
+ if type(reference) is not InsightRef:
756
+ raise TypeError("reference must be an exact InsightRef")
757
+ InsightRef.__post_init__(reference)
758
+ return {
759
+ "insight_id": reference.insight_id.value,
760
+ "insight_version": reference.version,
761
+ }
762
+
763
+
764
+ @dataclass(frozen=True, slots=True)
765
+ class HeldOutASNAssignmentCommitment:
766
+ """Authenticated full true/swapped selector assignment for one A/S/N wave.
767
+
768
+ Both two-card maps are retained so a downstream prequeue gate can prove
769
+ that the prompts it is about to release correspond to the prospectively
770
+ selected correct, score-swapped, and sham references. The commitment is
771
+ benchmark-neutral: domains choose cards and actions, while this record
772
+ binds only selector semantics and immutable insight references.
773
+ """
774
+
775
+ true_score_map: tuple[HeldOutScoreMapEntry, HeldOutScoreMapEntry]
776
+ score_swapped_map: tuple[HeldOutScoreMapEntry, HeldOutScoreMapEntry]
777
+ common_score_multiset: tuple[int, int]
778
+ adaptive_reference: InsightRef
779
+ score_swapped_reference: InsightRef
780
+ sham_reference: InsightRef
781
+ selector_policy_id: str = HELD_OUT_SELECTOR_POLICY_ID
782
+ selector_policy_version: int = HELD_OUT_SELECTOR_POLICY_VERSION
783
+
784
+ def __post_init__(self) -> None:
785
+ if self.selector_policy_id != HELD_OUT_SELECTOR_POLICY_ID:
786
+ raise ValueError("assignment names a different held-out selector policy")
787
+ if self.selector_policy_version != HELD_OUT_SELECTOR_POLICY_VERSION:
788
+ raise ValueError("assignment names a different selector policy version")
789
+ maps = (self.true_score_map, self.score_swapped_map)
790
+ if any(
791
+ type(score_map) is not tuple
792
+ or len(score_map) != 2
793
+ or any(type(item) is not HeldOutScoreMapEntry for item in score_map)
794
+ for score_map in maps
795
+ ):
796
+ raise TypeError("each score map must contain two exact map entries")
797
+ for score_map in maps:
798
+ for item in score_map:
799
+ item.__post_init__()
800
+ if score_map != tuple(
801
+ sorted(
802
+ score_map,
803
+ key=lambda item: (
804
+ item.reference.insight_id.value,
805
+ item.reference.version,
806
+ ),
807
+ )
808
+ ):
809
+ raise ValueError("score maps must be canonically reference-ordered")
810
+ true_by_reference = {item.reference: item for item in self.true_score_map}
811
+ swapped_by_reference = {
812
+ item.reference: item for item in self.score_swapped_map
813
+ }
814
+ if len(true_by_reference) != 2 or set(true_by_reference) != set(
815
+ swapped_by_reference
816
+ ):
817
+ raise ValueError("true and swapped maps must bind the same two cards")
818
+ for reference, true_entry in true_by_reference.items():
819
+ swapped_entry = swapped_by_reference[reference]
820
+ if (
821
+ true_entry.origin_contrast_id != swapped_entry.origin_contrast_id
822
+ or true_entry.origin_transfer_score
823
+ != swapped_entry.origin_transfer_score
824
+ or true_entry.assigned_selection_score
825
+ != true_entry.origin_transfer_score
826
+ ):
827
+ raise ValueError("score maps changed immutable card provenance")
828
+ if type(self.common_score_multiset) is not tuple or (
829
+ len(self.common_score_multiset) != 2
830
+ ):
831
+ raise TypeError("common_score_multiset must contain exactly two scores")
832
+ origin_scores = tuple(
833
+ sorted(item.origin_transfer_score for item in self.true_score_map)
834
+ )
835
+ if self.common_score_multiset != origin_scores:
836
+ raise ValueError("common score multiset differs from origin scores")
837
+ if tuple(
838
+ sorted(item.assigned_selection_score for item in self.true_score_map)
839
+ ) != self.common_score_multiset or tuple(
840
+ sorted(
841
+ item.assigned_selection_score for item in self.score_swapped_map
842
+ )
843
+ ) != self.common_score_multiset:
844
+ raise ValueError("true and swapped maps must share one score multiset")
845
+ if all(
846
+ true_by_reference[reference].assigned_selection_score
847
+ == swapped_by_reference[reference].assigned_selection_score
848
+ for reference in true_by_reference
849
+ ):
850
+ raise ValueError("score-swapped map must actually exchange the scores")
851
+ for reference in (
852
+ self.adaptive_reference,
853
+ self.score_swapped_reference,
854
+ self.sham_reference,
855
+ ):
856
+ _reference_record(reference)
857
+ if self.adaptive_reference not in true_by_reference:
858
+ raise ValueError("adaptive reference is absent from the true score map")
859
+ if self.score_swapped_reference not in true_by_reference:
860
+ raise ValueError(
861
+ "score-swapped reference is absent from the reflected score maps"
862
+ )
863
+ if self.adaptive_reference == self.score_swapped_reference:
864
+ raise ValueError("adaptive and score-swapped references must differ")
865
+ if self.sham_reference in true_by_reference:
866
+ raise ValueError("sham reference must be outside both reflected maps")
867
+ high_score = max(self.common_score_multiset)
868
+ if (
869
+ true_by_reference[self.adaptive_reference].assigned_selection_score
870
+ != high_score
871
+ or swapped_by_reference[
872
+ self.score_swapped_reference
873
+ ].assigned_selection_score
874
+ != high_score
875
+ ):
876
+ raise ValueError("chosen A/S references do not receive the high score")
877
+
878
+ def _unsigned_record(self) -> dict[str, object]:
879
+ self.__post_init__()
880
+ return {
881
+ "schema_version": 1,
882
+ "selector_policy_id": self.selector_policy_id,
883
+ "selector_policy_version": self.selector_policy_version,
884
+ "true_score_map": [item.to_record() for item in self.true_score_map],
885
+ "score_swapped_map": [
886
+ item.to_record() for item in self.score_swapped_map
887
+ ],
888
+ "common_score_multiset": list(self.common_score_multiset),
889
+ "chosen_references": {
890
+ "adaptive": _reference_record(self.adaptive_reference),
891
+ "score_swapped": _reference_record(
892
+ self.score_swapped_reference
893
+ ),
894
+ "sham": _reference_record(self.sham_reference),
895
+ },
896
+ }
897
+
898
+ @property
899
+ def assignment_sha256(self) -> str:
900
+ payload = json.dumps(
901
+ self._unsigned_record(),
902
+ allow_nan=False,
903
+ ensure_ascii=True,
904
+ separators=(",", ":"),
905
+ sort_keys=True,
906
+ ).encode("ascii")
907
+ return hashlib.sha256(_ASSIGNMENT_COMMITMENT_DOMAIN + payload).hexdigest()
908
+
909
+ def to_record(self) -> dict[str, object]:
910
+ record = self._unsigned_record()
911
+ return {**record, "assignment_sha256": self.assignment_sha256}
912
+
913
+
914
+ @dataclass(frozen=True, slots=True)
915
+ class HeldOutASNPlanSet:
916
+ assignments: HeldOutASNAssignments
917
+ assignment_commitment: HeldOutASNAssignmentCommitment
918
+ adaptive: InvocationPlan
919
+ score_swapped: InvocationPlan
920
+ sham: InvocationPlan
921
+
922
+
923
+ def register_neutral_sham_card(
924
+ *,
925
+ memory: InsightMemoryBank,
926
+ affected_paths: tuple[str, ...],
927
+ applicable_operator_kinds: tuple[str, ...],
928
+ insight_contract: ReflectionInsightContract | None = None,
929
+ ) -> InsightRef:
930
+ """Register a schema-matched exploratory hypothesis with neutral effects."""
931
+
932
+ if insight_contract is not None:
933
+ if type(insight_contract) is not ReflectionInsightContract:
934
+ raise TypeError(
935
+ "insight_contract must be an exact ReflectionInsightContract"
936
+ )
937
+ ReflectionInsightContract.__post_init__(insight_contract)
938
+ entry, _ = memory.add(
939
+ InsightDraft(
940
+ claim=(
941
+ "The supplied palette contains legal coordinated interventions "
942
+ "in each listed option family."
943
+ ),
944
+ trigger=("The frozen parent admits the listed finite action families."),
945
+ mechanism=(
946
+ "Each finite option specifies an internally consistent coordinated "
947
+ "change within its named family."
948
+ ),
949
+ affected_paths=affected_paths,
950
+ evidence_summary="Schema-matched factual palette description.",
951
+ confidence=0.5,
952
+ evidence_contrast_ids=(),
953
+ effect_predictions=(
954
+ ()
955
+ if insight_contract is None
956
+ else tuple(
957
+ MetricEffectPrediction(
958
+ metric_id=metric_id,
959
+ direction=MetricEffectDirection.UNKNOWN,
960
+ )
961
+ for metric_id in insight_contract.required_metric_ids
962
+ )
963
+ ),
964
+ recommended_option_families=(
965
+ ()
966
+ if insight_contract is None
967
+ else insight_contract.allowed_option_families
968
+ ),
969
+ recommended_option_ids=(
970
+ ()
971
+ if insight_contract is None
972
+ else insight_contract.allowed_option_ids
973
+ ),
974
+ action_template=(
975
+ None
976
+ if insight_contract is None
977
+ else (
978
+ "A legal option is represented by its named family and sealed "
979
+ "option identifier."
980
+ )
981
+ ),
982
+ falsification_condition=(
983
+ None
984
+ if insight_contract is None
985
+ else (
986
+ "The two named held-out metric values are the complete empirical "
987
+ "check for the intervention."
988
+ )
989
+ ),
990
+ ),
991
+ applicable_operator_kinds=applicable_operator_kinds,
992
+ origin=InsightOrigin.MANUAL,
993
+ )
994
+ if insight_contract is not None:
995
+ validate_reflection_insight_draft(
996
+ entry.draft,
997
+ insight_contract,
998
+ allow_all_unknown=True,
999
+ allow_missing_evidence=True,
1000
+ )
1001
+ if any(
1002
+ prediction.direction is not MetricEffectDirection.UNKNOWN
1003
+ for prediction in entry.draft.effect_predictions
1004
+ ):
1005
+ raise ValueError("the neutral sham must use only unknown predictions")
1006
+ if (
1007
+ entry.draft.recommended_option_families
1008
+ != insight_contract.allowed_option_families
1009
+ ):
1010
+ raise ValueError(
1011
+ "the neutral sham must expose the complete option-family vocabulary"
1012
+ )
1013
+ if entry.draft.recommended_option_ids != insight_contract.allowed_option_ids:
1014
+ raise ValueError(
1015
+ "the neutral sham must expose the precommitted exact option IDs"
1016
+ )
1017
+ return entry.reference
1018
+
1019
+
1020
+ def _path_text(path: JsonPath) -> str:
1021
+ parts = ["$"]
1022
+ for segment in path.segments:
1023
+ if type(segment) is ObjectKey:
1024
+ parts.append(f".{segment.value}")
1025
+ elif type(segment) is ArrayIndex:
1026
+ parts.append(f"[{segment.value}]")
1027
+ else: # pragma: no cover - JsonPath closes the segment union.
1028
+ raise AssertionError("unsupported JSON path segment")
1029
+ return "".join(parts)
1030
+
1031
+
1032
+ def _editable_paths(plan: InvocationPlan) -> tuple[str, ...] | None:
1033
+ contract = plan.mutation_contract
1034
+ if contract is not None:
1035
+ return tuple(_path_text(path) for path in contract.editable_paths)
1036
+ if plan.allowed_top_level:
1037
+ return tuple(f"$.{key}" for key in plan.allowed_top_level)
1038
+ return None
1039
+
1040
+
1041
+ def _exact_treatment_requirement(
1042
+ *,
1043
+ memory: InsightMemoryBank,
1044
+ reference: InsightRef,
1045
+ plan: InvocationPlan,
1046
+ assignment_role: TreatmentAssignmentRole,
1047
+ ) -> InsightTreatmentRequirement:
1048
+ """Bind one assigned card to its exact options in this parent palette."""
1049
+
1050
+ entry = _entry_for_reference(memory, reference)
1051
+ evidence = TreatmentInsightEvidence(
1052
+ reference=entry.reference,
1053
+ insight_content_sha256=entry.draft.content_sha256,
1054
+ applicable_operator_kinds=entry.applicable_operator_kinds,
1055
+ affected_paths=tuple(sorted(entry.draft.affected_paths)),
1056
+ recommended_option_families=tuple(
1057
+ sorted(entry.draft.recommended_option_families)
1058
+ ),
1059
+ recommended_option_ids=tuple(sorted(entry.draft.recommended_option_ids)),
1060
+ )
1061
+ if not evidence.recommended_option_ids:
1062
+ raise ValueError(
1063
+ "exact treatment assignment requires card-recommended option IDs"
1064
+ )
1065
+ contract = plan.finite_variation_contract
1066
+ if contract is None:
1067
+ raise ValueError("exact treatment assignment requires a finite contract")
1068
+ allowed_actions = tuple(
1069
+ sorted(
1070
+ (
1071
+ TreatmentActionBinding(
1072
+ option_id=option_id,
1073
+ option_identity_sha256=contract.resolve(
1074
+ option_id
1075
+ ).identity_sha256,
1076
+ )
1077
+ for option_id in evidence.recommended_option_ids
1078
+ ),
1079
+ key=lambda value: (value.option_id, value.option_identity_sha256),
1080
+ )
1081
+ )
1082
+ return InsightTreatmentRequirement(
1083
+ insight_bindings=(evidence.binding(),),
1084
+ finite_contract_sha256=contract.identity_sha256,
1085
+ allowed_actions=allowed_actions,
1086
+ claim_mode=TreatmentClaimMode.EXACT_REQUIRED,
1087
+ assignment_role=assignment_role,
1088
+ )
1089
+
1090
+
1091
+ def _plan_shape(plan: InvocationPlan) -> tuple[object, ...]:
1092
+ return (
1093
+ plan.operator_kind,
1094
+ plan.parents,
1095
+ plan.generation,
1096
+ plan.common_ancestor,
1097
+ plan.allowed_top_level,
1098
+ plan.phase,
1099
+ plan.mutation_contract,
1100
+ plan.mutation_response_mode,
1101
+ plan.atomic_replacement_options,
1102
+ plan.finite_variation_contract,
1103
+ plan.memory_subset_size,
1104
+ )
1105
+
1106
+
1107
+ @dataclass(frozen=True, slots=True)
1108
+ class HeldOutASNPlannerAdapter:
1109
+ """Resolve correct/swapped/sham cards and bind three matched base plans."""
1110
+
1111
+ mailbox: ReflectedCardMailbox
1112
+ memory: InsightMemoryBank
1113
+ sham_reference: InsightRef
1114
+
1115
+ def __post_init__(self) -> None:
1116
+ if type(self.mailbox) is not ReflectedCardMailbox:
1117
+ raise TypeError("mailbox must be an exact ReflectedCardMailbox")
1118
+ if type(self.memory) is not InsightMemoryBank:
1119
+ raise TypeError("memory must be an exact InsightMemoryBank")
1120
+ if type(self.sham_reference) is not InsightRef:
1121
+ raise TypeError("sham_reference must be an exact InsightRef")
1122
+ sham = _entry_for_reference(self.memory, self.sham_reference)
1123
+ if (
1124
+ sham.origin is not InsightOrigin.MANUAL
1125
+ or sham.lifecycle_state is not InsightLifecycleState.QUARANTINED
1126
+ or sham.evidence_lineage is not None
1127
+ or sham.draft.evidence_contrast_ids
1128
+ ):
1129
+ raise ValueError(
1130
+ "sham_reference must identify a manual, evidence-free quarantine card"
1131
+ )
1132
+
1133
+ def resolve(self, state: OptimizerState) -> HeldOutASNAssignments:
1134
+ batch = self.mailbox.read_verified(state=state)
1135
+ if batch.cards[0].origin_transfer_score == batch.cards[1].origin_transfer_score:
1136
+ raise HeldOutAssignmentUnavailable(
1137
+ HeldOutAssignmentUnavailableReason.EQUAL_ORIGIN_SCORES,
1138
+ "tied origin scores provide no correct-vs-swapped contrast",
1139
+ )
1140
+ low, high = sorted(
1141
+ batch.cards,
1142
+ key=lambda card: (card.origin_transfer_score, card.origin_contrast_id),
1143
+ )
1144
+ if self.sham_reference in {low.reference, high.reference}:
1145
+ raise ReflectiveFeedbackContractError(
1146
+ "sham card must differ from both outcome-grounded cards"
1147
+ )
1148
+ return HeldOutASNAssignments(
1149
+ adaptive=HeldOutArmAssignment(
1150
+ HeldOutArm.ADAPTIVE,
1151
+ high.reference,
1152
+ high.origin_transfer_score,
1153
+ high.origin_transfer_score,
1154
+ ),
1155
+ score_swapped=HeldOutArmAssignment(
1156
+ HeldOutArm.SCORE_SWAPPED,
1157
+ low.reference,
1158
+ low.origin_transfer_score,
1159
+ high.origin_transfer_score,
1160
+ ),
1161
+ sham=HeldOutArmAssignment(
1162
+ HeldOutArm.SHAM,
1163
+ self.sham_reference,
1164
+ None,
1165
+ None,
1166
+ ),
1167
+ )
1168
+
1169
+ def bind_plans(
1170
+ self,
1171
+ state: OptimizerState,
1172
+ *,
1173
+ adaptive_base: InvocationPlan,
1174
+ score_swapped_base: InvocationPlan,
1175
+ sham_base: InvocationPlan,
1176
+ ) -> HeldOutASNPlanSet:
1177
+ bases = (adaptive_base, score_swapped_base, sham_base)
1178
+ if any(type(plan) is not InvocationPlan for plan in bases):
1179
+ raise TypeError("held-out bases must be exact InvocationPlan values")
1180
+ for plan in bases:
1181
+ InvocationPlan.__post_init__(plan)
1182
+ if (
1183
+ plan.use_memory
1184
+ or plan.quarantine_test_insights
1185
+ or plan.resolved_insight_assignment is not None
1186
+ ):
1187
+ raise ValueError(
1188
+ "held-out base plans must not carry memory assignments"
1189
+ )
1190
+ if len({plan.label for plan in bases}) != 3:
1191
+ raise ValueError("held-out base plan labels must be distinct")
1192
+ first_shape = _plan_shape(bases[0])
1193
+ if any(_plan_shape(plan) != first_shape for plan in bases[1:]):
1194
+ raise ValueError("held-out base plans differ outside label and assignment")
1195
+ if adaptive_base.generation != 2 or state.generation != 1:
1196
+ raise ValueError("the held-out A/S/N block must be generation two")
1197
+
1198
+ batch = self.mailbox.read_verified(state=state)
1199
+ if len(adaptive_base.parents) != 1:
1200
+ raise ValueError("held-out A/S/N plans require exactly one shared parent")
1201
+ if adaptive_base.parents[0].candidate_id == batch.diagnostic_parent_id:
1202
+ raise ValueError("held-out parent must differ from the diagnostic parent")
1203
+ assignments = self.resolve(state)
1204
+ references = (
1205
+ assignments.adaptive.reference,
1206
+ assignments.score_swapped.reference,
1207
+ assignments.sham.reference,
1208
+ )
1209
+ for plan, reference in zip(bases, references, strict=True):
1210
+ try:
1211
+ self.memory.validate_quarantine_test_assignment(
1212
+ (reference,),
1213
+ operator_kind=plan.operator_kind.value,
1214
+ editable_paths=_editable_paths(plan),
1215
+ )
1216
+ except QuarantineAssignmentStructuralError as exc:
1217
+ raise HeldOutAssignmentUnavailable(
1218
+ HeldOutAssignmentUnavailableReason.STRUCTURALLY_INAPPLICABLE_ASSIGNMENT,
1219
+ (
1220
+ "one otherwise valid quarantine card is structurally "
1221
+ "inapplicable to its matched held-out plan"
1222
+ ),
1223
+ ) from exc
1224
+ try:
1225
+ treatment_requirements = tuple(
1226
+ _exact_treatment_requirement(
1227
+ memory=self.memory,
1228
+ reference=reference,
1229
+ plan=plan,
1230
+ assignment_role=(
1231
+ TreatmentAssignmentRole.SHAM_CONTROL
1232
+ if index == 2
1233
+ else TreatmentAssignmentRole.ACTIVE
1234
+ ),
1235
+ )
1236
+ for index, (plan, reference) in enumerate(
1237
+ zip(bases, references, strict=True)
1238
+ )
1239
+ )
1240
+ except (TypeError, ValueError) as exc:
1241
+ raise HeldOutAssignmentUnavailable(
1242
+ HeldOutAssignmentUnavailableReason.STRUCTURALLY_INAPPLICABLE_ASSIGNMENT,
1243
+ (
1244
+ "one assigned card lacks an exact action binding in its "
1245
+ "matched held-out finite palette"
1246
+ ),
1247
+ ) from exc
1248
+ true_entries = tuple(
1249
+ sorted(
1250
+ (
1251
+ HeldOutScoreMapEntry(
1252
+ reference=card.reference,
1253
+ origin_contrast_id=card.origin_contrast_id,
1254
+ origin_transfer_score=card.origin_transfer_score,
1255
+ assigned_selection_score=card.origin_transfer_score,
1256
+ )
1257
+ for card in batch.cards
1258
+ ),
1259
+ key=lambda item: (
1260
+ item.reference.insight_id.value,
1261
+ item.reference.version,
1262
+ ),
1263
+ )
1264
+ )
1265
+ swapped_scores = tuple(
1266
+ reversed(tuple(item.origin_transfer_score for item in true_entries))
1267
+ )
1268
+ score_swapped_entries = tuple(
1269
+ replace(item, assigned_selection_score=score)
1270
+ for item, score in zip(true_entries, swapped_scores, strict=True)
1271
+ )
1272
+ assignment_commitment = HeldOutASNAssignmentCommitment(
1273
+ true_score_map=true_entries, # type: ignore[arg-type]
1274
+ score_swapped_map=score_swapped_entries, # type: ignore[arg-type]
1275
+ common_score_multiset=tuple( # type: ignore[arg-type]
1276
+ sorted(item.origin_transfer_score for item in true_entries)
1277
+ ),
1278
+ adaptive_reference=assignments.adaptive.reference,
1279
+ score_swapped_reference=assignments.score_swapped.reference,
1280
+ sham_reference=assignments.sham.reference,
1281
+ )
1282
+ return HeldOutASNPlanSet(
1283
+ assignments=assignments,
1284
+ assignment_commitment=assignment_commitment,
1285
+ adaptive=replace(
1286
+ adaptive_base,
1287
+ quarantine_test_insights=(assignments.adaptive.reference,),
1288
+ insight_treatment_requirement=treatment_requirements[0],
1289
+ ),
1290
+ score_swapped=replace(
1291
+ score_swapped_base,
1292
+ quarantine_test_insights=(assignments.score_swapped.reference,),
1293
+ insight_treatment_requirement=treatment_requirements[1],
1294
+ ),
1295
+ sham=replace(
1296
+ sham_base,
1297
+ quarantine_test_insights=(assignments.sham.reference,),
1298
+ insight_treatment_requirement=treatment_requirements[2],
1299
+ ),
1300
+ )
1301
+
1302
+
1303
+ __all__ = [
1304
+ "G1ReflectionFeedbackInterceptor",
1305
+ "HeldOutASNAssignments",
1306
+ "HeldOutASNAssignmentCommitment",
1307
+ "HeldOutASNPlanSet",
1308
+ "HeldOutASNPlannerAdapter",
1309
+ "HeldOutArm",
1310
+ "HeldOutArmAssignment",
1311
+ "HeldOutScoreMapEntry",
1312
+ "HELD_OUT_SELECTOR_POLICY_ID",
1313
+ "HELD_OUT_SELECTOR_POLICY_VERSION",
1314
+ "HeldOutAssignmentUnavailable",
1315
+ "HeldOutAssignmentUnavailableReason",
1316
+ "REFLECTIVE_FEEDBACK_POLICY_ID",
1317
+ "REFLECTIVE_FEEDBACK_POLICY_VERSION",
1318
+ "ReflectedCard",
1319
+ "ReflectedCardBatch",
1320
+ "ReflectedCardMailbox",
1321
+ "ReflectiveFeedbackContractError",
1322
+ "build_reflected_card_batch",
1323
+ "reflection_contrast_id",
1324
+ "register_neutral_sham_card",
1325
+ ]