Architecture

System overview

Six independently-deployable services, a deterministic scoring path, and app-level multi-tenancy.

Services

ServiceStackResponsibility
tc-coreNestJS + Prisma + PostgresAuth/OAuth/RBAC, the interview HITL workflow state machine, the A2A/Integration API gateway, webhooks, admission control
tc-measurementFastAPIPlan Engine (interview plan generation) and the deterministic assessment/scoring pipeline. Ontology lives in Postgres reference.* tables
tc-realtimePythonMaya, the AI interviewer — WebRTC/LiveKit audio, STT/TTS, turn-taking, adaptive question flow
tc-webNext.js 15 (App Router)Recruiter dashboard, candidate interview experience, OAuth consent screen, and this docs site
tc-contractsTypeScript + ZodShared wire-shape validators/types — every cross-service payload is defined once here
tc-infraDocker ComposePostgres, Redis, Kafka, and the full application stack for local/dev

Deterministic scoring — no LLM in the scoring path

An LLM extracts and types evidence from what the candidate said (mapping each response to a competency and a coverage signal), but every scoring decision after that point — dedup, aggregation, and the final coverage_summary — runs through fixed, auditable rules. The same transcript always produces the same assessment: no model non-determinism in the number (or absence of a number) that a candidate's outcome depends on.

This is why the output is a coverage_summary rather than a score: coverage against a fixed competency ontology is something a deterministic pipeline can defend and explain; a single blended "fit" number is not.

Multi-tenancy

Every tenant-scoped table carries a tenantId column, and every query path in tc-core filters by the resolved tenant from the authenticated principal — never from a client-supplied value in the body. Platform tenants (Tenant.isPlatform) can act on behalf of their own customer tenants via X-Tenant-ID, but that header is validated to name a tenant that actually belongs to the calling platform on every request, so one platform's key can never reach another platform's data.

Runs are addressed by an opaque runId (run_<epoch>_<hex>) resolved as { tenantId, runId } — a syntactically valid runId belonging to a different tenant 404s rather than exposing that it exists.

For the partner-facing surface built on top of this architecture, see Integration API (A2A) and OAuth.
Was this page helpful?