Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:26:08 +08:00
commit 8f22ddf339
295 changed files with 59710 additions and 0 deletions

View File

@@ -0,0 +1 @@
# Auto-generated package initializer for skills.

View File

@@ -0,0 +1,329 @@
#!/usr/bin/env python3
"""
agent_compose.py - Recommend skills for Betty agents based on purpose
Analyzes skill artifact metadata to suggest compatible skill combinations.
"""
import os
import sys
import json
import yaml
from typing import Dict, Any, List, Optional, Set
from pathlib import Path
from betty.config import BASE_DIR
from betty.logging_utils import setup_logger
logger = setup_logger(__name__)
def load_registry() -> Dict[str, Any]:
"""Load skills registry."""
registry_path = os.path.join(BASE_DIR, "registry", "skills.json")
with open(registry_path) as f:
return json.load(f)
def extract_artifact_metadata(skill: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract artifact metadata from a skill.
Returns:
Dict with 'produces' and 'consumes' sets
"""
metadata = skill.get("artifact_metadata", {})
return {
"produces": set(a.get("type") for a in metadata.get("produces", [])),
"consumes": set(a.get("type") for a in metadata.get("consumes", []))
}
def find_skills_by_artifacts(
registry: Dict[str, Any],
produces: Optional[List[str]] = None,
consumes: Optional[List[str]] = None
) -> List[Dict[str, Any]]:
"""
Find skills that produce or consume specific artifacts.
Args:
registry: Skills registry
produces: Artifact types to produce
consumes: Artifact types to consume
Returns:
List of matching skills with metadata
"""
skills = registry.get("skills", [])
matches = []
for skill in skills:
if skill.get("status") != "active":
continue
artifacts = extract_artifact_metadata(skill)
# Check if skill produces required artifacts
produces_match = not produces or any(
artifact in artifacts["produces"] for artifact in produces
)
# Check if skill consumes specified artifacts
consumes_match = not consumes or any(
artifact in artifacts["consumes"] for artifact in consumes
)
if produces_match or consumes_match:
matches.append({
"name": skill["name"],
"description": skill.get("description", ""),
"produces": list(artifacts["produces"]),
"consumes": list(artifacts["consumes"]),
"tags": skill.get("tags", [])
})
return matches
def find_skills_for_purpose(
registry: Dict[str, Any],
purpose: str,
required_artifacts: Optional[List[str]] = None
) -> Dict[str, Any]:
"""
Find skills for agent purpose (alias for recommend_skills_for_purpose).
Args:
registry: Skills registry (for compatibility, currently unused)
purpose: Description of agent purpose
required_artifacts: Artifact types agent needs to work with
Returns:
Recommendation result with skills and rationale
"""
return recommend_skills_for_purpose(purpose, required_artifacts)
def recommend_skills_for_purpose(
agent_purpose: str,
required_artifacts: Optional[List[str]] = None
) -> Dict[str, Any]:
"""
Recommend skills based on agent purpose and required artifacts.
Args:
agent_purpose: Description of agent purpose
required_artifacts: Artifact types agent needs to work with
Returns:
Recommendation result with skills and rationale
"""
registry = load_registry()
recommended = []
rationale = {}
# Keyword matching for purpose
purpose_lower = agent_purpose.lower()
keywords = {
"api": ["api.define", "api.validate", "api.generate-models", "api.compatibility"],
"workflow": ["workflow.validate", "workflow.compose"],
"hook": ["hook.define"],
"validate": ["api.validate", "workflow.validate"],
"design": ["api.define"],
}
# Find skills by keywords
matched_by_keyword = set()
for keyword, skill_names in keywords.items():
if keyword in purpose_lower:
matched_by_keyword.update(skill_names)
# Find skills by required artifacts
matched_by_artifacts = set()
if required_artifacts:
artifact_skills = find_skills_by_artifacts(
registry,
produces=required_artifacts,
consumes=required_artifacts
)
matched_by_artifacts.update(s["name"] for s in artifact_skills)
# Combine matches
all_matches = matched_by_keyword | matched_by_artifacts
# Build recommendation with rationale
skills = registry.get("skills", [])
for skill in skills:
skill_name = skill.get("name")
if skill_name in all_matches:
reasons = []
if skill_name in matched_by_keyword:
reasons.append(f"Purpose matches skill capabilities")
artifacts = extract_artifact_metadata(skill)
if required_artifacts:
produces_match = artifacts["produces"] & set(required_artifacts)
consumes_match = artifacts["consumes"] & set(required_artifacts)
if produces_match:
reasons.append(f"Produces: {', '.join(produces_match)}")
if consumes_match:
reasons.append(f"Consumes: {', '.join(consumes_match)}")
recommended.append(skill_name)
rationale[skill_name] = {
"description": skill.get("description", ""),
"reasons": reasons,
"produces": list(artifacts["produces"]),
"consumes": list(artifacts["consumes"])
}
return {
"recommended_skills": recommended,
"rationale": rationale,
"total_recommended": len(recommended)
}
def analyze_artifact_flow(skills_metadata: List[Dict[str, Any]]) -> Dict[str, Any]:
"""
Analyze artifact flow between recommended skills.
Args:
skills_metadata: List of skill metadata
Returns:
Flow analysis showing how artifacts move between skills
"""
all_produces = set()
all_consumes = set()
flows = []
for skill in skills_metadata:
produces = set(skill.get("produces", []))
consumes = set(skill.get("consumes", []))
all_produces.update(produces)
all_consumes.update(consumes)
for artifact in produces:
consumers = [
s["name"] for s in skills_metadata
if artifact in s.get("consumes", [])
]
if consumers:
flows.append({
"artifact": artifact,
"producer": skill["name"],
"consumers": consumers
})
# Find gaps (consumed but not produced)
gaps = all_consumes - all_produces
return {
"flows": flows,
"gaps": list(gaps),
"fully_covered": len(gaps) == 0
}
def main():
"""CLI entry point."""
import argparse
parser = argparse.ArgumentParser(
description="Recommend skills for a Betty agent"
)
parser.add_argument(
"agent_purpose",
help="Description of what the agent should do"
)
parser.add_argument(
"--required-artifacts",
nargs="+",
help="Artifact types the agent needs to work with"
)
parser.add_argument(
"--output-format",
choices=["yaml", "json", "markdown"],
default="yaml",
help="Output format"
)
args = parser.parse_args()
logger.info(f"Finding skills for agent purpose: {args.agent_purpose}")
try:
# Get recommendations
result = recommend_skills_for_purpose(
args.agent_purpose,
args.required_artifacts
)
# Analyze artifact flow
skills_metadata = list(result["rationale"].values())
for skill_name, metadata in result["rationale"].items():
metadata["name"] = skill_name
flow_analysis = analyze_artifact_flow(skills_metadata)
result["artifact_flow"] = flow_analysis
# Format output
if args.output_format == "yaml":
print("\n# Recommended Skills for Agent\n")
print(f"# Purpose: {args.agent_purpose}\n")
print("skills_available:")
for skill in result["recommended_skills"]:
print(f" - {skill}")
print("\n# Rationale:")
for skill_name, rationale in result["rationale"].items():
print(f"\n# {skill_name}:")
print(f"# {rationale['description']}")
for reason in rationale["reasons"]:
print(f"# - {reason}")
elif args.output_format == "markdown":
print(f"\n## Recommended Skills for: {args.agent_purpose}\n")
print("### Skills\n")
for skill in result["recommended_skills"]:
rationale = result["rationale"][skill]
print(f"**{skill}**")
print(f"- {rationale['description']}")
for reason in rationale["reasons"]:
print(f" - {reason}")
print()
else: # json
print(json.dumps(result, indent=2))
# Show warnings for gaps
if flow_analysis["gaps"]:
logger.warning(f"\n⚠️ Artifact gaps detected:")
for gap in flow_analysis["gaps"]:
logger.warning(f" - '{gap}' is consumed but not produced")
logger.warning(" Consider adding skills that produce these artifacts")
logger.info(f"\n✅ Recommended {result['total_recommended']} skills")
sys.exit(0)
except Exception as e:
logger.error(f"Failed to compose agent: {e}")
result = {
"ok": False,
"status": "failed",
"error": str(e)
}
print(json.dumps(result, indent=2))
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,102 @@
name: agent.compose
version: 0.1.0
description: >
Recommend skills for a Betty agent based on its purpose and responsibilities.
Analyzes artifact flows, ensures skill compatibility, and suggests optimal
skill combinations for agent definitions.
inputs:
- name: agent_purpose
type: string
required: true
description: Description of what the agent should do (e.g., "Design and validate APIs")
- name: required_artifacts
type: array
required: false
description: Artifact types the agent needs to work with (e.g., ["openapi-spec"])
- name: output_format
type: string
required: false
default: yaml
description: Output format (yaml, json, or markdown)
- name: include_rationale
type: boolean
required: false
default: true
description: Include explanation of why each skill was recommended
outputs:
- name: recommended_skills
type: array
description: List of recommended skill names
- name: skills_with_rationale
type: object
description: Skills with explanation of why they were recommended
- name: artifact_flow
type: object
description: Diagram showing how artifacts flow between recommended skills
- name: compatibility_report
type: object
description: Validation that recommended skills work together
dependencies:
- registry.query
entrypoints:
- command: /agent/compose
handler: agent_compose.py
runtime: python
description: >
Recommend skills for an agent based on its purpose. Analyzes the registry
to find skills that produce/consume compatible artifacts, ensures no gaps
in artifact flow, and suggests optimal skill combinations.
parameters:
- name: agent_purpose
type: string
required: true
description: What the agent should do
- name: required_artifacts
type: array
required: false
description: Artifact types to work with
- name: output_format
type: string
required: false
default: yaml
description: Output format (yaml, json, markdown)
- name: include_rationale
type: boolean
required: false
default: true
description: Include explanations
permissions:
- filesystem:read
status: active
tags:
- agents
- composition
- artifacts
- scaffolding
- interoperability
- layer3
# This skill's own artifact metadata
artifact_metadata:
produces:
- type: agent-skill-recommendation
description: Recommended skills list with compatibility analysis for agent definitions
file_pattern: "agent-skills-recommendation.{yaml,json}"
content_type: application/yaml
consumes:
- type: registry-data
description: Betty Framework registry containing skills and their artifact metadata
required: true