feat(experimental): session-memory and multi-session-agent examples#1168
feat(experimental): session-memory and multi-session-agent examples#1168mattzcarey wants to merge 5 commits intofeat/session-managerfrom
Conversation
|
| private compactFn = createCompactFunction({ | ||
| summarize: (prompt) => | ||
| generateText({ model: this.getAI(), prompt }).then((r) => r.text), | ||
| protectHead: 1, | ||
| minTailMessages: 2, | ||
| tailTokenBudget: 100, | ||
| }); |
There was a problem hiding this comment.
🔴 Shared compactFn closure leaks previousSummary state across sessions
compactFn is a single class property created by createCompactFunction() at server.ts:33-39. Inside the closure, previousSummary is a mutable variable captured once (packages/agents/src/experimental/memory/utils/compaction-helpers.ts:435). When session A is compacted, previousSummary is set to session A's summary (compaction-helpers.ts:463). If session B is compacted next, buildSummaryPrompt receives session A's summary as the previousSummary parameter, causing the LLM to incorporate session A's context into session B's compaction summary — cross-session data contamination.
Was this helpful? React with 👍 or 👎 to provide feedback.
| @@ -0,0 +1,329 @@ | |||
| import { useState, useRef, useCallback } from "react"; | |||
There was a problem hiding this comment.
🟡 Missing useEffect for auto-scroll leaves messagesEndRef unused
messagesEndRef is created at line 93 and attached to a sentinel <div> at line 293, clearly intended for auto-scrolling to the bottom when new messages arrive. However, unlike the session-memory client (experimental/session-memory/src/client.tsx:99) which has useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]);, the multi-session-agent client never imports useEffect and never calls scrollIntoView. The chat area will not auto-scroll when new messages are added.
| import { useState, useRef, useCallback } from "react"; | |
| import { useState, useEffect, useRef, useCallback } from "react"; |
Was this helpful? React with 👍 or 👎 to provide feedback.
1731b1e to
9b4b630
Compare
030568a to
286de62
Compare
9b4b630 to
265f0dd
Compare
286de62 to
977b05c
Compare
| if (removed.length > 0) { | ||
| const summaryMsg = compacted.find((m) => | ||
| m.id.startsWith("compaction-summary-") | ||
| ); | ||
| if (summaryMsg) { | ||
| const summaryText = summaryMsg.parts | ||
| .filter((p) => p.type === "text") | ||
| .map((p) => (p as { text: string }).text) | ||
| .join("\n"); | ||
|
|
||
| this.session.addCompaction( | ||
| summaryText, | ||
| removed[0].id, | ||
| removed[removed.length - 1].id | ||
| ); |
There was a problem hiding this comment.
🔴 Second+ compaction silently fails because addCompaction stores virtual overlay message IDs that raw DB lookup can never match
The compact method calls session.getHistory() (line 131) which returns messages after applying compaction overlays — including virtual messages with IDs like compaction_<uuid> (packages/agents/src/experimental/memory/session/providers/agent.ts:297). After the first compaction, a subsequent compact call finds these virtual messages in its removed list (line 137), and passes removed[0].id (a virtual compaction_<uuid> ID) as fromMessageId to addCompaction (line 150). When getHistory() is later called, applyCompactions (agent.ts:284-315) iterates raw DB messages looking for this virtual ID — it will never find it, so the second compaction overlay is silently skipped. This means messages that should have been compacted persist in the history, causing the context window to grow unboundedly after the first compaction.
Trace of the failure
- First compaction:
addCompaction(summary, realMsgId, realMsgId)→ works correctly getHistory()now returns:[msg1, compaction_<uuid>, msg4, ..., msg12](overlay applied)- Second compaction's
removed[0].id="compaction_<uuid>"(virtual ID) addCompaction(summary, "compaction_<uuid>", msg10.id)stores this- Next
getHistory()→applyCompactionsscans raw DB messages forfromMessageId = "compaction_<uuid>"→ no match → overlay never applied
Was this helpful? React with 👍 or 👎 to provide feedback.
265f0dd to
4a81e4d
Compare
caf066c to
c45ac4a
Compare
068ba87 to
4a29fa3
Compare
Two examples demonstrating the Session API: session-memory: Single-session chat agent - Context blocks (soul + memory + todos) with frozen system prompt - update_context tool — AI saves facts to memory - Non-destructive compaction with hermes-style summary - Tool calls rendered inline (expandable cards) - Compact button to demo compaction multi-session-agent: Multi-session chat with sidebar UI - SessionManager for create/list/delete/switch sessions - Each session has independent memory and compaction - Cross-session FTS search via manager.tools() - Sidebar with chat list, search, create/delete
…Manager Update session-memory and multi-session-agent examples to use Session.create() and SessionManager.create() instead of manual provider construction. Much cleaner DX.
c45ac4a to
8aa9411
Compare
Summary
Two experimental examples demonstrating the Session API builder:
session-memory— Single sessionSession.create(this).withContext("memory", ...).withContext("todos", ...).withCachedPrompt()update_contexttoolmulti-session-agent— Multiple sessionsSessionManager.create(this).withContext("soul", ...).withContext("memory", ...).withCachedPrompt(){ ...await session.tools(), ...manager.tools() }Both use Workers AI (Kimi K2.5) via
workers-ai-provider.Stack
1/4 — #1166 Session API core
2/4 — #1167 SessionManager
3/4 — this PR
4/4 — #1169 Think integration (depends on this)
Test plan