Skip to content

Commit b41ea0d

Browse files
Ubuntuampcode-com
andcommitted
feat: implement Phase 4 shared memory — advanced cross-peer features
- Optimize cross-peer queries with peer.representation() instead of peer.chat() - Extend MemoryRecall handler for glasses cross-peer support - Add cross-peer context to web chat (chat.ts, chat.baml, bamlActions.ts) - Add group connections schema and CRUD (connectionGroups, connectionGroupMembers) - Update synthesis.baml with crossPeerPerspectives in MemoryContext - Add public query getActiveSharedMemoryConnectionsByMentraId for app layer - Add getActiveSharedMemoryGroupMembers internal query Amp-Thread-ID: https://ampcode.com/threads/T-019ca5da-0bdc-71c4-b883-2d206a12db38 Co-authored-by: Amp <amp@ampcode.com>
1 parent 004f156 commit b41ea0d

18 files changed

Lines changed: 1203 additions & 19 deletions

File tree

apps/application/src/baml_client/inlinedbaml.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/application/src/baml_client/partial_types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,11 @@ export namespace partial_types {
9696
peerCard: string[]
9797
recentMessages: string[]
9898
sessionSummaries: string[]
99+
crossPeerPerspectives: CrossPeerPerspective[]
100+
}
101+
export interface CrossPeerPerspective {
102+
label?: string | null
103+
perspective?: string | null
99104
}
100105
export interface MemoryCore {
101106
userName?: string | null

apps/application/src/baml_client/type_builder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export default class TypeBuilder {
4545

4646
LocationLite: ClassViewer<'LocationLite', "lat" | "lon" | "timezone">;
4747

48-
MemoryContext: ClassViewer<'MemoryContext', "explicitFacts" | "deductiveFacts" | "peerCard" | "recentMessages" | "sessionSummaries">;
48+
MemoryContext: ClassViewer<'MemoryContext', "explicitFacts" | "deductiveFacts" | "peerCard" | "recentMessages" | "sessionSummaries" | "crossPeerPerspectives">;
4949

5050
MemoryCore: ClassViewer<'MemoryCore', "userName" | "userFacts" | "deductiveFacts">;
5151

@@ -127,7 +127,7 @@ export default class TypeBuilder {
127127
]);
128128

129129
this.MemoryContext = this.tb.classViewer("MemoryContext", [
130-
"explicitFacts","deductiveFacts","peerCard","recentMessages","sessionSummaries",
130+
"explicitFacts","deductiveFacts","peerCard","recentMessages","sessionSummaries","crossPeerPerspectives",
131131
]);
132132

133133
this.MemoryCore = this.tb.classViewer("MemoryCore", [

apps/application/src/baml_client/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ export interface MemoryContext {
141141
peerCard: string[]
142142
recentMessages: string[]
143143
sessionSummaries: string[]
144+
crossPeerPerspectives: CrossPeerPerspective[]
145+
146+
}
147+
148+
export interface CrossPeerPerspective {
149+
label: string
150+
perspective: string
144151

145152
}
146153

apps/application/src/handlers/memory.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { b } from "@clairvoyant/baml-client";
22
import { api } from "@convex/_generated/api";
33
import type { Id } from "@convex/_generated/dataModel";
4+
import { Honcho } from "@honcho-ai/sdk";
45
import type { Peer, Session } from "@honcho-ai/sdk";
56
import type { AppSession } from "@mentra/sdk";
67
import { updateConversationResponse } from "../core/conversationLogger";
78
import { checkUserIsPro, convexClient } from "../core/convex";
89
import type { DisplayQueueManager } from "../core/displayQueue";
10+
import { env } from "../core/env";
911

1012
const memoryRunCallIds = new WeakMap<AppSession, number>();
1113

@@ -198,6 +200,82 @@ export async function MemoryRecall(
198200
peerRep = { explicit: [], deductive: [] };
199201
}
200202

203+
// Cross-peer queries: fetch perspectives from connected users
204+
let crossPeerPerspectives: Array<{ label: string; perspective: string }> = [];
205+
try {
206+
const connections = await convexClient.query(
207+
api.connections.getActiveSharedMemoryConnectionsByMentraId,
208+
{ mentraUserId },
209+
);
210+
211+
const cappedConnections = connections.slice(0, 3);
212+
if (cappedConnections.length > 0) {
213+
session.logger.info(
214+
`[startMemoryRecallFlow] Querying ${cappedConnections.length} cross-peer connections`,
215+
);
216+
217+
const honchoClient = new Honcho({
218+
apiKey: env.HONCHO_API_KEY,
219+
environment: "production",
220+
workspaceId: "with-context",
221+
});
222+
223+
const rawPerspectives = await Promise.all(
224+
cappedConnections.map(async (conn) => {
225+
try {
226+
const connectedPeer = await honchoClient.peer(
227+
`${conn.connectedUserId}-diatribe`,
228+
);
229+
const rep = await connectedPeer.representation({
230+
target: `${userId}-diatribe`,
231+
searchQuery: textQuery,
232+
searchTopK: 5,
233+
maxConclusions: 10,
234+
});
235+
return {
236+
label: conn.label ?? "Connected user",
237+
perspective: typeof rep.representation === "string" ? rep.representation : "",
238+
};
239+
} catch (error) {
240+
session.logger.warn(
241+
`[startMemoryRecallFlow] Cross-peer query failed for ${conn.connectedUserId}: ${error}`,
242+
);
243+
return null;
244+
}
245+
}),
246+
);
247+
248+
const validPerspectives = rawPerspectives.filter(
249+
(p): p is { label: string; perspective: string } =>
250+
p !== null && p.perspective.length > 0,
251+
);
252+
253+
// Sensitivity gate
254+
const sensitivityResults = await Promise.all(
255+
validPerspectives.map(async (p) => {
256+
try {
257+
const category = await b.CheckSensitivity(p.perspective);
258+
return { ...p, category };
259+
} catch {
260+
return { ...p, category: "SENSITIVE" as const };
261+
}
262+
}),
263+
);
264+
265+
crossPeerPerspectives = sensitivityResults
266+
.filter((p) => p.category === "SAFE")
267+
.map(({ label, perspective }) => ({ label, perspective }));
268+
269+
session.logger.info(
270+
`[startMemoryRecallFlow] Cross-peer: ${validPerspectives.length} valid, ${crossPeerPerspectives.length} safe`,
271+
);
272+
}
273+
} catch (error) {
274+
session.logger.warn(
275+
`[startMemoryRecallFlow] Cross-peer lookup failed: ${error}`,
276+
);
277+
}
278+
201279
// Build memory context
202280
const memoryContext = {
203281
explicitFacts: peerRep.explicit.map((e) => e.content),
@@ -207,6 +285,7 @@ export async function MemoryRecall(
207285
peerCard: contextData.peerCard,
208286
recentMessages: contextData.messages.slice(-5).map((m) => m.content),
209287
sessionSummaries,
288+
crossPeerPerspectives,
210289
};
211290

212291
// Synthesize response with BAML (replaces .chat() call)

baml_src/chat.baml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class ChatContext {
2929
userFacts string[] @description("Known facts about the user")
3030
deductiveFacts string[] @description("Inferred facts about the user")
3131
conversationHistory ChatConversationMessage[] @description("Previous messages in this chat")
32+
crossPeerPerspectives CrossPeerPerspective[] @description("Perspectives from connected users with shared memory enabled")
3233
}
3334

3435
function InterpretChatMessage(
@@ -50,6 +51,7 @@ Style:
5051
- Reference session context naturally
5152
- Keep responses brief - this is chat, not an essay
5253
- Don't be overly enthusiastic or use excessive exclamation marks
54+
- If connected user perspectives are available, weave them naturally with attribution (e.g. "Your wife mentioned...", "Alex has been looking into...")
5355

5456
DATE: {{ context.date }}
5557

@@ -75,6 +77,14 @@ USER PROFILE:
7577
- {{ fact }}
7678
{% endfor %}
7779

80+
{% endif %}
81+
{% if context.crossPeerPerspectives | length > 0 %}
82+
CONNECTED USER PERSPECTIVES:
83+
{% for p in context.crossPeerPerspectives %}
84+
From "{{ p.label }}":
85+
{{ p.perspective }}
86+
{% endfor %}
87+
7888
{% endif %}
7989
{% if context.conversationHistory | length > 0 %}
8090
CONVERSATION HISTORY:
@@ -114,6 +124,7 @@ test test_chat_simple_greeting {
114124
"User is interested in AI technologies"
115125
]
116126
conversationHistory []
127+
crossPeerPerspectives []
117128
}
118129
}
119130
}
@@ -142,6 +153,7 @@ test test_chat_with_history {
142153
createdAt "2024-12-24T16:00:00Z"
143154
}
144155
]
156+
crossPeerPerspectives []
145157
}
146158
}
147159
}
@@ -157,6 +169,7 @@ test test_chat_no_sessions {
157169
userFacts []
158170
deductiveFacts []
159171
conversationHistory []
172+
crossPeerPerspectives []
160173
}
161174
}
162175
}

