fix(profile): make accepted preferences durable - #9
Conversation
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a278a04181
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
| const staticFacts = [...profileFacts].sort((left, right) => left.localeCompare(right)); | ||
| const cachedFacts = profileCache.read(userGroupId); | ||
| const mergedFacts = [...new Set([...cachedFacts, ...staticFacts])].slice(0, CONFIG.maxProfileItems); |
There was a problem hiding this comment.
Do not resurrect invalidated preferences from the cache
When Graphiti invalidates or expires a previously cached preference—for example, replacing “concise” with “detailed”—the loop correctly filters the stale remote fact, but this merge unconditionally adds its cached text back. Because cache entries have no invalidation or removal mechanism, obsolete instructions remain in every profile and can conflict with their replacements; reconcile cached entries with Graphiti's temporal status before merging them.
Useful? React with 👍 / 👎.
| await store.write({ ...current, [groupId]: nextEntries }); | ||
| }, | ||
| read(groupId): readonly string[] { | ||
| return (store.read()?.[groupId] ?? []).map((entry) => entry.content); |
There was a problem hiding this comment.
Keep newest cache entries within the profile limit
When a user accumulates more than CONFIG.maxProfileItems preferences (five by default), save appends new entries but this method returns them oldest-first; both clients prepend that array and retain only its first maxProfileItems elements. Consequently, the sixth and every subsequent accepted preference can be saved successfully but never appear in the injected profile until older entries age out of the 20-entry cache, so return the newest entries first or apply the limit from the tail.
Useful? React with 👍 / 👎.
|
|
||
| return { | ||
| async save(groupId, content): Promise<void> { | ||
| const current = store.read() ?? {}; |
There was a problem hiding this comment.
Serialize the whole cache update transaction
When two profile addMemory operations complete concurrently, both save calls can read the same snapshot here before either queued store.write executes. Although AtomicJsonStore serializes the resulting writes, each write contains a separately computed whole-file snapshot, so the later one discards the earlier preference or another group's update; queue the read/merge/write transaction as one operation to preserve concurrent accepted preferences.
Useful? React with 👍 / 👎.
Summary\n- persist accepted user/style preferences in an atomic local profile cache\n- merge cache entries with active Graphiti facts during profile retrieval\n- keep Graphiti as the primary temporal store while surviving asynchronous 202 processing failures\n- release v0.6.1\n\n## Validation\n- 26 tests passing\n- typecheck passing\n- build passing\n- npm release workflow via v0.6.1 tag