Skip to content

feat(experimental): SessionManager — multi-session lifecycle and search#1167

Open
mattzcarey wants to merge 6 commits intofeat/session-api-corefrom
feat/session-manager
Open

feat(experimental): SessionManager — multi-session lifecycle and search#1167
mattzcarey wants to merge 6 commits intofeat/session-api-corefrom
feat/session-manager

Conversation

@mattzcarey
Copy link
Contributor

@mattzcarey mattzcarey commented Mar 23, 2026

Summary

  • SessionManager: registry of named sessions with lifecycle (create/get/list/delete/rename)
  • Chainable builder API: SessionManager.create(agent).withContext(...).withCachedPrompt()
  • getSession(id) uses Session.create().forSession(id) internally — each session gets auto-namespaced provider keys (e.g. memory_chat-123, _system_prompt_chat-123)
  • Convenience methods: append, appendAll, getHistory, clearMessages, deleteMessages
  • Compaction: needsCompaction, addCompaction, compactAndSplit
  • Usage tracking: addUsage (input/output tokens, cost)
  • Cross-session FTS search with session_search tool
  • Tool separation: session.tools()update_context, manager.tools()session_search
const manager = SessionManager.create(this)
  .withContext("soul", { defaultContent: "You are helpful.", readonly: true })
  .withContext("memory", { description: "Learned facts", maxTokens: 1100 })
  .withCachedPrompt();

// Each getSession(id) auto-namespaces providers per session
const session = manager.getSession("chat-123");
const tools = { ...await session.tools(), ...manager.tools() };

Stack

1/4 — #1166 Session API core
2/4 — this PR
3/4 — #1168 Examples (depends on this)
4/4 — #1169 Think integration (depends on 3)

Test plan

  • 13 DO-backed tests: session isolation, compaction isolation, system prompt persistence/refresh, clear isolation, manager CRUD, cross-session search, search tool, context block proxies, AgentContextProvider

@changeset-bot
Copy link

changeset-bot bot commented Mar 23, 2026

⚠️ No Changeset found

Latest commit: 4a29fa3

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 2 potential issues.

View 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +158 to +162
deleteMessages(messageIds: string[]): void {
for (const session of this._sessions.values()) {
session.deleteMessages(messageIds);
}
}
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 deleteMessages silently no-ops when no sessions have been accessed via getSession()

SessionManager.deleteMessages iterates over this._sessions.values(), which is an in-memory cache populated only by prior getSession() calls. If no sessions have been accessed yet, the map is empty and the loop body never executes — messages remain in the database untouched. Even when sessions are cached, the same SQL DELETE FROM assistant_messages WHERE id = ${id} (packages/agents/src/experimental/memory/session/providers/agent.ts:182) runs redundantly once per cached session, since it doesn't filter by session_id. The method should execute the deletes directly against the database rather than depending on cached session instances.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 9 additional findings in Devin Review.

Open in Devin Review


export { AgentContextProvider } from "./providers/agent-context";

export { SessionManager, type SessionInfo, type SessionManagerOptions } from "./manager";
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Missing changeset for public API addition of SessionManager

The AGENTS.md rule states: "Changes to packages/ that affect the public API or fix bugs need a changeset." This PR adds SessionManager, SessionInfo, and SessionManagerOptions to the public export of agents/experimental/memory/session (a published entry point in package.json). The .changeset/ directory contains only the auto-generated README.md and config.json — no changeset file was created for this change.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@mattzcarey mattzcarey force-pushed the feat/session-manager branch from 265f0dd to 4a81e4d Compare March 23, 2026 23:40
Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines +203 to +205
upsert(sessionId: string, message: UIMessage, parentId?: string): string {
return this.append(sessionId, message, parentId);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 upsert() silently drops updates to existing messages due to INSERT OR IGNORE semantics

upsert() at line 203-205 simply delegates to append(), which calls Session.appendMessage()AgentSessionProvider.appendMessage() which uses INSERT OR IGNORE (providers/agent.ts:166). This means if a message with the same id already exists, the entire operation is silently ignored — the message content is NOT updated. A method named "upsert" should update the existing message if the ID already exists, not silently discard the new content. Callers relying on upsert semantics would lose data without any error.

Prompt for agents
In packages/agents/src/experimental/memory/session/manager.ts line 203-205, the upsert() method delegates to append(), which uses INSERT OR IGNORE and silently drops updates to existing messages. To implement true upsert semantics, upsert() should first check if the message exists (via session.getMessage(message.id)), and if so, call session.updateMessage(message) instead of appendMessage(). Alternatively, rename the method to something that doesn't imply update semantics, like appendIfNew().
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 23, 2026

Open in StackBlitz

agents

npm i https://pkg.pr.new/agents@1167

@cloudflare/ai-chat

npm i https://pkg.pr.new/@cloudflare/ai-chat@1167

@cloudflare/codemode

npm i https://pkg.pr.new/@cloudflare/codemode@1167

hono-agents

npm i https://pkg.pr.new/hono-agents@1167

@cloudflare/shell

npm i https://pkg.pr.new/@cloudflare/shell@1167

@cloudflare/think

npm i https://pkg.pr.new/@cloudflare/think@1167

@cloudflare/voice

npm i https://pkg.pr.new/@cloudflare/voice@1167

@cloudflare/worker-bundler

npm i https://pkg.pr.new/@cloudflare/worker-bundler@1167

commit: 4a29fa3

devin-ai-integration[bot]

This comment was marked as resolved.

SessionManager for managing multiple named sessions:
- create/get/getSession/list/delete/rename
- Convenience: append/appendAll/getHistory/clearMessages by session ID
- compactAndSplit — end session, create new one seeded with summary
- addUsage — token tracking per session
- search() — cross-session FTS (queries shared table directly)
- tools() — returns session_search tool for LLM use
- Uses assistant_* table names for Think backward compat
- 8 new tests: create/get, list, delete, rename, search, search tool,
  context block proxies, AgentContextProvider persistence
…viders

SessionManager.create(agent) returns a SessionManager with chainable
config — .withContext(), .withCachedPrompt(), .maxContextMessages().

getSession(id) now uses Session.create().forSession(id) internally,
so each session automatically gets namespaced provider keys
(e.g. memory_chat-123, _system_prompt_chat-123).

Removes sessionOptions from constructor — context and cachedPrompt
are now top-level builder methods matching Session's API.
- upsert: check-then-update or insert (was silently dropping updates)
- fork: pass parentSessionId to create() for session lineage
- _ensureTable: also create assistant_fts (search could fail before any session access)
@mattzcarey mattzcarey force-pushed the feat/session-manager branch from 068ba87 to 4a29fa3 Compare March 24, 2026 00:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant