Commit 176d54b
fix: persist and extract from the complete turn, not just the response (#89)
Neo4jMemoryContextProvider.StoreAIContextAsync/PerformStoreAsync and
Neo4jChatHistoryProvider.StoreChatHistoryAsync only ran extraction over
the assistant's response messages -- a turn like "User: I prefer window
seats. / Assistant: Got it." only extracted from "Got it.", so a
user-stated preference was invisible to extraction unless the assistant
happened to repeat it.
Per explicit scoping: extraction must see request + response, but
message persistence must not blindly duplicate what another configured
component (a chat-history provider/store) may already be persisting.
Research confirmed there is no idempotency mechanism anywhere in the
stack today (Neo4jMessageRepository.AddAsync always CREATEs a new node;
MemoryService.AddMessageAsync always generates a fresh id; there is no
caller-supplied-id or MERGE-by-id path) -- so naively concatenating and
persisting everything would risk duplicate :Message nodes whenever a
host also has a chat-history component configured.
Two different fix shapes, split by what each provider already persists:
- Neo4jChatHistoryProvider already persisted both request and response
messages; the fix just also feeds the request messages to extraction
(previously discarded after being persisted). Zero new duplication
risk. Refactored StoreChatHistoryAsync's body into an internal
PerformStoreAsync test seam, mirroring the sibling provider.
- Neo4jMemoryContextProvider never persisted request messages; the fix
deliberately keeps it that way (avoiding the duplication risk) and
instead builds transient, never-persisted Message objects purely for
extraction. The trade-off -- extracted facts/preferences from the
user's request are captured correctly, but their EXTRACTED_FROM
provenance edge and source_message_ids won't resolve to a real node
unless another component also persisted that exact message -- is
documented, not silently accepted.
An adversarial review confirmed the no-duplication guarantee holds (no
path from ExtractAndPersistAsync ever calls AddMessageAsync) and found
one real gap worth fixing: request messages were being fed to extraction
without the same ChatRole.User filter recall already applies, so a
system prompt accumulated in RequestMessages could get minted into
spurious entities/facts/preferences every turn. Fixed in both providers.
It also noted (out of this issue's scope, pre-existing, not
introduced here) that response-message persistence itself has no
idempotency guarantee either -- documented as a known limitation when
combining more than one message-persisting component on the same agent.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>1 parent d284fe2 commit 176d54b
6 files changed
Lines changed: 556 additions & 23 deletions
File tree
- docs
- src/AgentMemory.AgentFramework
- tests
- AgentMemory.Tests.Integration/AgentFramework
- AgentMemory.Tests.Unit/AgentFramework
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
| 51 | + | |
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
| |||
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
61 | | - | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
62 | 83 | | |
63 | 84 | | |
64 | 85 | | |
| |||
Lines changed: 31 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
104 | 104 | | |
105 | 105 | | |
106 | 106 | | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
107 | 121 | | |
108 | 122 | | |
109 | 123 | | |
110 | | - | |
111 | | - | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
112 | 130 | | |
113 | 131 | | |
114 | 132 | | |
115 | 133 | | |
116 | | - | |
| 134 | + | |
117 | 135 | | |
118 | 136 | | |
119 | 137 | | |
120 | 138 | | |
121 | 139 | | |
| 140 | + | |
122 | 141 | | |
123 | 142 | | |
124 | 143 | | |
125 | 144 | | |
126 | | - | |
| 145 | + | |
127 | 146 | | |
128 | 147 | | |
129 | 148 | | |
| |||
136 | 155 | | |
137 | 156 | | |
138 | 157 | | |
139 | | - | |
140 | | - | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
141 | 165 | | |
142 | 166 | | |
143 | 167 | | |
144 | 168 | | |
145 | 169 | | |
146 | 170 | | |
147 | | - | |
| 171 | + | |
148 | 172 | | |
149 | 173 | | |
150 | 174 | | |
| |||
Lines changed: 37 additions & 3 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
| 20 | + | |
19 | 21 | | |
20 | 22 | | |
21 | 23 | | |
| |||
26 | 28 | | |
27 | 29 | | |
28 | 30 | | |
| 31 | + | |
| 32 | + | |
29 | 33 | | |
30 | 34 | | |
31 | 35 | | |
| |||
39 | 43 | | |
40 | 44 | | |
41 | 45 | | |
| 46 | + | |
| 47 | + | |
42 | 48 | | |
43 | 49 | | |
44 | 50 | | |
| |||
169 | 175 | | |
170 | 176 | | |
171 | 177 | | |
| 178 | + | |
172 | 179 | | |
173 | 180 | | |
174 | 181 | | |
175 | 182 | | |
176 | | - | |
| 183 | + | |
177 | 184 | | |
178 | 185 | | |
179 | 186 | | |
180 | 187 | | |
181 | 188 | | |
| 189 | + | |
182 | 190 | | |
183 | 191 | | |
184 | 192 | | |
| |||
188 | 196 | | |
189 | 197 | | |
190 | 198 | | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
191 | 214 | | |
192 | 215 | | |
193 | 216 | | |
| |||
203 | 226 | | |
204 | 227 | | |
205 | 228 | | |
206 | | - | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
207 | 241 | | |
208 | 242 | | |
209 | 243 | | |
210 | 244 | | |
211 | 245 | | |
212 | 246 | | |
213 | | - | |
| 247 | + | |
214 | 248 | | |
215 | 249 | | |
216 | 250 | | |
| |||
Lines changed: 119 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
0 commit comments