feat: Add DAARWIZZ agent with personality

DAARWIZZ - Official AI agent for DAARION.city ecosystem

Changes:
- gateway-bot/daarwizz_prompt.txt: System prompt defining DAARWIZZ personality
- gateway-bot/http_api.py: Load and inject DAARWIZZ context into Router requests
- gateway-bot/Dockerfile: Copy DAARWIZZ prompt file to container
- providers/llm_provider.py: Support context.system_prompt from Gateway

Features:
- Telegram webhook sends agent='daarwizz' to Router
- System prompt loaded from file (customizable)
- LLM receives full DAARWIZZ context + RBAC
- Discord support included

Usage:
- User messages DAARWIZZ in Telegram
- Gateway enriches with system prompt + RBAC
- Router routes to LLM with full context
- DAARWIZZ responds with DAO-aware answers

Next: Set TELEGRAM_BOT_TOKEN and test first dialog
This commit is contained in:
Ivan Tytar
2025-11-15 15:31:58 +01:00
parent 244c6171a8
commit be95bbad9c
4 changed files with 187 additions and 53 deletions

View File

@@ -2,21 +2,29 @@
FROM python:3.11-slim
LABEL maintainer="DAARION.city Team"
LABEL description="Bot Gateway - Telegram/Discord webhook handler"
LABEL description="Bot Gateway - Telegram/Discord webhook handler with DAARWIZZ"
LABEL version="0.2.0"
WORKDIR /app
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
COPY ../requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy requirements and install
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt httpx
COPY . .
# Copy gateway code
COPY gateway-bot/ /app/gateway-bot/
# Copy DAARWIZZ system prompt
COPY gateway-bot/daarwizz_prompt.txt /app/gateway-bot/daarwizz_prompt.txt
EXPOSE 9300
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:9300/health || exit 1
ENV DAARWIZZ_NAME=DAARWIZZ
ENV DAARWIZZ_PROMPT_PATH=/app/gateway-bot/daarwizz_prompt.txt
CMD ["python", "-m", "gateway_bot.main", "--host", "0.0.0.0", "--port", "9300"]

View File

@@ -0,0 +1,37 @@
Ти — DAARWIZZ, офіційний AI-агент екосистеми DAARION.city.
Твоя роль:
1. Допомагати учасникам спільноти розібратися з microDAO, ролями, правами доступу і DAO-процесами.
2. Пояснювати просто, конкретно і практично. Уникай довгої теорії, одразу давай корисні кроки.
3. Орієнтуватися на безпеку: не розкривати інформацію або дії, якщо у користувача немає відповідних прав (entitlements).
4. Якщо не знаєш точної відповіді — чесно це скажи і запропонуй, що можна зробити/кого запитати.
Контекст:
- Ти працюєш всередині системи DAGI Stack (Router + DevTools + CrewAI + microDAO RBAC).
- Ти отримуєш від Gateway інформацію про: dao_id, user_id, roles[], entitlements[].
- Ти відповідаєш від імені системи, але не прикидаєшся людиною.
Обмеження:
- Не давай юридичних, фінансових або медичних порад.
- Не вигадуй факти про DAO або токеноміку, яких немає в офіційних документах DAARION/microDAO.
- Якщо питання виходить за межі DAARION/microDAO, дай коротку відповідь і поверни розмову в контекст спільноти.
Стиль:
- Дружній, але професійний.
- Короткі абзаци, без води.
- Якщо користувач просить інструкцію — давай її покроково, нумерованим списком.
Про DAARION:
- DAARION.city — це екосистема децентралізованих автономних організацій (microDAO).
- Кожен microDAO має свою структуру ролей: admin, member, contributor, guest.
- Система RBAC (Role-Based Access Control) контролює доступ до функцій.
- Учасники можуть голосувати за пропозиції, виконувати завдання, отримувати винагороди.
Твої можливості:
- Відповідати на запитання про DAO, ролі, процеси
- Пояснювати, як використовувати систему
- Допомагати новим учасникам (onboarding)
- Перевіряти права доступу перед відповіддю
- Запускати workflows (через mode=crew) для складних завдань
Якщо користувач запитує про конкретний microDAO, використовуй інформацію з dao_id та rbac контексту.

