Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

feat(chat): let the agent find, load, and switch into its own past chats

merged#2855CedarCopilot

CedarCopilot wants to merge 6 commits into staging from feat/chat-history-search-agent-tool

Potential production issues

Agent tool calls may lose their request context after this change

A wrapped agent tool no longer receives the request context supplied by its caller. The same check passes on the current base and fails on this PR revision.

When an agent invokes a tool through the master tool surface.

Suggested fix: Preserve the caller's request context when wrapping the master tool execution, then rerun the same check on both revisions.

Technical evidence

The same behavior check passed on the current base and failed on this change.

Observed difference: withApprovalContext's wrapped execute still receives the real requestContext

Related changed code: apps/server/src/mastra/tools/master-surface.ts.

Live on prod, watching, 2 days leftTimeline and evidence
  1. Opened
    Sep 21, 2026, 4:37 AM
  2. Merged
    Sep 21, 2026, 5:46 AM
  3. Live on staging
    Sep 21, 2026, 5:46 AM
  4. Live on prod
    Sep 21, 2026, 5:46 AM
  5. Observed 0 hours
    Sep 21, 2026, 5:46 AM
  6. Watching

    Live on prod, watching, 2 days left

    Sep 21, 2026, 5:46 AM
  7. Pipelines steady after this deploy
    Sep 21, 2026, 5:46 AM
  8. Pipelines steady after this deploy
    Sep 21, 2026, 5:46 AM

Behaviors Libra is checking

The chat agent can load a selected past thread's recent messages, including prior tool calls and results, as a formatted transcript for the current turn without changing the user's active thread.Degradedlow confidence

Verification reached its budget before a verdict.

staging, checked Sep 22, 2026, 5:11 AM
Search, load, and switch operations are restricted to the authenticated caller's own undeleted threads and use the same not-found response for nonexistent or another user's thread IDs.Degradedlow confidence

Strict CloudWatch fallback found 1 prod failure log lines for [run-chat-agent-sdk] Skipping unusable MCP connection (The operation failed for an operation-specific reason) in the last 24h while checking /switch, threadswitchresponseprocessor.

staging, checked Sep 22, 2026, 5:17 AM
The chat agent can search the caller's undeleted past chat threads by substring matches in both thread titles and message content, returning recent matches with IDs, timestamps, match location, and message snippets.Degradedlow confidence

Strict CloudWatch fallback found 1 prod failure log lines for [run-chat-agent-sdk] Skipping unusable MCP connection (The operation failed for an operation-specific reason) in the last 24h while checking updatedat, threadswitchresponseprocessor.

staging, checked Sep 22, 2026, 5:17 AM
Chat history title and message-content substring searches use concurrent PostgreSQL trigram GIN indexes so searches continue to avoid full sequential scans as chat volume grows.Degradedlow confidence

Strict CloudWatch fallback found 1 prod failure log lines for [run-chat-agent-sdk] Skipping unusable MCP connection (The operation failed for an operation-specific reason) in the last 23h while checking /content, explain, postgresql, threadswitchresponseprocessor.

staging, checked Sep 22, 2026, 4:14 AM
The chat agent can move the user's active chat view into one of the user's own past threads by emitting a `thread-switch` event containing the thread ID, name, and persisted update timestamp.Degradedlow confidence

Strict CloudWatch fallback found 1 prod failure log lines for [run-chat-agent-sdk] Skipping unusable MCP connection (The operation failed for an operation-specific reason) in the last 23h while checking threadid, updatedat, threadswitchresponseprocessor.

staging, checked Sep 22, 2026, 4:14 AM
The frontend accepts only complete thread-switch events and hydrates the server-provided `updatedAt` before switching so a never-before-seen past thread fetches and displays its persisted messages instead of appearing as an empty loaded thread.Degradedlow confidence

Strict CloudWatch fallback found 1 prod failure log lines for [run-chat-agent-sdk] Skipping unusable MCP connection (The operation failed for an operation-specific reason) in the last 23h while checking threadswitchresponseprocessor, mainthreadid, threadid, updatedat.

staging, checked Sep 22, 2026, 4:28 AM

Failures attributed to this change

No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 21, 2026, 10:46 PM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 21, 2026, 10:46 PM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 21, 2026, 10:46 PM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 21, 2026, 10:46 PM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 20 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 22, 2026, 1:19 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 4 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 22, 2026, 1:28 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 22, 2026, 2:46 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 22, 2026, 2:46 AM
No prod customers are affected while this is only in staging. If promoted, prod impact is unknown because Libra still needs a concrete exception, route, and failed user action before assigning severity. 0 hits · no retained affected-user count · no retained trace sample.Introducedmedium confidence

internal_only

staging, first seen Sep 22, 2026, 4:19 AM

Libra has verdicts on 0 of 7 tracked behaviors on prod; 7 are still being checked. Libra checks hourly for 3 days after each deploy.

Summary

