Eloquent

Documentation

Architecture Overview

This guide provides a comprehensive overview of the Eloquent platform architecture, including system components, data flow, and design decisions.

System Architecture

CLIENT LAYER
   Eloquent App (Platform core, admin dashboard, workflows, agents)
   Next.js 16.1+ (App Router)
                    |
            HTTP (REST API)
                    |
API GATEWAY LAYER
   Middleware Stack:
   1. Recovery (panic handling)
   2. Request ID (correlation)
   3. CORS (cross-origin requests)
   4. Logging (structured logs)
   5. JWT Authentication
   6. Scope Authorization
                    |
            HTTP (Internal)
                    |
SERVICE LAYER
   Entities | KG Service | Agents | Integrations
   Chat | Workflow | Scheduler | Docs
   Go Microservices (REST APIs)
                    |
DATA LAYER
   PostgreSQL | ClickHouse | Redis
   (Entities)  | (Knowledge  | (Cache,
               |    Graph)   | Embeddings)

NATS (Service-to-Service Only): Used for internal async messaging between backend services (not clients). Subjects: entity.events.*, kg.events.*, workflow.events.*

Core Components

Frontend Applications

ApplicationPurposeURL
Eloquent AppPlatform core, admin dashboard, workflow designer, agent builder/*

All applications:

  • Built with Next.js 16.1+ (App Router)
  • Share packages from @elqnt/*
  • Communicate with backend via API Gateway (HTTP REST)
  • Use JWT tokens for authentication

Shared Packages (@elqnt/*)

packages/eloquent/
├── auth/       # Authentication context and hooks
├── api/        # API Gateway client and utilities
├── entity/     # Entity management hooks and types
├── kg/         # Knowledge graph hooks and types
├── chat/       # Chat/messaging types and hooks
├── workflow/   # Workflow engine integration
├── agents/     # Agent configuration types
├── types/      # Shared TypeScript types
└── react/      # Shared React components

Backend Services

ServiceAPI PathPurpose
Entities/api/v1/entities/*Dynamic entity CRUD with JSON Schema
KG Query/api/v1/kg/query/*Knowledge graph queries and search
KG Ingest/api/v1/kg/ingest/*Knowledge graph data ingestion
Agents/api/v1/agents/*AI agent configuration and execution
Chat/api/v1/chat/*Real-time messaging and chat history
Workflow/api/v1/workflow/*Business process automation
Auth/api/v1/auth/*Authentication and authorization
Docs/api/v1/docs/*Document processing and analysis

Data Stores

StorePurposeTechnology
PostgreSQLEntity definitions and recordsJSONB, per-org schemas
ClickHouseKnowledge graph nodes and edgesColumnar, per-org databases
RedisCache, embeddings, sessionsKey-value, TTL support

Multi-Tenancy

Every organization gets isolated resources:

Organization: org_0197b2d3-982f-7b79-b2bf-710f6def104a

PostgreSQL Schema:
└── org_0197b2d3_982f_7b79_b2bf_710f6def104a
    ├── entity_definitions
    ├── entity_records
    └── entity_views

ClickHouse Database:
└── org_0197b2d3_982f_7b79_b2bf_710f6def104a
    ├── kg_nodes
    └── kg_edges

Rule: Every request includes orgId in headers or body.

Communication Patterns

1. Browser to API Gateway to Service (Primary)

All browser requests communicate with backend services through the API Gateway using standard HTTP.

// Browser-side: Custom hook wrapping useEntities
import { useEntities } from "@elqnt/entity/hooks";

function useTickets() {
  const { createRecord, loading, error } = useEntities({
    baseUrl: config.apiGatewayUrl,
    orgId: config.orgId,
  });

  const createTicket = async (data) => {
    const record = await createRecord("ticket", { fields: data });
    return record;
  };

  return { createTicket, loading, error };
}

2. Two Communication Paths

PathUse CaseURL Source
Browser → GatewayInteractive CRUD, real-timeAPI_GATEWAY_URL_PUBLIC
Server → GatewaySSR, auth flows, sensitive opsAPI_GATEWAY_URL_INTERNAL

3. JWT Authentication

// Token generated server-side, used for all API requests
const token = await new jose.SignJWT({
  org_id: orgId,
  user_id: userId,
  email: userEmail,
  role: "user",
  scopes: ["read", "write"],
})
  .setProtectedHeader({ alg: "HS256" })
  .setExpirationTime("1h")
  .sign(secret);

// Token sent with every request
headers: { Authorization: `Bearer ${token}` }

4. Service-to-Service (NATS - Internal Only)

NATS is used only for internal backend service communication, not for client applications:

// Backend service publishes events
js.Publish("entity.events.created", eventPayload)

// Other backend services subscribe
nc.Subscribe("entity.events.created", func(msg *nats.Msg) {
  // Handle event
})

Data Flow Examples

Creating an Entity Record

1. User submits form in React component
          │
          ▼
2. API client sends POST to /api/v1/entities/records
          │
          ▼
3. API Gateway validates JWT token
          │
          ▼
4. Gateway proxies request to Entities Service
          │
          ▼
5. Service validates against JSON Schema
          │
          ▼
6. Service writes to PostgreSQL (org schema)
          │
          ▼
7. Service publishes internal event via NATS (optional)
          │
          ▼
8. HTTP response returned through Gateway
          │
          ▼
9. Hook updates local state, UI reflects change

Server-Side Rendering (SSR) Data Fetch

1. User navigates to /admin/projects
          │
          ▼
2. Server Component calls api-client-server.ts
          │
          ▼
3. Server generates JWT directly (has JWT_SECRET)
          │
          ▼
4. Request sent to API_GATEWAY_URL_INTERNAL
          │
          ▼
5. Gateway proxies to Projects Service
          │
          ▼
6. Data returned, rendered in Server Component
          │
          ▼
7. HTML sent to browser (instant load, no spinner)

NATS Usage (Internal Only)

CRITICAL: NATS is used only between backend Go services. Using NATS in client applications (browser or Next.js server) is strictly forbidden.

Allowed NATS Use Cases

  1. Service-to-Service Request/Response
  2. JetStream for Persistence (KV Store, Streams)

Security

Multi-Tenancy Isolation

  • Every request validated for orgId
  • PostgreSQL: Separate schemas per org
  • ClickHouse: Separate databases per org
  • No cross-org data access possible

Authentication Flow

1. User authenticates (OAuth/OIDC via NextAuth)
          │
          ▼
2. Session established in Next.js app
          │
          ▼
3. Browser requests: Fetch JWT via /api/gateway-token
   Server requests: Generate JWT directly (has JWT_SECRET)
          │
          ▼
4. JWT includes: orgId, userId, email, role, scopes
          │
          ▼
5. API Gateway validates JWT on every request
          │
          ▼
6. Gateway extracts claims and forwards to backend:
   - X-Org-ID, X-User-ID, X-User-Email headers

Next Steps