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,1454 @@
1
+ """Immutable event schema and canonical JSON codec.
2
+
3
+ The event vocabulary is intentionally small but covers the accounting boundaries
4
+ needed by the current loop. Large or mutable values are represented by artifact
5
+ IDs; objective vectors use immutable tuples.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import math
12
+ import re
13
+ import types
14
+ from dataclasses import dataclass, fields
15
+ from datetime import datetime, timezone
16
+ from decimal import Decimal
17
+ from enum import Enum
18
+ from typing import (
19
+ Any,
20
+ ClassVar,
21
+ Dict,
22
+ Optional,
23
+ Tuple,
24
+ Type,
25
+ Union,
26
+ get_args,
27
+ get_origin,
28
+ get_type_hints,
29
+ )
30
+
31
+ from agent_evolve.domain.artifact import ArtifactRef, ArtifactRole
32
+ from agent_evolve.domain.ids import (
33
+ ArtifactId,
34
+ CandidateId,
35
+ CorrelationId,
36
+ EvaluationAttemptId,
37
+ EvaluationId,
38
+ EventId,
39
+ GenerationId,
40
+ ID_TYPES,
41
+ LLMCallId,
42
+ OperatorInvocationId,
43
+ ProviderAttemptId,
44
+ RunId,
45
+ StableId,
46
+ )
47
+ from agent_evolve.domain.inline_text import InlineTextPolicy, validate_inline_text
48
+ from agent_evolve.domain.outcome import (
49
+ FailureCategory,
50
+ FailureCode,
51
+ FailureRecord,
52
+ validate_failure_pair,
53
+ )
54
+
55
+ CURRENT_EVENT_SCHEMA_VERSION = 1
56
+ ObjectiveValues = Tuple[Tuple[str, float], ...]
57
+ _POLICY_COMPONENT = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
58
+ _POLICY_CREDENTIAL_SHAPE = re.compile(
59
+ r"(?:(?<![A-Za-z0-9])(?:sk|pk|rk)-[A-Za-z0-9_-]{12,}(?![A-Za-z0-9])|"
60
+ r"(?<![A-Za-z0-9])or-v1-[A-Za-z0-9_-]{12,}(?![A-Za-z0-9])|"
61
+ r"(?<![A-Za-z0-9])(?:ghp_[A-Za-z0-9]{20,}|"
62
+ r"github_pat_[A-Za-z0-9_]{20,})(?![A-Za-z0-9])|"
63
+ r"(?<![A-Za-z0-9])glpat-[A-Za-z0-9_-]{12,}(?![A-Za-z0-9])|"
64
+ r"(?<![A-Za-z0-9])AKIA[0-9A-Z]{16}(?![A-Za-z0-9])|"
65
+ r"(?<![A-Za-z0-9])eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\."
66
+ r"[A-Za-z0-9_-]{8,}(?![A-Za-z0-9]))",
67
+ re.IGNORECASE,
68
+ )
69
+ _DECIMAL_TEXT = re.compile(r"^-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$")
70
+ _MAX_DECIMAL_DIGITS = 64
71
+ _MAX_DECIMAL_ABS_EXPONENT = 64
72
+ _MAX_DECIMAL_TEXT_LENGTH = 128
73
+ _DECODE_FAILED = object()
74
+
75
+
76
+ class EventCodecError(ValueError):
77
+ """An event record does not conform to the supported durable schema."""
78
+
79
+
80
+ class ValidationStage(str, Enum):
81
+ SCHEMA = "schema"
82
+ DETERMINISTIC_PRECHECK = "deterministic_precheck"
83
+
84
+
85
+ class EventPayload:
86
+ """Marker base for frozen payload value objects."""
87
+
88
+ EVENT_TYPE: ClassVar[str]
89
+
90
+
91
+ @dataclass(frozen=True, slots=True)
92
+ class ArtifactRegistered(EventPayload):
93
+ """A post-write journal record for one verified, sanitized artifact."""
94
+
95
+ EVENT_TYPE: ClassVar[str] = "ArtifactRegistered"
96
+
97
+ artifact_id: ArtifactId
98
+ sha256_hex: str
99
+ size_bytes: int
100
+ media_type: str
101
+ role: ArtifactRole
102
+ minimization_policy_id: str
103
+ minimization_policy_version: str
104
+ minimization_policy_config_sha256: str
105
+ sanitization_policy_id: str
106
+ sanitization_policy_version: str
107
+
108
+ def __post_init__(self) -> None:
109
+ # Reuse the exact ArtifactRef invariants rather than maintaining a
110
+ # second, subtly different metadata schema in the event vocabulary.
111
+ ArtifactRef(
112
+ artifact_id=self.artifact_id,
113
+ sha256_hex=self.sha256_hex,
114
+ size_bytes=self.size_bytes,
115
+ media_type=self.media_type,
116
+ )
117
+ if not isinstance(self.role, ArtifactRole):
118
+ raise TypeError("role must be an ArtifactRole")
119
+ for name, value in (
120
+ ("minimization_policy_id", self.minimization_policy_id),
121
+ ("minimization_policy_version", self.minimization_policy_version),
122
+ ("sanitization_policy_id", self.sanitization_policy_id),
123
+ ("sanitization_policy_version", self.sanitization_policy_version),
124
+ ):
125
+ if (
126
+ not isinstance(value, str)
127
+ or _POLICY_COMPONENT.fullmatch(value) is None
128
+ or _POLICY_CREDENTIAL_SHAPE.search(value)
129
+ ):
130
+ raise ValueError(
131
+ f"{name} must be a non-secret, storage-safe policy component"
132
+ )
133
+ _require_hash(
134
+ self.minimization_policy_config_sha256,
135
+ "minimization_policy_config_sha256",
136
+ )
137
+
138
+ @property
139
+ def artifact_ref(self) -> ArtifactRef:
140
+ """Reconstruct the complete reference claimed by this event."""
141
+
142
+ return ArtifactRef(
143
+ artifact_id=self.artifact_id,
144
+ sha256_hex=self.sha256_hex,
145
+ size_bytes=self.size_bytes,
146
+ media_type=self.media_type,
147
+ )
148
+
149
+
150
+ def _require_nonempty(value: str, name: str) -> None:
151
+ if not isinstance(value, str) or not value.strip():
152
+ raise ValueError(f"{name} must be a non-empty string")
153
+
154
+
155
+ def _require_nonnegative(value: int, name: str) -> None:
156
+ if type(value) is not int or value < 0:
157
+ raise ValueError(f"{name} must be a non-negative integer")
158
+
159
+
160
+ def _require_positive(value: int, name: str) -> None:
161
+ if type(value) is not int or value <= 0:
162
+ raise ValueError(f"{name} must be a positive integer")
163
+
164
+
165
+ def _require_hash(value: str, name: str) -> None:
166
+ _require_nonempty(value, name)
167
+ if len(value) != 64 or any(c not in "0123456789abcdef" for c in value):
168
+ raise ValueError(f"{name} must be a lowercase SHA-256 hex digest")
169
+
170
+
171
+ def _is_bounded_finite_decimal(value: Any) -> bool:
172
+ if type(value) is not Decimal or not value.is_finite():
173
+ return False
174
+ decimal_tuple = value.as_tuple()
175
+ return (
176
+ len(decimal_tuple.digits) <= _MAX_DECIMAL_DIGITS
177
+ and -_MAX_DECIMAL_ABS_EXPONENT
178
+ <= decimal_tuple.exponent
179
+ <= _MAX_DECIMAL_ABS_EXPONENT
180
+ )
181
+
182
+
183
+ def _require_bounded_finite_decimal(value: Any, name: str) -> None:
184
+ if not _is_bounded_finite_decimal(value):
185
+ raise TypeError(f"{name} must be a bounded finite Decimal")
186
+
187
+
188
+ def _validate_objective_values(values: ObjectiveValues) -> None:
189
+ if type(values) is not tuple:
190
+ raise TypeError("objective_values must be an immutable tuple")
191
+ if not values:
192
+ raise ValueError("objective_values must contain at least one declared objective")
193
+ names = set()
194
+ for item in values:
195
+ if type(item) is not tuple or len(item) != 2:
196
+ raise TypeError("each objective entry must be a (name, value) tuple")
197
+ name, value = item
198
+ _require_nonempty(name, "objective name")
199
+ validate_inline_text(
200
+ name,
201
+ field_name="objective name",
202
+ policy=InlineTextPolicy.METADATA_TOKEN,
203
+ )
204
+ if name in names:
205
+ raise ValueError(f"duplicate objective value {name!r}")
206
+ names.add(name)
207
+ if type(value) not in (int, float):
208
+ raise TypeError(f"objective {name!r} must be numeric")
209
+ if not math.isfinite(float(value)):
210
+ raise ValueError(f"objective {name!r} must be finite")
211
+
212
+
213
+ @dataclass(frozen=True, slots=True)
214
+ class RunStarted(EventPayload):
215
+ EVENT_TYPE: ClassVar[str] = "RunStarted"
216
+
217
+ experiment_spec_hash: str
218
+ manifest_artifact_id: Optional[ArtifactId] = None
219
+
220
+ def __post_init__(self) -> None:
221
+ _require_hash(self.experiment_spec_hash, "experiment_spec_hash")
222
+
223
+
224
+ @dataclass(frozen=True, slots=True)
225
+ class RunFinished(EventPayload):
226
+ EVENT_TYPE: ClassVar[str] = "RunFinished"
227
+
228
+ stop_reason: str
229
+
230
+ def __post_init__(self) -> None:
231
+ _require_nonempty(self.stop_reason, "stop_reason")
232
+
233
+
234
+ @dataclass(frozen=True, slots=True)
235
+ class RunAborted(EventPayload):
236
+ EVENT_TYPE: ClassVar[str] = "RunAborted"
237
+
238
+ category: FailureCategory
239
+ code: FailureCode
240
+ message: str
241
+ exception_type: Optional[str] = None
242
+ diagnostics_artifact_id: Optional[ArtifactId] = None
243
+
244
+ def __post_init__(self) -> None:
245
+ FailureRecord(
246
+ category=self.category,
247
+ code=self.code,
248
+ message=self.message,
249
+ exception_type=self.exception_type,
250
+ diagnostics_artifact_id=self.diagnostics_artifact_id,
251
+ )
252
+ if self.category is FailureCategory.CANDIDATE:
253
+ raise ValueError("candidate-attributable failures cannot abort a run")
254
+
255
+
256
+ @dataclass(frozen=True, slots=True)
257
+ class GenerationStarted(EventPayload):
258
+ EVENT_TYPE: ClassVar[str] = "GenerationStarted"
259
+
260
+ generation_id: GenerationId
261
+ ordinal: int
262
+
263
+ def __post_init__(self) -> None:
264
+ _require_positive(self.ordinal, "ordinal")
265
+
266
+
267
+ @dataclass(frozen=True, slots=True)
268
+ class GenerationCompleted(EventPayload):
269
+ EVENT_TYPE: ClassVar[str] = "GenerationCompleted"
270
+
271
+ generation_id: GenerationId
272
+ ordinal: int
273
+ pareto_size: int
274
+
275
+ def __post_init__(self) -> None:
276
+ _require_positive(self.ordinal, "ordinal")
277
+ _require_nonnegative(self.pareto_size, "pareto_size")
278
+
279
+
280
+ @dataclass(frozen=True, slots=True)
281
+ class OperatorSelected(EventPayload):
282
+ EVENT_TYPE: ClassVar[str] = "OperatorSelected"
283
+
284
+ operator_invocation_id: OperatorInvocationId
285
+ generation_id: GenerationId
286
+ operator_name: str
287
+ operator_version: str
288
+ requested_offspring: int
289
+
290
+ def __post_init__(self) -> None:
291
+ _require_nonempty(self.operator_name, "operator_name")
292
+ _require_nonempty(self.operator_version, "operator_version")
293
+ _require_nonnegative(self.requested_offspring, "requested_offspring")
294
+
295
+
296
+ @dataclass(frozen=True, slots=True)
297
+ class LLMCallRequested(EventPayload):
298
+ EVENT_TYPE: ClassVar[str] = "LLMCallRequested"
299
+
300
+ call_id: LLMCallId
301
+ operation: str
302
+ requested_model: str
303
+ generation_id: Optional[GenerationId] = None
304
+ operator_invocation_id: Optional[OperatorInvocationId] = None
305
+
306
+ def __post_init__(self) -> None:
307
+ _require_nonempty(self.operation, "operation")
308
+ _require_nonempty(self.requested_model, "requested_model")
309
+
310
+
311
+ @dataclass(frozen=True, slots=True)
312
+ class LLMRequestArtifactLinked(EventPayload):
313
+ """Link one sanitized request artifact to a logical LLM call."""
314
+
315
+ EVENT_TYPE: ClassVar[str] = "LLMRequestArtifactLinked"
316
+
317
+ call_id: LLMCallId
318
+ request_artifact_id: ArtifactId
319
+
320
+
321
+ @dataclass(frozen=True, slots=True)
322
+ class LLMCallStarted(EventPayload):
323
+ EVENT_TYPE: ClassVar[str] = "LLMCallStarted"
324
+
325
+ call_id: LLMCallId
326
+
327
+
328
+ @dataclass(frozen=True, slots=True)
329
+ class LLMCallCompleted(EventPayload):
330
+ EVENT_TYPE: ClassVar[str] = "LLMCallCompleted"
331
+
332
+ call_id: LLMCallId
333
+ response_artifact_id: Optional[ArtifactId] = None
334
+
335
+
336
+ @dataclass(frozen=True, slots=True)
337
+ class LLMCallFailed(EventPayload):
338
+ EVENT_TYPE: ClassVar[str] = "LLMCallFailed"
339
+
340
+ call_id: LLMCallId
341
+ category: FailureCategory
342
+ code: FailureCode
343
+ retryable: bool
344
+ message: str
345
+ exception_type: Optional[str] = None
346
+ diagnostics_artifact_id: Optional[ArtifactId] = None
347
+
348
+ def __post_init__(self) -> None:
349
+ FailureRecord(
350
+ category=self.category,
351
+ code=self.code,
352
+ message=self.message,
353
+ retryable=self.retryable,
354
+ exception_type=self.exception_type,
355
+ diagnostics_artifact_id=self.diagnostics_artifact_id,
356
+ )
357
+ if self.category is FailureCategory.CANDIDATE:
358
+ raise ValueError("LLM call failures cannot be candidate-attributable")
359
+
360
+
361
+ @dataclass(frozen=True, slots=True)
362
+ class LLMCallRetried(EventPayload):
363
+ EVENT_TYPE: ClassVar[str] = "LLMCallRetried"
364
+
365
+ call_id: LLMCallId
366
+ next_attempt_number: int
367
+
368
+ def __post_init__(self) -> None:
369
+ _require_positive(self.next_attempt_number, "next_attempt_number")
370
+ if self.next_attempt_number < 2:
371
+ raise ValueError("a retry attempt number must be at least 2")
372
+
373
+
374
+ @dataclass(frozen=True, slots=True)
375
+ class ProviderAttemptStarted(EventPayload):
376
+ EVENT_TYPE: ClassVar[str] = "ProviderAttemptStarted"
377
+
378
+ call_id: LLMCallId
379
+ provider_attempt_id: ProviderAttemptId
380
+ attempt_number: int
381
+
382
+ def __post_init__(self) -> None:
383
+ _require_positive(self.attempt_number, "attempt_number")
384
+
385
+
386
+ @dataclass(frozen=True, slots=True)
387
+ class ProviderAttemptCompleted(EventPayload):
388
+ EVENT_TYPE: ClassVar[str] = "ProviderAttemptCompleted"
389
+
390
+ call_id: LLMCallId
391
+ provider_attempt_id: ProviderAttemptId
392
+ resolved_provider: str
393
+ resolved_model: str
394
+ input_tokens: int = 0
395
+ output_tokens: int = 0
396
+ reasoning_tokens: int = 0
397
+ cache_read_tokens: int = 0
398
+ cache_write_tokens: int = 0
399
+ cost_usd: Decimal = Decimal("0")
400
+ latency_ns: int = 0
401
+
402
+ def __post_init__(self) -> None:
403
+ _require_nonempty(self.resolved_provider, "resolved_provider")
404
+ _require_nonempty(self.resolved_model, "resolved_model")
405
+ for name in (
406
+ "input_tokens",
407
+ "output_tokens",
408
+ "reasoning_tokens",
409
+ "cache_read_tokens",
410
+ "cache_write_tokens",
411
+ "latency_ns",
412
+ ):
413
+ _require_nonnegative(getattr(self, name), name)
414
+ _require_bounded_finite_decimal(self.cost_usd, "cost_usd")
415
+ if self.cost_usd < 0:
416
+ raise ValueError("cost_usd must be non-negative")
417
+
418
+
419
+ @dataclass(frozen=True, slots=True)
420
+ class ProviderAttemptFailed(EventPayload):
421
+ EVENT_TYPE: ClassVar[str] = "ProviderAttemptFailed"
422
+
423
+ call_id: LLMCallId
424
+ provider_attempt_id: ProviderAttemptId
425
+ category: FailureCategory
426
+ code: FailureCode
427
+ retryable: bool
428
+ message: str
429
+ latency_ns: int = 0
430
+ exception_type: Optional[str] = None
431
+ diagnostics_artifact_id: Optional[ArtifactId] = None
432
+
433
+ def __post_init__(self) -> None:
434
+ FailureRecord(
435
+ category=self.category,
436
+ code=self.code,
437
+ message=self.message,
438
+ retryable=self.retryable,
439
+ exception_type=self.exception_type,
440
+ diagnostics_artifact_id=self.diagnostics_artifact_id,
441
+ )
442
+ if self.category is FailureCategory.CANDIDATE:
443
+ raise ValueError("provider failures cannot be candidate-attributable")
444
+ _require_nonnegative(self.latency_ns, "latency_ns")
445
+
446
+
447
+ @dataclass(frozen=True, slots=True)
448
+ class CandidateProposed(EventPayload):
449
+ EVENT_TYPE: ClassVar[str] = "CandidateProposed"
450
+
451
+ candidate_id: CandidateId
452
+ generation_id: GenerationId
453
+ content_hash: str
454
+ proposal_index: int
455
+ operator_invocation_id: Optional[OperatorInvocationId] = None
456
+ generator_call_id: Optional[LLMCallId] = None
457
+ configuration_artifact_id: Optional[ArtifactId] = None
458
+
459
+ def __post_init__(self) -> None:
460
+ _require_hash(self.content_hash, "content_hash")
461
+ _require_nonnegative(self.proposal_index, "proposal_index")
462
+
463
+
464
+ @dataclass(frozen=True, slots=True)
465
+ class DuplicateDetected(EventPayload):
466
+ EVENT_TYPE: ClassVar[str] = "DuplicateDetected"
467
+
468
+ candidate_id: CandidateId
469
+ content_hash: str
470
+ canonical_candidate_id: Optional[CandidateId] = None
471
+ reason: str = "same_content"
472
+
473
+ def __post_init__(self) -> None:
474
+ _require_hash(self.content_hash, "content_hash")
475
+ _require_nonempty(self.reason, "reason")
476
+ if self.canonical_candidate_id == self.candidate_id:
477
+ raise ValueError("a duplicate cannot identify itself as its canonical occurrence")
478
+
479
+
480
+ @dataclass(frozen=True, slots=True)
481
+ class ValidationStarted(EventPayload):
482
+ EVENT_TYPE: ClassVar[str] = "ValidationStarted"
483
+
484
+ candidate_id: CandidateId
485
+ stage: ValidationStage
486
+
487
+
488
+ @dataclass(frozen=True, slots=True)
489
+ class ValidationCompleted(EventPayload):
490
+ EVENT_TYPE: ClassVar[str] = "ValidationCompleted"
491
+
492
+ candidate_id: CandidateId
493
+ stage: ValidationStage
494
+ ok: bool
495
+ failure_category: Optional[FailureCategory] = None
496
+ failure_code: Optional[FailureCode] = None
497
+
498
+ def __post_init__(self) -> None:
499
+ if not isinstance(self.ok, bool):
500
+ raise TypeError("ok must be bool")
501
+ if self.ok:
502
+ if self.failure_category is not None or self.failure_code is not None:
503
+ raise ValueError("successful validation cannot carry failure fields")
504
+ elif self.failure_category is None or self.failure_code is None:
505
+ raise ValueError("failed validation requires a failure category and code")
506
+ else:
507
+ validate_failure_pair(self.failure_category, self.failure_code)
508
+
509
+
510
+ @dataclass(frozen=True, slots=True)
511
+ class EvaluationRequested(EventPayload):
512
+ EVENT_TYPE: ClassVar[str] = "EvaluationRequested"
513
+
514
+ evaluation_id: EvaluationId
515
+ candidate_id: CandidateId
516
+ fidelity: str
517
+ seed: Optional[int] = None
518
+ cache_key_hash: Optional[str] = None
519
+
520
+ def __post_init__(self) -> None:
521
+ _require_nonempty(self.fidelity, "fidelity")
522
+ if self.seed is not None and type(self.seed) is not int:
523
+ raise TypeError("seed must be an integer or None")
524
+ if self.cache_key_hash is not None:
525
+ _require_hash(self.cache_key_hash, "cache_key_hash")
526
+
527
+
528
+ @dataclass(frozen=True, slots=True)
529
+ class EvaluationCacheHit(EventPayload):
530
+ EVENT_TYPE: ClassVar[str] = "EvaluationCacheHit"
531
+
532
+ evaluation_id: EvaluationId
533
+ candidate_id: CandidateId
534
+ fidelity: str
535
+ source_run_id: RunId
536
+ source_evaluation_id: EvaluationId
537
+ objective_values: ObjectiveValues
538
+
539
+ def __post_init__(self) -> None:
540
+ _require_nonempty(self.fidelity, "fidelity")
541
+ _validate_objective_values(self.objective_values)
542
+
543
+
544
+ @dataclass(frozen=True, slots=True)
545
+ class EvaluationCacheMiss(EventPayload):
546
+ EVENT_TYPE: ClassVar[str] = "EvaluationCacheMiss"
547
+
548
+ evaluation_id: EvaluationId
549
+ fidelity: str
550
+
551
+ def __post_init__(self) -> None:
552
+ _require_nonempty(self.fidelity, "fidelity")
553
+
554
+
555
+ @dataclass(frozen=True, slots=True)
556
+ class EvaluationCacheBypassed(EventPayload):
557
+ EVENT_TYPE: ClassVar[str] = "EvaluationCacheBypassed"
558
+
559
+ evaluation_id: EvaluationId
560
+ fidelity: str
561
+ reason: str
562
+
563
+ def __post_init__(self) -> None:
564
+ _require_nonempty(self.fidelity, "fidelity")
565
+ _require_nonempty(self.reason, "reason")
566
+
567
+
568
+ @dataclass(frozen=True, slots=True)
569
+ class EvaluationCacheStored(EventPayload):
570
+ EVENT_TYPE: ClassVar[str] = "EvaluationCacheStored"
571
+
572
+ evaluation_id: EvaluationId
573
+ fidelity: str
574
+ cache_key_hash: str
575
+
576
+ def __post_init__(self) -> None:
577
+ _require_nonempty(self.fidelity, "fidelity")
578
+ _require_hash(self.cache_key_hash, "cache_key_hash")
579
+
580
+
581
+ @dataclass(frozen=True, slots=True)
582
+ class EvaluationStarted(EventPayload):
583
+ EVENT_TYPE: ClassVar[str] = "EvaluationStarted"
584
+
585
+ evaluation_id: EvaluationId
586
+ evaluation_attempt_id: EvaluationAttemptId
587
+ fidelity: str
588
+ attempt_number: int
589
+
590
+ def __post_init__(self) -> None:
591
+ _require_nonempty(self.fidelity, "fidelity")
592
+ _require_positive(self.attempt_number, "attempt_number")
593
+
594
+
595
+ @dataclass(frozen=True, slots=True)
596
+ class EvaluationCompleted(EventPayload):
597
+ EVENT_TYPE: ClassVar[str] = "EvaluationCompleted"
598
+
599
+ evaluation_id: EvaluationId
600
+ evaluation_attempt_id: EvaluationAttemptId
601
+ fidelity: str
602
+ objective_values: ObjectiveValues
603
+ worker_time_ns: int = 0
604
+
605
+ def __post_init__(self) -> None:
606
+ _require_nonempty(self.fidelity, "fidelity")
607
+ _validate_objective_values(self.objective_values)
608
+ _require_nonnegative(self.worker_time_ns, "worker_time_ns")
609
+
610
+
611
+ @dataclass(frozen=True, slots=True)
612
+ class EvaluationFailed(EventPayload):
613
+ EVENT_TYPE: ClassVar[str] = "EvaluationFailed"
614
+
615
+ evaluation_id: EvaluationId
616
+ fidelity: str
617
+ category: FailureCategory
618
+ code: FailureCode
619
+ retryable: bool
620
+ terminal: bool
621
+ message: str
622
+ evaluation_attempt_id: Optional[EvaluationAttemptId] = None
623
+ worker_time_ns: int = 0
624
+ exception_type: Optional[str] = None
625
+ diagnostics_artifact_id: Optional[ArtifactId] = None
626
+
627
+ def __post_init__(self) -> None:
628
+ _require_nonempty(self.fidelity, "fidelity")
629
+ FailureRecord(
630
+ category=self.category,
631
+ code=self.code,
632
+ message=self.message,
633
+ retryable=self.retryable,
634
+ exception_type=self.exception_type,
635
+ diagnostics_artifact_id=self.diagnostics_artifact_id,
636
+ )
637
+ if not isinstance(self.retryable, bool) or not isinstance(self.terminal, bool):
638
+ raise TypeError("retryable and terminal must be bool")
639
+ if not self.terminal and not self.retryable:
640
+ raise ValueError("a non-terminal failure must be retryable")
641
+ if self.category is FailureCategory.CANDIDATE and not self.terminal:
642
+ raise ValueError("candidate-attributable evaluation failures must be terminal")
643
+ _require_nonnegative(self.worker_time_ns, "worker_time_ns")
644
+ if self.evaluation_attempt_id is None and self.worker_time_ns:
645
+ raise ValueError("worker_time_ns requires an evaluation_attempt_id")
646
+
647
+
648
+ @dataclass(frozen=True, slots=True)
649
+ class EvaluationRetried(EventPayload):
650
+ EVENT_TYPE: ClassVar[str] = "EvaluationRetried"
651
+
652
+ evaluation_id: EvaluationId
653
+ fidelity: str
654
+ next_attempt_number: int
655
+
656
+ def __post_init__(self) -> None:
657
+ _require_nonempty(self.fidelity, "fidelity")
658
+ _require_positive(self.next_attempt_number, "next_attempt_number")
659
+ if self.next_attempt_number < 2:
660
+ raise ValueError("a retry attempt number must be at least 2")
661
+
662
+
663
+ @dataclass(frozen=True, slots=True)
664
+ class CandidateAdmitted(EventPayload):
665
+ EVENT_TYPE: ClassVar[str] = "CandidateAdmitted"
666
+
667
+ candidate_id: CandidateId
668
+ evaluation_id: EvaluationId
669
+ archive_name: str = "all_time"
670
+
671
+ def __post_init__(self) -> None:
672
+ _require_nonempty(self.archive_name, "archive_name")
673
+
674
+
675
+ EVENT_PAYLOAD_TYPES: Tuple[Type[EventPayload], ...] = (
676
+ ArtifactRegistered,
677
+ RunStarted,
678
+ RunFinished,
679
+ RunAborted,
680
+ GenerationStarted,
681
+ GenerationCompleted,
682
+ OperatorSelected,
683
+ LLMCallRequested,
684
+ LLMRequestArtifactLinked,
685
+ LLMCallStarted,
686
+ LLMCallCompleted,
687
+ LLMCallFailed,
688
+ LLMCallRetried,
689
+ ProviderAttemptStarted,
690
+ ProviderAttemptCompleted,
691
+ ProviderAttemptFailed,
692
+ CandidateProposed,
693
+ DuplicateDetected,
694
+ ValidationStarted,
695
+ ValidationCompleted,
696
+ EvaluationRequested,
697
+ EvaluationCacheHit,
698
+ EvaluationCacheMiss,
699
+ EvaluationCacheBypassed,
700
+ EvaluationCacheStored,
701
+ EvaluationStarted,
702
+ EvaluationCompleted,
703
+ EvaluationFailed,
704
+ EvaluationRetried,
705
+ CandidateAdmitted,
706
+ )
707
+
708
+
709
+ _INLINE_TEXT_POLICIES: Dict[Type[EventPayload], Dict[str, InlineTextPolicy]] = {
710
+ ArtifactRegistered: {
711
+ "sha256_hex": InlineTextPolicy.SHA256,
712
+ "media_type": InlineTextPolicy.MEDIA_TYPE,
713
+ "minimization_policy_id": InlineTextPolicy.POLICY_COMPONENT,
714
+ "minimization_policy_version": InlineTextPolicy.POLICY_COMPONENT,
715
+ "minimization_policy_config_sha256": InlineTextPolicy.SHA256,
716
+ "sanitization_policy_id": InlineTextPolicy.POLICY_COMPONENT,
717
+ "sanitization_policy_version": InlineTextPolicy.POLICY_COMPONENT,
718
+ },
719
+ RunStarted: {"experiment_spec_hash": InlineTextPolicy.SHA256},
720
+ RunFinished: {"stop_reason": InlineTextPolicy.SAFE_SUMMARY},
721
+ RunAborted: {
722
+ "message": InlineTextPolicy.SAFE_SUMMARY,
723
+ "exception_type": InlineTextPolicy.EXCEPTION_TYPE,
724
+ },
725
+ OperatorSelected: {
726
+ "operator_name": InlineTextPolicy.METADATA_TOKEN,
727
+ "operator_version": InlineTextPolicy.METADATA_TOKEN,
728
+ },
729
+ LLMCallRequested: {
730
+ "operation": InlineTextPolicy.METADATA_TOKEN,
731
+ "requested_model": InlineTextPolicy.ROUTING_LABEL,
732
+ },
733
+ LLMCallFailed: {
734
+ "message": InlineTextPolicy.SAFE_SUMMARY,
735
+ "exception_type": InlineTextPolicy.EXCEPTION_TYPE,
736
+ },
737
+ ProviderAttemptCompleted: {
738
+ "resolved_provider": InlineTextPolicy.ROUTING_LABEL,
739
+ "resolved_model": InlineTextPolicy.ROUTING_LABEL,
740
+ },
741
+ ProviderAttemptFailed: {
742
+ "message": InlineTextPolicy.SAFE_SUMMARY,
743
+ "exception_type": InlineTextPolicy.EXCEPTION_TYPE,
744
+ },
745
+ CandidateProposed: {"content_hash": InlineTextPolicy.SHA256},
746
+ DuplicateDetected: {
747
+ "content_hash": InlineTextPolicy.SHA256,
748
+ "reason": InlineTextPolicy.METADATA_TOKEN,
749
+ },
750
+ EvaluationRequested: {
751
+ "fidelity": InlineTextPolicy.METADATA_TOKEN,
752
+ "cache_key_hash": InlineTextPolicy.SHA256,
753
+ },
754
+ EvaluationCacheHit: {"fidelity": InlineTextPolicy.METADATA_TOKEN},
755
+ EvaluationCacheMiss: {"fidelity": InlineTextPolicy.METADATA_TOKEN},
756
+ EvaluationCacheBypassed: {
757
+ "fidelity": InlineTextPolicy.METADATA_TOKEN,
758
+ "reason": InlineTextPolicy.SAFE_SUMMARY,
759
+ },
760
+ EvaluationCacheStored: {
761
+ "fidelity": InlineTextPolicy.METADATA_TOKEN,
762
+ "cache_key_hash": InlineTextPolicy.SHA256,
763
+ },
764
+ EvaluationStarted: {"fidelity": InlineTextPolicy.METADATA_TOKEN},
765
+ EvaluationCompleted: {"fidelity": InlineTextPolicy.METADATA_TOKEN},
766
+ EvaluationFailed: {
767
+ "fidelity": InlineTextPolicy.METADATA_TOKEN,
768
+ "message": InlineTextPolicy.SAFE_SUMMARY,
769
+ "exception_type": InlineTextPolicy.EXCEPTION_TYPE,
770
+ },
771
+ EvaluationRetried: {"fidelity": InlineTextPolicy.METADATA_TOKEN},
772
+ CandidateAdmitted: {"archive_name": InlineTextPolicy.METADATA_TOKEN},
773
+ }
774
+
775
+
776
+ def _annotation_is_optional_or_required_string(annotation: Any) -> bool:
777
+ if annotation is str:
778
+ return True
779
+ origin = get_origin(annotation)
780
+ args = get_args(annotation)
781
+ return origin in (Union, types.UnionType) and set(args) == {str, type(None)}
782
+
783
+
784
+ def _annotation_contains_string(annotation: Any) -> bool:
785
+ if annotation is str:
786
+ return True
787
+ return any(_annotation_contains_string(arg) for arg in get_args(annotation))
788
+
789
+
790
+ def _annotation_contains_type(annotation: Any, expected: Any) -> bool:
791
+ if annotation is expected:
792
+ return True
793
+ return any(
794
+ _annotation_contains_type(argument, expected)
795
+ for argument in get_args(annotation)
796
+ )
797
+
798
+
799
+ def _annotation_contains_tuple(annotation: Any) -> bool:
800
+ if get_origin(annotation) is tuple:
801
+ return True
802
+ return any(_annotation_contains_tuple(argument) for argument in get_args(annotation))
803
+
804
+
805
+ def _validate_supported_value_annotation(annotation: Any) -> None:
806
+ origin = get_origin(annotation)
807
+ arguments = get_args(annotation)
808
+ if origin in (Union, types.UnionType):
809
+ if len(arguments) != 2 or type(None) not in arguments:
810
+ raise RuntimeError("event unions must be a scalar optional value")
811
+ for argument in arguments:
812
+ if argument is not type(None):
813
+ _validate_supported_value_annotation(argument)
814
+ return
815
+ if origin is tuple:
816
+ for argument in arguments:
817
+ if argument is not Ellipsis:
818
+ _validate_supported_value_annotation(argument)
819
+ return
820
+ if origin is not None:
821
+ raise RuntimeError("event containers require an explicit codec design")
822
+ if annotation in (str, bool, int, float, Decimal, type(None)):
823
+ return
824
+ if annotation in ID_TYPES:
825
+ return
826
+ if isinstance(annotation, type) and issubclass(annotation, Enum):
827
+ return
828
+ raise RuntimeError("event field annotation has no durable codec policy")
829
+
830
+
831
+ _NESTED_STRING_FIELDS = {
832
+ (EvaluationCacheHit, "objective_values"),
833
+ (EvaluationCompleted, "objective_values"),
834
+ }
835
+ _CONTAINER_FIELDS = frozenset(_NESTED_STRING_FIELDS)
836
+ _DECIMAL_TEXT_FIELDS = {(ProviderAttemptCompleted, "cost_usd")}
837
+
838
+
839
+ def validate_event_value_schema() -> None:
840
+ """Audit every payload path that can serialize to durable JSON text."""
841
+
842
+ for payload_type in EVENT_PAYLOAD_TYPES:
843
+ try:
844
+ validate_inline_text(
845
+ payload_type.EVENT_TYPE,
846
+ field_name="event type",
847
+ policy=InlineTextPolicy.METADATA_TOKEN,
848
+ )
849
+ except (TypeError, ValueError):
850
+ valid_event_type = False
851
+ else:
852
+ valid_event_type = True
853
+ if not valid_event_type:
854
+ raise RuntimeError("event type violates the durable metadata policy")
855
+
856
+ hints = get_type_hints(payload_type)
857
+ for field in fields(payload_type):
858
+ annotation = hints[field.name]
859
+ _validate_supported_value_annotation(annotation)
860
+ path = (payload_type, field.name)
861
+ contains_nested_string = (
862
+ _annotation_contains_string(annotation)
863
+ and not _annotation_is_optional_or_required_string(annotation)
864
+ )
865
+ if contains_nested_string != (path in _NESTED_STRING_FIELDS):
866
+ raise RuntimeError(
867
+ "non-scalar event string fields require an explicit policy"
868
+ )
869
+ if _annotation_contains_tuple(annotation) != (path in _CONTAINER_FIELDS):
870
+ raise RuntimeError(
871
+ "event container fields require an explicit immutable policy"
872
+ )
873
+ contains_decimal = _annotation_contains_type(annotation, Decimal)
874
+ if contains_decimal != (path in _DECIMAL_TEXT_FIELDS):
875
+ raise RuntimeError(
876
+ "Decimal event fields require an explicit bounded-text policy"
877
+ )
878
+
879
+ for nested_type in _iter_annotation_leaf_types(annotation):
880
+ if not (
881
+ isinstance(nested_type, type)
882
+ and issubclass(nested_type, Enum)
883
+ ):
884
+ continue
885
+ for member in nested_type:
886
+ try:
887
+ validate_inline_text(
888
+ member.value,
889
+ field_name="enum value",
890
+ policy=InlineTextPolicy.ENUM_VALUE,
891
+ )
892
+ except (TypeError, ValueError):
893
+ valid_member = False
894
+ else:
895
+ valid_member = True
896
+ if not valid_member:
897
+ raise RuntimeError(
898
+ "event enum value violates the durable metadata policy"
899
+ )
900
+
901
+
902
+ def _iter_annotation_leaf_types(annotation: Any) -> Tuple[Any, ...]:
903
+ arguments = get_args(annotation)
904
+ if not arguments:
905
+ return (annotation,)
906
+ leaves = []
907
+ for argument in arguments:
908
+ if argument is not Ellipsis:
909
+ leaves.extend(_iter_annotation_leaf_types(argument))
910
+ return tuple(leaves)
911
+
912
+
913
+ def validate_inline_text_schema() -> None:
914
+ """Require an explicit storage policy for every direct event string field."""
915
+
916
+ validate_event_value_schema()
917
+ payload_types = set(EVENT_PAYLOAD_TYPES)
918
+ if set(_INLINE_TEXT_POLICIES) - payload_types:
919
+ raise RuntimeError("inline-text policy references an unregistered payload")
920
+ for payload_type in EVENT_PAYLOAD_TYPES:
921
+ hints = get_type_hints(payload_type)
922
+ nested_string_fields = {
923
+ field.name
924
+ for field in fields(payload_type)
925
+ if _annotation_contains_string(hints[field.name])
926
+ and not _annotation_is_optional_or_required_string(hints[field.name])
927
+ }
928
+ allowed_nested = {
929
+ field_name
930
+ for declared_type, field_name in _NESTED_STRING_FIELDS
931
+ if declared_type is payload_type
932
+ }
933
+ if nested_string_fields != allowed_nested:
934
+ raise RuntimeError(
935
+ "non-scalar event string fields require an explicit storage policy"
936
+ )
937
+ string_fields = {
938
+ field.name
939
+ for field in fields(payload_type)
940
+ if _annotation_is_optional_or_required_string(hints[field.name])
941
+ }
942
+ declared = _INLINE_TEXT_POLICIES.get(payload_type, {})
943
+ if string_fields != set(declared):
944
+ raise RuntimeError(
945
+ "event inline-text fields and policies do not match for "
946
+ f"{payload_type.EVENT_TYPE}"
947
+ )
948
+ if any(not isinstance(policy, InlineTextPolicy) for policy in declared.values()):
949
+ raise RuntimeError("event inline-text policies must use InlineTextPolicy")
950
+
951
+
952
+ def _validate_payload_inline_text(payload: EventPayload) -> None:
953
+ policies = _INLINE_TEXT_POLICIES.get(type(payload), {})
954
+ for field_name, policy in policies.items():
955
+ value = getattr(payload, field_name)
956
+ if value is None:
957
+ continue
958
+ validate_inline_text(value, field_name=field_name, policy=policy)
959
+
960
+
961
+ validate_inline_text_schema()
962
+ _PAYLOAD_BY_EVENT_TYPE: Dict[str, Type[EventPayload]] = {
963
+ payload_type.EVENT_TYPE: payload_type for payload_type in EVENT_PAYLOAD_TYPES
964
+ }
965
+ if len(_PAYLOAD_BY_EVENT_TYPE) != len(EVENT_PAYLOAD_TYPES): # pragma: no cover
966
+ raise RuntimeError("Event payload types must have unique EVENT_TYPE values")
967
+
968
+
969
+ @dataclass(frozen=True, slots=True)
970
+ class EventEnvelope:
971
+ schema_version: int
972
+ event_id: EventId
973
+ run_id: RunId
974
+ sequence_number: int
975
+ event_type: str
976
+ wall_timestamp_utc: datetime
977
+ monotonic_offset_ns: int
978
+ correlation_id: Optional[CorrelationId]
979
+ causation_event_id: Optional[EventId]
980
+ payload: EventPayload
981
+
982
+ def __post_init__(self) -> None:
983
+ if type(self.schema_version) is not int:
984
+ raise TypeError("schema_version must be an integer")
985
+ if self.schema_version != CURRENT_EVENT_SCHEMA_VERSION:
986
+ raise ValueError(
987
+ f"Unsupported event schema version {self.schema_version!r}; "
988
+ f"supported={CURRENT_EVENT_SCHEMA_VERSION}"
989
+ )
990
+ _require_positive(self.sequence_number, "sequence_number")
991
+ _require_nonnegative(self.monotonic_offset_ns, "monotonic_offset_ns")
992
+ if type(self.event_id) is not EventId or not _payload_field_round_trip_is_valid(
993
+ EventId,
994
+ self.event_id,
995
+ "event_id",
996
+ ):
997
+ raise TypeError("event_id must be an EventId")
998
+ if type(self.run_id) is not RunId or not _payload_field_round_trip_is_valid(
999
+ RunId,
1000
+ self.run_id,
1001
+ "run_id",
1002
+ ):
1003
+ raise TypeError("run_id must be a RunId")
1004
+ if self.correlation_id is not None and (
1005
+ type(self.correlation_id) is not CorrelationId
1006
+ or not _payload_field_round_trip_is_valid(
1007
+ CorrelationId,
1008
+ self.correlation_id,
1009
+ "correlation_id",
1010
+ )
1011
+ ):
1012
+ raise TypeError("correlation_id must be a CorrelationId or None")
1013
+ if self.causation_event_id is not None and (
1014
+ type(self.causation_event_id) is not EventId
1015
+ or not _payload_field_round_trip_is_valid(
1016
+ EventId,
1017
+ self.causation_event_id,
1018
+ "causation_event_id",
1019
+ )
1020
+ ):
1021
+ raise TypeError("causation_event_id must be an EventId or None")
1022
+ if type(self.payload) not in EVENT_PAYLOAD_TYPES:
1023
+ raise TypeError("payload must be a registered EventPayload")
1024
+ if type(self.event_type) is not str:
1025
+ raise TypeError("event_type must be an exact string")
1026
+ validate_inline_text(
1027
+ self.event_type,
1028
+ field_name="event_type",
1029
+ policy=InlineTextPolicy.METADATA_TOKEN,
1030
+ )
1031
+ payload_event_type = type(self.payload).EVENT_TYPE
1032
+ if type(payload_event_type) is not str:
1033
+ raise TypeError("payload event type must be an exact string")
1034
+ validate_inline_text(
1035
+ payload_event_type,
1036
+ field_name="payload event type",
1037
+ policy=InlineTextPolicy.METADATA_TOKEN,
1038
+ )
1039
+ _validate_payload_inline_text(self.payload)
1040
+ hints = get_type_hints(type(self.payload))
1041
+ for field in fields(self.payload):
1042
+ # Encoding followed by typed decoding is also the runtime type check
1043
+ # for direct (non-JSON) construction. It prevents, for example, a
1044
+ # RunId from being stored in a CandidateId field.
1045
+ if not _payload_field_round_trip_is_valid(
1046
+ hints[field.name],
1047
+ getattr(self.payload, field.name),
1048
+ field.name,
1049
+ ):
1050
+ raise TypeError(f"Invalid payload field {field.name}")
1051
+ if self.event_type != payload_event_type:
1052
+ raise ValueError("event_type does not match payload type")
1053
+ if type(self.wall_timestamp_utc) is not datetime:
1054
+ raise TypeError("wall_timestamp_utc must be an exact datetime")
1055
+ if self.wall_timestamp_utc.tzinfo is not timezone.utc:
1056
+ raise ValueError("wall_timestamp_utc must use the canonical UTC timezone")
1057
+
1058
+
1059
+ _ENVELOPE_FIELDS = {
1060
+ "schema_version",
1061
+ "event_id",
1062
+ "run_id",
1063
+ "sequence_number",
1064
+ "event_type",
1065
+ "wall_timestamp_utc",
1066
+ "monotonic_offset_ns",
1067
+ "correlation_id",
1068
+ "causation_event_id",
1069
+ "payload",
1070
+ }
1071
+
1072
+
1073
+ def _encode_value(value: Any) -> Any:
1074
+ if isinstance(value, StableId):
1075
+ return value.value
1076
+ if isinstance(value, Enum):
1077
+ return value.value
1078
+ if isinstance(value, Decimal):
1079
+ if not _is_bounded_finite_decimal(value):
1080
+ raise EventCodecError("Decimal exceeds the durable numeric-text policy")
1081
+ encoded = format(value, "f")
1082
+ if (
1083
+ len(encoded) > _MAX_DECIMAL_TEXT_LENGTH
1084
+ or _DECIMAL_TEXT.fullmatch(encoded) is None
1085
+ ):
1086
+ raise EventCodecError("Decimal exceeds the durable numeric-text policy")
1087
+ return encoded
1088
+ if isinstance(value, tuple):
1089
+ return [_encode_value(item) for item in value]
1090
+ if value is None or isinstance(value, (str, int, float, bool)):
1091
+ return value
1092
+ raise EventCodecError(f"Unsupported event payload value {type(value).__name__}")
1093
+
1094
+
1095
+ def _payload_field_round_trip_is_valid(
1096
+ annotation: Any,
1097
+ value: Any,
1098
+ field_name: str,
1099
+ ) -> bool:
1100
+ try:
1101
+ _decode_value(annotation, _encode_value(value), field_name)
1102
+ except Exception:
1103
+ return False
1104
+ return True
1105
+
1106
+
1107
+ def _decode_value(annotation: Any, value: Any, field_name: str) -> Any:
1108
+ origin = get_origin(annotation)
1109
+ args = get_args(annotation)
1110
+
1111
+ if origin in (Union, types.UnionType):
1112
+ if value is None and type(None) in args:
1113
+ return None
1114
+ non_none = [arg for arg in args if arg is not type(None)]
1115
+ if len(non_none) != 1:
1116
+ raise EventCodecError(f"Unsupported union annotation for {field_name}")
1117
+ return _decode_value(non_none[0], value, field_name)
1118
+
1119
+ if origin is tuple:
1120
+ if type(value) is not list:
1121
+ raise EventCodecError(f"{field_name} must be a JSON array")
1122
+ if len(args) == 2 and args[1] is Ellipsis:
1123
+ return tuple(_decode_value(args[0], item, field_name) for item in value)
1124
+ if len(args) != len(value):
1125
+ raise EventCodecError(f"{field_name} has the wrong tuple length")
1126
+ return tuple(
1127
+ _decode_value(item_type, item, field_name)
1128
+ for item_type, item in zip(args, value)
1129
+ )
1130
+
1131
+ if isinstance(annotation, type) and issubclass(annotation, StableId):
1132
+ if type(value) is not str:
1133
+ raise EventCodecError(f"{field_name} must be a string ID")
1134
+ try:
1135
+ decoded_id = annotation(value)
1136
+ except (TypeError, ValueError):
1137
+ decoded_id = _DECODE_FAILED
1138
+ if decoded_id is _DECODE_FAILED:
1139
+ raise EventCodecError(f"Invalid durable ID for {field_name}")
1140
+ return decoded_id
1141
+ if isinstance(annotation, type) and issubclass(annotation, Enum):
1142
+ try:
1143
+ decoded_enum = annotation(value)
1144
+ except Exception:
1145
+ decoded_enum = _DECODE_FAILED
1146
+ if decoded_enum is _DECODE_FAILED:
1147
+ raise EventCodecError(f"Invalid enum value for {field_name}")
1148
+ return decoded_enum
1149
+ if annotation is Decimal:
1150
+ if (
1151
+ type(value) is not str
1152
+ or len(value) > _MAX_DECIMAL_TEXT_LENGTH
1153
+ or _DECIMAL_TEXT.fullmatch(value) is None
1154
+ ):
1155
+ raise EventCodecError(f"{field_name} Decimal must be encoded as a string")
1156
+ try:
1157
+ decoded_decimal = Decimal(value)
1158
+ except Exception:
1159
+ decoded_decimal = _DECODE_FAILED
1160
+ if (
1161
+ decoded_decimal is _DECODE_FAILED
1162
+ or not _is_bounded_finite_decimal(decoded_decimal)
1163
+ or format(decoded_decimal, "f") != value
1164
+ ):
1165
+ raise EventCodecError(f"Invalid bounded Decimal for {field_name}")
1166
+ return decoded_decimal
1167
+ if annotation is str:
1168
+ if type(value) is not str:
1169
+ raise EventCodecError(f"{field_name} must be a string")
1170
+ return value
1171
+ if annotation is bool:
1172
+ if not isinstance(value, bool):
1173
+ raise EventCodecError(f"{field_name} must be bool")
1174
+ return value
1175
+ if annotation is int:
1176
+ if type(value) is not int:
1177
+ raise EventCodecError(f"{field_name} must be an integer")
1178
+ return value
1179
+ if annotation is float:
1180
+ if type(value) not in (int, float):
1181
+ raise EventCodecError(f"{field_name} must be numeric")
1182
+ return float(value)
1183
+ raise EventCodecError(f"Unsupported annotation {annotation!r} for {field_name}")
1184
+
1185
+
1186
+ def _payload_to_record(payload: EventPayload) -> Dict[str, Any]:
1187
+ return {field.name: _encode_value(getattr(payload, field.name)) for field in fields(payload)}
1188
+
1189
+
1190
+ def _payload_from_record(event_type: str, record: Any) -> EventPayload:
1191
+ payload_type = _PAYLOAD_BY_EVENT_TYPE.get(event_type)
1192
+ if payload_type is None:
1193
+ raise EventCodecError("Unsupported event_type")
1194
+ if type(record) is not dict:
1195
+ raise EventCodecError("payload must be a JSON object")
1196
+ if any(type(key) is not str for key in record):
1197
+ raise EventCodecError("payload keys must be exact strings")
1198
+ expected = {field.name for field in fields(payload_type)}
1199
+ if set(record) != expected:
1200
+ raise EventCodecError("Invalid payload fields")
1201
+ hints = get_type_hints(payload_type)
1202
+ try:
1203
+ kwargs = {
1204
+ name: _decode_value(hints[name], record[name], name)
1205
+ for name in expected
1206
+ }
1207
+ except Exception:
1208
+ kwargs = None
1209
+ if kwargs is None:
1210
+ raise EventCodecError("Invalid payload field encoding")
1211
+ try:
1212
+ payload = payload_type(**kwargs)
1213
+ except Exception:
1214
+ payload = None
1215
+ if payload is None:
1216
+ raise EventCodecError("Invalid event payload")
1217
+ return payload
1218
+
1219
+
1220
+ def _try_snapshot_payload(
1221
+ event_type: str,
1222
+ payload: EventPayload,
1223
+ ) -> EventPayload | object:
1224
+ try:
1225
+ return _payload_from_record(event_type, _payload_to_record(payload))
1226
+ except Exception:
1227
+ # Return inside the handler so secret-bearing extension exceptions do
1228
+ # not become context on the public snapshot/codec error.
1229
+ return _DECODE_FAILED
1230
+
1231
+
1232
+ def _try_construct_event_snapshot(
1233
+ event: EventEnvelope,
1234
+ payload: EventPayload,
1235
+ ) -> EventEnvelope | object:
1236
+ try:
1237
+ source_timestamp = event.wall_timestamp_utc
1238
+ timestamp = datetime(
1239
+ source_timestamp.year,
1240
+ source_timestamp.month,
1241
+ source_timestamp.day,
1242
+ source_timestamp.hour,
1243
+ source_timestamp.minute,
1244
+ source_timestamp.second,
1245
+ source_timestamp.microsecond,
1246
+ tzinfo=timezone.utc,
1247
+ fold=source_timestamp.fold,
1248
+ )
1249
+ return EventEnvelope(
1250
+ schema_version=event.schema_version,
1251
+ event_id=EventId(event.event_id.value),
1252
+ run_id=RunId(event.run_id.value),
1253
+ sequence_number=event.sequence_number,
1254
+ event_type=event.event_type,
1255
+ wall_timestamp_utc=timestamp,
1256
+ monotonic_offset_ns=event.monotonic_offset_ns,
1257
+ correlation_id=(
1258
+ CorrelationId(event.correlation_id.value)
1259
+ if event.correlation_id is not None
1260
+ else None
1261
+ ),
1262
+ causation_event_id=(
1263
+ EventId(event.causation_event_id.value)
1264
+ if event.causation_event_id is not None
1265
+ else None
1266
+ ),
1267
+ payload=payload,
1268
+ )
1269
+ except Exception:
1270
+ return _DECODE_FAILED
1271
+
1272
+
1273
+ def validated_event_snapshot(event: EventEnvelope) -> EventEnvelope:
1274
+ """Return an independently reconstructed event safe for persistence.
1275
+
1276
+ Frozen dataclasses can be mutated through low-level APIs, so adapters and
1277
+ serializers never trust a caller-owned instance merely because its original
1278
+ constructor ran.
1279
+ """
1280
+
1281
+ if type(event) is not EventEnvelope:
1282
+ raise EventCodecError("event must be an exact EventEnvelope")
1283
+ if (
1284
+ type(event.schema_version) is not int
1285
+ or type(event.event_id) is not EventId
1286
+ or type(event.run_id) is not RunId
1287
+ or type(event.sequence_number) is not int
1288
+ or type(event.event_type) is not str
1289
+ or type(event.wall_timestamp_utc) is not datetime
1290
+ or event.wall_timestamp_utc.tzinfo is not timezone.utc
1291
+ or type(event.monotonic_offset_ns) is not int
1292
+ or (
1293
+ event.correlation_id is not None
1294
+ and type(event.correlation_id) is not CorrelationId
1295
+ )
1296
+ or (
1297
+ event.causation_event_id is not None
1298
+ and type(event.causation_event_id) is not EventId
1299
+ )
1300
+ or type(event.payload) not in EVENT_PAYLOAD_TYPES
1301
+ ):
1302
+ raise EventCodecError("event violates the canonical envelope policy")
1303
+ payload = _try_snapshot_payload(event.event_type, event.payload)
1304
+ if payload is _DECODE_FAILED:
1305
+ raise EventCodecError("event payload could not be canonically reconstructed")
1306
+ assert isinstance(payload, EventPayload)
1307
+ snapshot = _try_construct_event_snapshot(event, payload)
1308
+ if snapshot is _DECODE_FAILED:
1309
+ raise EventCodecError("event could not be canonically reconstructed")
1310
+ assert isinstance(snapshot, EventEnvelope)
1311
+ return snapshot
1312
+
1313
+
1314
+ def event_to_record(event: EventEnvelope) -> Dict[str, Any]:
1315
+ """Return the schema-stable JSON-compatible representation of *event*."""
1316
+
1317
+ snapshot = validated_event_snapshot(event)
1318
+ timestamp = snapshot.wall_timestamp_utc.isoformat().replace(
1319
+ "+00:00", "Z"
1320
+ )
1321
+ return {
1322
+ "schema_version": snapshot.schema_version,
1323
+ "event_id": snapshot.event_id.value,
1324
+ "run_id": snapshot.run_id.value,
1325
+ "sequence_number": snapshot.sequence_number,
1326
+ "event_type": snapshot.event_type,
1327
+ "wall_timestamp_utc": timestamp,
1328
+ "monotonic_offset_ns": snapshot.monotonic_offset_ns,
1329
+ "correlation_id": (
1330
+ snapshot.correlation_id.value
1331
+ if snapshot.correlation_id is not None
1332
+ else None
1333
+ ),
1334
+ "causation_event_id": (
1335
+ snapshot.causation_event_id.value
1336
+ if snapshot.causation_event_id is not None
1337
+ else None
1338
+ ),
1339
+ "payload": _payload_to_record(snapshot.payload),
1340
+ }
1341
+
1342
+
1343
+ def event_from_record(record: Any) -> EventEnvelope:
1344
+ """Decode and validate one event record."""
1345
+
1346
+ if type(record) is not dict:
1347
+ raise EventCodecError("event record must be a JSON object")
1348
+ if any(type(key) is not str for key in record):
1349
+ raise EventCodecError("event envelope keys must be exact strings")
1350
+ if set(record) != _ENVELOPE_FIELDS:
1351
+ raise EventCodecError("Invalid envelope fields")
1352
+ schema_version = record["schema_version"]
1353
+ if type(schema_version) is not int:
1354
+ raise EventCodecError("schema_version must be an integer")
1355
+ if schema_version != CURRENT_EVENT_SCHEMA_VERSION:
1356
+ raise EventCodecError(
1357
+ f"Unsupported event schema version {schema_version!r}; "
1358
+ f"supported={CURRENT_EVENT_SCHEMA_VERSION}"
1359
+ )
1360
+ event_type = record["event_type"]
1361
+ if type(event_type) is not str:
1362
+ raise EventCodecError("event_type must be a string")
1363
+ timestamp_text = record["wall_timestamp_utc"]
1364
+ if type(timestamp_text) is not str:
1365
+ raise EventCodecError("wall_timestamp_utc must be a string")
1366
+ try:
1367
+ timestamp = datetime.fromisoformat(timestamp_text.replace("Z", "+00:00"))
1368
+ except ValueError:
1369
+ timestamp = None
1370
+ if timestamp is None:
1371
+ raise EventCodecError("wall_timestamp_utc is not ISO-8601")
1372
+ try:
1373
+ payload = _payload_from_record(event_type, record["payload"])
1374
+ envelope = EventEnvelope(
1375
+ schema_version=schema_version,
1376
+ event_id=EventId(record["event_id"]),
1377
+ run_id=RunId(record["run_id"]),
1378
+ sequence_number=record["sequence_number"],
1379
+ event_type=event_type,
1380
+ wall_timestamp_utc=timestamp,
1381
+ monotonic_offset_ns=record["monotonic_offset_ns"],
1382
+ correlation_id=(
1383
+ CorrelationId(record["correlation_id"])
1384
+ if record["correlation_id"] is not None
1385
+ else None
1386
+ ),
1387
+ causation_event_id=(
1388
+ EventId(record["causation_event_id"])
1389
+ if record["causation_event_id"] is not None
1390
+ else None
1391
+ ),
1392
+ payload=payload,
1393
+ )
1394
+ except Exception:
1395
+ envelope = None
1396
+ if envelope is None:
1397
+ raise EventCodecError("Invalid event envelope")
1398
+ return envelope
1399
+
1400
+
1401
+ def event_to_json(event: EventEnvelope) -> str:
1402
+ """Encode one event as canonical compact JSON (without a trailing newline)."""
1403
+
1404
+ try:
1405
+ encoded = json.dumps(
1406
+ event_to_record(event),
1407
+ sort_keys=True,
1408
+ separators=(",", ":"),
1409
+ ensure_ascii=False,
1410
+ allow_nan=False,
1411
+ )
1412
+ except Exception:
1413
+ encoded = None
1414
+ if encoded is None:
1415
+ raise EventCodecError("Could not encode event")
1416
+ return encoded
1417
+
1418
+
1419
+ def event_from_json(data: str) -> EventEnvelope:
1420
+ """Decode one JSON event line."""
1421
+
1422
+ if type(data) is not str:
1423
+ raise EventCodecError("event JSON must be an exact string")
1424
+
1425
+ def reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> Dict[str, Any]:
1426
+ record: Dict[str, Any] = {}
1427
+ for key, value in pairs:
1428
+ if key in record:
1429
+ raise EventCodecError("Duplicate JSON object key")
1430
+ record[key] = value
1431
+ return record
1432
+
1433
+ def reject_nonstandard_number(value: str) -> Any:
1434
+ raise EventCodecError("Non-standard JSON number")
1435
+
1436
+ try:
1437
+ record = json.loads(
1438
+ data,
1439
+ object_pairs_hook=reject_duplicate_keys,
1440
+ parse_constant=reject_nonstandard_number,
1441
+ )
1442
+ except EventCodecError as exc:
1443
+ parse_failure = str(exc)
1444
+ record = None
1445
+ except Exception:
1446
+ parse_failure = "syntax"
1447
+ record = None
1448
+ else:
1449
+ parse_failure = None
1450
+ if parse_failure not in (None, "syntax"):
1451
+ raise EventCodecError(parse_failure)
1452
+ if parse_failure == "syntax":
1453
+ raise EventCodecError("Malformed event JSON")
1454
+ return event_from_record(record)