agentops-cockpit 0.4.1__py3-none-any.whl → 0.9.5__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 (38) hide show
  1. agent_ops_cockpit/agent.py +137 -0
  2. agent_ops_cockpit/cli/main.py +114 -11
  3. agent_ops_cockpit/eval/load_test.py +15 -10
  4. agent_ops_cockpit/eval/quality_climber.py +23 -5
  5. agent_ops_cockpit/eval/red_team.py +16 -10
  6. agent_ops_cockpit/mcp_server.py +132 -0
  7. agent_ops_cockpit/ops/arch_review.py +125 -59
  8. agent_ops_cockpit/ops/cost_optimizer.py +0 -1
  9. agent_ops_cockpit/ops/evidence_bridge.py +132 -0
  10. agent_ops_cockpit/ops/frameworks.py +79 -10
  11. agent_ops_cockpit/ops/mcp_hub.py +1 -2
  12. agent_ops_cockpit/ops/orchestrator.py +363 -49
  13. agent_ops_cockpit/ops/pii_scrubber.py +1 -1
  14. agent_ops_cockpit/ops/policies.json +26 -0
  15. agent_ops_cockpit/ops/policy_engine.py +85 -0
  16. agent_ops_cockpit/ops/reliability.py +30 -10
  17. agent_ops_cockpit/ops/secret_scanner.py +10 -3
  18. agent_ops_cockpit/ops/ui_auditor.py +91 -96
  19. agent_ops_cockpit/ops/watcher.py +138 -0
  20. agent_ops_cockpit/ops/watchlist.json +88 -0
  21. agent_ops_cockpit/optimizer.py +380 -158
  22. agent_ops_cockpit/shadow/router.py +7 -8
  23. agent_ops_cockpit/system_prompt.md +13 -0
  24. agent_ops_cockpit/tests/golden_set.json +52 -0
  25. agent_ops_cockpit/tests/test_agent.py +34 -0
  26. agent_ops_cockpit/tests/test_arch_review.py +45 -0
  27. agent_ops_cockpit/tests/test_frameworks.py +100 -0
  28. agent_ops_cockpit/tests/test_optimizer.py +68 -0
  29. agent_ops_cockpit/tests/test_quality_climber.py +18 -0
  30. agent_ops_cockpit/tests/test_red_team.py +35 -0
  31. agent_ops_cockpit/tests/test_secret_scanner.py +24 -0
  32. agentops_cockpit-0.9.5.dist-info/METADATA +246 -0
  33. agentops_cockpit-0.9.5.dist-info/RECORD +47 -0
  34. {agentops_cockpit-0.4.1.dist-info → agentops_cockpit-0.9.5.dist-info}/entry_points.txt +1 -0
  35. agentops_cockpit-0.4.1.dist-info/METADATA +0 -171
  36. agentops_cockpit-0.4.1.dist-info/RECORD +0 -31
  37. {agentops_cockpit-0.4.1.dist-info → agentops_cockpit-0.9.5.dist-info}/WHEEL +0 -0
  38. {agentops_cockpit-0.4.1.dist-info → agentops_cockpit-0.9.5.dist-info}/licenses/LICENSE +0 -0
@@ -12,6 +12,9 @@ console = Console()
12
12
  SECRET_PATTERNS = {
13
13
  "Google API Key": r"AIza[0-9A-Za-z-_]{35}",
14
14
  "AWS Access Key": r"AKIA[0-9A-Z]{16}",
15
+ "OpenAI API Key": r"sk-[a-zA-Z0-9]{20,}",
16
+ "Anthropic API Key": r"sk-ant-[a-zA-Z0-9]{20,}",
17
+ "Azure OpenAI Key": r"[0-9a-f]{32}",
15
18
  "Generic Bearer Token": r"Bearer\s+[0-9a-zA-Z._-]{20,}",
16
19
  "Hardcoded API Variable": r"(?i)(api_key|app_secret|client_secret|access_token)\s*=\s*['\"][0-9a-zA-Z_-]{16,}['\"]",
17
20
  "GCP Service Account": r"\"type\":\s*\"service_account\"",
@@ -28,7 +31,7 @@ def scan(path: str = typer.Argument(".", help="Directory to scan for secrets")):
28
31
 
29
32
  for root, dirs, files in os.walk(path):
30
33
  # Skip virtual environments, git, and tests
31
- if any(skip in root for skip in [".venv", ".git", "src/backend/tests"]):
34
+ if any(skip in root.lower() for skip in [".venv", ".git", "tests", "test_", "node_modules"]):
32
35
  continue
33
36
 
34
37
  for file in files:
@@ -47,7 +50,7 @@ def scan(path: str = typer.Argument(".", help="Directory to scan for secrets")):
47
50
  "type": secret_name,
48
51
  "content": line.strip()[:50] + "..."
49
52
  })
