TWDAgentsHub

AI Orchestrator SaaS for Vietnamese SMEs — Team Overview Report

1 — Project Overview

A multi-tenant AI operations assistant that lets Vietnamese SME employees, accountants, managers, and CEOs interact via natural language to query ERP data, automate workflows, and receive proactive intelligence.

Inspired by OpenClaw (TS, local-first) and GoClaw (Go, enterprise) architectures. Built as a pnpm monorepo (Turborepo) with 3 apps: NestJS API + Admin UI (shadcn/ui) + Chat Widget (shadcn/ui). Shared types & Tailwind preset across all apps.

6
Layers
5
AI Agents
3
Channels (MVP)
3
Apps (Monorepo)
8w
Timeline
3-5
Developers
2 — System Architecture
graph TD
  subgraph Users["Users - Natural Language"]
    U1["Employee"]
    U2["Accountant"]
    U3["Manager"]
    U4["CEO"]
  end

  subgraph Channels["Channel Adapters"]
    CH1["REST API"]
    CH2["Web Chat"]
    CH3["Telegram"]
  end

  subgraph Orchestrator["AI Orchestrator - Central Brain"]
    O1["Intent Classifier"]
    O2["Plan Generator"]
    O3["Dispatcher"]
    O4["Aggregator"]
    O5["Verifier"]
  end

  subgraph Agents["Agent Layer - Domain Plugins"]
    A1["Finance"]
    A2["HR"]
    A3["Sales"]
    A4["Supply"]
    A5["Compliance"]
  end

  subgraph ERP["ERP Connector Layer"]
    E1["REST Client"]
    E2["Circuit Breaker"]
    E3["Redis Cache"]
  end

  subgraph Proactive["Proactive Intelligence"]
    P1["Cron Scheduler"]
    P2["Anomaly Detector"]
    P3["Alert Dispatch"]
  end

  subgraph Output["Output Layer"]
    OUT1["PDF/Excel Reports"]
    OUT2["Action Executor"]
    OUT3["Audit Trail"]
  end

  Users --> Channels
  Channels -->|"UnifiedMessage"| Orchestrator
  Orchestrator -->|"AgentTask"| Agents
  Agents -->|"ERPRequest"| ERP
  Proactive -->|"ProactiveAlert"| Channels
  Agents -->|"AgentResult"| Output
  ERP -->|"ERPResponse"| Agents

  classDef userCls fill:#0891b211,stroke:#0891b2,stroke-width:1.5px
  classDef chanCls fill:#05966911,stroke:#059669,stroke-width:1.5px
  classDef orchCls fill:#d9770611,stroke:#d97706,stroke-width:1.5px
  classDef agentCls fill:#0891b211,stroke:#0891b2,stroke-width:1.5px
  classDef erpCls fill:#be185d11,stroke:#be185d,stroke-width:1.5px
  classDef proCls fill:#65a30d11,stroke:#65a30d,stroke-width:1.5px
  classDef outCls fill:#dc262611,stroke:#dc2626,stroke-width:1.5px

  class U1,U2,U3,U4 userCls
  class CH1,CH2,CH3 chanCls
  class O1,O2,O3,O4,O5 orchCls
  class A1,A2,A3,A4,A5 agentCls
  class E1,E2,E3 erpCls
  class P1,P2,P3 proCls
  class OUT1,OUT2,OUT3 outCls
    

Monorepo Structure (pnpm workspaces + Turborepo)

twd-agents-hub/ ├── pnpm-workspace.yaml ├── turbo.json ├── packages/ │ └── shared-types/ # @twd/shared-types — UnifiedMessage, AgentTask, ERPRequest, AuditEntry ├── tooling/ │ ├── tailwind-config/ # @twd/tailwind-config — shared theme, colors, fonts │ ├── tsconfig/ # base.json, nestjs.json, react.json │ └── eslint-config/ └── apps/ ├── api/ # @twd/api — NestJS backend (all 6 layers) ├── admin/ # @twd/admin — React SPA + shadcn/ui + TanStack Table └── chat-widget/ # @twd/chat-widget — Embeddable React + shadcn/ui + Socket.io
apps/api

