Initial commit
This commit is contained in:
756
skills/agent.run/agent_run.py
Normal file
756
skills/agent.run/agent_run.py
Normal file
@@ -0,0 +1,756 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
agent_run.py – Implementation of the agent.run Skill
|
||||
|
||||
Executes a registered Betty agent by loading its manifest, constructing a Claude-friendly
|
||||
prompt, invoking the Claude API (or simulating), and logging execution results.
|
||||
|
||||
This skill supports both iterative and oneshot reasoning modes and can execute
|
||||
skills based on the agent's workflow pattern.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import json
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
from betty.config import (
|
||||
AGENTS_DIR, AGENTS_REGISTRY_FILE, REGISTRY_FILE,
|
||||
get_agent_manifest_path, get_skill_manifest_path,
|
||||
BETTY_HOME
|
||||
)
|
||||
from betty.validation import validate_path
|
||||
from betty.logging_utils import setup_logger
|
||||
from betty.errors import BettyError, format_error_response
|
||||
from betty.telemetry_capture import capture_skill_execution, capture_audit_entry
|
||||
from utils.telemetry_utils import capture_telemetry
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
# Agent logs directory
|
||||
AGENT_LOGS_DIR = os.path.join(BETTY_HOME, "agent_logs")
|
||||
|
||||
|
||||
def build_response(
|
||||
ok: bool,
|
||||
errors: Optional[List[str]] = None,
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Build standardized response.
|
||||
|
||||
Args:
|
||||
ok: Whether the operation was successful
|
||||
errors: List of error messages
|
||||
details: Additional details to include
|
||||
|
||||
Returns:
|
||||
Standardized response dictionary
|
||||
"""
|
||||
response: Dict[str, Any] = {
|
||||
"ok": ok,
|
||||
"status": "success" if ok else "failed",
|
||||
"errors": errors or [],
|
||||
"timestamp": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
if details is not None:
|
||||
response["details"] = details
|
||||
return response
|
||||
|
||||
|
||||
def load_agent_manifest(agent_path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Load agent manifest from path or agent name.
|
||||
|
||||
Args:
|
||||
agent_path: Path to agent.yaml or agent name (e.g., api.designer)
|
||||
|
||||
Returns:
|
||||
Agent manifest dictionary
|
||||
|
||||
Raises:
|
||||
BettyError: If agent cannot be loaded or is invalid
|
||||
"""
|
||||
# Check if it's a direct path to agent.yaml
|
||||
if os.path.exists(agent_path) and agent_path.endswith('.yaml'):
|
||||
manifest_path = agent_path
|
||||
# Check if it's an agent name
|
||||
else:
|
||||
manifest_path = get_agent_manifest_path(agent_path)
|
||||
if not os.path.exists(manifest_path):
|
||||
raise BettyError(
|
||||
f"Agent not found: {agent_path}",
|
||||
details={
|
||||
"agent_path": agent_path,
|
||||
"expected_path": manifest_path,
|
||||
"suggestion": "Use 'betty agent list' to see available agents"
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
with open(manifest_path) as f:
|
||||
manifest = yaml.safe_load(f)
|
||||
|
||||
if not isinstance(manifest, dict):
|
||||
raise BettyError("Agent manifest must be a dictionary")
|
||||
|
||||
# Validate required fields
|
||||
required_fields = ["name", "version", "description", "capabilities",
|
||||
"skills_available", "reasoning_mode"]
|
||||
missing = [f for f in required_fields if f not in manifest]
|
||||
if missing:
|
||||
raise BettyError(
|
||||
f"Agent manifest missing required fields: {', '.join(missing)}",
|
||||
details={"missing_fields": missing}
|
||||
)
|
||||
|
||||
return manifest
|
||||
except yaml.YAMLError as e:
|
||||
raise BettyError(f"Invalid YAML in agent manifest: {e}")
|
||||
|
||||
|
||||
def load_skill_registry() -> Dict[str, Any]:
|
||||
"""
|
||||
Load the skills registry.
|
||||
|
||||
Returns:
|
||||
Skills registry dictionary
|
||||
"""
|
||||
try:
|
||||
with open(REGISTRY_FILE) as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
logger.warning(f"Skills registry not found: {REGISTRY_FILE}")
|
||||
return {"skills": []}
|
||||
except json.JSONDecodeError as e:
|
||||
raise BettyError(f"Invalid JSON in skills registry: {e}")
|
||||
|
||||
|
||||
def get_skill_info(skill_name: str, registry: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Get skill information from registry.
|
||||
|
||||
Args:
|
||||
skill_name: Name of the skill
|
||||
registry: Skills registry
|
||||
|
||||
Returns:
|
||||
Skill info dictionary or None if not found
|
||||
"""
|
||||
for skill in registry.get("skills", []):
|
||||
if skill.get("name") == skill_name:
|
||||
return skill
|
||||
return None
|
||||
|
||||
|
||||
def construct_agent_prompt(
|
||||
agent_manifest: Dict[str, Any],
|
||||
task_context: Optional[str] = None
|
||||
) -> str:
|
||||
"""
|
||||
Construct a Claude-friendly prompt for the agent.
|
||||
|
||||
Args:
|
||||
agent_manifest: Agent manifest dictionary
|
||||
task_context: User-provided task or query
|
||||
|
||||
Returns:
|
||||
Constructed system prompt string suitable for Claude API
|
||||
"""
|
||||
agent_name = agent_manifest.get("name", "unknown")
|
||||
description = agent_manifest.get("description", "")
|
||||
capabilities = agent_manifest.get("capabilities", [])
|
||||
skills_available = agent_manifest.get("skills_available", [])
|
||||
reasoning_mode = agent_manifest.get("reasoning_mode", "oneshot")
|
||||
workflow_pattern = agent_manifest.get("workflow_pattern", "")
|
||||
context_requirements = agent_manifest.get("context_requirements", {})
|
||||
|
||||
# Build system prompt
|
||||
prompt = f"""You are {agent_name}, a specialized Betty Framework agent.
|
||||
|
||||
## AGENT DESCRIPTION
|
||||
{description}
|
||||
|
||||
## CAPABILITIES
|
||||
You have the following capabilities:
|
||||
"""
|
||||
for cap in capabilities:
|
||||
prompt += f" • {cap}\n"
|
||||
|
||||
prompt += f"""
|
||||
## REASONING MODE
|
||||
{reasoning_mode.upper()}: """
|
||||
|
||||
if reasoning_mode == "iterative":
|
||||
prompt += """You will analyze results from each skill invocation and determine
|
||||
the next steps dynamically. You may retry failed operations or adjust your
|
||||
approach based on feedback."""
|
||||
else:
|
||||
prompt += """You will plan and execute all necessary skills in a single pass.
|
||||
Analyze the task completely before determining the sequence of skill invocations."""
|
||||
|
||||
prompt += """
|
||||
|
||||
## AVAILABLE SKILLS
|
||||
You have access to the following Betty skills:
|
||||
"""
|
||||
for skill in skills_available:
|
||||
prompt += f" • {skill}\n"
|
||||
|
||||
if workflow_pattern:
|
||||
prompt += f"""
|
||||
## RECOMMENDED WORKFLOW
|
||||
{workflow_pattern}
|
||||
"""
|
||||
|
||||
if context_requirements:
|
||||
prompt += """
|
||||
## CONTEXT REQUIREMENTS
|
||||
The following context may be required for optimal performance:
|
||||
"""
|
||||
for key, value_type in context_requirements.items():
|
||||
prompt += f" • {key}: {value_type}\n"
|
||||
|
||||
if task_context:
|
||||
prompt += f"""
|
||||
## TASK
|
||||
{task_context}
|
||||
|
||||
## INSTRUCTIONS
|
||||
Analyze the task above and respond with a JSON object describing your execution plan:
|
||||
|
||||
{{
|
||||
"analysis": "Brief analysis of the task",
|
||||
"skills_to_invoke": [
|
||||
{{
|
||||
"skill": "skill.name",
|
||||
"purpose": "Why this skill is needed",
|
||||
"inputs": {{"param": "value"}},
|
||||
"order": 1
|
||||
}}
|
||||
],
|
||||
"reasoning": "Explanation of your approach"
|
||||
}}
|
||||
|
||||
Select skills from your available skills list and arrange them according to the
|
||||
workflow pattern. Ensure the sequence makes logical sense for accomplishing the task.
|
||||
"""
|
||||
else:
|
||||
prompt += """
|
||||
## READY STATE
|
||||
You are initialized and ready to accept tasks. When given a task, you will:
|
||||
1. Analyze the requirements
|
||||
2. Select appropriate skills from your available skills
|
||||
3. Determine the execution order based on your workflow pattern
|
||||
4. Provide a structured execution plan
|
||||
"""
|
||||
|
||||
return prompt
|
||||
|
||||
|
||||
def call_claude_api(prompt: str, agent_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Call the Claude API with the constructed prompt.
|
||||
|
||||
Currently simulates the API call. In production, this would:
|
||||
1. Use the Anthropic API client
|
||||
2. Send the prompt with appropriate parameters
|
||||
3. Parse the structured response
|
||||
|
||||
Args:
|
||||
prompt: The constructed system prompt
|
||||
agent_name: Name of the agent (for context)
|
||||
|
||||
Returns:
|
||||
Claude's response (currently mocked)
|
||||
"""
|
||||
# Check if we have ANTHROPIC_API_KEY in environment
|
||||
api_key = os.environ.get("ANTHROPIC_API_KEY")
|
||||
|
||||
if api_key:
|
||||
logger.info("Anthropic API key found - would call real API")
|
||||
# TODO: Implement actual API call
|
||||
# from anthropic import Anthropic
|
||||
# client = Anthropic(api_key=api_key)
|
||||
# response = client.messages.create(
|
||||
# model="claude-3-5-sonnet-20241022",
|
||||
# max_tokens=4096,
|
||||
# system=prompt,
|
||||
# messages=[{"role": "user", "content": "Execute the task"}]
|
||||
# )
|
||||
# return parse_claude_response(response)
|
||||
|
||||
logger.info("No API key found - using mock response")
|
||||
return generate_mock_response(prompt, agent_name)
|
||||
|
||||
|
||||
def generate_mock_response(prompt: str, agent_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Generate a mock Claude response for simulation.
|
||||
|
||||
Args:
|
||||
prompt: The system prompt
|
||||
agent_name: Name of the agent
|
||||
|
||||
Returns:
|
||||
Mock response dictionary
|
||||
"""
|
||||
# Extract task from prompt if present
|
||||
task_section = ""
|
||||
if "## TASK" in prompt:
|
||||
task_start = prompt.index("## TASK")
|
||||
task_end = prompt.index("## INSTRUCTIONS") if "## INSTRUCTIONS" in prompt else len(prompt)
|
||||
task_section = prompt[task_start:task_end].replace("## TASK", "").strip()
|
||||
|
||||
# Generate plausible skill selections based on agent name
|
||||
skills_to_invoke = []
|
||||
|
||||
if "api.designer" in agent_name:
|
||||
skills_to_invoke = [
|
||||
{
|
||||
"skill": "api.define",
|
||||
"purpose": "Create initial OpenAPI specification from requirements",
|
||||
"inputs": {"guidelines": "zalando", "format": "openapi-3.1"},
|
||||
"order": 1
|
||||
},
|
||||
{
|
||||
"skill": "api.validate",
|
||||
"purpose": "Validate the generated specification for compliance",
|
||||
"inputs": {"strict_mode": True},
|
||||
"order": 2
|
||||
},
|
||||
{
|
||||
"skill": "api.generate-models",
|
||||
"purpose": "Generate type-safe models from validated spec",
|
||||
"inputs": {"language": "typescript", "framework": "zod"},
|
||||
"order": 3
|
||||
}
|
||||
]
|
||||
elif "api.analyzer" in agent_name:
|
||||
skills_to_invoke = [
|
||||
{
|
||||
"skill": "api.validate",
|
||||
"purpose": "Analyze API specification for issues and best practices",
|
||||
"inputs": {"include_warnings": True},
|
||||
"order": 1
|
||||
},
|
||||
{
|
||||
"skill": "api.compatibility",
|
||||
"purpose": "Check compatibility with existing APIs",
|
||||
"inputs": {"check_breaking_changes": True},
|
||||
"order": 2
|
||||
}
|
||||
]
|
||||
else:
|
||||
# Generic response - extract skills from prompt
|
||||
if "AVAILABLE SKILLS" in prompt:
|
||||
skills_section_start = prompt.index("AVAILABLE SKILLS")
|
||||
skills_section_end = prompt.index("##", skills_section_start + 10) if prompt.count("##", skills_section_start) > 0 else len(prompt)
|
||||
skills_text = prompt[skills_section_start:skills_section_end]
|
||||
|
||||
import re
|
||||
skill_names = re.findall(r'• (\S+)', skills_text)
|
||||
|
||||
for i, skill_name in enumerate(skill_names[:3], 1):
|
||||
skills_to_invoke.append({
|
||||
"skill": skill_name,
|
||||
"purpose": f"Execute {skill_name} as part of agent workflow",
|
||||
"inputs": {},
|
||||
"order": i
|
||||
})
|
||||
|
||||
response = {
|
||||
"analysis": f"As {agent_name}, I will approach this task using my available skills in a structured sequence.",
|
||||
"skills_to_invoke": skills_to_invoke,
|
||||
"reasoning": "Selected skills follow the agent's workflow pattern and capabilities.",
|
||||
"mode": "simulated",
|
||||
"note": "This is a mock response. In production, Claude API would provide real analysis."
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def execute_skills(
|
||||
skills_plan: List[Dict[str, Any]],
|
||||
reasoning_mode: str
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Execute the planned skills (currently simulated).
|
||||
|
||||
In production, this would:
|
||||
1. For each skill in the plan:
|
||||
- Load the skill manifest
|
||||
- Prepare inputs
|
||||
- Execute the skill handler
|
||||
- Capture output
|
||||
2. In iterative mode: analyze results and potentially invoke more skills
|
||||
|
||||
Args:
|
||||
skills_plan: List of skills to invoke with their inputs
|
||||
reasoning_mode: 'iterative' or 'oneshot'
|
||||
|
||||
Returns:
|
||||
List of execution results
|
||||
"""
|
||||
results = []
|
||||
|
||||
for skill_info in skills_plan:
|
||||
execution_result = {
|
||||
"skill": skill_info.get("skill"),
|
||||
"purpose": skill_info.get("purpose"),
|
||||
"status": "simulated",
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"output": {
|
||||
"note": f"Simulated execution of {skill_info.get('skill')}",
|
||||
"inputs": skill_info.get("inputs", {}),
|
||||
"success": True
|
||||
}
|
||||
}
|
||||
|
||||
results.append(execution_result)
|
||||
|
||||
# In iterative mode, we might make decisions based on results
|
||||
if reasoning_mode == "iterative":
|
||||
execution_result["iterative_note"] = (
|
||||
"In iterative mode, the agent would analyze this result "
|
||||
"and potentially invoke additional skills or retry."
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def save_execution_log(
|
||||
agent_name: str,
|
||||
execution_data: Dict[str, Any]
|
||||
) -> str:
|
||||
"""
|
||||
Save execution log to agent_logs/<agent>.json
|
||||
|
||||
Args:
|
||||
agent_name: Name of the agent
|
||||
execution_data: Complete execution data to log
|
||||
|
||||
Returns:
|
||||
Path to the saved log file
|
||||
"""
|
||||
# Ensure logs directory exists
|
||||
os.makedirs(AGENT_LOGS_DIR, exist_ok=True)
|
||||
|
||||
# Generate log filename with timestamp
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
|
||||
log_filename = f"{agent_name}_{timestamp}.json"
|
||||
log_path = os.path.join(AGENT_LOGS_DIR, log_filename)
|
||||
|
||||
# Also maintain a "latest" symlink
|
||||
latest_path = os.path.join(AGENT_LOGS_DIR, f"{agent_name}_latest.json")
|
||||
|
||||
try:
|
||||
with open(log_path, 'w') as f:
|
||||
json.dump(execution_data, f, indent=2)
|
||||
|
||||
# Create/update latest symlink
|
||||
if os.path.exists(latest_path):
|
||||
os.remove(latest_path)
|
||||
os.symlink(os.path.basename(log_path), latest_path)
|
||||
|
||||
logger.info(f"Execution log saved to {log_path}")
|
||||
return log_path
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save execution log: {e}")
|
||||
raise BettyError(f"Failed to save execution log: {e}")
|
||||
|
||||
|
||||
def run_agent(
|
||||
agent_path: str,
|
||||
task_context: Optional[str] = None,
|
||||
save_log: bool = True
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Execute a Betty agent.
|
||||
|
||||
Args:
|
||||
agent_path: Path to agent manifest or agent name
|
||||
task_context: User-provided task or query
|
||||
save_log: Whether to save execution log to disk
|
||||
|
||||
Returns:
|
||||
Execution result dictionary
|
||||
"""
|
||||
logger.info(f"Running agent: {agent_path}")
|
||||
|
||||
# Track execution time for telemetry
|
||||
start_time = datetime.now(timezone.utc)
|
||||
|
||||
try:
|
||||
# Load agent manifest
|
||||
agent_manifest = load_agent_manifest(agent_path)
|
||||
agent_name = agent_manifest.get("name")
|
||||
reasoning_mode = agent_manifest.get("reasoning_mode", "oneshot")
|
||||
|
||||
logger.info(f"Loaded agent: {agent_name} (mode: {reasoning_mode})")
|
||||
|
||||
# Load skill registry
|
||||
skill_registry = load_skill_registry()
|
||||
|
||||
# Validate that agent's skills are available
|
||||
skills_available = agent_manifest.get("skills_available", [])
|
||||
skills_info = []
|
||||
missing_skills = []
|
||||
|
||||
for skill_name in skills_available:
|
||||
skill_info = get_skill_info(skill_name, skill_registry)
|
||||
if skill_info:
|
||||
skills_info.append({
|
||||
"name": skill_name,
|
||||
"description": skill_info.get("description", ""),
|
||||
"status": skill_info.get("status", "unknown")
|
||||
})
|
||||
else:
|
||||
missing_skills.append(skill_name)
|
||||
logger.warning(f"Skill not found in registry: {skill_name}")
|
||||
|
||||
# Construct agent prompt
|
||||
logger.info("Constructing agent prompt...")
|
||||
prompt = construct_agent_prompt(agent_manifest, task_context)
|
||||
|
||||
# Call Claude API (or mock)
|
||||
logger.info("Invoking Claude API...")
|
||||
claude_response = call_claude_api(prompt, agent_name)
|
||||
|
||||
# Execute skills based on Claude's plan
|
||||
skills_plan = claude_response.get("skills_to_invoke", [])
|
||||
logger.info(f"Executing {len(skills_plan)} skills...")
|
||||
execution_results = execute_skills(skills_plan, reasoning_mode)
|
||||
|
||||
# Build complete execution data
|
||||
execution_data = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"agent": {
|
||||
"name": agent_name,
|
||||
"version": agent_manifest.get("version"),
|
||||
"description": agent_manifest.get("description"),
|
||||
"reasoning_mode": reasoning_mode,
|
||||
"status": agent_manifest.get("status", "unknown")
|
||||
},
|
||||
"task_context": task_context or "No task provided",
|
||||
"prompt": prompt,
|
||||
"skills_available": skills_info,
|
||||
"missing_skills": missing_skills,
|
||||
"claude_response": claude_response,
|
||||
"execution_results": execution_results,
|
||||
"summary": {
|
||||
"skills_planned": len(skills_plan),
|
||||
"skills_executed": len(execution_results),
|
||||
"success": all(r.get("output", {}).get("success", False) for r in execution_results)
|
||||
}
|
||||
}
|
||||
|
||||
# Save log if requested
|
||||
log_path = None
|
||||
if save_log:
|
||||
log_path = save_execution_log(agent_name, execution_data)
|
||||
execution_data["log_path"] = log_path
|
||||
|
||||
# Calculate execution duration
|
||||
end_time = datetime.now(timezone.utc)
|
||||
duration_ms = int((end_time - start_time).total_seconds() * 1000)
|
||||
|
||||
# Capture telemetry for successful agent execution
|
||||
capture_skill_execution(
|
||||
skill_name="agent.run",
|
||||
inputs={
|
||||
"agent": agent_name,
|
||||
"task_context": task_context or "No task provided",
|
||||
},
|
||||
status="success" if execution_data["summary"]["success"] else "failed",
|
||||
duration_ms=duration_ms,
|
||||
agent=agent_name,
|
||||
caller="cli",
|
||||
reasoning_mode=reasoning_mode,
|
||||
skills_planned=len(skills_plan),
|
||||
skills_executed=len(execution_results),
|
||||
)
|
||||
|
||||
# Log audit entry for agent execution
|
||||
capture_audit_entry(
|
||||
skill_name="agent.run",
|
||||
status="success" if execution_data["summary"]["success"] else "failed",
|
||||
duration_ms=duration_ms,
|
||||
errors=None,
|
||||
metadata={
|
||||
"agent": agent_name,
|
||||
"reasoning_mode": reasoning_mode,
|
||||
"skills_executed": len(execution_results),
|
||||
"task_context": task_context or "No task provided",
|
||||
}
|
||||
)
|
||||
|
||||
return build_response(
|
||||
ok=True,
|
||||
details=execution_data
|
||||
)
|
||||
|
||||
except BettyError as e:
|
||||
logger.error(f"Agent execution failed: {e}")
|
||||
error_info = format_error_response(e, include_traceback=False)
|
||||
|
||||
# Calculate execution duration for failed case
|
||||
end_time = datetime.now(timezone.utc)
|
||||
duration_ms = int((end_time - start_time).total_seconds() * 1000)
|
||||
|
||||
# Capture telemetry for failed agent execution
|
||||
capture_skill_execution(
|
||||
skill_name="agent.run",
|
||||
inputs={"agent_path": agent_path},
|
||||
status="failed",
|
||||
duration_ms=duration_ms,
|
||||
caller="cli",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
# Log audit entry for failed agent execution
|
||||
capture_audit_entry(
|
||||
skill_name="agent.run",
|
||||
status="failed",
|
||||
duration_ms=duration_ms,
|
||||
errors=[str(e)],
|
||||
metadata={
|
||||
"agent_path": agent_path,
|
||||
"error_type": "BettyError",
|
||||
}
|
||||
)
|
||||
|
||||
return build_response(
|
||||
ok=False,
|
||||
errors=[str(e)],
|
||||
details={"error": error_info}
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {e}", exc_info=True)
|
||||
error_info = format_error_response(e, include_traceback=True)
|
||||
|
||||
# Calculate execution duration for failed case
|
||||
end_time = datetime.now(timezone.utc)
|
||||
duration_ms = int((end_time - start_time).total_seconds() * 1000)
|
||||
|
||||
# Capture telemetry for unexpected error
|
||||
capture_skill_execution(
|
||||
skill_name="agent.run",
|
||||
inputs={"agent_path": agent_path},
|
||||
status="failed",
|
||||
duration_ms=duration_ms,
|
||||
caller="cli",
|
||||
error=str(e),
|
||||
)
|
||||
|
||||
# Log audit entry for unexpected error
|
||||
capture_audit_entry(
|
||||
skill_name="agent.run",
|
||||
status="failed",
|
||||
duration_ms=duration_ms,
|
||||
errors=[f"Unexpected error: {str(e)}"],
|
||||
metadata={
|
||||
"agent_path": agent_path,
|
||||
"error_type": type(e).__name__,
|
||||
}
|
||||
)
|
||||
|
||||
return build_response(
|
||||
ok=False,
|
||||
errors=[f"Unexpected error: {str(e)}"],
|
||||
details={"error": error_info}
|
||||
)
|
||||
|
||||
|
||||
@capture_telemetry(skill_name="agent.run", caller="cli")
|
||||
def main():
|
||||
"""Main CLI entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
message = "Usage: agent_run.py <agent_path> [task_context] [--no-save-log]"
|
||||
response = build_response(
|
||||
False,
|
||||
errors=[message],
|
||||
details={
|
||||
"usage": message,
|
||||
"examples": [
|
||||
"agent_run.py api.designer",
|
||||
"agent_run.py api.designer 'Create API for user management'",
|
||||
"agent_run.py agents/api.designer/agent.yaml 'Design REST API'"
|
||||
]
|
||||
}
|
||||
)
|
||||
print(json.dumps(response, indent=2), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
agent_path = sys.argv[1]
|
||||
|
||||
# Parse optional arguments
|
||||
task_context = None
|
||||
save_log = True
|
||||
|
||||
for arg in sys.argv[2:]:
|
||||
if arg == "--no-save-log":
|
||||
save_log = False
|
||||
elif task_context is None:
|
||||
task_context = arg
|
||||
|
||||
try:
|
||||
result = run_agent(agent_path, task_context, save_log)
|
||||
|
||||
# Check if execution was successful
|
||||
if result['ok'] and 'details' in result and 'agent' in result['details']:
|
||||
# Pretty print for CLI usage
|
||||
print("\n" + "="*80)
|
||||
print(f"AGENT EXECUTION: {result['details']['agent']['name']}")
|
||||
print("="*80)
|
||||
|
||||
agent_info = result['details']['agent']
|
||||
print(f"\nAgent: {agent_info['name']} v{agent_info['version']}")
|
||||
print(f"Mode: {agent_info['reasoning_mode']}")
|
||||
print(f"Status: {agent_info['status']}")
|
||||
|
||||
print(f"\nTask: {result['details']['task_context']}")
|
||||
|
||||
print("\n" + "-"*80)
|
||||
print("CLAUDE RESPONSE:")
|
||||
print("-"*80)
|
||||
print(json.dumps(result['details']['claude_response'], indent=2))
|
||||
|
||||
print("\n" + "-"*80)
|
||||
print("EXECUTION RESULTS:")
|
||||
print("-"*80)
|
||||
for exec_result in result['details']['execution_results']:
|
||||
print(f"\n ✓ {exec_result['skill']}")
|
||||
print(f" Purpose: {exec_result['purpose']}")
|
||||
print(f" Status: {exec_result['status']}")
|
||||
|
||||
if 'log_path' in result['details']:
|
||||
print(f"\n📝 Log saved to: {result['details']['log_path']}")
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("EXECUTION COMPLETE")
|
||||
print("="*80 + "\n")
|
||||
else:
|
||||
# Execution failed - print error details
|
||||
print("\n" + "="*80)
|
||||
print("AGENT EXECUTION FAILED")
|
||||
print("="*80)
|
||||
print(f"\nErrors:")
|
||||
for error in result.get('errors', ['Unknown error']):
|
||||
print(f" ✗ {error}")
|
||||
print()
|
||||
|
||||
# Also output full JSON for programmatic use
|
||||
print(json.dumps(result, indent=2))
|
||||
sys.exit(0 if result['ok'] else 1)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\nInterrupted by user", file=sys.stderr)
|
||||
sys.exit(130)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user