50
- except Exception as e:
53
+ except Exception:
51
54
  continue
52
55
 
53
56
  table = Table(title="🛡️ Security Findings: Hardcoded Secrets")
@@ -57,6 +60,7 @@ def scan(path: str = typer.Argument(".", help="Directory to scan for secrets")):
57
60
  table.add_column("Suggestion", style="green")
58
61
 
59
62
  if findings:
63
+ console.print("\n[bold]🛠️ DEVELOPER ACTIONS REQUIRED:[/bold]")
60
64
  for finding in findings:
61
65
  table.add_row(
62
66
  finding["file"],
@@ -64,7 +68,10 @@ def scan(path: str = typer.Argument(".", help="Directory to scan for secrets")):
64
68
  finding["type"],
65
69
  "Move to Secret Manager"
66
70
  )
67
- console.print(table)
71
+ # Orchestrator parsing
72
+ console.print(f"ACTION: {finding['file']}:{finding['line']} | Found {finding['type']} leak | Move this credential to Google Cloud Secret Manager or .env file.")
73
+
74
+ console.print("\n", table)
68
75
  console.print(f"\n❌ [bold red]FAIL:[/bold red] Found {len(findings)} potential credential leaks.")
69
76
  console.print("💡 [bold green]Recommendation:[/bold green] Use Google Cloud Secret Manager or environment variables for all tokens.")
70
77
  raise typer.Exit(code=1)
@@ -5,116 +5,111 @@ from rich.console import Console
5
5
  from rich.table import Table
6
6
  from rich.panel import Panel
7
7
 
8
- app = typer.Typer(help="UI/UX Auditor: Governance and optimization for the Agent Face.")
8
+ app = typer.Typer(help="Face Auditor: Scan frontend code for A2UI alignment.")
9
9
  console = Console()
10
10
 
11
- class UIFinding:
12
- def __init__(self, id: str, category: str, severity: str, message: str, file: str, suggestion: str):
13
- self.id = id
14
- self.category = category
15
- self.severity = severity
16
- self.message = message
17
- self.file = file
18
- self.suggestion = suggestion
11
+ @app.command()
12
+ def audit(path: str = "src"):
13
+ """
14
+ Step 4: Frontend / A2UI Auditing.
15
+ Ensures frontend components are properly mapping surfaceId and detecting triggers.
16
+ """
17
+ console.print(Panel.fit("🎭 [bold blue]FACE AUDITOR: A2UI COMPONENT SCAN[/bold blue]", border_style="blue"))
18
+ console.print(f"Scanning directory: [yellow]{path}[/yellow]")
19
19
 
20
- def audit_ui_best_practices(src_path: str):
21
- findings = []
22
-
23
- for root, dirs, files in os.walk(src_path):
24
- if "node_modules" in root or ".venv" in root: continue
25
-
26
- for file in files:
27
- path = os.path.join(root, file)
28
- rel_path = os.path.relpath(path, src_path)
20
+ files_scanned = 0
21
+ issues = []
22
+
23
+ # Heuristic Patterns
24
+ surface_id_pattern = re.compile(r"surfaceId|['\"]surface-id['\"]")
25
+ registry_pattern = re.compile(r"A2UIRegistry|registerComponent")
26
+ trigger_pattern = re.compile(r"onTrigger|handleTrigger|agentAction")
27
+ ux_feedback_pattern = re.compile(r"Skeleton|Loading|Spinner|Progress")
28
+ a11y_pattern = re.compile(r"aria-label|role=|tabIndex|alt=")
29
+ legal_pattern = re.compile(r"Copyright|PrivacyPolicy|Disclaimer|TermsOfService|©")
30
+ marketing_pattern = re.compile(r"og:image|meta\s+name=['\"]description['\"]|favicon|logo")
31
+
32
+ for root, dirs, files in os.walk(path):
33
+ if any(d in root for d in [".venv", "node_modules", ".git", "dist"]):
34
+ continue
29
35
 
30
- if file.endswith((".tsx", ".jsx", ".ts", ".js")):
36
+ for file in files:
37
+ if file.endswith((".tsx", ".ts", ".js", ".jsx")):
38
+ files_scanned += 1
39
+ file_path = os.path.join(root, file)
40
+ rel_path = os.path.relpath(file_path, ".")
31
41
  try:
