agent_os_kernel 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 (337) hide show
  1. agent_control_plane/__init__.py +662 -0
  2. agent_control_plane/a2a_adapter.py +543 -0
  3. agent_control_plane/adapter.py +417 -0
  4. agent_control_plane/agent_hibernation.py +394 -0
  5. agent_control_plane/agent_kernel.py +470 -0
  6. agent_control_plane/compliance.py +720 -0
  7. agent_control_plane/constraint_graphs.py +478 -0
  8. agent_control_plane/control_plane.py +854 -0
  9. agent_control_plane/example_executors.py +195 -0
  10. agent_control_plane/execution_engine.py +231 -0
  11. agent_control_plane/flight_recorder.py +846 -0
  12. agent_control_plane/governance_layer.py +435 -0
  13. agent_control_plane/hf_utils.py +563 -0
  14. agent_control_plane/interfaces/__init__.py +55 -0
  15. agent_control_plane/interfaces/kernel_interface.py +361 -0
  16. agent_control_plane/interfaces/plugin_interface.py +497 -0
  17. agent_control_plane/interfaces/protocol_interfaces.py +387 -0
  18. agent_control_plane/kernel_space.py +1009 -0
  19. agent_control_plane/langchain_adapter.py +424 -0
  20. agent_control_plane/lifecycle.py +3113 -0
  21. agent_control_plane/mcp_adapter.py +653 -0
  22. agent_control_plane/ml_safety.py +563 -0
  23. agent_control_plane/multimodal.py +727 -0
  24. agent_control_plane/mute_agent.py +422 -0
  25. agent_control_plane/observability.py +787 -0
  26. agent_control_plane/orchestrator.py +482 -0
  27. agent_control_plane/plugin_registry.py +750 -0
  28. agent_control_plane/policy_engine.py +954 -0
  29. agent_control_plane/process_isolation.py +777 -0
  30. agent_control_plane/shadow_mode.py +310 -0
  31. agent_control_plane/signals.py +493 -0
  32. agent_control_plane/supervisor_agents.py +430 -0
  33. agent_control_plane/time_travel_debugger.py +557 -0
  34. agent_control_plane/tool_registry.py +452 -0
  35. agent_control_plane/vfs.py +697 -0
  36. agent_kernel/__init__.py +69 -0
  37. agent_kernel/analyzer.py +435 -0
  38. agent_kernel/auditor.py +36 -0
  39. agent_kernel/completeness_auditor.py +237 -0
  40. agent_kernel/detector.py +203 -0
  41. agent_kernel/kernel.py +744 -0
  42. agent_kernel/memory_manager.py +85 -0
  43. agent_kernel/models.py +374 -0
  44. agent_kernel/nudge_mechanism.py +263 -0
  45. agent_kernel/outcome_analyzer.py +338 -0
  46. agent_kernel/patcher.py +582 -0
  47. agent_kernel/semantic_analyzer.py +316 -0
  48. agent_kernel/semantic_purge.py +349 -0
  49. agent_kernel/simulator.py +449 -0
  50. agent_kernel/teacher.py +85 -0
  51. agent_kernel/triage.py +152 -0
  52. agent_os/__init__.py +409 -0
  53. agent_os/_adversarial_impl.py +200 -0
  54. agent_os/_circuit_breaker_impl.py +232 -0
  55. agent_os/_mcp_metrics.py +193 -0
  56. agent_os/adversarial.py +20 -0
  57. agent_os/agents_compat.py +490 -0
  58. agent_os/audit_logger.py +135 -0
  59. agent_os/base_agent.py +651 -0
  60. agent_os/circuit_breaker.py +34 -0
  61. agent_os/cli/__init__.py +659 -0
  62. agent_os/cli/cmd_audit.py +128 -0
  63. agent_os/cli/cmd_init.py +152 -0
  64. agent_os/cli/cmd_policy.py +41 -0
  65. agent_os/cli/cmd_policy_gen.py +180 -0
  66. agent_os/cli/cmd_validate.py +258 -0
  67. agent_os/cli/mcp_scan.py +265 -0
  68. agent_os/cli/output.py +192 -0
  69. agent_os/cli/policy_checker.py +330 -0
  70. agent_os/compat.py +74 -0
  71. agent_os/constraint_graph.py +234 -0
  72. agent_os/content_governance.py +140 -0
  73. agent_os/context_budget.py +305 -0
  74. agent_os/credential_redactor.py +224 -0
  75. agent_os/diff_policy.py +89 -0
  76. agent_os/egress_policy.py +159 -0
  77. agent_os/escalation.py +276 -0
  78. agent_os/event_bus.py +124 -0
  79. agent_os/exceptions.py +180 -0
  80. agent_os/execution_context_policy.py +141 -0
  81. agent_os/github_enterprise.py +96 -0
  82. agent_os/health.py +20 -0
  83. agent_os/integrations/__init__.py +279 -0
  84. agent_os/integrations/a2a_adapter.py +279 -0
  85. agent_os/integrations/agent_lightning/__init__.py +30 -0
  86. agent_os/integrations/anthropic_adapter.py +420 -0
  87. agent_os/integrations/autogen_adapter.py +620 -0
  88. agent_os/integrations/base.py +1137 -0
  89. agent_os/integrations/compat.py +229 -0
  90. agent_os/integrations/config.py +98 -0
  91. agent_os/integrations/conversation_guardian.py +957 -0
  92. agent_os/integrations/crewai_adapter.py +467 -0
  93. agent_os/integrations/drift_detector.py +425 -0
  94. agent_os/integrations/dry_run.py +124 -0
  95. agent_os/integrations/escalation.py +582 -0
  96. agent_os/integrations/gemini_adapter.py +364 -0
  97. agent_os/integrations/google_adk_adapter.py +633 -0
  98. agent_os/integrations/guardrails_adapter.py +394 -0
  99. agent_os/integrations/health.py +197 -0
  100. agent_os/integrations/langchain_adapter.py +654 -0
  101. agent_os/integrations/llamafirewall.py +343 -0
  102. agent_os/integrations/llamaindex_adapter.py +188 -0
  103. agent_os/integrations/logging.py +191 -0
  104. agent_os/integrations/maf_adapter.py +631 -0
  105. agent_os/integrations/mistral_adapter.py +365 -0
  106. agent_os/integrations/openai_adapter.py +816 -0
  107. agent_os/integrations/openai_agents_sdk.py +406 -0
  108. agent_os/integrations/policy_compose.py +171 -0
  109. agent_os/integrations/profiling.py +144 -0
  110. agent_os/integrations/pydantic_ai_adapter.py +420 -0
  111. agent_os/integrations/rate_limiter.py +130 -0
  112. agent_os/integrations/rbac.py +143 -0
  113. agent_os/integrations/registry.py +113 -0
  114. agent_os/integrations/scope_guard.py +303 -0
  115. agent_os/integrations/semantic_kernel_adapter.py +769 -0
  116. agent_os/integrations/smolagents_adapter.py +629 -0
  117. agent_os/integrations/templates.py +178 -0
  118. agent_os/integrations/token_budget.py +134 -0
  119. agent_os/integrations/tool_aliases.py +190 -0
  120. agent_os/integrations/webhooks.py +177 -0
  121. agent_os/lite.py +208 -0
  122. agent_os/mcp_gateway.py +385 -0
  123. agent_os/mcp_message_signer.py +273 -0
  124. agent_os/mcp_protocols.py +161 -0
  125. agent_os/mcp_response_scanner.py +232 -0
  126. agent_os/mcp_security.py +924 -0
  127. agent_os/mcp_session_auth.py +231 -0
  128. agent_os/mcp_sliding_rate_limiter.py +184 -0
  129. agent_os/memory_guard.py +409 -0
  130. agent_os/metrics.py +134 -0
  131. agent_os/mute.py +428 -0
  132. agent_os/mute_agent.py +209 -0
  133. agent_os/policies/__init__.py +77 -0
  134. agent_os/policies/async_evaluator.py +275 -0
  135. agent_os/policies/backends.py +670 -0
  136. agent_os/policies/bridge.py +169 -0
  137. agent_os/policies/budget.py +85 -0
  138. agent_os/policies/cli.py +294 -0
  139. agent_os/policies/conflict_resolution.py +270 -0
  140. agent_os/policies/data_classification.py +252 -0
  141. agent_os/policies/evaluator.py +239 -0
  142. agent_os/policies/policy_schema.json +228 -0
  143. agent_os/policies/rate_limiting.py +145 -0
  144. agent_os/policies/schema.py +115 -0
  145. agent_os/policies/shared.py +331 -0
  146. agent_os/prompt_injection.py +694 -0
  147. agent_os/providers.py +182 -0
  148. agent_os/py.typed +0 -0
  149. agent_os/retry.py +81 -0
  150. agent_os/reversibility.py +251 -0
  151. agent_os/sandbox.py +432 -0
  152. agent_os/sandbox_provider.py +140 -0
  153. agent_os/secure_codegen.py +525 -0
  154. agent_os/security_skills.py +538 -0
  155. agent_os/semantic_policy.py +422 -0
  156. agent_os/server/__init__.py +15 -0
  157. agent_os/server/__main__.py +25 -0
  158. agent_os/server/app.py +277 -0
  159. agent_os/server/models.py +104 -0
  160. agent_os/shift_left_metrics.py +130 -0
  161. agent_os/stateless.py +742 -0
  162. agent_os/supervisor.py +148 -0
  163. agent_os/task_outcome.py +148 -0
  164. agent_os/transparency.py +181 -0
  165. agent_os/trust_root.py +128 -0
  166. agent_os_kernel-3.1.0.dist-info/METADATA +1269 -0
  167. agent_os_kernel-3.1.0.dist-info/RECORD +337 -0
  168. agent_os_kernel-3.1.0.dist-info/WHEEL +4 -0
  169. agent_os_kernel-3.1.0.dist-info/entry_points.txt +2 -0
  170. agent_os_kernel-3.1.0.dist-info/licenses/LICENSE +21 -0
  171. agent_os_observability/__init__.py +27 -0
  172. agent_os_observability/dashboards.py +898 -0
  173. agent_os_observability/metrics.py +398 -0
  174. agent_os_observability/server.py +223 -0
  175. agent_os_observability/tracer.py +232 -0
  176. agent_primitives/__init__.py +24 -0
  177. agent_primitives/failures.py +84 -0
  178. agent_primitives/py.typed +0 -0
  179. amb_core/__init__.py +177 -0
  180. amb_core/adapters/__init__.py +57 -0
  181. amb_core/adapters/aws_sqs_broker.py +376 -0
  182. amb_core/adapters/azure_servicebus_broker.py +340 -0
  183. amb_core/adapters/kafka_broker.py +260 -0
  184. amb_core/adapters/nats_broker.py +285 -0
  185. amb_core/adapters/rabbitmq_broker.py +235 -0
  186. amb_core/adapters/redis_broker.py +262 -0
  187. amb_core/broker.py +145 -0
  188. amb_core/bus.py +481 -0
  189. amb_core/cloudevents.py +509 -0
  190. amb_core/dlq.py +345 -0
  191. amb_core/hf_utils.py +536 -0
  192. amb_core/memory_broker.py +410 -0
  193. amb_core/models.py +141 -0
  194. amb_core/persistence.py +529 -0
  195. amb_core/schema.py +294 -0
  196. amb_core/tracing.py +358 -0
  197. atr/__init__.py +640 -0
  198. atr/access.py +348 -0
  199. atr/composition.py +645 -0
  200. atr/decorator.py +357 -0
  201. atr/executor.py +384 -0
  202. atr/health.py +557 -0
  203. atr/hf_utils.py +449 -0
  204. atr/injection.py +422 -0
  205. atr/metrics.py +440 -0
  206. atr/policies.py +403 -0
  207. atr/py.typed +2 -0
  208. atr/registry.py +452 -0
  209. atr/schema.py +480 -0
  210. atr/tools/safe/__init__.py +75 -0
  211. atr/tools/safe/calculator.py +467 -0
  212. atr/tools/safe/datetime_tool.py +443 -0
  213. atr/tools/safe/file_reader.py +402 -0
  214. atr/tools/safe/http_client.py +316 -0
  215. atr/tools/safe/json_parser.py +374 -0
  216. atr/tools/safe/text_tool.py +537 -0
  217. atr/tools/safe/toolkit.py +175 -0
  218. caas/__init__.py +162 -0
  219. caas/api/__init__.py +7 -0
  220. caas/api/server.py +1328 -0
  221. caas/caching.py +834 -0
  222. caas/cli.py +210 -0
  223. caas/conversation.py +223 -0
  224. caas/decay.py +72 -0
  225. caas/detection/__init__.py +9 -0
  226. caas/detection/detector.py +238 -0
  227. caas/enrichment.py +130 -0
  228. caas/gateway/__init__.py +27 -0
  229. caas/gateway/trust_gateway.py +474 -0
  230. caas/hf_utils.py +479 -0
  231. caas/ingestion/__init__.py +23 -0
  232. caas/ingestion/processors.py +253 -0
  233. caas/ingestion/structure_parser.py +188 -0
  234. caas/models.py +356 -0
  235. caas/pragmatic_truth.py +444 -0
  236. caas/routing/__init__.py +10 -0
  237. caas/routing/heuristic_router.py +58 -0
  238. caas/storage/__init__.py +9 -0
  239. caas/storage/store.py +389 -0
  240. caas/triad.py +213 -0
  241. caas/tuning/__init__.py +9 -0
  242. caas/tuning/tuner.py +329 -0
  243. caas/vfs/__init__.py +14 -0
  244. caas/vfs/filesystem.py +452 -0
  245. cmvk/__init__.py +218 -0
  246. cmvk/audit.py +402 -0
  247. cmvk/benchmarks.py +478 -0
  248. cmvk/constitutional.py +904 -0
  249. cmvk/hf_utils.py +301 -0
  250. cmvk/metrics.py +473 -0
  251. cmvk/profiles.py +300 -0
  252. cmvk/py.typed +0 -0
  253. cmvk/types.py +12 -0
  254. cmvk/verification.py +956 -0
  255. emk/__init__.py +89 -0
  256. emk/causal.py +352 -0
  257. emk/hf_utils.py +421 -0
  258. emk/indexer.py +83 -0
  259. emk/py.typed +0 -0
  260. emk/schema.py +204 -0
  261. emk/sleep_cycle.py +347 -0
  262. emk/store.py +281 -0
  263. iatp/__init__.py +166 -0
  264. iatp/attestation.py +461 -0
  265. iatp/cli.py +317 -0
  266. iatp/hf_utils.py +472 -0
  267. iatp/ipc_pipes.py +580 -0
  268. iatp/main.py +412 -0
  269. iatp/models/__init__.py +447 -0
  270. iatp/policy_engine.py +337 -0
  271. iatp/py.typed +2 -0
  272. iatp/recovery.py +321 -0
  273. iatp/security/__init__.py +270 -0
  274. iatp/sidecar/__init__.py +519 -0
  275. iatp/telemetry/__init__.py +164 -0
  276. iatp/tests/__init__.py +1 -0
  277. iatp/tests/test_attestation.py +370 -0
  278. iatp/tests/test_cli.py +131 -0
  279. iatp/tests/test_ed25519_attestation.py +211 -0
  280. iatp/tests/test_models.py +130 -0
  281. iatp/tests/test_policy_engine.py +347 -0
  282. iatp/tests/test_recovery.py +281 -0
  283. iatp/tests/test_security.py +222 -0
  284. iatp/tests/test_sidecar.py +167 -0
  285. iatp/tests/test_telemetry.py +175 -0
  286. mcp_kernel_server/__init__.py +28 -0
  287. mcp_kernel_server/cli.py +274 -0
  288. mcp_kernel_server/resources.py +217 -0
  289. mcp_kernel_server/server.py +564 -0
  290. mcp_kernel_server/tools.py +1174 -0
  291. mute_agent/__init__.py +68 -0
  292. mute_agent/core/__init__.py +1 -0
  293. mute_agent/core/execution_agent.py +166 -0
  294. mute_agent/core/handshake_protocol.py +201 -0
  295. mute_agent/core/reasoning_agent.py +238 -0
  296. mute_agent/knowledge_graph/__init__.py +1 -0
  297. mute_agent/knowledge_graph/graph_elements.py +65 -0
  298. mute_agent/knowledge_graph/multidimensional_graph.py +170 -0
  299. mute_agent/knowledge_graph/subgraph.py +224 -0
  300. mute_agent/listener/__init__.py +43 -0
  301. mute_agent/listener/adapters/__init__.py +31 -0
  302. mute_agent/listener/adapters/base_adapter.py +189 -0
  303. mute_agent/listener/adapters/caas_adapter.py +344 -0
  304. mute_agent/listener/adapters/control_plane_adapter.py +436 -0
  305. mute_agent/listener/adapters/iatp_adapter.py +332 -0
  306. mute_agent/listener/adapters/scak_adapter.py +251 -0
  307. mute_agent/listener/listener.py +610 -0
  308. mute_agent/listener/state_observer.py +436 -0
  309. mute_agent/listener/threshold_config.py +313 -0
  310. mute_agent/super_system/__init__.py +1 -0
  311. mute_agent/super_system/router.py +204 -0
  312. mute_agent/visualization/__init__.py +10 -0
  313. mute_agent/visualization/graph_debugger.py +502 -0
  314. nexus/README.md +60 -0
  315. nexus/__init__.py +51 -0
  316. nexus/arbiter.py +359 -0
  317. nexus/client.py +466 -0
  318. nexus/dmz.py +444 -0
  319. nexus/escrow.py +430 -0
  320. nexus/exceptions.py +286 -0
  321. nexus/pyproject.toml +36 -0
  322. nexus/registry.py +393 -0
  323. nexus/reputation.py +425 -0
  324. nexus/schemas/__init__.py +51 -0
  325. nexus/schemas/compliance.py +276 -0
  326. nexus/schemas/escrow.py +251 -0
  327. nexus/schemas/manifest.py +225 -0
  328. nexus/schemas/receipt.py +208 -0
  329. nexus/tests/__init__.py +0 -0
  330. nexus/tests/conftest.py +146 -0
  331. nexus/tests/test_arbiter.py +192 -0
  332. nexus/tests/test_dmz.py +194 -0
  333. nexus/tests/test_escrow.py +276 -0
  334. nexus/tests/test_exceptions.py +225 -0
  335. nexus/tests/test_registry.py +232 -0
  336. nexus/tests/test_reputation.py +328 -0
  337. nexus/tests/test_schemas.py +295 -0
