Initial commit
This commit is contained in:
376
skills/agent.define/SKILL.md
Normal file
376
skills/agent.define/SKILL.md
Normal file
@@ -0,0 +1,376 @@
|
||||
# agent.define Skill
|
||||
|
||||
Validates and registers agent manifests for the Betty Framework.
|
||||
|
||||
## Purpose
|
||||
|
||||
The `agent.define` skill is the Layer 2 (Reasoning Layer) equivalent of `skill.define`. It validates agent manifests (`agent.yaml`) for schema compliance, verifies skill references, and updates the central Agent Registry.
|
||||
|
||||
## Capabilities
|
||||
|
||||
- Validate agent manifest structure and required fields
|
||||
- Verify agent name and version formats
|
||||
- Validate reasoning mode enum values
|
||||
- Check that all referenced skills exist in skill registry
|
||||
- Ensure capabilities and skills lists are non-empty
|
||||
- Validate status lifecycle values
|
||||
- Register valid agents in `/registry/agents.json`
|
||||
- Update existing agent entries with new versions
|
||||
|
||||
## Usage
|
||||
|
||||
### Command Line
|
||||
|
||||
```bash
|
||||
python skills/agent.define/agent_define.py <path_to_agent.yaml>
|
||||
```
|
||||
|
||||
### Arguments
|
||||
|
||||
| Argument | Type | Required | Description |
|
||||
|----------|------|----------|-------------|
|
||||
| `manifest_path` | string | Yes | Path to the agent.yaml file to validate |
|
||||
|
||||
### Exit Codes
|
||||
|
||||
- `0`: Validation succeeded and agent was registered
|
||||
- `1`: Validation failed or registration error
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### Required Fields
|
||||
|
||||
All agent manifests must include:
|
||||
|
||||
| Field | Type | Validation |
|
||||
|-------|------|------------|
|
||||
| `name` | string | Must match `^[a-z][a-z0-9._-]*$` |
|
||||
| `version` | string | Must follow semantic versioning |
|
||||
| `description` | string | Non-empty string (1-200 chars recommended) |
|
||||
| `capabilities` | array[string] | Must contain at least one item |
|
||||
| `skills_available` | array[string] | Must contain at least one item, all skills must exist in registry |
|
||||
| `reasoning_mode` | enum | Must be `iterative` or `oneshot` |
|
||||
|
||||
### Optional Fields
|
||||
|
||||
| Field | Type | Default | Validation |
|
||||
|-------|------|---------|------------|
|
||||
| `status` | enum | `draft` | Must be `draft`, `active`, `deprecated`, or `archived` |
|
||||
| `context_requirements` | object | `{}` | Any valid object |
|
||||
| `workflow_pattern` | string | `null` | Any string |
|
||||
| `example_task` | string | `null` | Any string |
|
||||
| `error_handling` | object | `{}` | Any valid object |
|
||||
| `output` | object | `{}` | Any valid object |
|
||||
| `tags` | array[string] | `[]` | Array of strings |
|
||||
| `dependencies` | array[string] | `[]` | Array of strings |
|
||||
|
||||
### Name Format
|
||||
|
||||
Agent names must:
|
||||
- Start with a lowercase letter
|
||||
- Contain only lowercase letters, numbers, dots, hyphens, underscores
|
||||
- Follow pattern: `<domain>.<action>`
|
||||
|
||||
**Valid**: `api.designer`, `compliance.checker`, `data-migrator`
|
||||
**Invalid**: `ApiDesigner`, `1agent`, `agent_name`
|
||||
|
||||
### Version Format
|
||||
|
||||
Versions must follow semantic versioning: `MAJOR.MINOR.PATCH[-prerelease]`
|
||||
|
||||
**Valid**: `0.1.0`, `1.0.0`, `2.3.1-beta`, `1.0.0-rc.1`
|
||||
**Invalid**: `1.0`, `v1.0.0`, `1.0.0.0`
|
||||
|
||||
### Reasoning Mode
|
||||
|
||||
Must be one of:
|
||||
- `iterative`: Agent can retry with feedback and refine based on errors
|
||||
- `oneshot`: Agent executes once without retry
|
||||
|
||||
### Skills Validation
|
||||
|
||||
All skills in `skills_available` must exist in the skill registry (`/registry/skills.json`).
|
||||
|
||||
## Response Format
|
||||
|
||||
### Success Response
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"status": "success",
|
||||
"errors": [],
|
||||
"path": "agents/api.designer/agent.yaml",
|
||||
"details": {
|
||||
"valid": true,
|
||||
"errors": [],
|
||||
"path": "agents/api.designer/agent.yaml",
|
||||
"manifest": {
|
||||
"name": "api.designer",
|
||||
"version": "0.1.0",
|
||||
"description": "Design RESTful APIs...",
|
||||
"capabilities": [...],
|
||||
"skills_available": [...],
|
||||
"reasoning_mode": "iterative"
|
||||
},
|
||||
"status": "registered",
|
||||
"registry_updated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Failure Response
|
||||
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"status": "failed",
|
||||
"errors": [
|
||||
"Missing required fields: capabilities, skills_available",
|
||||
"Invalid reasoning_mode: 'hybrid'. Must be one of: iterative, oneshot"
|
||||
],
|
||||
"path": "agents/bad.agent/agent.yaml",
|
||||
"details": {
|
||||
"valid": false,
|
||||
"errors": [
|
||||
"Missing required fields: capabilities, skills_available",
|
||||
"Invalid reasoning_mode: 'hybrid'. Must be one of: iterative, oneshot"
|
||||
],
|
||||
"path": "agents/bad.agent/agent.yaml"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Validate Iterative Agent
|
||||
|
||||
**Agent Manifest** (`agents/api.designer/agent.yaml`):
|
||||
```yaml
|
||||
name: api.designer
|
||||
version: 0.1.0
|
||||
description: "Design RESTful APIs following enterprise guidelines"
|
||||
|
||||
capabilities:
|
||||
- Design RESTful APIs from requirements
|
||||
- Apply Zalando guidelines automatically
|
||||
- Generate OpenAPI 3.1 specs
|
||||
- Iteratively refine based on validation feedback
|
||||
|
||||
skills_available:
|
||||
- api.define
|
||||
- api.validate
|
||||
- api.generate-models
|
||||
|
||||
reasoning_mode: iterative
|
||||
|
||||
status: draft
|
||||
|
||||
tags:
|
||||
- api
|
||||
- design
|
||||
- openapi
|
||||
```
|
||||
|
||||
**Command**:
|
||||
```bash
|
||||
python skills/agent.define/agent_define.py agents/api.designer/agent.yaml
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```json
|
||||
{
|
||||
"ok": true,
|
||||
"status": "success",
|
||||
"errors": [],
|
||||
"path": "agents/api.designer/agent.yaml",
|
||||
"details": {
|
||||
"valid": true,
|
||||
"status": "registered",
|
||||
"registry_updated": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Example 2: Validation Errors
|
||||
|
||||
**Agent Manifest** (`agents/bad.agent/agent.yaml`):
|
||||
```yaml
|
||||
name: BadAgent # Invalid: must be lowercase
|
||||
version: 1.0 # Invalid: must be semver
|
||||
description: "Test agent"
|
||||
capabilities: [] # Invalid: must have at least one
|
||||
skills_available:
|
||||
- nonexistent.skill # Invalid: skill doesn't exist
|
||||
reasoning_mode: hybrid # Invalid: must be iterative or oneshot
|
||||
```
|
||||
|
||||
**Command**:
|
||||
```bash
|
||||
python skills/agent.define/agent_define.py agents/bad.agent/agent.yaml
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```json
|
||||
{
|
||||
"ok": false,
|
||||
"status": "failed",
|
||||
"errors": [
|
||||
"Invalid name: Invalid agent name: 'BadAgent'. Must start with lowercase letter...",
|
||||
"Invalid version: Invalid version: '1.0'. Must follow semantic versioning...",
|
||||
"Invalid reasoning_mode: Invalid reasoning_mode: 'hybrid'. Must be one of: iterative, oneshot",
|
||||
"capabilities must contain at least one item",
|
||||
"Skills not found in registry: nonexistent.skill"
|
||||
],
|
||||
"path": "agents/bad.agent/agent.yaml"
|
||||
}
|
||||
```
|
||||
|
||||
### Example 3: Oneshot Agent
|
||||
|
||||
**Agent Manifest** (`agents/api.analyzer/agent.yaml`):
|
||||
```yaml
|
||||
name: api.analyzer
|
||||
version: 0.1.0
|
||||
description: "Analyze API specifications for compatibility"
|
||||
|
||||
capabilities:
|
||||
- Detect breaking changes between API versions
|
||||
- Generate compatibility reports
|
||||
- Suggest migration paths
|
||||
|
||||
skills_available:
|
||||
- api.compatibility
|
||||
|
||||
reasoning_mode: oneshot
|
||||
|
||||
output:
|
||||
success:
|
||||
- Compatibility report
|
||||
- Breaking changes list
|
||||
failure:
|
||||
- Error analysis
|
||||
|
||||
status: active
|
||||
|
||||
tags:
|
||||
- api
|
||||
- analysis
|
||||
- compatibility
|
||||
```
|
||||
|
||||
**Command**:
|
||||
```bash
|
||||
python skills/agent.define/agent_define.py agents/api.analyzer/agent.yaml
|
||||
```
|
||||
|
||||
**Result**: Agent validated and registered successfully.
|
||||
|
||||
## Integration
|
||||
|
||||
### With Registry
|
||||
|
||||
The skill automatically updates `/registry/agents.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"registry_version": "1.0.0",
|
||||
"generated_at": "2025-10-23T10:30:00Z",
|
||||
"agents": [
|
||||
{
|
||||
"name": "api.designer",
|
||||
"version": "0.1.0",
|
||||
"description": "Design RESTful APIs following enterprise guidelines",
|
||||
"reasoning_mode": "iterative",
|
||||
"skills_available": ["api.define", "api.validate", "api.generate-models"],
|
||||
"capabilities": ["Design RESTful APIs from requirements", ...],
|
||||
"status": "draft",
|
||||
"tags": ["api", "design", "openapi"],
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### With Other Skills
|
||||
|
||||
- **Depends on**: `skill.define` (for skill registry validation)
|
||||
- **Used by**: Future `command.define` skill (to register commands that invoke agents)
|
||||
- **Complements**: `workflow.compose` (agents orchestrate skills; workflows execute fixed sequences)
|
||||
|
||||
## Common Errors
|
||||
|
||||
### Missing Skills in Registry
|
||||
|
||||
**Error**:
|
||||
```
|
||||
Skills not found in registry: api.nonexistent, data.missing
|
||||
```
|
||||
|
||||
**Solution**: Ensure all skills in `skills_available` are registered in `/registry/skills.json`. Check skill names for typos.
|
||||
|
||||
### Invalid Reasoning Mode
|
||||
|
||||
**Error**:
|
||||
```
|
||||
Invalid reasoning_mode: 'hybrid'. Must be one of: iterative, oneshot
|
||||
```
|
||||
|
||||
**Solution**: Use `iterative` for agents that retry with feedback, or `oneshot` for deterministic execution.
|
||||
|
||||
### Empty Capabilities
|
||||
|
||||
**Error**:
|
||||
```
|
||||
capabilities must contain at least one item
|
||||
```
|
||||
|
||||
**Solution**: Add at least one capability string describing what the agent can do.
|
||||
|
||||
### Invalid Name Format
|
||||
|
||||
**Error**:
|
||||
```
|
||||
Invalid agent name: 'API-Designer'. Must start with lowercase letter...
|
||||
```
|
||||
|
||||
**Solution**: Use lowercase names following pattern `<domain>.<action>` (e.g., `api.designer`).
|
||||
|
||||
## Development
|
||||
|
||||
### Testing
|
||||
|
||||
Create test agent manifests in `/agents/test/`:
|
||||
|
||||
```bash
|
||||
# Create test directory
|
||||
mkdir -p agents/test.agent
|
||||
|
||||
# Create minimal test manifest
|
||||
cat > agents/test.agent/agent.yaml << EOF
|
||||
name: test.agent
|
||||
version: 0.1.0
|
||||
description: "Test agent"
|
||||
capabilities:
|
||||
- Test capability
|
||||
skills_available:
|
||||
- skill.define
|
||||
reasoning_mode: oneshot
|
||||
status: draft
|
||||
EOF
|
||||
|
||||
# Validate
|
||||
python skills/agent.define/agent_define.py agents/test.agent/agent.yaml
|
||||
```
|
||||
|
||||
### Registry Location
|
||||
|
||||
- Skill registry: `/registry/skills.json` (read for validation)
|
||||
- Agent registry: `/registry/agents.json` (updated by this skill)
|
||||
|
||||
## See Also
|
||||
|
||||
- [Agent Schema Reference](../../docs/agent-schema-reference.md) - Complete field specifications
|
||||
- [Betty Architecture](../../docs/betty-architecture.md) - Five-layer architecture overview
|
||||
- [Agent Implementation Plan](../../docs/agent-define-implementation-plan.md) - Implementation details
|
||||
- `/agents/README.md` - Agent directory documentation
|
||||
1
skills/agent.define/__init__.py
Normal file
1
skills/agent.define/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Auto-generated package initializer for skills.
|
||||
419
skills/agent.define/agent_define.py
Executable file
419
skills/agent.define/agent_define.py
Executable file
@@ -0,0 +1,419 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
agent_define.py – Implementation of the agent.define Skill
|
||||
Validates agent manifests (agent.yaml) and registers them in the Agent Registry.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import yaml
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime, timezone
|
||||
from pydantic import ValidationError as PydanticValidationError
|
||||
|
||||
|
||||
from betty.config import (
|
||||
BASE_DIR,
|
||||
REQUIRED_AGENT_FIELDS,
|
||||
AGENTS_REGISTRY_FILE,
|
||||
REGISTRY_FILE,
|
||||
)
|
||||
from betty.enums import AgentStatus, ReasoningMode
|
||||
from betty.validation import (
|
||||
validate_path,
|
||||
validate_manifest_fields,
|
||||
validate_agent_name,
|
||||
validate_version,
|
||||
validate_reasoning_mode,
|
||||
validate_skills_exist
|
||||
)
|
||||
from betty.logging_utils import setup_logger
|
||||
from betty.errors import AgentValidationError, AgentRegistryError, format_error_response
|
||||
from betty.models import AgentManifest
|
||||
from betty.file_utils import atomic_write_json
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def build_response(ok: bool, path: str, errors: Optional[List[str]] = None, details: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||
"""
|
||||
Build standardized response dictionary.
|
||||
|
||||
Args:
|
||||
ok: Whether operation succeeded
|
||||
path: Path to agent manifest
|
||||
errors: List of error messages
|
||||
details: Additional details
|
||||
|
||||
Returns:
|
||||
Response dictionary
|
||||
"""
|
||||
response: Dict[str, Any] = {
|
||||
"ok": ok,
|
||||
"status": "success" if ok else "failed",
|
||||
"errors": errors or [],
|
||||
"path": path,
|
||||
}
|
||||
|
||||
if details is not None:
|
||||
response["details"] = details
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def load_agent_manifest(path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Load and parse an agent manifest from YAML file.
|
||||
|
||||
Args:
|
||||
path: Path to agent manifest file
|
||||
|
||||
Returns:
|
||||
Parsed manifest dictionary
|
||||
|
||||
Raises:
|
||||
AgentValidationError: If manifest cannot be loaded or parsed
|
||||
"""
|
||||
try:
|
||||
with open(path) as f:
|
||||
manifest = yaml.safe_load(f)
|
||||
return manifest
|
||||
except FileNotFoundError:
|
||||
raise AgentValidationError(f"Manifest file not found: {path}")
|
||||
except yaml.YAMLError as e:
|
||||
raise AgentValidationError(f"Failed to parse YAML: {e}")
|
||||
|
||||
|
||||
def load_skill_registry() -> Dict[str, Any]:
|
||||
"""
|
||||
Load skill registry for validation.
|
||||
|
||||
Returns:
|
||||
Skill registry dictionary
|
||||
|
||||
Raises:
|
||||
AgentValidationError: If registry cannot be loaded
|
||||
"""
|
||||
try:
|
||||
with open(REGISTRY_FILE) as f:
|
||||
return json.load(f)
|
||||
except FileNotFoundError:
|
||||
raise AgentValidationError(f"Skill registry not found: {REGISTRY_FILE}")
|
||||
except json.JSONDecodeError as e:
|
||||
raise AgentValidationError(f"Failed to parse skill registry: {e}")
|
||||
|
||||
|
||||
def validate_agent_schema(manifest: Dict[str, Any]) -> List[str]:
|
||||
"""
|
||||
Validate agent manifest using Pydantic schema.
|
||||
|
||||
Args:
|
||||
manifest: Agent manifest dictionary
|
||||
|
||||
Returns:
|
||||
List of validation errors (empty if valid)
|
||||
"""
|
||||
errors: List[str] = []
|
||||
|
||||
try:
|
||||
AgentManifest.model_validate(manifest)
|
||||
logger.info("Pydantic schema validation passed for agent manifest")
|
||||
except PydanticValidationError as exc:
|
||||
logger.warning("Pydantic schema validation failed for agent manifest")
|
||||
for error in exc.errors():
|
||||
field = ".".join(str(loc) for loc in error["loc"])
|
||||
message = error["msg"]
|
||||
error_type = error["type"]
|
||||
errors.append(f"Schema validation error at '{field}': {message} (type: {error_type})")
|
||||
|
||||
return errors
|
||||
|
||||
|
||||
def validate_manifest(path: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate that an agent manifest meets all requirements.
|
||||
|
||||
Validation checks:
|
||||
1. Required fields are present
|
||||
2. Name format is valid
|
||||
3. Version format is valid
|
||||
4. Reasoning mode is valid
|
||||
5. All referenced skills exist in skill registry
|
||||
6. Capabilities list is non-empty
|
||||
7. Skills list is non-empty
|
||||
|
||||
Args:
|
||||
path: Path to agent manifest file
|
||||
|
||||
Returns:
|
||||
Dictionary with validation results:
|
||||
- valid: Boolean indicating if manifest is valid
|
||||
- errors: List of validation errors (if any)
|
||||
- manifest: The parsed manifest (if valid)
|
||||
- path: Path to the manifest file
|
||||
"""
|
||||
validate_path(path, must_exist=True)
|
||||
|
||||
logger.info(f"Validating agent manifest: {path}")
|
||||
|
||||
errors = []
|
||||
|
||||
# Load manifest
|
||||
try:
|
||||
manifest = load_agent_manifest(path)
|
||||
except AgentValidationError as e:
|
||||
return {
|
||||
"valid": False,
|
||||
"errors": [str(e)],
|
||||
"path": path
|
||||
}
|
||||
|
||||
# Check required fields first so high-level issues are reported clearly
|
||||
missing = validate_manifest_fields(manifest, REQUIRED_AGENT_FIELDS)
|
||||
if missing:
|
||||
missing_message = f"Missing required fields: {', '.join(missing)}"
|
||||
errors.append(missing_message)
|
||||
logger.warning(f"Missing required fields: {missing}")
|
||||
|
||||
# Validate with Pydantic schema while continuing custom validation
|
||||
schema_errors = validate_agent_schema(manifest)
|
||||
errors.extend(schema_errors)
|
||||
|
||||
name = manifest.get("name")
|
||||
if name is not None:
|
||||
try:
|
||||
validate_agent_name(name)
|
||||
except Exception as e:
|
||||
errors.append(f"Invalid name: {str(e)}")
|
||||
logger.warning(f"Invalid name: {e}")
|
||||
|
||||
version = manifest.get("version")
|
||||
if version is not None:
|
||||
try:
|
||||
validate_version(version)
|
||||
except Exception as e:
|
||||
errors.append(f"Invalid version: {str(e)}")
|
||||
logger.warning(f"Invalid version: {e}")
|
||||
|
||||
reasoning_mode = manifest.get("reasoning_mode")
|
||||
if reasoning_mode is not None:
|
||||
try:
|
||||
validate_reasoning_mode(reasoning_mode)
|
||||
except Exception as e:
|
||||
errors.append(f"Invalid reasoning_mode: {str(e)}")
|
||||
logger.warning(f"Invalid reasoning_mode: {e}")
|
||||
elif "reasoning_mode" not in missing:
|
||||
errors.append("reasoning_mode must be provided")
|
||||
logger.warning("Reasoning mode missing")
|
||||
|
||||
# Validate capabilities is non-empty
|
||||
capabilities = manifest.get("capabilities", [])
|
||||
if not capabilities or len(capabilities) == 0:
|
||||
errors.append("capabilities must contain at least one item")
|
||||
logger.warning("Empty capabilities list")
|
||||
|
||||
# Validate skills_available is non-empty
|
||||
skills_available = manifest.get("skills_available", [])
|
||||
if not skills_available or len(skills_available) == 0:
|
||||
errors.append("skills_available must contain at least one item")
|
||||
logger.warning("Empty skills_available list")
|
||||
|
||||
# Validate all skills exist in skill registry
|
||||
if skills_available:
|
||||
try:
|
||||
skill_registry = load_skill_registry()
|
||||
missing_skills = validate_skills_exist(skills_available, skill_registry)
|
||||
if missing_skills:
|
||||
errors.append(f"Skills not found in registry: {', '.join(missing_skills)}")
|
||||
logger.warning(f"Missing skills: {missing_skills}")
|
||||
except AgentValidationError as e:
|
||||
errors.append(f"Could not validate skills: {str(e)}")
|
||||
logger.error(f"Skill validation error: {e}")
|
||||
|
||||
# Validate status if present
|
||||
if "status" in manifest:
|
||||
valid_statuses = [s.value for s in AgentStatus]
|
||||
if manifest["status"] not in valid_statuses:
|
||||
errors.append(f"Invalid status: '{manifest['status']}'. Must be one of: {', '.join(valid_statuses)}")
|
||||
logger.warning(f"Invalid status: {manifest['status']}")
|
||||
|
||||
if errors:
|
||||
logger.warning(f"Validation failed with {len(errors)} error(s)")
|
||||
return {
|
||||
"valid": False,
|
||||
"errors": errors,
|
||||
"path": path
|
||||
}
|
||||
|
||||
logger.info("✅ Agent manifest validation passed")
|
||||
return {
|
||||
"valid": True,
|
||||
"errors": [],
|
||||
"path": path,
|
||||
"manifest": manifest
|
||||
}
|
||||
|
||||
|
||||
def load_agent_registry() -> Dict[str, Any]:
|
||||
"""
|
||||
Load existing agent registry.
|
||||
|
||||
Returns:
|
||||
Agent registry dictionary, or new empty registry if file doesn't exist
|
||||
"""
|
||||
if not os.path.exists(AGENTS_REGISTRY_FILE):
|
||||
logger.info("Agent registry not found, creating new registry")
|
||||
return {
|
||||
"registry_version": "1.0.0",
|
||||
"generated_at": datetime.now(timezone.utc).isoformat(),
|
||||
"agents": []
|
||||
}
|
||||
|
||||
try:
|
||||
with open(AGENTS_REGISTRY_FILE) as f:
|
||||
registry = json.load(f)
|
||||
logger.info(f"Loaded agent registry with {len(registry.get('agents', []))} agent(s)")
|
||||
return registry
|
||||
except json.JSONDecodeError as e:
|
||||
raise AgentRegistryError(f"Failed to parse agent registry: {e}")
|
||||
|
||||
|
||||
def update_agent_registry(manifest: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
Add or update agent in the agent registry.
|
||||
|
||||
Args:
|
||||
manifest: Validated agent manifest
|
||||
|
||||
Returns:
|
||||
True if registry was updated successfully
|
||||
|
||||
Raises:
|
||||
AgentRegistryError: If registry update fails
|
||||
"""
|
||||
logger.info(f"Updating agent registry for: {manifest['name']}")
|
||||
|
||||
# Load existing registry
|
||||
registry = load_agent_registry()
|
||||
|
||||
# Create registry entry
|
||||
entry = {
|
||||
"name": manifest["name"],
|
||||
"version": manifest["version"],
|
||||
"description": manifest["description"],
|
||||
"reasoning_mode": manifest["reasoning_mode"],
|
||||
"skills_available": manifest["skills_available"],
|
||||
"capabilities": manifest.get("capabilities", []),
|
||||
"status": manifest.get("status", "draft"),
|
||||
"tags": manifest.get("tags", []),
|
||||
"dependencies": manifest.get("dependencies", [])
|
||||
}
|
||||
|
||||
# Check if agent already exists
|
||||
agents = registry.get("agents", [])
|
||||
existing_index = None
|
||||
for i, agent in enumerate(agents):
|
||||
if agent["name"] == manifest["name"]:
|
||||
existing_index = i
|
||||
break
|
||||
|
||||
if existing_index is not None:
|
||||
# Update existing agent
|
||||
agents[existing_index] = entry
|
||||
logger.info(f"Updated existing agent: {manifest['name']}")
|
||||
else:
|
||||
# Add new agent
|
||||
agents.append(entry)
|
||||
logger.info(f"Added new agent: {manifest['name']}")
|
||||
|
||||
registry["agents"] = agents
|
||||
registry["generated_at"] = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Write registry back to disk atomically
|
||||
try:
|
||||
atomic_write_json(AGENTS_REGISTRY_FILE, registry)
|
||||
logger.info(f"Agent registry updated successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
raise AgentRegistryError(f"Failed to write agent registry: {e}")
|
||||
|
||||
|
||||
def main():
|
||||
"""Main CLI entry point."""
|
||||
if len(sys.argv) < 2:
|
||||
message = "Usage: agent_define.py <path_to_agent.yaml>"
|
||||
response = build_response(
|
||||
False,
|
||||
path="",
|
||||
errors=[message],
|
||||
details={"error": {"error": "UsageError", "message": message, "details": {}}},
|
||||
)
|
||||
print(json.dumps(response, indent=2))
|
||||
sys.exit(1)
|
||||
|
||||
path = sys.argv[1]
|
||||
|
||||
try:
|
||||
# Validate manifest
|
||||
validation = validate_manifest(path)
|
||||
details = dict(validation)
|
||||
|
||||
if validation.get("valid"):
|
||||
# Update registry
|
||||
try:
|
||||
registry_updated = update_agent_registry(validation["manifest"])
|
||||
details["status"] = "registered"
|
||||
details["registry_updated"] = registry_updated
|
||||
except AgentRegistryError as e:
|
||||
logger.error(f"Registry update failed: {e}")
|
||||
details["status"] = "validated"
|
||||
details["registry_updated"] = False
|
||||
details["registry_error"] = str(e)
|
||||
else:
|
||||
# Check if there are schema validation errors
|
||||
has_schema_errors = any("Schema validation error" in err for err in validation.get("errors", []))
|
||||
if has_schema_errors:
|
||||
details["error"] = {
|
||||
"type": "SchemaError",
|
||||
"error": "SchemaError",
|
||||
"message": "Agent manifest schema validation failed",
|
||||
"details": {"errors": validation.get("errors", [])}
|
||||
}
|
||||
|
||||
# Build response
|
||||
response = build_response(
|
||||
bool(validation.get("valid")),
|
||||
path=path,
|
||||
errors=validation.get("errors", []),
|
||||
details=details,
|
||||
)
|
||||
print(json.dumps(response, indent=2))
|
||||
sys.exit(0 if response["ok"] else 1)
|
||||
|
||||
except AgentValidationError as e:
|
||||
logger.error(str(e))
|
||||
error_info = format_error_response(e)
|
||||
response = build_response(
|
||||
False,
|
||||
path=path,
|
||||
errors=[error_info.get("message", str(e))],
|
||||
details={"error": error_info},
|
||||
)
|
||||
print(json.dumps(response, indent=2))
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {e}")
|
||||
error_info = format_error_response(e, include_traceback=True)
|
||||
response = build_response(
|
||||
False,
|
||||
path=path,
|
||||
errors=[error_info.get("message", str(e))],
|
||||
details={"error": error_info},
|
||||
)
|
||||
print(json.dumps(response, indent=2))
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
45
skills/agent.define/skill.yaml
Normal file
45
skills/agent.define/skill.yaml
Normal file
@@ -0,0 +1,45 @@
|
||||
name: agent.define
|
||||
version: 0.1.0
|
||||
description: >
|
||||
Validates and registers agent manifests for the Betty Framework.
|
||||
Ensures schema compliance, validates skill references, and updates the Agent Registry.
|
||||
|
||||
inputs:
|
||||
- name: manifest_path
|
||||
type: string
|
||||
required: true
|
||||
description: Path to the agent.yaml file to validate
|
||||
|
||||
outputs:
|
||||
- name: validation_result
|
||||
type: object
|
||||
description: Validation results including errors and warnings
|
||||
- name: registry_updated
|
||||
type: boolean
|
||||
description: Whether agent was successfully registered
|
||||
|
||||
dependencies:
|
||||
- skill.define
|
||||
|
||||
status: active
|
||||
|
||||
entrypoints:
|
||||
- command: /agent/define
|
||||
handler: agent_define.py
|
||||
runtime: python
|
||||
description: >
|
||||
Validate an agent manifest and register it in the Agent Registry.
|
||||
parameters:
|
||||
- name: manifest_path
|
||||
type: string
|
||||
required: true
|
||||
description: Path to the agent.yaml file to validate
|
||||
permissions:
|
||||
- filesystem:read
|
||||
- filesystem:write
|
||||
|
||||
tags:
|
||||
- agents
|
||||
- validation
|
||||
- registry
|
||||
- layer2
|
||||
Reference in New Issue
Block a user