agent_hypervisor 3.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.
Files changed (60) hide show
  1. agent_hypervisor-3.1.0.dist-info/METADATA +824 -0
  2. agent_hypervisor-3.1.0.dist-info/RECORD +60 -0
  3. agent_hypervisor-3.1.0.dist-info/WHEEL +4 -0
  4. agent_hypervisor-3.1.0.dist-info/entry_points.txt +2 -0
  5. agent_hypervisor-3.1.0.dist-info/licenses/LICENSE +21 -0
  6. hypervisor/__init__.py +160 -0
  7. hypervisor/api/__init__.py +7 -0
  8. hypervisor/api/models.py +285 -0
  9. hypervisor/api/server.py +742 -0
  10. hypervisor/audit/__init__.py +4 -0
  11. hypervisor/audit/commitment.py +76 -0
  12. hypervisor/audit/delta.py +135 -0
  13. hypervisor/audit/gc.py +99 -0
  14. hypervisor/cli/__init__.py +3 -0
  15. hypervisor/cli/formatters.py +99 -0
  16. hypervisor/cli/session_commands.py +200 -0
  17. hypervisor/constants.py +106 -0
  18. hypervisor/core.py +352 -0
  19. hypervisor/integrations/__init__.py +10 -0
  20. hypervisor/integrations/iatp_adapter.py +142 -0
  21. hypervisor/integrations/nexus_adapter.py +108 -0
  22. hypervisor/integrations/verification_adapter.py +122 -0
  23. hypervisor/liability/__init__.py +142 -0
  24. hypervisor/liability/attribution.py +86 -0
  25. hypervisor/liability/ledger.py +121 -0
  26. hypervisor/liability/quarantine.py +119 -0
  27. hypervisor/liability/slashing.py +80 -0
  28. hypervisor/liability/vouching.py +134 -0
  29. hypervisor/models.py +277 -0
  30. hypervisor/observability/__init__.py +27 -0
  31. hypervisor/observability/causal_trace.py +70 -0
  32. hypervisor/observability/event_bus.py +222 -0
  33. hypervisor/observability/prometheus_collector.py +248 -0
  34. hypervisor/observability/saga_span_exporter.py +341 -0
  35. hypervisor/providers.py +121 -0
  36. hypervisor/py.typed +0 -0
  37. hypervisor/reversibility/__init__.py +3 -0
  38. hypervisor/reversibility/registry.py +108 -0
  39. hypervisor/rings/__init__.py +21 -0
  40. hypervisor/rings/breach_detector.py +200 -0
  41. hypervisor/rings/classifier.py +78 -0
  42. hypervisor/rings/elevation.py +219 -0
  43. hypervisor/rings/enforcer.py +97 -0
  44. hypervisor/saga/__init__.py +22 -0
  45. hypervisor/saga/checkpoint.py +110 -0
  46. hypervisor/saga/dsl.py +190 -0
  47. hypervisor/saga/fan_out.py +126 -0
  48. hypervisor/saga/orchestrator.py +229 -0
  49. hypervisor/saga/schema.py +244 -0
  50. hypervisor/saga/state_machine.py +157 -0
  51. hypervisor/security/__init__.py +13 -0
  52. hypervisor/security/kill_switch.py +200 -0
  53. hypervisor/security/rate_limiter.py +190 -0
  54. hypervisor/session/__init__.py +194 -0
  55. hypervisor/session/intent_locks.py +118 -0
  56. hypervisor/session/isolation.py +37 -0
  57. hypervisor/session/sso.py +169 -0
  58. hypervisor/session/vector_clock.py +118 -0
  59. hypervisor/verification/__init__.py +3 -0
  60. hypervisor/verification/history.py +173 -0
