xml-pipeline/xml_pipeline/config/agents.py
dullfig e653d63bc1 Rename agentserver to xml_pipeline, add console example
OSS restructuring for open-core model:
- Rename package from agentserver/ to xml_pipeline/
- Update all imports (44 Python files, 31 docs/configs)
- Update pyproject.toml for OSS distribution (v0.3.0)
- Move prompt_toolkit from core to optional [console] extra
- Remove auth/server/lsp from core optional deps (-> Nextra)

New console example in examples/console/:
- Self-contained demo with handlers and config
- Uses prompt_toolkit (optional, falls back to input())
- No password auth, no TUI, no LSP — just the basics
- Shows how to use xml-pipeline as a library

Import changes:
- from agentserver.* -> from xml_pipeline.*
- CLI entry points updated: xml_pipeline.cli:main

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-19 21:41:19 -08:00

243 lines
6.5 KiB
Python

"""
Agent configuration management.
Each agent has its own YAML config file in ~/.xml-pipeline/agents/
containing behavior settings (prompt, model, temperature, etc.)
The main organism.yaml defines wiring (listeners, peers, routing).
Agent YAML files define behavior.
"""
from dataclasses import dataclass, field, asdict
from pathlib import Path
from typing import Optional, Dict, Any, List
import yaml
CONFIG_DIR = Path.home() / ".xml-pipeline"
AGENTS_DIR = CONFIG_DIR / "agents"
@dataclass
class AgentConfig:
"""
Configuration for an individual agent.
Stored in ~/.xml-pipeline/agents/{name}.yaml
"""
name: str
# System prompt for the LLM
prompt: str = ""
# Model selection
model: str = "default" # "default", "claude-sonnet", "claude-opus", "gpt-4", etc.
# Generation parameters
temperature: float = 0.7
max_tokens: int = 4096
# Behavior flags
verbose: bool = False # Log detailed reasoning
confirm_actions: bool = False # Ask before tool calls
# Tool permissions (if empty, uses defaults from wiring)
allowed_tools: List[str] = field(default_factory=list)
blocked_tools: List[str] = field(default_factory=list)
# Custom metadata
metadata: Dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
"""Convert to dict for YAML serialization."""
d = asdict(self)
# Remove name from dict (it's the filename)
del d["name"]
return d
@classmethod
def from_dict(cls, name: str, data: Dict[str, Any]) -> "AgentConfig":
"""Create from dict (loaded from YAML)."""
return cls(
name=name,
prompt=data.get("prompt", ""),
model=data.get("model", "default"),
temperature=data.get("temperature", 0.7),
max_tokens=data.get("max_tokens", 4096),
verbose=data.get("verbose", False),
confirm_actions=data.get("confirm_actions", False),
allowed_tools=data.get("allowed_tools", []),
blocked_tools=data.get("blocked_tools", []),
metadata=data.get("metadata", {}),
)
def to_yaml(self) -> str:
"""Serialize to YAML string."""
return yaml.dump(
self.to_dict(),
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
@classmethod
def from_yaml(cls, name: str, yaml_str: str) -> "AgentConfig":
"""Parse from YAML string."""
data = yaml.safe_load(yaml_str) or {}
return cls.from_dict(name, data)
class AgentConfigStore:
"""
Manages agent configuration files.
Usage:
store = AgentConfigStore()
# Load or create config
config = store.get("greeter")
# Modify and save
config.prompt = "You are a friendly greeter."
store.save(config)
# Get path for editing
path = store.path_for("greeter")
"""
def __init__(self, agents_dir: Path = AGENTS_DIR):
self.agents_dir = agents_dir
self._ensure_dir()
def _ensure_dir(self) -> None:
"""Create agents directory if needed."""
self.agents_dir.mkdir(parents=True, exist_ok=True)
def path_for(self, name: str) -> Path:
"""Get path to agent's config file."""
return self.agents_dir / f"{name}.yaml"
def exists(self, name: str) -> bool:
"""Check if agent config exists."""
return self.path_for(name).exists()
def get(self, name: str) -> AgentConfig:
"""
Load agent config, creating default if not exists.
"""
path = self.path_for(name)
if path.exists():
with open(path) as f:
data = yaml.safe_load(f) or {}
return AgentConfig.from_dict(name, data)
# Return default config (not saved yet)
return AgentConfig(name=name)
def save(self, config: AgentConfig) -> Path:
"""
Save agent config to file.
Returns path to saved file.
"""
path = self.path_for(config.name)
with open(path, "w") as f:
yaml.dump(
config.to_dict(),
f,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
return path
def save_yaml(self, name: str, yaml_content: str) -> Path:
"""
Save raw YAML content for an agent.
Used when saving from editor.
"""
path = self.path_for(name)
# Validate YAML before saving
yaml.safe_load(yaml_content) # Raises on invalid YAML
with open(path, "w") as f:
f.write(yaml_content)
return path
def load_yaml(self, name: str) -> str:
"""
Load raw YAML content for editing.
Returns default template if file doesn't exist.
"""
path = self.path_for(name)
if path.exists():
with open(path) as f:
return f.read()
# Return default template
return self._default_template(name)
def _default_template(self, name: str) -> str:
"""Generate default YAML template for new agent."""
return f"""# Agent configuration for: {name}
# This file defines behavior settings for the agent.
# The wiring (peers, routing) is in organism.yaml.
# System prompt - instructions for the LLM
prompt: |
You are {name}, an AI assistant.
Respond helpfully and concisely.
# Model selection: "default", "claude-sonnet", "claude-opus", "gpt-4", etc.
model: default
# Generation parameters
temperature: 0.7
max_tokens: 4096
# Behavior flags
verbose: false # Log detailed reasoning
confirm_actions: false # Ask before tool calls
# Tool permissions (empty = use defaults from wiring)
allowed_tools: []
blocked_tools: []
# Custom metadata (available to handler)
metadata: {{}}
"""
def list_agents(self) -> List[str]:
"""List all configured agents."""
return [
p.stem for p in self.agents_dir.glob("*.yaml")
]
def delete(self, name: str) -> bool:
"""Delete agent config file."""
path = self.path_for(name)
if path.exists():
path.unlink()
return True
return False
# Global instance
_store: Optional[AgentConfigStore] = None
def get_agent_config_store() -> AgentConfigStore:
"""Get the global agent config store."""
global _store
if _store is None:
_store = AgentConfigStore()
return _store