Vancetope — Session Compact (manual)
“Compact” manually triggers the memory compaction of a Session: older chat turns are folded into an
ARCHIVED_CHATsummary to reduce the context window — the same mechanism the Engine otherwise runs automatically at its threshold. The action is located in the Session menu of the chat list (🗜). It runs on the process Lane (serialized with Turns), is owner-only, and a no-op (“nothing left to compact”) is a normal result, not an error.
1. Initial Situation
MemoryCompactionService.compact(process) exists and is run by the Engines in the middle of a Turn via compactIfNeeded(...). Manually, compaction was previously only accessible via WebSocket (PROCESS_COMPACT → ProcessCompactHandler, Foot /compact) — no REST endpoint. compact(process) is self-sufficient: it resolves its summarizer AiChatConfig from the settings (ai.default.provider/ai.default.model) and does not require ModelInfo (which is only needed by the threshold-controlled compactIfNeeded path).
2. Lane Safety (and a Race Condition Fixed)
Compaction writes a new MemoryDocument and calls chatMessageService.markArchived(...) on the active chat lines. If this runs in parallel with a Turn that has already built its prompt from activeHistory(...), its in-memory prompt/anchor becomes inconsistent. Therefore, manual compaction runs via LaneScheduler.submit(chatProcessId, …): the process Lane is FIFO, compaction runs between Turns, never in parallel.
The existing WS handler ProcessCompactHandler previously called compact(process) synchronously on the WS thread (off-lane) — this race condition has been fixed: the WS/Foot path now also runs via the Lane.
3. “Already Compacted” / No-op
compact() returns a CompactionResult with compacted=false + reason if there’s nothing to do — especially if the active history is ≤ keepRecent (FordProperties, default 10), or everything is PINNED/STRONG/in the recent anchor, or the summarizer returns an empty/erroneous response. After a compaction, the active history shrinks to ~10 → an immediate second call is a clean no-op. The UI displays this as neutral information (“Nothing to compact”), not as an error.
4. “Someone is Currently Working on It”
Signals: SessionConnectionRegistry.connectionCount(sessionId), SessionDocument.boundConnectionId (≠ null = bound), ThinkProcessDocument.getStatus() == RUNNING.
Policy: Warn + Confirm if client is connected. The Web UI checks the bound flag of the Session (already in the list data) and displays a confirmation dialog for a connected Session. Execution still occurs safely on the Lane — the confirmation is purely a user pre-warning, not a technical necessity.
5. API
POST /brain/{tenant}/sessions/{id}/compact (owner-only, Action.EXECUTE):
- Resolves the chat process (
sessionId → chatProcessId → ThinkProcessDocument; if missing, no-op responsereason="no chat process"). - Enqueues
compact(process)on the Lane and waits up toCOMPACT_TIMEOUT_MS(60 s). On timeout:deferred=true— compaction remains enqueued and runs after the current Turn. - Returns
SessionCompactResponse { compacted, messagesCompacted, summaryChars, memoryId, reason, deferred }.
WS path unchanged in contract (PROCESS_COMPACT / ProcessCompactResponse), only internally switched to the Lane.
6. Web UI
🗜 Compact in the SessionActionsMenu (owner-gated, in any Session state). Confirmation dialog for a connected Session. The result appears as a transient info banner in the Session list (PickerView): “Compacted: N messages summarized” / “Nothing to compact” / “Compaction enqueued”. Shared action logic in the Composable useSessionActions (compact()), together with the other Session actions.
7. Limitations (v1)
- No dedicated progress indicator while the summarizer LLM call is running; the UI waits for the HTTP response or receives “enqueued” if a Turn is in progress. Connected clients also see the existing
StatusTag.COMPACTIONprogress frame. - Affects the
chatprocess of the Session. Worker sub-processes do not have their own manual Compact trigger. - No range/topic recompaction via this action (only sliding-window); the range variant remains internal to the engine/Plan Mode.