@@ -0,0 +1,610 @@
1
+ # Copyright (c) Microsoft Corporation.
2
+ # Licensed under the MIT License.
3
+ """
4
+ Listener Agent - Layer 5 Reference Implementation
5
+
6
+ The Listener Agent is a passive observer that monitors graph states
7
+ without interfering until configured thresholds are exceeded.
8
+
9
+ Architecture:
10
+ - Consolidates: agent-control-plane, scak, iatp, caas
11
+ - Pattern: Observer with threshold-based intervention
12
+ - Principle: Monitor passively, intervene only when necessary
13
+
14
+ This module is pure wiring - it delegates to lower layers:
15
+ - Knowledge graph operations → scak (intelligence layer)
16
+ - Security validation → iatp (security layer)
17
+ - Context management → caas (context layer)
18
+ - Base orchestration → agent-control-plane
19
+ """
20
+
21
+ from typing import Dict, Any, Optional, List, Callable
22
+ from dataclasses import dataclass, field
23
+ from datetime import datetime, timedelta
24
+ from enum import Enum, auto
25
+ import threading
26
+ import time
27
+ from collections import deque
28
+
29
+ from ..knowledge_graph.multidimensional_graph import MultidimensionalKnowledgeGraph
30
+ from ..core.handshake_protocol import HandshakeProtocol, HandshakeSession, HandshakeState
31
+ from ..core.reasoning_agent import ReasoningAgent
32
+ from ..core.execution_agent import ExecutionAgent
33
+ from ..super_system.router import SuperSystemRouter
34
+
35
+ from .threshold_config import (
36
+ ThresholdConfig,
37
+ ThresholdType,
38
+ InterventionLevel,
39
+ ThresholdRule,
40
+ DEFAULT_THRESHOLDS,
41
+ )
42
+ from .state_observer import StateObserver, ObservationResult
43
+
44
+
45
+ class ListenerState(Enum):
46
+ """States of the Listener Agent."""
47
+
48
+ # Not actively observing
49
+ IDLE = "idle"
50
+
51
+ # Passively observing - no intervention
52
+ OBSERVING = "observing"
53
+
54
+ # Detected threshold breach - evaluating response
55
+ EVALUATING = "evaluating"
56
+
57
+ # Actively intervening
58
+ INTERVENING = "intervening"
59
+
60
+ # Intervention complete, returning to observation
61
+ RECOVERING = "recovering"
62
+
63
+ # Stopped - not operational
64
+ STOPPED = "stopped"
65
+
66
+
67
+ @dataclass
68
+ class InterventionEvent:
69
+ """
70
+ Record of a Listener intervention.
71
+
72
+ This provides an audit trail of when and why the Listener
73
+ transitioned from passive observation to active intervention.
74
+ """
75
+
76
+ event_id: str
77
+ timestamp: datetime
78
+ triggered_rules: List[ThresholdRule]
79
+ intervention_level: InterventionLevel
80
+ metrics_snapshot: Dict[str, float]
81
+ context: Dict[str, Any]
82
+ action_taken: str
83
+ outcome: Optional[str] = None
84
+ duration_ms: Optional[float] = None
85
+
86
+
87
+ @dataclass
88
+ class ListenerConfig:
89
+ """Configuration for the Listener Agent."""
90
+
91
+ # Threshold configuration
92
+ thresholds: ThresholdConfig = field(default_factory=lambda: DEFAULT_THRESHOLDS)
93
+
94
+ # Observation settings
95
+ observation_interval_seconds: float = 1.0
96
+ max_observation_history: int = 1000
97
+
98
+ # Intervention settings
99
+ auto_intervention: bool = True
100
+ require_confirmation: bool = False
101
+ max_interventions_per_minute: int = 10
102
+
103
+ # Recovery settings
104
+ recovery_observation_count: int = 5
105
+ recovery_success_threshold: float = 0.8
106
+
107
+
108
+ class ListenerAgent:
109
+ """
110
+ Layer 5 Reference Implementation: The Listener Agent
111
+
112
+ A passive observer that monitors graph states and only intervenes
113
+ when configured thresholds are exceeded.
114
+
115
+ Design Principles:
116
+ 1. Passive by default - observe without interference
117
+ 2. Threshold-driven intervention - clear, configurable triggers
118
+ 3. Minimal footprint - delegate to lower layers
119
+ 4. Full audit trail - every intervention is logged
120
+
121
+ Usage:
122
+ ```python
123
+ # Create core components
124
+ kg = MultidimensionalKnowledgeGraph()
125
+ protocol = HandshakeProtocol()
126
+ router = SuperSystemRouter(kg)
127
+
128
+ # Create and start listener
129
+ listener = ListenerAgent(kg, protocol, router)
130
+ listener.start()
131
+
132
+ # Listener now monitors passively...
133
+ # When thresholds are exceeded, it intervenes automatically
134
+
135
+ # Stop when done
136
+ listener.stop()
137
+ ```
138
+ """
139
+
140
+ def __init__(
141
+ self,
142
+ knowledge_graph: MultidimensionalKnowledgeGraph,
143
+ protocol: HandshakeProtocol,
144
+ router: SuperSystemRouter,
145
+ config: Optional[ListenerConfig] = None,
146
+ # Optional lower-layer adapters
147
+ security_adapter: Optional[Any] = None, # iatp adapter
148
+ context_adapter: Optional[Any] = None, # caas adapter
149
+ ):
150
+ """
151
+ Initialize the Listener Agent.
152
+
153
+ Args:
154
+ knowledge_graph: The graph to monitor (via scak)
155
+ protocol: The handshake protocol to observe
156
+ router: The super system router
157
+ config: Listener configuration
158
+ security_adapter: Optional adapter to iatp security layer
159
+ context_adapter: Optional adapter to caas context layer
160
+ """
161
+ self.knowledge_graph = knowledge_graph
162
+ self.protocol = protocol
163
+ self.router = router
164
+ self.config = config or ListenerConfig()
165
+
166
+ # Lower-layer adapters (for consolidated stack)
167
+ self._security_adapter = security_adapter
168
+ self._context_adapter = context_adapter
169
+
170
+ # State observer
171
+ self.observer = StateObserver(
172
+ knowledge_graph=knowledge_graph,
173
+ protocol=protocol,
174
+ router=router,
175
+ sample_window_seconds=self.config.thresholds.window_size_seconds,
176
+ )
177
+
178
+ # Current state
179
+ self._state = ListenerState.IDLE
180
+ self._state_lock = threading.Lock()
181
+
182
+ # Observation thread
183
+ self._observation_thread: Optional[threading.Thread] = None
184
+ self._stop_event = threading.Event()
185
+
186
+ # Intervention tracking
187
+ self._interventions: deque = deque(
188
+ maxlen=self.config.max_observation_history
189
+ )
190
+ self._intervention_count_this_minute = 0
191
+ self._minute_start = datetime.now()
192
+ self._event_counter = 0
193
+
194
+ # Callbacks
195
+ self._intervention_callbacks: List[Callable[[InterventionEvent], None]] = []
196
+ self._state_change_callbacks: List[Callable[[ListenerState, ListenerState], None]] = []
197
+
198
+ @property
199
+ def state(self) -> ListenerState:
200
+ """Get current listener state."""
201
+ with self._state_lock:
202
+ return self._state
203
+
204
+ def _set_state(self, new_state: ListenerState) -> None:
205
+ """Set listener state with callback notification."""
206
+ with self._state_lock:
207
+ old_state = self._state
208
+ self._state = new_state
209
+
210
+ # Notify callbacks outside lock
211
+ for callback in self._state_change_callbacks:
212
+ try:
213
+ callback(old_state, new_state)
214
+ except Exception:
215
+ pass # Don't let callback errors affect listener
216
+
217
+ def start(self) -> None:
218
+ """
219
+ Start the Listener Agent.
220
+
221
+ Begins passive observation of graph states. The listener will
222
+ continue observing until stop() is called or an intervention
223
+ threshold is exceeded.
224
+ """
225
+ if self._state != ListenerState.IDLE and self._state != ListenerState.STOPPED:
226
+ raise RuntimeError(f"Cannot start listener in state {self._state}")
227
+
228
+ self._stop_event.clear()
229
+ self._set_state(ListenerState.OBSERVING)
230
+
231
+ # Start observation thread
232
+ self._observation_thread = threading.Thread(
233
+ target=self._observation_loop,
234
+ name="ListenerAgent-Observer",
235
+ daemon=True,
236
+ )
237
+ self._observation_thread.start()
238
+
239
+ def stop(self) -> None:
240
+ """
241
+ Stop the Listener Agent.
242
+
243
+ Ceases observation and any ongoing intervention.
244
+ """
245
+ self._stop_event.set()
246
+ self._set_state(ListenerState.STOPPED)
247
+
248
+ if self._observation_thread and self._observation_thread.is_alive():
249
+ self._observation_thread.join(timeout=5.0)
250
+
251
+ def observe_once(self, context: Optional[Dict[str, Any]] = None) -> ObservationResult:
252
+ """
253
+ Perform a single observation cycle.
254
+
255
+ This is useful for synchronous observation without starting
256
+ the background observation loop.
257
+
258
+ Args:
259
+ context: Optional context to include
260
+
261
+ Returns:
262
+ ObservationResult from this cycle
263
+ """
264
+ return self.observer.observe(context)
265
+
266
+ def evaluate_thresholds(
267
+ self,
268
+ observation: ObservationResult
269
+ ) -> tuple[List[ThresholdRule], InterventionLevel]:
270
+ """
271
+ Evaluate observation against configured thresholds.
272
+
273
+ Args:
274
+ observation: The observation to evaluate
275
+
276
+ Returns:
277
+ Tuple of (triggered_rules, max_intervention_level)
278
+ """
279
+ # Convert observation to threshold metrics
280
+ threshold_metrics = observation.to_threshold_metrics()
281
+
282
+ # Evaluate against thresholds
283
+ triggered_rules = self.config.thresholds.evaluate_all(
284
+ threshold_metrics,
285
+ context={"observation": observation},
286
+ )
287
+
288
+ # Get maximum intervention level
289
+ max_level = self.config.thresholds.get_maximum_intervention_level(
290
+ triggered_rules
291
+ )
292
+
293
+ return triggered_rules, max_level
294
+
295
+ def _observation_loop(self) -> None:
296
+ """Background observation loop."""
297
+ while not self._stop_event.is_set():
298
+ try:
299
+ self._run_observation_cycle()
300
+ except Exception as e:
301
+ # Log error but continue observation
302
+ # In production, this would integrate with logging framework
303
+ pass
304
+
305
+ # Wait for next observation interval
306
+ self._stop_event.wait(self.config.observation_interval_seconds)
307
+
308
+ def _run_observation_cycle(self) -> None:
309
+ """Run a single observation cycle."""
310
+ # Perform observation
311
+ observation = self.observer.observe()
312
+
313
+ # Evaluate thresholds
314
+ triggered_rules, intervention_level = self.evaluate_thresholds(observation)
315
+
316
+ if not triggered_rules:
317
+ # No thresholds exceeded - continue passive observation
318
+ return
319
+
320
+ # Thresholds exceeded - evaluate intervention
321
+ self._set_state(ListenerState.EVALUATING)
322
+
323
+ # Check rate limiting
324
+ if not self._can_intervene():
325
+ self._set_state(ListenerState.OBSERVING)
326
+ return
327
+
328
+ # Determine if intervention is needed based on level
329
+ if intervention_level == InterventionLevel.OBSERVE:
330
+ # Log only
331
+ self._set_state(ListenerState.OBSERVING)
332
+ return
333
+
334
+ # Perform intervention
335
+ if self.config.auto_intervention:
336
+ self._perform_intervention(
337
+ triggered_rules,
338
+ intervention_level,
339
+ observation,
340
+ )
341
+
342
+ # Return to observation (or recovery)
343
+ if self._state == ListenerState.INTERVENING:
344
+ self._set_state(ListenerState.RECOVERING)
345
+ # In recovery mode, continue observation with heightened awareness
346
+ self._recovery_check()
347
+
348
+ def _can_intervene(self) -> bool:
349
+ """Check if intervention is allowed (rate limiting)."""
350
+ now = datetime.now()
351
+
352
+ # Reset counter if minute has passed
353
+ if (now - self._minute_start).total_seconds() >= 60:
354
+ self._intervention_count_this_minute = 0
355
+ self._minute_start = now
356
+
357
+ return self._intervention_count_this_minute < self.config.max_interventions_per_minute
358
+
359
+ def _perform_intervention(
360
+ self,
361
+ triggered_rules: List[ThresholdRule],
362
+ intervention_level: InterventionLevel,
363
+ observation: ObservationResult,
364
+ ) -> InterventionEvent:
365
+ """
366
+ Perform an intervention based on triggered rules.
367
+
368
+ This is where the Listener transitions from passive to active.
369
+ """
370
+ self._set_state(ListenerState.INTERVENING)
371
+ start_time = datetime.now()
372
+
373
+ # Generate event ID
374
+ self._event_counter += 1
375
+ event_id = f"intervention_{self._event_counter}_{start_time.timestamp()}"
376
+
377
+ # Determine action based on intervention level
378
+ action_taken = self._determine_action(intervention_level, triggered_rules)
379
+
380
+ # Execute intervention action
381
+ outcome = self._execute_intervention_action(
382
+ action_taken,
383
+ intervention_level,
384
+ triggered_rules,
385
+ )
386
+
387
+ # Calculate duration
388
+ duration_ms = (datetime.now() - start_time).total_seconds() * 1000
389
+
390
+ # Create event record
391
+ event = InterventionEvent(
392
+ event_id=event_id,
393
+ timestamp=start_time,
394
+ triggered_rules=triggered_rules,
395
+ intervention_level=intervention_level,
396
+ metrics_snapshot=observation.derived_metrics.copy(),
397
+ context={
398
+ "anomalies": observation.anomalies_detected,
399
+ "graph_snapshot": observation.graph_snapshot,
400
+ },
401
+ action_taken=action_taken,
402
+ outcome=outcome,
403
+ duration_ms=duration_ms,
404
+ )
405
+
406
+ # Store and notify
407
+ self._interventions.append(event)
408
+ self._intervention_count_this_minute += 1
409
+
410
+ for callback in self._intervention_callbacks:
411
+ try:
412
+ callback(event)
413
+ except Exception:
414
+ pass
415
+
416
+ return event
417
+
418
+ def _determine_action(
419
+ self,
420
+ level: InterventionLevel,
421
+ rules: List[ThresholdRule],
422
+ ) -> str:
423
+ """Determine intervention action based on level and rules."""
424
+ if level == InterventionLevel.WARN:
425
+ return "emit_warning"
426
+ elif level == InterventionLevel.SOFT_BLOCK:
427
+ return "require_confirmation"
428
+ elif level == InterventionLevel.HARD_BLOCK:
429
+ return "block_pending_actions"
430
+ elif level == InterventionLevel.EMERGENCY:
431
+ return "emergency_halt"
432
+ else:
433
+ return "observe_only"
434
+
435
+ def _execute_intervention_action(
436
+ self,
437
+ action: str,
438
+ level: InterventionLevel,
439
+ rules: List[ThresholdRule],
440
+ ) -> str:
441
+ """
442
+ Execute the determined intervention action.
443
+
444
+ This is where we wire together the lower layers:
445
+ - Use iatp for security-related interventions
446
+ - Use caas to update context
447
+ - Use agent-control-plane for action blocking
448
+ """
449
+ if action == "emit_warning":
450
+ # Log warning - in production, integrate with alerting system
451
+ return f"Warning emitted for {len(rules)} triggered rules"
452
+
453
+ elif action == "require_confirmation":
454
+ # Mark pending sessions as requiring confirmation
455
+ pending_blocked = 0
456
+ for session_id, session in self.protocol.sessions.items():
457
+ if session.state in [HandshakeState.VALIDATED, HandshakeState.ACCEPTED]:
458
+ session.metadata["requires_confirmation"] = True
459
+ session.metadata["confirmation_reason"] = (
460
+ f"Threshold breach: {[r.description for r in rules]}"
461
+ )
462
+ pending_blocked += 1
463
+ return f"Soft block applied to {pending_blocked} pending sessions"
464
+
465
+ elif action == "block_pending_actions":
466
+ # Reject all pending sessions
467
+ blocked = 0
468
+ for session_id, session in list(self.protocol.sessions.items()):
469
+ if session.state in [HandshakeState.INITIATED, HandshakeState.NEGOTIATING,
470
+ HandshakeState.VALIDATED, HandshakeState.ACCEPTED]:
471
+ self.protocol.reject_proposal(
472
+ session_id,
473
+ reason=f"Listener intervention: {level.value}"
474
+ )
475
+ blocked += 1
476
+ return f"Hard block applied, {blocked} sessions rejected"
477
+
478
+ elif action == "emergency_halt":
479
+ # Emergency halt - reject all and set protective state
480
+ halted = 0
481
+ for session_id, session in list(self.protocol.sessions.items()):
482
+ if session.state != HandshakeState.COMPLETED:
483
+ try:
484
+ self.protocol.reject_proposal(
485
+ session_id,
486
+ reason="EMERGENCY: System halt by Listener"
487
+ )
488
+ halted += 1
489
+ except ValueError:
490
+ pass # Session may already be in terminal state
491
+
492
+ # If security adapter available, notify it
493
+ if self._security_adapter:
494
+ try:
495
+ self._security_adapter.emergency_alert(
496
+ reason="Listener emergency halt",
497
+ triggered_rules=[r.description for r in rules],
498
+ )
499
+ except Exception:
500
+ pass
501
+
502
+ return f"Emergency halt: {halted} sessions terminated"
503
+
504
+ return "No action taken"
505
+
506
+ def _recovery_check(self) -> None:
507
+ """
508
+ Check if system has recovered after intervention.
509
+
510
+ Performs additional observations to verify system stability
511
+ before returning to normal observation.
512
+ """
513
+ success_count = 0
514
+
515
+ for _ in range(self.config.recovery_observation_count):
516
+ if self._stop_event.is_set():
517
+ break
518
+
519
+ observation = self.observer.observe()
520
+ triggered_rules, level = self.evaluate_thresholds(observation)
521
+
522
+ # Check if we're back to safe levels
523
+ if level in [InterventionLevel.OBSERVE, InterventionLevel.WARN]:
524
+ success_count += 1
525
+
526
+ time.sleep(self.config.observation_interval_seconds)
527
+
528
+ # Calculate recovery success rate
529
+ success_rate = success_count / self.config.recovery_observation_count
530
+
531
+ if success_rate >= self.config.recovery_success_threshold:
532
+ self._set_state(ListenerState.OBSERVING)
533
+ else:
534
+ # Still unstable - may need additional intervention
535
+ self._set_state(ListenerState.EVALUATING)
536
+
537
+ # === Public API for external integration ===
538
+
539
+ def register_intervention_callback(
540
+ self,
541
+ callback: Callable[[InterventionEvent], None]
542
+ ) -> None:
543
+ """Register a callback to be notified of interventions."""
544
+ self._intervention_callbacks.append(callback)
545
+
546
+ def register_state_change_callback(
547
+ self,
548
+ callback: Callable[[ListenerState, ListenerState], None]
549
+ ) -> None:
550
+ """Register a callback to be notified of state changes."""
551
+ self._state_change_callbacks.append(callback)
552
+
553
+ def get_intervention_history(
554
+ self,
555
+ count: Optional[int] = None
556
+ ) -> List[InterventionEvent]:
557
+ """Get recent intervention events."""
558
+ events = list(self._interventions)
559
+ if count is not None:
560
+ events = events[-count:]
561
+ return events
562
+
563
+ def get_statistics(self) -> Dict[str, Any]:
564
+ """Get listener statistics."""
565
+ observation_history = self.observer.get_observation_history()
566
+
567
+ return {
568
+ "state": self._state.value,
569
+ "total_observations": len(observation_history),
570
+ "total_interventions": len(self._interventions),
571
+ "interventions_this_minute": self._intervention_count_this_minute,
572
+ "active_thresholds": len(self.config.thresholds.rules),
573
+ "enabled_thresholds": sum(
574
+ 1 for r in self.config.thresholds.rules.values() if r.enabled
575
+ ),
576
+ "observer_baselines": len(self.observer._baselines),
577
+ }
578
+
579
+ def update_threshold(
580
+ self,
581
+ threshold_type: ThresholdType,
582
+ new_value: float
583
+ ) -> None:
584
+ """Update a threshold value at runtime."""
585
+ rule = self.config.thresholds.get_rule(threshold_type)
586
+ if rule:
587
+ rule.value = new_value
588
+
589
+ def enable_threshold(self, threshold_type: ThresholdType) -> None:
590
+ """Enable a threshold rule."""
591
+ self.config.thresholds.enable_rule(threshold_type)
592
+
593
+ def disable_threshold(self, threshold_type: ThresholdType) -> None:
594
+ """Disable a threshold rule."""
595
+ self.config.thresholds.disable_rule(threshold_type)
596
+
597
+ def calibrate(self, observation_count: int = 10) -> None:
598
+ """
599
+ Calibrate baselines during known-good operation.
600
+
601
+ Call this during normal operation to establish baseline
602
+ metrics for anomaly detection.
603
+ """
604
+ # Collect observations
605
+ for _ in range(observation_count):
606
+ self.observer.observe()
607
+ time.sleep(self.config.observation_interval_seconds)
608
+
609
+ # Calibrate observer baselines
610
+ self.observer.calibrate_baselines(observation_count)