Skip to main content

System Architecture

Genie Helper is a multi-service platform that combines AnythingLLM, Directus CMS, Ollama, and Stagehand browser automation into a unified AI operations platform for content creators.

Architecture Overview

Browser → geniehelper.com (React SPA)
  → /app/*          authenticated creator dashboard
  → /api/directus/  Directus REST (port 8055) — data layer
  → /api/llm/       AnythingLLM (port 3001)   — chat + agent + embed widget

AnythingLLM Agent
  → directus MCP   (17 tools): CRUD collections, trigger flows, manage users/files
  → ollama MCP     (3 tools):  generate, chat, list-models
  → stagehand MCP  (9 tools):  browser sessions, navigate, act, extract, cookies, screenshot
  → Action Runner: [ACTION:slug:{"params"}] tag interceptor → pre-built flows

Media Worker (BullMQ)
  → scrape_profile  — Stagehand OF login + data extraction
  → publish_post    — Stagehand-based cross-platform posting
  → apply_watermark — ImageMagick watermarking
  → create_teaser   — FFmpeg video preview generation
  → post_scheduler  — polls scheduled_posts every 60s

Service Layout

AnythingLLM

Port: 3001
PM2 Name: anything-llm
Role: Chat API, AI agent, embed widget
Files: server/, storage/

Directus CMS

Port: 8055
PM2 Name: agentx-cms
Role: Collections, auth, REST API
Files: cms/

Stagehand

Port: 3002
PM2 Name: stagehand-server
Role: Browser automation
Files: scripts/stagehand-mcp-server.mjs

Dashboard

Port: 3100
PM2 Name: genie-dashboard
Role: React SPA (serve dashboard/dist/)
Files: dashboard/

Media Worker

Port:
PM2 Name: media-worker
Role: BullMQ consumer (Redis)
Files: media-worker/

Collector

Port:
PM2 Name: anything-collector
Role: Document ingestion
Files: AnythingLLM collector scripts

Ollama

Port: 11434
PM2 Name: (system)
Role: Local LLM inference
Files: System service

MCP Servers (29 Tools Total)

Genie Helper uses the Model Context Protocol to give the AI agent access to three core systems:

Directus MCP (17 Tools)

Script: scripts/directus-mcp-server.mjs
  • list-collections — List all available collections
  • get-collection-schema — Get schema for a specific collection
  • read-items — Read multiple items from a collection
  • read-item — Read a single item by ID
  • create-item — Create a new item in a collection
  • update-item — Update an existing item
  • delete-item — Delete an item
  • search-items — Search items with filters
  • trigger-flow — Trigger a Directus Flow
  • get-me — Get current user info
  • list-users — List all users
  • get-user — Get a specific user
  • update-user — Update user details
  • create-user — Create a new user
  • list-files — List uploaded files
  • get-file — Get file details
  • list-flows — List all automation flows

Ollama MCP (3 Tools)

Script: scripts/ollama-mcp-server.mjs
  • generate — Generate text completions
  • chat — Interactive chat with model
  • list-models — List available local models

Stagehand MCP (9 Tools)

Script: scripts/stagehand-mcp-server.mjs
  • start-session — Initialize browser session
  • navigate — Navigate to URL
  • act — Perform browser action (click, type, etc.)
  • extract — Extract data from page
  • observe — Observe page state
  • close-session — Close browser session
  • set-cookies — Set browser cookies
  • get-cookies — Get browser cookies
  • screenshot — Capture screenshot

MCP Configuration

Config File: storage/plugins/anythingllm_mcp_servers.json
Auto-boot: Patched server/utils/boot/index.jsbootMCPServers() on startup

Data Layer (Directus Collections)

Genie Helper uses Directus as its primary data store with 11+ key collections:
CollectionPurposeKey Fields
creator_profilesPlatform accounts, encrypted credentials, scrape statusplatform, username, credentials (encrypted)
scraped_mediaContent with engagement metricsurl, views, likes, revenue
scheduled_postsPost queue (worker polls every 60s)scheduled_at, platforms, status
media_jobsBullMQ job recordsjob_type, status, result
hitl_sessionsHuman-in-the-loop login requestsplatform, status, completed_at
platform_sessionsEncrypted browser cookies from extensionplatform, cookies (encrypted)
taxonomy_dimensions6 super-concept content classificationdimension_name, weight
taxonomy_mapping3,208 classified tags across 6 conceptstag, dimension, confidence
fan_profilesFan engagement datausername, ltv, last_interaction
action_flowsAction Runner flow definitionsslug, steps (JSON)
agent_auditsEvery ACTION execution loggedaction_slug, success, error
All platform credentials are encrypted with AES-256-GCM using server/utils/credentialsCrypto.js before storage.

Action Runner System

The Action Runner is a middleware layer that intercepts special tags from the AI model’s output:

How It Works

1

Model Emits Action Tag

The AI model outputs: [ACTION:taxonomy-tag:{"media_id":123}] in its prose
2

Action Runner Intercepts

The action-runner plugin detects the tag and strips it from visible chat
3

Flow Execution

Looks up taxonomy-tag in action_flows collection and executes the steps JSON
4

Status Streaming

Streams execution status back to the chat interface in real-time
5

Audit Logging

Records execution to agent_audits (success/error/miss)

Available Actions

{
  "slug": "taxonomy-tag",
  "steps": [
    {
      "type": "mcp_call",
      "server": "directus",
      "tool": "read-item",
      "params": {"collection": "scraped_media", "id": "{{media_id}}"}
    },
    {
      "type": "mcp_call",
      "server": "ollama",
      "tool": "generate",
      "params": {"model": "scout-fast-tag", "prompt": "Classify: {{item.title}}"}
    },
    {
      "type": "mcp_call",
      "server": "directus",
      "tool": "update-item",
      "params": {"collection": "scraped_media", "id": "{{media_id}}", "data": {"tags": "{{classification}}"}}
    }
  ]
}
Action SlugPurposeMCP Tools Used
scout-analyzeScrape URL + AI analysisstagehand:navigate, stagehand:extract, ollama:generate
taxonomy-tagAuto-classify content with taxonomydirectus:read-item, ollama:generate, directus:update-item
post-createDraft platform-specific postdirectus:read-items, ollama:chat, directus:create-item
message-generateFan engagement messagedirectus:read-item, ollama:generate
memory-recallSearch stored data + summarizedirectus:search-items, ollama:generate
media-processQueue media jobdirectus:create-item (media_jobs)

Media Worker (BullMQ)

The Media Worker is a BullMQ consumer that processes asynchronous jobs: Queues:
  • scrape-jobs — Profile scraping
  • media-jobs — Media processing
  • onboarding-jobs — User onboarding workflows
Job Types:

scrape_profile

Uses Stagehand to log into OnlyFans, extract stats and content, store in scraped_media

publish_post

Cross-platform posting via Stagehand browser automation

apply_watermark

ImageMagick-based image watermarking (100ms per operation)

create_teaser

FFmpeg video preview generation (~30s CPU per clip)

post_scheduler

Polls scheduled_posts every 60s and publishes ready posts
Worker File: media-worker/index.js
Redis Backend: Required for BullMQ job queue

Dashboard (React SPA)

Path: dashboard/
Stack: React 18 + Vite + Tailwind CSS + React Router v6
Doc Root: dashboard/dist/ served at geniehelper.com/

Routes

Public Routes:
  • / — Home
  • /pricing — Pricing plans
  • /about — About page
  • /register — Registration (invite-gated)
  • /login — Login
Authenticated Routes (/app/*):
  • /app/dashboard — Main dashboard
  • /app/media — Media library
  • /app/calendar — Post calendar
  • /app/fans — Fan management
  • /app/analytics — Performance analytics
  • /app/platforms — Platform connections
  • /app/settings — User settings
Admin Routes:
  • /admin — Directus + AnythingLLM iframes
  • /view-as — User impersonation

AI Chat Widget

AnythingLLM embed widget injected bottom-right on all /app/* routes:
  • Embed ID: cf54a9c0-224c-469d-b97b-5dc8095eac82 (Administrator workspace)
  • Greeting: Set via data-greeting attribute on script tag
  • Component: dashboard/src/components/AgentWidget/index.jsx
  • Trigger: window.__genieOpenChat()

API Proxies

Nginx Configuration:
  • /api/directus/http://localhost:8055
  • /api/llm/http://localhost:3001
Authentication:
  • Directus JWT (auto-refresh)
  • sessionStorage for impersonation tabs
Registration:
  • Invite-gated — Alpha invite code validated against AnythingLLM invite API

Data Flow Examples

Profile Scraping Flow

1

User Clicks 'Scrape Profile'

Dashboard sends POST to /api/queue/scrape-profile with creator_profile_id
2

Job Enqueued

BullMQ job created in scrape-jobs queue, record added to media_jobs
3

Media Worker Picks Up Job

Worker checks platform_sessions for encrypted cookies
4

Stagehand Session Started

If cookies exist: set-cookies + navigate to platform
If cookies missing: Create hitl_sessions record (yellow dashboard banner)
5

Data Extraction

Stagehand extract tool scrapes stats, content URLs, engagement metrics
6

Data Storage

Items created in scraped_media collection via Directus API
7

Job Completion

media_jobs record updated with status and result count

Content Classification Flow

1

User Asks 'Tag this content'

Chat message sent to AnythingLLM agent
2

Agent Emits Action

Model outputs: [ACTION:taxonomy-tag:{"media_id":123}]
3

Action Runner Intercepts

Looks up taxonomy-tag flow in action_flows collection
4

Step 1: Read Media

MCP call: directus:read-item from scraped_media
5

Step 2: AI Classification

MCP call: ollama:generate with scout-fast-tag model
6

Step 3: Update Tags

MCP call: directus:update-item with classified tags
7

Audit Log

Execution recorded in agent_audits collection

Security Architecture

Credential Encryption

File: server/utils/credentialsCrypto.js
// Encrypted credentials stored as:
{
  "enc": "v1:iv:tag:ciphertext"
}

// Functions:
encryptJSON(plainObject) → { enc: "v1:..." }
decryptJSON(encObject) → plainObject
Key Points:
  • AES-256-GCM encryption
  • No encryption keys in browser — all crypto is server-side only
  • Handles both new object format and legacy raw strings

Authentication Flow

  1. User logs in via /login (React form)
  2. Credentials sent to /api/directus/auth/login
  3. Directus returns JWT access token + refresh token
  4. Dashboard stores tokens in localStorage
  5. All API requests include Authorization: Bearer <token>
  6. Auto-refresh when access token expires

RBAC Synchronization

User roles are synced between Directus and AnythingLLM: Endpoint: server/endpoints/api/rbacSync.js
Webhook: Directus fires webhook on user create/update
Secret: RBAC_SYNC_WEBHOOK_SECRET validates webhook authenticity

Nginx Reverse Proxy

Plesk Users: All nginx changes via Plesk UI only. Never add location / — Plesk generates its own, duplicate causes startup failure.
Current proxy layout (full config in docs/nginx/geniehelper.com.vhost_nginx.conf):
# SPA fallback for React Router
error_page 404 =200 /index.html;

location /api/directus/ {
    proxy_pass http://127.0.0.1:8055/;
    proxy_set_header Host $host;
}

location /api/llm/ {
    proxy_pass http://127.0.0.1:3001/;
    proxy_http_version 1.1;
    proxy_set_header Upgrade "upgrade";
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $host;
    proxy_read_timeout 86400;
}
Critical rules:
  • Sub-path locations (/app/, /api/llm/) and exact-match (= /path) are safe
  • SPA routing: error_page 404 =200 /index.html;
  • WebSocket: use "upgrade" literal for Connection header

Project Structure

agentx/
├── scripts/          MCP servers + CLI utilities
├── server/           AnythingLLM backend (Express + Prisma)
│   ├── endpoints/api/
│   │   ├── captions.js          POST /api/captions/generate
│   │   ├── credentials.js       AES-256-GCM credential endpoints
│   │   ├── impersonate.js       Admin impersonation
│   │   ├── queue.js             BullMQ job management
│   │   ├── rbacSync.js          Directus ↔ AnythingLLM user sync
│   │   └── register.js          Invite-gated registration proxy
│   └── utils/
│       ├── actionRunner/        Action flow executor
│       ├── credentialsCrypto.js AES-256-GCM encryption
│       └── boot/                MCP server auto-boot
├── dashboard/        React SPA (Vite + Tailwind + React Router v6)
│   └── src/
│       ├── pages/               Route pages
│       ├── components/Layout/   AppLayout (embed widget), Sidebar, PublicLayout
│       └── utils/api.js         Directus + LLM API client
├── media-worker/     BullMQ media processing worker
├── cms/              Directus extensions
├── storage/          AnythingLLM persistent storage + MCP config
├── docs/nginx/       Nginx vhost configs (Plesk reference)
└── .claude/sessions/ Development session logs

Performance Considerations

Server Specs: $100/mo IONOS dedicated VPS, self-hosted
Resource Usage:
  • LLM inference: CPU-bound Qwen2.5 7B ~2-5s/call, ~4.8GB RAM pinned
  • Stagehand sessions: ~300MB RAM/active browser → ~33 concurrent sessions hard ceiling (10GB available)
  • FFmpeg clip generation: ~30s CPU per clip (real bottleneck)
  • Sharp/ImageMagick watermark: ~100ms (effectively zero cost → unlimited justified)
Scaling Limits:
  • Current Plesk VPS is CPU-only
  • dolphin3:8b and any >7B model will stall on CPU
  • Production upgrade: GPU VPS or stick with qwen-2.5:latest

Next Steps

Quick Start

Install and configure Genie Helper on your server

API Reference

Explore the Directus and AnythingLLM API endpoints

MCP Servers

Learn how to extend MCP servers with custom tools

Deployment

Production deployment best practices

Build docs developers (and LLMs) love