control-zero 0.2.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 (44) hide show
  1. control_zero/__init__.py +31 -0
  2. control_zero/client.py +584 -0
  3. control_zero/integrations/crewai/__init__.py +53 -0
  4. control_zero/integrations/crewai/agent.py +267 -0
  5. control_zero/integrations/crewai/crew.py +381 -0
  6. control_zero/integrations/crewai/task.py +291 -0
  7. control_zero/integrations/crewai/tool.py +299 -0
  8. control_zero/integrations/langchain/__init__.py +58 -0
  9. control_zero/integrations/langchain/agent.py +311 -0
  10. control_zero/integrations/langchain/callbacks.py +441 -0
  11. control_zero/integrations/langchain/chain.py +319 -0
  12. control_zero/integrations/langchain/graph.py +441 -0
  13. control_zero/integrations/langchain/tool.py +271 -0
  14. control_zero/llm/__init__.py +77 -0
  15. control_zero/llm/anthropic/__init__.py +35 -0
  16. control_zero/llm/anthropic/client.py +136 -0
  17. control_zero/llm/anthropic/messages.py +375 -0
  18. control_zero/llm/base.py +551 -0
  19. control_zero/llm/cohere/__init__.py +32 -0
  20. control_zero/llm/cohere/client.py +402 -0
  21. control_zero/llm/gemini/__init__.py +34 -0
  22. control_zero/llm/gemini/client.py +486 -0
  23. control_zero/llm/groq/__init__.py +32 -0
  24. control_zero/llm/groq/client.py +330 -0
  25. control_zero/llm/mistral/__init__.py +32 -0
  26. control_zero/llm/mistral/client.py +319 -0
  27. control_zero/llm/ollama/__init__.py +31 -0
  28. control_zero/llm/ollama/client.py +439 -0
  29. control_zero/llm/openai/__init__.py +34 -0
  30. control_zero/llm/openai/chat.py +331 -0
  31. control_zero/llm/openai/client.py +182 -0
  32. control_zero/logging/__init__.py +5 -0
  33. control_zero/logging/async_logger.py +65 -0
  34. control_zero/mcp/__init__.py +5 -0
  35. control_zero/mcp/middleware.py +148 -0
  36. control_zero/policy/__init__.py +5 -0
  37. control_zero/policy/enforcer.py +99 -0
  38. control_zero/secrets/__init__.py +5 -0
  39. control_zero/secrets/manager.py +77 -0
  40. control_zero/types.py +51 -0
  41. control_zero-0.2.0.dist-info/METADATA +216 -0
  42. control_zero-0.2.0.dist-info/RECORD +44 -0
  43. control_zero-0.2.0.dist-info/WHEEL +4 -0
  44. control_zero-0.2.0.dist-info/licenses/LICENSE +17 -0
