Migration Guide¶
Upgrading to 0.8.0¶
No breaking API or schema changes. Two things to be aware of:
Behavior change: SessionMemoryAdvisor no longer duplicates history inside a tool-calling loop¶
At default orders, ToolCallingAdvisor (order HIGHEST_PRECEDENCE + 300) wraps
SessionMemoryAdvisor (order HIGHEST_PRECEDENCE + 1000), so the advisor's
before()/after() run once per round of the tool-call loop. Before 0.8.0, from round 2
onward before() prepended the session history again even though the prompt already
carried this turn's messages, sending duplicate messages to the model.
before() now detects that the retrieved history is already a contiguous run in the
prompt and skips prepending it (the same guard as Spring AI's
MessageChatMemoryAdvisor, spring-ai GH-6211). Persisted events were never duplicated;
only the prompt sent to the model was affected.
Action needed: none. If you worked around the duplication — e.g. by calling
ToolCallingAdvisor's .disableInternalConversationHistory() or giving
SessionMemoryAdvisor a custom order so it wraps the loop — those setups still work, but
the workaround is no longer required at default orders. See the "Default advisor order"
note in the ChatClient reference doc for details.
Dependency baseline: Spring AI 2.0.1, Spring Boot 4.1.1¶
0.8.0 builds against Spring AI 2.0.1 and Spring Boot 4.1.1 (previously 2.0.0 / 4.0.7). Align your application's Spring AI BOM and Spring Boot versions accordingly.
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();
}