Pular para o conteúdo principal
O ChatCLI é um projeto Go modular organizado em pacotes com responsabilidades bem definidas. Após a Fase 3 de decomposição de monolitos, todos os arquivos principais foram divididos em módulos focados seguindo princípios SOLID. Esta página documenta a arquitetura interna para contribuidores e usuários avançados.

Visão geral dos pacotes

chatcli/
|-- main.go                    # Entrypoint
|-- cli/                       # Interface do usuário e modos
|   |-- cli.go                 # Struct principal ChatCLI + Start() (~923 linhas)
|   |-- cli_helpers.go         # Métodos auxiliares extraídos de cli.go
|   |-- cli_history.go         # Gerenciamento de histórico
|   |-- cli_llm.go             # processLLMRequest + spinner
|   |-- cli_mode.go            # Lógica de troca de modos
|   |-- cli_output.go          # Formatação de saída
|   |-- cli_prompt.go          # Construção de prompts
|   |-- cli_session.go         # Gerenciamento de sessões
|   |-- cli_tools.go           # Registro e tratamento de tools
|   |-- agent_mode.go          # Modo agente core (~1498 linhas)
|   |-- agent_mode_helpers.go  # Helpers do modo agente
|   |-- agent_mode_tools.go    # Tratamento de tools do agente
|   |-- agent_mode_ui.go       # Renderização UI do agente
|   |-- agent_tool_sanitizer.go # Pipeline de sanitização de args
|   |-- command_handler.go     # Roteamento de comandos / (~185 linhas)
|   |-- command_handler_session.go  # Comandos de sessão
|   |-- command_handler_context.go  # Comandos de contexto
|   |-- command_handler_plugins.go  # Comandos de plugins
|   |-- command_handler_agent.go    # Comandos de agente
|   |-- context_handler.go     # Gerenciamento de contexto (~385 linhas)
|   |-- context_handler_attach.go   # Lógica de attach
|   |-- context_handler_create.go   # Lógica de criação
|   |-- context_handler_resolve.go  # Resolução de contexto
|   |-- rewind.go              # Checkpoints e restauracao de conversa
|   |-- compact_command.go     # Compactação guiada de histórico
|   |-- history_compactor.go   # Pipeline de compactação (3 níveis)
|   |-- history_trimmer.go     # Trimming near-lossless (nivel 1)
|   |-- memory_worker.go       # Worker background de memória
|   |-- agent/                 # Sistema multi-agent
|   |   |-- workers/           # 12 agents especialistas
|   |   |-- toolcall_parser.go # Parser stateful de tool calls
|   |   |-- ui_renderer.go     # Renderizacao da UI
|   |   +-- dispatcher.go      # Dispatcher assincrono
|   |-- bus/                   # Message bus interno
|   |-- workspace/             # Bootstrap files + memória + context builder
|   |   |-- bootstrap.go       # BootstrapLoader com cache mtime
|   |   |-- memory.go          # MemoryStore facade (delega ao memory.Manager)
|   |   |-- context_builder.go # Combina bootstrap + memória no prompt (smart retrieval)
|   |   +-- memory/            # Sistema de memória estruturado
|   |       |-- store.go       # Manager (orquestrador central)
|   |       |-- facts.go       # FactIndex (fatos com scores e decay)
|   |       |-- profile.go     # UserProfile (perfil automático)
|   |       |-- topics.go      # TopicTracker (tópicos recorrentes)
|   |       |-- projects.go    # ProjectTracker (projetos ativos)
|   |       |-- patterns.go    # PatternDetector (padrões de uso)
|   |       |-- retriever.go   # Smart retrieval por keywords
|   |       |-- compactor.go   # Compactação LLM + score-based
|   |       +-- migration.go   # Migração do MEMORY.md legado
|   |-- skills/                # Sistema de skills
|   |-- mcp/                   # Gerenciador MCP
|   +-- ctxmgr/                # Pacote de gerenciamento de contexto
|-- config/                    # Configuração e migração
|-- i18n/                      # Internacionalização (892 chaves de tradução)
|-- llm/                       # Comunicação com LLMs
|   |-- registry/              # Auto-registro de provedores
|   |-- fallback/              # Cadeia de fallback
|   |-- client/                # Interface LLMClient + ToolAwareClient
|   |-- openai/                # Provedor OpenAI
|   |-- claudeai/              # Provedor Anthropic
|   |-- googleai/              # Provedor Google
|   |-- xai/                   # Provedor xAI
|   |-- zai/                   # Provedor ZAI
|   |-- minimax/               # Provedor MiniMax
|   |-- moonshot/              # Provedor Moonshot (Kimi)
|   |-- ollama/                # Provedor Ollama
|   +-- copilot/               # Provedor GitHub Copilot
|-- models/                    # Structs: ToolDefinition, ToolCall, LLMResponse
|-- server/                    # Servidor gRPC
|   |-- handler.go             # Handler principal (~420 linhas)
|   |-- handler_session.go     # RPCs de sessão
|   |-- handler_resources.go   # RPCs de descoberta de recursos
|   +-- handler_aiops.go       # RPCs AIOps
|-- client/remote/             # Cliente gRPC
|-- k8s/                       # Kubernetes Watcher
|-- operator/                  # K8s Operator (AIOps)
|   |-- api/v1alpha1/          # Tipos dos 17 CRDs
|   +-- controllers/           # Reconcilers e engines
|-- proto/                     # Definições protobuf
|-- utils/                     # Funções auxiliares
+-- version/                   # Informações de versão


