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:599 → kernel.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)
- 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.
- Create a new session
S2 for A (POST /api/agents/A/sessions). S2 becomes active; entry.session_id == S2.
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).
- 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.
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_summarylives in thecanonical_sessionstable, keyed byagent_id(one row per agent):Compaction writes it via
store_llm_summarywithON 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_sessionexposes it whenever the requested session is the agent's active session, with no check that this session's own history was ever compacted:create_agent_session(sessions.rs:599→kernel.create_agent_session) makes the new session the active one (updatesentry.session_id) but does not clearcanonical_sessions.compacted_summary. The new session therefore inherits the prior summary, and the dashboard rendersCompactionSummaryBanneron message #1.Reproduce (synthetic)
A. Hold a conversation in sessionS1long enough to trigger history compaction (or force/compact). →canonical_sessions[A].compacted_summaryis set fromS1.S2forA(POST /api/agents/A/sessions).S2becomes active;entry.session_id == S2.GET /api/agents/A/session(no?sessionId=). Response carriescompacted_summary= the summary ofS1, despiteS2having no compacted history (message_count small, no summary block in its own transcript).S2shows the banner "Session summary (older messages compacted)" containingS1's content, on the very first message.Confirmed against
upstream/main(both cited files matchupstream/mainwith 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:
compaction_cursor > 0/ per-session marker), rather thantarget_session_id == entry.session_id+ a non-null agent-scoped summary.canonical_sessions.compacted_summarywhen a brand-new active session is created with no compacted history of its own.Notes
canonical_sessionsis 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.