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,1613 @@
1
+ """Card-blind empirical feedback from evaluated portfolio actions.
2
+
3
+ The selector's complete card/action attribution remains in its internal audit.
4
+ This module exposes only treatment-neutral action facts to later generations:
5
+ sealed option identity, categorical forecasts, meaningful observed directions,
6
+ correctness, parent relation, and reward. Candidate-attributable infeasibility
7
+ is retained as a no-yield action but is never converted into metric calibration
8
+ or a parent relation. Consequently a currently unassigned card cannot leak back
9
+ through outcome history.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import hashlib
15
+ import json
16
+ import math
17
+ import re
18
+ from dataclasses import dataclass, field
19
+ from enum import Enum
20
+ from typing import Protocol, Sequence
21
+
22
+ from agent_evolve.application.agentic_evolution import EvolutionCandidate
23
+ from agent_evolve.application.decision_metric_projection import (
24
+ project_candidate_decision_metrics,
25
+ )
26
+ from agent_evolve.application.contextual_campaign_outcomes import (
27
+ ContextualPortfolioOutcomeBatch,
28
+ observe_contextual_portfolio_outcomes,
29
+ )
30
+ from agent_evolve.application.contextual_search_controller import (
31
+ ContextualSearchLedger,
32
+ )
33
+ from agent_evolve.application.outcome_relation import OutcomeRelation
34
+ from agent_evolve.application.evolution_campaign import (
35
+ ArchiveUtilitySnapshot,
36
+ CampaignGenerationKind,
37
+ )
38
+ from agent_evolve.application.campaign_execution import CampaignStageRequest
39
+ from agent_evolve.application.portfolio_evolution import (
40
+ PortfolioCandidateFailureEvidence,
41
+ PortfolioMemberDisposition,
42
+ PortfolioVariationWaveRequest,
43
+ PortfolioVariationWaveResult,
44
+ )
45
+ from agent_evolve.domain.patch import require_sha256
46
+ from agent_evolve.domain.typed_json import (
47
+ FrozenJsonObject,
48
+ freeze_json,
49
+ thaw_json,
50
+ typed_json_sha256,
51
+ )
52
+ from agent_evolve.policies.selection.forecast_calibration import (
53
+ BetaCorrectnessPrior,
54
+ ForecastCalibrationObservation,
55
+ ForecastCalibrationScope,
56
+ ForecastCalibrationSnapshot,
57
+ ForecastPredictionReceipt,
58
+ MeaningfulDirectionRequest,
59
+ MeaningfulMetricDirectionAdjudicator,
60
+ build_calibration_snapshot,
61
+ observe_forecast,
62
+ )
63
+ from agent_evolve.ports.decision_metric_projection import DecisionMetricProjection
64
+ from agent_evolve.ports.agentic_generator import MetricEffectDirection
65
+ from agent_evolve.ports.contextual_search_allocation import (
66
+ ContextualPortfolioAllocationRealization,
67
+ )
68
+
69
+
70
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
71
+ _CANONICAL_JSON_PATH = re.compile(
72
+ r"^\$\.[^.\[\]\s]+(?:\.[^.\[\]\s]+|\[(?:0|[1-9][0-9]*)\])*$"
73
+ )
74
+ _CANDIDATE_OUTCOME_DOMAIN = b"agent-evolve:portfolio-parent-outcome:v1\x00"
75
+ _ACTION_DOMAIN = b"agent-evolve:portfolio-action-feedback:v1\x00"
76
+ _RECEIPT_DOMAIN = b"agent-evolve:portfolio-outcome-feedback:v1\x00"
77
+ _METRIC_TRANSITION_DOMAIN = b"agent-evolve:decision-metric-transition:v1\x00"
78
+ _CONTEXTUAL_QUERY_DOMAIN = b"agent-evolve:contextual-outcome-query:v1\x00"
79
+ _CONTEXTUAL_HISTORY_DOMAIN = b"agent-evolve:contextual-outcome-history:v1\x00"
80
+
81
+
82
+ def _canonical_json(value: object) -> bytes:
83
+ return json.dumps(
84
+ value,
85
+ allow_nan=False,
86
+ ensure_ascii=True,
87
+ separators=(",", ":"),
88
+ sort_keys=True,
89
+ ).encode("ascii", errors="strict")
90
+
91
+
92
+ def _hash(domain: bytes, value: object) -> str:
93
+ return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
94
+
95
+
96
+ def _candidate_outcome_sha256(candidate: EvolutionCandidate) -> str:
97
+ EvolutionCandidate.__post_init__(candidate)
98
+ return _hash(
99
+ _CANDIDATE_OUTCOME_DOMAIN,
100
+ {
101
+ "candidate_id": candidate.candidate_id.value,
102
+ "configuration_sha256": candidate.occurrence.configuration_hash,
103
+ "objectives": [
104
+ [metric_id, value.hex()] for metric_id, value in candidate.objectives
105
+ ],
106
+ "valid": candidate.valid,
107
+ "operator_compliant": candidate.operator_compliant,
108
+ "evidence_compliant": candidate.evidence_compliant,
109
+ "detailed_evaluation_sha256": (
110
+ None
111
+ if candidate.detailed_evaluation is None
112
+ else candidate.detailed_evaluation.evidence_sha256
113
+ ),
114
+ },
115
+ )
116
+
117
+
118
+ def _canonical_tokens(values: tuple[str, ...], *, name: str) -> tuple[str, ...]:
119
+ if type(values) is not tuple or any(
120
+ type(value) is not str or _TOKEN.fullmatch(value) is None for value in values
121
+ ):
122
+ raise TypeError(f"{name} must be an exact tuple of closed tokens")
123
+ if values != tuple(sorted(set(values))):
124
+ raise ValueError(f"{name} must be unique and canonically sorted")
125
+ return values
126
+
127
+
128
+ def _canonical_paths(values: tuple[str, ...], *, name: str) -> tuple[str, ...]:
129
+ if type(values) is not tuple or any(
130
+ type(value) is not str or _CANONICAL_JSON_PATH.fullmatch(value) is None
131
+ for value in values
132
+ ):
133
+ raise TypeError(f"{name} must be an exact candidate-path tuple")
134
+ if values != tuple(sorted(set(values))):
135
+ raise ValueError(f"{name} must be unique and canonically sorted")
136
+ return values
137
+
138
+
139
+ def _paths_overlap(first: str, second: str) -> bool:
140
+ return (
141
+ first == second
142
+ or first.startswith(second + ".")
143
+ or first.startswith(second + "[")
144
+ or second.startswith(first + ".")
145
+ or second.startswith(first + "[")
146
+ )
147
+
148
+
149
+ @dataclass(frozen=True, slots=True)
150
+ class DecisionMetricTransition:
151
+ """One engine-authored parent/child metric transition.
152
+
153
+ Direction-only history is insufficient for contextual transfer: the same
154
+ target can be an increase for one parent and a decrease for another. This
155
+ receipt preserves the exact numeric baseline and child value while keeping
156
+ the benchmark-owned meaningful-direction adjudication authoritative.
157
+ """
158
+
159
+ metric_id: str
160
+ parent_value: float
161
+ child_value: float
162
+ actual_direction: MetricEffectDirection
163
+ adjudication_receipt_sha256: str
164
+
165
+ def __post_init__(self) -> None:
166
+ if type(self.metric_id) is not str or _TOKEN.fullmatch(self.metric_id) is None:
167
+ raise ValueError("metric_id must use the closed token grammar")
168
+ for name in ("parent_value", "child_value"):
169
+ value = getattr(self, name)
170
+ if type(value) is not float or not math.isfinite(value):
171
+ raise TypeError(f"{name} must be a finite canonical float")
172
+ if (
173
+ type(self.actual_direction) is not MetricEffectDirection
174
+ or self.actual_direction is MetricEffectDirection.UNKNOWN
175
+ ):
176
+ raise ValueError("actual_direction must be a known metric direction")
177
+ require_sha256(
178
+ self.adjudication_receipt_sha256,
179
+ "adjudication_receipt_sha256",
180
+ )
181
+
182
+ def _unsigned_record(self) -> dict[str, object]:
183
+ self.__post_init__()
184
+ return {
185
+ "schema_version": 1,
186
+ "metric_id": self.metric_id,
187
+ "parent_value_hex": self.parent_value.hex(),
188
+ "child_value_hex": self.child_value.hex(),
189
+ "delta_hex": (self.child_value - self.parent_value).hex(),
190
+ "actual_direction": self.actual_direction.value,
191
+ "adjudication_receipt_sha256": self.adjudication_receipt_sha256,
192
+ }
193
+
194
+ @property
195
+ def transition_sha256(self) -> str:
196
+ return _hash(_METRIC_TRANSITION_DOMAIN, self._unsigned_record())
197
+
198
+ def to_record(self) -> dict[str, object]:
199
+ return {
200
+ **self._unsigned_record(),
201
+ "transition_sha256": self.transition_sha256,
202
+ }
203
+
204
+
205
+ class OutcomeTransferScope(str, Enum):
206
+ """How far an historical action is transferred to the current parent."""
207
+
208
+ SAME_PARENT = "same_parent"
209
+ SAME_LINEAGE = "same_lineage"
210
+ CROSS_LINEAGE_ANALOGY = "cross_lineage_analogy"
211
+
212
+
213
+ @dataclass(frozen=True, slots=True)
214
+ class ContextualOutcomeQuery:
215
+ """Deterministic prior-only retrieval with cross-lineage transfer opt-in."""
216
+
217
+ current_parent_candidate_id: str
218
+ current_parent_configuration_sha256: str
219
+ cutoff_wave_index_exclusive: int
220
+ lineage_candidate_ids: tuple[str, ...] = ()
221
+ families: tuple[str, ...] = ()
222
+ changed_paths: tuple[str, ...] = ()
223
+ max_actions: int = 24
224
+ include_cross_lineage_analogies: bool = False
225
+
226
+ def __post_init__(self) -> None:
227
+ if (
228
+ type(self.current_parent_candidate_id) is not str
229
+ or _TOKEN.fullmatch(self.current_parent_candidate_id) is None
230
+ ):
231
+ raise ValueError(
232
+ "current_parent_candidate_id must use the closed token grammar"
233
+ )
234
+ require_sha256(
235
+ self.current_parent_configuration_sha256,
236
+ "current_parent_configuration_sha256",
237
+ )
238
+ if (
239
+ type(self.cutoff_wave_index_exclusive) is not int
240
+ or self.cutoff_wave_index_exclusive <= 0
241
+ ):
242
+ raise ValueError("cutoff_wave_index_exclusive must be positive")
243
+ _canonical_tokens(self.lineage_candidate_ids, name="lineage_candidate_ids")
244
+ _canonical_tokens(self.families, name="families")
245
+ _canonical_paths(self.changed_paths, name="changed_paths")
246
+ if type(self.max_actions) is not int or self.max_actions <= 0:
247
+ raise ValueError("max_actions must be positive")
248
+ if type(self.include_cross_lineage_analogies) is not bool:
249
+ raise TypeError("include_cross_lineage_analogies must be an exact bool")
250
+
251
+ def _unsigned_record(self) -> dict[str, object]:
252
+ self.__post_init__()
253
+ return {
254
+ "schema_version": 1,
255
+ "current_parent_candidate_id": self.current_parent_candidate_id,
256
+ "current_parent_configuration_sha256": (
257
+ self.current_parent_configuration_sha256
258
+ ),
259
+ "cutoff_wave_index_exclusive": self.cutoff_wave_index_exclusive,
260
+ "lineage_candidate_ids": list(self.lineage_candidate_ids),
261
+ "families": list(self.families),
262
+ "changed_paths": list(self.changed_paths),
263
+ "max_actions": self.max_actions,
264
+ "include_cross_lineage_analogies": (self.include_cross_lineage_analogies),
265
+ }
266
+
267
+ @property
268
+ def query_sha256(self) -> str:
269
+ return _hash(_CONTEXTUAL_QUERY_DOMAIN, self._unsigned_record())
270
+
271
+ def to_record(self) -> dict[str, object]:
272
+ return {**self._unsigned_record(), "query_sha256": self.query_sha256}
273
+
274
+
275
+ @dataclass(frozen=True, slots=True)
276
+ class PortfolioActionOutcomeFeedback:
277
+ """One terminal selected action with no memory-card or rationale fields.
278
+
279
+ A scored action carries calibrated metric transitions and one parent
280
+ relation. Candidate-attributable infeasibility instead remains as a
281
+ no-yield action: it is useful search history, but cannot be laundered into
282
+ forecast calibration, a metric transition, or a parent comparison.
283
+ """
284
+
285
+ wave_index: int
286
+ request_sha256: str
287
+ ranked_decision_sha256: str
288
+ proposal_sha256: str
289
+ parent_candidate_id: str
290
+ parent_candidate_identity_sha256: str
291
+ parent_outcome_sha256: str
292
+ candidate_id: str
293
+ candidate_outcome_sha256: str
294
+ option_id: str
295
+ option_identity_sha256: str
296
+ family: str
297
+ changed_paths: tuple[str, ...]
298
+ observations: tuple[ForecastCalibrationObservation, ...]
299
+ parent_relation: OutcomeRelation | None
300
+ reward: float
301
+ dominates_parent: bool
302
+ better_than_parent: bool
303
+ metric_transitions: tuple[DecisionMetricTransition, ...] = ()
304
+ disposition: PortfolioMemberDisposition = PortfolioMemberDisposition.SCORED
305
+ candidate_failure: PortfolioCandidateFailureEvidence | None = None
306
+
307
+ def __post_init__(self) -> None:
308
+ if type(self.wave_index) is not int or self.wave_index <= 0:
309
+ raise ValueError("wave_index must be a positive exact integer")
310
+ for name in (
311
+ "request_sha256",
312
+ "ranked_decision_sha256",
313
+ "proposal_sha256",
314
+ "parent_candidate_identity_sha256",
315
+ "parent_outcome_sha256",
316
+ "candidate_outcome_sha256",
317
+ "option_identity_sha256",
318
+ ):
319
+ require_sha256(getattr(self, name), name)
320
+ for name in ("parent_candidate_id", "candidate_id", "option_id", "family"):
321
+ value = getattr(self, name)
322
+ if type(value) is not str or _TOKEN.fullmatch(value) is None:
323
+ raise ValueError(f"{name} must use the closed token grammar")
324
+ if self.parent_candidate_id == self.candidate_id:
325
+ raise ValueError("action feedback child cannot equal its parent")
326
+ if type(self.changed_paths) is not tuple or any(
327
+ type(value) is not str or not value.startswith("$.")
328
+ for value in self.changed_paths
329
+ ):
330
+ raise TypeError("changed_paths must be an exact candidate-path tuple")
331
+ if not self.changed_paths or self.changed_paths != tuple(
332
+ sorted(set(self.changed_paths))
333
+ ):
334
+ raise ValueError("changed_paths must be non-empty, unique, and canonical")
335
+ if type(self.observations) is not tuple or any(
336
+ type(value) is not ForecastCalibrationObservation
337
+ for value in self.observations
338
+ ):
339
+ raise TypeError("observations must contain exact calibration observations")
340
+ for value in self.observations:
341
+ value.revalidate()
342
+ metric_ids = tuple(value.prediction.metric_id for value in self.observations)
343
+ if metric_ids != tuple(sorted(set(metric_ids))):
344
+ raise ValueError("observations must use unique canonical metric order")
345
+ for value in self.observations:
346
+ prediction = value.prediction
347
+ if (
348
+ prediction.wave_index != self.wave_index
349
+ or prediction.selector_decision_sha256 != self.proposal_sha256
350
+ or prediction.parent_candidate_identity_sha256
351
+ != self.parent_candidate_identity_sha256
352
+ or prediction.option_id != self.option_id
353
+ or prediction.option_identity_sha256 != self.option_identity_sha256
354
+ or prediction.family != self.family
355
+ or value.adjudication.parent_outcome_sha256
356
+ != self.parent_outcome_sha256
357
+ or value.adjudication.child_outcome_sha256
358
+ != self.candidate_outcome_sha256
359
+ ):
360
+ raise ValueError("forecast observation belongs to a foreign action")
361
+ if type(self.reward) is not float or not math.isfinite(self.reward):
362
+ raise TypeError("reward must be a finite canonical float")
363
+ if (
364
+ type(self.dominates_parent) is not bool
365
+ or type(self.better_than_parent) is not bool
366
+ ):
367
+ raise TypeError("parent-comparison projections must be exact bools")
368
+ if type(self.metric_transitions) is not tuple or any(
369
+ type(value) is not DecisionMetricTransition
370
+ for value in self.metric_transitions
371
+ ):
372
+ raise TypeError(
373
+ "metric_transitions must contain exact DecisionMetricTransition values"
374
+ )
375
+ for value in self.metric_transitions:
376
+ DecisionMetricTransition.__post_init__(value)
377
+ transition_metric_ids = tuple(
378
+ value.metric_id for value in self.metric_transitions
379
+ )
380
+ if transition_metric_ids and transition_metric_ids != metric_ids:
381
+ raise ValueError(
382
+ "metric transitions must exactly cover observation metric order"
383
+ )
384
+ if self.metric_transitions:
385
+ for transition, observation in zip(
386
+ self.metric_transitions,
387
+ self.observations,
388
+ strict=True,
389
+ ):
390
+ numeric_request = MeaningfulDirectionRequest(
391
+ benchmark_sha256=(observation.prediction.scope.benchmark_sha256),
392
+ session_sha256=observation.prediction.scope.session_sha256,
393
+ wave_index=self.wave_index,
394
+ parent_candidate_identity_sha256=(
395
+ self.parent_candidate_identity_sha256
396
+ ),
397
+ option_id=self.option_id,
398
+ option_identity_sha256=self.option_identity_sha256,
399
+ metric_id=transition.metric_id,
400
+ parent_outcome_sha256=self.parent_outcome_sha256,
401
+ child_outcome_sha256=self.candidate_outcome_sha256,
402
+ parent_metric_value=transition.parent_value,
403
+ child_metric_value=transition.child_value,
404
+ )
405
+ try:
406
+ observation.adjudication.require_request(numeric_request)
407
+ except ValueError as error:
408
+ raise ValueError(
409
+ "metric transition numeric values differ from its "
410
+ "adjudication request"
411
+ ) from error
412
+ if (
413
+ transition.actual_direction
414
+ is not observation.adjudication.actual_direction
415
+ or transition.adjudication_receipt_sha256
416
+ != observation.adjudication.receipt_sha256
417
+ ):
418
+ raise ValueError(
419
+ "metric transition differs from its adjudication receipt"
420
+ )
421
+ if type(self.disposition) is not PortfolioMemberDisposition:
422
+ raise TypeError("disposition must be exact PortfolioMemberDisposition")
423
+ failure = self.candidate_failure
424
+ if self.disposition is PortfolioMemberDisposition.SCORED:
425
+ if not self.observations:
426
+ raise ValueError("scored action requires calibration observations")
427
+ if type(self.parent_relation) is not OutcomeRelation:
428
+ raise TypeError("scored action requires one exact parent relation")
429
+ if failure is not None:
430
+ raise ValueError("scored action cannot carry candidate failure evidence")
431
+ else:
432
+ if self.observations or self.metric_transitions:
433
+ raise ValueError(
434
+ "candidate-infeasible action cannot publish metric calibration"
435
+ )
436
+ if self.parent_relation is not None:
437
+ raise ValueError(
438
+ "candidate-infeasible action cannot publish a parent relation"
439
+ )
440
+ if self.dominates_parent or self.better_than_parent:
441
+ raise ValueError(
442
+ "candidate-infeasible action cannot publish improvement flags"
443
+ )
444
+ if type(failure) is not PortfolioCandidateFailureEvidence:
445
+ raise TypeError(
446
+ "candidate-infeasible action requires exact failure evidence"
447
+ )
448
+ PortfolioCandidateFailureEvidence.__post_init__(failure)
449
+
450
+ def _unsigned_record(self) -> dict[str, object]:
451
+ self.__post_init__()
452
+ record: dict[str, object] = {
453
+ "schema_version": 2 if self.metric_transitions else 1,
454
+ "wave_index": self.wave_index,
455
+ "request_sha256": self.request_sha256,
456
+ "ranked_decision_sha256": self.ranked_decision_sha256,
457
+ "proposal_sha256": self.proposal_sha256,
458
+ "parent_candidate_id": self.parent_candidate_id,
459
+ "parent_candidate_identity_sha256": (self.parent_candidate_identity_sha256),
460
+ "parent_outcome_sha256": self.parent_outcome_sha256,
461
+ "candidate_id": self.candidate_id,
462
+ "candidate_outcome_sha256": self.candidate_outcome_sha256,
463
+ "option_id": self.option_id,
464
+ "option_identity_sha256": self.option_identity_sha256,
465
+ "family": self.family,
466
+ "changed_paths": list(self.changed_paths),
467
+ "observations": [value.to_record() for value in self.observations],
468
+ "parent_relation": (
469
+ None if self.parent_relation is None else self.parent_relation.value
470
+ ),
471
+ "reward_hex": self.reward.hex(),
472
+ "dominates_parent": self.dominates_parent,
473
+ "better_than_parent": self.better_than_parent,
474
+ }
475
+ if self.metric_transitions:
476
+ record["metric_transitions"] = [
477
+ value.to_record() for value in self.metric_transitions
478
+ ]
479
+ if self.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE:
480
+ failure = self.candidate_failure
481
+ assert type(failure) is PortfolioCandidateFailureEvidence
482
+ record.update(
483
+ {
484
+ "schema_version": 3,
485
+ "disposition": self.disposition.value,
486
+ "yield_status": "candidate_infeasible_no_yield",
487
+ "candidate_failure": failure.to_record(),
488
+ "calibration_excluded": True,
489
+ "parent_relation_excluded": True,
490
+ }
491
+ )
492
+ return record
493
+
494
+ @property
495
+ def feedback_sha256(self) -> str:
496
+ return _hash(_ACTION_DOMAIN, self._unsigned_record())
497
+
498
+ def to_record(self) -> dict[str, object]:
499
+ return {**self._unsigned_record(), "feedback_sha256": self.feedback_sha256}
500
+
501
+ def to_prompt_record(self) -> dict[str, object]:
502
+ """Render only treatment-neutral empirical facts for future selectors."""
503
+
504
+ self.__post_init__()
505
+ transitions = {value.metric_id: value for value in self.metric_transitions}
506
+ if self.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE:
507
+ failure = self.candidate_failure
508
+ assert type(failure) is PortfolioCandidateFailureEvidence
509
+ return {
510
+ "wave_index": self.wave_index,
511
+ "option_id": self.option_id,
512
+ "option_identity_sha256": self.option_identity_sha256,
513
+ "family": self.family,
514
+ "changed_paths": list(self.changed_paths),
515
+ "outcome_status": "candidate_infeasible_no_yield",
516
+ "failure_code": failure.failure_code.value,
517
+ "failure_evidence_sha256": failure.evidence_sha256,
518
+ "metric_feedback": [],
519
+ "parent_relation": None,
520
+ "reward_hex": self.reward.hex(),
521
+ "dominates_parent": False,
522
+ "better_than_parent": False,
523
+ }
524
+ return {
525
+ "wave_index": self.wave_index,
526
+ "option_id": self.option_id,
527
+ "option_identity_sha256": self.option_identity_sha256,
528
+ "family": self.family,
529
+ "changed_paths": list(self.changed_paths),
530
+ "metric_feedback": [
531
+ {
532
+ "metric_id": value.prediction.metric_id,
533
+ "predicted_direction": value.prediction.asserted_direction.value,
534
+ "confidence": value.prediction.confidence.value,
535
+ "observed_direction": value.adjudication.actual_direction.value,
536
+ "correctness": value.correctness,
537
+ **(
538
+ {}
539
+ if value.prediction.metric_id not in transitions
540
+ else {
541
+ "parent_value_hex": transitions[
542
+ value.prediction.metric_id
543
+ ].parent_value.hex(),
544
+ "child_value_hex": transitions[
545
+ value.prediction.metric_id
546
+ ].child_value.hex(),
547
+ "delta_hex": (
548
+ transitions[value.prediction.metric_id].child_value
549
+ - transitions[value.prediction.metric_id].parent_value
550
+ ).hex(),
551
+ }
552
+ ),
553
+ }
554
+ for value in self.observations
555
+ ],
556
+ "parent_relation": self.parent_relation.value,
557
+ "reward_hex": self.reward.hex(),
558
+ "dominates_parent": self.dominates_parent,
559
+ "better_than_parent": self.better_than_parent,
560
+ }
561
+
562
+
563
+ def _classify_outcome_transfer_scope(
564
+ action: PortfolioActionOutcomeFeedback,
565
+ query: ContextualOutcomeQuery,
566
+ ) -> OutcomeTransferScope:
567
+ """Derive transfer distance solely from authenticated action/query facts."""
568
+
569
+ if action.parent_candidate_id == query.current_parent_candidate_id:
570
+ return OutcomeTransferScope.SAME_PARENT
571
+ if action.candidate_id == query.current_parent_candidate_id:
572
+ return OutcomeTransferScope.SAME_LINEAGE
573
+ if (
574
+ action.parent_candidate_identity_sha256
575
+ == query.current_parent_configuration_sha256
576
+ ):
577
+ return OutcomeTransferScope.SAME_PARENT
578
+ lineage = set(query.lineage_candidate_ids)
579
+ if action.parent_candidate_id in lineage or action.candidate_id in lineage:
580
+ return OutcomeTransferScope.SAME_LINEAGE
581
+ return OutcomeTransferScope.CROSS_LINEAGE_ANALOGY
582
+
583
+
584
+ @dataclass(frozen=True, slots=True)
585
+ class PortfolioOutcomeFeedbackReceipt:
586
+ """Complete all-members join for one sealed evaluated portfolio."""
587
+
588
+ wave_index: int
589
+ request_sha256: str
590
+ ranked_decision_sha256: str
591
+ scope: ForecastCalibrationScope
592
+ actions: tuple[PortfolioActionOutcomeFeedback, ...]
593
+ receipt_sha256: str = field(init=False, default="")
594
+
595
+ def __post_init__(self) -> None:
596
+ if type(self.wave_index) is not int or self.wave_index <= 0:
597
+ raise ValueError("wave_index must be a positive exact integer")
598
+ require_sha256(self.request_sha256, "request_sha256")
599
+ require_sha256(self.ranked_decision_sha256, "ranked_decision_sha256")
600
+ if type(self.scope) is not ForecastCalibrationScope:
601
+ raise TypeError("scope must be exact ForecastCalibrationScope")
602
+ self.scope.revalidate()
603
+ if (
604
+ type(self.actions) is not tuple
605
+ or not self.actions
606
+ or any(
607
+ type(value) is not PortfolioActionOutcomeFeedback
608
+ for value in self.actions
609
+ )
610
+ ):
611
+ raise ValueError("actions must contain exact feedback values")
612
+ for value in self.actions:
613
+ value.__post_init__()
614
+ if len({value.option_id for value in self.actions}) != len(self.actions):
615
+ raise ValueError("feedback receipt cannot repeat an action")
616
+ for value in self.actions:
617
+ if (
618
+ value.wave_index != self.wave_index
619
+ or value.request_sha256 != self.request_sha256
620
+ or value.ranked_decision_sha256 != self.ranked_decision_sha256
621
+ or any(
622
+ observation.prediction.scope != self.scope
623
+ for observation in value.observations
624
+ )
625
+ ):
626
+ raise ValueError("feedback action belongs to a foreign wave")
627
+ computed = _hash(_RECEIPT_DOMAIN, self._unsigned_record())
628
+ if self.receipt_sha256 not in ("", computed):
629
+ raise ValueError("receipt_sha256 does not authenticate feedback")
630
+ object.__setattr__(self, "receipt_sha256", computed)
631
+
632
+ def _unsigned_record(self) -> dict[str, object]:
633
+ record: dict[str, object] = {
634
+ "schema_version": 1,
635
+ "wave_index": self.wave_index,
636
+ "request_sha256": self.request_sha256,
637
+ "ranked_decision_sha256": self.ranked_decision_sha256,
638
+ "scope_sha256": self.scope.scope_sha256,
639
+ "actions": [value.to_record() for value in self.actions],
640
+ "treatment_visibility": "card_blind_action_outcomes_only",
641
+ }
642
+ infeasible_count = sum(
643
+ value.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE
644
+ for value in self.actions
645
+ )
646
+ if infeasible_count:
647
+ record.update(
648
+ {
649
+ "schema_version": 2,
650
+ "ranked_itt_action_count": len(self.actions),
651
+ "scored_action_count": len(self.actions) - infeasible_count,
652
+ "candidate_infeasible_action_count": infeasible_count,
653
+ "candidate_infeasible_semantics": (
654
+ "retained_as_no_yield_action_without_metric_calibration_or_"
655
+ "parent_relation"
656
+ ),
657
+ }
658
+ )
659
+ return record
660
+
661
+ def to_record(self) -> dict[str, object]:
662
+ self.__post_init__()
663
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
664
+
665
+
666
+ @dataclass(frozen=True, slots=True)
667
+ class ContextualOutcomeHistoryReceipt:
668
+ """Authenticated, relevance-ranked empirical history for one parent.
669
+
670
+ The receipt deliberately labels transfer distance. Cross-lineage facts are
671
+ analogies, never silently presented as though the same action had already
672
+ been evaluated on the current parent.
673
+ """
674
+
675
+ query: ContextualOutcomeQuery
676
+ actions: tuple[PortfolioActionOutcomeFeedback, ...]
677
+ transfer_scopes: tuple[OutcomeTransferScope, ...]
678
+ receipt_sha256: str = field(init=False, default="")
679
+
680
+ def __post_init__(self) -> None:
681
+ if type(self.query) is not ContextualOutcomeQuery:
682
+ raise TypeError("query must be an exact ContextualOutcomeQuery")
683
+ ContextualOutcomeQuery.__post_init__(self.query)
684
+ if type(self.actions) is not tuple or any(
685
+ type(value) is not PortfolioActionOutcomeFeedback for value in self.actions
686
+ ):
687
+ raise TypeError("actions must contain exact feedback values")
688
+ if type(self.transfer_scopes) is not tuple or any(
689
+ type(value) is not OutcomeTransferScope for value in self.transfer_scopes
690
+ ):
691
+ raise TypeError("transfer_scopes must contain exact scope values")
692
+ if len(self.actions) != len(self.transfer_scopes):
693
+ raise ValueError("actions and transfer_scopes must have equal length")
694
+ if len(self.actions) > self.query.max_actions:
695
+ raise ValueError("contextual history exceeds the requested bound")
696
+ if len({value.feedback_sha256 for value in self.actions}) != len(self.actions):
697
+ raise ValueError("contextual history cannot repeat feedback actions")
698
+ if any(
699
+ value.wave_index >= self.query.cutoff_wave_index_exclusive
700
+ for value in self.actions
701
+ ):
702
+ raise ValueError("contextual history contains same/future-wave evidence")
703
+ if (
704
+ not self.query.include_cross_lineage_analogies
705
+ and OutcomeTransferScope.CROSS_LINEAGE_ANALOGY in self.transfer_scopes
706
+ ):
707
+ raise ValueError("contextual history contains forbidden cross-lineage data")
708
+ expected_scopes = tuple(
709
+ _classify_outcome_transfer_scope(action, self.query)
710
+ for action in self.actions
711
+ )
712
+ if self.transfer_scopes != expected_scopes:
713
+ raise ValueError(
714
+ "contextual history transfer scopes differ from query lineage"
715
+ )
716
+ computed = _hash(_CONTEXTUAL_HISTORY_DOMAIN, self._unsigned_record())
717
+ if self.receipt_sha256 not in ("", computed):
718
+ raise ValueError("receipt_sha256 does not authenticate contextual history")
719
+ object.__setattr__(self, "receipt_sha256", computed)
720
+
721
+ def _unsigned_record(self) -> dict[str, object]:
722
+ return {
723
+ "schema_version": 1,
724
+ "query_sha256": self.query.query_sha256,
725
+ "actions": [
726
+ {
727
+ "feedback_sha256": action.feedback_sha256,
728
+ "transfer_scope": scope.value,
729
+ }
730
+ for action, scope in zip(
731
+ self.actions,
732
+ self.transfer_scopes,
733
+ strict=True,
734
+ )
735
+ ],
736
+ "epistemic_status": "observational_predictive_history_not_causal_credit",
737
+ }
738
+
739
+ def to_record(self) -> dict[str, object]:
740
+ self.__post_init__()
741
+ return {
742
+ **self._unsigned_record(),
743
+ "query": self.query.to_record(),
744
+ "receipt_sha256": self.receipt_sha256,
745
+ }
746
+
747
+ def to_prompt_record(self) -> FrozenJsonObject:
748
+ self.__post_init__()
749
+ frozen = freeze_json(
750
+ {
751
+ "schema_version": 1,
752
+ "receipt_sha256": self.receipt_sha256,
753
+ "query_sha256": self.query.query_sha256,
754
+ "cutoff_wave_index_exclusive": (self.query.cutoff_wave_index_exclusive),
755
+ "current_parent_candidate_id": (self.query.current_parent_candidate_id),
756
+ "current_parent_configuration_sha256": (
757
+ self.query.current_parent_configuration_sha256
758
+ ),
759
+ "actions": [
760
+ {
761
+ "transfer_scope": scope.value,
762
+ "source_parent_candidate_id": action.parent_candidate_id,
763
+ "source_parent_configuration_sha256": (
764
+ action.parent_candidate_identity_sha256
765
+ ),
766
+ "source_child_candidate_id": action.candidate_id,
767
+ "source_child_outcome_sha256": (
768
+ action.candidate_outcome_sha256
769
+ ),
770
+ "applicability": (
771
+ "direct_same_parent_evidence"
772
+ if scope is OutcomeTransferScope.SAME_PARENT
773
+ else (
774
+ "same_lineage_transfer_evidence"
775
+ if scope is OutcomeTransferScope.SAME_LINEAGE
776
+ else "cross_lineage_analogy_requires_revalidation"
777
+ )
778
+ ),
779
+ **action.to_prompt_record(),
780
+ }
781
+ for action, scope in zip(
782
+ self.actions,
783
+ self.transfer_scopes,
784
+ strict=True,
785
+ )
786
+ ],
787
+ "epistemic_status": (
788
+ "observational_predictive_history_not_causal_credit"
789
+ ),
790
+ "card_and_rationale_fields_excluded": True,
791
+ }
792
+ )
793
+ if type(frozen) is not FrozenJsonObject:
794
+ raise AssertionError("contextual outcome history did not freeze")
795
+ return frozen
796
+
797
+
798
+ def observe_selected_portfolio_forecasts(
799
+ *,
800
+ wave_index: int,
801
+ parent: EvolutionCandidate,
802
+ result: PortfolioVariationWaveResult,
803
+ selected_predictions: tuple[ForecastPredictionReceipt, ...],
804
+ adjudicator: MeaningfulMetricDirectionAdjudicator,
805
+ decision_metric_projection: DecisionMetricProjection | None = None,
806
+ ) -> PortfolioOutcomeFeedbackReceipt:
807
+ """Join only evaluated K4 forecasts to exact outcomes and adjudication.
808
+
809
+ The optional benchmark projection exposes semantic objective plus sealed
810
+ violation/constraint values. ``None`` intentionally retains the legacy
811
+ objective-map path and therefore its byte-identical feedback receipts.
812
+ """
813
+
814
+ if type(parent) is not EvolutionCandidate:
815
+ raise TypeError("parent must be exact EvolutionCandidate")
816
+ EvolutionCandidate.__post_init__(parent)
817
+ if type(result) is not PortfolioVariationWaveResult:
818
+ raise TypeError("result must be exact PortfolioVariationWaveResult")
819
+ PortfolioVariationWaveResult.__post_init__(result)
820
+ decision = result.selection_decision
821
+ if decision is None:
822
+ raise ValueError("forecast feedback requires the typed selection decision")
823
+ if type(selected_predictions) is not tuple or any(
824
+ type(value) is not ForecastPredictionReceipt for value in selected_predictions
825
+ ):
826
+ raise TypeError("selected_predictions must be an exact prediction tuple")
827
+ for value in selected_predictions:
828
+ value.revalidate()
829
+ if decision_metric_projection is not None:
830
+ if type(decision_metric_projection) is not DecisionMetricProjection:
831
+ raise TypeError("decision_metric_projection must be exact or None")
832
+ decision_metric_projection.__post_init__()
833
+ if not selected_predictions:
834
+ raise ValueError("forecast feedback requires selected predictions")
835
+ scopes = {value.scope for value in selected_predictions}
836
+ proposals = {value.selector_decision_sha256 for value in selected_predictions}
837
+ if len(scopes) != 1 or len(proposals) != 1:
838
+ raise ValueError("selected predictions mix calibration scopes or proposals")
839
+ scope = next(iter(scopes))
840
+ proposal_sha256 = next(iter(proposals))
841
+ parent_identity = parent.occurrence.configuration_hash
842
+ if any(
843
+ value.wave_index != wave_index
844
+ or value.parent_candidate_identity_sha256 != parent_identity
845
+ for value in selected_predictions
846
+ ):
847
+ raise ValueError("selected predictions belong to a foreign parent or wave")
848
+ by_option_metric = {
849
+ (value.option_id, value.metric_id): value for value in selected_predictions
850
+ }
851
+ if len(by_option_metric) != len(selected_predictions):
852
+ raise ValueError("selected predictions repeat an option/metric cell")
853
+ parent_outcome_sha256 = _candidate_outcome_sha256(parent)
854
+ parent_metrics = (
855
+ parent.objective_map
856
+ if decision_metric_projection is None
857
+ else project_candidate_decision_metrics(
858
+ parent,
859
+ decision_metric_projection,
860
+ ).metric_map
861
+ )
862
+ actions: list[PortfolioActionOutcomeFeedback] = []
863
+ for selected, member, outcome in zip(
864
+ decision.members,
865
+ result.receipt.members,
866
+ result.outcomes,
867
+ strict=True,
868
+ ):
869
+ materialization = member.materialization
870
+ child = outcome.candidate
871
+ if child is None: # Closed by result validation.
872
+ raise AssertionError("validated portfolio result lost its child")
873
+ if (
874
+ selected.option_id != materialization.option_id
875
+ or selected.option_identity_sha256 != materialization.option_identity_sha256
876
+ ):
877
+ raise ValueError("selection and materialization action identities differ")
878
+ predictions = tuple(
879
+ sorted(
880
+ (
881
+ value
882
+ for (option_id, _), value in by_option_metric.items()
883
+ if option_id == selected.option_id
884
+ ),
885
+ key=lambda value: value.metric_id,
886
+ )
887
+ )
888
+ if tuple(value.metric_id for value in predictions) != tuple(
889
+ sorted(parent_metrics)
890
+ ):
891
+ raise ValueError("selected forecasts differ from parent decision metrics")
892
+ if member.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE:
893
+ failure = member.candidate_failure
894
+ if type(failure) is not PortfolioCandidateFailureEvidence:
895
+ raise AssertionError(
896
+ "validated infeasible member lost candidate failure evidence"
897
+ )
898
+ actions.append(
899
+ PortfolioActionOutcomeFeedback(
900
+ wave_index=wave_index,
901
+ request_sha256=decision.request_sha256,
902
+ ranked_decision_sha256=decision.decision_sha256,
903
+ proposal_sha256=proposal_sha256,
904
+ parent_candidate_id=parent.candidate_id.value,
905
+ parent_candidate_identity_sha256=parent_identity,
906
+ parent_outcome_sha256=parent_outcome_sha256,
907
+ candidate_id=child.candidate_id.value,
908
+ candidate_outcome_sha256=member.outcome_sha256,
909
+ option_id=selected.option_id,
910
+ option_identity_sha256=selected.option_identity_sha256,
911
+ family=selected.family,
912
+ changed_paths=materialization.changed_paths,
913
+ observations=(),
914
+ parent_relation=None,
915
+ reward=member.reward,
916
+ dominates_parent=False,
917
+ better_than_parent=False,
918
+ disposition=member.disposition,
919
+ candidate_failure=failure,
920
+ )
921
+ )
922
+ continue
923
+ child_metrics = (
924
+ child.objective_map
925
+ if decision_metric_projection is None
926
+ else project_candidate_decision_metrics(
927
+ child,
928
+ decision_metric_projection,
929
+ ).metric_map
930
+ )
931
+ if set(child_metrics) != set(parent_metrics):
932
+ raise ValueError(
933
+ "selected forecasts differ from evaluated decision metrics"
934
+ )
935
+ observations: list[ForecastCalibrationObservation] = []
936
+ for prediction in predictions:
937
+ metric_id = prediction.metric_id
938
+ request = MeaningfulDirectionRequest(
939
+ benchmark_sha256=scope.benchmark_sha256,
940
+ session_sha256=scope.session_sha256,
941
+ wave_index=wave_index,
942
+ parent_candidate_identity_sha256=parent_identity,
943
+ option_id=selected.option_id,
944
+ option_identity_sha256=selected.option_identity_sha256,
945
+ metric_id=metric_id,
946
+ parent_outcome_sha256=parent_outcome_sha256,
947
+ child_outcome_sha256=member.outcome_sha256,
948
+ parent_metric_value=parent_metrics[metric_id],
949
+ child_metric_value=child_metrics[metric_id],
950
+ )
951
+ observations.append(observe_forecast(prediction, request, adjudicator))
952
+ actions.append(
953
+ PortfolioActionOutcomeFeedback(
954
+ wave_index=wave_index,
955
+ request_sha256=decision.request_sha256,
956
+ ranked_decision_sha256=decision.decision_sha256,
957
+ proposal_sha256=proposal_sha256,
958
+ parent_candidate_id=parent.candidate_id.value,
959
+ parent_candidate_identity_sha256=parent_identity,
960
+ parent_outcome_sha256=parent_outcome_sha256,
961
+ candidate_id=child.candidate_id.value,
962
+ candidate_outcome_sha256=member.outcome_sha256,
963
+ option_id=selected.option_id,
964
+ option_identity_sha256=selected.option_identity_sha256,
965
+ family=selected.family,
966
+ changed_paths=materialization.changed_paths,
967
+ observations=tuple(observations),
968
+ parent_relation=member.parent_relations[0],
969
+ reward=member.reward,
970
+ dominates_parent=member.dominates_any_parent,
971
+ better_than_parent=member.better_than_any_parent,
972
+ metric_transitions=tuple(
973
+ DecisionMetricTransition(
974
+ metric_id=observation.prediction.metric_id,
975
+ parent_value=float(
976
+ parent_metrics[observation.prediction.metric_id]
977
+ ),
978
+ child_value=float(
979
+ child_metrics[observation.prediction.metric_id]
980
+ ),
981
+ actual_direction=observation.adjudication.actual_direction,
982
+ adjudication_receipt_sha256=(
983
+ observation.adjudication.receipt_sha256
984
+ ),
985
+ )
986
+ for observation in observations
987
+ ),
988
+ disposition=member.disposition,
989
+ )
990
+ )
991
+ calibrated_cells = {
992
+ (action.option_id, observation.prediction.metric_id)
993
+ for action in actions
994
+ for observation in action.observations
995
+ }
996
+ excluded_cells = {
997
+ (action.option_id, prediction.metric_id)
998
+ for action in actions
999
+ if action.disposition is PortfolioMemberDisposition.CANDIDATE_INFEASIBLE
1000
+ for (option_id, _), prediction in by_option_metric.items()
1001
+ if option_id == action.option_id
1002
+ }
1003
+ if set(by_option_metric) != calibrated_cells.union(excluded_cells):
1004
+ raise ValueError("unevaluated or missing K8 forecasts entered K4 feedback")
1005
+ return PortfolioOutcomeFeedbackReceipt(
1006
+ wave_index=wave_index,
1007
+ request_sha256=decision.request_sha256,
1008
+ ranked_decision_sha256=decision.decision_sha256,
1009
+ scope=scope,
1010
+ actions=tuple(actions),
1011
+ )
1012
+
1013
+
1014
+ @dataclass(slots=True)
1015
+ class PortfolioOutcomeFeedbackLedger:
1016
+ """Append-only in-memory ledger with prior-wave snapshot projections."""
1017
+
1018
+ receipts: list[PortfolioOutcomeFeedbackReceipt] = field(default_factory=list)
1019
+
1020
+ def append(self, receipt: PortfolioOutcomeFeedbackReceipt) -> None:
1021
+ if type(receipt) is not PortfolioOutcomeFeedbackReceipt:
1022
+ raise TypeError("receipt must be exact PortfolioOutcomeFeedbackReceipt")
1023
+ receipt.__post_init__()
1024
+ if any(
1025
+ value.receipt_sha256 == receipt.receipt_sha256 for value in self.receipts
1026
+ ):
1027
+ raise ValueError("feedback ledger already contains this receipt")
1028
+ if any(
1029
+ value.request_sha256 == receipt.request_sha256 for value in self.receipts
1030
+ ):
1031
+ raise ValueError("feedback ledger already contains this selector request")
1032
+ self.receipts.append(receipt)
1033
+
1034
+ @property
1035
+ def observations(self) -> tuple[ForecastCalibrationObservation, ...]:
1036
+ return tuple(
1037
+ observation
1038
+ for receipt in self.receipts
1039
+ for action in receipt.actions
1040
+ for observation in action.observations
1041
+ )
1042
+
1043
+ def calibration_snapshot(
1044
+ self,
1045
+ *,
1046
+ scope: ForecastCalibrationScope,
1047
+ cutoff_wave_index_exclusive: int,
1048
+ prior: BetaCorrectnessPrior = BetaCorrectnessPrior(),
1049
+ family_min_support: int = 4,
1050
+ ) -> ForecastCalibrationSnapshot:
1051
+ return build_calibration_snapshot(
1052
+ self.observations,
1053
+ scope=scope,
1054
+ cutoff_wave_index_exclusive=cutoff_wave_index_exclusive,
1055
+ prior=prior,
1056
+ family_min_support=family_min_support,
1057
+ )
1058
+
1059
+ def prompt_history(
1060
+ self,
1061
+ *,
1062
+ cutoff_wave_index_exclusive: int,
1063
+ max_actions: int | None = None,
1064
+ ) -> FrozenJsonObject:
1065
+ if (
1066
+ type(cutoff_wave_index_exclusive) is not int
1067
+ or cutoff_wave_index_exclusive <= 0
1068
+ ):
1069
+ raise ValueError("cutoff_wave_index_exclusive must be positive")
1070
+ if max_actions is not None and (
1071
+ type(max_actions) is not int or max_actions <= 0
1072
+ ):
1073
+ raise ValueError("max_actions must be positive or None")
1074
+ actions = [
1075
+ action
1076
+ for receipt in self.receipts
1077
+ if receipt.wave_index < cutoff_wave_index_exclusive
1078
+ for action in receipt.actions
1079
+ ]
1080
+ actions.sort(
1081
+ key=lambda value: (
1082
+ value.wave_index,
1083
+ value.request_sha256,
1084
+ value.option_id,
1085
+ )
1086
+ )
1087
+ if max_actions is not None:
1088
+ actions = actions[-max_actions:]
1089
+ frozen = freeze_json(
1090
+ {
1091
+ "schema_version": 1,
1092
+ "cutoff_wave_index_exclusive": cutoff_wave_index_exclusive,
1093
+ "actions": [value.to_prompt_record() for value in actions],
1094
+ "treatment_visibility": "action_outcomes_only",
1095
+ }
1096
+ )
1097
+ if type(frozen) is not FrozenJsonObject:
1098
+ raise AssertionError("feedback history did not freeze as an object")
1099
+ return frozen
1100
+
1101
+ def contextual_history(
1102
+ self,
1103
+ query: ContextualOutcomeQuery,
1104
+ ) -> ContextualOutcomeHistoryReceipt:
1105
+ """Retrieve prior-only evidence with explicit parent-transfer distance.
1106
+
1107
+ Filtering is intentionally structural and deterministic in v1. It is
1108
+ safer than a learned similarity score while the latter lacks held-out
1109
+ calibration, and it makes the exact evidence set replayable.
1110
+ """
1111
+
1112
+ if type(query) is not ContextualOutcomeQuery:
1113
+ raise TypeError("query must be an exact ContextualOutcomeQuery")
1114
+ ContextualOutcomeQuery.__post_init__(query)
1115
+ ranked: list[
1116
+ tuple[
1117
+ tuple[int, int, str, str, str],
1118
+ PortfolioActionOutcomeFeedback,
1119
+ OutcomeTransferScope,
1120
+ ]
1121
+ ] = []
1122
+ for receipt in self.receipts:
1123
+ if receipt.wave_index >= query.cutoff_wave_index_exclusive:
1124
+ continue
1125
+ for action in receipt.actions:
1126
+ if query.families and action.family not in query.families:
1127
+ continue
1128
+ if query.changed_paths and not any(
1129
+ _paths_overlap(observed, requested)
1130
+ for observed in action.changed_paths
1131
+ for requested in query.changed_paths
1132
+ ):
1133
+ continue
1134
+ scope = _classify_outcome_transfer_scope(action, query)
1135
+ if scope is OutcomeTransferScope.SAME_PARENT:
1136
+ scope_rank = 0
1137
+ elif scope is OutcomeTransferScope.SAME_LINEAGE:
1138
+ scope_rank = 1
1139
+ else:
1140
+ scope_rank = 2
1141
+ if not query.include_cross_lineage_analogies:
1142
+ continue
1143
+ ranked.append(
1144
+ (
1145
+ (
1146
+ scope_rank,
1147
+ -action.wave_index,
1148
+ action.request_sha256,
1149
+ action.option_id,
1150
+ action.feedback_sha256,
1151
+ ),
1152
+ action,
1153
+ scope,
1154
+ )
1155
+ )
1156
+ ranked.sort(key=lambda value: value[0])
1157
+ chosen = ranked[: query.max_actions]
1158
+ return ContextualOutcomeHistoryReceipt(
1159
+ query=query,
1160
+ actions=tuple(value[1] for value in chosen),
1161
+ transfer_scopes=tuple(value[2] for value in chosen),
1162
+ )
1163
+
1164
+
1165
+ class SelectedForecastProvider(Protocol):
1166
+ """Recover the typed selected K4 forecasts from a sealed selector result."""
1167
+
1168
+ def __call__(
1169
+ self,
1170
+ wave: PortfolioVariationWaveRequest,
1171
+ result: PortfolioVariationWaveResult,
1172
+ ) -> tuple[ForecastPredictionReceipt, ...]: ...
1173
+
1174
+
1175
+ class SelectedSearchSourceProvider(Protocol):
1176
+ """Recover framework source labels aligned to one evaluated portfolio."""
1177
+
1178
+ def __call__(
1179
+ self,
1180
+ wave: PortfolioVariationWaveRequest,
1181
+ result: PortfolioVariationWaveResult,
1182
+ ) -> tuple[str, ...]: ...
1183
+
1184
+
1185
+ class SelectedAllocationRealizationProvider(Protocol):
1186
+ """Recover objective-blind requested-to-realized allocation evidence."""
1187
+
1188
+ def __call__(
1189
+ self,
1190
+ wave: PortfolioVariationWaveRequest,
1191
+ result: PortfolioVariationWaveResult,
1192
+ ) -> ContextualPortfolioAllocationRealization | None: ...
1193
+
1194
+
1195
+ class ContextualMarginalUtilityProjector(Protocol):
1196
+ """Project a pre-generation utility snapshot onto evaluated candidates."""
1197
+
1198
+ def project(
1199
+ self,
1200
+ *,
1201
+ snapshot: ArchiveUtilitySnapshot,
1202
+ results: tuple[PortfolioVariationWaveResult, ...],
1203
+ ) -> tuple[tuple[float, ...], ...]: ...
1204
+
1205
+
1206
+ class DirectionAdjudicatorProvider(Protocol):
1207
+ """Return the benchmark/session-bound adjudicator for one completed wave."""
1208
+
1209
+ def __call__(
1210
+ self,
1211
+ wave: PortfolioVariationWaveRequest,
1212
+ result: PortfolioVariationWaveResult,
1213
+ ) -> MeaningfulMetricDirectionAdjudicator: ...
1214
+
1215
+
1216
+ @dataclass(slots=True)
1217
+ class CalibratedCampaignOutcomeUpdater:
1218
+ """Zero-call campaign bridge from completed waves to prior-only feedback."""
1219
+
1220
+ ledger: PortfolioOutcomeFeedbackLedger
1221
+ selected_forecasts: SelectedForecastProvider
1222
+ adjudicator_for: DirectionAdjudicatorProvider
1223
+ decision_metric_projection: DecisionMetricProjection | None = None
1224
+ contextual_ledger: ContextualSearchLedger | None = None
1225
+ selected_search_sources: SelectedSearchSourceProvider | None = None
1226
+ contextual_marginal_utility: ContextualMarginalUtilityProjector | None = None
1227
+ contextual_campaign_scope_sha256: str | None = None
1228
+ selected_allocation_realization: (
1229
+ SelectedAllocationRealizationProvider | None
1230
+ ) = None
1231
+ _prepared: dict[
1232
+ str,
1233
+ tuple[
1234
+ tuple[str, ...],
1235
+ tuple[PortfolioOutcomeFeedbackReceipt, ...],
1236
+ tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]] | None,
1237
+ ContextualPortfolioOutcomeBatch | None,
1238
+ tuple[ContextualPortfolioAllocationRealization, ...],
1239
+ ],
1240
+ ] = field(init=False, default_factory=dict)
1241
+
1242
+ def __post_init__(self) -> None:
1243
+ if type(self.ledger) is not PortfolioOutcomeFeedbackLedger:
1244
+ raise TypeError("ledger must be exact PortfolioOutcomeFeedbackLedger")
1245
+ if not callable(self.selected_forecasts):
1246
+ raise TypeError("selected_forecasts must be callable")
1247
+ if not callable(self.adjudicator_for):
1248
+ raise TypeError("adjudicator_for must be callable")
1249
+ if self.decision_metric_projection is not None:
1250
+ if type(self.decision_metric_projection) is not DecisionMetricProjection:
1251
+ raise TypeError("decision_metric_projection must be exact or None")
1252
+ self.decision_metric_projection.__post_init__()
1253
+ contextual = (
1254
+ self.contextual_ledger,
1255
+ self.selected_search_sources,
1256
+ self.contextual_marginal_utility,
1257
+ self.contextual_campaign_scope_sha256,
1258
+ )
1259
+ if any(value is not None for value in contextual) and not all(
1260
+ value is not None for value in contextual
1261
+ ):
1262
+ raise ValueError("contextual outcome dependencies are all-or-none")
1263
+ if self.contextual_ledger is not None:
1264
+ if type(self.contextual_ledger) is not ContextualSearchLedger:
1265
+ raise TypeError("contextual_ledger must be exact or None")
1266
+ if not callable(self.selected_search_sources):
1267
+ raise TypeError("selected_search_sources must be callable")
1268
+ if not callable(
1269
+ getattr(self.contextual_marginal_utility, "project", None)
1270
+ ):
1271
+ raise TypeError("contextual marginal utility must implement its port")
1272
+ assert self.contextual_campaign_scope_sha256 is not None
1273
+ require_sha256(
1274
+ self.contextual_campaign_scope_sha256,
1275
+ "contextual_campaign_scope_sha256",
1276
+ )
1277
+ if self.selected_allocation_realization is not None:
1278
+ if self.contextual_ledger is None:
1279
+ raise ValueError(
1280
+ "allocation realization feedback requires contextual outcomes"
1281
+ )
1282
+ if not callable(self.selected_allocation_realization):
1283
+ raise TypeError("selected_allocation_realization must be callable")
1284
+
1285
+ async def prepare_update(
1286
+ self,
1287
+ request: CampaignStageRequest,
1288
+ waves: tuple[PortfolioVariationWaveRequest, ...],
1289
+ results: tuple[PortfolioVariationWaveResult, ...],
1290
+ prior_memory: FrozenJsonObject,
1291
+ ):
1292
+ from agent_evolve.application.portfolio_campaign_runtime import (
1293
+ CampaignPortfolioOutcomePreparation,
1294
+ )
1295
+
1296
+ self.__post_init__()
1297
+ if type(request) is not CampaignStageRequest:
1298
+ raise TypeError("request must be exact CampaignStageRequest")
1299
+ CampaignStageRequest.__post_init__(request)
1300
+ if request.step.kind is not CampaignGenerationKind.PORTFOLIO:
1301
+ raise ValueError("outcome feedback requires a portfolio stage")
1302
+ if (
1303
+ type(waves) is not tuple
1304
+ or type(results) is not tuple
1305
+ or len(waves) != len(results)
1306
+ or not waves
1307
+ ):
1308
+ raise ValueError("waves and results must be equal non-empty tuples")
1309
+ if type(prior_memory) is not FrozenJsonObject:
1310
+ raise TypeError("prior_memory must be an exact frozen object")
1311
+ pending: list[PortfolioOutcomeFeedbackReceipt] = []
1312
+ for wave, result in zip(waves, results, strict=True):
1313
+ if type(wave) is not PortfolioVariationWaveRequest:
1314
+ raise TypeError("waves must contain exact portfolio requests")
1315
+ if type(result) is not PortfolioVariationWaveResult:
1316
+ raise TypeError("results must contain exact portfolio results")
1317
+ predictions = self.selected_forecasts(wave, result)
1318
+ adjudicator = self.adjudicator_for(wave, result)
1319
+ pending.append(
1320
+ observe_selected_portfolio_forecasts(
1321
+ wave_index=request.step.generation,
1322
+ parent=wave.parent,
1323
+ result=result,
1324
+ selected_predictions=predictions,
1325
+ adjudicator=adjudicator,
1326
+ decision_metric_projection=self.decision_metric_projection,
1327
+ )
1328
+ )
1329
+ # Validate the whole stage without publishing any new ledger entry.
1330
+ combined = (*self.ledger.receipts, *pending)
1331
+ validate_feedback_ledger(combined)
1332
+ preview_ledger = PortfolioOutcomeFeedbackLedger()
1333
+ for receipt in combined:
1334
+ preview_ledger.append(receipt)
1335
+ memory = thaw_json(prior_memory)
1336
+ feedback = {
1337
+ "schema_version": 1,
1338
+ "latest_wave_index": request.step.generation,
1339
+ "stage_receipt_sha256s": [value.receipt_sha256 for value in pending],
1340
+ "ledger_receipt_sha256s": [
1341
+ value.receipt_sha256 for value in preview_ledger.receipts
1342
+ ],
1343
+ "observation_count": len(preview_ledger.observations),
1344
+ "prompt_history": thaw_json(
1345
+ preview_ledger.prompt_history(
1346
+ cutoff_wave_index_exclusive=request.step.generation + 1,
1347
+ )
1348
+ ),
1349
+ "provider_calls": 0,
1350
+ }
1351
+ memory["portfolio_outcome_feedback"] = feedback
1352
+ contextual_prior_state: (
1353
+ tuple[tuple[str, ...], tuple[str, ...], tuple[str, ...]] | None
1354
+ ) = None
1355
+ contextual_batch: ContextualPortfolioOutcomeBatch | None = None
1356
+ contextual_realizations: tuple[
1357
+ ContextualPortfolioAllocationRealization, ...
1358
+ ] = ()
1359
+ contextual_evidence: dict[str, object] | None = None
1360
+ if self.contextual_ledger is not None:
1361
+ assert self.selected_search_sources is not None
1362
+ assert self.contextual_marginal_utility is not None
1363
+ assert self.contextual_campaign_scope_sha256 is not None
1364
+ selected_sources = tuple(
1365
+ self.selected_search_sources(wave, result)
1366
+ for wave, result in zip(waves, results, strict=True)
1367
+ )
1368
+ marginal_utilities = self.contextual_marginal_utility.project(
1369
+ snapshot=request.archive_utility,
1370
+ results=results,
1371
+ )
1372
+ # The authenticated campaign cadence alternates portfolio and
1373
+ # recombination generations, so controller waves are 1, 2, ...
1374
+ # for campaign generations 1, 3, ... .
1375
+ controller_wave_index = (request.step.generation + 1) // 2
1376
+ contextual_batch = observe_contextual_portfolio_outcomes(
1377
+ campaign_scope_sha256=self.contextual_campaign_scope_sha256,
1378
+ wave_index=controller_wave_index,
1379
+ waves=waves,
1380
+ results=results,
1381
+ selected_source_ids=selected_sources,
1382
+ marginal_utilities=marginal_utilities,
1383
+ )
1384
+ contextual_prior_state = (
1385
+ tuple(
1386
+ value.observation_sha256
1387
+ for value in self.contextual_ledger.observations
1388
+ ),
1389
+ tuple(
1390
+ value.credit_sha256
1391
+ for value in self.contextual_ledger.delayed_credits
1392
+ ),
1393
+ tuple(
1394
+ value.realization_sha256
1395
+ for value in self.contextual_ledger.allocation_realizations
1396
+ ),
1397
+ )
1398
+ preview_contextual = ContextualSearchLedger(
1399
+ observations=list(self.contextual_ledger.observations),
1400
+ delayed_credits=list(self.contextual_ledger.delayed_credits),
1401
+ allocation_realizations=list(
1402
+ self.contextual_ledger.allocation_realizations
1403
+ ),
1404
+ )
1405
+ preview_contextual.append_batch(contextual_batch.observations)
1406
+ if self.selected_allocation_realization is not None:
1407
+ recovered = tuple(
1408
+ self.selected_allocation_realization(wave, result)
1409
+ for wave, result in zip(waves, results, strict=True)
1410
+ )
1411
+ if any(value is None for value in recovered):
1412
+ raise ValueError(
1413
+ "contextual run omitted an allocation realization"
1414
+ )
1415
+ contextual_realizations = tuple(
1416
+ sorted(
1417
+ (
1418
+ value
1419
+ for value in recovered
1420
+ if type(value)
1421
+ is ContextualPortfolioAllocationRealization
1422
+ ),
1423
+ key=lambda value: (
1424
+ value.controller_wave_index,
1425
+ value.slice_id,
1426
+ value.realization_sha256,
1427
+ ),
1428
+ )
1429
+ )
1430
+ if len(contextual_realizations) != len(recovered):
1431
+ raise TypeError(
1432
+ "allocation realization provider returned a foreign value"
1433
+ )
1434
+ preview_contextual.append_allocation_realization_batch(
1435
+ contextual_realizations
1436
+ )
1437
+ contextual_evidence = {
1438
+ "batch": contextual_batch.to_record(),
1439
+ "ledger_observation_count_after": len(
1440
+ preview_contextual.observations
1441
+ ),
1442
+ "ledger_delayed_credit_count_after": len(
1443
+ preview_contextual.delayed_credits
1444
+ ),
1445
+ "allocation_realization_sha256s": [
1446
+ value.realization_sha256
1447
+ for value in contextual_realizations
1448
+ ],
1449
+ "ledger_allocation_realization_count_after": len(
1450
+ preview_contextual.allocation_realizations
1451
+ ),
1452
+ "provider_calls": 0,
1453
+ }
1454
+ memory["contextual_search_controller"] = {
1455
+ "schema_version": 1,
1456
+ "latest_wave_index": controller_wave_index,
1457
+ "latest_batch_sha256": contextual_batch.batch_sha256,
1458
+ "latest_observation_sha256s": [
1459
+ value.observation_sha256
1460
+ for value in contextual_batch.observations
1461
+ ],
1462
+ "ledger_observation_count": len(
1463
+ preview_contextual.observations
1464
+ ),
1465
+ "ledger_delayed_credit_count": len(
1466
+ preview_contextual.delayed_credits
1467
+ ),
1468
+ "ledger_allocation_realization_count": len(
1469
+ preview_contextual.allocation_realizations
1470
+ ),
1471
+ "provider_calls": 0,
1472
+ }
1473
+ frozen = freeze_json(memory)
1474
+ if type(frozen) is not FrozenJsonObject:
1475
+ raise AssertionError("updated campaign memory did not freeze as an object")
1476
+ preparation = CampaignPortfolioOutcomePreparation(
1477
+ request_sha256=request.request_sha256,
1478
+ generation=request.step.generation,
1479
+ wave_request_sha256s=tuple(
1480
+ wave.selection_request.request_sha256 for wave in waves
1481
+ ),
1482
+ result_receipt_sha256s=tuple(
1483
+ result.receipt.receipt_sha256 for result in results
1484
+ ),
1485
+ prior_memory_sha256=typed_json_sha256(prior_memory),
1486
+ updated_memory=frozen,
1487
+ evidence=freeze_json(
1488
+ {
1489
+ "pending_feedback_receipt_sha256s": [
1490
+ value.receipt_sha256 for value in pending
1491
+ ],
1492
+ "ledger_receipt_count_after": len(preview_ledger.receipts),
1493
+ "contextual_outcomes": contextual_evidence,
1494
+ "provider_calls": 0,
1495
+ }
1496
+ ),
1497
+ )
1498
+ if type(preparation.evidence) is not FrozenJsonObject:
1499
+ raise AssertionError("outcome preparation evidence was not an object")
1500
+ if preparation.preparation_sha256 in self._prepared:
1501
+ raise ValueError("outcome update preparation already exists")
1502
+ self._prepared[preparation.preparation_sha256] = (
1503
+ tuple(value.receipt_sha256 for value in self.ledger.receipts),
1504
+ tuple(pending),
1505
+ contextual_prior_state,
1506
+ contextual_batch,
1507
+ contextual_realizations,
1508
+ )
1509
+ return preparation
1510
+
1511
+ def commit_update(self, preparation) -> None:
1512
+ from agent_evolve.application.portfolio_campaign_runtime import (
1513
+ CampaignPortfolioOutcomePreparation,
1514
+ )
1515
+
1516
+ if type(preparation) is not CampaignPortfolioOutcomePreparation:
1517
+ raise TypeError("preparation must be exact")
1518
+ prepared = self._prepared.pop(preparation.preparation_sha256, None)
1519
+ if prepared is None:
1520
+ raise ValueError("outcome update preparation is unavailable")
1521
+ (
1522
+ prior_receipts,
1523
+ pending,
1524
+ contextual_prior_state,
1525
+ contextual_batch,
1526
+ contextual_realizations,
1527
+ ) = prepared
1528
+ if tuple(value.receipt_sha256 for value in self.ledger.receipts) != (
1529
+ prior_receipts
1530
+ ):
1531
+ raise RuntimeError("outcome ledger changed after preparation")
1532
+ if contextual_prior_state is not None:
1533
+ if self.contextual_ledger is None or contextual_batch is None:
1534
+ raise RuntimeError("contextual outcome dependencies disappeared")
1535
+ current_contextual_state = (
1536
+ tuple(
1537
+ value.observation_sha256
1538
+ for value in self.contextual_ledger.observations
1539
+ ),
1540
+ tuple(
1541
+ value.credit_sha256
1542
+ for value in self.contextual_ledger.delayed_credits
1543
+ ),
1544
+ tuple(
1545
+ value.realization_sha256
1546
+ for value in self.contextual_ledger.allocation_realizations
1547
+ ),
1548
+ )
1549
+ if current_contextual_state != contextual_prior_state:
1550
+ raise RuntimeError("contextual search ledger changed after preparation")
1551
+ self.ledger.receipts.extend(pending)
1552
+ if contextual_batch is not None:
1553
+ assert self.contextual_ledger is not None
1554
+ self.contextual_ledger.observations.extend(
1555
+ contextual_batch.observations
1556
+ )
1557
+ self.contextual_ledger.allocation_realizations.extend(
1558
+ contextual_realizations
1559
+ )
1560
+
1561
+ def abort_update(self, preparation) -> None:
1562
+ from agent_evolve.application.portfolio_campaign_runtime import (
1563
+ CampaignPortfolioOutcomePreparation,
1564
+ )
1565
+
1566
+ if type(preparation) is not CampaignPortfolioOutcomePreparation:
1567
+ raise TypeError("preparation must be exact")
1568
+ self._prepared.pop(preparation.preparation_sha256, None)
1569
+
1570
+ async def update(
1571
+ self,
1572
+ request: CampaignStageRequest,
1573
+ waves: tuple[PortfolioVariationWaveRequest, ...],
1574
+ results: tuple[PortfolioVariationWaveResult, ...],
1575
+ prior_memory: FrozenJsonObject,
1576
+ ) -> FrozenJsonObject:
1577
+ """Backward-compatible immediate publication wrapper."""
1578
+
1579
+ preparation = await self.prepare_update(
1580
+ request,
1581
+ waves,
1582
+ results,
1583
+ prior_memory,
1584
+ )
1585
+ self.commit_update(preparation)
1586
+ return preparation.updated_memory
1587
+
1588
+
1589
+ def validate_feedback_ledger(
1590
+ values: Sequence[PortfolioOutcomeFeedbackReceipt],
1591
+ ) -> None:
1592
+ ledger = PortfolioOutcomeFeedbackLedger()
1593
+ for value in values:
1594
+ ledger.append(value)
1595
+
1596
+
1597
+ __all__ = [
1598
+ "CalibratedCampaignOutcomeUpdater",
1599
+ "ContextualOutcomeHistoryReceipt",
1600
+ "ContextualOutcomeQuery",
1601
+ "DecisionMetricTransition",
1602
+ "DirectionAdjudicatorProvider",
1603
+ "ContextualMarginalUtilityProjector",
1604
+ "OutcomeTransferScope",
1605
+ "PortfolioActionOutcomeFeedback",
1606
+ "PortfolioOutcomeFeedbackLedger",
1607
+ "PortfolioOutcomeFeedbackReceipt",
1608
+ "SelectedForecastProvider",
1609
+ "SelectedAllocationRealizationProvider",
1610
+ "SelectedSearchSourceProvider",
1611
+ "observe_selected_portfolio_forecasts",
1612
+ "validate_feedback_ledger",
1613
+ ]