@@ -0,0 +1,190 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # Public Preview — basic implementation
4
+ """Runtime-layer per-agent/per-ring rate limiting.
5
+
6
+ This module enforces token-bucket limits per agent, session, and execution ring
7
+ inside the hypervisor runtime layer.
8
+
9
+ See also:
10
+ - agent_os.integrations.rate_limiter: tool-call policy limits in Agent OS.
11
+ - agentmesh.services.rate_limiter: service/proxy-level limits in Agent Mesh.
12
+ - agentmesh.services.rate_limit_middleware: HTTP edge middleware in Agent Mesh.
13
+ - agent_os.policies.rate_limiting: shared token-bucket primitives.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass, field
19
+ from datetime import UTC, datetime
20
+
21
+ from hypervisor.constants import (
22
+ RATE_LIMIT_FALLBACK,
23
+ RATE_LIMIT_RING_0,
24
+ RATE_LIMIT_RING_1,
25
+ RATE_LIMIT_RING_2,
26
+ RATE_LIMIT_RING_3,
27
+ )
28
+ from hypervisor.models import ExecutionRing
29
+
30
+
31
+ class RateLimitExceeded(Exception):
32
+ """Raised when an agent exceeds their rate limit."""
33
+
34
+
35
+ @dataclass
36
+ class TokenBucket:
37
+ """A token bucket for rate limiting."""
38
+
39
+ capacity: float
40
+ tokens: float
41
+ refill_rate: float # tokens per second
42
+ last_refill: datetime = field(default_factory=lambda: datetime.now(UTC))
43
+
44
+ def consume(self, tokens: float = 1.0) -> bool:
45
+ """Try to consume tokens. Returns True if successful."""
46
+ self._refill()
47
+ if self.tokens >= tokens:
48
+ self.tokens -= tokens
49
+ return True
50
+ return False
51
+
52
+ def _refill(self) -> None:
53
+ """Refill tokens based on elapsed time."""
54
+ now = datetime.now(UTC)
55
+ elapsed = (now - self.last_refill).total_seconds()
56
+ self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
57
+ self.last_refill = now
58
+
59
+ @property
60
+ def available(self) -> float:
61
+ self._refill()
62
+ return self.tokens
63
+
64
+
65
+ # Default rate limits per ring (requests per second, burst capacity)
66
+ DEFAULT_RING_LIMITS: dict[ExecutionRing, tuple[float, float]] = {
67
+ ExecutionRing.RING_0_ROOT: RATE_LIMIT_RING_0,
68
+ ExecutionRing.RING_1_PRIVILEGED: RATE_LIMIT_RING_1,
69
+ ExecutionRing.RING_2_STANDARD: RATE_LIMIT_RING_2,
70
+ ExecutionRing.RING_3_SANDBOX: RATE_LIMIT_RING_3,
71
+ }
72
+
73
+
74
+ @dataclass
75
+ class RateLimitStats:
76
+ """Statistics for an agent's rate limiting."""
77
+
78
+ agent_did: str
79
+ ring: ExecutionRing
80
+ total_requests: int = 0
81
+ rejected_requests: int = 0
82
+ tokens_available: float = 0.0
83
+ capacity: float = 0.0
84
+
85
+
86
+ class AgentRateLimiter:
87
+ """
88
+ Rate limiting per agent per ring using token buckets.
89
+
90
+ Higher-privilege rings get more generous limits. When an agent
91
+ is promoted/demoted, their bucket is recreated with new limits.
92
+ """
93
+
94
+ def __init__(
95
+ self,
96
+ ring_limits: dict[ExecutionRing, tuple[float, float]] | None = None,
97
+ ) -> None:
98
+ self._limits = ring_limits or dict(DEFAULT_RING_LIMITS)
99
+ # (agent_did, session_id) -> TokenBucket
100
+ self._buckets: dict[str, TokenBucket] = {}
101
+ self._stats: dict[str, RateLimitStats] = {}
102
+
103
+ def check(
104
+ self,
105
+ agent_did: str,
106
+ session_id: str,
107
+ ring: ExecutionRing,
108
+ cost: float = 1.0,
109
+ ) -> bool:
110
+ """
111
+ Check if an agent can make a request.
112
+
113
+ Returns True if allowed, raises RateLimitExceeded if not.
114
+ """
115
+ key = f"{agent_did}:{session_id}"
116
+ bucket = self._get_or_create_bucket(key, ring)
117
+
118
+ # Track stats
119
+ stats = self._stats.setdefault(
120
+ key,
121
+ RateLimitStats(agent_did=agent_did, ring=ring),
122
+ )
123
+ stats.total_requests += 1
124
+
125
+ if not bucket.consume(cost):
126
+ stats.rejected_requests += 1
127
+ raise RateLimitExceeded(
128
+ f"Agent {agent_did} exceeded rate limit for ring "
129
+ f"{ring.value} ({stats.rejected_requests} rejections)"
130
+ )
131
+ return True
132
+
133
+ def try_check(
134
+ self,
135
+ agent_did: str,
136
+ session_id: str,
137
+ ring: ExecutionRing,
138
+ cost: float = 1.0,
139
+ ) -> bool:
140
+ """Like check(), but returns False instead of raising."""
141
+ try:
142
+ return self.check(agent_did, session_id, ring, cost)
143
+ except RateLimitExceeded:
144
+ return False
145
+
146
+ def update_ring(
147
+ self,
148
+ agent_did: str,
149
+ session_id: str,
150
+ new_ring: ExecutionRing,
151
+ ) -> None:
152
+ """Update an agent's rate limit when their ring changes."""
153
+ key = f"{agent_did}:{session_id}"
154
+ rate, capacity = self._limits.get(
155
+ new_ring, RATE_LIMIT_FALLBACK
156
+ )
157
+ self._buckets[key] = TokenBucket(
158
+ capacity=capacity,
159
+ tokens=capacity, # Start full
160
+ refill_rate=rate,
161
+ )
162
+ if key in self._stats:
163
+ self._stats[key].ring = new_ring
164
+
165
+ def get_stats(self, agent_did: str, session_id: str) -> RateLimitStats | None:
166
+ """Get rate limit stats for an agent."""
167
+ key = f"{agent_did}:{session_id}"
168
+ stats = self._stats.get(key)
169
+ if stats:
170
+ bucket = self._buckets.get(key)
171
+ if bucket:
172
+ stats.tokens_available = bucket.available
173
+ stats.capacity = bucket.capacity
174
+ return stats
175
+
176
+ def _get_or_create_bucket(
177
+ self, key: str, ring: ExecutionRing
178
+ ) -> TokenBucket:
179
+ if key not in self._buckets:
180
+ rate, capacity = self._limits.get(ring, RATE_LIMIT_FALLBACK)
181
+ self._buckets[key] = TokenBucket(
182
+ capacity=capacity,
183
+ tokens=capacity,
184
+ refill_rate=rate,
185
+ )
186
+ return self._buckets[key]
187
+
188
+ @property
189
+ def tracked_agents(self) -> int:
190
+ return len(self._buckets)
@@ -0,0 +1,194 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """Shared Session Object — lifecycle manager for multi-agent sessions."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import uuid
8
+ from dataclasses import dataclass, field
9
+ from datetime import UTC, datetime, timezone
10
+ from typing import Any, Optional
11
+
12
+ from hypervisor.models import (
13
+ ConsistencyMode,
14
+ ExecutionRing,
15
+ SessionConfig,
16
+ SessionParticipant,
17
+ SessionState,
18
+ )
19
+ from hypervisor.session.sso import SessionVFS
20
+
21
+
22
+ class SharedSessionObject:
23
+ """
24
+ Encapsulates a multi-agent interaction.
25
+
26
+ Every Shared Session has:
27
+ - SessionID: UUID bound to a DID
28
+ - ConsistencyMode: Strong (consensus) or Eventual (gossip)
29
+ - StateSubstrate: A VFS representing the shared world
30
+ - LiabilityMatrix: Registry of who sponsors for whom
31
+
32
+ Lifecycle: created → handshaking → active → terminating → archived
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ config: SessionConfig,
38
+ creator_did: str,
39
+ session_id: str | None = None,
40
+ ):
41
+ self.session_id = session_id or f"session:{uuid.uuid4()}"
42
+ self.creator_did = creator_did
43
+ self.config = config
44
+ self.state = SessionState.CREATED
45
+ self.consistency_mode = config.consistency_mode
46
+
47
+ # Participants
48
+ self._participants: dict[str, SessionParticipant] = {}
49
+
50
+ # VFS state substrate (namespace for this session)
51
+ self.vfs_namespace = f"/sessions/{self.session_id}"
52
+ self.vfs = SessionVFS(self.session_id, namespace=self.vfs_namespace)
53
+ self._vfs_snapshots: dict[str, Any] = {}
54
+
55
+ # Timestamps
56
+ self.created_at = datetime.now(UTC)
57
+ self.terminated_at: datetime | None = None
58
+
59
+ @property
60
+ def participants(self) -> list[SessionParticipant]:
61
+ """Active participants in this session."""
62
+ return [p for p in self._participants.values() if p.is_active]
63
+
64
+ @property
65
+ def participant_count(self) -> int:
66
+ # Avoid building a filtered list just to count
67
+ return sum(1 for p in self._participants.values() if p.is_active)
68
+
69
+ def _assert_state(self, *allowed: SessionState) -> None:
70
+ if self.state not in allowed:
71
+ raise SessionLifecycleError(
72
+ f"Operation not allowed in state {self.state.value}. "
73
+ f"Allowed: {[s.value for s in allowed]}"
74
+ )
75
+
76
+ def begin_handshake(self) -> None:
77
+ """Transition to handshaking phase."""
78
+ self._assert_state(SessionState.CREATED)
79
+ self.state = SessionState.HANDSHAKING
80
+
81
+ def activate(self) -> None:
82
+ """Transition to active execution phase."""
83
+ self._assert_state(SessionState.HANDSHAKING)
84
+ if not self._participants:
85
+ raise SessionLifecycleError("Cannot activate session with no participants")
86
+ self.state = SessionState.ACTIVE
87
+
88
+ def join(
89
+ self,
90
+ agent_did: str,
91
+ sigma_raw: float = 0.0,
92
+ eff_score: float = 0.0,
93
+ ring: ExecutionRing = ExecutionRing.RING_3_SANDBOX,
94
+ ) -> SessionParticipant:
95
+ """Add an agent to this session."""
96
+ self._assert_state(SessionState.HANDSHAKING, SessionState.ACTIVE)
97
+
98
+ if agent_did in self._participants:
99
+ raise SessionParticipantError(f"Agent {agent_did} already in session")
100
+ if self.participant_count >= self.config.max_participants:
101
+ raise SessionParticipantError(
102
+ f"Session at capacity ({self.config.max_participants})"
103
+ )
104
+ if eff_score < self.config.min_eff_score and ring != ExecutionRing.RING_3_SANDBOX:
105
+ raise SessionParticipantError(
106
+ f"eff_score {eff_score:.2f} below minimum {self.config.min_eff_score:.2f}"
107
+ )
108
+
109
+ participant = SessionParticipant(
110
+ agent_did=agent_did,
111
+ ring=ring,
112
+ sigma_raw=sigma_raw,
113
+ eff_score=eff_score,
114
+ )
115
+ self._participants[agent_did] = participant
116
+ return participant
117
+
118
+ def leave(self, agent_did: str) -> None:
119
+ """Remove an agent from this session."""
120
+ if agent_did not in self._participants:
121
+ raise SessionParticipantError(f"Agent {agent_did} not in session")
122
+ self._participants[agent_did].is_active = False
123
+
124
+ def get_participant(self, agent_did: str) -> SessionParticipant:
125
+ """Get a participant by DID."""
126
+ if agent_did not in self._participants:
127
+ raise SessionParticipantError(f"Agent {agent_did} not in session")
128
+ return self._participants[agent_did]
129
+
130
+ def update_ring(self, agent_did: str, new_ring: ExecutionRing) -> None:
131
+ """Update an agent's ring level (escalation or demotion)."""
132
+ participant = self.get_participant(agent_did)
133
+ participant.ring = new_ring
134
+
135
+ def force_consistency_mode(self, mode: ConsistencyMode) -> None:
136
+ """Force a consistency mode (e.g., when non-reversible actions are detected)."""
137
+ self.consistency_mode = mode
138
+
139
+ def terminate(self) -> None:
140
+ """Begin session termination."""
141
+ self._assert_state(SessionState.ACTIVE, SessionState.HANDSHAKING)
142
+ self.state = SessionState.TERMINATING
143
+ self.terminated_at = datetime.now(UTC)
144
+
145
+ def archive(self) -> None:
146
+ """Archive the session after audit commitment."""
147
+ self._assert_state(SessionState.TERMINATING)
148
+ self.state = SessionState.ARCHIVED
149
+
150
+ def create_vfs_snapshot(self, snapshot_id: str | None = None) -> str:
151
+ """Create a VFS state snapshot for rollback.
152
+
153
+ Captures both VFS file state (snapshot) and participant metadata.
154
+ """
155
+ self._assert_state(SessionState.ACTIVE)
156
+ # Snapshot the VFS file state
157
+ sid = self.vfs.create_snapshot(snapshot_id)
158
+ # Also snapshot participant metadata for full restore
159
+ self._vfs_snapshots[sid] = {
160
+ "created_at": datetime.now(UTC).isoformat(),
161
+ "participant_states": {
162
+ did: {"ring": p.ring.value, "eff_score": p.eff_score}
163
+ for did, p in self._participants.items()
164
+ },
165
+ }
166
+ return sid
167
+
168
+ def restore_vfs_snapshot(
169
+ self, snapshot_id: str, agent_did: str
170
+ ) -> None:
171
+ """Restore VFS to a previous snapshot.
172
+
173
+ Args:
174
+ snapshot_id: ID returned by create_vfs_snapshot.
175
+ agent_did: Agent requesting the restore (recorded in audit log).
176
+ """
177
+ self._assert_state(SessionState.ACTIVE)
178
+ self.vfs.restore_snapshot(snapshot_id, agent_did)
179
+
180
+ def __repr__(self) -> str:
181
+ return (
182
+ f"SharedSessionObject(id={self.session_id!r}, "
183
+ f"state={self.state.value}, "
184
+ f"participants={self.participant_count}, "
185
+ f"mode={self.consistency_mode.value})"
186
+ )
187
+
188
+
189
+ class SessionLifecycleError(Exception):
190
+ """Raised when a session lifecycle transition is invalid."""
191
+
192
+
193
+ class SessionParticipantError(Exception):
194
+ """Raised for participant-related errors."""
@@ -0,0 +1,118 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # Public Preview — basic implementation
4
+ """
5
+ Resource Locks — stub implementation.
6
+
7
+ Public Preview: locks are not enforced. All acquire calls succeed.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import uuid
13
+ from dataclasses import dataclass, field
14
+ from datetime import UTC, datetime
15
+ from enum import Enum
16
+
17
+
18
+ class LockIntent(str, Enum):
19
+ """Types of lock intent."""
20
+
21
+ READ = "read"
22
+ WRITE = "write"
23
+ EXCLUSIVE = "exclusive"
24
+
25
+
26
+ @dataclass
27
+ class IntentLock:
28
+ """A declared resource lock on a resource."""
29
+
30
+ lock_id: str = field(default_factory=lambda: f"lock:{uuid.uuid4().hex[:8]}")
31
+ agent_did: str = ""
32
+ session_id: str = ""
33
+ resource_path: str = ""
34
+ intent: LockIntent = LockIntent.READ
35
+ acquired_at: datetime = field(default_factory=lambda: datetime.now(UTC))
36
+ is_active: bool = True
37
+ saga_step_id: str | None = None
38
+
39
+
40
+ class LockContentionError(Exception):
41
+ """Raised when lock contention is detected."""
42
+
43
+
44
+ class DeadlockError(Exception):
45
+ """Raised when a deadlock is detected."""
46
+
47
+
48
+ class IntentLockManager:
49
+ """
50
+ Resource lock stub (Public Preview: all locks succeed, no contention).
51
+ """
52
+
53
+ def __init__(self) -> None:
54
+ self._locks: dict[str, IntentLock] = {}
55
+
56
+ def acquire(
57
+ self,
58
+ agent_did: str,
59
+ session_id: str,
60
+ resource_path: str,
61
+ intent: LockIntent,
62
+ saga_step_id: str | None = None,
63
+ ) -> IntentLock:
64
+ """Acquire a lock (Public Preview: always succeeds)."""
65
+ lock = IntentLock(
66
+ agent_did=agent_did,
67
+ session_id=session_id,
68
+ resource_path=resource_path,
69
+ intent=intent,
70
+ saga_step_id=saga_step_id,
71
+ )
72
+ self._locks[lock.lock_id] = lock
73
+ return lock
74
+
75
+ def release(self, lock_id: str) -> None:
76
+ """Release a lock."""
77
+ lock = self._locks.get(lock_id)
78
+ if lock:
79
+ lock.is_active = False
80
+
81
+ def release_agent_locks(self, agent_did: str, session_id: str) -> int:
82
+ count = 0
83
+ for lock in list(self._locks.values()):
84
+ if lock.agent_did == agent_did and lock.session_id == session_id and lock.is_active:
85
+ lock.is_active = False
86
+ count += 1
87
+ return count
88
+
89
+ def release_session_locks(self, session_id: str) -> int:
90
+ count = 0
91
+ for lock in list(self._locks.values()):
92
+ if lock.session_id == session_id and lock.is_active:
93
+ lock.is_active = False
94
+ count += 1
95
+ return count
96
+
97
+ def get_agent_locks(self, agent_did: str, session_id: str) -> list[IntentLock]:
98
+ return [
99
+ lock for lock in self._locks.values()
100
+ if lock.agent_did == agent_did
101
+ and lock.session_id == session_id
102
+ and lock.is_active
103
+ ]
104
+
105
+ def get_resource_locks(self, resource_path: str) -> list[IntentLock]:
106
+ return [
107
+ lock for lock in self._locks.values()
108
+ if lock.resource_path == resource_path
109
+ and lock.is_active
110
+ ]
111
+
112
+ @property
113
+ def active_lock_count(self) -> int:
114
+ return sum(1 for lock in self._locks.values() if lock.is_active)
115
+
116
+ @property
117
+ def contention_points(self) -> list[str]:
118
+ return []
@@ -0,0 +1,37 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ # Public Preview — basic implementation
4
+ """
5
+ Session Isolation Levels — stub implementation.
6
+
7
+ Public Preview: all access is serialized via a single lock.
8
+ Isolation levels are retained for API compatibility but not enforced.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from enum import Enum
14
+
15
+
16
+ class IsolationLevel(str, Enum):
17
+ """Session isolation levels (Public Preview: not enforced)."""
18
+
19
+ SNAPSHOT = "snapshot"
20
+ READ_COMMITTED = "read_committed"
21
+ SERIALIZABLE = "serializable"
22
+
23
+ @property
24
+ def requires_vector_clocks(self) -> bool:
25
+ return False
26
+
27
+ @property
28
+ def requires_intent_locks(self) -> bool:
29
+ return False
30
+
31
+ @property
32
+ def allows_concurrent_writes(self) -> bool:
33
+ return True
34
+
35
+ @property
36
+ def coordination_cost(self) -> str:
37
+ return "none"