The backend is built with ElysiaJS and runs on Bun. It consists of two services:
- Lab Server (
apps/lab/server): Handles participant sessions, events, and delivering experiments. - Manager Server (
apps/manager/server): Handles experiment configuration management, media uploads, and administrative tasks.
Architecture
- Runtime: Bun
- Framework: Elysia
- Database: MongoDB
- Validation: TypeBox (integrated with Elysia)
- Authentication: Better Auth (with MongoDB adapter)
Validation (TypeBox)
All endpoints enforce strict input/output validation using TypeBox schemas. This ensures type safety and automatic Swagger documentation generation.
Example Schema:
const ConfigSchema = Type.Object({
configId: Type.String(),
owner: Type.String(),
requireAuth: Type.Optional(Type.Boolean()),
// ...
});
Authentication
Authentication is handled by Better Auth.
- Manager Routes: All routes are protected by
authMiddleware, which verifies the session cookie or bearer token. - Lab Routes: Routes derive an auth context (
requireAuth,user) from the config’srequireAuthflag. When auth is required, the server will attempt to read a Better Auth session (cookies) and can attachuserIdto sessions when available.
Endpoints
Lab Server (Port 3001)
Sessions
POST /sessions/start: Start a new session.- Body:
{ configId } - Return:
{ status, sessionId, configId, currentPageId, page, config, session_state, endedAt }
- Body:
GET /sessions/:id: Get session state.POST /sessions/:id/advance: Trigger an action.- Body:
{ target, idempotencyKey } - Return:
{ sessionId, configId, currentPageId, page, endedAt }
- Body:
POST /sessions/:id/state: Update user state fields.POST /sessions/:id/events: Log a user interaction event.
Configs
GET /configs/:configId: Fetch a specific experiment configuration.
Manager Server (Port 3002)
Configs
POST /configs/upload: Upload/Publish a new experiment config.- Body:
{ configId, owner, checksum, config, metadata }
- Body:
GET /configs: List configs (supports filtering by owner).DELETE /configs/:configId: Delete a config.
Media
POST /media/upload: Upload a file to Cloud Storage (via abstraction).- Body:
{ bucket, object, data (base64), ... }
- Body:
GET /media: List media objects.DELETE /media/:object: Delete a media object.
Live Updates (SSE)
GET /sessions/:id/stream: Server-Sent Events stream for the session. Events include:match_found: Matchmaking succeeded with group infomatch_timeout: Matchmaking timed outchat_message: New chat message in groupchat_message_delta: Streaming agent response chunkchat_ended: Chat session terminated (by agent tool)state_updated: User state changed
Chat
GET /chat/:groupId/history: Get chat message historyPOST /chat/:groupId/send: Send a message to the groupPOST /chat/:groupId/start-agents: Trigger agents withsendFirstMessage: true
Matchmaking
POST /sessions/:id/matchmake: Join a matchmaking poolPOST /sessions/:id/matchmake/cancel: Leave the matchmaking queue
Workspace
GET /workspace/:groupId: Fetch workspace documentPOST /workspace/:groupId/update: Update workspace content/fields
Randomization
POST /sessions/:id/randomize: Assign treatment condition (idempotent)
AI Agents
Agents are server-hosted LLM participants that join chat rooms. Configuration is stored in the experiment config's agents array.
- Supported providers: OpenAI, Anthropic (inferred from model name)
- Streaming: Agent responses stream via SSE
chat_message_deltaevents - Tools: Agents can invoke tools like
end_chatandassign_state - Keys: API keys stay server-side (
OPENAI_API_KEY,ANTHROPIC_API_KEY)