1818
1919class AgentType (Enum ):
2020 """Agent type categories."""
21-
21+
2222 DESIGN_TIME = "design_time" # Code repos, CI/CD, design tools
2323 RUNTIME = "runtime" # Containers, cloud, APIs
2424 LANGUAGE = "language" # Language-specific agents
@@ -28,7 +28,7 @@ class AgentType(Enum):
2828
2929class AgentStatus (Enum ):
3030 """Agent status."""
31-
31+
3232 IDLE = "idle"
3333 CONNECTING = "connecting"
3434 MONITORING = "monitoring"
@@ -41,7 +41,7 @@ class AgentStatus(Enum):
4141@dataclass
4242class AgentConfig :
4343 """Agent configuration."""
44-
44+
4545 agent_id : str
4646 agent_type : AgentType
4747 name : str
@@ -57,7 +57,7 @@ class AgentConfig:
5757@dataclass
5858class AgentData :
5959 """Data collected by agent."""
60-
60+
6161 agent_id : str
6262 timestamp : datetime
6363 data_type : str # sarif, sbom, cve, design_context, runtime_metrics, etc.
@@ -67,7 +67,7 @@ class AgentData:
6767
6868class BaseAgent (ABC ):
6969 """Base class for all FixOps agents."""
70-
70+
7171 def __init__ (self , config : AgentConfig , fixops_api_url : str , fixops_api_key : str ):
7272 """Initialize agent."""
7373 self .config = config
@@ -80,77 +80,80 @@ def __init__(self, config: AgentConfig, fixops_api_url: str, fixops_api_key: str
8080 self .collection_count = 0
8181 self .push_count = 0
8282 self ._stop_requested = False
83-
83+
8484 @abstractmethod
8585 async def connect (self ) -> bool :
8686 """Connect to target system."""
8787 pass
88-
88+
8989 @abstractmethod
9090 async def disconnect (self ):
9191 """Disconnect from target system."""
9292 pass
93-
93+
9494 @abstractmethod
9595 async def collect_data (self ) -> List [AgentData ]:
9696 """Collect data from target system."""
9797 pass
98-
98+
9999 async def push_data (self , data : List [AgentData ]) -> bool :
100100 """Push data to FixOps API."""
101101 import aiohttp
102-
102+
103103 try :
104104 self .status = AgentStatus .PUSHING
105-
105+
106106 async with aiohttp .ClientSession () as session :
107107 for agent_data in data :
108108 # Push to appropriate FixOps endpoint
109109 endpoint = self ._get_endpoint (agent_data .data_type )
110110 url = f"{ self .fixops_api_url } { endpoint } "
111-
111+
112112 headers = {
113113 "X-API-Key" : self .fixops_api_key ,
114114 "Content-Type" : "application/json" ,
115115 }
116-
116+
117117 payload = {
118118 "agent_id" : agent_data .agent_id ,
119119 "timestamp" : agent_data .timestamp .isoformat (),
120120 "data_type" : agent_data .data_type ,
121121 "data" : agent_data .data ,
122122 "metadata" : agent_data .metadata ,
123123 }
124-
125- async with session .post (url , json = payload , headers = headers ) as response :
124+
125+ async with session .post (
126+ url , json = payload , headers = headers
127+ ) as response :
126128 if response .status not in [200 , 201 ]:
127129 error_text = await response .text ()
128130 logger .error (
129131 f"Failed to push { agent_data .data_type } from { self .config .agent_id } : "
130132 f"{ response .status } - { error_text } "
131133 )
132134 return False
133-
135+
134136 self .push_count += 1
135137 self .last_push = datetime .now (timezone .utc )
136-
138+
137139 logger .info (
138140 f"Successfully pushed { len (data )} data items from { self .config .agent_id } "
139141 )
140142 return True
141-
143+
142144 except Exception as e :
143145 logger .error (f"Error pushing data from { self .config .agent_id } : { e } " )
144146 self .error_count += 1
145147 return False
146-
148+
147149 finally :
148150 if not self ._stop_requested :
149151 self .status = AgentStatus .MONITORING
152+
150153 def request_stop (self ):
151154 """Signal the agent to stop after the current iteration."""
152155 self ._stop_requested = True
153-
156+
154157 def _get_endpoint (self , data_type : str ) -> str :
155158 """Get FixOps API endpoint for data type."""
156159 endpoints = {
@@ -165,53 +168,53 @@ def _get_endpoint(self, data_type: str) -> str:
165168 "iac_scan" : "/api/v1/ingest/iac-scan" ,
166169 }
167170 return endpoints .get (data_type , "/api/v1/ingest/data" )
168-
171+
169172 async def run (self ):
170173 """Main agent loop."""
171174 if not self .config .enabled :
172175 logger .info (f"Agent { self .config .agent_id } is disabled" )
173176 return
174-
177+
175178 try :
176179 # Connect
177180 self .status = AgentStatus .CONNECTING
178181 if not await self .connect ():
179182 self .status = AgentStatus .ERROR
180183 logger .error (f"Failed to connect agent { self .config .agent_id } " )
181184 return
182-
185+
183186 self .status = AgentStatus .MONITORING
184-
187+
185188 # Main monitoring loop
186- while not self ._stop_requested and self .status != AgentStatus .DISCONNECTED :
189+ while not self ._stop_requested and self .status != AgentStatus .DISCONNECTED :
187190 try :
188191 # Collect data
189192 self .status = AgentStatus .COLLECTING
190193 data = await self .collect_data ()
191194 self .last_collection = datetime .now (timezone .utc )
192195 self .collection_count += len (data )
193-
196+
194197 if data :
195198 # Push data
196199 success = await self .push_data (data )
197200 if not success :
198201 self .error_count += 1
199-
200- if self ._stop_requested :
201- break
202-
203- self .status = AgentStatus .MONITORING
204-
202+
203+ if self ._stop_requested :
204+ break
205+
206+ self .status = AgentStatus .MONITORING
207+
205208 # Wait for next polling interval
206- await asyncio .sleep (self .config .polling_interval )
207- if self ._stop_requested :
208- break
209-
209+ await asyncio .sleep (self .config .polling_interval )
210+ if self ._stop_requested :
211+ break
212+
210213 except Exception as e :
211214 logger .error (f"Error in agent { self .config .agent_id } loop: { e } " )
212215 self .error_count += 1
213216 self .status = AgentStatus .ERROR
214-
217+
215218 # Retry logic
216219 if self .error_count < self .config .retry_count :
217220 await asyncio .sleep (self .config .retry_delay )
@@ -221,15 +224,15 @@ async def run(self):
221224 f"Agent { self .config .agent_id } exceeded retry count, stopping"
222225 )
223226 break
224-
227+
225228 except Exception as e :
226229 logger .error (f"Fatal error in agent { self .config .agent_id } : { e } " )
227230 self .status = AgentStatus .ERROR
228-
231+
229232 finally :
230233 await self .disconnect ()
231234 self .status = AgentStatus .DISCONNECTED
232-
235+
233236 def get_status (self ) -> Dict [str , Any ]:
234237 """Get agent status."""
235238 return {
@@ -241,9 +244,7 @@ def get_status(self) -> Dict[str, Any]:
241244 "last_collection" : (
242245 self .last_collection .isoformat () if self .last_collection else None
243246 ),
244- "last_push" : (
245- self .last_push .isoformat () if self .last_push else None
246- ),
247+ "last_push" : (self .last_push .isoformat () if self .last_push else None ),
247248 "collection_count" : self .collection_count ,
248249 "push_count" : self .push_count ,
249250 "error_count" : self .error_count ,
@@ -252,41 +253,41 @@ def get_status(self) -> Dict[str, Any]:
252253
253254class AgentFramework :
254255 """FixOps Agent Framework - Manages all agents."""
255-
256+
256257 def __init__ (self , fixops_api_url : str , fixops_api_key : str ):
257258 """Initialize agent framework."""
258259 self .fixops_api_url = fixops_api_url
259260 self .fixops_api_key = fixops_api_key
260261 self .agents : Dict [str , BaseAgent ] = {}
261262 self .running = False
262-
263+
263264 def register_agent (self , agent : BaseAgent ):
264265 """Register an agent."""
265266 self .agents [agent .config .agent_id ] = agent
266267 logger .info (f"Registered agent: { agent .config .agent_id } " )
267-
268+
268269 async def start_all (self ):
269270 """Start all enabled agents."""
270271 self .running = True
271-
272+
272273 tasks = []
273274 for agent in self .agents .values ():
274275 if agent .config .enabled :
275276 task = asyncio .create_task (agent .run ())
276277 tasks .append (task )
277-
278+
278279 logger .info (f"Started { len (tasks )} agents" )
279280 await asyncio .gather (* tasks , return_exceptions = True )
280-
281+
281282 async def stop_all (self ):
282283 """Stop all agents."""
283284 self .running = False
284-
285+
285286 for agent in self .agents .values ():
286287 agent .request_stop ()
287-
288+
288289 logger .info ("Stopped all agents" )
289-
290+
290291 def get_all_status (self ) -> List [Dict [str , Any ]]:
291292 """Get status of all agents."""
292293 return [agent .get_status () for agent in self .agents .values ()]
0 commit comments