diff --git a/.github/workflows/staging.yml b/.github/workflows/staging.yml index 3cf8dc9..67f91ef 100644 --- a/.github/workflows/staging.yml +++ b/.github/workflows/staging.yml @@ -35,5 +35,5 @@ jobs: run: bun install - name: Run tests - run: bun run test + run: bunx turbo run test --filter='!python-tools' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f721231..9daf848 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -30,19 +30,9 @@ jobs: uses: oven-sh/setup-bun@v2 with: bun-version: latest - - - name: Setup uv - uses: astral-sh/setup-uv@v4 - with: - version: "latest" - name: Install dependencies run: bun install - - - name: Install Python dependencies - run: cd packages/python-tools && uv sync - name: Run tests - run: | - export PATH="$HOME/.local/bin:$PATH" - bun run test + run: bunx turbo run test --filter='!python-tools' diff --git a/AGENTS.md b/AGENTS.md index 8c5d9a1..4cc84da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -400,3 +400,56 @@ Environment Variables & Port Configuration - Add ngrok domains to `ALLOWED_ORIGINS` for external dev access Done. This guide covers the minimal, repeatable steps for adding new tools and flows with token‑efficient prompts and predictable UX. + +Conversation Logging (ML Training Data) + +The system captures all user transcripts and LLM responses to Convex for ML training and improvement. + +**Architecture:** +- **Schema**: `packages/convex/schema.ts` → `conversationLogs` table +- **Convex mutations**: `packages/convex/conversationLogs.ts` → `logConversation`, `updateResponse` +- **Application utility**: `apps/application/src/core/conversationLogger.ts` → fire-and-forget logging functions + +**Data Flow:** +1. `handleTranscription` receives utterance → routes via BAML `b.Route()` +2. Immediately after routing: `logConversation(userId, sessionId, transcript, route)` creates initial record +3. Handler processes request → displays response on glasses +4. Handler calls `updateConversationResponse(userId, sessionId, transcript, response)` to capture formatted output + +**LogContext Pattern:** +Handlers that capture responses receive an optional `logContext` parameter: +```ts +logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string } +``` + +After displaying lines, call: +```ts +if (logContext) { + const responseText = lines.map((l) => `W: ${l}`).join("\n"); + updateConversationResponse( + logContext.convexUserId, + logContext.sessionId, + logContext.transcript, + responseText, + ); +} +``` + +**Coverage by Route:** +| Route | Initial Log | Response Captured | Notes | +|-------|-------------|-------------------|-------| +| WEATHER | ✓ | ✓ | Weather summary lines | +| MAPS | ✓ | ✓ | Place recommendations | +| WEB_SEARCH | ✓ | ✓ | Search answer lines | +| KNOWLEDGE | ✓ | ✓ | General knowledge answer | +| MEMORY_RECALL | ✓ | ✓ | Synthesized memory lines | +| MEMORY_CAPTURE | ✓ | ✗ | Silent operation (stores to Honcho) | +| PASSTHROUGH | ✓ | ✓ (when hint shown) | Null case for ambient speech | +| NOTE_THIS | ✓ | ✗ | Meta-action, not content response | +| FOLLOW_UP | ✓ | ✗ | Meta-action, not content response | + +**Key Points:** +- Use fire-and-forget pattern: `void convexClient.mutation(...)` with `.catch()` for error logging +- Response field captures the formatted text shown on glasses (e.g., `"W: 72°F and sunny"`) +- PASSTHROUGH with no hint = null response (valuable for training router to identify ambient speech) +- Initial log happens in `transcriptionFlow.ts`; response update happens in individual handlers diff --git a/apps/application/src/core/conversationLogger.ts b/apps/application/src/core/conversationLogger.ts new file mode 100644 index 0000000..f427a54 --- /dev/null +++ b/apps/application/src/core/conversationLogger.ts @@ -0,0 +1,53 @@ +import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; +import { convexClient } from "./convex"; + +/** + * Logs a conversation to Convex for ML training data. + * Uses fire-and-forget pattern - errors are logged but don't block. + */ +export function logConversation( + userId: Id<"users">, + sessionId: string, + transcript: string, + route: string, + response?: string, +): void { + void convexClient + .mutation(api.conversationLogs.logConversation, { + userId, + sessionId, + transcript, + route, + response, + }) + .catch((error) => { + console.error("[ConversationLogger] Failed to log conversation:", error); + }); +} + +/** + * Updates the response field for a previously logged conversation. + * Used by handlers to capture the final formatted response shown on glasses. + * Uses fire-and-forget pattern - errors are logged but don't block. + */ +export function updateConversationResponse( + userId: Id<"users">, + sessionId: string, + transcript: string, + response: string, +): void { + void convexClient + .mutation(api.conversationLogs.updateResponse, { + userId, + sessionId, + transcript, + response, + }) + .catch((error) => { + console.error( + "[ConversationLogger] Failed to update conversation response:", + error, + ); + }); +} diff --git a/apps/application/src/handlers/hints.ts b/apps/application/src/handlers/hints.ts index 5c6cd76..98c2715 100644 --- a/apps/application/src/handlers/hints.ts +++ b/apps/application/src/handlers/hints.ts @@ -1,7 +1,9 @@ import { b, HintCategory } from "@clairvoyant/baml-client"; import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; import type { Peer, Session } from "@honcho-ai/sdk"; import type { AppSession } from "@mentra/sdk"; +import { updateConversationResponse } from "../core/conversationLogger"; import { checkUserIsPro, convexClient } from "../core/convex"; import type { DisplayQueueManager } from "../core/displayQueue"; @@ -14,6 +16,7 @@ export async function tryPassthroughHint( peers: Peer[], mentraUserId: string, displayQueue: DisplayQueueManager, + logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }, ): Promise { const runId = Date.now(); hintRunIds.set(session, runId); @@ -163,6 +166,16 @@ export async function tryPassthroughHint( durationMs: 4000, priority: 3, }); + + // Log the hint response for ML training + if (logContext) { + updateConversationResponse( + logContext.convexUserId, + logContext.sessionId, + logContext.transcript, + `H: ${hintResult.hint}`, + ); + } } catch (error) { session.logger.error(`[tryPassthroughHint] Error: ${String(error)}`); } diff --git a/apps/application/src/handlers/knowledge.ts b/apps/application/src/handlers/knowledge.ts index 9edab24..edc3187 100644 --- a/apps/application/src/handlers/knowledge.ts +++ b/apps/application/src/handlers/knowledge.ts @@ -1,7 +1,9 @@ import { b } from "@clairvoyant/baml-client"; import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; import type { Peer, Session } from "@honcho-ai/sdk"; import type { AppSession } from "@mentra/sdk"; +import { updateConversationResponse } from "../core/conversationLogger"; import { checkUserIsPro, convexClient } from "../core/convex"; import type { DisplayQueueManager } from "../core/displayQueue"; import { showTextDuringOperation } from "../core/textWall"; @@ -17,6 +19,7 @@ export async function startKnowledgeFlow( peers: Peer[], mentraUserId: string, displayQueue: DisplayQueueManager, + logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }, ) { const runId = Date.now(); knowledgeRunIds.set(session, runId); @@ -228,6 +231,21 @@ export async function startKnowledgeFlow( priority: 2, }); } + + if (logContext && answerLines.length > 0) { + const responseText = answerLines + .map((line, i) => { + const label = answerLines.length > 1 ? `A${i + 1}` : "A"; + return `Q: ${questionLine}\n${label}: ${line}`; + }) + .join("\n"); + updateConversationResponse( + logContext.convexUserId, + logContext.sessionId, + logContext.transcript, + responseText, + ); + } } else { displayQueue.enqueue({ text: "// Clairvoyant\nK: No question detected.", diff --git a/apps/application/src/handlers/maps.ts b/apps/application/src/handlers/maps.ts index 8b03c4f..45a63cc 100644 --- a/apps/application/src/handlers/maps.ts +++ b/apps/application/src/handlers/maps.ts @@ -1,7 +1,9 @@ import { b } from "@clairvoyant/baml-client"; import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; import type { Peer, Session } from "@honcho-ai/sdk"; import type { AppSession } from "@mentra/sdk"; +import { updateConversationResponse } from "../core/conversationLogger"; import { checkUserIsPro, convexClient, @@ -30,6 +32,7 @@ async function processPlacesData( places: PlaceSuggestion[], runId: number, displayQueue: DisplayQueueManager, + logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }, ) { if (!places?.length) { displayQueue.enqueue({ @@ -188,6 +191,16 @@ async function processPlacesData( priority: 2, }); } + + if (logContext && lines.length > 0) { + const responseText = lines.map((l) => `M: ${l}`).join("\n"); + updateConversationResponse( + logContext.convexUserId, + logContext.sessionId, + logContext.transcript, + responseText, + ); + } } export async function startMapsFlow( @@ -197,6 +210,7 @@ export async function startMapsFlow( peers: Peer[], mentraUserId: string, displayQueue: DisplayQueueManager, + logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }, ) { const runId = Date.now(); mapsRunIds.set(session, runId); @@ -287,6 +301,7 @@ export async function startMapsFlow( places, runId, displayQueue, + logContext, ); } catch (error) { session.logger.error(`[startMapsFlow] Maps flow error: ${String(error)}`); @@ -376,6 +391,7 @@ export async function startMapsFlow( places, runId, displayQueue, + logContext, ); } catch (error) { session.logger.error( diff --git a/apps/application/src/handlers/memory.ts b/apps/application/src/handlers/memory.ts index 84e7bb5..11ee0a5 100644 --- a/apps/application/src/handlers/memory.ts +++ b/apps/application/src/handlers/memory.ts @@ -1,7 +1,9 @@ import { b } from "@clairvoyant/baml-client"; import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; import type { Peer, Session } from "@honcho-ai/sdk"; import type { AppSession } from "@mentra/sdk"; +import { updateConversationResponse } from "../core/conversationLogger"; import { checkUserIsPro, convexClient } from "../core/convex"; import type { DisplayQueueManager } from "../core/displayQueue"; @@ -78,6 +80,7 @@ export async function MemoryRecall( peers: Peer[], mentraUserId: string, displayQueue: DisplayQueueManager, + logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }, ) { const runId = Date.now(); memoryRunCallIds.set(session, runId); @@ -251,6 +254,17 @@ export async function MemoryRecall( priority: 2, }); } + + // Log the response for ML training + if (logContext) { + const responseText = lines.map((l) => `R: ${l}`).join("\n"); + updateConversationResponse( + logContext.convexUserId, + logContext.sessionId, + logContext.transcript, + responseText, + ); + } } else { session.logger.error( `[startMemoryRecallFlow] No lines in synthesis results`, diff --git a/apps/application/src/handlers/search.ts b/apps/application/src/handlers/search.ts index 1ba5cb7..8aaa436 100644 --- a/apps/application/src/handlers/search.ts +++ b/apps/application/src/handlers/search.ts @@ -1,7 +1,9 @@ import { b } from "@clairvoyant/baml-client"; import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; import type { Peer, Session } from "@honcho-ai/sdk"; import type { AppSession } from "@mentra/sdk"; +import { updateConversationResponse } from "../core/conversationLogger"; import { checkUserIsPro, convexClient } from "../core/convex"; import type { DisplayQueueManager } from "../core/displayQueue"; import { showTextDuringOperation } from "../core/textWall"; @@ -18,6 +20,7 @@ export async function startWebSearchFlow( peers: Peer[], mentraUserId: string, displayQueue: DisplayQueueManager, + logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }, ) { const runId = Date.now(); webSearchRunIds.set(session, runId); @@ -231,6 +234,16 @@ export async function startWebSearchFlow( priority: 2, }); } + + if (logContext) { + const responseText = lines.map((l) => `S: ${l}`).join("\n"); + updateConversationResponse( + logContext.convexUserId, + logContext.sessionId, + logContext.transcript, + responseText, + ); + } } else { session.logger.error(`[startWebSearchFlow] No lines in answerLines`); } diff --git a/apps/application/src/handlers/weather.ts b/apps/application/src/handlers/weather.ts index 29d6bf6..0119ac5 100644 --- a/apps/application/src/handlers/weather.ts +++ b/apps/application/src/handlers/weather.ts @@ -1,8 +1,10 @@ import { b } from "@clairvoyant/baml-client"; import type { FormattedWeather } from "@clairvoyant/baml-client/types"; import { api } from "@convex/_generated/api"; +import type { Id } from "@convex/_generated/dataModel"; import type { Peer, Session } from "@honcho-ai/sdk"; import type { AppSession } from "@mentra/sdk"; +import { updateConversationResponse } from "../core/conversationLogger"; import { checkUserIsPro, convexClient, @@ -30,6 +32,7 @@ async function processWeatherData( displayQueue: DisplayQueueManager, memorySession?: Session, peers?: Peer[], + logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }, ) { // Fetch memory context if available let memoryContext: { @@ -161,6 +164,16 @@ async function processWeatherData( priority: 2, }); } + + if (logContext && weatherLines.lines.length > 0) { + const responseText = weatherLines.lines.map((l) => `W: ${l}`).join("\n"); + updateConversationResponse( + logContext.convexUserId, + logContext.sessionId, + logContext.transcript, + responseText, + ); + } } export async function startWeatherFlow( @@ -168,6 +181,7 @@ export async function startWeatherFlow( memorySession: Session | undefined, peers: Peer[] | undefined, displayQueue: DisplayQueueManager, + logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }, ) { const mentraUserId = session.userId; let preferredUnit: "C" | "F" = "C"; @@ -250,6 +264,7 @@ export async function startWeatherFlow( displayQueue, memorySession, peers, + logContext, ); // TODO: Add a BAML to check if the weather is inclement and if so and if so, ask the user if they're appropriately dressed for the weather. @@ -337,6 +352,7 @@ export async function startWeatherFlow( displayQueue, memorySession, peers, + logContext, ); } catch (err) { session.logger.error( diff --git a/apps/application/src/transcriptionFlow.ts b/apps/application/src/transcriptionFlow.ts index 16ba2de..d3736a1 100644 --- a/apps/application/src/transcriptionFlow.ts +++ b/apps/application/src/transcriptionFlow.ts @@ -3,6 +3,7 @@ import type { Id } from "@convex/_generated/dataModel"; import type { Peer, Session } from "@honcho-ai/sdk"; import type { AppSession, TranscriptionData } from "@mentra/sdk"; import { recordToolInvocation } from "./core/convex"; +import { logConversation } from "./core/conversationLogger"; import type { DisplayQueueManager } from "./core/displayQueue"; import { tryPassthroughHint } from "./handlers/hints"; import { startKnowledgeFlow } from "./handlers/knowledge"; @@ -31,11 +32,17 @@ export async function handleTranscription( session.logger.warn(`[Clairvoyant] No routing decision made. Resetting...`); return; } + + // Log conversation for ML training (fire-and-forget) + logConversation(convexUserId, sessionId, data.text, routing.routing); + + const logContext = { convexUserId, sessionId, transcript: data.text }; + switch (routing.routing) { case Router.WEATHER: session.logger.info(`[Clairvoyant] Weather route: starting async flow`); void recordToolInvocation(mentraUserId, Router.WEATHER); - void startWeatherFlow(session, memorySession, peers, displayQueue); + void startWeatherFlow(session, memorySession, peers, displayQueue, logContext); return; case Router.MAPS: @@ -48,6 +55,7 @@ export async function handleTranscription( peers, mentraUserId, displayQueue, + logContext, ); return; @@ -63,6 +71,7 @@ export async function handleTranscription( peers, mentraUserId, displayQueue, + logContext, ); return; @@ -76,6 +85,7 @@ export async function handleTranscription( peers, mentraUserId, displayQueue, + logContext, ); return; @@ -99,7 +109,7 @@ export async function handleTranscription( `[Clairvoyant] Memory Recall route: starting async flow`, ); void recordToolInvocation(mentraUserId, Router.MEMORY_RECALL); - void MemoryRecall(data.text, session, memorySession, peers, mentraUserId, displayQueue); + void MemoryRecall(data.text, session, memorySession, peers, mentraUserId, displayQueue, logContext); return; case Router.PASSTHROUGH: @@ -113,6 +123,7 @@ export async function handleTranscription( peers, mentraUserId, displayQueue, + logContext, ); return; diff --git a/docs/autonomous/prd.json b/docs/autonomous/prd.json deleted file mode 100644 index a7e3d57..0000000 --- a/docs/autonomous/prd.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "project": "remove-payment-gating", - "branchName": "autonomous/remove-payment-gating", - "description": "Remove payment gating from the application layer. All features previously behind Pro subscription are now free. Backend Convex schema and Polar webhooks remain intact for potential future use.", - "userStories": [ - { - "id": "US-001", - "title": "Bypass isPro check in application", - "description": "As a user, I can access all features without a Pro subscription because the isPro check always returns true.", - "acceptanceCriteria": [ - "checkUserIsPro() in apps/application/src/core/convex.ts always returns true", - "Typecheck passes: bun run build", - "Lint passes: bun run check" - ], - "priority": 1, - "passes": true, - "notes": "Function now returns true immediately, parameter renamed to _mentraUserId to indicate unused" - }, - { - "id": "US-002", - "title": "Remove isPro gate from daily summaries", - "description": "As a user, I can view my daily summaries without a Pro subscription.", - "acceptanceCriteria": [ - "packages/convex/dailySummaries.ts getForUser removes subscription check", - "Always returns isPro: true with summaries data", - "Typecheck passes: bun run build", - "Lint passes: bun run check" - ], - "priority": 2, - "passes": true, - "notes": "Removed polar subscription check and early return. Always returns isPro: true. Removed unused polar import." - }, - { - "id": "US-003", - "title": "Remove subscription gate from session summaries", - "description": "As a user, my session summaries are stored regardless of subscription status.", - "acceptanceCriteria": [ - "packages/convex/sessionSummaries.ts upsert removes subscription check (lines 24-31)", - "Always stores session summaries for all users", - "Typecheck passes: bun run build", - "Lint passes: bun run check" - ], - "priority": 3, - "passes": true, - "notes": "Removed polar subscription check and early return. Now stores session summaries for all users. Removed unused polar import." - }, - { - "id": "US-004", - "title": "Comment out Billing section in Settings", - "description": "As a user, I no longer see the Billing section in Settings since payments are disabled.", - "acceptanceCriteria": [ - "apps/web/src/components/SettingsPage.tsx Billing button (lines 140-154) is commented out", - "Billing card section (lines 291-310) is commented out", - "SubscriptionCard import can be commented out", - "Typecheck passes: bun run build", - "Lint passes: bun run check" - ], - "priority": 4, - "passes": true, - "notes": "Commented out SubscriptionCard import, Billing button, and Billing card section with JSX comments" - }, - { - "id": "US-005", - "title": "Remove Pro upgrade prompt from Memory page", - "description": "As a user, I see my memory log directly without an upgrade prompt.", - "acceptanceCriteria": [ - "apps/web/src/components/MemoryPage.tsx removes the !result.isPro conditional block (lines 63-88)", - "Users go straight to viewing summaries or 'no memories yet' state", - "Typecheck passes: bun run build", - "Lint passes: bun run check" - ], - "priority": 5, - "passes": true, - "notes": "Removed !result.isPro conditional block. Users now go straight to summaries or 'no memories yet' state. Removed unused CardDescription import." - }, - { - "id": "US-006", - "title": "Unify features list on Home page", - "description": "As a user, I see all features in a single unified list without Free/Pro distinction.", - "acceptanceCriteria": [ - "apps/web/src/components/HomePage.tsx FEATURES array: all items have isPro: false", - "UI collapses Free/Pro sections into single 'Features' list", - "Remove 'Upgrade' badge from Pro heading", - "Typecheck passes: bun run build", - "Lint passes: bun run check" - ], - "priority": 6, - "passes": true, - "notes": "Set all isPro: false in FEATURES array. Collapsed Free/Pro sections into single unified list. Removed Pro badge from accordion triggers." - } - ] -} diff --git a/docs/autonomous/progress.txt b/docs/autonomous/progress.txt deleted file mode 100644 index 502054b..0000000 --- a/docs/autonomous/progress.txt +++ /dev/null @@ -1,62 +0,0 @@ -# Progress Log - -## Iteration Counter -current_iteration: 7 -max_iterations: 15 - -## Codebase Patterns - -- Build check: `bun run build` -- Lint check: `bun run check` -- Package manager: bun -- Monorepo structure: apps/application, apps/web, packages/convex -- isPro gating pattern: `checkUserIsPro()` in apps/application/src/core/convex.ts -- Convex subscription check: `polar.getCurrentSubscription(ctx, { userId })` - ---- - -## Execution Log - -(Entries appended below as iterations complete) - ---- - -## 2026-01-17 | US-001 | T-019bcf05-d7a5-725a-920d-5893c5e42879 -**Changes:** Modified checkUserIsPro() to always return true, bypassing all Pro checks in application handlers -**Files:** apps/application/src/core/convex.ts -**Learnings:** Pre-existing lint errors in codebase (36 errors), unrelated to payment gating changes - ---- - -## 2026-01-17 | US-002 | T-019bcf14-b957-7596-a765-6977690c16b4 -**Changes:** Removed polar subscription check from getForUser query. Always returns isPro: true with summaries. Removed unused polar import. -**Files:** packages/convex/dailySummaries.ts -**Learnings:** None - straightforward pattern matching US-001 - ---- - -## 2026-01-17 | US-003 | T-019bcf15-c3db-76bd-9131-479e7e8a8787 -**Changes:** Removed polar subscription check from upsert mutation. Session summaries now stored for all users. Removed unused polar import. -**Files:** packages/convex/sessionSummaries.ts -**Learnings:** None - same pattern as US-001 and US-002 - ---- - -## 2026-01-17 | US-004 | T-019bcf16-a824-75dc-9360-9b2c5fa777bf -**Changes:** Commented out Billing section in Settings - SubscriptionCard import, Billing button, and Billing card section -**Files:** apps/web/src/components/SettingsPage.tsx -**Learnings:** None - straightforward JSX comment pattern - ---- - -## 2026-01-17 | US-005 | T-019bcf17-9b44-7058-b0d4-079f92ef715c -**Changes:** Removed !result.isPro conditional block from MemoryPage.tsx. Users now see summaries or "no memories yet" directly. Removed unused CardDescription import. -**Files:** apps/web/src/components/MemoryPage.tsx -**Learnings:** None - straightforward removal of conditional block - ---- - -## 2026-01-17 | US-006 | T-019bcf18-7598-72cb-b2fb-ca7b86c9f04f -**Changes:** Set all isPro: false in FEATURES array. Collapsed Free/Pro sections into single unified "Features" list. Removed Pro badge from accordion triggers. -**Files:** apps/web/src/components/HomePage.tsx -**Learnings:** None - straightforward data change and UI simplification diff --git a/docs/autonomous/prompt.md b/docs/autonomous/prompt.md deleted file mode 100644 index 9854da4..0000000 --- a/docs/autonomous/prompt.md +++ /dev/null @@ -1,71 +0,0 @@ -# Ralph Mode: Remove Payment Gating - -You are executing Ralph Mode via Amp handoffs. Follow these rules strictly. - -## Context - -The Mantra platform CTO confirmed WebView-based payment processing is not possible. We are removing all payment gating from the application layer so all features are free. Backend Convex schema and Polar webhooks remain intact for potential future use. - -## Execution Steps (In Order) - -1. **Read progress.txt FIRST** — Check "Codebase Patterns" section at top -2. **Read prd.json** — Find current state -3. **Check iteration limit** — If `current_iteration >= max_iterations`, output `COMPLETE` and STOP -4. **Check branch** — Ensure on `autonomous/remove-payment-gating`, checkout if needed -5. **Pick next story** — First story where `passes: false` (lowest priority number) -6. **Implement ONE story** — Complete all acceptance criteria -7. **Run quality checks** — `bun run build` and `bun run check` -8. **Update AGENTS.md** — If patterns discovered, add to relevant AGENTS.md -9. **Commit** — `git add -A && git commit -m "feat: [US-XXX] - title"` -10. **Update prd.json** — Set `passes: true`, add notes -11. **Update progress.txt** — Increment counter, append log entry -12. **Check completion**: - - If ALL stories pass → output `COMPLETE` and STOP - - If `current_iteration >= max_iterations` → output `COMPLETE` and STOP - - Otherwise → handoff to fresh thread - -## Handoff Format - -When handing off, use this goal: -``` -Execute Ralph Mode for remove-payment-gating. Read docs/autonomous/prompt.md for instructions. -``` - -## Progress Report Format - -APPEND to progress.txt (never replace existing content): - -```markdown ---- - -## YYYY-MM-DD | US-XXX | T- -**Changes:** Brief description of what was implemented -**Files:** file1.ts, file2.ts -**Learnings:** Patterns discovered for future iterations -``` - -## Stop Conditions - -Output `COMPLETE` when: -- All stories have `passes: true` -- `current_iteration >= max_iterations` -- Unrecoverable error (document in progress.txt first) - -## Reference Files - -| File | Purpose | -|------|---------| -| apps/application/src/core/convex.ts | checkUserIsPro() function (US-001) | -| packages/convex/dailySummaries.ts | getForUser query with isPro gate (US-002) | -| packages/convex/sessionSummaries.ts | upsert mutation with subscription gate (US-003) | -| apps/web/src/components/SettingsPage.tsx | Billing section to comment out (US-004) | -| apps/web/src/components/MemoryPage.tsx | isPro upgrade prompt to remove (US-005) | -| apps/web/src/components/HomePage.tsx | FEATURES array and Free/Pro UI (US-006) | - -## Quality Requirements - -- ALL commits must pass typecheck (`bun run build`) -- ALL commits must pass lint (`bun run check`) -- Do NOT commit broken code -- Keep changes focused and minimal -- Follow existing patterns in codebase diff --git a/packages/convex/_generated/api.d.ts b/packages/convex/_generated/api.d.ts index 572c7da..0d89eb7 100644 --- a/packages/convex/_generated/api.d.ts +++ b/packages/convex/_generated/api.d.ts @@ -12,6 +12,7 @@ import type * as analytics from "../analytics.js"; import type * as bamlActions from "../bamlActions.js"; import type * as chat from "../chat.js"; import type * as chatQueries from "../chatQueries.js"; +import type * as conversationLogs from "../conversationLogs.js"; import type * as cronManagement from "../cronManagement.js"; import type * as dailySummaries from "../dailySummaries.js"; import type * as dailySynthesis from "../dailySynthesis.js"; @@ -47,6 +48,7 @@ declare const fullApi: ApiFromModules<{ bamlActions: typeof bamlActions; chat: typeof chat; chatQueries: typeof chatQueries; + conversationLogs: typeof conversationLogs; cronManagement: typeof cronManagement; dailySummaries: typeof dailySummaries; dailySynthesis: typeof dailySynthesis; diff --git a/packages/convex/conversationLogs.ts b/packages/convex/conversationLogs.ts new file mode 100644 index 0000000..3c4e235 --- /dev/null +++ b/packages/convex/conversationLogs.ts @@ -0,0 +1,75 @@ +import { v } from "convex/values"; +import { mutation, query } from "./_generated/server"; + +export const logConversation = mutation({ + args: { + userId: v.id("users"), + sessionId: v.string(), + transcript: v.string(), + route: v.string(), + response: v.optional(v.string()), + }, + handler: async (ctx, args) => { + return await ctx.db.insert("conversationLogs", { + userId: args.userId, + sessionId: args.sessionId, + transcript: args.transcript, + route: args.route, + response: args.response, + }); + }, +}); + +export const getBySession = query({ + args: { + sessionId: v.string(), + }, + handler: async (ctx, args) => { + return await ctx.db + .query("conversationLogs") + .withIndex("by_session", (q) => q.eq("sessionId", args.sessionId)) + .order("desc") + .collect(); + }, +}); + +export const getByUser = query({ + args: { + userId: v.id("users"), + }, + handler: async (ctx, args) => { + return await ctx.db + .query("conversationLogs") + .withIndex("by_user", (q) => q.eq("userId", args.userId)) + .order("desc") + .collect(); + }, +}); + +export const updateResponse = mutation({ + args: { + userId: v.id("users"), + sessionId: v.string(), + transcript: v.string(), + response: v.string(), + }, + handler: async (ctx, args) => { + const log = await ctx.db + .query("conversationLogs") + .withIndex("by_session", (q) => q.eq("sessionId", args.sessionId)) + .filter((q) => + q.and( + q.eq(q.field("userId"), args.userId), + q.eq(q.field("transcript"), args.transcript), + ), + ) + .order("desc") + .first(); + + if (log) { + await ctx.db.patch(log._id, { response: args.response }); + return log._id; + } + return null; + }, +}); diff --git a/packages/convex/schema.ts b/packages/convex/schema.ts index ecd86a0..3740084 100644 --- a/packages/convex/schema.ts +++ b/packages/convex/schema.ts @@ -140,4 +140,14 @@ export default defineSchema({ content: v.string(), createdAt: v.string(), }).index("by_followup", ["followupId"]), + conversationLogs: defineTable({ + userId: v.id("users"), + sessionId: v.string(), + transcript: v.string(), + route: v.string(), + response: v.optional(v.string()), + }) + .index("by_user", ["userId"]) + .index("by_session", ["sessionId"]) + .index("by_route", ["route"]), });