Migration Guide¶
Upgrading to 0.6.0¶
Compaction now archives events instead of deleting them, so the full history stays searchable through Recall Storage (issue #21). This introduces four breaking changes.
Breaking: core module artifact renamed from spring-ai-session-management to spring-ai-session¶
The groupId (org.springaicommunity) and Java package
(org.springframework.ai.session) are unchanged — only the Maven artifact/module name
moved:
<!-- Before (0.5.x) -->
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-session-management</artifactId>
</dependency>
<!-- After (0.6.0) -->
<dependency>
<groupId>org.springaicommunity</groupId>
<artifactId>spring-ai-session</artifactId>
</dependency>
No other module (spring-ai-session-jdbc, spring-ai-autoconfigure-session,
spring-ai-autoconfigure-session-jdbc, spring-ai-starter-session-jdbc,
spring-ai-session-bom) changed name. If you depend on the JDBC starter or the BOM rather
than the core artifact directly, no change is needed.
Breaking: SessionRepository.replaceEvents(...) replaced by compactEvents(...)¶
Both replaceEvents overloads are removed. Custom SessionRepository implementations must
implement the new method instead:
// Before (0.5.x)
boolean replaceEvents(String sessionId, List<SessionEvent> events, long expectedVersion);
// After (0.6.0): archive the removed events, swap in the new active window
boolean compactEvents(String sessionId, List<SessionEvent> archivedEvents,
List<SessionEvent> retainedEvents, long expectedVersion);
The implementation must mark archivedEvents as archived (retained, not deleted), set the
active event set to retainedEvents, and preserve any previously-archived events.
Breaking: getEvents(sessionId) / getMessages(sessionId) now include archived events¶
EventFilter.all() (the default for the no-arg accessors) returns the full log,
archived events included. To get the active context window — what you pass to the model —
use the new EventFilter.active():
// Active context window only (archived events excluded)
sessionService.getEvents(sessionId, EventFilter.active());
// Recall Storage: full history, archived included (unchanged)
sessionService.getEvents(sessionId, EventFilter.keywordSearch("topic"));
SessionMemoryAdvisor already forces active() when building the prompt, so no change is
needed there.
Breaking: JDBC schema adds seq and archived columns¶
AI_SESSION_EVENT gains an archived flag and a monotonic seq ordering column (events
are now ordered by seq, not timestamp). Recreate the table or apply, e.g. for H2:
ALTER TABLE AI_SESSION_EVENT ADD COLUMN archived BOOLEAN NOT NULL DEFAULT FALSE;
ALTER TABLE AI_SESSION_EVENT ADD COLUMN seq BIGINT GENERATED BY DEFAULT AS IDENTITY;
See the bundled schema-{h2,postgresql,mysql}.sql for the full DDL per dialect.
Upgrading to 0.3.0¶
Breaking: SessionMemoryAdvisor.Builder.defaultSessionId() removed¶
What changed
defaultSessionId(String) has been removed from SessionMemoryAdvisor.Builder. The
advisor no longer falls back to a shared session — SESSION_ID_CONTEXT_KEY is now
required on every request. Omitting it throws IllegalStateException.
Why
A single default session ID was shared across all requests to the same advisor instance, silently merging conversation history from different users or threads. This is a correctness and security issue in any multi-user deployment.
How to migrate
Before (0.2.x):
SessionMemoryAdvisor advisor = SessionMemoryAdvisor.builder(sessionService)
.defaultSessionId("my-session-id") // ← removed
.build();
// Session ID came from the advisor default — no per-request param needed
client.prompt().user("Hello").call().content();
After (0.3.0):
SessionMemoryAdvisor advisor = SessionMemoryAdvisor.builder(sessionService)
.build();
// Session ID must be passed on every request
client.prompt()
.user("Hello")
.advisors(a -> a.param(SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY, sessionId))
.call()
.content();
In a typical web application, resolve the session ID from the authenticated principal or HTTP session in the controller, then pass it per call:
@PostMapping("/chat")
String chat(@AuthenticationPrincipal UserDetails user, @RequestBody String message) {
String sessionId = resolveSessionId(user.getUsername());
return chatClient.prompt()
.user(message)
.advisors(a -> a.param(SessionMemoryAdvisor.SESSION_ID_CONTEXT_KEY, sessionId))
.call()
.content();
}