Learn how to build stateful triage sessions, checkpoint agent state to disk, and write pure replayable functions — grounded in real support-automation work.
No Módulo 01, o agente de triagem extraiu um objeto JSON estruturado de um único ticket de suporte. Funcionou — por um turno. No momento em que um cliente enviou um follow-up, o problema surgiu.
A primeira vez que aconteceu no trabalho de automação de suporte, um cliente abriu um ticket sobre
uma exportação de dados com falha. O agente triou corretamente: {"category": "data", "priority": "high"}.
Então o cliente respondeu: "Na verdade, começou na última terça, não quinta." O agente re-triou
do zero. Não tinha memória do primeiro turno. Cada follow-up era tratado como um ticket totalmente
novo, perdendo todo o contexto anterior.
Pense no estado de sessão como um prontuário médico. Sem um, cada visita começa do zero — o médico faz as mesmas perguntas de triagem, perde o histórico e toma decisões que contradizem o plano da semana passada. O prontuário não é apenas útil; é o mecanismo que torna a continuidade possível.
O padrão que quebra: Um agente sem estado chamado em cada turno re-classifica o mesmo ticket repetidamente, perde o contexto do cliente e contradiz suas próprias respostas anteriores. Isso não é um problema de modelo — é um problema de gerenciamento de estado.
Aqui está a versão sem estado de turno único com que o Módulo 01 terminou:
Cada chamada a triage_ticket começa fria. A lista messages é reconstruída do zero. Não há
como o modelo referenciar o que disse um turno atrás.
O que realmente quebra em produção: O cliente re-afirma o contexto que o agente já ouviu. O agente contradiz conselhos anteriores. A conversa entra em loop. A equipe de suporte intervém porque o agente não consegue manter o fio.
A solução é direta: manter um dict de sessão que acumula o histórico da conversa e passá-lo em cada chamada de API. Cada turno adiciona ao histórico; cada chamada recebe o histórico completo.
Duas funções fazem o trabalho. A primeira constrói a lista messages a partir do histórico:
A segunda chama a API com o histórico completo e registra a resposta:
Agora uma sessão é apenas um dict com uma lista history. Iniciar uma sessão se parece com isso:
O modelo vê ambos os turnos. Pode referenciar a primeira classificação ao responder ao follow-up. A conversa se mantém coerente.
Observe a janela de contexto. Uma sessão longa com dezenas de turnos eventualmente transbordará. O Módulo 03 aborda a compressão de contexto em profundidade. Por ora, mantenha as sessões curtas ou resuma turnos antigos antes que se acumulem.
Antes de continuar: se um cliente enviar 30 follow-ups em uma sessão, o que acontece com a
lista messages passada para a API? Como você decidiria quando parar de incluir turnos mais antigos?
Anote sua resposta — o Módulo 03 a testará.
Uma sessão com estado na memória é frágil. Uma reinicialização do servidor, um timeout de processo ou um deploy noturno a apaga. No trabalho de automação de suporte, as sessões podiam durar horas — um cliente poderia abrir um ticket de manhã e responder depois do almoço. Manter tudo na memória não era uma opção.
A solução é o checkpoint: serializar a sessão em disco após cada turno. Se o processo reiniciar, carregue o checkpoint e continue de onde parou.
Quando o cliente responder horas depois — em um novo processo — retome do disco e continue:
A sessão é serializável porque contém apenas valores Python simples: strings, listas, dicts. Sem objetos de cliente, sem handles de arquivo abertos, sem threads. Isso é deliberado — tudo que a sessão precisa para se reconstruir deve caber em JSON.
Faça checkpoint após cada turno, não no final. Se você só fizer checkpoint na conclusão e o processo travar no meio da sessão, o cliente começa de novo. Faça checkpoint cedo e com frequência.
Prompt reflexivo: O que você serializaria em disco versus manteria apenas na memória, e por quê? Considere: o objeto cliente da Anthropic, o objeto de resposta bruto da API, uma conexão de banco de dados, o endereço de email do cliente e a lista de categorias de tickets. Quais desses pertencem ao dict de sessão?
A abordagem de checkpoint funciona porque triage_turn é uma função pura: dado o mesmo dict de
sessão e a mesma mensagem do cliente, sempre produz a mesma chamada de API. Ela não mantém
estado interno. O estado vive no dict de sessão — fora da função, de propriedade explícita do
chamador.
Isso importa mais quando algo dá errado. No trabalho clínico de IA (de-identificado), qualquer decisão incerta de IA tinha que aguardar revisão de um clínico antes de chegar a um paciente. O agente poderia ser interrompido em qualquer ponto e tinha que retomar de exatamente onde parou. Isso só foi possível porque cada função de processamento era pura — o clínico poderia inspecionar o dict de sessão, ver cada decisão anterior e aprovar ou substituir antes que o agente continuasse. Sem estado oculto, sem contadores internos, nada que vivesse apenas dentro de um objeto em execução.
Este é o Fator 5 (Possua Seu Fluxo de Controle) e o Fator 6 (Funções Sem Estado, Estado Explícito)
na prática. O fluxo de controle — quando chamar classify_ticket, quando fazer checkpoint, quando
escalar — vive no chamador. A função em si é simples e reexecutável.
O teste para "sem estado o suficiente": Você pode reexecutar a função recarregando o dict de sessão do disco e chamando-a novamente? Se sim, a função é sem estado o suficiente. Se você precisar reconstruir o estado interno do objeto primeiro, não é.
Apply what you have learned about session state, checkpointing, and stateless functions.
1. A customer sends three follow-up messages to a stateless triage agent. What does the agent see on the third call?
Correct Answer: B
A stateless agent builds a fresh messages list on every call. Without a session dict accumulating history, the third call receives only the third message.
2. In build_messages, why is the agent reply appended with role 'assistant' rather than role 'user'?
Correct Answer: A
The Anthropic messages API requires turns to alternate between 'user' and 'assistant'. Placing the prior agent reply as 'assistant' tells the model this is what it previously said — not another customer message.
3. You checkpoint after every turn. The process crashes after saving turn 3 but before processing turn 4. When you resume, where does the session pick up?
Correct Answer: C
Checkpointing after every turn means the last good save includes turn 3. On resume, the session picks up with history containing turns 1 through 3, and processes turn 4 next.
4. Which of these objects should NOT be included in a serialized session dict?
Correct Answer: C
The Anthropic() client holds network connections and is not JSON-serializable. It must be re-created when the session is resumed. Session dicts should contain only plain values: strings, lists, dicts, numbers.
5. What makes classify_ticket(ticket_text, system_prompt) a 'pure' function in this context?
Correct Answer: C
A pure function's output depends only on its inputs. Because classify_ticket holds no internal counters or state, you can re-call it with the same arguments and get the same API call — which makes it testable and replayable from a saved session.
6. A 50-turn session keeps the full history in the session dict and passes all 50 turns to every API call. What breaks first?
Correct Answer: B
Every API call with all 50 turns eventually exceeds the model's context window. The API returns an error. Module 03 addresses context compression to prevent this.
A sessão de triagem abaixo lida com dois turnos mas não faz checkpoint. Adicione checkpoint e retomada para que a sessão sobreviva a uma reinicialização de processo entre os turnos.
O que você está construindo: envolva triage_turn para que cada chamada salve a sessão em
disco e possa ser retomada carregando do disco em um novo processo Python.
Dica 1: json.dumps(session) serializa um dict simples. Use pathlib.Path(path).write_text()
para salvar. Use json.loads(pathlib.Path(path).read_text()) para restaurar.
Dica 2: Chame checkpoint(session, path) imediatamente após cada chamada de triage_turn —
antes de qualquer outro trabalho. Assim, um crash após o turno mas antes do checkpoint perde apenas
a escrita do checkpoint, não o resultado do turno.
Dica 3: Para testar a retomada, execute o primeiro turno em um script Python, depois inicie
um segundo script que chama resume(path) e executa o segundo turno. Se o histórico mostrar
ambos os turnos, funcionou.
No próximo módulo você adicionará compressão de contexto para que sessões longas não transbordem a janela de contexto — e verá como a estrutura de sessão com estado que você construiu aqui torna isso direto de adicionar.