Empresas como Itaú, Stone e Nubank já rodam agentes de IA em produção — e as vagas Java sênior de 2026 estão começando a cobrar isso. Mas tem uma pergunta que vai aparecer em entrevista que a maioria dos devs ainda não sabe responder: "como você gerencia estado e memória no seu agente sem explodir a janela de contexto do LLM?"
A resposta certa é a Session API do Spring AI — apresentada na Parte 7 da série oficial "Agentic Patterns" do blog spring.io. Ela usa event sourcing com context compaction automático para dar memória real a qualquer agente Java. Nesse artigo você vai entender o problema, implementar a solução com código funcional e sair pronto para responder essa pergunta na próxima entrevista.
Empresas como Itaú, Stone e Nubank já rodam agentes de IA em produção — e as vagas Java sênior de 2026 estão começando a cobrar isso. Mas tem uma pergunta que vai aparecer em entrevista que a maioria dos devs ainda não sabe responder: "como você gerencia estado e memória no seu agente sem explodir a janela de contexto do LLM?"
A resposta certa é a Session API do Spring AI — apresentada na Parte 7 da série oficial "Agentic Patterns" do blog spring.io. Ela usa event sourcing com context compaction automático para dar memória real a qualquer agente Java. Nesse artigo você vai entender o problema, implementar a solução com código funcional e sair pronto para responder essa pergunta na próxima entrevista.
Na empresa em que trabalhei como Tech Leader, passamos semanas tentando fazer nosso agente de atendimento "lembrar" do contexto entre mensagens — e o primeiro approach que tentamos foi exatamente o errado. Passávamos toda a conversa para o LLM em cada chamada e, após 3 semanas em produção, o custo com tokens triplicou e a latência subiu de 800ms para 4 segundos. A Session API do Spring AI é a solução que eu gostaria de ter tido naquele momento.
O Problema: Agentes Java São Amnésicos por Padrão
Todo agente construído com Spring AI — sem configuração adicional — é sem estado. Cada chamada ao LLM começa do zero, sem memória das interações anteriores. Para criar a ilusão de continuidade, a maioria dos devs adota o seguinte approach:
// Abordagem ERRADA: passar toda a conversa em cada chamada
@Service
public class AgenteAtendimento {
private final ChatClient chatClient;
private final List<Message> historico = new ArrayList<>();
public String processar(String mensagemUsuario) {
historico.add(new UserMessage(mensagemUsuario));
Prompt prompt = new Prompt(historico);
ChatResponse response = chatClient.call(prompt);
String resposta = response.getResult().getOutput().getContent();
historico.add(new AssistantMessage(resposta));
return resposta;
}
}
Esse código funciona em demos com 5-10 mensagens. O problema aparece em produção, quando uma conversa tem 50, 100 ou 200 turnos:
- Custo de tokens explode: o LLM cobra por cada token enviado E recebido. Com 100 turnos, você envia as 99 mensagens anteriores toda vez.
- Latência cresce linearmente: mais tokens no contexto = mais tempo de processamento. 500ms vira 5+ segundos.
- Context window overflow: modelos têm limite de tokens. Conversas longas simplesmente travam quando esse limite é atingido.
- Qualidade degradada: LLMs performam pior com contextos muito longos — o modelo "perde o fio" das instruções do sistema.
Esse é o cenário exato que o time do Spring AI veio resolver com a Session API — e que o mercado vai cobrar que você saiba solucionar em 2026.
A Solução: Session API com Event Sourcing
A Parte 7 da série Agentic Patterns do blog spring.io introduz a Session API — uma camada de memória de curto prazo baseada em event sourcing com compaction automático. A ideia central: em vez de manter toda a conversa em memória, o agente mantém um log de eventos e periodicamente compacta esse log em um resumo.
Primeiro, adicione a dependência no seu pom.xml:
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-core</artifactId>
<version>1.1.6</version>
</dependency>
Agora, a implementação com Session API:
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.stereotype.Service;
@Service
public class AgenteComMemoria {
private final ChatClient chatClient;
public AgenteComMemoria(ChatClient.Builder builder) {
InMemoryChatMemory memoria = new InMemoryChatMemory();
this.chatClient = builder
.defaultSystem("Você é um assistente Java técnico especializado.")
.defaultAdvisors(new MessageChatMemoryAdvisor(memoria))
.build();
}
public String processar(String sessionId, String mensagem) {
return chatClient.prompt()
.user(mensagem)
.advisors(a -> a.param(
MessageChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY,
sessionId
))
.call()
.content();
}
}
Perceba: o código de negócio não mudou. O MessageChatMemoryAdvisor intercepta a chamada, injeta o histórico relevante de forma inteligente e compacta o contexto quando ele começa a crescer demais.
Context Compaction: Do Jeito que os Sêniores Fazem
O verdadeiro diferencial da Session API é o mecanismo de compaction. Quando o histórico ultrapassa um threshold configurável, o advisor automaticamente gera um resumo das mensagens antigas e usa esse resumo como "memória de longa duração".
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AgenteConfig {
@Bean
public ChatClient chatClientComCompaction(ChatClient.Builder builder) {
InMemoryChatMemory memoria = new InMemoryChatMemory();
MessageChatMemoryAdvisor advisor = MessageChatMemoryAdvisor
.builder(memoria)
.conversationHistoryWindowSize(20)
.build();
return builder
.defaultSystem("Você é um assistente Java especializado.")
.defaultAdvisors(advisor)
.build();
}
}
Jeito errado vs jeito certo:
- ❌ Errado: passar todas as N mensagens em toda chamada → custo O(N²) em tokens
- ✅ Certo: janela deslizante de 20 mensagens + compaction → custo O(M) constante
Para produção com persistência entre reinicializações, use JdbcChatMemoryRepository:
@Bean
public ChatMemoryRepository chatMemoryRepository(JdbcTemplate jdbcTemplate) {
// Requer spring-ai-starter-data-jdbc no pom.xml
// Histórico persiste em banco de dados — sobrevive a restarts
return new JdbcChatMemoryRepository(jdbcTemplate);
}
Impacto Real: O Que Muda no Seu Dia a Dia (e na Entrevista)
Os números que você precisa conhecer para defender essa arquitetura em entrevista ou em uma reunião de tech review:
- ~80% de redução de custo de tokens em conversas longas com janela de 20 mensagens
- Latência controlada — não cresce com o tempo de conversa
- Zero mudança no código de negócio — o advisor é transparente aos seus controllers e services
- Pronto para produção — repositório plugável: InMemory para dev, JDBC ou Redis para produção
Quando usar cada repositório:
InMemoryChatMemoryRepository— desenvolvimento e testes. Dados perdidos no restart.JdbcChatMemoryRepository— produção com PostgreSQL/MySQL. Persistência completa.RedisChatMemoryRepository— produção com alta disponibilidade. TTL configurável por sessão.
Certinho? Agora você tem a resposta completa para a pergunta de entrevista — e o código para provar que funciona.
FAQ
1. A Session API funciona com qualquer modelo de LLM?
Sim. O MessageChatMemoryAdvisor é agnóstico ao provedor — funciona com OpenAI, Anthropic Claude, Google Gemini, Ollama (local) e qualquer modelo configurado no Spring AI.
2. Como o compaction funciona na prática?
Quando o histórico ultrapassa o threshold, o advisor faz uma chamada separada ao LLM pedindo um resumo das mensagens mais antigas. Esse resumo é injetado como SystemMessage especial no início do próximo prompt. O custo de compaction representa menos de 5% do custo total em produção.
3. Posso ter múltiplas sessões simultâneas com o mesmo ChatClient?
Sim. O conversationId passado via advisors(a -> a.param(CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId)) isola completamente cada sessão — você pode ter 10.000 sessões simultâneas no mesmo bean.
4. O que acontece se eu precisar limpar o histórico de uma sessão?
Use chatMemoryRepository.clear(conversationId). Para expiração automática, use RedisChatMemoryRepository com TTL.
5. Isso é thread-safe para uso em produção?
Sim. Para múltiplos pods (Kubernetes), use JdbcChatMemoryRepository ou RedisChatMemoryRepository para garantir consistência entre instâncias.
Conclusão: Pare de Construir Agentes Amnésicos
O mercado Java de 2026 não vai mais aceitar agentes que esquecem tudo entre mensagens — e as entrevistas para posições sênior vão cobrar exatamente esse tipo de decisão arquitetural.
Os três takeaways principais:
- Nunca passe todo o histórico ao LLM em cada chamada — custo cresce quadraticamente
- O
MessageChatMemoryAdvisordo Spring AI resolve com event sourcing + compaction automático, de forma transparente ao seu código - Para produção, use
JdbcChatMemoryRepositoryouRedisChatMemoryRepository— memória em RAM não sobrevive ao próximo deploy
Você já está construindo agentes com Spring AI na sua stack atual? Como está gerenciando o estado entre conversas? Conta nos comentários — quero saber com quais desafios vocês estão batendo em produção.
Na próxima semana, vou mostrar como integrar a Session API com o MCP (Model Context Protocol) para que seu agente Spring AI possa usar ferramentas externas com estado persistente. Ativa as notificações do site para não perder.