Como o Dreamalizing roda um modelo de linguagem sem nuvem
Um sonho é o que há de mais pessoal que alguém pode escrever. Por isso, o texto do sonho no Dreamalizing nunca é enviado para um serviço de IA externo. Este artigo descreve como isso funciona tecnicamente, quais escolhas resultaram disso e o que deu errado no caminho.
Foi escrito para desenvolvedores e para todos que desejam verificar se a promessa de privacidade é mais do que apenas uma frase na página inicial.
A promessa, em uma frase
Seu texto de sonho é processado pelo nosso próprio modelo de linguagem em servidores dentro da União Europeia. Seus dados são armazenados de forma criptografada e seu texto de sonho não é enviado para serviços de IA externos.
Essa frase está no aplicativo, nas lojas e neste site. Tudo abaixo existe para torná-la realidade, mesmo que alguém cometa um erro de digitação em um arquivo de configuração.
A cadeia, do telefone ao modelo
O aplicativo (React Native, Expo) fala com uma API (NestJS, PostgreSQL) em um servidor alugado na UE. Esse servidor não possui um modelo de linguagem. Ele alcança, via um túnel WireGuard, um gateway em hardware próprio, e esse gateway fala com o modelo.
O modelo é o Gemma 4 com 26 bilhões de parâmetros em uma configuração mixture-of-experts, dos quais cerca de 4 bilhões por token estão ativos, em quantização de 8 bits. Um único modelo faz tudo: as perguntas, a análise e a extração estruturada. A janela de contexto é fixa em 32.768 tokens, pois a análise mais pesada até agora usou 13.671 tokens e a saída pode chegar a 8.192 tokens.
Até agosto, rodavam dois modelos: um mais pesado para as perguntas e a análise, e um mais leve para a extração. Isso se mostrou mais caro do que o retorno que trazia. Os dois modelos competiam por memória, fazendo com que o modelo fosse carregado três vezes por sonho, levando de 7 a 10 segundos cada vez. O modelo mais pesado realiza a extração 3,7 segundos mais devagar, mas economiza 27 segundos de tempo de carregamento.
Local-only como monitoramento, não como intenção
A API possui uma camada de roteamento que pode enviar para múltiplos provedores. Isso é útil durante o desenvolvimento e perigoso em produção. Por isso, existem três travas.
Primeiro, uma flag rígida no runtime. Se estiver ativada, a API recusa qualquer chamada com conteúdo de sonho para um provedor não local. Existe uma válvula de escape, mas ela está vinculada a uma data de validade de no máximo quatorze dias e expira automaticamente; uma data inválida ou muito distante é ignorada e o monitoramento permanece fechado.
Segundo, o gateway. Cada model-id que a API solicita começa com o prefixo local-. O gateway lê isso como local-only e, assim, recusa a entrega de tal solicitação a um provedor de nuvem, mesmo que uma cadeia de fallback aponte para lá.
Terceiro, testes. Um teste garante que a API nunca remova o prefixo, outro garante que a configuração do modelo dos ambientes de teste e produção sejam idênticas. O primeiro teste existe porque a API removia o prefixo até o final de agosto. Por causa disso, o fallback do gateway poderia, em teoria, enviar texto de sonho para a nuvem, com status 200 e sem sinalização. O app ainda não havia sido lançado e não tínhamos usuários ativos além de nós mesmos. Nós fechamos o caminho e adicionamos o teste, para que isso não seja mais possível, nem mesmo sem ser percebido.
Tempo de espera é uma escolha de design
Um modelo local em uma única máquina é mais lento que uma API de nuvem. As perguntas chegam após 20 a 40 segundos, a análise após 30 a 60. Não escondemos isso; o aplicativo diz o que está acontecendo e quanto tempo deve levar aproximadamente.
A ordem foi planejada para isso. Primeiro, as perguntas vão para o aplicativo, baseadas no texto bruto do sonho. Depois, em paralelo, uma segunda chamada constrói os dados estruturados que a análise precisará mais tarde. Enquanto isso, o sonhador responde às perguntas e não percebe a segunda chamada.
Na máquina, roda um limitador que permite apenas uma chamada por usuário por vez e nunca deixa chamadas opcionais passarem à frente das obrigatórias. Aprendemos isso último através de um incidente em produção: uma chamada opcional substituiu a obrigatória, e o sonhador ficou esperando por nada.
Armazenamento e offline
O texto do sonho e as respostas são armazenados de forma criptografada por usuário usando AES-256-GCM e uma chave própria por usuário, que por sua vez fica sob uma chave mestra. No telefone, a fila de sonhos ainda não enviados fica em um banco de dados SQLCipher criptografado com uma chave no armazenamento seguro do dispositivo.
Cada ação de escrita carrega um ID de operação estável. Se a conexão cair após o envio, mas antes da resposta, uma repetição resultará no mesmo sonho, não em um segundo. O registro desses IDs nunca contém o texto do sonho, apenas o tipo de ação, um hash e o resultado.
O custo
Uma máquina é um teto. Com usuários simultâneos, as chamadas se acumulam e o tempo de espera cresce. Esse é o preço de não usar nuvem, e a razão pela qual começamos na Holanda e não globalmente.
O segundo preço é que o modelo é inferior aos maiores modelos de nuvem. Por isso, sempre medimos mudanças de prompt no modelo local, nunca em um modelo de nuvem; o que funciona em um modelo maior não funciona aqui automaticamente.
O que vem a seguir
Perguntas sobre a arquitetura são bem-vindas. O que está aqui é o estado de setembro de 2026; se algo mudar, este artigo mudará junto.