feat: auto-delete old conversations, and stop a tool call that repeats #15

Merged
adrianbonpin merged 2 commits from feat/auto-delete-and-tool-call-controls into prod 2026-10-05 09:05:17 +00:00
Owner

Adds opt-in auto-deletion of old conversations and their screenshots, two manual maintenance actions for the same jobs, plus a round limit, a repeat guard and a Force Stop action for tool calls.

Auto-deletion (off by default)

New Delete Old Chats After preference: Disabled / 1 day / 7 days / 30 days / 90 days.

  • Age is counted from updatedAt, so a chat started last month and used this morning is not old.
  • The conversation you are in is never deleted. The sweep runs after startup resolution, so it cannot delete the chat on its way to the screen.
  • Checked on every command open (chat, sessions, setup) and after every answer. A toast only when something actually went.
  • Disabled means nothing is read and nothing is written, which also keeps the common case off the hot path.

Smart screenshot cleanup

  • A deleted conversation takes its screenshots with it, including on a manual single delete and on Delete All. This was previously leaking image files.
  • A snip you never sent belongs to no conversation, so the orphan pass lists the snip directory, compares it against the paths on the session index and collects anything unreferenced older than 24 hours. The wait is what protects a snip you are still deciding what to ask about.
  • A screenshot two conversations share survives either one being deleted.
  • Every ambiguity resolves to deleting nothing: unknown preference value means off, an unverifiable referenced set skips the orphan pass, and paths are checked to be inside the snip directory before anything is removed.

Maintenance actions in the setup screen

A Maintenance section with the same two jobs as explicit, confirmed actions:

Action What it deletes
Clear Out All Chats Every conversation, and every screenshot they captured.
Clear Out Unattached Photos Captures no conversation refers to any more.
  • Both confirmAlert first, and neither shares the settings action panel, so a destructive action is never the default that Return fires on a row the user was only reading.
  • Both always report what they did, including "there was nothing to delete". That is deliberately the opposite of the background sweep, which stays silent unless it deleted something: a button that seems to do nothing reads as a bug.
  • The photo clean-up uses the same rule as the sweep, grace period included, and works whether or not auto-deletion is on. That is the point of it: decide when your history is cleared rather than have Lam decide.
  • When a conversation cannot be read, it says it could not check and deletes nothing rather than risking an image a surviving chat still shows.

Tool calls

  • Tool Call Limit preference replaces the hardcoded 8: 4 / 8 / 16 / Unlimited.
  • The same call twice, a third refuses to run. The agent counts tool name plus sorted arguments per turn. The third identical call is not executed, the loop breaks, and the closing round is told the call was repeating rather than that a budget ran out. This is the guard a round cap cannot be, since one of the settings is Unlimited.
  • Force Stop (was Stop Generating) stays the primary action for the whole turn and aborts mid-tool-call.
  • Live activity is always visible, even with Show Tool Calls off: Reading a page…, then Reading a page · the same call again (2). A failed call or a refused repeat is also listed in the transcript regardless of the toggle.

Notes for review

  • SessionSummary gains attachmentPaths so deciding what is still referenced does not mean reading every transcript after every answer. An index written before the field existed is repaired from its transcripts before anything is deleted: a missing list is not an empty one, and reading it as empty would mark every screenshot in existing history as an orphan.
  • runAgent's new maxToolRounds treats omitted as the default cap and null as unlimited, so a caller that forgets to pass the preference cannot be the one that removes the guard.
  • clearSessions and removeSnipFiles now return counts, which is what the manual actions report. removeSnipFiles counts a file that was already missing, because the question it answers is whether the file is still there.
  • Eight existing agent.test.ts cases drove the cap by repeating an identical call, i.e. they were the runaway loop. Their arguments are now distinct, so they still test the cap, with new cases covering the repeat guard.
  • vitest.config.mts sets fileParallelism: false: the retention and snip tests use the real file system in one fixed snip directory, so two files at once would clear each other's fixtures.

Verification

npm test 437 passed, npm run typecheck, npm run lint:code, vici lint (manifest valid) and vici build all clean.

