agent-learning-kit 0.1.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_learning_kit-0.1.0.dist-info/METADATA +381 -0
- agent_learning_kit-0.1.0.dist-info/RECORD +642 -0
- agent_learning_kit-0.1.0.dist-info/WHEEL +4 -0
- agent_learning_kit-0.1.0.dist-info/entry_points.txt +5 -0
- agent_learning_kit-0.1.0.dist-info/licenses/LICENSE +173 -0
- agent_learning_kit-0.1.0.dist-info/licenses/NOTICE +7 -0
- fi/__init__.py +5 -0
- fi/alk/__init__.py +57 -0
- fi/alk/_facade.py +31 -0
- fi/alk/_module_alias.py +68 -0
- fi/alk/_paths.py +14 -0
- fi/alk/_schema.py +522 -0
- fi/alk/actions.py +727 -0
- fi/alk/bench/__init__.py +517 -0
- fi/alk/bench/_codeexec.py +213 -0
- fi/alk/bench/_coding.py +215 -0
- fi/alk/bench/_docker.py +237 -0
- fi/alk/bench/_grader.py +286 -0
- fi/alk/bench/_pull.py +212 -0
- fi/alk/bench/_voice.py +147 -0
- fi/alk/capabilities.py +627 -0
- fi/alk/cli.py +6396 -0
- fi/alk/config.py +130 -0
- fi/alk/cua_loop.py +562 -0
- fi/alk/evals.py +2351 -0
- fi/alk/extensions.py +163 -0
- fi/alk/harness/ARCHITECTURE.md +231 -0
- fi/alk/harness/DESIGN.md +246 -0
- fi/alk/harness/ENVIRONMENT_CONFORMANCE.md +127 -0
- fi/alk/harness/HOW-IT-WORKS.md +297 -0
- fi/alk/harness/IMPLEMENTATION_AND_VALIDATION_STATUS.md +229 -0
- fi/alk/harness/README.md +417 -0
- fi/alk/harness/__init__.py +77 -0
- fi/alk/harness/__main__.py +3 -0
- fi/alk/harness/amend.py +312 -0
- fi/alk/harness/artifacts.py +319 -0
- fi/alk/harness/authoring_entrypoint.py +189 -0
- fi/alk/harness/authoring_runtime_validation.py +267 -0
- fi/alk/harness/backends/README.md +43 -0
- fi/alk/harness/backends/__init__.py +122 -0
- fi/alk/harness/backends/base.py +241 -0
- fi/alk/harness/backends/claude.py +211 -0
- fi/alk/harness/backends/files.py +182 -0
- fi/alk/harness/backends/vertex_gemini.py +457 -0
- fi/alk/harness/background_noise.py +95 -0
- fi/alk/harness/build.py +385 -0
- fi/alk/harness/bundle.py +593 -0
- fi/alk/harness/bundle_author_v2.py +1831 -0
- fi/alk/harness/bundle_v2.py +719 -0
- fi/alk/harness/call_runner.py +1440 -0
- fi/alk/harness/callback_http_adapter.py +111 -0
- fi/alk/harness/catalogue.py +287 -0
- fi/alk/harness/chat.py +428 -0
- fi/alk/harness/chat_call_runner.py +506 -0
- fi/alk/harness/checks.py +136 -0
- fi/alk/harness/cli.py +1354 -0
- fi/alk/harness/config.py +338 -0
- fi/alk/harness/contract.py +718 -0
- fi/alk/harness/credentials.py +674 -0
- fi/alk/harness/data/persona_vocabulary.json +111 -0
- fi/alk/harness/environment.py +99 -0
- fi/alk/harness/environment_plan.py +168 -0
- fi/alk/harness/events.py +125 -0
- fi/alk/harness/executor.py +304 -0
- fi/alk/harness/folder.py +234 -0
- fi/alk/harness/generated_runtime.py +815 -0
- fi/alk/harness/github.py +72 -0
- fi/alk/harness/hosted_authoring_entrypoint.py +183 -0
- fi/alk/harness/hosted_entrypoint.py +2402 -0
- fi/alk/harness/hosted_scheduler.py +2218 -0
- fi/alk/harness/job.py +426 -0
- fi/alk/harness/judge.py +184 -0
- fi/alk/harness/livekit_source.py +50 -0
- fi/alk/harness/livekit_tool_trace_bootstrap.py +71 -0
- fi/alk/harness/observability.py +208 -0
- fi/alk/harness/outbound.py +3252 -0
- fi/alk/harness/packaging.py +515 -0
- fi/alk/harness/persona_guides.py +157 -0
- fi/alk/harness/platform.py +692 -0
- fi/alk/harness/process_preflight.py +764 -0
- fi/alk/harness/process_runtime.py +5670 -0
- fi/alk/harness/prove.py +425 -0
- fi/alk/harness/provider_import.py +703 -0
- fi/alk/harness/provider_lifecycle.py +392 -0
- fi/alk/harness/provision.py +2896 -0
- fi/alk/harness/reception.py +147 -0
- fi/alk/harness/retell_chat_call_runner.py +373 -0
- fi/alk/harness/run/__init__.py +296 -0
- fi/alk/harness/run/alk.py +184 -0
- fi/alk/harness/run/call.py +162 -0
- fi/alk/harness/run/conversation.py +264 -0
- fi/alk/harness/run/data/voices_by_language_and_gender.json +693 -0
- fi/alk/harness/run/evidence.py +195 -0
- fi/alk/harness/run/grade.py +598 -0
- fi/alk/harness/run/live.py +297 -0
- fi/alk/harness/run/models.py +56 -0
- fi/alk/harness/run/platform_evals.py +227 -0
- fi/alk/harness/run/sdk_voice.py +130 -0
- fi/alk/harness/run/simulation.py +1209 -0
- fi/alk/harness/run/stage.py +91 -0
- fi/alk/harness/run/targets.py +508 -0
- fi/alk/harness/run/tools.py +601 -0
- fi/alk/harness/run/voice.py +340 -0
- fi/alk/harness/runtime.py +172 -0
- fi/alk/harness/sandbox_server.py +2011 -0
- fi/alk/harness/sandbox_worker.py +44 -0
- fi/alk/harness/scenario.py +1048 -0
- fi/alk/harness/scenario_source.py +879 -0
- fi/alk/harness/scenario_tools.py +1143 -0
- fi/alk/harness/scenarios.py +915 -0
- fi/alk/harness/secrets.py +168 -0
- fi/alk/harness/service_catalog.py +97 -0
- fi/alk/harness/session.py +391 -0
- fi/alk/harness/sessions.py +372 -0
- fi/alk/harness/simulator.py +76 -0
- fi/alk/harness/simulator_voice.py +928 -0
- fi/alk/harness/skills/build-environment/SKILL.md +538 -0
- fi/alk/harness/skills/harness.md +131 -0
- fi/alk/harness/skills/kinds/chat.md +48 -0
- fi/alk/harness/skills/kinds/voice-voicemail.md +63 -0
- fi/alk/harness/skills/kinds/voice.md +59 -0
- fi/alk/harness/skills/plan-suite/SKILL.md +103 -0
- fi/alk/harness/skills/provision-environment/SKILL.md +136 -0
- fi/alk/harness/skills/run-scenarios/SKILL.md +112 -0
- fi/alk/harness/skills/understand-agent/SKILL.md +251 -0
- fi/alk/harness/skills/write-scenarios/SKILL.md +606 -0
- fi/alk/harness/skills/write-scenarios/references/refusals.md +28 -0
- fi/alk/harness/skills/write-scenarios/references/world-api.md +92 -0
- fi/alk/harness/source_data_invariants.py +444 -0
- fi/alk/harness/source_tool_evidence.py +79 -0
- fi/alk/harness/sources.py +253 -0
- fi/alk/harness/spend.py +140 -0
- fi/alk/harness/tool_trace_proxy.py +104 -0
- fi/alk/harness/tools.py +1018 -0
- fi/alk/harness/understand.py +169 -0
- fi/alk/harness/voicemail_audio.py +74 -0
- fi/alk/harness/world/__init__.py +33 -0
- fi/alk/harness/world/errors.py +68 -0
- fi/alk/harness/world/expectations.py +91 -0
- fi/alk/harness/world/handle.py +538 -0
- fi/alk/harness/world/kinds.py +196 -0
- fi/alk/harness/world/mutate.py +186 -0
- fi/alk/harness/world/probe.py +413 -0
- fi/alk/harness/world/provision.py +511 -0
- fi/alk/harness/world/provisioned.py +191 -0
- fi/alk/harness/world/runtime.py +616 -0
- fi/alk/harness/world/snapshot.py +288 -0
- fi/alk/harness/world/stores/__init__.py +305 -0
- fi/alk/harness/world/stores/container.py +215 -0
- fi/alk/harness/world/stores/inprocess.py +346 -0
- fi/alk/harness/world/stores/postgres.py +481 -0
- fi/alk/harness/world/stores/prove.py +202 -0
- fi/alk/harness/world/stores/sqlite.py +245 -0
- fi/alk/harness/world/stores/written.py +182 -0
- fi/alk/harness/world/tools.py +1516 -0
- fi/alk/harness/world/workspace.py +144 -0
- fi/alk/image_loop.py +453 -0
- fi/alk/image_perturb.py +241 -0
- fi/alk/improve.py +274 -0
- fi/alk/live/__init__.py +154 -0
- fi/alk/live/_attribution.py +184 -0
- fi/alk/live/_capture.py +264 -0
- fi/alk/live/_codec.py +391 -0
- fi/alk/live/_contract.py +134 -0
- fi/alk/live/_loopback.py +316 -0
- fi/alk/live/_perturb.py +449 -0
- fi/alk/live/_runner.py +386 -0
- fi/alk/live/_stats.py +561 -0
- fi/alk/live/_transcript.py +240 -0
- fi/alk/live/_workers/__init__.py +9 -0
- fi/alk/live/_workers/a2a_worker.py +316 -0
- fi/alk/live/_workers/langgraph_worker.py +217 -0
- fi/alk/live/_workers/livekit_worker.py +207 -0
- fi/alk/live/_workers/mcp_loopback_server.py +46 -0
- fi/alk/live/_workers/mcp_worker.py +158 -0
- fi/alk/live/_workers/pipecat_worker.py +189 -0
- fi/alk/live/a2a_lane.py +138 -0
- fi/alk/live/langgraph_lane.py +339 -0
- fi/alk/live/livekit_lane.py +376 -0
- fi/alk/live/mcp_lane.py +172 -0
- fi/alk/live/pipecat_lane.py +341 -0
- fi/alk/live/voice_redteam.py +494 -0
- fi/alk/loss.py +306 -0
- fi/alk/optimize.py +36260 -0
- fi/alk/practice/__init__.py +51 -0
- fi/alk/practice/_assess.py +103 -0
- fi/alk/practice/_budget.py +81 -0
- fi/alk/practice/_calibrate.py +69 -0
- fi/alk/practice/_capstone.py +86 -0
- fi/alk/practice/_contract.py +91 -0
- fi/alk/practice/_diagnose.py +79 -0
- fi/alk/practice/_drill.py +196 -0
- fi/alk/practice/_experiment.py +720 -0
- fi/alk/practice/_schedule.py +102 -0
- fi/alk/practice/_store.py +194 -0
- fi/alk/practice/_trainer.py +245 -0
- fi/alk/practice/_update.py +125 -0
- fi/alk/redteam.py +2621 -0
- fi/alk/rewardhack.py +237 -0
- fi/alk/simulate.py +10351 -0
- fi/alk/studio/__init__.py +82 -0
- fi/alk/studio/_bias.py +314 -0
- fi/alk/studio/_calibration.py +522 -0
- fi/alk/studio/_coverage.py +262 -0
- fi/alk/studio/_download.py +665 -0
- fi/alk/studio/_fidelity_attack.py +114 -0
- fi/alk/studio/_generate.py +652 -0
- fi/alk/studio/_library.py +370 -0
- fi/alk/studio/_scan.py +134 -0
- fi/alk/studio/_upgrade.py +42 -0
- fi/alk/studio/_vendor.py +172 -0
- fi/alk/suite.py +4200 -0
- fi/alk/tasks.py +828 -0
- fi/alk/telemetry/__init__.py +149 -0
- fi/alk/telemetry/_contract.py +141 -0
- fi/alk/telemetry/_emit.py +182 -0
- fi/alk/telemetry/_ledger.py +296 -0
- fi/alk/telemetry/_queue.py +127 -0
- fi/alk/telemetry/_row.py +294 -0
- fi/alk/telemetry/_run.py +233 -0
- fi/alk/telemetry/_sync.py +193 -0
- fi/alk/telemetry/_url.py +119 -0
- fi/alk/trinity.py +49397 -0
- fi/alk/voice_loop.py +174 -0
- fi/api/__init__.py +1 -0
- fi/api/auth.py +137 -0
- fi/api/types.py +29 -0
- fi/cli/__init__.py +9 -0
- fi/cli/assertions/__init__.py +25 -0
- fi/cli/assertions/conditions.py +76 -0
- fi/cli/assertions/evaluator.py +286 -0
- fi/cli/assertions/exit_codes.py +20 -0
- fi/cli/assertions/parser.py +131 -0
- fi/cli/assertions/reporter.py +194 -0
- fi/cli/commands/__init__.py +9 -0
- fi/cli/commands/config.py +165 -0
- fi/cli/commands/export.py +208 -0
- fi/cli/commands/init.py +112 -0
- fi/cli/commands/list_cmd.py +213 -0
- fi/cli/commands/run.py +486 -0
- fi/cli/commands/validate.py +173 -0
- fi/cli/commands/view.py +424 -0
- fi/cli/config/__init__.py +6 -0
- fi/cli/config/defaults.py +206 -0
- fi/cli/config/loader.py +155 -0
- fi/cli/config/schema.py +174 -0
- fi/cli/main.py +78 -0
- fi/cli/output/__init__.py +6 -0
- fi/cli/output/formatters.py +106 -0
- fi/cli/output/reporters.py +46 -0
- fi/cli/storage/__init__.py +5 -0
- fi/cli/storage/run_history.py +249 -0
- fi/cli/utils/__init__.py +5 -0
- fi/cli/utils/console.py +44 -0
- fi/evals/__init__.py +131 -0
- fi/evals/autoeval/__init__.py +137 -0
- fi/evals/autoeval/analyzer.py +211 -0
- fi/evals/autoeval/config.py +244 -0
- fi/evals/autoeval/export.py +213 -0
- fi/evals/autoeval/interactive.py +283 -0
- fi/evals/autoeval/pipeline.py +625 -0
- fi/evals/autoeval/prompts.py +139 -0
- fi/evals/autoeval/recommender.py +242 -0
- fi/evals/autoeval/rules.py +589 -0
- fi/evals/autoeval/templates.py +299 -0
- fi/evals/autoeval/types.py +232 -0
- fi/evals/core/__init__.py +16 -0
- fi/evals/core/cloud_registry.py +184 -0
- fi/evals/core/engines.py +368 -0
- fi/evals/core/evaluate.py +319 -0
- fi/evals/core/judge_prompt.py +90 -0
- fi/evals/core/prompt_generator.py +83 -0
- fi/evals/core/registry.py +57 -0
- fi/evals/core/result.py +55 -0
- fi/evals/evaluator.py +721 -0
- fi/evals/execution.py +168 -0
- fi/evals/feedback/__init__.py +32 -0
- fi/evals/feedback/calibrator.py +160 -0
- fi/evals/feedback/collector.py +214 -0
- fi/evals/feedback/hooks.py +81 -0
- fi/evals/feedback/retriever.py +128 -0
- fi/evals/feedback/store.py +272 -0
- fi/evals/feedback/types.py +99 -0
- fi/evals/framework/README.md +79 -0
- fi/evals/framework/__init__.py +267 -0
- fi/evals/framework/backends/Dockerfile.eval-runner +33 -0
- fi/evals/framework/backends/__init__.py +99 -0
- fi/evals/framework/backends/_container.py +141 -0
- fi/evals/framework/backends/_utils.py +145 -0
- fi/evals/framework/backends/base.py +223 -0
- fi/evals/framework/backends/celery_backend.py +417 -0
- fi/evals/framework/backends/celery_worker.py +78 -0
- fi/evals/framework/backends/kubernetes_backend.py +665 -0
- fi/evals/framework/backends/ray_backend.py +521 -0
- fi/evals/framework/backends/temporal.py +350 -0
- fi/evals/framework/backends/temporal_worker.py +126 -0
- fi/evals/framework/backends/thread_pool.py +286 -0
- fi/evals/framework/context.py +258 -0
- fi/evals/framework/enrichment.py +306 -0
- fi/evals/framework/evals/__init__.py +68 -0
- fi/evals/framework/evals/agentic.py +399 -0
- fi/evals/framework/evals/builder.py +609 -0
- fi/evals/framework/evals/semantic.py +142 -0
- fi/evals/framework/evaluator.py +647 -0
- fi/evals/framework/evaluators/__init__.py +22 -0
- fi/evals/framework/evaluators/blocking.py +347 -0
- fi/evals/framework/evaluators/non_blocking.py +577 -0
- fi/evals/framework/propagation.py +421 -0
- fi/evals/framework/protocols.py +385 -0
- fi/evals/framework/registry.py +370 -0
- fi/evals/framework/resilience/__init__.py +150 -0
- fi/evals/framework/resilience/circuit_breaker.py +309 -0
- fi/evals/framework/resilience/degradation.py +355 -0
- fi/evals/framework/resilience/health.py +505 -0
- fi/evals/framework/resilience/rate_limiter.py +228 -0
- fi/evals/framework/resilience/retry.py +274 -0
- fi/evals/framework/resilience/types.py +288 -0
- fi/evals/framework/resilience/wrapper.py +433 -0
- fi/evals/framework/types.py +218 -0
- fi/evals/guardrails/README.md +915 -0
- fi/evals/guardrails/__init__.py +96 -0
- fi/evals/guardrails/backends/__init__.py +43 -0
- fi/evals/guardrails/backends/azure.py +361 -0
- fi/evals/guardrails/backends/base.py +88 -0
- fi/evals/guardrails/backends/generic_llm.py +163 -0
- fi/evals/guardrails/backends/granite.py +216 -0
- fi/evals/guardrails/backends/llamaguard.py +221 -0
- fi/evals/guardrails/backends/local_base.py +479 -0
- fi/evals/guardrails/backends/openai.py +365 -0
- fi/evals/guardrails/backends/qwen.py +170 -0
- fi/evals/guardrails/backends/shieldgemma.py +154 -0
- fi/evals/guardrails/backends/turing.py +235 -0
- fi/evals/guardrails/backends/vllm_client.py +321 -0
- fi/evals/guardrails/backends/wildguard.py +188 -0
- fi/evals/guardrails/base.py +888 -0
- fi/evals/guardrails/config.py +221 -0
- fi/evals/guardrails/discovery.py +243 -0
- fi/evals/guardrails/gateway.py +437 -0
- fi/evals/guardrails/registry.py +231 -0
- fi/evals/guardrails/scanners/__init__.py +127 -0
- fi/evals/guardrails/scanners/base.py +191 -0
- fi/evals/guardrails/scanners/code_injection.py +243 -0
- fi/evals/guardrails/scanners/eval_delegate.py +574 -0
- fi/evals/guardrails/scanners/invisible_chars.py +351 -0
- fi/evals/guardrails/scanners/jailbreak.py +412 -0
- fi/evals/guardrails/scanners/language.py +288 -0
- fi/evals/guardrails/scanners/pipeline.py +260 -0
- fi/evals/guardrails/scanners/regex.py +311 -0
- fi/evals/guardrails/scanners/secrets.py +274 -0
- fi/evals/guardrails/scanners/topics.py +649 -0
- fi/evals/guardrails/scanners/urls.py +341 -0
- fi/evals/guardrails/types.py +96 -0
- fi/evals/llm/__init__.py +3 -0
- fi/evals/llm/base_llm_provider.py +35 -0
- fi/evals/llm/providers/litellm.py +70 -0
- fi/evals/local/__init__.py +90 -0
- fi/evals/local/evaluator.py +690 -0
- fi/evals/local/execution_mode.py +121 -0
- fi/evals/local/llm.py +489 -0
- fi/evals/local/metrics/__init__.py +19 -0
- fi/evals/local/registry.py +360 -0
- fi/evals/manager.py +1018 -0
- fi/evals/manager_types.py +362 -0
- fi/evals/metrics/__init__.py +185 -0
- fi/evals/metrics/agents/__init__.py +74 -0
- fi/evals/metrics/agents/metrics.py +693 -0
- fi/evals/metrics/agents/report.py +36463 -0
- fi/evals/metrics/agents/types.py +160 -0
- fi/evals/metrics/base_llm_metric.py +111 -0
- fi/evals/metrics/base_metric.py +138 -0
- fi/evals/metrics/code_security/__init__.py +305 -0
- fi/evals/metrics/code_security/analyzer.py +985 -0
- fi/evals/metrics/code_security/benchmarks/__init__.py +73 -0
- fi/evals/metrics/code_security/benchmarks/builtin.py +750 -0
- fi/evals/metrics/code_security/benchmarks/loader.py +580 -0
- fi/evals/metrics/code_security/benchmarks/types.py +308 -0
- fi/evals/metrics/code_security/detectors/__init__.py +186 -0
- fi/evals/metrics/code_security/detectors/base.py +394 -0
- fi/evals/metrics/code_security/detectors/cryptography.py +345 -0
- fi/evals/metrics/code_security/detectors/injection.py +744 -0
- fi/evals/metrics/code_security/detectors/secrets.py +287 -0
- fi/evals/metrics/code_security/detectors/serialization.py +192 -0
- fi/evals/metrics/code_security/joint_metrics.py +588 -0
- fi/evals/metrics/code_security/judges/__init__.py +83 -0
- fi/evals/metrics/code_security/judges/base.py +238 -0
- fi/evals/metrics/code_security/judges/dual_judge.py +534 -0
- fi/evals/metrics/code_security/judges/llm_judge.py +301 -0
- fi/evals/metrics/code_security/judges/pattern_judge.py +515 -0
- fi/evals/metrics/code_security/metrics.py +388 -0
- fi/evals/metrics/code_security/modes/__init__.py +63 -0
- fi/evals/metrics/code_security/modes/adversarial.py +284 -0
- fi/evals/metrics/code_security/modes/autocomplete.py +198 -0
- fi/evals/metrics/code_security/modes/base.py +283 -0
- fi/evals/metrics/code_security/modes/instruct.py +253 -0
- fi/evals/metrics/code_security/modes/repair.py +230 -0
- fi/evals/metrics/code_security/reports/__init__.py +57 -0
- fi/evals/metrics/code_security/reports/generator.py +404 -0
- fi/evals/metrics/code_security/reports/leaderboard.py +509 -0
- fi/evals/metrics/code_security/types.py +534 -0
- fi/evals/metrics/function_calling/__init__.py +34 -0
- fi/evals/metrics/function_calling/metrics.py +573 -0
- fi/evals/metrics/function_calling/types.py +87 -0
- fi/evals/metrics/hallucination/__init__.py +54 -0
- fi/evals/metrics/hallucination/detector.py +149 -0
- fi/evals/metrics/hallucination/metrics.py +390 -0
- fi/evals/metrics/hallucination/nli.py +253 -0
- fi/evals/metrics/hallucination/sentinel.py +106 -0
- fi/evals/metrics/hallucination/types.py +132 -0
- fi/evals/metrics/heuristics/aggregation_metrics.py +85 -0
- fi/evals/metrics/heuristics/json_metrics.py +87 -0
- fi/evals/metrics/heuristics/similarity_metrics.py +375 -0
- fi/evals/metrics/heuristics/string_metrics.py +391 -0
- fi/evals/metrics/llm_as_judges/__init__.py +17 -0
- fi/evals/metrics/llm_as_judges/custom_judge/metric.py +112 -0
- fi/evals/metrics/llm_as_judges/custom_judge/prompts.py +26 -0
- fi/evals/metrics/llm_as_judges/types.py +48 -0
- fi/evals/metrics/rag/__init__.py +111 -0
- fi/evals/metrics/rag/advanced/__init__.py +14 -0
- fi/evals/metrics/rag/advanced/multi_hop.py +283 -0
- fi/evals/metrics/rag/advanced/source_attribution.py +344 -0
- fi/evals/metrics/rag/generation/__init__.py +17 -0
- fi/evals/metrics/rag/generation/answer_relevancy.py +176 -0
- fi/evals/metrics/rag/generation/context_utilization.py +245 -0
- fi/evals/metrics/rag/generation/faithfulness.py +241 -0
- fi/evals/metrics/rag/generation/groundedness.py +131 -0
- fi/evals/metrics/rag/rag_score.py +277 -0
- fi/evals/metrics/rag/retrieval/__init__.py +20 -0
- fi/evals/metrics/rag/retrieval/context_entity_recall.py +124 -0
- fi/evals/metrics/rag/retrieval/context_precision.py +158 -0
- fi/evals/metrics/rag/retrieval/context_recall.py +106 -0
- fi/evals/metrics/rag/retrieval/noise_sensitivity.py +163 -0
- fi/evals/metrics/rag/retrieval/ranking.py +261 -0
- fi/evals/metrics/rag/types.py +100 -0
- fi/evals/metrics/rag/utils/__init__.py +62 -0
- fi/evals/metrics/rag/utils/claims.py +189 -0
- fi/evals/metrics/rag/utils/entities.py +244 -0
- fi/evals/metrics/rag/utils/nli.py +92 -0
- fi/evals/metrics/rag/utils/similarity.py +345 -0
- fi/evals/metrics/structured/__init__.py +114 -0
- fi/evals/metrics/structured/field_completeness.py +313 -0
- fi/evals/metrics/structured/hierarchy_score.py +366 -0
- fi/evals/metrics/structured/json_validation.py +190 -0
- fi/evals/metrics/structured/schema_compliance.py +280 -0
- fi/evals/metrics/structured/structured_output_score.py +298 -0
- fi/evals/metrics/structured/types.py +108 -0
- fi/evals/metrics/structured/validators/__init__.py +30 -0
- fi/evals/metrics/structured/validators/base.py +189 -0
- fi/evals/metrics/structured/validators/json_validator.py +196 -0
- fi/evals/metrics/structured/validators/pydantic_validator.py +178 -0
- fi/evals/metrics/structured/validators/yaml_validator.py +248 -0
- fi/evals/otel/__init__.py +266 -0
- fi/evals/otel/config.py +400 -0
- fi/evals/otel/conventions.py +463 -0
- fi/evals/otel/enrichment.py +371 -0
- fi/evals/otel/instrumentors/__init__.py +140 -0
- fi/evals/otel/instrumentors/anthropic.py +517 -0
- fi/evals/otel/instrumentors/base.py +382 -0
- fi/evals/otel/instrumentors/openai.py +673 -0
- fi/evals/otel/processors/__init__.py +36 -0
- fi/evals/otel/processors/base.py +473 -0
- fi/evals/otel/processors/cost.py +445 -0
- fi/evals/otel/processors/evaluation.py +559 -0
- fi/evals/otel/processors/llm.py +462 -0
- fi/evals/otel/tracer.py +506 -0
- fi/evals/otel/types.py +232 -0
- fi/evals/otel_utils.py +23 -0
- fi/evals/protect.py +671 -0
- fi/evals/protect_input_adapter.py +154 -0
- fi/evals/streaming/__init__.py +88 -0
- fi/evals/streaming/buffer.py +213 -0
- fi/evals/streaming/evaluator.py +551 -0
- fi/evals/streaming/policy.py +307 -0
- fi/evals/streaming/scorers.py +368 -0
- fi/evals/streaming/types.py +238 -0
- fi/evals/templates.py +472 -0
- fi/evals/types.py +156 -0
- fi/opt/__init__.py +221 -0
- fi/opt/_objective_scoring.py +85 -0
- fi/opt/base/__init__.py +11 -0
- fi/opt/base/base_generator.py +33 -0
- fi/opt/base/base_mapper.py +26 -0
- fi/opt/base/base_optimizer.py +45 -0
- fi/opt/base/evaluator.py +211 -0
- fi/opt/components.py +3095 -0
- fi/opt/datamappers/__init__.py +3 -0
- fi/opt/datamappers/basic_mapper.py +40 -0
- fi/opt/deployment.py +1021 -0
- fi/opt/evidence.py +4332 -0
- fi/opt/generators/__init__.py +3 -0
- fi/opt/generators/litellm.py +66 -0
- fi/opt/integrations/__init__.py +23 -0
- fi/opt/integrations/generative_suite.py +410 -0
- fi/opt/integrations/simulate.py +1313 -0
- fi/opt/mutations.py +771 -0
- fi/opt/observability.py +4639 -0
- fi/opt/optimizer_trace.py +889 -0
- fi/opt/optimizers/__init__.py +80 -0
- fi/opt/optimizers/agent.py +331 -0
- fi/opt/optimizers/agent_bandit.py +392 -0
- fi/opt/optimizers/agent_curriculum.py +635 -0
- fi/opt/optimizers/agent_evolution.py +894 -0
- fi/opt/optimizers/agent_feedback.py +1863 -0
- fi/opt/optimizers/agent_pareto.py +547 -0
- fi/opt/optimizers/agent_social_memory.py +1113 -0
- fi/opt/optimizers/agent_tpe.py +321 -0
- fi/opt/optimizers/bayesian_search.py +449 -0
- fi/opt/optimizers/council.py +2075 -0
- fi/opt/optimizers/futureagi_replay.py +799 -0
- fi/opt/optimizers/gepa.py +322 -0
- fi/opt/optimizers/metaprompt.py +243 -0
- fi/opt/optimizers/promptwizard.py +417 -0
- fi/opt/optimizers/protegi.py +329 -0
- fi/opt/optimizers/random_search.py +224 -0
- fi/opt/research.py +518 -0
- fi/opt/simulation.py +260 -0
- fi/opt/targets.py +232 -0
- fi/opt/types.py +66 -0
- fi/opt/utils/__init__.py +4 -0
- fi/opt/utils/early_stopping.py +266 -0
- fi/opt/utils/setup_logging.py +82 -0
- fi/simulate/__init__.py +540 -0
- fi/simulate/_hashing.py +35 -0
- fi/simulate/_logging.py +10 -0
- fi/simulate/adapters.py +87 -0
- fi/simulate/agent/__init__.py +120 -0
- fi/simulate/agent/browser.py +658 -0
- fi/simulate/agent/definition.py +587 -0
- fi/simulate/agent/frameworks.py +3528 -0
- fi/simulate/agent/generic.py +8286 -0
- fi/simulate/agent/import_probe.py +227 -0
- fi/simulate/agent/memory.py +905 -0
- fi/simulate/agent/mocks.py +101 -0
- fi/simulate/agent/multi_agent.py +361 -0
- fi/simulate/agent/orchestration.py +903 -0
- fi/simulate/agent/realtime.py +665 -0
- fi/simulate/agent/wrapper.py +99 -0
- fi/simulate/agent/wrappers/__init__.py +18 -0
- fi/simulate/agent/wrappers/anthropic.py +62 -0
- fi/simulate/agent/wrappers/gemini.py +65 -0
- fi/simulate/agent/wrappers/http.py +404 -0
- fi/simulate/agent/wrappers/langchain.py +80 -0
- fi/simulate/agent/wrappers/openai.py +75 -0
- fi/simulate/agent/wrappers/websocket.py +326 -0
- fi/simulate/artifacts/__init__.py +11 -0
- fi/simulate/artifacts/manifest.py +62 -0
- fi/simulate/cli.py +20560 -0
- fi/simulate/endpoints/__init__.py +45 -0
- fi/simulate/endpoints/_http_actor.py +73 -0
- fi/simulate/endpoints/actor_sources.py +243 -0
- fi/simulate/endpoints/base.py +107 -0
- fi/simulate/endpoints/builtins.py +10 -0
- fi/simulate/endpoints/callable.py +95 -0
- fi/simulate/endpoints/http.py +76 -0
- fi/simulate/endpoints/livekit.py +138 -0
- fi/simulate/endpoints/originators.py +132 -0
- fi/simulate/endpoints/profiles.py +348 -0
- fi/simulate/endpoints/retell.py +633 -0
- fi/simulate/endpoints/vapi.py +205 -0
- fi/simulate/endpoints/websocket.py +76 -0
- fi/simulate/environment.py +33026 -0
- fi/simulate/environments/__init__.py +11 -0
- fi/simulate/environments/base.py +73 -0
- fi/simulate/environments/chat.py +697 -0
- fi/simulate/environments/voice.py +212 -0
- fi/simulate/evaluation/__init__.py +4 -0
- fi/simulate/evaluation/ai_eval.py +227 -0
- fi/simulate/evidence/__init__.py +35 -0
- fi/simulate/evidence/base.py +59 -0
- fi/simulate/evidence/caller_observed.py +50 -0
- fi/simulate/evidence/livekit_instrumentation.py +51 -0
- fi/simulate/evidence/livekit_room.py +50 -0
- fi/simulate/evidence/otel.py +49 -0
- fi/simulate/evidence/providers/__init__.py +24 -0
- fi/simulate/evidence/providers/base.py +61 -0
- fi/simulate/evidence/providers/retell.py +376 -0
- fi/simulate/evidence/providers/vapi.py +426 -0
- fi/simulate/hosted/__init__.py +32 -0
- fi/simulate/hosted/child_entrypoint.py +306 -0
- fi/simulate/hosted/job.py +150 -0
- fi/simulate/hosted/targets.py +53 -0
- fi/simulate/instrumentation/__init__.py +5 -0
- fi/simulate/instrumentation/livekit/__init__.py +122 -0
- fi/simulate/manifest.py +1033 -0
- fi/simulate/matrix_cli.py +165 -0
- fi/simulate/realtime/__init__.py +40 -0
- fi/simulate/realtime/events.py +107 -0
- fi/simulate/realtime/media.py +61 -0
- fi/simulate/realtime/session.py +91 -0
- fi/simulate/recording/__init__.py +5 -0
- fi/simulate/recording/room_recorder.py +326 -0
- fi/simulate/registry.py +185 -0
- fi/simulate/results/__init__.py +9 -0
- fi/simulate/results/base.py +18 -0
- fi/simulate/results/filesystem.py +71 -0
- fi/simulate/results/futureagi.py +1340 -0
- fi/simulate/runtime/__init__.py +85 -0
- fi/simulate/runtime/capabilities.py +40 -0
- fi/simulate/runtime/events.py +63 -0
- fi/simulate/runtime/failures.py +25 -0
- fi/simulate/runtime/ids.py +34 -0
- fi/simulate/runtime/plan.py +70 -0
- fi/simulate/runtime/planner.py +102 -0
- fi/simulate/runtime/report.py +174 -0
- fi/simulate/runtime/run.py +75 -0
- fi/simulate/runtime/runner.py +333 -0
- fi/simulate/runtime/spec.py +186 -0
- fi/simulate/simulation/__init__.py +30 -0
- fi/simulate/simulation/behavior_policy.py +425 -0
- fi/simulate/simulation/bridge/__init__.py +9 -0
- fi/simulate/simulation/bridge/audio.py +29 -0
- fi/simulate/simulation/bridge/connector.py +46 -0
- fi/simulate/simulation/bridge/livekit.py +252 -0
- fi/simulate/simulation/bridge/retell.py +188 -0
- fi/simulate/simulation/bridge/vapi.py +177 -0
- fi/simulate/simulation/contract.py +419 -0
- fi/simulate/simulation/engines/__init__.py +12 -0
- fi/simulate/simulation/engines/base.py +21 -0
- fi/simulate/simulation/engines/cloud.py +517 -0
- fi/simulate/simulation/engines/livekit.py +4167 -0
- fi/simulate/simulation/engines/local_text.py +89 -0
- fi/simulate/simulation/fidelity.py +374 -0
- fi/simulate/simulation/gemini_tts_stream.py +110 -0
- fi/simulate/simulation/generator.py +91 -0
- fi/simulate/simulation/goal_machine.py +185 -0
- fi/simulate/simulation/livekit_models.py +467 -0
- fi/simulate/simulation/matrix.py +170 -0
- fi/simulate/simulation/models.py +279 -0
- fi/simulate/simulation/runner.py +153 -0
- fi/simulate/simulation/synthetic.py +880 -0
- fi/simulate/simulation/voice_prompt.py +502 -0
- fi/simulate/simulator/__init__.py +55 -0
- fi/simulate/simulator/builtins.py +53 -0
- fi/simulate/suite.py +1288 -0
- fi/simulate/utils/routes.py +164 -0
- fi/simulate/voice.py +225 -0
- fi/simulate/voice_cli.py +182 -0
- fi/utils/__init__.py +1 -0
- fi/utils/constants.py +14 -0
- fi/utils/errors.py +200 -0
- fi/utils/executor.py +26 -0
- fi/utils/routes.py +119 -0
- fi/utils/utils.py +17 -0
|
@@ -0,0 +1,2075 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import math
|
|
6
|
+
from dataclasses import dataclass, field, replace
|
|
7
|
+
from itertools import combinations
|
|
8
|
+
from typing import Any, Callable, Iterable, List, Mapping, Optional, Sequence
|
|
9
|
+
|
|
10
|
+
from ..base.base_optimizer import BaseOptimizer
|
|
11
|
+
from ..components import ComponentDiagnosis, relevant_search_paths
|
|
12
|
+
from ..targets import AgentCandidate, CandidateEvaluation, OptimizationTarget
|
|
13
|
+
from ..types import EvaluationResult, IterationHistory, OptimizationResult
|
|
14
|
+
from .agent import (
|
|
15
|
+
_dedupe_diagnoses,
|
|
16
|
+
_diagnose_candidate_evaluation,
|
|
17
|
+
_dump_model,
|
|
18
|
+
_history_from_candidate,
|
|
19
|
+
_normalize_candidate_evaluation,
|
|
20
|
+
_normalize_diagnoses,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
logger = logging.getLogger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
DEFAULT_COUNCIL_ROLES = ("explorer", "critic", "synthesizer", "steward")
|
|
27
|
+
SOCIETY_ROLES = (
|
|
28
|
+
"explorer",
|
|
29
|
+
"critic",
|
|
30
|
+
"synthesizer",
|
|
31
|
+
"steward",
|
|
32
|
+
"specialist",
|
|
33
|
+
"adversary",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Phase 4 society vocabulary (scholarly design devices used as deterministic
|
|
37
|
+
# engineering metadata — psychometric/philological grounding only, zero
|
|
38
|
+
# doctrinal claims).
|
|
39
|
+
|
|
40
|
+
GUNA_AXES = ("rajas", "sattva", "tamas")
|
|
41
|
+
|
|
42
|
+
GUNA_ARCHETYPE_DEFAULTS: dict[str, tuple[float, float, float]] = {
|
|
43
|
+
# (rajas, sattva, tamas) — dominant axis per the Phase-4 architecture
|
|
44
|
+
# archetype-default table (canon home; values stay byte-identical).
|
|
45
|
+
"focused_action": (0.8, 0.4, 0.2), # arjuna — explorer
|
|
46
|
+
"prudent_critic": (0.7, 0.5, 0.4), # vidura — adversary
|
|
47
|
+
"orchestrator": (0.5, 0.6, 0.4), # sutradhara
|
|
48
|
+
"working_memory": (0.4, 0.6, 0.5), # smriti
|
|
49
|
+
"bridge_builder": (0.6, 0.5, 0.3), # hanuman
|
|
50
|
+
"charioteer_counsel": (0.3, 0.8, 0.4), # krishna — critic
|
|
51
|
+
"collective_synthesis": (0.2, 0.9, 0.3), # sangha — synthesizer
|
|
52
|
+
"minimal_process_guardian": (0.1, 0.5, 0.9),# dharma_steward — steward
|
|
53
|
+
"": (0.5, 0.5, 0.5),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
CHAMBER_TOKENS = ("samiti", "sabha")
|
|
57
|
+
|
|
58
|
+
# Chambers are ORTHOGONAL to phases/stages: within every phase samiti roles
|
|
59
|
+
# generate widely and sabha roles deliberate/promote — chamber derives from
|
|
60
|
+
# role kind, never from phase.
|
|
61
|
+
SAMITI_PROPOSAL_KINDS = frozenset({"specialist", "explorer", "adversary"})
|
|
62
|
+
SABHA_PROPOSAL_KINDS = frozenset(
|
|
63
|
+
{"critic", "synthesizer", "coverage_synthesis", "steward"}
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
PANCA_AVAYAVA_MEMBERS = (
|
|
67
|
+
# Five-member (panca-avayava) proposal justification — Nyaya-Sutra syllogism
|
|
68
|
+
# structure used as an auditable record schema (Pramana arXiv:2604.04937
|
|
69
|
+
# operationalization precedent). Scholarly design device, not a doctrinal
|
|
70
|
+
# claim.
|
|
71
|
+
"pratijna", # claim: what this patch asserts will improve
|
|
72
|
+
"hetu", # reason: the diagnosis/metric evidence relied on
|
|
73
|
+
"udaharana", # rule + example: the prior candidate/row exhibiting the rule
|
|
74
|
+
"upanaya", # application: why the rule covers THIS candidate
|
|
75
|
+
"nigamana", # conclusion: the expected admissible evidence delta
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
HETVABHASA_REJECTION_CLASSES = (
|
|
79
|
+
"savyabhichara", # inconclusive reason: evidence does not discriminate candidates
|
|
80
|
+
"viruddha", # contradictory reason: evidence contradicts the claim
|
|
81
|
+
"satpratipaksha", # counterbalanced: an equal counter-justification exists
|
|
82
|
+
"asiddha", # unestablished reason: cited evidence/row not found in lineage
|
|
83
|
+
"badhita", # defeated: claim contradicted by a stronger admissible check
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
CRITIQUE_OPERATOR_CLASSES = (
|
|
87
|
+
"vada", # truth-seeking review — critic
|
|
88
|
+
"jalpa", # adversarial stress — adversary; findings admissible only via evidence
|
|
89
|
+
"vitanda", # refutation-only veto pass — may reject, never proposes
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@dataclass(frozen=True)
|
|
94
|
+
class AgentSearchProposal:
|
|
95
|
+
"""One deterministic candidate patch proposed by an agent-search strategy."""
|
|
96
|
+
|
|
97
|
+
patch: dict[str, Any]
|
|
98
|
+
role: str
|
|
99
|
+
parent_ids: tuple[str, ...]
|
|
100
|
+
reason: str
|
|
101
|
+
metadata: Mapping[str, Any] = field(default_factory=dict)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@dataclass(frozen=True)
|
|
105
|
+
class AgentSearchState:
|
|
106
|
+
"""Read-only state passed to a pluggable agent-search strategy."""
|
|
107
|
+
|
|
108
|
+
seed_candidate: AgentCandidate
|
|
109
|
+
evaluations: Sequence[CandidateEvaluation]
|
|
110
|
+
search_space: Mapping[str, List[Any]]
|
|
111
|
+
search_paths: Sequence[str]
|
|
112
|
+
diagnoses: Sequence[ComponentDiagnosis]
|
|
113
|
+
beam_width: int
|
|
114
|
+
max_proposals: int
|
|
115
|
+
round_number: int
|
|
116
|
+
# Phase 4: round-scoped pooled-diagnosis society ledger (GEA experience
|
|
117
|
+
# pooling). None = legacy behavior.
|
|
118
|
+
ledger: Optional[Mapping[str, Any]] = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@dataclass(frozen=True)
|
|
122
|
+
class AgentSocietyRole:
|
|
123
|
+
"""One role node in a deterministic society-search proposal graph."""
|
|
124
|
+
|
|
125
|
+
name: str
|
|
126
|
+
proposal_kind: str
|
|
127
|
+
phase: int = 1
|
|
128
|
+
depends_on: tuple[str, ...] = ()
|
|
129
|
+
path_prefixes: tuple[str, ...] = ()
|
|
130
|
+
archetype: str = ""
|
|
131
|
+
description: str = ""
|
|
132
|
+
# Phase 4 (placed after the legacy fields so positional construction stays
|
|
133
|
+
# byte-compatible): ONE nested optional guna mapping — {"rajas", "sattva",
|
|
134
|
+
# "tamas"} each in [0, 1]; None/absent = derive from archetype defaults —
|
|
135
|
+
# and an optional chamber ("samiti" | "sabha"); None = derive from role
|
|
136
|
+
# kind. No sentinel values.
|
|
137
|
+
guna: Optional[Mapping[str, float]] = None
|
|
138
|
+
chamber: Optional[str] = None
|
|
139
|
+
|
|
140
|
+
def to_metadata(self) -> dict[str, Any]:
|
|
141
|
+
return {
|
|
142
|
+
"name": self.name,
|
|
143
|
+
"proposal_kind": self.proposal_kind,
|
|
144
|
+
"phase": self.phase,
|
|
145
|
+
"depends_on": list(self.depends_on),
|
|
146
|
+
"path_prefixes": list(self.path_prefixes),
|
|
147
|
+
"archetype": self.archetype,
|
|
148
|
+
"description": self.description,
|
|
149
|
+
"guna": dict(self.guna) if self.guna is not None else None,
|
|
150
|
+
"chamber": self.chamber,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
ROLE_GRAPH_PROPOSAL_KINDS = {
|
|
155
|
+
"adversary",
|
|
156
|
+
"coverage_synthesis",
|
|
157
|
+
"critic",
|
|
158
|
+
"explorer",
|
|
159
|
+
"specialist",
|
|
160
|
+
"steward",
|
|
161
|
+
"synthesizer",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
DEFAULT_SOCIETY_ROLE_GRAPH = (
|
|
166
|
+
AgentSocietyRole(
|
|
167
|
+
name="sutradhara",
|
|
168
|
+
proposal_kind="specialist",
|
|
169
|
+
phase=1,
|
|
170
|
+
path_prefixes=("multi_agent", "orchestration", "router", "graph"),
|
|
171
|
+
archetype="orchestrator",
|
|
172
|
+
description="Bundle coordination, routing, handoff, and graph repairs.",
|
|
173
|
+
),
|
|
174
|
+
AgentSocietyRole(
|
|
175
|
+
name="smriti",
|
|
176
|
+
proposal_kind="specialist",
|
|
177
|
+
phase=1,
|
|
178
|
+
path_prefixes=("memory", "retrieval", "retriever"),
|
|
179
|
+
archetype="working_memory",
|
|
180
|
+
description="Bundle memory, retrieval, and retained context repairs.",
|
|
181
|
+
),
|
|
182
|
+
AgentSocietyRole(
|
|
183
|
+
name="arjuna",
|
|
184
|
+
proposal_kind="explorer",
|
|
185
|
+
phase=1,
|
|
186
|
+
archetype="focused_action",
|
|
187
|
+
description="Probe one controllable path at a time under metric feedback.",
|
|
188
|
+
),
|
|
189
|
+
AgentSocietyRole(
|
|
190
|
+
name="hanuman",
|
|
191
|
+
proposal_kind="specialist",
|
|
192
|
+
phase=1,
|
|
193
|
+
path_prefixes=("tools", "framework", "voice", "browser", "cua", "implementation"),
|
|
194
|
+
archetype="bridge_builder",
|
|
195
|
+
description="Bundle tool, framework, world-interface, and runtime repairs.",
|
|
196
|
+
),
|
|
197
|
+
AgentSocietyRole(
|
|
198
|
+
name="vidura",
|
|
199
|
+
proposal_kind="adversary",
|
|
200
|
+
phase=1,
|
|
201
|
+
path_prefixes=("security", "policy", "trust", "environment"),
|
|
202
|
+
archetype="prudent_critic",
|
|
203
|
+
description="Stress policy, security, trust-boundary, and environment choices.",
|
|
204
|
+
),
|
|
205
|
+
AgentSocietyRole(
|
|
206
|
+
name="krishna",
|
|
207
|
+
proposal_kind="critic",
|
|
208
|
+
phase=2,
|
|
209
|
+
depends_on=("arjuna", "sutradhara", "smriti"),
|
|
210
|
+
archetype="charioteer_counsel",
|
|
211
|
+
description="Test one more change against current strong partial candidates.",
|
|
212
|
+
),
|
|
213
|
+
AgentSocietyRole(
|
|
214
|
+
name="sangha",
|
|
215
|
+
proposal_kind="coverage_synthesis",
|
|
216
|
+
phase=2,
|
|
217
|
+
depends_on=("sutradhara", "smriti", "hanuman", "vidura", "arjuna"),
|
|
218
|
+
archetype="collective_synthesis",
|
|
219
|
+
description="Combine best path representatives across role evidence.",
|
|
220
|
+
),
|
|
221
|
+
AgentSocietyRole(
|
|
222
|
+
name="dharma_steward",
|
|
223
|
+
proposal_kind="steward",
|
|
224
|
+
phase=3,
|
|
225
|
+
depends_on=("sangha", "krishna"),
|
|
226
|
+
archetype="minimal_process_guardian",
|
|
227
|
+
description="Remove one change at a time to keep only metric-proven repairs.",
|
|
228
|
+
),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class AgentSearchStrategy:
|
|
233
|
+
"""Proposal-generation strategy for framework-neutral agent optimization."""
|
|
234
|
+
|
|
235
|
+
name = "agent_search_strategy"
|
|
236
|
+
roles: Sequence[str] = ()
|
|
237
|
+
|
|
238
|
+
def propose(self, state: AgentSearchState) -> List[AgentSearchProposal]:
|
|
239
|
+
raise NotImplementedError
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class DeterministicCouncilStrategy(AgentSearchStrategy):
|
|
243
|
+
"""Current council search: explore, critique, synthesize, and steward."""
|
|
244
|
+
|
|
245
|
+
name = "deterministic_council_search"
|
|
246
|
+
roles = DEFAULT_COUNCIL_ROLES
|
|
247
|
+
|
|
248
|
+
def propose(self, state: AgentSearchState) -> List[AgentSearchProposal]:
|
|
249
|
+
return _build_round_proposals(
|
|
250
|
+
seed_candidate=state.seed_candidate,
|
|
251
|
+
evaluations=state.evaluations,
|
|
252
|
+
search_space=dict(state.search_space),
|
|
253
|
+
search_paths=state.search_paths,
|
|
254
|
+
beam_width=state.beam_width,
|
|
255
|
+
max_proposals=state.max_proposals,
|
|
256
|
+
round_number=state.round_number,
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class SocietySearchStrategy(AgentSearchStrategy):
|
|
261
|
+
"""
|
|
262
|
+
Deterministic role-diverse search for multi-interaction agent systems.
|
|
263
|
+
|
|
264
|
+
The strategy keeps metric-bound candidate evaluation, but allocates proposal
|
|
265
|
+
slots across social roles so search can test isolated mutations, component
|
|
266
|
+
bundles, stress combinations, synthesis, critique, and simplification.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
name = "deterministic_society_search"
|
|
270
|
+
roles = SOCIETY_ROLES
|
|
271
|
+
|
|
272
|
+
def propose(self, state: AgentSearchState) -> List[AgentSearchProposal]:
|
|
273
|
+
return _build_society_proposals(
|
|
274
|
+
seed_candidate=state.seed_candidate,
|
|
275
|
+
evaluations=state.evaluations,
|
|
276
|
+
search_space=dict(state.search_space),
|
|
277
|
+
search_paths=state.search_paths,
|
|
278
|
+
diagnoses=state.diagnoses,
|
|
279
|
+
beam_width=state.beam_width,
|
|
280
|
+
max_proposals=state.max_proposals,
|
|
281
|
+
round_number=state.round_number,
|
|
282
|
+
ledger=state.ledger,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class SocietyRoleGraphSearchStrategy(AgentSearchStrategy):
|
|
287
|
+
"""
|
|
288
|
+
Deterministic society search with explicit role graph metadata.
|
|
289
|
+
|
|
290
|
+
Role names and archetypes are inspiration labels only. Candidate acceptance
|
|
291
|
+
still depends entirely on the provided metric/evaluator contract.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
name = "deterministic_role_graph_society_search"
|
|
295
|
+
|
|
296
|
+
def __init__(
|
|
297
|
+
self,
|
|
298
|
+
role_graph: Optional[Sequence[AgentSocietyRole | Mapping[str, Any]]] = None,
|
|
299
|
+
*,
|
|
300
|
+
max_paths_per_proposal: int = 1,
|
|
301
|
+
staged_conditioning: Optional[Mapping[str, Any]] = None,
|
|
302
|
+
) -> None:
|
|
303
|
+
if max_paths_per_proposal < 1:
|
|
304
|
+
raise ValueError("max_paths_per_proposal must be at least 1.")
|
|
305
|
+
self.role_graph = _normalize_society_role_graph(role_graph)
|
|
306
|
+
self.roles = tuple(role.name for role in self.role_graph)
|
|
307
|
+
# Guna patch-radius base: explorer/adversary streams propose
|
|
308
|
+
# max(1, round(rajas * max_paths_per_proposal)) paths per patch. The
|
|
309
|
+
# default of 1 reproduces the legacy single-path radius for every
|
|
310
|
+
# default-archetype triple.
|
|
311
|
+
self.max_paths_per_proposal = max_paths_per_proposal
|
|
312
|
+
# 4C staged conditioning declaration (stage -> phase -> path-class);
|
|
313
|
+
# the strategy EXECUTES stages through role-graph phases — this is the
|
|
314
|
+
# declared map the optimizer trace proves the order from.
|
|
315
|
+
self.staged_conditioning = (
|
|
316
|
+
dict(staged_conditioning) if staged_conditioning is not None else None
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def propose(self, state: AgentSearchState) -> List[AgentSearchProposal]:
|
|
320
|
+
return _build_role_graph_society_proposals(
|
|
321
|
+
seed_candidate=state.seed_candidate,
|
|
322
|
+
evaluations=state.evaluations,
|
|
323
|
+
search_space=dict(state.search_space),
|
|
324
|
+
search_paths=state.search_paths,
|
|
325
|
+
diagnoses=state.diagnoses,
|
|
326
|
+
beam_width=state.beam_width,
|
|
327
|
+
max_proposals=state.max_proposals,
|
|
328
|
+
round_number=state.round_number,
|
|
329
|
+
role_graph=self.role_graph,
|
|
330
|
+
ledger=state.ledger,
|
|
331
|
+
max_paths_per_proposal=self.max_paths_per_proposal,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
def to_metadata(self) -> dict[str, Any]:
|
|
335
|
+
metadata = {
|
|
336
|
+
"role_graph": [role.to_metadata() for role in self.role_graph],
|
|
337
|
+
"role_graph_inspiration": (
|
|
338
|
+
"human social coordination, metacognition, and Hindu mythic "
|
|
339
|
+
"archetypes used only as deterministic proposal metadata"
|
|
340
|
+
),
|
|
341
|
+
"guna_mix": _guna_mix(self.role_graph),
|
|
342
|
+
"chambers": {
|
|
343
|
+
chamber: [
|
|
344
|
+
role.name
|
|
345
|
+
for role in self.role_graph
|
|
346
|
+
if (role.chamber or _chamber_for_proposal_kind(role.proposal_kind))
|
|
347
|
+
== chamber
|
|
348
|
+
]
|
|
349
|
+
for chamber in CHAMBER_TOKENS
|
|
350
|
+
},
|
|
351
|
+
"max_paths_per_proposal": self.max_paths_per_proposal,
|
|
352
|
+
}
|
|
353
|
+
if self.staged_conditioning is not None:
|
|
354
|
+
metadata["staged_conditioning"] = dict(self.staged_conditioning)
|
|
355
|
+
return metadata
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class CouncilAgentOptimizer(BaseOptimizer):
|
|
359
|
+
"""
|
|
360
|
+
Optimizes agent configs with deterministic multi-round social search.
|
|
361
|
+
|
|
362
|
+
`AgentOptimizer` is best when exhaustive candidate enumeration is acceptable.
|
|
363
|
+
This optimizer is intended for multi-interaction agents where useful fixes
|
|
364
|
+
are often combinations of partial changes: one role explores isolated
|
|
365
|
+
mutations, one critiques the current best candidate, one synthesizes strong
|
|
366
|
+
partial candidates, and one steward tests whether combined patches can be
|
|
367
|
+
simplified without losing score.
|
|
368
|
+
"""
|
|
369
|
+
|
|
370
|
+
def __init__(
|
|
371
|
+
self,
|
|
372
|
+
target: Optional[OptimizationTarget] = None,
|
|
373
|
+
*,
|
|
374
|
+
evaluate_candidate: Optional[
|
|
375
|
+
Callable[[AgentCandidate], CandidateEvaluation | EvaluationResult | float]
|
|
376
|
+
] = None,
|
|
377
|
+
simulation_evaluator: Any = None,
|
|
378
|
+
diagnoses: Optional[Iterable[ComponentDiagnosis | dict[str, Any]]] = None,
|
|
379
|
+
max_rounds: int = 3,
|
|
380
|
+
beam_width: int = 4,
|
|
381
|
+
max_proposals_per_round: int = 16,
|
|
382
|
+
target_score: float = 1.0,
|
|
383
|
+
include_seed: bool = True,
|
|
384
|
+
auto_diagnose: bool = True,
|
|
385
|
+
diagnostic_score_threshold: float = 0.85,
|
|
386
|
+
search_strategy: Optional[AgentSearchStrategy | str] = None,
|
|
387
|
+
samiti_budget: Optional[int] = None,
|
|
388
|
+
sabha_budget: Optional[int] = None,
|
|
389
|
+
society_ledger: bool = False,
|
|
390
|
+
social_memory: Optional[Any] = None,
|
|
391
|
+
) -> None:
|
|
392
|
+
if max_rounds < 1:
|
|
393
|
+
raise ValueError("max_rounds must be at least 1.")
|
|
394
|
+
if beam_width < 1:
|
|
395
|
+
raise ValueError("beam_width must be at least 1.")
|
|
396
|
+
if max_proposals_per_round < 1:
|
|
397
|
+
raise ValueError("max_proposals_per_round must be at least 1.")
|
|
398
|
+
_validate_chamber_budgets(samiti_budget, sabha_budget)
|
|
399
|
+
|
|
400
|
+
self.target = target
|
|
401
|
+
self.evaluate_candidate = evaluate_candidate
|
|
402
|
+
self.simulation_evaluator = simulation_evaluator
|
|
403
|
+
self.diagnoses = _normalize_diagnoses(diagnoses)
|
|
404
|
+
self.max_rounds = max_rounds
|
|
405
|
+
self.beam_width = beam_width
|
|
406
|
+
self.max_proposals_per_round = max_proposals_per_round
|
|
407
|
+
self.target_score = target_score
|
|
408
|
+
self.include_seed = include_seed
|
|
409
|
+
self.auto_diagnose = auto_diagnose
|
|
410
|
+
self.diagnostic_score_threshold = diagnostic_score_threshold
|
|
411
|
+
self.search_strategy = _resolve_search_strategy(search_strategy)
|
|
412
|
+
self.samiti_budget = samiti_budget
|
|
413
|
+
self.sabha_budget = sabha_budget
|
|
414
|
+
self.society_ledger = society_ledger
|
|
415
|
+
self.social_memory = social_memory
|
|
416
|
+
super().__init__()
|
|
417
|
+
|
|
418
|
+
def optimize(
|
|
419
|
+
self,
|
|
420
|
+
evaluator: Any = None,
|
|
421
|
+
data_mapper: Any = None,
|
|
422
|
+
dataset: Optional[List[dict[str, Any]]] = None,
|
|
423
|
+
metric: Optional[Callable] = None,
|
|
424
|
+
*,
|
|
425
|
+
target: Optional[OptimizationTarget] = None,
|
|
426
|
+
evaluate_candidate: Optional[
|
|
427
|
+
Callable[[AgentCandidate], CandidateEvaluation | EvaluationResult | float]
|
|
428
|
+
] = None,
|
|
429
|
+
simulation_evaluator: Any = None,
|
|
430
|
+
diagnoses: Optional[Iterable[ComponentDiagnosis | dict[str, Any]]] = None,
|
|
431
|
+
max_rounds: Optional[int] = None,
|
|
432
|
+
beam_width: Optional[int] = None,
|
|
433
|
+
max_proposals_per_round: Optional[int] = None,
|
|
434
|
+
target_score: Optional[float] = None,
|
|
435
|
+
include_seed: Optional[bool] = None,
|
|
436
|
+
auto_diagnose: Optional[bool] = None,
|
|
437
|
+
diagnostic_score_threshold: Optional[float] = None,
|
|
438
|
+
search_strategy: Optional[AgentSearchStrategy | str] = None,
|
|
439
|
+
samiti_budget: Optional[int] = None,
|
|
440
|
+
sabha_budget: Optional[int] = None,
|
|
441
|
+
society_ledger: Optional[bool] = None,
|
|
442
|
+
social_memory: Optional[Any] = None,
|
|
443
|
+
**kwargs: Any,
|
|
444
|
+
) -> OptimizationResult:
|
|
445
|
+
active_target = target or self.target
|
|
446
|
+
if active_target is None:
|
|
447
|
+
raise ValueError("CouncilAgentOptimizer requires a target.")
|
|
448
|
+
|
|
449
|
+
active_evaluator = (
|
|
450
|
+
evaluate_candidate
|
|
451
|
+
or self.evaluate_candidate
|
|
452
|
+
or getattr(simulation_evaluator, "evaluate_candidate", None)
|
|
453
|
+
or getattr(self.simulation_evaluator, "evaluate_candidate", None)
|
|
454
|
+
)
|
|
455
|
+
if active_evaluator is None:
|
|
456
|
+
raise ValueError(
|
|
457
|
+
"CouncilAgentOptimizer requires evaluate_candidate or simulation_evaluator."
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
active_diagnoses = _normalize_diagnoses(diagnoses)
|
|
461
|
+
if diagnoses is None:
|
|
462
|
+
active_diagnoses = list(self.diagnoses)
|
|
463
|
+
active_max_rounds = self.max_rounds if max_rounds is None else max_rounds
|
|
464
|
+
active_beam_width = self.beam_width if beam_width is None else beam_width
|
|
465
|
+
active_max_proposals = (
|
|
466
|
+
self.max_proposals_per_round
|
|
467
|
+
if max_proposals_per_round is None
|
|
468
|
+
else max_proposals_per_round
|
|
469
|
+
)
|
|
470
|
+
if active_max_rounds < 1:
|
|
471
|
+
raise ValueError("max_rounds must be at least 1.")
|
|
472
|
+
if active_beam_width < 1:
|
|
473
|
+
raise ValueError("beam_width must be at least 1.")
|
|
474
|
+
if active_max_proposals < 1:
|
|
475
|
+
raise ValueError("max_proposals_per_round must be at least 1.")
|
|
476
|
+
active_target_score = (
|
|
477
|
+
self.target_score if target_score is None else target_score
|
|
478
|
+
)
|
|
479
|
+
use_include_seed = self.include_seed if include_seed is None else include_seed
|
|
480
|
+
use_auto_diagnose = self.auto_diagnose if auto_diagnose is None else auto_diagnose
|
|
481
|
+
active_diagnostic_threshold = (
|
|
482
|
+
self.diagnostic_score_threshold
|
|
483
|
+
if diagnostic_score_threshold is None
|
|
484
|
+
else diagnostic_score_threshold
|
|
485
|
+
)
|
|
486
|
+
active_search_strategy = (
|
|
487
|
+
self.search_strategy
|
|
488
|
+
if search_strategy is None
|
|
489
|
+
else _resolve_search_strategy(search_strategy)
|
|
490
|
+
)
|
|
491
|
+
active_samiti_budget = (
|
|
492
|
+
self.samiti_budget if samiti_budget is None else samiti_budget
|
|
493
|
+
)
|
|
494
|
+
active_sabha_budget = (
|
|
495
|
+
self.sabha_budget if sabha_budget is None else sabha_budget
|
|
496
|
+
)
|
|
497
|
+
_validate_chamber_budgets(active_samiti_budget, active_sabha_budget)
|
|
498
|
+
use_society_ledger = (
|
|
499
|
+
self.society_ledger if society_ledger is None else bool(society_ledger)
|
|
500
|
+
)
|
|
501
|
+
active_social_memory = (
|
|
502
|
+
self.social_memory if social_memory is None else social_memory
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
seed_candidate = active_target.seed_candidate()
|
|
506
|
+
evaluated: dict[str, CandidateEvaluation] = {}
|
|
507
|
+
history: List[IterationHistory] = []
|
|
508
|
+
role_counts: dict[str, int] = {}
|
|
509
|
+
round_summaries: List[dict[str, Any]] = []
|
|
510
|
+
best: CandidateEvaluation | None = None
|
|
511
|
+
|
|
512
|
+
role_chambers = _strategy_role_chambers(active_search_strategy)
|
|
513
|
+
chamber_budgets = {
|
|
514
|
+
"samiti": active_samiti_budget,
|
|
515
|
+
"sabha": active_sabha_budget,
|
|
516
|
+
}
|
|
517
|
+
chamber_used = {"samiti": 0, "sabha": 0}
|
|
518
|
+
chamber_skipped = {"samiti": 0, "sabha": 0}
|
|
519
|
+
rejections: List[dict[str, Any]] = []
|
|
520
|
+
ledger_rounds: List[dict[str, Any]] = []
|
|
521
|
+
current_ledger: Optional[dict[str, Any]] = None
|
|
522
|
+
persisted_via: Optional[str] = None
|
|
523
|
+
if use_society_ledger and active_social_memory is not None:
|
|
524
|
+
persisted_via = active_social_memory.__class__.__name__
|
|
525
|
+
prior_ledgers = list(
|
|
526
|
+
getattr(active_social_memory, "society_ledgers", None) or []
|
|
527
|
+
)
|
|
528
|
+
prior_diagnoses = [
|
|
529
|
+
dict(item)
|
|
530
|
+
for entry in prior_ledgers
|
|
531
|
+
if isinstance(entry, Mapping)
|
|
532
|
+
for item in entry.get("diagnoses", []) or []
|
|
533
|
+
if isinstance(item, Mapping)
|
|
534
|
+
]
|
|
535
|
+
if prior_diagnoses:
|
|
536
|
+
# Cross-campaign preload: previously persisted ledgers seed
|
|
537
|
+
# round 1 of this campaign (GEA experience pooling).
|
|
538
|
+
current_ledger = {
|
|
539
|
+
"round": 0,
|
|
540
|
+
"diagnoses": prior_diagnoses,
|
|
541
|
+
"pooled_from_candidates": sum(
|
|
542
|
+
int(entry.get("pooled_from_candidates") or 0)
|
|
543
|
+
for entry in prior_ledgers
|
|
544
|
+
if isinstance(entry, Mapping)
|
|
545
|
+
),
|
|
546
|
+
"preloaded": True,
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if use_include_seed:
|
|
550
|
+
seed_evaluation = self._evaluate(
|
|
551
|
+
seed_candidate,
|
|
552
|
+
active_evaluator,
|
|
553
|
+
evaluated,
|
|
554
|
+
history,
|
|
555
|
+
role_counts,
|
|
556
|
+
role="seed",
|
|
557
|
+
round_number=0,
|
|
558
|
+
)
|
|
559
|
+
best = seed_evaluation
|
|
560
|
+
if use_auto_diagnose and not active_diagnoses:
|
|
561
|
+
active_diagnoses = _diagnose_candidate_evaluation(
|
|
562
|
+
seed_evaluation,
|
|
563
|
+
failing_threshold=active_diagnostic_threshold,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
search_paths = _ordered_search_paths(active_target, active_diagnoses)
|
|
567
|
+
if not search_paths:
|
|
568
|
+
raise ValueError("CouncilAgentOptimizer target search space cannot be empty.")
|
|
569
|
+
|
|
570
|
+
for round_number in range(1, active_max_rounds + 1):
|
|
571
|
+
proposals = active_search_strategy.propose(
|
|
572
|
+
AgentSearchState(
|
|
573
|
+
seed_candidate=seed_candidate,
|
|
574
|
+
evaluations=list(evaluated.values()),
|
|
575
|
+
search_space=active_target.search_space,
|
|
576
|
+
search_paths=search_paths,
|
|
577
|
+
diagnoses=active_diagnoses,
|
|
578
|
+
beam_width=active_beam_width,
|
|
579
|
+
max_proposals=active_max_proposals,
|
|
580
|
+
round_number=round_number,
|
|
581
|
+
ledger=current_ledger,
|
|
582
|
+
)
|
|
583
|
+
)
|
|
584
|
+
admitted_proposals: List[AgentSearchProposal] = []
|
|
585
|
+
for proposal in proposals:
|
|
586
|
+
duplicate_id = _candidate_id_for_patch(seed_candidate, proposal.patch)
|
|
587
|
+
if duplicate_id in evaluated:
|
|
588
|
+
rejections.append(
|
|
589
|
+
{
|
|
590
|
+
"round": round_number,
|
|
591
|
+
"role": proposal.role,
|
|
592
|
+
"candidate_id": duplicate_id,
|
|
593
|
+
"rejected": True,
|
|
594
|
+
"hetvabhasa_class": "savyabhichara",
|
|
595
|
+
"detail": (
|
|
596
|
+
"duplicate patch: evidence does not discriminate "
|
|
597
|
+
"from an already-evaluated candidate"
|
|
598
|
+
),
|
|
599
|
+
}
|
|
600
|
+
)
|
|
601
|
+
continue
|
|
602
|
+
admitted_proposals.append(proposal)
|
|
603
|
+
proposals = admitted_proposals
|
|
604
|
+
logger.info(
|
|
605
|
+
"Council round %s evaluating %s proposal(s)",
|
|
606
|
+
round_number,
|
|
607
|
+
len(proposals),
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
round_best = best
|
|
611
|
+
round_evaluated = 0
|
|
612
|
+
round_evaluations: List[CandidateEvaluation] = []
|
|
613
|
+
for proposal in proposals:
|
|
614
|
+
allowed_paths = set(search_paths)
|
|
615
|
+
if proposal.patch and not (set(proposal.patch) & allowed_paths):
|
|
616
|
+
# Locality breach is recorded (asiddha) but not enforced
|
|
617
|
+
# here — promotion-time enforcement is the replay veto's
|
|
618
|
+
# job; in-round evidence must stay visible.
|
|
619
|
+
rejections.append(
|
|
620
|
+
{
|
|
621
|
+
"round": round_number,
|
|
622
|
+
"role": proposal.role,
|
|
623
|
+
"candidate_id": _candidate_id_for_patch(
|
|
624
|
+
seed_candidate, proposal.patch
|
|
625
|
+
),
|
|
626
|
+
"rejected": False,
|
|
627
|
+
"hetvabhasa_class": "asiddha",
|
|
628
|
+
"detail": (
|
|
629
|
+
"patch touches no path inside the diagnosed "
|
|
630
|
+
"search locality"
|
|
631
|
+
),
|
|
632
|
+
}
|
|
633
|
+
)
|
|
634
|
+
chamber = _proposal_chamber(proposal, role_chambers)
|
|
635
|
+
candidate = seed_candidate.with_patch(
|
|
636
|
+
proposal.patch,
|
|
637
|
+
metadata={
|
|
638
|
+
"kind": "council_proposal",
|
|
639
|
+
"optimizer": self.__class__.__name__,
|
|
640
|
+
"proposal_role": proposal.role,
|
|
641
|
+
"proposal_reason": proposal.reason,
|
|
642
|
+
"proposal_round": round_number,
|
|
643
|
+
"proposal_parent_ids": list(proposal.parent_ids),
|
|
644
|
+
"proposal_metadata": dict(proposal.metadata),
|
|
645
|
+
},
|
|
646
|
+
)
|
|
647
|
+
is_new_candidate = candidate.id not in evaluated
|
|
648
|
+
declared_chamber_budget = chamber_budgets.get(chamber)
|
|
649
|
+
if (
|
|
650
|
+
is_new_candidate
|
|
651
|
+
and declared_chamber_budget is not None
|
|
652
|
+
and chamber_used[chamber] >= declared_chamber_budget
|
|
653
|
+
):
|
|
654
|
+
chamber_skipped[chamber] += 1
|
|
655
|
+
continue
|
|
656
|
+
evaluation = self._evaluate(
|
|
657
|
+
candidate,
|
|
658
|
+
active_evaluator,
|
|
659
|
+
evaluated,
|
|
660
|
+
history,
|
|
661
|
+
role_counts,
|
|
662
|
+
role=proposal.role,
|
|
663
|
+
round_number=round_number,
|
|
664
|
+
)
|
|
665
|
+
if is_new_candidate:
|
|
666
|
+
chamber_used[chamber] += 1
|
|
667
|
+
round_evaluations.append(evaluation)
|
|
668
|
+
parent_scores = [
|
|
669
|
+
evaluated[parent_id].score
|
|
670
|
+
for parent_id in proposal.parent_ids
|
|
671
|
+
if parent_id in evaluated and parent_id != candidate.id
|
|
672
|
+
]
|
|
673
|
+
role_kind = str(
|
|
674
|
+
proposal.metadata.get("role_kind") or proposal.role
|
|
675
|
+
)
|
|
676
|
+
if parent_scores and evaluation.score < max(parent_scores):
|
|
677
|
+
rejections.append(
|
|
678
|
+
{
|
|
679
|
+
"round": round_number,
|
|
680
|
+
"role": proposal.role,
|
|
681
|
+
"candidate_id": candidate.id,
|
|
682
|
+
"rejected": True,
|
|
683
|
+
"hetvabhasa_class": "viruddha",
|
|
684
|
+
"detail": (
|
|
685
|
+
f"score {evaluation.score:.4f} regresses parent "
|
|
686
|
+
f"best {max(parent_scores):.4f}"
|
|
687
|
+
),
|
|
688
|
+
}
|
|
689
|
+
)
|
|
690
|
+
elif (
|
|
691
|
+
role_kind == "steward"
|
|
692
|
+
and parent_scores
|
|
693
|
+
and evaluation.score == max(parent_scores)
|
|
694
|
+
):
|
|
695
|
+
rejections.append(
|
|
696
|
+
{
|
|
697
|
+
"round": round_number,
|
|
698
|
+
"role": proposal.role,
|
|
699
|
+
"candidate_id": proposal.parent_ids[0]
|
|
700
|
+
if proposal.parent_ids
|
|
701
|
+
else candidate.id,
|
|
702
|
+
"rejected": True,
|
|
703
|
+
"hetvabhasa_class": "satpratipaksha",
|
|
704
|
+
"detail": (
|
|
705
|
+
"steward removal kept the score unchanged: the "
|
|
706
|
+
"removed change carries an equal counter-"
|
|
707
|
+
"justification"
|
|
708
|
+
),
|
|
709
|
+
}
|
|
710
|
+
)
|
|
711
|
+
if round_best is None or evaluation.score > round_best.score:
|
|
712
|
+
round_best = evaluation
|
|
713
|
+
round_evaluated += 1
|
|
714
|
+
if best is None or evaluation.score > best.score:
|
|
715
|
+
best = evaluation
|
|
716
|
+
logger.info(
|
|
717
|
+
"New best council candidate %s score=%.4f",
|
|
718
|
+
candidate.id,
|
|
719
|
+
evaluation.score,
|
|
720
|
+
)
|
|
721
|
+
if best.score >= active_target_score:
|
|
722
|
+
break
|
|
723
|
+
|
|
724
|
+
if use_society_ledger:
|
|
725
|
+
pooled_diagnoses: List[ComponentDiagnosis] = []
|
|
726
|
+
contributing = 0
|
|
727
|
+
for evaluation in round_evaluations:
|
|
728
|
+
candidate_diagnoses = _diagnose_candidate_evaluation(
|
|
729
|
+
evaluation,
|
|
730
|
+
failing_threshold=active_diagnostic_threshold,
|
|
731
|
+
)
|
|
732
|
+
if candidate_diagnoses:
|
|
733
|
+
contributing += 1
|
|
734
|
+
pooled_diagnoses.extend(candidate_diagnoses)
|
|
735
|
+
round_ledger = {
|
|
736
|
+
"round": round_number,
|
|
737
|
+
"diagnoses": [
|
|
738
|
+
_dump_model(item)
|
|
739
|
+
for item in _dedupe_diagnoses(pooled_diagnoses)
|
|
740
|
+
],
|
|
741
|
+
"pooled_from_candidates": len(round_evaluations),
|
|
742
|
+
"contributing_candidates": contributing,
|
|
743
|
+
}
|
|
744
|
+
ledger_rounds.append(
|
|
745
|
+
{
|
|
746
|
+
"round": round_number,
|
|
747
|
+
"diagnoses_pooled": len(round_ledger["diagnoses"]),
|
|
748
|
+
"pooled_from_candidates": round_ledger[
|
|
749
|
+
"pooled_from_candidates"
|
|
750
|
+
],
|
|
751
|
+
"persisted_via": persisted_via,
|
|
752
|
+
}
|
|
753
|
+
)
|
|
754
|
+
current_ledger = round_ledger
|
|
755
|
+
if active_social_memory is not None:
|
|
756
|
+
society_ledgers = getattr(
|
|
757
|
+
active_social_memory, "society_ledgers", None
|
|
758
|
+
)
|
|
759
|
+
if society_ledgers is None:
|
|
760
|
+
society_ledgers = []
|
|
761
|
+
active_social_memory.society_ledgers = society_ledgers
|
|
762
|
+
society_ledgers.append(dict(round_ledger))
|
|
763
|
+
|
|
764
|
+
if round_best is not None and use_auto_diagnose:
|
|
765
|
+
round_diagnoses = _diagnose_candidate_evaluation(
|
|
766
|
+
round_best,
|
|
767
|
+
failing_threshold=active_diagnostic_threshold,
|
|
768
|
+
)
|
|
769
|
+
if round_diagnoses:
|
|
770
|
+
active_diagnoses = _dedupe_diagnoses(
|
|
771
|
+
[*active_diagnoses, *round_diagnoses]
|
|
772
|
+
)
|
|
773
|
+
search_paths = _ordered_search_paths(active_target, active_diagnoses)
|
|
774
|
+
|
|
775
|
+
round_summaries.append(
|
|
776
|
+
{
|
|
777
|
+
"round": round_number,
|
|
778
|
+
"proposals": len(proposals),
|
|
779
|
+
"evaluated": round_evaluated,
|
|
780
|
+
"best_score": best.score if best is not None else None,
|
|
781
|
+
"search_paths": list(search_paths),
|
|
782
|
+
}
|
|
783
|
+
)
|
|
784
|
+
if best is not None and best.score >= active_target_score:
|
|
785
|
+
break
|
|
786
|
+
|
|
787
|
+
if best is None:
|
|
788
|
+
raise ValueError("CouncilAgentOptimizer did not evaluate any candidates.")
|
|
789
|
+
|
|
790
|
+
strategy_name = getattr(
|
|
791
|
+
active_search_strategy,
|
|
792
|
+
"name",
|
|
793
|
+
active_search_strategy.__class__.__name__,
|
|
794
|
+
)
|
|
795
|
+
strategy_roles = list(getattr(active_search_strategy, "roles", ()))
|
|
796
|
+
metadata = {
|
|
797
|
+
"optimizer": self.__class__.__name__,
|
|
798
|
+
"strategy": strategy_name,
|
|
799
|
+
"roles": strategy_roles,
|
|
800
|
+
"target_name": best.candidate.target_name,
|
|
801
|
+
"best_candidate_id": best.candidate.id,
|
|
802
|
+
"search_paths": list(search_paths),
|
|
803
|
+
"rounds": round_summaries,
|
|
804
|
+
"beam_width": active_beam_width,
|
|
805
|
+
"max_proposals_per_round": active_max_proposals,
|
|
806
|
+
"role_evaluations": role_counts,
|
|
807
|
+
}
|
|
808
|
+
strategy_metadata = _strategy_metadata(active_search_strategy)
|
|
809
|
+
if strategy_metadata:
|
|
810
|
+
metadata["strategy_metadata"] = strategy_metadata
|
|
811
|
+
if "role_graph" in strategy_metadata:
|
|
812
|
+
metadata["role_graph"] = strategy_metadata["role_graph"]
|
|
813
|
+
if "guna_mix" in strategy_metadata:
|
|
814
|
+
metadata["guna_mix"] = strategy_metadata["guna_mix"]
|
|
815
|
+
if active_diagnoses:
|
|
816
|
+
metadata["diagnostics"] = [_dump_model(item) for item in active_diagnoses]
|
|
817
|
+
metadata["auto_diagnosed"] = use_auto_diagnose
|
|
818
|
+
|
|
819
|
+
# Phase 4 society/governance surfaces (additive; ranking comes only
|
|
820
|
+
# from the evaluation suite — external-verification rule).
|
|
821
|
+
metadata["ranking_source"] = "evaluation_suite"
|
|
822
|
+
metadata["chambers"] = {
|
|
823
|
+
chamber: {
|
|
824
|
+
"roles": sorted(
|
|
825
|
+
name
|
|
826
|
+
for name, value in role_chambers.items()
|
|
827
|
+
if value == chamber
|
|
828
|
+
and name in set(strategy_roles) | set(role_counts)
|
|
829
|
+
),
|
|
830
|
+
"declared_budget": chamber_budgets[chamber],
|
|
831
|
+
"evaluations_used": chamber_used[chamber],
|
|
832
|
+
"skipped_proposals": chamber_skipped[chamber],
|
|
833
|
+
}
|
|
834
|
+
for chamber in CHAMBER_TOKENS
|
|
835
|
+
}
|
|
836
|
+
if rejections:
|
|
837
|
+
metadata["rejections"] = rejections
|
|
838
|
+
if ledger_rounds:
|
|
839
|
+
metadata["ledger_rounds"] = ledger_rounds
|
|
840
|
+
metadata["society_ledger"] = True
|
|
841
|
+
metadata["nirnaya"] = [
|
|
842
|
+
{
|
|
843
|
+
"round": round_summaries[-1]["round"] if round_summaries else 1,
|
|
844
|
+
"decision": "promote",
|
|
845
|
+
"selected_candidate_id": best.candidate.id,
|
|
846
|
+
"justification": _justification(
|
|
847
|
+
pratijna=(
|
|
848
|
+
f"candidate {best.candidate.id} is the promotable winner"
|
|
849
|
+
),
|
|
850
|
+
hetu=(
|
|
851
|
+
f"top admissible evaluation score {best.score:.4f} from "
|
|
852
|
+
"the evaluation suite"
|
|
853
|
+
),
|
|
854
|
+
udaharana=(
|
|
855
|
+
"rule: selection is single-lineage — the steward promotes "
|
|
856
|
+
"the top-ranked evidence-backed candidate, never an average"
|
|
857
|
+
),
|
|
858
|
+
upanaya=(
|
|
859
|
+
f"candidate {best.candidate.id} holds the top rank in this "
|
|
860
|
+
"run's lineage"
|
|
861
|
+
),
|
|
862
|
+
nigamana=(
|
|
863
|
+
"promotion is expected to re-close every frozen evidence "
|
|
864
|
+
"row on replay"
|
|
865
|
+
),
|
|
866
|
+
),
|
|
867
|
+
"rejected_alternatives": [
|
|
868
|
+
{
|
|
869
|
+
"candidate_id": rejection.get("candidate_id"),
|
|
870
|
+
"hetvabhasa_class": rejection.get("hetvabhasa_class"),
|
|
871
|
+
}
|
|
872
|
+
for rejection in rejections
|
|
873
|
+
if rejection.get("rejected")
|
|
874
|
+
],
|
|
875
|
+
"replay_verdict": None,
|
|
876
|
+
"admissible_evidence_refs": [best.candidate.id],
|
|
877
|
+
"frozen_rows_closed": None,
|
|
878
|
+
}
|
|
879
|
+
]
|
|
880
|
+
|
|
881
|
+
return OptimizationResult(
|
|
882
|
+
best_generator=best.candidate,
|
|
883
|
+
best_candidate=best.candidate,
|
|
884
|
+
history=history,
|
|
885
|
+
final_score=best.score,
|
|
886
|
+
total_iterations=len(history),
|
|
887
|
+
total_evaluations=len(history),
|
|
888
|
+
metadata=metadata,
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
def _evaluate(
|
|
892
|
+
self,
|
|
893
|
+
candidate: AgentCandidate,
|
|
894
|
+
evaluator: Callable[[AgentCandidate], CandidateEvaluation | EvaluationResult | float],
|
|
895
|
+
evaluated: dict[str, CandidateEvaluation],
|
|
896
|
+
history: List[IterationHistory],
|
|
897
|
+
role_counts: dict[str, int],
|
|
898
|
+
*,
|
|
899
|
+
role: str,
|
|
900
|
+
round_number: int,
|
|
901
|
+
) -> CandidateEvaluation:
|
|
902
|
+
if candidate.id in evaluated:
|
|
903
|
+
return evaluated[candidate.id]
|
|
904
|
+
|
|
905
|
+
value = evaluator(candidate)
|
|
906
|
+
evaluation = _normalize_candidate_evaluation(value, candidate)
|
|
907
|
+
evaluation.metadata = {
|
|
908
|
+
**candidate.metadata,
|
|
909
|
+
**evaluation.metadata,
|
|
910
|
+
"proposal_role": role,
|
|
911
|
+
"proposal_round": round_number,
|
|
912
|
+
}
|
|
913
|
+
evaluated[candidate.id] = evaluation
|
|
914
|
+
history.append(_history_from_candidate(evaluation))
|
|
915
|
+
role_counts[role] = role_counts.get(role, 0) + 1
|
|
916
|
+
return evaluation
|
|
917
|
+
|
|
918
|
+
|
|
919
|
+
class SocietyAgentOptimizer(CouncilAgentOptimizer):
|
|
920
|
+
"""
|
|
921
|
+
Council optimizer preset using role-diverse society search.
|
|
922
|
+
|
|
923
|
+
It is deterministic by default and uses the same `OptimizationTarget` and
|
|
924
|
+
evaluator contracts as `AgentOptimizer`/`CouncilAgentOptimizer`.
|
|
925
|
+
"""
|
|
926
|
+
|
|
927
|
+
def __init__(
|
|
928
|
+
self,
|
|
929
|
+
*args: Any,
|
|
930
|
+
search_strategy: Optional[AgentSearchStrategy | str] = None,
|
|
931
|
+
**kwargs: Any,
|
|
932
|
+
) -> None:
|
|
933
|
+
super().__init__(
|
|
934
|
+
*args,
|
|
935
|
+
search_strategy=search_strategy or SocietySearchStrategy(),
|
|
936
|
+
**kwargs,
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
|
|
940
|
+
def _ordered_search_paths(
|
|
941
|
+
target: OptimizationTarget,
|
|
942
|
+
diagnoses: Sequence[ComponentDiagnosis],
|
|
943
|
+
) -> List[str]:
|
|
944
|
+
allowed_paths = relevant_search_paths(target.search_space, diagnoses)
|
|
945
|
+
return [path for path in target.search_space if path in allowed_paths]
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
def _resolve_search_strategy(
|
|
949
|
+
strategy: Optional[AgentSearchStrategy | str | Mapping[str, Any]],
|
|
950
|
+
) -> AgentSearchStrategy:
|
|
951
|
+
if strategy is None or strategy == "council":
|
|
952
|
+
return DeterministicCouncilStrategy()
|
|
953
|
+
if strategy == "society":
|
|
954
|
+
return SocietySearchStrategy()
|
|
955
|
+
if isinstance(strategy, str) and strategy in {"role_graph", "society_role_graph"}:
|
|
956
|
+
return SocietyRoleGraphSearchStrategy()
|
|
957
|
+
if isinstance(strategy, AgentSearchStrategy):
|
|
958
|
+
return strategy
|
|
959
|
+
if isinstance(strategy, Mapping):
|
|
960
|
+
# Phase 4 (extend-only): JSON-declarable strategy — lets optimization
|
|
961
|
+
# manifests declare a staged role-graph society search without
|
|
962
|
+
# constructing strategy objects.
|
|
963
|
+
token = str(
|
|
964
|
+
strategy.get("strategy")
|
|
965
|
+
or strategy.get("name")
|
|
966
|
+
or strategy.get("type")
|
|
967
|
+
or "role_graph"
|
|
968
|
+
)
|
|
969
|
+
if token not in {"role_graph", "society_role_graph"}:
|
|
970
|
+
return _resolve_search_strategy(token)
|
|
971
|
+
return SocietyRoleGraphSearchStrategy(
|
|
972
|
+
strategy.get("role_graph"),
|
|
973
|
+
max_paths_per_proposal=int(strategy.get("max_paths_per_proposal", 1)),
|
|
974
|
+
staged_conditioning=strategy.get("staged_conditioning"),
|
|
975
|
+
)
|
|
976
|
+
if hasattr(strategy, "propose"):
|
|
977
|
+
return strategy # type: ignore[return-value]
|
|
978
|
+
raise ValueError(
|
|
979
|
+
"search_strategy must be 'council', 'society', 'role_graph', "
|
|
980
|
+
"'society_role_graph', a strategy mapping, or an AgentSearchStrategy."
|
|
981
|
+
)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def _strategy_metadata(strategy: AgentSearchStrategy) -> dict[str, Any]:
|
|
985
|
+
to_metadata = getattr(strategy, "to_metadata", None)
|
|
986
|
+
if callable(to_metadata):
|
|
987
|
+
metadata = to_metadata()
|
|
988
|
+
if isinstance(metadata, Mapping):
|
|
989
|
+
return dict(metadata)
|
|
990
|
+
return {}
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def _validate_chamber_budgets(
|
|
994
|
+
samiti_budget: Optional[int],
|
|
995
|
+
sabha_budget: Optional[int],
|
|
996
|
+
) -> None:
|
|
997
|
+
if samiti_budget is not None and samiti_budget < 1:
|
|
998
|
+
raise ValueError("samiti_budget must be at least 1 when declared.")
|
|
999
|
+
if sabha_budget is not None and sabha_budget < 1:
|
|
1000
|
+
raise ValueError("sabha_budget must be at least 1 when declared.")
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def _strategy_role_chambers(strategy: AgentSearchStrategy) -> dict[str, str]:
|
|
1004
|
+
"""Role name/kind -> chamber map for evaluation attribution."""
|
|
1005
|
+
|
|
1006
|
+
chambers: dict[str, str] = {}
|
|
1007
|
+
role_graph = getattr(strategy, "role_graph", None) or ()
|
|
1008
|
+
for role in role_graph:
|
|
1009
|
+
if isinstance(role, AgentSocietyRole):
|
|
1010
|
+
chambers[role.name] = role.chamber or _chamber_for_proposal_kind(
|
|
1011
|
+
role.proposal_kind
|
|
1012
|
+
)
|
|
1013
|
+
for kind in ROLE_GRAPH_PROPOSAL_KINDS:
|
|
1014
|
+
chambers.setdefault(kind, _chamber_for_proposal_kind(kind))
|
|
1015
|
+
return chambers
|
|
1016
|
+
|
|
1017
|
+
|
|
1018
|
+
def _proposal_chamber(
|
|
1019
|
+
proposal: AgentSearchProposal,
|
|
1020
|
+
role_chambers: Mapping[str, str],
|
|
1021
|
+
) -> str:
|
|
1022
|
+
explicit = proposal.metadata.get("role_chamber")
|
|
1023
|
+
if explicit in CHAMBER_TOKENS:
|
|
1024
|
+
return str(explicit)
|
|
1025
|
+
if proposal.role in role_chambers:
|
|
1026
|
+
return role_chambers[proposal.role]
|
|
1027
|
+
role_kind = str(proposal.metadata.get("role_kind") or proposal.role)
|
|
1028
|
+
return _chamber_for_proposal_kind(role_kind)
|
|
1029
|
+
|
|
1030
|
+
|
|
1031
|
+
def _normalize_society_role_graph(
|
|
1032
|
+
role_graph: Optional[Sequence[AgentSocietyRole | Mapping[str, Any]]],
|
|
1033
|
+
) -> tuple[AgentSocietyRole, ...]:
|
|
1034
|
+
roles: List[AgentSocietyRole] = []
|
|
1035
|
+
for item in role_graph or DEFAULT_SOCIETY_ROLE_GRAPH:
|
|
1036
|
+
if isinstance(item, AgentSocietyRole):
|
|
1037
|
+
role = item
|
|
1038
|
+
elif isinstance(item, Mapping):
|
|
1039
|
+
role = AgentSocietyRole(
|
|
1040
|
+
name=str(item["name"]),
|
|
1041
|
+
proposal_kind=str(item["proposal_kind"]),
|
|
1042
|
+
phase=int(item.get("phase", 1)),
|
|
1043
|
+
depends_on=tuple(str(value) for value in item.get("depends_on", ())),
|
|
1044
|
+
path_prefixes=tuple(
|
|
1045
|
+
str(value) for value in item.get("path_prefixes", ())
|
|
1046
|
+
),
|
|
1047
|
+
archetype=str(item.get("archetype", "")),
|
|
1048
|
+
description=str(item.get("description", "")),
|
|
1049
|
+
guna=item.get("guna"),
|
|
1050
|
+
chamber=item.get("chamber"),
|
|
1051
|
+
)
|
|
1052
|
+
else:
|
|
1053
|
+
raise TypeError("role_graph entries must be AgentSocietyRole or mappings")
|
|
1054
|
+
if role.proposal_kind not in ROLE_GRAPH_PROPOSAL_KINDS:
|
|
1055
|
+
raise ValueError(
|
|
1056
|
+
f"Unsupported society role proposal_kind '{role.proposal_kind}'."
|
|
1057
|
+
)
|
|
1058
|
+
if role.phase < 1:
|
|
1059
|
+
raise ValueError("society role phase must be at least 1.")
|
|
1060
|
+
# Phase 4: resolve absent guna through the archetype-default table,
|
|
1061
|
+
# validate explicit triples, derive absent chamber from role kind.
|
|
1062
|
+
guna = _normalized_guna(role)
|
|
1063
|
+
chamber = role.chamber or _chamber_for_proposal_kind(role.proposal_kind)
|
|
1064
|
+
if chamber not in CHAMBER_TOKENS:
|
|
1065
|
+
raise ValueError(
|
|
1066
|
+
f"society role chamber must be one of {CHAMBER_TOKENS}, "
|
|
1067
|
+
f"got {chamber!r}."
|
|
1068
|
+
)
|
|
1069
|
+
roles.append(replace(role, guna=guna, chamber=chamber))
|
|
1070
|
+
|
|
1071
|
+
names = [role.name for role in roles]
|
|
1072
|
+
if len(names) != len(set(names)):
|
|
1073
|
+
raise ValueError("society role names must be unique.")
|
|
1074
|
+
return tuple(roles)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def _normalized_guna(role: AgentSocietyRole) -> dict[str, float]:
|
|
1078
|
+
if role.guna is None:
|
|
1079
|
+
rajas, sattva, tamas = GUNA_ARCHETYPE_DEFAULTS.get(
|
|
1080
|
+
role.archetype, GUNA_ARCHETYPE_DEFAULTS[""]
|
|
1081
|
+
)
|
|
1082
|
+
return {"rajas": rajas, "sattva": sattva, "tamas": tamas}
|
|
1083
|
+
guna = dict(role.guna)
|
|
1084
|
+
if set(guna) != set(GUNA_AXES):
|
|
1085
|
+
raise ValueError(
|
|
1086
|
+
f"society role guna must declare exactly the axes {GUNA_AXES}, "
|
|
1087
|
+
f"got {sorted(guna)}."
|
|
1088
|
+
)
|
|
1089
|
+
for axis in GUNA_AXES:
|
|
1090
|
+
value = guna[axis]
|
|
1091
|
+
if not isinstance(value, (int, float)) or isinstance(value, bool):
|
|
1092
|
+
raise ValueError(f"society role guna {axis} must be a number in [0, 1].")
|
|
1093
|
+
if not 0.0 <= float(value) <= 1.0:
|
|
1094
|
+
raise ValueError(
|
|
1095
|
+
f"society role guna {axis} must be in [0, 1], got {value!r}."
|
|
1096
|
+
)
|
|
1097
|
+
return {axis: float(guna[axis]) for axis in GUNA_AXES}
|
|
1098
|
+
|
|
1099
|
+
|
|
1100
|
+
def _chamber_for_proposal_kind(proposal_kind: str) -> str:
|
|
1101
|
+
return "samiti" if proposal_kind in SAMITI_PROPOSAL_KINDS else "sabha"
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
def _guna_mix(role_graph: Sequence[AgentSocietyRole]) -> dict[str, float]:
|
|
1105
|
+
"""Society mean guna triple — the declared, tunable meta-parameter."""
|
|
1106
|
+
|
|
1107
|
+
resolved = [_normalized_guna(role) for role in role_graph]
|
|
1108
|
+
if not resolved:
|
|
1109
|
+
return {axis: 0.0 for axis in GUNA_AXES}
|
|
1110
|
+
return {
|
|
1111
|
+
axis: round(sum(item[axis] for item in resolved) / len(resolved), 4)
|
|
1112
|
+
for axis in GUNA_AXES
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
|
|
1116
|
+
def _guna_radius(rajas: float, max_paths_per_proposal: int) -> int:
|
|
1117
|
+
"""Mechanical rajas mapping: patch-radius units for generative streams."""
|
|
1118
|
+
|
|
1119
|
+
return max(1, round(rajas * max_paths_per_proposal))
|
|
1120
|
+
|
|
1121
|
+
|
|
1122
|
+
def _validate_justification(justification: Mapping[str, Any]) -> dict[str, str]:
|
|
1123
|
+
"""Reject panca-avayava mappings missing any member or carrying empties."""
|
|
1124
|
+
|
|
1125
|
+
if not isinstance(justification, Mapping):
|
|
1126
|
+
raise ValueError("proposal justification must be a mapping.")
|
|
1127
|
+
record: dict[str, str] = {}
|
|
1128
|
+
for member in PANCA_AVAYAVA_MEMBERS:
|
|
1129
|
+
value = str(justification.get(member) or "").strip()
|
|
1130
|
+
if not value:
|
|
1131
|
+
raise ValueError(
|
|
1132
|
+
f"proposal justification is missing a non-empty '{member}' member."
|
|
1133
|
+
)
|
|
1134
|
+
record[member] = value
|
|
1135
|
+
return record
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
def _justification(
|
|
1139
|
+
*,
|
|
1140
|
+
pratijna: str,
|
|
1141
|
+
hetu: str,
|
|
1142
|
+
udaharana: str,
|
|
1143
|
+
upanaya: str,
|
|
1144
|
+
nigamana: str,
|
|
1145
|
+
) -> dict[str, str]:
|
|
1146
|
+
return {
|
|
1147
|
+
"pratijna": pratijna,
|
|
1148
|
+
"hetu": hetu,
|
|
1149
|
+
"udaharana": udaharana,
|
|
1150
|
+
"upanaya": upanaya,
|
|
1151
|
+
"nigamana": nigamana,
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
def _ledger_diagnoses(
|
|
1156
|
+
ledger: Optional[Mapping[str, Any]],
|
|
1157
|
+
) -> List[ComponentDiagnosis]:
|
|
1158
|
+
if not ledger:
|
|
1159
|
+
return []
|
|
1160
|
+
return _normalize_diagnoses(
|
|
1161
|
+
item
|
|
1162
|
+
for item in ledger.get("diagnoses", []) or []
|
|
1163
|
+
if isinstance(item, (Mapping, ComponentDiagnosis))
|
|
1164
|
+
)
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
def _build_round_proposals(
|
|
1168
|
+
*,
|
|
1169
|
+
seed_candidate: AgentCandidate,
|
|
1170
|
+
evaluations: Sequence[CandidateEvaluation],
|
|
1171
|
+
search_space: dict[str, List[Any]],
|
|
1172
|
+
search_paths: Sequence[str],
|
|
1173
|
+
beam_width: int,
|
|
1174
|
+
max_proposals: int,
|
|
1175
|
+
round_number: int,
|
|
1176
|
+
) -> List[AgentSearchProposal]:
|
|
1177
|
+
proposals: List[AgentSearchProposal] = []
|
|
1178
|
+
seen: set[str] = set()
|
|
1179
|
+
ranked = sorted(
|
|
1180
|
+
evaluations,
|
|
1181
|
+
key=lambda item: (item.score, -len(item.candidate.patch), item.candidate.id),
|
|
1182
|
+
reverse=True,
|
|
1183
|
+
)
|
|
1184
|
+
changed_ranked = [item for item in ranked if item.candidate.patch]
|
|
1185
|
+
beam = ranked[:beam_width] or [
|
|
1186
|
+
CandidateEvaluation(candidate=seed_candidate, score=0.0)
|
|
1187
|
+
]
|
|
1188
|
+
|
|
1189
|
+
if round_number > 1:
|
|
1190
|
+
for proposal in _synthesis_proposals(changed_ranked[:beam_width], search_paths):
|
|
1191
|
+
_append_proposal(proposals, seen, proposal, max_proposals)
|
|
1192
|
+
|
|
1193
|
+
if round_number > 1:
|
|
1194
|
+
for evaluation in beam:
|
|
1195
|
+
if not evaluation.candidate.patch:
|
|
1196
|
+
continue
|
|
1197
|
+
for proposal in _critic_proposals(
|
|
1198
|
+
evaluation.candidate,
|
|
1199
|
+
search_space,
|
|
1200
|
+
search_paths,
|
|
1201
|
+
):
|
|
1202
|
+
_append_proposal(proposals, seen, proposal, max_proposals)
|
|
1203
|
+
|
|
1204
|
+
for proposal in _explorer_proposals(seed_candidate, search_space, search_paths):
|
|
1205
|
+
_append_proposal(proposals, seen, proposal, max_proposals)
|
|
1206
|
+
|
|
1207
|
+
if round_number > 1:
|
|
1208
|
+
for evaluation in changed_ranked[:beam_width]:
|
|
1209
|
+
for proposal in _steward_proposals(evaluation.candidate):
|
|
1210
|
+
_append_proposal(proposals, seen, proposal, max_proposals)
|
|
1211
|
+
|
|
1212
|
+
return proposals
|
|
1213
|
+
|
|
1214
|
+
|
|
1215
|
+
def _build_society_proposals(
|
|
1216
|
+
*,
|
|
1217
|
+
seed_candidate: AgentCandidate,
|
|
1218
|
+
evaluations: Sequence[CandidateEvaluation],
|
|
1219
|
+
search_space: dict[str, List[Any]],
|
|
1220
|
+
search_paths: Sequence[str],
|
|
1221
|
+
diagnoses: Sequence[ComponentDiagnosis],
|
|
1222
|
+
beam_width: int,
|
|
1223
|
+
max_proposals: int,
|
|
1224
|
+
round_number: int,
|
|
1225
|
+
ledger: Optional[Mapping[str, Any]] = None,
|
|
1226
|
+
) -> List[AgentSearchProposal]:
|
|
1227
|
+
ranked = sorted(
|
|
1228
|
+
evaluations,
|
|
1229
|
+
key=lambda item: (item.score, -len(item.candidate.patch), item.candidate.id),
|
|
1230
|
+
reverse=True,
|
|
1231
|
+
)
|
|
1232
|
+
changed_ranked = [item for item in ranked if item.candidate.patch]
|
|
1233
|
+
beam = ranked[:beam_width] or [
|
|
1234
|
+
CandidateEvaluation(candidate=seed_candidate, score=0.0)
|
|
1235
|
+
]
|
|
1236
|
+
pooled_diagnoses = list(diagnoses)
|
|
1237
|
+
ledger_diagnoses = _ledger_diagnoses(ledger)
|
|
1238
|
+
if ledger_diagnoses:
|
|
1239
|
+
# GEA experience pooling: no role reasons only from its own
|
|
1240
|
+
# candidate's diagnoses — the round-scoped society ledger joins in.
|
|
1241
|
+
pooled_diagnoses = _dedupe_diagnoses([*pooled_diagnoses, *ledger_diagnoses])
|
|
1242
|
+
|
|
1243
|
+
streams: List[Iterable[AgentSearchProposal]] = []
|
|
1244
|
+
if round_number > 1:
|
|
1245
|
+
streams.append(_coverage_synthesis_proposals(changed_ranked, search_paths))
|
|
1246
|
+
streams.append(_synthesis_proposals(changed_ranked[:beam_width], search_paths))
|
|
1247
|
+
streams.append(
|
|
1248
|
+
proposal
|
|
1249
|
+
for evaluation in beam
|
|
1250
|
+
if evaluation.candidate.patch
|
|
1251
|
+
for proposal in _critic_proposals(
|
|
1252
|
+
evaluation.candidate,
|
|
1253
|
+
search_space,
|
|
1254
|
+
search_paths,
|
|
1255
|
+
)
|
|
1256
|
+
)
|
|
1257
|
+
|
|
1258
|
+
streams.extend(
|
|
1259
|
+
[
|
|
1260
|
+
_specialist_proposals(
|
|
1261
|
+
seed_candidate,
|
|
1262
|
+
search_space,
|
|
1263
|
+
search_paths,
|
|
1264
|
+
pooled_diagnoses,
|
|
1265
|
+
),
|
|
1266
|
+
_explorer_proposals(seed_candidate, search_space, search_paths),
|
|
1267
|
+
_adversary_proposals(
|
|
1268
|
+
seed_candidate,
|
|
1269
|
+
ranked[:beam_width],
|
|
1270
|
+
search_space,
|
|
1271
|
+
search_paths,
|
|
1272
|
+
),
|
|
1273
|
+
]
|
|
1274
|
+
)
|
|
1275
|
+
|
|
1276
|
+
if round_number > 1:
|
|
1277
|
+
streams.append(
|
|
1278
|
+
proposal
|
|
1279
|
+
for evaluation in changed_ranked[:beam_width]
|
|
1280
|
+
for proposal in _steward_proposals(evaluation.candidate)
|
|
1281
|
+
)
|
|
1282
|
+
|
|
1283
|
+
return _interleave_proposal_streams(streams, max_proposals)
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
def _build_role_graph_society_proposals(
|
|
1287
|
+
*,
|
|
1288
|
+
seed_candidate: AgentCandidate,
|
|
1289
|
+
evaluations: Sequence[CandidateEvaluation],
|
|
1290
|
+
search_space: dict[str, List[Any]],
|
|
1291
|
+
search_paths: Sequence[str],
|
|
1292
|
+
diagnoses: Sequence[ComponentDiagnosis],
|
|
1293
|
+
beam_width: int,
|
|
1294
|
+
max_proposals: int,
|
|
1295
|
+
round_number: int,
|
|
1296
|
+
role_graph: Sequence[AgentSocietyRole],
|
|
1297
|
+
ledger: Optional[Mapping[str, Any]] = None,
|
|
1298
|
+
max_paths_per_proposal: int = 1,
|
|
1299
|
+
) -> List[AgentSearchProposal]:
|
|
1300
|
+
ranked = sorted(
|
|
1301
|
+
evaluations,
|
|
1302
|
+
key=lambda item: (item.score, -len(item.candidate.patch), item.candidate.id),
|
|
1303
|
+
reverse=True,
|
|
1304
|
+
)
|
|
1305
|
+
changed_ranked = [item for item in ranked if item.candidate.patch]
|
|
1306
|
+
beam = ranked[:beam_width] or [
|
|
1307
|
+
CandidateEvaluation(candidate=seed_candidate, score=0.0)
|
|
1308
|
+
]
|
|
1309
|
+
evaluated_roles = {
|
|
1310
|
+
str(evaluation.metadata.get("proposal_role"))
|
|
1311
|
+
for evaluation in evaluations
|
|
1312
|
+
if evaluation.metadata.get("proposal_role")
|
|
1313
|
+
}
|
|
1314
|
+
pooled_diagnoses = list(diagnoses)
|
|
1315
|
+
ledger_diagnoses = _ledger_diagnoses(ledger)
|
|
1316
|
+
if ledger_diagnoses:
|
|
1317
|
+
pooled_diagnoses = _dedupe_diagnoses([*pooled_diagnoses, *ledger_diagnoses])
|
|
1318
|
+
|
|
1319
|
+
streams: List[Iterable[AgentSearchProposal]] = []
|
|
1320
|
+
for role in _ordered_role_graph_roles(role_graph, round_number):
|
|
1321
|
+
if not _society_role_is_active(role, evaluated_roles, round_number):
|
|
1322
|
+
continue
|
|
1323
|
+
role_paths = _role_search_paths(role, search_paths)
|
|
1324
|
+
if role.proposal_kind != "steward" and not role_paths:
|
|
1325
|
+
continue
|
|
1326
|
+
stream = _role_graph_stream(
|
|
1327
|
+
role,
|
|
1328
|
+
seed_candidate=seed_candidate,
|
|
1329
|
+
ranked=ranked,
|
|
1330
|
+
changed_ranked=changed_ranked,
|
|
1331
|
+
beam=beam,
|
|
1332
|
+
search_space=search_space,
|
|
1333
|
+
search_paths=role_paths,
|
|
1334
|
+
diagnoses=pooled_diagnoses,
|
|
1335
|
+
beam_width=beam_width,
|
|
1336
|
+
round_number=round_number,
|
|
1337
|
+
max_paths_per_proposal=max_paths_per_proposal,
|
|
1338
|
+
)
|
|
1339
|
+
streams.append(stream)
|
|
1340
|
+
|
|
1341
|
+
return _interleave_proposal_streams(streams, max_proposals)
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
def _ordered_role_graph_roles(
|
|
1345
|
+
role_graph: Sequence[AgentSocietyRole],
|
|
1346
|
+
round_number: int,
|
|
1347
|
+
) -> List[AgentSocietyRole]:
|
|
1348
|
+
if round_number <= 1:
|
|
1349
|
+
return list(role_graph)
|
|
1350
|
+
priority = {
|
|
1351
|
+
"coverage_synthesis": 0,
|
|
1352
|
+
"synthesizer": 0,
|
|
1353
|
+
"critic": 1,
|
|
1354
|
+
"adversary": 2,
|
|
1355
|
+
"specialist": 2,
|
|
1356
|
+
"explorer": 2,
|
|
1357
|
+
"steward": 3,
|
|
1358
|
+
}
|
|
1359
|
+
return [
|
|
1360
|
+
role
|
|
1361
|
+
for _, role in sorted(
|
|
1362
|
+
enumerate(role_graph),
|
|
1363
|
+
key=lambda item: (priority.get(item[1].proposal_kind, 2), item[0]),
|
|
1364
|
+
)
|
|
1365
|
+
]
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
def _society_role_is_active(
|
|
1369
|
+
role: AgentSocietyRole,
|
|
1370
|
+
evaluated_roles: set[str],
|
|
1371
|
+
round_number: int,
|
|
1372
|
+
) -> bool:
|
|
1373
|
+
if role.phase > round_number:
|
|
1374
|
+
return False
|
|
1375
|
+
if not role.depends_on:
|
|
1376
|
+
return True
|
|
1377
|
+
return bool(set(role.depends_on) & evaluated_roles)
|
|
1378
|
+
|
|
1379
|
+
|
|
1380
|
+
def _role_graph_stream(
|
|
1381
|
+
role: AgentSocietyRole,
|
|
1382
|
+
*,
|
|
1383
|
+
seed_candidate: AgentCandidate,
|
|
1384
|
+
ranked: Sequence[CandidateEvaluation],
|
|
1385
|
+
changed_ranked: Sequence[CandidateEvaluation],
|
|
1386
|
+
beam: Sequence[CandidateEvaluation],
|
|
1387
|
+
search_space: dict[str, List[Any]],
|
|
1388
|
+
search_paths: Sequence[str],
|
|
1389
|
+
diagnoses: Sequence[ComponentDiagnosis],
|
|
1390
|
+
beam_width: int,
|
|
1391
|
+
round_number: int,
|
|
1392
|
+
max_paths_per_proposal: int = 1,
|
|
1393
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1394
|
+
# Deterministic guna behavioral mappings (pure functions of the resolved
|
|
1395
|
+
# triple): rajas scales generative patch radius, sattva scales synthesis
|
|
1396
|
+
# breadth / reconciliation, tamas scales steward removal aggressiveness.
|
|
1397
|
+
guna = _normalized_guna(role)
|
|
1398
|
+
radius_units = _guna_radius(guna["rajas"], max_paths_per_proposal)
|
|
1399
|
+
if role.proposal_kind == "specialist":
|
|
1400
|
+
proposals = _specialist_proposals(
|
|
1401
|
+
seed_candidate,
|
|
1402
|
+
search_space,
|
|
1403
|
+
search_paths,
|
|
1404
|
+
diagnoses,
|
|
1405
|
+
)
|
|
1406
|
+
elif role.proposal_kind == "explorer":
|
|
1407
|
+
proposals = _explorer_proposals(
|
|
1408
|
+
seed_candidate,
|
|
1409
|
+
search_space,
|
|
1410
|
+
search_paths,
|
|
1411
|
+
max_paths=radius_units,
|
|
1412
|
+
)
|
|
1413
|
+
elif role.proposal_kind == "adversary":
|
|
1414
|
+
proposals = _adversary_proposals(
|
|
1415
|
+
seed_candidate,
|
|
1416
|
+
ranked[:beam_width],
|
|
1417
|
+
search_space,
|
|
1418
|
+
search_paths,
|
|
1419
|
+
max_boundary_paths=3 * radius_units,
|
|
1420
|
+
)
|
|
1421
|
+
elif role.proposal_kind == "critic" and round_number > 1:
|
|
1422
|
+
proposals = (
|
|
1423
|
+
proposal
|
|
1424
|
+
for evaluation in beam
|
|
1425
|
+
if evaluation.candidate.patch
|
|
1426
|
+
for proposal in _critic_proposals(
|
|
1427
|
+
evaluation.candidate,
|
|
1428
|
+
search_space,
|
|
1429
|
+
search_paths,
|
|
1430
|
+
)
|
|
1431
|
+
)
|
|
1432
|
+
elif role.proposal_kind == "coverage_synthesis" and round_number > 1:
|
|
1433
|
+
proposals = _coverage_synthesis_proposals(
|
|
1434
|
+
changed_ranked,
|
|
1435
|
+
search_paths,
|
|
1436
|
+
reconcile=guna["sattva"] >= 0.5,
|
|
1437
|
+
)
|
|
1438
|
+
elif role.proposal_kind == "synthesizer" and round_number > 1:
|
|
1439
|
+
breadth = max(1, round(guna["sattva"] * beam_width))
|
|
1440
|
+
proposals = _synthesis_proposals(changed_ranked[:breadth], search_paths)
|
|
1441
|
+
elif role.proposal_kind == "steward" and round_number > 1:
|
|
1442
|
+
allowed = set(search_paths)
|
|
1443
|
+
proposals = (
|
|
1444
|
+
proposal
|
|
1445
|
+
for evaluation in changed_ranked[:beam_width]
|
|
1446
|
+
for proposal in _steward_proposals(evaluation.candidate, tamas=guna["tamas"])
|
|
1447
|
+
if not allowed or allowed & set(proposal.patch)
|
|
1448
|
+
)
|
|
1449
|
+
else:
|
|
1450
|
+
proposals = ()
|
|
1451
|
+
|
|
1452
|
+
return _annotate_role_graph_proposals(role, proposals)
|
|
1453
|
+
|
|
1454
|
+
|
|
1455
|
+
def _annotate_role_graph_proposals(
|
|
1456
|
+
role: AgentSocietyRole,
|
|
1457
|
+
proposals: Iterable[AgentSearchProposal],
|
|
1458
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1459
|
+
role_guna = _normalized_guna(role)
|
|
1460
|
+
role_chamber = role.chamber or _chamber_for_proposal_kind(role.proposal_kind)
|
|
1461
|
+
for proposal in proposals:
|
|
1462
|
+
metadata = {
|
|
1463
|
+
**dict(proposal.metadata),
|
|
1464
|
+
"role_kind": role.proposal_kind,
|
|
1465
|
+
"role_phase": role.phase,
|
|
1466
|
+
"role_archetype": role.archetype,
|
|
1467
|
+
"role_description": role.description,
|
|
1468
|
+
"role_path_prefixes": list(role.path_prefixes),
|
|
1469
|
+
"role_depends_on": list(role.depends_on),
|
|
1470
|
+
"role_guna": dict(role_guna),
|
|
1471
|
+
"role_chamber": role_chamber,
|
|
1472
|
+
}
|
|
1473
|
+
yield AgentSearchProposal(
|
|
1474
|
+
patch=proposal.patch,
|
|
1475
|
+
role=role.name,
|
|
1476
|
+
parent_ids=proposal.parent_ids,
|
|
1477
|
+
reason=f"{role.proposal_kind}:{proposal.reason}",
|
|
1478
|
+
metadata=metadata,
|
|
1479
|
+
)
|
|
1480
|
+
|
|
1481
|
+
|
|
1482
|
+
def _role_search_paths(
|
|
1483
|
+
role: AgentSocietyRole,
|
|
1484
|
+
search_paths: Sequence[str],
|
|
1485
|
+
) -> List[str]:
|
|
1486
|
+
if not role.path_prefixes:
|
|
1487
|
+
return list(search_paths)
|
|
1488
|
+
return [
|
|
1489
|
+
path
|
|
1490
|
+
for path in search_paths
|
|
1491
|
+
if any(path == prefix or path.startswith(f"{prefix}.") for prefix in role.path_prefixes)
|
|
1492
|
+
]
|
|
1493
|
+
|
|
1494
|
+
|
|
1495
|
+
def _interleave_proposal_streams(
|
|
1496
|
+
streams: Sequence[Iterable[AgentSearchProposal]],
|
|
1497
|
+
max_proposals: int,
|
|
1498
|
+
) -> List[AgentSearchProposal]:
|
|
1499
|
+
proposals: List[AgentSearchProposal] = []
|
|
1500
|
+
seen: set[str] = set()
|
|
1501
|
+
iterators = [iter(stream) for stream in streams]
|
|
1502
|
+
active = [True for _ in iterators]
|
|
1503
|
+
|
|
1504
|
+
while len(proposals) < max_proposals and any(active):
|
|
1505
|
+
for index, iterator in enumerate(iterators):
|
|
1506
|
+
if not active[index]:
|
|
1507
|
+
continue
|
|
1508
|
+
while True:
|
|
1509
|
+
try:
|
|
1510
|
+
proposal = next(iterator)
|
|
1511
|
+
except StopIteration:
|
|
1512
|
+
active[index] = False
|
|
1513
|
+
break
|
|
1514
|
+
before = len(proposals)
|
|
1515
|
+
_append_proposal(proposals, seen, proposal, max_proposals)
|
|
1516
|
+
if len(proposals) > before:
|
|
1517
|
+
break
|
|
1518
|
+
if len(proposals) >= max_proposals:
|
|
1519
|
+
break
|
|
1520
|
+
if len(proposals) >= max_proposals:
|
|
1521
|
+
break
|
|
1522
|
+
return proposals
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
def _specialist_proposals(
|
|
1526
|
+
seed_candidate: AgentCandidate,
|
|
1527
|
+
search_space: dict[str, List[Any]],
|
|
1528
|
+
search_paths: Sequence[str],
|
|
1529
|
+
diagnoses: Sequence[ComponentDiagnosis],
|
|
1530
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1531
|
+
target_name = seed_candidate.target_name or "the optimization target"
|
|
1532
|
+
for group_key, paths in _path_groups(search_paths, diagnoses).items():
|
|
1533
|
+
patch: dict[str, Any] = {}
|
|
1534
|
+
for path in paths:
|
|
1535
|
+
value = _first_non_seed_value(seed_candidate, search_space, path)
|
|
1536
|
+
if value is not _NO_VALUE:
|
|
1537
|
+
patch[path] = value
|
|
1538
|
+
if not patch:
|
|
1539
|
+
continue
|
|
1540
|
+
evidence = "; ".join(
|
|
1541
|
+
diagnosis.evidence
|
|
1542
|
+
for diagnosis in diagnoses
|
|
1543
|
+
if diagnosis.evidence
|
|
1544
|
+
and _diagnostic_group_key(sorted(patch)[0], [diagnosis])
|
|
1545
|
+
) or f"component grouping over declared search paths {sorted(patch)}"
|
|
1546
|
+
yield AgentSearchProposal(
|
|
1547
|
+
patch=patch,
|
|
1548
|
+
role="specialist",
|
|
1549
|
+
parent_ids=(seed_candidate.id,),
|
|
1550
|
+
reason=f"apply_component_bundle:{group_key}",
|
|
1551
|
+
metadata={
|
|
1552
|
+
"justification": _justification(
|
|
1553
|
+
pratijna=(
|
|
1554
|
+
f"bundling component '{group_key}' repairs improves {target_name}"
|
|
1555
|
+
),
|
|
1556
|
+
hetu=evidence,
|
|
1557
|
+
udaharana=(
|
|
1558
|
+
f"seed candidate {seed_candidate.id} exhibits the diagnosed "
|
|
1559
|
+
f"component state; rule: diagnosed components are repaired as one bundle"
|
|
1560
|
+
),
|
|
1561
|
+
upanaya=(
|
|
1562
|
+
f"this candidate patches exactly the '{group_key}' paths "
|
|
1563
|
+
f"{sorted(patch)}"
|
|
1564
|
+
),
|
|
1565
|
+
nigamana=(
|
|
1566
|
+
"expect the diagnosed-component metrics to close on the "
|
|
1567
|
+
"next admissible evaluation"
|
|
1568
|
+
),
|
|
1569
|
+
)
|
|
1570
|
+
},
|
|
1571
|
+
)
|
|
1572
|
+
|
|
1573
|
+
|
|
1574
|
+
def _adversary_proposals(
|
|
1575
|
+
seed_candidate: AgentCandidate,
|
|
1576
|
+
ranked: Sequence[CandidateEvaluation],
|
|
1577
|
+
search_space: dict[str, List[Any]],
|
|
1578
|
+
search_paths: Sequence[str],
|
|
1579
|
+
max_boundary_paths: int = 3,
|
|
1580
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1581
|
+
target_name = seed_candidate.target_name or "the optimization target"
|
|
1582
|
+
boundary_patch: dict[str, Any] = {}
|
|
1583
|
+
for path in search_paths:
|
|
1584
|
+
value = _last_non_seed_value(seed_candidate, search_space, path)
|
|
1585
|
+
if value is not _NO_VALUE:
|
|
1586
|
+
boundary_patch[path] = value
|
|
1587
|
+
if len(boundary_patch) >= max_boundary_paths:
|
|
1588
|
+
break
|
|
1589
|
+
if boundary_patch:
|
|
1590
|
+
yield AgentSearchProposal(
|
|
1591
|
+
patch=boundary_patch,
|
|
1592
|
+
role="adversary",
|
|
1593
|
+
parent_ids=(seed_candidate.id,),
|
|
1594
|
+
reason="stress_boundary_combination",
|
|
1595
|
+
metadata={
|
|
1596
|
+
"justification": _justification(
|
|
1597
|
+
pratijna=(
|
|
1598
|
+
f"a boundary-value combination stresses {target_name} "
|
|
1599
|
+
"into revealing brittle settings"
|
|
1600
|
+
),
|
|
1601
|
+
hetu=(
|
|
1602
|
+
"search space declares boundary values on paths "
|
|
1603
|
+
f"{sorted(boundary_patch)}"
|
|
1604
|
+
),
|
|
1605
|
+
udaharana=(
|
|
1606
|
+
f"seed candidate {seed_candidate.id} holds interior values; "
|
|
1607
|
+
"rule: adversarial probes test the declared extremes"
|
|
1608
|
+
),
|
|
1609
|
+
upanaya=(
|
|
1610
|
+
"this candidate combines the last non-seed value of each "
|
|
1611
|
+
"boundary path in one patch"
|
|
1612
|
+
),
|
|
1613
|
+
nigamana=(
|
|
1614
|
+
"expect either a robustness confirmation or an admissible "
|
|
1615
|
+
"failure signal at the boundary"
|
|
1616
|
+
),
|
|
1617
|
+
)
|
|
1618
|
+
},
|
|
1619
|
+
)
|
|
1620
|
+
|
|
1621
|
+
for evaluation in ranked:
|
|
1622
|
+
source_patch = dict(evaluation.candidate.patch)
|
|
1623
|
+
if not source_patch:
|
|
1624
|
+
continue
|
|
1625
|
+
for path in search_paths:
|
|
1626
|
+
value = _last_non_seed_value(evaluation.candidate, search_space, path)
|
|
1627
|
+
if value is _NO_VALUE:
|
|
1628
|
+
continue
|
|
1629
|
+
patch = {**source_patch, path: value}
|
|
1630
|
+
yield AgentSearchProposal(
|
|
1631
|
+
patch=patch,
|
|
1632
|
+
role="adversary",
|
|
1633
|
+
parent_ids=(evaluation.candidate.id,),
|
|
1634
|
+
reason="stress_candidate_with_boundary_change",
|
|
1635
|
+
metadata={
|
|
1636
|
+
"justification": _justification(
|
|
1637
|
+
pratijna=(
|
|
1638
|
+
f"candidate {evaluation.candidate.id} should survive a "
|
|
1639
|
+
f"boundary change on {path}"
|
|
1640
|
+
),
|
|
1641
|
+
hetu=(
|
|
1642
|
+
f"candidate {evaluation.candidate.id} scored "
|
|
1643
|
+
f"{evaluation.score:.4f} with patch {sorted(source_patch)}"
|
|
1644
|
+
),
|
|
1645
|
+
udaharana=(
|
|
1646
|
+
"rule: strong candidates are stress-tested with one "
|
|
1647
|
+
"additional boundary value before promotion"
|
|
1648
|
+
),
|
|
1649
|
+
upanaya=(
|
|
1650
|
+
f"this candidate keeps the parent patch and sets {path} "
|
|
1651
|
+
"to its boundary value"
|
|
1652
|
+
),
|
|
1653
|
+
nigamana=(
|
|
1654
|
+
"expect a measurable score delta isolating the boundary "
|
|
1655
|
+
"sensitivity of the parent"
|
|
1656
|
+
),
|
|
1657
|
+
)
|
|
1658
|
+
},
|
|
1659
|
+
)
|
|
1660
|
+
|
|
1661
|
+
|
|
1662
|
+
def _path_groups(
|
|
1663
|
+
search_paths: Sequence[str],
|
|
1664
|
+
diagnoses: Sequence[ComponentDiagnosis],
|
|
1665
|
+
) -> dict[str, List[str]]:
|
|
1666
|
+
groups: dict[str, List[str]] = {}
|
|
1667
|
+
for path in search_paths:
|
|
1668
|
+
group_key = _diagnostic_group_key(path, diagnoses) or path.split(".", 1)[0]
|
|
1669
|
+
groups.setdefault(group_key, []).append(path)
|
|
1670
|
+
return groups
|
|
1671
|
+
|
|
1672
|
+
|
|
1673
|
+
def _diagnostic_group_key(
|
|
1674
|
+
path: str,
|
|
1675
|
+
diagnoses: Sequence[ComponentDiagnosis],
|
|
1676
|
+
) -> Optional[str]:
|
|
1677
|
+
for diagnosis in diagnoses:
|
|
1678
|
+
for suggested_path in diagnosis.suggested_paths:
|
|
1679
|
+
if path == suggested_path or path.startswith(f"{suggested_path}."):
|
|
1680
|
+
return f"{diagnosis.component}:{suggested_path}"
|
|
1681
|
+
if path == diagnosis.component or path.startswith(f"{diagnosis.component}."):
|
|
1682
|
+
return diagnosis.component
|
|
1683
|
+
return None
|
|
1684
|
+
|
|
1685
|
+
|
|
1686
|
+
class _NoValue:
|
|
1687
|
+
pass
|
|
1688
|
+
|
|
1689
|
+
|
|
1690
|
+
_NO_VALUE = _NoValue()
|
|
1691
|
+
|
|
1692
|
+
|
|
1693
|
+
def _first_non_seed_value(
|
|
1694
|
+
seed_candidate: AgentCandidate,
|
|
1695
|
+
search_space: dict[str, List[Any]],
|
|
1696
|
+
path: str,
|
|
1697
|
+
) -> Any:
|
|
1698
|
+
current = seed_candidate.get_path(path)
|
|
1699
|
+
for value in search_space.get(path, []):
|
|
1700
|
+
if value != current:
|
|
1701
|
+
return value
|
|
1702
|
+
return _NO_VALUE
|
|
1703
|
+
|
|
1704
|
+
|
|
1705
|
+
def _last_non_seed_value(
|
|
1706
|
+
candidate: AgentCandidate,
|
|
1707
|
+
search_space: dict[str, List[Any]],
|
|
1708
|
+
path: str,
|
|
1709
|
+
) -> Any:
|
|
1710
|
+
current = candidate.get_path(path)
|
|
1711
|
+
for value in reversed(search_space.get(path, [])):
|
|
1712
|
+
if value != current:
|
|
1713
|
+
return value
|
|
1714
|
+
return _NO_VALUE
|
|
1715
|
+
|
|
1716
|
+
|
|
1717
|
+
def _synthesis_proposals(
|
|
1718
|
+
evaluations: Sequence[CandidateEvaluation],
|
|
1719
|
+
search_paths: Sequence[str],
|
|
1720
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1721
|
+
if len(evaluations) < 2:
|
|
1722
|
+
return
|
|
1723
|
+
|
|
1724
|
+
allowed = set(search_paths)
|
|
1725
|
+
all_sources = tuple(evaluations)
|
|
1726
|
+
yield AgentSearchProposal(
|
|
1727
|
+
patch=_merge_ranked_patches(all_sources, allowed),
|
|
1728
|
+
role="synthesizer",
|
|
1729
|
+
parent_ids=tuple(item.candidate.id for item in all_sources),
|
|
1730
|
+
reason="combine_best_partial_candidates",
|
|
1731
|
+
metadata={
|
|
1732
|
+
"justification": _justification(
|
|
1733
|
+
pratijna="merging the strongest partial candidates compounds their gains",
|
|
1734
|
+
hetu=(
|
|
1735
|
+
"evaluated parents "
|
|
1736
|
+
f"{[item.candidate.id for item in all_sources]} each improved "
|
|
1737
|
+
"disjoint or compatible paths"
|
|
1738
|
+
),
|
|
1739
|
+
udaharana=(
|
|
1740
|
+
"rule: compatible partial repairs are merged rank-first so the "
|
|
1741
|
+
"strongest parent wins conflicting paths"
|
|
1742
|
+
),
|
|
1743
|
+
upanaya="this candidate is the rank-first merge of every parent patch",
|
|
1744
|
+
nigamana=(
|
|
1745
|
+
"expect a combined score at or above the best parent on the "
|
|
1746
|
+
"next admissible evaluation"
|
|
1747
|
+
),
|
|
1748
|
+
)
|
|
1749
|
+
},
|
|
1750
|
+
)
|
|
1751
|
+
for left, right in combinations(evaluations, 2):
|
|
1752
|
+
yield AgentSearchProposal(
|
|
1753
|
+
patch=_merge_ranked_patches((left, right), allowed),
|
|
1754
|
+
role="synthesizer",
|
|
1755
|
+
parent_ids=(left.candidate.id, right.candidate.id),
|
|
1756
|
+
reason="combine_pairwise_partial_candidates",
|
|
1757
|
+
metadata={
|
|
1758
|
+
"justification": _justification(
|
|
1759
|
+
pratijna=(
|
|
1760
|
+
f"the pair {left.candidate.id} + {right.candidate.id} "
|
|
1761
|
+
"combines compatible repairs"
|
|
1762
|
+
),
|
|
1763
|
+
hetu=(
|
|
1764
|
+
f"parents scored {left.score:.4f} and {right.score:.4f} on "
|
|
1765
|
+
"admissible evaluations"
|
|
1766
|
+
),
|
|
1767
|
+
udaharana=(
|
|
1768
|
+
"rule: pairwise merges isolate which parent combination "
|
|
1769
|
+
"carries the gain"
|
|
1770
|
+
),
|
|
1771
|
+
upanaya="this candidate merges exactly the two parent patches",
|
|
1772
|
+
nigamana=(
|
|
1773
|
+
"expect the pairwise merge to attribute the combined gain "
|
|
1774
|
+
"on the next admissible evaluation"
|
|
1775
|
+
),
|
|
1776
|
+
)
|
|
1777
|
+
},
|
|
1778
|
+
)
|
|
1779
|
+
|
|
1780
|
+
|
|
1781
|
+
def _coverage_synthesis_proposals(
|
|
1782
|
+
evaluations: Sequence[CandidateEvaluation],
|
|
1783
|
+
search_paths: Sequence[str],
|
|
1784
|
+
*,
|
|
1785
|
+
reconcile: bool = True,
|
|
1786
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1787
|
+
if not evaluations:
|
|
1788
|
+
return
|
|
1789
|
+
|
|
1790
|
+
allowed = set(search_paths)
|
|
1791
|
+
patch: dict[str, Any] = {}
|
|
1792
|
+
parent_ids: List[str] = []
|
|
1793
|
+
for path in search_paths:
|
|
1794
|
+
path_evaluations = [
|
|
1795
|
+
evaluation
|
|
1796
|
+
for evaluation in evaluations
|
|
1797
|
+
if path in evaluation.candidate.patch and path in allowed
|
|
1798
|
+
]
|
|
1799
|
+
if not path_evaluations:
|
|
1800
|
+
continue
|
|
1801
|
+
if not reconcile:
|
|
1802
|
+
# Low-sattva synthesis skips conflicting paths instead of
|
|
1803
|
+
# reconciling them (deterministic guna mapping; default-archetype
|
|
1804
|
+
# sattva >= 0.5 keeps the legacy reconciliation).
|
|
1805
|
+
distinct_values = {
|
|
1806
|
+
json.dumps(
|
|
1807
|
+
evaluation.candidate.patch[path], sort_keys=True, default=str
|
|
1808
|
+
)
|
|
1809
|
+
for evaluation in path_evaluations
|
|
1810
|
+
}
|
|
1811
|
+
if len(distinct_values) > 1:
|
|
1812
|
+
continue
|
|
1813
|
+
selected = max(
|
|
1814
|
+
path_evaluations,
|
|
1815
|
+
key=lambda item: (
|
|
1816
|
+
item.score,
|
|
1817
|
+
-len(item.candidate.patch),
|
|
1818
|
+
item.candidate.id,
|
|
1819
|
+
),
|
|
1820
|
+
)
|
|
1821
|
+
patch[path] = selected.candidate.patch[path]
|
|
1822
|
+
parent_ids.append(selected.candidate.id)
|
|
1823
|
+
|
|
1824
|
+
if patch:
|
|
1825
|
+
yield AgentSearchProposal(
|
|
1826
|
+
patch=patch,
|
|
1827
|
+
role="synthesizer",
|
|
1828
|
+
parent_ids=tuple(dict.fromkeys(parent_ids)),
|
|
1829
|
+
reason="combine_best_path_representatives",
|
|
1830
|
+
metadata={
|
|
1831
|
+
"justification": _justification(
|
|
1832
|
+
pratijna=(
|
|
1833
|
+
"selecting the best representative per path covers the "
|
|
1834
|
+
"whole repaired surface"
|
|
1835
|
+
),
|
|
1836
|
+
hetu=(
|
|
1837
|
+
f"per-path winners {sorted(set(parent_ids))} carry the "
|
|
1838
|
+
"highest admissible score for their path"
|
|
1839
|
+
),
|
|
1840
|
+
udaharana=(
|
|
1841
|
+
"rule: coverage synthesis promotes each path's best "
|
|
1842
|
+
"evidence-backed value"
|
|
1843
|
+
),
|
|
1844
|
+
upanaya=(
|
|
1845
|
+
f"this candidate sets {sorted(patch)} to their per-path "
|
|
1846
|
+
"winning values"
|
|
1847
|
+
),
|
|
1848
|
+
nigamana=(
|
|
1849
|
+
"expect coverage of every repaired path without losing "
|
|
1850
|
+
"any single-path gain"
|
|
1851
|
+
),
|
|
1852
|
+
)
|
|
1853
|
+
},
|
|
1854
|
+
)
|
|
1855
|
+
|
|
1856
|
+
|
|
1857
|
+
def _critic_proposals(
|
|
1858
|
+
source_candidate: AgentCandidate,
|
|
1859
|
+
search_space: dict[str, List[Any]],
|
|
1860
|
+
search_paths: Sequence[str],
|
|
1861
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1862
|
+
source_patch = dict(source_candidate.patch)
|
|
1863
|
+
for path in search_paths:
|
|
1864
|
+
for value in search_space.get(path, []):
|
|
1865
|
+
if source_candidate.get_path(path) == value:
|
|
1866
|
+
continue
|
|
1867
|
+
patch = {**source_patch, path: value}
|
|
1868
|
+
yield AgentSearchProposal(
|
|
1869
|
+
patch=patch,
|
|
1870
|
+
role="critic",
|
|
1871
|
+
parent_ids=(source_candidate.id,),
|
|
1872
|
+
reason="test_next_change_against_current_candidate",
|
|
1873
|
+
metadata={
|
|
1874
|
+
"justification": _justification(
|
|
1875
|
+
pratijna=(
|
|
1876
|
+
f"candidate {source_candidate.id} improves further with "
|
|
1877
|
+
f"{path} changed"
|
|
1878
|
+
),
|
|
1879
|
+
hetu=(
|
|
1880
|
+
f"parent patch {sorted(source_patch)} passed an "
|
|
1881
|
+
"admissible evaluation and the search space declares "
|
|
1882
|
+
f"another value for {path}"
|
|
1883
|
+
),
|
|
1884
|
+
udaharana=(
|
|
1885
|
+
"rule: critics test exactly one more change against the "
|
|
1886
|
+
"current strong candidate"
|
|
1887
|
+
),
|
|
1888
|
+
upanaya=(
|
|
1889
|
+
f"this candidate keeps the parent patch and sets {path} "
|
|
1890
|
+
"to the next declared value"
|
|
1891
|
+
),
|
|
1892
|
+
nigamana=(
|
|
1893
|
+
f"expect the evaluation to confirm or refute {path} as "
|
|
1894
|
+
"the next improving change"
|
|
1895
|
+
),
|
|
1896
|
+
)
|
|
1897
|
+
},
|
|
1898
|
+
)
|
|
1899
|
+
|
|
1900
|
+
|
|
1901
|
+
def _explorer_proposals(
|
|
1902
|
+
seed_candidate: AgentCandidate,
|
|
1903
|
+
search_space: dict[str, List[Any]],
|
|
1904
|
+
search_paths: Sequence[str],
|
|
1905
|
+
max_paths: int = 1,
|
|
1906
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1907
|
+
target_name = seed_candidate.target_name or "the optimization target"
|
|
1908
|
+
for path in search_paths:
|
|
1909
|
+
for value in search_space.get(path, []):
|
|
1910
|
+
if seed_candidate.get_path(path) == value:
|
|
1911
|
+
continue
|
|
1912
|
+
yield AgentSearchProposal(
|
|
1913
|
+
patch={path: value},
|
|
1914
|
+
role="explorer",
|
|
1915
|
+
parent_ids=(seed_candidate.id,),
|
|
1916
|
+
reason="isolate_single_path_effect",
|
|
1917
|
+
metadata={
|
|
1918
|
+
"justification": _justification(
|
|
1919
|
+
pratijna=f"setting {path} improves {target_name}",
|
|
1920
|
+
hetu=(
|
|
1921
|
+
"the declared search space lists an untested value "
|
|
1922
|
+
f"for {path}"
|
|
1923
|
+
),
|
|
1924
|
+
udaharana=(
|
|
1925
|
+
f"seed candidate {seed_candidate.id} holds "
|
|
1926
|
+
f"{seed_candidate.get_path(path)!r} on {path}; rule: "
|
|
1927
|
+
"isolated single-path probes attribute metric deltas"
|
|
1928
|
+
),
|
|
1929
|
+
upanaya=(
|
|
1930
|
+
f"this candidate patches only {path}, so any score "
|
|
1931
|
+
"delta is attributable to it"
|
|
1932
|
+
),
|
|
1933
|
+
nigamana=(
|
|
1934
|
+
f"expect an admissible evaluation-score delta for {path}"
|
|
1935
|
+
),
|
|
1936
|
+
)
|
|
1937
|
+
},
|
|
1938
|
+
)
|
|
1939
|
+
if max_paths > 1:
|
|
1940
|
+
# Rajas-widened exploration: deterministic sliding windows of adjacent
|
|
1941
|
+
# admissible paths, each set to its first non-seed value. max_paths == 1
|
|
1942
|
+
# (the default radius for every default-archetype triple) skips this
|
|
1943
|
+
# block entirely, preserving legacy proposals byte-for-byte.
|
|
1944
|
+
paths = list(search_paths)
|
|
1945
|
+
for start in range(len(paths)):
|
|
1946
|
+
window = paths[start : start + max_paths]
|
|
1947
|
+
if len(window) < 2:
|
|
1948
|
+
continue
|
|
1949
|
+
patch: dict[str, Any] = {}
|
|
1950
|
+
for path in window:
|
|
1951
|
+
value = _first_non_seed_value(seed_candidate, search_space, path)
|
|
1952
|
+
if value is not _NO_VALUE:
|
|
1953
|
+
patch[path] = value
|
|
1954
|
+
if len(patch) < 2:
|
|
1955
|
+
continue
|
|
1956
|
+
yield AgentSearchProposal(
|
|
1957
|
+
patch=patch,
|
|
1958
|
+
role="explorer",
|
|
1959
|
+
parent_ids=(seed_candidate.id,),
|
|
1960
|
+
reason="explore_adjacent_path_window",
|
|
1961
|
+
metadata={
|
|
1962
|
+
"justification": _justification(
|
|
1963
|
+
pratijna=(
|
|
1964
|
+
f"jointly setting {sorted(patch)} improves {target_name}"
|
|
1965
|
+
),
|
|
1966
|
+
hetu=(
|
|
1967
|
+
"high-rajas exploration widens the mutation radius over "
|
|
1968
|
+
"adjacent admissible paths"
|
|
1969
|
+
),
|
|
1970
|
+
udaharana=(
|
|
1971
|
+
f"seed candidate {seed_candidate.id} holds the seed "
|
|
1972
|
+
"values; rule: widened probes test interacting paths "
|
|
1973
|
+
"together"
|
|
1974
|
+
),
|
|
1975
|
+
upanaya=(
|
|
1976
|
+
f"this candidate patches the adjacent window {sorted(patch)}"
|
|
1977
|
+
),
|
|
1978
|
+
nigamana=(
|
|
1979
|
+
"expect an admissible evaluation delta attributable to "
|
|
1980
|
+
"the window"
|
|
1981
|
+
),
|
|
1982
|
+
)
|
|
1983
|
+
},
|
|
1984
|
+
)
|
|
1985
|
+
|
|
1986
|
+
|
|
1987
|
+
def _steward_proposals(
|
|
1988
|
+
source_candidate: AgentCandidate,
|
|
1989
|
+
*,
|
|
1990
|
+
tamas: Optional[float] = None,
|
|
1991
|
+
) -> Iterable[AgentSearchProposal]:
|
|
1992
|
+
if len(source_candidate.patch) < 2:
|
|
1993
|
+
return
|
|
1994
|
+
patch_paths = list(source_candidate.patch)
|
|
1995
|
+
if tamas is None:
|
|
1996
|
+
removal_limit = len(patch_paths)
|
|
1997
|
+
else:
|
|
1998
|
+
# Tamas mapping: removal attempts per round scale with the steward's
|
|
1999
|
+
# tamas (ceil keeps every default-archetype triple at full coverage
|
|
2000
|
+
# for the patch sizes the deterministic fixtures use).
|
|
2001
|
+
removal_limit = max(1, math.ceil(float(tamas) * len(patch_paths)))
|
|
2002
|
+
for path in patch_paths[:removal_limit]:
|
|
2003
|
+
patch = {
|
|
2004
|
+
key: value
|
|
2005
|
+
for key, value in source_candidate.patch.items()
|
|
2006
|
+
if key != path
|
|
2007
|
+
}
|
|
2008
|
+
yield AgentSearchProposal(
|
|
2009
|
+
patch=patch,
|
|
2010
|
+
role="steward",
|
|
2011
|
+
parent_ids=(source_candidate.id,),
|
|
2012
|
+
reason="remove_one_change_to_check_minimality",
|
|
2013
|
+
metadata={
|
|
2014
|
+
"justification": _justification(
|
|
2015
|
+
pratijna=(
|
|
2016
|
+
f"candidate {source_candidate.id} keeps its score without "
|
|
2017
|
+
f"the change on {path}"
|
|
2018
|
+
),
|
|
2019
|
+
hetu=(
|
|
2020
|
+
f"parent patch {sorted(source_candidate.patch)} passed an "
|
|
2021
|
+
"admissible evaluation with multiple combined changes"
|
|
2022
|
+
),
|
|
2023
|
+
udaharana=(
|
|
2024
|
+
"rule: stewards remove one change at a time so only "
|
|
2025
|
+
"metric-proven repairs survive"
|
|
2026
|
+
),
|
|
2027
|
+
upanaya=(
|
|
2028
|
+
f"this candidate is the parent patch minus {path} and "
|
|
2029
|
+
"nothing else"
|
|
2030
|
+
),
|
|
2031
|
+
nigamana=(
|
|
2032
|
+
f"expect an equal score if {path} was unnecessary, or a "
|
|
2033
|
+
"regression proving it was load-bearing"
|
|
2034
|
+
),
|
|
2035
|
+
)
|
|
2036
|
+
},
|
|
2037
|
+
)
|
|
2038
|
+
|
|
2039
|
+
|
|
2040
|
+
def _merge_ranked_patches(
|
|
2041
|
+
evaluations: Sequence[CandidateEvaluation],
|
|
2042
|
+
allowed_paths: set[str],
|
|
2043
|
+
) -> dict[str, Any]:
|
|
2044
|
+
patch: dict[str, Any] = {}
|
|
2045
|
+
for evaluation in evaluations:
|
|
2046
|
+
for path, value in evaluation.candidate.patch.items():
|
|
2047
|
+
if path in allowed_paths and path not in patch:
|
|
2048
|
+
patch[path] = value
|
|
2049
|
+
return patch
|
|
2050
|
+
|
|
2051
|
+
|
|
2052
|
+
def _append_proposal(
|
|
2053
|
+
proposals: List[AgentSearchProposal],
|
|
2054
|
+
seen: set[str],
|
|
2055
|
+
proposal: AgentSearchProposal,
|
|
2056
|
+
max_proposals: int,
|
|
2057
|
+
) -> None:
|
|
2058
|
+
if len(proposals) >= max_proposals or not proposal.patch:
|
|
2059
|
+
return
|
|
2060
|
+
key = _canonical_patch(proposal.patch)
|
|
2061
|
+
if key in seen:
|
|
2062
|
+
return
|
|
2063
|
+
seen.add(key)
|
|
2064
|
+
proposals.append(proposal)
|
|
2065
|
+
|
|
2066
|
+
|
|
2067
|
+
def _candidate_id_for_patch(
|
|
2068
|
+
seed_candidate: AgentCandidate,
|
|
2069
|
+
patch: dict[str, Any],
|
|
2070
|
+
) -> str:
|
|
2071
|
+
return seed_candidate.with_patch(patch).id
|
|
2072
|
+
|
|
2073
|
+
|
|
2074
|
+
def _canonical_patch(patch: dict[str, Any]) -> str:
|
|
2075
|
+
return json.dumps(patch, sort_keys=True, default=str)
|