1. Syncode
Syncode
  • Syncode
    • Conventions
    • Response Schemas
    • Error Taxonomy
    • Resource Model
    • Permission Model
    • Cross-Cutting Concerns
    • Security
    • Collab-Plane
    • Execution-Plane
    • AI-Plane
  • SynCode Control Plane API
    • Auth
      • Create a new account
      • Authenticate and get tokens
      • Refresh access token
      • Invalidate refresh token
      • Change current user's password
      • Request password reset email
      • Reset password with token
    • Users
      • Get current user profile
      • Update current user profile
      • Soft-delete account
      • Get public profile of another user
      • Upload avatar (presigned URL)
      • Get usage quotas and limits
      • Get current active room (for reconnection)
      • Get time-series training statistics
    • Rooms
      • Participants
        • List all participants in a room
        • Update participant (role, mute)
        • Kick a participant from the room
      • Control
        • Advance room phase
        • Select or change the problem
        • Update room settings
        • Lock code editor, run, and submit
        • Unlock code editor, run, and submit
        • Pause the coding timer
        • Resume the coding timer
        • Request a role swap (peer mode)
        • Accept or decline a role swap request
      • Media
        • Generate LiveKit access token
        • Record participant's recording consent
        • Start session recording
        • Stop session recording
      • AI
        • Send a message to AI interviewer
        • Poll AI message result
        • Get AI conversation history
        • Request a targeted hint
        • Get hint result
        • Request code review
        • Get review result
        • Get cross-session weakness tracking
      • StaticAnalysis
        • Request static analysis
        • Get analysis result
      • Feedback
        • Submit peer evaluation
        • Get all feedback for this room
        • Get my submitted feedback
      • Create a new room
      • List rooms for current user
      • Get room details
      • Destroy a room (host only)
      • Join a room via room code
      • Leave a room
      • Lookup room by invite code
      • Execute code (interactive run)
      • Submit code against test cases
      • List past runs in this room
      • List past submissions in this room
    • Problems
      • List and search problems
      • Create a problem (admin)
      • List all available tags
      • Get problem details
      • Update a problem (admin)
      • Delete a problem (admin)
    • Bookmarks
      • List bookmarked problems
      • Bookmark a problem
      • Remove bookmark
    • Execution
      • Get execution result (single run)
      • Get submission status and aggregated results
    • Sessions
      • List my session history
      • Get session details
      • Soft-delete a session
      • Get training report
      • Get session event timeline
      • Get code snapshots
      • Get recording download URL
      • Get peer feedback for this session
      • Get whiteboard export
      • Get AI conversation history
      • Compare multiple session reports
    • Matchmaking
      • Enter the matchmaking queue
      • Cancel matchmaking
      • Get current match status
      • Accept a proposed match
      • Decline a proposed match
    • Admin
      • System overview stats
      • List all users
      • Get user details (admin view)
      • Update user (ban, role change)
      • List all rooms
      • Force-close a room
      • Query audit logs
    • Health
      • Deep health check
    • Schemas
      • RoomStatus
      • CreateDocumentRequest
      • RoomRole
      • CreateDocumentResponse
      • RoomMode
      • DestroyDocumentResponse
      • SupportedLanguage
      • KickUserRequest
      • Difficulty
      • KickUserResponse
      • UserRole
      • LockEditorRequest
      • ErrorResponse
      • LockEditorResponse
      • Pagination
      • SnapshotReadyPayload
      • UserProfile
      • UserDisconnectedPayload
      • PublicProfile
      • CallbackAckResponse
      • RoomConfig
      • RoomParticipantSummary
      • RoomSummary
      • RoomDetail
      • RoomPreview
      • ProblemSummary
      • ProblemDetail
      • ProblemExample
      • TestCase
      • TagInfo
      • AiMessage
      • WeaknessEntry
      • PeerFeedbackRatings
      • PeerFeedbackEntry
      • SessionSummary
      • SessionDetail
      • SessionParticipant
      • SessionEvent
      • CodeSnapshot
      • Evidence
      • ReportDimension
      • AdminDashboard
      • AdminUserEntry
      • AdminUserDetail
      • AdminRoomEntry
      • AuditLogEntry
      • HealthResponse
      • MatchOpponent
  • SynCode Collab Plane API
    • Documents
      • Create a Yjs document
      • Destroy a Yjs document
      • Kick a user from the document
      • Toggle editor lock
    • Health
      • Health check
    • Callbacks
      • [Callback] Snapshot ready
      • [Callback] User disconnected
    • Schemas
      • CreateDocumentRequest
      • CreateDocumentResponse
      • DestroyDocumentResponse
      • KickUserRequest
      • KickUserResponse
      • SnapshotReadyPayload
      • LockEditorRequest
      • UserDisconnectedPayload
      • LockEditorResponse
      • CallbackAckResponse
      • ErrorResponse
  1. Syncode

AI-Plane

AI-powered interview assistant: generates hints, performs code reviews, and conducts interactive AI interviews.

Overview#

