teleo-codex/ops/pipeline-v2/telegram/agent_config.py
m3taversal 7bfce6b706 commit telegram bot module from VPS — 20 files never previously in repo
Pulled from /opt/teleo-eval/telegram/ on VPS. Includes:
- bot.py (92K), kb_retrieval.py, kb_tools.py (agentic retrieval)
- retrieval.py (RRF merge, query decomposition, entity traversal)
- response.py (system prompt builder, response parser)
- agent_config.py, agent_runner.py (multi-agent template unit support)
- approval_stages.py, approvals.py, digest.py (approval workflow)
- eval_checks.py, eval.py (response quality checks)
- output_gate.py, x_publisher.py, x_client.py, x_search.py (X pipeline)
- market_data.py, worktree_lock.py (utilities)
- rio.yaml, theseus.yaml (agent configs)

These files were deployed to VPS but never committed to the repo.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:02:32 +02:00

160 lines
5.2 KiB
Python

#!/usr/bin/env python3
"""Agent config loader and validator.
Loads YAML config files from telegram/agents/*.yaml, validates required fields,
resolves file paths. Used by bot.py and future agent_runner.py.
Epimetheus owns this module.
"""
import logging
import os
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
logger = logging.getLogger("tg.agent_config")
SECRETS_DIR = "/opt/teleo-eval/secrets"
WORKTREE_DIR = "/opt/teleo-eval/workspaces/main"
REQUIRED_FIELDS = ["name", "handle", "bot_token_file", "pentagon_agent_id", "domain"]
REQUIRED_VOICE_FIELDS = ["voice_summary", "voice_definition"]
REQUIRED_KB_FIELDS = ["kb_scope"]
@dataclass
class AgentConfig:
"""Validated agent configuration loaded from YAML."""
name: str
handle: str
x_handle: Optional[str]
bot_token_file: str
pentagon_agent_id: str
domain: str
kb_scope_primary: list[str]
voice_summary: str
voice_definition: str
domain_expertise: str
learnings_file: str
opsec_additional_patterns: list[str] = field(default_factory=list)
response_model: str = "anthropic/claude-opus-4-6"
triage_model: str = "anthropic/claude-haiku-4.5"
max_tokens: int = 1024
max_response_per_user_per_hour: int = 30
def to_dict(self) -> dict:
"""Convert to dict for passing to build_system_prompt."""
return {
"name": self.name,
"handle": self.handle,
"x_handle": self.x_handle,
"domain": self.domain,
"voice_definition": self.voice_definition,
"voice_summary": self.voice_summary,
"domain_expertise": self.domain_expertise,
"pentagon_agent_id": self.pentagon_agent_id,
}
@property
def bot_token_path(self) -> str:
return os.path.join(SECRETS_DIR, self.bot_token_file)
@property
def learnings_path(self) -> str:
return os.path.join(WORKTREE_DIR, self.learnings_file)
@property
def handle_regex(self) -> re.Pattern:
"""Regex matching this agent's @handle with optional @botname suffix."""
clean = self.handle.lstrip("@")
return re.compile(rf"@{re.escape(clean)}(?:@\w+)?", re.IGNORECASE)
def load_agent_config(config_path: str) -> AgentConfig:
"""Load and validate an agent YAML config file.
Raises ValueError on validation failure.
"""
import yaml
with open(config_path) as f:
raw = yaml.safe_load(f)
errors = []
# Required fields
for fld in REQUIRED_FIELDS + REQUIRED_VOICE_FIELDS:
if fld not in raw or not raw[fld]:
errors.append(f"Missing required field: {fld}")
# KB scope
kb_scope = raw.get("kb_scope", {})
if not isinstance(kb_scope, dict) or "primary" not in kb_scope:
errors.append("Missing kb_scope.primary (list of primary domain dirs)")
elif not isinstance(kb_scope["primary"], list) or len(kb_scope["primary"]) == 0:
errors.append("kb_scope.primary must be a non-empty list")
# Learnings file
if "learnings_file" not in raw:
errors.append("Missing required field: learnings_file")
if errors:
raise ValueError(
f"Agent config validation failed ({config_path}):\n"
+ "\n".join(f" - {e}" for e in errors)
)
return AgentConfig(
name=raw["name"],
handle=raw["handle"],
x_handle=raw.get("x_handle"),
bot_token_file=raw["bot_token_file"],
pentagon_agent_id=raw["pentagon_agent_id"],
domain=raw["domain"],
kb_scope_primary=kb_scope["primary"],
voice_summary=raw["voice_summary"],
voice_definition=raw["voice_definition"],
domain_expertise=raw.get("domain_expertise", ""),
learnings_file=raw["learnings_file"],
opsec_additional_patterns=raw.get("opsec_additional_patterns", []),
response_model=raw.get("response_model", "anthropic/claude-opus-4-6"),
triage_model=raw.get("triage_model", "anthropic/claude-haiku-4.5"),
max_tokens=raw.get("max_tokens", 1024),
max_response_per_user_per_hour=raw.get("max_response_per_user_per_hour", 30),
)
def validate_agent_config(config_path: str) -> list[str]:
"""Validate config file and check runtime dependencies.
Returns list of warnings (empty = all good).
Raises ValueError on hard failures.
"""
config = load_agent_config(config_path)
warnings = []
# Check bot token file exists
if not os.path.exists(config.bot_token_path):
warnings.append(f"Bot token file not found: {config.bot_token_path}")
# Check primary KB dirs exist
for d in config.kb_scope_primary:
full = os.path.join(WORKTREE_DIR, d)
if not os.path.isdir(full):
warnings.append(f"KB scope dir not found: {full}")
# Check learnings file parent dir exists
learnings_dir = os.path.dirname(config.learnings_path)
if not os.path.isdir(learnings_dir):
warnings.append(f"Learnings dir not found: {learnings_dir}")
# Validate OPSEC patterns compile
for i, pattern in enumerate(config.opsec_additional_patterns):
try:
re.compile(pattern, re.IGNORECASE)
except re.error as e:
warnings.append(f"Invalid OPSEC regex pattern [{i}]: {e}")
return warnings