Struct principal: ChatCLI

A struct ChatCLI em cli/cli.go é o coração do sistema. Seus campos mais importantes estão organizados por responsabilidade:

LLM e Provider

CampoTipoDescrição
Clientclient.LLMClientCliente ativo do provedor LLM
managermanager.LLMManagerGerenciador de provedores (cria clientes, lista disponíveis)
ProviderstringNome do provedor ativo (ex: "ANTHROPIC", "OPENAI")
ModelstringModelo ativo (ex: "claude-sonnet-4-20250514")

Histórico e Compactação

CampoTipoDescrição
history[]models.MessageHistórico unificado de conversa (compartilhado entre todos os modos)
historyCompactor*HistoryCompactorPipeline de compactação em 3 níveis
historyManager*HistoryManagerPersistência de histórico de comandos em disco

Controle de Execução

CampoTipoDescrição
isExecutingatomic.BoolFlag atômica: true enquanto aguarda resposta do LLM
operationCancelcontext.CancelFuncCancela a operação LLM em andamento (Ctrl+C)
processingDonechan struct{}Sinaliza fim do processamento para o loop principal
interactionStateInteractionStateEstado da interação: Normal, SwitchingProvider, Processing, AgentMode
executionProfileExecutionProfilePerfil de execução: Normal, Agent, Coder

Subsistemas

CampoTipoDescrição
agentMode*AgentModeInstância do modo agente (loop ReAct)
pluginManager*plugins.ManagerGerenciador de plugins (built-in + externos)
commandHandler*CommandHandlerRoteador de comandos /
contextHandler*ContextHandlerGerenciador de contextos (/context)
personaHandler*PersonaHandlerGerenciador de personas/agents
skillHandler*SkillHandlerRegistro e execução de skills
contextBuilder*workspace.ContextBuilderCombina bootstrap + memória no system prompt
memoryStore*workspace.MemoryStoreFacade para o sistema de memória estruturada
memWorker*memoryWorkerWorker background de extração de memória

Estado Auxiliar

CampoTipoDescrição
checkpoints[]conversationCheckpointPontos de restauração do histórico (max 20)
messageQueue[]stringFila FIFO de mensagens digitadas durante processamento (type-ahead)
lastEscTimetime.TimeDetecção de Esc+Esc para menu de rewind
sessionManager*SessionManagerSalvar/carregar sessões em disco

Fluxo de inicialização

