fix(core): restore state signals without thread scans#18182
Conversation
Avoid scanning entire long thread histories when state signal tracking already has the message ids needed to restore active copies. Co-Authored-By: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
Remove the legacy full-thread fallback from state signal history restoration so missing tracked ids degrade to local state instead of scanning every message. Co-Authored-By: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
🦋 Changeset detectedLatest commit: a343a76 The changes in this PR will be included in the next version bump. This PR includes changesets to release 23 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Walkthrough
ChangesState Signal Resolution Refactor
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
PR triageLinked issue check skipped for core contributor @TylerBarnes. PR complexity score
Applied label: Changed test gateChanged Test Gate is pending. The |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/agent/state-signals.ts`:
- Around line 179-183: The call to memoryStore.listMessagesById on line 183
lacks error handling; if this async call rejects due to a transient database or
storage failure, the error propagates and the local-history fallback is skipped.
Wrap the await memoryStore.listMessagesById call in a try-catch block, catching
any errors and falling back to returning the localHistory with contextWindow
(the same fallback returned on line 180) when the call fails, ensuring
resilience against transient storage failures.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0898238e-8f46-458e-83e9-da57ccd4387d
📒 Files selected for processing (4)
.changeset/cuddly-ears-grab.mdpackages/core/src/agent/state-signals.tspackages/core/src/processors/runner.test.tspackages/core/src/processors/runner.ts
💤 Files with no reviewable changes (1)
- packages/core/src/processors/runner.ts
| if (trackedSignalIds.size === 0 || typeof memoryStore.listMessagesById !== 'function') { | ||
| return { ...localHistory, contextWindow }; | ||
| } | ||
|
|
||
| const storedMessages = await memoryStore.listMessagesById({ messageIds: [...trackedSignalIds] }); |
There was a problem hiding this comment.
Preserve local fallback when ID lookup throws.
Line 183 calls storage directly; if listMessagesById rejects (transient DB/store failure), this path throws and skips the intended local-history fallback.
🔧 Suggested fix
- const storedMessages = await memoryStore.listMessagesById({ messageIds: [...trackedSignalIds] });
+ let storedMessages: { messages: MastraDBMessage[] };
+ try {
+ storedMessages = await memoryStore.listMessagesById({ messageIds: [...trackedSignalIds] });
+ } catch {
+ return { ...localHistory, contextWindow };
+ }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/core/src/agent/state-signals.ts` around lines 179 - 183, The call to
memoryStore.listMessagesById on line 183 lacks error handling; if this async
call rejects due to a transient database or storage failure, the error
propagates and the local-history fallback is skipped. Wrap the await
memoryStore.listMessagesById call in a try-catch block, catching any errors and
falling back to returning the localHistory with contextWindow (the same fallback
returned on line 180) when the call fails, ensuring resilience against transient
storage failures.
CalebBarnes
left a comment
There was a problem hiding this comment.
Reviewed — LGTM. Clean fix for the long-thread restore path.
Swapping the full-thread listMessages({ perPage: false }) scan for a targeted listMessagesById({ messageIds }) built from tracking.activeCopies + lastSnapshotSignalId is the right call, and the fallback to local history avoids any unbounded scan.
A few things I checked that hold up:
- Ordering is preserved even though
orderBy: createdAt ASCwas dropped —mergeStateSignalsandderiveStateSignalHistoryboth re-sort viasortStateSignals, so the id-lookup result is fine regardless of adapter return order (pg/libsql actually returnDESC). - Dropping
resourceIdis safe:listMessagesByIdlooks up purely by id, the var is still used elsewhere inrunner.ts, anddbMessagesToStateSignalsstill getsthreadIdfor scoping. - Adapter coverage:
listMessagesByIdis an abstract method onMemoryStoreBaseand is implemented across the storage adapters (confirmed real queries in pg + libsql), so the fast path engages in practice — thetypeof !== 'function'guard is just defensive cover for old/custom stores.
One non-blocking nit: trackedSignalIds.size === 0 is effectively dead — the !tracking.lastSnapshotSignalId early-return above guarantees the set always has ≥1 entry. Harmless, just noting it.
Verified locally (HEAD a343a76f): runner.test.ts (75) passed with no type errors, tsc --noEmit clean, prettier clean on changed files.
This fixes long-thread state signal restore by loading tracked signal
messages directly by id instead of scanning thread history.
Before:
```ts
await memoryStore.listMessages({
threadId,
resourceId,
perPage: false,
});
```
After:
```ts
await memoryStore.listMessagesById({ messageIds: trackedSignalIds });
```
If tracked ids or id-based lookup are unavailable, we now fall back to
local in-memory state instead of doing an unbounded thread scan.
Verified with:
`pnpm --filter ./packages/core test -- src/processors/runner.test.ts
--bail 1 --reporter=dot`
`pnpm --filter ./packages/core check`
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## ELI5
Imagine you have a very long list of messages and need to find a few
specific ones. The old way was to look through every single message in
the list one by one until you found what you needed. The new way is much
smarter: it says "give me these specific messages by their ID" instead,
skipping all the others. This makes it much faster when threads get
really long.
## Overview
This PR improves the performance of state signal restoration in
long-running threads by eliminating expensive full-thread message scans.
Instead of iterating through all messages in a thread's history, the
system now uses a targeted ID-based lookup to fetch only the tracked
signal messages it needs.
## Changes
**Core Logic Update** (`packages/core/src/agent/state-signals.ts`)
- Modified `resolveStateSignalHistory` to collect tracked signal message
IDs from `tracking.activeCopies` and `tracking.lastSnapshotSignalId`
upfront
- Replaced `memoryStore.listMessages({ threadId, resourceId, perPage:
false })` (full thread scan) with `memoryStore.listMessagesById({
messageIds })` (targeted lookup)
- Removed the `resourceId` parameter from the function signature since
it's no longer needed for the more efficient ID-based lookup
- Graceful degradation: if `listMessagesById` is unavailable or tracking
yields no IDs, the function returns the local in-memory state without
attempting expensive fallbacks
**Caller Update** (`packages/core/src/processors/runner.ts`)
- Removed the now-unnecessary `resourceId` argument from the
`resolveStateSignalHistory` call
**Test Updates** (`packages/core/src/processors/runner.test.ts`)
- Updated mock to provide both `listMessages` and `listMessagesById`
methods
- Changed test expectations to verify that only `listMessagesById` is
called (with the specific message IDs) and `listMessages` is not called
## Impact
This optimization prevents performance degradation as thread histories
grow longer, especially in long-running conversation scenarios where
state signals need to be restored. The changes were verified against
existing test suites in the core package.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Mastra Code (openai/gpt-5.5) <noreply@mastra.ai>
This fixes long-thread state signal restore by loading tracked signal messages directly by id instead of scanning thread history.
Before:
After:
If tracked ids or id-based lookup are unavailable, we now fall back to local in-memory state instead of doing an unbounded thread scan.
Verified with:
pnpm --filter ./packages/core test -- src/processors/runner.test.ts --bail 1 --reporter=dotpnpm --filter ./packages/core checkELI5
Imagine you have a very long list of messages and need to find a few specific ones. The old way was to look through every single message in the list one by one until you found what you needed. The new way is much smarter: it says "give me these specific messages by their ID" instead, skipping all the others. This makes it much faster when threads get really long.
Overview
This PR improves the performance of state signal restoration in long-running threads by eliminating expensive full-thread message scans. Instead of iterating through all messages in a thread's history, the system now uses a targeted ID-based lookup to fetch only the tracked signal messages it needs.
Changes
Core Logic Update (
packages/core/src/agent/state-signals.ts)resolveStateSignalHistoryto collect tracked signal message IDs fromtracking.activeCopiesandtracking.lastSnapshotSignalIdupfrontmemoryStore.listMessages({ threadId, resourceId, perPage: false })(full thread scan) withmemoryStore.listMessagesById({ messageIds })(targeted lookup)resourceIdparameter from the function signature since it's no longer needed for the more efficient ID-based lookuplistMessagesByIdis unavailable or tracking yields no IDs, the function returns the local in-memory state without attempting expensive fallbacksCaller Update (
packages/core/src/processors/runner.ts)resourceIdargument from theresolveStateSignalHistorycallTest Updates (
packages/core/src/processors/runner.test.ts)listMessagesandlistMessagesByIdmethodslistMessagesByIdis called (with the specific message IDs) andlistMessagesis not calledImpact
This optimization prevents performance degradation as thread histories grow longer, especially in long-running conversation scenarios where state signals need to be restored. The changes were verified against existing test suites in the core package.