Skip to content

Commit 6c4d290

Browse files
Implement conversation logging feature and privacy opt-out enhancements (#79)
2 parents 213ac2b + 6096eed commit 6c4d290

48 files changed

Lines changed: 2423 additions & 1432 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/staging.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,5 @@ jobs:
3535
run: bun install
3636

3737
- name: Run tests
38-
run: bun run test
38+
run: bunx turbo run test --filter='!python-tools'
3939

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
name: Update Dependencies
2+
3+
on:
4+
schedule:
5+
# Runs every other Monday at 9am UTC (biweekly check)
6+
- cron: '0 9 * * 1'
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
update-deps:
15+
runs-on: ubuntu-latest
16+
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'schedule' && (github.run_number % 2 == 0))
17+
steps:
18+
- name: Checkout code
19+
uses: actions/checkout@v5
20+
with:
21+
ref: staging
22+
23+
- name: Setup Bun
24+
uses: oven-sh/setup-bun@v2
25+
with:
26+
bun-version: latest
27+
28+
- name: Update dependencies with taze
29+
run: bunx taze -r -w
30+
31+
- name: Install updated dependencies
32+
run: bun install
33+
34+
- name: Check for changes
35+
id: changes
36+
run: |
37+
if git diff --quiet; then
38+
echo "has_changes=false" >> $GITHUB_OUTPUT
39+
else
40+
echo "has_changes=true" >> $GITHUB_OUTPUT
41+
fi
42+
43+
- name: Create Pull Request
44+
if: steps.changes.outputs.has_changes == 'true'
45+
uses: peter-evans/create-pull-request@v7
46+
with:
47+
token: ${{ secrets.GITHUB_TOKEN }}
48+
branch: chore/update-dependencies
49+
base: staging
50+
commit-message: 'chore: update dependencies'
51+
title: 'chore: update dependencies'
52+
body: |
53+
Automated dependency update via `bunx taze -r -w`.
54+
55+
Please review the changes and ensure tests pass before merging.
56+
labels: dependencies
57+
delete-branch: true

AGENTS.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,3 +400,56 @@ Environment Variables & Port Configuration
400400
- Add ngrok domains to `ALLOWED_ORIGINS` for external dev access
401401
402402
Done. This guide covers the minimal, repeatable steps for adding new tools and flows with token‑efficient prompts and predictable UX.
403+
404+
Conversation Logging (ML Training Data)
405+
406+
The system captures all user transcripts and LLM responses to Convex for ML training and improvement.
407+
408+
**Architecture:**
409+
- **Schema**: `packages/convex/schema.ts` → `conversationLogs` table
410+
- **Convex mutations**: `packages/convex/conversationLogs.ts` → `logConversation`, `updateResponse`
411+
- **Application utility**: `apps/application/src/core/conversationLogger.ts` → fire-and-forget logging functions
412+
413+
**Data Flow:**
414+
1. `handleTranscription` receives utterance → routes via BAML `b.Route()`
415+
2. Immediately after routing: `logConversation(userId, sessionId, transcript, route)` creates initial record
416+
3. Handler processes request → displays response on glasses
417+
4. Handler calls `updateConversationResponse(userId, sessionId, transcript, response)` to capture formatted output
418+
419+
**LogContext Pattern:**
420+
Handlers that capture responses receive an optional `logContext` parameter:
421+
```ts
422+
logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string }
423+
```
424+
425+
After displaying lines, call:
426+
```ts
427+
if (logContext) {
428+
const responseText = lines.map((l) => `W: ${l}`).join("\n");
429+
updateConversationResponse(
430+
logContext.convexUserId,
431+
logContext.sessionId,
432+
logContext.transcript,
433+
responseText,
434+
);
435+
}
436+
```
437+
438+
**Coverage by Route:**
439+
| Route | Initial Log | Response Captured | Notes |
440+
|-------|-------------|-------------------|-------|
441+
| WEATHER | ✓ | ✓ | Weather summary lines |
442+
| MAPS | ✓ | ✓ | Place recommendations |
443+
| WEB_SEARCH | ✓ | ✓ | Search answer lines |
444+
| KNOWLEDGE | ✓ | ✓ | General knowledge answer |
445+
| MEMORY_RECALL | ✓ | ✓ | Synthesized memory lines |
446+
| MEMORY_CAPTURE | ✓ | ✗ | Silent operation (stores to Honcho) |
447+
| PASSTHROUGH | ✓ | ✓ (when hint shown) | Null case for ambient speech |
448+
| NOTE_THIS | ✓ | ✗ | Meta-action, not content response |
449+
| FOLLOW_UP | ✓ | ✗ | Meta-action, not content response |
450+
451+
**Key Points:**
452+
- Use fire-and-forget pattern: `void convexClient.mutation(...)` with `.catch()` for error logging
453+
- Response field captures the formatted text shown on glasses (e.g., `"W: 72°F and sunny"`)
454+
- PASSTHROUGH with no hint = null response (valuable for training router to identify ambient speech)
455+
- Initial log happens in `transcriptionFlow.ts`; response update happens in individual handlers