Split out of #2814 , this PR is only the chat-based (in-app agent) half of that PR, plus two capabilities #2814 never had: the agent can now find, read, and actually switch the user into its own past chat threads. The popover UI search from #2814 (History/Recent popover text search) is deliberately left out; it'll ship separately if we still want it.

Three tools, meant to chain:

  • search-chat-history , search the user's own past chat threads by title AND message content , not title-only, since a thread's title can say nothing about what's actually inside it (e.g. a CSV account upload buried in a message). Returns a threadId + snippet per match.
  • load-chat-history , given a threadId, reads that thread's own message history (including prior tool calls/results) back into the agent's context for the current turn, so it can continue the topic without the user re-explaining it. Does not touch the frontend.
  • switch-chat-thread , given a threadId, actually moves the user's screen into that thread , the same effect as clicking it in the History/Recent popover.

All three registered on chatAgent / chatAgentUnderstudy (mastra/agents/chat-agent.ts) and the family-surface Master agent via IN_APP_ORCHESTRATION_FAMILIES (mastra/tools/master-surface.ts).

Implementation

  • services/chat/search-chat-threads.ts , shared search core behind search-chat-history. ILIKE match on chat_threads.name and chat_messages.content.
  • mastra/tools/chat/searchChatHistoryTool.ts / loadChatHistoryTool.ts / switchChatThreadTool.ts , the three tools. All self-scoped: verify the thread belongs to the calling user before doing anything, same "no such thread" message whether it's missing or owned by someone else (never confirms cross-user existence).
  • load-chat-history reuses loadHeadlessHistory/formatHeadlessHistory (mastra/utils/headless-history.ts) , the same loader that hydrates Slack/iMessage headless threads , rather than writing a new reader.
  • switch-chat-thread emits a thread-switch SSE object event (same mechanism manage-context's context-updated event already uses) carrying { threadId, name }.
  • apps/mail/.../responseProcessors/threadSwitchResponseProcessor.ts , the frontend side. Calls the store's existing switchThread(threadId, name) action (messagesSlice.ts) , the exact function the History/Recent popover click path already uses, which hydrates messages from the DB itself when they aren't cached. No new frontend loading logic , this only adds the agent-triggered entry point into an existing, already-tested mechanism.
  • Switching is safe mid-generation: agentConnectionSlice tracks streaming per threadId, and every response processor is bound to the thread it originated from regardless of which thread is active in the UI , so the agent's own in-flight response keeps streaming into its original thread; the user just isn't looking at it live until they switch back. That's an existing, tested state (background streaming into an inactive thread), not a new race.
  • db/migrations/chat_search_trgm.sql , pg_trgm GIN trigram indexes backing the ILIKE search. Must be applied out-of-band (CREATE INDEX CONCURRENTLY can't run in a transaction) , see the migration file's header for the exact psql invocation and pooler caveat.

Left out of this PR (still in #2814, not touched here): EmbeddedCedarChat.tsx (History popover text search UI), LeftSidebarContent.tsx (Recent popover text search UI), and the chat.searchThreads tRPC query those two UI surfaces call.

Known gaps carried over from #2814's review

  • The Agent SDK harness (used by viewer accounts and some flagged users) doesn't have any of the three tools registered , only the Mastra chat agents and the family-surface Master agent do.
  • The snippet query in search-chat-threads.ts loads every matching message across a user's matched threads before picking one per thread in application code , fine at today'
Show production surfaces and changed-file mapping

Production surfaces

Libra has not measured any production surfaces for this change yet.

Changed files → surfaces

  • apps/mail/modules/cedar-os/__tests__/store/agentConnection/responseProcessors/threadSwitchResponseProcessor.test.tsxno production surface mapped
  • apps/mail/modules/cedar-os/src/store/agentConnection/responseProcessors/initializeResponseProcessorRegistry.tsno production surface mapped
  • apps/mail/modules/cedar-os/src/store/agentConnection/responseProcessors/threadSwitchResponseProcessor.tsno production surface mapped
  • apps/server/src/db/chat-schema.tsno production surface mapped
  • apps/server/src/db/migrations/chat_search_trgm.sqlno production surface mapped
  • apps/server/src/mastra/agents/chat-agent.tsno production surface mapped
  • apps/server/src/mastra/tools/__tests__/approval-flag-guard.test.tsno production surface mapped
  • apps/server/src/mastra/tools/__tests__/master-surface.test.tsno production surface mapped
  • apps/server/src/mastra/tools/chat/loadChatHistoryTool.tsno production surface mapped
  • apps/server/src/mastra/tools/chat/searchChatHistoryTool.tsno production surface mapped
  • apps/server/src/mastra/tools/chat/switchChatThreadTool.tsno production surface mapped
  • apps/server/src/mastra/tools/master-surface.tsno production surface mapped
  • apps/server/src/services/chat/search-chat-threads.integration.test.tsno production surface mapped
  • apps/server/src/services/chat/search-chat-threads.tsno production surface mapped