Files
gh-epieczko-betty/agents/meta.suggest/meta_suggest.py
2025-11-29 18:26:08 +08:00

372 lines
13 KiB
Python
Executable File

#!/usr/bin/env python3
"""
meta.suggest - Context-Aware Next-Step Recommender
Helps Claude decide what to do next after an agent completes by analyzing
context and suggesting compatible next steps.
"""
import json
import yaml
import sys
import os
from pathlib import Path
from typing import Dict, List, Any, Optional, Set
from datetime import datetime
# Add parent directory to path for imports
parent_dir = str(Path(__file__).parent.parent.parent)
sys.path.insert(0, parent_dir)
# Import meta.compatibility
meta_comp_path = parent_dir + "/agents/meta.compatibility"
sys.path.insert(0, meta_comp_path)
import meta_compatibility
class SuggestionEngine:
"""Context-aware suggestion engine"""
def __init__(self, base_dir: str = "."):
"""Initialize with base directory"""
self.base_dir = Path(base_dir)
self.compatibility_analyzer = meta_compatibility.CompatibilityAnalyzer(base_dir)
self.compatibility_analyzer.scan_agents()
self.compatibility_analyzer.build_compatibility_map()
def suggest_next_steps(
self,
context_agent: str,
artifacts_produced: Optional[List[str]] = None,
goal: Optional[str] = None
) -> Dict[str, Any]:
"""
Suggest next steps based on context
Args:
context_agent: Agent that just ran
artifacts_produced: List of artifact file paths produced
goal: Optional user goal
Returns:
Suggestion report with recommendations
"""
# Get compatibility info for the agent
compatibility = self.compatibility_analyzer.find_compatible(context_agent)
if "error" in compatibility:
return {
"error": compatibility["error"],
"context": {
"agent": context_agent,
"artifacts": artifacts_produced or []
}
}
# Determine artifact types produced
artifact_types = set()
if artifacts_produced:
artifact_types = self._infer_artifact_types(artifacts_produced)
else:
artifact_types = set(compatibility.get("produces", []))
suggestions = []
# Suggestion 1: Validate/analyze what was created
if context_agent not in ["meta.compatibility", "meta.suggest"]:
suggestions.append({
"action": "Analyze compatibility",
"agent": "meta.compatibility",
"command": f"python3 agents/meta.compatibility/meta_compatibility.py analyze {context_agent}",
"rationale": f"Understand what agents can work with {context_agent}'s outputs",
"artifacts_needed": [],
"produces": ["compatibility-graph"],
"priority": "medium",
"estimated_duration": "< 1 minute"
})
# Suggestion 2: Use compatible agents
can_feed_to = compatibility.get("can_feed_to", [])
for compatible in can_feed_to[:3]: # Top 3
next_agent = compatible["agent"]
artifact = compatible["artifact"]
suggestions.append({
"action": f"Process with {next_agent}",
"agent": next_agent,
"rationale": compatible["rationale"],
"artifacts_needed": [artifact],
"produces": self._get_agent_produces(next_agent),
"priority": "high",
"estimated_duration": "varies"
})
# Suggestion 3: If agent created something, suggest testing/validation
if artifact_types and context_agent in ["meta.agent", "meta.artifact"]:
suggestions.append({
"action": "Test the created artifact",
"rationale": "Verify the artifact works as expected",
"artifacts_needed": list(artifact_types),
"priority": "high",
"manual": True
})
# Suggestion 4: If gaps exist, suggest filling them
gaps = compatibility.get("gaps", [])
if gaps:
for gap in gaps[:2]: # Top 2 gaps
suggestions.append({
"action": f"Create producer for '{gap['artifact']}'",
"rationale": gap["issue"],
"severity": gap.get("severity", "medium"),
"priority": "low",
"manual": True
})
# Rank suggestions
suggestions = self._rank_suggestions(suggestions, goal)
# Build report
report = {
"context": {
"agent": context_agent,
"artifacts_produced": artifacts_produced or [],
"artifact_types": list(artifact_types),
"timestamp": datetime.now().isoformat()
},
"suggestions": suggestions,
"primary_suggestion": suggestions[0] if suggestions else None,
"alternatives": suggestions[1:4] if len(suggestions) > 1 else [],
"warnings": self._generate_warnings(context_agent, compatibility, gaps)
}
return report
def _infer_artifact_types(self, artifact_paths: List[str]) -> Set[str]:
"""Infer artifact types from file paths"""
types = set()
for path in artifact_paths:
path_lower = path.lower()
# Pattern matching
if ".openapi." in path_lower:
types.add("openapi-spec")
elif "agent.yaml" in path_lower:
types.add("agent-definition")
elif "readme.md" in path_lower:
if "agent" in path_lower:
types.add("agent-documentation")
elif ".validation." in path_lower:
types.add("validation-report")
elif ".optimization." in path_lower:
types.add("optimization-report")
elif ".compatibility." in path_lower:
types.add("compatibility-graph")
elif ".pipeline." in path_lower:
types.add("pipeline-suggestion")
elif ".workflow." in path_lower:
types.add("workflow-definition")
return types
def _get_agent_produces(self, agent_name: str) -> List[str]:
"""Get what an agent produces"""
if agent_name in self.compatibility_analyzer.agents:
agent_def = self.compatibility_analyzer.agents[agent_name]
produces, _ = self.compatibility_analyzer.extract_artifacts(agent_def)
return list(produces)
return []
def _rank_suggestions(self, suggestions: List[Dict], goal: Optional[str] = None) -> List[Dict]:
"""Rank suggestions by relevance"""
priority_order = {"high": 3, "medium": 2, "low": 1}
# Sort by priority, then by manual (auto first)
return sorted(
suggestions,
key=lambda s: (
-priority_order.get(s.get("priority", "medium"), 2),
s.get("manual", False) # Auto suggestions first
)
)
def _generate_warnings(
self,
agent: str,
compatibility: Dict,
gaps: List[Dict]
) -> List[Dict]:
"""Generate warnings based on context"""
warnings = []
# Warn about gaps
if gaps:
warnings.append({
"type": "gaps",
"message": f"{agent} requires artifacts that aren't produced by any agent",
"details": [g["artifact"] for g in gaps],
"severity": "medium"
})
# Warn if no compatible agents
if not compatibility.get("can_feed_to") and not compatibility.get("can_receive_from"):
warnings.append({
"type": "isolated",
"message": f"{agent} has no compatible agents",
"details": "This agent can't be used in multi-agent pipelines",
"severity": "low"
})
return warnings
def analyze_project(self) -> Dict[str, Any]:
"""Analyze entire project and suggest improvements"""
# Generate compatibility graph
graph = self.compatibility_analyzer.generate_compatibility_graph()
suggestions = []
# Suggest filling gaps
for gap in graph.get("gaps", []):
suggestions.append({
"action": f"Create agent/skill to produce '{gap['artifact']}'",
"rationale": gap["issue"],
"priority": "medium",
"impact": f"Enables {len(gap.get('consumers', []))} agents"
})
# Suggest creating more agents if few exist
if graph["metadata"]["total_agents"] < 5:
suggestions.append({
"action": "Create more agents using meta.agent",
"rationale": "Expand agent ecosystem for more capabilities",
"priority": "low"
})
# Suggest documentation if gaps exist
if graph.get("gaps"):
suggestions.append({
"action": "Document artifact standards for gaps",
"rationale": "Clarify requirements for missing artifacts",
"priority": "low"
})
return {
"project_analysis": {
"total_agents": graph["metadata"]["total_agents"],
"total_artifacts": graph["metadata"]["total_artifact_types"],
"total_relationships": len(graph["relationships"]),
"total_gaps": len(graph["gaps"])
},
"suggestions": suggestions,
"gaps": graph["gaps"],
"timestamp": datetime.now().isoformat()
}
def main():
"""CLI entry point"""
import argparse
parser = argparse.ArgumentParser(
description="meta.suggest - Context-Aware Next-Step Recommender"
)
parser.add_argument(
"--context",
help="Agent that just ran"
)
parser.add_argument(
"--artifacts",
nargs="+",
help="Artifacts that were produced"
)
parser.add_argument(
"--goal",
help="User's goal (for better suggestions)"
)
parser.add_argument(
"--analyze-project",
action="store_true",
help="Analyze entire project and suggest improvements"
)
parser.add_argument(
"--format",
choices=["json", "text"],
default="text",
help="Output format"
)
args = parser.parse_args()
engine = SuggestionEngine()
if args.analyze_project:
print("🔍 Analyzing project...\n")
result = engine.analyze_project()
if args.format == "text":
print(f"📊 Project Analysis:")
print(f" Total Agents: {result['project_analysis']['total_agents']}")
print(f" Total Artifacts: {result['project_analysis']['total_artifacts']}")
print(f" Relationships: {result['project_analysis']['total_relationships']}")
print(f" Gaps: {result['project_analysis']['total_gaps']}")
if result.get("suggestions"):
print(f"\n💡 Suggestions ({len(result['suggestions'])}):")
for i, suggestion in enumerate(result["suggestions"], 1):
print(f"\n {i}. {suggestion['action']}")
print(f" {suggestion['rationale']}")
print(f" Priority: {suggestion['priority']}")
else:
print(json.dumps(result, indent=2))
elif args.context:
print(f"💡 Suggesting next steps after '{args.context}'...\n")
result = engine.suggest_next_steps(
args.context,
args.artifacts,
args.goal
)
if args.format == "text":
if "error" in result:
print(f"❌ Error: {result['error']}")
return
print(f"Context: {result['context']['agent']}")
if result['context']['artifact_types']:
print(f"Produced: {', '.join(result['context']['artifact_types'])}")
if result.get("primary_suggestion"):
print(f"\n🌟 Primary Suggestion:")
ps = result["primary_suggestion"]
print(f" {ps['action']}")
print(f" Rationale: {ps['rationale']}")
if not ps.get("manual"):
print(f" Command: {ps.get('command', 'N/A')}")
print(f" Priority: {ps['priority']}")
if result.get("alternatives"):
print(f"\n🔄 Alternatives:")
for i, alt in enumerate(result["alternatives"], 1):
print(f"\n {i}. {alt['action']}")
print(f" {alt['rationale']}")
if result.get("warnings"):
print(f"\n⚠️ Warnings:")
for warning in result["warnings"]:
print(f"{warning['message']}")
else:
print(json.dumps(result, indent=2))
else:
parser.print_help()
if __name__ == "__main__":
main()