Multi-agent Telegram bot architecture with PostgreSQL-native state. Claude Skill.
SaferSkills independently audited telegram-agent-router (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A Telegram bot with multiple specialists should be designed as a message router with durable state, not as one giant prompt.
The architecture in this repository optimizes for:
Telegram update
→ webhook authenticity + update validation
→ durable dedupe/enqueue
→ successful acknowledgment
→ worker
→ active-session lookup/create
→ command/callback/session-lock fast path
→ bounded intent classification only if needed
→ agent contract
→ response validation
→ Telegram send with Retry-After handling
→ memory/audit writes under retention policyDo not run a slow LLM call as the only work inside the webhook request path. Telegram can retry unsuccessful/interrupted deliveries; deduplicate using update_id before executing expensive work.
Prefer deterministic routing before an LLM classifier:
Low confidence should not silently become a high-impact action. Ask for clarification or route to a bounded fallback.
Every agent must expose a narrow interface and explicit capability boundary.
from dataclasses import dataclass, field
from typing import Any
@dataclass(frozen=True)
class AgentContext:
bot_id: str
user_id: int
chat_id: int
session_id: str
message: str
recent_history: tuple[dict[str, Any], ...] = ()
user_memory: tuple[dict[str, Any], ...] = ()
active_flow: str | None = None
@dataclass
class AgentResponse:
text: str
agent_name: str
memory_writes: list[dict[str, Any]] = field(default_factory=list)
follow_up_job: dict[str, Any] | None = None
lock_flow: str | None = None
unlock_flow: bool = FalseTool access is not inherited just because one agent hands work to another. The dispatcher must select an agent and the runtime must enforce that agent's own permissions.
The executable PostgreSQL schema is schema/telegram_agent_router.sql.
Key design choices:
bot_id scopes every tenant/bot-owned row;app.bot_id is missing;get_or_create_session() actually inserts a session when none exists;update_id is unique per bot for webhook dedupe;FOR UPDATE SKIP LOCKED;PostgreSQL suitability still depends on measured workload. A durable broker/stream or external store remains justified when required semantics or isolation exceed the PostgreSQL design.
When specialists must exchange work reliably across process restarts, keep logical agent identity separate from the current execution lease. Apply schema/durable_coordination.sql after the base schema to add:
The delivery contract is at-least-once until acknowledgement. A peer request never expands the receiving agent's own capability boundary. Proactive/scheduled sends are distinct from reply-linked sends; do not apply reply-only freshness guards to unrelated outbound work.
Do not hard-code one universal “30 messages/sec” limiter for all traffic classes.
Current Telegram guidance distinguishes at least:
Always honor API 429 responses and retry_after. Treat numeric platform limits as upstream facts that can change; see references/telegram-constraints.md and VERIFICATION.md.
sendMessage accepts text up to the Bot API's documented character limit. Chunk only at safe boundaries and validate after entity parsing/formatting. The executable reference implementation includes a deterministic splitter in examples/router_contract.py.
For supported private-chat scenarios, Telegram also exposes draft-streaming methods such as sendMessageDraft. Treat draft streaming as optional UX; the durable final message still follows the normal send path and current upstream constraints must be checked.
Conversation content, user IDs, inferred preferences and dispatch logs can be personal data.
A production-capable design needs more than a queue table:
retry_after support;Before claiming the architecture works:
python scripts/validate_markdown.pypython scripts/validate_skill.pypython -m unittest discover -s tests -p 'test_*.py'schema/telegram_agent_router.sql and tests/runtime.sql against PostgreSQL 18 or the production target major;NOT_PROVEN.references/telegram-constraints.mdreferences/agent-design.mdreferences/router-patterns.mdreferences/anti-patterns.mdreferences/privacy-security.mdreferences/SKILL.pre-2026-08-15.mdVERIFICATION.mdevals/cases.jsonl~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.