Repository navigation
[Feature] Let ACP clients exempt sessions they still reference from retention (age and count limits) #1895
Description
Description
Nanocoder deletes saved sessions that have not been accessed for retentionDays (default 30) and, beyond maxSessions (default 100), the least recently accessed ones. A client that keeps a durable list of conversations, such as Nanocoder Desktop, has no way to tell Nanocoder "I still reference this session, don't delete it". When retention removes a session, the client's thread silently becomes unusable for continuing.
I'd like:
- A way for an ACP client to mark sessions as retained, so retention skips them.
- A way to read the effective retention settings, so a client can tell users what will happen to threads that are not retained.
Tracked from Nano-Collective/nanocoder-desktop#12 (item 9).
Use Case
Nanocoder Desktop keeps one durable thread per conversation and uses the Nanocoder session ID as the join key (its success item SP-06 is "close and relaunch everything, then continue the same chat"). A thread idle for more than 30 days is deleted the next time any Nanocoder process starts, along with its plan, task and walkthrough artifacts. After that the desktop can only show the thread view-only and offer "start a new thread".
The same applies to any ACP client that stores session IDs, such as an editor extension with a thread list.
How the desktop would use it:
- When it creates or keeps a thread, it marks the session retained.
- On "Remove from desktop" it clears the mark, so removed threads don't stay forever.
- On "Delete chat history" it calls
session/delete. - When a thread's session is missing, it reads the retention settings to explain the likely cause ("not opened for N days") and to tell users up front how retention treats old threads.
Today the only way to avoid this is to raise retentionDays and maxSessions in the user's nanocoder-preferences.json. That is the user's own configuration, and a client must not rewrite it. Neither setting can be turned off, since both have a minimum of 1.
Milestone 4 of the desktop plans for this. Until then the fallback is the view-only state with an explanation ("accept and explain"), so this is not blocking.
Proposed Solution
- A retained mark on a session. An extension method, for example
_nanocoder/pinSession {sessionId, pinned: boolean}, stores it in the session file and index. The method name follows the convention settled in [Feature] Rename ACP extension methods to the reserved_prefix and advertise them under_metainagentCapabilities#1893. - Retention skips retained sessions.
cleanupOldSessionsandenforceSessionLimitsleave them alone. Whether they count towardmaxSessionsis an open question below. - Explicit deletion still works.
session/deleteand the CLI's delete remove a retained session, with its artifacts. - Report it.
session/listentries (or their_meta) say whether a session is retained, so a client can find marks it no longer needs. - Expose the settings (read-only). For example in
initialize's_meta, or a separate method: the effectiveretentionDaysandmaxSessions.
Alternatives Considered
- A "touch" method that refreshes
lastAccessedAtwithout replaying the session. It is small and reuses the age logic, but it only delays deletion, and the client has to call it for every thread on a schedule. - Resume every thread periodically.
session/resumealready refresheslastAccessedAt, but it replays history and loads the session into memory. That is too heavy to do for every thread. - Do nothing and document it ("accept and explain"). The client tells users that old threads may disappear, and they raise the limits themselves. This is the desktop's fallback today.
- A client-side registry of "referenced" IDs that Nanocoder checks. More flexible, but stale references would keep sessions forever, and the mark is simpler to reason about.
- Edit the user's retention settings. Ruled out: it conflicts with Nanocoder owning its configuration.
Additional Context
- I have searched existing issues to ensure this is not a duplicate
- This feature aligns with the project's goals (local-first AI assistance)
Current behavior, checked against main at fc01e23:
source/session/session-manager.ts(~L117-135):initialize()runscleanupOldSessions()once per process. ACP callssessionManager.initialize()innewSession,loadSessionandresumeSession(source/acp/acp-agent.ts~L142, L173, L677).source/session/session-manager.ts(~L567-607):cleanupOldSessionsdeletes every session whoselastAccessedAtis older thanretentionDays(default 30, minimum 1), together with its artifacts.source/session/session-manager.ts(~L521-565):enforceSessionLimitsdeletes the least recently accessed sessions beyondmaxSessions(default 100, minimum 1). It is called only fromcreateSession(~L164), which the terminal UI's autosave uses (source/hooks/useSessionAutosave.ts). ACP saves throughsaveSession(source/acp/acp-agent.ts~L1110, L1140). The store is shared between the CLI and ACP processes, so the count cap can still remove ACP sessions.source/session/session-manager.ts(~L418-430):loadSessionrefresheslastAccessedAt.readSession(~L404) does not.source/session/session-manager.ts(~L75-86, L189-200) andsource/session/session-validation.ts: the index entry is built from an explicit list of fields, and validation requires only the existing fields, so a new field is allowed in a session file but must be added to the index projection.docs/features/session-management.md:retentionDaysandmaxSessionshave a minimum of 1, so retention cannot be turned off, and deleting or expiring a session deletes its artifacts too.
Implementation Notes (optional)
Add an optional field to Session and SessionMetadata (see question 1), include it in toSessionMetadata and in the index entry built by saveSession, and filter retained sessions out of both cleanup functions. renameSession already shows the pattern for an optional flag that survives autosave (titleManuallySet).
A limitation to document: protection applies only when the process running cleanup knows about the field. An older Nanocoder sharing the same store could still delete a retained session.
Open questions for maintainers:
- Boolean or holder list? A plain
retained: trueis simplest. AretainedBy: string[]list of client names (fromclientInfo.nameatinitialize) lets the VS Code extension and the desktop share a store without one clearing the other's protection. The session stays protected while the list is non-empty. - Pin flag or touch method? I prefer the flag because it protects against both rules, and the touch method is the fallback if a smaller change is wanted.
- Do retained sessions count toward
maxSessions? If they do, many retained sessions can push unretained recent ones out. - Privacy. Retention exists partly so conversation data doesn't sit on disk forever. Should retained sessions be limited in number, or visible in a CLI list so users can unpin them?
- Who may clear the mark? Only the client that set it, or any client or the CLI?
- Method names and whether to read settings through
initialize_metaor a separate method.
Acceptance criteria:
- A client can mark a session retained and clear the mark.
- Neither the age rule nor the count rule deletes a retained session.
-
session/deletestill deletes a retained session, together with its artifacts. -
session/listshows whether a session is retained. - The mark survives autosave, rename and resume.
- Unretained sessions are cleaned up exactly as before.
- A client can read the effective
retentionDaysandmaxSessions. - Docs describe the mark, the settings, the limits, and the older-process caveat.
Test plan:
- Unit (
session-manager):cleanupOldSessionsandenforceSessionLimitskeep retained sessions and delete the others. - Unit: the mark round-trips through the session file and the index.
- ACP spec: set the mark, age the session past the cutoff, restart, and confirm the session is still listed and resumable.
- ACP spec:
session/deleteremoves a retained session. - ACP spec: the retention settings are reported with their effective values.
- Regression: sessions without the mark are deleted as before.
Out of scope: changing the default limits, a UI for managing retained sessions, and the timeline snapshots and checkpoints, which have their own pruning (7 days, 20 sessions).
Activity