@@ -0,0 +1,267 @@
1
+ """
2
+ Governed CrewAI Agent wrapper.
3
+
4
+ Provides governance enforcement for CrewAI agents including:
5
+ - Role-based access control
6
+ - Tool filtering based on agent role
7
+ - Action logging
8
+ - Execution tracking
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+ from functools import wraps
15
+ from typing import Any, Callable, Dict, List, Optional
16
+
17
+ try:
18
+ from crewai import Agent
19
+ from crewai.tools import BaseTool as CrewAIBaseTool
20
+ CREWAI_AVAILABLE = True
21
+ except ImportError:
22
+ CREWAI_AVAILABLE = False
23
+ class Agent:
24
+ pass
25
+ class CrewAIBaseTool:
26
+ pass
27
+
28
+ from control_zero.client import ControlZeroClient
29
+ from control_zero.policy import PolicyDecision, PolicyDeniedError
30
+ from control_zero.integrations.crewai.tool import GovernedCrewTool, wrap_crew_tools
31
+
32
+
33
+ class GovernedCrewAgent:
34
+ """
35
+ Governance wrapper for CrewAI Agent.
36
+
37
+ Wraps a CrewAI Agent to provide:
38
+ - Role-based policy enforcement
39
+ - Tool-level governance
40
+ - Action audit logging
41
+ - Execution tracking
42
+
43
+ Usage:
44
+ from crewai import Agent
45
+
46
+ researcher = Agent(
47
+ role="Senior Researcher",
48
+ goal="Research topics thoroughly",
49
+ backstory="You are an expert researcher...",
50
+ tools=[search_tool, scrape_tool],
51
+ )
52
+
53
+ governed_researcher = GovernedCrewAgent(
54
+ agent=researcher,
55
+ client=client,
56
+ wrap_tools=True,
57
+ )
58
+
59
+ # Use in a crew
60
+ crew = Crew(
61
+ agents=[governed_researcher.agent],
62
+ tasks=[...],
63
+ )
64
+ """
65
+
66
+ def __init__(
67
+ self,
68
+ agent: Agent,
69
+ client: ControlZeroClient,
70
+ wrap_tools: bool = True,
71
+ agent_name: Optional[str] = None,
72
+ allowed_tools: Optional[List[str]] = None,
73
+ ):
74
+ """
75
+ Initialize governed agent.
76
+
77
+ Args:
78
+ agent: The CrewAI Agent to wrap
79
+ client: Control Zero client
80
+ wrap_tools: Whether to wrap agent tools with governance
81
+ agent_name: Name for logging (defaults to agent.role)
82
+ allowed_tools: List of allowed tool names (filters tools)
83
+ """
84
+ if not CREWAI_AVAILABLE:
85
+ raise ImportError(
86
+ "crewai is required. Install with: pip install crewai"
87
+ )
88
+
89
+ self._agent = agent
90
+ self._client = client
91
+ self._agent_name = agent_name or agent.role
92
+ self._allowed_tools = allowed_tools
93
+
94
+ # Filter tools if allowlist specified
95
+ if allowed_tools and agent.tools:
96
+ agent.tools = [
97
+ t for t in agent.tools
98
+ if getattr(t, 'name', str(t)) in allowed_tools
99
+ ]
100
+
101
+ # Wrap tools with governance
102
+ if wrap_tools and agent.tools:
103
+ self._wrap_agent_tools()
104
+
105
+ # Tracking
106
+ self._total_actions = 0
107
+ self._session_start = time.time()
108
+
109
+ def _wrap_agent_tools(self) -> None:
110
+ """Wrap all agent tools with governance."""
111
+ governed_tools = []
112
+ for tool in self._agent.tools:
113
+ if isinstance(tool, GovernedCrewTool):
114
+ governed_tools.append(tool)
115
+ elif isinstance(tool, CrewAIBaseTool):
116
+ governed_tools.append(
117
+ GovernedCrewTool(
118
+ base_tool=tool,
119
+ client=self._client,
120
+ )
121
+ )
122
+ else:
123
+ # Callable tools - wrap inline
124
+ governed_tools.append(tool)
125
+ self._agent.tools = governed_tools
126
+
127
+ def _check_policy(self, action: str) -> PolicyDecision:
128
+ """Check policy for agent action."""
129
+ if hasattr(self._client, '_policy_cache') and self._client._policy_cache:
130
+ return self._client._policy_cache.evaluate(
131
+ f"crewai:agent:{self._agent_name}",
132
+ action
133
+ )
134
+ return PolicyDecision(effect="allow")
135
+
136
+ def execute_task(
137
+ self,
138
+ task: Any,
139
+ context: Optional[str] = None,
140
+ tools: Optional[List] = None,
141
+ ) -> str:
142
+ """
143
+ Execute a task with governance.
144
+
145
+ Args:
146
+ task: The task to execute
147
+ context: Additional context
148
+ tools: Override tools for this execution
149
+
150
+ Returns:
151
+ Task result string
152
+ """
153
+ decision = self._check_policy("execute_task")
154
+
155
+ if decision.effect == "deny":
156
+ self._log_action("execute_task", "denied", 0, decision)
157
+ raise PolicyDeniedError(decision)
158
+
159
+ start = time.perf_counter()
160
+ self._total_actions += 1
161
+
162
+ try:
163
+ # Execute task using agent
164
+ result = self._agent.execute_task(
165
+ task=task,
166
+ context=context,
167
+ tools=tools or self._agent.tools,
168
+ )
169
+
170
+ latency_ms = int((time.perf_counter() - start) * 1000)
171
+ self._log_action("execute_task", "success", latency_ms, decision, {
172
+ "task_description": str(task.description)[:200] if hasattr(task, 'description') else None,
173
+ "result_length": len(result) if result else 0,
174
+ })
175
+
176
+ return result
177
+
178
+ except PolicyDeniedError:
179
+ raise
180
+ except Exception as e:
181
+ latency_ms = int((time.perf_counter() - start) * 1000)
182
+ self._log_action("execute_task", "error", latency_ms, decision, error=e)
183
+ raise
184
+
185
+ def _log_action(
186
+ self,
187
+ action: str,
188
+ status: str,
189
+ latency_ms: int,
190
+ decision: PolicyDecision,
191
+ metadata: Optional[Dict] = None,
192
+ error: Optional[Exception] = None,
193
+ ) -> None:
194
+ """Log agent action."""
195
+ log_data = metadata or {}
196
+ log_data["agent_role"] = self._agent.role
197
+ log_data["total_actions"] = self._total_actions
198
+
199
+ self._client._log(
200
+ tool=f"crewai:agent:{self._agent_name}",
201
+ method=action,
202
+ status=status,
203
+ latency_ms=latency_ms,
204
+ policy_decision=decision,
205
+ error_type=type(error).__name__ if error else None,
206
+ error_message=str(error) if error else None,
207
+ **log_data
208
+ )
209
+
210
+ @property
211
+ def agent(self) -> Agent:
212
+ """Get underlying CrewAI agent."""
213
+ return self._agent
214
+
215
+ @property
216
+ def role(self) -> str:
217
+ """Get agent role."""
218
+ return self._agent.role
219
+
220
+ @property
221
+ def goal(self) -> str:
222
+ """Get agent goal."""
223
+ return self._agent.goal
224
+
225
+ @property
226
+ def tools(self) -> List:
227
+ """Get agent tools."""
228
+ return self._agent.tools
229
+
230
+ @property
231
+ def total_actions(self) -> int:
232
+ """Get total actions executed."""
233
+ return self._total_actions
234
+
235
+
236
+ def governed_agent(
237
+ client: ControlZeroClient,
238
+ wrap_tools: bool = True,
239
+ allowed_tools: Optional[List[str]] = None,
240
+ ) -> Callable:
241
+ """
242
+ Decorator to create a governed CrewAI agent.
243
+
244
+ Usage:
245
+ @governed_agent(client, wrap_tools=True)
246
+ def create_researcher():
247
+ return Agent(
248
+ role="Researcher",
249
+ goal="Research topics",
250
+ backstory="You are an expert researcher",
251
+ tools=[search_tool],
252
+ )
253
+
254
+ researcher = create_researcher() # Returns GovernedCrewAgent
255
+ """
256
+ def decorator(func: Callable) -> Callable:
257
+ @wraps(func)
258
+ def wrapper(*args, **kwargs) -> GovernedCrewAgent:
259
+ agent = func(*args, **kwargs)
260
+ return GovernedCrewAgent(
261
+ agent=agent,
262
+ client=client,
263
+ wrap_tools=wrap_tools,
264
+ allowed_tools=allowed_tools,
265
+ )
266
+ return wrapper
267
+ return decorator
@@ -0,0 +1,381 @@
1
+ """
2
+ Governed CrewAI Crew wrapper.
3
+
4
+ Provides governance enforcement for CrewAI crews including:
5
+ - Crew-level policy enforcement
6
+ - Agent role-based access control
7
+ - Task execution governance
8
+ - Inter-agent communication logging
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ try:
17
+ from crewai import Crew, Agent, Task, Process
18
+ CREWAI_AVAILABLE = True
19
+ except ImportError:
20
+ CREWAI_AVAILABLE = False
21
+ class Crew:
22
+ pass
23
+ class Agent:
24
+ pass
25
+ class Task:
26
+ pass
27
+ class Process:
28
+ sequential = "sequential"
29
+ hierarchical = "hierarchical"
30
+
31
+ from control_zero.client import ControlZeroClient
32
+ from control_zero.policy import PolicyDecision, PolicyDeniedError
33
+ from control_zero.integrations.crewai.agent import GovernedCrewAgent
34
+ from control_zero.integrations.crewai.task import GovernedTask
35
+
36
+
37
+ class GovernedCrew:
38
+ """
39
+ Governance wrapper for CrewAI Crew.
40
+
41
+ Wraps a CrewAI Crew to provide:
42
+ - Crew-level policy enforcement
43
+ - Automatic agent and task governance wrapping
44
+ - Execution audit logging
45
+ - Resource tracking
46
+
47
+ Usage:
48
+ from crewai import Crew, Agent, Task
49
+
50
+ researcher = Agent(role="Researcher", ...)
51
+ writer = Agent(role="Writer", ...)
52
+
53
+ research_task = Task(description="...", agent=researcher)
54
+ writing_task = Task(description="...", agent=writer)
55
+
56
+ crew = Crew(
57
+ agents=[researcher, writer],
58
+ tasks=[research_task, writing_task],
59
+ )
60
+
61
+ governed_crew = GovernedCrew(
62
+ crew=crew,
63
+ client=client,
64
+ crew_name="content_creation",
65
+ )
66
+
67
+ result = governed_crew.kickoff()
68
+ """
69
+
70
+ def __init__(
71
+ self,
72
+ crew: Crew,
73
+ client: ControlZeroClient,
74
+ crew_name: str = "crewai_crew",
75
+ wrap_agents: bool = True,
76
+ wrap_tasks: bool = True,
77
+ max_rpm: Optional[int] = None,
78
+ max_iterations: Optional[int] = None,
79
+ log_outputs: bool = False,
80
+ ):
81
+ """
82
+ Initialize governed crew.
83
+
84
+ Args:
85
+ crew: The CrewAI Crew to wrap
86
+ client: Control Zero client
87
+ crew_name: Name for logging purposes
88
+ wrap_agents: Whether to wrap agents with governance
89
+ wrap_tasks: Whether to wrap tasks with governance
90
+ max_rpm: Maximum requests per minute (rate limit)
91
+ max_iterations: Maximum iterations for the crew
92
+ log_outputs: Whether to log output content
93
+ """
94
+ if not CREWAI_AVAILABLE:
95
+ raise ImportError(
96
+ "crewai is required. Install with: pip install crewai"
97
+ )
98
+
99
+ self._crew = crew
100
+ self._client = client
101
+ self._crew_name = crew_name
102
+ self._log_outputs = log_outputs
103
+
104
+ # Apply limits
105
+ if max_rpm:
106
+ self._crew.max_rpm = max_rpm
107
+ if max_iterations:
108
+ self._crew.max_iterations = max_iterations
109
+
110
+ # Wrap agents and tasks with governance
111
+ if wrap_agents:
112
+ self._governed_agents = self._wrap_agents()
113
+ else:
114
+ self._governed_agents = []
115
+
116
+ if wrap_tasks:
117
+ self._governed_tasks = self._wrap_tasks()
118
+ else:
119
+ self._governed_tasks = []
120
+
121
+ # Tracking
122
+ self._kickoff_count = 0
123
+ self._total_tokens = 0
124
+ self._session_start = time.time()
125
+
126
+ def _wrap_agents(self) -> List[GovernedCrewAgent]:
127
+ """Wrap crew agents with governance."""
128
+ governed = []
129
+ for agent in self._crew.agents:
130
+ if isinstance(agent, GovernedCrewAgent):
131
+ governed.append(agent)
132
+ else:
133
+ governed.append(
134
+ GovernedCrewAgent(
135
+ agent=agent,
136
+ client=self._client,
137
+ wrap_tools=True,
138
+ )
139
+ )
140
+ return governed
141
+
142
+ def _wrap_tasks(self) -> List[GovernedTask]:
143
+ """Wrap crew tasks with governance."""
144
+ governed = []
145
+ for task in self._crew.tasks:
146
+ if isinstance(task, GovernedTask):
147
+ governed.append(task)
148
+ else:
149
+ governed.append(
150
+ GovernedTask(
151
+ task=task,
152
+ client=self._client,
153
+ log_outputs=self._log_outputs,
154
+ )
155
+ )
156
+ return governed
157
+
158
+ def _check_policy(self, action: str) -> PolicyDecision:
159
+ """Check policy for crew action."""
160
+ if hasattr(self._client, '_policy_cache') and self._client._policy_cache:
161
+ return self._client._policy_cache.evaluate(
162
+ f"crewai:crew:{self._crew_name}",
163
+ action
164
+ )
165
+ return PolicyDecision(effect="allow")
166
+
167
+ def kickoff(
168
+ self,
169
+ inputs: Optional[Dict[str, Any]] = None,
170
+ ) -> Any:
171
+ """
172
+ Execute the crew with governance.
173
+
174
+ Args:
175
+ inputs: Input variables for the crew
176
+
177
+ Returns:
178
+ Crew execution result
179
+ """
180
+ decision = self._check_policy("kickoff")
181
+
182
+ if decision.effect == "deny":
183
+ self._log_kickoff("denied", 0, decision)
184
+ raise PolicyDeniedError(decision)
185
+
186
+ start = time.perf_counter()
187
+ self._kickoff_count += 1
188
+
189
+ try:
190
+ # Execute crew
191
+ result = self._crew.kickoff(inputs=inputs)
192
+
193
+ latency_ms = int((time.perf_counter() - start) * 1000)
194
+
195
+ metadata = {
196
+ "kickoff_count": self._kickoff_count,
197
+ "num_agents": len(self._crew.agents),
198
+ "num_tasks": len(self._crew.tasks),
199
+ "process_type": str(getattr(self._crew, 'process', 'sequential')),
200
+ }
201
+
202
+ if self._log_outputs and result:
203
+ metadata["result_preview"] = str(result)[:500]
204
+
205
+ self._log_kickoff("success", latency_ms, decision, metadata)
206
+
207
+ return result
208
+
209
+ except PolicyDeniedError:
210
+ raise
211
+ except Exception as e:
212
+ latency_ms = int((time.perf_counter() - start) * 1000)
213
+ self._log_kickoff("error", latency_ms, decision, error=e)
214
+ raise
215
+
216
+ async def akickoff(
217
+ self,
218
+ inputs: Optional[Dict[str, Any]] = None,
219
+ ) -> Any:
220
+ """
221
+ Async execute the crew with governance.
222
+
223
+ Args:
224
+ inputs: Input variables for the crew
225
+
226
+ Returns:
227
+ Crew execution result
228
+ """
229
+ decision = self._check_policy("kickoff")
230
+
231
+ if decision.effect == "deny":
232
+ self._log_kickoff("denied", 0, decision)
233
+ raise PolicyDeniedError(decision)
234
+
235
+ start = time.perf_counter()
236
+ self._kickoff_count += 1
237
+
238
+ try:
239
+ # Execute crew async
240
+ if hasattr(self._crew, 'akickoff'):
241
+ result = await self._crew.akickoff(inputs=inputs)
242
+ else:
243
+ # Fallback to sync
244
+ result = self._crew.kickoff(inputs=inputs)
245
+
246
+ latency_ms = int((time.perf_counter() - start) * 1000)
247
+
248
+ metadata = {
249
+ "kickoff_count": self._kickoff_count,
250
+ "num_agents": len(self._crew.agents),
251
+ "num_tasks": len(self._crew.tasks),
252
+ "async": True,
253
+ }
254
+
255
+ if self._log_outputs and result:
256
+ metadata["result_preview"] = str(result)[:500]
257
+
258
+ self._log_kickoff("success", latency_ms, decision, metadata)
259
+
260
+ return result
261
+
262
+ except PolicyDeniedError:
263
+ raise
264
+ except Exception as e:
265
+ latency_ms = int((time.perf_counter() - start) * 1000)
266
+ self._log_kickoff("error", latency_ms, decision, error=e)
267
+ raise
268
+
269
+ def kickoff_for_each(
270
+ self,
271
+ inputs: List[Dict[str, Any]],
272
+ ) -> List[Any]:
273
+ """
274
+ Execute crew for each input set with governance.
275
+
276
+ Args:
277
+ inputs: List of input dictionaries
278
+
279
+ Returns:
280
+ List of results
281
+ """
282
+ decision = self._check_policy("kickoff_for_each")
283
+
284
+ if decision.effect == "deny":
285
+ self._log_kickoff("denied", 0, decision, {"action": "kickoff_for_each"})
286
+ raise PolicyDeniedError(decision)
287
+
288
+ start = time.perf_counter()
289
+ results = []
290
+
291
+ try:
292
+ for input_set in inputs:
293
+ result = self._crew.kickoff(inputs=input_set)
294
+ results.append(result)
295
+ self._kickoff_count += 1
296
+
297
+ latency_ms = int((time.perf_counter() - start) * 1000)
298
+
299
+ self._log_kickoff("success", latency_ms, decision, {
300
+ "action": "kickoff_for_each",
301
+ "batch_size": len(inputs),
302
+ "successful": len(results),
303
+ })
304
+
305
+ return results
306
+
307
+ except PolicyDeniedError:
308
+ raise
309
+ except Exception as e:
310
+ latency_ms = int((time.perf_counter() - start) * 1000)
311
+ self._log_kickoff("error", latency_ms, decision, {
312
+ "action": "kickoff_for_each",
313
+ "completed": len(results),
314
+ }, error=e)
315
+ raise
316
+
317
+ def _log_kickoff(
318
+ self,
319
+ status: str,
320
+ latency_ms: int,
321
+ decision: PolicyDecision,
322
+ metadata: Optional[Dict] = None,
323
+ error: Optional[Exception] = None,
324
+ ) -> None:
325
+ """Log crew kickoff."""
326
+ log_data = metadata or {}
327
+ log_data["crew_name"] = self._crew_name
328
+
329
+ self._client._log(
330
+ tool=f"crewai:crew:{self._crew_name}",
331
+ method="kickoff",
332
+ status=status,
333
+ latency_ms=latency_ms,
334
+ policy_decision=decision,
335
+ error_type=type(error).__name__ if error else None,
336
+ error_message=str(error) if error else None,
337
+ **log_data
338
+ )
339
+
340
+ @property
341
+ def crew(self) -> Crew:
342
+ """Get underlying CrewAI crew."""
343
+ return self._crew
344
+
345
+ @property
346
+ def agents(self) -> List:
347
+ """Get crew agents."""
348
+ return self._crew.agents
349
+
350
+ @property
351
+ def tasks(self) -> List:
352
+ """Get crew tasks."""
353
+ return self._crew.tasks
354
+
355
+ @property
356
+ def governed_agents(self) -> List[GovernedCrewAgent]:
357
+ """Get governed agent wrappers."""
358
+ return self._governed_agents
359
+
360
+ @property
361
+ def governed_tasks(self) -> List[GovernedTask]:
362
+ """Get governed task wrappers."""
363
+ return self._governed_tasks
364
+
365
+ @property
366
+ def kickoff_count(self) -> int:
367
+ """Get total kickoff count."""
368
+ return self._kickoff_count
369
+
370
+ @property
371
+ def usage_metrics(self) -> Dict[str, Any]:
372
+ """Get usage metrics for the crew."""
373
+ session_duration = time.time() - self._session_start
374
+ return {
375
+ "crew_name": self._crew_name,
376
+ "kickoff_count": self._kickoff_count,
377
+ "num_agents": len(self._crew.agents),
378
+ "num_tasks": len(self._crew.tasks),
379
+ "session_duration_seconds": round(session_duration, 2),
380
+ "total_tokens": self._total_tokens,
381
+ }