apps/api/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
"test": "echo 'No tests configured for @clairvoyant/api'"
2020
},
2121
"dependencies": {
22-
"@elysiajs/cors": "^1.4.0",
22+
"@elysiajs/cors": "^1.4.1",
2323
"@t3-oss/env-core": "^0.13.10",
24-
"convex": "^1.31.2",
24+
"convex": "^1.32.0",
2525
"jose": "^6.1.3",
2626
"jsonwebtoken": "^9.0.3",
2727
"zod": "^3.25.76"

apps/application/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
"@boundaryml/baml": "0.215.0",
1616
"@clairvoyant/baml-client": "workspace:*",
1717
"@honcho-ai/sdk": "^1.6.0",
18-
"@mentra/sdk": "^2.1.28",
18+
"@mentra/sdk": "^2.1.29",
1919
"@t3-oss/env-core": "^0.13.10",
2020
"@tavily/core": "^0.5.14",
21-
"convex": "^1.31.2",
21+
"convex": "^1.32.0",
2222
"exa-js": "^1.10.2",
2323
"openai": "^5.23.2",
2424
"zod": "^3.25.76"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { api } from "@convex/_generated/api";
2+
import type { Id } from "@convex/_generated/dataModel";
3+
import { convexClient } from "./convex";
4+
5+
/**
6+
* Logs a conversation to Convex for ML training data.
7+
* Uses fire-and-forget pattern - errors are logged but don't block.
8+
*/
9+
export function logConversation(
10+
userId: Id<"users">,
11+
sessionId: string,
12+
transcript: string,
13+
route: string,
14+
response?: string,
15+
): void {
16+
void convexClient
17+
.mutation(api.conversationLogs.logConversation, {
18+
userId,
19+
sessionId,
20+
transcript,
21+
route,
22+
response,
23+
})
24+
.catch((error) => {
25+
console.error("[ConversationLogger] Failed to log conversation:", error);
26+
});
27+
}
28+
29+
/**
30+
* Updates the response field for a previously logged conversation.
31+
* Used by handlers to capture the final formatted response shown on glasses.
32+
* Uses fire-and-forget pattern - errors are logged but don't block.
33+
*/
34+
export function updateConversationResponse(
35+
userId: Id<"users">,
36+
sessionId: string,
37+
transcript: string,
38+
response: string,
39+
): void {
40+
void convexClient
41+
.mutation(api.conversationLogs.updateResponse, {
42+
userId,
43+
sessionId,
44+
transcript,
45+
response,
46+
})
47+
.catch((error) => {
48+
console.error(
49+
"[ConversationLogger] Failed to update conversation response:",
50+
error,
51+
);
52+
});
53+
}

apps/application/src/handlers/hints.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { b, HintCategory } from "@clairvoyant/baml-client";
22
import { api } from "@convex/_generated/api";
3+
import type { Id } from "@convex/_generated/dataModel";
34
import type { Peer, Session } from "@honcho-ai/sdk";
45
import type { AppSession } from "@mentra/sdk";
6+
import { updateConversationResponse } from "../core/conversationLogger";
57
import { checkUserIsPro, convexClient } from "../core/convex";
68
import type { DisplayQueueManager } from "../core/displayQueue";
79

@@ -14,6 +16,7 @@ export async function tryPassthroughHint(
1416
peers: Peer[],
1517
mentraUserId: string,
1618
displayQueue: DisplayQueueManager,
19+
logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string },
1720
): Promise<void> {
1821
const runId = Date.now();
1922
hintRunIds.set(session, runId);
@@ -163,6 +166,16 @@ export async function tryPassthroughHint(
163166
durationMs: 4000,
164167
priority: 3,
165168
});
169+
170+
// Log the hint response for ML training
171+
if (logContext) {
172+
updateConversationResponse(
173+
logContext.convexUserId,
174+
logContext.sessionId,
175+
logContext.transcript,
176+
`H: ${hintResult.hint}`,
177+
);
178+
}
166179
} catch (error) {
167180
session.logger.error(`[tryPassthroughHint] Error: ${String(error)}`);
168181
}