32
- with open(path, "r", errors="ignore") as f:
33
- content = f.read()
42
+ with open(file_path, 'r') as f:
43
+ lines = f.readlines()
44
+ content = "".join(lines)
34
45
 
35
- # 1. A2UI Compliance: Missing surface IDs
36
- if "a2-surface" in content and "surfaceId" not in content:
37
- findings.append(UIFinding(
38
- "a2ui_surface_id", "A2UI Protocol", "HIGH",
39
- "Detected A2UI surface without unique surfaceId.",
40
- rel_path, "Add `surfaceId` for automated browser testing."
41
- ))
42
-
43
- # 2. Accessibility: Missing ARIA
44
- if "<button" in content.lower() and "aria-label" not in content.lower() and "children" not in content.lower():
45
- findings.append(UIFinding(
46
- "aria_label", "Accessibility", "MEDIUM",
47
- "Interactive button lacks description.",
48
- rel_path, "Add `aria-label` for screen readers."
49
- ))
46
+ findings = []
47
+
48
+ # Heuristic with Line Numbers
49
+ if not surface_id_pattern.search(content):
50
+ findings.append({"line": 1, "issue": "Missing 'surfaceId' mapping", "fix": "Add 'surfaceId' prop to the root component or exported interface."})
51
+
52
+ if not registry_pattern.search(content) and "Registry" in file:
53
+ findings.append({"line": 1, "issue": "Registry component without A2UIRegistry registration", "fix": "Wrap component in A2UIRegistry.registerComponent()."})
54
+
55
+ if "Button" in file and not trigger_pattern.search(content):
56
+ # Try to find the button line
57
+ line_no = 1
58
+ for i, line in enumerate(lines):
59
+ if "<button" in line.lower() or "<Button" in line:
60
+ line_no = i + 1
61
+ break
62
+ findings.append({"line": line_no, "issue": "Interactive component without Tool/Agent triggers", "fix": "Ensure the button calls an agent tool trigger (onTrigger)."})
50
63
 
51
- # 3. Optimization: Large Component Detection
52
- if len(content.splitlines()) > 300:
53
- findings.append(UIFinding(
54
- "large_component", "Refactor", "MEDIUM",
55
- f"Component file is very large ({len(content.splitlines())} lines).",
56
- rel_path, "Split into smaller sub-components for better performance."
57
- ))
64
+ if not ux_feedback_pattern.search(content) and ("Page" in file or "View" in file):
65
+ findings.append({"line": 1, "issue": "Missing 'Thinking' feedback (Skeleton/Spinner)", "fix": "Implement a Loading state or Skeleton component for agent latency."})
66
+
67
+ if not a11y_pattern.search(content) and ("Button" in file or "Input" in file):
68
+ line_no = 1
69
+ for i, line in enumerate(lines):
70
+ if "<button" in line.lower() or "<input" in line.lower():
71
+ line_no = i + 1
72
+ break
73
+ findings.append({"line": line_no, "issue": "Missing i18n/Accessibility labels (aria-label)", "fix": "Add aria-label or alt tags for screen readers and i18n."})
58
74
 
59
- # 4. Streamlit: Hardcoded Secrets
60
- if "st.secrets" not in content and (".env" in content or "API_KEY" in content):
61
- findings.append(UIFinding(
62
- "st_secrets", "Streamlit Security", "HIGH",
63
- "Detected likely hardcoded keys instead of st.secrets.",
64
- rel_path, "Move tokens to .streamlit/secrets.toml."
65
- ))
75
+ if not legal_pattern.search(content) and ("Page" in file or "Layout" in file or "Footer" in file):
76
+ findings.append({"line": 1, "issue": "Missing Legal Disclaimer or Privacy Policy link", "fix": "Add a footer link to the mandatory Privacy Policy / TOS."})
77
+
78
+ if not marketing_pattern.search(content) and ("index" in file.lower() or "head" in file.lower() or "App" in file):
79
+ findings.append({"line": 1, "issue": "Missing Branding (Logo) or SEO Metadata (OG/Description)", "fix": "Add meta tags (og:image, description) and project logo."})
66
80
 
67
- # 5. Angular: Reactive Pattern
68
- if "@Component" in content and "signal" not in content.lower() and "Observable" not in content:
69
- findings.append(UIFinding(
70
- "angular_reactivity", "Angular Performance", "MEDIUM",
71
- "Component lacks reactive patterns (Signals/Observables).",
72
- rel_path, "Use Signals for low-latency Agent output sync."
73
- ))
81
+ if findings:
82
+ issues.append({"file": rel_path, "findings": findings})
74
83
 
