⏳ feat: Add Ephemeral Retention Mode for Forced Temporary Chats - #13811
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3f6757b041
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2de5b1f164
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5481e9a498
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
https://github.com/danny-avila/LibreChat/blob/aae969b970f20ecc95d6985a4f6f75b93531d1dc/api/server/routes/convos.js#L222
Pass retention config when pinning conversations
When interface.retentionMode: "ephemeral" is enabled and a user pins or unpins an older non-temporary conversation, this route still calls saveConvo with only userId, unlike the archive/update routes above. Since saveConvo only forces isTemporary/expiredAt when it receives interfaceConfig, the pin write preserves the conversation as permanent and visible, bypassing the forced-temporary policy. Add configMiddleware here and pass req.config.interfaceConfig into saveConvo.
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7b4b36de6f
ℹ️ 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".
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
f63be38 to
596c5b4
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 596c5b4118
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b170f5ff3
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 144ad1e5b3
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f91cbfeb66
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1cb50554df
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3378917bdf
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5dfae72636
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 18119a74ec
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e798bc5919
ℹ️ 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".
e798bc5 to
94642c8
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 94642c8cd2
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f1bfb90c4e
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 920814a3d4
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4aa8ae7ab
ℹ️ 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".
c4aa8ae to
b960221
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1950beceaa
ℹ️ 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".
| if (!savedResponseMessage) { | ||
| throw new Error('Re-pause response progress could not be persisted'); | ||
| } | ||
| await applyForcedRetention( |
There was a problem hiding this comment.
Stamp forced retention before empty re-pauses return
Fresh evidence beyond the earlier resume fix is that this stamp is reached only after persistRePauseProgress returns early when the resumed segment has no content or attachments. If a pre-policy permanent job is resumed after ephemeral is enabled and immediately pauses again without emitting output, only the job metadata becomes temporary; the conversation remains permanently visible and, if the new approval expires or is abandoned, no later finalization converts it. Stamp the parent before the empty-progress return, adding the message ID only when a message was actually saved.
AGENTS.md reference: AGENTS.md:L42-L44
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 2bd0faf: the resume controller now stamps the parent through applyForcedRetention right after persisting the forced job metadata and before client initialization, so a pre-policy chat converts even when the resumed segment re-pauses with no output or is later abandoned; the saved response is still stamped after its save. resume.spec.js adds the empty re-pause case (fails without the fix) and asserts stamp ordering; full spec 153/153.
Adds RetentionMode.EPHEMERAL, which forces every newly saved chat temporary: hidden from history and search, given the temporary-chat expiration, and with the toggle locked on. Forces isTemporary and applies the temporary expiration in the normal conversation and message save methods, routes Assistants/Threads messages through the normal message save, applies the all-data retention policy to file uploads and shared links, and stamps imports, forks, and duplicates. Locks the frontend toggle and keyboard shortcut behind an administrator-enforced badge. The default temporary mode and the existing all mode are unchanged.
Input and output messages were saved with the raw request, so under ephemeral retention they missed the forced-temporary classification the conversation received. Route both through getConversationWriteContext and announce the stored reply as saved.
A resumed response was saved with a fresh forced-temporary window while the re-stamp kept the conversation's earlier deadline, so a chat that paused again could leave its response stored after the conversation expired. Stamp the saved response with the parent's deadline.
stampForcedRetention released a converted chat's bookmark counts but kept its tags, so deleting that chat before TTL removal decremented the same tags again and reduced counts owned by other visible chats. Clear the tags in the conditional conversion update that releases them.
An ephemeral import, fork or duplicate skipped its tag count increment but kept the source tags, so deleting the copy before TTL removal decremented counts owned by other visible chats. Store the same tags the import counts, which is none for a forced-temporary copy.
2bd0faf to
e9f9321
Compare
|
@codex review Please review the current PR head 6604d70. State the exact reviewed commit and ignore findings that apply only to earlier heads. Purpose supplied by the requester: Canary rebase with conflict resolution in resume.js and chatV1/chatV2, plus 3 fixes since reviewed head 1950bec: forced-retention resume submission state, startup-config gate on resume, concurrent stamp deadline reuse |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
…eChat-AI#13811) * feat: add ephemeral retention mode for forced temporary chats Adds RetentionMode.EPHEMERAL, which forces every newly saved chat temporary: hidden from history and search, given the temporary-chat expiration, and with the toggle locked on. Forces isTemporary and applies the temporary expiration in the normal conversation and message save methods, routes Assistants/Threads messages through the normal message save, applies the all-data retention policy to file uploads and shared links, and stamps imports, forks, and duplicates. Locks the frontend toggle and keyboard shortcut behind an administrator-enforced badge. The default temporary mode and the existing all mode are unchanged. * fix: enforce ephemeral retention when resuming paused chats * fix: preserve forced retention across pauses and assistant turns * test: Use Real Module IDs for Installed Package Mocks * test: Wait for Resumed Retention Persistence * test: Control Mongo Index Setup in OpenID Fixture * test: Close Mobile Drawer Before Retention Resume Scenario * fix: Store Responses API Messages Under the Retention Write Context Input and output messages were saved with the raw request, so under ephemeral retention they missed the forced-temporary classification the conversation received. Route both through getConversationWriteContext and announce the stored reply as saved. * fix: Align Resumed Responses With the Parent Retention Deadline A resumed response was saved with a fresh forced-temporary window while the re-stamp kept the conversation's earlier deadline, so a chat that paused again could leave its response stored after the conversation expired. Stamp the saved response with the parent's deadline. * fix: Clear Released Bookmark Tags When Forcing a Chat Temporary stampForcedRetention released a converted chat's bookmark counts but kept its tags, so deleting that chat before TTL removal decremented the same tags again and reduced counts owned by other visible chats. Clear the tags in the conditional conversion update that releases them. * fix: Store No Bookmark Tags on Forced-Temporary Imports An ephemeral import, fork or duplicate skipped its tag count increment but kept the source tags, so deleting the copy before TTL removal decremented counts owned by other visible chats. Store the same tags the import counts, which is none for a forced-temporary copy. * test: Provide the Forced-Temporary Retention Helper to the Header Stacking Mock * test: Reuse One Admin Login per Worker in Ephemeral Retention Scenarios * fix: Keep Legacy Temporary Chats Temporary When Forking Under All-Data Retention * fix: Convert a Resumed Chat Under Ephemeral Retention Before It Can Re-Pause Empty * fix: Treat a Reloaded Pre-Policy Run as Temporary Once Retention Is Forced * fix: Stamp Messages with the Deadline a Concurrent Retention Stamp Stored * fix: Wait for the Startup Config Before Rebuilding a Resumed Submission * test: Wait for the Resumed Response Row in the Ephemeral Resume Scenario * test: Provide the Real Command Executor Stamp to the Callbacks Mock (cherry picked from commit 02a70e9) Original-PR: LibreChat-AI#13811
Summary
Fixes #10052. LibreChat can expire chats a user marks temporary, and
retentionMode: "all"can put a deadline on everything while leaving it visible, but an operator who needs every conversation to behave like a temporary chat has no way to say so: the toggle is the user's, and anything they leave off is stored permanently.This adds
retentionMode: "ephemeral". While it is active every newly saved chat is forced temporary: it is hidden from history and search, it carries the temporary-chat expiration, and the Temporary Chat toggle is locked on with an administrator-enforced label rather than disappearing. The four routes that write a message without going through the retention-aware conversation save (the text edit, the artifact edit, feedback, and the parallel-response branch) re-stamp the conversation holding that message, so a write inside a chat that predates the setting can never leave the message expiring while the chat around it stays permanent and visible.Enabling the mode converts records as they are written; it is not a migration. Chats nobody touches again stay as they are, and the default
temporarymode and the existingallmode are unchanged. Docs: LibreChat-AI/docs#611How it works
The decisions live in
packages/apiand the CJS callers only wire them up.applyForcedRetentionreturns immediately unless the mode isephemeral, re-stamps the message when the caller has not already saved it, and writes the conversation withnoUpsert; neither write upserts, so a row deleted while the edit was in flight stays deleted instead of returning as a skeleton.resolveImportRetentionFieldsandresolveImportTagCountsdecide what an imported, forked or duplicated record is stored with and whether its bookmark tags are counted. A fork or duplicate passes its source's classification, so underalla copy of a chat the user marked temporary stays temporary, as it did before this change.saveConvoandsaveMessagegained one branch each: a caller-supplied deadline is honored and the row is still marked temporary, otherwise the temporary-chat expiration is computed.saveMessagealso gained thenoUpsertoptionsaveConvoalready had.Two places treated all-data retention as the literal
"all"and so skipped the new mode: the resumable agent job's metadata, which now records a pausedephemeralturn as temporary with its original deadline so resume and abort restore it as such, and the client's shared history-cache insert, which now refuses a temporary conversation exactly as the SSE upsert path already did, so a forced-temporary fork or duplicate never appears in the sidebar. Every other literal comparison was inspected and is correct as all-only.The Temporary Chat permission is overlaid where the control renders rather than written into stored role permissions, so enabling and later disabling the mode leaves an operator's configured value untouched.
Type of change
Testing
Tested environments/configuration:
interface.retentionMode: "ephemeral"withtemporaryChatRetention: 1, plus a control run with the setting absentAutomated tests:
e2e/specs/mock/scenarios/ephemeral-retention.spec.ts, each run on desktop light, desktop dark and mobile against a live server and MongoDB: a new chat is saved temporary and stays out of history; the toggle is locked on, stays keyboard reachable and ignores a click; editing a message converts the chat holding it; branching a response converts the chat holding it; a duplicated chat is temporary and adds no bookmark count; and, with the setting absent, a chat is still saved permanently and still appears in history.packages/api/src/conversations/retention.spec.tscovers the three helpers: theephemeralgate, the non-upserting writes, the optional message re-stamp, no write at all undertemporary,allor an absent mode, and the import fields and tag counts including the fallback deadline.packages/data-schemas/src/methods/message.spec.tsprovesnoUpsertagainst a real in-memory MongoDB: a deleted message is not recreated, an existing one is still re-stamped.api/server/routes/__tests__/messages-retention.spec.jsandmessages-update.spec.js; the stored-permission behavior inpackages/api/src/app/permissions.spec.ts; the history-cache guard inclient/src/utils/convos.spec.ts.api259 suites / 5554 tests,client231 suites / 4115 tests for the touched areas, pluspackages/api,packages/data-schemasandpackages/data-provider. ESLint, Prettier, import sorting andtsc --noEmitare clean for every changed file.Screenshots / recordings
Stills are not attached. The only visible change is the Temporary Chat control in the header, and its enforced state is asserted by an automated scenario rather than shown:
the-temporary-toggle-is-locked-on-under-ephemeral-retentionruns on desktop light, desktop dark and mobile and checks that the control is pressed, carries the administrator-enforced label, stays reachable by keyboard, and still reads as pressed after a click. The button keeps its place, size and icon; what changes is the pressed state, the label, the not-allowed cursor, and that the keyboard shortcut no longer toggles it. Say the word if you would rather have a before-and-after pair and I will capture one.Risk / compatibility
No migration and no schema change: the new mode is one more value of an existing enum, and records are converted as they are written. Deployments on
temporaryoralltake no new write and no new read on any path. The conversation re-stamp adds at most onesaveConvoper message edit, artifact edit or branch, and only whileephemeralis active. MongoDB TTL deletion remains asynchronous, and the PR does not promise graph-wide deadline capping or retroactive bookmark-count reconciliation.Checklist