The AI-plane is a standalone NestJS application context (NestFactory.createApplicationContext()), no HTTP server. It pulls jobs from BullMQ queues, calls LLM providers, and pushes results back through paired result queues. The control-plane is the only thing that talks to it, and only via Redis.
The AI-plane handles three job types: interview responses (conversation + follow-ups), hint generation, and code review.

Architecture#

Queue Contracts#

AI Interview Response#

Covers AI interviewer conversation and code-aware follow-up questions.
PropertyValue
Job queueai.interview-response
Result queueai.interview-response.results
Concurrency3
Job kind tagai:interview

HTTP request body#

What the frontend sends to POST /rooms/:roomId/ai/message:

BullMQ job payload#

What the control-plane enqueues after enrichment:

Result payload#

weaknessSignals is internal to the control-plane's weakness aggregation pipeline; it is not returned in HTTP polling responses.

Sequence diagram#

Generate Hint#

Covers layered hint generation for candidates.
PropertyValue
Job queueai.generate-hint
Result queueai.generate-hint.results
Concurrency5
Job kind tagai:hint

HTTP request body#

What the frontend sends to POST /rooms/:roomId/ai/hint:

BullMQ job payload#

What the control-plane enqueues after enrichment:

Result payload#

Sequence diagram#

Code Review#

Covers structured evaluation reports, improvement suggestions, and evidence-based scoring.
PropertyValue
Job queueai.review-code
Result queueai.review-code.results
Concurrency3
Job kind tagai:review

HTTP request body#

What the frontend sends to POST /rooms/:roomId/ai/review:

BullMQ job payload#

What the control-plane enqueues after enrichment:

Result payload#

Typed categories, line-level suggestions, and evidence-based scoring:
weaknessSignals, same as interview response, is internal only, stripped from HTTP polling responses.

Sequence diagram#

Conversation History#

Served by the control-plane directly from PostgreSQL. No AI-plane involvement.
PropertyValue
EndpointGET /rooms/:roomId/ai/messages
AuthBearer (requires code:view room capability)
PaginationCursor-based (cursor, limit default 50)
Response:
Individual messages come through the interview response queue. The control-plane persists them and serves history from the DB.

AI Capabilities by Room Mode#

CapabilityAI-mode roomsPeer-mode rooms
AI interview conversationYesNo
AI follow-up questionsYesNo
Adaptive difficultyYesNo
TTS voice outputYesNo
STT voice inputYesNo
Hint generationYesYes
Code reviewYesYes (post-session)
Weakness signal emissionYesYes (from reviews)
In AI-mode, the AI runs the interview. In peer-mode, hints and reviews are supplementary tools for the human participants.

Adaptive Difficulty#

Question difficulty adjusts per-session based on candidate performance. Each interview response job analyzes conversation history and code quality to decide the next difficulty tier.
Difficulty signals (inputs):
SignalSourceIndicates
Code correctnessTest case pass rate from execution resultsSolution quality
Response timeTimestamps between messagesComfort with topic
Hint usageCount of hint requests in sessionStruggle level
Conversation depthNumber of follow-ups without resolutionDifficulty
Code complexityCyclomatic complexity, nesting depthSolution sophistication
Difficulty adjustments (outputs):
Current performanceNext question difficultyFollow-up type
Solving quickly, no hintshard: deeper algorithmic questionsquestion
Moderate pace, few hintsmedium: standard follow-upsquestion or evaluation
Struggling, multiple hintseasy: simpler sub-problemshint or encouragement
The difficulty field in InterviewResponseResult tells the frontend which tier the AI is targeting, so the UI can show progress indicators.

Voice: TTS and STT#

In AI-mode rooms, the AI interviewer communicates by voice. This involves two directions: text-to-speech (TTS) for the AI's spoken output, and speech-to-text (STT) for transcribing the candidate's voice input.

TTS (AI speaks)#

After the LLM generates a text response, the AI-plane sends it to a TTS provider, uploads the resulting audio to SeaweedFS with a presigned URL (1-hour expiry), and returns that URL as audioUrl in the job result. The frontend fetches and plays it.
The TTS provider is abstracted behind an ITtsProvider interface so implementations can be swapped without changing job processing logic.
Environment variables:
VarDescription
TTS_PROVIDERopenai / google / azure / none
TTS_VOICEVoice ID (provider-specific)
TTS_AUDIO_FORMATmp3 / ogg (default: mp3)
When TTS_PROVIDER=none or unset, audio generation is skipped and audioUrl is omitted from the result.

STT (Candidate speaks)#

The candidate speaks into their microphone. The browser captures audio via the Web Audio API / MediaRecorder, encodes it (Opus in WebM or raw PCM), and sends chunks to the control-plane. The control-plane forwards the audio to the AI-plane for transcription, and the resulting text is injected into the conversation as a user message before triggering the next interview response job.
Flow:
Queue contract:
PropertyValue
Job queueai.transcribe
Result queueai.transcribe.results
Concurrency5
Job kind tagai:transcribe
Job payload:
Result payload:
Like TTS, the STT provider is abstracted behind an ISttProvider interface.
Environment variables:
VarDescription
STT_PROVIDERopenai / google / azure / none
STT_LANGUAGEDefault language hint (BCP-47 tag, e.g., en)
STT_MAX_AUDIO_SIZE_MBMaximum upload size (default: 25 MB, matching most provider limits)
When STT_PROVIDER=none or unset, the voice input endpoint returns 400 and voice input is unavailable. Users type instead.

