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,769 @@
1
+ """Bounded, cancellation-safe asynchronous scheduling for provider-neutral LLM tasks.
2
+
3
+ This application service owns concurrency, attempt budgets, timeout enforcement,
4
+ and retry sleeps. Provider adapters only execute one attempt and classify their
5
+ exceptions; they never sleep or retry.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import asyncio
11
+ from collections import deque
12
+ from dataclasses import dataclass, replace
13
+ from typing import Callable, Deque, Dict, Generic, Optional, TypeVar, cast
14
+
15
+ from agent_evolve.domain.llm_task_queue import (
16
+ AttemptRequestEvidence,
17
+ AttemptStatus,
18
+ AttemptTelemetry,
19
+ CancellationReason,
20
+ LLMAttemptContext,
21
+ LLMTask,
22
+ LLMTaskOutcome,
23
+ RetryBudgetPartition,
24
+ RetryBudgetUsage,
25
+ QueueSnapshot,
26
+ RetryClassification,
27
+ RetryDisposition,
28
+ RetryReason,
29
+ SanitizedAttemptFailure,
30
+ TaskOutcomeStatus,
31
+ TaskTelemetry,
32
+ retry_budget_partition,
33
+ )
34
+ from agent_evolve.ports.clock import Clock
35
+ from agent_evolve.ports.llm_task_queue import (
36
+ AsyncLLMTaskExecutor,
37
+ AsyncRuntime,
38
+ AttemptPreparingExecutor,
39
+ BackoffPolicy,
40
+ ExecutorRetiredError,
41
+ PreparedLLMAttempt,
42
+ RetryClassifier,
43
+ )
44
+
45
+
46
+ RequestT = TypeVar("RequestT")
47
+ ResponseT = TypeVar("ResponseT")
48
+ CancellationOutcomeSink = Callable[[LLMTaskOutcome[ResponseT]], None]
49
+
50
+
51
+ class LLMTaskQueueError(RuntimeError):
52
+ """Base for admission/lifecycle failures before a task is accepted."""
53
+
54
+
55
+ class LLMTaskQueueFullError(LLMTaskQueueError):
56
+ pass
57
+
58
+
59
+ class LLMTaskQueueClosedError(LLMTaskQueueError):
60
+ pass
61
+
62
+
63
+ class DuplicateLLMTaskError(LLMTaskQueueError):
64
+ pass
65
+
66
+
67
+ @dataclass(slots=True)
68
+ class _Entry(Generic[RequestT, ResponseT]):
69
+ task: LLMTask[RequestT]
70
+ submitted_ns: int
71
+ future: "asyncio.Future[LLMTaskOutcome[ResponseT]]"
72
+ runner: Optional["asyncio.Task[None]"] = None
73
+ cancellation_reason: Optional[CancellationReason] = None
74
+ first_started_ns: Optional[int] = None
75
+
76
+
77
+ class AsyncLLMTaskQueue(Generic[RequestT, ResponseT]):
78
+ """Run accepted LLM tasks with hard in-flight and pending bounds.
79
+
80
+ ``submit`` admits atomically. If all execution slots and pending slots are
81
+ occupied, it raises :class:`LLMTaskQueueFullError` instead of retaining an
82
+ unbounded population of blocked producers. Once accepted, a task receives a
83
+ typed outcome unless its own submitter is cancelled.
84
+ """
85
+
86
+ def __init__(
87
+ self,
88
+ *,
89
+ executor: AsyncLLMTaskExecutor[RequestT, ResponseT],
90
+ retry_classifier: RetryClassifier,
91
+ backoff_policy: BackoffPolicy,
92
+ clock: Clock,
93
+ max_in_flight: int,
94
+ max_pending: int,
95
+ attempt_timeout_ns: int | None,
96
+ runtime: AsyncRuntime,
97
+ ) -> None:
98
+ if not isinstance(executor, AsyncLLMTaskExecutor):
99
+ raise TypeError("executor must implement AsyncLLMTaskExecutor")
100
+ if not isinstance(retry_classifier, RetryClassifier):
101
+ raise TypeError("retry_classifier must implement RetryClassifier")
102
+ if not isinstance(backoff_policy, BackoffPolicy):
103
+ raise TypeError("backoff_policy must implement BackoffPolicy")
104
+ if not isinstance(clock, Clock):
105
+ raise TypeError("clock must implement Clock")
106
+ if type(max_in_flight) is not int or max_in_flight < 1:
107
+ raise ValueError("max_in_flight must be a positive integer")
108
+ if type(max_pending) is not int or max_pending < 0:
109
+ raise ValueError("max_pending must be a non-negative integer")
110
+ if attempt_timeout_ns is not None and (
111
+ type(attempt_timeout_ns) is not int or attempt_timeout_ns <= 0
112
+ ):
113
+ raise ValueError(
114
+ "attempt_timeout_ns must be a positive integer or None"
115
+ )
116
+ if not isinstance(runtime, AsyncRuntime):
117
+ raise TypeError("runtime must implement AsyncRuntime")
118
+
119
+ self._executor = executor
120
+ self._retry_classifier = retry_classifier
121
+ self._backoff_policy = backoff_policy
122
+ self._clock = clock
123
+ self._max_in_flight = max_in_flight
124
+ self._max_pending = max_pending
125
+ self._attempt_timeout_ns = attempt_timeout_ns
126
+ self._runtime = runtime
127
+
128
+ self._lock = asyncio.Lock()
129
+ self._pending: Deque[_Entry[RequestT, ResponseT]] = deque()
130
+ self._active: Dict[str, _Entry[RequestT, ResponseT]] = {}
131
+ self._live_task_ids: set[str] = set()
132
+ self._closed = False
133
+ self._executor_retirement_owner: Optional[str] = None
134
+
135
+ async def __aenter__(self) -> "AsyncLLMTaskQueue[RequestT, ResponseT]":
136
+ return self
137
+
138
+ async def __aexit__(self, exc_type, exc, traceback) -> None:
139
+ del exc_type, exc, traceback
140
+ await self.aclose()
141
+
142
+ async def snapshot(self) -> QueueSnapshot:
143
+ async with self._lock:
144
+ return QueueSnapshot(
145
+ max_in_flight=self._max_in_flight,
146
+ max_pending=self._max_pending,
147
+ in_flight=len(self._active),
148
+ pending=len(self._pending),
149
+ closed=self._closed,
150
+ )
151
+
152
+ async def submit(
153
+ self,
154
+ task: LLMTask[RequestT],
155
+ *,
156
+ cancellation_outcome_sink: CancellationOutcomeSink[ResponseT] | None = None,
157
+ ) -> LLMTaskOutcome[ResponseT]:
158
+ """Atomically admit and await one task.
159
+
160
+ Cancelling this coroutine removes a pending task or cancels its active
161
+ provider awaitable, then waits for scheduler cleanup before propagating
162
+ ``CancelledError``. If supplied, ``cancellation_outcome_sink`` receives
163
+ the entry's one terminal outcome after that cleanup has drained and
164
+ before cancellation propagates. This narrow receipt hook lets an outer
165
+ runner durably account for work whose submitter cannot consume the
166
+ normal return value; it is never called on the ordinary return path.
167
+ """
168
+
169
+ if type(task) is not LLMTask:
170
+ raise TypeError("task must be an exact LLMTask")
171
+ if cancellation_outcome_sink is not None and not callable(
172
+ cancellation_outcome_sink
173
+ ):
174
+ raise TypeError("cancellation_outcome_sink must be callable or None")
175
+ loop = asyncio.get_running_loop()
176
+ submitted_ns = self._clock.monotonic_ns()
177
+ entry = _Entry(
178
+ task=task,
179
+ submitted_ns=submitted_ns,
180
+ future=loop.create_future(),
181
+ )
182
+
183
+ async with self._lock:
184
+ if self._closed:
185
+ raise LLMTaskQueueClosedError("the LLM task queue is closed")
186
+ if task.task_id in self._live_task_ids:
187
+ raise DuplicateLLMTaskError(f"task {task.task_id!r} is already live")
188
+ if len(self._active) < self._max_in_flight:
189
+ self._live_task_ids.add(task.task_id)
190
+ self._start_locked(entry)
191
+ elif len(self._pending) < self._max_pending:
192
+ self._live_task_ids.add(task.task_id)
193
+ self._pending.append(entry)
194
+ else:
195
+ raise LLMTaskQueueFullError(
196
+ "all in-flight and pending LLM task slots are occupied"
197
+ )
198
+
199
+ try:
200
+ return await asyncio.shield(entry.future)
201
+ except asyncio.CancelledError:
202
+ outcome = await self._cancel_for_submitter(entry)
203
+ if cancellation_outcome_sink is not None:
204
+ try:
205
+ cancellation_outcome_sink(outcome)
206
+ except Exception:
207
+ # Recorder failures must not replace submitter cancellation.
208
+ # A higher layer that requires publication can surface a
209
+ # typed ``CancelledError`` subclass instead.
210
+ pass
211
+ raise
212
+
213
+ async def aclose(self) -> None:
214
+ """Close admission and cancel all accepted work with typed outcomes."""
215
+
216
+ runners: list[asyncio.Task[None]] = []
217
+ async with self._lock:
218
+ self._closed = True
219
+ now_ns = self._clock.monotonic_ns()
220
+ while self._pending:
221
+ entry = self._pending.popleft()
222
+ entry.cancellation_reason = CancellationReason.QUEUE_CLOSED
223
+ self._resolve_pending_cancellation(entry, now_ns)
224
+ self._live_task_ids.discard(entry.task.task_id)
225
+ for entry in tuple(self._active.values()):
226
+ entry.cancellation_reason = CancellationReason.QUEUE_CLOSED
227
+ if entry.runner is not None and not entry.runner.done():
228
+ entry.runner.cancel()
229
+ runners.append(entry.runner)
230
+
231
+ if runners:
232
+ await asyncio.gather(*runners, return_exceptions=True)
233
+
234
+ def _start_locked(self, entry: _Entry[RequestT, ResponseT]) -> None:
235
+ task_id = entry.task.task_id
236
+ if task_id in self._active or entry.runner is not None:
237
+ raise RuntimeError("queue attempted to start an entry twice")
238
+ self._active[task_id] = entry
239
+ entry.runner = asyncio.create_task(
240
+ self._run_entry(entry),
241
+ name=f"agent-evolve-llm-{task_id}",
242
+ )
243
+
244
+ async def _cancel_for_submitter(
245
+ self,
246
+ entry: _Entry[RequestT, ResponseT],
247
+ ) -> LLMTaskOutcome[ResponseT]:
248
+ runner: Optional[asyncio.Task[None]] = None
249
+ async with self._lock:
250
+ if entry.task.task_id in self._live_task_ids:
251
+ entry.cancellation_reason = CancellationReason.SUBMITTER_CANCELLED
252
+ try:
253
+ self._pending.remove(entry)
254
+ except ValueError:
255
+ runner = entry.runner
256
+ if runner is not None and not runner.done():
257
+ runner.cancel()
258
+ else:
259
+ completed_ns = self._clock.monotonic_ns()
260
+ self._resolve_pending_cancellation(entry, completed_ns)
261
+ self._live_task_ids.discard(entry.task.task_id)
262
+
263
+ if runner is not None:
264
+ await asyncio.gather(runner, return_exceptions=True)
265
+ # ``entry.future`` is shielded from submitter cancellation. Pending
266
+ # cancellation resolves it above; active cancellation resolves it only
267
+ # after the provider awaitable and queue runner have fully drained.
268
+ return await asyncio.shield(entry.future)
269
+
270
+ def _safe_elapsed(self, later_ns: int, earlier_ns: int) -> int:
271
+ elapsed = later_ns - earlier_ns
272
+ if elapsed < 0:
273
+ raise RuntimeError("injected monotonic clock moved backwards")
274
+ return elapsed
275
+
276
+ def _classify(
277
+ self,
278
+ error: Exception,
279
+ *,
280
+ context: LLMAttemptContext,
281
+ ) -> tuple[RetryClassification, str]:
282
+ try:
283
+ classification = self._retry_classifier.classify(error, context=context)
284
+ if type(classification) is not RetryClassification:
285
+ raise TypeError("retry classifier returned a non-classification")
286
+ return classification, type(error).__name__
287
+ except Exception as classifier_error:
288
+ return (
289
+ RetryClassification(
290
+ disposition=RetryDisposition.FAIL,
291
+ reason=RetryReason.INTERNAL,
292
+ ),
293
+ type(classifier_error).__name__,
294
+ )
295
+
296
+ def _policy_delay(
297
+ self,
298
+ *,
299
+ entry: _Entry[RequestT, ResponseT],
300
+ attempt_number: int,
301
+ classification: RetryClassification,
302
+ ) -> tuple[Optional[int], Optional[str]]:
303
+ try:
304
+ delay = self._backoff_policy.delay_ns(
305
+ task_id=entry.task.task_id,
306
+ failed_attempt_number=attempt_number,
307
+ classification=classification,
308
+ )
309
+ if type(delay) is not int or delay < 0:
310
+ raise ValueError("backoff policy returned an invalid delay")
311
+ return delay, None
312
+ except Exception as policy_error:
313
+ return None, type(policy_error).__name__
314
+
315
+ async def _run_entry(self, entry: _Entry[RequestT, ResponseT]) -> None:
316
+ attempts: list[AttemptTelemetry] = []
317
+ previous_end_ns = entry.submitted_ns
318
+ active_attempt_number: Optional[int] = None
319
+ active_attempt_start_ns: Optional[int] = None
320
+ active_request_evidence: Optional[AttemptRequestEvidence] = None
321
+ outcome: Optional[LLMTaskOutcome[ResponseT]] = None
322
+ previous_failure: Optional[SanitizedAttemptFailure] = None
323
+ active_output_failure: Optional[SanitizedAttemptFailure] = None
324
+ retry_budget_usage = (
325
+ RetryBudgetUsage() if entry.task.retry_budget is not None else None
326
+ )
327
+
328
+ try:
329
+ for attempt_number in range(1, entry.task.max_attempts + 1):
330
+ active_attempt_number = attempt_number
331
+ attempt_start_ns = self._clock.monotonic_ns()
332
+ active_attempt_start_ns = attempt_start_ns
333
+ if entry.first_started_ns is None:
334
+ entry.first_started_ns = attempt_start_ns
335
+ wait_time_ns = self._safe_elapsed(attempt_start_ns, previous_end_ns)
336
+ timeout_ns = (
337
+ entry.task.attempt_timeout_ns
338
+ if entry.task.attempt_timeout_ns is not None
339
+ else self._attempt_timeout_ns
340
+ )
341
+ context = LLMAttemptContext(
342
+ task_id=entry.task.task_id,
343
+ attempt_number=attempt_number,
344
+ attempt_timeout_ns=timeout_ns,
345
+ previous_failure=previous_failure,
346
+ active_output_failure=active_output_failure,
347
+ retry_budget_usage=retry_budget_usage,
348
+ )
349
+
350
+ try:
351
+ request_evidence: Optional[AttemptRequestEvidence] = None
352
+ if isinstance(self._executor, AttemptPreparingExecutor):
353
+ prepared = self._executor.prepare_attempt(
354
+ entry.task.request,
355
+ context=context,
356
+ )
357
+ if type(prepared) is not PreparedLLMAttempt:
358
+ raise TypeError(
359
+ "attempt preparing executor returned an invalid value"
360
+ )
361
+ PreparedLLMAttempt.__post_init__(prepared)
362
+ request_evidence = prepared.request_evidence
363
+ attempt_awaitable = prepared.execute_once()
364
+ else:
365
+ attempt_awaitable = self._executor.execute(
366
+ entry.task.request,
367
+ context=context,
368
+ )
369
+ active_request_evidence = request_evidence
370
+ response = await self._runtime.wait_for(
371
+ attempt_awaitable,
372
+ timeout_ns,
373
+ )
374
+ except asyncio.CancelledError:
375
+ raise
376
+ except Exception as error:
377
+ attempt_end_ns = self._clock.monotonic_ns()
378
+ service_time_ns = self._safe_elapsed(
379
+ attempt_end_ns, attempt_start_ns
380
+ )
381
+ previous_end_ns = attempt_end_ns
382
+ classification, error_type = self._classify(error, context=context)
383
+ executor_retired = isinstance(error, ExecutorRetiredError)
384
+ if executor_retired and (
385
+ classification.disposition is not RetryDisposition.FAIL
386
+ or classification.retry_after is not None
387
+ ):
388
+ classification = RetryClassification(
389
+ disposition=RetryDisposition.FAIL,
390
+ reason=classification.reason,
391
+ sanitized_failure=classification.sanitized_failure,
392
+ )
393
+ timed_out = isinstance(error, TimeoutError)
394
+ attempts_remain = attempt_number < entry.task.max_attempts
395
+ budget_partition: Optional[RetryBudgetPartition] = None
396
+ budget_allows_retry = True
397
+ if (
398
+ classification.disposition is RetryDisposition.RETRY
399
+ and entry.task.retry_budget is not None
400
+ ):
401
+ try:
402
+ budget_partition = retry_budget_partition(
403
+ classification.reason
404
+ )
405
+ except (TypeError, ValueError):
406
+ classification = RetryClassification(
407
+ disposition=RetryDisposition.FAIL,
408
+ reason=RetryReason.INTERNAL,
409
+ sanitized_failure=(
410
+ classification.sanitized_failure
411
+ ),
412
+ )
413
+ error_type = "InvalidRetryBudgetClassification"
414
+ else:
415
+ if retry_budget_usage is None:
416
+ raise AssertionError(
417
+ "partitioned retry budget has no usage ledger"
418
+ )
419
+ budget_allows_retry = retry_budget_usage.used(
420
+ budget_partition
421
+ ) < entry.task.retry_budget.limit(budget_partition)
422
+ will_retry = (
423
+ classification.disposition is RetryDisposition.RETRY
424
+ and attempts_remain
425
+ and budget_allows_retry
426
+ )
427
+ policy_delay_ns = 0
428
+ retry_after_ns = (
429
+ classification.retry_after.delay_ns
430
+ if classification.retry_after is not None
431
+ else 0
432
+ )
433
+ scheduled_delay_ns = 0
434
+
435
+ if will_retry:
436
+ policy_delay, policy_error_type = self._policy_delay(
437
+ entry=entry,
438
+ attempt_number=attempt_number,
439
+ classification=classification,
440
+ )
441
+ if policy_delay is None:
442
+ classification = RetryClassification(
443
+ disposition=RetryDisposition.FAIL,
444
+ reason=RetryReason.INTERNAL,
445
+ sanitized_failure=(classification.sanitized_failure),
446
+ )
447
+ error_type = cast(str, policy_error_type)
448
+ retry_after_ns = 0
449
+ will_retry = False
450
+ else:
451
+ policy_delay_ns = policy_delay
452
+ scheduled_delay_ns = max(policy_delay_ns, retry_after_ns)
453
+
454
+ if will_retry and budget_partition is not None:
455
+ if retry_budget_usage is None:
456
+ raise AssertionError(
457
+ "partitioned retry budget has no usage ledger"
458
+ )
459
+ retry_budget_usage = retry_budget_usage.consume(
460
+ budget_partition
461
+ )
462
+
463
+ attempts.append(
464
+ AttemptTelemetry(
465
+ attempt_number=attempt_number,
466
+ status=(
467
+ AttemptStatus.TIMED_OUT
468
+ if timed_out
469
+ else (
470
+ AttemptStatus.RETRYABLE_FAILURE
471
+ if classification.disposition
472
+ is RetryDisposition.RETRY
473
+ else AttemptStatus.TERMINAL_FAILURE
474
+ )
475
+ ),
476
+ wait_time_ns=wait_time_ns,
477
+ service_time_ns=service_time_ns,
478
+ will_retry=will_retry,
479
+ policy_backoff_ns=policy_delay_ns,
480
+ retry_after_ns=retry_after_ns,
481
+ scheduled_delay_ns=scheduled_delay_ns,
482
+ classification=classification,
483
+ error_type=error_type,
484
+ request_evidence=request_evidence,
485
+ )
486
+ )
487
+ active_attempt_number = None
488
+ active_attempt_start_ns = None
489
+ active_request_evidence = None
490
+
491
+ if executor_retired:
492
+ await self._retire_executor(entry)
493
+
494
+ if not will_retry:
495
+ completed_ns = self._clock.monotonic_ns()
496
+ outcome = self._failure_outcome(
497
+ entry,
498
+ attempts,
499
+ completed_ns=completed_ns,
500
+ exhausted=(
501
+ classification.disposition is RetryDisposition.RETRY
502
+ and (
503
+ not attempts_remain
504
+ or not budget_allows_retry
505
+ )
506
+ ),
507
+ )
508
+ break
509
+
510
+ current_failure = classification.sanitized_failure
511
+ if (
512
+ current_failure is not None
513
+ and current_failure.kind == "output_invalid"
514
+ and current_failure.retryable
515
+ ):
516
+ active_output_failure = current_failure
517
+ previous_failure = current_failure
518
+ await self._runtime.sleep(scheduled_delay_ns)
519
+ continue
520
+
521
+ attempt_end_ns = self._clock.monotonic_ns()
522
+ attempts.append(
523
+ AttemptTelemetry(
524
+ attempt_number=attempt_number,
525
+ status=AttemptStatus.SUCCEEDED,
526
+ wait_time_ns=wait_time_ns,
527
+ service_time_ns=self._safe_elapsed(
528
+ attempt_end_ns,
529
+ attempt_start_ns,
530
+ ),
531
+ will_retry=False,
532
+ request_evidence=request_evidence,
533
+ )
534
+ )
535
+ active_attempt_number = None
536
+ active_attempt_start_ns = None
537
+ active_request_evidence = None
538
+ outcome = self._success_outcome(
539
+ entry,
540
+ attempts,
541
+ response=response,
542
+ completed_ns=attempt_end_ns,
543
+ )
544
+ break
545
+
546
+ except asyncio.CancelledError:
547
+ completed_ns = self._clock.monotonic_ns()
548
+ if (
549
+ active_attempt_number is not None
550
+ and active_attempt_start_ns is not None
551
+ ):
552
+ attempts.append(
553
+ AttemptTelemetry(
554
+ attempt_number=active_attempt_number,
555
+ status=AttemptStatus.CANCELLED,
556
+ wait_time_ns=self._safe_elapsed(
557
+ active_attempt_start_ns,
558
+ previous_end_ns,
559
+ ),
560
+ service_time_ns=self._safe_elapsed(
561
+ completed_ns,
562
+ active_attempt_start_ns,
563
+ ),
564
+ will_retry=False,
565
+ error_type="CancelledError",
566
+ request_evidence=active_request_evidence,
567
+ )
568
+ )
569
+ outcome = self._cancelled_outcome(
570
+ entry, attempts, completed_ns=completed_ns
571
+ )
572
+ except Exception as internal_error:
573
+ completed_ns = self._clock.monotonic_ns()
574
+ internal_classification = RetryClassification(
575
+ disposition=RetryDisposition.FAIL,
576
+ reason=RetryReason.INTERNAL,
577
+ )
578
+ if (
579
+ active_attempt_number is not None
580
+ and active_attempt_start_ns is not None
581
+ ):
582
+ attempts.append(
583
+ AttemptTelemetry(
584
+ attempt_number=active_attempt_number,
585
+ status=AttemptStatus.TERMINAL_FAILURE,
586
+ wait_time_ns=self._safe_elapsed(
587
+ active_attempt_start_ns,
588
+ previous_end_ns,
589
+ ),
590
+ service_time_ns=self._safe_elapsed(
591
+ completed_ns,
592
+ active_attempt_start_ns,
593
+ ),
594
+ will_retry=False,
595
+ classification=internal_classification,
596
+ error_type=type(internal_error).__name__,
597
+ request_evidence=active_request_evidence,
598
+ )
599
+ )
600
+ elif attempts:
601
+ last = attempts[-1]
602
+ last_failure = (
603
+ None
604
+ if last.classification is None
605
+ else last.classification.sanitized_failure
606
+ )
607
+ attempts[-1] = replace(
608
+ last,
609
+ status=AttemptStatus.TERMINAL_FAILURE,
610
+ will_retry=False,
611
+ policy_backoff_ns=0,
612
+ retry_after_ns=0,
613
+ scheduled_delay_ns=0,
614
+ classification=RetryClassification(
615
+ disposition=internal_classification.disposition,
616
+ reason=internal_classification.reason,
617
+ sanitized_failure=last_failure,
618
+ ),
619
+ error_type=type(internal_error).__name__,
620
+ )
621
+ else:
622
+ if entry.first_started_ns is None:
623
+ entry.first_started_ns = completed_ns
624
+ attempts.append(
625
+ AttemptTelemetry(
626
+ attempt_number=1,
627
+ status=AttemptStatus.TERMINAL_FAILURE,
628
+ wait_time_ns=self._safe_elapsed(
629
+ entry.first_started_ns,
630
+ entry.submitted_ns,
631
+ ),
632
+ service_time_ns=self._safe_elapsed(
633
+ completed_ns,
634
+ entry.first_started_ns,
635
+ ),
636
+ will_retry=False,
637
+ classification=internal_classification,
638
+ error_type=type(internal_error).__name__,
639
+ )
640
+ )
641
+ outcome = self._failure_outcome(
642
+ entry,
643
+ attempts,
644
+ completed_ns=completed_ns,
645
+ exhausted=False,
646
+ )
647
+ finally:
648
+ await self._finish_entry(entry)
649
+ if outcome is not None and not entry.future.done():
650
+ entry.future.set_result(outcome)
651
+
652
+ def _telemetry(
653
+ self,
654
+ entry: _Entry[RequestT, ResponseT],
655
+ attempts: list[AttemptTelemetry],
656
+ *,
657
+ completed_ns: int,
658
+ ) -> TaskTelemetry:
659
+ if entry.first_started_ns is None:
660
+ queue_time_ns = self._safe_elapsed(completed_ns, entry.submitted_ns)
661
+ service_time_ns = 0
662
+ else:
663
+ queue_time_ns = self._safe_elapsed(
664
+ entry.first_started_ns, entry.submitted_ns
665
+ )
666
+ service_time_ns = self._safe_elapsed(completed_ns, entry.first_started_ns)
667
+ return TaskTelemetry(
668
+ task_id=entry.task.task_id,
669
+ queue_time_ns=queue_time_ns,
670
+ service_time_ns=service_time_ns,
671
+ total_time_ns=self._safe_elapsed(completed_ns, entry.submitted_ns),
672
+ attempts=tuple(attempts),
673
+ )
674
+
675
+ def _success_outcome(
676
+ self,
677
+ entry: _Entry[RequestT, ResponseT],
678
+ attempts: list[AttemptTelemetry],
679
+ *,
680
+ response: ResponseT,
681
+ completed_ns: int,
682
+ ) -> LLMTaskOutcome[ResponseT]:
683
+ return LLMTaskOutcome(
684
+ status=TaskOutcomeStatus.SUCCEEDED,
685
+ response=response,
686
+ telemetry=self._telemetry(entry, attempts, completed_ns=completed_ns),
687
+ )
688
+
689
+ def _failure_outcome(
690
+ self,
691
+ entry: _Entry[RequestT, ResponseT],
692
+ attempts: list[AttemptTelemetry],
693
+ *,
694
+ completed_ns: int,
695
+ exhausted: bool,
696
+ ) -> LLMTaskOutcome[ResponseT]:
697
+ return LLMTaskOutcome(
698
+ status=(
699
+ TaskOutcomeStatus.ATTEMPTS_EXHAUSTED
700
+ if exhausted
701
+ else TaskOutcomeStatus.TERMINAL_FAILURE
702
+ ),
703
+ telemetry=self._telemetry(entry, attempts, completed_ns=completed_ns),
704
+ )
705
+
706
+ def _cancelled_outcome(
707
+ self,
708
+ entry: _Entry[RequestT, ResponseT],
709
+ attempts: list[AttemptTelemetry],
710
+ *,
711
+ completed_ns: int,
712
+ ) -> LLMTaskOutcome[ResponseT]:
713
+ return LLMTaskOutcome(
714
+ status=TaskOutcomeStatus.CANCELLED,
715
+ cancellation_reason=(
716
+ entry.cancellation_reason or CancellationReason.SUBMITTER_CANCELLED
717
+ ),
718
+ telemetry=self._telemetry(entry, attempts, completed_ns=completed_ns),
719
+ )
720
+
721
+ def _resolve_pending_cancellation(
722
+ self,
723
+ entry: _Entry[RequestT, ResponseT],
724
+ completed_ns: int,
725
+ ) -> None:
726
+ if not entry.future.done():
727
+ entry.future.set_result(
728
+ self._cancelled_outcome(entry, [], completed_ns=completed_ns)
729
+ )
730
+
731
+ async def _finish_entry(self, entry: _Entry[RequestT, ResponseT]) -> None:
732
+ async with self._lock:
733
+ task_id = entry.task.task_id
734
+ if self._active.get(task_id) is entry:
735
+ del self._active[task_id]
736
+ self._live_task_ids.discard(task_id)
737
+ if not self._closed and self._pending:
738
+ promoted = self._pending.popleft()
739
+ self._start_locked(promoted)
740
+
741
+ async def _retire_executor(self, source: _Entry[RequestT, ResponseT]) -> None:
742
+ """Fail closed after an attempt permanently retires shared execution."""
743
+
744
+ sibling_runners: list[asyncio.Task[None]] = []
745
+ async with self._lock:
746
+ # Only one runner may coordinate retirement. A cancelled sibling
747
+ # can still finish its transport-abort path and arrive here; if it
748
+ # also cancelled and awaited the first runner, the two Tasks would
749
+ # form a cancellation cycle inside asyncio.gather.
750
+ if self._executor_retirement_owner is not None:
751
+ return
752
+ self._executor_retirement_owner = source.task.task_id
753
+ self._closed = True
754
+ now_ns = self._clock.monotonic_ns()
755
+ while self._pending:
756
+ entry = self._pending.popleft()
757
+ entry.cancellation_reason = CancellationReason.EXECUTOR_RETIRED
758
+ self._resolve_pending_cancellation(entry, now_ns)
759
+ self._live_task_ids.discard(entry.task.task_id)
760
+ for entry in tuple(self._active.values()):
761
+ if entry is source:
762
+ continue
763
+ entry.cancellation_reason = CancellationReason.EXECUTOR_RETIRED
764
+ if entry.runner is not None and not entry.runner.done():
765
+ entry.runner.cancel()
766
+ sibling_runners.append(entry.runner)
767
+
768
+ if sibling_runners:
769
+ await asyncio.gather(*sibling_runners, return_exceptions=True)