Chapters
On this page
DOC-05 / Technical reference · Chapter 06
The Hub
The mothership's private cockpit: /hub/* routes, layered modules, agent consoles and two-tier authentication.
What the Hub is for
The Hub is the private administration interface of the Synedre OS agentic harness — a Nuxt application served locally on the mothership's host machine, reachable only over an SSH tunnel behind an nginx authentication gate (the "knock-gate"). From this cockpit the Founder pilots agents, chantiers, runs, negotiations, memory and infrastructure.
The Hub is standalone: it extends no other application. No internationalisation, no theme switch — it is a dark-only cockpit, in French, built for a single operator.
Layered architecture (Nuxt layers)
The Hub is not a monolith: its features are split into modules, loaded as Nuxt layers through the extends: key of the Nuxt config. Each layer ships a manifest.ts and its own Nuxt config — it is this "layer" pattern (not "Nuxt module") that makes loading work; placed in the wrong slot, these folders fail to start.
Each layer auto-discovers its folders via Nitro and can therefore contribute:
- pages under the
/hub/*route; - server endpoints under
/api/hub/*,/api/bo/*,/api/voice/*; - server-side code (database access, utilities).
mothership-app/
├── nuxt.config.ts extends: [ ./modules/* ] (≈28 layers)
├── layouts/hub.vue the cockpit's single layout
├── middleware/hub-auth.ts no-op (auth happens upstream)
├── server/
│ ├── middleware/ injects the founder session
│ ├── utils/session.ts requireEmployeeSession / getSession / ...
│ └── api/{bo,hub,voice}/ CORE endpoints
├── pages/hub/ a few CORE pages (index = 302 redirect)
└── modules/
├── agents/ (~37 /hub/* pages, ~100 /api/hub endpoints) ← the big one
├── chantier/ (cockpits + chantiers/[codename] + CLI console)
├── drill/ cicatrices/ conduite/ automates/ backlog/ …
└── fleet/ monitoring/ incidents/ invoicing/ reunions/ academy/ (server-only)
About a dozen modules are server-only (API + database only, no Vue page); their surface is consumed by other pages or by the Python automates.
The /hub/* route map
A handful of CORE pages live directly in the app; most /hub/* pages are carried by the modules. The agents module concentrates the vast majority (agents, brainstorm, chantier, clients, conseil, negotiations, runs, sessions, memory, doctrine, skills, settings, dispatch, reactor…).
The root route /hub is not a distinct page: it is a plain 302 redirect to the dashboard /hub/synedre.
| Module | /hub/* pages |
|---|---|
agents | the core (agents, brainstorm, chantier, clients, negotiations, runs, memory, doctrine, skills…) |
backlog | /hub/backlog, /hub/planning, /hub/system/backlog |
chantier | /hub/chantiers/[codename], /hub/cockpits |
drill | /hub/drill, /hub/drill/[agent] |
cicatrices | /hub/cicatrices |
conduite | /hub/conduites |
automates | /hub/system/automates |
Navigation (sidebar)
The cockpit's sidebar is hard-coded in the layout, organised into 7 zones by activity:
| Zone | Entries |
|---|---|
| PILOTING | Dashboard · emails · brainstorm · user-model |
| BUSINESS | clients · negotiations · advisory · legal |
| EXECUTION | backlog · chantier · runs · questions · new task · sessions |
| DOCTRINE & LEARNING | doctrine · constitution · scars · drill |
| AGENTIC | agents · reactor · skills · proposals · automates · dispatch · learning |
| MEMORY | memory (Brain) · semantic recall (RAG) |
| SYSTEM | sre · cost · backups · settings |
The active-entry marker fires on an exact match or on a sub-route (route.path.startsWith(to + '/')).
The three endpoint families
The /api/{bo,hub,voice} split is more than organisational: it carries a rights semantics.
/api/bo/* Back-Office "founder/cron" — under the knock-gate, sometimes WITHOUT app auth
/api/hub/* Employee cockpit surface — requireEmployeeSession almost everywhere
/api/voice/* Audio transcription — requireEmployeeSession + CSRF guard
/api/bo/* — Back-Office
Endpoints organised by sub-domain (learning, chantier, scars, cost, inbox, mothership, sre, tenants). One quirk: some /api/bo/* rely on the knock-gate rather than on requireEmployeeSession — the request already arrives "owner-only" from upstream. This is the case for the mailbox synchronisation endpoint, which a system cron calls directly (the native scheduler being no longer operational, a host cron acts as a safety net).
/api/hub/* — cockpit surface
Roughly 125 endpoints, the overwhelming majority in the agents module. Nearly all call requireEmployeeSession; the exceptions are internal helpers or SSE streams. Typical pattern:
export default defineEventHandler(async (event) => {
requireEmployeeSession(event)
...
})
Main families: chantier/, travail/, runs/, negociations/, conseil/, clients/, brainstorm/, memory/, doctrine/, skills/, sessions/, questions/, settings/, dispatch, synedre-cli/.
/api/voice/* — transcription
A single endpoint: a multipart POST (audio WAV required, optional locale) returning { text, locale, duration_ms, model, backend }. Guardrails: requireEmployeeSession, a maximum audio size, a per-session rate limit, audio never persisted to disk (RAM only), and CSRF defence by origin verification.
Two-tier authentication
┌─────────────────────────────────────────────────────────────┐
│ INFRA LAYER (the real gate) │
│ SSH tunnel (local) + nginx knock-gate (cookie + slug) │
└───────────────────────────────┬─────────────────────────────┘
│ request already arrives "owner-only"
┌────────────────────────────────▼─────────────────────────────┐
│ APPLICATION LAYER (Nuxt) │
│ middleware: AUTOMATICALLY injects a founder session │
│ → requireEmployeeSession always sees a valid employee │
└──────────────────────────────────────────────────────────────┘
The session guards
getSession(event): decodes the session cookie (base64url JSON signed with HMAC-SHA256). Returnsnullif absent or invalid.requireEmployeeSession(event): returns the session ifuserType === 'employee', otherwise throws a 401. This is the standard guard for/api/hub/*and/api/voice/*.requireFounderSession: additionally requires SuperAdmin status (403 otherwise).requireRoleOrSaas(event, roles): requires a given role OR SuperAdmin status.isSuperAdminSaaS: a static allow-list of addresses, deliberately hard-coded (not in the database) — "touching this list means a code change plus a review".
Session shape: employeeId, email, firstname, lastname, role, profileId, clientId, userType, isAdmin.
The founder auto-session
A server middleware, on every request without a valid session cookie, signs and sets a cookie whose payload is a constant FOUNDER_SESSION (employee #1, SuperAdmin role, admin, valid 30 days). Consequence: on the mothership runtime, requireEmployeeSession always passes — the real security is the infrastructure (SSH + knock-gate). This middleware is scoped to the mothership: it exists neither in the OSS core nor in tenants; on public sites requireEmployeeSession remains mandatory.
The hub-auth middleware is a no-op
The Nuxt hub-auth middleware is today a no-op (defineNuxtRouteMiddleware(() => {})), disabled because the nginx knock-gate and the SSH tunnel handle authentication upstream. It stays exported because many pages still reference it through definePageMeta({ middleware: 'hub-auth' }).
The nginx knock-gate (cookie + slug) lives on the nginx infrastructure side, not in the Nuxt repository. See the VPS runbook.
Hub layout
- Single layout: as the app is standalone, it overrides no parent layout.
- No dynamic user: a fixed
userstub is set (Nuxt auth removed). useHead: titleTemplate "… — Synedre OS", the octopus favicon.- Pages declare
definePageMeta({ layout: 'hub', middleware: 'hub-auth', ssr: false }). Thessr: falseis common on real-time pages (reactor, chantier) — client-only rendering.
The console surfaces
The Hub materialises several agent-piloting "consoles". The central data model rests on the Python entities under synedre/ac_entities/ and PostgreSQL tables.
Runs — /hub/runs
A run (entity sy_run) is a scoped execution of Atlas (chat / email / cron) over a mothership or tenant perimeter. The response exposes { id_run, source, trigger, scope, title, status, ref_type, ref_id, date_add, finished_at }. Source atlas-inbox = email, console = chat. Emails with a question/chantier/noise intent are not materialised as runs. Note: sy_task_run is not a run.
Chantiers — /hub/chantier, /hub/cockpits
A chantier breaks down into travaux, and those into tasks (entities ChantierEntity / TravailEntity / TacheEntity). The chantier module notably adds the live CLI console (cli-session/{start,stop,reset}) and an SSE stream of agent events, plus the atlas-runs endpoints (draft-reply, spawn-fix, profile). Canonical creation goes through create_with_skeleton (the 7-step procedure).
Naming debt: the chantier and the travail still use the legacyps_ac_*prefix, while the tasks and the link tables have already moved to thesy_*prefix (a Strangler Fig migration in progress).
CLI console — synedre-cli
The synedre-cli/ endpoints (status, send-keys, stream) forward to a host daemon that drives a real Claude CLI session from the cockpit. If the daemon is down: no 5xx, the state returns alive=false, and the error message is never exposed to the client.
Negotiations — /hub/negociations
Table sy_negociation. Pipeline: forwarding an email → Atlas classifies the intent as negociation → creation of a sy_negociation item → the negotiation page where the agents work (need, audit, proposal, qualification…).
Fleet — server-only module
The fleet module has no page of its own (yet). Its table describes tenant instances. The operational source of truth for the VPS fleet, however, remains a separate table dedicated to server mapping.
The fleet table holds password-/API-key-type columns inherited from the former PaaS — inconsistent with the "zero secret in the business database" doctrine. To be audited; do not treat it as the canonical secrets source.
Reactor — /hub/reactor
A live orbital view: three concentric rings by agent group (direction, framing/execution, validation), the octopus logo at the centre, an SSE stream via a dedicated composable. Client-only rendering, adapted from the public marketing page.
Entry points to pick up the code
| You want to… | Look at |
|---|---|
| Add a sidebar entry | the cockpit layout (navigation constant) |
| Add a /hub/X page | modules/<module>/pages/hub/X.vue + definePageMeta({ layout:'hub' }) |
| Add a cockpit endpoint | modules/<module>/server/api/hub/... + requireEmployeeSession(event) |
| Understand the auth | server/utils/session.ts + the auto-session middleware |
| Wire a new layer | the extends: key of the Nuxt config + the module's manifest.ts |
| Runs / chantiers (database) | entities synedre/ac_entities/ + tables sy_run, ps_ac_chantier, sy_negociation |
No secret in clear text here. The session-cookie signing secret is consumed by an encryption utility (HMAC); its environment variable lives in the unversioned .env files, never in the code.