# Trinity - Autonomous Agent Orchestration Platform - Architecture > **ARCHIVED 2026-06-11** — superseded by the restructured `docs/memory/architecture.md` (same content, deduplicated into Cross-Cutting Subsystem blocks). Kept for reference only; do not update. > **Purpose**: Documents the CURRENT system design. Update only when implementing changes. ## System Overview **Trinity** is an **autonomous agent orchestration and infrastructure platform** — sovereign infrastructure for deploying, orchestrating, and governing fleets of autonomous AI agents on your own hardware. Each agent runs as an isolated Docker container with standardized interfaces for credentials, tools, and MCP server integrations. ``` ┌─────────────────────────────────────────────────────────────────────────────┐ │ Trinity Agent Platform │ ├─────────────────────────────────────────────────────────────────────────────┤ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Frontend │ │ Backend │ │ MCP Server │ │ Vector │ │ │ │ (Vue.js) │ │ (FastAPI) │ │ (FastMCP) │ │ (Logs) │ │ │ │ :80 │ │ :8000 │ │ :8080 │ │ :8686 │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ │ └─────────────────┼─────────────────┼─────────────────┘ │ │ │ │ │ │ ┌──────┴──────┐ ┌──────┴──────┐ │ │ │ Redis │ │ Docker │ │ │ │ :6379 │ │ Engine │ │ │ └─────────────┘ └──────┬──────┘ │ │ │ │ │ ┌───────────────────────────────────┼───────────────────────────┐ │ │ │ │ │ │ │ ┌────┴────┐ ┌─────────┐ ┌─────────┴┐ ┌─────────┐ │ │ │ │ Agent 1 │ │ Agent 2 │ │ Agent 3 │ │ Agent N │ │ │ │ │ :8000 │ │ :8000 │ │ :8000 │ │ :8000 │ │ │ │ └─────────┘ └─────────┘ └──────────┘ └─────────┘ │ │ │ Agent Network (172.28.0.0/16) │ │ └─────────────────────────────────────────────────────────────────────────────┘ ``` ## Technology Stack ### Frontend | Technology | Version | Purpose | |------------|---------|---------| | Vue.js | 3.x | UI framework (Composition API) | | Vue Flow | 1.48.0 | Node-based graph visualization | | Tailwind CSS | 3.x | Styling | | Pinia | 2.x | State management | | Vite | 5.x | Build system | ### Backend | Technology | Version | Purpose | |------------|---------|---------| | FastAPI | 0.100+ | REST API framework | | Python | 3.11 | Runtime | | Docker SDK | 7.x | Container management | | SQLite | 3.x | Relational data persistence | | Redis | 7.x | Secrets/cache storage | | httpx | 0.24+ | Async HTTP client | ### Agent Runtime | Technology | Version | Purpose | |------------|---------|---------| | Python | 3.11 | Primary runtime | | Node.js | 20 | JavaScript runtime | | Go | 1.21 | Go runtime | | Claude Code | Latest | AI agent | ### Infrastructure | Technology | Purpose | |------------|---------| | Docker | Container orchestration | | nginx | Reverse proxy (production) | | Cloudflare Tunnel | Public endpoint access (webhooks, public chat) | | Tailscale | Private VPN access | | GCP | Cloud hosting | | Vertex AI Search | Documentation Q&A (public endpoint) | --- ## Component Details ### Backend (`src/backend/`) **Modular Architecture (refactored 2025-11-29):** | Module | Purpose | |--------|---------| | `main.py` | FastAPI app initialization, WebSocket manager, router mounting | | `config.py` | Centralized configuration constants | | `models.py` | All Pydantic request/response models | | `dependencies.py` | FastAPI dependencies (auth, token validation, role hierarchy, agent access control) | | `database.py` | SQLite persistence facade — orchestrates 27 domain operation classes from `db/` (users, ownership, MCP keys, schedules, executions, chat, activities, subscriptions, monitoring, audit log, Slack/Telegram, payments, operator queue, skills, tags, …) | | ~~`credentials.py`~~ | **REMOVED (2026-02-05)** - CRED-002 replaced with `routers/credentials.py` file injection system | **Routers (`routers/`)** — 53 router modules: *Core Agent:* - `agents.py` - Core CRUD, start/stop, logs, stats, queue, activities, terminal (642 lines) - `agent_config.py` - Per-agent settings: autonomy, read-only, resources, capabilities, capacity, timeout, api-key - `agent_files.py` - Files, info, playbooks, permissions, metrics, shared folders, file-sharing toggle + list/revoke (FILES-001) - `loops.py` - Sequential agent loops: start/get/stop + agent-scoped list (#740) - `files.py` - Public download endpoint for outbound agent file sharing (FILES-001) - `agent_rename.py` - Rename endpoint (RENAME-001) - `agent_ssh.py` - SSH access endpoint - `credentials.py` - Credential injection/export/import (CRED-002 simplified system) - `chat.py` - Agent chat/activity monitoring - `chat/` - Chat sub-router directory - `internal.py` - Internal endpoints for agent startup, scheduler task execution (no auth) - `templates.py` - Template listing and GitHub repo fetching - `sharing.py` - Agent sharing between users - `git.py` - Git sync endpoints (status, sync, log, pull) *Auth & Security:* - `auth.py` - Authentication endpoints (admin login, email auth, token validation) - `users.py` - User management (list users, update roles) (ROLE-001) - `mcp_keys.py` - MCP API key management - `setup.py` - First-time setup wizard *Scheduling & Execution:* - `schedules.py` - Agent scheduling CRUD and control - `executions.py` - Execution list and details - `analytics.py` - Agent-scoped Overview analytics: `GET /{name}/analytics?window=` — day-bucketed execution trends grouped by `triggered_by` (#1107) *Organization & Tags:* - `tags.py` - Agent tagging - `system_views.py` - Saved system views - `systems.py` - System manifest deployment *Monitoring & Operations:* - `monitoring.py` - Fleet health monitoring (MON-001) - `telemetry.py` - Host telemetry (CPU/memory/disk) - `activities.py` - Activity timeline endpoints - `agent_dashboard.py` - Agent-defined dashboard (dashboard.yaml) - `alerts.py` - Cost threshold alerts - `notifications.py` - Agent notifications - `operator_queue.py` - Operating Room queue (OPS-001) - `ops.py` - Operating Room sync service - `logs.py` - Container log endpoints - `observability.py` - Observability data - `audit.py` - Audit trail *Public Access & Monetization:* - `public_links.py` - Public agent link management - `public.py` - Public chat endpoints - `paid.py` - x402 payment-gated chat (NVM-001) - `nevermined.py` - Nevermined payment config management - `slack.py` - Slack integration (OAuth, events, multi-agent channel routing, per-agent channel binding) (SLACK-001/002) - `telegram.py` - Telegram bot integration (webhook receiver, bot binding, group config) (TELEGRAM-001/TGRAM-GROUP) - `whatsapp.py` - WhatsApp via Twilio (webhook receiver, binding CRUD + test) (WHATSAPP-001) - `voip.py` - VoIP telephony: per-agent Twilio-voice binding CRUD (owner-only), outbound call trigger (idempotent, rate-limited), and the Media Streams WebSocket entrypoint. Feature-flag gated (`voip_available`, default OFF) (VOIP-001, #1056) - `webhooks.py` - Public webhook trigger endpoint + JWT-auth webhook management (WEBHOOK-001, #291) - `messages.py` - Proactive agent-to-user messaging (#321) - `public_memory.py` - Per-user memory write endpoint for channel sessions (MEM-001, #888) *Subscriptions & Skills:* - `subscriptions.py` - Subscription management (SUB-002) - `skills.py` - Skill CRUD and assignment - `settings.py` - Platform admin settings (includes Slack transport management: connect/disconnect/install) *Content & Files:* - `image_generation.py` - Image generation REST endpoints (IMG-001) - `avatar.py` - Agent avatar generation and serving (AVATAR-001) - `docs.py` - Documentation endpoints *System:* - `system_agent.py` - System agent management **Services (`services/`)** — 37 service modules: *Core:* - `docker_service.py` - Docker container management - `docker_utils.py` - Docker utility helpers - `template_service.py` - GitHub template cloning and processing - `agent_client.py` - HTTP client for agent container communication (chat, session, injection); Redis-backed **transport** circuit breaker (`CircuitState`, `agent:circuit:{name}`) with exponential backoff + dormant state (#631); only TCP/connection failures count toward the circuit — HTTP 4xx/5xx and 502/503/504 are treated as application errors and skip the failure counter (#474). Shared Redis plumbing (fail-open client, Lua `ScriptCache`, decode helpers) was extracted to the top-level `redis_breaker_util.py` so the dispatch breaker (#526) reuses it without duplication (RELIABILITY-007). - `settings_service.py` - Centralized settings retrieval (API keys, ops config, agent quotas) *Execution & Scheduling:* - `task_execution_service.py` - Unified task execution lifecycle (slot mgmt, activity tracking, sanitization) (EXEC-024). On reader-race empty results (502 dict body with `num_turns < 5`, `raw_message_count == 0`, `parse_failure_count == 0`), fires one in-line auto-retry with the same `execution_id` capped at 300s, persisting `retry_count` and rolling previous-attempt cost into the terminal write (#678). **Dispatch breaker outcome recording (#526):** the single execution path, so it records every outcome to `DispatchBreaker` (gated on the combined global+per-agent flag) — `record_outcome(None)` at the success terminal (resets), `record_outcome(AUTH)` gated on `error_code == AUTH` at the HTTP-error terminal (counts). On the `→open` transition it backgrounds `_fail_backlog_and_audit` via `_spawn_bg` (holds a strong task ref so the fire-and-forget drain can't be GC'd mid-flight) — `db.fail_queued_for_agent` → FAILED + clear in-memory queue + audit; if that task is still lost or its DB write throws, the 60s breaker-aware `run_maintenance` sweep re-fails the queued backlog for any still-open breaker (~60s worst case, not the 24h generic expiry). Catches `CircuitOpen` from `acquire` → `TaskExecutionResult(CIRCUIT_OPEN)` + FAILED row; the 3b pre-dispatch check also fast-fails on a non-probe-consuming dispatch `state == "open"` read, but ONLY on the backlog-drain path (`slot_already_held and not dispatch_gate_checked`) so it never blocks a probe an upstream `acquire` gate already admitted. - `capacity_manager.py` - **Unified capacity facade (#428, CAPACITY-CONSOLIDATE).** Single public API for admit/release/status across `/chat` (`max_concurrent=max_parallel_tasks`, `queue_in_memory` policy) and `/task` (`queue_persistent` policy). Composes `slot_service.py` and `backlog_service.py` internally; owns the in-memory overflow store (Redis LIST, depth 3). Replaces the prior three-class pyramid (`SlotService` + `ExecutionQueue` + `BacklogService`); `ExecutionQueue` deleted, the other two are now private internals. `acquire(... breaker_enabled=False)` gates on the dispatch breaker at the TOP of `acquire` (before the overflow branch) when both the per-agent flag and global `DISPATCH_BREAKER_ENABLED` are on. A `deny` (open within cooldown, or a sibling holds the probe) raises `CircuitOpen` before any slot/overflow work, so a doomed task is never enqueued (the no-enqueue invariant, #526 D2). When the breaker is open and the call holds the half-open **probe**, the probe is admitted ONLY into a free slot — if slots are full the probe fast-fails (`CircuitOpen`) rather than enqueuing, so the no-enqueue invariant extends across the half-open window and the probe always leads to a recorded dispatch instead of a verdict-less backlog row that would stall the breaker's backoff (#526 F1). - `slot_service.py` - Internal: atomic N-ary capacity counter (Redis ZSET) with dynamic per-agent TTL (CAPACITY-001). Used only by `CapacityManager`. - `backlog_service.py` - Internal: persistent SQLite-backed FIFO overflow store with drain-on-release (BACKLOG-001). Used only by `CapacityManager`. - `dispatch_breaker.py` - **Per-agent dispatch circuit breaker (RELIABILITY-007, #526).** Producer-side breaker fed *only* by execution outcomes in `task_execution_service` — counts **AUTH only** (`error_code == AUTH`, agent answers HTTP 503), NOT TIMEOUT/AGENT_ERROR (D10). Consecutive-failure machine (`closed → open → half-open(probe) → closed`, default threshold 3, base cooldown 30s, exp backoff) in Redis `agent:dispatch:{name}` reusing the proven `CircuitState` Lua pattern (D9). Separate namespace + separate Lua from the transport breaker, so the two never contaminate each other's counter. `record_outcome(error_code)` returns the `(prior,new)` transition; the **caller** backgrounds the drain on `→open` (no `capacity`/`db` import here → no circular dep, D3). Fail-open on Redis down; never raises. Exposes `record_failure("missed_heartbeat")` as the #307 heartbeat seam. `record_success` is a no-op write (Lua early-return) when the breaker is already closed with zero failures, so a healthy breaker-enabled agent doesn't churn Redis on every successful execution. - `scheduler_service.py` - APScheduler-based scheduling service - `cleanup_service.py` - Active watchdog reconciliation + passive stale recovery for executions, activities, and slots (CLEANUP-001, #129) *Real-time delivery:* - `event_bus.py` - Redis Streams transport for WebSocket delivery (`EventBus` publisher + `StreamDispatcher` consumer, reconnect replay via `last-event-id`, 3-failure client eviction, MAXLEN-trimmed stream) (RELIABILITY-003, #306) *Monitoring & Activities:* - `activity_service.py` - Activity tracking and timeline - `monitoring_service.py` - Fleet-wide health monitoring (MON-001) - `monitoring_alerts.py` - Alert threshold configuration - `heartbeat_service.py` - Agent push-heartbeat liveness layer (RELIABILITY-004, #307). Owns all Redis heartbeat keys; `record_heartbeat` (SETEX 15s + persistent `seen` marker), `read_heartbeat`, `heartbeat_status`/`heartbeat_status_bulk` (one pipelined round-trip, D4), `authorize_heartbeat` (Option B — only the agent's own agent-scoped MCP key), and `run_heartbeat_watch_loop`/`process_watch_tick` (5s loop, 3-miss guard, fires a cooldown-debounced operator alert via `monitoring_alerts` on the alive→stale transition; writes no health row). Additive to the 30s `monitoring_service.py`, which stays authoritative. - `operator_queue_service.py` - Operating Room sync with agent containers (OPS-001) *Auth & Credentials:* - `credential_encryption.py` - AES-256-GCM encryption for .credentials.enc files (CRED-002) - `subscription_service.py` - Subscription management (SUB-002) - `ssh_service.py` - Ephemeral SSH credential generation - `email_service.py` - Email sending for verification codes *Git & GitHub:* - `git_service.py` - Git sync operations for GitHub-native agents; persistent-state allowlist primitive (S4, #383) - `github_service.py` - GitHub API client (repo creation, validation, org detection) *Integrations:* - `slack_service.py` - Slack API client (OAuth, messaging, verification) (SLACK-001) - `nevermined_payment_service.py` - x402 payment verification and settlement (NVM-001) - `proactive_message_service.py` - Agent-to-user proactive messaging with rate limiting and audit (#321) - `agent_shared_files_service.py` - Outbound file sharing: path validation, MIME blocklist, quota, Docker `get_archive` extraction, URL building (FILES-001) - `loop_service.py` - Sequential agent loops: in-process `asyncio.Task` runner, cooperative stop, template substitution, WS events (`loop_run_completed`, `loop_completed`) (#740) - `voip_service.py` - VoIP outbound-call orchestration (VOIP-001, #1056): gate checks (flag/binding) + abuse controls (rate-limit per owner+destination, durable per-agent daily cap), stages a Gemini session intent in Redis keyed by a `call_id` (distinct from the `vs_` VoiceSession id), mints a call-bound WSS ticket, calls Twilio `calls.create()`, and dispatches the post-call transcript to the **main agent** via `task_execution_service.execute_task(triggered_by="voip")` (default ON). Never calls `connect_and_stream` (cross-worker safety — the WS handler does). **Channel Adapters (`adapters/`)** — Pluggable external messaging (SLACK-002): *Core:* - `base.py` - `ChannelAdapter` ABC, `NormalizedMessage`, `ChannelResponse` models - `message_router.py` - `ChannelMessageRouter`: rate limiting, agent resolution, execution pipeline; injects MEM-001 per-user memory into `execute_task(system_prompt=…)` gated on `verified_email and not is_group` (#895) *Slack:* - `slack_adapter.py` - Slack adapter: DMs, @mentions, thread replies, agent identity via `chat:write.customize` - `transports/slack_socket.py` - Socket Mode transport: N concurrent WebSockets per `SLACK_SOCKET_CONNECTION_COUNT` env var (default 2, range 1–10), per-client watchdog, envelope-ID dedup ring against possible cross-connection duplicate delivery (#244) - `transports/slack_webhook.py` - HTTP webhook transport (fallback for production) *Telegram:* - `telegram_adapter.py` - Telegram adapter: DMs, group chats (@mention/observe modes), voice transcription, /login flow - `transports/telegram_webhook.py` - Telegram Bot API webhook (inbound POST + setWebhook registration) *WhatsApp (via Twilio):* - `whatsapp_adapter.py` - WhatsApp adapter: DMs via Twilio (WHATSAPP-001); media with SSRF-gated downloads; `/login`/`/logout`/`/whoami` command handlers + markdown→WhatsApp syntax conversion (#467) - `transports/twilio_webhook.py` - Twilio webhook transport: HMAC-SHA1 signature (via `twilio.request_validator`), MessageSid dedup, form-encoded body *VoIP Telephony (via Twilio Media Streams) — a voice transport, NOT a text `ChannelAdapter` (VOIP-001, #1056):* - `transports/twilio_media_stream.py` - Media Streams WS bridge (`handle_media_stream`): `accept()`-then-authenticate — Twilio does NOT forward the `` query string, so the call-bound ticket arrives as a `` in the first `start` frame (`start.customParameters.ticket`), read only after the handshake completes (#1073); a query-string `?ticket=` is still honored as a fallback for non-Twilio/diagnostic clients. Then scope check, `GETDEL` staged intent (consume-once), creates the Gemini `VoiceSession` on the connecting worker, runs the unmodified `connect_and_stream`. Per-connection `_CallBridge`: inbound μ-law→PCM resample, outbound queue + paced 20ms 160-byte μ-law sender, `clear`-on-barge-in, `streamSid` capture, teardown ties Gemini-end→Twilio-close + SETNX-guarded single transcript save (`source="voice"`) + post-call processing dispatch. - `transports/voip_audio.py` - Pure stdlib-`audioop` codec helpers (`ulaw8k_to_pcm16k`, `pcm24k_to_ulaw8k` direct 3:1, `pop_frames`). Carries per-direction `ratecv` state across chunks (anti-click). `audioop-lts` pinned for Python ≥ 3.13. *Database:* - `db/slack_channels.py` - Workspace connections (encrypted bot tokens), channel-agent bindings, active threads - `db/telegram_channels.py` - Telegram bindings (encrypted bot tokens), group configs, chat links - `db/whatsapp_channels.py` - WhatsApp (Twilio) bindings (encrypted AuthToken), chat links, verified-email read/write/by-email lookup (#467 Phase 2) - `db/voip.py` - VoIP `voip_bindings` (encrypted Twilio-voice AuthToken, `from_number`, `inbound_number` [Phase 2], `daily_call_cap`) + `voip_call_logs` lifecycle + durable daily-cap window count (VOIP-001) *Content & Media:* - `image_generation_service.py` - Platform image generation via Gemini (prompt refinement + image gen) (IMG-001) - `image_generation_prompts.py` - Best practices prompts for image generation use cases (IMG-001) *Skills & System:* - `skill_service.py` - Skill CRUD and injection - `system_agent_service.py` - System agent lifecycle management - `system_service.py` - System manifest operations - `log_archive_service.py` - Log archival - `archive_storage.py` - Archive storage backend **Logging (`logging_config.py`):** - Structured JSON logging for production - Captured by Vector via Docker stdout/stderr - OpenTelemetry trace ID included in log entries for log-trace correlation (RELIABILITY-002) **OpenTelemetry Tracing (`main.py`):** - Auto-instrumentation for FastAPI, httpx, and Redis (RELIABILITY-002) - `traceparent` header propagated through inter-agent calls - Traces exported to OTel Collector via OTLP/gRPC (`trinity-otel-collector:4317`) - Configurable sampling via `OTEL_SAMPLE_RATE` (default 10%) - Enabled via `OTEL_ENABLED=1` environment variable **Utilities (`utils/`):** - `helpers.py` - Shared helper functions **Docker Integration:** - Uses `docker-py` SDK - Containers labeled with `trinity.*` prefix - Docker is the source of truth (no in-memory registry) ### Frontend (`src/frontend/`) **Key Directories:** - `src/views/` - Page components (Dashboard, Agents, Templates, Settings, AgentCollaboration) - `src/stores/` - Pinia state (agents.js, auth.js, collaborations.js) - `src/components/` - Reusable UI components (NavBar, CredentialsPanel, AgentNode) - `src/utils/` - WebSocket client, helpers **State Management:** - `stores/agents.js` - Agent CRUD, chat, activity - `stores/auth.js` - Email/admin authentication + JWT - `stores/collaborations.js` - Collaboration graph state, WebSocket integration - `stores/loops.js` - Sequential agent loops UI state, agent-scoped, WebSocket-driven live progress (#1106) - `stores/executions.js` - Fleet execution list/stats **+ agent Overview analytics** (`fetchAgentAnalytics`, cached per `${name}:${window}`, never polled) (#1107) **Top-nav IA — Operations (#1109):** the former separate **Health** (`/monitoring`), **Ops** (`/operating-room`), and **Executions** (`/executions`) top-nav entries are consolidated into a single **Operations** entry (`views/Operations.vue`, route `/operations`) — a `?tab=`-driven tabbed view: **Needs Response · Notifications · Health · Executions · Resolved**. Health/Executions content lives in tab-embeddable `components/MonitoringPanel.vue` / `ExecutionsPanel.vue` (extracted from the deleted standalone views, mirroring the #1107 `*Panel.vue` pattern); each tab is toggled by `v-if` so its store-owned polling tears down on tab-leave. The **Health** tab is admin-gated at the tab level (`authStore.role === 'admin'`) and non-admin `?tab=health` deep links are coerced to the default tab. The NavBar carries **one** unified badge (pending operator-queue + notifications, with critical-pulse) — the separate executions running-count badge is dropped (running count lives inside the Executions tab). Legacy `/monitoring`, `/executions`, `/operating-room`, `/events` routes redirect (function-form, query-preserving) to the matching Operations tab. Pure frontend IA change; no backend endpoints change. The per-execution detail route (`/agents/:name/executions/:executionId`, `ExecutionDetail.vue`) is unchanged. **Tab overflow — `OverflowTabs.vue` (#1114):** the Agent Detail tab strip (`views/AgentDetail.vue`, the `