Skip to content

Dashboard Service

The Aegis Dashboard is a FastAPI application that provides the web interface, API endpoints, and administrative tools for Project Aegis.

Overview

  • Framework: FastAPI
  • Container: aegis-dashboard
  • Port: 8080 (internal), mapped to public domains via Traefik
  • URLs:
  • https://aegisagent.ai (primary)
  • https://aegis.rbnk.uk (redirects to primary)
  • https://intel.aegisagent.ai (intel subdomain)

Architecture

Application Structure

The dashboard follows a modular architecture with separate route modules:

aegis/dashboard/
├── app.py                 # Main FastAPI application
├── routes/                # 58+ route modules (22,203 total lines)
│   ├── system.py         # Health, status, rate limits
│   ├── intel_api.py      # Intel API subscriptions
│   ├── vonage.py         # WhatsApp webhooks
│   ├── marketplace.py    # API marketplace
│   ├── blog.py           # Blog content
│   └── ...
├── templates/            # Jinja2 HTML templates
└── repository.py         # Data access layer

Middleware Stack

Applied in order (first = outermost):

  1. ProxyHeadersMiddleware - Trusts X-Forwarded-* headers from Traefik
  2. CORSMiddleware - CORS headers for API access
  3. SecurityHeadersMiddleware - Security headers (X-Content-Type-Options, X-Frame-Options, etc.)

Security Headers

All responses include: - X-Content-Type-Options: nosniff - X-Frame-Options: DENY - X-XSS-Protection: 1; mode=block - Referrer-Policy: strict-origin-when-cross-origin - Cache-Control: no-store, no-cache, must-revalidate

API Endpoints

System APIs

Endpoint Method Description
/health GET Health check (returns database status)
/api/status GET System status (phase, version, timestamp)
/api/tasks GET Task statistics
/api/ooda GET OODA cycle success rate
/api/memory/recent GET Recent episodic memories
/api/rate-limit GET Check rate limit status

Intel APIs

Endpoint Method Description
/api/intel/pricing GET Get pricing tiers
/api/intel/key/create POST Create new API key
/api/intel/key/verify GET Verify API key
/api/intel/keys GET List API keys
/api/intel/docs GET API documentation

File Sharing APIs

Endpoint Method Description
/api/files/share POST Create shareable link
/share/{token} GET Access shared file

HTML Pages

Route Description
/ Landing page (intel dashboard for intel subdomain)
/products Products overview
/research Research API landing
/api-docs Human-friendly API documentation
/dashboard Internal status dashboard
/analytics Usage analytics with charts
/orchestration Agent orchestration visualization
/market Market research dashboard
/blog Blog post listing
/blog/{slug} Individual blog post
/docs/architecture System architecture docs

Startup Process

1. Initialization

app = create_app()

2. Startup Event

On application start (@app.on_event("startup")):

  1. Create asyncpg connection pool (2-10 connections)
  2. Host: host.docker.internal (PostgreSQL on host)
  3. Database: aegis
  4. User: agent

  5. Initialize ShareTokenRepository

  6. Creates file_shares table if not exists
  7. Manages time-limited file sharing tokens

  8. Start APScheduler

  9. Loads scheduled jobs from aegis.scheduler
  10. See scheduler.md for details

  11. Resume Meshy 3D Tasks

  12. Checks for incomplete 3D model generation tasks
  13. Resumes polling for completion

3. Shutdown Event

On application shutdown (@app.on_event("shutdown")):

  1. Stop APScheduler
  2. Close asyncpg connection pool

Configuration

Environment Variables

Core settings from docker-compose.yml:

# Database
POSTGRES_HOST=host.docker.internal
POSTGRES_PORT=5432
POSTGRES_USER=agent
POSTGRES_PASSWORD=agent
POSTGRES_DB=aegis

# External APIs
GITHUB_TOKEN=<token>
STRIPE_SECRET_KEY=<key>
ZAI_API_KEY=<key>
CLAUDE_CODE_OAUTH_TOKEN=<token>
DISCORD_ALERTS_CHANNEL=<channel_id>
TELEGRAM_BOT_TOKEN=<token>

# Vonage/WhatsApp
VONAGE_API_KEY=<key>
VONAGE_API_SECRET=<secret>
VONAGE_APPLICATION_ID=<app_id>
VONAGE_WHATSAPP_NUMBER=+447441443388
VONAGE_PRIVATE_KEY_B64=<base64_key>
VONAGE_SIGNATURE_SECRET=<secret>

# Email (Resend)
SMTP_HOST=smtp.resend.com
SMTP_PORT=587
SMTP_USER=<resend_key>
SMTP_PASSWORD=<resend_key>
SMTP_FROM=noreply@aegisagent.ai
RESEND_API_KEY=<key>

# Knowledge Graph
FALKORDB_HOST=host.docker.internal
FALKORDB_PORT=6379
OLLAMA_BASE_URL=http://host.docker.internal:11434

# Base URL
AEGIS_BASE_URL=https://aegisagent.ai

Volumes

volumes:
  - /var/run/docker.sock:/var/run/docker.sock:ro  # Docker API (read-only)
  - /home/agent/memory:/home/agent/memory          # Memory files
  - /home/agent/downloads:/home/agent/downloads    # Download cache
  - /home/agent/projects/aegis-core/.beads:/app/.beads:ro  # Beads tasks
  - /home/agent/.secure:/home/agent/.secure:ro     # Secrets

Traefik Integration

Docker Labels

The dashboard container uses Traefik labels for routing:

labels:
  - "traefik.enable=true"

  # Main router (aegisagent.ai)
  - "traefik.http.routers.aegis.rule=Host(`aegisagent.ai`)"
  - "traefik.http.routers.aegis.entrypoints=websecure"
  - "traefik.http.routers.aegis.tls.certresolver=cf"
  - "traefik.http.services.aegis.loadbalancer.server.port=8080"

  # Redirect router (aegis.rbnk.uk → aegisagent.ai)
  - "traefik.http.routers.aegis-redirect.rule=Host(`aegis.rbnk.uk`)"
  - "traefik.http.routers.aegis-redirect.middlewares=aegis-redirect-mw"
  - "traefik.http.middlewares.aegis-redirect-mw.redirectregex.regex=^https://aegis\\.rbnk\\.uk(.*)"
  - "traefik.http.middlewares.aegis-redirect-mw.redirectregex.replacement=https://aegisagent.ai$$1"
  - "traefik.http.middlewares.aegis-redirect-mw.redirectregex.permanent=true"

  # Intel subdomain
  - "traefik.http.routers.intel.rule=Host(`intel.aegisagent.ai`)"
  - "traefik.http.routers.intel.service=aegis"

SSL/TLS

  • Certificate resolver: Cloudflare DNS challenge (cf)
  • Email: aegis@richardbankole.com
  • Automatic renewal via Let's Encrypt

Rate Limiting

Rate limits are applied per client key (IP or API key):

from aegis.ratelimit import check_rate_limit, get_client_key

key, tier = get_client_key(request, api_key)
allowed, info = check_rate_limit(key, endpoint, tier)

Tiers: - Anonymous: IP-based, lower limits - Basic/Pro/Enterprise: API key-based, higher limits

Database Integration

Connection Pool

The dashboard uses asyncpg for async PostgreSQL operations:

pool = await asyncpg.create_pool(
    host=settings.postgres_host,
    port=settings.postgres_port,
    user=settings.postgres_user,
    password=settings.postgres_password,
    database=settings.postgres_db,
    min_size=2,
    max_size=10,
)
app.state.pg_pool = pool

Access pool in routes:

async def my_endpoint(request: Request):
    pool = request.app.state.pg_pool
    async with pool.acquire() as conn:
        result = await conn.fetch("SELECT * FROM table")

Synchronous Access

For synchronous operations, use aegis.db:

from aegis.db import db

status = db.get_state("operational_status")
memories = db.fetch_all("SELECT * FROM episodic_memory LIMIT 10")

Health Checks

Container Health

Docker healthcheck runs every 30 seconds:

curl http://localhost:8080/health

Returns:

{
  "status": "healthy",
  "database": "connected"
}

If database is unreachable:

{
  "status": "unhealthy",
  "database": "error message"
}

Development

Running Locally

cd /home/agent/projects/aegis-core

# Build image
docker compose build dashboard

# Start service
docker compose up -d dashboard

# View logs
docker compose logs -f dashboard

# Restart after code changes
docker compose restart dashboard

Adding New Routes

  1. Create route module in aegis/dashboard/routes/my_feature.py
  2. Define router:

    from fastapi import APIRouter
    router = APIRouter(tags=["MyFeature"])
    
    @router.get("/api/my-feature")
    async def my_endpoint():
        return {"status": "ok"}
    

  3. Export router in aegis/dashboard/routes/__init__.py:

    from .my_feature import router as my_feature_router
    

  4. Include in aegis/dashboard/app.py:

    from aegis.dashboard.routes import my_feature_router
    app.include_router(my_feature_router)
    

Adding Templates

  1. Create Jinja2 template in aegis/dashboard/templates/my_page.html
  2. Add route in app.py:
    @app.get("/my-page", response_class=HTMLResponse)
    async def my_page(request: Request):
        return templates.TemplateResponse(
            request, "my_page.html", context={"data": "value"}
        )
    

Monitoring

Logs

View logs with structured logging:

docker compose logs -f dashboard | grep -v "GET /health"

Log format (structlog):

2026-01-25T12:00:00Z event=request_received method=GET path=/api/status
2026-01-25T12:00:01Z event=asyncpg_pool_initialized

Metrics

Key metrics tracked: - Request count per endpoint - Response times - Database connection pool utilization - Rate limit hits - Error rates

Troubleshooting

Dashboard won't start

  1. Check PostgreSQL is running:

    PGPASSWORD=agent psql -h localhost -U agent -d aegis -c "SELECT 1"
    

  2. Check Docker socket access:

    docker ps
    

  3. View container logs:

    docker compose logs dashboard
    

Database connection errors

Ensure host.docker.internal resolves inside container:

docker compose exec dashboard ping -c 1 host.docker.internal

SSL/certificate issues

Check Traefik logs:

docker logs traefik 2>&1 | grep -i cert

Verify Cloudflare DNS challenge works:

docker exec traefik cat /letsencrypt/acme.json

Rate limiting too strict

Adjust limits in aegis/ratelimit.py or bypass with API key.