Real-time competitor intelligence platform: scheduled harvesters pull public market signals, a three-stage Spring AI pipeline filters and interprets them, and a Vue 3 dashboard consumes insights via Server-Sent Events (SSE) and REST. Ask Agent deep-dives on any article; with optional RAG (pgvector), answers can cite the current story plus related harvested history.
Live demo: aegis-dashboard-c4vm.onrender.com — see Public demo for what is enabled on the hosted deploy vs locally.
- Public demo
- Problem and outcome
- Project scope
- What this project demonstrates
- Tech stack
- Architecture
- Data and control flows
- Repository structure
- Environment variables
- Quick start
- API surface
- Ask Agent and RAG
- Screenshots
- Data model
- Development and testing
- Operational notes
- Deploy on Render
- Future enhancements
| Dashboard | https://aegis-dashboard-c4vm.onrender.com |
| API | https://aegis-api-vu7l.onrender.com (REST + SSE only — no UI) |
The hosted deploy is a portfolio demo on Render + Neon free tier:
- Ask Agent works on any threat card using the current article and server
OPENAI_API_KEY(hosted-key trial; rate-limited). - RAG is disabled (
AEGIS_RAG_ENABLED=false) to limit Neon compute. You will not see the RAG badge, per-card semantically related stories, or multi-article citations on the public site. - Tracked competitors:
Google,Amazon,OpenAI(seeAEGIS_TRACKED_COMPETITORSinrender.yaml).
RAG demo on request: Full vector retrieval (pgvector, cited sources, related stories) is implemented in-repo. Reviewers can run it locally (AEGIS_RAG_ENABLED=true in Docker) or ask the maintainer to enable RAG on a deploy (AEGIS_RAG_ENABLED=true; one-time AEGIS_RAG_BACKFILL_ON_STARTUP=true if the index is empty). See Ask Agent and RAG.
Problem: Product, strategy, and marketing teams need a single place to watch competitive moves (launches, hiring, partnerships, filings) without drowning in raw feeds.
Outcome: Aegis collects heterogeneous sources into one schema, reduces noise with an LLM gate, classifies and scores threat, streams results so operators see high-signal updates as they land, and surfaces them in a paginated, filterable dashboard with honest DB counts—plus Ask Agent strategic Q&A on any item, with optional RAG grounding and cited sources.
| Area | Description |
|---|---|
| Ingestion | Scheduled harvesters (RSS, GDELT, Reddit, Hacker News, SEC EDAR/EDGAR-style search, GitHub, Google News, financial/contract/industry feeds per application.yml). |
| Persistence | Raw articles in PostgreSQL (pgvector for RAG); Flyway-managed schema (V1–V6). |
| AI pipeline | Noise canceler → market analyst (category) → strategist (threat 1–10 + advice); failures never break the chain. |
| Realtime UX | SSE insight stream + paginated REST feed (/feed, /stats, /analytics), filters, competitor drill-down, harvest status, settings. |
| Ask Agent + RAG | Per-article deep-dive Q&A with pgvector retrieval over harvested news; cited sources in API + UI. |
| Configuration | Env-based DB, OpenAI key (server + optional user override in UI), tracked competitors, RAG feature flags. |
| Local run | Docker Compose (Postgres + API + Nginx SPA). |
| Cloud run | Render blueprint (render.yaml): API + static site; Neon for Postgres. |
| Area | Notes |
|---|---|
| Multi-tenant auth | No built-in user accounts or RBAC; suitable for internal/single-team or demo. |
| SLA / HA | Single API instance; no horizontal scaling story in-repo. |
| Source connectors as products | New sources require code changes (harvester + config), not plug-in marketplace. |
| Long-term secret store | User BYOK keys are per-browser-session on the API (in-memory); optional browser storage. Server OPENAI_API_KEY is the durable default for harvest + demo. |
- OpenAI is the configured LLM provider (Spring AI); agents degrade safely if the key is missing.
- RAG is off by default (
AEGIS_RAG_ENABLED=false); enable explicitly for Ask Agent retrieval and indexing. - CORS is configurable via
AEGIS_CORS_ALLOWED_ORIGINS— use your exact dashboard origin in production (not*). - Public demo: hosted-key trial is bound to
X-Aegis-Session+ client IP; interactive endpoints are rate-limited. Competitor list mutations can be disabled viaAEGIS_COMPETITORS_MUTATIONS_ENABLED=false(set inrender.yaml). - Legal / ToS of each external source are the operator’s responsibility; URLs and cadences live in configuration.
- Full-stack Java 21 + Spring Boot 3.4 (WebFlux) with Vue 3 + Vite + TypeScript
- SSE-first UI instead of polling-only dashboards
- Agent-shaped orchestration with isolated
@Serviceagents and safe fallbacks - RAG over harvested news — Spring AI
PgVectorStore, chunking/embeddings, cited sources in API + UI - Honest at scale — paginated feed with DB-backed totals, pipeline stats, and server-side search (title + summary)
- Contract alignment: Java records ↔ TypeScript interfaces
- Infrastructure: Docker Compose locally (
pgvector/pgvector:pg16); Blueprint for Render + Neon (SPA rewrites for Vue Router)
| Layer | Technology |
|---|---|
| Backend | Java 21, Spring Boot 3.4, WebFlux, Spring AI (OpenAI + embeddings), @Async orchestration |
| Data | PostgreSQL 16 + pgvector, Spring Data JPA, Flyway |
| RAG | Spring AI PgVectorStore, OpenAI embeddings, RagIndexingService / RagRetrievalService |
| Frontend | Vue 3 (<script setup lang="ts">), Vite, Pinia, Tailwind CSS |
| Realtime | Flux<ServerSentEvent<T>>, Pinia + useSse.ts (EventSource) |
| Local infra | Docker Compose, Nginx (frontend container proxies /api to backend) |
| Cloud | Render (render.yaml) + Neon Postgres |
| Testing | JUnit 5 + AssertJ + Mockito; Vitest; Playwright (e2e) |
flowchart LR
subgraph Sources["External data"]
RSS[RSS feeds]
GDELT[GDELT]
SEC[SEC / filings]
SOCIAL[Reddit / HN]
GH[GitHub]
GNEWS[Google News]
FIN[Financial / contracts / industry]
end
subgraph Aegis["Aegis platform"]
H[Harvesters scheduler]
PG[(PostgreSQL + pgvector)]
ORCH[Agent orchestration]
RAG[RAG index / retrieve]
API[Spring WebFlux API]
SSE[SSE publisher]
end
subgraph Clients["Clients"]
UI[Vue dashboard]
end
Sources --> H
H --> PG
H --> ORCH
ORCH --> PG
ORCH --> RAG
RAG --> PG
ORCH --> SSE
API --> PG
API --> RAG
SSE --> API
UI <-- REST / SSE --> API
flowchart TB
subgraph Browser
SPA[Vue SPA]
end
subgraph Edge["Local: Nginx container"]
NGX[Nginx + static assets + /api proxy]
end
subgraph App["Backend container"]
SB[Spring Boot]
end
subgraph Data
DB[(PostgreSQL)]
end
SPA --> NGX
NGX -->|"/api reverse proxy"| SB
SPA -->|Render: direct HTTPS to API| SB
SB --> DB
Locally, the browser hits localhost:3000; Nginx forwards /api to the backend. On Render, the static site is a separate URL; VITE_API_BASE_URL points the browser at the API for REST and SSE.
flowchart TB
subgraph Controllers
IC[InsightController]
SC[SettingsController]
CC[CompetitorController]
HC[HarvestStatusController]
end
subgraph Services
AOS[AgentOrchestrationService]
IS[InsightService]
CS[CompetitorService]
DS[DeepDiveService]
RIS[RagIndexingService]
RRS[RagRetrievalService]
end
subgraph Agents
NC[NoiseCancelerAgent]
MA[MarketAnalystAgent]
ST[StrategistAgent]
end
subgraph Harvesters
HSET[Scheduled harvesters]
end
IC --> IS
IC --> DS
SC --> DCP[DynamicChatClientProvider]
AOS --> NC
AOS --> MA
AOS --> ST
AOS --> RIS
DS --> RRS
HSET --> AOS
IS --> PG[(Repositories)]
AOS --> PG
RIS --> PG
RRS --> PG
sequenceDiagram
participant Cron as Scheduler
participant Harv as Harvester
participant DB as Postgres
participant Orch as Orchestration @Async
participant N as NoiseCanceler
participant M as MarketAnalyst
participant S as Strategist
participant Sink as InsightService / Sinks.Many
participant RAG as RagIndexingService
participant Client as Dashboard EventSource
Cron->>Harv: tick
Harv->>DB: save competitor_news
Harv->>Orch: processAsync(article, newsId)
Orch->>N: isRelevant?
alt not relevant
N-->>Orch: discard
else relevant
Orch->>M: categorize()
Orch->>S: analyze()
Orch->>DB: save agent_insights
Orch->>RAG: indexNewsAsync (if RAG enabled)
Orch->>Sink: publish InsightEvent
Sink-->>Client: SSE insight
end
sequenceDiagram
participant UI as ThreatCard
participant API as InsightController
participant DD as DeepDiveService
participant RAG as RagRetrievalService
participant LLM as ChatClient
participant DB as Postgres
UI->>API: POST /deep-dive {newsId, question}
API->>DD: deepDive()
DD->>RAG: retrieve(question, article)
RAG->>DB: similarity search (pgvector)
DD->>LLM: prompt + relatedContext
LLM-->>DD: analysis
DD->>DB: save deep_dive_log (sources_json, rag_used)
DD-->>API: {analysis, sources, ragUsed}
API-->>UI: render answer + Sources used panel
See Ask Agent and RAG for env flags, backfill, and UI details.
flowchart TD
START[Request needs ChatClient]
RT{Runtime key set?}
ENV{Env OPENAI_API_KEY valid?}
USE_RT[Use user key from Settings]
USE_ENV[Use server env key]
FAIL[ApiKeyNotConfigured]
START --> RT
RT -->|yes| USE_RT
RT -->|no| ENV
ENV -->|yes| USE_ENV
ENV -->|no| FAIL
Users can PUT /api/settings/openai-key to override; DELETE /api/settings/openai-key reverts to the server key when one exists (configured / serverKeyAvailable in /api/settings/status).
.
├── backend/
│ ├── Dockerfile
│ ├── pom.xml
│ └── src/main/java/com/aegis/
│ ├── agent/ # AI stages (noise, analyst, strategist)
│ ├── config/ # CORS, WebClient, RagConfig, DynamicChatClientProvider
│ ├── controller/ # REST + SSE
│ ├── dto/ # Java records (API contracts, DeepDiveSource, etc.)
│ ├── entity/ # JPA entities
│ ├── harvester/ # Source-specific ingestion
│ ├── repository/
│ ├── service/ # Orchestration, insights, competitors, deep-dive, RAG
│ └── util/
│ └── src/main/resources/
│ ├── application.yml
│ ├── application-local.yml # optional local profile (H2)
│ └── db/migration/ # Flyway V1–V6 (pgvector + RAG store)
├── frontend/
│ ├── Dockerfile
│ ├── nginx.conf
│ ├── public/_redirects # SPA fallback (Render + static hosts)
│ ├── scripts/capture-readme-screenshots.mjs
│ └── src/
│ ├── components/ # ThreatCard, InsightFeed*, PipelineStatsBar, analytics panels
│ ├── composables/ # useSse.ts, useInsightFeed.ts, useFeedFilters.ts
│ ├── lib/ # insightLabels.ts, categoryLabels.ts
│ ├── stores/
│ ├── types/insight.ts
│ └── views/ # Dashboard, CompetitorView
├── docker-compose.yml # postgres: pgvector/pgvector:pg16
├── docker-compose.override.yml.example # optional local Postgres on 5434
├── docs/screenshots/ # README images (npm run screenshots)
├── render.yaml # Render Blueprint
└── .env.example
| Variable | Where | Purpose |
|---|---|---|
POSTGRES_PASSWORD |
Docker Compose | DB password for local stack |
OPENAI_API_KEY |
.env, Render |
Server default OpenAI key |
SPRING_AI_OPENAI_API_KEY |
optional | Alias fallback for Spring property |
TRACKED_COMPETITORS |
Docker Compose .env |
Maps to AEGIS_TRACKED_COMPETITORS in the API container |
AEGIS_TRACKED_COMPETITORS |
Render aegis-api |
Comma-separated competitors to harvest (e.g. Google,Amazon,OpenAI) |
DATABASE_URL |
Neon → Render aegis-api |
Neon pooled postgresql://… URL. Mapped to JDBC via RenderDatabaseEnvironmentPostProcessor. |
SPRING_DATASOURCE_URL |
Render aegis-api |
Alternative to DATABASE_URL: jdbc:postgresql://…-pooler.….neon.tech/neondb?sslmode=require |
SPRING_DATASOURCE_USERNAME |
Render aegis-api |
Use with split JDBC URL (e.g. neondb_owner) |
SPRING_DATASOURCE_PASSWORD |
Render aegis-api |
Neon role password (use with split JDBC URL) |
SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE |
Render aegis-api |
Connection pool size (demo: 2) |
AEGIS_SOURCES_GOOGLENEWS_CRON |
Render aegis-api |
Spring 6-field cron (default hourly: 0 0 * * * *) |
AEGIS_SOURCES_GDELT_CRON |
optional | e.g. 0 15 * * * * (staggered hourly) |
AEGIS_SOURCES_HACKERNEWS_CRON |
optional | e.g. 0 30 * * * * |
AEGIS_SOURCES_RSS_CRON |
optional | e.g. 0 0 */2 * * * |
AEGIS_SOURCES_REDDIT_CRON |
optional | e.g. 0 30 */2 * * * |
PORT |
Render / PaaS | HTTP listen port (server.port) |
AEGIS_CORS_ALLOWED_ORIGINS |
Render / prod | Comma-separated origin patterns for browser clients |
AEGIS_COMPETITORS_MUTATIONS_ENABLED |
Render / prod | false disables POST/DELETE competitors (Blueprint default) |
AEGIS_INTERACTIVE_MAX_PER_MINUTE |
Render / prod | Rate limit for Ask Agent + AI Lookup per session/IP (default 30) |
AEGIS_DEMO_TRIAL_MINUTES |
optional | Hosted-key demo length (default 5) |
AEGIS_DEMO_TRIAL_ENABLED |
optional | Set false to disable hosted-key trial locally |
AEGIS_RAG_ENABLED |
optional | Enable pgvector RAG for Ask Agent (default false; keep off on Neon free tier demo) |
AEGIS_RAG_BACKFILL_ON_STARTUP |
optional | Index existing articles on API startup (one-time; set false after backfill) |
VITE_API_BASE_URL |
Frontend build | Public API base URL (e.g. https://aegis-api.onrender.com) |
See .env.example for the canonical local template.
- Docker Desktop (recommended), or Java 21 + Node 20+ + PostgreSQL 16
- OpenAI API key (for AI stages); configurable in
.envor app Settings
cp .env.example .envEdit .env: set at least POSTGRES_PASSWORD and OPENAI_API_KEY. For RAG locally, also set AEGIS_RAG_ENABLED=true (optional one-time AEGIS_RAG_BACKFILL_ON_STARTUP=true).
docker compose up --build| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend | http://localhost:8080 |
| Postgres | localhost:5432 (or 5434 if using docker-compose.override.yml when 5432 is busy) |
Port conflict: If another Postgres uses 5432, copy docker-compose.override.yml.example to docker-compose.override.yml (gitignored) to bind Aegis Postgres on 5434.
Backend (Postgres must be running and match application.yml defaults or env):
cd backend
mvn spring-boot:run
# or: ./mvnw spring-boot:run (if wrapper present)Frontend:
cd frontend
npm install
npm run devVite dev server proxies /api to http://localhost:8080 (see vite.config.ts).
| Method | Endpoint | Purpose |
|---|---|---|
GET |
/api/insights/stream |
SSE stream of insights |
GET |
/api/insights/feed |
Paginated feed (competitor, category, minThreat, search, dateFrom, dateTo, sort, offset, limit, ids) |
GET |
/api/insights/stats |
DB totals + today harvested/analyzed/filtered |
GET |
/api/insights/analytics?days=7 |
Category/source mix + high-threat by competitor |
GET |
/api/insights/competitor/{name}/summary |
Per-competitor breakdown |
GET |
/api/insights/{newsId}/related |
Semantically related stories via RAG (per-card; requires AEGIS_RAG_ENABLED) |
GET |
/api/insights/latest?limit=20 |
Recent insights (per competitor cap) |
GET |
/api/insights/threats?minLevel=7 |
Paginated high-threat feed |
POST |
/api/insights/deep-dive |
LLM deep-dive on a news item (returns analysis + cited sources) |
GET |
/api/insights/deep-dive/history?newsId= |
Prior Ask Agent Q&A for that article |
GET |
/api/insights/deep-dive/history/recent |
Last 30 Ask Agent queries across all articles |
GET |
/api/settings/status |
OpenAI configuration flags |
PUT |
/api/settings/openai-key |
Set runtime user key |
DELETE |
/api/settings/openai-key |
Clear user key (revert to server key if set) |
GET |
/actuator/health |
Liveness (Render / load balancers) |
Example deep-dive body:
{
"newsId": 123,
"question": "What does this imply for enterprise pricing?"
}Example deep-dive response:
{
"analysis": "Answer:\nOpenAI's move signals…\n\nStrategic implications:\n• …",
"sources": [
{
"newsId": 123,
"title": "Headline of current article",
"excerpt": "First ~400 chars of body…",
"sourceUrl": "https://…",
"currentArticle": true
},
{
"newsId": 456,
"title": "Related prior story",
"excerpt": "…",
"sourceUrl": "https://…",
"currentArticle": false
}
],
"ragUsed": true
}Additional routes exist for competitors and harvest status—see backend/.../controller/.
| Score | UI tier | Meaning |
|---|---|---|
| 9–10 | Critical | Existential / direct niche threat (Strategist LLM) |
| 7–8 | High | Matches High threat ≥7 filter and API minLevel=7 |
| 5–6 | Elevated | Monitor and plan |
| 1–4 | Low | Awareness only |
Post-processing floors: LEGAL ≥5, PARTNERSHIP ≥4, EDGAR source ≥5 (ThreatLevelAdjuster).
- Paginated feed —
GET /api/insights/feedwith honesttotal/hasMore; SSE prepends new items. - Filters — competitor, category, date (7d/30d/custom), search, sort, high-threat.
- Similar headlines — title-token clusters in the feed (
clusterKey); not the same as per-card RAG related stories. - Read / star / dismiss — stored in browser localStorage only; unread filter applies to the loaded feed, not the full DB.
- Starred — IDs from localStorage, items fetched via
GET /feed?ids=1,2,3. - Competitor page —
/competitor/:name(SPA;public/_redirects+ Render rewrite). - UI theme — dark-only dashboard (no light mode or theme toggle).
Example feed response:
{
"items": [{ "id": 1, "threatLevel": 8, "clusterKey": null, "ragAvailable": true }],
"total": 3721,
"hasMore": true
}Example stats response (shape only — live counts change as harvesters run):
{
"totalArticles": 6600,
"totalInsights": 1100,
"filteredArticles": 5200,
"todayHarvested": 120,
"todayAnalyzed": 18,
"todayFiltered": 95,
"highThreatCount": 380
}Ask Agent (per threat card) sends a strategic question about one harvested article. When AEGIS_RAG_ENABLED=true, the backend:
- Embeds the question and searches pgvector (
aegis_rag_store) for related chunks from the same competitor. - Injects retrieved context into the deep-dive prompt.
- Returns structured sources (current article + related history) and
ragUsed: truewhen retrieval contributed.
flowchart LR
Q[User question] --> DD[DeepDiveService]
DD --> RAG[RagRetrievalService]
RAG --> VS[(pgvector store)]
RAG --> CN[(competitor_news URLs)]
DD --> LLM[ChatClient]
LLM --> UI[ThreatCard sources panel]
DD --> LOG[(deep_dive_log)]
UI behavior
- Sources used (n) — collapsible list with This article vs Related labels and clickable
sourceUrllinks. - RAG badge when vector retrieval contributed.
- Previous asks — clickable history per article; restores full answer, sources, and question text.
Indexing
- New insights are indexed asynchronously after the agent pipeline (
RagIndexingService). - One-time backfill: set
AEGIS_RAG_BACKFILL_ON_STARTUP=true, wait for completion, then set back tofalse(avoids re-indexing on every deploy). - Local Docker uses
pgvector/pgvector:pg16; production uses Neon with thevectorextension (Flyway V5).
Flyway
| Version | Migration |
|---|---|
| V5 | vector extension + aegis_rag_store |
| V6 | deep_dive_log.sources_json, deep_dive_log.rag_used |
Paginated competitor feed with honest DB counts, sidebar filters (search, high-threat, starred, date range), pipeline stats, analytics panel, and Ask Agent on each card. Search runs server-side against article titles and AI summaries (~350ms debounce).
Per-competitor summary (category/source mix, high-threat count) and threat-sorted insight list at /competitor/:name.
Click Ask Agent on any threat card. Prior questions are selectable; the full answer and Sources used panel restore from deep_dive_log. Per-card semantically related (RAG) stories are separate from feed similar headlines clusters.
Screenshot captured with AEGIS_RAG_ENABLED=true locally. The public hosted demo runs without vector retrieval.
Refresh after UI changes (Docker stack at localhost:3000, API at localhost:8080):
cd frontend && npm run screenshots| Table | Role |
|---|---|
competitor_news |
Normalized raw harvest rows |
agent_insights |
AI output linked to competitor_news |
deep_dive_log |
Ask Agent history (question, analysis, sources_json, rag_used) |
aegis_rag_store |
pgvector embeddings for RAG (Spring AI PgVectorStore) |
Migrations: backend/src/main/resources/db/migration/ (V1–V6).
# Backend
cd backend && mvn test
# Frontend unit + typecheck
cd frontend && npm run build && npm run test
# Frontend e2e (Playwright; starts vite preview)
cd frontend && npm run test:e2e
# Refresh README screenshots (Docker stack running)
cd frontend && npm run screenshotsA .github/workflows/ci.yml workflow is included locally for optional GitHub Actions (requires workflow OAuth scope to push).
- Compose orders backend after Postgres healthy to avoid Flyway races.
- Harvesters self-heal: bad upstream keys or HTTP errors are logged; the next cron tick retries.
- SSE delivery uses a central reactive sink (
Sinks.Many) as the hot path after persistence. - RAG indexing runs async after each insight; backfill is sequential to protect the DB pool—disable
AEGIS_RAG_BACKFILL_ON_STARTUPafter the first full index. - Free Render tiers may spin down the API—scheduled harvests and long-lived SSE pause until the service wakes.
- Neon free tier (100 CU-hrs/mo): use pooled connection string,
SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=2, slower harvest crons (seerender.yaml),AEGIS_RAG_ENABLED=false, and suspendaegis-apiwhen not demoing. Header stat “competitors” counts distinct names in the loaded feed (historical rows), notAEGIS_TRACKED_COMPETITORS.
Database: Neon Postgres (free tier is fine). Apps: Render via render.yaml (no Render Postgres — avoids the one-free-DB-per-account limit).
| Resource | Where | Role |
|---|---|---|
| Postgres | Neon project (e.g. aegis-db) |
Data storage; Flyway runs on API startup |
| Web Service | Render aegis-api |
Docker image from backend/ |
| Static Site | Render aegis-dashboard |
Vue build → dist |
- Neon: Create a project → copy pooled connection string → keep secret (never commit).
- GitHub: Push this repo.
- Render: New → Blueprint → select repo.
- When prompted, set secrets in the Render dashboard (Blueprint defaults in
render.yamlcover the rest):- Database (pick one):
DATABASE_URL— Neon pooled connection string (…-pooler.….neon.tech/…), orSPRING_DATASOURCE_URL+SPRING_DATASOURCE_USERNAME+SPRING_DATASOURCE_PASSWORD— JDBC URL without embedded password (reliable on Render)
OPENAI_API_KEY— team default OpenAI key (set a billing cap in OpenAI)AEGIS_RAG_ENABLED— leavefalsefor demo (saves Neon compute);trueonly if you need related-story RAGAEGIS_RAG_BACKFILL_ON_STARTUP—falseunless doing a one-time index
- Database (pick one):
- Confirm blueprint values match your dashboard URL in
AEGIS_CORS_ALLOWED_ORIGINS(e.g.https://aegis-dashboard-c4vm.onrender.com). - Wait for deploy (first API Docker build may take several minutes).
- Open the
aegis-dashboardstatic site URL (not the API hostname); optional Settings override for OpenAI key. - SPA routing: the static site ships
public/_redirectsandrender.yamlincludes a/* → /index.htmlrewrite so/competitor/:nameworks on refresh.
Render deploys two public URLs — do not open the API root in a browser expecting the UI.
| Service | Live example | Use |
|---|---|---|
| Dashboard (UI) | https://aegis-dashboard-c4vm.onrender.com |
Open this in the browser |
| API (JSON/SSE) | https://aegis-api-vu7l.onrender.com |
REST + SSE only |
- The API has no homepage. Visiting the API root returns Spring’s 404 Whitelabel page — that is normal, not a crash.
- Health check:
GET /actuator/health→{"status":"UP",...} - Smoke test:
GET /api/insights/stats
Set AEGIS_CORS_ALLOWED_ORIGINS to your exact dashboard URL (committed in render.yaml for the reference deploy).
Public demo vs RAG: The reference deploy keeps RAG off for Neon cost. Ask Agent still answers from the selected article. To show full RAG (related stories + cited history), enable AEGIS_RAG_ENABLED=true on your fork or ask for a maintainer-led demo — see Public demo.
- Server:
OPENAI_API_KEYonaegis-api(harvest pipeline + hosted demo). - User BYOK: Settings sends key with
X-Aegis-Session; stored per session on the API (not global). DELETE clears only that session’s override.
| Idea | Benefit |
|---|---|
| Cross-competitor RAG | Retrieve related context across all tracked competitors, not just the current article’s competitor. |
| AuthN / multi-tenant | Per-tenant competitor lists and insight isolation; OAuth2 or API keys for B2B. |
| Synced read/star state | Server-backed bookmarks and read receipts (today: browser localStorage only). |
| Full-text search | Search article body and Ask Agent history, not just title + summary. |
| Job queue | Move heavy harvest + agent work off the web thread entirely (e.g. Redis/SQS) for burst handling. |
| Observability | Structured logging correlation IDs, metrics (Micrometer + Prometheus), tracing (OpenTelemetry). |
| Connector SDK | Declarative source config (YAML) with shared HarvesterSupport patterns to add feeds without a new class each time. |
| Alerting | Webhooks or email when threatLevel crosses thresholds or for specific categories. |
| Data retention | Scheduled archival/cleanup for large Neon datasets. |
Raw feeds are cheap; decisions are expensive. Aegis compresses signal by combining durable storage, structured LLM stages, a live UI, and cited Ask Agent answers grounded in your own harvested corpus—so teams react to competitor moves with context, not noise.
Add a LICENSE and contribution guidelines if you open-source the repo; align with your organization’s policy.


