# Nexus Nexus is the operations platform for the Noveria ecosystem. OpenClaw is an adapter-backed agent runtime, not a dependency of the frontend or domain model. > πŸ“‹ **Architektur-Review** (2026-06-22): Board-first Orchestrierung, sichere > Backend-BrΓΌcke und Gateway-Integration geprΓΌft. Siehe > [`docs/architecture-board-first-orchestration.md`](docs/architecture-board-first-orchestration.md) > CI runs automatically on every push. CD runs **inside the green CI run** > on main or can be triggered **manually** (workflow_dispatch). Deploy reads > `VERSION` but does not mutate Git or create tags. Rollback and database backup > are separate manual workflows. > See [phases/deployment.md](phases/deployment.md) for full CD documentation. ## Current foundation - Vue 3, TypeScript, Pinia, Vue Router and Tailwind CSS - ASP.NET Core 10 REST API (Minimal API pattern) - Entity Framework Core and PostgreSQL - JWT owner authentication with rotating refresh sessions - `IAgentRuntime` abstraction with an OpenClaw adapter (Ollama and NVIDIA removed β€” OpenClaw-only) - Responsive dark-mode operations dashboard - Traefik reverse-proxy with Let's Encrypt TLS on `nexus.noveria.net` ## Local/container start ```bash cp .env.template .env # Replace every placeholder, especially POSTGRES_PASSWORD, JWT_KEY and BOOTSTRAP_OWNER_EMAIL. docker compose up --build -d curl http://127.0.0.1:18880/health ``` On an empty database the API creates exactly one owner from `BOOTSTRAP_OWNER_EMAIL`, derives the initial display name from that email, and logs a generated temporary password once. After first seed the password lives only in PostgreSQL. Existing databases are never overwritten by the bootstrap process. The API is exposed via Traefik reverse-proxy with automatic Let's Encrypt TLS. Health checks, rate limiting, and security headers are active. ## Workspace mounts The API container mounts agent workspaces from the host for file browsing and the config editor. These are mounted under `/mnt/workspace-{agentId}`: | Host path | Container mount | |---|---| | `/home/projekte_bao/openclaw/data/openclaw/workspace-iris` | `/mnt/workspace-iris` | | `/home/projekte_bao/openclaw/data/openclaw/workspace-programmer` | `/mnt/workspace-programmer` | | `/home/projekte_bao/openclaw/data/openclaw/workspace-reviewer` | `/mnt/workspace-reviewer` | | `/home/projekte_bao/openclaw/data/openclaw/workspace-architekt` | `/mnt/workspace-architekt` | | `/home/projekte_bao/openclaw/data/openclaw/workspace-researcher` | `/mnt/workspace-researcher` | | `/home/projekte_bao/openclaw/data/openclaw/workspace-executor` | `/mnt/workspace-executor` | ## Frontend architecture ### Source layout ``` frontend/src/ β”œβ”€β”€ App.vue # Root shell with sidebar + standalone views β”œβ”€β”€ main.ts # App bootstrap β”œβ”€β”€ router.ts # Vue Router config β”œβ”€β”€ types/ β”‚ β”œβ”€β”€ index.ts # Re-exports β”‚ β”œβ”€β”€ agent.ts # AgentInfo, AgentDetail, TeamMember β”‚ β”œβ”€β”€ config.ts # ConfigFileInfo, SecurityStatus β”‚ β”œβ”€β”€ dashboard.ts # OperationsSnapshot, RuntimeStatus, etc. β”‚ └── project.ts # MemoryFile, DocFile types β”œβ”€β”€ stores/ β”‚ β”œβ”€β”€ auth.ts # Auth store (JWT, login/refresh/logout) β”‚ └── operations.ts # Operations store (snapshot, CRUD, approve/reject) β”œβ”€β”€ services/ β”‚ └── api.ts # Authenticated fetch wrapper (auto-refresh) β”œβ”€β”€ composables/ β”‚ └── useTime.ts # Greeting composable (Morgen/Tag/Abend) β”œβ”€β”€ views/ β”‚ β”œβ”€β”€ LoginView.vue β”‚ β”œβ”€β”€ DashboardView.vue # New dashboard (Phase 2) β”‚ β”œβ”€β”€ ProjectDetailView.vue β”‚ β”œβ”€β”€ SettingsView.vue β”‚ β”œβ”€β”€ MemoryView.vue β”‚ β”œβ”€β”€ DocsView.vue β”‚ β”œβ”€β”€ TeamView.vue β”‚ β”œβ”€β”€ SecurityView.vue β”‚ β”œβ”€β”€ IncidentsView.vue β”‚ β”œβ”€β”€ CalendarView.vue β”‚ β”œβ”€β”€ AgentDetailView.vue β”‚ └── AgentsIndexView.vue β”œβ”€β”€ components/ β”‚ β”œβ”€β”€ layout/ β”‚ β”‚ β”œβ”€β”€ AppSidebar.vue β”‚ β”‚ └── AppHeader.vue β”‚ └── dashboard/ # New dashboard components (Phase 2) β”‚ β”œβ”€β”€ IrisPanel.vue # Agent overview + metrics + chat β”‚ β”œβ”€β”€ OperationsFeed.vue # Live activity feed with filters β”‚ β”œβ”€β”€ AgendaPanel.vue # Daily agenda with checkboxes + localStorage β”‚ β”œβ”€β”€ ActiveInitiatives.vue # Project cards with progress β”‚ └── RecentlyFinished.vue # Quick status chips └── ModuleView.vue ``` ### App.vue `standaloneViews` whitelist New views must be registered in the `standaloneViews` computed property in `App.vue` (line ~34). Without this entry, `RouterView` will not render the component β€” the route is valid but the template stays empty. ### New dashboard (Phase 2) The dashboard was redesigned with a three-column layout: - **IrisPanel** β€” Agent avatar/greeting, metrics counters (open tasks, blocked, overdue, today), AI suggestions, quick action buttons, and an inline chat box. - **OperationsFeed** β€” Searchable/filterable activity feed with colour-coded status dots and yesterday/today/week grouping. - **AgendaPanel** β€” Daily agenda with checkable items persisted in `localStorage` under key `nexus-agenda-done`. Items are sectioned into "Heute", "Morgen", and "ÜberfΓ€llig". - **ActiveInitiatives** β€” Project initiative cards with progress bars, status badges (healthy/attention/blocked/paused/completed), and last activity timestamps. - **RecentlyFinished** β€” Horizontally scrollable chip list of recently completed items. ### Authentication - Passwords use versioned PBKDF2-SHA256 hashes with random salts and 210,000 iterations. - Access tokens expire after 15 minutes and are held only in browser memory. - Refresh tokens are random, stored only as SHA-256 hashes in PostgreSQL, rotated on use and checked for reuse. - The browser receives the refresh token only as a `HttpOnly`, `Secure`, `SameSite=Strict` cookie. - Login and refresh endpoints are rate-limited per forwarded client IP (5 attempts/minute). - All `/api/v1` operations routes require a valid access token; `/health` remains public. - Swagger is enabled only in the Development environment. - CSRF protection via `X-CSRF-TOKEN` header and `nexus-csrf` cookie (not HttpOnly). ### Security - Never commit `.env`. - Generate `JWT_KEY` from at least 32 random bytes. - Rotate any credential that has appeared in chat before using it. - Do not expose PostgreSQL or the API container directly. - Keep OpenClaw behind the `IAgentRuntime` contract. - Keep the API reachable only through the bundled web proxy or another trusted reverse proxy. ## Frontend routes (SPA) The SPA uses history-mode routes. Standalone views (whitelisted in App.vue): | Route | View | Description | |---|---|---| | `/login` | LoginView | Owner login | | `/dashboard` | DashboardView | Operations snapshot with IrisPanel, Feed, Agenda | | `/memory` | MemoryView | Memory file browser with search | | `/docs` | DocsView | Documentation file browser | | `/team` | TeamView | Agent team org map | | `/security` | SecurityView | Security status center | | `/projects/:id` | ProjectDetailView | Project detail | | `/incidents` | IncidentsView | Incident diary | | `/calendar` | CalendarView | Cron/scheduler overview | | `/agents` | AgentsIndexView | Agent inventory | | `/agents/:id` | AgentDetailView | Agent detail + config editor | | `/settings` | SettingsView | Profile + password management | Legacy ModuleView routes (not standalone, rendered through `ModuleView.vue`): | Route | Name | Description | |---|---|---| | `/projects` | Projects | Project portfolio | | `/tasks` | Task Board | Task board with visible parent/child agent flow | | `/models` | Models | Provider routing status | | `/activity` | Activity | Audit timeline | | `/chat` | Mobile Chat | Owner-chat preview | ## API endpoints ### MCP Agent Data Plane Nexus exposes an MCP endpoint at `/mcp` for agent-facing board operations. It uses the official `ModelContextProtocol.AspNetCore` SDK with stateless streamable HTTP transport. Tools are a thin facade over `ITaskBridgeService`; they must not duplicate board business logic. Auth follows the bridge rules: requests provide `X-Agent-Id` and/or `X-Nexus-Api-Key`. Secrets stay in OpenClaw/Gateway config and are never embedded in frontend code. Registered tools: | Tool | Purpose | |---|---| | `nexus_get_board` | Full task board | | `nexus_agent_overview` | Waiting/stale workflow overview | | `nexus_get_task` | Single task | | `nexus_get_children` | Child tasks for a parent | | `nexus_get_activity` | Task activity history | | `nexus_create_task` | Create parent/standalone task | | `nexus_create_child_task` | Create visible delegation child task | | `nexus_update_status` | Update status using the canonical enum only | | `nexus_append_activity` | Append checkpoint/activity | | `nexus_handoff` | Handoff to a known agent | The compatible `/api/bridge` HTTP facade remains available for internal diagnostics and transition clients. New agent integrations should use MCP; `/api/dashboard` is UI/admin surface, not an agent contract. ### Mission Control Gateway Plane Nexus keeps the Browser -> Nexus -> OpenClaw boundary: the frontend never talks to OpenClaw directly. Read-only Gateway status is exposed through `GET /api/dashboard/gateway`; it reports reachability, discovered Gateway version and the optional `Integrations:OpenClaw:RequiredVersion` pin. A set pin does not mutate production config, but makes protocol drift visible in the UI. Agent activity shown as "Thinking" is redacted before display. Lines containing token, password, bearer, authorization, API key or secret markers are replaced with a redaction marker. Persisted audit-worthy events should be written as short Activity entries, not raw session transcripts. Nexus activity updates stream live through the Dashboard SSE channel and are filtered by explicit `agentIds`. Gateway session history is read-only fallback data: it is fetched on demand, redacted before display and not persisted as a long-term raw transcript. Agent "Now" and "Today" summaries are deterministic derivations from redacted Nexus activity plus redacted Gateway history; Nexus does not call an LLM to summarize this feed. Config writes and approval actions are owner-only. Config saves validate before replacement, keep a `.bak` when an existing file is replaced, write audit events without file contents or secrets and return structured `validation`, `backup` and `reloadCheck` results. Workspace Markdown hot reload is currently reported truthfully as `not_supported`; JSON validation exists in the save path but JSON files are not exposed unless they are explicitly allowlisted for editing. ### Backend Bridge (Agent-zu-Backend, NICHT Frontend) Der `/api/bridge/` Pfad ist ein strukturierter MCP-artiger Kommando-Adapter fΓΌr die Agent-zu-Backend-Kommunikation. Kein Frontend-Code ruft diese Endpunkte auf. Auth: `X-Agent-Id` Header, `X-Nexus-Api-Key`, oder JWT. Rate-Limited (30/min). | Methode | Pfad | Kommando | Beschreibung | |---|---|---|---| | `GET` | `/api/bridge/health` | β€” | Bridge-Health-Check | | `POST` | `/api/bridge/tasks` | `create_task` | Neue Top-Level-Task erstellen | | `POST` | `/api/bridge/tasks/{id}/children` | `create_child_task` | Child-Task unter Parent erstellen | | `PATCH` | `/api/bridge/tasks/{id}/status` | `update_status` | Task-Status Γ€ndern | | `POST` | `/api/bridge/tasks/{id}/activity` | `append_activity` | AktivitΓ€tseintrag anhΓ€ngen | | `POST` | `/api/bridge/tasks/{id}/handoff` | `handoff` | Task an anderen Agent ΓΌbergeben | | `GET` | `/api/bridge/board` | `get_board` | VollstΓ€ndiges Task-Board | | `GET` | `/api/bridge/tasks/{id}` | `get_task` | Einzelne Task abrufen | | `GET` | `/api/bridge/tasks/{id}/children` | `get_children` | Child-Tasks abrufen | | `GET` | `/api/bridge/tasks/{id}/activity` | `get_activity` | Task-AktivitΓ€t abrufen | | `GET` | `/api/bridge/agent-overview` | `get_agent_overview` | Agent-Workflow-Übersicht | Response-Format (TaskBridgeCommandResponse): ```json { "ok": true, "command": "create_task", "data": { ... }, "error": null, "timestamp": "2026-06-22T15:30:00.000Z" } ``` ### Health & Auth (public or rate-limited) | Method | Path | Auth | Description | |---|---|---|---| | `GET` | `/health` | No | Health check with runtime + PostgreSQL | | `GET` | `/api/v1/auth/csrf` | No | Get CSRF token | | `POST` | `/api/v1/auth/login` | No (rate-limited) | Login with email/password | | `POST` | `/api/v1/auth/refresh` | No (rate-limited) | Refresh access token | | `POST` | `/api/v1/auth/logout` | No | Clear refresh token | | `GET` | `/api/v1/auth/me` | Yes | Current user info | | `PATCH` | `/api/v1/auth/profile` | Yes | Update display name | | `POST` | `/api/v1/auth/change-password` | Yes | Change password (min 10 chars) | ### Operations | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/operations/snapshot` | Full operations snapshot (runtime, agents, projects, tasks, activity, metrics) | ### Parent/Child task flow The Task Board now models OpenClaw delegation as a visible parent/child flow: - Iris keeps the parent task `In progress` while delegated work is running. - Delegated agent work is represented as visible child tasks linked via `parentTaskId`. - Child tasks use the normal visible states (`Backlog`, `In progress`, `Review`, `Blocked`, `Done`) instead of a separate hidden delegation lane. - Agent progress hints on parent tasks derive from recent activity and child-task status summaries. - Full workflow documentation: [`docs/openclaw-task-board-flow.md`](docs/openclaw-task-board-flow.md) ### Projects | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/projects` | List all projects | | `POST` | `/api/v1/projects` | Create project | | `GET` | `/api/v1/projects/{id}` | Get project detail | | `PATCH` | `/api/v1/projects/{id}` | Update project (name, description, status) | | `DELETE` | `/api/v1/projects/{id}` | Delete or archive project (archives if has tasks) | ### Tasks | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/tasks` | List all tasks | | `POST` | `/api/v1/tasks` | Create task | | `GET` | `/api/v1/tasks/pending-approval` | Owner-only pending approvals | | `PATCH` | `/api/v1/tasks/{id}` | Update task (title, priority, projectId) | | `PATCH` | `/api/v1/tasks/{id}/state` | Update task state | | `POST` | `/api/v1/tasks/{id}/approve` | Owner-only approve task (in-progress -> done) | | `POST` | `/api/v1/tasks/{id}/reject` | Owner-only reject task (in-progress -> backlog) | | `DELETE` | `/api/v1/tasks/{id}` | Delete task (only done/backlog states) | ### Agents | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/agents` | List all agents | | `GET` | `/api/v1/agents/{id}` | Agent detail (with sub-agents, identity) | | `GET` | `/api/v1/agents/{id}/activity` | Agent-specific activity (last 50) | | `GET` | `/api/v1/agents/{id}/summary` | Redacted deterministic Now/Today summary | | `POST` | `/api/v1/agents/{id}/command` | Send command to agent | | `GET` | `/api/v1/agents/{id}/config` | List agent config files (IDENTITY.md, SOUL.md, etc.) | | `GET` | `/api/v1/agents/{id}/config/{fileName}` | Read config file content | | `PUT` | `/api/v1/agents/{id}/config/{fileName}` | Owner-only validated config save with backup/audit/reload result | ### Memory & Docs | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/memory` | List memory files (daily + MEMORY.md) | | `GET` | `/api/v1/memory/search?q=` | Full-text search in memory files | | `GET` | `/api/v1/memory/{name}` | Get memory file content | | `GET` | `/api/v1/docs` | List documentation files by category | | `GET` | `/api/v1/docs/{**path}` | Get doc file content (catch-all) | ### Activity & Team | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/activity` | Paginated activity feed (supports `type`, `sort`, `page`, `pageSize`) | | `GET` | `/api/v1/routing` | Model routing status | | `GET` | `/api/v1/team` | Team org map with identity excerpts | | `GET` | `/api/v1/incidents` | List incident diary entries | | `GET` | `/api/v1/incidents/{name}` | Get incident detail | | `GET` | `/api/v1/security/status` | Security configuration status | ### Calendar (Scheduler) | Method | Path | Description | |---|---|---| | `GET` | `/api/v1/calendar` | Cron job overview (gateway or fallback) | | `GET` | `/api/v1/calendar/upcoming` | Upcoming cron jobs | ### Chat | Method | Path | Auth | Description | |---|---|---|---| | `POST` | `/api/v1/chat` | Yes (rate-limited) | Route message through IAgentRuntime | Project and task mutations create activity records. The API applies committed EF Core migrations after PostgreSQL becomes healthy. No destructive endpoints are implemented on the data layer. ## State machine (tasks) Tasks follow a simple state machine: ``` Backlog β†’ In progress β†’ Done Backlog β†’ Blocked β†’ In progress / Done ``` - Only `In progress` or `Blocked` tasks can be approved (β†’ Done) or rejected (β†’ Backlog). - Only `Done` or `Backlog` tasks can be deleted. ## Runtime chat and model routing `POST /api/v1/chat` routes authenticated owner messages through the `IAgentRuntime` contract. The browser never receives a Gateway password or model provider key. Conversation IDs are stable per browser and Iris is the default agent target. The configured model-routing policy routes through the OpenClaw Gateway only. Ollama and NVIDIA providers have been removed. Currently active models: | Agent | Model | |-------|-------| | Iris | `openai/gpt-5.4` | | Programmer, Executor | `deepseek/deepseek-v4-flash` | | Reviewer, Architekt, Researcher | `deepseek/deepseek-v4-pro` | Claude models (Sonnet 4.6, Opus 4.6/4.7/4.8) are available via `claude-cli` backend. The Settings module reports runtime and provider state without exposing credentials. ## CI/CD ### CI β€” Automatic Every push to `main` triggers `.gitea/workflows/ci.yaml`: - **Backend**: .NET restore β†’ build β†’ test - **Frontend**: pnpm install β†’ type-check β†’ test β†’ build - **Security**: Scan for hardcoded secrets in source code CI must never break. If it does, Reviewer fixes. ### CD β€” Auto + Manual (CD v4) Deployment can happen automatically or manually: #### Auto-Deploy (after successful CI jobs on main) - Runs as the final `Deploy Nexus` job in `.gitea/workflows/ci.yaml` - Starts only after backend, frontend, and security jobs succeed on `main` - Deploys the current `main` version after CI succeeds. - This replaces `workflow_run`, which did not create deploy runs in this Gitea 1.26.3 installation. - The deploy script reads `VERSION`; it does not mutate Git, bump versions, or create tags #### Manual Deploy (`workflow_dispatch`) 1. DevOps triggers `Deploy Nexus Manual` in Gitea Actions 2. Workflow validates `VERSION`, builds and deploys `main` 3. Health check + smoke test verify the deployment #### Rollback (`workflow_dispatch`) 1. DevOps triggers `Rollback to Previous Version` in Gitea Actions 2. Enters target git tag (e.g. `v0.2.49`) + confirmation `ROLLBACK` 3. Workflow checks out the tag, rebuilds with `--no-cache`, redeploys 4. Health check + smoke test verify the rollback #### Database Backup (`workflow_dispatch`) 1. DevOps triggers `Database Backup` in Gitea Actions 2. Optionally also copies backup to a host path (`/home/projekte_bao/backups`) 3. Workflow dumps PostgreSQL via `pg_dumpall`, gzips, and uploads as a Gitea artifact 4. Artifacts are retained for 90 days (configurable) 5. Optional nightly schedule (uncomment the cron trigger in `backup.yaml`) #### Failure Handling When deploy or rollback fails: - **DevOps (Architekt)** analyses the error - **Reviewer (Code-Fixer)** fixes the problem - **DevOps** re-deploys to verify the fix The workflow outputs a formatted handoff message with the job URL. Full CD documentation: [phases/deployment.md](phases/deployment.md)