Skip to content

bug(api): compaction-summary banner leaks across sessions — agent-scoped canonical summary shown on a freshly created session #6225

Description

@neo-wanderer

Summary

The dashboard "Session summary (older messages compacted)" banner appears on a freshly created session that was never compacted, showing the summary of a previous, unrelated conversation. The compaction summary is persisted per-agent, but the banner is gated only on "is this the agent's active session?" — never on "was this session compacted?".

Root cause

compacted_summary lives in the canonical_sessions table, keyed by agent_id (one row per agent):

-- crates/librefang-memory/src/migration.rs:498 (migration v5)
CREATE TABLE IF NOT EXISTS canonical_sessions (
    agent_id TEXT PRIMARY KEY,
    messages BLOB NOT NULL,
    compaction_cursor INTEGER NOT NULL DEFAULT 0,
    compacted_summary TEXT,
    updated_at TEXT NOT NULL
);

Compaction writes it via store_llm_summary with ON CONFLICT(agent_id) DO UPDATE
(crates/librefang-memory/src/session.rs:1291, persist at :1757). So the summary is agent-scoped and outlives any individual session.

The session GET handler get_agent_session exposes it whenever the requested session is the agent's active session, with no check that this session's own history was ever compacted:

// crates/librefang-api/src/routes/agents/sessions.rs:268
let compacted_summary: Option<String> = if target_session_id == entry.session_id {
    state.kernel.memory_substrate()
        .canonical_context(agent_id, None, Some(0))   // per-AGENT row
        .ok()
        .and_then(|(summary, _)| summary)
} else {
    None
};

create_agent_session (sessions.rs:599kernel.create_agent_session) makes the new session the active one (updates entry.session_id) but does not clear canonical_sessions.compacted_summary. The new session therefore inherits the prior summary, and the dashboard renders CompactionSummaryBanner on message #1.

Reproduce (synthetic)

  1. Agent A. Hold a conversation in session S1 long enough to trigger history compaction (or force /compact). → canonical_sessions[A].compacted_summary is set from S1.
  2. Create a new session S2 for A (POST /api/agents/A/sessions). S2 becomes active; entry.session_id == S2.
  3. GET /api/agents/A/session (no ?sessionId=). Response carries compacted_summary = the summary of S1, despite S2 having no compacted history (message_count small, no summary block in its own transcript).
  4. Dashboard chat for S2 shows the banner "Session summary (older messages compacted)" containing S1's content, on the very first message.

Confirmed against upstream/main (both cited files match upstream/main with an empty diff). The session's own stored transcript is intact — nothing is lost; the banner is misleading only.

Expected

The banner should reflect this session's compaction state, not the agent's last-ever compaction. Options:

  • Gate the banner on whether the current active session's own messages have been compacted (e.g. a compaction_cursor > 0 / per-session marker), rather than target_session_id == entry.session_id + a non-null agent-scoped summary.
  • Or clear / not surface canonical_sessions.compacted_summary when a brand-new active session is created with no compacted history of its own.

Notes

canonical_sessions is intentionally per-agent (migration v5: "cross-channel persistent memory"), so the storage shape is likely correct — the defect is the read/display gate treating an agent-scoped summary as session-scoped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/apiREST/WS endpoints and dashboardarea/kernelCore kernel (scheduling, RBAC, workflows)has-prA pull request has been linked to this issue

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions