Files
xtrm-agent/xtrm_agent/config.py
Kaloyan Danchev 378d599125 Initial implementation of xtrm-agent multi-agent system
Multi-agent AI automation system with shared message bus, specialized
roles (coder/researcher/reviewer), and deny-by-default security.

- Config system with Pydantic validation and YAML loading
- Async message bus with inter-agent delegation
- LLM providers: Anthropic (Claude) and LiteLLM (DeepSeek/Kimi/MiniMax)
- Tool system: registry, builtins (file/bash/web), approval engine, MCP client
- Agent engine with tool-calling loop and orchestrator for multi-agent management
- CLI channel (REPL) and Discord channel
- Docker + Dockge deployment config
- Typer CLI: chat, serve, status, agents commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 10:21:42 +02:00

115 lines
3.2 KiB
Python

"""Configuration system — YAML config + Pydantic validation."""
from __future__ import annotations
from pathlib import Path
from typing import Any
import yaml
from pydantic import BaseModel, Field
class ProviderConfig(BaseModel):
"""Single LLM provider configuration."""
provider: str = "anthropic"
model: str = "claude-sonnet-4-5-20250929"
max_tokens: int = 8192
temperature: float = 0.3
api_key_env: str = ""
class LLMConfig(BaseModel):
"""LLM providers section."""
providers: dict[str, ProviderConfig] = Field(default_factory=dict)
class CLIChannelConfig(BaseModel):
enabled: bool = True
default_agent: str = "coder"
class DiscordChannelConfig(BaseModel):
enabled: bool = False
token_env: str = "DISCORD_BOT_TOKEN"
default_agent: str = "coder"
allowed_users: list[str] = Field(default_factory=list)
class ChannelsConfig(BaseModel):
cli: CLIChannelConfig = Field(default_factory=CLIChannelConfig)
discord: DiscordChannelConfig = Field(default_factory=DiscordChannelConfig)
class ToolsConfig(BaseModel):
workspace: str = "./data"
auto_approve: list[str] = Field(
default_factory=lambda: ["read_file", "list_dir", "web_fetch", "delegate"]
)
require_approval: list[str] = Field(
default_factory=lambda: ["bash", "write_file", "edit_file"]
)
class MCPServerConfig(BaseModel):
"""Single MCP server configuration."""
command: str = ""
args: list[str] = Field(default_factory=list)
env: dict[str, str] = Field(default_factory=dict)
url: str = ""
class OrchestratorConfig(BaseModel):
max_concurrent: int = 5
delegation_timeout: int = 120
class AgentFileConfig(BaseModel):
"""Parsed from agent markdown frontmatter."""
name: str = ""
provider: str = "anthropic"
model: str = ""
temperature: float = 0.3
max_iterations: int = 30
tools: list[str] = Field(default_factory=list)
instructions: str = ""
class Config(BaseModel):
"""Top-level application config."""
llm: LLMConfig = Field(default_factory=LLMConfig)
channels: ChannelsConfig = Field(default_factory=ChannelsConfig)
tools: ToolsConfig = Field(default_factory=ToolsConfig)
mcp_servers: dict[str, MCPServerConfig] = Field(default_factory=dict)
agents: dict[str, str] = Field(default_factory=dict)
orchestrator: OrchestratorConfig = Field(default_factory=OrchestratorConfig)
def load_config(path: str | Path = "config.yaml") -> Config:
"""Load and validate config from YAML file."""
p = Path(path)
if not p.exists():
return Config()
raw = yaml.safe_load(p.read_text()) or {}
return Config.model_validate(raw)
def parse_agent_file(path: str | Path) -> AgentFileConfig:
"""Parse a markdown agent definition with YAML frontmatter."""
text = Path(path).read_text()
if not text.startswith("---"):
return AgentFileConfig(instructions=text)
parts = text.split("---", 2)
if len(parts) < 3:
return AgentFileConfig(instructions=text)
frontmatter = yaml.safe_load(parts[1]) or {}
body = parts[2].strip()
frontmatter["instructions"] = body
return AgentFileConfig.model_validate(frontmatter)