Por que a memória Honcho do Hermes cai em outro projeto


Você mantém três repositórios: um monorepo de backend, um frontend e uma pasta de scripts de operação que não abre há dois anos. Semana passada você reescreveu o fluxo de estorno no monorepo. Hoje abre o Hermes dentro da pasta de scripts, pergunta como ficou aquele refactor — e recebe uma resposta detalhada e confiante sobre um projeto que nunca foi assunto naquele diretório. O inverso assusta mais: um repositório recém-clonado, sem uma linha escrita, e a primeira conversa já “lembra” do seu diretório home inteiro. Você roda o Hermes por projeto exatamente para que cada repositório tenha a própria memória. O Honcho estava arquivando em outro lugar.

Os prompts estavam certos. O destino não estava. O PR #112602 (mesclado em 2026-09-16) corrige os dois caminhos de código que roteavam as sessões para o lugar errado. A seguir: o que quebrou, como configurar a sessionStrategy para ela realmente valer e como descobrir em qual sessão sua memória caiu.

Dois termos, uma frase cada

Honcho é um dos memory providers do Hermes: ele modela você como um perfil de usuário que persiste entre sessões — user representation, peer card — em vez de tratar cada conversa como uma ilha.

sessionStrategy define como as sessões do Honcho (pense em “baldes de memória”) se mapeiam no seu trabalho. O padrão é per-directory, com quatro níveis:

sessionStrategy O que faz
per-session Cada execução do hermes ganha uma sessão nova; a memória volta pelas ferramentas. A documentação recomenda isso para quem está começando
per-directory Uma sessão por diretório de trabalho; o contexto se acumula entre execuções (padrão)
per-repo Uma sessão por repositório git
global Uma única sessão para todos os diretórios

Um detalhe que vale manter separado: no modo hybrid / context, o Honcho injeta duas camadas de contexto no system prompt a cada turno — uma camada base (resumo da sessão, user representation, peer card, autorrepresentação da IA) e um suplemento dialético gerado por LLM. contextCadence tem padrão 1 e dialecticCadence tem padrão 2 (a documentação sugere 1–5); o resultado concatenado é truncado pelo orçamento de contextTokens. Memória de sessão e perfil de usuário são coisas diferentes — misturar as duas torna esses bugs de roteamento muito mais difíceis de diagnosticar.

Se você prefere manter a memória no próprio disco, o caminho com LanceDB está em como o Hermes implementa memória semântica: mesmo problema, outro armazenamento.

O sintoma: três repositórios, uma memória só

Três variações da mesma queixa, todas com a mesma configuração:

  • Você pergunta sobre o repositório B estando no A, e o Honcho traz os detalhes de B.
  • Um repositório novo já chega carregado de preferências e conclusões de projetos antigos.
  • A configuração diz per-repo, mas o agente se comporta como global.

As três significam a mesma coisa: todos os projetos caem na mesma sessão, e essa sessão não tem o nome de nenhum dos seus repositórios.

Causa um: um título automático renomeia a sessão

O Hermes escreve títulos de conversa para você. A proveniência deles é derived ou llm (o mesmo vale para os títulos de lineage dos branches do Desktop). Eles existem para você reconhecer conversas numa lista: são metadados de exibição.

O código antigo discordava. Bastava a sessão ter um título para ele entrar na derivação do nome da sessão. Então um título automático como “Refactor the billing module” arrancava a conversa da sessão do repositório e a renomeava para Refactor-the-billing-module. Sua sessionStrategy continuava configurada; só não era consultada.

Causa dois: a resolução usava o diretório de inicialização, não o workspace

Essa é mais silenciosa. A resolução da session key rodava contra os.getcwd(), o diretório de inicialização do processo backend. No Desktop ou num messaging gateway, esse processo costuma iniciar em $HOME; então, não importava de qual repositório você escrevesse, o workspace que o Honcho enxergava era o seu diretório home. Todos os projetos colapsavam num único balde chamado home.