baml_src/synthesis.baml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ class MemoryContext {
44
peerCard string[]
55
recentMessages string[]
66
sessionSummaries string[] @description("Summaries of past sessions, e.g. 'Dec 20: discussed weather and navigation'")
7+
crossPeerPerspectives CrossPeerPerspective[] @description("Perspectives from connected users with shared memory enabled")
78
}
89

910
class MemorySynthesisLines {
@@ -46,6 +47,14 @@ PAST SESSION SUMMARIES:
4647
- {{ summary }}
4748
{% endfor %}
4849

50+
{% if context.crossPeerPerspectives | length > 0 %}
51+
CONNECTED USER PERSPECTIVES:
52+
{% for p in context.crossPeerPerspectives %}
53+
From "{{ p.label }}":
54+
{{ p.perspective }}
55+
{% endfor %}
56+
57+
{% endif %}
4958
QUERY: {{ query }}
5059

5160
Instructions:
@@ -56,6 +65,7 @@ Instructions:
5665
- If you don't have enough information to answer, say so briefly
5766
- Focus on what's most relevant to the query
5867
- Ignore any alphanumeric IDs or hashes in the facts (e.g., "j979r44m..." prefixes) — extract only the meaningful content
68+
- If connected users have relevant perspectives, naturally mention them (e.g. "Sarah thinks...", "Alex mentioned...")
5969
"#
6070
}
6171

