Applications
The Sustentus monorepo contains eight applications. The platform itself is apps/web; the others are
the agentic app, the tenant management console, the marketing site, role-based dashboards, two Nextra
doc sites, and Storybook.
Overview
| Application | Purpose | Stack | Port |
|---|---|---|---|
| Web | Main platform — all six persona portals + API/AI | Next.js 16 | 3000 |
| Agent | The agentic layer, beside the platform | Next.js 16 | 3007 |
| Console | Tenant management for Sustentus staff + partners | Next.js 16 | 3006 |
| Dashboards | Role-based analytics dashboards | Next.js 16 | 3005 |
| Marketing | Public marketing site | Next.js 15 | 3001 |
| Documentation | This documentation site | Nextra | — |
| Help | Help & support centre | Nextra | — |
| Storybook | Component docs for @sustentus/ui | Storybook 10 | 3002 |
Application details
Web (apps/web)
The main platform — Next.js 16 with the App Router and Turbopack. Hosts the portals for all six
personas (admin, CSM, SDM, expert, vendor, customer), the server surface (route handlers + Server
Actions), and the runtime AI (the BRD agent via @sustentus/services/ai). Imports UI from
@sustentus/ui and data/logic from @sustentus/services/server. See [apps/web/AGENTS.md] in the
repo for app-specific rules.
Agent (apps/agent)
Next.js 16 agentic app (port 3007) — the home of the agentic layer, deployed as the
agentic-interface Vercel project. It sits beside the platform rather than inside it: proxy.ts
(this repo’s Next 16 middleware file) gates every route, so the gate is deny-by-default at the edge
rather than per-layout. It runs against the same Clerk instance as the platform, so on a shared root
domain a signed-in platform user is already signed in here.
It serves two populations. A tenant user holds a Clerk org and a platform role, both read
from the session claims — no database call at the edge. Sustentus staff and invited partners
hold neither: they are org-less Clerk users carrying the publicMetadata console-access marker,
which the gate reads live from Clerk so a revocation denies the next request. Everyone else gets an
honest “no workspace” state, and /api/** answers 403.
Tenant scoping is the same one-tenant binding the platform uses: every AI tool is built by
createTenantContext(tenantId) and a tool’s execute never receives a tenant id, so the model
cannot point one at another tenant. Staff and partners choose which tenant they are working in
— staff any tenant, a partner only those assigned to them, bounded by the same ConsoleTenantScope
the console uses — and bind to that one. Their extra privilege is reach, not power: nothing extra
inside the tenant they pick. The selection cookie is re-authorised against that scope on every
request and never trusted on its own.
Imports UI from @sustentus/ui; @sustentus/services through /ai and /shared anywhere and
/server in server-only files (never a client component). The root route is a streamed conversation
with the Sustentus assistant, built from the shared @sustentus/ui AI primitives, reaching the model
through the Vercel AI Gateway and reading its tenant’s catalogue through a tenant-bound tool.
Conversations are saved, per user and per tenant: a conversation rail lists the caller’s recent
threads (a drawer below the mobile breakpoint), /c/<threadId> reopens one with its full history,
and each can be renamed or deleted. Reading is the author’s alone — there is no staff or
cross-tenant view. The route is the writer: the client posts one new message and a thread id, the
transcript is read back from the store, and only a bounded window of it is replayed to the model so
a long-lived thread does not make every turn cost more than the last. Persistence degrades on its
own: if a write fails the turn still streams, unsaved. Every assistant turn also writes an agent-run
record — agent, model, tool calls with the tenant each was bound to, token usage and outcome — which
is the join key the accounting and tracing work reads. The header links back to the platform when
PLATFORM_APP_URL resolves (the same parsing rules as the console’s CONSOLE_APP_URL; unset, the
link is simply absent).
It also serves GET /api/ai/availability, a signed-in-only route that reports whether the gateway
can serve — ok, degraded or unknown, and nothing else. The conversation asks once on mount and
warns above the composer when the answer is degraded, so a credit outage is visible before a user
spends a turn discovering it. See AI Gateway.
Console (apps/console)
Next.js 16 tenant management console (port 3006) — the Sustentus-side operational surface, deployed
as the tenant-management Vercel project. Its users are org-less Clerk users carrying a
publicMetadata console-access marker, not platform roles: Sustentus staff see every tenant,
partners see only the tenants assigned to them. Imports UI from @sustentus/ui and data from
@sustentus/services/server. Learn more →
Dashboards (apps/dashboards)
Next.js 16 role-based dashboards (port 3005). Self-contained — it does not import
@sustentus/ui; UI primitives live locally in components/ui/, and all text uses the local
Typography component. Charts use Recharts.
Marketing (apps/marketing)
Next.js 15 public site (port 3001) — landing, pricing, and waitlist signup with Clerk auth. Imports
UI from @sustentus/ui. Learn more →
Documentation (apps/docs)
This Nextra site — MDX content under app/business/ (direction, strategy, roles, journey) and
app/technical/ (architecture, applications, packages, development). Built-in Pagefind search.
Learn more →
Help (apps/help)
Nextra help & support centre for end users.
Storybook (apps/storybook)
Storybook 10 (port 3002) documenting every component exported from @sustentus/ui — one Default
story per component, props exposed via Controls. Learn more →
Running applications
pnpm dev # all apps
pnpm web:dev # main platform (3000)
pnpm agent:dev # agentic app (3007)
pnpm --filter @sustentus/console dev # console (3006)
pnpm dashboards:dev # dashboards (3005)
pnpm marketing:dev # marketing (3001)
pnpm storybook:dev # storybook (3002)Building
pnpm build # all apps and packagesTurborepo builds the shared packages (@sustentus/ui, @sustentus/services) before the apps that
depend on them.