O diagrama abaixo mostra a sequência completa de boot, de main.go até o loop interativo:
main.go
  |
  +-- Checa subcomandos (server, connect, watch)
  +-- cli.PreprocessArgs() + cli.Parse() -> opts
  +-- i18n.Init()
  +-- godotenv.Load(.env)
  +-- utils.InitializeLogger() -> zap.Logger
  +-- config.InitGlobal() + config.Global.Load()
  |
  +-- manager.NewLLMManager(logger)
  |     +-- Provedores se auto-registram via init() no registry
  |     +-- Manager itera providers, checa env vars, cria clientes
  |
  +-- cli.NewChatCLI(manager, logger)
  |     +-- plugins.NewManager() -> registra built-in (CoderPlugin)
  |     +-- configureProviderAndModel() (config.Global + env)
  |     +-- manager.GetClient(provider, model) -> LLMClient
  |     +-- NewSessionManager()
  |     +-- NewContextHandler()
  |     +-- detectProjectDir(): caminha até raiz procurando .agent ou .git
  |     +-- workspace.NewBootstrapLoader(workspaceDir, globalDir)
  |     +-- workspace.NewMemoryStore(memDir)
  |     +-- workspace.NewContextBuilder(bootstrap, memory)
  |     +-- memoryWorker.start() (goroutine background)
  |     +-- NewPersonaHandler() + SetProjectDir()
  |     +-- NewSkillHandler()
  |     +-- NewCommandHandler(cli)
  |     +-- NewAgentMode(cli, logger)
  |     +-- historyManager.LoadHistory() (histórico de comandos)
  |
  +-- chatCLI.ApplyOverrides(provider, model) (flags CLI)
  +-- HandleOneShotOrFatal(ctx, opts) -> retorna se -p flag
  +-- handleGracefulShutdown(cancel, logger)
  +-- chatCLI.Start(ctx)
        +-- PrintWelcomeScreen()
        +-- Loop: prompt.New() com panic/recover para troca de modos

detectProjectDir()

Caminha do diretório atual até a raiz procurando marcadores de projeto:
  1. .agent/ (marcador explícito do ChatCLI) — prioridade
  2. .git/ (convencao comum)
Retorna o caminho do projeto ou "" se nenhum marcador for encontrado.

Fluxo de mensagens: modo chat

No modo interativo normal, cada mensagem do usuário segue este pipeline:
Usuário digita texto
       |
       v
  executor(in string)
       |
       +-- Detecção de paste (BracketedPasteParser)
       +-- Append ao commandHistory
       +-- Checa comandos / (roteamento via CommandHandler)
       +-- Checa /run, /agent, /coder -> panic(agentModeRequest)
       |
       v
  processLLMRequest(in)
       |
       +-- Suprime animação (go-prompt gerencia spinner no prefix)
       +-- Inicia spinner goroutine (250ms tick + SIGWINCH)
       +-- saveCheckpoint() (deep copy do histórico)
       +-- Append mensagem do usuário ao history
       +-- BuildPromptMessages() (system prompt + contextos attached)
       +-- Client.SendPrompt(ctx, prompt, history, maxTokens)
       |
       v
  Resposta do LLM
       |
       +-- Append resposta ao history
       +-- Renderiza via Glamour (markdown)
       +-- memWorker.nudge() (trigger extração de memória)
       +-- Drena messageQueue (type-ahead)

Type-ahead (messageQueue)

Mensagens digitadas enquanto o LLM processa são armazenadas em uma fila FIFO (messageQueue). Após cada resposta, o sistema drena a fila e processa cada mensagem sequencialmente, sem necessidade de re-digitação. O modo coder tem sua própria fila com indicador visual em tempo real (▼ N msg fila) quando há mensagens enfileiradas.

Input guard (anti-typeahead em prompts de segurança)

Distinto do typeahead “bom” acima, quando uma security box aparece no meio de um turn — três camadas descartam input em queue para evitar consumir bytes como y/n:
  • TTY flushTCIFLUSH (Linux), TIOCFLUSH (BSD/Darwin), FlushConsoleInputBuffer (Windows).
  • Drain channel — esvazia o buffer non-blocking do reader.
  • 250ms debounce — descarta input nos primeiros 250ms após a box renderizar.
Adicionalmente, no início de cada turn do agente é feito stty sane no /dev/tty controlador para recuperar de um teardown anterior do go-prompt que pudesse ter deixado o terminal em raw mode.

Fluxo de mensagens: modo Agent/Coder

