| name | twinme-dev |
| description | TwinMe development patterns for memory stream services, platform integrations, API endpoints, and twin chat context. Use when creating new backend services, API routes, or platform connectors. |
| user-invocable | false |
TwinMe Development Patterns
Backend Service Pattern
All backend services follow this structure:
import { supabaseAdmin } from './database.js';
import { complete, TIER_ANALYSIS } from './llmGateway.js';
export async function myFunction(userId) {
if (!userId) throw new Error('userId required');
const { data, error } = await supabaseAdmin
.from('table_name')
.select('*')
.eq('user_id', userId);
if (error) throw error;
return data;
}
API Route Pattern
import { Router } from 'express';
import { authenticateUser } from '../middleware/auth.js';
const router = Router();
router.use(authenticateUser);
router.get('/', async (req, res) => {
try {
const userId = req.user.id;
const data = await myFunction(userId);
res.json({ success: true, data });
} catch (error) {
console.error('[Route] Error:', error.message);
res.status(500).json({ success: false, error: error.message });
}
});
export default router;
Register in api/server.js: app.use('/api/newroute', newRoutes);
Frontend API Client Pattern
import { authFetch } from './apiBase';
export const newAPI = {
getItems: async (): Promise<Item[]> => {
const response = await authFetch('/items');
if (!response.ok) throw new Error(`Failed: ${response.statusText}`);
const data = await response.json();
return data.data ?? [];
},
};
Memory Stream Integration
When a new service needs to store observations:
import { addPlatformObservation } from './memoryStreamService.js';
await addPlatformObservation(
userId,
'Natural language observation about what happened',
'platform_name',
{ ingestion_source: 'on_demand', raw_data: structuredData }
);
Twin Chat Context Integration
To add a new context source to twin chat:
- Add a parallel fetch in
api/services/twinContextBuilder.js inside Promise.all
- Add the section to the system prompt in
api/routes/twin-chat.js
- Track in
contextSources object for debugging
LLM Calls
ALL LLM calls go through the gateway:
import { complete, TIER_ANALYSIS, TIER_EXTRACTION, TIER_CHAT } from './llmGateway.js';
const result = await complete({
tier: TIER_ANALYSIS,
messages: [{ role: 'user', content: prompt }],
temperature: 0.3,
maxTokens: 1000,
userId,
serviceName: 'goal_suggestion'
});
Database Migrations
- Location:
database/migrations/YYYYMMDD_description.sql — the canonical tree for ALL NEW migrations. Do not add files to database/supabase/migrations/: it is a read-only archive of migrations that WERE applied to prod (base schema + audit-track mirrors — e.g. its 20260514 file is annotated "Applied to prod... Mirrored here"), NOT unapplied/diverged wholesale; it just is no longer where new work goes. Beware: at least one table (user_transactions, two 20260420 files) has conflicting duplicate definitions across the two trees, so neither file alone is authoritative for that table — check the live DB. supabase/migrations/ (14 ancient files) and api/migrations/ are DEAD legacy dirs; never apply them (see the README.md in each migration dir, audit 2026-07-04).
- Apply via Supabase MCP:
mcp__supabase__apply_migration (pass name = the filename stem; the recorded version auto-generates as a 14-digit timestamp and will NOT match the file's date prefix — that is expected, not a bug)
- FK constraints MUST reference
public.users(id) not auth.users(id)
- Always enable RLS and create policies for user isolation
- Always add service_role full access policy
Platform Data Extraction
Platform data flows through Nango proxy for token management:
Platform API -> Nango -> extractionOrchestrator -> observationIngestion -> Memory Stream
Metric types for goal tracking: sleep_hours, recovery_score, hrv, meeting_count, focus_time, listening_hours
Testing Checklist