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,1568 @@
|
|
|
1
|
+
"""Provider-neutral all-option action forecasts with trusted resolution.
|
|
2
|
+
|
|
3
|
+
Models author only numeric forecast drafts and opaque citations. Trusted code
|
|
4
|
+
binds a complete draft batch back to one optimization-semantics snapshot, one
|
|
5
|
+
parent-bound finite-action contract, and authenticated prompt-visible card
|
|
6
|
+
views. Partial batches are deliberately not representable after resolution.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
import re
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from enum import Enum
|
|
17
|
+
from typing import Protocol, runtime_checkable
|
|
18
|
+
|
|
19
|
+
from agent_evolve.core.action_semantics import ActionSpaceSemantics
|
|
20
|
+
from agent_evolve.core.optimization_semantics import OptimizationSemantics
|
|
21
|
+
from agent_evolve.domain.finite_variation import (
|
|
22
|
+
FiniteActionEvidenceBinding,
|
|
23
|
+
FiniteVariationContract,
|
|
24
|
+
validate_finite_variation_contract,
|
|
25
|
+
)
|
|
26
|
+
from agent_evolve.domain.ids import LLMCallId
|
|
27
|
+
from agent_evolve.domain.patch import require_sha256
|
|
28
|
+
from agent_evolve.domain.typed_json import FrozenJsonObject, freeze_json, typed_json_sha256
|
|
29
|
+
from agent_evolve.ports.agentic_generator import AgenticCallTelemetry
|
|
30
|
+
from agent_evolve.ports.portfolio_selection import (
|
|
31
|
+
PortfolioCard,
|
|
32
|
+
PortfolioCardSourceRegistry,
|
|
33
|
+
PortfolioExperimentalViewReceipt,
|
|
34
|
+
portfolio_card_action_evidence_sha256,
|
|
35
|
+
portfolio_card_snapshot_sha256,
|
|
36
|
+
validate_portfolio_experimental_view,
|
|
37
|
+
)
|
|
38
|
+
from agent_evolve.ports.structured_generator import MAX_OUTPUT_TOKENS
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
|
|
42
|
+
_OPTION_ID = re.compile(r"^[a-z][a-z0-9_.-]{0,255}$")
|
|
43
|
+
_METRIC_ID = re.compile(r"^[a-z][a-z0-9_.:-]{0,191}$")
|
|
44
|
+
_REQUEST_DOMAIN = b"agent-evolve:action-forecast-request:v2\x00"
|
|
45
|
+
_BATCH_DOMAIN = b"agent-evolve:resolved-action-forecast-batch:v2\x00"
|
|
46
|
+
_PARTITION_POLICY_DOMAIN = b"agent-evolve:action-forecast-partition-policy:v1\x00"
|
|
47
|
+
_BLOCK_SPEC_DOMAIN = b"agent-evolve:action-forecast-block-spec:v1\x00"
|
|
48
|
+
_PARTITION_LAYOUT_DOMAIN = b"agent-evolve:action-forecast-partition-layout:v1\x00"
|
|
49
|
+
_BLOCK_REQUEST_DOMAIN = b"agent-evolve:action-forecast-block-request:v1\x00"
|
|
50
|
+
_RESOLVED_BLOCK_DOMAIN = b"agent-evolve:resolved-action-forecast-block:v1\x00"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _canonical_json(value: object) -> bytes:
|
|
54
|
+
return json.dumps(
|
|
55
|
+
value,
|
|
56
|
+
allow_nan=False,
|
|
57
|
+
ensure_ascii=True,
|
|
58
|
+
separators=(",", ":"),
|
|
59
|
+
sort_keys=True,
|
|
60
|
+
).encode("ascii")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _hash(domain: bytes, value: object) -> str:
|
|
64
|
+
return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _finite_float(value: object, name: str) -> float:
|
|
68
|
+
if type(value) is not float or not math.isfinite(value):
|
|
69
|
+
raise TypeError(f"{name} must be a finite canonical float")
|
|
70
|
+
return value
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _canonical_metric_ids(values: tuple[str, ...], name: str) -> None:
|
|
74
|
+
if type(values) is not tuple or any(
|
|
75
|
+
type(value) is not str or _METRIC_ID.fullmatch(value) is None
|
|
76
|
+
for value in values
|
|
77
|
+
):
|
|
78
|
+
raise TypeError(f"{name} must be an exact tuple of metric identifiers")
|
|
79
|
+
if not values:
|
|
80
|
+
raise ValueError(f"{name} must be non-empty")
|
|
81
|
+
if values != tuple(sorted(set(values))):
|
|
82
|
+
raise ValueError(f"{name} must be unique and canonical")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@dataclass(frozen=True, slots=True)
|
|
86
|
+
class ParentMetricValue:
|
|
87
|
+
"""One finite parent value in the optimization semantics' metric space."""
|
|
88
|
+
|
|
89
|
+
metric_id: str
|
|
90
|
+
value: float
|
|
91
|
+
|
|
92
|
+
def __post_init__(self) -> None:
|
|
93
|
+
_canonical_metric_ids((self.metric_id,), "metric_id")
|
|
94
|
+
_finite_float(self.value, "value")
|
|
95
|
+
|
|
96
|
+
def to_record(self) -> dict[str, object]:
|
|
97
|
+
self.__post_init__()
|
|
98
|
+
return {"metric_id": self.metric_id, "value_hex": self.value.hex()}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True, slots=True)
|
|
102
|
+
class MetricForecastScale:
|
|
103
|
+
"""Explicit positive scale used to interpret one metric's delta forecast."""
|
|
104
|
+
|
|
105
|
+
metric_id: str
|
|
106
|
+
delta_scale: float
|
|
107
|
+
definition_sha256: str
|
|
108
|
+
|
|
109
|
+
def __post_init__(self) -> None:
|
|
110
|
+
_canonical_metric_ids((self.metric_id,), "metric_id")
|
|
111
|
+
_finite_float(self.delta_scale, "delta_scale")
|
|
112
|
+
if self.delta_scale <= 0.0:
|
|
113
|
+
raise ValueError("delta_scale must be strictly positive")
|
|
114
|
+
require_sha256(self.definition_sha256, "definition_sha256")
|
|
115
|
+
|
|
116
|
+
def to_record(self) -> dict[str, object]:
|
|
117
|
+
self.__post_init__()
|
|
118
|
+
return {
|
|
119
|
+
"metric_id": self.metric_id,
|
|
120
|
+
"delta_scale_hex": self.delta_scale.hex(),
|
|
121
|
+
"definition_sha256": self.definition_sha256,
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True, slots=True)
|
|
126
|
+
class ActionEvidenceCitation:
|
|
127
|
+
"""Model-authored pointer to one prompt-visible card/action binding."""
|
|
128
|
+
|
|
129
|
+
card_key: str
|
|
130
|
+
action_binding_identity_sha256: str
|
|
131
|
+
|
|
132
|
+
def __post_init__(self) -> None:
|
|
133
|
+
if type(self.card_key) is not str or _TOKEN.fullmatch(self.card_key) is None:
|
|
134
|
+
raise ValueError("card_key must use the closed lowercase token grammar")
|
|
135
|
+
require_sha256(
|
|
136
|
+
self.action_binding_identity_sha256,
|
|
137
|
+
"action_binding_identity_sha256",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def to_record(self) -> dict[str, object]:
|
|
141
|
+
self.__post_init__()
|
|
142
|
+
return {
|
|
143
|
+
"card_key": self.card_key,
|
|
144
|
+
"action_binding_identity_sha256": (
|
|
145
|
+
self.action_binding_identity_sha256
|
|
146
|
+
),
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class ActionForecastEvidenceMode(str, Enum):
|
|
151
|
+
"""Request-scoped evidence/citation treatment contract."""
|
|
152
|
+
|
|
153
|
+
GROUNDED = "grounded"
|
|
154
|
+
CATALOG_ONLY = "catalog_only"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(frozen=True, slots=True)
|
|
158
|
+
class ActionMetricForecast:
|
|
159
|
+
"""Calibratable delta quantiles for one required metric."""
|
|
160
|
+
|
|
161
|
+
metric_id: str
|
|
162
|
+
p10_delta: float
|
|
163
|
+
p50_delta: float
|
|
164
|
+
p90_delta: float
|
|
165
|
+
confidence: float
|
|
166
|
+
citations: tuple[ActionEvidenceCitation, ...]
|
|
167
|
+
|
|
168
|
+
def __post_init__(self) -> None:
|
|
169
|
+
_canonical_metric_ids((self.metric_id,), "metric_id")
|
|
170
|
+
for name in ("p10_delta", "p50_delta", "p90_delta", "confidence"):
|
|
171
|
+
_finite_float(getattr(self, name), name)
|
|
172
|
+
if not self.p10_delta <= self.p50_delta <= self.p90_delta:
|
|
173
|
+
raise ValueError("delta quantiles must satisfy p10 <= p50 <= p90")
|
|
174
|
+
if not 0.0 <= self.confidence <= 1.0:
|
|
175
|
+
raise ValueError("confidence must lie in [0,1]")
|
|
176
|
+
if type(self.citations) is not tuple or any(
|
|
177
|
+
type(value) is not ActionEvidenceCitation for value in self.citations
|
|
178
|
+
):
|
|
179
|
+
raise TypeError("citations must be an exact tuple of action citations")
|
|
180
|
+
for citation in self.citations:
|
|
181
|
+
citation.__post_init__()
|
|
182
|
+
keys = tuple(
|
|
183
|
+
(value.card_key, value.action_binding_identity_sha256)
|
|
184
|
+
for value in self.citations
|
|
185
|
+
)
|
|
186
|
+
if keys != tuple(sorted(set(keys))):
|
|
187
|
+
raise ValueError("citations must be unique and canonical")
|
|
188
|
+
|
|
189
|
+
def to_record(self) -> dict[str, object]:
|
|
190
|
+
self.__post_init__()
|
|
191
|
+
return {
|
|
192
|
+
"metric_id": self.metric_id,
|
|
193
|
+
"p10_delta_hex": self.p10_delta.hex(),
|
|
194
|
+
"p50_delta_hex": self.p50_delta.hex(),
|
|
195
|
+
"p90_delta_hex": self.p90_delta.hex(),
|
|
196
|
+
"confidence_hex": self.confidence.hex(),
|
|
197
|
+
"citations": [value.to_record() for value in self.citations],
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
@dataclass(frozen=True, slots=True)
|
|
202
|
+
class ActionForecastDraft:
|
|
203
|
+
"""Untrusted structured output for exactly one opaque finite option."""
|
|
204
|
+
|
|
205
|
+
option_id: str
|
|
206
|
+
probability_valid: float
|
|
207
|
+
metric_forecasts: tuple[ActionMetricForecast, ...]
|
|
208
|
+
|
|
209
|
+
def __post_init__(self) -> None:
|
|
210
|
+
if type(self.option_id) is not str or _OPTION_ID.fullmatch(self.option_id) is None:
|
|
211
|
+
raise ValueError("option_id must use the closed finite-option grammar")
|
|
212
|
+
_finite_float(self.probability_valid, "probability_valid")
|
|
213
|
+
if not 0.0 <= self.probability_valid <= 1.0:
|
|
214
|
+
raise ValueError("probability_valid must lie in [0,1]")
|
|
215
|
+
if type(self.metric_forecasts) is not tuple or not self.metric_forecasts or any(
|
|
216
|
+
type(value) is not ActionMetricForecast
|
|
217
|
+
for value in self.metric_forecasts
|
|
218
|
+
):
|
|
219
|
+
raise ValueError(
|
|
220
|
+
"metric_forecasts must be a non-empty exact tuple of forecasts"
|
|
221
|
+
)
|
|
222
|
+
for value in self.metric_forecasts:
|
|
223
|
+
value.__post_init__()
|
|
224
|
+
metric_ids = tuple(value.metric_id for value in self.metric_forecasts)
|
|
225
|
+
_canonical_metric_ids(metric_ids, "metric_forecasts")
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _prompt_action_bindings(
|
|
229
|
+
card: PortfolioCard,
|
|
230
|
+
) -> tuple[FiniteActionEvidenceBinding, ...]:
|
|
231
|
+
"""Read only the authenticated prompt-visible action-evidence projection.
|
|
232
|
+
|
|
233
|
+
Source provenance is intentionally not consulted here: experimental card
|
|
234
|
+
views may permute or redact presented action evidence while retaining their
|
|
235
|
+
immutable source binding. ``PortfolioCard.__post_init__`` authenticates
|
|
236
|
+
whichever projection ``prompt_record`` exposes.
|
|
237
|
+
"""
|
|
238
|
+
|
|
239
|
+
card.__post_init__()
|
|
240
|
+
return card.finite_action_evidence
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _validate_admitted_card_views(
|
|
244
|
+
cards: tuple[PortfolioCard, ...],
|
|
245
|
+
source_registry: PortfolioCardSourceRegistry,
|
|
246
|
+
) -> None:
|
|
247
|
+
"""Apply the same closed source/donor checks as scientific selection."""
|
|
248
|
+
|
|
249
|
+
if type(source_registry) is not PortfolioCardSourceRegistry:
|
|
250
|
+
raise TypeError("source_registry must be trusted PortfolioCardSourceRegistry")
|
|
251
|
+
source_registry.__post_init__()
|
|
252
|
+
references = tuple(card.reference for card in cards)
|
|
253
|
+
if len(set(references)) != len(references):
|
|
254
|
+
raise ValueError("cards cannot repeat an exact insight reference")
|
|
255
|
+
source_bindings = {
|
|
256
|
+
card.source_binding.binding_sha256: card.source_binding
|
|
257
|
+
for card in cards
|
|
258
|
+
if card.source_binding is not None
|
|
259
|
+
}
|
|
260
|
+
if len(source_bindings) != len(cards):
|
|
261
|
+
raise ValueError("forecast requests require source-bound cards only")
|
|
262
|
+
admitted = {
|
|
263
|
+
binding.binding_sha256: binding
|
|
264
|
+
for binding in source_registry.source_bindings
|
|
265
|
+
}
|
|
266
|
+
if admitted.keys() != source_bindings.keys():
|
|
267
|
+
raise ValueError("source registry differs from the request card source set")
|
|
268
|
+
for binding_sha256, binding in source_bindings.items():
|
|
269
|
+
if admitted[binding_sha256] != binding:
|
|
270
|
+
raise ValueError("source registry binding differs from the request card")
|
|
271
|
+
for card in cards:
|
|
272
|
+
receipt = card.derived_view_receipt
|
|
273
|
+
if receipt is None:
|
|
274
|
+
continue
|
|
275
|
+
evidence_source_sha256 = receipt.evidence_source_binding_sha256
|
|
276
|
+
if evidence_source_sha256 is not None:
|
|
277
|
+
evidence_source = source_bindings.get(evidence_source_sha256)
|
|
278
|
+
if evidence_source is None:
|
|
279
|
+
raise ValueError("derived evidence donor is outside the request")
|
|
280
|
+
if (
|
|
281
|
+
receipt.derived_evidence_sha256
|
|
282
|
+
!= evidence_source.source_evidence_sha256
|
|
283
|
+
):
|
|
284
|
+
raise ValueError("derived evidence differs from its admitted donor")
|
|
285
|
+
score_source_sha256 = receipt.score_source_binding_sha256
|
|
286
|
+
if score_source_sha256 is not None:
|
|
287
|
+
score_source = source_bindings.get(score_source_sha256)
|
|
288
|
+
if score_source is None:
|
|
289
|
+
raise ValueError("derived score donor is outside the request")
|
|
290
|
+
if (
|
|
291
|
+
receipt.derived_score_state_sha256
|
|
292
|
+
!= score_source.source_score_state_sha256
|
|
293
|
+
):
|
|
294
|
+
raise ValueError("derived score state differs from its admitted donor")
|
|
295
|
+
action_source_sha256 = receipt.action_evidence_source_binding_sha256
|
|
296
|
+
if action_source_sha256 is not None:
|
|
297
|
+
action_source = source_bindings.get(action_source_sha256)
|
|
298
|
+
if action_source is None:
|
|
299
|
+
raise ValueError("derived action-evidence donor is outside the request")
|
|
300
|
+
if (
|
|
301
|
+
receipt.derived_action_evidence_sha256
|
|
302
|
+
!= portfolio_card_action_evidence_sha256(
|
|
303
|
+
action_source.finite_action_evidence
|
|
304
|
+
)
|
|
305
|
+
):
|
|
306
|
+
raise ValueError(
|
|
307
|
+
"derived action evidence differs from its admitted donor"
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@dataclass(frozen=True, slots=True)
|
|
312
|
+
class ActionForecastRequest:
|
|
313
|
+
"""One logical call that must forecast every option and required metric."""
|
|
314
|
+
|
|
315
|
+
call_id: LLMCallId
|
|
316
|
+
operation: str
|
|
317
|
+
instruction: str
|
|
318
|
+
context: FrozenJsonObject
|
|
319
|
+
optimization_semantics: OptimizationSemantics
|
|
320
|
+
action_semantics: ActionSpaceSemantics
|
|
321
|
+
finite_variation_contract: FiniteVariationContract
|
|
322
|
+
cards: tuple[PortfolioCard, ...]
|
|
323
|
+
source_registry: PortfolioCardSourceRegistry | None
|
|
324
|
+
evidence_mode: ActionForecastEvidenceMode
|
|
325
|
+
experimental_view_receipt: PortfolioExperimentalViewReceipt | None
|
|
326
|
+
parent_metric_values: tuple[ParentMetricValue, ...]
|
|
327
|
+
metric_scales: tuple[MetricForecastScale, ...]
|
|
328
|
+
max_output_tokens: int = MAX_OUTPUT_TOKENS
|
|
329
|
+
temperature: float | None = None
|
|
330
|
+
|
|
331
|
+
def __post_init__(self) -> None:
|
|
332
|
+
if type(self.call_id) is not LLMCallId:
|
|
333
|
+
raise TypeError("call_id must be an exact LLMCallId")
|
|
334
|
+
LLMCallId.__post_init__(self.call_id)
|
|
335
|
+
if type(self.operation) is not str or _TOKEN.fullmatch(self.operation) is None:
|
|
336
|
+
raise ValueError("operation must use the closed lowercase token grammar")
|
|
337
|
+
if (
|
|
338
|
+
type(self.instruction) is not str
|
|
339
|
+
or not self.instruction.strip()
|
|
340
|
+
or self.instruction != self.instruction.strip()
|
|
341
|
+
):
|
|
342
|
+
raise ValueError("instruction must be canonical non-empty text")
|
|
343
|
+
if type(self.context) is not FrozenJsonObject:
|
|
344
|
+
raise TypeError("context must be an exact FrozenJsonObject")
|
|
345
|
+
if freeze_json(self.context) is not self.context:
|
|
346
|
+
raise TypeError("context must already be frozen typed JSON")
|
|
347
|
+
if type(self.optimization_semantics) is not OptimizationSemantics:
|
|
348
|
+
raise TypeError(
|
|
349
|
+
"optimization_semantics must be exact OptimizationSemantics"
|
|
350
|
+
)
|
|
351
|
+
OptimizationSemantics.__post_init__(self.optimization_semantics)
|
|
352
|
+
validate_finite_variation_contract(self.finite_variation_contract)
|
|
353
|
+
if type(self.action_semantics) is not ActionSpaceSemantics:
|
|
354
|
+
raise TypeError("action_semantics must be exact ActionSpaceSemantics")
|
|
355
|
+
ActionSpaceSemantics.__post_init__(self.action_semantics)
|
|
356
|
+
self.action_semantics.validate_contract_binding(
|
|
357
|
+
(
|
|
358
|
+
self.finite_variation_contract.catalog_id,
|
|
359
|
+
self.finite_variation_contract.catalog_version,
|
|
360
|
+
self.finite_variation_contract.catalog_definition_sha256,
|
|
361
|
+
),
|
|
362
|
+
tuple(
|
|
363
|
+
option.family
|
|
364
|
+
for option in self.finite_variation_contract.options
|
|
365
|
+
),
|
|
366
|
+
)
|
|
367
|
+
if type(self.cards) is not tuple or any(
|
|
368
|
+
type(card) is not PortfolioCard for card in self.cards
|
|
369
|
+
):
|
|
370
|
+
raise TypeError("cards must be an exact tuple of PortfolioCard")
|
|
371
|
+
for card in self.cards:
|
|
372
|
+
card.__post_init__()
|
|
373
|
+
card_keys = tuple(card.card_key for card in self.cards)
|
|
374
|
+
if card_keys != tuple(sorted(set(card_keys))):
|
|
375
|
+
raise ValueError("cards must use unique canonical card_key order")
|
|
376
|
+
if type(self.evidence_mode) is not ActionForecastEvidenceMode:
|
|
377
|
+
raise TypeError("evidence_mode must be exact ActionForecastEvidenceMode")
|
|
378
|
+
if self.evidence_mode is ActionForecastEvidenceMode.GROUNDED:
|
|
379
|
+
if not self.cards:
|
|
380
|
+
raise ValueError("grounded forecasts require admitted evidence cards")
|
|
381
|
+
if type(self.source_registry) is not PortfolioCardSourceRegistry:
|
|
382
|
+
raise ValueError("grounded forecasts require a trusted source registry")
|
|
383
|
+
_validate_admitted_card_views(self.cards, self.source_registry)
|
|
384
|
+
if type(self.experimental_view_receipt) is not PortfolioExperimentalViewReceipt:
|
|
385
|
+
raise ValueError(
|
|
386
|
+
"grounded forecasts require a scientific experimental-view receipt"
|
|
387
|
+
)
|
|
388
|
+
validate_portfolio_experimental_view(
|
|
389
|
+
cards=self.cards,
|
|
390
|
+
finite_variation_contract=self.finite_variation_contract,
|
|
391
|
+
source_registry=self.source_registry,
|
|
392
|
+
receipt=self.experimental_view_receipt,
|
|
393
|
+
)
|
|
394
|
+
else:
|
|
395
|
+
if (
|
|
396
|
+
self.cards
|
|
397
|
+
or self.source_registry is not None
|
|
398
|
+
or self.experimental_view_receipt is not None
|
|
399
|
+
):
|
|
400
|
+
raise ValueError(
|
|
401
|
+
"catalog-only forecasts forbid cards, source admission, and "
|
|
402
|
+
"experimental-view receipts"
|
|
403
|
+
)
|
|
404
|
+
if type(self.parent_metric_values) is not tuple or any(
|
|
405
|
+
type(value) is not ParentMetricValue
|
|
406
|
+
for value in self.parent_metric_values
|
|
407
|
+
):
|
|
408
|
+
raise TypeError("parent_metric_values must contain exact values")
|
|
409
|
+
if type(self.metric_scales) is not tuple or any(
|
|
410
|
+
type(value) is not MetricForecastScale for value in self.metric_scales
|
|
411
|
+
):
|
|
412
|
+
raise TypeError("metric_scales must contain exact values")
|
|
413
|
+
for value in self.parent_metric_values:
|
|
414
|
+
value.__post_init__()
|
|
415
|
+
for value in self.metric_scales:
|
|
416
|
+
value.__post_init__()
|
|
417
|
+
parent_ids = tuple(value.metric_id for value in self.parent_metric_values)
|
|
418
|
+
scale_ids = tuple(value.metric_id for value in self.metric_scales)
|
|
419
|
+
_canonical_metric_ids(parent_ids, "parent_metric_values")
|
|
420
|
+
_canonical_metric_ids(scale_ids, "metric_scales")
|
|
421
|
+
if parent_ids != scale_ids:
|
|
422
|
+
raise ValueError("parent metric values and scales must cover identical metrics")
|
|
423
|
+
semantic_ids = {metric.metric_id for metric in self.optimization_semantics.metrics}
|
|
424
|
+
if not set(parent_ids).issubset(semantic_ids):
|
|
425
|
+
raise ValueError("forecast metrics escape the optimization semantics")
|
|
426
|
+
if type(self.max_output_tokens) is not int or not (
|
|
427
|
+
1 <= self.max_output_tokens <= MAX_OUTPUT_TOKENS
|
|
428
|
+
):
|
|
429
|
+
raise ValueError(
|
|
430
|
+
f"max_output_tokens must lie in [1, {MAX_OUTPUT_TOKENS}]"
|
|
431
|
+
)
|
|
432
|
+
if self.temperature is not None and (
|
|
433
|
+
isinstance(self.temperature, bool)
|
|
434
|
+
or not isinstance(self.temperature, (int, float))
|
|
435
|
+
or not math.isfinite(float(self.temperature))
|
|
436
|
+
or not 0.0 <= float(self.temperature) <= 2.0
|
|
437
|
+
):
|
|
438
|
+
raise ValueError("temperature must be finite in [0,2] or None")
|
|
439
|
+
|
|
440
|
+
@property
|
|
441
|
+
def required_metric_ids(self) -> tuple[str, ...]:
|
|
442
|
+
self.__post_init__()
|
|
443
|
+
return tuple(value.metric_id for value in self.parent_metric_values)
|
|
444
|
+
|
|
445
|
+
@property
|
|
446
|
+
def context_sha256(self) -> str:
|
|
447
|
+
self.__post_init__()
|
|
448
|
+
return typed_json_sha256(self.context)
|
|
449
|
+
|
|
450
|
+
@property
|
|
451
|
+
def card_snapshot_sha256(self) -> str:
|
|
452
|
+
self.__post_init__()
|
|
453
|
+
return portfolio_card_snapshot_sha256(self.cards)
|
|
454
|
+
|
|
455
|
+
def to_record(self) -> dict[str, object]:
|
|
456
|
+
self.__post_init__()
|
|
457
|
+
return {
|
|
458
|
+
"schema_version": 2,
|
|
459
|
+
"call_id": self.call_id.value,
|
|
460
|
+
"operation": self.operation,
|
|
461
|
+
"instruction_sha256": hashlib.sha256(
|
|
462
|
+
self.instruction.encode("utf-8", errors="strict")
|
|
463
|
+
).hexdigest(),
|
|
464
|
+
"context_sha256": self.context_sha256,
|
|
465
|
+
"optimization_semantics": {
|
|
466
|
+
"semantics_id": self.optimization_semantics.semantics_id,
|
|
467
|
+
"semantics_version": self.optimization_semantics.semantics_version,
|
|
468
|
+
"definition_sha256": self.optimization_semantics.definition_sha256,
|
|
469
|
+
},
|
|
470
|
+
"action_semantics": {
|
|
471
|
+
"semantics_id": self.action_semantics.semantics_id,
|
|
472
|
+
"semantics_version": self.action_semantics.semantics_version,
|
|
473
|
+
"definition_sha256": self.action_semantics.definition_sha256,
|
|
474
|
+
},
|
|
475
|
+
"finite_contract_identity_sha256": (
|
|
476
|
+
self.finite_variation_contract.identity_sha256
|
|
477
|
+
),
|
|
478
|
+
"card_snapshot_sha256": self.card_snapshot_sha256,
|
|
479
|
+
"source_registry_sha256": (
|
|
480
|
+
None
|
|
481
|
+
if self.source_registry is None
|
|
482
|
+
else self.source_registry.registry_sha256
|
|
483
|
+
),
|
|
484
|
+
"evidence_mode": self.evidence_mode.value,
|
|
485
|
+
"experimental_view_receipt": (
|
|
486
|
+
None
|
|
487
|
+
if self.experimental_view_receipt is None
|
|
488
|
+
else {
|
|
489
|
+
"arm": self.experimental_view_receipt.arm.value,
|
|
490
|
+
"receipt_sha256": self.experimental_view_receipt.receipt_sha256,
|
|
491
|
+
}
|
|
492
|
+
),
|
|
493
|
+
"parent_metric_values": [
|
|
494
|
+
value.to_record() for value in self.parent_metric_values
|
|
495
|
+
],
|
|
496
|
+
"metric_scales": [value.to_record() for value in self.metric_scales],
|
|
497
|
+
"max_output_tokens": self.max_output_tokens,
|
|
498
|
+
"temperature_hex": (
|
|
499
|
+
None if self.temperature is None else float(self.temperature).hex()
|
|
500
|
+
),
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
@property
|
|
504
|
+
def request_sha256(self) -> str:
|
|
505
|
+
return _hash(_REQUEST_DOMAIN, self.to_record())
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
509
|
+
class ActionForecastPartitionPolicyBinding:
|
|
510
|
+
"""Identified, resource-bounded policy for partitioning one target frame."""
|
|
511
|
+
|
|
512
|
+
policy_id: str
|
|
513
|
+
policy_version: int
|
|
514
|
+
policy_definition_sha256: str
|
|
515
|
+
max_rows_per_block: int
|
|
516
|
+
max_metric_cells_per_block: int
|
|
517
|
+
|
|
518
|
+
def __post_init__(self) -> None:
|
|
519
|
+
if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
|
|
520
|
+
raise ValueError("policy_id must use the closed token grammar")
|
|
521
|
+
if type(self.policy_version) is not int or self.policy_version <= 0:
|
|
522
|
+
raise ValueError("policy_version must be a positive exact integer")
|
|
523
|
+
require_sha256(self.policy_definition_sha256, "policy_definition_sha256")
|
|
524
|
+
if type(self.max_rows_per_block) is not int or self.max_rows_per_block <= 0:
|
|
525
|
+
raise ValueError("max_rows_per_block must be a positive exact integer")
|
|
526
|
+
if (
|
|
527
|
+
type(self.max_metric_cells_per_block) is not int
|
|
528
|
+
or self.max_metric_cells_per_block <= 0
|
|
529
|
+
):
|
|
530
|
+
raise ValueError(
|
|
531
|
+
"max_metric_cells_per_block must be a positive exact integer"
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
535
|
+
self.__post_init__()
|
|
536
|
+
return {
|
|
537
|
+
"schema_version": 1,
|
|
538
|
+
"policy_id": self.policy_id,
|
|
539
|
+
"policy_version": self.policy_version,
|
|
540
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
541
|
+
"max_rows_per_block": self.max_rows_per_block,
|
|
542
|
+
"max_metric_cells_per_block": self.max_metric_cells_per_block,
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def binding_sha256(self) -> str:
|
|
547
|
+
return _hash(_PARTITION_POLICY_DOMAIN, self._unsigned_record())
|
|
548
|
+
|
|
549
|
+
def to_record(self) -> dict[str, object]:
|
|
550
|
+
return {**self._unsigned_record(), "binding_sha256": self.binding_sha256}
|
|
551
|
+
|
|
552
|
+
def __eq__(self, other: object) -> bool:
|
|
553
|
+
return (
|
|
554
|
+
type(self) is ActionForecastPartitionPolicyBinding
|
|
555
|
+
and type(other) is ActionForecastPartitionPolicyBinding
|
|
556
|
+
and self.binding_sha256 == other.binding_sha256
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
__hash__ = None
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
563
|
+
class ActionForecastBlockSpec:
|
|
564
|
+
"""One immutable contiguous slice of global finite-option positions."""
|
|
565
|
+
|
|
566
|
+
block_index: int
|
|
567
|
+
global_row_start: int
|
|
568
|
+
global_row_stop: int
|
|
569
|
+
option_identity_sha256s: tuple[str, ...]
|
|
570
|
+
|
|
571
|
+
def __post_init__(self) -> None:
|
|
572
|
+
if type(self.block_index) is not int or self.block_index < 0:
|
|
573
|
+
raise ValueError("block_index must be a non-negative exact integer")
|
|
574
|
+
if type(self.global_row_start) is not int or self.global_row_start < 0:
|
|
575
|
+
raise ValueError(
|
|
576
|
+
"global_row_start must be a non-negative exact integer"
|
|
577
|
+
)
|
|
578
|
+
if (
|
|
579
|
+
type(self.global_row_stop) is not int
|
|
580
|
+
or self.global_row_stop <= self.global_row_start
|
|
581
|
+
):
|
|
582
|
+
raise ValueError("global_row_stop must be greater than global_row_start")
|
|
583
|
+
if type(self.option_identity_sha256s) is not tuple or any(
|
|
584
|
+
type(value) is not str for value in self.option_identity_sha256s
|
|
585
|
+
):
|
|
586
|
+
raise TypeError(
|
|
587
|
+
"option_identity_sha256s must be an exact tuple of digests"
|
|
588
|
+
)
|
|
589
|
+
if len(self.option_identity_sha256s) != self.row_count:
|
|
590
|
+
raise ValueError("block option identities must cover every local row")
|
|
591
|
+
for index, value in enumerate(self.option_identity_sha256s):
|
|
592
|
+
require_sha256(value, f"option_identity_sha256s[{index}]")
|
|
593
|
+
if len(set(self.option_identity_sha256s)) != len(
|
|
594
|
+
self.option_identity_sha256s
|
|
595
|
+
):
|
|
596
|
+
raise ValueError("a block cannot repeat an option identity")
|
|
597
|
+
|
|
598
|
+
@property
|
|
599
|
+
def row_count(self) -> int:
|
|
600
|
+
return self.global_row_stop - self.global_row_start
|
|
601
|
+
|
|
602
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
603
|
+
self.__post_init__()
|
|
604
|
+
return {
|
|
605
|
+
"schema_version": 1,
|
|
606
|
+
"block_index": self.block_index,
|
|
607
|
+
"global_row_start": self.global_row_start,
|
|
608
|
+
"global_row_stop": self.global_row_stop,
|
|
609
|
+
"option_identity_sha256s": list(self.option_identity_sha256s),
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
@property
|
|
613
|
+
def block_spec_sha256(self) -> str:
|
|
614
|
+
return _hash(_BLOCK_SPEC_DOMAIN, self._unsigned_record())
|
|
615
|
+
|
|
616
|
+
def to_record(self) -> dict[str, object]:
|
|
617
|
+
return {
|
|
618
|
+
**self._unsigned_record(),
|
|
619
|
+
"block_spec_sha256": self.block_spec_sha256,
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
def __eq__(self, other: object) -> bool:
|
|
623
|
+
return (
|
|
624
|
+
type(self) is ActionForecastBlockSpec
|
|
625
|
+
and type(other) is ActionForecastBlockSpec
|
|
626
|
+
and self.block_spec_sha256 == other.block_spec_sha256
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
__hash__ = None
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
633
|
+
class ActionForecastPartitionLayout:
|
|
634
|
+
"""Complete deterministic partition of a sealed option/metric frame."""
|
|
635
|
+
|
|
636
|
+
finite_contract_identity_sha256: str
|
|
637
|
+
option_identity_sha256s: tuple[str, ...]
|
|
638
|
+
metric_ids: tuple[str, ...]
|
|
639
|
+
partition_policy: ActionForecastPartitionPolicyBinding
|
|
640
|
+
blocks: tuple[ActionForecastBlockSpec, ...]
|
|
641
|
+
|
|
642
|
+
def __post_init__(self) -> None:
|
|
643
|
+
require_sha256(
|
|
644
|
+
self.finite_contract_identity_sha256,
|
|
645
|
+
"finite_contract_identity_sha256",
|
|
646
|
+
)
|
|
647
|
+
if type(self.option_identity_sha256s) is not tuple or not (
|
|
648
|
+
self.option_identity_sha256s
|
|
649
|
+
) or any(type(value) is not str for value in self.option_identity_sha256s):
|
|
650
|
+
raise ValueError(
|
|
651
|
+
"option_identity_sha256s must be a non-empty exact tuple"
|
|
652
|
+
)
|
|
653
|
+
for index, value in enumerate(self.option_identity_sha256s):
|
|
654
|
+
require_sha256(value, f"option_identity_sha256s[{index}]")
|
|
655
|
+
if len(set(self.option_identity_sha256s)) != len(
|
|
656
|
+
self.option_identity_sha256s
|
|
657
|
+
):
|
|
658
|
+
raise ValueError("partition layouts cannot repeat an option identity")
|
|
659
|
+
_canonical_metric_ids(self.metric_ids, "metric_ids")
|
|
660
|
+
if type(self.partition_policy) is not ActionForecastPartitionPolicyBinding:
|
|
661
|
+
raise TypeError(
|
|
662
|
+
"partition_policy must be an exact partition-policy binding"
|
|
663
|
+
)
|
|
664
|
+
self.partition_policy.__post_init__()
|
|
665
|
+
if type(self.blocks) is not tuple or not self.blocks or any(
|
|
666
|
+
type(value) is not ActionForecastBlockSpec for value in self.blocks
|
|
667
|
+
):
|
|
668
|
+
raise ValueError("blocks must be a non-empty exact tuple of block specs")
|
|
669
|
+
|
|
670
|
+
cursor = 0
|
|
671
|
+
metric_count = len(self.metric_ids)
|
|
672
|
+
for expected_index, block in enumerate(self.blocks):
|
|
673
|
+
block.__post_init__()
|
|
674
|
+
if block.block_index != expected_index:
|
|
675
|
+
raise ValueError("block indices must be contiguous canonical positions")
|
|
676
|
+
if block.global_row_start != cursor:
|
|
677
|
+
raise ValueError("blocks must provide gap-free, overlap-free coverage")
|
|
678
|
+
if block.global_row_stop > self.row_count:
|
|
679
|
+
raise ValueError("a block extends beyond the global option frame")
|
|
680
|
+
expected_identities = self.option_identity_sha256s[
|
|
681
|
+
block.global_row_start : block.global_row_stop
|
|
682
|
+
]
|
|
683
|
+
if block.option_identity_sha256s != expected_identities:
|
|
684
|
+
raise ValueError("block option identities differ from their global slice")
|
|
685
|
+
if block.row_count > self.partition_policy.max_rows_per_block:
|
|
686
|
+
raise ValueError("a block exceeds max_rows_per_block")
|
|
687
|
+
if (
|
|
688
|
+
block.row_count * metric_count
|
|
689
|
+
> self.partition_policy.max_metric_cells_per_block
|
|
690
|
+
):
|
|
691
|
+
raise ValueError("a block exceeds max_metric_cells_per_block")
|
|
692
|
+
cursor = block.global_row_stop
|
|
693
|
+
if cursor != self.row_count:
|
|
694
|
+
raise ValueError("blocks do not completely cover the global option frame")
|
|
695
|
+
|
|
696
|
+
@property
|
|
697
|
+
def row_count(self) -> int:
|
|
698
|
+
return len(self.option_identity_sha256s)
|
|
699
|
+
|
|
700
|
+
@property
|
|
701
|
+
def block_count(self) -> int:
|
|
702
|
+
return len(self.blocks)
|
|
703
|
+
|
|
704
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
705
|
+
self.__post_init__()
|
|
706
|
+
return {
|
|
707
|
+
"schema_version": 1,
|
|
708
|
+
"finite_contract_identity_sha256": (
|
|
709
|
+
self.finite_contract_identity_sha256
|
|
710
|
+
),
|
|
711
|
+
"option_identity_sha256s": list(self.option_identity_sha256s),
|
|
712
|
+
"metric_ids": list(self.metric_ids),
|
|
713
|
+
"partition_policy": self.partition_policy.to_record(),
|
|
714
|
+
"blocks": [value.to_record() for value in self.blocks],
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
@property
|
|
718
|
+
def layout_sha256(self) -> str:
|
|
719
|
+
return _hash(_PARTITION_LAYOUT_DOMAIN, self._unsigned_record())
|
|
720
|
+
|
|
721
|
+
def to_record(self) -> dict[str, object]:
|
|
722
|
+
return {**self._unsigned_record(), "layout_sha256": self.layout_sha256}
|
|
723
|
+
|
|
724
|
+
def __eq__(self, other: object) -> bool:
|
|
725
|
+
return (
|
|
726
|
+
type(self) is ActionForecastPartitionLayout
|
|
727
|
+
and type(other) is ActionForecastPartitionLayout
|
|
728
|
+
and self.layout_sha256 == other.layout_sha256
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
__hash__ = None
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def validate_action_forecast_partition_layout(
|
|
735
|
+
request: ActionForecastRequest,
|
|
736
|
+
layout: ActionForecastPartitionLayout,
|
|
737
|
+
) -> None:
|
|
738
|
+
"""Bind a reusable partition geometry to one exact forecast request."""
|
|
739
|
+
|
|
740
|
+
if type(request) is not ActionForecastRequest:
|
|
741
|
+
raise TypeError("request must be an exact ActionForecastRequest")
|
|
742
|
+
request.__post_init__()
|
|
743
|
+
if type(layout) is not ActionForecastPartitionLayout:
|
|
744
|
+
raise TypeError("layout must be an exact ActionForecastPartitionLayout")
|
|
745
|
+
layout.__post_init__()
|
|
746
|
+
contract = request.finite_variation_contract
|
|
747
|
+
if layout.finite_contract_identity_sha256 != contract.identity_sha256:
|
|
748
|
+
raise ValueError("partition layout is bound to a different finite contract")
|
|
749
|
+
if layout.option_identity_sha256s != tuple(
|
|
750
|
+
option.identity_sha256 for option in contract.options
|
|
751
|
+
):
|
|
752
|
+
raise ValueError("partition layout option frame differs from the request")
|
|
753
|
+
if layout.metric_ids != request.required_metric_ids:
|
|
754
|
+
raise ValueError("partition layout metric frame differs from the request")
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
758
|
+
class ActionForecastBlockRequest:
|
|
759
|
+
"""One exact provider-neutral block call within a global logical request."""
|
|
760
|
+
|
|
761
|
+
request: ActionForecastRequest
|
|
762
|
+
layout: ActionForecastPartitionLayout
|
|
763
|
+
block: ActionForecastBlockSpec
|
|
764
|
+
block_call_id: LLMCallId
|
|
765
|
+
|
|
766
|
+
def __post_init__(self) -> None:
|
|
767
|
+
if type(self.request) is not ActionForecastRequest:
|
|
768
|
+
raise TypeError("request must be an exact ActionForecastRequest")
|
|
769
|
+
self.request.__post_init__()
|
|
770
|
+
validate_action_forecast_partition_layout(self.request, self.layout)
|
|
771
|
+
if type(self.block) is not ActionForecastBlockSpec:
|
|
772
|
+
raise TypeError("block must be an exact ActionForecastBlockSpec")
|
|
773
|
+
self.block.__post_init__()
|
|
774
|
+
if self.block.block_index >= self.layout.block_count:
|
|
775
|
+
raise ValueError("block index is outside the partition layout")
|
|
776
|
+
if self.block != self.layout.blocks[self.block.block_index]:
|
|
777
|
+
raise ValueError("block differs from its exact partition-layout position")
|
|
778
|
+
if type(self.block_call_id) is not LLMCallId:
|
|
779
|
+
raise TypeError("block_call_id must be an exact LLMCallId")
|
|
780
|
+
LLMCallId.__post_init__(self.block_call_id)
|
|
781
|
+
|
|
782
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
783
|
+
self.__post_init__()
|
|
784
|
+
return {
|
|
785
|
+
"schema_version": 1,
|
|
786
|
+
"request_sha256": self.request.request_sha256,
|
|
787
|
+
"layout_sha256": self.layout.layout_sha256,
|
|
788
|
+
"block_spec_sha256": self.block.block_spec_sha256,
|
|
789
|
+
"block_index": self.block.block_index,
|
|
790
|
+
"block_call_id": self.block_call_id.value,
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
@property
|
|
794
|
+
def block_request_sha256(self) -> str:
|
|
795
|
+
return _hash(_BLOCK_REQUEST_DOMAIN, self._unsigned_record())
|
|
796
|
+
|
|
797
|
+
def to_record(self) -> dict[str, object]:
|
|
798
|
+
return {
|
|
799
|
+
**self._unsigned_record(),
|
|
800
|
+
"block_request_sha256": self.block_request_sha256,
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
def __eq__(self, other: object) -> bool:
|
|
804
|
+
return (
|
|
805
|
+
type(self) is ActionForecastBlockRequest
|
|
806
|
+
and type(other) is ActionForecastBlockRequest
|
|
807
|
+
and self.block_request_sha256 == other.block_request_sha256
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
__hash__ = None
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
@dataclass(frozen=True, slots=True)
|
|
814
|
+
class ResolvedActionEvidenceCitation:
|
|
815
|
+
"""Trusted citation resolved against the exact prompt-visible card view."""
|
|
816
|
+
|
|
817
|
+
card_key: str
|
|
818
|
+
card_source_binding_sha256: str
|
|
819
|
+
action_binding_identity_sha256: str
|
|
820
|
+
contrast_id: str
|
|
821
|
+
source_option_id: str
|
|
822
|
+
source_option_identity_sha256: str
|
|
823
|
+
source_contract_identity_sha256: str
|
|
824
|
+
|
|
825
|
+
def __post_init__(self) -> None:
|
|
826
|
+
if type(self.card_key) is not str or _TOKEN.fullmatch(self.card_key) is None:
|
|
827
|
+
raise ValueError("card_key must use the closed lowercase token grammar")
|
|
828
|
+
if type(self.source_option_id) is not str or _OPTION_ID.fullmatch(
|
|
829
|
+
self.source_option_id
|
|
830
|
+
) is None:
|
|
831
|
+
raise ValueError("source_option_id must use the finite-option grammar")
|
|
832
|
+
for name in (
|
|
833
|
+
"card_source_binding_sha256",
|
|
834
|
+
"action_binding_identity_sha256",
|
|
835
|
+
"contrast_id",
|
|
836
|
+
"source_option_identity_sha256",
|
|
837
|
+
"source_contract_identity_sha256",
|
|
838
|
+
):
|
|
839
|
+
require_sha256(getattr(self, name), name)
|
|
840
|
+
|
|
841
|
+
def to_record(self) -> dict[str, object]:
|
|
842
|
+
self.__post_init__()
|
|
843
|
+
return {
|
|
844
|
+
"card_key": self.card_key,
|
|
845
|
+
"card_source_binding_sha256": self.card_source_binding_sha256,
|
|
846
|
+
"action_binding_identity_sha256": self.action_binding_identity_sha256,
|
|
847
|
+
"contrast_id": self.contrast_id,
|
|
848
|
+
"source_option_id": self.source_option_id,
|
|
849
|
+
"source_option_identity_sha256": self.source_option_identity_sha256,
|
|
850
|
+
"source_contract_identity_sha256": self.source_contract_identity_sha256,
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
@dataclass(frozen=True, slots=True)
|
|
855
|
+
class ResolvedActionMetricForecast:
|
|
856
|
+
metric_id: str
|
|
857
|
+
p10_delta: float
|
|
858
|
+
p50_delta: float
|
|
859
|
+
p90_delta: float
|
|
860
|
+
confidence: float
|
|
861
|
+
citations: tuple[ResolvedActionEvidenceCitation, ...]
|
|
862
|
+
|
|
863
|
+
def __post_init__(self) -> None:
|
|
864
|
+
if type(self.citations) is not tuple or any(
|
|
865
|
+
type(value) is not ResolvedActionEvidenceCitation
|
|
866
|
+
for value in self.citations
|
|
867
|
+
):
|
|
868
|
+
raise TypeError("citations must be an exact tuple of resolved citations")
|
|
869
|
+
for value in self.citations:
|
|
870
|
+
value.__post_init__()
|
|
871
|
+
unresolved = ActionMetricForecast(
|
|
872
|
+
metric_id=self.metric_id,
|
|
873
|
+
p10_delta=self.p10_delta,
|
|
874
|
+
p50_delta=self.p50_delta,
|
|
875
|
+
p90_delta=self.p90_delta,
|
|
876
|
+
confidence=self.confidence,
|
|
877
|
+
citations=tuple(
|
|
878
|
+
ActionEvidenceCitation(
|
|
879
|
+
card_key=value.card_key,
|
|
880
|
+
action_binding_identity_sha256=(
|
|
881
|
+
value.action_binding_identity_sha256
|
|
882
|
+
),
|
|
883
|
+
)
|
|
884
|
+
for value in self.citations
|
|
885
|
+
),
|
|
886
|
+
)
|
|
887
|
+
del unresolved
|
|
888
|
+
|
|
889
|
+
def to_record(self) -> dict[str, object]:
|
|
890
|
+
self.__post_init__()
|
|
891
|
+
return {
|
|
892
|
+
"metric_id": self.metric_id,
|
|
893
|
+
"p10_delta_hex": self.p10_delta.hex(),
|
|
894
|
+
"p50_delta_hex": self.p50_delta.hex(),
|
|
895
|
+
"p90_delta_hex": self.p90_delta.hex(),
|
|
896
|
+
"confidence_hex": self.confidence.hex(),
|
|
897
|
+
"citations": [value.to_record() for value in self.citations],
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
@dataclass(frozen=True, slots=True)
|
|
902
|
+
class ResolvedActionForecast:
|
|
903
|
+
option_id: str
|
|
904
|
+
option_identity_sha256: str
|
|
905
|
+
child_configuration_sha256: str
|
|
906
|
+
family: str
|
|
907
|
+
probability_valid: float
|
|
908
|
+
metric_forecasts: tuple[ResolvedActionMetricForecast, ...]
|
|
909
|
+
|
|
910
|
+
def __post_init__(self) -> None:
|
|
911
|
+
if type(self.option_id) is not str or _OPTION_ID.fullmatch(self.option_id) is None:
|
|
912
|
+
raise ValueError("option_id must use the finite-option grammar")
|
|
913
|
+
if type(self.family) is not str or _TOKEN.fullmatch(self.family) is None:
|
|
914
|
+
raise ValueError("family must use the closed token grammar")
|
|
915
|
+
require_sha256(self.option_identity_sha256, "option_identity_sha256")
|
|
916
|
+
require_sha256(
|
|
917
|
+
self.child_configuration_sha256,
|
|
918
|
+
"child_configuration_sha256",
|
|
919
|
+
)
|
|
920
|
+
_finite_float(self.probability_valid, "probability_valid")
|
|
921
|
+
if not 0.0 <= self.probability_valid <= 1.0:
|
|
922
|
+
raise ValueError("probability_valid must lie in [0,1]")
|
|
923
|
+
if type(self.metric_forecasts) is not tuple or not self.metric_forecasts or any(
|
|
924
|
+
type(value) is not ResolvedActionMetricForecast
|
|
925
|
+
for value in self.metric_forecasts
|
|
926
|
+
):
|
|
927
|
+
raise ValueError("metric_forecasts must contain resolved values")
|
|
928
|
+
for value in self.metric_forecasts:
|
|
929
|
+
value.__post_init__()
|
|
930
|
+
_canonical_metric_ids(
|
|
931
|
+
tuple(value.metric_id for value in self.metric_forecasts),
|
|
932
|
+
"metric_forecasts",
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
def to_record(self) -> dict[str, object]:
|
|
936
|
+
self.__post_init__()
|
|
937
|
+
return {
|
|
938
|
+
"option_id": self.option_id,
|
|
939
|
+
"option_identity_sha256": self.option_identity_sha256,
|
|
940
|
+
"child_configuration_sha256": self.child_configuration_sha256,
|
|
941
|
+
"family": self.family,
|
|
942
|
+
"probability_valid_hex": self.probability_valid.hex(),
|
|
943
|
+
"metric_forecasts": [
|
|
944
|
+
value.to_record() for value in self.metric_forecasts
|
|
945
|
+
],
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
|
|
949
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
950
|
+
class ResolvedActionForecastBlock:
|
|
951
|
+
"""Trusted partial forecast receipt for one exact partition block."""
|
|
952
|
+
|
|
953
|
+
request_sha256: str
|
|
954
|
+
block_request_sha256: str
|
|
955
|
+
layout_sha256: str
|
|
956
|
+
block_spec_sha256: str
|
|
957
|
+
block_index: int
|
|
958
|
+
forecasts: tuple[ResolvedActionForecast, ...]
|
|
959
|
+
policy_id: str
|
|
960
|
+
policy_version: int
|
|
961
|
+
policy_definition_sha256: str
|
|
962
|
+
|
|
963
|
+
def __post_init__(self) -> None:
|
|
964
|
+
for name in (
|
|
965
|
+
"request_sha256",
|
|
966
|
+
"block_request_sha256",
|
|
967
|
+
"layout_sha256",
|
|
968
|
+
"block_spec_sha256",
|
|
969
|
+
"policy_definition_sha256",
|
|
970
|
+
):
|
|
971
|
+
require_sha256(getattr(self, name), name)
|
|
972
|
+
if type(self.block_index) is not int or self.block_index < 0:
|
|
973
|
+
raise ValueError("block_index must be a non-negative exact integer")
|
|
974
|
+
if type(self.forecasts) is not tuple or not self.forecasts or any(
|
|
975
|
+
type(value) is not ResolvedActionForecast for value in self.forecasts
|
|
976
|
+
):
|
|
977
|
+
raise ValueError("forecasts must be a non-empty exact resolved tuple")
|
|
978
|
+
for value in self.forecasts:
|
|
979
|
+
value.__post_init__()
|
|
980
|
+
option_ids = tuple(value.option_id for value in self.forecasts)
|
|
981
|
+
if len(set(option_ids)) != len(option_ids):
|
|
982
|
+
raise ValueError("a resolved block cannot repeat an option")
|
|
983
|
+
if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
|
|
984
|
+
raise ValueError("policy_id must use the closed token grammar")
|
|
985
|
+
if type(self.policy_version) is not int or self.policy_version <= 0:
|
|
986
|
+
raise ValueError("policy_version must be a positive exact integer")
|
|
987
|
+
|
|
988
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
989
|
+
self.__post_init__()
|
|
990
|
+
return {
|
|
991
|
+
"schema_version": 1,
|
|
992
|
+
"request_sha256": self.request_sha256,
|
|
993
|
+
"block_request_sha256": self.block_request_sha256,
|
|
994
|
+
"layout_sha256": self.layout_sha256,
|
|
995
|
+
"block_spec_sha256": self.block_spec_sha256,
|
|
996
|
+
"block_index": self.block_index,
|
|
997
|
+
"forecasts": [value.to_record() for value in self.forecasts],
|
|
998
|
+
"policy_id": self.policy_id,
|
|
999
|
+
"policy_version": self.policy_version,
|
|
1000
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
@property
|
|
1004
|
+
def receipt_sha256(self) -> str:
|
|
1005
|
+
return _hash(_RESOLVED_BLOCK_DOMAIN, self._unsigned_record())
|
|
1006
|
+
|
|
1007
|
+
def to_record(self) -> dict[str, object]:
|
|
1008
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
1009
|
+
|
|
1010
|
+
def __eq__(self, other: object) -> bool:
|
|
1011
|
+
return (
|
|
1012
|
+
type(self) is ResolvedActionForecastBlock
|
|
1013
|
+
and type(other) is ResolvedActionForecastBlock
|
|
1014
|
+
and self.receipt_sha256 == other.receipt_sha256
|
|
1015
|
+
)
|
|
1016
|
+
|
|
1017
|
+
__hash__ = None
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
1021
|
+
class ResolvedActionForecastBatch:
|
|
1022
|
+
"""Complete all-option forecast receipt bound to one exact request."""
|
|
1023
|
+
|
|
1024
|
+
request_sha256: str
|
|
1025
|
+
context_sha256: str
|
|
1026
|
+
optimization_semantics_definition_sha256: str
|
|
1027
|
+
action_semantics_definition_sha256: str
|
|
1028
|
+
finite_contract_identity_sha256: str
|
|
1029
|
+
card_snapshot_sha256: str
|
|
1030
|
+
forecasts: tuple[ResolvedActionForecast, ...]
|
|
1031
|
+
policy_id: str
|
|
1032
|
+
policy_version: int
|
|
1033
|
+
policy_definition_sha256: str
|
|
1034
|
+
|
|
1035
|
+
def __post_init__(self) -> None:
|
|
1036
|
+
for name in (
|
|
1037
|
+
"request_sha256",
|
|
1038
|
+
"context_sha256",
|
|
1039
|
+
"optimization_semantics_definition_sha256",
|
|
1040
|
+
"action_semantics_definition_sha256",
|
|
1041
|
+
"finite_contract_identity_sha256",
|
|
1042
|
+
"card_snapshot_sha256",
|
|
1043
|
+
"policy_definition_sha256",
|
|
1044
|
+
):
|
|
1045
|
+
require_sha256(getattr(self, name), name)
|
|
1046
|
+
if type(self.forecasts) is not tuple or not self.forecasts or any(
|
|
1047
|
+
type(value) is not ResolvedActionForecast for value in self.forecasts
|
|
1048
|
+
):
|
|
1049
|
+
raise ValueError("forecasts must be a non-empty exact resolved tuple")
|
|
1050
|
+
for value in self.forecasts:
|
|
1051
|
+
value.__post_init__()
|
|
1052
|
+
option_ids = tuple(value.option_id for value in self.forecasts)
|
|
1053
|
+
if len(set(option_ids)) != len(option_ids):
|
|
1054
|
+
raise ValueError("resolved forecasts cannot repeat an option")
|
|
1055
|
+
if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
|
|
1056
|
+
raise ValueError("policy_id must use the closed token grammar")
|
|
1057
|
+
if type(self.policy_version) is not int or self.policy_version <= 0:
|
|
1058
|
+
raise ValueError("policy_version must be a positive exact integer")
|
|
1059
|
+
|
|
1060
|
+
def _unsigned_record(self) -> dict[str, object]:
|
|
1061
|
+
self.__post_init__()
|
|
1062
|
+
return {
|
|
1063
|
+
"schema_version": 2,
|
|
1064
|
+
"request_sha256": self.request_sha256,
|
|
1065
|
+
"context_sha256": self.context_sha256,
|
|
1066
|
+
"optimization_semantics_definition_sha256": (
|
|
1067
|
+
self.optimization_semantics_definition_sha256
|
|
1068
|
+
),
|
|
1069
|
+
"action_semantics_definition_sha256": (
|
|
1070
|
+
self.action_semantics_definition_sha256
|
|
1071
|
+
),
|
|
1072
|
+
"finite_contract_identity_sha256": self.finite_contract_identity_sha256,
|
|
1073
|
+
"card_snapshot_sha256": self.card_snapshot_sha256,
|
|
1074
|
+
"forecasts": [value.to_record() for value in self.forecasts],
|
|
1075
|
+
"policy_id": self.policy_id,
|
|
1076
|
+
"policy_version": self.policy_version,
|
|
1077
|
+
"policy_definition_sha256": self.policy_definition_sha256,
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
@property
|
|
1081
|
+
def receipt_sha256(self) -> str:
|
|
1082
|
+
return _hash(_BATCH_DOMAIN, self._unsigned_record())
|
|
1083
|
+
|
|
1084
|
+
def to_record(self) -> dict[str, object]:
|
|
1085
|
+
return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
|
|
1086
|
+
|
|
1087
|
+
def __eq__(self, other: object) -> bool:
|
|
1088
|
+
return (
|
|
1089
|
+
type(self) is ResolvedActionForecastBatch
|
|
1090
|
+
and type(other) is ResolvedActionForecastBatch
|
|
1091
|
+
and self.receipt_sha256 == other.receipt_sha256
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
__hash__ = None
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
def _resolved_citation_index(
|
|
1098
|
+
request: ActionForecastRequest,
|
|
1099
|
+
) -> dict[tuple[str, str], ResolvedActionEvidenceCitation]:
|
|
1100
|
+
"""Resolve each immutable prompt-visible citation exactly once.
|
|
1101
|
+
|
|
1102
|
+
Callers validate the complete request before constructing this index. The
|
|
1103
|
+
request and every nested card/binding are frozen values, so rebuilding and
|
|
1104
|
+
revalidating the same provenance graph for every option/metric citation
|
|
1105
|
+
adds no protection. Keeping the exact card key in the lookup key preserves
|
|
1106
|
+
the original rejection of cross-card and ambiguous binding references.
|
|
1107
|
+
"""
|
|
1108
|
+
|
|
1109
|
+
index: dict[tuple[str, str], ResolvedActionEvidenceCitation] = {}
|
|
1110
|
+
for card in request.cards:
|
|
1111
|
+
if card.source_binding is None:
|
|
1112
|
+
raise ValueError("forecast request contains an unbound evidence card")
|
|
1113
|
+
for binding in _prompt_action_bindings(card):
|
|
1114
|
+
key = (card.card_key, binding.identity_sha256)
|
|
1115
|
+
if key in index:
|
|
1116
|
+
raise ValueError(
|
|
1117
|
+
"forecast request contains an ambiguous prompt action binding"
|
|
1118
|
+
)
|
|
1119
|
+
index[key] = ResolvedActionEvidenceCitation(
|
|
1120
|
+
card_key=card.card_key,
|
|
1121
|
+
card_source_binding_sha256=card.source_binding.binding_sha256,
|
|
1122
|
+
action_binding_identity_sha256=binding.identity_sha256,
|
|
1123
|
+
contrast_id=binding.contrast_id,
|
|
1124
|
+
source_option_id=binding.option_id,
|
|
1125
|
+
source_option_identity_sha256=binding.option_identity_sha256,
|
|
1126
|
+
source_contract_identity_sha256=binding.contract_identity_sha256,
|
|
1127
|
+
)
|
|
1128
|
+
return index
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
def _resolve_indexed_citation(
|
|
1132
|
+
citation_index: dict[tuple[str, str], ResolvedActionEvidenceCitation],
|
|
1133
|
+
*,
|
|
1134
|
+
prompt_card_keys: frozenset[str],
|
|
1135
|
+
citation: ActionEvidenceCitation,
|
|
1136
|
+
) -> ResolvedActionEvidenceCitation:
|
|
1137
|
+
if citation.card_key not in prompt_card_keys:
|
|
1138
|
+
raise ValueError("forecast cites a card outside the request snapshot")
|
|
1139
|
+
resolved = citation_index.get(
|
|
1140
|
+
(citation.card_key, citation.action_binding_identity_sha256)
|
|
1141
|
+
)
|
|
1142
|
+
if resolved is None:
|
|
1143
|
+
raise ValueError("forecast cites an absent or ambiguous prompt action binding")
|
|
1144
|
+
return resolved
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
def resolve_action_forecast_block(
|
|
1148
|
+
block_request: ActionForecastBlockRequest,
|
|
1149
|
+
drafts: tuple[ActionForecastDraft, ...],
|
|
1150
|
+
*,
|
|
1151
|
+
policy_id: str,
|
|
1152
|
+
policy_version: int,
|
|
1153
|
+
policy_definition_sha256: str,
|
|
1154
|
+
) -> ResolvedActionForecastBlock:
|
|
1155
|
+
"""Resolve exactly one block without representing it as a complete batch."""
|
|
1156
|
+
|
|
1157
|
+
if type(block_request) is not ActionForecastBlockRequest:
|
|
1158
|
+
raise TypeError("block_request must be an exact ActionForecastBlockRequest")
|
|
1159
|
+
block_request.__post_init__()
|
|
1160
|
+
request = block_request.request
|
|
1161
|
+
block_spec = block_request.block
|
|
1162
|
+
options = request.finite_variation_contract.options[
|
|
1163
|
+
block_spec.global_row_start : block_spec.global_row_stop
|
|
1164
|
+
]
|
|
1165
|
+
if type(drafts) is not tuple or any(
|
|
1166
|
+
type(value) is not ActionForecastDraft for value in drafts
|
|
1167
|
+
):
|
|
1168
|
+
raise TypeError("drafts must be an exact tuple of ActionForecastDraft")
|
|
1169
|
+
for value in drafts:
|
|
1170
|
+
value.__post_init__()
|
|
1171
|
+
if tuple(value.option_id for value in drafts) != tuple(
|
|
1172
|
+
option.option_id for option in options
|
|
1173
|
+
):
|
|
1174
|
+
raise ValueError(
|
|
1175
|
+
"block forecast order/coverage differs from its global option slice"
|
|
1176
|
+
)
|
|
1177
|
+
|
|
1178
|
+
required_metric_ids = request.required_metric_ids
|
|
1179
|
+
citation_index = _resolved_citation_index(request)
|
|
1180
|
+
prompt_card_keys = frozenset(card.card_key for card in request.cards)
|
|
1181
|
+
resolved: list[ResolvedActionForecast] = []
|
|
1182
|
+
for option, draft in zip(options, drafts, strict=True):
|
|
1183
|
+
if tuple(value.metric_id for value in draft.metric_forecasts) != (
|
|
1184
|
+
required_metric_ids
|
|
1185
|
+
):
|
|
1186
|
+
raise ValueError("every block row must forecast every required metric")
|
|
1187
|
+
for metric in draft.metric_forecasts:
|
|
1188
|
+
if request.evidence_mode is ActionForecastEvidenceMode.GROUNDED:
|
|
1189
|
+
if not metric.citations:
|
|
1190
|
+
raise ValueError(
|
|
1191
|
+
"grounded block forecasts require citations for every metric"
|
|
1192
|
+
)
|
|
1193
|
+
elif metric.citations:
|
|
1194
|
+
raise ValueError("catalog-only block forecasts forbid citations")
|
|
1195
|
+
resolved.append(
|
|
1196
|
+
ResolvedActionForecast(
|
|
1197
|
+
option_id=option.option_id,
|
|
1198
|
+
option_identity_sha256=option.identity_sha256,
|
|
1199
|
+
child_configuration_sha256=option.child_configuration_sha256,
|
|
1200
|
+
family=option.family,
|
|
1201
|
+
probability_valid=draft.probability_valid,
|
|
1202
|
+
metric_forecasts=tuple(
|
|
1203
|
+
ResolvedActionMetricForecast(
|
|
1204
|
+
metric_id=metric.metric_id,
|
|
1205
|
+
p10_delta=metric.p10_delta,
|
|
1206
|
+
p50_delta=metric.p50_delta,
|
|
1207
|
+
p90_delta=metric.p90_delta,
|
|
1208
|
+
confidence=metric.confidence,
|
|
1209
|
+
citations=tuple(
|
|
1210
|
+
_resolve_indexed_citation(
|
|
1211
|
+
citation_index,
|
|
1212
|
+
prompt_card_keys=prompt_card_keys,
|
|
1213
|
+
citation=citation,
|
|
1214
|
+
)
|
|
1215
|
+
for citation in metric.citations
|
|
1216
|
+
),
|
|
1217
|
+
)
|
|
1218
|
+
for metric in draft.metric_forecasts
|
|
1219
|
+
),
|
|
1220
|
+
)
|
|
1221
|
+
)
|
|
1222
|
+
result = ResolvedActionForecastBlock(
|
|
1223
|
+
request_sha256=request.request_sha256,
|
|
1224
|
+
block_request_sha256=block_request.block_request_sha256,
|
|
1225
|
+
layout_sha256=block_request.layout.layout_sha256,
|
|
1226
|
+
block_spec_sha256=block_spec.block_spec_sha256,
|
|
1227
|
+
block_index=block_spec.block_index,
|
|
1228
|
+
forecasts=tuple(resolved),
|
|
1229
|
+
policy_id=policy_id,
|
|
1230
|
+
policy_version=policy_version,
|
|
1231
|
+
policy_definition_sha256=policy_definition_sha256,
|
|
1232
|
+
)
|
|
1233
|
+
validate_resolved_action_forecast_block(block_request, result)
|
|
1234
|
+
return result
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def validate_resolved_action_forecast_block(
|
|
1238
|
+
block_request: ActionForecastBlockRequest,
|
|
1239
|
+
block: ResolvedActionForecastBlock,
|
|
1240
|
+
) -> None:
|
|
1241
|
+
"""Revalidate one resolved partial receipt against its global positions."""
|
|
1242
|
+
|
|
1243
|
+
if type(block_request) is not ActionForecastBlockRequest:
|
|
1244
|
+
raise TypeError("block_request must be an exact ActionForecastBlockRequest")
|
|
1245
|
+
block_request.__post_init__()
|
|
1246
|
+
if type(block) is not ResolvedActionForecastBlock:
|
|
1247
|
+
raise TypeError("block must be an exact ResolvedActionForecastBlock")
|
|
1248
|
+
block.__post_init__()
|
|
1249
|
+
request = block_request.request
|
|
1250
|
+
spec = block_request.block
|
|
1251
|
+
if (
|
|
1252
|
+
block.request_sha256 != request.request_sha256
|
|
1253
|
+
or block.block_request_sha256 != block_request.block_request_sha256
|
|
1254
|
+
or block.layout_sha256 != block_request.layout.layout_sha256
|
|
1255
|
+
or block.block_spec_sha256 != spec.block_spec_sha256
|
|
1256
|
+
or block.block_index != spec.block_index
|
|
1257
|
+
):
|
|
1258
|
+
raise ValueError("resolved block is bound to a different block request")
|
|
1259
|
+
options = request.finite_variation_contract.options[
|
|
1260
|
+
spec.global_row_start : spec.global_row_stop
|
|
1261
|
+
]
|
|
1262
|
+
if tuple(value.option_id for value in block.forecasts) != tuple(
|
|
1263
|
+
option.option_id for option in options
|
|
1264
|
+
):
|
|
1265
|
+
raise ValueError("resolved block order/coverage differs from its global slice")
|
|
1266
|
+
|
|
1267
|
+
required_metric_ids = request.required_metric_ids
|
|
1268
|
+
citation_index = _resolved_citation_index(request)
|
|
1269
|
+
prompt_card_keys = frozenset(card.card_key for card in request.cards)
|
|
1270
|
+
for option, forecast in zip(options, block.forecasts, strict=True):
|
|
1271
|
+
if (
|
|
1272
|
+
forecast.option_identity_sha256 != option.identity_sha256
|
|
1273
|
+
or forecast.child_configuration_sha256
|
|
1274
|
+
!= option.child_configuration_sha256
|
|
1275
|
+
or forecast.family != option.family
|
|
1276
|
+
):
|
|
1277
|
+
raise ValueError("resolved block differs from its sealed finite option")
|
|
1278
|
+
if tuple(value.metric_id for value in forecast.metric_forecasts) != (
|
|
1279
|
+
required_metric_ids
|
|
1280
|
+
):
|
|
1281
|
+
raise ValueError("resolved block metric coverage differs from request")
|
|
1282
|
+
for metric in forecast.metric_forecasts:
|
|
1283
|
+
if request.evidence_mode is ActionForecastEvidenceMode.GROUNDED:
|
|
1284
|
+
if not metric.citations:
|
|
1285
|
+
raise ValueError(
|
|
1286
|
+
"grounded resolved blocks require citations for every metric"
|
|
1287
|
+
)
|
|
1288
|
+
elif metric.citations:
|
|
1289
|
+
raise ValueError("catalog-only resolved blocks forbid citations")
|
|
1290
|
+
for citation in metric.citations:
|
|
1291
|
+
unresolved = ActionEvidenceCitation(
|
|
1292
|
+
card_key=citation.card_key,
|
|
1293
|
+
action_binding_identity_sha256=(
|
|
1294
|
+
citation.action_binding_identity_sha256
|
|
1295
|
+
),
|
|
1296
|
+
)
|
|
1297
|
+
expected = _resolve_indexed_citation(
|
|
1298
|
+
citation_index,
|
|
1299
|
+
prompt_card_keys=prompt_card_keys,
|
|
1300
|
+
citation=unresolved,
|
|
1301
|
+
)
|
|
1302
|
+
if citation != expected:
|
|
1303
|
+
raise ValueError(
|
|
1304
|
+
"resolved block citation differs from prompt card evidence"
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
def resolve_action_forecasts(
|
|
1309
|
+
request: ActionForecastRequest,
|
|
1310
|
+
drafts: tuple[ActionForecastDraft, ...],
|
|
1311
|
+
*,
|
|
1312
|
+
policy_id: str,
|
|
1313
|
+
policy_version: int,
|
|
1314
|
+
policy_definition_sha256: str,
|
|
1315
|
+
) -> ResolvedActionForecastBatch:
|
|
1316
|
+
"""Resolve one complete all-option output or publish no forecast receipt."""
|
|
1317
|
+
|
|
1318
|
+
if type(request) is not ActionForecastRequest:
|
|
1319
|
+
raise TypeError("request must be an exact ActionForecastRequest")
|
|
1320
|
+
request.__post_init__()
|
|
1321
|
+
required_metric_ids = tuple(
|
|
1322
|
+
value.metric_id for value in request.parent_metric_values
|
|
1323
|
+
)
|
|
1324
|
+
request_sha256 = request.request_sha256
|
|
1325
|
+
context_sha256 = typed_json_sha256(request.context)
|
|
1326
|
+
contract = request.finite_variation_contract
|
|
1327
|
+
contract_identity_sha256 = contract.identity_sha256
|
|
1328
|
+
card_snapshot_sha256 = portfolio_card_snapshot_sha256(request.cards)
|
|
1329
|
+
if type(drafts) is not tuple or any(
|
|
1330
|
+
type(value) is not ActionForecastDraft for value in drafts
|
|
1331
|
+
):
|
|
1332
|
+
raise TypeError("drafts must be an exact tuple of ActionForecastDraft")
|
|
1333
|
+
for value in drafts:
|
|
1334
|
+
value.__post_init__()
|
|
1335
|
+
option_ids = tuple(value.option_id for value in drafts)
|
|
1336
|
+
if len(set(option_ids)) != len(option_ids):
|
|
1337
|
+
raise ValueError("forecast output repeats a finite option")
|
|
1338
|
+
expected = {option.option_id for option in contract.options}
|
|
1339
|
+
foreign = set(option_ids) - expected
|
|
1340
|
+
if foreign:
|
|
1341
|
+
raise ValueError("forecast output contains a foreign finite option")
|
|
1342
|
+
missing = expected - set(option_ids)
|
|
1343
|
+
if missing:
|
|
1344
|
+
raise ValueError("forecast output is incomplete for the finite contract")
|
|
1345
|
+
by_option = {value.option_id: value for value in drafts}
|
|
1346
|
+
citation_index = _resolved_citation_index(request)
|
|
1347
|
+
prompt_card_keys = frozenset(card.card_key for card in request.cards)
|
|
1348
|
+
resolved: list[ResolvedActionForecast] = []
|
|
1349
|
+
for option in contract.options:
|
|
1350
|
+
draft = by_option[option.option_id]
|
|
1351
|
+
if tuple(value.metric_id for value in draft.metric_forecasts) != (
|
|
1352
|
+
required_metric_ids
|
|
1353
|
+
):
|
|
1354
|
+
raise ValueError("every option must forecast every required metric exactly")
|
|
1355
|
+
for metric in draft.metric_forecasts:
|
|
1356
|
+
if request.evidence_mode is ActionForecastEvidenceMode.GROUNDED:
|
|
1357
|
+
if not metric.citations:
|
|
1358
|
+
raise ValueError(
|
|
1359
|
+
"grounded forecasts require card/action citations for every metric"
|
|
1360
|
+
)
|
|
1361
|
+
elif metric.citations:
|
|
1362
|
+
raise ValueError("catalog-only forecasts forbid evidence citations")
|
|
1363
|
+
resolved.append(
|
|
1364
|
+
ResolvedActionForecast(
|
|
1365
|
+
option_id=option.option_id,
|
|
1366
|
+
option_identity_sha256=option.identity_sha256,
|
|
1367
|
+
child_configuration_sha256=option.child_configuration_sha256,
|
|
1368
|
+
family=option.family,
|
|
1369
|
+
probability_valid=draft.probability_valid,
|
|
1370
|
+
metric_forecasts=tuple(
|
|
1371
|
+
ResolvedActionMetricForecast(
|
|
1372
|
+
metric_id=metric.metric_id,
|
|
1373
|
+
p10_delta=metric.p10_delta,
|
|
1374
|
+
p50_delta=metric.p50_delta,
|
|
1375
|
+
p90_delta=metric.p90_delta,
|
|
1376
|
+
confidence=metric.confidence,
|
|
1377
|
+
citations=tuple(
|
|
1378
|
+
_resolve_indexed_citation(
|
|
1379
|
+
citation_index,
|
|
1380
|
+
prompt_card_keys=prompt_card_keys,
|
|
1381
|
+
citation=citation,
|
|
1382
|
+
)
|
|
1383
|
+
for citation in metric.citations
|
|
1384
|
+
),
|
|
1385
|
+
)
|
|
1386
|
+
for metric in draft.metric_forecasts
|
|
1387
|
+
),
|
|
1388
|
+
)
|
|
1389
|
+
)
|
|
1390
|
+
batch = ResolvedActionForecastBatch(
|
|
1391
|
+
request_sha256=request_sha256,
|
|
1392
|
+
context_sha256=context_sha256,
|
|
1393
|
+
optimization_semantics_definition_sha256=(
|
|
1394
|
+
request.optimization_semantics.definition_sha256
|
|
1395
|
+
),
|
|
1396
|
+
action_semantics_definition_sha256=(
|
|
1397
|
+
request.action_semantics.definition_sha256
|
|
1398
|
+
),
|
|
1399
|
+
finite_contract_identity_sha256=contract_identity_sha256,
|
|
1400
|
+
card_snapshot_sha256=card_snapshot_sha256,
|
|
1401
|
+
forecasts=tuple(resolved),
|
|
1402
|
+
policy_id=policy_id,
|
|
1403
|
+
policy_version=policy_version,
|
|
1404
|
+
policy_definition_sha256=policy_definition_sha256,
|
|
1405
|
+
)
|
|
1406
|
+
validate_resolved_action_forecasts(request, batch)
|
|
1407
|
+
return batch
|
|
1408
|
+
|
|
1409
|
+
|
|
1410
|
+
def validate_resolved_action_forecasts(
|
|
1411
|
+
request: ActionForecastRequest,
|
|
1412
|
+
batch: ResolvedActionForecastBatch,
|
|
1413
|
+
) -> None:
|
|
1414
|
+
"""Revalidate a forecast receipt against its exact trusted request."""
|
|
1415
|
+
|
|
1416
|
+
if type(request) is not ActionForecastRequest:
|
|
1417
|
+
raise TypeError("request must be an exact ActionForecastRequest")
|
|
1418
|
+
request.__post_init__()
|
|
1419
|
+
required_metric_ids = tuple(
|
|
1420
|
+
value.metric_id for value in request.parent_metric_values
|
|
1421
|
+
)
|
|
1422
|
+
request_sha256 = request.request_sha256
|
|
1423
|
+
context_sha256 = typed_json_sha256(request.context)
|
|
1424
|
+
contract = request.finite_variation_contract
|
|
1425
|
+
contract_identity_sha256 = contract.identity_sha256
|
|
1426
|
+
card_snapshot_sha256 = portfolio_card_snapshot_sha256(request.cards)
|
|
1427
|
+
options_by_id = {option.option_id: option for option in contract.options}
|
|
1428
|
+
if type(batch) is not ResolvedActionForecastBatch:
|
|
1429
|
+
raise TypeError("batch must be an exact ResolvedActionForecastBatch")
|
|
1430
|
+
batch.__post_init__()
|
|
1431
|
+
if (
|
|
1432
|
+
batch.request_sha256 != request_sha256
|
|
1433
|
+
or batch.context_sha256 != context_sha256
|
|
1434
|
+
or batch.optimization_semantics_definition_sha256
|
|
1435
|
+
!= request.optimization_semantics.definition_sha256
|
|
1436
|
+
or batch.action_semantics_definition_sha256
|
|
1437
|
+
!= request.action_semantics.definition_sha256
|
|
1438
|
+
or batch.finite_contract_identity_sha256
|
|
1439
|
+
!= contract_identity_sha256
|
|
1440
|
+
or batch.card_snapshot_sha256 != card_snapshot_sha256
|
|
1441
|
+
):
|
|
1442
|
+
raise ValueError("forecast batch is bound to a different request snapshot")
|
|
1443
|
+
if tuple(value.option_id for value in batch.forecasts) != tuple(
|
|
1444
|
+
option.option_id for option in contract.options
|
|
1445
|
+
):
|
|
1446
|
+
raise ValueError("resolved forecast order/coverage differs from the contract")
|
|
1447
|
+
citation_index = _resolved_citation_index(request)
|
|
1448
|
+
prompt_card_keys = frozenset(card.card_key for card in request.cards)
|
|
1449
|
+
for forecast in batch.forecasts:
|
|
1450
|
+
option = options_by_id.get(forecast.option_id)
|
|
1451
|
+
if option is None:
|
|
1452
|
+
raise ValueError("resolved forecast option is outside the sealed contract")
|
|
1453
|
+
if (
|
|
1454
|
+
forecast.option_identity_sha256 != option.identity_sha256
|
|
1455
|
+
or forecast.child_configuration_sha256
|
|
1456
|
+
!= option.child_configuration_sha256
|
|
1457
|
+
or forecast.family != option.family
|
|
1458
|
+
):
|
|
1459
|
+
raise ValueError("resolved forecast differs from its sealed finite option")
|
|
1460
|
+
if tuple(value.metric_id for value in forecast.metric_forecasts) != (
|
|
1461
|
+
required_metric_ids
|
|
1462
|
+
):
|
|
1463
|
+
raise ValueError("resolved forecast metric coverage differs from request")
|
|
1464
|
+
for metric in forecast.metric_forecasts:
|
|
1465
|
+
if request.evidence_mode is ActionForecastEvidenceMode.GROUNDED:
|
|
1466
|
+
if not metric.citations:
|
|
1467
|
+
raise ValueError(
|
|
1468
|
+
"grounded resolved forecasts require card/action "
|
|
1469
|
+
"citations for every metric"
|
|
1470
|
+
)
|
|
1471
|
+
elif metric.citations:
|
|
1472
|
+
raise ValueError(
|
|
1473
|
+
"catalog-only resolved forecasts forbid evidence citations"
|
|
1474
|
+
)
|
|
1475
|
+
for citation in metric.citations:
|
|
1476
|
+
unresolved = ActionEvidenceCitation(
|
|
1477
|
+
card_key=citation.card_key,
|
|
1478
|
+
action_binding_identity_sha256=(
|
|
1479
|
+
citation.action_binding_identity_sha256
|
|
1480
|
+
),
|
|
1481
|
+
)
|
|
1482
|
+
expected = _resolve_indexed_citation(
|
|
1483
|
+
citation_index,
|
|
1484
|
+
prompt_card_keys=prompt_card_keys,
|
|
1485
|
+
citation=unresolved,
|
|
1486
|
+
)
|
|
1487
|
+
if citation != expected:
|
|
1488
|
+
raise ValueError("resolved citation differs from prompt card evidence")
|
|
1489
|
+
|
|
1490
|
+
|
|
1491
|
+
@dataclass(frozen=True, slots=True)
|
|
1492
|
+
class ActionForecastResult:
|
|
1493
|
+
forecasts: ResolvedActionForecastBatch
|
|
1494
|
+
telemetry: AgenticCallTelemetry | None
|
|
1495
|
+
|
|
1496
|
+
def __post_init__(self) -> None:
|
|
1497
|
+
if type(self.forecasts) is not ResolvedActionForecastBatch:
|
|
1498
|
+
raise TypeError("forecasts must be an exact ResolvedActionForecastBatch")
|
|
1499
|
+
self.forecasts.__post_init__()
|
|
1500
|
+
if self.telemetry is not None:
|
|
1501
|
+
if type(self.telemetry) is not AgenticCallTelemetry:
|
|
1502
|
+
raise TypeError("telemetry must be exact AgenticCallTelemetry or None")
|
|
1503
|
+
self.telemetry.__post_init__()
|
|
1504
|
+
|
|
1505
|
+
|
|
1506
|
+
@dataclass(frozen=True, slots=True)
|
|
1507
|
+
class ActionForecastBlockResult:
|
|
1508
|
+
"""One physical block execution and its optional provider telemetry."""
|
|
1509
|
+
|
|
1510
|
+
forecasts: ResolvedActionForecastBlock
|
|
1511
|
+
telemetry: AgenticCallTelemetry | None
|
|
1512
|
+
|
|
1513
|
+
def __post_init__(self) -> None:
|
|
1514
|
+
if type(self.forecasts) is not ResolvedActionForecastBlock:
|
|
1515
|
+
raise TypeError(
|
|
1516
|
+
"forecasts must be an exact ResolvedActionForecastBlock"
|
|
1517
|
+
)
|
|
1518
|
+
self.forecasts.__post_init__()
|
|
1519
|
+
if self.telemetry is not None:
|
|
1520
|
+
if type(self.telemetry) is not AgenticCallTelemetry:
|
|
1521
|
+
raise TypeError("telemetry must be exact AgenticCallTelemetry or None")
|
|
1522
|
+
self.telemetry.__post_init__()
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
@runtime_checkable
|
|
1526
|
+
class ActionForecastPolicy(Protocol):
|
|
1527
|
+
"""Forecast every sealed option; partial provider results cannot escape."""
|
|
1528
|
+
|
|
1529
|
+
async def forecast(self, request: ActionForecastRequest) -> ActionForecastResult: ...
|
|
1530
|
+
|
|
1531
|
+
|
|
1532
|
+
@runtime_checkable
|
|
1533
|
+
class ActionForecastBlockPolicy(Protocol):
|
|
1534
|
+
"""Forecast one sealed block; the application owns global reassembly."""
|
|
1535
|
+
|
|
1536
|
+
async def forecast_block(
|
|
1537
|
+
self,
|
|
1538
|
+
request: ActionForecastBlockRequest,
|
|
1539
|
+
) -> ActionForecastBlockResult: ...
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
__all__ = [
|
|
1543
|
+
"ActionEvidenceCitation",
|
|
1544
|
+
"ActionForecastBlockPolicy",
|
|
1545
|
+
"ActionForecastBlockRequest",
|
|
1546
|
+
"ActionForecastBlockResult",
|
|
1547
|
+
"ActionForecastBlockSpec",
|
|
1548
|
+
"ActionForecastDraft",
|
|
1549
|
+
"ActionForecastEvidenceMode",
|
|
1550
|
+
"ActionForecastPartitionLayout",
|
|
1551
|
+
"ActionForecastPartitionPolicyBinding",
|
|
1552
|
+
"ActionForecastPolicy",
|
|
1553
|
+
"ActionForecastRequest",
|
|
1554
|
+
"ActionForecastResult",
|
|
1555
|
+
"ActionMetricForecast",
|
|
1556
|
+
"MetricForecastScale",
|
|
1557
|
+
"ParentMetricValue",
|
|
1558
|
+
"ResolvedActionEvidenceCitation",
|
|
1559
|
+
"ResolvedActionForecast",
|
|
1560
|
+
"ResolvedActionForecastBatch",
|
|
1561
|
+
"ResolvedActionForecastBlock",
|
|
1562
|
+
"ResolvedActionMetricForecast",
|
|
1563
|
+
"resolve_action_forecast_block",
|
|
1564
|
+
"resolve_action_forecasts",
|
|
1565
|
+
"validate_action_forecast_partition_layout",
|
|
1566
|
+
"validate_resolved_action_forecast_block",
|
|
1567
|
+
"validate_resolved_action_forecasts",
|
|
1568
|
+
]
|