Skip to content

Commit 7c0d868

Browse files
TylerBarnesMastra Code (openai/gpt-5.5)
andauthored
fix(core): restore state signals without thread scans (#18182)
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>
1 parent f79daa2 commit 7c0d868

4 files changed

Lines changed: 20 additions & 13 deletions

File tree

.changeset/cuddly-ears-grab.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@mastra/core': patch
3+
---
4+
5+
Improved state signal restoration so long threads can resume without scanning every message.

packages/core/src/agent/state-signals.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,14 +150,12 @@ export async function resolveStateSignalHistory({
150150
messageList,
151151
memory,
152152
threadId,
153-
resourceId,
154153
stateId,
155154
tracking,
156155
}: {
157156
messageList: MessageList;
158157
memory: MastraMemory;
159158
threadId: string;
160-
resourceId: string;
161159
stateId: string;
162160
tracking?: StateSignalTracking;
163161
}): Promise<StateSignalHistory> {
@@ -172,12 +170,17 @@ export async function resolveStateSignalHistory({
172170
const memoryStore = await memory.storage.getStore('memory');
173171
if (!memoryStore) return { ...localHistory, contextWindow };
174172

175-
const storedMessages = await memoryStore.listMessages({
176-
threadId,
177-
resourceId,
178-
perPage: false,
179-
orderBy: { field: 'createdAt', direction: 'ASC' },
180-
});
173+
const trackedSignalIds = new Set<string>();
174+
for (const activeCopy of tracking.activeCopies ?? []) {
175+
trackedSignalIds.add(activeCopy.id);
176+
}
177+
trackedSignalIds.add(tracking.lastSnapshotSignalId);
178+
179+
if (trackedSignalIds.size === 0 || typeof memoryStore.listMessagesById !== 'function') {
180+
return { ...localHistory, contextWindow };
181+
}
182+
183+
const storedMessages = await memoryStore.listMessagesById({ messageIds: [...trackedSignalIds] });
181184
const storedStateSignals = dbMessagesToStateSignals(storedMessages.messages, stateId, threadId);
182185
const resolvedStateSignals = mergeStateSignals(storedStateSignals, localStateSignals);
183186

packages/core/src/processors/runner.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3270,10 +3270,11 @@ describe('ProcessorRunner', () => {
32703270
perPage: false,
32713271
hasMore: false,
32723272
}));
3273+
const listMessagesById = vi.fn(async () => ({ messages: storedMessages.get.all.db() }));
32733274
const memory = {
32743275
getThreadById: vi.fn(async () => thread),
32753276
saveThread: vi.fn(async ({ thread }) => thread),
3276-
storage: { getStore: vi.fn(async () => ({ listMessages })) },
3277+
storage: { getStore: vi.fn(async () => ({ listMessages, listMessagesById })) },
32773278
};
32783279
const computeStateSignal = vi.fn((_args: any) => undefined);
32793280

@@ -3295,9 +3296,8 @@ describe('ProcessorRunner', () => {
32953296
memory: memory as any,
32963297
});
32973298

3298-
expect(listMessages).toHaveBeenCalledWith(
3299-
expect.objectContaining({ threadId: 'thread-1', resourceId: 'resource-1', perPage: false }),
3300-
);
3299+
expect(listMessages).not.toHaveBeenCalled();
3300+
expect(listMessagesById).toHaveBeenCalledWith({ messageIds: ['stored-snapshot'] });
33013301
const computeArgs = computeStateSignal.mock.calls[0]?.[0];
33023302
expect(computeArgs.activeStateSignals.map(signal => signal.id)).toEqual(
33033303
expect.arrayContaining([snapshot.id, delta.id, localDelta.id]),

packages/core/src/processors/runner.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,6 @@ export class ProcessorRunner {
374374
messageList,
375375
memory: resolvedMemory,
376376
threadId: resolvedThreadId,
377-
resourceId: resolvedResourceId,
378377
stateId,
379378
tracking,
380379
});

0 commit comments

Comments
 (0)