apps/application/src/handlers/knowledge.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { b } from "@clairvoyant/baml-client";
22
import { api } from "@convex/_generated/api";
3+
import type { Id } from "@convex/_generated/dataModel";
34
import type { Peer, Session } from "@honcho-ai/sdk";
45
import type { AppSession } from "@mentra/sdk";
6+
import { updateConversationResponse } from "../core/conversationLogger";
57
import { checkUserIsPro, convexClient } from "../core/convex";
68
import type { DisplayQueueManager } from "../core/displayQueue";
79
import { showTextDuringOperation } from "../core/textWall";
@@ -17,6 +19,7 @@ export async function startKnowledgeFlow(
1719
peers: Peer[],
1820
mentraUserId: string,
1921
displayQueue: DisplayQueueManager,
22+
logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string },
2023
) {
2124
const runId = Date.now();
2225
knowledgeRunIds.set(session, runId);
@@ -228,6 +231,21 @@ export async function startKnowledgeFlow(
228231
priority: 2,
229232
});
230233
}
234+
235+
if (logContext && answerLines.length > 0) {
236+
const responseText = answerLines
237+
.map((line, i) => {
238+
const label = answerLines.length > 1 ? `A${i + 1}` : "A";
239+
return `Q: ${questionLine}\n${label}: ${line}`;
240+
})
241+
.join("\n");
242+
updateConversationResponse(
243+
logContext.convexUserId,
244+
logContext.sessionId,
245+
logContext.transcript,
246+
responseText,
247+
);
248+
}
231249
} else {
232250
displayQueue.enqueue({
233251
text: "// Clairvoyant\nK: No question detected.",

apps/application/src/handlers/maps.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { b } from "@clairvoyant/baml-client";
22
import { api } from "@convex/_generated/api";
3+
import type { Id } from "@convex/_generated/dataModel";
34
import type { Peer, Session } from "@honcho-ai/sdk";
45
import type { AppSession } from "@mentra/sdk";
6+
import { updateConversationResponse } from "../core/conversationLogger";
57
import {
68
checkUserIsPro,
79
convexClient,
@@ -30,6 +32,7 @@ async function processPlacesData(
3032
places: PlaceSuggestion[],
3133
runId: number,
3234
displayQueue: DisplayQueueManager,
35+
logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string },
3336
) {
3437
if (!places?.length) {
3538
displayQueue.enqueue({
@@ -188,6 +191,16 @@ async function processPlacesData(
188191
priority: 2,
189192
});
190193
}
194+
195+
if (logContext && lines.length > 0) {
196+
const responseText = lines.map((l) => `M: ${l}`).join("\n");
197+
updateConversationResponse(
198+
logContext.convexUserId,
199+
logContext.sessionId,
200+
logContext.transcript,
201+
responseText,
202+
);
203+
}
191204
}
192205

193206
export async function startMapsFlow(
@@ -197,6 +210,7 @@ export async function startMapsFlow(
197210
peers: Peer[],
198211
mentraUserId: string,
199212
displayQueue: DisplayQueueManager,
213+
logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string },
200214
) {
201215
const runId = Date.now();
202216
mapsRunIds.set(session, runId);
@@ -287,6 +301,7 @@ export async function startMapsFlow(
287301
places,
288302
runId,
289303
displayQueue,
304+
logContext,
290305
);
291306
} catch (error) {
292307
session.logger.error(`[startMapsFlow] Maps flow error: ${String(error)}`);
@@ -376,6 +391,7 @@ export async function startMapsFlow(
376391
places,
377392
runId,
378393
displayQueue,
394+
logContext,
379395
);
380396
} catch (error) {
381397
session.logger.error(

apps/application/src/handlers/memory.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { b } from "@clairvoyant/baml-client";
22
import { api } from "@convex/_generated/api";
3+
import type { Id } from "@convex/_generated/dataModel";
34
import type { Peer, Session } from "@honcho-ai/sdk";
45
import type { AppSession } from "@mentra/sdk";
6+
import { updateConversationResponse } from "../core/conversationLogger";
57
import { checkUserIsPro, convexClient } from "../core/convex";
68
import type { DisplayQueueManager } from "../core/displayQueue";
79

@@ -78,6 +80,7 @@ export async function MemoryRecall(
7880
peers: Peer[],
7981
mentraUserId: string,
8082
displayQueue: DisplayQueueManager,
83+
logContext?: { convexUserId: Id<"users">; sessionId: string; transcript: string },
8184
) {
8285
const runId = Date.now();
8386
memoryRunCallIds.set(session, runId);
@@ -251,6 +254,17 @@ export async function MemoryRecall(
251254
priority: 2,
252255
});
253256
}
257+
258+
// Log the response for ML training
259+
if (logContext) {
260+
const responseText = lines.map((l) => `R: ${l}`).join("\n");
261+
updateConversationResponse(
262+
logContext.convexUserId,
263+
logContext.sessionId,
264+
logContext.transcript,
265+
responseText,
266+
);
267+
}
254268
} else {
255269
session.logger.error(
256270
`[startMemoryRecallFlow] No lines in synthesis results`,

0 commit comments

Comments
 (0)