É por isso que tantas configurações pareciam global enquanto o arquivo de configuração dizia outra coisa.

O que o PR #112602 mudou

A correção é deliberadamente estreita: uma mudança por bug, ambas no caminho de resolução.

  • plugins/memory/honcho/client.py::_resolve_session_name_base — um título com proveniência derived ou llm é metadado de exibição e pula o branch do título por completo. Um /title explícito (proveniência user) continua vencendo. Um título sem origem registrada mantém o comportamento antigo.
  • plugins/memory/honcho/__init__.py::_resolve_session_key — resolve contra kwargs["cwd"] ou resolve_agent_cwd() (pinned session cwd → TERMINAL_CWD → diretório de inicialização): o mesmo contrato que o system prompt e a descoberta de arquivos de contexto já usavam, em vez de os.getcwd().
  • hermes_state_common.py — as constantes TITLE_SOURCE_* ficam no módulo irmão cycle-safe, então SessionDB e o plugin leem o mesmo vocabulário.
  • agent/agent_init.py — AIAgent(cwd=...) grava agent.session_cwd, e _memory_provider_init_kwargs passa session_title_source e cwd para o provider.
  • tui_gateway/* — os caminhos de eager-resume, branch, compute-host e deferred build passam cwd_override (o registro de sessão deles ainda não está em _sessions, então _set_session_context apagaria o cwd). Um workspace que muda durante um deferred build também chega ao agente já construído.

Vale memorizar a precedência. Messaging gateways mantêm a session key estável por chat, independentemente de estratégia ou título. Para as outras sessões: identidade per-session → mapeamento manual de diretório → título explícito → a estratégia configurada, e vence o primeiro que casar.

A mesclagem foi validada de ponta a ponta com imports reais (um SessionDB real, um HonchoMemoryProvider real, cwd do processo apontando para um $HOME falso, cwd da sessão amarrado a um repositório git chamado myapp, estratégia per-repo):

Caso Antes Depois
sem título home myapp
título LLM “Refactor the billing module” Refactor-the-billing-module myapp
/title My Explicit Name explícito My-Explicit-Name My-Explicit-Name

A terceira linha é intencional: um título escrito por você continua sendo uma sobreposição.

Configurar a sessionStrategy corretamente

O assistente continua sendo o caminho com menos erro — ele pergunta sobre o gateway e escreve as chaves por você:

hermes memory setup    # escolha "honcho" na lista de providers

Configurando à mão, o config.yaml precisa no mínimo disto:

# ~/.hermes/config.yaml
memory:
  provider: honcho
echo 'HONCHO_API_KEY=***' >> ~/.hermes/.env

A API key sai em honcho.dev. Configurações como sessionStrategy ficam no bloco de settings do provider Honcho — o mesmo que o hermes memory setup escreve. Se preferir não editar à mão, aperte [e] no prompt do assistente e defina as chaves no editor dele.

Como escolher sua estratégia:

  • per-repo — um monorepo mais vários repositórios independentes, cada um com memória própria. É o cenário lá de cima.
  • per-directory — você trabalha num diretório a longo prazo e não precisa isolar subpastas. Não é à toa que é o padrão.
  • per-session — cada execução começa limpa e a memória é buscada por ferramentas. É também o melhor ajuste para depurar, porque separa com mais nitidez memória de sessão e perfil de usuário.
  • global — só quando você realmente quer um perfil compartilhado entre todos os projetos.

Os demais ajustes (em boa parte ligados a gateway, listados juntos para referência):

Ajuste Padrão O que faz
sessionStrategy per-directory Os quatro níveis acima; estratégias por diretório e mapeamentos manuais de sessions usam o diretório de trabalho lógico da sessão, não o diretório de inicialização do backend
a2aSessions true DMs de outros bots são gravadas na própria sessão por bot remetente, nunca na do humano; false ignora totalmente turnos escritos por bots
pinUserPeer false Só gateway. Quando true, todo usuário da plataforma colapsa em peerName
userPeerAliases {} Só gateway. Mapa de IDs de runtime para peers ({"7654321": "alice"}), muitos para um
runtimePeerPrefix "" Só gateway. Dá namespace a IDs de runtime desconhecidos (telegram_7654321) quando nenhum alias casa

A resolução de peers no gateway testa de cima para baixo, e o primeiro que casar vence: pinUserPeer → userPeerAliases[id] → runtimePeerPrefix + id → ID de runtime cru → peerName → fallback da session key. Se fatos de outra pessoa estão grudando em você no Telegram ou no Discord, é essa tabela que você precisa ler. Um aviso da documentação: virar pinUserPeer de true para false não migra dados — o histórico sob peerName fica onde está. Os detalhes de comando estão na referência do hermes memory.

Como saber em qual sessão a memória caiu

Depois da correção o nome da sessão é derivável, então você pode raciocinar de trás para frente:

  1. Olhe o formato do nome. Em estratégias por diretório ou repositório ele é o nome do diretório ou do repositório (na validação, myapp); sem informação de workspace, é o diretório de inicialização (home); um título explícito vira a forma com hífens (My-Explicit-Name). Um nome de sessão com formato de título é a assinatura do bug antigo: se você vê Refactor-the-billing-module, o título tomou conta da sessão.
  2. Faça um teste cruzado. No repositório A, peça ao agente para registrar uma conclusão que só pertence a A (uma conclusão escrita por honcho_conclude, ou o user card por trás do honcho_profile). Depois vá para o repositório B e pergunte sobre ela. Com per-repo, B deve ficar em branco. Se ele responder, os dois repositórios falam com a mesma sessão.
  3. Confirme o próprio diretório de trabalho. Peça ao agente o diretório de trabalho da execução atual e compare com o repositório onde você acha que está. A cadeia é contexto de sessão → terminal.cwd com escopo → diretório de inicialização; se algum elo devolver $HOME, sua memória vai para o balde home.
  4. Use per-session como controle. Troque temporariamente para ele: toda sessão nova deve começar vazia. Se fatos ainda aparecerem, eles vêm do perfil de usuário (user representation / peer card), não da memória de sessão — isso é intencional, não contaminação cruzada.

Sessões criadas antes da correção

A documentação é direta: sessões criadas antes de a proveniência do título ser registrada mantêm o comportamento antigo. Um título automático antigo não se distingue de um título de usuário antigo, então um título sem origem é tratado como sobreposição explícita. Atualizar não devolve essas sessões para a sessão do repositório.

Existe um segundo limite: esta mudança não migra mensagens já gravadas em sessões roteadas errado. O histórico que caiu no balde errado fica lá. Na prática: abra uma sessão nova no diretório correto — ela resolve para o nome de sessão certo — e continue usando /title explícito quando quiser uma sobreposição deliberada, porque esse comportamento não foi mexido.

Se você gosta de transformar conclusões em notas duráveis, fazer o agente escrever no seu vault do Obsidian estando no diretório de projeto certo contorna o problema inteiro: o sistema de arquivos já é particionado por projeto. E se você agenda trabalho, a mesma cadeia de memória aparece em cron e continuidade de memória.

Resumo

  • A memória do Honcho tem um destino definido, e esse destino é decidido pela sessionStrategy mais o diretório de trabalho da sessão.
  • Dois bugs quebraram uma metade cada: títulos automáticos (derived / llm) não anulam mais a estratégia, e a resolução agora segue o workspace da conversa em vez do diretório de inicialização do processo. Um /title explícito continua sendo sua sobreposição manual.
  • Use per-repo entre vários repositórios, per-directory para trabalho longo num diretório só e per-session para começar limpo ou caçar um bug de roteamento.
  • Para diagnosticar, leia primeiro o formato do nome da sessão e depois faça uma pergunta cruzando repositórios.
  • Sessões antigas e histórico já mal roteado não se movem sozinhos: comece sessões novas a partir do diretório correto.