A entrada no modo agente usa um mecanismo de panic/recover para sair do loop do go-prompt:
Usuário digita "/agent QUERY" ou "/coder QUERY" ou "/run QUERY"
       |
       v
  executor() -> panic(agentModeRequest)
       |
       v
  Start() recover -> detecta agentModeRequest
       +-- restoreTerminal() (no modo coder)
       |
       v
  agentMode.Run(ctx, query, additionalContext, systemPromptOverride)
       |
       v
  === CONFIGURAÇÃO ===
  1. saveCheckpoint()
  2. Composição do system prompt:
     |-- Se persona ativa: Persona + FormatInstructions (Coder ou Agent)
     |-- Se não: Prompt padrão (CoderSystemPrompt ou Agent default)
  3. Prepend workspace context (SOUL.md, USER.md, IDENTITY.md, RULES.md, MEMORY.md)
     |-- Smart retrieval: extrai keywords das últimas 3 mensagens
  4. Append tool context (descrição dos plugins disponíveis)
  5. Append orchestrator prompt (se multi-agent ativo)
       |
       v
  === LOOP ReAct ===
  Para cada turno (até maxTurns):
       |
       +-- Constroi turn history com anchor reminder
       +-- Checa budget de tokens -> compactação se > 60%
       +-- Client.SendPrompt(ctx, systemInstruction, turnHistory, 0)
       |
       v
  Parse da resposta (reasoning, explanation, tool_calls, agent_calls, commands)
       |
       +-- Prioridade 0: agent_calls -> dispatch para workers paralelos
       +-- Prioridade 1: tool_calls -> sanitize args -> security check -> execute plugin -> acumula output
       +-- Prioridade 2: command blocks (formato legado)
       +-- Prioridade 3: resposta final (nenhuma ação = fim do loop)
       |
       v
  Injeta resultados batch no histórico -> próximo turno

Anchor Reminder

A cada turno, o agente injeta um lembrete curto no histórico para manter o LLM focado na tarefa original. Isso evita drift em conversas longas.

Parsing de tool calls

O parser em cli/agent/toolcall_parser.go usa um scanner stateful (não regex) para robustez:

Formatos suportados

<!-- XML self-closing -->
<tool_call name="@coder" args="--file main.go --content '...'" />

<!-- XML paired -->
<tool_call name="@coder" args="..."></tool_call>

<!-- JSON fallback -->
{"tool_call":"@coder","args":{"file":"main.go"}}

Algoritmo do scanner

  1. Busca <tool_call case-insensitive
  2. Verifica que o próximo char é whitespace ou > (não parte de outra tag)
  3. scanTagEnd(): avança respeitando aspas (single e double quotes)
    • Dentro de aspas, > é tratado como texto literal
    • Suporta entidades HTML (&gt;, &quot;, etc.)
  4. Extrai atributos name e args independente da ordem
  5. Se self-closing falha, tenta paired tags com </tool_call>
  6. Fallback para JSON: tenta parseJSONToolCalls() em paralelo

Por que não regex?

Argumentos de tools frequentemente contêm >, ", e JSON aninhado. Regex não consegue distinguir > dentro de um atributo quoted de > que fecha a tag. O scanner stateful resolve isso rastreando o estado de quote.

Pipeline de sanitização de argumentos

