# 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/batch - `routines` — 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/weekly - `fasting-protocols` — 5 endpoints: GET/GET/:id/POST/PUT/:id/DELETE/:id /fasting/protocols - `body-stages` — 2 endpoints: GET /fasting/stages, POST /fasting/autophagy-confidence All endpoints use JWT auth via `Authorization: Bearer `, `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=`, 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`) with `syncNow`, `setSyncEnabled`, `login`, `logout`, `syncedAddTimer`, `syncedUpdateTimer`, `syncedRemoveTimer`. Auto-syncs on 60s interval. Merges pulled timers into Zustand store. - **`auth-api.ts`** (32 lines) — `@bytelyst/auth-client` wrapper with `getAuthClient()`, `PRODUCT_ID = 'chronomind'`. - **`auth-context.tsx`** — React auth context with login/register/logout/forgotPassword/changePassword/deleteAccount. - **`store.ts`** — Zustand store with `addTimer`, `removeTimer`, `updateTimer`, `pause`, `resume`, etc. Uses `localStorage` persistence. - **`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 ObservableObject` singleton with `pullDelta()`, `pushTimer()`, `updateTimer()`, `deleteTimer()`, `pushOfflineQueue()`, `enqueueChange()`, `enqueueDelete()`, periodic sync (60s), `timerToDTO()`, `dtoToTimerPatch()`. DTOs match server schema. - **BUT:** `PlatformSyncManager` is NOT called from any SwiftUI view or `TimerStore`. The `TimerStore` uses `UserDefaults` directly. `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 with `pullDelta()`, `createTimer()`, `updateTimer()`, `deleteTimer()`, `batchUpsert()`, `pullRoutinesDelta()`. Uses `HttpURLConnection`. - **`SyncRepository.kt`** (246 lines) — Hilt `@Singleton` with `sync()`, `enqueueCreate()`, `enqueueUpdate()`, `enqueueDelete()`, offline queue in `SharedPreferences`. Merges pulled timers into Room via `timerDao.upsert()`. - **BUT:** `SyncRepository` is NOT injected into `TimerViewModel`. The `TimerViewModel` calls `TimerDao` directly 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 with `getAuthClient()`, `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-forget `syncSessionToBackend()` that calls `createSession()` 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`** — Has `loginWithAuth`, `registerWithAuth`, `hydrateFromToken`, `logout` wired. - **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:** 1. `web/src/components/Dashboard.tsx` — Currently destructures `useSync()` but only uses status fields. Need to also destructure `syncedAddTimer`, `syncedUpdateTimer`, `syncedRemoveTimer` and call them alongside the Zustand store mutations. 2. `web/src/components/CreateTimerModal.tsx` — After `addTimer(newTimer)`, also call `syncedAddTimer(newTimer)`. 3. `web/src/components/TimerCard.tsx` — After dismiss/complete/snooze actions that call store mutations, also call `syncedUpdateTimer`. 4. `web/src/components/AlarmOverlay.tsx` — After dismiss/snooze, call `syncedUpdateTimer`. 5. `web/src/components/PomodoroView.tsx` — After round complete/session complete, call `syncedUpdateTimer`. 6. `web/src/components/RoutineEditor.tsx` + `web/src/components/RoutineRunner.tsx` — Need routine sync. The `useSync` hook currently only syncs timers. Extend `fullSync()` in `platform-sync.ts` to also pull/push routines in the same sync cycle. **Pattern to follow:** ```typescript // 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. - `syncedAddTimer` etc. check `isSyncEnabled()` 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 `useSync` in 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 call `pullRoutineDelta()` and `batchUpsertRoutines()` for routines in the offline queue. The routine DTO conversion functions already exist (`routineToDTO`, `dtoToRoutinePatch`). Extend `SyncResult` to include `pulledRoutines` and merge them into the routine store. #### B. ChronoMind iOS — Wire PlatformSyncManager into TimerStore **Files to modify:** 1. `ios/ChronoMind/Shared/Store/TimerStore.swift` — Add a reference to `PlatformSyncManager.shared`. After every timer add/update/delete in the store, call `PlatformSyncManager.shared.enqueueChange(timer, .create/.update)` or `.enqueueDelete(timerId:)`. 2. `ios/ChronoMind/App/ContentView.swift` or root view — On app launch, call `PlatformSyncManager.shared.restoreAuthToken()` equivalent (token is already restored in `init`). On appear, trigger initial sync. 3. `ios/ChronoMind/Views/Components/` — No changes needed; store is the single source of truth. **Critical rules:** - `PlatformSyncManager` is `@MainActor` — safe to call from SwiftUI views. - The `sync(localTimers:)` method pulls delta AND pushes offline queue. Call it on app foreground (`scenePhase == .active`). - `enqueueChange` is synchronous (UserDefaults write) — call it inline after store mutations. - Do NOT block UI on sync. The `async` sync runs in background. #### C. ChronoMind Android — Wire SyncRepository into TimerViewModel **Files to modify:** 1. `android/app/src/main/java/com/chronomind/app/viewmodel/TimerViewModel.kt` — Inject `SyncRepository` via Hilt constructor. After every `timerDao.insert/update/delete`, also call `syncRepository.enqueueCreate/enqueueUpdate/enqueueDelete`. 2. `android/app/src/main/java/com/chronomind/app/di/AppModule.kt` — Ensure `SyncRepository` is provided (it's already `@Singleton` with `@Inject constructor` — Hilt should auto-provide). 3. App startup (e.g., `MainActivity.kt`) — Call `syncRepository.restoreAuthToken()` and trigger initial `sync()` in a coroutine scope. **Critical rules:** - `sync()` is a `suspend fun` — call from `viewModelScope.launch(Dispatchers.IO)`. - `enqueueCreate/enqueueUpdate/enqueueDelete` are 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:** 1. `src/store/fasting-store.ts` — The `syncSessionToBackend()` function is fire-and-forget on session START only. Add: - On `endFast()`: call `updateSession(session.id, { status, endedAt, waterIntake, notes })`. - On `pauseFast()`/`resumeFast()`: call `updateSession(session.id, { status, pausedAt })`. - On `addMoodCheckin()`: call `updateSession(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 calls `listSessions()` and populates `sessionHistory` from the server (for cross-device history). 2. `src/api/fasting-api.ts` — Already complete. No changes needed. 3. **New file: `src/screens/auth/AuthScreen.tsx`** — Login/register screen. Use `useUserStore().loginWithAuth` and `registerWithAuth` which 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 4. `src/app/(tabs)/_layout.tsx` or navigation — Add auth gate: if user not authenticated and `preference !== 'local_only'`, show AuthScreen. 5. `src/store/fasting-store.ts` `startFast()` — Currently generates `userId: 'local'`. When authenticated, use `useUserStore.getState().profile?.id` instead. **Critical rules:** - NomGap is React Native (Expo) — NO web-specific APIs (`localStorage`, `window`). Use MMKV for persistence. - `@bytelyst/auth-client` is already configured in `src/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 ```bash # 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 operations` - `feat(ios): connect PlatformSyncManager to TimerStore` - `feat(android): inject SyncRepository into TimerViewModel` - `feat(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: ```typescript 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()`): 1. `brains/route.ts` — container: `brains` 2. `memory/route.ts` — container: `memory_items` 3. `streak/route.ts` — container: `streaks` 4. `notifications/route.ts` — container: `notification_log` 5. `brief/route.ts` — container: `daily_briefs` 6. `reflection/route.ts` — container: `reflections` 7. `share-card/route.ts` — container: `share_cards` 8. `brain-growth/route.ts` — container: `brain_insights` 9. `analytics/route.ts` — container: `analytics_events` 10. `insights/route.ts` — container: `brain_insights` 11. `seed/route.ts` — calls `ensureContainers()` **~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_CONTAINERS` array (9 containers), `ensureContainers()`. - **`src/lib/user.ts`** — `resolveUserId(headers)` for extracting userId from request. - **`src/lib/abuse.ts`** — `checkRateLimit()` for rate limiting. - **`.env.example`** — Shows `COSMOS_ENDPOINT`, `COSMOS_KEY`, `COSMOS_DATABASE` env vars. - **Pattern in existing routes** (e.g., `brains/route.ts`) — Shows exactly how to do the dual-mode: check `isCosmosConfigured()`, get container, use parameterized queries with `partitionKey: 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`: ```typescript // 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: 1. Import `getCosmosContainer, isCosmosConfigured` from `@/lib/cosmos` 2. At the top of the handler, get the container: `const container = isCosmosConfigured() ? getCosmosContainer("") : null;` 3. For every READ operation: if `container`, query Cosmos with parameterized SQL; else use the existing in-memory array/map. 4. For every WRITE operation: if `container`, use `container.items.create()` / `container.item(id, partitionKey).replace()` / `container.item(id, partitionKey).delete()`; else use the existing in-memory mutation. 5. **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 ID - `userId: string` — partition key value - `productId: 'mindlyst'` — **CRITICAL: missing from current routes.** Add `productId: 'mindlyst'` to every `create()` call. - `createdAt: string` — ISO timestamp - `updatedAt: string | null` — ISO timestamp on updates Check existing routes (`brains`, `memory`, `streak`, etc.) and add `productId: 'mindlyst'` where missing. ### Critical Rules 1. **Never remove the in-memory fallback.** The `else` branch must always work for local dev without Cosmos. 2. **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 }] }`. 3. **Always pass `{ partitionKey: userId }` to queries and point reads.** Cosmos requires this for cross-partition query avoidance. 4. **Use `container.item(id, partitionKey).read/replace/delete()`** for single-document operations — NOT queries. 5. **Add `productId: 'mindlyst'`** to every document creation. 6. **Do NOT add new npm dependencies.** `@azure/cosmos` is already in `package.json`. 7. **Do NOT modify the `cosmos.ts` client pattern.** The singleton + `getCosmosContainer()` approach is correct. 8. **Handle Cosmos errors gracefully** — wrap in try/catch, return 500 with generic error message (never expose Cosmos error details to client). 9. **Brain chat embedding cache** (`brain-chat/route.ts`) uses a `Map` with 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):** ```typescript 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):** ```typescript 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 ```bash # 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 --noEmit` passes with zero errors - [ ] `npx next build --webpack` succeeds - [ ] All 11 routes that need Cosmos wiring now import `isCosmosConfigured` + `getCosmosContainer` - [ ] Every `container.items.create()` call includes `productId: 'mindlyst'` - [ ] In-memory fallback still works (test with no `COSMOS_*` env vars set) - [ ] No new npm dependencies added - [ ] `MINDLYST_CONTAINERS` in `cosmos.ts` updated with new container definitions - [ ] `seed/route.ts` `ensureContainers()` 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-triggers` - `feat(web): wire triage + brain-chat + brain-packs routes to Cosmos DB` - `feat(web): wire referral + ab-test + waitlist + email-capture routes to Cosmos DB` - `feat(web): wire engagement + context-triggers + nudge + monitoring routes to Cosmos DB` - `fix(web): add productId: 'mindlyst' to all Cosmos document creation calls`