75
- except: continue
84
+ except Exception:
85
+ pass
76
86
 
77
- if file.endswith(".css"):
78
- try:
79
- with open(path, "r", errors="ignore") as f:
80
- content = f.read()
81
-
82
- # 4. Responsive: Missing Media Queries
83
- if "@media" not in content:
84
- findings.append(UIFinding(
85
- "missing_media_queries", "UX / Responsive", "HIGH",
86
- "No media queries detected in CSS file.",
87
- rel_path, "Implement mobile-first responsive design."
88
- ))
89
- except: continue
90
-
91
- return findings
87
+ console.print(f"📝 Scanned [bold]{files_scanned}[/bold] frontend files.")
92
88
 
93
- @app.command()
94
- def audit(path: str = typer.Argument("src", help="Path to the frontend source code")):
95
- """
96
- Runs a comprehensive UI/UX best practice audit on the codebase.
97
- """
98
- console.print(Panel.fit("🎨 [bold magenta]FACE AUDITOR: UI/UX GOVERNANCE[/bold magenta]", border_style="magenta"))
99
-
100
- findings = audit_ui_best_practices(path)
101
-
102
- table = Table(title="🎨 UI/UX Audit Results")
103
- table.add_column("Category", style="cyan")
104
- table.add_column("Severity", style="bold")
105
- table.add_column("Message", style="white")
106
- table.add_column("File", style="dim")
107
- table.add_column("Suggestion", style="green")
89
+ table = Table(title="🔍 A2UI Audit Findings")
90
+ table.add_column("File:Line", style="cyan")
91
+ table.add_column("Issue", style="red")
92
+ table.add_column("Recommended Fix", style="green")
108
93
 
109
- if findings:
110
- for f in findings:
111
- severity_style = "red" if f.severity == "HIGH" else "yellow"
112
- table.add_row(f.category, f"[{severity_style}]{f.severity}[/{severity_style}]", f.message, f.file, f.suggestion)
113
-
114
- console.print(table)
115
- console.print(f"\n⚠️ Found {len(findings)} UI/UX improvement opportunities.")
94
+ if not issues:
95
+ table.add_row("All Files", "[green]A2UI Ready[/green]", "No action needed.")
96
+ else:
97
+ # Structured output for Orchestrator parsing
98
+ console.print("\n[bold]🛠️ DEVELOPER ACTIONS REQUIRED:[/bold]")
99
+ for issue in issues:
100
+ for finding in issue["findings"]:
101
+ table.add_row(f"{issue['file']}:{finding['line']}", finding["issue"], finding["fix"])
102
+ # This line is for the orchestrator to parse easily
103
+ console.print(f"ACTION: {issue['file']}:{finding['line']} | {finding['issue']} | {finding['fix']}")
104
+
105
+ console.print("\n", table)
106
+
107
+
108
+ if len(issues) > 5:
109
+ console.print("\n⚠️ [yellow]Recommendation:[/yellow] Your 'Face' layer has fragmented A2UI surface mappings.")
110
+ console.print("💡 Use the A2UI Registry to unify how your agent logic triggers visual surfaces.")
116
111
  else:
117
- console.print("✅ [bold green]PASS:[/bold green] UI/UX architecture aligns with Agent Ops standards.")
112
+ console.print("\n✅ [bold green]Frontend is Well-Architected for GenUI interactions.[/bold green]")
118
113
 
119
114
  if __name__ == "__main__":
120
115
  app()
