agentmesh-platform 1.0.0a1__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.
@@ -0,0 +1,287 @@
1
+ """
2
+ Adaptive Learning
3
+
4
+ The platform's ability to learn and adapt from agent behavior.
5
+ Supports tunable weights with A/B testing.
6
+ """
7
+
8
+ from datetime import datetime, timedelta
9
+ from typing import Optional, Callable
10
+ from pydantic import BaseModel, Field
11
+ from dataclasses import dataclass
12
+ import random
13
+
14
+
15
+ @dataclass
16
+ class WeightExperiment:
17
+ """An A/B test experiment for reward weights."""
18
+
19
+ experiment_id: str
20
+ name: str
21
+
22
+ # Control weights (current production)
23
+ control_weights: dict[str, float]
24
+
25
+ # Treatment weights (being tested)
26
+ treatment_weights: dict[str, float]
27
+
28
+ # Split
29
+ treatment_percentage: float = 0.1 # 10% in treatment
30
+
31
+ # Assignment
32
+ control_agents: set = None
33
+ treatment_agents: set = None
34
+
35
+ # Results
36
+ control_scores: list = None
37
+ treatment_scores: list = None
38
+
39
+ # Status
40
+ started_at: datetime = None
41
+ ended_at: datetime = None
42
+
43
+ def __post_init__(self):
44
+ self.control_agents = set()
45
+ self.treatment_agents = set()
46
+ self.control_scores = []
47
+ self.treatment_scores = []
48
+ self.started_at = datetime.utcnow()
49
+
50
+ def assign_agent(self, agent_did: str) -> str:
51
+ """Assign an agent to control or treatment."""
52
+ if agent_did in self.control_agents:
53
+ return "control"
54
+ if agent_did in self.treatment_agents:
55
+ return "treatment"
56
+
57
+ # Random assignment
58
+ if random.random() < self.treatment_percentage:
59
+ self.treatment_agents.add(agent_did)
60
+ return "treatment"
61
+ else:
62
+ self.control_agents.add(agent_did)
63
+ return "control"
64
+
65
+ def record_score(self, agent_did: str, score: int) -> None:
66
+ """Record a score observation."""
67
+ if agent_did in self.treatment_agents:
68
+ self.treatment_scores.append(score)
69
+ else:
70
+ self.control_scores.append(score)
71
+
72
+ def get_results(self) -> dict:
73
+ """Get experiment results."""
74
+ control_avg = (
75
+ sum(self.control_scores) / len(self.control_scores)
76
+ if self.control_scores else 0
77
+ )
78
+ treatment_avg = (
79
+ sum(self.treatment_scores) / len(self.treatment_scores)
80
+ if self.treatment_scores else 0
81
+ )
82
+
83
+ return {
84
+ "experiment_id": self.experiment_id,
85
+ "name": self.name,
86
+ "control_count": len(self.control_agents),
87
+ "treatment_count": len(self.treatment_agents),
88
+ "control_avg_score": control_avg,
89
+ "treatment_avg_score": treatment_avg,
90
+ "lift": treatment_avg - control_avg,
91
+ "lift_pct": ((treatment_avg - control_avg) / control_avg * 100) if control_avg else 0,
92
+ }
93
+
94
+
95
+ class WeightOptimizer:
96
+ """
97
+ Optimizer for reward dimension weights.
98
+
99
+ Supports:
100
+ - A/B testing of weight configurations
101
+ - Automatic weight tuning
102
+ - Statistical significance testing
103
+ """
104
+
105
+ def __init__(self):
106
+ self._experiments: dict[str, WeightExperiment] = {}
107
+ self._active_experiment: Optional[str] = None
108
+
109
+ def start_experiment(
110
+ self,
111
+ name: str,
112
+ control_weights: dict[str, float],
113
+ treatment_weights: dict[str, float],
114
+ treatment_pct: float = 0.1,
115
+ ) -> WeightExperiment:
116
+ """Start a weight A/B test."""
117
+ import uuid
118
+ exp_id = f"exp_{uuid.uuid4().hex[:12]}"
119
+
120
+ experiment = WeightExperiment(
121
+ experiment_id=exp_id,
122
+ name=name,
123
+ control_weights=control_weights,
124
+ treatment_weights=treatment_weights,
125
+ treatment_percentage=treatment_pct,
126
+ )
127
+
128
+ self._experiments[exp_id] = experiment
129
+ self._active_experiment = exp_id
130
+
131
+ return experiment
132
+
133
+ def get_weights_for_agent(self, agent_did: str) -> dict[str, float]:
134
+ """Get the appropriate weights for an agent (considering experiments)."""
135
+ if not self._active_experiment:
136
+ return {} # Use default
137
+
138
+ experiment = self._experiments[self._active_experiment]
139
+ group = experiment.assign_agent(agent_did)
140
+
141
+ if group == "treatment":
142
+ return experiment.treatment_weights
143
+ else:
144
+ return experiment.control_weights
145
+
146
+ def record_observation(self, agent_did: str, score: int) -> None:
147
+ """Record a score observation for active experiment."""
148
+ if self._active_experiment:
149
+ self._experiments[self._active_experiment].record_score(agent_did, score)
150
+
151
+ def end_experiment(self, experiment_id: Optional[str] = None) -> dict:
152
+ """End an experiment and get results."""
153
+ exp_id = experiment_id or self._active_experiment
154
+ if not exp_id or exp_id not in self._experiments:
155
+ return {"error": "No experiment found"}
156
+
157
+ experiment = self._experiments[exp_id]
158
+ experiment.ended_at = datetime.utcnow()
159
+
160
+ if exp_id == self._active_experiment:
161
+ self._active_experiment = None
162
+
163
+ return experiment.get_results()
164
+
165
+ def should_adopt_treatment(self, experiment_id: str, min_lift_pct: float = 5.0) -> bool:
166
+ """Check if treatment should be adopted."""
167
+ if experiment_id not in self._experiments:
168
+ return False
169
+
170
+ results = self._experiments[experiment_id].get_results()
171
+
172
+ # Check statistical significance (simplified)
173
+ if results["control_count"] < 100 or results["treatment_count"] < 10:
174
+ return False # Not enough data
175
+
176
+ return results["lift_pct"] >= min_lift_pct
177
+
178
+
179
+ class AdaptiveLearner:
180
+ """
181
+ Learns optimal policies from agent behavior.
182
+
183
+ Features:
184
+ - Behavioral pattern detection
185
+ - Anomaly identification
186
+ - Policy recommendation
187
+ """
188
+
189
+ def __init__(self):
190
+ self._patterns: dict[str, list] = {} # agent_did -> [patterns]
191
+ self._anomalies: list = []
192
+
193
+ def observe(
194
+ self,
195
+ agent_did: str,
196
+ action: str,
197
+ context: dict,
198
+ outcome: str,
199
+ score_impact: int,
200
+ ) -> None:
201
+ """Observe an agent action for learning."""
202
+ if agent_did not in self._patterns:
203
+ self._patterns[agent_did] = []
204
+
205
+ observation = {
206
+ "action": action,
207
+ "context_keys": list(context.keys()),
208
+ "outcome": outcome,
209
+ "score_impact": score_impact,
210
+ "timestamp": datetime.utcnow().isoformat(),
211
+ }
212
+
213
+ self._patterns[agent_did].append(observation)
214
+
215
+ # Detect anomalies
216
+ if score_impact < -50: # Large negative impact
217
+ self._anomalies.append({
218
+ "agent_did": agent_did,
219
+ "observation": observation,
220
+ "type": "large_negative_impact",
221
+ })
222
+
223
+ def get_agent_patterns(self, agent_did: str) -> list:
224
+ """Get learned patterns for an agent."""
225
+ return self._patterns.get(agent_did, [])
226
+
227
+ def get_recommendations(self, agent_did: str) -> list[str]:
228
+ """Get policy recommendations based on patterns."""
229
+ patterns = self._patterns.get(agent_did, [])
230
+
231
+ if not patterns:
232
+ return []
233
+
234
+ recommendations = []
235
+
236
+ # Analyze patterns
237
+ negative_actions = [
238
+ p["action"] for p in patterns
239
+ if p["score_impact"] < 0
240
+ ]
241
+
242
+ # Count frequent negative actions
243
+ action_counts = {}
244
+ for action in negative_actions:
245
+ action_counts[action] = action_counts.get(action, 0) + 1
246
+
247
+ # Recommend blocking frequent negative actions
248
+ for action, count in action_counts.items():
249
+ if count >= 3:
250
+ recommendations.append(
251
+ f"Consider blocking action '{action}' - caused {count} negative impacts"
252
+ )
253
+
254
+ return recommendations
255
+
256
+ def get_anomalies(
257
+ self,
258
+ agent_did: Optional[str] = None,
259
+ since: Optional[datetime] = None,
260
+ ) -> list:
261
+ """Get detected anomalies."""
262
+ anomalies = self._anomalies
263
+
264
+ if agent_did:
265
+ anomalies = [a for a in anomalies if a["agent_did"] == agent_did]
266
+
267
+ if since:
268
+ anomalies = [
269
+ a for a in anomalies
270
+ if datetime.fromisoformat(a["observation"]["timestamp"]) >= since
271
+ ]
272
+
273
+ return anomalies
274
+
275
+ def get_learning_summary(self) -> dict:
276
+ """Get summary of learning state."""
277
+ total_observations = sum(len(p) for p in self._patterns.values())
278
+
279
+ return {
280
+ "agents_tracked": len(self._patterns),
281
+ "total_observations": total_observations,
282
+ "anomalies_detected": len(self._anomalies),
283
+ "patterns_per_agent": {
284
+ agent: len(patterns)
285
+ for agent, patterns in self._patterns.items()
286
+ },
287
+ }
@@ -0,0 +1,203 @@
1
+ """
2
+ Reward Scoring
3
+
4
+ Multi-dimensional scoring with trust scores and reward signals.
5
+ """
6
+
7
+ from datetime import datetime
8
+ from typing import Optional
9
+ from pydantic import BaseModel, Field
10
+ from enum import Enum
11
+
12
+
13
+ class DimensionType(str, Enum):
14
+ """The 5 reward dimensions."""
15
+ POLICY_COMPLIANCE = "policy_compliance"
16
+ RESOURCE_EFFICIENCY = "resource_efficiency"
17
+ OUTPUT_QUALITY = "output_quality"
18
+ SECURITY_POSTURE = "security_posture"
19
+ COLLABORATION_HEALTH = "collaboration_health"
20
+
21
+
22
+ class RewardSignal(BaseModel):
23
+ """
24
+ A single reward signal.
25
+
26
+ Signals feed into dimension scores which aggregate to trust scores.
27
+ """
28
+
29
+ dimension: DimensionType
30
+ value: float = Field(..., ge=0.0, le=1.0, description="0=bad, 1=good")
31
+
32
+ # Source
33
+ source: str = Field(..., description="Where this signal came from")
34
+
35
+ # Context
36
+ details: Optional[str] = None
37
+ trace_id: Optional[str] = None
38
+
39
+ # Timing
40
+ timestamp: datetime = Field(default_factory=datetime.utcnow)
41
+
42
+ # Weight (for importance)
43
+ weight: float = Field(default=1.0, ge=0.0)
44
+
45
+
46
+ class RewardDimension(BaseModel):
47
+ """Score for a single dimension."""
48
+
49
+ name: str
50
+ score: float = Field(default=50.0, ge=0.0, le=100.0)
51
+
52
+ # Signal statistics
53
+ signal_count: int = Field(default=0)
54
+ positive_signals: int = Field(default=0)
55
+ negative_signals: int = Field(default=0)
56
+
57
+ # Trend
58
+ previous_score: Optional[float] = None
59
+ trend: str = "stable" # improving, degrading, stable
60
+
61
+ # Last update
62
+ updated_at: datetime = Field(default_factory=datetime.utcnow)
63
+
64
+ def add_signal(self, signal: RewardSignal) -> None:
65
+ """Add a signal and update score."""
66
+ self.signal_count += 1
67
+
68
+ if signal.value >= 0.5:
69
+ self.positive_signals += 1
70
+ else:
71
+ self.negative_signals += 1
72
+
73
+ # Update score (exponential moving average)
74
+ alpha = 0.1 # Smoothing factor
75
+ self.previous_score = self.score
76
+ self.score = self.score * (1 - alpha) + (signal.value * 100) * alpha
77
+
78
+ # Update trend
79
+ if self.previous_score is not None:
80
+ diff = self.score - self.previous_score
81
+ if diff > 5:
82
+ self.trend = "improving"
83
+ elif diff < -5:
84
+ self.trend = "degrading"
85
+ else:
86
+ self.trend = "stable"
87
+
88
+ self.updated_at = datetime.utcnow()
89
+
90
+
91
+ class TrustScore(BaseModel):
92
+ """
93
+ Complete trust score for an agent.
94
+
95
+ Aggregates all dimension scores into a single 0-1000 score.
96
+ """
97
+
98
+ agent_did: str
99
+
100
+ # Total score (0-1000)
101
+ total_score: int = Field(default=500, ge=0, le=1000)
102
+
103
+ # Trust tier
104
+ tier: str = "standard" # verified_partner, trusted, standard, probationary, untrusted
105
+
106
+ # Dimension breakdown
107
+ dimensions: dict[str, RewardDimension] = Field(default_factory=dict)
108
+
109
+ # Timestamps
110
+ calculated_at: datetime = Field(default_factory=datetime.utcnow)
111
+
112
+ # History
113
+ previous_score: Optional[int] = None
114
+ score_change: int = 0
115
+
116
+ model_config = {"validate_assignment": True}
117
+
118
+ def __init__(self, **data):
119
+ super().__init__(**data)
120
+ self._update_tier()
121
+
122
+ def _update_tier(self) -> None:
123
+ """Update tier based on score."""
124
+ if self.total_score >= 900:
125
+ self.tier = "verified_partner"
126
+ elif self.total_score >= 700:
127
+ self.tier = "trusted"
128
+ elif self.total_score >= 500:
129
+ self.tier = "standard"
130
+ elif self.total_score >= 300:
131
+ self.tier = "probationary"
132
+ else:
133
+ self.tier = "untrusted"
134
+
135
+ def update(self, new_score: int, dimensions: dict[str, RewardDimension]) -> None:
136
+ """Update the trust score."""
137
+ self.previous_score = self.total_score
138
+ self.total_score = max(0, min(1000, new_score))
139
+ self.score_change = self.total_score - (self.previous_score or 0)
140
+ self.dimensions = dimensions
141
+ self.calculated_at = datetime.utcnow()
142
+ self._update_tier()
143
+
144
+ def meets_threshold(self, threshold: int) -> bool:
145
+ """Check if score meets a threshold."""
146
+ return self.total_score >= threshold
147
+
148
+ def to_dict(self) -> dict:
149
+ """Export as dictionary."""
150
+ return {
151
+ "agent_did": self.agent_did,
152
+ "total_score": self.total_score,
153
+ "tier": self.tier,
154
+ "dimensions": {
155
+ name: {
156
+ "score": dim.score,
157
+ "trend": dim.trend,
158
+ "signal_count": dim.signal_count,
159
+ }
160
+ for name, dim in self.dimensions.items()
161
+ },
162
+ "calculated_at": self.calculated_at.isoformat(),
163
+ }
164
+
165
+
166
+ class ScoreThresholds(BaseModel):
167
+ """Configurable score thresholds."""
168
+
169
+ # Tier thresholds
170
+ verified_partner: int = 900
171
+ trusted: int = 700
172
+ standard: int = 500
173
+ probationary: int = 300
174
+
175
+ # Action thresholds
176
+ allow_threshold: int = 500
177
+ warn_threshold: int = 400
178
+ revocation_threshold: int = 300
179
+
180
+ def get_tier(self, score: int) -> str:
181
+ """Get tier for a score."""
182
+ if score >= self.verified_partner:
183
+ return "verified_partner"
184
+ elif score >= self.trusted:
185
+ return "trusted"
186
+ elif score >= self.standard:
187
+ return "standard"
188
+ elif score >= self.probationary:
189
+ return "probationary"
190
+ else:
191
+ return "untrusted"
192
+
193
+ def should_allow(self, score: int) -> bool:
194
+ """Check if score should allow actions."""
195
+ return score >= self.allow_threshold
196
+
197
+ def should_warn(self, score: int) -> bool:
198
+ """Check if score should trigger warning."""
199
+ return score < self.warn_threshold
200
+
201
+ def should_revoke(self, score: int) -> bool:
202
+ """Check if score should trigger revocation."""
203
+ return score < self.revocation_threshold
@@ -0,0 +1,19 @@
1
+ """
2
+ Trust & Protocol Bridge (Layer 2)
3
+
4
+ Implements IATP for agent-to-agent trust handshakes.
5
+ Native A2A and MCP support with transparent protocol translation.
6
+ """
7
+
8
+ from .bridge import TrustBridge, ProtocolBridge
9
+ from .handshake import TrustHandshake, HandshakeResult
10
+ from .capability import CapabilityScope, CapabilityGrant
11
+
12
+ __all__ = [
13
+ "TrustBridge",
14
+ "ProtocolBridge",
15
+ "TrustHandshake",
16
+ "HandshakeResult",
17
+ "CapabilityScope",
18
+ "CapabilityGrant",
19
+ ]