@@ -78,6 +88,7 @@ test test_memory_synthesis_name {
7888
"What is my name?"
7989
]
8090
sessionSummaries []
91+
crossPeerPerspectives []
8192
}
8293
}
8394
@@assert({{ this.lines|length <= 3 }})
@@ -107,6 +118,7 @@ test test_memory_synthesis_children {
107118
"Tell me about my daughters"
108119
]
109120
sessionSummaries []
121+
crossPeerPerspectives []
110122
}
111123
}
112124
@@assert({{ this.lines|length <= 3 }})
@@ -130,6 +142,7 @@ test test_memory_synthesis_no_info {
130142
"What car do I drive?"
131143
]
132144
sessionSummaries []
145+
crossPeerPerspectives []
133146
}
134147
}
135148
@@assert({{ this.lines|length <= 3 }})
@@ -156,6 +169,7 @@ test test_memory_synthesis_age {
156169
"How old am I?"
157170
]
158171
sessionSummaries []
172+
crossPeerPerspectives []
159173
}
160174
}
161175
@@assert({{ this.lines|length <= 3 }})

docs/SHARED_MEMORY_PLAN.md

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Shared Memory Between Users — Implementation Plan
22

3-
## Status: Architecture Finalized / Ready for Phase 0
3+
## Status: Phases 0-4 Complete / Ready for Phase 5 (Passive Sharing & Smart Detection)
44

55
## Problem
66

@@ -221,15 +221,16 @@ When a connection is revoked (either side):
221221

222222
## Implementation Phases
223223

224-
### Phase 0: SDK Evaluation (prerequisite)
224+
### Phase 0: SDK Evaluation ✅ COMPLETE
225225