Após o parsing, cada tool call passa por um pipeline de 7 etapas em agent_tool_sanitizer.go:
Raw args do LLM
       |
  1. HTML unescape (&quot; -> ", &#10; -> \n, &gt; -> >)
  2. Normalização de linhas (CRLF -> LF)
  3. Continuação de linha (backslash + newline -> espaço)
  3b. Unescape de JSON-ish strings (se detectado)
  4. Remoção de backslash+espaço bogus (fora de aspas)
  4b. Correção de aspas desbalanceadas com barra final
  5. Remoção de barras invertidas finais pendentes
  6. Normalização de espaços múltiplos (preserva dentro de aspas)
  7. Correções semânticas para @coder (se aplicável)
       |
       v
  Detecção de formato:
  +-- Se JSON válido -> buildArgvFromJSONMap()
  +-- Se CLI-style -> splitToolArgsMultiline()
       |
       v
  Args limpos prontos para execução


Compactação de histórico (3 níveis)

O HistoryCompactor em cli/history_compactor.go gerencia o tamanho do histórico através de um pipeline progressivo:
Histórico excede budget de tokens
       |
       v
  NIVEL 1: Trimming (near-lossless)
  |-- Remove mensagens de contexto injetado (>3000 chars)
  |-- Remove blocos <reasoning> do assistente
  |-- Compacta XML verboso em tool outputs
  |-- Deduplica mensagens identicas consecutivas
  |-- Trunca outputs de ferramentas muito longos
  |
  +-- Se dentro do budget -> retorna
       |
       v
  NIVEL 2: Sumarização estruturada
  |-- Divide histórico em: [system | meio (sumarizar) | recentes (manter)]
  |-- Preserva as 8 mensagens mais recentes verbatim
  |-- Envia bloco do meio para LLM com prompt de extração de fatos
  |-- Resultado: mensagem única "summary" substituindo o bloco
  |-- Timeout independente (10 min) para não bloquear o fluxo
  |
  +-- Se dentro do budget -> retorna
       |
       v
  NIVEL 3: Truncamento de emergência
  |-- Último recurso: descarta mensagens do meio sem sumarização
  |-- Mantém system messages + mensagens recentes
  |-- Logga warning para visibilidade

Trigger no modo agente

No modo agente, a compactação é verificada a cada turno do loop ReAct. O trigger ocorre quando o uso de tokens ultrapassa 60% do budget do modelo.

Sistema de checkpoint/rewind

O rewind.go implementa um sistema de snapshots do histórico:

Estrutura

type conversationCheckpoint struct {
    Timestamp time.Time
    Label     string           // resumo auto-gerado da última mensagem do usuário
    History   []models.Message // deep copy do histórico naquele ponto
    MsgCount  int              // número de mensagens no checkpoint
}

Comportamento

  • Quando salva: Antes de cada chamada ao LLM (saveCheckpoint())
  • Limite: Máximo de 20 checkpoints (FIFO — os mais antigos são descartados)
  • Deep copy: Cada checkpoint contém uma cópia completa e independente do histórico
  • Trigger: Pressionar Esc+Esc (dois Esc em menos de 500ms) abre o menu de rewind
  • Restauração: O usuário escolhe um checkpoint, e o histórico é substituído pela cópia salva

Registry de provedores

Padrão de auto-registro

Cada provedor LLM se registra automaticamente via init() no pacote llm/registry:
llm/openai/register.go      -> init() { registry.Register(ProviderInfo{...}) }
llm/claudeai/register.go    -> init() { registry.Register(ProviderInfo{...}) }
llm/googleai/register.go    -> init() { registry.Register(ProviderInfo{...}) }
llm/xai/register.go         -> init() { registry.Register(ProviderInfo{...}) }
llm/zai/register.go         -> init() { registry.Register(ProviderInfo{...}) }
llm/minimax/register.go     -> init() { registry.Register(ProviderInfo{...}) }
llm/ollama/register.go      -> init() { registry.Register(ProviderInfo{...}) }
llm/copilot/register.go     -> init() { registry.Register(ProviderInfo{...}) }

ProviderInfo

type ProviderInfo struct {
    Name         string           // chave única (ex: "OPENAI")
    DisplayName  string           // nome para exibição (ex: "OpenAI")
    Factory      ProviderFactory  // função que cria LLMClient a partir de ProviderConfig
    EnvKeys      []string         // variáveis de ambiente para descoberta de API key
    RequiresAuth bool             // se precisa de autenticação OAuth
}

Fluxo de descoberta

manager.NewLLMManager(logger)
       |
       +-- registry.List() -> nomes de todos os provedores registrados
       +-- Para cada provedor:
       |     +-- Checa EnvKeys (os.Getenv)
       |     +-- Se encontrou key ou RequiresAuth: marca como disponível
       +-- GetClient(provider, model):
             +-- registry.Get(provider) -> ProviderInfo
             +-- info.Factory(ProviderConfig{...}) -> LLMClient
Este padrão elimina blocos switch/case. Para adicionar um novo provedor, basta criar o pacote com register.go e implementar a interface LLMClient.

Memory Worker (processo background)

O memoryWorker em cli/memory_worker.go extrai memórias da conversa em background:

Parâmetros

ConstanteValorDescrição
memoryMinNewMessages4Mínimo de novas mensagens para trigger
memoryCooldown2 minTempo mínimo entre extrações
memoryExtractTimeout60sTimeout da chamada LLM de extração
compactionCheckInterval6hFrequência de checagem de compactação
dailyCleanupInterval24hFrequência de limpeza de notas diárias

Triggers

  1. nudge(): Chamado após cada resposta do LLM. Se houver >= 4 novas mensagens e cooldown expirado, executa extração
  2. Ticker de 3 minutos: O loop background verifica periodicamente (para sessões longas onde o usuário digita pouco)
  3. Compaction ticker (6h): Consolida fatos antigos com scores baixos
  4. Cleanup ticker (24h): Remove notas diárias expiradas

Pipeline de extracao

nudge() ou ticker
       |
       v
  maybeExtract()
       +-- Checa: >= 4 novas mensagens && cooldown expirado
       +-- Envia conversa recente para LLM com prompt de extração
       +-- LLM retorna JSON estruturado com categorias:
       |     - DAILY: notas do dia (efêmeras)
       |     - LONGTERM: fatos permanentes
       |     - PROFILE_UPDATE: atualizações de perfil do usuário
       |     - TOPICS: tópicos recorrentes
       |     - PROJECTS: projetos ativos
       |
       +-- Resultados armazenados em arquivos JSON estruturados
       +-- PatternDetector registra padrões de uso

Componentes principais

CLI e Modos

A struct ChatCLI em cli/cli.go é o ponto central (~923 linhas após decomposição). O método Start() inicia o modo interativo usando Bubble Tea (Charmbracelet). Métodos auxiliares, gerenciamento de histórico, troca de modos, formatação de saída, construção de prompts, gerenciamento de sessões e tratamento de tools foram extraídos para arquivos dedicados (cli_helpers.go, cli_history.go, cli_mode.go, cli_output.go, cli_prompt.go, cli_session.go, cli_tools.go).
ModoArquivoDescrição
Interativocli/cli.goPrompt interativo com auto-completação
Agentecli/agent_mode.goPlanejamento e execução de tarefas
Codercli/cli.go + cli/agent_mode.goLoop de engenharia com tool calls
One-shotcli/cli.go (flag -p)Execução única sem TUI
A troca entre modos usa um mecanismo de panic/recover para sair do loop do go-prompt. O ChatCLI usa um histórico unificado (cli.history) compartilhado entre todos os modos. Ao trocar de modo, o contexto completo é preservado. Os comandos /compact e /rewind operam diretamente sobre esse histórico único.

Message Bus

O pacote cli/bus implementa um barramento de mensagens tipado com:
  • Pub/sub com filtros por canal e tipo
  • Request-reply com correlation IDs
  • Métricas atômicas de throughput

Multi-Agent

O sistema de orquestração em cli/agent/workers gerencia 12 agents especialistas que executam em goroutines paralelas com semáforo configurável. Cada worker possui:
  • Mini ReAct loop isolado (observe -> reason -> act)
  • Skills próprias (scripts aceleradores e descritivas)
  • File locks (mutex per-filepath)
  • Timeout e max turns configuráveis

Tecnologias

CategoriaBiblioteca
TUIBubble Tea (Charmbracelet)
MarkdownGlamour (Charmbracelet)
CoresLipgloss (Charmbracelet)
LoggerZap (Uber)
Log rotationLumberjack
Env filesGodotenv
i18ngolang.org/x/text
gRPCgoogle.golang.org/grpc
Kubernetesk8s.io/client-go
Operatorcontroller-runtime (Kubebuilder)
Protobufgoogle.golang.org/protobuf

Padrões de design

  • Auto-registro via init() — Provedores se registram automaticamente
  • Interface-drivenLLMClient e ToolAwareClient para polimorfismo
  • Fallback chain — Classificacao inteligente de erros + cooldown exponencial
  • Stateful parser — Parsing de XML com atributos escapados (mais robusto que regex)
  • embed.FS — Arquivos i18n embarcados no binário (sempre usa /, nunca filepath.Join)
  • Panic/recover — Troca de modos no go-prompt sem reiniciar o processo
  • Histórico unificado — Um único array de mensagens compartilhado entre chat, agent e coder
  • Checkpoint/rewind — Deep copy do histórico antes de cada chamada LLM, com restauração seletiva
  • Compactação em 3 níveis — Trimming near-lossless, sumarização estruturada, truncamento de emergência
  • Type-ahead queue — Mensagens digitadas durante processamento são enfileiradas e drenadas automaticamente
  • Workspace context injection — Bootstrap + memória injetados automaticamente em todo system prompt
  • Context injection via system prompt — Contextos attached (/context attach) são injetados como SystemParts com CacheControl no modelo de mensagem, permitindo cache automático por provider (Anthropic cache_control: ephemeral, OpenAI prompt caching, Google context caching)
  • Background memory extraction — Worker em goroutine extrai e consolida memórias da conversa sem bloquear o fluxo principal