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,714 @@
1
+ """Randomized fixed-size insight retrieval with logged causal propensities.
2
+
3
+ The policy mixes a deterministic top-k subset with a uniformly random k-subset.
4
+ That small, explicit exploration component gives every eligible insight a known
5
+ conditional inclusion probability. Accumulated trials can therefore estimate
6
+ selected-versus-unselected marginal effects without pretending that an unseen
7
+ insight caused the outcome of one batch.
8
+
9
+ The estimators are intentionally modest. They provide stabilized inverse-
10
+ propensity contrasts for one context stratum and an optional two-insight
11
+ interaction contrast. They do not claim to solve nonstationarity, interference,
12
+ or arbitrary high-order subset interactions; the complete decisions remain
13
+ available for richer offline models.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import math
19
+ from dataclasses import dataclass
20
+ from enum import Enum
21
+ from fractions import Fraction
22
+ from numbers import Real
23
+ from typing import Mapping, Optional, Protocol, Sequence, Tuple
24
+
25
+ from agent_evolve.domain.ids import CandidateId, OperatorInvocationId
26
+ from agent_evolve.domain.insight import InsightRef
27
+
28
+ _LOWER_SHA256 = frozenset("0123456789abcdef")
29
+
30
+
31
+ class RandomSubsetSource(Protocol):
32
+ """The narrow random-source surface needed by the selector."""
33
+
34
+ def randrange(self, stop: int) -> int: ...
35
+ def sample(self, population: Sequence[InsightRef], k: int) -> list[InsightRef]: ...
36
+
37
+
38
+ class InsightSelectionMode(str, Enum):
39
+ EXPLOIT = "exploit"
40
+ EXPLORE_UNIFORM = "explore_uniform"
41
+
42
+
43
+ def _require_hash(value: str, name: str) -> None:
44
+ if (
45
+ type(value) is not str
46
+ or len(value) != 64
47
+ or any(character not in _LOWER_SHA256 for character in value)
48
+ ):
49
+ raise ValueError(f"{name} must be a lowercase SHA-256 digest")
50
+
51
+
52
+ def _finite_score(value: Real, name: str) -> float:
53
+ if isinstance(value, bool) or not isinstance(value, Real):
54
+ raise TypeError(f"{name} must be a real number")
55
+ result = float(value)
56
+ if not math.isfinite(result):
57
+ raise ValueError(f"{name} must be finite")
58
+ return result
59
+
60
+
61
+ def _sorted_refs(values: Sequence[InsightRef]) -> Tuple[InsightRef, ...]:
62
+ result = tuple(sorted(values))
63
+ if any(not isinstance(value, InsightRef) for value in result):
64
+ raise TypeError("insight collections must contain InsightRef values")
65
+ if len(set(result)) != len(result):
66
+ raise ValueError("insight collections cannot contain duplicates")
67
+ return result
68
+
69
+
70
+ def _top_k(
71
+ score_snapshot: Tuple[Tuple[InsightRef, float], ...],
72
+ subset_size: int,
73
+ ) -> Tuple[InsightRef, ...]:
74
+ ranked = sorted(
75
+ score_snapshot,
76
+ key=lambda item: (-item[1], item[0].insight_id.value, item[0].version),
77
+ )
78
+ return tuple(sorted(reference for reference, _ in ranked[:subset_size]))
79
+
80
+
81
+ @dataclass(frozen=True, slots=True)
82
+ class InsightSelectionDecision:
83
+ """One replayable retrieval decision and its conditional assignment law."""
84
+
85
+ context_hash: str
86
+ eligible: Tuple[InsightRef, ...]
87
+ selected: Tuple[InsightRef, ...]
88
+ exploitation_subset: Tuple[InsightRef, ...]
89
+ score_snapshot: Tuple[Tuple[InsightRef, float], ...]
90
+ subset_size: int
91
+ exploration_probability: Fraction
92
+ mode: InsightSelectionMode
93
+ selected_subset_probability: Fraction
94
+ policy_id: str = "epsilon_greedy_uniform_k_subset"
95
+ policy_version: int = 1
96
+
97
+ def __post_init__(self) -> None:
98
+ _require_hash(self.context_hash, "context_hash")
99
+ eligible = _sorted_refs(self.eligible)
100
+ selected = _sorted_refs(self.selected)
101
+ exploitation = _sorted_refs(self.exploitation_subset)
102
+ if eligible != self.eligible or selected != self.selected:
103
+ raise ValueError("eligible and selected insights must use canonical sorted order")
104
+ if exploitation != self.exploitation_subset:
105
+ raise ValueError("exploitation_subset must use canonical sorted order")
106
+ if type(self.subset_size) is not int or self.subset_size < 0:
107
+ raise ValueError("subset_size must be a non-negative integer")
108
+ if self.subset_size > len(eligible):
109
+ raise ValueError("subset_size cannot exceed the eligible set")
110
+ if len(selected) != self.subset_size or len(exploitation) != self.subset_size:
111
+ raise ValueError("selected subsets must have exactly subset_size members")
112
+ if not set(selected).issubset(eligible) or not set(exploitation).issubset(eligible):
113
+ raise ValueError("selected subsets must be drawn from eligible insights")
114
+ if type(self.exploration_probability) is not Fraction:
115
+ raise TypeError("exploration_probability must be an exact Fraction")
116
+ if not Fraction(0) <= self.exploration_probability <= Fraction(1):
117
+ raise ValueError("exploration_probability must lie in [0,1]")
118
+ if not isinstance(self.mode, InsightSelectionMode):
119
+ raise TypeError("mode must be an InsightSelectionMode")
120
+ if (
121
+ self.exploration_probability == 0
122
+ and self.mode is not InsightSelectionMode.EXPLOIT
123
+ ):
124
+ raise ValueError("zero exploration probability requires exploit mode")
125
+ if (
126
+ self.exploration_probability == 1
127
+ and self.mode is not InsightSelectionMode.EXPLORE_UNIFORM
128
+ ):
129
+ raise ValueError("unit exploration probability requires uniform-exploration mode")
130
+ if type(self.selected_subset_probability) is not Fraction:
131
+ raise TypeError("selected_subset_probability must be an exact Fraction")
132
+ if self.selected_subset_probability <= 0 or self.selected_subset_probability > 1:
133
+ raise ValueError("selected_subset_probability must lie in (0,1]")
134
+ if type(self.policy_id) is not str or self.policy_id != "epsilon_greedy_uniform_k_subset":
135
+ raise ValueError("unsupported insight selection policy_id")
136
+ if type(self.policy_version) is not int or self.policy_version != 1:
137
+ raise ValueError("unsupported insight selection policy_version")
138
+
139
+ if type(self.score_snapshot) is not tuple:
140
+ raise TypeError("score_snapshot must be an immutable tuple")
141
+ score_refs = []
142
+ canonical_scores = []
143
+ for item in self.score_snapshot:
144
+ if type(item) is not tuple or len(item) != 2:
145
+ raise TypeError("score_snapshot entries must be (InsightRef, score) tuples")
146
+ reference, score = item
147
+ if not isinstance(reference, InsightRef):
148
+ raise TypeError("score_snapshot keys must be InsightRef values")
149
+ score_refs.append(reference)
150
+ canonical_scores.append((reference, _finite_score(score, "insight score")))
151
+ if tuple(canonical_scores) != self.score_snapshot:
152
+ raise TypeError("score_snapshot scores must already be canonical floats")
153
+ if tuple(score_refs) != eligible:
154
+ raise ValueError("score_snapshot must align exactly with canonical eligible insights")
155
+ if _top_k(self.score_snapshot, self.subset_size) != exploitation:
156
+ raise ValueError("exploitation_subset does not match the recorded scores")
157
+
158
+ expected_subset_probability = self._subset_probability(selected)
159
+ if self.selected_subset_probability != expected_subset_probability:
160
+ raise ValueError("selected_subset_probability does not match the policy law")
161
+ if self.mode is InsightSelectionMode.EXPLOIT and selected != exploitation:
162
+ raise ValueError("exploit mode must select the exploitation subset")
163
+
164
+ @property
165
+ def credit_identifiable(self) -> bool:
166
+ """Whether individual inclusion has overlap under this decision law."""
167
+
168
+ return any(
169
+ Fraction(0) < self.inclusion_probability(reference) < Fraction(1)
170
+ for reference in self.eligible
171
+ )
172
+
173
+ def _uniform_subset_probability(self) -> Fraction:
174
+ count = math.comb(len(self.eligible), self.subset_size)
175
+ return Fraction(1, count)
176
+
177
+ def _subset_probability(self, subset: Tuple[InsightRef, ...]) -> Fraction:
178
+ probability = self.exploration_probability * self._uniform_subset_probability()
179
+ if subset == self.exploitation_subset:
180
+ probability += 1 - self.exploration_probability
181
+ return probability
182
+
183
+ def inclusion_probability(self, reference: InsightRef) -> Fraction:
184
+ """Return the exact conditional probability that ``reference`` is selected."""
185
+
186
+ if reference not in self.eligible:
187
+ raise ValueError("insight was not eligible for this decision")
188
+ count = len(self.eligible)
189
+ uniform = Fraction(self.subset_size, count) if count else Fraction(0)
190
+ exploit = Fraction(int(reference in self.exploitation_subset))
191
+ return self.exploration_probability * uniform + (1 - self.exploration_probability) * exploit
192
+
193
+ def joint_cell_probability(
194
+ self,
195
+ first: InsightRef,
196
+ second: InsightRef,
197
+ first_selected: bool,
198
+ second_selected: bool,
199
+ ) -> Fraction:
200
+ """Exact probability for one two-insight inclusion/exclusion cell."""
201
+
202
+ if first == second:
203
+ raise ValueError("pair probabilities require two distinct insights")
204
+ if type(first_selected) is not bool or type(second_selected) is not bool:
205
+ raise TypeError("pair cell flags must be bool")
206
+ if first not in self.eligible or second not in self.eligible:
207
+ raise ValueError("both insights must be eligible for this decision")
208
+ count = len(self.eligible)
209
+ if count < 2:
210
+ raise ValueError("pair probabilities require at least two eligible insights")
211
+ uniform_both = Fraction(
212
+ self.subset_size * (self.subset_size - 1),
213
+ count * (count - 1),
214
+ )
215
+ exploit_both = Fraction(
216
+ int(first in self.exploitation_subset and second in self.exploitation_subset)
217
+ )
218
+ both = (
219
+ self.exploration_probability * uniform_both
220
+ + (1 - self.exploration_probability) * exploit_both
221
+ )
222
+ first_probability = self.inclusion_probability(first)
223
+ second_probability = self.inclusion_probability(second)
224
+ cells = {
225
+ (True, True): both,
226
+ (True, False): first_probability - both,
227
+ (False, True): second_probability - both,
228
+ (False, False): 1 - first_probability - second_probability + both,
229
+ }
230
+ probability = cells[(first_selected, second_selected)]
231
+ if probability < 0: # pragma: no cover - algebraic implementation guard.
232
+ raise RuntimeError("invalid negative pair-cell probability")
233
+ return probability
234
+
235
+
236
+ @dataclass(frozen=True, slots=True)
237
+ class EpsilonGreedySubsetSelector:
238
+ """Mix deterministic score exploitation with uniform fixed-size exploration."""
239
+
240
+ exploration_probability: Fraction = Fraction(1, 4)
241
+
242
+ def __post_init__(self) -> None:
243
+ if type(self.exploration_probability) is not Fraction:
244
+ raise TypeError("exploration_probability must be an exact Fraction")
245
+ if not Fraction(0) <= self.exploration_probability <= Fraction(1):
246
+ raise ValueError("exploration_probability must lie in [0,1]")
247
+
248
+ def select(
249
+ self,
250
+ *,
251
+ context_hash: str,
252
+ eligible: Sequence[InsightRef],
253
+ scores: Mapping[InsightRef, Real],
254
+ subset_size: int,
255
+ rng: RandomSubsetSource,
256
+ ) -> InsightSelectionDecision:
257
+ """Select a subset and record the complete conditional assignment law."""
258
+
259
+ _require_hash(context_hash, "context_hash")
260
+ canonical_eligible = _sorted_refs(eligible)
261
+ if type(subset_size) is not int or subset_size < 0:
262
+ raise ValueError("subset_size must be a non-negative integer")
263
+ if subset_size > len(canonical_eligible):
264
+ raise ValueError("subset_size cannot exceed the eligible set")
265
+ if set(scores) != set(canonical_eligible):
266
+ raise ValueError("scores must contain exactly the eligible insight references")
267
+ score_snapshot = tuple(
268
+ (reference, _finite_score(scores[reference], "insight score"))
269
+ for reference in canonical_eligible
270
+ )
271
+ exploitation = _top_k(score_snapshot, subset_size)
272
+
273
+ if self.exploration_probability == 0:
274
+ mode = InsightSelectionMode.EXPLOIT
275
+ elif self.exploration_probability == 1:
276
+ mode = InsightSelectionMode.EXPLORE_UNIFORM
277
+ else:
278
+ # Integer sampling preserves the exact logged Fraction law. A
279
+ # float threshold would silently implement a nearby dyadic law for
280
+ # most rational epsilon values and can double very small rates.
281
+ denominator = self.exploration_probability.denominator
282
+ draw = rng.randrange(denominator)
283
+ if type(draw) is not int:
284
+ raise TypeError("random source randrange must return an integer")
285
+ if draw < 0 or draw >= denominator:
286
+ raise ValueError("random source randrange result is out of bounds")
287
+ mode = (
288
+ InsightSelectionMode.EXPLORE_UNIFORM
289
+ if draw < self.exploration_probability.numerator
290
+ else InsightSelectionMode.EXPLOIT
291
+ )
292
+
293
+ if mode is InsightSelectionMode.EXPLORE_UNIFORM:
294
+ sampled = rng.sample(canonical_eligible, subset_size)
295
+ selected = _sorted_refs(sampled)
296
+ if len(selected) != subset_size or not set(selected).issubset(canonical_eligible):
297
+ raise ValueError("random source returned an invalid uniform subset")
298
+ else:
299
+ selected = exploitation
300
+
301
+ uniform_probability = Fraction(1, math.comb(len(canonical_eligible), subset_size))
302
+ selected_probability = self.exploration_probability * uniform_probability
303
+ if selected == exploitation:
304
+ selected_probability += 1 - self.exploration_probability
305
+ return InsightSelectionDecision(
306
+ context_hash=context_hash,
307
+ eligible=canonical_eligible,
308
+ selected=selected,
309
+ exploitation_subset=exploitation,
310
+ score_snapshot=score_snapshot,
311
+ subset_size=subset_size,
312
+ exploration_probability=self.exploration_probability,
313
+ mode=mode,
314
+ selected_subset_probability=selected_probability,
315
+ )
316
+
317
+
318
+ @dataclass(frozen=True, slots=True)
319
+ class InsightTrial:
320
+ """One randomized selection unit and its predeclared aggregate reward.
321
+
322
+ A single operator invocation may generate several candidates from the same
323
+ selected insight subset. Those candidates must be aggregated into this one
324
+ credit unit; copying the reward into one trial per child would be
325
+ pseudoreplication and is rejected through unique invocation/candidate checks.
326
+ """
327
+
328
+ credit_unit_id: OperatorInvocationId
329
+ candidate_ids: Tuple[CandidateId, ...]
330
+ reward_definition_hash: str
331
+ decision: InsightSelectionDecision
332
+ reward: float
333
+ treatment_binding_sha256: str | None = None
334
+ generation: int | None = None
335
+
336
+ def __post_init__(self) -> None:
337
+ if not isinstance(self.credit_unit_id, OperatorInvocationId):
338
+ raise TypeError("credit_unit_id must be an OperatorInvocationId")
339
+ if type(self.candidate_ids) is not tuple:
340
+ raise TypeError("candidate_ids must be an immutable tuple")
341
+ if any(not isinstance(value, CandidateId) for value in self.candidate_ids):
342
+ raise TypeError("candidate_ids must contain only CandidateId values")
343
+ if len(set(self.candidate_ids)) != len(self.candidate_ids):
344
+ raise ValueError("candidate_ids cannot contain duplicates")
345
+ _require_hash(self.reward_definition_hash, "reward_definition_hash")
346
+ if not isinstance(self.decision, InsightSelectionDecision):
347
+ raise TypeError("decision must be an InsightSelectionDecision")
348
+ if type(self.reward) is not float or not math.isfinite(self.reward):
349
+ raise TypeError("reward must be a finite canonical float")
350
+ if self.treatment_binding_sha256 is not None:
351
+ _require_hash(
352
+ self.treatment_binding_sha256,
353
+ "treatment_binding_sha256",
354
+ )
355
+ if self.generation is not None and (
356
+ type(self.generation) is not int or self.generation <= 0
357
+ ):
358
+ raise ValueError("generation must be a positive exact integer or None")
359
+
360
+
361
+ @dataclass(frozen=True, slots=True)
362
+ class MarginalEffectEstimate:
363
+ insight: InsightRef
364
+ context_hash: Optional[str]
365
+ subset_size: Optional[int]
366
+ exploration_probability: Optional[Fraction]
367
+ reward_definition_hash: Optional[str]
368
+ policy_id: Optional[str]
369
+ policy_version: Optional[int]
370
+ effect: Optional[float]
371
+ treated_mean: Optional[float]
372
+ control_mean: Optional[float]
373
+ treated_trials: int
374
+ control_trials: int
375
+ treated_effective_sample_size: float
376
+ control_effective_sample_size: float
377
+ eligible_trials: int
378
+ overlap_trials: int
379
+
380
+ @property
381
+ def identified(self) -> bool:
382
+ return self.effect is not None
383
+
384
+
385
+ @dataclass(frozen=True, slots=True)
386
+ class PairSynergyEstimate:
387
+ first: InsightRef
388
+ second: InsightRef
389
+ context_hash: Optional[str]
390
+ subset_size: Optional[int]
391
+ exploration_probability: Optional[Fraction]
392
+ reward_definition_hash: Optional[str]
393
+ policy_id: Optional[str]
394
+ policy_version: Optional[int]
395
+ synergy: Optional[float]
396
+ cell_means: Tuple[Tuple[str, Optional[float]], ...]
397
+ cell_trials: Tuple[Tuple[str, int], ...]
398
+ cell_effective_sample_sizes: Tuple[Tuple[str, float], ...]
399
+ eligible_trials: int
400
+ overlap_trials: int
401
+
402
+ @property
403
+ def identified(self) -> bool:
404
+ return self.synergy is not None
405
+
406
+
407
+ @dataclass(frozen=True, slots=True)
408
+ class _WeightedSummary:
409
+ mean: Optional[float]
410
+ exact_mean: Optional[Fraction]
411
+ trials: int
412
+ effective_sample_size: float
413
+
414
+
415
+ def _validate_trials(trials: Sequence[InsightTrial]) -> None:
416
+ credit_unit_ids = []
417
+ candidate_ids = []
418
+ for trial in trials:
419
+ if not isinstance(trial, InsightTrial):
420
+ raise TypeError("trials must contain InsightTrial values")
421
+ credit_unit_ids.append(trial.credit_unit_id)
422
+ candidate_ids.extend(trial.candidate_ids)
423
+ if len(set(credit_unit_ids)) != len(credit_unit_ids):
424
+ raise ValueError("an operator invocation may appear in the credit trial set only once")
425
+ if len(set(candidate_ids)) != len(candidate_ids):
426
+ raise ValueError("a candidate may appear in the insight-credit trial set only once")
427
+
428
+
429
+ def _validate_reward_definition(trials: Sequence[InsightTrial]) -> Optional[str]:
430
+ definitions = {trial.reward_definition_hash for trial in trials}
431
+ if len(definitions) > 1:
432
+ raise ValueError("an insight-credit estimate cannot mix reward definitions")
433
+ return next(iter(definitions), None)
434
+
435
+
436
+ def _weighted_summary(observations: Sequence[Tuple[float, Fraction]]) -> _WeightedSummary:
437
+ if not observations:
438
+ return _WeightedSummary(None, None, 0, 0.0)
439
+ # Accumulate the complete Hájek numerator and denominator as rationals.
440
+ # Normalizing weights and rewards in separate float domains can double-
441
+ # underflow a product even when the final weighted mean is representable.
442
+ # Fraction.from_float preserves the exact admitted binary-float reward.
443
+ exact_weights = [1 / probability for _, probability in observations]
444
+ rewards = [reward for reward, _ in observations]
445
+ weight_sum = sum(exact_weights, Fraction(0))
446
+ weighted_reward_sum = sum(
447
+ (
448
+ weight * Fraction.from_float(reward)
449
+ for weight, reward in zip(exact_weights, rewards, strict=True)
450
+ ),
451
+ Fraction(0),
452
+ )
453
+ exact_mean = weighted_reward_sum / weight_sum
454
+ mean = _exact_to_finite_float(exact_mean, "weighted mean")
455
+ squared_weight_sum = sum(
456
+ (weight * weight for weight in exact_weights), Fraction(0)
457
+ )
458
+ exact_effective_sample_size = weight_sum * weight_sum / squared_weight_sum
459
+ effective_sample_size = _exact_to_finite_float(
460
+ exact_effective_sample_size, "effective sample size"
461
+ )
462
+ return _WeightedSummary(
463
+ mean, exact_mean, len(observations), effective_sample_size
464
+ )
465
+
466
+
467
+ def _validate_estimand_stratum(
468
+ trials: Sequence[InsightTrial],
469
+ requested_context_hash: Optional[str],
470
+ assignment_trials: Sequence[InsightTrial],
471
+ ) -> tuple[
472
+ Optional[str], Optional[int], Optional[Fraction], Optional[str], Optional[int]
473
+ ]:
474
+ """Reject silent pooling across distinct policy-relative estimands."""
475
+
476
+ contexts = {trial.decision.context_hash for trial in trials}
477
+ if requested_context_hash is None:
478
+ if len(contexts) > 1:
479
+ raise ValueError(
480
+ "an insight-credit estimate cannot mix context strata"
481
+ )
482
+ resolved_context = next(iter(contexts), None)
483
+ else:
484
+ resolved_context = requested_context_hash
485
+ if any(context != requested_context_hash for context in contexts):
486
+ raise ValueError("included trial does not match the requested context stratum")
487
+
488
+ subset_sizes = {trial.decision.subset_size for trial in trials}
489
+ if len(subset_sizes) > 1:
490
+ raise ValueError(
491
+ "an insight-credit estimate cannot mix subset-size estimands"
492
+ )
493
+ exploration_probabilities = {
494
+ trial.decision.exploration_probability for trial in assignment_trials
495
+ }
496
+ if len(exploration_probabilities) > 1:
497
+ raise ValueError(
498
+ "an insight-credit estimate cannot mix exploration-policy strata"
499
+ )
500
+ policies = {
501
+ (trial.decision.policy_id, trial.decision.policy_version) for trial in trials
502
+ }
503
+ if len(policies) > 1:
504
+ raise ValueError("an insight-credit estimate cannot mix policy versions")
505
+ policy_id, policy_version = next(iter(policies), (None, None))
506
+ return (
507
+ resolved_context,
508
+ next(iter(subset_sizes), None),
509
+ next(iter(exploration_probabilities), None),
510
+ policy_id,
511
+ policy_version,
512
+ )
513
+
514
+
515
+ def _exact_to_finite_float(value: Fraction, name: str) -> float:
516
+ try:
517
+ result = float(value)
518
+ except (OverflowError, ValueError):
519
+ raise ValueError(f"{name} cannot be represented as a finite float") from None
520
+ if not math.isfinite(result) or (value != 0 and result == 0.0):
521
+ raise ValueError(f"{name} cannot be represented as a finite float")
522
+ return result
523
+
524
+
525
+ def _finite_linear_contrast(
526
+ values: Sequence[Fraction], coefficients: Sequence[int]
527
+ ) -> float:
528
+ if len(values) != len(coefficients) or not values:
529
+ raise ValueError("linear contrast inputs are invalid")
530
+ result = sum(
531
+ (
532
+ coefficient * value
533
+ for value, coefficient in zip(values, coefficients, strict=True)
534
+ ),
535
+ Fraction(0),
536
+ )
537
+ return _exact_to_finite_float(result, "effect contrast")
538
+
539
+
540
+ def estimate_marginal_effect(
541
+ trials: Sequence[InsightTrial],
542
+ insight: InsightRef,
543
+ *,
544
+ context_hash: Optional[str] = None,
545
+ ) -> MarginalEffectEstimate:
546
+ """Estimate selected-minus-unselected reward with stabilized IPW means.
547
+
548
+ Propensities are conditional on each trial's eligible set and score state.
549
+ Decisions with deterministic inclusion or exclusion provide no overlap and
550
+ are reported but excluded from the contrast.
551
+ """
552
+
553
+ _validate_trials(trials)
554
+ if not isinstance(insight, InsightRef):
555
+ raise TypeError("insight must be an InsightRef")
556
+ if context_hash is not None:
557
+ _require_hash(context_hash, "context_hash")
558
+ eligible_trials = 0
559
+ overlap_trials = 0
560
+ assignment_trials: list[InsightTrial] = []
561
+ treated: list[Tuple[float, Fraction]] = []
562
+ control: list[Tuple[float, Fraction]] = []
563
+ included_trials: list[InsightTrial] = []
564
+ for trial in trials:
565
+ decision = trial.decision
566
+ if context_hash is not None and decision.context_hash != context_hash:
567
+ continue
568
+ if insight not in decision.eligible:
569
+ continue
570
+ included_trials.append(trial)
571
+ eligible_trials += 1
572
+ inclusion = decision.inclusion_probability(insight)
573
+ if inclusion <= 0 or inclusion >= 1:
574
+ continue
575
+ overlap_trials += 1
576
+ assignment_trials.append(trial)
577
+ if insight in decision.selected:
578
+ treated.append((trial.reward, inclusion))
579
+ else:
580
+ control.append((trial.reward, 1 - inclusion))
581
+ reward_definition_hash = _validate_reward_definition(included_trials)
582
+ (
583
+ resolved_context,
584
+ subset_size,
585
+ exploration_probability,
586
+ policy_id,
587
+ policy_version,
588
+ ) = _validate_estimand_stratum(
589
+ included_trials, context_hash, assignment_trials
590
+ )
591
+ treated_summary = _weighted_summary(treated)
592
+ control_summary = _weighted_summary(control)
593
+ effect = None
594
+ if (
595
+ treated_summary.exact_mean is not None
596
+ and control_summary.exact_mean is not None
597
+ ):
598
+ effect = _finite_linear_contrast(
599
+ (treated_summary.exact_mean, control_summary.exact_mean), (1, -1)
600
+ )
601
+ return MarginalEffectEstimate(
602
+ insight=insight,
603
+ context_hash=resolved_context,
604
+ subset_size=subset_size,
605
+ exploration_probability=exploration_probability,
606
+ reward_definition_hash=reward_definition_hash,
607
+ policy_id=policy_id,
608
+ policy_version=policy_version,
609
+ effect=effect,
610
+ treated_mean=treated_summary.mean,
611
+ control_mean=control_summary.mean,
612
+ treated_trials=treated_summary.trials,
613
+ control_trials=control_summary.trials,
614
+ treated_effective_sample_size=treated_summary.effective_sample_size,
615
+ control_effective_sample_size=control_summary.effective_sample_size,
616
+ eligible_trials=eligible_trials,
617
+ overlap_trials=overlap_trials,
618
+ )
619
+
620
+
621
+ def estimate_pair_synergy(
622
+ trials: Sequence[InsightTrial],
623
+ first: InsightRef,
624
+ second: InsightRef,
625
+ *,
626
+ context_hash: Optional[str] = None,
627
+ ) -> PairSynergyEstimate:
628
+ """Estimate ``E11 - E10 - E01 + E00`` for two insight versions.
629
+
630
+ Only decisions assigning positive probability to all four pair cells are
631
+ eligible for this interaction contrast. This makes fixed-size designs with
632
+ structurally impossible cells fail closed instead of fabricating synergy.
633
+ """
634
+
635
+ _validate_trials(trials)
636
+ if not isinstance(first, InsightRef) or not isinstance(second, InsightRef):
637
+ raise TypeError("pair members must be InsightRef values")
638
+ if first == second:
639
+ raise ValueError("pair synergy requires two distinct insight versions")
640
+ if second < first:
641
+ first, second = second, first
642
+ if context_hash is not None:
643
+ _require_hash(context_hash, "context_hash")
644
+ cell_order = ((True, True), (True, False), (False, True), (False, False))
645
+ cell_names = {
646
+ (True, True): "11",
647
+ (True, False): "10",
648
+ (False, True): "01",
649
+ (False, False): "00",
650
+ }
651
+ observations: dict[Tuple[bool, bool], list[Tuple[float, Fraction]]] = {
652
+ cell: [] for cell in cell_order
653
+ }
654
+ eligible_trials = 0
655
+ overlap_trials = 0
656
+ assignment_trials: list[InsightTrial] = []
657
+ included_trials: list[InsightTrial] = []
658
+ for trial in trials:
659
+ decision = trial.decision
660
+ if context_hash is not None and decision.context_hash != context_hash:
661
+ continue
662
+ if first not in decision.eligible or second not in decision.eligible:
663
+ continue
664
+ included_trials.append(trial)
665
+ eligible_trials += 1
666
+ probabilities = {
667
+ cell: decision.joint_cell_probability(first, second, *cell)
668
+ for cell in cell_order
669
+ }
670
+ if any(probability <= 0 for probability in probabilities.values()):
671
+ continue
672
+ overlap_trials += 1
673
+ assignment_trials.append(trial)
674
+ observed_cell = (first in decision.selected, second in decision.selected)
675
+ observations[observed_cell].append(
676
+ (trial.reward, probabilities[observed_cell])
677
+ )
678
+ reward_definition_hash = _validate_reward_definition(included_trials)
679
+ (
680
+ resolved_context,
681
+ subset_size,
682
+ exploration_probability,
683
+ policy_id,
684
+ policy_version,
685
+ ) = _validate_estimand_stratum(
686
+ included_trials, context_hash, assignment_trials
687
+ )
688
+ summaries = {cell: _weighted_summary(observations[cell]) for cell in cell_order}
689
+ means = {cell: summaries[cell].mean for cell in cell_order}
690
+ exact_means = {cell: summaries[cell].exact_mean for cell in cell_order}
691
+ synergy = None
692
+ if all(exact_means[cell] is not None for cell in cell_order):
693
+ synergy = _finite_linear_contrast(
694
+ tuple(exact_means[cell] for cell in cell_order), # type: ignore[arg-type]
695
+ (1, -1, -1, 1),
696
+ )
697
+ return PairSynergyEstimate(
698
+ first=first,
699
+ second=second,
700
+ context_hash=resolved_context,
701
+ subset_size=subset_size,
702
+ exploration_probability=exploration_probability,
703
+ reward_definition_hash=reward_definition_hash,
704
+ policy_id=policy_id,
705
+ policy_version=policy_version,
706
+ synergy=synergy,
707
+ cell_means=tuple((cell_names[cell], summaries[cell].mean) for cell in cell_order),
708
+ cell_trials=tuple((cell_names[cell], summaries[cell].trials) for cell in cell_order),
709
+ cell_effective_sample_sizes=tuple(
710
+ (cell_names[cell], summaries[cell].effective_sample_size) for cell in cell_order
711
+ ),
712
+ eligible_trials=eligible_trials,
713
+ overlap_trials=overlap_trials,
714
+ )