# 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 ### 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` | Tasks in progress older than 1 hour | | `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` | Approve task (in-progress β†’ done) | | `POST` | `/api/v1/tasks/{id}/reject` | 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) | | `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}` | Save config file (atomic write) | ### 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)