ilc-core 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.
- ilc_core/agent.py +273 -0
- ilc_core/analysis/agent_descriptors.py +93 -0
- ilc_core/analysis/agent_dossier_export.py +110 -0
- ilc_core/analysis/agent_profiles.py +214 -0
- ilc_core/analysis/claim_scores.py +99 -0
- ilc_core/analysis/competency_kpis.py +173 -0
- ilc_core/analysis/econ_accounting.py +77 -0
- ilc_core/analysis/econ_kpis.py +238 -0
- ilc_core/analysis/embedding_pipeline.py +464 -0
- ilc_core/analysis/epistemic_code.py +67 -0
- ilc_core/analysis/epoch_report_export.py +104 -0
- ilc_core/analysis/fairness_metrics.py +344 -0
- ilc_core/analysis/freshness_gate.py +107 -0
- ilc_core/analysis/genesis_accrual_governor.py +224 -0
- ilc_core/analysis/governance_weight.py +226 -0
- ilc_core/analysis/graph_kpis.py +131 -0
- ilc_core/analysis/laplacian_analytics.py +407 -0
- ilc_core/analysis/light_cone_kpis.py +131 -0
- ilc_core/analysis/local_spectral_analytics.py +131 -0
- ilc_core/analysis/namespace_health.py +130 -0
- ilc_core/analysis/node_value_conformance.py +89 -0
- ilc_core/analysis/node_value_extraction.py +90 -0
- ilc_core/analysis/node_value_governance_conformance.py +309 -0
- ilc_core/analysis/node_value_input_canon.py +317 -0
- ilc_core/analysis/node_value_kernel.py +334 -0
- ilc_core/analysis/node_value_policy_migration.py +134 -0
- ilc_core/analysis/path_lift_counterfactual.py +210 -0
- ilc_core/analysis/problem_space_kpis.py +106 -0
- ilc_core/analysis/reuse_diversity_invariants.py +156 -0
- ilc_core/analysis/routed_tasks.py +190 -0
- ilc_core/analysis/routed_tasks_export.py +58 -0
- ilc_core/analysis/spectral_trajectory.py +167 -0
- ilc_core/analysis/spectral_utils.py +184 -0
- ilc_core/analysis/stress_and_cohesion.py +108 -0
- ilc_core/analysis/stress_response_kpis.py +137 -0
- ilc_core/analysis/task_routing_suggestions.py +190 -0
- ilc_core/analysis/utility_flow_rewards.py +403 -0
- ilc_core/asgi.py +6 -0
- ilc_core/cli/_cli_error.py +38 -0
- ilc_core/cli/_key_utils.py +24 -0
- ilc_core/cli/canon_bundle_key_registry.py +348 -0
- ilc_core/cli/canon_bundle_key_registry_bundle.py +198 -0
- ilc_core/cli/canon_bundle_key_registry_channel.py +155 -0
- ilc_core/cli/canon_bundle_key_registry_channel_sign.py +82 -0
- ilc_core/cli/canon_bundle_key_registry_channel_verify.py +89 -0
- ilc_core/cli/canon_bundle_key_registry_fetch.py +144 -0
- ilc_core/cli/canon_bundle_key_registry_promotion.py +137 -0
- ilc_core/cli/canon_bundle_key_registry_sync.py +325 -0
- ilc_core/cli/canon_bundle_pipeline.py +238 -0
- ilc_core/cli/canon_bundle_replay.py +109 -0
- ilc_core/cli/canon_bundle_sign.py +57 -0
- ilc_core/cli/canon_bundle_validate.py +87 -0
- ilc_core/cli/canon_cli.py +31 -0
- ilc_core/cli/canon_cluster_a_replay_proof.py +616 -0
- ilc_core/cli/canon_consumer.py +277 -0
- ilc_core/cli/canon_export.py +76 -0
- ilc_core/cli/d2e_agent_cli.py +69 -0
- ilc_core/cli/d2e_lifecycle_cli.py +138 -0
- ilc_core/cli/d2e_query_truth_cli.py +144 -0
- ilc_core/cli/d2e_submit_cli.py +179 -0
- ilc_core/cli/ep_task_cli.py +189 -0
- ilc_core/cli/main.py +1241 -0
- ilc_core/cli/mcp_cli.py +140 -0
- ilc_core/config.py +83 -0
- ilc_core/consensus/__init__.py +107 -0
- ilc_core/consensus/circuit_breaker_interface.py +183 -0
- ilc_core/consensus/clustering.py +31 -0
- ilc_core/consensus/diversity_floor_runtime.py +139 -0
- ilc_core/consensus/engine.py +454 -0
- ilc_core/consensus/epoch_state_runtime.py +349 -0
- ilc_core/consensus/finality_evaluator.py +417 -0
- ilc_core/consensus/governance.py +254 -0
- ilc_core/consensus/popperian_gate_runtime.py +126 -0
- ilc_core/consensus/production_bridge.py +638 -0
- ilc_core/consensus/reputation.py +146 -0
- ilc_core/crypto/__init__.py +43 -0
- ilc_core/crypto/cbor_canonical.py +86 -0
- ilc_core/crypto/cose_sign1.py +248 -0
- ilc_core/economics/entropy.py +80 -0
- ilc_core/economics/epoch_attribution_settle_runtime.py +557 -0
- ilc_core/economics/epoch_ledger.py +81 -0
- ilc_core/economics/onboarding.py +67 -0
- ilc_core/economics/outcome.py +53 -0
- ilc_core/economics/passive_ecu_attribution_runtime.py +100 -0
- ilc_core/economics/reward.py +76 -0
- ilc_core/economics/rl_agents.py +62 -0
- ilc_core/economics/telemetry.py +78 -0
- ilc_core/encoding/__init__.py +38 -0
- ilc_core/encoding/cidv1.py +278 -0
- ilc_core/encoding/dag_cbor.py +478 -0
- ilc_core/encoding/varint.py +79 -0
- ilc_core/epistemic/__init__.py +209 -0
- ilc_core/epistemic/aesthetic_panel_runtime.py +98 -0
- ilc_core/epistemic/ingestion_shadow_harness.py +498 -0
- ilc_core/epistemic/jury_activation_gate.py +432 -0
- ilc_core/epistemic/jury_assignment_runtime.py +721 -0
- ilc_core/epistemic/jury_incentive_runtime.py +102 -0
- ilc_core/epistemic/maintenance_lottery_runtime.py +256 -0
- ilc_core/epistemic/node_submission_runtime.py +129 -0
- ilc_core/epistemic/novelty_check_runtime.py +50 -0
- ilc_core/epistemic/refutation_runtime.py +93 -0
- ilc_core/epistemic/reuse_centrality_runtime.py +72 -0
- ilc_core/epistemic/review_lane_admission_runtime.py +499 -0
- ilc_core/epistemic/truth_primitive_graph_store.py +284 -0
- ilc_core/epistemic/truth_primitive_submission_runtime.py +456 -0
- ilc_core/epistemic/vrf_proof_verifier.py +346 -0
- ilc_core/epoch/__init__.py +345 -0
- ilc_core/epoch/allocation_distributor_runtime.py +358 -0
- ilc_core/epoch/ecu_price_clamp_runtime.py +230 -0
- ilc_core/epoch/epoch_boundary_witness_runtime.py +56 -0
- ilc_core/epoch/epoch_emission_runtime.py +194 -0
- ilc_core/epoch/epoch_snapshot_runtime.py +421 -0
- ilc_core/epoch/fee_burn_split_runtime.py +159 -0
- ilc_core/epoch/issuance_economics_integration_gate.py +527 -0
- ilc_core/epoch/treasury_governance_runtime.py +225 -0
- ilc_core/epoch/validator_reward_pool_routing_runtime.py +293 -0
- ilc_core/eve/__init__.py +1 -0
- ilc_core/eve/capsule.py +218 -0
- ilc_core/eve/capsule_builder.py +125 -0
- ilc_core/exceptions.py +187 -0
- ilc_core/genesis/__init__.py +99 -0
- ilc_core/genesis/admission_control_bootstrap.py +126 -0
- ilc_core/genesis/assertion_schema.py +470 -0
- ilc_core/genesis/genesis_intervention_runtime.py +466 -0
- ilc_core/genesis/genesis_state_bundle_runtime.py +329 -0
- ilc_core/genesis/schema.py +24 -0
- ilc_core/genesis/validator_bootstrap_runtime.py +178 -0
- ilc_core/genesis/work_task.py +80 -0
- ilc_core/governance/__init__.py +114 -0
- ilc_core/governance/challenge_node_runtime.py +553 -0
- ilc_core/governance/fork_legitimacy_runtime.py +533 -0
- ilc_core/graph/__init__.py +210 -0
- ilc_core/graph/agent_graph_projection_runtime.py +539 -0
- ilc_core/graph/sidecar_public_path_preflight.py +614 -0
- ilc_core/graph/sidecar_query_runtime.py +531 -0
- ilc_core/graph.py +203 -0
- ilc_core/hardware.py +119 -0
- ilc_core/identity/__init__.py +51 -0
- ilc_core/identity/agent_id_runtime.py +150 -0
- ilc_core/identity/endorsement_packet_schema.py +350 -0
- ilc_core/identity/epoch_endorsement_runtime.py +567 -0
- ilc_core/identity/genesis_record_schema.py +316 -0
- ilc_core/identity/log_redaction_runtime.py +171 -0
- ilc_core/identity/sybil_resistance_runtime.py +149 -0
- ilc_core/ledger/__init__.py +29 -0
- ilc_core/ledger/backend.py +338 -0
- ilc_core/ledger/canon_bundle_audit_artifact.py +158 -0
- ilc_core/ledger/canon_bundle_key_registry.py +707 -0
- ilc_core/ledger/canon_bundle_key_registry_bundle.py +313 -0
- ilc_core/ledger/canon_bundle_key_registry_channel.py +429 -0
- ilc_core/ledger/canon_bundle_key_registry_channel_signing.py +272 -0
- ilc_core/ledger/canon_bundle_key_registry_fetch.py +374 -0
- ilc_core/ledger/canon_bundle_key_registry_promotion.py +272 -0
- ilc_core/ledger/canon_bundle_key_registry_sync.py +947 -0
- ilc_core/ledger/canon_bundle_key_registry_sync_state.py +114 -0
- ilc_core/ledger/canon_bundle_pipeline_report.py +106 -0
- ilc_core/ledger/canon_bundle_replay_report.py +148 -0
- ilc_core/ledger/canon_bundle_replay_verify.py +169 -0
- ilc_core/ledger/canon_bundle_utils.py +65 -0
- ilc_core/ledger/canon_export.py +125 -0
- ilc_core/ledger/canon_export_bundle.py +134 -0
- ilc_core/ledger/canon_export_bundle_report.py +62 -0
- ilc_core/ledger/canon_export_bundle_sign.py +124 -0
- ilc_core/ledger/canon_export_bundle_validate.py +214 -0
- ilc_core/ledger/canon_export_bundle_verify_sig.py +106 -0
- ilc_core/ledger/canon_export_format.py +134 -0
- ilc_core/ledger/canon_export_validate.py +159 -0
- ilc_core/ledger/canon_loader.py +317 -0
- ilc_core/ledger/cdl048_conversion_sweeper_runtime.py +1187 -0
- ilc_core/ledger/claimability_proof_binding_runtime.py +670 -0
- ilc_core/ledger/ecu_active_layer_runtime.py +361 -0
- ilc_core/ledger/ecu_ilc_lifecycle_runtime.py +209 -0
- ilc_core/ledger/exact_numeric.py +102 -0
- ilc_core/ledger/ledger_export.py +221 -0
- ilc_core/ledger/lmdb_backend.py +180 -0
- ilc_core/ledger/persistent_backend.py +177 -0
- ilc_core/ledger/public_economics_admission_firewall.py +278 -0
- ilc_core/ledger/settlement_metrics.py +95 -0
- ilc_core/ledger/settlement_verification.py +184 -0
- ilc_core/ledger/stake_snapshot.py +50 -0
- ilc_core/links.py +21 -0
- ilc_core/logging_config.py +36 -0
- ilc_core/mcp/__init__.py +1 -0
- ilc_core/mcp/node_service.py +43 -0
- ilc_core/mcp/schema.py +114 -0
- ilc_core/mcp/schemas/__init__.py +1 -0
- ilc_core/mcp/schemas/ilc_mcp_tools_mvp_schema_v0.1.json +315 -0
- ilc_core/mcp/schemas/mcp_tool_call_event_schema_v0.1.json +95 -0
- ilc_core/mcp/service.py +396 -0
- ilc_core/mining/benchmark.py +304 -0
- ilc_core/network/__init__.py +23 -0
- ilc_core/network/d2d/__init__.py +31 -0
- ilc_core/network/d2d/bootstrap_fetch_runtime.py +244 -0
- ilc_core/network/d2d/cdl087_observability.py +228 -0
- ilc_core/network/d2d/cdl087_serving_peer_evidence.py +393 -0
- ilc_core/network/d2d/centrality_delta_gossip_runtime.py +241 -0
- ilc_core/network/d2d/gossip.py +253 -0
- ilc_core/network/d2d/gossip_peer_registry.py +161 -0
- ilc_core/network/d2d/gossip_transport.py +185 -0
- ilc_core/network/d2d/http_fetch_transport_runtime.py +305 -0
- ilc_core/network/d2d/http_gossip_transport_runtime.py +447 -0
- ilc_core/network/d2d/interface.py +221 -0
- ilc_core/network/d2d/peer.py +169 -0
- ilc_core/network/d2d/peer_fingerprint_cache.py +235 -0
- ilc_core/network/d2d/persistent_fetch_rate_limiter_runtime.py +204 -0
- ilc_core/network/d2d/routing_reputation_runtime.py +167 -0
- ilc_core/network/d2d/spectral_beacon.py +768 -0
- ilc_core/network/d2d/spectral_routing_runtime.py +304 -0
- ilc_core/network/d2d/transport_principal_pre_public_path.py +413 -0
- ilc_core/network/d2d/transport_principal_public_path_preflight.py +513 -0
- ilc_core/network/d2d/truth_primitive_fetch_runtime.py +462 -0
- ilc_core/network/d2d/truth_primitive_gossip_runtime.py +206 -0
- ilc_core/network/gossip.py +85 -0
- ilc_core/network/node_load_export.py +75 -0
- ilc_core/network/peer.py +167 -0
- ilc_core/network/star_map/__init__.py +0 -0
- ilc_core/network/star_map/star_map_route_index_runtime.py +401 -0
- ilc_core/network/topology.py +151 -0
- ilc_core/network/wire_transport_runtime.py +359 -0
- ilc_core/node/__init__.py +103 -0
- ilc_core/node/devnet.py +210 -0
- ilc_core/node/executable_descriptor_runtime_363.py +409 -0
- ilc_core/node/node_dissemination_runtime_362.py +510 -0
- ilc_core/node/node_schema_core_runtime_360.py +424 -0
- ilc_core/node/node_startup_runtime.py +569 -0
- ilc_core/node/node_v0.py +122 -0
- ilc_core/node/promotion_continuity_runtime_364.py +438 -0
- ilc_core/node/tier3_runtime_linkage_runtime.py +156 -0
- ilc_core/node/timed_out_lifecycle_runtime_411.py +52 -0
- ilc_core/node/validation_lifecycle_runtime_361.py +353 -0
- ilc_core/privacy/__init__.py +69 -0
- ilc_core/privacy/lane.py +369 -0
- ilc_core/privacy/metrics.py +297 -0
- ilc_core/privacy/monitor.py +197 -0
- ilc_core/privacy/transfer_mixing_framework.py +81 -0
- ilc_core/protocol/__init__.py +128 -0
- ilc_core/protocol/bundle_verify.py +120 -0
- ilc_core/protocol/commit_epoch_emission_runtime.py +480 -0
- ilc_core/protocol/event_export.py +203 -0
- ilc_core/protocol/event_log.py +719 -0
- ilc_core/protocol/event_log_retention.py +261 -0
- ilc_core/protocol/governance_weighted_decision.py +334 -0
- ilc_core/protocol/harness_interfaces.py +151 -0
- ilc_core/protocol/ilc_cluster_a_acceptance_evidence.py +396 -0
- ilc_core/protocol/ilc_cluster_a_clause_binding.py +166 -0
- ilc_core/protocol/ilc_cluster_a_conformance.py +346 -0
- ilc_core/protocol/ilc_cluster_a_ingest.py +568 -0
- ilc_core/protocol/ilc_cluster_a_replay_attestation.py +196 -0
- ilc_core/protocol/ilc_cluster_a_replay_proof_batch.py +179 -0
- ilc_core/protocol/ilc_cluster_a_replay_proof_batch_compare.py +201 -0
- ilc_core/protocol/ilc_cluster_a_replay_proof_batch_ops.py +101 -0
- ilc_core/protocol/ilc_cluster_a_replay_proof_ci_gate.py +502 -0
- ilc_core/protocol/ilc_cluster_a_replay_proof_package.py +288 -0
- ilc_core/protocol/ilc_cluster_a_replay_proof_schemas.py +47 -0
- ilc_core/protocol/ilc_governance_record_validate.py +88 -0
- ilc_core/protocol/ilc_receipt_validate.py +86 -0
- ilc_core/protocol/ilc_transcript_validate.py +124 -0
- ilc_core/protocol/ilc_wire_validate.py +107 -0
- ilc_core/protocol/known_records_migration.py +114 -0
- ilc_core/protocol/mapper.py +112 -0
- ilc_core/protocol/ndjson_bundle.py +584 -0
- ilc_core/protocol/params.py +168 -0
- ilc_core/protocol/primitive_type_registry.py +27 -0
- ilc_core/protocol/public_init_admission_runtime.py +141 -0
- ilc_core/protocol/public_receipt_runtime.py +208 -0
- ilc_core/protocol/public_wallet_runtime.py +214 -0
- ilc_core/protocol/schema.py +30 -0
- ilc_core/protocol/schemas/canon_bundle_key_registry_bundle_v0.1.json +12 -0
- ilc_core/protocol/schemas/canon_bundle_key_registry_channel_v0.1.json +10 -0
- ilc_core/protocol/schemas/canon_bundle_key_registry_channel_v0.2.json +37 -0
- ilc_core/protocol/schemas/canon_bundle_key_registry_channel_v0.3.json +22 -0
- ilc_core/protocol/schemas/canon_bundle_key_registry_channel_v0.4.json +24 -0
- ilc_core/protocol/schemas/canon_bundle_key_registry_v0.1.json +57 -0
- ilc_core/protocol/schemas/canon_export_format_v0.1.json +92 -0
- ilc_core/protocol/schemas/commit_epoch_event_schema_v0.1.json +112 -0
- ilc_core/protocol/schemas/ilc_canonical_transcript_schema_v0.1.json +69 -0
- ilc_core/protocol/schemas/ilc_cluster_a_replay_proof_batch_compare_v0.1.json +73 -0
- ilc_core/protocol/schemas/ilc_cluster_a_replay_proof_batch_ops_contract_v0.1.json +58 -0
- ilc_core/protocol/schemas/ilc_cluster_a_replay_proof_batch_report_v0.1.json +120 -0
- ilc_core/protocol/schemas/ilc_cluster_a_replay_proof_ci_gate_report_v0.1.json +173 -0
- ilc_core/protocol/schemas/ilc_governance_record_schema_v0.1.json +91 -0
- ilc_core/protocol/schemas/ilc_protocol_wire_format_v0.1.json +145 -0
- ilc_core/protocol/schemas/ilc_receipt_schema_v0.1.json +78 -0
- ilc_core/protocol/schemas/mcp_tool_call_event_schema_v0.1.json +95 -0
- ilc_core/rc/__init__.py +89 -0
- ilc_core/rc/atlas_graph_discipline.py +1425 -0
- ilc_core/rc/economic_cycle_runtime.py +766 -0
- ilc_core/rc/genesis_v0_2_signing_ceremony_gate.py +841 -0
- ilc_core/rc/local_skill_preview.py +223 -0
- ilc_core/rc/package_boundary_inventory.py +337 -0
- ilc_core/rc/package_profile_ci_gate.py +463 -0
- ilc_core/rc/package_profiles.py +435 -0
- ilc_core/rc/public_rc_exclude_disposition.py +507 -0
- ilc_core/rc/public_rc_publication_claim_gate.py +770 -0
- ilc_core/rc/release_artifact_manifest_rehearsal.py +538 -0
- ilc_core/rc/release_artifact_production_gate.py +934 -0
- ilc_core/rc/release_key_envelope_rehearsal.py +602 -0
- ilc_core/rc/release_keys_envelopes_generation_gate.py +897 -0
- ilc_core/rc/source_allowlist_export_execution_gate.py +823 -0
- ilc_core/rc/source_allowlist_export_rehearsal.py +954 -0
- ilc_core/reputation/__init__.py +19 -0
- ilc_core/reputation/temporal_decay_runtime.py +128 -0
- ilc_core/schema/__init__.py +21 -0
- ilc_core/schema/d2_schema_baseline_runtime.py +219 -0
- ilc_core/security/__init__.py +69 -0
- ilc_core/security/key_compromise_runtime.py +261 -0
- ilc_core/security/rollback_resistance_runtime.py +100 -0
- ilc_core/security/signer_lineage_runtime.py +350 -0
- ilc_core/server.py +380 -0
- ilc_core/sidecars/__init__.py +16 -0
- ilc_core/sidecars/claim_nullifier_registry_v1.py +542 -0
- ilc_core/sidecars/claimability_receipt_verifier.py +1460 -0
- ilc_core/sidecars/confidential_coordination_capability.py +1453 -0
- ilc_core/sidecars/confidential_coordination_gossip_policy.py +1126 -0
- ilc_core/sidecars/confidential_coordination_sealed_sender.py +1351 -0
- ilc_core/sidecars/confidential_coordination_shard.py +1272 -0
- ilc_core/sidecars/local_graph_memory_projection.py +1084 -0
- ilc_core/sidecars/public_fetch_p2p_readiness.py +730 -0
- ilc_core/sidecars/registry_manifest.py +1157 -0
- ilc_core/sidecars/transport_principal_admission.py +1126 -0
- ilc_core/sidecars/value_path_activation_boundary_preflight.py +617 -0
- ilc_core/sidecars/wallet_action_semantics_preflight.py +573 -0
- ilc_core/sim/devnet_epoch_orchestrator.py +247 -0
- ilc_core/sim/devnet_experiments.py +186 -0
- ilc_core/sim/devnet_multi_epoch.py +298 -0
- ilc_core/sim/devnet_scenarios.py +315 -0
- ilc_core/sim/harness_closed_loop_controllers.py +180 -0
- ilc_core/sim/harness_econ_scenarios.py +201 -0
- ilc_core/sim/harness_param_grid.py +227 -0
- ilc_core/sim/sim_fetch_01/__init__.py +0 -0
- ilc_core/sim/sim_fetch_01/sim_fetch_01_harness.py +1694 -0
- ilc_core/sim/sim_fetch_01/werner_topology_pressure_profile.py +193 -0
- ilc_core/star_map/__init__.py +1 -0
- ilc_core/star_map/debruijn_harness.py +207 -0
- ilc_core/star_map/route_index.py +310 -0
- ilc_core/storage/__init__.py +33 -0
- ilc_core/storage/interfaces.py +48 -0
- ilc_core/storage/lmdb_graph_pruning_runtime.py +212 -0
- ilc_core/storage/lmdb_public_runtime.py +236 -0
- ilc_core/storage/truth_primitive_graph_lmdb_adapter.py +88 -0
- ilc_core/testing/__init__.py +15 -0
- ilc_core/testing/phase_commit_manifest.py +65 -0
- ilc_core/testing/ratification_mutation_scope_guardrail.py +211 -0
- ilc_core/types.py +389 -0
- ilc_core/validator/__init__.py +117 -0
- ilc_core/validator/admission_ejection_runtime.py +431 -0
- ilc_core/validator/re_admission_runtime.py +53 -0
- ilc_core/validator/staking_liveness_runtime.py +53 -0
- ilc_core/validator/topology_shuffle_runtime.py +323 -0
- ilc_core/validator/trust_tier_runtime.py +82 -0
- ilc_core/work/task_queue.py +127 -0
- ilc_core-0.1.0.dist-info/METADATA +163 -0
- ilc_core-0.1.0.dist-info/RECORD +357 -0
- ilc_core-0.1.0.dist-info/WHEEL +5 -0
- ilc_core-0.1.0.dist-info/entry_points.txt +11 -0
- ilc_core-0.1.0.dist-info/licenses/LICENSE +28 -0
- ilc_core-0.1.0.dist-info/top_level.txt +1 -0
ilc_core/agent.py
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
import logging
|
|
3
|
+
from decimal import Decimal, InvalidOperation
|
|
4
|
+
from .types import Node
|
|
5
|
+
from .graph import EpistemicGraph
|
|
6
|
+
from .exceptions import InsufficientStakeError
|
|
7
|
+
from .consensus.engine import ConsensusEngine
|
|
8
|
+
from .mining.benchmark import PoWBenchmark
|
|
9
|
+
from .economics.onboarding import OnboardingVault
|
|
10
|
+
from .identity.log_redaction_runtime import redact_agent_id_for_log
|
|
11
|
+
import time
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
|
|
15
|
+
DRAFT_SIGNATURE = "draft_unsigned_mvp"
|
|
16
|
+
|
|
17
|
+
class EveAgent:
|
|
18
|
+
def __init__(self, agent_id: str, graph: EpistemicGraph, consensus: ConsensusEngine, vault: Optional[OnboardingVault] = None):
|
|
19
|
+
self.id = agent_id
|
|
20
|
+
self.graph = graph
|
|
21
|
+
self.consensus = consensus
|
|
22
|
+
self.vault = vault
|
|
23
|
+
|
|
24
|
+
self.wallet_balance = Decimal("0")
|
|
25
|
+
self.credit_balance = Decimal("0")
|
|
26
|
+
|
|
27
|
+
# Onboarding: Request Credit if Vault exists
|
|
28
|
+
if self.vault:
|
|
29
|
+
credit = self.vault.request_starter_credit(self.id)
|
|
30
|
+
self.credit_balance += credit
|
|
31
|
+
self.wallet_balance += credit # Credit is liquid for staking
|
|
32
|
+
|
|
33
|
+
self.benchmark_engine = PoWBenchmark(matrix_size=1000)
|
|
34
|
+
self.trust_vector = {
|
|
35
|
+
"accuracy": 0.5,
|
|
36
|
+
"precision": 0.5,
|
|
37
|
+
"potential": 0.0,
|
|
38
|
+
"tier": "unknown"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def decide_stake_for_claim(self, requested_stake: Decimal) -> Decimal:
|
|
42
|
+
"""
|
|
43
|
+
Decide how much to stake for a claim.submit task.
|
|
44
|
+
|
|
45
|
+
MVP behavior (backwards-compatible):
|
|
46
|
+
- Query the current ECU fee for "claim.submit" from Governance.
|
|
47
|
+
- Ensure the chosen stake is at least the ECU fee.
|
|
48
|
+
- Never exceed the agent's wallet_balance.
|
|
49
|
+
- If requested_stake >= required_fee and <= wallet_balance, keep it unchanged.
|
|
50
|
+
(This preserves existing tests that pass explicit stakes like 5.0 or 10.0.)
|
|
51
|
+
- If requested_stake < required_fee but wallet can afford required_fee, bump up
|
|
52
|
+
to exactly required_fee.
|
|
53
|
+
- If wallet_balance < required_fee, return 0.0 to signal "cannot stake".
|
|
54
|
+
"""
|
|
55
|
+
self._normalize_balances()
|
|
56
|
+
stake_amount = _agent_coerce_decimal(requested_stake, "agent_requested_stake_invalid")
|
|
57
|
+
if stake_amount < Decimal("0"):
|
|
58
|
+
raise InsufficientStakeError(
|
|
59
|
+
self.id,
|
|
60
|
+
stake=stake_amount,
|
|
61
|
+
required=Decimal("0"),
|
|
62
|
+
message="requested_stake cannot be negative",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Ask consensus governance for current ECU fee.
|
|
66
|
+
required_fee = self.consensus.governance.get_task_fee_ecu("claim.submit")
|
|
67
|
+
min_required = required_fee
|
|
68
|
+
|
|
69
|
+
# Wallet cannot afford even the minimum fee → no staking.
|
|
70
|
+
if self.wallet_balance < min_required:
|
|
71
|
+
return Decimal("0")
|
|
72
|
+
|
|
73
|
+
# Preserve explicit stakes from callers if they are sane.
|
|
74
|
+
if stake_amount >= min_required and stake_amount <= self.wallet_balance:
|
|
75
|
+
return stake_amount
|
|
76
|
+
|
|
77
|
+
# If requested stake is below the required fee, bump up to exactly the fee
|
|
78
|
+
# as long as the wallet can pay.
|
|
79
|
+
if stake_amount < min_required and self.wallet_balance >= min_required:
|
|
80
|
+
return min_required
|
|
81
|
+
|
|
82
|
+
# If requested stake is more than wallet_balance, cap at wallet_balance.
|
|
83
|
+
if stake_amount > self.wallet_balance:
|
|
84
|
+
return self.wallet_balance
|
|
85
|
+
|
|
86
|
+
# Fallback: refuse to stake in any weird edge case.
|
|
87
|
+
return Decimal("0")
|
|
88
|
+
|
|
89
|
+
def perform_pow_benchmark(self) -> float:
|
|
90
|
+
"""
|
|
91
|
+
Run a hardware / PoW-style benchmark and update this agent's
|
|
92
|
+
trust_vector["potential"].
|
|
93
|
+
|
|
94
|
+
The resulting score ∈ [0, 1] is used as a proxy for hardware capability
|
|
95
|
+
(and, in future phases, can inform how aggressively the agent chooses
|
|
96
|
+
to spend stake on ILC tasks).
|
|
97
|
+
"""
|
|
98
|
+
# Preserving Phase H Logic
|
|
99
|
+
result = self.benchmark_engine.run(self.id)
|
|
100
|
+
self.trust_vector["potential"] = result["score"]
|
|
101
|
+
self.trust_vector["tier"] = result["tier"]
|
|
102
|
+
logger.info(
|
|
103
|
+
"agent_hardware_verified agent=%s device=%s tier=%s",
|
|
104
|
+
redact_agent_id_for_log(self.id),
|
|
105
|
+
result["device"],
|
|
106
|
+
result["tier"],
|
|
107
|
+
)
|
|
108
|
+
return result["score"]
|
|
109
|
+
|
|
110
|
+
def mine_thought(self, content: str, parent_id: str, stake: Decimal) -> Optional[Node]:
|
|
111
|
+
"""
|
|
112
|
+
Mine a new claim node linked to parent_id, staking some amount of
|
|
113
|
+
the agent's wallet balance.
|
|
114
|
+
|
|
115
|
+
This MVP version now routes the requested stake through
|
|
116
|
+
decide_stake_for_claim(...) to ensure we at least pay the ECU fee
|
|
117
|
+
and never exceed wallet_balance.
|
|
118
|
+
"""
|
|
119
|
+
# Decide how much we can/should actually stake given ECU fees + wallet.
|
|
120
|
+
chosen_stake = self.decide_stake_for_claim(stake)
|
|
121
|
+
|
|
122
|
+
# If we cannot stake anything meaningful, abort with the existing message.
|
|
123
|
+
if chosen_stake <= Decimal("0") or self.wallet_balance < chosen_stake:
|
|
124
|
+
logger.warning(
|
|
125
|
+
"agent_stake_insufficient_funds agent=%s wallet=%s chosen=%s",
|
|
126
|
+
redact_agent_id_for_log(self.id),
|
|
127
|
+
self.wallet_balance,
|
|
128
|
+
chosen_stake,
|
|
129
|
+
)
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
# Construct the node exactly as before.
|
|
133
|
+
node = Node(
|
|
134
|
+
id="",
|
|
135
|
+
type="claim",
|
|
136
|
+
content=content,
|
|
137
|
+
agent_id=self.id,
|
|
138
|
+
signature=DRAFT_SIGNATURE,
|
|
139
|
+
)
|
|
140
|
+
node.id = node.compute_id()
|
|
141
|
+
self.graph.add_node(node)
|
|
142
|
+
self.graph.add_edge_by_ids(node.id, parent_id, "derives_from")
|
|
143
|
+
|
|
144
|
+
# Deduct the chosen stake and register it with the consensus engine.
|
|
145
|
+
self.wallet_balance -= chosen_stake
|
|
146
|
+
success = self.consensus.register_stake(node.id, chosen_stake)
|
|
147
|
+
if not success:
|
|
148
|
+
# In case governance rejects the stake (e.g., ECU fee changed mid-flight),
|
|
149
|
+
# revert the wallet deduction and abort.
|
|
150
|
+
self.wallet_balance += chosen_stake
|
|
151
|
+
logger.warning(
|
|
152
|
+
"agent_stake_rejected_by_consensus agent=%s chosen=%s",
|
|
153
|
+
redact_agent_id_for_log(self.id),
|
|
154
|
+
chosen_stake,
|
|
155
|
+
)
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
logger.info(
|
|
159
|
+
"agent_claim_minted agent=%s node=%s",
|
|
160
|
+
redact_agent_id_for_log(self.id),
|
|
161
|
+
node.id[:8],
|
|
162
|
+
)
|
|
163
|
+
return node
|
|
164
|
+
|
|
165
|
+
def auto_mine_claim(self, content: str, parent_id: str) -> Optional[Node]:
|
|
166
|
+
"""
|
|
167
|
+
Strategy helper: automatically choose a stake for a claim.submit task
|
|
168
|
+
based on:
|
|
169
|
+
- current wallet_balance,
|
|
170
|
+
- the ECU fee for "claim.submit" (via Governance),
|
|
171
|
+
- this agent's hardware potential (trust_vector["potential"]).
|
|
172
|
+
|
|
173
|
+
This is primarily intended for simulations and does not change the
|
|
174
|
+
behavior of mine_thought(...) when called directly with an explicit
|
|
175
|
+
stake.
|
|
176
|
+
|
|
177
|
+
MVP strategy:
|
|
178
|
+
- Ensure we have a hardware potential score (run perform_pow_benchmark()
|
|
179
|
+
if needed).
|
|
180
|
+
- Map potential ∈ [0, 1] to a stake fraction ∈ [f_min, f_max] of the
|
|
181
|
+
current wallet_balance.
|
|
182
|
+
- Compute candidate_stake = fraction * wallet_balance.
|
|
183
|
+
- Route candidate_stake through decide_stake_for_claim(...) to enforce
|
|
184
|
+
ECU fees and wallet caps.
|
|
185
|
+
- If the resulting stake is valid, call mine_thought(...) with that
|
|
186
|
+
chosen stake.
|
|
187
|
+
"""
|
|
188
|
+
# Ensure we have a potential measurement.
|
|
189
|
+
self._normalize_balances()
|
|
190
|
+
if self.trust_vector.get("potential", 0.0) <= 0.0:
|
|
191
|
+
self.perform_pow_benchmark()
|
|
192
|
+
|
|
193
|
+
potential = self.trust_vector.get("potential", 0.0)
|
|
194
|
+
|
|
195
|
+
# Low potential → more cautious; high potential → more aggressive.
|
|
196
|
+
# Allow external config (e.g., hardware archetypes) to override.
|
|
197
|
+
f_min = getattr(self, "strategy_stake_fraction_min", 0.05)
|
|
198
|
+
f_max = getattr(self, "strategy_stake_fraction_max", 0.25)
|
|
199
|
+
frac = f_min + (f_max - f_min) * potential
|
|
200
|
+
|
|
201
|
+
# Derive a candidate stake from current wallet.
|
|
202
|
+
candidate_stake = Decimal(str(frac)) * self.wallet_balance
|
|
203
|
+
|
|
204
|
+
# Let the ECU-aware helper adjust or reject.
|
|
205
|
+
chosen = self.decide_stake_for_claim(candidate_stake)
|
|
206
|
+
|
|
207
|
+
if chosen <= Decimal("0") or self.wallet_balance < chosen:
|
|
208
|
+
logger.warning(
|
|
209
|
+
"agent_auto_mine_aborted agent=%s wallet=%s chosen=%s",
|
|
210
|
+
redact_agent_id_for_log(self.id),
|
|
211
|
+
self.wallet_balance,
|
|
212
|
+
chosen,
|
|
213
|
+
)
|
|
214
|
+
return None
|
|
215
|
+
|
|
216
|
+
# Delegate actual minting to the existing mine_thought path.
|
|
217
|
+
return self.mine_thought(content, parent_id, chosen)
|
|
218
|
+
|
|
219
|
+
def receive_reward(self, amount: Decimal):
|
|
220
|
+
"""Handle earnings and auto-repayment."""
|
|
221
|
+
self._normalize_balances()
|
|
222
|
+
reward_amount = _agent_coerce_decimal(amount, "agent_reward_invalid")
|
|
223
|
+
if self.vault:
|
|
224
|
+
repayment, net = self.vault.process_repayment(self.id, reward_amount)
|
|
225
|
+
self.wallet_balance += net
|
|
226
|
+
# Credit balance is technically liability, but simplistic tracking here:
|
|
227
|
+
if repayment > Decimal("0"):
|
|
228
|
+
logger.info(
|
|
229
|
+
"agent_reward_repayment agent=%s repaid=%s net=%s",
|
|
230
|
+
redact_agent_id_for_log(self.id),
|
|
231
|
+
repayment,
|
|
232
|
+
net,
|
|
233
|
+
)
|
|
234
|
+
else:
|
|
235
|
+
self.wallet_balance += reward_amount
|
|
236
|
+
|
|
237
|
+
def refute_node(self, target_id: str, stake: Decimal):
|
|
238
|
+
self._normalize_balances()
|
|
239
|
+
stake_amount = _agent_coerce_decimal(stake, "agent_refutation_stake_invalid")
|
|
240
|
+
if self.wallet_balance < stake_amount:
|
|
241
|
+
logger.warning(
|
|
242
|
+
"agent_refute_insufficient_balance agent=%s target=%s wallet=%s stake=%s",
|
|
243
|
+
redact_agent_id_for_log(self.id),
|
|
244
|
+
target_id,
|
|
245
|
+
self.wallet_balance,
|
|
246
|
+
stake_amount,
|
|
247
|
+
)
|
|
248
|
+
return False
|
|
249
|
+
self.wallet_balance -= stake_amount
|
|
250
|
+
self.consensus.process_contradiction(target_id, stake_amount)
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
def _normalize_balances(self) -> None:
|
|
254
|
+
self.wallet_balance = _agent_coerce_decimal(
|
|
255
|
+
self.wallet_balance, "agent_wallet_balance_invalid"
|
|
256
|
+
)
|
|
257
|
+
self.credit_balance = _agent_coerce_decimal(
|
|
258
|
+
self.credit_balance, "agent_credit_balance_invalid"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _agent_coerce_decimal(value: object, token: str) -> Decimal:
|
|
263
|
+
if isinstance(value, bool):
|
|
264
|
+
raise ValueError(token)
|
|
265
|
+
if not isinstance(value, (Decimal, int, float, str)):
|
|
266
|
+
raise ValueError(token)
|
|
267
|
+
try:
|
|
268
|
+
amount = value if isinstance(value, Decimal) else Decimal(str(value))
|
|
269
|
+
except (InvalidOperation, ValueError) as exc:
|
|
270
|
+
raise ValueError(token) from exc
|
|
271
|
+
if not amount.is_finite():
|
|
272
|
+
raise ValueError(f"{token}_non_finite")
|
|
273
|
+
return amount
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from dataclasses import dataclass, field, asdict
|
|
2
|
+
from typing import Dict, List, TypeAlias
|
|
3
|
+
from os import PathLike
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import csv
|
|
6
|
+
|
|
7
|
+
from ilc_core.analysis.problem_space_kpis import (
|
|
8
|
+
ProblemSpace,
|
|
9
|
+
compute_problem_space_kpis,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
AgentDescriptorRow: TypeAlias = Dict[str, str | float]
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class AgentDescriptor:
|
|
17
|
+
"""
|
|
18
|
+
Lightweight descriptor of an agent's behavior across problem spaces.
|
|
19
|
+
"""
|
|
20
|
+
agent_id: str
|
|
21
|
+
problem_space_counts: Dict[str, float] = field(default_factory=dict)
|
|
22
|
+
total_tasks: float = 0.0
|
|
23
|
+
dominant_problem_space: str = "OTHER"
|
|
24
|
+
|
|
25
|
+
def as_dict(self) -> AgentDescriptorRow:
|
|
26
|
+
data = {
|
|
27
|
+
"agent_id": self.agent_id,
|
|
28
|
+
"total_tasks": self.total_tasks,
|
|
29
|
+
"dominant_problem_space": self.dominant_problem_space,
|
|
30
|
+
}
|
|
31
|
+
# Flatten counts with a prefix for clarity.
|
|
32
|
+
for space, count in self.problem_space_counts.items():
|
|
33
|
+
data[f"space_{space}"] = count
|
|
34
|
+
return data
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def build_agent_descriptors_from_task_rows(
|
|
38
|
+
task_rows: List[Dict[str, str]],
|
|
39
|
+
) -> Dict[str, AgentDescriptor]:
|
|
40
|
+
"""
|
|
41
|
+
Build AgentDescriptors from in-memory task rows.
|
|
42
|
+
"""
|
|
43
|
+
space_kpis = compute_problem_space_kpis(task_rows)
|
|
44
|
+
descriptors: Dict[str, AgentDescriptor] = {}
|
|
45
|
+
|
|
46
|
+
for agent_id, stats in space_kpis.items():
|
|
47
|
+
total_tasks = stats.get("total_tasks", 0.0)
|
|
48
|
+
|
|
49
|
+
# Extract per-space counts (excluding total_tasks).
|
|
50
|
+
problem_space_counts = {
|
|
51
|
+
k: v for k, v in stats.items() if k != "total_tasks"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Identify dominant problem space (ties broken arbitrarily by max).
|
|
55
|
+
if problem_space_counts:
|
|
56
|
+
dominant_space = max(problem_space_counts.items(), key=lambda kv: kv[1])[0]
|
|
57
|
+
else:
|
|
58
|
+
dominant_space = "OTHER"
|
|
59
|
+
|
|
60
|
+
descriptors[agent_id] = AgentDescriptor(
|
|
61
|
+
agent_id=agent_id,
|
|
62
|
+
problem_space_counts=problem_space_counts,
|
|
63
|
+
total_tasks=total_tasks,
|
|
64
|
+
dominant_problem_space=dominant_space,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return descriptors
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def load_tasks_csv(path: PathLike) -> List[Dict[str, str]]:
|
|
71
|
+
"""
|
|
72
|
+
Convenience loader for tasks.csv to feed into descriptor builders.
|
|
73
|
+
"""
|
|
74
|
+
p = Path(path)
|
|
75
|
+
rows: List[Dict[str, str]] = []
|
|
76
|
+
if not p.exists():
|
|
77
|
+
return rows
|
|
78
|
+
|
|
79
|
+
with p.open("r", encoding="utf-8", newline="") as f:
|
|
80
|
+
reader = csv.DictReader(f)
|
|
81
|
+
for row in reader:
|
|
82
|
+
rows.append(dict(row))
|
|
83
|
+
return rows
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def build_agent_descriptors_from_tasks_csv(
|
|
87
|
+
tasks_csv_path: PathLike,
|
|
88
|
+
) -> Dict[str, AgentDescriptor]:
|
|
89
|
+
"""
|
|
90
|
+
High-level helper: load tasks.csv and build AgentDescriptors.
|
|
91
|
+
"""
|
|
92
|
+
rows = load_tasks_csv(tasks_csv_path)
|
|
93
|
+
return build_agent_descriptors_from_task_rows(rows)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from os import PathLike
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Dict, List, TypeAlias
|
|
4
|
+
import csv
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
from ilc_core.analysis.agent_profiles import AgentProfile
|
|
8
|
+
|
|
9
|
+
ScalarValue: TypeAlias = str | int | float | bool | None
|
|
10
|
+
AgentDossierRow: TypeAlias = Dict[str, ScalarValue]
|
|
11
|
+
AgentDossierRows: TypeAlias = List[AgentDossierRow]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def flatten_profile_for_csv(profile: AgentProfile) -> AgentDossierRow:
|
|
15
|
+
"""
|
|
16
|
+
Flatten an AgentProfile into a 1-level dict suitable for CSV export.
|
|
17
|
+
|
|
18
|
+
Strategy:
|
|
19
|
+
- Use profile.as_dict() as the base.
|
|
20
|
+
- Keep simple scalar fields (agent_id, econ_*, claim_*, influence_*, etc.).
|
|
21
|
+
- For nested dicts (competency, stress_response, light_cone), extract
|
|
22
|
+
a small number of top-level summary scalars, and also include a
|
|
23
|
+
*_json column with the full nested data as JSON.
|
|
24
|
+
"""
|
|
25
|
+
base = profile.as_dict()
|
|
26
|
+
row: AgentDossierRow = {}
|
|
27
|
+
|
|
28
|
+
# Always include agent_id
|
|
29
|
+
row["agent_id"] = base.get("agent_id")
|
|
30
|
+
|
|
31
|
+
# Pass through flat econ/claim/influence keys
|
|
32
|
+
for k, v in base.items():
|
|
33
|
+
if k == "agent_id":
|
|
34
|
+
continue
|
|
35
|
+
if k in ("competency", "stress_response", "light_cone"):
|
|
36
|
+
continue
|
|
37
|
+
# Only include scalars; skip any unexpected nested structures
|
|
38
|
+
if not isinstance(v, (dict, list)):
|
|
39
|
+
row[k] = v
|
|
40
|
+
|
|
41
|
+
# Competency summary
|
|
42
|
+
comp = base.get("competency") or {}
|
|
43
|
+
if comp:
|
|
44
|
+
global_c = comp.get("global") or {}
|
|
45
|
+
row["competency_total_tasks"] = global_c.get("total_tasks", 0)
|
|
46
|
+
row["competency_avg_success_rate"] = global_c.get("avg_success_rate", 0.0)
|
|
47
|
+
row["competency_json"] = json.dumps(comp)
|
|
48
|
+
|
|
49
|
+
# Stress-response summary
|
|
50
|
+
stress = base.get("stress_response") or {}
|
|
51
|
+
if stress:
|
|
52
|
+
# Expect shape like {"by_stress_bucket": {...}, "preference": "..."} for example.
|
|
53
|
+
row["stress_preference"] = stress.get("preference", "")
|
|
54
|
+
row["stress_response_json"] = json.dumps(stress)
|
|
55
|
+
|
|
56
|
+
# Light-cone summary
|
|
57
|
+
lc = base.get("light_cone") or {}
|
|
58
|
+
if lc:
|
|
59
|
+
row["light_cone_score"] = lc.get("light_cone_score", 0.0)
|
|
60
|
+
row["light_cone_reach"] = lc.get("reach_score", 0.0)
|
|
61
|
+
row["light_cone_horizon"] = lc.get("horizon_score", 0.0)
|
|
62
|
+
row["light_cone_domain_span"] = lc.get("domain_span", 0)
|
|
63
|
+
row["light_cone_json"] = json.dumps(lc)
|
|
64
|
+
|
|
65
|
+
return row
|
|
66
|
+
|
|
67
|
+
def export_agent_dossiers_to_csv(
|
|
68
|
+
profiles: Dict[str, AgentProfile],
|
|
69
|
+
path: PathLike,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""
|
|
72
|
+
Export all AgentProfiles to a CSV file.
|
|
73
|
+
|
|
74
|
+
- Unions all keys across flattened rows to form the header.
|
|
75
|
+
- Writes one row per agent.
|
|
76
|
+
"""
|
|
77
|
+
p = Path(path)
|
|
78
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
|
|
80
|
+
rows: AgentDossierRows = [flatten_profile_for_csv(pf) for pf in profiles.values()]
|
|
81
|
+
|
|
82
|
+
# Handle empty gracefully
|
|
83
|
+
if not rows:
|
|
84
|
+
fieldnames = ["agent_id"]
|
|
85
|
+
else:
|
|
86
|
+
# Union of all keys
|
|
87
|
+
keys = set()
|
|
88
|
+
for r in rows:
|
|
89
|
+
keys.update(r.keys())
|
|
90
|
+
fieldnames = sorted(keys)
|
|
91
|
+
|
|
92
|
+
with p.open("w", newline="", encoding="utf-8") as f:
|
|
93
|
+
writer = csv.DictWriter(f, fieldnames=fieldnames)
|
|
94
|
+
writer.writeheader()
|
|
95
|
+
for r in rows:
|
|
96
|
+
writer.writerow(r)
|
|
97
|
+
|
|
98
|
+
def export_agent_dossiers_to_json(
|
|
99
|
+
profiles: Dict[str, AgentProfile],
|
|
100
|
+
path: PathLike,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Export all AgentProfiles to a JSON file as a list of flattened rows.
|
|
104
|
+
"""
|
|
105
|
+
p = Path(path)
|
|
106
|
+
p.parent.mkdir(parents=True, exist_ok=True)
|
|
107
|
+
|
|
108
|
+
rows = [flatten_profile_for_csv(pf) for pf in profiles.values()]
|
|
109
|
+
with p.open("w", encoding="utf-8") as f:
|
|
110
|
+
json.dump(rows, f, indent=2)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Dict, List, Optional, TypedDict, TypeAlias
|
|
3
|
+
from ilc_core.analysis.claim_scores import ClaimInfluenceRow
|
|
4
|
+
from ilc_core.analysis.competency_kpis import AgentCompetencySummaryMap
|
|
5
|
+
from ilc_core.analysis.light_cone_kpis import AgentLightConeRow
|
|
6
|
+
from os import PathLike
|
|
7
|
+
|
|
8
|
+
from ilc_core.analysis.econ_kpis import (
|
|
9
|
+
load_tasks_csv,
|
|
10
|
+
load_epochs_csv,
|
|
11
|
+
load_claims_csv,
|
|
12
|
+
compute_agent_econ_kpis,
|
|
13
|
+
compute_claim_kpis,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class AgentInfluenceRow(TypedDict):
|
|
18
|
+
total_influence: float
|
|
19
|
+
num_influenced_claims: float
|
|
20
|
+
total_net_support: float
|
|
21
|
+
avg_influence: float
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
AgentInfluenceMap: TypeAlias = Dict[str, AgentInfluenceRow]
|
|
25
|
+
AgentLightConeMap: TypeAlias = Dict[str, AgentLightConeRow]
|
|
26
|
+
ProfileNestedMap: TypeAlias = Dict[str, object]
|
|
27
|
+
ProfileValue: TypeAlias = str | int | float | bool | None | ProfileNestedMap
|
|
28
|
+
AgentProfileDict: TypeAlias = Dict[str, ProfileValue]
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class AgentProfile:
|
|
32
|
+
agent_id: str
|
|
33
|
+
node_id: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
# Econ KPIs (from compute_basic_kpis)
|
|
36
|
+
econ: Dict[str, float] = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
# Claim KPIs (from compute_claim_kpis)
|
|
39
|
+
claims: Dict[str, float] = field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
# Influence KPIs (from compute_agent_influence_kpis)
|
|
42
|
+
influence: Dict[str, float] = field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
# Competency (from attach_competency_to_profiles)
|
|
45
|
+
competency: ProfileNestedMap = field(default_factory=dict)
|
|
46
|
+
|
|
47
|
+
# Stress response patterns (from stress_response_kpis)
|
|
48
|
+
stress_response: ProfileNestedMap = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
# Light Cone KPIs (from light_cone_kpis)
|
|
51
|
+
light_cone: ProfileNestedMap = field(default_factory=dict)
|
|
52
|
+
|
|
53
|
+
def as_dict(self) -> AgentProfileDict:
|
|
54
|
+
"""
|
|
55
|
+
Flatten into a single dict for printing/JSON export.
|
|
56
|
+
Namespaces econ_* and claim_* keys to avoid collisions.
|
|
57
|
+
"""
|
|
58
|
+
data: AgentProfileDict = {"agent_id": self.agent_id}
|
|
59
|
+
if self.node_id is not None:
|
|
60
|
+
data["node_id"] = self.node_id
|
|
61
|
+
|
|
62
|
+
for k, v in self.econ.items():
|
|
63
|
+
data[f"econ_{k}"] = v
|
|
64
|
+
for k, v in self.claims.items():
|
|
65
|
+
data[f"claim_{k}"] = v
|
|
66
|
+
for k, v in self.influence.items():
|
|
67
|
+
data[f"influence_{k}"] = v
|
|
68
|
+
if self.competency:
|
|
69
|
+
data["competency"] = self.competency
|
|
70
|
+
if self.stress_response:
|
|
71
|
+
data["stress_response"] = self.stress_response
|
|
72
|
+
if self.light_cone:
|
|
73
|
+
data["light_cone"] = self.light_cone
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
def build_agent_profiles(
|
|
77
|
+
tasks_csv_path: PathLike,
|
|
78
|
+
epochs_csv_path: PathLike,
|
|
79
|
+
claims_csv_path: PathLike,
|
|
80
|
+
) -> Dict[str, AgentProfile]:
|
|
81
|
+
"""
|
|
82
|
+
Load CSVs, compute econ + claim KPIs, and join them into AgentProfile objects.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
{agent_id: AgentProfile}
|
|
86
|
+
"""
|
|
87
|
+
tasks = load_tasks_csv(tasks_csv_path)
|
|
88
|
+
epochs = load_epochs_csv(epochs_csv_path)
|
|
89
|
+
claims = load_claims_csv(claims_csv_path)
|
|
90
|
+
|
|
91
|
+
econ_kpis = compute_agent_econ_kpis(tasks)
|
|
92
|
+
claim_kpis = compute_claim_kpis(claims)
|
|
93
|
+
|
|
94
|
+
profiles: Dict[str, AgentProfile] = {}
|
|
95
|
+
|
|
96
|
+
# Seed profiles from econ
|
|
97
|
+
for agent_id, econ_stats in econ_kpis.items():
|
|
98
|
+
profiles.setdefault(agent_id, AgentProfile(agent_id=agent_id)).econ = dict(econ_stats)
|
|
99
|
+
|
|
100
|
+
# Merge claim stats
|
|
101
|
+
for agent_id, claim_stats in claim_kpis.items():
|
|
102
|
+
profile = profiles.setdefault(agent_id, AgentProfile(agent_id=agent_id))
|
|
103
|
+
profile.claims = dict(claim_stats)
|
|
104
|
+
|
|
105
|
+
return profiles
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def attach_competency_to_profiles(
|
|
109
|
+
profiles: Dict[str, AgentProfile],
|
|
110
|
+
competency_summary: AgentCompetencySummaryMap,
|
|
111
|
+
) -> Dict[str, AgentProfile]:
|
|
112
|
+
"""
|
|
113
|
+
Attach competency summaries to existing AgentProfile objects.
|
|
114
|
+
If an agent_id in competency_summary is not present in profiles,
|
|
115
|
+
create a new profile shell for it.
|
|
116
|
+
"""
|
|
117
|
+
for agent_id, comp in competency_summary.items():
|
|
118
|
+
profile = profiles.get(agent_id)
|
|
119
|
+
if profile is None:
|
|
120
|
+
profile = AgentProfile(agent_id=agent_id)
|
|
121
|
+
profiles[agent_id] = profile
|
|
122
|
+
profile.competency = comp
|
|
123
|
+
return profiles
|
|
124
|
+
|
|
125
|
+
def compute_agent_influence_kpis(
|
|
126
|
+
claim_influence_rows: List[ClaimInfluenceRow],
|
|
127
|
+
claims_csv_path: PathLike,
|
|
128
|
+
) -> AgentInfluenceMap:
|
|
129
|
+
"""
|
|
130
|
+
Aggregate claim influence metrics per agent.
|
|
131
|
+
|
|
132
|
+
Uses claims.csv to map claim_id -> agent_id, then aggregates:
|
|
133
|
+
|
|
134
|
+
total_influence
|
|
135
|
+
avg_influence
|
|
136
|
+
num_influenced_claims
|
|
137
|
+
total_net_support
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
{agent_id: {metric_name: value}}
|
|
141
|
+
"""
|
|
142
|
+
claim_rows = load_claims_csv(claims_csv_path)
|
|
143
|
+
claim_to_agent: Dict[str, str] = {}
|
|
144
|
+
for row in claim_rows:
|
|
145
|
+
cid = row.get("id")
|
|
146
|
+
aid = row.get("agent_id") or "unknown"
|
|
147
|
+
if cid:
|
|
148
|
+
claim_to_agent[cid] = aid
|
|
149
|
+
|
|
150
|
+
per_agent: AgentInfluenceMap = {}
|
|
151
|
+
|
|
152
|
+
for row in claim_influence_rows:
|
|
153
|
+
claim_id = row.claim_id
|
|
154
|
+
agent_id = claim_to_agent.get(claim_id, "unknown")
|
|
155
|
+
|
|
156
|
+
stats = per_agent.setdefault(
|
|
157
|
+
agent_id,
|
|
158
|
+
{
|
|
159
|
+
"total_influence": 0.0,
|
|
160
|
+
"num_influenced_claims": 0.0,
|
|
161
|
+
"total_net_support": 0.0,
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
stats["total_influence"] += row.influence_score
|
|
166
|
+
stats["num_influenced_claims"] += 1.0
|
|
167
|
+
stats["total_net_support"] += float(row.net_support)
|
|
168
|
+
|
|
169
|
+
# Compute derived metrics like avg_influence
|
|
170
|
+
for stats in per_agent.values():
|
|
171
|
+
n = stats["num_influenced_claims"]
|
|
172
|
+
stats["avg_influence"] = stats["total_influence"] / n if n > 0 else 0.0
|
|
173
|
+
|
|
174
|
+
return per_agent
|
|
175
|
+
|
|
176
|
+
def attach_influence_to_profiles(
|
|
177
|
+
profiles: Dict[str, AgentProfile],
|
|
178
|
+
claim_influence_rows: List[ClaimInfluenceRow],
|
|
179
|
+
claims_csv_path: PathLike,
|
|
180
|
+
) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Compute per-agent influence KPIs and merge them into existing profiles in-place.
|
|
183
|
+
"""
|
|
184
|
+
influence_kpis = compute_agent_influence_kpis(
|
|
185
|
+
claim_influence_rows,
|
|
186
|
+
claims_csv_path,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
for agent_id, stats in influence_kpis.items():
|
|
190
|
+
profile = profiles.setdefault(agent_id, AgentProfile(agent_id=agent_id))
|
|
191
|
+
profile.influence = dict(stats)
|
|
192
|
+
|
|
193
|
+
def attach_light_cone_to_profiles(
|
|
194
|
+
profiles: Dict[str, AgentProfile],
|
|
195
|
+
light_cone_rows: AgentLightConeMap,
|
|
196
|
+
) -> Dict[str, AgentProfile]:
|
|
197
|
+
"""
|
|
198
|
+
Attach light-cone metrics to existing AgentProfile objects.
|
|
199
|
+
If an agent_id appears only in light_cone_rows, create a shell profile.
|
|
200
|
+
"""
|
|
201
|
+
# Note: light_cone_rows is expected to be Dict[str, AgentLightConeRow]
|
|
202
|
+
# We iterate and access attributes.
|
|
203
|
+
for agent_id, row in light_cone_rows.items():
|
|
204
|
+
profile = profiles.get(agent_id)
|
|
205
|
+
if profile is None:
|
|
206
|
+
profile = AgentProfile(agent_id=agent_id)
|
|
207
|
+
profiles[agent_id] = profile
|
|
208
|
+
profile.light_cone = {
|
|
209
|
+
"reach_score": row.reach_score,
|
|
210
|
+
"horizon_score": row.horizon_score,
|
|
211
|
+
"domain_span": row.domain_span,
|
|
212
|
+
"light_cone_score": row.light_cone_score,
|
|
213
|
+
}
|
|
214
|
+
return profiles
|