Commit 95b14cd
authored
fix(core): preserve tagged system messages when processors return systemMessages (#16950)
## Description
Returned `systemMessages` from processors used to call
`replaceAllSystemMessages()`, which cleared both untagged system
messages and tagged system messages owned by other processors. That
meant a later processor returning a system message array could strip
tags such as `observational-memory`, breaking later refresh/clear
behavior and causing stale or duplicated memory context across steps.
This PR updates `ProcessorRunner` so returned `systemMessages` replace
only untagged system messages. Tagged system messages are preserved for
the processors that own them, and returned entries that correspond to
previously tagged messages are not duplicated as untagged copies.
- Preserves tagged system messages when regular input processors return
`systemMessages`
- Preserves tagged system messages when `processInputStep` processors
return `systemMessages`
- Adds regression coverage for OM-like tagged system messages with
channel context and for runner-level returned `systemMessages` behavior
## Context
#17168 already fixed the channel-specific symptom by switching
`ChatChannelProcessor` from returning `{ systemMessages: [...] }` to
calling `messageList.addSystem(msg, this.id)`. Its PR description
explicitly defers the general fix to this PR:
> "the bug is general to any processor that returns `systemMessages`…
The runner-side fix (general, broader) is being pursued separately in
#16950."
The bug class is still live for any other processor that returns a
`systemMessages` array. The clearest example today is
`BrowserContextProcessor` (`packages/core/src/browser/processor.ts:85`):
```ts
const systemMessages = [...args.systemMessages, { role: 'system' as const, content: lines.join(' ') }];
return { messages: args.messages, systemMessages };
```
An agent using Observational Memory together with a browser tool would
hit the same tag-wipe behavior today. This PR closes that gap once at
the runner level so every current and future processor returning
`systemMessages` is safe.
## Type of Change
- [x] Bug fix (non-breaking change that fixes an issue)
- [ ] New feature (non-breaking change that adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Documentation update
- [ ] Code refactoring
- [ ] Performance improvement
- [ ] Test update
## Checklist
- [ ] I have made corresponding changes to the documentation (if
applicable)
- [x] I have added tests that prove my fix is effective or that my
feature works
- [ ] I have addressed all Coderabbit comments on this PR
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## ELI5
When one processor updates the system messages (notes/instructions), it
was accidentally deleting all system messages from other processors
too—including special labeled ones that shouldn't be touched. This fix
makes sure that only regular, unlabeled messages get replaced, while
labeled messages (like "observational-memory" tags) stay safe and
protected.
---
## Overview
This PR fixes a bug in `ProcessorRunner` where processors returning
`systemMessages` were inadvertently clearing **tagged** system messages
owned by other processors. Previously, `replaceAllSystemMessages()`
would strip all system messages regardless of ownership tags, breaking
functionality that relies on those tags (like observational memory
refresh/clear behavior). The fix preserves tagged system messages while
allowing processors to replace only untagged system messages.
## Changes
### Core Logic Updates (`packages/core/src/processors/runner.ts`)
- Added internal helper functions to manage system message merging:
- `getTaggedSystemMessages`: Extracts system messages that have tags by
comparing all system messages against untagged ones
- `applyReturnedSystemMessages`: Intelligently applies
processor-returned system messages, preserving tagged ones while
replacing untagged ones
- Updated `runInputProcessors`: Processors returning `systemMessages`
now call `applyReturnedSystemMessages()` instead of
`replaceAllSystemMessages()`, preserving tags
- Updated `runProcessInputStep`: Applies the same preservation logic
when step processors return `systemMessages`
### Type Documentation (`packages/core/src/processors/index.ts`)
- Clarified JSDoc comments for `processInput` and `processInputStep`
return types to explicitly document that returned `systemMessages`
replace only untagged messages while preserving tagged system messages
owned by other processors
### Test Coverage
- **`process-input-step.test.ts`** (+86 lines): Added two regression
tests in the `systemMessages modification` suite:
- Verifies that appending untagged system messages preserves existing
tagged messages (e.g., `observational-memory` tags)
- Validates ordering semantics when untagged messages are prepended:
tagged messages remain in original positions while untagged messages
appear in expected sequence
- **`runner.test.ts`** (+11/-6 lines): Updated existing Issue `#9969`
regression test to assert that after a processor returns modified
`systemMessages`, only untagged messages are reflected in the prompt,
while tagged system messages remain unchanged and retain original
content
### Release Documentation
- Added `.changeset` entry documenting the patch-level fix for
`@mastra/core`
## Impact
This change prevents tagged system messages (used by observational
memory and similar features) from being inadvertently dropped when
subsequent processors return updated system messages, ensuring
consistent and predictable behavior across processor chains.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/mastra-ai/mastra/pull/16950?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->1 parent 2c79486 commit 95b14cd
18 files changed
Lines changed: 422 additions & 111 deletions
File tree
- .changeset
- client-sdks/ai-sdk/src
- packages/core/src
- agent/message-list
- tests
- channels
- loop/workflows/agentic-execution
- processors
- processors
- workflows
- evented
- workflows/inngest/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
498 | 498 | | |
499 | 499 | | |
500 | 500 | | |
501 | | - | |
| 501 | + | |
502 | 502 | | |
503 | 503 | | |
504 | 504 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1231 | 1231 | | |
1232 | 1232 | | |
1233 | 1233 | | |
1234 | | - | |
1235 | | - | |
1236 | | - | |
| 1234 | + | |
| 1235 | + | |
| 1236 | + | |
1237 | 1237 | | |
1238 | 1238 | | |
1239 | | - | |
1240 | 1239 | | |
1241 | | - | |
1242 | 1240 | | |
1243 | | - | |
1244 | 1241 | | |
1245 | | - | |
1246 | | - | |
1247 | | - | |
| 1242 | + | |
| 1243 | + | |
1248 | 1244 | | |
1249 | 1245 | | |
1250 | 1246 | | |
| |||
Lines changed: 8 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4281 | 4281 | | |
4282 | 4282 | | |
4283 | 4283 | | |
4284 | | - | |
| 4284 | + | |
4285 | 4285 | | |
4286 | 4286 | | |
4287 | | - | |
| 4287 | + | |
4288 | 4288 | | |
4289 | 4289 | | |
4290 | 4290 | | |
| |||
4293 | 4293 | | |
4294 | 4294 | | |
4295 | 4295 | | |
4296 | | - | |
4297 | | - | |
4298 | | - | |
4299 | | - | |
| 4296 | + | |
| 4297 | + | |
4300 | 4298 | | |
4301 | 4299 | | |
4302 | | - | |
| 4300 | + | |
4303 | 4301 | | |
4304 | 4302 | | |
4305 | 4303 | | |
4306 | 4304 | | |
4307 | 4305 | | |
4308 | 4306 | | |
4309 | 4307 | | |
4310 | | - | |
4311 | | - | |
| 4308 | + | |
| 4309 | + | |
| 4310 | + | |
4312 | 4311 | | |
4313 | 4312 | | |
4314 | 4313 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
56 | | - | |
57 | | - | |
58 | | - | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| |||
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
72 | | - | |
73 | | - | |
| 72 | + | |
74 | 73 | | |
75 | 74 | | |
76 | 75 | | |
| |||
82 | 81 | | |
83 | 82 | | |
84 | 83 | | |
85 | | - | |
| 84 | + | |
86 | 85 | | |
87 | 86 | | |
88 | 87 | | |
89 | | - | |
| 88 | + | |
90 | 89 | | |
91 | 90 | | |
92 | 91 | | |
| |||
Lines changed: 5 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
761 | 761 | | |
762 | 762 | | |
763 | 763 | | |
764 | | - | |
| 764 | + | |
765 | 765 | | |
766 | 766 | | |
767 | 767 | | |
| |||
823 | 823 | | |
824 | 824 | | |
825 | 825 | | |
826 | | - | |
827 | | - | |
828 | | - | |
829 | | - | |
830 | | - | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
831 | 830 | | |
832 | 831 | | |
833 | 832 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
96 | | - | |
| 96 | + | |
| 97 | + | |
97 | 98 | | |
98 | 99 | | |
99 | 100 | | |
| |||
116 | 117 | | |
117 | 118 | | |
118 | 119 | | |
119 | | - | |
| 120 | + | |
120 | 121 | | |
121 | 122 | | |
122 | 123 | | |
| |||
165 | 166 | | |
166 | 167 | | |
167 | 168 | | |
168 | | - | |
| 169 | + | |
169 | 170 | | |
170 | 171 | | |
171 | 172 | | |
| |||
220 | 221 | | |
221 | 222 | | |
222 | 223 | | |
223 | | - | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
224 | 228 | | |
225 | 229 | | |
226 | 230 | | |
| |||
405 | 409 | | |
406 | 410 | | |
407 | 411 | | |
408 | | - | |
| 412 | + | |
409 | 413 | | |
410 | 414 | | |
411 | 415 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1741 | 1741 | | |
1742 | 1742 | | |
1743 | 1743 | | |
| 1744 | + | |
| 1745 | + | |
| 1746 | + | |
| 1747 | + | |
| 1748 | + | |
| 1749 | + | |
| 1750 | + | |
| 1751 | + | |
| 1752 | + | |
| 1753 | + | |
| 1754 | + | |
| 1755 | + | |
| 1756 | + | |
| 1757 | + | |
| 1758 | + | |
| 1759 | + | |
| 1760 | + | |
| 1761 | + | |
| 1762 | + | |
| 1763 | + | |
| 1764 | + | |
| 1765 | + | |
| 1766 | + | |
| 1767 | + | |
| 1768 | + | |
| 1769 | + | |
| 1770 | + | |
| 1771 | + | |
| 1772 | + | |
| 1773 | + | |
| 1774 | + | |
| 1775 | + | |
| 1776 | + | |
| 1777 | + | |
| 1778 | + | |
| 1779 | + | |
| 1780 | + | |
| 1781 | + | |
| 1782 | + | |
| 1783 | + | |
| 1784 | + | |
| 1785 | + | |
| 1786 | + | |
| 1787 | + | |
| 1788 | + | |
| 1789 | + | |
| 1790 | + | |
| 1791 | + | |
| 1792 | + | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
| 1796 | + | |
| 1797 | + | |
| 1798 | + | |
| 1799 | + | |
| 1800 | + | |
| 1801 | + | |
| 1802 | + | |
| 1803 | + | |
| 1804 | + | |
| 1805 | + | |
| 1806 | + | |
| 1807 | + | |
| 1808 | + | |
| 1809 | + | |
| 1810 | + | |
| 1811 | + | |
| 1812 | + | |
| 1813 | + | |
| 1814 | + | |
| 1815 | + | |
| 1816 | + | |
| 1817 | + | |
| 1818 | + | |
| 1819 | + | |
| 1820 | + | |
| 1821 | + | |
| 1822 | + | |
| 1823 | + | |
| 1824 | + | |
| 1825 | + | |
| 1826 | + | |
| 1827 | + | |
| 1828 | + | |
| 1829 | + | |
| 1830 | + | |
| 1831 | + | |
| 1832 | + | |
| 1833 | + | |
| 1834 | + | |
| 1835 | + | |
| 1836 | + | |
| 1837 | + | |
| 1838 | + | |
| 1839 | + | |
| 1840 | + | |
| 1841 | + | |
| 1842 | + | |
| 1843 | + | |
| 1844 | + | |
| 1845 | + | |
| 1846 | + | |
| 1847 | + | |
| 1848 | + | |
| 1849 | + | |
| 1850 | + | |
| 1851 | + | |
| 1852 | + | |
| 1853 | + | |
| 1854 | + | |
| 1855 | + | |
| 1856 | + | |
| 1857 | + | |
| 1858 | + | |
| 1859 | + | |
| 1860 | + | |
| 1861 | + | |
| 1862 | + | |
| 1863 | + | |
| 1864 | + | |
| 1865 | + | |
1744 | 1866 | | |
1745 | 1867 | | |
1746 | 1868 | | |
| |||
0 commit comments