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.
- agent_evolve/__init__.py +722 -0
- agent_evolve/agentic.py +2800 -0
- agent_evolve/api.py +767 -0
- agent_evolve/application/__init__.py +1580 -0
- agent_evolve/application/action_allocation.py +744 -0
- agent_evolve/application/action_allocation_frame.py +347 -0
- agent_evolve/application/action_allocation_frame_commit.py +185 -0
- agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
- agent_evolve/application/action_allocation_frame_v3.py +338 -0
- agent_evolve/application/action_archive_value.py +497 -0
- agent_evolve/application/action_evidence_consistency.py +455 -0
- agent_evolve/application/action_forecast_partitioning.py +1471 -0
- agent_evolve/application/action_metric_projection.py +211 -0
- agent_evolve/application/action_role_value.py +680 -0
- agent_evolve/application/action_score_authorities.py +363 -0
- agent_evolve/application/action_structural_signature.py +116 -0
- agent_evolve/application/action_target_realization.py +402 -0
- agent_evolve/application/agentic_evolution.py +7734 -0
- agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
- agent_evolve/application/anchor_residual_identification.py +463 -0
- agent_evolve/application/archive_conditioned_action_target.py +208 -0
- agent_evolve/application/artifact_journal.py +246 -0
- agent_evolve/application/artifact_replay.py +347 -0
- agent_evolve/application/budgeted_optimizer.py +1828 -0
- agent_evolve/application/calibrated_campaign.py +485 -0
- agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
- agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
- agent_evolve/application/campaign_capacity_recourse.py +254 -0
- agent_evolve/application/campaign_contextual_outcomes.py +119 -0
- agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
- agent_evolve/application/campaign_evidence_registry.py +262 -0
- agent_evolve/application/campaign_execution.py +2537 -0
- agent_evolve/application/campaign_generation_audit.py +942 -0
- agent_evolve/application/campaign_learning.py +1812 -0
- agent_evolve/application/campaign_learning_runtime.py +1977 -0
- agent_evolve/application/campaign_search_phase.py +227 -0
- agent_evolve/application/campaign_selector_context_extension.py +220 -0
- agent_evolve/application/campaign_variation_envelope.py +649 -0
- agent_evolve/application/campaign_variation_trace.py +451 -0
- agent_evolve/application/candidate_archive_consequence.py +128 -0
- agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
- agent_evolve/application/composite_outcome_updater.py +145 -0
- agent_evolve/application/composition_portfolio_selection.py +363 -0
- agent_evolve/application/concurrent_stage.py +144 -0
- agent_evolve/application/contextual_action_allocation.py +181 -0
- agent_evolve/application/contextual_campaign_outcomes.py +267 -0
- agent_evolve/application/contextual_campaign_planning.py +1366 -0
- agent_evolve/application/contextual_delayed_credit.py +651 -0
- agent_evolve/application/contextual_search_controller.py +2374 -0
- agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
- agent_evolve/application/decision_metric_projection.py +112 -0
- agent_evolve/application/derived_action_semantics.py +129 -0
- agent_evolve/application/detailed_evaluation.py +449 -0
- agent_evolve/application/earned_lineage.py +1011 -0
- agent_evolve/application/effective_choice_audit.py +484 -0
- agent_evolve/application/empirical_consequence_calibration.py +908 -0
- agent_evolve/application/evaluation_accounting.py +325 -0
- agent_evolve/application/evaluation_cache.py +199 -0
- agent_evolve/application/evaluation_escrow.py +547 -0
- agent_evolve/application/evaluation_recourse.py +253 -0
- agent_evolve/application/event_recorder.py +151 -0
- agent_evolve/application/evolution_campaign.py +1840 -0
- agent_evolve/application/executable_hypothesis.py +323 -0
- agent_evolve/application/factorial_branch_pilot.py +772 -0
- agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
- agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
- agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
- agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
- agent_evolve/application/finite_action_selection.py +188 -0
- agent_evolve/application/finite_action_set.py +306 -0
- agent_evolve/application/finite_action_transition.py +537 -0
- agent_evolve/application/finite_variation_eligibility.py +296 -0
- agent_evolve/application/forecast_geometry_portfolio.py +799 -0
- agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
- agent_evolve/application/front_proximity_admission.py +311 -0
- agent_evolve/application/front_proximity_parent_basis.py +458 -0
- agent_evolve/application/frozen_hurdle_score.py +659 -0
- agent_evolve/application/g3_causal_screen.py +2257 -0
- agent_evolve/application/g3_causal_validation.py +1046 -0
- agent_evolve/application/g3_postseal_curation.py +818 -0
- agent_evolve/application/gated_agentic_generator.py +205 -0
- agent_evolve/application/generation_feedback.py +293 -0
- agent_evolve/application/generative_proposal_journal.py +185 -0
- agent_evolve/application/geometry_conditional_elasticity.py +453 -0
- agent_evolve/application/global_wave_action_allocation.py +1151 -0
- agent_evolve/application/head_mass_conditional_seat.py +268 -0
- agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
- agent_evolve/application/identifiable_reflection_learning.py +395 -0
- agent_evolve/application/identifiable_reflection_request.py +364 -0
- agent_evolve/application/in_memory_residual_archive.py +341 -0
- agent_evolve/application/insight_memory.py +1804 -0
- agent_evolve/application/live_runtime_manifest.py +758 -0
- agent_evolve/application/llm_task_queue.py +769 -0
- agent_evolve/application/matched_finite_action_block.py +409 -0
- agent_evolve/application/materialized_action_broker.py +2328 -0
- agent_evolve/application/materialized_action_constraints.py +83 -0
- agent_evolve/application/materialized_variation.py +211 -0
- agent_evolve/application/multi_option_evolution.py +1536 -0
- agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
- agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
- agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
- agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
- agent_evolve/application/outcome_relation.py +193 -0
- agent_evolve/application/paired_allocation_comparison.py +241 -0
- agent_evolve/application/paired_block_schedule.py +127 -0
- agent_evolve/application/parent_measurement.py +226 -0
- agent_evolve/application/pareto_archive.py +811 -0
- agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
- agent_evolve/application/portfolio_evolution.py +2950 -0
- agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
- agent_evolve/application/portfolio_memory_attribution.py +581 -0
- agent_evolve/application/portfolio_memory_dose.py +788 -0
- agent_evolve/application/portfolio_memory_matched_control.py +938 -0
- agent_evolve/application/portfolio_memory_transfer.py +297 -0
- agent_evolve/application/portfolio_optimization_memory.py +363 -0
- agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
- agent_evolve/application/portfolio_projection.py +335 -0
- agent_evolve/application/portfolio_recombination.py +2032 -0
- agent_evolve/application/post_evolution_reflection.py +834 -0
- agent_evolve/application/postcommit_rank_authority.py +245 -0
- agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
- agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
- agent_evolve/application/prequential_residual_exploration.py +343 -0
- agent_evolve/application/prequential_score_portfolio.py +954 -0
- agent_evolve/application/projections.py +292 -0
- agent_evolve/application/protected_action_committee.py +1027 -0
- agent_evolve/application/protected_branch_pilot.py +376 -0
- agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
- agent_evolve/application/provider_replay.py +910 -0
- agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
- agent_evolve/application/recombination_residual_expert.py +403 -0
- agent_evolve/application/reflection_workflow.py +571 -0
- agent_evolve/application/region_conditional_credit.py +911 -0
- agent_evolve/application/residual_campaign_runtime.py +531 -0
- agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
- agent_evolve/application/residual_headroom_ledger.py +1544 -0
- agent_evolve/application/residual_learning_transaction.py +396 -0
- agent_evolve/application/residual_portfolio_evolution.py +1228 -0
- agent_evolve/application/residual_reachability.py +749 -0
- agent_evolve/application/residual_stage_credit.py +499 -0
- agent_evolve/application/same_prefix_paired_audit.py +1580 -0
- agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
- agent_evolve/application/sequential_lineage_allocation.py +1017 -0
- agent_evolve/application/sequential_market_replay.py +1395 -0
- agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
- agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
- agent_evolve/application/single_score_action_allocation.py +299 -0
- agent_evolve/application/source_exposure_allocation.py +906 -0
- agent_evolve/application/staged_memory.py +210 -0
- agent_evolve/application/stratified_cold_start_allocation.py +732 -0
- agent_evolve/application/support_guarded_hurdle_score.py +549 -0
- agent_evolve/application/target_conditioned_action_forecast.py +595 -0
- agent_evolve/application/target_conditioned_campaign.py +566 -0
- agent_evolve/application/treatment_assignment.py +201 -0
- agent_evolve/application/trusted_objective_evidence.py +217 -0
- agent_evolve/application/two_stage_action_evolution.py +1131 -0
- agent_evolve/application/v8lite_allocation_policy.py +1083 -0
- agent_evolve/application/v9_candidate_policy.py +1303 -0
- agent_evolve/bootstrap.py +108 -0
- agent_evolve/campaign_presets.py +517 -0
- agent_evolve/campaign_profiles.py +452 -0
- agent_evolve/campaign_variation_topology.py +288 -0
- agent_evolve/campaign_workload.py +950 -0
- agent_evolve/cli.py +797 -0
- agent_evolve/contract.py +241 -0
- agent_evolve/core/__init__.py +91 -0
- agent_evolve/core/action_semantics.py +411 -0
- agent_evolve/core/authored.py +105 -0
- agent_evolve/core/formatting.py +286 -0
- agent_evolve/core/optimization_semantics.py +324 -0
- agent_evolve/core/problem.py +167 -0
- agent_evolve/core/results.py +323 -0
- agent_evolve/core/stats.py +70 -0
- agent_evolve/core/telemetry.py +100 -0
- agent_evolve/domain/__init__.py +89 -0
- agent_evolve/domain/artifact.py +162 -0
- agent_evolve/domain/durable_text.py +68 -0
- agent_evolve/domain/event.py +1454 -0
- agent_evolve/domain/finite_action_set.py +426 -0
- agent_evolve/domain/finite_variation.py +526 -0
- agent_evolve/domain/generative_emission.py +559 -0
- agent_evolve/domain/ids.py +163 -0
- agent_evolve/domain/inline_text.py +106 -0
- agent_evolve/domain/insight.py +27 -0
- agent_evolve/domain/lineage.py +737 -0
- agent_evolve/domain/llm_task_queue.py +960 -0
- agent_evolve/domain/outcome.py +96 -0
- agent_evolve/domain/patch.py +854 -0
- agent_evolve/domain/typed_json.py +542 -0
- agent_evolve/domain/variation_space.py +158 -0
- agent_evolve/driver.py +1014 -0
- agent_evolve/harness/__init__.py +29 -0
- agent_evolve/harness/base.py +242 -0
- agent_evolve/harness/directives.py +163 -0
- agent_evolve/harness/generative_seal.py +479 -0
- agent_evolve/harness/registry.py +41 -0
- agent_evolve/infrastructure/__init__.py +39 -0
- agent_evolve/infrastructure/artifacts/__init__.py +6 -0
- agent_evolve/infrastructure/artifacts/_verification.py +67 -0
- agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
- agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
- agent_evolve/infrastructure/asyncio_runtime.py +109 -0
- agent_evolve/infrastructure/authored_runtime.py +188 -0
- agent_evolve/infrastructure/authored_worker.py +171 -0
- agent_evolve/infrastructure/clock.py +53 -0
- agent_evolve/infrastructure/events/__init__.py +6 -0
- agent_evolve/infrastructure/events/_validation.py +89 -0
- agent_evolve/infrastructure/events/in_memory.py +56 -0
- agent_evolve/infrastructure/events/jsonl.py +193 -0
- agent_evolve/infrastructure/exception_provenance.py +215 -0
- agent_evolve/infrastructure/ids.py +118 -0
- agent_evolve/infrastructure/lineage_codec.py +1836 -0
- agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
- agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
- agent_evolve/infrastructure/resource_lease.py +370 -0
- agent_evolve/infrastructure/sanitization/__init__.py +8 -0
- agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
- agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
- agent_evolve/infrastructure/stream_liveness.py +383 -0
- agent_evolve/infrastructure/subprocess_boundary.py +136 -0
- agent_evolve/integrations/__init__.py +1 -0
- agent_evolve/integrations/botorch/__init__.py +28 -0
- agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
- agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
- agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
- agent_evolve/integrations/completion.py +242 -0
- agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
- agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
- agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
- agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
- agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
- agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
- agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
- agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
- agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
- agent_evolve/integrations/pydantic_ai/harness.py +159 -0
- agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
- agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
- agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
- agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
- agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
- agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
- agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
- agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
- agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
- agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
- agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
- agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
- agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
- agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
- agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
- agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
- agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
- agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
- agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
- agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
- agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
- agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
- agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
- agent_evolve/integrations/pymoo_adapter.py +242 -0
- agent_evolve/policies/__init__.py +17 -0
- agent_evolve/policies/check.py +469 -0
- agent_evolve/policies/emit_scaffold.py +451 -0
- agent_evolve/policies/feedback/__init__.py +37 -0
- agent_evolve/policies/feedback/held_out_asn.py +1325 -0
- agent_evolve/policies/genetic.py +607 -0
- agent_evolve/policies/llm_backoff.py +183 -0
- agent_evolve/policies/llm_chooser.py +226 -0
- agent_evolve/policies/llm_generator.py +1760 -0
- agent_evolve/policies/llm_init.py +267 -0
- agent_evolve/policies/llm_operator.py +109 -0
- agent_evolve/policies/llm_prior.py +194 -0
- agent_evolve/policies/llm_surrogate.py +334 -0
- agent_evolve/policies/measurement_evidence.py +704 -0
- agent_evolve/policies/memory/__init__.py +223 -0
- agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
- agent_evolve/policies/memory/compatibility_matching.py +593 -0
- agent_evolve/policies/memory/global_falsification.py +1841 -0
- agent_evolve/policies/memory/prompt_shape.py +503 -0
- agent_evolve/policies/memory/randomized_subset.py +714 -0
- agent_evolve/policies/memory/staged_causal.py +1270 -0
- agent_evolve/policies/memory/treatment_compliance.py +759 -0
- agent_evolve/policies/objective_resolution/__init__.py +17 -0
- agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
- agent_evolve/policies/operator_portfolio.py +407 -0
- agent_evolve/policies/reguidance.py +1133 -0
- agent_evolve/policies/reward/__init__.py +83 -0
- agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
- agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
- agent_evolve/policies/reward/affine_hypervolume.py +490 -0
- agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
- agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
- agent_evolve/policies/reward/frozen_archive.py +360 -0
- agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
- agent_evolve/policies/search_state.py +208 -0
- agent_evolve/policies/selection/__init__.py +345 -0
- agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
- agent_evolve/policies/selection/affine_frontier_context.py +330 -0
- agent_evolve/policies/selection/affine_frontier_target.py +473 -0
- agent_evolve/policies/selection/archive_elite.py +1346 -0
- agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
- agent_evolve/policies/selection/calibrated_slate.py +1394 -0
- agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
- agent_evolve/policies/selection/common_candidate_pool.py +685 -0
- agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
- agent_evolve/policies/selection/disjoint_pairs.py +479 -0
- agent_evolve/policies/selection/elite_explorer.py +719 -0
- agent_evolve/policies/selection/finite_action.py +187 -0
- agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
- agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
- agent_evolve/policies/selection/forecast_calibration.py +922 -0
- agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
- agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
- agent_evolve/policies/selection/full_support_slate.py +91 -0
- agent_evolve/policies/selection/meaningful_direction.py +240 -0
- agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
- agent_evolve/policies/selection/model_anchored_slate.py +826 -0
- agent_evolve/policies/selection/phenotype_recourse.py +979 -0
- agent_evolve/policies/selection/proposal_support.py +368 -0
- agent_evolve/policies/selection/random_portfolio.py +254 -0
- agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
- agent_evolve/policies/selection/residual_frontier.py +463 -0
- agent_evolve/policies/selection/residual_frontier_target.py +605 -0
- agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
- agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
- agent_evolve/policies/selection/target_conditioned_features.py +812 -0
- agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
- agent_evolve/policies/selection/task_keyed_palette.py +906 -0
- agent_evolve/policies/semantics.py +147 -0
- agent_evolve/policies/structure.py +362 -0
- agent_evolve/policies/structured_output_budget.py +62 -0
- agent_evolve/policies/surrogate.py +696 -0
- agent_evolve/policies/variation/__init__.py +1 -0
- agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
- agent_evolve/policies/variation/crossover_inheritance.py +575 -0
- agent_evolve/policies/variation/disjoint_recombination.py +611 -0
- agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
- agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
- agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
- agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
- agent_evolve/policies/variation/typed_patch.py +1981 -0
- agent_evolve/policies/weighted_prior.py +394 -0
- agent_evolve/ports/__init__.py +383 -0
- agent_evolve/ports/action_allocation.py +733 -0
- agent_evolve/ports/action_allocation_frame.py +1153 -0
- agent_evolve/ports/action_allocation_frame_commit.py +294 -0
- agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
- agent_evolve/ports/action_allocation_frame_v3.py +995 -0
- agent_evolve/ports/action_forecast.py +1568 -0
- agent_evolve/ports/action_metric_projection.py +165 -0
- agent_evolve/ports/agentic_generator.py +1561 -0
- agent_evolve/ports/archive_context.py +136 -0
- agent_evolve/ports/artifact_sanitizer.py +44 -0
- agent_evolve/ports/artifact_store.py +225 -0
- agent_evolve/ports/clock.py +13 -0
- agent_evolve/ports/contextual_search_allocation.py +827 -0
- agent_evolve/ports/decision_metric_projection.py +258 -0
- agent_evolve/ports/event_store.py +55 -0
- agent_evolve/ports/executable_hypothesis.py +557 -0
- agent_evolve/ports/finite_acquisition.py +377 -0
- agent_evolve/ports/finite_acquisition_batch.py +296 -0
- agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
- agent_evolve/ports/finite_acquisition_json.py +247 -0
- agent_evolve/ports/finite_acquisition_space.py +168 -0
- agent_evolve/ports/finite_action_selection.py +348 -0
- agent_evolve/ports/finite_action_set.py +256 -0
- agent_evolve/ports/frontier_target.py +396 -0
- agent_evolve/ports/generation_failure.py +43 -0
- agent_evolve/ports/hard_feasibility.py +233 -0
- agent_evolve/ports/id_factory.py +34 -0
- agent_evolve/ports/llm_task_queue.py +93 -0
- agent_evolve/ports/objective_resolution.py +419 -0
- agent_evolve/ports/paired_allocation_comparison.py +401 -0
- agent_evolve/ports/paired_block_schedule.py +475 -0
- agent_evolve/ports/parent_measurement.py +336 -0
- agent_evolve/ports/portfolio_memory_dose.py +643 -0
- agent_evolve/ports/portfolio_selection.py +3169 -0
- agent_evolve/ports/postcommit_rank_authority.py +467 -0
- agent_evolve/ports/presented_action_evidence.py +794 -0
- agent_evolve/ports/resource_lease.py +162 -0
- agent_evolve/ports/structured_generator.py +734 -0
- agent_evolve/ports/structured_output_budget.py +120 -0
- agent_evolve/ports/subprocess_boundary.py +138 -0
- agent_evolve/ports/treatment_assignment.py +466 -0
- agent_evolve/ports/variation_catalog.py +76 -0
- agent_evolve/ports/variation_source.py +226 -0
- agent_evolve/proposal_mode.py +157 -0
- agent_evolve/proposers/__init__.py +10 -0
- agent_evolve/proposers/random_proposer.py +188 -0
- agent_evolve/provider_accounting.py +163 -0
- agent_evolve/py.typed +0 -0
- agent_evolve/reference_method.py +1570 -0
- agent_evolve/session/__init__.py +11 -0
- agent_evolve/session/authorship.py +864 -0
- agent_evolve/session/evaluate.py +236 -0
- agent_evolve/session/fidelity.py +237 -0
- agent_evolve/session/genetic_loop.py +742 -0
- agent_evolve/session/loop.py +803 -0
- agent_evolve/session/screening.py +671 -0
- agent_evolve/settings.py +376 -0
- agent_evolve/workload_kit.py +368 -0
- agent_evolve/workload_prompt.py +398 -0
- agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
- agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
- agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
- agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
- agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
- 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)
|