Chapters

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
agentsthe 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:

ZoneEntries
PILOTINGDashboard · emails · brainstorm · user-model
BUSINESSclients · negotiations · advisory · legal
EXECUTIONbacklog · chantier · runs · questions · new task · sessions
DOCTRINE & LEARNINGdoctrine · constitution · scars · drill
AGENTICagents · reactor · skills · proposals · automates · dispatch · learning
MEMORYmemory (Brain) · semantic recall (RAG)
SYSTEMsre · 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). Returns null if absent or invalid.
  • requireEmployeeSession(event): returns the session if userType === '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 user stub is set (Nuxt auth removed).
  • useHead: titleTemplate "… — Synedre OS", the octopus favicon.
  • Pages declare definePageMeta({ layout: 'hub', middleware: 'hub-auth', ssr: false }). The ssr: false is 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 legacy ps_ac_* prefix, while the tasks and the link tables have already moved to the sy_* 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 entrythe cockpit layout (navigation constant)
Add a /hub/X pagemodules/<module>/pages/hub/X.vue + definePageMeta({ layout:'hub' })
Add a cockpit endpointmodules/<module>/server/api/hub/... + requireEmployeeSession(event)
Understand the authserver/utils/session.ts + the auto-session middleware
Wire a new layerthe 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.