View File

@@ -3,6 +3,8 @@ Bot Gateway HTTP API
Handles incoming webhooks from Telegram, Discord, etc.
"""
import logging
import os
from pathlib import Path
from typing import Dict, Any, Optional
from datetime import datetime
@@ -16,6 +18,36 @@ logger = logging.getLogger(__name__)
router = APIRouter()
# ========================================
# DAARWIZZ Configuration
# ========================================
DAARWIZZ_NAME = os.getenv("DAARWIZZ_NAME", "DAARWIZZ")
DAARWIZZ_PROMPT_PATH = os.getenv(
"DAARWIZZ_PROMPT_PATH",
str(Path(__file__).parent / "daarwizz_prompt.txt"),
)
def load_daarwizz_prompt() -> str:
"""Load DAARWIZZ system prompt from file"""
try:
p = Path(DAARWIZZ_PROMPT_PATH)
if not p.exists():
logger.warning(f"DAARWIZZ prompt file not found: {DAARWIZZ_PROMPT_PATH}")
return f"Ти — {DAARWIZZ_NAME}, AI-агент екосистеми DAARION.city. Допомагай учасникам з DAO-процесами."
prompt = p.read_text(encoding="utf-8")
logger.info(f"DAARWIZZ system prompt loaded ({len(prompt)} chars)")
return prompt
except Exception as e:
logger.error(f"Error loading DAARWIZZ prompt: {e}")
return f"Ти — {DAARWIZZ_NAME}, AI-агент екосистеми DAARION.city."
DAARWIZZ_SYSTEM_PROMPT = load_daarwizz_prompt()
# ========================================
# Request Models
# ========================================
@@ -90,38 +122,45 @@ async def telegram_webhook(update: TelegramUpdate):
logger.info(f"Telegram message from {username} (tg:{user_id}) in chat {chat_id}: {text[:50]}")
# Build request to Router
# Build request to Router with DAARWIZZ context
router_request = {
"prompt": text,
"mode": "chat",
"source": "telegram",
"dao_id": dao_id,
"user_id": f"tg:{user_id}",
"session_id": f"tg:{chat_id}:{dao_id}",
"message": text,
"payload": {
"message": text,
"agent": "daarwizz", # DAARWIZZ agent identifier
"metadata": {
"source": "telegram",
"dao_id": dao_id,
"user_id": f"tg:{user_id}",
"session_id": f"tg:{chat_id}:{dao_id}",
"username": username,
"chat_id": chat_id,
"timestamp": datetime.now().isoformat()
}
},
"context": {
"agent_name": DAARWIZZ_NAME,
"system_prompt": DAARWIZZ_SYSTEM_PROMPT,
# RBAC context will be injected by Router
},
}
# Send to Router
router_response = await send_to_router(router_request)
logger.info(f"Sending to Router: agent=daarwizz, dao={dao_id}, user=tg:{user_id}")
response = await send_to_router(router_request)
# TODO: Send response back to Telegram via Bot API
# For now, just return the router response
# Extract response text
if isinstance(response, dict):
answer_text = response.get("response", "Вибач, я зараз не можу відповісти.")
else:
answer_text = "Вибач, сталася помилка."
logger.info(f"Router response: {answer_text[:100]}")
# Send response back to Telegram
await send_telegram_message(chat_id, answer_text)
return {"ok": True, "agent": "daarwizz"}
return {
"status": "ok",
"processed": True,
"router_response": router_response
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Telegram webhook error: {e}")
logger.error(f"Error handling Telegram webhook: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@@ -151,50 +190,87 @@ async def discord_webhook(message: DiscordMessage):
user_id = author.get("id", "unknown")
username = author.get("username", "")
# Get DAO ID for this guild/channel
dao_id = get_dao_id(guild_id, "discord")
# Get DAO ID for this channel
dao_id = get_dao_id(channel_id, "discord")
logger.info(f"Discord message from {username} (dc:{user_id}) in guild {guild_id}: {text[:50]}")
logger.info(f"Discord message from {username} (discord:{user_id}): {text[:50]}")
# Build request to Router
# Build request to Router with DAARWIZZ context
router_request = {
"prompt": text,
"mode": "chat",
"source": "discord",
"dao_id": dao_id,
"user_id": f"dc:{user_id}",
"session_id": f"dc:{channel_id}:{dao_id}",
"message": text,
"payload": {
"message": text,
"agent": "daarwizz",
"metadata": {
"source": "discord",
"dao_id": dao_id,
"user_id": f"discord:{user_id}",
"session_id": f"discord:{channel_id}:{dao_id}",
"username": username,
"channel_id": channel_id,
"guild_id": guild_id,
"timestamp": datetime.now().isoformat()
}
},
"context": {
"agent_name": DAARWIZZ_NAME,
"system_prompt": DAARWIZZ_SYSTEM_PROMPT,
},
}
# Send to Router
router_response = await send_to_router(router_request)
response = await send_to_router(router_request)
# TODO: Send response back to Discord via Bot API
# Extract response text
if isinstance(response, dict):
answer_text = response.get("response", "Sorry, I can't respond right now.")
else:
answer_text = "Sorry, an error occurred."
logger.info(f"Router response: {answer_text[:100]}")
# TODO: Send response back to Discord
# await send_discord_message(channel_id, answer_text)
return {"ok": True, "agent": "daarwizz", "response": answer_text}
return {
"status": "ok",
"processed": True,
"router_response": router_response
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Discord webhook error: {e}")
logger.error(f"Error handling Discord webhook: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
# ========================================
# Helper Functions
# ========================================
async def send_telegram_message(chat_id: str, text: str):
"""Send message to Telegram chat"""
import httpx
telegram_token = os.getenv("TELEGRAM_BOT_TOKEN")
if not telegram_token:
logger.error("TELEGRAM_BOT_TOKEN not set")
return
url = f"https://api.telegram.org/bot{telegram_token}/sendMessage"
payload = {
"chat_id": chat_id,
"text": text,
"parse_mode": "Markdown",
}
try:
async with httpx.AsyncClient() as client:
response = await client.post(url, json=payload, timeout=10.0)
response.raise_for_status()
logger.info(f"Telegram message sent to chat {chat_id}")
except Exception as e:
logger.error(f"Error sending Telegram message: {e}")
@router.get("/health")
async def health():
"""Health check endpoint"""
return {
"status": "healthy",
"service": "bot-gateway"
"agent": DAARWIZZ_NAME,
"system_prompt_loaded": len(DAARWIZZ_SYSTEM_PROMPT) > 0,
"timestamp": datetime.utcnow().isoformat(),
}

View File

@@ -156,8 +156,20 @@ class LLMProvider(Provider):
)
def _get_system_prompt(self, req: RouterRequest) -> Optional[str]:
"""Get system prompt based on agent"""
# This can be enhanced to load from config
"""Get system prompt based on agent or context"""
# 1. Check if context.system_prompt provided (e.g., from Gateway)
context = req.payload.get("context") or {}
if "system_prompt" in context:
return context["system_prompt"]
# 2. Agent-specific system prompts
if req.agent == "daarwizz":
return (
"Ти — DAARWIZZ, офіційний AI-агент екосистеми DAARION.city. "
"Допомагай учасникам з microDAO, ролями та процесами. "
"Відповідай коротко, практично, враховуй RBAC контекст користувача."
)
if req.agent == "devtools":
return (
"Ти - DevTools Agent в екосистемі DAARION.city. "
@@ -165,4 +177,5 @@ class LLMProvider(Provider):
"рефакторингом та написанням тестів. "
"Відповідай коротко, конкретно, з прикладами коду коли потрібно."
)
return None