Spring AI 2.1.0-RC1 adds session memory, tool-call confirmation, and message parts. A hiring lab shows the pause, and the approval row the application still owns.
A technical recruiter types one sentence into a hiring chat: "Please offer this candidate $190,000 a year for the Senior Backend Engineer role." The chatbot does not write that offer. It asks a Java program to do it. The new Spring AI release can stop that request and wait for a person. It still does not know whether $190,000 is a legal salary, or which person is allowed to say yes. That decision stays in ordinary application code. A technical recruiter, a chatbot, and $190,000 The people in this story are fake users in a sample app. Their logins are short on purpose. Their jobs are the ones a real hiring team would have. Alice is the Technical Recruiter. She owns this requisition. She can read the candidate, schedule the tech screen, and ask for an offer. She cannot approve a salary outside the published range. Bob is the Engineering Manager, and the hiring manager for this role. He is not Alice's boss in the story. He is the only person allowed to approve an unusual salary. He can allow the offer. He still cannot open Alice's chat transcript. Carol is a Technical Recruiter at another company. Dave is a Recruiting Coordinator on Alice's team, but he does not own this requisition. For both of them, the candidate looks missing. The open job is Senior Backend Engineer. The application is nicknamed APP-1. The posted range is $120,000 to $160,000 a year. That range is the pay band. $190,000 is above it, the kind of number a hiring manager has to sign. $90,000 is below it, so the system refuses the offer even after a person says yes. A second sample job, at Carol's company, is Customer Support Engineer, posted at $70,000 to $90,000. This story stays on the senior backend role. "The model" is the chatbot, a large language model. In the live demo I use a small OpenAI model through OpenRouter. The model can talk, and it can ask to run a Java method. It cannot update the database by itself. "Extend an offer" means create the salary offer for that candidate. Alice's sentence makes the model ask for a method named extendOffer . Spring AI refuses to run it yet. The screen waits. The candidate is still at the offer step. No salary has been saved. - Alice then clicks Allow. - The server answers 403 Forbidden. - She is logged in, and she can see the waiting request, but she is not the approver. - Bob clicks Allow on that same waiting request. - Now the method runs, and the $190,000 offer is saved. - The candidate is still not hired. Hired happens only after someone records that the candidate accepted. Alice asks for $190,000. The database does not change until Bob says yes. The few words this story uses Word In this article it means Model The chatbot. It proposes an action. It does not update the database. Tool A Java method the chatbot is allowed to ask for, such as "move this candidate to the next step." Pause Spring AI stops before the method runs and waits for a person to allow or deny it. Stage Where the candidate is: applied, recruiter screen, tech screen, onsite, offer, or hired. Tenant Which company the data belongs to. Alice, Bob, and Dave share one. Carol's Customer Support Engineer req is in another, so the Senior Backend Engineer application looks missing to her (HTTP 404). Release candidate A preview build, not the version I would run in production. Spring AI 2.1.0-RC1 shipped on 23 October 2026. The stable line is still 2.0.2. What Spring AI 2.1 added Spring AI is the Java library I use to call a chatbot from a Spring Boot app. Version 2.0 can already run a tool loop: the model asks for a method, Spring runs it, the model answers. Version 2.1.0-RC1 adds three things I could not get from that stable line together. Session memory keeps the conversation as a log you only append to. Older Spring AI chat memory was a flat list of messages for one conversation id. The new log can be shortened for the next prompt without deleting history. The cut happens on a full turn, so the model never sees a tool request without its result. The old ChatMemory API is deprecated and planned for removal in Spring AI 3.0. Tool confirmation is the pause. If any method in the model's reply needs a person, none of the methods in that reply run. My code collects allow or deny, then resumes that same reply. The Spring AI docs say the library stores nothing while it waits. If my server restarts and I did not save the reply, there is nothing to continue. Message parts split one chat message into ordered pieces: text, reasoning, a tool request, a tool result, media, or a piece this version does not recognize yet. Some providers attach private bytes that must be sent back only to that same provider. OpenAI, Anthropic, Google, Bedrock, Ollama, Mistral, and DeepSeek speak this shape in the release candidate. Older methods like getText() still work. Spring AI 2.1 can Your application still must Keep a chat log and show the model a shorter view of it Decide who may read that log Stop before a sensitive method runs Decide whether this person may press Allow Run the same saved reply after Allow Check the pay band and the hiring step Carry provider-specific bytes inside a message Refuse or strip those bytes if you switch chatbot vendors Forget the wait when the process stops Save the waiting request, and know what a crash means How each feature sits in the sample The repo is github.com/josalero/learning-with-me/tree/main/stateful-ai-agent . Spring AI supplies the chat log, the pause, and the message pieces. The classes under dev.mytechprofile.statefulagent supply the hiring rules. If a class name below is not in Spring's packages, it is mine. The release candidate also adds vector search with a precomputed embedding, OpenSearch upsert, and overlap in the token text splitter. This sample does not use those. It only exercises the three features a hiring chat needs: remember the conversation, stop before a write, and keep vendor-specific message pieces intact. New in Spring AI 2.1.0-RC1 What was awkward before Where the sample uses it Session memory Chat memory was one flat list. A long chat either forgot the opening or resent every message. ModelProviderRegistry builds the client. AgentTurnService attaches the session on each message. The JDBC starter creates AI_SESSION , AI_SESSION_INDEX , and AI_SESSION_EVENT . Tool-call confirmation Spring AI 2.0 ran the Java method as soon as the chatbot asked for it. A salary could be saved before a person looked. HiringTools marks every write with requiresConfirmation . AgentTurnService notices the pause. ApprovalWorkflow runs the saved reply only after Allow. Message parts A message was mostly one text blob. Reasoning, tool calls, and private vendor bytes could be dropped or sent to the wrong chatbot. SanitizingSessionDecorator rewrites a foreign part before the next prompt. ConversationController.PartView shows the part type when you read the log. SessionServiceDecorator Declaring your own SessionService bean turns the library's wrappers off. GuardingSessionDecorator hides another user's session. SanitizingSessionDecorator wraps the same auto-configured bean. The sample never declares SessionService itself. ChatMemory deprecated The old flat memory API is scheduled for removal in Spring AI 3.0. The sample does not call ChatMemory . Every turn goes through SessionService . Session memory, in the repo build.gradle pulls in spring-ai-starter-model-session-jdbc . On startup that starter creates the three AI_SESSION* tables. Flyway, in V1__app_schema.sql , creates only the hiring tables: the application, the scorecard, the offer, agent_session , and tool_approval . Each user message in AgentTurnService calls SessionAdvisors.session(sessionId) . That tells Spring AI which log to read and append. The chatbot then sees the shortened view, not a list the application assembled by hand. toolContext is how the Java method later learns the company and the session. The chatbot does not get to invent those. AgentTurnService.java — atta...