Weakness Tracking#

Cross-session weakness aggregation.

Data flow#

How it works#

The AI-plane tags interview and review results with weaknessSignals, short string identifiers like 'edge_cases', 'time_complexity', 'off_by_one'. The control-plane's result consumer persists these to PostgreSQL, tied to the user and session. GET /users/me/ai/weaknesses aggregates across sessions:
Future enhancement: the AI-plane could receive the user's historical weaknesses as part of the job data, letting it probe known weak areas.

Weakness categories#

CategoryDescriptionDetected from
edge_casesMissing boundary/edge case handlingCode review, follow-up questions
time_complexitySuboptimal algorithmic complexityCode review, interview discussion
space_complexityExcessive memory usageCode review
variable_namingPoor variable/function namingCode review (readability)
code_structureDeeply nested or poorly organized codeCode review (readability)
off_by_oneOff-by-one errors in loops/indicesCode review (correctness)
input_validationMissing null/empty/type checksCode review (edge cases)
communicationUnclear explanation of approachInterview conversation analysis

Rate Limiting#

Rate limits are enforced by the control-plane before jobs are enqueued.
ScopeLimitWindowEnforced at
AI hints3 requests5 minPer user per room
AI messages20 requests1 minPer user per room
Over-limit requests get 429 Too Many Requests with a Retry-After header. The job never hits the queue. The 429 body includes a user-facing message so the frontend can display quota exhaustion clearly.

Result Caching#

The control-plane caches job results in Redis after consuming them from result queues.
PropertyValue
Cache key formatai-result:{jobId}
TTL24 hours (86400 seconds)
StorageRedis (ICacheService)
Written byControl-plane result queue consumer
Read byControl-plane GET endpoint
Same Redis instance as execution results and other caches.
Polling flow: The frontend polls the per-type endpoint (GET /rooms/:roomId/ai/message/:jobId, GET /rooms/:roomId/ai/hint/:jobId, or GET /rooms/:roomId/ai/review/:jobId) until it gets completed or failed. The control-plane checks the cache first; if nothing is cached yet, it queries BullMQ job status via IAiClient.getHintJobStatus() / getReviewJobStatus() / getInterviewJobStatus() and returns the queue state (queued | running).

Error Handling#

LLM provider failures#

Error typeAI-plane behaviorControl-plane behavior
LLM API timeoutJob fails, BullMQ retries (exponential backoff)Returns queued or running status to frontend
LLM API rate limitJob fails with retryable errorSame as timeout
LLM API auth errorJob fails permanently (no retry)Returns failed status with error message
Invalid LLM responseLogged, job fails, retryReturns failed if retries exhausted
TTS failureLogged, result returned without audioUrlTransparent. Result has no audio
STT failureJob fails permanently (no retry; audio may be corrupted)Returns failed status with error message

BullMQ retry configuration#

SettingValue
Max attempts3
Backoff typeExponential
Backoff delay5000ms (5s, 10s, 20s)
Stall detection30s lock duration
Dead letterJobs moved to DLQ after max attempts

Circuit breaker (control-plane side)#

IAiClient is wrapped with a circuit breaker proxy. When the AI-plane is unresponsive:
CircuitBreakerOpenError -> 503 Service Unavailable
CircuitBreakerTimeoutError -> 504 Gateway Timeout
healthCheck() bypasses the circuit breaker; it always makes a real call so the control-plane health endpoint can report AI-plane status accurately.

Observability#

OTel for traces and metrics, pino-opentelemetry-transport for structured log shipping.

Metrics#

MetricTypeLabelsDescription
ai.queue.depthGaugequeueJobs waiting in each queue
ai.queue.activeGaugequeueJobs currently being processed
ai.job.duration_msHistogramqueue, statusEnd-to-end job processing time
ai.llm.latency_msHistogramprovider, modelLLM API call latency
ai.llm.tokens.inputCounterprovider, modelInput tokens consumed
ai.llm.tokens.outputCounterprovider, modelOutput tokens generated
ai.llm.failuresCounterprovider, error_typeLLM API failures by type
ai.tts.latency_msHistogramproviderTTS generation latency
ai.tts.failuresCounterproviderTTS generation failures
ai.stt.latency_msHistogramproviderSTT transcription latency
ai.stt.failuresCounterproviderSTT transcription failures
ai.stt.audio_duration_msHistogramproviderDuration of audio submitted for transcription

Tracing#

Each job creates a span with:
ai.job.id: BullMQ job ID
ai.job.queue: queue name
ai.job.type: hint / review / interview / transcribe
Child spans for LLM calls, TTS generation, and STT transcription

Logging#

Structured logs via nestjs-pino with:
Log level: debug in development, info in production
OTel log shipping when OTEL_EXPORTER_OTLP_ENDPOINT is configured
Service name: ai-plane
Modified at 2026-03-12 05:26:10
Previous
Execution-Plane
Next
Create a new account
Built with