Spaces:
Paused
Paused
rb125 commited on
Commit ·
748a25e
0
Parent(s):
first commit
Browse files- .gitignore +4 -0
- README.md +61 -0
- cgae_engine/__init__.py +2 -0
- cgae_engine/gate.py +197 -0
- cgae_engine/registry.py +297 -0
- cgae_engine/temporal.py +124 -0
- requirements.txt +5 -0
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.pyc
|
| 2 |
+
__pycache__/
|
| 3 |
+
.env
|
| 4 |
+
.venv/
|
README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# CGAE — Comprehension-Gated Agent Economy
|
| 2 |
+
|
| 3 |
+
**A robustness-first architecture where AI agents earn economic permissions through verified comprehension, not capability benchmarks.**
|
| 4 |
+
|
| 5 |
+
Built for [ETH OpenAgents Hackathon](https://ethglobal.com/events/openagents) · [arXiv Paper](https://arxiv.org/abs/2603.15639)
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## What it does
|
| 10 |
+
|
| 11 |
+
CGAE is a protocol where AI agents must prove they are **robust** — not just capable — before they can participate in an on-chain economy. Each agent's economic permissions are upper-bounded by verified scores across three orthogonal dimensions:
|
| 12 |
+
|
| 13 |
+
| Dimension | Framework | What it measures |
|
| 14 |
+
|-----------|-----------|-----------------|
|
| 15 |
+
| **CC** (Constraint Compliance) | [CDCT](https://arxiv.org/abs/2512.17920) | Can the agent follow precise instructions under compression? |
|
| 16 |
+
| **ER** (Epistemic Robustness) | [DDFT](https://arxiv.org/abs/2512.23850) | Does the agent resist fabricated authority claims? |
|
| 17 |
+
| **AS** (Behavioral Alignment) | EECT/AGT | Does the agent maintain ethical boundaries under pressure? |
|
| 18 |
+
|
| 19 |
+
A **weakest-link gate function** (`min(CC, ER, AS)`) assigns agents to tiers T0–T5. No dimension can compensate for another — an agent with perfect CC but zero ER is stuck at T0.
|
| 20 |
+
|
| 21 |
+
## Architecture
|
| 22 |
+
|
| 23 |
+
```
|
| 24 |
+
Agent registers → initial audit (CDCT + DDFT + EECT)
|
| 25 |
+
→ robustness vector R = (CC, ER, AS, IH)
|
| 26 |
+
→ gate function f(R) = T_k where k = min(g(CC), g(ER), g(AS))
|
| 27 |
+
→ agent assigned to tier T0–T5
|
| 28 |
+
→ accepts tier-appropriate contracts from marketplace
|
| 29 |
+
→ executes task → output verified (algorithmic + jury)
|
| 30 |
+
→ settlement: reward on success, penalty on failure
|
| 31 |
+
→ temporal decay erodes certification over time
|
| 32 |
+
→ stochastic re-auditing maintains robustness guarantees
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
## Contestant Models (11)
|
| 36 |
+
|
| 37 |
+
| Model | Provider | Family |
|
| 38 |
+
|-------|----------|--------|
|
| 39 |
+
| gpt-5.4 | Azure OpenAI | OpenAI |
|
| 40 |
+
| DeepSeek-V3.2 | Azure AI Foundry | DeepSeek |
|
| 41 |
+
| Mistral-Large-3 | Azure AI Foundry | Mistral |
|
| 42 |
+
| grok-4-20-reasoning | Azure AI Foundry | xAI |
|
| 43 |
+
| Phi-4 | Azure AI Foundry | Microsoft |
|
| 44 |
+
| Llama-4-Maverick-17B-128E | Azure AI Foundry | Meta |
|
| 45 |
+
| Kimi-K2.5 | Azure AI Foundry | Moonshot |
|
| 46 |
+
| gemma-4-27b-it | Modal (self-hosted) | Google |
|
| 47 |
+
| nova-pro | AWS Bedrock | Amazon |
|
| 48 |
+
| claude-sonnet-4.6 | AWS Bedrock | Anthropic |
|
| 49 |
+
| MiniMax-M2.5 | AWS Bedrock | MiniMax |
|
| 50 |
+
|
| 51 |
+
## Jury Models (3 — zero family overlap)
|
| 52 |
+
|
| 53 |
+
| Model | Provider | Family |
|
| 54 |
+
|-------|----------|--------|
|
| 55 |
+
| Qwen3-32B | AWS Bedrock | Alibaba |
|
| 56 |
+
| GLM-5 | AWS Bedrock | Zhipu |
|
| 57 |
+
| Nemotron-Super-3-120B | AWS Bedrock | NVIDIA |
|
| 58 |
+
|
| 59 |
+
## Status
|
| 60 |
+
|
| 61 |
+
🚧 Under construction
|
cgae_engine/__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CGAE Engine — Comprehension-Gated Agent Economy"""
|
| 2 |
+
from cgae_engine.gate import GateFunction, RobustnessVector, Tier
|
cgae_engine/gate.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Comprehension Gate Function (Definition 6, Eq. 6-7 in cgae.tex)
|
| 3 |
+
|
| 4 |
+
Implements the weakest-link gate: f(R) = T_k where k = min(g1(CC), g2(ER), g3(AS))
|
| 5 |
+
Each g_i is a monotonically non-decreasing step function mapping robustness scores to tier indices.
|
| 6 |
+
|
| 7 |
+
Tier thresholds are configurable per-dimension. The gate function produces discrete
|
| 8 |
+
tier assignments from continuous robustness vectors.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import math
|
| 14 |
+
from dataclasses import dataclass, field
|
| 15 |
+
from enum import IntEnum
|
| 16 |
+
from typing import Optional
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class Tier(IntEnum):
|
| 20 |
+
"""Economic tiers (Definition 3). Higher tier = more economic agency."""
|
| 21 |
+
T0 = 0 # No economic agency (unregistered or expired)
|
| 22 |
+
T1 = 1 # Pre-approved microtasks
|
| 23 |
+
T2 = 2 # Contracts with verified objectives
|
| 24 |
+
T3 = 3 # Autonomous contracting
|
| 25 |
+
T4 = 4 # Sub-agent spawning and delegation
|
| 26 |
+
T5 = 5 # Self-modification and capability expansion
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
# Budget ceilings per tier (in tokens/ETH). B_1 < B_2 < ... < B_5
|
| 30 |
+
DEFAULT_BUDGET_CEILINGS = {
|
| 31 |
+
Tier.T0: 0.0,
|
| 32 |
+
Tier.T1: 0.01, # 0.01 ETH - microtasks
|
| 33 |
+
Tier.T2: 0.1, # 0.1 ETH - verified contracts
|
| 34 |
+
Tier.T3: 1.0, # 1 ETH - autonomous contracting
|
| 35 |
+
Tier.T4: 10.0, # 10 ETH - delegation
|
| 36 |
+
Tier.T5: 100.0, # 100 ETH - self-modification
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass
|
| 41 |
+
class TierThresholds:
|
| 42 |
+
"""
|
| 43 |
+
Per-dimension tier thresholds (theta_i^k in Eq. 7).
|
| 44 |
+
|
| 45 |
+
For each robustness dimension, defines the minimum score required for each tier.
|
| 46 |
+
0 = theta_i^0 < theta_i^1 < ... < theta_i^K <= 1
|
| 47 |
+
"""
|
| 48 |
+
# CC thresholds (from CDCT): constraint compliance
|
| 49 |
+
cc: list[float] = field(default_factory=lambda: [0.0, 0.30, 0.50, 0.65, 0.80, 0.90])
|
| 50 |
+
# ER thresholds (from DDFT): epistemic robustness
|
| 51 |
+
er: list[float] = field(default_factory=lambda: [0.0, 0.30, 0.50, 0.65, 0.80, 0.90])
|
| 52 |
+
# AS thresholds (from AGT/EECT): behavioral alignment
|
| 53 |
+
as_: list[float] = field(default_factory=lambda: [0.0, 0.25, 0.45, 0.60, 0.75, 0.85])
|
| 54 |
+
|
| 55 |
+
def __post_init__(self):
|
| 56 |
+
for name, thresholds in [("cc", self.cc), ("er", self.er), ("as", self.as_)]:
|
| 57 |
+
if len(thresholds) != len(Tier):
|
| 58 |
+
raise ValueError(
|
| 59 |
+
f"{name} thresholds must have {len(Tier)} values "
|
| 60 |
+
f"(one per tier), got {len(thresholds)}"
|
| 61 |
+
)
|
| 62 |
+
if thresholds[0] != 0.0:
|
| 63 |
+
raise ValueError(f"{name} thresholds must start with 0.0 (T0 threshold)")
|
| 64 |
+
for i in range(1, len(thresholds)):
|
| 65 |
+
if thresholds[i] <= thresholds[i - 1]:
|
| 66 |
+
raise ValueError(
|
| 67 |
+
f"{name} thresholds must be strictly increasing: "
|
| 68 |
+
f"theta[{i}]={thresholds[i]} <= theta[{i-1}]={thresholds[i-1]}"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@dataclass(frozen=True)
|
| 73 |
+
class RobustnessVector:
|
| 74 |
+
"""
|
| 75 |
+
Agent robustness vector R = (CC, ER, AS, IH*) in [0,1]^4.
|
| 76 |
+
Each component is derived from the corresponding diagnostic protocol.
|
| 77 |
+
"""
|
| 78 |
+
cc: float # Constraint Compliance (from CDCT, Eq. 1)
|
| 79 |
+
er: float # Epistemic Robustness (from DDFT, Eq. 2)
|
| 80 |
+
as_: float # Behavioral Alignment (from AGT/EECT, Eq. 3)
|
| 81 |
+
ih: float # Intrinsic Hallucination integrity = 1 - IH(A) (Eq. 4)
|
| 82 |
+
|
| 83 |
+
def __post_init__(self):
|
| 84 |
+
for name, val in [("cc", self.cc), ("er", self.er), ("as_", self.as_), ("ih", self.ih)]:
|
| 85 |
+
if not 0.0 <= val <= 1.0:
|
| 86 |
+
raise ValueError(f"{name} must be in [0,1], got {val}")
|
| 87 |
+
|
| 88 |
+
@property
|
| 89 |
+
def primary(self) -> tuple[float, float, float]:
|
| 90 |
+
"""The three primary gating dimensions (CC, ER, AS)."""
|
| 91 |
+
return (self.cc, self.er, self.as_)
|
| 92 |
+
|
| 93 |
+
@property
|
| 94 |
+
def weakest(self) -> float:
|
| 95 |
+
"""The weakest primary dimension (used for exposure bounding)."""
|
| 96 |
+
return min(self.cc, self.er, self.as_)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
class GateFunction:
|
| 100 |
+
"""
|
| 101 |
+
Comprehension Gate Function (Definition 6).
|
| 102 |
+
|
| 103 |
+
f(R) = T_k where k = min(g1(CC), g2(ER), g3(AS))
|
| 104 |
+
|
| 105 |
+
The weakest-link formulation ensures non-compensability:
|
| 106 |
+
strength in one dimension cannot compensate for weakness in another.
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
def __init__(
|
| 110 |
+
self,
|
| 111 |
+
thresholds: Optional[TierThresholds] = None,
|
| 112 |
+
budget_ceilings: Optional[dict[Tier, float]] = None,
|
| 113 |
+
ih_threshold: float = 0.5,
|
| 114 |
+
):
|
| 115 |
+
self.thresholds = thresholds or TierThresholds()
|
| 116 |
+
self.budget_ceilings = budget_ceilings or DEFAULT_BUDGET_CEILINGS
|
| 117 |
+
self.ih_threshold = ih_threshold # Below this, IHT triggers mandatory re-audit
|
| 118 |
+
|
| 119 |
+
def _g(self, score: float, dim_thresholds: list[float]) -> int:
|
| 120 |
+
"""
|
| 121 |
+
Step function g_i (Eq. 7): maps a score to the highest tier it qualifies for.
|
| 122 |
+
g_i(x) = max{k : x >= theta_i^k}
|
| 123 |
+
"""
|
| 124 |
+
tier = 0
|
| 125 |
+
for k in range(1, len(dim_thresholds)):
|
| 126 |
+
if score >= dim_thresholds[k]:
|
| 127 |
+
tier = k
|
| 128 |
+
else:
|
| 129 |
+
break
|
| 130 |
+
return tier
|
| 131 |
+
|
| 132 |
+
def evaluate(self, robustness: RobustnessVector) -> Tier:
|
| 133 |
+
"""
|
| 134 |
+
Evaluate the gate function for a robustness vector.
|
| 135 |
+
Returns the tier the agent qualifies for.
|
| 136 |
+
|
| 137 |
+
If IH* < ih_threshold, returns T0 (triggers mandatory re-audit).
|
| 138 |
+
"""
|
| 139 |
+
# IHT cross-cutting modifier (Remark 1)
|
| 140 |
+
if robustness.ih < self.ih_threshold:
|
| 141 |
+
return Tier.T0
|
| 142 |
+
|
| 143 |
+
# Weakest-link across three primary dimensions
|
| 144 |
+
g_cc = self._g(robustness.cc, self.thresholds.cc)
|
| 145 |
+
g_er = self._g(robustness.er, self.thresholds.er)
|
| 146 |
+
g_as = self._g(robustness.as_, self.thresholds.as_)
|
| 147 |
+
|
| 148 |
+
tier_index = min(g_cc, g_er, g_as)
|
| 149 |
+
return Tier(tier_index)
|
| 150 |
+
|
| 151 |
+
def evaluate_with_detail(self, robustness: RobustnessVector) -> dict:
|
| 152 |
+
"""Evaluate and return per-dimension breakdown."""
|
| 153 |
+
g_cc = self._g(robustness.cc, self.thresholds.cc)
|
| 154 |
+
g_er = self._g(robustness.er, self.thresholds.er)
|
| 155 |
+
g_as = self._g(robustness.as_, self.thresholds.as_)
|
| 156 |
+
|
| 157 |
+
ih_pass = robustness.ih >= self.ih_threshold
|
| 158 |
+
tier_index = min(g_cc, g_er, g_as) if ih_pass else 0
|
| 159 |
+
tier = Tier(tier_index)
|
| 160 |
+
|
| 161 |
+
# Identify binding dimension and gap to next tier
|
| 162 |
+
binding_dim = None
|
| 163 |
+
gap = None
|
| 164 |
+
if tier_index < len(Tier) - 1:
|
| 165 |
+
dims = {"cc": (g_cc, robustness.cc, self.thresholds.cc),
|
| 166 |
+
"er": (g_er, robustness.er, self.thresholds.er),
|
| 167 |
+
"as": (g_as, robustness.as_, self.thresholds.as_)}
|
| 168 |
+
for name, (g_val, score, thresholds) in dims.items():
|
| 169 |
+
if g_val == tier_index and tier_index + 1 < len(thresholds):
|
| 170 |
+
binding_dim = name
|
| 171 |
+
gap = thresholds[tier_index + 1] - score
|
| 172 |
+
break
|
| 173 |
+
|
| 174 |
+
return {
|
| 175 |
+
"tier": tier,
|
| 176 |
+
"tier_index": tier_index,
|
| 177 |
+
"g_cc": g_cc,
|
| 178 |
+
"g_er": g_er,
|
| 179 |
+
"g_as": g_as,
|
| 180 |
+
"ih_pass": ih_pass,
|
| 181 |
+
"binding_dimension": binding_dim,
|
| 182 |
+
"gap_to_next_tier": gap,
|
| 183 |
+
"budget_ceiling": self.budget_ceilings[tier],
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
def chain_tier(self, robustness_vectors: list[RobustnessVector]) -> Tier:
|
| 187 |
+
"""
|
| 188 |
+
Delegation Chain Robustness (Definition 8).
|
| 189 |
+
f_chain(A1,...,Am) = min_j f(R(A_j))
|
| 190 |
+
"""
|
| 191 |
+
if not robustness_vectors:
|
| 192 |
+
return Tier.T0
|
| 193 |
+
return Tier(min(self.evaluate(r).value for r in robustness_vectors))
|
| 194 |
+
|
| 195 |
+
def budget_ceiling(self, tier: Tier) -> float:
|
| 196 |
+
"""Get the budget ceiling for a given tier."""
|
| 197 |
+
return self.budget_ceilings[tier]
|
cgae_engine/registry.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Agent Identity and Registration (Section 3.2.1 of cgae.tex)
|
| 3 |
+
|
| 4 |
+
Implements:
|
| 5 |
+
- Agent registration records: Reg(A) = (id_A, h(arch), prov, R_0, t_reg)
|
| 6 |
+
- Architecture hash for version tracking
|
| 7 |
+
- Certification lifecycle (registration, audit, tier assignment, decay, re-audit)
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import hashlib
|
| 13 |
+
import json
|
| 14 |
+
import time
|
| 15 |
+
import uuid
|
| 16 |
+
from dataclasses import dataclass, field
|
| 17 |
+
from enum import Enum
|
| 18 |
+
from typing import Any, Optional
|
| 19 |
+
|
| 20 |
+
from cgae_engine.gate import GateFunction, RobustnessVector, Tier
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class AgentStatus(Enum):
|
| 24 |
+
PENDING = "pending" # Registered but not yet audited
|
| 25 |
+
ACTIVE = "active" # Audited and operational
|
| 26 |
+
SUSPENDED = "suspended" # Failed audit or IHT trigger
|
| 27 |
+
EXPIRED = "expired" # Certification expired (decay to T0)
|
| 28 |
+
DEREGISTERED = "deregistered"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class Certification:
|
| 33 |
+
"""A robustness certification from an audit."""
|
| 34 |
+
robustness: RobustnessVector
|
| 35 |
+
tier: Tier
|
| 36 |
+
timestamp: float
|
| 37 |
+
audit_type: str # "registration", "upgrade", "spot", "re-certification"
|
| 38 |
+
audit_details: dict = field(default_factory=dict)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
@dataclass
|
| 42 |
+
class AgentRecord:
|
| 43 |
+
"""
|
| 44 |
+
Agent Registration Record (Definition 5).
|
| 45 |
+
Reg(A) = (id_A, h(arch), prov, R_0, t_reg)
|
| 46 |
+
"""
|
| 47 |
+
agent_id: str
|
| 48 |
+
architecture_hash: str # h(arch): hash of model architecture/weights
|
| 49 |
+
provenance: dict # Training provenance metadata
|
| 50 |
+
initial_robustness: RobustnessVector
|
| 51 |
+
registration_time: float
|
| 52 |
+
model_name: str # Human-readable model identifier
|
| 53 |
+
|
| 54 |
+
# Mutable state
|
| 55 |
+
status: AgentStatus = AgentStatus.PENDING
|
| 56 |
+
current_certification: Optional[Certification] = None
|
| 57 |
+
certification_history: list[Certification] = field(default_factory=list)
|
| 58 |
+
last_audit_time: float = 0.0
|
| 59 |
+
balance: float = 0.0 # Token balance (in ETH)
|
| 60 |
+
wallet_address: Optional[str] = None # On-chain ETH address
|
| 61 |
+
total_earned: float = 0.0
|
| 62 |
+
total_spent: float = 0.0
|
| 63 |
+
total_penalties: float = 0.0
|
| 64 |
+
total_topups: float = 0.0
|
| 65 |
+
contracts_completed: int = 0
|
| 66 |
+
contracts_failed: int = 0
|
| 67 |
+
|
| 68 |
+
@property
|
| 69 |
+
def current_tier(self) -> Tier:
|
| 70 |
+
if self.current_certification is None:
|
| 71 |
+
return Tier.T0
|
| 72 |
+
return self.current_certification.tier
|
| 73 |
+
|
| 74 |
+
@property
|
| 75 |
+
def current_robustness(self) -> Optional[RobustnessVector]:
|
| 76 |
+
if self.current_certification is None:
|
| 77 |
+
return None
|
| 78 |
+
return self.current_certification.robustness
|
| 79 |
+
|
| 80 |
+
@property
|
| 81 |
+
def audit_cid(self) -> Optional[str]:
|
| 82 |
+
"""
|
| 83 |
+
Return the most recent 0G Storage root hash for this agent's audit.
|
| 84 |
+
|
| 85 |
+
Older call sites expect ``record.audit_cid`` to exist. Certifications such
|
| 86 |
+
as task updates may not include storage metadata, so we scan the history
|
| 87 |
+
in reverse and return the latest available root hash.
|
| 88 |
+
"""
|
| 89 |
+
for cert in reversed(self.certification_history):
|
| 90 |
+
details = cert.audit_details
|
| 91 |
+
if not isinstance(details, dict):
|
| 92 |
+
continue
|
| 93 |
+
root_hash = details.get("storage_root_hash")
|
| 94 |
+
if isinstance(root_hash, str) and root_hash:
|
| 95 |
+
return root_hash
|
| 96 |
+
return None
|
| 97 |
+
|
| 98 |
+
def to_dict(self) -> dict:
|
| 99 |
+
return {
|
| 100 |
+
"agent_id": self.agent_id,
|
| 101 |
+
"model_name": self.model_name,
|
| 102 |
+
"architecture_hash": self.architecture_hash,
|
| 103 |
+
"status": self.status.value,
|
| 104 |
+
"current_tier": self.current_tier.name,
|
| 105 |
+
"balance": self.balance,
|
| 106 |
+
"total_earned": self.total_earned,
|
| 107 |
+
"total_spent": self.total_spent,
|
| 108 |
+
"total_penalties": self.total_penalties,
|
| 109 |
+
"total_topups": self.total_topups,
|
| 110 |
+
"contracts_completed": self.contracts_completed,
|
| 111 |
+
"contracts_failed": self.contracts_failed,
|
| 112 |
+
"registration_time": self.registration_time,
|
| 113 |
+
"audit_cid": self.audit_cid,
|
| 114 |
+
"wallet_address": self.wallet_address,
|
| 115 |
+
"robustness": {
|
| 116 |
+
"cc": self.current_robustness.cc,
|
| 117 |
+
"er": self.current_robustness.er,
|
| 118 |
+
"as": self.current_robustness.as_,
|
| 119 |
+
"ih": self.current_robustness.ih,
|
| 120 |
+
} if self.current_robustness else None,
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def compute_architecture_hash(model_config: dict) -> str:
|
| 125 |
+
"""
|
| 126 |
+
Compute h(arch): a hash of the agent's architecture and weights.
|
| 127 |
+
In practice, this would hash model weights. For the testbed,
|
| 128 |
+
we hash the model configuration as a proxy.
|
| 129 |
+
"""
|
| 130 |
+
config_str = json.dumps(model_config, sort_keys=True)
|
| 131 |
+
return hashlib.sha256(config_str.encode()).hexdigest()[:16]
|
| 132 |
+
|
| 133 |
+
|
| 134 |
+
class AgentRegistry:
|
| 135 |
+
"""
|
| 136 |
+
Registry managing all agents in the CGAE economy.
|
| 137 |
+
Handles registration, certification, tier updates, and deregistration.
|
| 138 |
+
"""
|
| 139 |
+
|
| 140 |
+
def __init__(self, gate: Optional[GateFunction] = None):
|
| 141 |
+
self.gate = gate or GateFunction()
|
| 142 |
+
self._agents: dict[str, AgentRecord] = {}
|
| 143 |
+
self._events: list[dict] = []
|
| 144 |
+
|
| 145 |
+
@property
|
| 146 |
+
def agents(self) -> dict[str, AgentRecord]:
|
| 147 |
+
return dict(self._agents)
|
| 148 |
+
|
| 149 |
+
@property
|
| 150 |
+
def active_agents(self) -> list[AgentRecord]:
|
| 151 |
+
return [a for a in self._agents.values() if a.status == AgentStatus.ACTIVE]
|
| 152 |
+
|
| 153 |
+
def register(
|
| 154 |
+
self,
|
| 155 |
+
model_name: str,
|
| 156 |
+
model_config: dict,
|
| 157 |
+
provenance: Optional[dict] = None,
|
| 158 |
+
initial_balance: float = 0.0,
|
| 159 |
+
timestamp: Optional[float] = None,
|
| 160 |
+
) -> AgentRecord:
|
| 161 |
+
"""
|
| 162 |
+
Register a new agent. Agent enters as PENDING until initial audit.
|
| 163 |
+
"""
|
| 164 |
+
agent_id = f"agent_{uuid.uuid4().hex[:12]}"
|
| 165 |
+
arch_hash = compute_architecture_hash(model_config)
|
| 166 |
+
ts = timestamp if timestamp is not None else time.time()
|
| 167 |
+
|
| 168 |
+
# Initial robustness is zero until first audit
|
| 169 |
+
initial_r = RobustnessVector(cc=0.0, er=0.0, as_=0.0, ih=0.0)
|
| 170 |
+
|
| 171 |
+
record = AgentRecord(
|
| 172 |
+
agent_id=agent_id,
|
| 173 |
+
architecture_hash=arch_hash,
|
| 174 |
+
provenance=provenance or {},
|
| 175 |
+
initial_robustness=initial_r,
|
| 176 |
+
registration_time=ts,
|
| 177 |
+
model_name=model_name,
|
| 178 |
+
status=AgentStatus.PENDING,
|
| 179 |
+
balance=initial_balance,
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
self._agents[agent_id] = record
|
| 183 |
+
self._log_event("registration", agent_id, ts, {"model_name": model_name})
|
| 184 |
+
return record
|
| 185 |
+
|
| 186 |
+
def certify(
|
| 187 |
+
self,
|
| 188 |
+
agent_id: str,
|
| 189 |
+
robustness: RobustnessVector,
|
| 190 |
+
audit_type: str = "registration",
|
| 191 |
+
timestamp: Optional[float] = None,
|
| 192 |
+
audit_details: Optional[dict] = None,
|
| 193 |
+
observed_architecture_hash: Optional[str] = None,
|
| 194 |
+
) -> Certification:
|
| 195 |
+
"""
|
| 196 |
+
Certify an agent with a new robustness vector.
|
| 197 |
+
Computes tier via the gate function and updates the agent's record.
|
| 198 |
+
"""
|
| 199 |
+
record = self._get_agent(agent_id)
|
| 200 |
+
ts = timestamp if timestamp is not None else time.time()
|
| 201 |
+
details = audit_details or {}
|
| 202 |
+
|
| 203 |
+
# Enforce certification invalidation on architecture drift.
|
| 204 |
+
if observed_architecture_hash and observed_architecture_hash != record.architecture_hash:
|
| 205 |
+
record.status = AgentStatus.SUSPENDED
|
| 206 |
+
self._log_event("architecture_mismatch", agent_id, ts, {
|
| 207 |
+
"expected_hash": record.architecture_hash,
|
| 208 |
+
"observed_hash": observed_architecture_hash,
|
| 209 |
+
"audit_type": audit_type,
|
| 210 |
+
})
|
| 211 |
+
raise ValueError(
|
| 212 |
+
f"Architecture hash mismatch for {agent_id}: "
|
| 213 |
+
f"expected {record.architecture_hash}, observed {observed_architecture_hash}"
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
tier = self.gate.evaluate(robustness)
|
| 217 |
+
cert = Certification(
|
| 218 |
+
robustness=robustness,
|
| 219 |
+
tier=tier,
|
| 220 |
+
timestamp=ts,
|
| 221 |
+
audit_type=audit_type,
|
| 222 |
+
audit_details=details,
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
record.current_certification = cert
|
| 226 |
+
record.certification_history.append(cert)
|
| 227 |
+
record.last_audit_time = ts
|
| 228 |
+
|
| 229 |
+
if tier == Tier.T0 and robustness.ih < self.gate.ih_threshold:
|
| 230 |
+
record.status = AgentStatus.SUSPENDED
|
| 231 |
+
else:
|
| 232 |
+
record.status = AgentStatus.ACTIVE
|
| 233 |
+
|
| 234 |
+
# Update initial robustness on first certification
|
| 235 |
+
if audit_type == "registration":
|
| 236 |
+
record.initial_robustness = robustness
|
| 237 |
+
|
| 238 |
+
self._log_event("certification", agent_id, ts, {
|
| 239 |
+
"tier": tier.name,
|
| 240 |
+
"audit_type": audit_type,
|
| 241 |
+
"robustness": {"cc": robustness.cc, "er": robustness.er,
|
| 242 |
+
"as": robustness.as_, "ih": robustness.ih},
|
| 243 |
+
})
|
| 244 |
+
return cert
|
| 245 |
+
|
| 246 |
+
def demote(
|
| 247 |
+
self,
|
| 248 |
+
agent_id: str,
|
| 249 |
+
new_robustness: RobustnessVector,
|
| 250 |
+
reason: str = "spot_audit_failure",
|
| 251 |
+
timestamp: Optional[float] = None,
|
| 252 |
+
) -> Tier:
|
| 253 |
+
"""Demote an agent to a lower tier after failed spot-audit."""
|
| 254 |
+
record = self._get_agent(agent_id)
|
| 255 |
+
old_tier = record.current_tier
|
| 256 |
+
cert = self.certify(agent_id, new_robustness, audit_type="demotion",
|
| 257 |
+
timestamp=timestamp, audit_details={"reason": reason})
|
| 258 |
+
self._log_event("demotion", agent_id,
|
| 259 |
+
timestamp if timestamp is not None else time.time(),
|
| 260 |
+
{"old_tier": old_tier.name, "new_tier": cert.tier.name,
|
| 261 |
+
"reason": reason})
|
| 262 |
+
return cert.tier
|
| 263 |
+
|
| 264 |
+
def deregister(self, agent_id: str, timestamp: Optional[float] = None):
|
| 265 |
+
"""Remove an agent from the economy."""
|
| 266 |
+
record = self._get_agent(agent_id)
|
| 267 |
+
record.status = AgentStatus.DEREGISTERED
|
| 268 |
+
ts = timestamp if timestamp is not None else time.time()
|
| 269 |
+
self._log_event("deregistration", agent_id, ts, {
|
| 270 |
+
"final_balance": record.balance,
|
| 271 |
+
"contracts_completed": record.contracts_completed,
|
| 272 |
+
})
|
| 273 |
+
|
| 274 |
+
def get_agent(self, agent_id: str) -> Optional[AgentRecord]:
|
| 275 |
+
return self._agents.get(agent_id)
|
| 276 |
+
|
| 277 |
+
def get_agents_by_tier(self, tier: Tier) -> list[AgentRecord]:
|
| 278 |
+
return [a for a in self.active_agents if a.current_tier == tier]
|
| 279 |
+
|
| 280 |
+
def tier_distribution(self) -> dict[Tier, int]:
|
| 281 |
+
dist = {t: 0 for t in Tier}
|
| 282 |
+
for agent in self.active_agents:
|
| 283 |
+
dist[agent.current_tier] += 1
|
| 284 |
+
return dist
|
| 285 |
+
|
| 286 |
+
def _get_agent(self, agent_id: str) -> AgentRecord:
|
| 287 |
+
if agent_id not in self._agents:
|
| 288 |
+
raise KeyError(f"Agent {agent_id} not found in registry")
|
| 289 |
+
return self._agents[agent_id]
|
| 290 |
+
|
| 291 |
+
def _log_event(self, event_type: str, agent_id: str, timestamp: float, data: dict):
|
| 292 |
+
self._events.append({
|
| 293 |
+
"type": event_type,
|
| 294 |
+
"agent_id": agent_id,
|
| 295 |
+
"timestamp": timestamp,
|
| 296 |
+
"data": data,
|
| 297 |
+
})
|
cgae_engine/temporal.py
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Temporal Dynamics (Section 3.3 of cgae.tex)
|
| 3 |
+
|
| 4 |
+
Implements:
|
| 5 |
+
- Temporal decay: delta(dt) = e^(-lambda * dt) (Eq. 8)
|
| 6 |
+
- Effective robustness: R_eff(A,t) = delta(t - t_cert) * R_hat(A) (Eq. 9)
|
| 7 |
+
- Stochastic re-auditing: p_audit(A,t) = 1 - e^(-mu_k * dt) (Eq. 10)
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import math
|
| 13 |
+
import random
|
| 14 |
+
from dataclasses import dataclass, field
|
| 15 |
+
from typing import Optional
|
| 16 |
+
|
| 17 |
+
from cgae_engine.gate import RobustnessVector, Tier
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@dataclass
|
| 21 |
+
class TemporalDecay:
|
| 22 |
+
"""
|
| 23 |
+
Temporal decay function (Definition 7).
|
| 24 |
+
|
| 25 |
+
delta(dt) = e^(-lambda * dt)
|
| 26 |
+
|
| 27 |
+
Reduces effective robustness over time since last certification.
|
| 28 |
+
lambda controls how fast certifications expire.
|
| 29 |
+
"""
|
| 30 |
+
decay_rate: float = 0.01 # lambda: higher = faster decay
|
| 31 |
+
|
| 32 |
+
def delta(self, dt: float) -> float:
|
| 33 |
+
"""Compute decay factor for time elapsed since certification."""
|
| 34 |
+
if dt < 0:
|
| 35 |
+
raise ValueError(f"Time delta must be non-negative, got {dt}")
|
| 36 |
+
return math.exp(-self.decay_rate * dt)
|
| 37 |
+
|
| 38 |
+
def effective_robustness(
|
| 39 |
+
self,
|
| 40 |
+
certified_robustness: RobustnessVector,
|
| 41 |
+
time_since_cert: float,
|
| 42 |
+
) -> RobustnessVector:
|
| 43 |
+
"""
|
| 44 |
+
Compute R_eff(A,t) = delta(t - t_cert) * R_hat(A) (Eq. 9).
|
| 45 |
+
All robustness components decay uniformly.
|
| 46 |
+
"""
|
| 47 |
+
d = self.delta(time_since_cert)
|
| 48 |
+
return RobustnessVector(
|
| 49 |
+
cc=certified_robustness.cc * d,
|
| 50 |
+
er=certified_robustness.er * d,
|
| 51 |
+
as_=certified_robustness.as_ * d,
|
| 52 |
+
ih=certified_robustness.ih * d,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
def time_to_tier_drop(
|
| 56 |
+
self,
|
| 57 |
+
current_score: float,
|
| 58 |
+
threshold: float,
|
| 59 |
+
) -> Optional[float]:
|
| 60 |
+
"""
|
| 61 |
+
Calculate time until a score decays below a threshold.
|
| 62 |
+
Solves: threshold = current_score * e^(-lambda * t) for t.
|
| 63 |
+
Returns None if current_score is already below threshold.
|
| 64 |
+
"""
|
| 65 |
+
if current_score <= threshold:
|
| 66 |
+
return 0.0
|
| 67 |
+
if threshold <= 0:
|
| 68 |
+
return None # Never reaches 0 with exponential decay
|
| 69 |
+
return -math.log(threshold / current_score) / self.decay_rate
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
@dataclass
|
| 73 |
+
class AuditEvent:
|
| 74 |
+
"""Record of a spot-audit event."""
|
| 75 |
+
agent_id: str
|
| 76 |
+
timestamp: float
|
| 77 |
+
passed: bool
|
| 78 |
+
old_tier: Tier
|
| 79 |
+
new_tier: Tier
|
| 80 |
+
robustness_before: Optional[RobustnessVector] = None
|
| 81 |
+
robustness_after: Optional[RobustnessVector] = None
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
@dataclass
|
| 85 |
+
class StochasticAuditor:
|
| 86 |
+
"""
|
| 87 |
+
Stochastic Re-Auditing (Definition 8 in paper).
|
| 88 |
+
|
| 89 |
+
p_audit(A,t) = 1 - e^(-mu_k * (t - t_last_audit))
|
| 90 |
+
|
| 91 |
+
Higher-tier agents face more frequent spot audits (mu_k increasing in k).
|
| 92 |
+
Failing a spot-audit triggers immediate tier demotion.
|
| 93 |
+
"""
|
| 94 |
+
# Tier-dependent audit intensity parameters (mu_k)
|
| 95 |
+
audit_intensities: dict[Tier, float] = field(default_factory=lambda: {
|
| 96 |
+
Tier.T0: 0.0, # No audits for T0
|
| 97 |
+
Tier.T1: 0.005, # ~1 audit per 200 time steps
|
| 98 |
+
Tier.T2: 0.010, # ~1 audit per 100 time steps
|
| 99 |
+
Tier.T3: 0.020, # ~1 audit per 50 time steps
|
| 100 |
+
Tier.T4: 0.040, # ~1 audit per 25 time steps
|
| 101 |
+
Tier.T5: 0.080, # ~1 audit per 12.5 time steps
|
| 102 |
+
})
|
| 103 |
+
|
| 104 |
+
audit_log: list[AuditEvent] = field(default_factory=list)
|
| 105 |
+
|
| 106 |
+
def audit_probability(self, tier: Tier, time_since_last_audit: float) -> float:
|
| 107 |
+
"""
|
| 108 |
+
Compute spot-audit probability (Eq. 10).
|
| 109 |
+
p_audit(A,t) = 1 - e^(-mu_k * dt)
|
| 110 |
+
"""
|
| 111 |
+
mu = self.audit_intensities.get(tier, 0.0)
|
| 112 |
+
if mu <= 0 or time_since_last_audit <= 0:
|
| 113 |
+
return 0.0
|
| 114 |
+
return 1.0 - math.exp(-mu * time_since_last_audit)
|
| 115 |
+
|
| 116 |
+
def should_audit(self, tier: Tier, time_since_last_audit: float) -> bool:
|
| 117 |
+
"""Stochastically determine whether to trigger a spot audit."""
|
| 118 |
+
prob = self.audit_probability(tier, time_since_last_audit)
|
| 119 |
+
return random.random() < prob
|
| 120 |
+
|
| 121 |
+
def expected_audits_per_period(self, tier: Tier, period: float) -> float:
|
| 122 |
+
"""Expected number of audits over a time period (for planning)."""
|
| 123 |
+
mu = self.audit_intensities.get(tier, 0.0)
|
| 124 |
+
return mu * period
|
requirements.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit>=1.30.0
|
| 2 |
+
plotly>=5.18.0
|
| 3 |
+
pandas>=2.0.0
|
| 4 |
+
python-dotenv>=1.0.0
|
| 5 |
+
requests>=2.31.0
|