| name | speak-migration-deep-dive |
| description | Execute Speak major re-architecture and migration strategies for language learning platforms.
Use when migrating to or from Speak, performing major version upgrades,
or re-platforming existing language learning integrations.
Trigger with phrases like "migrate speak", "speak migration",
"switch to speak", "speak replatform", "speak upgrade major".
|
| allowed-tools | Read, Write, Edit, Bash(npm:*), Bash(node:*), Bash(kubectl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
Speak Migration Deep Dive
Overview
Comprehensive guide for migrating to or from Speak, or major version upgrades for language learning platforms.
Prerequisites
- Current system documentation
- Speak SDK installed
- Feature flag infrastructure
- Rollback strategy tested
- User communication plan
Migration Types
| Type | Complexity | Duration | Risk |
|---|
| Fresh install | Low | Days | Low |
| From competitor (Duolingo API, etc.) | Medium | Weeks | Medium |
| SDK major version upgrade | Medium | Weeks | Medium |
| Full language platform migration | High | Months | High |
Pre-Migration Assessment
Step 1: Current State Analysis
find . -name "*.ts" -o -name "*.py" | xargs grep -l "language\|lesson\|speech" > learning-files.txt
wc -l learning-files.txt
npm list | grep -i "language\|speech\|duolingo\|babbel"
pip freeze | grep -i "language\|speech"
grep -r "interface.*Lesson\|type.*Lesson" src/ --include="*.ts"
Step 2: Data Inventory
interface MigrationInventory {
userCount: number;
activeUsersLast30Days: number;
lessonRecordsCount: number;
languagesUsed: string[];
averageLessonsPerUser: number;
audioRecordingsCount: number;
totalAudioDurationHours: number;
audioStorageGB: number;
pronunciationScoresCount: number;
vocabularyEntriesCount: number;
streakRecordsCount: number;
apiEndpoints: string[];
webhooksConfigured: string[];
customFeatures: string[];
}
async function assessMigration(): Promise<MigrationInventory> {
const [users, lessons, audio, progress] = await Promise.all([
assessUserData(),
assessLessonData(),
assessAudioData(),
assessProgressData(),
]);
{
...users,
...lessons,
...audio,
...progress,
: (),
: (),
: (),
};
}
Step 3: Language Support Mapping
const LANGUAGE_MAPPING: Record<string, string> = {
'spanish': 'es',
'korean': 'ko',
'japanese': 'ja',
'mandarin': 'zh-CN',
'french': 'fr',
'german': 'de',
'portuguese': 'pt-BR',
'indonesian': 'id',
};
function validateLanguageSupport(
currentLanguages: string[]
): LanguageValidation {
const supported: string[] = [];
const unsupported: string[] = [];
for (const lang of currentLanguages) {
if (LANGUAGE_MAPPING[lang]) {
supported.push(lang);
} else {
unsupported.push(lang);
}
}
return {
supported,
unsupported,
migrationReady: unsupported.length === 0,
recommendation: unsupported.length > 0
?
: ,
};
}
Migration Strategy: Strangler Fig Pattern
Phase 1: Parallel Run
┌─────────────────┐ ┌─────────────┐
│ Old Language │ │ Speak │
│ Platform │ ──▶ │ (Shadow) │
│ (100%) │ │ (0%) │
└─────────────────┘ └─────────────┘
Phase 2: Feature-by-Feature Migration
┌─────────────────┐ ┌─────────────┐
│ Old Platform │ │ Speak │
│ (Core only) │ ──▶ │ (New) │
│ │ │ Features │
└─────────────────┘ └─────────────┘
Phase 3: Gradual User Migration
┌─────────────────┐ ┌─────────────┐
│ Old Platform │ │ Speak │
│ (50% users) │ ──▶ │ (50%) │
└─────────────────┘ └─────────────┘
Phase 4: Complete
┌─────────────────┐ ┌─────────────┐
│ Old Platform │ │ Speak │
│ (Deprecated) │ ──▶ │ (100%) │
└─────────────────┘ └─────────────┘
Implementation Plan
Phase 1: Setup (Week 1-2)
npm install @speak/language-sdk
cp .env.example .env.speak
npx tsx -e "
const { SpeakClient } = require('@speak/language-sdk');
const client = new SpeakClient({
apiKey: process.env.SPEAK_API_KEY,
appId: process.env.SPEAK_APP_ID,
});
client.health.check().then(console.log);
"
Phase 2: Adapter Layer (Week 3-4)
interface LanguageServiceAdapter {
startLesson(config: LessonConfig): Promise<LessonSession>;
endLesson(sessionId: string): Promise<LessonSummary>;
recognizeSpeech(audio: ArrayBuffer): Promise<RecognitionResult>;
scorePronunciation(audio: ArrayBuffer, text: string): Promise<PronunciationScore>;
getProgress(userId: string): Promise<UserProgress>;
updateProgress(userId: string, progress: Partial<UserProgress>): Promise<void>;
}
class LegacyLanguageAdapter implements LanguageServiceAdapter {
async startLesson(config: ): <> {
legacyClient..(config);
}
}
{
: ;
() {
. = client;
}
(: ): <> {
speakConfig = .(config);
session = ...(speakConfig);
.(session);
}
(: ): {
{
: [config.],
: config.,
: config.,
: config.,
};
}
(: ): {
{
: session.,
: session.,
: session.,
};
}
}
Phase 3: Data Migration (Week 5-8)
interface MigrationBatch {
users: UserMigration[];
startedAt: Date;
completedAt?: Date;
errors: MigrationError[];
}
async function migrateUserData(): Promise<MigrationReport> {
const batchSize = 100;
let processed = 0;
let errors: MigrationError[] = [];
const totalUsers = await db.users.count();
console.log(`Migrating ${totalUsers} users...`);
for await (const batch of iterateUserBatches(batchSize)) {
const results = await Promise.allSettled(
batch.map(user => migrateUser(user))
);
for (let i = 0; i < results.length; i++) {
if (results[i].status === 'rejected') {
errors.({
: batch[i].,
: results[i].,
: (),
});
}
}
processed += batch.;
.();
();
}
{ processed, errors, : (processed - errors.) / processed };
}
(): <> {
speakUser = speakClient..({
: user.,
: user.,
: {
: [user.],
: user..( [l]),
},
});
legacyProgress = legacyDb..(user.);
speakClient..(speakUser., {
: legacyProgress.,
: legacyProgress.,
: legacyProgress.,
});
db..({
: user.,
: speakUser.,
: (),
});
}
Phase 4: Traffic Shift (Week 9-12)
function getLanguageAdapter(userId: string): LanguageServiceAdapter {
const speakPercentage = getFeatureFlag('speak_migration_percentage');
const userInSpeakCohort = isUserInMigrationCohort(userId, speakPercentage);
if (userInSpeakCohort) {
return new SpeakLanguageAdapter(speakClient);
}
return new LegacyLanguageAdapter();
}
async function adjustMigrationPercentage(): Promise<void> {
const metrics = await getMigrationMetrics();
if (metrics.speakErrorRate > 0.05) {
console.error('High error rate detected, reducing Speak traffic');
await setFeatureFlag('speak_migration_percentage', Math.max(0, metrics.currentPercentage - 10));
return;
}
if (metrics. < && metrics.) {
newPercentage = .(, metrics. + );
.();
(, newPercentage);
}
}
Audio Migration
async function migrateAudioRecordings(userId: string): Promise<void> {
const legacyAudioFiles = await legacyStorage.listUserAudio(userId);
for (const file of legacyAudioFiles) {
const audioData = await legacyStorage.download(file.id);
const optimizedAudio = await optimizeAudioForSpeak(audioData);
await newStorage.upload({
userId: getMigratedUserId(userId),
audioData: optimizedAudio,
metadata: {
legacyId: file.id,
language: file.language,
duration: file.duration,
migratedAt: new Date(),
},
});
}
}
Rollback Plan
#!/bin/bash
echo "=== Speak Migration Rollback ==="
kubectl set env deployment/language-service SPEAK_MIGRATION_PERCENTAGE=0
kubectl set env deployment/language-service SPEAK_ENABLED=false
kubectl rollout restart deployment/language-service
kubectl rollout status deployment/language-service
curl -f https://api.yourapp.com/health | jq '.services.language'
echo "Rollback complete. Legacy language service active."
Post-Migration Validation
async function validateMigration(): Promise<ValidationReport> {
const checks = [
{ name: 'User count match', fn: checkUserCounts },
{ name: 'Progress data intact', fn: checkProgressData },
{ name: 'Audio accessible', fn: checkAudioAccess },
{ name: 'All languages working', fn: checkLanguageSupport },
{ name: 'Speech recognition', fn: checkSpeechRecognition },
{ name: 'Lesson completion flow', fn: checkLessonFlow },
{ name: 'Webhook delivery', fn: checkWebhooks },
{ name: 'Performance baseline', fn: checkPerformance },
];
const results = await Promise.all(
checks.map(async c => ({
name: c.name,
result: await c.fn(),
}))
);
return {
checks: results,
passed: results.every(r => r.result.success),
: (),
};
}
(): <> {
( userId userIds) {
notifications.(userId, {
: ,
: ,
: ,
: {
: ,
: ,
},
});
}
}
Output
- Migration assessment complete
- Adapter layer implemented
- User data migrated successfully
- Traffic fully shifted to Speak
- Rollback tested and documented
Error Handling
| Issue | Cause | Solution |
|---|
| Data mismatch | Transform errors | Validate transforms |
| Performance drop | No caching | Add caching layer |
| User confusion | UI changes | Provide tutorials |
| Audio format error | Incompatible format | Re-encode audio |
Examples
Quick Migration Status
const status = await validateMigration();
console.log(`Migration ${status.passed ? 'PASSED' : 'FAILED'}`);
status.checks.forEach(c =>
console.log(` ${c.result.success ? 'OK' : 'FAIL'} ${c.name}`)
);
Resources
Post-Migration
After completing migration, refer back to the standard skills for ongoing operations.