25 KiB
Agent Prompts: Client-Side Sync Integration + MindLyst Cosmos Persistence
Two self-contained prompts for AI coding agents. Each prompt includes full context, exact file paths, what exists, what's missing, and verification commands. No guesswork required.
Prompt 1: Client-Side Sync Integration (ChronoMind + NomGap)
Context
Platform-service (Fastify 5, port 4003) in learning_ai_common_plat/services/platform-service/ has fully built and tested REST APIs:
ChronoMind modules (130 tests):
timers— 7 endpoints: GET /timers, GET /timers/sync, GET /timers/:id, POST /timers, PUT /timers/:id, DELETE /timers/:id, POST /timers/batchroutines— 7 endpoints: same pattern as timers at /routines/*households— 9 endpoints at /households/*shared-timers— 6 endpoints at /households/:householdId/timers/*
NomGap modules (52 tests):
fasting-sessions— 6 endpoints: POST/GET/GET/:id/PUT /fasting/sessions, GET /fasting/sessions/stats, GET /fasting/sessions/stats/weeklyfasting-protocols— 5 endpoints: GET/GET/:id/POST/PUT/:id/DELETE/:id /fasting/protocolsbody-stages— 2 endpoints: GET /fasting/stages, POST /fasting/autophagy-confidence
All endpoints use JWT auth via Authorization: Bearer <token>, x-product-id header, x-request-id header, and productId field in Cosmos documents. Sync protocol uses syncVersion (monotonic integer) for optimistic concurrency, delta sync via ?since=<ISO>, and batch upsert returning { synced, conflicts, errors }.
What Already Exists (DO NOT REBUILD)
ChronoMind Web (learning_ai_clock/web/src/lib/)
platform-sync.ts(447 lines) — Full sync client with DTOs (SyncTimerDTO,SyncRoutineDTO,OfflineQueueItem), API functions (pullDelta,pushTimer,updateRemoteTimer,deleteRemoteTimer,batchUpsert,pullRoutineDelta,pushRoutine,updateRemoteRoutine,deleteRemoteRoutine,batchUpsertRoutines), offline queue (enqueueChange,enqueueDeleteChange,loadOfflineQueue,saveOfflineQueue),fullSync(), and DTO converters (timerToDTO,dtoToTimerPatch,routineToDTO,dtoToRoutinePatch).use-sync.ts(198 lines) — React hook (useSync) withsyncNow,setSyncEnabled,login,logout,syncedAddTimer,syncedUpdateTimer,syncedRemoveTimer. Auto-syncs on 60s interval. Merges pulled timers into Zustand store.auth-api.ts(32 lines) —@bytelyst/auth-clientwrapper withgetAuthClient(),PRODUCT_ID = 'chronomind'.auth-context.tsx— React auth context with login/register/logout/forgotPassword/changePassword/deleteAccount.store.ts— Zustand store withaddTimer,removeTimer,updateTimer,pause,resume, etc. UseslocalStoragepersistence.routine-store.ts— Zustand store for routines.
Dashboard.tsx already imports useSync and destructures { isSyncing, syncEnabled, pendingChanges, lastError } for status display. But it does NOT call syncedAddTimer/syncedUpdateTimer/syncedRemoveTimer — timer mutations go directly to the Zustand store without enqueueing sync changes.
ChronoMind iOS (learning_ai_clock/ios/ChronoMind/Shared/Cloud/)
PlatformSyncManager.swift(450 lines) — Full@MainActor ObservableObjectsingleton withpullDelta(),pushTimer(),updateTimer(),deleteTimer(),pushOfflineQueue(),enqueueChange(),enqueueDelete(), periodic sync (60s),timerToDTO(),dtoToTimerPatch(). DTOs match server schema.- BUT:
PlatformSyncManageris NOT called from any SwiftUI view orTimerStore. TheTimerStoreusesUserDefaultsdirectly.sync(localTimers:)method exists but no view triggers it.
ChronoMind Android (learning_ai_clock/android/app/src/main/java/com/chronomind/app/sync/)
PlatformApiClient.kt(182 lines) — HTTP client withpullDelta(),createTimer(),updateTimer(),deleteTimer(),batchUpsert(),pullRoutinesDelta(). UsesHttpURLConnection.SyncRepository.kt(246 lines) — Hilt@Singletonwithsync(),enqueueCreate(),enqueueUpdate(),enqueueDelete(), offline queue inSharedPreferences. Merges pulled timers into Room viatimerDao.upsert().- BUT:
SyncRepositoryis NOT injected intoTimerViewModel. TheTimerViewModelcallsTimerDaodirectly without sync.
NomGap (learning_ai_fastgap/src/)
api/client.ts(116 lines) — Fetch wrapper with auth token via@bytelyst/auth-client,x-product-id: nomgap,x-request-id, timeout, 401 retry.api/auth-api.ts— Auth client withgetAuthClient(),PRODUCT_ID = 'nomgap'.api/fasting-api.ts(90 lines) —createSession(),getSession(),listSessions(),updateSession(),getUserStats(),getWeeklyStats()calling platform-service/fasting/sessions/*.store/fasting-store.ts— Has a fire-and-forgetsyncSessionToBackend()that callscreateSession()on session start, but.catch(() => {})silently swallows errors. No session-end sync, no session-update sync, no history pull from server.store/user-store.ts— HasloginWithAuth,registerWithAuth,hydrateFromToken,logoutwired.- NO login/register UI screens exist yet (only store actions).
What Needs To Be Built
A. ChronoMind Web — Wire sync into timer/routine CRUD
Files to modify:
web/src/components/Dashboard.tsx— Currently destructuresuseSync()but only uses status fields. Need to also destructuresyncedAddTimer,syncedUpdateTimer,syncedRemoveTimerand call them alongside the Zustand store mutations.web/src/components/CreateTimerModal.tsx— AfteraddTimer(newTimer), also callsyncedAddTimer(newTimer).web/src/components/TimerCard.tsx— After dismiss/complete/snooze actions that call store mutations, also callsyncedUpdateTimer.web/src/components/AlarmOverlay.tsx— After dismiss/snooze, callsyncedUpdateTimer.web/src/components/PomodoroView.tsx— After round complete/session complete, callsyncedUpdateTimer.web/src/components/RoutineEditor.tsx+web/src/components/RoutineRunner.tsx— Need routine sync. TheuseSynchook currently only syncs timers. ExtendfullSync()inplatform-sync.tsto also pull/push routines in the same sync cycle.
Pattern to follow:
// In Dashboard.tsx, pass sync functions down or lift via context
const { syncedAddTimer, syncedUpdateTimer, syncedRemoveTimer } = useSync();
// In every component that mutates timers:
// AFTER the Zustand store call (which updates UI immediately), ALSO enqueue sync
addTimer(newTimer);
syncedAddTimer(newTimer); // non-blocking — just enqueues in localStorage
Critical rules:
- Zustand store is the source of truth for UI. Sync is fire-and-enqueue, never blocking.
syncedAddTimeretc. checkisSyncEnabled()internally — safe to call always.- Don't wrap in try/catch — the enqueue is synchronous localStorage write.
- Pass sync functions through props or React context — do NOT import
useSyncin every child component (breaks hook rules if used in non-component functions).
Bug to fix in fullSync() (platform-sync.ts line 294-339):
fullSync()currently only syncs timers. It needs to ALSO callpullRoutineDelta()andbatchUpsertRoutines()for routines in the offline queue. The routine DTO conversion functions already exist (routineToDTO,dtoToRoutinePatch). ExtendSyncResultto includepulledRoutinesand merge them into the routine store.
B. ChronoMind iOS — Wire PlatformSyncManager into TimerStore
Files to modify:
ios/ChronoMind/Shared/Store/TimerStore.swift— Add a reference toPlatformSyncManager.shared. After every timer add/update/delete in the store, callPlatformSyncManager.shared.enqueueChange(timer, .create/.update)or.enqueueDelete(timerId:).ios/ChronoMind/App/ContentView.swiftor root view — On app launch, callPlatformSyncManager.shared.restoreAuthToken()equivalent (token is already restored ininit). On appear, trigger initial sync.ios/ChronoMind/Views/Components/— No changes needed; store is the single source of truth.
Critical rules:
PlatformSyncManageris@MainActor— safe to call from SwiftUI views.- The
sync(localTimers:)method pulls delta AND pushes offline queue. Call it on app foreground (scenePhase == .active). enqueueChangeis synchronous (UserDefaults write) — call it inline after store mutations.- Do NOT block UI on sync. The
asyncsync runs in background.
C. ChronoMind Android — Wire SyncRepository into TimerViewModel
Files to modify:
android/app/src/main/java/com/chronomind/app/viewmodel/TimerViewModel.kt— InjectSyncRepositoryvia Hilt constructor. After everytimerDao.insert/update/delete, also callsyncRepository.enqueueCreate/enqueueUpdate/enqueueDelete.android/app/src/main/java/com/chronomind/app/di/AppModule.kt— EnsureSyncRepositoryis provided (it's already@Singletonwith@Inject constructor— Hilt should auto-provide).- App startup (e.g.,
MainActivity.kt) — CallsyncRepository.restoreAuthToken()and trigger initialsync()in a coroutine scope.
Critical rules:
sync()is asuspend fun— call fromviewModelScope.launch(Dispatchers.IO).enqueueCreate/enqueueUpdate/enqueueDeleteare regular (non-suspend) functions — safe to call inline.- Trigger sync on app resume (Activity
onResume).
D. NomGap — Complete session sync + add login UI
Files to modify:
-
src/store/fasting-store.ts— ThesyncSessionToBackend()function is fire-and-forget on session START only. Add:- On
endFast(): callupdateSession(session.id, { status, endedAt, waterIntake, notes }). - On
pauseFast()/resumeFast(): callupdateSession(session.id, { status, pausedAt }). - On
addMoodCheckin(): callupdateSession(session.id, { ... })with updated checkins. - On
tick()stage transitions: optionally batch these (don't call API on every tick — debounce to every 5 minutes or on stage change). - Add a
loadSessionHistory()action that callslistSessions()and populatessessionHistoryfrom the server (for cross-device history).
- On
-
src/api/fasting-api.ts— Already complete. No changes needed. -
New file:
src/screens/auth/AuthScreen.tsx— Login/register screen. UseuseUserStore().loginWithAuthandregisterWithAuthwhich already exist. Include:- Email + password fields
- Login / Register toggle
- Error display
- "Continue without account" option (sets local-only mode)
- Navigate to main app on success
-
src/app/(tabs)/_layout.tsxor navigation — Add auth gate: if user not authenticated andpreference !== 'local_only', show AuthScreen. -
src/store/fasting-store.tsstartFast()— Currently generatesuserId: 'local'. When authenticated, useuseUserStore.getState().profile?.idinstead.
Critical rules:
- NomGap is React Native (Expo) — NO web-specific APIs (
localStorage,window). Use MMKV for persistence. @bytelyst/auth-clientis already configured insrc/api/auth-api.ts.- Session sync should be resilient to offline — the existing
.catch(() => {})pattern is correct for fire-and-forget, but add an offline queue similar to ChronoMind's pattern for session updates that fail. - Do NOT sync on every
tick()— debounce stage transitions to avoid API spam.
Verification Commands
# ChronoMind web
cd learning_ai_clock/web && npm test && npm run typecheck
# ChronoMind iOS
# Open ChronoMind.xcodeproj, Cmd+B
# ChronoMind Android
cd learning_ai_clock/android && ./gradlew :app:compileDebugKotlin
# NomGap
cd learning_ai_fastgap && npm test && npm run typecheck
# Platform-service (should still pass — no server changes)
cd learning_ai_common_plat && pnpm --filter @lysnrai/platform-service test
Commit Convention
feat(web): wire timer sync into Dashboard CRUD operationsfeat(ios): connect PlatformSyncManager to TimerStorefeat(android): inject SyncRepository into TimerViewModelfeat(app): add session sync on end/pause/resume + auth screen
Prompt 2: MindLyst Cosmos DB Persistence — Remove In-Memory Fallbacks
Context
MindLyst web (learning_multimodal_memory_agents/mindlyst-native/web/) is a Next.js 16 App Router application with 33 API route files in src/app/api/. The data layer has a dual-mode pattern:
const container = isCosmosConfigured() ? getCosmosContainer('brains') : null;
if (container) {
// Cosmos path
} else {
// In-memory fallback path
}
11 routes already have working Cosmos paths (they use isCosmosConfigured() + getCosmosContainer()):
brains/route.ts— container:brainsmemory/route.ts— container:memory_itemsstreak/route.ts— container:streaksnotifications/route.ts— container:notification_logbrief/route.ts— container:daily_briefsreflection/route.ts— container:reflectionsshare-card/route.ts— container:share_cardsbrain-growth/route.ts— container:brain_insightsanalytics/route.ts— container:analytics_eventsinsights/route.ts— container:brain_insightsseed/route.ts— callsensureContainers()
~22 routes are PURELY in-memory — they use module-level const items: T[] = [] or let state = {...} or new Map(). These lose all data on server restart. They need to be wired to Cosmos using the same dual-mode pattern.
What Already Exists (DO NOT REBUILD)
src/lib/cosmos.ts(74 lines) —isCosmosConfigured(),getCosmosContainer(containerId),MINDLYST_CONTAINERSarray (9 containers),ensureContainers().src/lib/user.ts—resolveUserId(headers)for extracting userId from request.src/lib/abuse.ts—checkRateLimit()for rate limiting..env.example— ShowsCOSMOS_ENDPOINT,COSMOS_KEY,COSMOS_DATABASEenv vars.- Pattern in existing routes (e.g.,
brains/route.ts) — Shows exactly how to do the dual-mode: checkisCosmosConfigured(), get container, use parameterized queries withpartitionKey: userId.
What Needs To Be Built
Phase 1: Add new containers to cosmos.ts
Add these containers to MINDLYST_CONTAINERS in src/lib/cosmos.ts:
// Add to MINDLYST_CONTAINERS array:
{ id: "triage_results", partitionKey: "/userId" },
{ id: "brain_packs", partitionKey: "/userId" },
{ id: "referrals", partitionKey: "/userId" },
{ id: "ab_tests", partitionKey: "/userId" },
{ id: "waitlist", partitionKey: "/id" }, // no userId for public waitlist
{ id: "email_captures", partitionKey: "/userId" },
{ id: "engagement_data", partitionKey: "/userId" },
{ id: "context_triggers", partitionKey: "/userId" },
{ id: "brain_chats", partitionKey: "/userId" },
Phase 2: Wire each purely-in-memory route to Cosmos
For each route below, follow this exact pattern:
- Import
getCosmosContainer, isCosmosConfiguredfrom@/lib/cosmos - At the top of the handler, get the container:
const container = isCosmosConfigured() ? getCosmosContainer("<name>") : null; - For every READ operation: if
container, query Cosmos with parameterized SQL; else use the existing in-memory array/map. - For every WRITE operation: if
container, usecontainer.items.create()/container.item(id, partitionKey).replace()/container.item(id, partitionKey).delete(); else use the existing in-memory mutation. - Keep the in-memory fallback intact — don't remove it. It's needed for local dev without Cosmos.
Routes to wire (sorted by data importance):
| # | Route File | Container | Partition Key | In-Memory Pattern |
|---|---|---|---|---|
| 1 | triage/route.ts |
triage_results |
/userId |
Module-level arrays for triage results and retry queue |
| 2 | brain-chat/route.ts |
brain_chats |
/userId |
new Map() for conversation history + embedding cache |
| 3 | brain-packs/route.ts |
brain_packs |
/userId |
Arrays for packs, submissions, moderation queue |
| 4 | referral/route.ts |
referrals |
/userId |
Arrays for referral links and activations |
| 5 | ab-test/route.ts |
ab_tests |
/userId |
Arrays for experiments and user assignments |
| 6 | waitlist/route.ts |
waitlist |
/id |
Array for waitlist entries (public, no userId) |
| 7 | email-capture/route.ts |
email_captures |
/userId |
Array for captured emails |
| 8 | engagement/route.ts |
engagement_data |
/userId |
Arrays for segments and campaigns |
| 9 | context-triggers/route.ts |
context_triggers |
/userId |
Arrays for location/calendar triggers |
| 10 | nudge/route.ts |
memory_items |
/userId |
Queries existing memory_items container (reads memory items > 48h not acted on) — may just need container reference, not new container |
| 11 | monitoring/route.ts |
analytics_events |
/userId |
Reads from analytics — may just need container reference |
| 12 | share-templates/route.ts |
— | — | Static data (12 templates). DO NOT persist to Cosmos — these are constants, not user data. |
| 13 | store-listing/route.ts |
— | — | Static data. DO NOT persist. |
| 14 | launch/route.ts |
— | — | Static launch plan data. DO NOT persist. |
| 15 | onboarding-email/route.ts |
— | — | Static email templates. DO NOT persist. |
| 16 | prompts/route.ts |
— | — | Computed prompts. DO NOT persist. |
| 17 | capture-config/route.ts |
— | — | Static config. DO NOT persist. |
| 18 | accessibility-config/route.ts |
— | — | Static config. DO NOT persist. |
| 19 | push-content/route.ts |
— | — | Static content definitions. DO NOT persist. |
| 20 | scheduler/route.ts |
— | — | Config/definitions for offline queue + sync. DO NOT persist. |
| 21 | thumbnails/route.ts |
— | — | Metadata cache. DO NOT persist (cache is ephemeral by design). |
| 22 | extract/route.ts |
— | — | Proxy to extraction-service. No data to persist. |
Only routes 1-11 need Cosmos wiring. Routes 12-22 are static data, computed responses, or proxies — they don't store user data and should NOT be persisted.
Phase 3: Document type consistency
Every Cosmos document MUST include:
id: string— unique document IDuserId: string— partition key valueproductId: 'mindlyst'— CRITICAL: missing from current routes. AddproductId: 'mindlyst'to everycreate()call.createdAt: string— ISO timestampupdatedAt: string | null— ISO timestamp on updates
Check existing routes (brains, memory, streak, etc.) and add productId: 'mindlyst' where missing.
Critical Rules
- Never remove the in-memory fallback. The
elsebranch must always work for local dev without Cosmos. - Always use parameterized queries — never string-interpolate user input into SQL. Pattern:
{ query: "SELECT * FROM c WHERE c.userId = @userId", parameters: [{ name: "@userId", value: userId }] }. - Always pass
{ partitionKey: userId }to queries and point reads. Cosmos requires this for cross-partition query avoidance. - Use
container.item(id, partitionKey).read/replace/delete()for single-document operations — NOT queries. - Add
productId: 'mindlyst'to every document creation. - Do NOT add new npm dependencies.
@azure/cosmosis already inpackage.json. - Do NOT modify the
cosmos.tsclient pattern. The singleton +getCosmosContainer()approach is correct. - Handle Cosmos errors gracefully — wrap in try/catch, return 500 with generic error message (never expose Cosmos error details to client).
- Brain chat embedding cache (
brain-chat/route.ts) uses aMapwith 5-min TTL for performance. Keep the in-memory cache even when Cosmos is configured — persist conversation history to Cosmos but keep the embedding cache in memory.
Example Transformation
Before (pure in-memory):
const referrals: Referral[] = [];
export async function POST(request: NextRequest) {
const body = await request.json();
const referral = { id: `ref_${Date.now()}`, ...body };
referrals.push(referral);
return NextResponse.json(referral, { status: 201 });
}
After (dual-mode):
import { getCosmosContainer, isCosmosConfigured } from '@/lib/cosmos';
import { resolveUserId } from '@/lib/user';
const referrals: Referral[] = []; // in-memory fallback
export async function POST(request: NextRequest) {
const container = isCosmosConfigured() ? getCosmosContainer('referrals') : null;
const userId = resolveUserId(request.headers);
const body = await request.json();
const referral = {
id: `ref_${Date.now()}_${crypto.randomUUID()}`,
userId,
productId: 'mindlyst',
...body,
createdAt: new Date().toISOString(),
};
if (container) {
await container.items.create(referral);
} else {
referrals.push(referral);
}
return NextResponse.json(referral, { status: 201 });
}
Verification Commands
# Type-check (catches import errors, type mismatches)
cd learning_multimodal_memory_agents/mindlyst-native/web && npx tsc --noEmit
# Build (catches runtime import issues)
cd learning_multimodal_memory_agents/mindlyst-native/web && npx next build --webpack
# Manual smoke test: start dev server, hit API routes
cd learning_multimodal_memory_agents/mindlyst-native/web && npm run dev -- -p 3050
# Then: curl http://localhost:3050/api/brains (should return default brains in-memory mode)
# Then: curl http://localhost:3050/api/streak (should return streak)
Verification Checklist (agent must confirm each)
npx tsc --noEmitpasses with zero errorsnpx next build --webpacksucceeds- All 11 routes that need Cosmos wiring now import
isCosmosConfigured+getCosmosContainer - Every
container.items.create()call includesproductId: 'mindlyst' - In-memory fallback still works (test with no
COSMOS_*env vars set) - No new npm dependencies added
MINDLYST_CONTAINERSincosmos.tsupdated with new container definitionsseed/route.tsensureContainers()will auto-create all new containers
Commit Convention
feat(web): add Cosmos containers for triage, brain-chat, brain-packs, referrals, ab-tests, waitlist, email-capture, engagement, context-triggersfeat(web): wire triage + brain-chat + brain-packs routes to Cosmos DBfeat(web): wire referral + ab-test + waitlist + email-capture routes to Cosmos DBfeat(web): wire engagement + context-triggers + nudge + monitoring routes to Cosmos DBfix(web): add productId: 'mindlyst' to all Cosmos document creation calls