NestJS 11 backend. All 6 layers. Serves admin static files at /admin/*

apps/admin

React + Vite + shadcn/ui. Settings, audit logs, usage charts, user mgmt. TanStack Table + recharts.

apps/chat-widget

Embeddable React widget. Socket.io. Builds as UMD bundle (<100KB). Shared Tailwind preset.

3 — Layer Scopes

Layer 1: Channel Adapters

REST API Web Chat Telegram Zalo (post-MVP)

Normalize messages from different channels into UnifiedMessage format. Each adapter implements ChannelAdapter interface.

// Input (from channels) channel: 'telegram' | 'web' | 'api' raw payload: webhook/websocket/HTTP
// Output (to Orchestrator) UnifiedMessage { id, tenantId, userId, channel, content, attachments, locale, metadata: { conversationId } }

Layer 2: AI Orchestrator (Central Brain)

Intent Classification Plan Generation Agent Dispatch Result Aggregation Self-Verification

Understands user intent, creates execution plans, coordinates domain agents, aggregates results, and verifies response quality. Uses tiered LLM strategy: fast model for internal steps, smart model for agent execution.

// Input OrchestratorInput { message: UnifiedMessage, conversationHistory: Message[], tenantContext: { tenantId, config, userRole, userPermissions } }
// Output OrchestratorOutput { response: ChannelResponse, plan: ExecutionPlan, audit: AuditEntry, metrics: { totalDuration, llmTokensUsed } }

Layer 3: Agent Layer (Domain Plugins)

Finance HR Sales Supply Compliance

Plugin-based domain agents with 3-layer behavior system: Base (code, locked safety rules) + Soul (DB, tenant-customizable personality/rules/workflows) + Runtime (per-request user context). Tenants customize agents via admin UI soul editor — similar to GoClaw's SOUL.md but multi-tenant.

// Input AgentTask { taskId, tenantId, action, params: Record<string, any>, context: { userRole, conversationSnippet, previousResults } }
// Output AgentResult { taskId, status, data, actions: PendingAction[], explanation, confidence, sources: string[] }
3-Layer Agent Behavior (Per Tenant)
Layer 1: Base (Code)

Safety rules, tool definitions, mandatory constraints. Locked — not editable by tenants.

Layer 2: Soul (DB)

Personality, company rules, workflows, terminology, tool toggles. Editable per tenant via admin UI.

Layer 3: Runtime (Request)

User name, role, permissions, current date, conversation history. Injected per request.

Layer 4: ERP Connector

REST Client Circuit Breaker Redis Cache Rate Limiter Adapter Pattern

Abstraction layer for ERP APIs with hybrid model support: Twendee ERP + customer ERPs via ERPAdapter interface. Opossum circuit breaker, per-tenant auth, response caching.

// Input ERPRequest { tenantId, module, endpoint, method: 'GET'|'POST'|'PUT', params, body, cacheTTL }
// Output ERPResponse { success, data, cached, timestamp, requestDuration, error: { code, message, retryable } }

Layer 5: Proactive Intelligence

Cron Jobs Anomaly Detection Threshold Alerts CEO Daily Briefing

AI that acts proactively without being asked. BullMQ cron jobs for scheduled analysis, event-triggered anomaly detection, configurable threshold alerts per tenant.

// Triggers cron: "0 8 * * *" (daily briefing) event: expense_created, leave_req threshold: inventory < min_stock
// Output ProactiveAlert { type: anomaly|reminder|report, severity: info|warning|critical, title, message, data, suggestedActions, recipients }

Layer 6: Output

PDF Reports Excel Reports Action Executor Audit Trail

Report generation (Vietnamese locale), confirmed action execution, and full audit trail for every AI decision. Every LLM call logged with input, plan, results, tokens, and reasoning.

// Report Output ReportOutput { fileUrl, format: 'pdf'|'excel', generatedAt, expiresAt }
// Audit Entry AuditEntry { tenantId, userId, action, input, plan, agentResults, finalResponse, llmProvider, tokensUsed, duration, decision }
4 — Data Flows

4.1 Main Chat Flow (User Request → Response)

sequenceDiagram
  participant U as User
  participant CH as Channel Adapter
  participant MW as Tenant Middleware
  participant ORC as Orchestrator
  participant LLM as LLM Service
  participant AGT as Domain Agent
  participant ERP as ERP Connector
  participant DB as PostgreSQL
  participant AUD as Audit Service

  U->>CH: Send message
  CH->>CH: parseIncoming to UnifiedMessage
  CH->>MW: Pass with JWT
  MW->>DB: SET app.current_tenant_id
  MW->>ORC: OrchestratorInput

  rect rgb(217,119,6,0.05)
    Note over ORC,LLM: Orchestrator Pipeline
    ORC->>LLM: Classify intent (fast model)
    LLM-->>ORC: intent + targetAgents
    ORC->>LLM: Generate plan (fast model)
    LLM-->>ORC: ExecutionPlan
  end

  ORC->>AGT: Dispatch AgentTask
  rect rgb(8,145,178,0.05)
    Note over AGT,ERP: Agent Execution
    AGT->>LLM: Tool calling (smart model)
    LLM-->>AGT: Tool call request
    AGT->>ERP: ERPRequest
    ERP-->>AGT: ERPResponse
    AGT-->>ORC: AgentResult
  end

  ORC->>LLM: Verify response (fast model)
  LLM-->>ORC: Verified
  ORC->>AUD: Log AuditEntry
  ORC->>DB: Save conversation
  ORC-->>CH: ChannelResponse
  CH-->>U: Formatted reply
    

4.2 ERP Connector Flow (Circuit Breaker + Cache)

graph TD
  REQ["Agent Tool Call"] --> CACHE{"Redis Cache?"}
  CACHE -->|"HIT"| CACHED["Return cached ERPResponse"]
  CACHE -->|"MISS"| AUTH["Get Tenant Auth Token"]
  AUTH --> REG{"Adapter Registry"}
  REG --> TWD["TwendeeERPAdapter"]
  REG --> MISA["MISAAdapter"]
  REG --> SAP["SAPAdapter"]
  TWD --> CB{"Circuit Breaker"}
  MISA --> CB
  SAP --> CB
  CB -->|"CLOSED"| HTTP["HTTP Request to ERP"]
  CB -->|"OPEN"| STALE["Return stale cache"]
  CB -->|"HALF-OPEN"| TEST["Test one request"]
  HTTP --> STORE["Cache Response in Redis"]
  STORE --> RES["Return ERPResponse"]
  TEST -->|"Success"| RES
  TEST -->|"Fail"| STALE

  classDef hitCls fill:#05966911,stroke:#059669,stroke-width:1.5px
  classDef missCls fill:#d9770611,stroke:#d97706,stroke-width:1.5px
  classDef errCls fill:#dc262611,stroke:#dc2626,stroke-width:1.5px

  class CACHED,RES hitCls
  class STALE errCls
  class REQ,AUTH missCls
    

4.3 Proactive Intelligence Flow

graph TD
  CRON["BullMQ Scheduler"] --> JOB["Proactive Job Processor"]
  EVENT["ERP Event Webhook"] --> JOB
  JOB --> CTX["Set Tenant RLS Context"]
  CTX --> DETECT{"Detection Type"}
  DETECT -->|"Scheduled"| BRIEF["Daily CEO Briefing"]
  DETECT -->|"Anomaly"| ANOMALY["Anomaly Detector"]
  DETECT -->|"Threshold"| THRESH["Threshold Monitor"]
  BRIEF --> ALERT["Generate ProactiveAlert"]
  ANOMALY --> ALERT
  THRESH --> ALERT
  ALERT --> DISPATCH["Alert Dispatch Service"]
  DISPATCH --> WEB["Web Chat Push"]
  DISPATCH --> TG["Telegram Message"]
  DISPATCH --> LOG["Audit Log"]

  classDef trigCls fill:#65a30d11,stroke:#65a30d,stroke-width:1.5px
  classDef alertCls fill:#d9770611,stroke:#d97706,stroke-width:1.5px
  classDef outCls fill:#0891b211,stroke:#0891b2,stroke-width:1.5px

  class CRON,EVENT trigCls
  class ALERT,ANOMALY,THRESH alertCls
  class WEB,TG,LOG outCls
    

4.4 Multi-Step Execution (Parallel + Sequential)

graph TD
  PLAN["ExecutionPlan: mixed strategy"] --> BATCH1

  subgraph BATCH1["Batch 1 - Parallel"]
    STEP_A["Step A: Finance Agent"]
    STEP_B["Step B: HR Agent"]
  end

  BATCH1 --> AGG1["Collect Results A + B"]
  AGG1 --> STEP_C["Step C: Sales Agent
depends on A, B"] STEP_C --> STEP_D["Step D: Aggregator
depends on C"] STEP_D --> VERIFY["Verifier: Complete?"] VERIFY -->|"Yes"| RESP["Final Response"] VERIFY -->|"No"| RETRY["Retry or Clarify"] classDef parallelCls fill:#0891b211,stroke:#0891b2,stroke-width:1.5px classDef seqCls fill:#d9770611,stroke:#d97706,stroke-width:1.5px classDef doneCls fill:#05966911,stroke:#059669,stroke-width:1.5px class STEP_A,STEP_B parallelCls class STEP_C,STEP_D seqCls class RESP doneCls
5 — Multi-Tenancy Design
PostgreSQL Row-Level Security (RLS)

Every data table uses RLS with tenant_id = current_setting('app.current_tenant_id'). Tenant context is set per request in middleware from JWT token. BullMQ workers manually set context before DB queries.

erDiagram
  TENANTS ||--o{ USERS : has
  TENANTS ||--o{ AGENT_SOULS : customizes
  TENANTS ||--o{ CONVERSATIONS : owns
  TENANTS ||--o{ AUDIT_LOGS : records
  TENANTS ||--o{ PROACTIVE_RULES : configures

  TENANTS {
    uuid id PK
    text name
    text slug
    enum llm_provider
    text llm_api_key_encrypted
    text erp_type
    jsonb erp_config_encrypted
    jsonb agent_permissions
  }

  USERS {
    uuid id PK
    uuid tenant_id FK
    text email
    text password_hash
    enum role
    jsonb permissions
  }

  AGENT_SOULS {
    uuid id PK
    uuid tenant_id FK
    text agent_type
    text personality
    text company_rules
    text workflows
    jsonb terminology
    jsonb enabled_tools
    jsonb tool_config
  }

  CONVERSATIONS {
    uuid id PK
    uuid tenant_id FK
    uuid user_id FK
    text channel
    jsonb messages
  }

  AUDIT_LOGS {
    uuid id PK
    uuid tenant_id FK
    uuid user_id FK
    text action
    text input
    jsonb plan
    jsonb agent_results
    text final_response
    text llm_provider
    int tokens_used
    int duration_ms
  }

  PROACTIVE_RULES {
    uuid id PK
    uuid tenant_id FK
    text name
    enum type
    text cron_expression
    jsonb config
    boolean enabled
    jsonb recipients
  }
    
6 — Tech Stack
LayerTechnologyPurpose
Monorepopnpm workspaces + Turborepo3 apps + shared types + shared Tailwind
RuntimeNode.js 22+ / TypeScript 5.xCore platform
BackendNestJS 11Modular monolith with DI
Admin UIReact + Vite + shadcn/ui + TanStack TableSettings, audit, usage, users
Chat WidgetReact + Vite + shadcn/ui + Socket.ioEmbeddable UMD bundle
StylingTailwind CSS (shared preset)Consistent design system across FE apps
DatabasePostgreSQL 16 + Drizzle ORMRLS multi-tenancy
Cache/QueueRedis 7 + BullMQCaching, job queue, token metering
LLMVercel AI SDK (@ai-sdk/*)Multi-provider: OpenAI, Claude, Gemini, Ollama
TelegramgrammY + @grammyjs/nestjsBot API with webhooks
AuthJWT + Passport.jsMulti-tenant authentication
ERP ResilienceOpossum + AxiosCircuit breaker + HTTP client
ChartsrechartsUsage dashboard charts (admin UI)
ReportsPDFKit + ExcelJSVietnamese locale PDF/Excel
EncryptionAES-256-GCMAPI keys, credentials at rest
TestingVitest + SupertestUnit + integration + E2E
DeploymentDocker + Docker ComposeContainer orchestration
7 — Docker & Infrastructure
Hybrid Deployment Strategy

One Docker image for API + Admin UI (static files baked in at build time). Chat widget deployed separately to CDN (embedded on customer websites). PostgreSQL + Redis as managed services or Docker containers.

graph TD
  subgraph Internet
    BROWSER["Browser / Mobile"]
    CUST["Customer Website"]
  end

  subgraph NGINX["Nginx Reverse Proxy"]
    SSL["SSL Termination"]
    WS["WebSocket Upgrade"]
  end

  subgraph DOCKER["Docker Image - API + Admin"]
    API["NestJS API
Port 3000"] ADMIN["Admin UI
/admin/* static"] FONTS["Vietnamese Fonts"] end subgraph DATA["Data Layer"] PG[("PostgreSQL 16
+ RLS")] REDIS[("Redis 7
Cache + Queue")] end CDN["CDN
chat-widget.umd.js"] TELEGRAM["Telegram API"] ERP["ERP Systems"] BROWSER -->|"HTTPS"| NGINX CUST -->|"script tag"| CDN CDN -->|"WebSocket"| NGINX NGINX --> API NGINX -->|"WS upgrade"| API TELEGRAM -->|"Webhook"| NGINX API --> PG API --> REDIS API --> ERP API --> TELEGRAM classDef dockerCls fill:#0891b211,stroke:#0891b2,stroke-width:2px classDef dataCls fill:#05966911,stroke:#059669,stroke-width:1.5px classDef extCls fill:none,stroke-dasharray:5 5 class API,ADMIN,FONTS dockerCls class PG,REDIS dataCls class TELEGRAM,ERP,CDN extCls

Docker Build Pipeline (Multi-stage)

# Monorepo multi-stage Dockerfile Stage 1: deps # Install pnpm deps (cached layer) Stage 2: builder # Build: shared-types → admin (→ static) → api Stage 3: runner # node:22-alpine + compiled JS + admin static + fonts Image size: ~180MB | Non-root user | Healthcheck built-in
Development
docker-compose.yml ├── postgres:16-alpine (5432) ├── redis:7-alpine (6379) └── app (hot-reload) (3000) pnpm dev → Turborepo parallel pnpm db:migrate → Drizzle push
Production
docker-compose.prod.yml ├── nginx (SSL + WS proxy) ├── app (512MB limit) ├── postgres (persistent vol) └── redis (256MB limit) Chat widget → CDN deploy Makefile → build/deploy scripts

Chat Widget CDN

Widget built as UMD bundle (<100KB gzipped), uploaded to CDN on tag push. Customers embed via script tag:

<script src="https://cdn.twdagents.com/widget/v1/chat-widget.umd.js"></script> <script>TWDChat.init({ serverUrl: 'https://api.twdagents.com', token: 'JWT' })</script>

Versioned paths (/v1/, /v2/) — customers control which version they embed. Widget connects to API via WebSocket.

8 — Implementation Timeline
Phase 1 — Weeks 1-2
Foundation & Core Setup
Monorepo setup (pnpm workspaces + Turborepo + shared-types + tailwind-config). NestJS scaffold in apps/api/, PostgreSQL+RLS, JWT auth, tiered LLM abstraction (fast/smart), orchestrator pipeline, REST API adapter, Redis+BullMQ, Docker Compose. Vietnamese LLM benchmark sprint (Day 1-2).
Dev A: Monorepo+Scaffold+DB+Auth • Dev B: LLM+Orchestrator+Redis
Phase 2 — Weeks 3-4
ERP Connector & Core Agents
ERP adapter pattern (hybrid: Twendee+customer ERPs), Opossum circuit breaker, agent plugin system, Finance Agent, HR Agent, audit logging.
Dev A: ERP Connector • Dev B: Agent Framework+Finance • Dev C: HR+Audit
Phase 3 — Weeks 5-6
Channels & Additional Agents
Chat widget in apps/chat-widget/ (shadcn/ui + Socket.io), Telegram Bot (grammY), Sales Agent, Supply Agent, multi-step execution with dependency resolution.
Dev A: Chat Widget (apps/chat-widget/) • Dev B: Telegram • Dev C: Sales Agent • Dev D: Supply Agent
Phase 4 — Weeks 7-8
Intelligence, Admin UI & Deployment
Admin UI in apps/admin/ (React + shadcn/ui + TanStack Table + recharts): tenant settings, audit logs, usage dashboard, chat console, user management. Compliance Agent, proactive intelligence, PDF/Excel reports, CEO daily briefing, E2E testing, Docker deployment.
Dev A: Admin UI+Compliance • Dev B: Proactive Intelligence • Dev C: Reports+Dashboard API • Dev D: E2E+Docker
Phase dependencies: Phase 1 → 2 → 3 → 4 (sequential). Each phase builds on the previous. Any Phase 1 delay cascades into all subsequent phases.
9 — Key Decisions (Validated)
DecisionChoiceRationale
ArchitectureModular Monolith (NestJS)Fast to build in 2 months; clean module boundaries; split to microservices later
OrchestrationPure TS + Vercel AI SDKNo LangGraph/CrewAI; simpler stack; built-in tool calling
LLM CostTiered models (fast/smart)GPT-4o-mini for internal steps; full model for agent execution. ~70% cost reduction
ERP ModelHybrid + Adapter PatternERPAdapter interface per ERP type; TwendeeERPAdapter first; others post-MVP
Multi-TenancyPostgreSQL RLS + DrizzleDB-level isolation; middleware sets tenant context per request
Monorepopnpm workspaces + Turborepo3 apps share types + Tailwind; single Docker build; cached parallel builds
Frontendshadcn/ui + Tailwind + TanStack TableZero bundle overhead; same design system for admin + chat widget
DeploymentHybrid: 1 Docker image + CDN widgetAPI+Admin in one image (~180MB); widget on CDN for customer embedding
Agent Behavior3-layer system (Base+Soul+Runtime)Safety locked in code; tenant customizes via admin soul editor; runtime context per request
Zalo OADeferred to post-MVPApproval timeline uncertain; focus on REST + Web Chat + Telegram
Vietnamese LLMBenchmark in Week 1Test GPT-4o, Claude, Gemini with finance/HR queries before building agents
10 — Risk Assessment
RiskLikelihoodImpactMitigation
Vietnamese NLP quality varies by LLMMediumHighWeek 1 benchmark sprint; tenant can switch provider
Multi-tenant data leakageLowCriticalRLS at DB + middleware + integration tests with wrong tenant
2-month timeline tight for 5 agentsHighHighFinance+HR first; others can be simplified for MVP
LLM hallucination on financial dataMediumHighSystem prompt rules: never fabricate numbers; always cite ERP source
ERP API format varies per customerHighHighERPAdapter pattern; data transformation layer per adapter
RLS race condition in concurrent requestsMediumHighScope set_config to transaction; per-request context isolation
Unresolved Questions
  1. ERP API documentation — OpenAPI/Swagger spec available?
  2. Data residency — must data stay in Vietnam?
  3. Self-hosted model choice for Ollama — Vistral, Qwen2, or other?
  4. Per-tenant billing/token metering UX
TWDAgentsHub — Generated 2026-04-07 — Plans: 260407-2203-ai-orchestrator-saas