@@ -0,0 +1,138 @@
1
+ import json
2
+ import os
3
+ import urllib.request
4
+ import xml.etree.ElementTree as ET
5
+ import re
6
+ import sys
7
+ from datetime import datetime
8
+ from typing import Dict, Optional
9
+ import importlib.metadata
10
+ from packaging import version
11
+ from rich.console import Console
12
+ from rich.table import Table
13
+ from rich.panel import Panel
14
+
15
+ console = Console()
16
+
17
+ WATCHLIST_PATH = os.path.join(os.path.dirname(__file__), "watchlist.json")
18
+
19
+ def get_local_version(package_name: str) -> str:
20
+ try:
21
+ return importlib.metadata.version(package_name)
22
+ except importlib.metadata.PackageNotFoundError:
23
+ return "Not Installed"
24
+
25
+ def clean_version(v_str: str) -> str:
26
+ """Extracts a clean version number from strings like 'v1.2.3', 'package==1.2.3', '2026-01-28 (v0.1.0)'"""
27
+ # Look for patterns like X.Y.Z or X.Y
28
+ match = re.search(r'(\d+\.\d+(?:\.\d+)?(?:[a-zA-Z]+\d+)?)', v_str)
29
+ if match:
30
+ return match.group(1)
31
+ return v_str.strip().lstrip('v')
32
+
33
+ def fetch_latest_from_atom(url: str) -> Optional[Dict[str, str]]:
34
+ try:
35
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
36
+ with urllib.request.urlopen(req, timeout=10) as response:
37
+ tree = ET.parse(response)
38
+ root = tree.getroot()
39
+ ns = {'ns': 'http://www.w3.org/2005/Atom'}
40
+
41
+ latest_entry = root.find('ns:entry', ns)
42
+ if latest_entry is not None:
43
+ title = latest_entry.find('ns:title', ns).text
44
+ updated = latest_entry.find('ns:updated', ns).text
45
+ raw_v = title.strip().split()[-1]
46
+ return {
47
+ "version": clean_version(raw_v) if "==" not in raw_v else clean_version(raw_v.split("==")[-1]),
48
+ "date": updated,
49
+ "title": title
50
+ }
51
+ except Exception:
52
+ # console.print(f"[dim red]Error fetching {url}: {e}[/dim red]")
53
+ return None
54
+ return None
55
+
56
+ def run_watch():
57
+ console.print(Panel.fit("🔍 [bold blue]AGENTOPS COCKPIT: ECOSYSTEM WATCHER[/bold blue]", border_style="blue"))
58
+
59
+ if not os.path.exists(WATCHLIST_PATH):
60
+ console.print(f"❌ [red]Watchlist not found at {WATCHLIST_PATH}[/red]")
61
+ return
62
+
63
+ with open(WATCHLIST_PATH, 'r') as f:
64
+ watchlist = json.load(f)
65
+
66
+ table = Table(title=f"Ecosystem Pulse - {datetime.now().strftime('%Y-%m-%d')}", show_header=True, header_style="bold magenta")
67
+ table.add_column("Category", style="cyan")
68
+ table.add_column("Component", style="white")
69
+ table.add_column("Local", style="yellow")
70
+ table.add_column("Latest", style="green")
71
+ table.add_column("Status", justify="center")
72
+
73
+ updates_found = []
74
+
75
+ for category, items in watchlist.items():
76
+ for name, info in items.items():
77
+ package = info.get("package")
78
+ local_v_raw = get_local_version(package) if package else None
79
+ local_v = local_v_raw if local_v_raw else "N/A"
80
+
81
+ with console.status(f"[dim]Checking {name}..."):
82
+ latest_info = fetch_latest_from_atom(info["feed"])
83
+
84
+ if latest_info:
85
+ latest_v = latest_info["version"]
86
+
87
+ is_outdated = False
88
+ if local_v_raw and local_v_raw != "Not Installed":
89
+ try:
90
+ is_outdated = version.parse(latest_v) > version.parse(local_v_raw)
91
+ except Exception:
92
+ is_outdated = latest_v > local_v_raw
93
+
94
+ status = "🚨 [bold red]UPDATE[/bold red]" if is_outdated else "✅ [green]OK[/green]"
95
+ if local_v == "Not Installed":
96
+ status = "➕ [dim]NEW[/dim]"
97
+ if package is None:
98
+ status = "🌐 [blue]SPEC[/blue]"
99
+
100
+ display_local = local_v if local_v != "Not Installed" else "[dim]Not Installed[/dim]"
101
+ table.add_row(
102
+ category.upper(),
103
+ name,
104
+ display_local,
105
+ latest_v,
106
+ status
107
+ )
108
+
109
+ if is_outdated:
110
+ updates_found.append({
111
+ "name": name,
112
+ "current": local_v,
113
+ "latest": latest_v,
114
+ "package": package,
115
+ "desc": info["description"]
116
+ })
117
+ else:
118
+ table.add_row(category.upper(), name, local_v, "[red]Fetch Failed[/red]", "❓")
119
+
120
+ console.print(table)
121
+
122
+ if updates_found:
123
+ console.print("\n[bold yellow]⚠️ Actionable Intelligence:[/bold yellow]")
124
+ for up in updates_found:
125
+ console.print(f"• [bold]{up['name']}[/bold]: {up['current']} ➔ [bold green]{up['latest']}[/bold green]")
126
+ console.print(f" [dim]{up['desc']}[/dim]")
127
+
128
+ pkgs = " ".join([u['package'] for u in updates_found if u['package']])
129
+ if pkgs:
130
+ console.print(f"\n[bold cyan]Pro-tip:[/bold cyan] Run `pip install --upgrade {pkgs}` to sync.")
131
+
132
+ # Exit with special code to signal updates to CI/CD
133
+ sys.exit(2)
134
+ else:
135
+ console.print("\n[bold green]✨ All components are currently in sync with the latest stable releases.[/bold green]")
136
+
137
+ if __name__ == "__main__":
138
+ run_watch()
@@ -0,0 +1,88 @@
1
+ {
2
+ "orchestration_frameworks": {
3
+ "google-adk": {
4
+ "feed": "https://github.com/google/adk-python/releases.atom",
5
+ "package": "google-adk",
6
+ "description": "Agent Development Kit - Core reasoning & tool orchestration",
7
+ "min_version_for_optimizations": "0.15.0"
8
+ },
9
+ "crewai": {
10
+ "feed": "https://github.com/crewAIInc/crewAI/releases.atom",
11
+ "package": "crewai",
12
+ "description": "CrewAI - Role-playing multi-agent systems",
13
+ "min_version_for_optimizations": "1.0.0"
14
+ },
15
+ "langgraph": {
16
+ "feed": "https://github.com/langchain-ai/langgraph/releases.atom",
17
+ "package": "langgraph",
18
+ "description": "LangGraph - Stateful multi-agent orchestration",
19
+ "min_version_for_optimizations": "0.1.0"
20
+ },
21
+ "autogen": {
22
+ "feed": "https://github.com/microsoft/autogen/releases.atom",
23
+ "package": "pyautogen",
24
+ "description": "Microsoft AutoGen - Conversational agent framework",
25
+ "min_version_for_optimizations": "0.4.0"
26
+ },
27
+ "openai-agents": {
28
+ "feed": "https://github.com/openai/openai-agents-python/releases.atom",
29
+ "package": "openai-agents",
30
+ "description": "OpenAI Agents SDK - Multi-agent workflows",
31
+ "min_version_for_optimizations": "0.5.0"
32
+ },
33
+ "copilotkit": {
34
+ "feed": "https://github.com/CopilotKit/CopilotKit/releases.atom",
35
+ "package": "@copilotkit/react-core",
36
+ "description": "In-app AI Copilots & UX components",
37
+ "min_version_for_optimizations": "1.20.0"
38
+ }
39
+ },
40
+ "platform_services": {
41
+ "agent-engine": {
42
+ "feed": "https://github.com/googleapis/python-aiplatform/releases.atom",
43
+ "package": "google-cloud-aiplatform",
44
+ "description": "Vertex AI Agent Engine - Managed agent execution",
45
+ "min_version_for_optimizations": "1.70.0"
46
+ }
47
+ },
48
+ "compatibility_rules": [
49
+ {
50
+ "component": "langgraph",
51
+ "works_well_with": [
52
+ "google-adk",
53
+ "openai-agents"
54
+ ],
55
+ "incompatible_with": [
56
+ "crewai"
57
+ ],
58
+ "reason": "CrewAI and LangGraph both attempt to manage the orchestration loop and state, leading to cyclic-dependency conflicts."
59
+ },
60
+ {
61
+ "component": "google-adk",
62
+ "works_well_with": [
63
+ "langgraph",
64
+ "mcp"
65
+ ],
66
+ "incompatible_with": [
67
+ "pyautogen"
68
+ ],
69
+ "reason": "AutoGen's conversational loop pattern conflicts with ADK's strictly typed tool orchestration."
70
+ },
71
+ {
72
+ "component": "mcp",
73
+ "works_well_with": [
74
+ "google-adk",
75
+ "langgraph",
76
+ "openai-agents"
77
+ ],
78
+ "incompatible_with": [],
79
+ "reason": "MCP is a universal standard and thrives in multi-framework environments."
80
+ }
81
+ ],
82
+ "research_sources": {
83
+ "well_architected": "https://cloud.google.com/architecture/framework",
84
+ "security_best_practices": "https://cloud.google.com/architecture/framework/security",
85
+ "cost_optimization": "https://cloud.google.com/architecture/framework/cost-optimization",
86
+ "operational_excellence": "https://cloud.google.com/architecture/framework/operational-excellence"
87
+ }
88
+ }