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,1270 @@
1
+ """Wave-sealed causal memory for staged agentic evolution.
2
+
3
+ This module is deliberately framework- and provider-free. It defines the
4
+ immutable records that an application layer can bind into an invocation plan,
5
+ plus deterministic policies for diagnostic credit and later matched controls.
6
+
7
+ The central timing rule is strict: every assignment in a diagnostic wave is
8
+ resolved against one immutable score snapshot, and no reward from that wave is
9
+ visible until the complete wave is sealed. Successful calls use their frozen
10
+ reward. Model/schema failures and candidate failures receive the
11
+ pre-registered no-yield reward under an intention-to-treat (ITT) estimand.
12
+ Infrastructure failures invalidate the whole wave and publish no checkpoint.
13
+
14
+ For insight ``i``, the frozen causal-search score is::
15
+
16
+ support_i = min(treated_ESS_i, control_ESS_i)
17
+ shrink_i = support_i / (support_i + n0)
18
+ mean_i = prior_i + shrink_i * effect_i
19
+ uncertainty_i = c / sqrt(support_i + n0)
20
+ retrieval_i = mean_i + beta * uncertainty_i
21
+
22
+ An unidentified effect is exactly zero for the update (not evidence of harm),
23
+ while its support remains zero. ``n0``, ``c``, and ``beta`` are recorded in
24
+ every snapshot and therefore cannot drift silently between replay and use.
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import hashlib
30
+ import json
31
+ import math
32
+ import re
33
+ from collections import Counter
34
+ from collections.abc import Mapping, Sequence
35
+ from dataclasses import dataclass, field
36
+ from enum import Enum
37
+ from fractions import Fraction
38
+ from numbers import Real
39
+
40
+ from agent_evolve.domain.ids import CandidateId, OperatorInvocationId
41
+ from agent_evolve.domain.insight import InsightRef
42
+ from agent_evolve.policies.memory.randomized_subset import (
43
+ EpsilonGreedySubsetSelector,
44
+ InsightSelectionDecision,
45
+ InsightSelectionMode,
46
+ InsightTrial,
47
+ estimate_marginal_effect,
48
+ )
49
+
50
+
51
+ _LOWER_SHA256 = frozenset("0123456789abcdef")
52
+ _SAFE_BLOCK_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,127}$")
53
+ _SELECTION_DECISION_DOMAIN = b"agent-evolve:insight-selection-decision:v1\x00"
54
+ _ASSIGNMENT_DOMAIN = b"agent-evolve:resolved-insight-assignment:v1\x00"
55
+ _SCORE_SNAPSHOT_DOMAIN = b"agent-evolve:causal-memory-score-snapshot:v1\x00"
56
+ _WAVE_DOMAIN = b"agent-evolve:frozen-diagnostic-memory-wave:v1\x00"
57
+
58
+
59
+ def _require_sha256(value: str, name: str) -> None:
60
+ if (
61
+ type(value) is not str
62
+ or len(value) != 64
63
+ or any(character not in _LOWER_SHA256 for character in value)
64
+ ):
65
+ raise ValueError(f"{name} must be a lowercase SHA-256 digest")
66
+
67
+
68
+ def _require_block_id(value: str, name: str) -> None:
69
+ if type(value) is not str or _SAFE_BLOCK_ID.fullmatch(value) is None:
70
+ raise ValueError(f"{name} must be a bounded durable identifier")
71
+
72
+
73
+ def _canonical_float(value: Real, name: str) -> float:
74
+ if isinstance(value, bool) or not isinstance(value, Real):
75
+ raise TypeError(f"{name} must be a real number")
76
+ result = float(value)
77
+ if not math.isfinite(result):
78
+ raise ValueError(f"{name} must be finite")
79
+ return result
80
+
81
+
82
+ def _require_canonical_float(value: float, name: str) -> None:
83
+ if type(value) is not float or not math.isfinite(value):
84
+ raise TypeError(f"{name} must be a finite canonical float")
85
+
86
+
87
+ def _ref_record(reference: InsightRef) -> dict[str, object]:
88
+ return {
89
+ "insight_id": reference.insight_id.value,
90
+ "version": reference.version,
91
+ }
92
+
93
+
94
+ def _fraction_record(value: Fraction) -> list[int]:
95
+ return [value.numerator, value.denominator]
96
+
97
+
98
+ def _hash_record(domain: bytes, record: Mapping[str, object]) -> str:
99
+ payload = json.dumps(
100
+ record,
101
+ ensure_ascii=True,
102
+ allow_nan=False,
103
+ separators=(",", ":"),
104
+ sort_keys=True,
105
+ ).encode("ascii", errors="strict")
106
+ return hashlib.sha256(domain + payload).hexdigest()
107
+
108
+
109
+ def _decision_record(decision: InsightSelectionDecision) -> dict[str, object]:
110
+ return {
111
+ "policy_id": decision.policy_id,
112
+ "policy_version": decision.policy_version,
113
+ "context_hash": decision.context_hash,
114
+ "eligible": [_ref_record(reference) for reference in decision.eligible],
115
+ "selected": [_ref_record(reference) for reference in decision.selected],
116
+ "exploitation_subset": [
117
+ _ref_record(reference) for reference in decision.exploitation_subset
118
+ ],
119
+ "score_snapshot": [
120
+ {
121
+ "reference": _ref_record(reference),
122
+ "score_hex": score.hex(),
123
+ }
124
+ for reference, score in decision.score_snapshot
125
+ ],
126
+ "subset_size": decision.subset_size,
127
+ "exploration_probability": _fraction_record(decision.exploration_probability),
128
+ "mode": decision.mode.value,
129
+ "selected_subset_probability": _fraction_record(
130
+ decision.selected_subset_probability
131
+ ),
132
+ }
133
+
134
+
135
+ def insight_selection_decision_sha256(
136
+ decision: InsightSelectionDecision,
137
+ ) -> str:
138
+ """Return a stable digest of the complete assignment law and realization."""
139
+
140
+ if not isinstance(decision, InsightSelectionDecision):
141
+ raise TypeError("decision must be an InsightSelectionDecision")
142
+ return _hash_record(_SELECTION_DECISION_DOMAIN, _decision_record(decision))
143
+
144
+
145
+ class MemoryAssignmentArm(str, Enum):
146
+ """Predeclared role of one invocation in a staged memory experiment."""
147
+
148
+ DIAGNOSTIC = "diagnostic"
149
+ ADAPTIVE = "adaptive"
150
+ SCORE_SHUFFLED_CONTROL = "score_shuffled_control"
151
+ UNIFORM_CONTROL = "uniform_control"
152
+
153
+
154
+ class DelayedCreditMode(str, Enum):
155
+ """When and under which estimand an assignment may update memory."""
156
+
157
+ WAVE_SEALED_ITT = "wave_sealed_intention_to_treat"
158
+
159
+
160
+ @dataclass(frozen=True, slots=True)
161
+ class ResolvedInsightAssignment:
162
+ """One plan-ready retrieval assignment bound to immutable evidence.
163
+
164
+ ``score_snapshot_sha256`` identifies the complete
165
+ :class:`MemoryScoreSnapshot`, not merely its score table. The exact score
166
+ table and selected references remain embedded in ``selection_decision``.
167
+ Application code should construct values with :meth:`resolve` whenever the
168
+ source snapshot is available; direct construction remains useful for a
169
+ durable codec and is revalidated at a frozen-wave boundary.
170
+ """
171
+
172
+ credit_unit_id: OperatorInvocationId
173
+ exact_context_hash: str
174
+ estimand_stratum_hash: str
175
+ block_id: str
176
+ arm: MemoryAssignmentArm
177
+ selection_decision: InsightSelectionDecision
178
+ selection_decision_sha256: str
179
+ score_snapshot_sha256: str
180
+ prompt_shape_sha256: str
181
+ credit_mode: DelayedCreditMode = DelayedCreditMode.WAVE_SEALED_ITT
182
+ assignment_sha256: str = field(init=False)
183
+
184
+ def __post_init__(self) -> None:
185
+ if not isinstance(self.credit_unit_id, OperatorInvocationId):
186
+ raise TypeError("credit_unit_id must be an OperatorInvocationId")
187
+ _require_sha256(self.exact_context_hash, "exact_context_hash")
188
+ _require_sha256(self.estimand_stratum_hash, "estimand_stratum_hash")
189
+ _require_block_id(self.block_id, "block_id")
190
+ if not isinstance(self.arm, MemoryAssignmentArm):
191
+ raise TypeError("arm must be a MemoryAssignmentArm")
192
+ if not isinstance(self.selection_decision, InsightSelectionDecision):
193
+ raise TypeError("selection_decision must be an InsightSelectionDecision")
194
+ if self.selection_decision.context_hash != self.exact_context_hash:
195
+ raise ValueError(
196
+ "selection decision context does not match exact_context_hash"
197
+ )
198
+ _require_sha256(self.selection_decision_sha256, "selection_decision_sha256")
199
+ if self.selection_decision_sha256 != insight_selection_decision_sha256(
200
+ self.selection_decision
201
+ ):
202
+ raise ValueError("selection_decision_sha256 does not match the decision")
203
+ _require_sha256(self.score_snapshot_sha256, "score_snapshot_sha256")
204
+ _require_sha256(self.prompt_shape_sha256, "prompt_shape_sha256")
205
+ if not isinstance(self.credit_mode, DelayedCreditMode):
206
+ raise TypeError("credit_mode must be a DelayedCreditMode")
207
+ self._validate_arm_law()
208
+ object.__setattr__(
209
+ self,
210
+ "assignment_sha256",
211
+ _hash_record(_ASSIGNMENT_DOMAIN, self.to_record()),
212
+ )
213
+
214
+ def _validate_arm_law(self) -> None:
215
+ decision = self.selection_decision
216
+ if self.arm is MemoryAssignmentArm.DIAGNOSTIC:
217
+ if not decision.credit_identifiable:
218
+ raise ValueError(
219
+ "diagnostic assignments require inclusion-probability overlap"
220
+ )
221
+ return
222
+ if self.arm in {
223
+ MemoryAssignmentArm.ADAPTIVE,
224
+ MemoryAssignmentArm.SCORE_SHUFFLED_CONTROL,
225
+ }:
226
+ if (
227
+ decision.exploration_probability != 0
228
+ or decision.mode is not InsightSelectionMode.EXPLOIT
229
+ ):
230
+ raise ValueError(
231
+ "adaptive and score-shuffled arms require deterministic exploitation"
232
+ )
233
+ return
234
+ if self.arm is MemoryAssignmentArm.UNIFORM_CONTROL and (
235
+ decision.exploration_probability != 1
236
+ or decision.mode is not InsightSelectionMode.EXPLORE_UNIFORM
237
+ ):
238
+ raise ValueError(
239
+ "uniform-control assignments require the exact uniform-subset law"
240
+ )
241
+
242
+ @classmethod
243
+ def resolve(
244
+ cls,
245
+ *,
246
+ credit_unit_id: OperatorInvocationId,
247
+ snapshot: MemoryScoreSnapshot,
248
+ expected_snapshot_sha256: str,
249
+ block_id: str,
250
+ arm: MemoryAssignmentArm,
251
+ selection_decision: InsightSelectionDecision,
252
+ prompt_shape_sha256: str,
253
+ credit_mode: DelayedCreditMode = DelayedCreditMode.WAVE_SEALED_ITT,
254
+ ) -> ResolvedInsightAssignment:
255
+ """Bind a decision to the expected current snapshot, failing stale."""
256
+
257
+ if not isinstance(snapshot, MemoryScoreSnapshot):
258
+ raise TypeError("snapshot must be a MemoryScoreSnapshot")
259
+ _require_sha256(expected_snapshot_sha256, "expected_snapshot_sha256")
260
+ if snapshot.snapshot_sha256 != expected_snapshot_sha256:
261
+ raise StaleMemorySnapshotError(
262
+ "current memory snapshot differs from the predeclared snapshot"
263
+ )
264
+ assignment = cls(
265
+ credit_unit_id=credit_unit_id,
266
+ exact_context_hash=snapshot.exact_context_hash,
267
+ estimand_stratum_hash=snapshot.estimand_stratum_hash,
268
+ block_id=block_id,
269
+ arm=arm,
270
+ selection_decision=selection_decision,
271
+ selection_decision_sha256=insight_selection_decision_sha256(
272
+ selection_decision
273
+ ),
274
+ score_snapshot_sha256=snapshot.snapshot_sha256,
275
+ prompt_shape_sha256=prompt_shape_sha256,
276
+ credit_mode=credit_mode,
277
+ )
278
+ assignment.validate_against_snapshot(snapshot)
279
+ return assignment
280
+
281
+ def validate_against_snapshot(self, snapshot: MemoryScoreSnapshot) -> None:
282
+ """Revalidate the snapshot binding and arm-specific score relationship."""
283
+
284
+ if not isinstance(snapshot, MemoryScoreSnapshot):
285
+ raise TypeError("snapshot must be a MemoryScoreSnapshot")
286
+ if self.score_snapshot_sha256 != snapshot.snapshot_sha256:
287
+ raise StaleMemorySnapshotError(
288
+ "assignment is bound to a different memory snapshot"
289
+ )
290
+ if (
291
+ self.exact_context_hash != snapshot.exact_context_hash
292
+ or self.estimand_stratum_hash != snapshot.estimand_stratum_hash
293
+ ):
294
+ raise ValueError("assignment and snapshot strata differ")
295
+ decision = self.selection_decision
296
+ expected_refs = tuple(entry.reference for entry in snapshot.entries)
297
+ if decision.eligible != expected_refs:
298
+ raise ValueError(
299
+ "selection decision eligible set differs from the score snapshot"
300
+ )
301
+ observed_scores = tuple(score for _, score in decision.score_snapshot)
302
+ snapshot_scores = tuple(entry.retrieval_score for entry in snapshot.entries)
303
+ if self.arm is MemoryAssignmentArm.SCORE_SHUFFLED_CONTROL:
304
+ observed_multiset = Counter(score.hex() for score in observed_scores)
305
+ snapshot_multiset = Counter(score.hex() for score in snapshot_scores)
306
+ if observed_multiset != snapshot_multiset:
307
+ raise ValueError(
308
+ "score-shuffled control must preserve the snapshot score multiset"
309
+ )
310
+ elif observed_scores != snapshot_scores:
311
+ raise ValueError(
312
+ "selection decision scores differ from the bound memory snapshot"
313
+ )
314
+
315
+ def to_record(self) -> dict[str, object]:
316
+ """Return a deterministic JSON-ready record used by plan hashing."""
317
+
318
+ return {
319
+ "schema_version": 1,
320
+ "credit_unit_id": self.credit_unit_id.value,
321
+ "exact_context_hash": self.exact_context_hash,
322
+ "estimand_stratum_hash": self.estimand_stratum_hash,
323
+ "block_id": self.block_id,
324
+ "arm": self.arm.value,
325
+ "selection_decision": _decision_record(self.selection_decision),
326
+ "selection_decision_sha256": self.selection_decision_sha256,
327
+ "score_snapshot_sha256": self.score_snapshot_sha256,
328
+ "prompt_shape_sha256": self.prompt_shape_sha256,
329
+ "credit_mode": self.credit_mode.value,
330
+ }
331
+
332
+
333
+ class StaleMemorySnapshotError(ValueError):
334
+ """Raised when an assignment or wave refers to a superseded checkpoint."""
335
+
336
+
337
+ class IncompleteMemoryWaveError(ValueError):
338
+ """Raised when a wave cannot be sealed with exactly one receipt per unit."""
339
+
340
+
341
+ @dataclass(frozen=True, slots=True)
342
+ class CausalSearchScore:
343
+ """Auditable score and support for one exact insight version."""
344
+
345
+ reference: InsightRef
346
+ prior_score: float
347
+ effect_estimate: float | None
348
+ treated_trials: int
349
+ control_trials: int
350
+ treated_effective_sample_size: float
351
+ control_effective_sample_size: float
352
+ effective_support: float
353
+ shrinkage: float
354
+ posterior_mean: float
355
+ uncertainty_bonus: float
356
+ retrieval_score: float
357
+
358
+ def __post_init__(self) -> None:
359
+ if not isinstance(self.reference, InsightRef):
360
+ raise TypeError("reference must be an InsightRef")
361
+ for name in (
362
+ "prior_score",
363
+ "treated_effective_sample_size",
364
+ "control_effective_sample_size",
365
+ "effective_support",
366
+ "shrinkage",
367
+ "posterior_mean",
368
+ "uncertainty_bonus",
369
+ "retrieval_score",
370
+ ):
371
+ _require_canonical_float(getattr(self, name), name)
372
+ if self.effect_estimate is not None:
373
+ _require_canonical_float(self.effect_estimate, "effect_estimate")
374
+ for name in ("treated_trials", "control_trials"):
375
+ value = getattr(self, name)
376
+ if type(value) is not int or value < 0:
377
+ raise ValueError(f"{name} must be a non-negative exact integer")
378
+ if (
379
+ min(
380
+ self.treated_effective_sample_size,
381
+ self.control_effective_sample_size,
382
+ self.effective_support,
383
+ self.uncertainty_bonus,
384
+ )
385
+ < 0
386
+ ):
387
+ raise ValueError("sample support and uncertainty cannot be negative")
388
+ if not 0.0 <= self.shrinkage < 1.0:
389
+ raise ValueError("shrinkage must lie in [0,1)")
390
+
391
+ @property
392
+ def identified(self) -> bool:
393
+ return self.effect_estimate is not None
394
+
395
+ def to_record(self) -> dict[str, object]:
396
+ return {
397
+ "reference": _ref_record(self.reference),
398
+ "prior_score_hex": self.prior_score.hex(),
399
+ "effect_estimate_hex": (
400
+ None if self.effect_estimate is None else self.effect_estimate.hex()
401
+ ),
402
+ "treated_trials": self.treated_trials,
403
+ "control_trials": self.control_trials,
404
+ "treated_ess_hex": self.treated_effective_sample_size.hex(),
405
+ "control_ess_hex": self.control_effective_sample_size.hex(),
406
+ "effective_support_hex": self.effective_support.hex(),
407
+ "shrinkage_hex": self.shrinkage.hex(),
408
+ "posterior_mean_hex": self.posterior_mean.hex(),
409
+ "uncertainty_bonus_hex": self.uncertainty_bonus.hex(),
410
+ "retrieval_score_hex": self.retrieval_score.hex(),
411
+ }
412
+
413
+
414
+ class MemoryTrialTerminalStatus(str, Enum):
415
+ """Terminal classification used by the frozen ITT policy."""
416
+
417
+ SUCCEEDED = "succeeded"
418
+ MODEL_FAILURE = "model_or_schema_failure"
419
+ CANDIDATE_FAILURE = "candidate_failure"
420
+ INFRASTRUCTURE_FAILURE = "infrastructure_failure"
421
+
422
+
423
+ @dataclass(frozen=True, slots=True)
424
+ class MemoryAssignmentReceipt:
425
+ """One terminal result; publication order has no scoring meaning."""
426
+
427
+ assignment_sha256: str
428
+ credit_unit_id: OperatorInvocationId
429
+ status: MemoryTrialTerminalStatus
430
+ candidate_ids: tuple[CandidateId, ...] = ()
431
+ observed_reward: float | None = None
432
+
433
+ def __post_init__(self) -> None:
434
+ _require_sha256(self.assignment_sha256, "assignment_sha256")
435
+ if not isinstance(self.credit_unit_id, OperatorInvocationId):
436
+ raise TypeError("credit_unit_id must be an OperatorInvocationId")
437
+ if not isinstance(self.status, MemoryTrialTerminalStatus):
438
+ raise TypeError("status must be a MemoryTrialTerminalStatus")
439
+ if type(self.candidate_ids) is not tuple or any(
440
+ not isinstance(value, CandidateId) for value in self.candidate_ids
441
+ ):
442
+ raise TypeError("candidate_ids must be a tuple of CandidateId values")
443
+ if self.candidate_ids != tuple(sorted(set(self.candidate_ids))):
444
+ raise ValueError("candidate_ids must be unique and canonically sorted")
445
+ if self.status is MemoryTrialTerminalStatus.SUCCEEDED:
446
+ if not self.candidate_ids:
447
+ raise ValueError("a successful receipt must identify a candidate")
448
+ _require_canonical_float(self.observed_reward, "observed_reward")
449
+ elif self.observed_reward is not None:
450
+ raise ValueError("failure receipts cannot carry an observed reward")
451
+
452
+ def to_record(self) -> dict[str, object]:
453
+ return {
454
+ "assignment_sha256": self.assignment_sha256,
455
+ "credit_unit_id": self.credit_unit_id.value,
456
+ "status": self.status.value,
457
+ "candidate_ids": [value.value for value in self.candidate_ids],
458
+ "observed_reward_hex": (
459
+ None if self.observed_reward is None else self.observed_reward.hex()
460
+ ),
461
+ }
462
+
463
+
464
+ @dataclass(frozen=True, slots=True)
465
+ class CausalMemoryObservation:
466
+ """A sealed, non-infrastructure assignment with its ITT reward."""
467
+
468
+ assignment: ResolvedInsightAssignment
469
+ status: MemoryTrialTerminalStatus
470
+ candidate_ids: tuple[CandidateId, ...]
471
+ credited_reward: float
472
+ reward_definition_hash: str
473
+
474
+ def __post_init__(self) -> None:
475
+ if not isinstance(self.assignment, ResolvedInsightAssignment):
476
+ raise TypeError("assignment must be a ResolvedInsightAssignment")
477
+ if self.assignment.arm is not MemoryAssignmentArm.DIAGNOSTIC:
478
+ raise ValueError("causal memory observations must be diagnostic")
479
+ if self.status not in {
480
+ MemoryTrialTerminalStatus.SUCCEEDED,
481
+ MemoryTrialTerminalStatus.MODEL_FAILURE,
482
+ MemoryTrialTerminalStatus.CANDIDATE_FAILURE,
483
+ }:
484
+ raise ValueError("infrastructure failures cannot enter causal evidence")
485
+ if type(self.candidate_ids) is not tuple or any(
486
+ not isinstance(value, CandidateId) for value in self.candidate_ids
487
+ ):
488
+ raise TypeError("candidate_ids must be a tuple of CandidateId values")
489
+ if self.candidate_ids != tuple(sorted(set(self.candidate_ids))):
490
+ raise ValueError("candidate_ids must be unique and canonically sorted")
491
+ _require_canonical_float(self.credited_reward, "credited_reward")
492
+ _require_sha256(self.reward_definition_hash, "reward_definition_hash")
493
+
494
+ @property
495
+ def reward_was_imputed(self) -> bool:
496
+ return self.status is not MemoryTrialTerminalStatus.SUCCEEDED
497
+
498
+ def to_trial(self) -> InsightTrial:
499
+ return InsightTrial(
500
+ credit_unit_id=self.assignment.credit_unit_id,
501
+ candidate_ids=self.candidate_ids,
502
+ reward_definition_hash=self.reward_definition_hash,
503
+ decision=self.assignment.selection_decision,
504
+ reward=self.credited_reward,
505
+ )
506
+
507
+ def to_record(self) -> dict[str, object]:
508
+ return {
509
+ "assignment": self.assignment.to_record(),
510
+ "assignment_sha256": self.assignment.assignment_sha256,
511
+ "status": self.status.value,
512
+ "candidate_ids": [value.value for value in self.candidate_ids],
513
+ "credited_reward_hex": self.credited_reward.hex(),
514
+ "reward_definition_hash": self.reward_definition_hash,
515
+ "reward_was_imputed": self.reward_was_imputed,
516
+ }
517
+
518
+
519
+ @dataclass(frozen=True, slots=True)
520
+ class MemoryScoreSnapshot:
521
+ """An immutable causal score checkpoint and its complete valid evidence."""
522
+
523
+ exact_context_hash: str
524
+ estimand_stratum_hash: str
525
+ checkpoint_index: int
526
+ entries: tuple[CausalSearchScore, ...]
527
+ observations: tuple[CausalMemoryObservation, ...]
528
+ prior_effective_sample_size: float
529
+ uncertainty_scale: float
530
+ exploration_weight: float
531
+ reward_definition_hash: str | None = None
532
+ parent_snapshot_sha256: str | None = None
533
+ source_wave_sha256: str | None = None
534
+ scoring_policy_id: str = "min_ess_shrunken_causal_ucb"
535
+ scoring_policy_version: int = 1
536
+ snapshot_sha256: str = field(init=False)
537
+
538
+ def __post_init__(self) -> None:
539
+ _require_sha256(self.exact_context_hash, "exact_context_hash")
540
+ _require_sha256(self.estimand_stratum_hash, "estimand_stratum_hash")
541
+ if type(self.checkpoint_index) is not int or self.checkpoint_index < 0:
542
+ raise ValueError("checkpoint_index must be a non-negative exact integer")
543
+ if type(self.entries) is not tuple or not self.entries:
544
+ raise ValueError("entries must be a non-empty exact tuple")
545
+ if any(not isinstance(entry, CausalSearchScore) for entry in self.entries):
546
+ raise TypeError("entries must contain CausalSearchScore values")
547
+ references = tuple(entry.reference for entry in self.entries)
548
+ if references != tuple(sorted(set(references))):
549
+ raise ValueError("score entries must be unique and canonically sorted")
550
+ if type(self.observations) is not tuple or any(
551
+ not isinstance(value, CausalMemoryObservation)
552
+ for value in self.observations
553
+ ):
554
+ raise TypeError(
555
+ "observations must be a tuple of CausalMemoryObservation values"
556
+ )
557
+ observation_keys = tuple(
558
+ value.assignment.assignment_sha256 for value in self.observations
559
+ )
560
+ if observation_keys != tuple(sorted(set(observation_keys))):
561
+ raise ValueError("observations must be unique and canonically sorted")
562
+ for observation in self.observations:
563
+ assignment = observation.assignment
564
+ if (
565
+ assignment.exact_context_hash != self.exact_context_hash
566
+ or assignment.estimand_stratum_hash != self.estimand_stratum_hash
567
+ ):
568
+ raise ValueError(
569
+ "snapshot observations must share its estimand stratum"
570
+ )
571
+ for name in (
572
+ "prior_effective_sample_size",
573
+ "uncertainty_scale",
574
+ "exploration_weight",
575
+ ):
576
+ _require_canonical_float(getattr(self, name), name)
577
+ if self.prior_effective_sample_size <= 0:
578
+ raise ValueError("prior_effective_sample_size must be positive")
579
+ if self.uncertainty_scale < 0 or self.exploration_weight < 0:
580
+ raise ValueError("uncertainty parameters cannot be negative")
581
+ if self.scoring_policy_id != "min_ess_shrunken_causal_ucb":
582
+ raise ValueError("unsupported scoring_policy_id")
583
+ if (
584
+ type(self.scoring_policy_version) is not int
585
+ or self.scoring_policy_version != 1
586
+ ):
587
+ raise ValueError("unsupported scoring_policy_version")
588
+
589
+ if self.checkpoint_index == 0:
590
+ if (
591
+ self.observations
592
+ or self.reward_definition_hash is not None
593
+ or self.parent_snapshot_sha256 is not None
594
+ or self.source_wave_sha256 is not None
595
+ ):
596
+ raise ValueError("a genesis snapshot cannot contain wave evidence")
597
+ else:
598
+ if not self.observations:
599
+ raise ValueError("a non-genesis snapshot must contain evidence")
600
+ if self.reward_definition_hash is None:
601
+ raise ValueError("a non-genesis snapshot requires a reward definition")
602
+ _require_sha256(self.reward_definition_hash, "reward_definition_hash")
603
+ if self.parent_snapshot_sha256 is None or self.source_wave_sha256 is None:
604
+ raise ValueError(
605
+ "a non-genesis snapshot requires parent and wave hashes"
606
+ )
607
+ _require_sha256(self.parent_snapshot_sha256, "parent_snapshot_sha256")
608
+ _require_sha256(self.source_wave_sha256, "source_wave_sha256")
609
+ if any(
610
+ observation.reward_definition_hash != self.reward_definition_hash
611
+ for observation in self.observations
612
+ ):
613
+ raise ValueError("snapshot cannot mix reward definitions")
614
+
615
+ for entry in self.entries:
616
+ support = min(
617
+ entry.treated_effective_sample_size,
618
+ entry.control_effective_sample_size,
619
+ )
620
+ shrinkage = support / (support + self.prior_effective_sample_size)
621
+ effect = 0.0 if entry.effect_estimate is None else entry.effect_estimate
622
+ posterior_mean = entry.prior_score + shrinkage * effect
623
+ uncertainty = self.uncertainty_scale / math.sqrt(
624
+ support + self.prior_effective_sample_size
625
+ )
626
+ retrieval = posterior_mean + self.exploration_weight * uncertainty
627
+ expected = (
628
+ support,
629
+ shrinkage,
630
+ posterior_mean,
631
+ uncertainty,
632
+ retrieval,
633
+ )
634
+ observed = (
635
+ entry.effective_support,
636
+ entry.shrinkage,
637
+ entry.posterior_mean,
638
+ entry.uncertainty_bonus,
639
+ entry.retrieval_score,
640
+ )
641
+ if observed != expected:
642
+ raise ValueError(
643
+ "score entry does not match the frozen scoring formula"
644
+ )
645
+
646
+ # Re-run the existing causal estimator at this trust boundary. Besides
647
+ # defending the formula, this rejects duplicate credit/candidate units
648
+ # and silent mixtures of assignment or reward strata.
649
+ trials = tuple(observation.to_trial() for observation in self.observations)
650
+ for entry in self.entries:
651
+ estimate = estimate_marginal_effect(
652
+ trials,
653
+ entry.reference,
654
+ context_hash=self.exact_context_hash,
655
+ )
656
+ if (
657
+ entry.effect_estimate != estimate.effect
658
+ or entry.treated_trials != estimate.treated_trials
659
+ or entry.control_trials != estimate.control_trials
660
+ or entry.treated_effective_sample_size
661
+ != estimate.treated_effective_sample_size
662
+ or entry.control_effective_sample_size
663
+ != estimate.control_effective_sample_size
664
+ ):
665
+ raise ValueError("score entry does not match its causal evidence")
666
+
667
+ object.__setattr__(
668
+ self,
669
+ "snapshot_sha256",
670
+ _hash_record(_SCORE_SNAPSHOT_DOMAIN, self.to_record()),
671
+ )
672
+
673
+ @property
674
+ def retrieval_scores(self) -> dict[InsightRef, float]:
675
+ return {entry.reference: entry.retrieval_score for entry in self.entries}
676
+
677
+ def to_record(self) -> dict[str, object]:
678
+ return {
679
+ "schema_version": 1,
680
+ "exact_context_hash": self.exact_context_hash,
681
+ "estimand_stratum_hash": self.estimand_stratum_hash,
682
+ "checkpoint_index": self.checkpoint_index,
683
+ "entries": [entry.to_record() for entry in self.entries],
684
+ "observations": [value.to_record() for value in self.observations],
685
+ "prior_effective_sample_size_hex": (self.prior_effective_sample_size.hex()),
686
+ "uncertainty_scale_hex": self.uncertainty_scale.hex(),
687
+ "exploration_weight_hex": self.exploration_weight.hex(),
688
+ "reward_definition_hash": self.reward_definition_hash,
689
+ "parent_snapshot_sha256": self.parent_snapshot_sha256,
690
+ "source_wave_sha256": self.source_wave_sha256,
691
+ "scoring_policy_id": self.scoring_policy_id,
692
+ "scoring_policy_version": self.scoring_policy_version,
693
+ }
694
+
695
+
696
+ @dataclass(frozen=True, slots=True)
697
+ class CausalSearchScorePolicy:
698
+ """Frozen support-shrinkage and uncertainty-aware retrieval policy."""
699
+
700
+ prior_effective_sample_size: float = 4.0
701
+ uncertainty_scale: float = 1.0
702
+ exploration_weight: float = 0.25
703
+ policy_id: str = "min_ess_shrunken_causal_ucb"
704
+ policy_version: int = 1
705
+
706
+ def __post_init__(self) -> None:
707
+ for name in (
708
+ "prior_effective_sample_size",
709
+ "uncertainty_scale",
710
+ "exploration_weight",
711
+ ):
712
+ _require_canonical_float(getattr(self, name), name)
713
+ if self.prior_effective_sample_size <= 0:
714
+ raise ValueError("prior_effective_sample_size must be positive")
715
+ if self.uncertainty_scale < 0 or self.exploration_weight < 0:
716
+ raise ValueError("uncertainty parameters cannot be negative")
717
+ if self.policy_id != "min_ess_shrunken_causal_ucb":
718
+ raise ValueError("unsupported causal score policy_id")
719
+ if type(self.policy_version) is not int or self.policy_version != 1:
720
+ raise ValueError("unsupported causal score policy_version")
721
+
722
+ def genesis(
723
+ self,
724
+ *,
725
+ exact_context_hash: str,
726
+ estimand_stratum_hash: str,
727
+ priors: Mapping[InsightRef, Real],
728
+ ) -> MemoryScoreSnapshot:
729
+ """Create checkpoint zero without manufacturing causal evidence."""
730
+
731
+ if not isinstance(priors, Mapping) or not priors:
732
+ raise ValueError("priors must be a non-empty mapping")
733
+ if any(not isinstance(reference, InsightRef) for reference in priors):
734
+ raise TypeError("prior keys must be InsightRef values")
735
+ entries = tuple(
736
+ self._entry(
737
+ reference=reference,
738
+ prior_score=_canonical_float(priors[reference], "prior score"),
739
+ effect_estimate=None,
740
+ treated_trials=0,
741
+ control_trials=0,
742
+ treated_ess=0.0,
743
+ control_ess=0.0,
744
+ )
745
+ for reference in sorted(priors)
746
+ )
747
+ return MemoryScoreSnapshot(
748
+ exact_context_hash=exact_context_hash,
749
+ estimand_stratum_hash=estimand_stratum_hash,
750
+ checkpoint_index=0,
751
+ entries=entries,
752
+ observations=(),
753
+ prior_effective_sample_size=self.prior_effective_sample_size,
754
+ uncertainty_scale=self.uncertainty_scale,
755
+ exploration_weight=self.exploration_weight,
756
+ scoring_policy_id=self.policy_id,
757
+ scoring_policy_version=self.policy_version,
758
+ )
759
+
760
+ def score_evidence(
761
+ self,
762
+ *,
763
+ parent: MemoryScoreSnapshot,
764
+ observations: Sequence[CausalMemoryObservation],
765
+ reward_definition_hash: str,
766
+ source_wave_sha256: str,
767
+ ) -> MemoryScoreSnapshot:
768
+ """Recompute one immutable checkpoint from all sealed evidence."""
769
+
770
+ self._require_compatible(parent)
771
+ _require_sha256(reward_definition_hash, "reward_definition_hash")
772
+ _require_sha256(source_wave_sha256, "source_wave_sha256")
773
+ if isinstance(observations, (str, bytes)) or not isinstance(
774
+ observations, Sequence
775
+ ):
776
+ raise TypeError("observations must be a sequence")
777
+ current = tuple(observations)
778
+ if not current:
779
+ raise ValueError("a checkpoint requires at least one new observation")
780
+ if any(not isinstance(value, CausalMemoryObservation) for value in current):
781
+ raise TypeError("observations must contain CausalMemoryObservation values")
782
+ combined = tuple(
783
+ sorted(
784
+ (*parent.observations, *current),
785
+ key=lambda value: value.assignment.assignment_sha256,
786
+ )
787
+ )
788
+ trials = tuple(observation.to_trial() for observation in combined)
789
+ entries = []
790
+ for prior_entry in parent.entries:
791
+ estimate = estimate_marginal_effect(
792
+ trials,
793
+ prior_entry.reference,
794
+ context_hash=parent.exact_context_hash,
795
+ )
796
+ entries.append(
797
+ self._entry(
798
+ reference=prior_entry.reference,
799
+ prior_score=prior_entry.prior_score,
800
+ effect_estimate=estimate.effect,
801
+ treated_trials=estimate.treated_trials,
802
+ control_trials=estimate.control_trials,
803
+ treated_ess=estimate.treated_effective_sample_size,
804
+ control_ess=estimate.control_effective_sample_size,
805
+ )
806
+ )
807
+ return MemoryScoreSnapshot(
808
+ exact_context_hash=parent.exact_context_hash,
809
+ estimand_stratum_hash=parent.estimand_stratum_hash,
810
+ checkpoint_index=parent.checkpoint_index + 1,
811
+ entries=tuple(entries),
812
+ observations=combined,
813
+ prior_effective_sample_size=self.prior_effective_sample_size,
814
+ uncertainty_scale=self.uncertainty_scale,
815
+ exploration_weight=self.exploration_weight,
816
+ reward_definition_hash=reward_definition_hash,
817
+ parent_snapshot_sha256=parent.snapshot_sha256,
818
+ source_wave_sha256=source_wave_sha256,
819
+ scoring_policy_id=self.policy_id,
820
+ scoring_policy_version=self.policy_version,
821
+ )
822
+
823
+ def _entry(
824
+ self,
825
+ *,
826
+ reference: InsightRef,
827
+ prior_score: float,
828
+ effect_estimate: float | None,
829
+ treated_trials: int,
830
+ control_trials: int,
831
+ treated_ess: float,
832
+ control_ess: float,
833
+ ) -> CausalSearchScore:
834
+ support = min(treated_ess, control_ess)
835
+ shrinkage = support / (support + self.prior_effective_sample_size)
836
+ effect = 0.0 if effect_estimate is None else effect_estimate
837
+ posterior_mean = prior_score + shrinkage * effect
838
+ uncertainty = self.uncertainty_scale / math.sqrt(
839
+ support + self.prior_effective_sample_size
840
+ )
841
+ retrieval = posterior_mean + self.exploration_weight * uncertainty
842
+ return CausalSearchScore(
843
+ reference=reference,
844
+ prior_score=prior_score,
845
+ effect_estimate=effect_estimate,
846
+ treated_trials=treated_trials,
847
+ control_trials=control_trials,
848
+ treated_effective_sample_size=treated_ess,
849
+ control_effective_sample_size=control_ess,
850
+ effective_support=support,
851
+ shrinkage=shrinkage,
852
+ posterior_mean=posterior_mean,
853
+ uncertainty_bonus=uncertainty,
854
+ retrieval_score=retrieval,
855
+ )
856
+
857
+ def _require_compatible(self, snapshot: MemoryScoreSnapshot) -> None:
858
+ if not isinstance(snapshot, MemoryScoreSnapshot):
859
+ raise TypeError("snapshot must be a MemoryScoreSnapshot")
860
+ observed = (
861
+ snapshot.prior_effective_sample_size,
862
+ snapshot.uncertainty_scale,
863
+ snapshot.exploration_weight,
864
+ snapshot.scoring_policy_id,
865
+ snapshot.scoring_policy_version,
866
+ )
867
+ expected = (
868
+ self.prior_effective_sample_size,
869
+ self.uncertainty_scale,
870
+ self.exploration_weight,
871
+ self.policy_id,
872
+ self.policy_version,
873
+ )
874
+ if observed != expected:
875
+ raise ValueError("score policy differs from the parent snapshot")
876
+
877
+
878
+ @dataclass(frozen=True, slots=True)
879
+ class FrozenDiagnosticMemoryWave:
880
+ """Complete pre-call diagnostic assignments against one score checkpoint."""
881
+
882
+ wave_id: str
883
+ prior_snapshot: MemoryScoreSnapshot
884
+ assignments: tuple[ResolvedInsightAssignment, ...]
885
+ reward_definition_hash: str
886
+ no_yield_reward: float
887
+ wave_sha256: str = field(init=False)
888
+
889
+ def __post_init__(self) -> None:
890
+ _require_block_id(self.wave_id, "wave_id")
891
+ if not isinstance(self.prior_snapshot, MemoryScoreSnapshot):
892
+ raise TypeError("prior_snapshot must be a MemoryScoreSnapshot")
893
+ if type(self.assignments) is not tuple or not self.assignments:
894
+ raise ValueError("assignments must be a non-empty exact tuple")
895
+ if any(
896
+ not isinstance(value, ResolvedInsightAssignment)
897
+ for value in self.assignments
898
+ ):
899
+ raise TypeError("assignments must contain ResolvedInsightAssignment values")
900
+ hashes = tuple(value.assignment_sha256 for value in self.assignments)
901
+ if hashes != tuple(sorted(set(hashes))):
902
+ raise ValueError("assignments must be unique and canonically sorted")
903
+ credit_ids = tuple(value.credit_unit_id for value in self.assignments)
904
+ if len(set(credit_ids)) != len(credit_ids):
905
+ raise ValueError("a wave cannot repeat a credit unit")
906
+ for assignment in self.assignments:
907
+ if assignment.arm is not MemoryAssignmentArm.DIAGNOSTIC:
908
+ raise ValueError("a diagnostic wave can contain only diagnostic arms")
909
+ assignment.validate_against_snapshot(self.prior_snapshot)
910
+ first = self.assignments[0].selection_decision
911
+ stratum = (
912
+ first.eligible,
913
+ first.subset_size,
914
+ first.exploration_probability,
915
+ first.policy_id,
916
+ first.policy_version,
917
+ )
918
+ if any(
919
+ (
920
+ value.selection_decision.eligible,
921
+ value.selection_decision.subset_size,
922
+ value.selection_decision.exploration_probability,
923
+ value.selection_decision.policy_id,
924
+ value.selection_decision.policy_version,
925
+ )
926
+ != stratum
927
+ for value in self.assignments[1:]
928
+ ):
929
+ raise ValueError("a diagnostic wave cannot mix assignment-law strata")
930
+ _require_sha256(self.reward_definition_hash, "reward_definition_hash")
931
+ _require_canonical_float(self.no_yield_reward, "no_yield_reward")
932
+ if (
933
+ self.prior_snapshot.reward_definition_hash is not None
934
+ and self.prior_snapshot.reward_definition_hash
935
+ != self.reward_definition_hash
936
+ ):
937
+ raise ValueError("a diagnostic lineage cannot change reward definition")
938
+ object.__setattr__(
939
+ self,
940
+ "wave_sha256",
941
+ _hash_record(_WAVE_DOMAIN, self.to_record()),
942
+ )
943
+
944
+ def to_record(self) -> dict[str, object]:
945
+ return {
946
+ "schema_version": 1,
947
+ "wave_id": self.wave_id,
948
+ "prior_snapshot_sha256": self.prior_snapshot.snapshot_sha256,
949
+ "assignment_sha256s": [
950
+ value.assignment_sha256 for value in self.assignments
951
+ ],
952
+ "reward_definition_hash": self.reward_definition_hash,
953
+ "no_yield_reward_hex": self.no_yield_reward.hex(),
954
+ }
955
+
956
+
957
+ class MemoryCheckpointClosureStatus(str, Enum):
958
+ SEALED = "sealed"
959
+ INVALIDATED_INFRASTRUCTURE = "invalidated_infrastructure"
960
+
961
+
962
+ @dataclass(frozen=True, slots=True)
963
+ class MemoryCheckpointClosure:
964
+ """Explicit result: exactly one checkpoint or an invalidated wave."""
965
+
966
+ wave_sha256: str
967
+ status: MemoryCheckpointClosureStatus
968
+ receipts: tuple[MemoryAssignmentReceipt, ...]
969
+ observations: tuple[CausalMemoryObservation, ...]
970
+ snapshot: MemoryScoreSnapshot | None
971
+
972
+ def __post_init__(self) -> None:
973
+ _require_sha256(self.wave_sha256, "wave_sha256")
974
+ if not isinstance(self.status, MemoryCheckpointClosureStatus):
975
+ raise TypeError("status must be a MemoryCheckpointClosureStatus")
976
+ if type(self.receipts) is not tuple or any(
977
+ not isinstance(value, MemoryAssignmentReceipt) for value in self.receipts
978
+ ):
979
+ raise TypeError("receipts must contain MemoryAssignmentReceipt values")
980
+ receipt_hashes = tuple(value.assignment_sha256 for value in self.receipts)
981
+ if receipt_hashes != tuple(sorted(set(receipt_hashes))):
982
+ raise ValueError("closure receipts must be unique and canonically sorted")
983
+ if type(self.observations) is not tuple or any(
984
+ not isinstance(value, CausalMemoryObservation)
985
+ for value in self.observations
986
+ ):
987
+ raise TypeError("observations must contain CausalMemoryObservation values")
988
+ if self.status is MemoryCheckpointClosureStatus.SEALED:
989
+ if self.snapshot is None or not self.observations:
990
+ raise ValueError(
991
+ "a sealed closure requires observations and a snapshot"
992
+ )
993
+ if any(
994
+ receipt.status is MemoryTrialTerminalStatus.INFRASTRUCTURE_FAILURE
995
+ for receipt in self.receipts
996
+ ):
997
+ raise ValueError(
998
+ "a sealed closure cannot contain infrastructure failure"
999
+ )
1000
+ elif self.snapshot is not None or self.observations:
1001
+ raise ValueError(
1002
+ "an infrastructure-invalidated closure cannot publish evidence"
1003
+ )
1004
+ elif not any(
1005
+ receipt.status is MemoryTrialTerminalStatus.INFRASTRUCTURE_FAILURE
1006
+ for receipt in self.receipts
1007
+ ):
1008
+ raise ValueError(
1009
+ "an infrastructure-invalidated closure requires an infrastructure failure"
1010
+ )
1011
+
1012
+
1013
+ @dataclass(frozen=True, slots=True)
1014
+ class WaveSealedCheckpointBuilder:
1015
+ """Atomically convert one complete terminal wave into causal memory."""
1016
+
1017
+ score_policy: CausalSearchScorePolicy
1018
+
1019
+ def __post_init__(self) -> None:
1020
+ if not isinstance(self.score_policy, CausalSearchScorePolicy):
1021
+ raise TypeError("score_policy must be a CausalSearchScorePolicy")
1022
+
1023
+ def close(
1024
+ self,
1025
+ wave: FrozenDiagnosticMemoryWave,
1026
+ receipts: Sequence[MemoryAssignmentReceipt],
1027
+ ) -> MemoryCheckpointClosure:
1028
+ """Seal only an exact complete receipt set, independent of completion order."""
1029
+
1030
+ if not isinstance(wave, FrozenDiagnosticMemoryWave):
1031
+ raise TypeError("wave must be a FrozenDiagnosticMemoryWave")
1032
+ self.score_policy._require_compatible(wave.prior_snapshot)
1033
+ if isinstance(receipts, (str, bytes)) or not isinstance(receipts, Sequence):
1034
+ raise TypeError("receipts must be a sequence")
1035
+ terminal = tuple(receipts)
1036
+ if any(not isinstance(value, MemoryAssignmentReceipt) for value in terminal):
1037
+ raise TypeError("receipts must contain MemoryAssignmentReceipt values")
1038
+ by_hash: dict[str, MemoryAssignmentReceipt] = {}
1039
+ for receipt in terminal:
1040
+ if receipt.assignment_sha256 in by_hash:
1041
+ raise IncompleteMemoryWaveError(
1042
+ "a diagnostic assignment has more than one terminal receipt"
1043
+ )
1044
+ by_hash[receipt.assignment_sha256] = receipt
1045
+ expected = {value.assignment_sha256 for value in wave.assignments}
1046
+ observed = set(by_hash)
1047
+ if expected != observed:
1048
+ missing = sorted(expected - observed)
1049
+ extra = sorted(observed - expected)
1050
+ raise IncompleteMemoryWaveError(
1051
+ f"receipt set differs from frozen wave: missing={missing}, extra={extra}"
1052
+ )
1053
+ assignments = {value.assignment_sha256: value for value in wave.assignments}
1054
+ canonical_receipts = tuple(by_hash[key] for key in sorted(by_hash))
1055
+ for receipt in canonical_receipts:
1056
+ assignment = assignments[receipt.assignment_sha256]
1057
+ if receipt.credit_unit_id != assignment.credit_unit_id:
1058
+ raise ValueError("receipt credit unit differs from its assignment")
1059
+
1060
+ if any(
1061
+ value.status is MemoryTrialTerminalStatus.INFRASTRUCTURE_FAILURE
1062
+ for value in canonical_receipts
1063
+ ):
1064
+ return MemoryCheckpointClosure(
1065
+ wave_sha256=wave.wave_sha256,
1066
+ status=MemoryCheckpointClosureStatus.INVALIDATED_INFRASTRUCTURE,
1067
+ receipts=canonical_receipts,
1068
+ observations=(),
1069
+ snapshot=None,
1070
+ )
1071
+
1072
+ observations = []
1073
+ for receipt in canonical_receipts:
1074
+ reward = (
1075
+ receipt.observed_reward
1076
+ if receipt.status is MemoryTrialTerminalStatus.SUCCEEDED
1077
+ else wave.no_yield_reward
1078
+ )
1079
+ assert reward is not None # Enforced by MemoryAssignmentReceipt.
1080
+ observations.append(
1081
+ CausalMemoryObservation(
1082
+ assignment=assignments[receipt.assignment_sha256],
1083
+ status=receipt.status,
1084
+ candidate_ids=receipt.candidate_ids,
1085
+ credited_reward=reward,
1086
+ reward_definition_hash=wave.reward_definition_hash,
1087
+ )
1088
+ )
1089
+ canonical_observations = tuple(
1090
+ sorted(
1091
+ observations,
1092
+ key=lambda value: value.assignment.assignment_sha256,
1093
+ )
1094
+ )
1095
+ snapshot = self.score_policy.score_evidence(
1096
+ parent=wave.prior_snapshot,
1097
+ observations=canonical_observations,
1098
+ reward_definition_hash=wave.reward_definition_hash,
1099
+ source_wave_sha256=wave.wave_sha256,
1100
+ )
1101
+ return MemoryCheckpointClosure(
1102
+ wave_sha256=wave.wave_sha256,
1103
+ status=MemoryCheckpointClosureStatus.SEALED,
1104
+ receipts=canonical_receipts,
1105
+ observations=canonical_observations,
1106
+ snapshot=snapshot,
1107
+ )
1108
+
1109
+
1110
+ class _NoRandom:
1111
+ def randrange(self, stop: int) -> int: # pragma: no cover - exact 0/1 only.
1112
+ raise AssertionError(f"unexpected branch draw with stop={stop}")
1113
+
1114
+ def sample(self, population, k: int): # pragma: no cover - exploit only.
1115
+ raise AssertionError("unexpected subset sample")
1116
+
1117
+
1118
+ class _ExactSubsetRandom(_NoRandom):
1119
+ def __init__(self, selected: tuple[InsightRef, ...]) -> None:
1120
+ self._selected = selected
1121
+
1122
+ def sample(self, population, k: int) -> list[InsightRef]:
1123
+ if k != len(self._selected) or not set(self._selected).issubset(population):
1124
+ raise RuntimeError("internal exact subset does not match selector inputs")
1125
+ return list(self._selected)
1126
+
1127
+
1128
+ def _unrank_combination(
1129
+ values: tuple[InsightRef, ...], subset_size: int, rank: int
1130
+ ) -> tuple[InsightRef, ...]:
1131
+ count = len(values)
1132
+ combination_count = math.comb(count, subset_size)
1133
+ if type(rank) is not int or rank < 0 or rank >= combination_count:
1134
+ raise ValueError(
1135
+ f"subset_rank must lie in [0, {combination_count}) for this snapshot"
1136
+ )
1137
+ selected_indices = []
1138
+ remaining_rank = rank
1139
+ start = 0
1140
+ for position in range(subset_size):
1141
+ remaining_positions = subset_size - position - 1
1142
+ for index in range(start, count):
1143
+ suffix_count = math.comb(count - index - 1, remaining_positions)
1144
+ if remaining_rank < suffix_count:
1145
+ selected_indices.append(index)
1146
+ start = index + 1
1147
+ break
1148
+ remaining_rank -= suffix_count
1149
+ return tuple(values[index] for index in selected_indices)
1150
+
1151
+
1152
+ def _unrank_permutation(values: tuple[float, ...], rank: int) -> tuple[float, ...]:
1153
+ permutation_count = math.factorial(len(values))
1154
+ if type(rank) is not int or rank < 0 or rank >= permutation_count:
1155
+ raise ValueError(
1156
+ f"permutation_rank must lie in [0, {permutation_count}) for this snapshot"
1157
+ )
1158
+ remaining = list(values)
1159
+ result = []
1160
+ remaining_rank = rank
1161
+ for slots in range(len(values), 0, -1):
1162
+ block_size = math.factorial(slots - 1)
1163
+ index, remaining_rank = divmod(remaining_rank, block_size)
1164
+ result.append(remaining.pop(index))
1165
+ return tuple(result)
1166
+
1167
+
1168
+ @dataclass(frozen=True, slots=True)
1169
+ class DeterministicMemoryControlPolicy:
1170
+ """Exact replayable controls parameterized by recorded integer ranks.
1171
+
1172
+ A uniformly sampled integer in ``[0, n!)`` yields an exact uniform law over
1173
+ labelled score permutations; a uniformly sampled integer in ``[0, C(n,k))``
1174
+ yields an exact uniform law over k-subsets. This policy performs only the
1175
+ deterministic rank-to-realization mapping so randomization stays outside
1176
+ provider code and is trivial to replay.
1177
+ """
1178
+
1179
+ def adaptive(
1180
+ self,
1181
+ *,
1182
+ snapshot: MemoryScoreSnapshot,
1183
+ subset_size: int,
1184
+ ) -> InsightSelectionDecision:
1185
+ return self._select(
1186
+ snapshot=snapshot,
1187
+ subset_size=subset_size,
1188
+ scores=snapshot.retrieval_scores,
1189
+ exploration_probability=Fraction(0),
1190
+ rng=_NoRandom(),
1191
+ )
1192
+
1193
+ def score_shuffled(
1194
+ self,
1195
+ *,
1196
+ snapshot: MemoryScoreSnapshot,
1197
+ subset_size: int,
1198
+ permutation_rank: int,
1199
+ ) -> InsightSelectionDecision:
1200
+ references = tuple(entry.reference for entry in snapshot.entries)
1201
+ values = tuple(entry.retrieval_score for entry in snapshot.entries)
1202
+ permuted = _unrank_permutation(values, permutation_rank)
1203
+ return self._select(
1204
+ snapshot=snapshot,
1205
+ subset_size=subset_size,
1206
+ scores=dict(zip(references, permuted, strict=True)),
1207
+ exploration_probability=Fraction(0),
1208
+ rng=_NoRandom(),
1209
+ )
1210
+
1211
+ def uniform(
1212
+ self,
1213
+ *,
1214
+ snapshot: MemoryScoreSnapshot,
1215
+ subset_size: int,
1216
+ subset_rank: int,
1217
+ ) -> InsightSelectionDecision:
1218
+ references = tuple(entry.reference for entry in snapshot.entries)
1219
+ if type(subset_size) is not int or subset_size < 0:
1220
+ raise ValueError("subset_size must be a non-negative exact integer")
1221
+ if subset_size > len(references):
1222
+ raise ValueError("subset_size cannot exceed the snapshot size")
1223
+ selected = _unrank_combination(references, subset_size, subset_rank)
1224
+ return self._select(
1225
+ snapshot=snapshot,
1226
+ subset_size=subset_size,
1227
+ scores=snapshot.retrieval_scores,
1228
+ exploration_probability=Fraction(1),
1229
+ rng=_ExactSubsetRandom(selected),
1230
+ )
1231
+
1232
+ @staticmethod
1233
+ def _select(
1234
+ *,
1235
+ snapshot: MemoryScoreSnapshot,
1236
+ subset_size: int,
1237
+ scores: Mapping[InsightRef, Real],
1238
+ exploration_probability: Fraction,
1239
+ rng: _NoRandom,
1240
+ ) -> InsightSelectionDecision:
1241
+ if not isinstance(snapshot, MemoryScoreSnapshot):
1242
+ raise TypeError("snapshot must be a MemoryScoreSnapshot")
1243
+ return EpsilonGreedySubsetSelector(exploration_probability).select(
1244
+ context_hash=snapshot.exact_context_hash,
1245
+ eligible=tuple(entry.reference for entry in snapshot.entries),
1246
+ scores=scores,
1247
+ subset_size=subset_size,
1248
+ rng=rng,
1249
+ )
1250
+
1251
+
1252
+ __all__ = [
1253
+ "CausalMemoryObservation",
1254
+ "CausalSearchScore",
1255
+ "CausalSearchScorePolicy",
1256
+ "DelayedCreditMode",
1257
+ "DeterministicMemoryControlPolicy",
1258
+ "FrozenDiagnosticMemoryWave",
1259
+ "IncompleteMemoryWaveError",
1260
+ "MemoryAssignmentArm",
1261
+ "MemoryAssignmentReceipt",
1262
+ "MemoryCheckpointClosure",
1263
+ "MemoryCheckpointClosureStatus",
1264
+ "MemoryScoreSnapshot",
1265
+ "MemoryTrialTerminalStatus",
1266
+ "ResolvedInsightAssignment",
1267
+ "StaleMemorySnapshotError",
1268
+ "WaveSealedCheckpointBuilder",
1269
+ "insight_selection_decision_sha256",
1270
+ ]