Adds opt-in auto-deletion of old conversations and their screenshots, two manual maintenance actions for the same jobs, plus a round limit, a repeat guard and a Force Stop action for tool calls. ## Auto-deletion (off by default) New **Delete Old Chats After** preference: Disabled / 1 day / 7 days / 30 days / 90 days. - Age is counted from `updatedAt`, so a chat started last month and used this morning is not old. - The conversation you are in is never deleted. The sweep runs *after* startup resolution, so it cannot delete the chat on its way to the screen. - Checked on every command open (chat, sessions, setup) and after every answer. A toast only when something actually went. - Disabled means nothing is read and nothing is written, which also keeps the common case off the hot path. ### Smart screenshot cleanup - A deleted conversation takes its screenshots with it, including on a manual single delete and on Delete All. This was previously leaking image files. - A snip you never sent belongs to no conversation, so the orphan pass lists the snip directory, compares it against the paths on the session index and collects anything unreferenced older than 24 hours. The wait is what protects a snip you are still deciding what to ask about. - A screenshot two conversations share survives either one being deleted. - Every ambiguity resolves to deleting nothing: unknown preference value means off, an unverifiable referenced set skips the orphan pass, and paths are checked to be inside the snip directory before anything is removed. ## Maintenance actions in the setup screen A Maintenance section with the same two jobs as explicit, confirmed actions: | Action | What it deletes | | --- | --- | | **Clear Out All Chats** | Every conversation, and every screenshot they captured. | | **Clear Out Unattached Photos** | Captures no conversation refers to any more. | - Both `confirmAlert` first, and neither shares the settings action panel, so a destructive action is never the default that Return fires on a row the user was only reading. - Both always report what they did, including "there was nothing to delete". That is deliberately the opposite of the background sweep, which stays silent unless it deleted something: a button that seems to do nothing reads as a bug. - The photo clean-up uses the same rule as the sweep, grace period included, and works whether or not auto-deletion is on. That is the point of it: decide when your history is cleared rather than have Lam decide. - When a conversation cannot be read, it says it could not check and deletes nothing rather than risking an image a surviving chat still shows. ## Tool calls - **Tool Call Limit** preference replaces the hardcoded 8: 4 / 8 / 16 / Unlimited. - **The same call twice, a third refuses to run.** The agent counts tool name plus sorted arguments per turn. The third identical call is not executed, the loop breaks, and the closing round is told the call was repeating rather than that a budget ran out. This is the guard a round cap cannot be, since one of the settings is Unlimited. - **Force Stop** (was Stop Generating) stays the primary action for the whole turn and aborts mid-tool-call. - **Live activity is always visible**, even with Show Tool Calls off: `Reading a page…`, then `Reading a page · the same call again (2)`. A failed call or a refused repeat is also listed in the transcript regardless of the toggle. ## Notes for review - `SessionSummary` gains `attachmentPaths` so deciding what is still referenced does not mean reading every transcript after every answer. An index written before the field existed is repaired from its transcripts before anything is deleted: a missing list is not an empty one, and reading it as empty would mark every screenshot in existing history as an orphan. - `runAgent`'s new `maxToolRounds` treats omitted as the default cap and `null` as unlimited, so a caller that forgets to pass the preference cannot be the one that removes the guard. - `clearSessions` and `removeSnipFiles` now return counts, which is what the manual actions report. `removeSnipFiles` counts a file that was already missing, because the question it answers is whether the file is still there. - Eight existing `agent.test.ts` cases drove the cap by repeating an identical call, i.e. they *were* the runaway loop. Their arguments are now distinct, so they still test the cap, with new cases covering the repeat guard. - `vitest.config.mts` sets `fileParallelism: false`: the retention and snip tests use the real file system in one fixed snip directory, so two files at once would clear each other's fixtures. ## Verification `npm test` 437 passed, `npm run typecheck`, `npm run lint:code`, `vici lint` (manifest valid) and `vici build` all clean.
adrianbonpin added 1 commit 2026-10-05 08:59:48 +00:00
adrianbonpin added 1 commit 2026-10-05 09:03:18 +00:00
adrianbonpin merged commit 05fd9ff13b into prod 2026-10-05 09:05:17 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: adrianbonpin/lam-agent-vicinae#15