Architecture
Four product binaries in one Go workspace. They are deployed separately, own their schemas outright, and never call each other's databases.
platform/ the one shared Go module (auth, db, config, email, waitlist)
apps/
nn_converse/ autonomous voice agent
nn_intel/ post-call batch analysis
nn_assist/ real-time in-call coaching
nn_coach/ simulated-call training & scoring
Each apps/<name> and platform/ is its own Go module. go.work at the repo
root wires them together for local development only — every app's go.mod
carries its own replace pointing at platform, so no module needs the
workspace file to build.
What each binary does
nextneural_converse
An AI agent that holds the phone call itself. Inbound calls are matched to a flow by the number dialled; outbound campaigns dial a contact list on a schedule. The media stream runs over a WebSocket to the telephony provider, with speech recognition, an LLM, and speech synthesis in the loop.
Built around: agents (the persona and voice) → flows (inbound line or outbound campaign) → customers → calls.
nextneural_intel
Batch analysis of call recordings that already happened. You upload audio, it transcribes, runs extraction against configurable fields, and scores the call. Results roll up into a dashboard, a leaderboard, and scheduled reports.
Built around: projects → uploads → calls (each an analysed recording) → customers and segments.
nextneural_assist
Live coaching while a human agent is on the phone. It transcribes the call in real time and pushes prompts to the agent's screen over a WebSocket.
Built around: agents (human salespeople) → customers → live calls, with a knowledge base backing the suggestions.
nextneural_coach
Practice calls against a simulated customer. A persona and a scenario define
who the trainee is talking to; a run is one practice call, scored against a
rubric. It can call nextneural_intel to have a simulated run judged by the same
extraction a real call gets.
Built around: personas + scenarios → runs → scores.
What they share
Everything in platform/ is identical across the four binaries:
| Package | What it provides |
|---|---|
platform/auth | JWT sessions, API keys, scopes, the role hierarchy |
platform/otpsignup | Email OTP signup |
platform/org | Organization setup, bootstrap superadmin |
platform/waitlist | Approval gating for new organizations |
platform/email | Invite and password-reset delivery |
platform/db | Connection pooling and migration running |
platform/config | Deployment mode, superadmin domains |
This is why Authentication, Accounts, Organizations, API keys, and Errors are documented once rather than four times.
The request pipeline
Every org-scoped route runs the same middleware chain, in this order:
RequireAuth / RequireAuthOrAPIKey ← credential is valid
↓
ResolveCaller ← role and email read fresh from the DB
↓
RequirePasswordSet ← invited users must set a password first
↓
RequireOrg ← 403 needs_org_setup if there is no org
↓
requireWaitlistApproval ← 403 needs_waitlist_approval if pending
↓
requireAppAccess ← org must be entitled to this app
↓
RequireRole / RequireScope ← per-route, where applicable
Two details worth knowing:
Role and waitlist status are read from the database on every request, not from the token. An admin approving an organization takes effect on the caller's very next request rather than whenever their token is next reissued.
RequireRole refuses API keys outright. Anything gated on a role is a
human-admin action by design — a key that could mint further keys would make
revocation meaningless.
Databases
Each binary owns one Postgres database, with no shared tables and no
cross-app migrations. Migrations apply themselves at boot: every main.go
calls db.Migrate(cfg.DatabaseURL) before it starts serving, so pointing
DATABASE_URL at an empty database is all it takes to create the schema.
make build-migrations APP=<name> is a compile-time step, not a database
one — it copies db/migrations/*.sql into internal/db/migrations/ so
go:embed can bake them into the binary.
Not in this workspace
The on-prem license agent (nn_admin) and the cloud licensing control plane
(nn_super_admin) live elsewhere. None of the four binaries here contains
license-check code: they are supervised from outside the process.