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,542 @@
|
|
|
1
|
+
"""Immutable, type-sensitive JSON values and canonical hashing.
|
|
2
|
+
|
|
3
|
+
The standard-library JSON codec intentionally treats several Python values as
|
|
4
|
+
interchangeable (notably ``True`` and ``1``) and offers no immutable container
|
|
5
|
+
representation. Evolutionary lineage needs stricter evidence: hashes and
|
|
6
|
+
patch preconditions must identify the exact typed tree that was observed.
|
|
7
|
+
|
|
8
|
+
This module is an inward domain codec. It accepts only exact built-in JSON
|
|
9
|
+
types, freezes objects/arrays, rejects cycles and non-finite numbers, and emits
|
|
10
|
+
a small length-framed binary canonical form. It is not an interchange JSON
|
|
11
|
+
serializer and it has no filesystem or framework dependency.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import hashlib
|
|
17
|
+
import math
|
|
18
|
+
import struct
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import Union
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
_TYPED_JSON_HASH_DOMAIN = b"agent-evolve:typed-json:v1\x00"
|
|
24
|
+
_UINT64_MAX = (1 << 64) - 1
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
28
|
+
class TypedJsonLimits:
|
|
29
|
+
"""Hard resource bounds for validation and canonicalization.
|
|
30
|
+
|
|
31
|
+
User-provided limits may only tighten these process-independent ceilings.
|
|
32
|
+
This keeps a serialized patch from claiming effectively unbounded limits.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
max_depth: int = 64
|
|
36
|
+
max_nodes: int = 50_000
|
|
37
|
+
max_container_items: int = 10_000
|
|
38
|
+
max_string_bytes: int = 1_048_576
|
|
39
|
+
max_integer_bits: int = 4096
|
|
40
|
+
max_canonical_bytes: int = 8_388_608
|
|
41
|
+
|
|
42
|
+
def __post_init__(self) -> None:
|
|
43
|
+
ceilings = {
|
|
44
|
+
"max_depth": 64,
|
|
45
|
+
"max_nodes": 50_000,
|
|
46
|
+
"max_container_items": 10_000,
|
|
47
|
+
"max_string_bytes": 1_048_576,
|
|
48
|
+
"max_integer_bits": 4096,
|
|
49
|
+
"max_canonical_bytes": 8_388_608,
|
|
50
|
+
}
|
|
51
|
+
for name, ceiling in ceilings.items():
|
|
52
|
+
value = getattr(self, name)
|
|
53
|
+
if type(value) is not int:
|
|
54
|
+
raise TypeError(f"{name} must be an exact integer")
|
|
55
|
+
if value <= 0 or value > ceiling:
|
|
56
|
+
raise ValueError(f"{name} must lie in [1, {ceiling}]")
|
|
57
|
+
|
|
58
|
+
def _validated_values(self) -> tuple[int, int, int, int, int, int]:
|
|
59
|
+
if type(self) is not TypedJsonLimits:
|
|
60
|
+
raise TypeError("limits must be an exact TypedJsonLimits value")
|
|
61
|
+
TypedJsonLimits.__post_init__(self)
|
|
62
|
+
return (
|
|
63
|
+
self.max_depth,
|
|
64
|
+
self.max_nodes,
|
|
65
|
+
self.max_container_items,
|
|
66
|
+
self.max_string_bytes,
|
|
67
|
+
self.max_integer_bits,
|
|
68
|
+
self.max_canonical_bytes,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def __eq__(self, other: object) -> bool:
|
|
72
|
+
if type(self) is not TypedJsonLimits or type(other) is not TypedJsonLimits:
|
|
73
|
+
return False
|
|
74
|
+
return self._validated_values() == other._validated_values()
|
|
75
|
+
|
|
76
|
+
def __hash__(self) -> int:
|
|
77
|
+
return hash((TypedJsonLimits, self._validated_values()))
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
DEFAULT_TYPED_JSON_LIMITS = TypedJsonLimits()
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def validate_typed_json_limits(limits: TypedJsonLimits) -> None:
|
|
84
|
+
"""Revalidate an exact limits value at every public trust boundary.
|
|
85
|
+
|
|
86
|
+
Frozen dataclasses prevent ordinary assignment, but callers may still hand
|
|
87
|
+
a domain service an instance that did not pass its generated constructor
|
|
88
|
+
(for example, a value reconstructed by a custom codec). Consumers must
|
|
89
|
+
therefore validate the complete value rather than relying on construction
|
|
90
|
+
having happened elsewhere.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
if type(limits) is not TypedJsonLimits:
|
|
94
|
+
raise TypeError("limits must be an exact TypedJsonLimits value")
|
|
95
|
+
TypedJsonLimits.__post_init__(limits)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _utf8_bytes(value: str, *, limits: TypedJsonLimits, name: str) -> bytes:
|
|
99
|
+
if type(value) is not str:
|
|
100
|
+
raise TypeError(f"{name} must be an exact string")
|
|
101
|
+
if len(value) > limits.max_string_bytes:
|
|
102
|
+
raise ValueError(f"{name} exceeds max_string_bytes")
|
|
103
|
+
try:
|
|
104
|
+
encoded = value.encode("utf-8", errors="strict")
|
|
105
|
+
except UnicodeEncodeError as exc:
|
|
106
|
+
raise ValueError(f"{name} is not valid UTF-8 text") from exc
|
|
107
|
+
if len(encoded) > limits.max_string_bytes:
|
|
108
|
+
raise ValueError(f"{name} exceeds max_string_bytes")
|
|
109
|
+
return encoded
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _is_scalar(value: object) -> bool:
|
|
113
|
+
return value is None or type(value) in (bool, int, float, str)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _validate_scalar(value: object, *, limits: TypedJsonLimits) -> None:
|
|
117
|
+
value_type = type(value)
|
|
118
|
+
if value is None or value_type is bool:
|
|
119
|
+
return
|
|
120
|
+
if value_type is int:
|
|
121
|
+
if value.bit_length() > limits.max_integer_bits:
|
|
122
|
+
raise ValueError("integer exceeds max_integer_bits")
|
|
123
|
+
return
|
|
124
|
+
if value_type is float:
|
|
125
|
+
if not math.isfinite(value):
|
|
126
|
+
raise ValueError("floating-point values must be finite")
|
|
127
|
+
return
|
|
128
|
+
if value_type is str:
|
|
129
|
+
_utf8_bytes(value, limits=limits, name="string value")
|
|
130
|
+
return
|
|
131
|
+
raise TypeError("value is not an exact typed-JSON scalar")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
135
|
+
class FrozenJsonArray:
|
|
136
|
+
"""An immutable typed-JSON array."""
|
|
137
|
+
|
|
138
|
+
items: tuple["FrozenJsonValue", ...]
|
|
139
|
+
|
|
140
|
+
def __post_init__(self) -> None:
|
|
141
|
+
if type(self.items) is not tuple:
|
|
142
|
+
raise TypeError("FrozenJsonArray.items must be an exact tuple")
|
|
143
|
+
_validate_frozen(self, limits=DEFAULT_TYPED_JSON_LIMITS)
|
|
144
|
+
|
|
145
|
+
def __eq__(self, other: object) -> bool:
|
|
146
|
+
if type(self) is not FrozenJsonArray or type(other) is not FrozenJsonArray:
|
|
147
|
+
return False
|
|
148
|
+
return typed_json_equal(self, other)
|
|
149
|
+
|
|
150
|
+
__hash__ = None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
154
|
+
class FrozenJsonObject:
|
|
155
|
+
"""An immutable typed-JSON object in canonical UTF-8 key order."""
|
|
156
|
+
|
|
157
|
+
items: tuple[tuple[str, "FrozenJsonValue"], ...]
|
|
158
|
+
|
|
159
|
+
def __post_init__(self) -> None:
|
|
160
|
+
if type(self.items) is not tuple:
|
|
161
|
+
raise TypeError("FrozenJsonObject.items must be an exact tuple")
|
|
162
|
+
_validate_frozen(self, limits=DEFAULT_TYPED_JSON_LIMITS)
|
|
163
|
+
|
|
164
|
+
def __eq__(self, other: object) -> bool:
|
|
165
|
+
if type(self) is not FrozenJsonObject or type(other) is not FrozenJsonObject:
|
|
166
|
+
return False
|
|
167
|
+
return typed_json_equal(self, other)
|
|
168
|
+
|
|
169
|
+
__hash__ = None
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
FrozenJsonValue = Union[
|
|
173
|
+
None,
|
|
174
|
+
bool,
|
|
175
|
+
int,
|
|
176
|
+
float,
|
|
177
|
+
str,
|
|
178
|
+
FrozenJsonArray,
|
|
179
|
+
FrozenJsonObject,
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def is_frozen_json_value(value: object) -> bool:
|
|
184
|
+
"""Return whether *value* has an exact frozen typed-JSON runtime type."""
|
|
185
|
+
|
|
186
|
+
return _is_scalar(value) or type(value) in (FrozenJsonArray, FrozenJsonObject)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass(slots=True)
|
|
190
|
+
class _ValidationState:
|
|
191
|
+
nodes: int = 0
|
|
192
|
+
text_bytes: int = 0
|
|
193
|
+
active_container_ids: set[int] = field(default_factory=set)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _count_text_bytes(
|
|
197
|
+
state: _ValidationState | "_FreezeState",
|
|
198
|
+
encoded: bytes,
|
|
199
|
+
*,
|
|
200
|
+
limits: TypedJsonLimits,
|
|
201
|
+
) -> None:
|
|
202
|
+
state.text_bytes += len(encoded)
|
|
203
|
+
if state.text_bytes > limits.max_canonical_bytes:
|
|
204
|
+
raise ValueError("typed-JSON text exceeds max_canonical_bytes")
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _validate_frozen(
|
|
208
|
+
value: FrozenJsonValue,
|
|
209
|
+
*,
|
|
210
|
+
limits: TypedJsonLimits,
|
|
211
|
+
depth: int = 0,
|
|
212
|
+
state: _ValidationState | None = None,
|
|
213
|
+
) -> None:
|
|
214
|
+
if state is None:
|
|
215
|
+
# Limits are immutable and shared through the complete traversal. A
|
|
216
|
+
# public boundary validates them once; recursive nodes must not repeat
|
|
217
|
+
# the same six-field validation thousands of times.
|
|
218
|
+
validate_typed_json_limits(limits)
|
|
219
|
+
state = _ValidationState()
|
|
220
|
+
if depth > limits.max_depth:
|
|
221
|
+
raise ValueError("typed-JSON value exceeds max_depth")
|
|
222
|
+
state.nodes += 1
|
|
223
|
+
if state.nodes > limits.max_nodes:
|
|
224
|
+
raise ValueError("typed-JSON value exceeds max_nodes")
|
|
225
|
+
|
|
226
|
+
if _is_scalar(value):
|
|
227
|
+
_validate_scalar(value, limits=limits)
|
|
228
|
+
if type(value) is str:
|
|
229
|
+
_count_text_bytes(
|
|
230
|
+
state,
|
|
231
|
+
_utf8_bytes(value, limits=limits, name="string value"),
|
|
232
|
+
limits=limits,
|
|
233
|
+
)
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
if type(value) is FrozenJsonArray:
|
|
237
|
+
if type(value.items) is not tuple:
|
|
238
|
+
raise TypeError("FrozenJsonArray.items must be an exact tuple")
|
|
239
|
+
if len(value.items) > limits.max_container_items:
|
|
240
|
+
raise ValueError("array exceeds max_container_items")
|
|
241
|
+
identity = id(value)
|
|
242
|
+
if identity in state.active_container_ids:
|
|
243
|
+
raise ValueError("frozen typed-JSON values cannot contain cycles")
|
|
244
|
+
state.active_container_ids.add(identity)
|
|
245
|
+
try:
|
|
246
|
+
for item in value.items:
|
|
247
|
+
if not is_frozen_json_value(item):
|
|
248
|
+
raise TypeError(
|
|
249
|
+
"frozen arrays may contain only frozen typed-JSON values"
|
|
250
|
+
)
|
|
251
|
+
_validate_frozen(
|
|
252
|
+
item,
|
|
253
|
+
limits=limits,
|
|
254
|
+
depth=depth + 1,
|
|
255
|
+
state=state,
|
|
256
|
+
)
|
|
257
|
+
finally:
|
|
258
|
+
state.active_container_ids.remove(identity)
|
|
259
|
+
return
|
|
260
|
+
|
|
261
|
+
if type(value) is FrozenJsonObject:
|
|
262
|
+
if type(value.items) is not tuple:
|
|
263
|
+
raise TypeError("FrozenJsonObject.items must be an exact tuple")
|
|
264
|
+
if len(value.items) > limits.max_container_items:
|
|
265
|
+
raise ValueError("object exceeds max_container_items")
|
|
266
|
+
identity = id(value)
|
|
267
|
+
if identity in state.active_container_ids:
|
|
268
|
+
raise ValueError("frozen typed-JSON values cannot contain cycles")
|
|
269
|
+
state.active_container_ids.add(identity)
|
|
270
|
+
try:
|
|
271
|
+
previous_key_bytes: bytes | None = None
|
|
272
|
+
for entry in value.items:
|
|
273
|
+
if type(entry) is not tuple or len(entry) != 2:
|
|
274
|
+
raise TypeError(
|
|
275
|
+
"frozen object entries must be exact (key, value) tuples"
|
|
276
|
+
)
|
|
277
|
+
key, item = entry
|
|
278
|
+
key_bytes = _utf8_bytes(key, limits=limits, name="object key")
|
|
279
|
+
_count_text_bytes(state, key_bytes, limits=limits)
|
|
280
|
+
if previous_key_bytes is not None and key_bytes <= previous_key_bytes:
|
|
281
|
+
raise ValueError(
|
|
282
|
+
"frozen object keys must be unique and in canonical UTF-8 order"
|
|
283
|
+
)
|
|
284
|
+
previous_key_bytes = key_bytes
|
|
285
|
+
if not is_frozen_json_value(item):
|
|
286
|
+
raise TypeError(
|
|
287
|
+
"frozen objects may contain only frozen typed-JSON values"
|
|
288
|
+
)
|
|
289
|
+
_validate_frozen(
|
|
290
|
+
item,
|
|
291
|
+
limits=limits,
|
|
292
|
+
depth=depth + 1,
|
|
293
|
+
state=state,
|
|
294
|
+
)
|
|
295
|
+
finally:
|
|
296
|
+
state.active_container_ids.remove(identity)
|
|
297
|
+
return
|
|
298
|
+
|
|
299
|
+
raise TypeError("value is not a frozen typed-JSON value")
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@dataclass(slots=True)
|
|
303
|
+
class _FreezeState:
|
|
304
|
+
active_container_ids: set[int]
|
|
305
|
+
nodes: int = 0
|
|
306
|
+
text_bytes: int = 0
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def freeze_json(
|
|
310
|
+
value: object,
|
|
311
|
+
*,
|
|
312
|
+
limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS,
|
|
313
|
+
) -> FrozenJsonValue:
|
|
314
|
+
"""Validate and deeply freeze an exact built-in JSON-like value.
|
|
315
|
+
|
|
316
|
+
``dict`` and ``list`` subclasses are rejected even if they appear benign:
|
|
317
|
+
their iteration and indexing methods are executable behavior. Tuples are
|
|
318
|
+
not accepted as raw arrays; tuples are reserved for the immutable internal
|
|
319
|
+
representation.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
validate_typed_json_limits(limits)
|
|
323
|
+
if is_frozen_json_value(value):
|
|
324
|
+
_validate_frozen(value, limits=limits)
|
|
325
|
+
return value
|
|
326
|
+
|
|
327
|
+
state = _FreezeState(active_container_ids=set())
|
|
328
|
+
|
|
329
|
+
def visit(current: object, depth: int) -> FrozenJsonValue:
|
|
330
|
+
if depth > limits.max_depth:
|
|
331
|
+
raise ValueError("typed-JSON value exceeds max_depth")
|
|
332
|
+
state.nodes += 1
|
|
333
|
+
if state.nodes > limits.max_nodes:
|
|
334
|
+
raise ValueError("typed-JSON value exceeds max_nodes")
|
|
335
|
+
|
|
336
|
+
if _is_scalar(current):
|
|
337
|
+
_validate_scalar(current, limits=limits)
|
|
338
|
+
if type(current) is str:
|
|
339
|
+
_count_text_bytes(
|
|
340
|
+
state,
|
|
341
|
+
_utf8_bytes(current, limits=limits, name="string value"),
|
|
342
|
+
limits=limits,
|
|
343
|
+
)
|
|
344
|
+
return current # type: ignore[return-value]
|
|
345
|
+
|
|
346
|
+
current_type = type(current)
|
|
347
|
+
if current_type not in (dict, list):
|
|
348
|
+
raise TypeError(
|
|
349
|
+
"typed-JSON values require exact dict/list containers and exact scalars"
|
|
350
|
+
)
|
|
351
|
+
if len(current) > limits.max_container_items:
|
|
352
|
+
raise ValueError("container exceeds max_container_items")
|
|
353
|
+
identity = id(current)
|
|
354
|
+
if identity in state.active_container_ids:
|
|
355
|
+
raise ValueError("typed-JSON values cannot contain cycles")
|
|
356
|
+
state.active_container_ids.add(identity)
|
|
357
|
+
try:
|
|
358
|
+
if current_type is list:
|
|
359
|
+
frozen_items = tuple(visit(item, depth + 1) for item in current)
|
|
360
|
+
# The raw tree has been checked incrementally and the complete
|
|
361
|
+
# frozen root is checked once below. Calling the public
|
|
362
|
+
# dataclass constructor here would recursively revalidate each
|
|
363
|
+
# just-built child subtree at every ancestor.
|
|
364
|
+
frozen_array = object.__new__(FrozenJsonArray)
|
|
365
|
+
object.__setattr__(frozen_array, "items", frozen_items)
|
|
366
|
+
return frozen_array
|
|
367
|
+
|
|
368
|
+
encoded_entries: list[tuple[bytes, str, FrozenJsonValue]] = []
|
|
369
|
+
for key, item in current.items():
|
|
370
|
+
key_bytes = _utf8_bytes(key, limits=limits, name="object key")
|
|
371
|
+
_count_text_bytes(state, key_bytes, limits=limits)
|
|
372
|
+
encoded_entries.append((key_bytes, key, visit(item, depth + 1)))
|
|
373
|
+
encoded_entries.sort(key=lambda entry: entry[0])
|
|
374
|
+
frozen_object = object.__new__(FrozenJsonObject)
|
|
375
|
+
object.__setattr__(
|
|
376
|
+
frozen_object,
|
|
377
|
+
"items",
|
|
378
|
+
tuple((key, item) for _, key, item in encoded_entries),
|
|
379
|
+
)
|
|
380
|
+
return frozen_object
|
|
381
|
+
finally:
|
|
382
|
+
state.active_container_ids.remove(identity)
|
|
383
|
+
|
|
384
|
+
frozen = visit(value, 0)
|
|
385
|
+
_validate_frozen(frozen, limits=limits)
|
|
386
|
+
return frozen
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def thaw_json(value: FrozenJsonValue) -> object:
|
|
390
|
+
"""Return a fresh exact ``dict``/``list`` representation."""
|
|
391
|
+
|
|
392
|
+
_validate_frozen(value, limits=DEFAULT_TYPED_JSON_LIMITS)
|
|
393
|
+
|
|
394
|
+
def visit(current: FrozenJsonValue) -> object:
|
|
395
|
+
# The complete immutable graph was validated above. Recursing through
|
|
396
|
+
# this private visitor preserves that one trust-boundary check without
|
|
397
|
+
# revalidating every descendant subtree before thawing it.
|
|
398
|
+
if _is_scalar(current):
|
|
399
|
+
return current
|
|
400
|
+
if type(current) is FrozenJsonArray:
|
|
401
|
+
return [visit(item) for item in current.items]
|
|
402
|
+
if type(current) is FrozenJsonObject:
|
|
403
|
+
return {key: visit(item) for key, item in current.items}
|
|
404
|
+
raise AssertionError("validated frozen value had an impossible type")
|
|
405
|
+
|
|
406
|
+
return visit(value)
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _uint64(value: int) -> bytes:
|
|
410
|
+
if type(value) is not int or value < 0 or value > _UINT64_MAX:
|
|
411
|
+
raise ValueError("canonical length is outside uint64 range")
|
|
412
|
+
return value.to_bytes(8, "big", signed=False)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@dataclass(slots=True)
|
|
416
|
+
class _CanonicalWriter:
|
|
417
|
+
limit: int
|
|
418
|
+
chunks: list[bytes]
|
|
419
|
+
size: int = 0
|
|
420
|
+
|
|
421
|
+
def add(self, value: bytes) -> None:
|
|
422
|
+
if type(value) is not bytes:
|
|
423
|
+
raise TypeError("canonical chunks must be exact bytes")
|
|
424
|
+
self.size += len(value)
|
|
425
|
+
if self.size > self.limit:
|
|
426
|
+
raise ValueError("typed-JSON canonical form exceeds max_canonical_bytes")
|
|
427
|
+
self.chunks.append(value)
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
def canonical_typed_json_bytes(
|
|
431
|
+
value: object,
|
|
432
|
+
*,
|
|
433
|
+
limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS,
|
|
434
|
+
) -> bytes:
|
|
435
|
+
"""Encode *value* in the version-1 type-sensitive canonical form."""
|
|
436
|
+
|
|
437
|
+
frozen = freeze_json(value, limits=limits)
|
|
438
|
+
writer = _CanonicalWriter(limits.max_canonical_bytes, [])
|
|
439
|
+
|
|
440
|
+
def encode(current: FrozenJsonValue) -> None:
|
|
441
|
+
if current is None:
|
|
442
|
+
writer.add(b"n")
|
|
443
|
+
elif type(current) is bool:
|
|
444
|
+
writer.add(b"b1" if current else b"b0")
|
|
445
|
+
elif type(current) is int:
|
|
446
|
+
integer_bytes = str(current).encode("ascii", errors="strict")
|
|
447
|
+
writer.add(b"i")
|
|
448
|
+
writer.add(_uint64(len(integer_bytes)))
|
|
449
|
+
writer.add(integer_bytes)
|
|
450
|
+
elif type(current) is float:
|
|
451
|
+
writer.add(b"f")
|
|
452
|
+
writer.add(struct.pack(">d", current))
|
|
453
|
+
elif type(current) is str:
|
|
454
|
+
string_bytes = _utf8_bytes(current, limits=limits, name="string value")
|
|
455
|
+
writer.add(b"s")
|
|
456
|
+
writer.add(_uint64(len(string_bytes)))
|
|
457
|
+
writer.add(string_bytes)
|
|
458
|
+
elif type(current) is FrozenJsonArray:
|
|
459
|
+
writer.add(b"a")
|
|
460
|
+
writer.add(_uint64(len(current.items)))
|
|
461
|
+
for item in current.items:
|
|
462
|
+
encode(item)
|
|
463
|
+
elif type(current) is FrozenJsonObject:
|
|
464
|
+
writer.add(b"o")
|
|
465
|
+
writer.add(_uint64(len(current.items)))
|
|
466
|
+
for key, item in current.items:
|
|
467
|
+
key_bytes = _utf8_bytes(key, limits=limits, name="object key")
|
|
468
|
+
writer.add(_uint64(len(key_bytes)))
|
|
469
|
+
writer.add(key_bytes)
|
|
470
|
+
encode(item)
|
|
471
|
+
else: # pragma: no cover - freeze_json closes the union.
|
|
472
|
+
raise AssertionError("unsupported frozen typed-JSON value")
|
|
473
|
+
|
|
474
|
+
encode(frozen)
|
|
475
|
+
return b"".join(writer.chunks)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def typed_json_sha256(
|
|
479
|
+
value: object,
|
|
480
|
+
*,
|
|
481
|
+
limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS,
|
|
482
|
+
) -> str:
|
|
483
|
+
"""Hash the exact typed tree with an explicit versioned domain tag."""
|
|
484
|
+
|
|
485
|
+
payload = canonical_typed_json_bytes(value, limits=limits)
|
|
486
|
+
return _typed_json_sha256_canonical_bytes(payload)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _typed_json_sha256_canonical_bytes(payload: bytes) -> str:
|
|
490
|
+
"""Hash canonical bytes already produced by this module's encoder.
|
|
491
|
+
|
|
492
|
+
This is an internal de-duplication seam for trusted domain validators that
|
|
493
|
+
need both canonical bytes and their typed-JSON digest. Public callers must
|
|
494
|
+
continue through :func:`typed_json_sha256`, which validates and encodes.
|
|
495
|
+
"""
|
|
496
|
+
|
|
497
|
+
if type(payload) is not bytes:
|
|
498
|
+
raise TypeError("canonical typed-JSON payload must be exact bytes")
|
|
499
|
+
digest = hashlib.sha256()
|
|
500
|
+
digest.update(_TYPED_JSON_HASH_DOMAIN)
|
|
501
|
+
digest.update(payload)
|
|
502
|
+
return digest.hexdigest()
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def typed_json_equal(
|
|
506
|
+
left: object,
|
|
507
|
+
right: object,
|
|
508
|
+
*,
|
|
509
|
+
limits: TypedJsonLimits = DEFAULT_TYPED_JSON_LIMITS,
|
|
510
|
+
) -> bool:
|
|
511
|
+
"""Type-sensitive structural equality for validated values."""
|
|
512
|
+
|
|
513
|
+
return canonical_typed_json_bytes(left, limits=limits) == canonical_typed_json_bytes(
|
|
514
|
+
right,
|
|
515
|
+
limits=limits,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
def is_json_scalar(value: object) -> bool:
|
|
520
|
+
"""Validate the exact scalar type without accepting numeric subclasses."""
|
|
521
|
+
|
|
522
|
+
if not _is_scalar(value):
|
|
523
|
+
return False
|
|
524
|
+
_validate_scalar(value, limits=DEFAULT_TYPED_JSON_LIMITS)
|
|
525
|
+
return True
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
__all__ = [
|
|
529
|
+
"DEFAULT_TYPED_JSON_LIMITS",
|
|
530
|
+
"FrozenJsonArray",
|
|
531
|
+
"FrozenJsonObject",
|
|
532
|
+
"FrozenJsonValue",
|
|
533
|
+
"TypedJsonLimits",
|
|
534
|
+
"canonical_typed_json_bytes",
|
|
535
|
+
"freeze_json",
|
|
536
|
+
"is_frozen_json_value",
|
|
537
|
+
"is_json_scalar",
|
|
538
|
+
"thaw_json",
|
|
539
|
+
"typed_json_equal",
|
|
540
|
+
"typed_json_sha256",
|
|
541
|
+
"validate_typed_json_limits",
|
|
542
|
+
]
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"""Immutable option identities for finite atomic variation spaces.
|
|
2
|
+
|
|
3
|
+
An :class:`AtomicEditOption` is deliberately more than a provider-facing
|
|
4
|
+
replacement value. It binds that value to one typed path, one variation
|
|
5
|
+
family, and exact source metadata so observations cannot silently migrate
|
|
6
|
+
between coordinates or catalog revisions.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
|
|
15
|
+
from agent_evolve.domain.patch import (
|
|
16
|
+
JsonPath,
|
|
17
|
+
canonical_path_bytes,
|
|
18
|
+
validate_json_path,
|
|
19
|
+
)
|
|
20
|
+
from agent_evolve.domain.typed_json import (
|
|
21
|
+
FrozenJsonValue,
|
|
22
|
+
canonical_typed_json_bytes,
|
|
23
|
+
freeze_json,
|
|
24
|
+
is_json_scalar,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
_OPTION_ID = re.compile(r"^[a-z][a-z0-9_.-]{0,255}$")
|
|
29
|
+
_FAMILY = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
|
|
30
|
+
_METADATA_KEY = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
|
|
31
|
+
_MAX_METADATA_ITEMS = 64
|
|
32
|
+
_MAX_METADATA_VALUE_BYTES = 16_384
|
|
33
|
+
_ATOMIC_EDIT_OPTION_DOMAIN = b"agent-evolve:atomic-edit-option:v1\x00"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _utf8(value: str, *, name: str, max_bytes: int) -> bytes:
|
|
37
|
+
if type(value) is not str:
|
|
38
|
+
raise TypeError(f"{name} must be an exact string")
|
|
39
|
+
try:
|
|
40
|
+
encoded = value.encode("utf-8", errors="strict")
|
|
41
|
+
except UnicodeEncodeError as exc:
|
|
42
|
+
raise ValueError(f"{name} must be strict UTF-8") from exc
|
|
43
|
+
if len(encoded) > max_bytes:
|
|
44
|
+
raise ValueError(f"{name} exceeds its byte limit")
|
|
45
|
+
return encoded
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _frame(payload: bytes) -> bytes:
|
|
49
|
+
if type(payload) is not bytes:
|
|
50
|
+
raise TypeError("framed payloads must be exact bytes")
|
|
51
|
+
return len(payload).to_bytes(8, "big", signed=False) + payload
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
55
|
+
class AtomicEditOption:
|
|
56
|
+
"""One closed, source-bound scalar replacement at one exact JSON path.
|
|
57
|
+
|
|
58
|
+
``metadata`` is canonical: keys are unique and lexicographically ordered.
|
|
59
|
+
This makes the complete option identity independent of mapping iteration
|
|
60
|
+
order while retaining a compact tuple representation at the domain edge.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
option_id: str
|
|
64
|
+
path: JsonPath
|
|
65
|
+
replacement: FrozenJsonValue
|
|
66
|
+
family: str
|
|
67
|
+
metadata: tuple[tuple[str, str], ...] = ()
|
|
68
|
+
|
|
69
|
+
def __post_init__(self) -> None:
|
|
70
|
+
if type(self.option_id) is not str or _OPTION_ID.fullmatch(
|
|
71
|
+
self.option_id
|
|
72
|
+
) is None:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
"option_id must use the closed lowercase identifier grammar"
|
|
75
|
+
)
|
|
76
|
+
if type(self.path) is not JsonPath:
|
|
77
|
+
raise TypeError("path must be an exact JsonPath")
|
|
78
|
+
validate_json_path(self.path)
|
|
79
|
+
if not self.path.segments:
|
|
80
|
+
raise ValueError("an atomic edit option cannot replace the root")
|
|
81
|
+
if freeze_json(self.replacement) is not self.replacement:
|
|
82
|
+
raise TypeError("replacement must already be frozen typed JSON")
|
|
83
|
+
if not is_json_scalar(self.replacement):
|
|
84
|
+
raise TypeError("replacement must be a typed-JSON scalar")
|
|
85
|
+
if type(self.family) is not str or _FAMILY.fullmatch(self.family) is None:
|
|
86
|
+
raise ValueError("family must use the closed lowercase token grammar")
|
|
87
|
+
if type(self.metadata) is not tuple:
|
|
88
|
+
raise TypeError("metadata must be an exact tuple")
|
|
89
|
+
if len(self.metadata) > _MAX_METADATA_ITEMS:
|
|
90
|
+
raise ValueError("metadata exceeds the item limit")
|
|
91
|
+
|
|
92
|
+
keys: list[str] = []
|
|
93
|
+
for item in self.metadata:
|
|
94
|
+
if type(item) is not tuple or len(item) != 2:
|
|
95
|
+
raise TypeError("metadata entries must be exact key/value tuples")
|
|
96
|
+
key, value = item
|
|
97
|
+
if type(key) is not str or _METADATA_KEY.fullmatch(key) is None:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
"metadata keys must use the closed lowercase token grammar"
|
|
100
|
+
)
|
|
101
|
+
_utf8(
|
|
102
|
+
value,
|
|
103
|
+
name=f"metadata value for {key!r}",
|
|
104
|
+
max_bytes=_MAX_METADATA_VALUE_BYTES,
|
|
105
|
+
)
|
|
106
|
+
keys.append(key)
|
|
107
|
+
if keys != sorted(keys) or len(set(keys)) != len(keys):
|
|
108
|
+
raise ValueError("metadata keys must be unique and canonically sorted")
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def identity_sha256(self) -> str:
|
|
112
|
+
"""Return a type-sensitive digest binding every semantic field."""
|
|
113
|
+
|
|
114
|
+
validate_atomic_edit_option(self)
|
|
115
|
+
digest = hashlib.sha256()
|
|
116
|
+
digest.update(_ATOMIC_EDIT_OPTION_DOMAIN)
|
|
117
|
+
digest.update(_frame(self.option_id.encode("ascii")))
|
|
118
|
+
digest.update(_frame(canonical_path_bytes(self.path)))
|
|
119
|
+
digest.update(_frame(canonical_typed_json_bytes(self.replacement)))
|
|
120
|
+
digest.update(_frame(self.family.encode("ascii")))
|
|
121
|
+
digest.update(len(self.metadata).to_bytes(8, "big", signed=False))
|
|
122
|
+
for key, value in self.metadata:
|
|
123
|
+
digest.update(_frame(key.encode("ascii")))
|
|
124
|
+
digest.update(_frame(value.encode("utf-8", errors="strict")))
|
|
125
|
+
return digest.hexdigest()
|
|
126
|
+
|
|
127
|
+
def _validated_values(
|
|
128
|
+
self,
|
|
129
|
+
) -> tuple[str, bytes, bytes, str, tuple[tuple[str, str], ...]]:
|
|
130
|
+
if type(self) is not AtomicEditOption:
|
|
131
|
+
raise TypeError("option must be an exact AtomicEditOption")
|
|
132
|
+
AtomicEditOption.__post_init__(self)
|
|
133
|
+
return (
|
|
134
|
+
self.option_id,
|
|
135
|
+
canonical_path_bytes(self.path),
|
|
136
|
+
canonical_typed_json_bytes(self.replacement),
|
|
137
|
+
self.family,
|
|
138
|
+
self.metadata,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
def __eq__(self, other: object) -> bool:
|
|
142
|
+
if type(self) is not AtomicEditOption or type(other) is not AtomicEditOption:
|
|
143
|
+
return False
|
|
144
|
+
return self._validated_values() == other._validated_values()
|
|
145
|
+
|
|
146
|
+
def __hash__(self) -> int:
|
|
147
|
+
return hash((AtomicEditOption, self._validated_values()))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def validate_atomic_edit_option(option: AtomicEditOption) -> None:
|
|
151
|
+
"""Revalidate an exact option at a public trust boundary."""
|
|
152
|
+
|
|
153
|
+
if type(option) is not AtomicEditOption:
|
|
154
|
+
raise TypeError("option must be an exact AtomicEditOption")
|
|
155
|
+
AtomicEditOption.__post_init__(option)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
__all__ = ["AtomicEditOption", "validate_atomic_edit_option"]
|