# Architecture | معمارية النظام ## System overview ```mermaid graph TB subgraph Clients["Clients & Sources"] W[Website Forms] WA[WhatsApp Business] E[Email] LI[LinkedIn] end subgraph Gateway["FastAPI Gateway (api/main.py)"] MW[Middleware: RequestID, CORS, structured logs] R1[/api/v1/leads/] R2[/api/v1/sales/] R3[/api/v1/sectors/] R4[/api/v1/agents/] R5[/api/v1/webhooks/] R6[/health/] end subgraph Pipeline["Phase 8 — Acquisition Pipeline"] direction TB P1[Intake] --> P2[Pain Extractor] P2 --> P3[ICP Matcher] P3 --> P4[Qualification] P4 --> P5[CRM Sync] P5 --> P6[Booking] P6 --> P7[Proposal] end subgraph Growth["Phase 9 — Growth"] G1[Sector Intel] G2[Market Research] G3[Content Creator] G4[Distribution] G5[Enrichment] G6[Competitor Monitor] end subgraph LLM["LLM Router (core/llm/router.py)"] CL[Anthropic Claude
reasoning, writing] GM[Gemini
research, multimodal] GQ[Groq
fast classification] DS[DeepSeek
code] GL[GLM
Arabic, bulk] end subgraph Storage["Persistence"] PG[(PostgreSQL
Leads, Deals)] RD[(Redis
cache, queues)] MG[(MongoDB
unstructured)] end subgraph External["External Services"] HS[HubSpot CRM] CAL[Calendly / Google Cal] EMAIL[Resend / SendGrid] N8N[n8n workflows] LF[Langfuse tracing] end Clients --> Gateway R1 --> Pipeline R3 --> Growth Pipeline --> LLM Growth --> LLM Pipeline --> Storage Growth --> Storage Pipeline --> External Growth --> External LLM -.tracing.-> LF ``` ## Key design decisions ### 1. `.env`-only configuration via `pydantic-settings` All secrets live in `core/config/settings.py`, wrapped in `SecretStr`. No module may read env vars directly — they go through `get_settings()`. This keeps secrets centralized, typed, and out of application code. ### 2. Task → Provider routing with fallback `core/config/models.py` declares a routing table mapping every `Task` enum value to a primary `Provider`, plus a fallback chain. The `ModelRouter` tries primary, then each fallback, recording usage metrics and fallback triggers. This means **any single LLM outage is non-fatal** for the system. ### 3. Agents share a common base class `core/agents/base.py::BaseAgent` provides: - A bound structlog logger with `agent`/`agent_id` context - Access to the shared `ModelRouter` - A robust `parse_json_response()` utility that handles fenced blocks, raw JSON, and messy LLM output Every agent exposes a single `async def run(**kwargs)` method — easy to compose. ### 4. Pipeline as orchestrated, not monolithic `auto_client_acquisition/pipeline.py::AcquisitionPipeline` runs steps sequentially but **wraps each step in its own try/except**. If ICP match fails, qualification still runs. The final `PipelineResult` object contains a `warnings` list listing any non-fatal failures. This mirrors real-world sales ops where you want partial data rather than total failure. ### 5. Bilingual first-class Arabic is not an afterthought: - Locale detection in `core/utils.py` from Unicode ranges - Arabic-heavy tasks routed to GLM by default (`Task.ARABIC_TASKS`) - Sales scripts and prompts ship in both AR and EN - Content agent picks GLM or Claude based on target locale ### 6. Security defaults - `.gitignore` blocks `.env*` except `.env.example` - Pre-commit runs `gitleaks` + `detect-secrets` on every commit - CI re-runs those + `trufflehog` on every PR - LinkedIn integration disabled by default (explicit ToS choice) - Webhook signatures verified (`WhatsAppClient.verify_signature`) - Docker container runs as non-root `app` user ### 7. Observable - Every request gets a `X-Request-ID` header (via `RequestIDMiddleware`) - All logs are structured JSON in production (structlog) - LLM router tracks calls, tokens, errors, and fallback count per provider - Optional Langfuse integration for LLM-level tracing ## Folder map ``` ai-company-saudi/ ├── core/ # foundation │ ├── config/ # settings + routing table │ ├── llm/ # provider clients + router │ ├── agents/ # BaseAgent + orchestrator │ ├── prompts/ # engineered prompts + sales scripts │ ├── logging.py │ ├── errors.py │ └── utils.py ├── auto_client_acquisition/ # Phase 8 │ ├── agents/ # 9 agents │ └── pipeline.py # orchestrator ├── autonomous_growth/ # Phase 9 │ ├── agents/ # 6 agents │ └── orchestrator.py ├── integrations/ # external APIs │ ├── whatsapp.py │ ├── email.py │ ├── calendar.py │ ├── hubspot.py │ ├── linkedin.py │ ├── n8n.py │ └── saudi_market.py ├── api/ # FastAPI app │ ├── main.py # factory │ ├── routers/ # 6 routers │ ├── schemas/ # Pydantic models │ ├── middleware.py │ └── dependencies.py # DI ├── db/ # persistence │ ├── models.py # SQLAlchemy 2.0 │ ├── session.py │ └── migrations/ # Alembic ├── dashboard/ │ └── analytics.py # KPI aggregation ├── tests/ │ ├── conftest.py # fixtures + LLM mocks │ ├── unit/ # intake, ICP, pain, router │ └── integration/ # API, pipeline ├── scripts/ # seed + demo ├── docs/ # this folder ├── .github/ # CI/CD + templates ├── Dockerfile ├── docker-compose.yml ├── Makefile └── pyproject.toml ``` ## Request lifecycle example A `POST /api/v1/leads` with a new lead flows as: 1. **Middleware** assigns a request ID, binds it to structlog context. 2. **Router** (`api/routers/leads.py`) validates input via Pydantic. 3. **DI** resolves a singleton `AcquisitionPipeline`. 4. **Pipeline** runs: - `IntakeAgent` normalizes + dedup - `PainExtractorAgent` runs keyword pass, tries LLM enrichment (Arabic→GLM) - `ICPMatcherAgent` scores Fit across 5 dimensions - `QualificationAgent` generates BANT questions (Claude) - `CRMAgent` upserts to HubSpot (best-effort) - If fit≥0.5: `BookingAgent` picks Calendly or Google Cal - If fit≥0.7 + opt-in: `ProposalAgent` drafts proposal via Claude 5. **Pipeline** returns a `PipelineResult` with any `warnings` from failed steps. 6. **Router** serializes to `PipelineResponse`. 7. **Middleware** logs completion with duration. Total LLM calls: 2–4 depending on flags. With caching and Groq for classification, p50 < 5s.