Beacon — Investigation Workbench
Status & scope
Status: Implemented (partial) — beacon/ Angular frontend + FastAPI backend live; pages (Home, Monitor, Explore, Insight, Edition, Inbox, Auditor) shipping. Outstanding gaps tracked in beacon/frontend/GAP_ANALYSIS.md.
Package: beacon
Depends on: platform.axonis-core, platform.service-contract, platform.ingress-routing
Milestone: P2
Purpose
Beacon is the investigation workbench: an Angular frontend served by a Python/FastAPI backend. The backend is a first-class Axonis service (conforms to platform.service-contract), not a simple proxy. It provides:
- Static file serving — serves the Angular frontend as an SPA
- MCP proxy — forwards tool calls from the frontend to the appropriate backend service
- Chat routing — routes chat requests to Oracle (when available) or Cortex (standalone), via the agentspace ingress
- Optional LLM — Beacon may have its own LLM for frontend-specific orchestration
- Memory access — uses axonis-core MemoryService for session memory (conversation history, preferences)
Architecture
Browser → Beacon (/api/mcp, /api/v1/chat) → agentspace.cluster.local → Oracle or Cortex
Browser → Beacon (/) → Angular SPA (static files)
Beacon does not detect Oracle availability at the application level. It uses the platform's ingress topology (platform.ingress-routing): all chat and MCP calls go to agentspace.cluster.local, which Traefik routes to Oracle (when deployed) or Cortex (fallback). Beacon is agnostic to which backend is active.
Package Structure
beacon/
beacon/
__init__.py
config.py # Settings: AGENTSPACE_URL, CORTEX_URL, ORACLE_URL, BEACON_LLM_*
core/
__init__.py
server/
__init__.py
__main__.py # Starlette: /agentspace (proxy), /api/v1, /health, /service-info, / (SPA)
api/
__init__.py
routes.py # REST API routes (/chat, /chat/history, /chat/{conv_id})
mcp/
__init__.py
server.py # Beacon's own MCP tools (if any)
proxy.py # MCP proxy — forwards calls to agentspace ingress
middleware/
__init__.py
auth.py # Token validation (forwards Bearer token to backend services verbatim)
frontend/ # Angular source
src/
dist/ # Built output, served as static files
charts/beacon/ # Bitnami Helm chart
.gitlab-ci.yml
.gitlab-ci-templates/
Dockerfile
pyproject.toml
Endpoints
| Method | Path | Purpose |
|---|---|---|
| GET | / |
Angular SPA (index.html) |
| POST | /api/mcp |
MCP proxy → agentspace ingress |
| POST | /api/v1/chat |
Chat proxy → agentspace /api/v1/chat |
| GET | /api/v1/chat/history |
Memory recall via MemoryService |
| DELETE | /api/v1/chat/{conv_id} |
Clear conversation (ConversationStore) |
| GET | /health |
Standard health check |
| GET | /service-info |
Standard service info |
Static file serving
The Angular dist/ directory is mounted at /. Beacon's backend serves index.html for all unmatched paths so the Angular router handles client-side navigation. Backend API routes (/api/*, /health, /service-info) take priority over the SPA fallback.
Backend Routing
All AI calls are forwarded to the agentspace ingress (platform.ingress-routing), which resolves to Oracle or Cortex automatically:
- MCP tool calls →
POST {AGENTSPACE_URL}/agentspace/mcp - Chat →
POST {AGENTSPACE_URL}/api/v1/chat
Direct service URLs are available for exceptional cases (admin tooling, explicit bypass):
- Direct Cortex →
{CORTEX_URL}(bypasses Oracle and the agentspace ingress) - Direct Oracle →
{ORACLE_URL}(bypasses the agentspace ingress)
Beacon uses httpx.AsyncClient for all upstream calls. Bearer tokens are forwarded verbatim.
Configuration
AGENTSPACE_URL http://agentspace.cluster.local # Primary routing target (ingress-resolved)
CORTEX_URL http://cortex:8002 # Direct Cortex (bypass ingress)
ORACLE_URL http://oracle:8001 # Direct Oracle (bypass ingress)
BEACON_LLM_PROVIDER (optional) # LLM provider for Beacon's own use
BEACON_LLM_MODEL (optional)
BEACON_LLM_API_KEY (optional)
AGENTSPACE_URL defaults to http://agentspace.cluster.local in K8s. For local development (Cortex only, no K8s), set it to http://localhost:8002 to point directly at Cortex.
Multi-Agent Sessions
Beacon supports multiple concurrent agent conversations per user. Each conversation has its own conversation_id. Beacon generates a new conversation_id when none is provided, and passes it through to the backend on every request. MemoryService scoping by conversation_id prevents cross-session context contamination (see platform.axonis-core).
Example: a user may have one conversation running a broad Oracle analysis (conversation_id=A) and simultaneously working on a focused Cortex investigation (conversation_id=B). Both are active through Beacon; their memory contexts are fully isolated.
class ChatRequest(BaseModel):
message: str
conversation_id: str = "" # empty = new conversation; Beacon generates a UUID if absent
model: str = "default"
On each chat request, if conversation_id is empty, Beacon generates a UUID and returns it in the response. The frontend tracks conversation_id per session and includes it in subsequent requests.
Training dashboard
Beacon provides a workbench surface that renders live training progress for in-flight model runs. The data source is titan's training-metrics surface (component.titan.runtime#training-metrics); Beacon owns only the UI — it surfaces what titan exposes and does not compute or persist training metrics itself.
- #REQ.training-dashboard-view — Beacon MUST provide a workbench view that displays live training progress for a run: per-run status/stage and accumulating quality metrics sourced from
component.titan.runtime#REQ.training-run-metrics, refreshed while the run is in flight. - #REQ.training-dask-metrics-view — the view MUST surface the run's live Dask metrics (cluster/worker status, task progress, resource utilisation) sourced from
component.titan.runtime#REQ.training-dask-metrics, e.g. by embedding or linking the run's Dask scheduler dashboard.
LLM Capability (Optional)
If BEACON_LLM_* is configured, Beacon may run its own LLM for frontend-specific orchestration (e.g., interpreting user intent, summarising chat history, generating suggested next steps) before forwarding to the backend. Configured via LLMSpec.from_env(prefix="BEACON_LLM") per platform.service-contract.
Falls back to simple passthrough if no LLM is configured. llm_spec.is_configured() must be checked before every LLM call.
Memory
Beacon uses MemoryService from axonis.memory.service with service="beacon". It stores conversation metadata (not the full LLM transcript — that is stored by Oracle or Cortex). Retrieval via GET /api/v1/chat/history surfaces the last N turns from the relevant conversation_id.
from axonis.memory.service import MemoryService
memory = MemoryService(service="beacon")
history = memory.recall(query="", conversation_id=conv_id, token=caller_token)
Entry Point
# server/__main__.py
from starlette.applications import Starlette
from starlette.routing import Mount, Route
from starlette.staticfiles import StaticFiles
SERVICE_NAME = "beacon"
SERVICE_VERSION = "<version>"
app = Starlette(
routes=[
Route("/health", health),
Route("/service-info", service_info),
Mount("/api/v1", app=rest_app),
Mount("/api/mcp", app=mcp_proxy_app),
Mount("/", app=StaticFiles(directory="frontend/dist", html=True)),
],
lifespan=lifespan,
)
Dependencies
[project]
dependencies = [
"axonis-core",
"starlette>=0.36.0",
"fastapi>=0.110.0",
"uvicorn[standard]>=0.29.0",
"httpx>=0.27.0",
]
Invariants
- Beacon never calls backend services from the Angular frontend directly. All calls go through Beacon's backend.
- Beacon does not implement domain logic. It proxies, routes, and optionally orchestrates.
- Bearer tokens are forwarded verbatim to backend services. Beacon does not validate, modify, or mint tokens.
- MCP tool calls always go to the agentspace ingress unless a specific direct URL is explicitly configured and the caller knows it is bypassing Oracle.
- Beacon is the only service that may serve static files (the Angular frontend).
AGENTSPACE_URLmust always be configurable via environment variable. Beacon must not hard-code any service hostname.
Test Expectations
- MCP proxy tests (mock agentspace backend, verify forwarding and token passthrough)
- Chat proxy tests (verify conversation_id generation and forwarding)
- Multi-session isolation tests (two concurrent conversation_ids, verify no context bleed)
- Memory tests (MemoryService recall with forwarded token)
- Static file serving tests (SPA fallback for unmatched paths)
- Optional LLM tests (configured and unconfigured paths)
- Auth tests (token forwarded, not validated or modified by Beacon)
- Health check and service-info tests
Depends on: platform.axonis-core, platform.ingress-routing, platform.service-contract
Required by: component.beacon.ticketing, component.oracle.apollo