Pular para o conteúdo
astorlm
Idioma: Português
← Mapa

Nível 12

Sessões

O histórico de um agente vive na memória, e a memória acaba com o programa. Uma sessão vai anotando tudo, então a conversa sobrevive ao processo: você pode retomá-la amanhã, ou bifurcá-la.
1/34 Dobras do bandoneón:
  • user
  • assistant
  • tool_result
O herói precisa de um jeito de entrar na Caverna dos Ecos. Astor trabalha com um diário de aventura: um arquivo de sessão em disco onde cada mensagem é anotada assim que existe.

EventBus

O problema

O histórico do nível 2 é uma lista na memória. Quando o programa para (um deploy, uma queda, alguém que fecha a aba e volta amanhã), a lista some e o agente começa do zero, perguntando de novo tudo o que já sabia.

Salvar só no final também não basta: uma execução que falha no meio é justamente a que você ia querer olhar, ou retomar de onde parou.

A solução

Dê a cada conversa um id e um lugar no disco, e trate o histórico como esse arquivo, não como uma variável. Três movimentos dão conta:

  • Salvar no caminho

    Escreva cada mensagem no arquivo da sessão assim que ela existe, uma linha por mensagem. Se algo cair no meio, nada se perde.

  • Retomar

    Um agente novo com o mesmo id de sessão lê o arquivo de volta para o histórico antes da próxima requisição.

  • Bifurcar

    Copie o histórico para uma sessão nova para testar outra coisa. A original fica como estava.

JSONL, uma mensagem JSON por linha, é o formato de costume: acrescentar uma mensagem é acrescentar uma linha, e uma última linha escrita pela metade é fácil de detectar. O Claude Code e o Codex guardam as próprias sessões do mesmo jeito, e é assim que as retomam.

O elenco

O mesmo elenco de sempre, numa aventura.

O diário de aventura o arquivo de sessão
Um slot por id de sessão, uma marca por mensagem, escrita assim que ela existe.
O bandoneón o histórico
O que o agente tem na memória. Esvazia quando o jogo é desligado; o diário, não.
CONTINUE retomar
Um agente novo com o mesmo id, com o diário lido de volta para o histórico.
COPY A QUEST bifurcar
Um segundo slot que começa como cópia do primeiro. Depois, cada um cresce por conta própria.
Astor o loop
Roda o loop como sempre, e cada mensagem vai para o diário.
A vila as ferramentas
O ancião, o guarda e a loja: talk_to e buy.

O código

Com astorlm: passe um sessionId e um FileSessionManager. O agente carrega a sessão quando é criado e a salva depois de cada mensagem; um id novo começa uma vazia. fork() devolve um agente novo sobre uma cópia da sessão.

Do zero: o loop do nível 2, com o histórico lido primeiro de um arquivo JSONL e cada mensagem nova acrescentada no final. Bifurcar é copiar o arquivo.

import { FileSessionManager, OpenAIProvider, createLocalAgent, tool } from 'astorlm'
import { z } from 'zod'

const talkTo = tool({
  name: 'talk_to',
  description: 'Talk to someone in town. Returns what they say.',
  schema: z.object({ npc: z.string() }),
  execute: async ({ npc }) => town.talk(npc), // your code
})

const buy = tool({
  name: 'buy',
  description: 'Buy an item at the shop. Returns the price and the gold left.',
  schema: z.object({ item: z.string() }),
  execute: async ({ item }) => shop.buy(item), // your code
})

// The adventure log: quest-1.jsonl (one message per line) and quest-1.meta.json, in this folder.
const sessionManager = new FileSessionManager({ dir: './sessions' })

const quest = (sessionId: string) =>
  createLocalAgent({
    // Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
    provider: new OpenAIProvider({
      baseURL: 'http://localhost:11434/v1', // e.g. Ollama's default address
      model: 'your-model', // e.g. 'llama3.1', 'gpt-4o-mini'
      apiKey: 'YOUR_API_KEY', // local servers usually ignore it
    }),
    tools: [talkTo, buy],
    sessionId, // a new id starts an empty log; a known one loads it
    sessionManager,
  })

// Day 1. Every message is saved as soon as it's added, so a crash loses nothing.
const day1 = await quest('quest-1')
await day1.run('I need to get into the Cave of Echoes. Find out what it takes, and get what you can.')

// …the process ends. Day 2: a new agent on the same id reads the log back before answering.
const day2 = await quest('quest-1')
console.log(day2.getMessages().length) // 6: yesterday is in the history
await day2.run('I got the Silver Key from the mayor’s daughter. What now?')

// A fork: a new session with a copy of every message. quest-1 stays as it was.
const west = await day2.fork({ newSessionId: 'quest-2' })
await west.run('Suppose the key doesn’t fit. Is there another way in?')

O que observar

  • Uma sessão retomada manda tudo de novo. O dia 30 de uma conversa longa carrega os 29 anteriores. Sessões guardam o histórico; compactá-lo (nível 9) é o que evita que ele ultrapasse a janela de contexto.
  • Bifurcar copia o histórico, não o mundo. A tocha foi comprada uma vez, e está nos dois slots. E-mails enviados, pedidos feitos e arquivos escritos pelas ferramentas aconteceram de verdade, em todos os ramos.
  • Confira a última linha ao retomar. Se o programa morreu entre uma chamada de ferramenta e o seu resultado, o diário termina com uma chamada que ninguém respondeu, e a maioria dos provedores a rejeita. Descarte-a, ou acrescente um resultado de erro para ela.
  • Um só escritor por sessão. Dois processos acrescentando linhas ao mesmo arquivo ao mesmo tempo as misturam. Dê a cada execução a sua sessão, ou uma trava.
  • Arquivos de sessão são dados pessoais. Guardam tudo o que o usuário disse e cada resultado de ferramenta. Decida onde ficam, quem pode lê-los e quando são apagados.