226226
**Goal**: Confirm `peer.chat(query, { target })` is available and plan upgrade if needed.
227227

228-
1. Check current `@honcho-ai/sdk` version in `package.json` against latest v3.x
229-
2. Test if `peer.chat(query, { target })` (dialectic API) works with our version
230-
3. If upgrade needed, plan a separate PR for SDK migration
231-
4. Verify backward compatibility with existing `getContext()` / `addMessages()` patterns
232-
5. **Decision gate**: If dialectic API is unavailable, we need an alternative approach (manual context merging via `getContext()` on both peers)
228+
**Result**: Our `@honcho-ai/sdk@1.6.0` already supports the full v3 dialectic API including `peer.chat(query, { target })` for cross-peer queries. No SDK upgrade needed.
229+
230+
- `peer.chat(query, { target?: string | Peer, session?: string | Session, reasoningLevel?: string })` — confirmed available
231+
- `peer.context({ target })` — cross-peer context retrieval confirmed
232+
- `peer.representation({ target })` — cross-peer representation confirmed
233+
- Existing `session.getContext()` / `session.addMessages()` patterns remain compatible
233234

234235
### Phase 1: Connections (Convex only, no Honcho changes)
235236

@@ -272,14 +273,19 @@ When a connection is revoked (either side):
272273
3. Update `InterpretEmailReply` prompt for attributed cross-peer weaving
273274
4. Test: Ajay replies to an email note about a topic Sarah has discussed, her perspective enriches the reply
274275

275-
### Phase 4: Advanced Cross-Peer Features (future)
276+
### Phase 4: Advanced Cross-Peer Features ✅ COMPLETE
276277

277278
**Goal**: Deeper cross-peer reasoning and additional surfaces.
278279

279-
1. Use `session.working_rep(peerA, peerB)` for pre-computed cross-peer representations (faster than per-query `peer.chat`)
280-
2. Extend to real-time glasses responses (MemoryRecall handler) for "What does Sarah think about X?" queries
281-
3. Add cross-peer context to web chat (`chat.ts` `sendMessage`) for daily recap conversations
282-
4. Group connections (3+ users, e.g., a team sharing context)
280+
**Result**: All four sub-tasks implemented.
281+
282+
1. **Optimized with `peer.representation()`**: Replaced `peer.chat()` with `peer.representation({ target, searchQuery, searchTopK: 5, maxConclusions: 10 })` in `followupsChat.ts` and `emailReply.ts`. The `representation()` method returns pre-computed representations without agentic reasoning, which is faster since BAML handles interpretation. Applied across all integration surfaces.
283+
284+
2. **Extended MemoryRecall for glasses**: Added cross-peer support to `apps/application/src/handlers/memory.ts`. Uses public query `connections.getActiveSharedMemoryConnectionsByMentraId` (since the app layer can't access internal queries), queries up to 3 connected peers via `peer.representation()`, gates through `b.CheckSensitivity()` directly, and passes `crossPeerPerspectives` to `b.SynthesizeMemory()`. Updated `baml_src/synthesis.baml` with cross-peer context in `MemoryContext` class and prompt.
285+
286+
3. **Added cross-peer to web chat**: Extended `packages/convex/chat.ts` `sendMessage` with the same cross-peer query pattern (connections → representation → sensitivity gate → BAML). Updated `baml_src/chat.baml` `ChatContext` class with `crossPeerPerspectives` field and prompt section. Updated `bamlActions.ts` `interpretChatMessage` args.
287+
288+
4. **Group connections**: Added `connectionGroups` and `connectionGroupMembers` tables to schema. Added mutations (`createConnectionGroup`, `acceptGroupInvite`, `leaveConnectionGroup`, `toggleGroupSharedMemory`, `updateGroupMemberLabel`), public query (`getGroupsForUser`), and internal query (`getActiveSharedMemoryGroupMembers`) to `connections.ts`.
283289

284290
### Phase 5: Passive Sharing & Smart Detection (future)
285291

0 commit comments

Comments
 (0)