1. Mapa dos Programas
Cada arquivo do projeto, onde mora e o que faz. A coluna Linhas e aproximada — o projeto todo soma ~3.400 linhas de Python.
Agent — O cerebro
| Arquivo | Linhas | O que faz |
|---|---|---|
nanobot/agent/loop.py |
406 | Motor principal — recebe mensagens, chama LLM, executa tools |
nanobot/agent/context.py |
~130 | Monta o prompt — junta bootstrap files, memoria, skills, historico |
nanobot/agent/subagent.py |
~90 | Gerencia subagentes (max 5 simultaneos) |
nanobot/agent/memory.py |
~80 | Memoria do agente (lembrar coisas entre sessoes) |
nanobot/agent/skills.py |
~100 | Carrega skills sob demanda |
Tools — As maos do agente
| Arquivo | Linhas | O que faz |
|---|---|---|
nanobot/agent/tools/registry.py |
~50 | Registro central de tools disponiveis |
nanobot/agent/tools/filesystem.py |
~120 | Tools: read_file, write_file, edit_file, list_dir |
nanobot/agent/tools/shell.py |
~130 | Tool: exec (executa comandos no terminal) |
nanobot/agent/tools/web.py |
~170 | Tools: web_search, web_fetch |
nanobot/agent/tools/message.py |
~40 | Tool: message (envia mensagem pro usuario) |
nanobot/agent/tools/spawn.py |
~60 | Tool: spawn (cria subagentes) |
nanobot/agent/tools/cron.py |
~110 | Tool: cron (agenda tarefas) |
Message Bus — O correio interno
| Arquivo | Linhas | O que faz |
|---|---|---|
nanobot/bus/queue.py |
92 | Fila de mensagens (asyncio.Queue, max 1000) |
nanobot/bus/events.py |
~50 | Tipos de mensagem (InboundMessage, OutboundMessage) |
Channels — As portas de entrada
| Arquivo | Linhas | O que faz |
|---|---|---|
nanobot/channels/base.py |
~130 | Classe base de todos os canais |
nanobot/channels/telegram.py |
400 | Canal Telegram (polling + envio) |
nanobot/channels/discord.py |
~220 | Canal Discord (WebSocket) |
nanobot/channels/whatsapp.py |
~150 | Canal WhatsApp (bridge Node.js) |
nanobot/channels/feishu.py |
~270 | Canal Feishu (WebSocket) |
nanobot/channels/dingtalk.py |
~100 | Canal DingTalk (Stream) |
Providers — Conexao com a IA
| Arquivo | Linhas | O que faz |
|---|---|---|
nanobot/providers/base.py |
~60 | Interface base do provider LLM |
nanobot/providers/registry.py |
~100 | Registro de providers (auto-detect, prefixing) |
Configuracao e Sessoes
| Arquivo | Linhas | O que faz |
|---|---|---|
nanobot/config/schema.py |
~100 | Schema de configuracao (Pydantic) |
nanobot/session/manager.py |
249 | Gerencia sessoes (JSONL, cache LRU) |
Seguranca
| Arquivo | Linhas | O que faz |
|---|---|---|
nanobot/security/ratelimit.py |
~100 | Rate limiter (token bucket por sessao) |
nanobot/security/sanitize.py |
~50 | Sanitiza erros e resultados de tools |
nanobot/security/validators.py |
~70 | Valida paths, filenames, inputs |
Servicos
| Arquivo | Linhas | O que faz |
|---|---|---|
nanobot/cron/service.py |
346 | Servico de tarefas agendadas |
nanobot/heartbeat/ |
~40 | Heartbeat para wake-up proativo |
nanobot/cli/commands.py |
846 | Todos os comandos CLI (gateway, agent, status, etc.) |
2. Limites e Protecoes
O nanobot tem dezenas de limites espalhados pelo codigo. Cada um existe para evitar que o sistema trave, consuma memoria demais, ou seja abusado. Esta secao documenta todos eles.
Fila de Mensagens (MessageBus)
| Constante | Valor | Onde | O que acontece |
|---|---|---|---|
MAX_QUEUE_SIZE |
1000 | bus/queue.py |
Tanto a fila de entrada quanto a de saida seguram no maximo 1000 mensagens |
| Timeout da fila | 5 segundos | bus/queue.py |
Se a fila nao tiver espaco em 5s, a mensagem e perdida com warning "backpressure" |
Importante: a fila e in-memory
A fila do MessageBus e uma asyncio.Queue — ela existe apenas na memoria RAM. Se o processo morrer, tudo que esta na fila desaparece. Quando a fila enche (1000 mensagens), as novas sao descartadas (dropped), nao salvas. Nao ha persistencia.
A fila e o "corredor" entre channels e agent. Mensagens passam por ela rapidamente. Se estiver acumulando 1000, significa que o agent esta muito lento ou parado.
Sessoes (Session)
| Constante | Valor | Onde | O que acontece |
|---|---|---|---|
MAX_MESSAGES_PER_SESSION |
1000 | session/manager.py |
Cada sessao guarda no maximo 1000 mensagens em memoria e disco. Quando excede, as mais antigas sao descartadas (mantem as ultimas 1000) |
MAX_MESSAGE_SIZE |
100 KB | session/manager.py |
Mensagens individuais maiores que 100KB sao truncadas |
MAX_CACHED_SESSIONS |
100 | session/manager.py |
So 100 sessoes ficam na RAM (cache LRU). As demais sao carregadas do disco sob demanda |
SESSION_EXPIRY_DAYS |
30 dias | session/manager.py |
Sessoes com mais de 30 dias sao deletadas no proximo acesso |
get_history(max_messages=50) |
50 | session/manager.py |
Quando monta o contexto para o LLM, envia apenas as ultimas 50 mensagens (nao as 1000) |
Por que 1000 no disco mas so 50 para o LLM?
O arquivo .jsonl guarda 1000 mensagens como backup completo da conversa. Mas o LLM recebe apenas as ultimas 50 porque: (a) modelos tem limite de tokens, (b) mensagens muito antigas raramente sao relevantes, e (c) menos mensagens = resposta mais rapida e mais barata.
Fila vs. Sessao — A diferenca crucial
Os dois tem limite de 1000, mas sao coisas completamente diferentes:
Fila (MessageBus)
- Onde: Memoria RAM apenas
- O que guarda: Mensagens em transito (entrada e saida)
- Quando enche: Mensagens novas sao descartadas (perdidas pra sempre)
- Duracao: Segundos (mensagens passam rapido)
- Persistencia: Nenhuma — morre com o processo
- Analogia: Uma esteira rolante. Se nao cabe mais, cai no chao
Sessao (Session)
- Onde: Memoria RAM (cache) + disco (
.jsonl) - O que guarda: Historico completo de uma conversa
- Quando enche: As mensagens mais antigas sao removidas (mantem as 1000 mais recentes)
- Duracao: Ate 30 dias
- Persistencia: Arquivo no disco, escrita atomica
- Analogia: Um caderno. Quando enche, arranca as primeiras paginas
FILA (transito) SESSAO (historico)
================= ==================
Channel --> [msg][msg][msg] --> Agent Arquivo .jsonl no disco:
^^^^^^^^^^^^^^^^ +---------------------------+
Max 1000 in-memory | msg 1 (mais antiga) | ← descartada
Se cheia: DROP | msg 2 | quando > 1000
| ... |
Mensagem fica SEGUNDOS aqui. | msg 999 |
Sem persistencia. | msg 1000 (mais recente) |
+---------------------------+
Mensagem fica ATE 30 DIAS.
Rate Limiting (Token Bucket)
| Operacao | Limite | Janela | Por |
|---|---|---|---|
channel_message |
10 | por minuto | sessao |
tool_exec |
5 | por minuto | sessao |
web_fetch |
20 | por minuto | sessao |
file_write |
30 | por minuto | sessao |
subagent_spawn |
3 | a cada 5 minutos | sessao |
cron_job |
10 | por hora | sessao |
Contexto e Arquivos
| Constante | Valor | O que faz |
|---|---|---|
MAX_BOOTSTRAP_FILE_SIZE |
500 KB | Bootstrap files (SOUL.md, AGENTS.md etc.) maiores que 500KB sao ignorados |
MAX_IMAGE_SIZE |
5 MB | Tamanho maximo por imagem enviada |
MAX_IMAGES_PER_MESSAGE |
5 | Maximo de imagens por mensagem |
MAX_READ_SIZE |
10 MB | Tamanho maximo que o tool read_file consegue ler |
MAX_WRITE_SIZE |
10 MB | Tamanho maximo que o tool write_file consegue gravar |
| Saida do shell | 10.000 chars | Saida do tool exec e truncada em 10.000 caracteres |
Outros Limites
| Constante | Valor | O que faz |
|---|---|---|
MAX_CONCURRENT_SUBAGENTS |
5 | Maximo de subagentes rodando ao mesmo tempo |
MAX_REDIRECTS |
5 | Maximo de redirecionamentos HTTP no web_fetch |
MAX_ATTACHMENT_BYTES |
20 MB | Tamanho maximo de anexo no Discord |
| Channel message size | 50 KB | Tamanho maximo de mensagem no canal base |
| Feishu dedup cache | 1000 → 500 | Cache de deduplicacao do Feishu: quando atinge 1000 entradas, faz trim para 500 |
3. Rate Limiting Explicado
O sistema de rate limiting do nanobot usa o algoritmo Token Bucket (balde de fichas). Cada tipo de operacao tem seu proprio balde, e cada sessao (usuario) tem seus proprios baldes independentes.
Como funciona o Token Bucket
Analogia: tanque de agua
Imagine um tanque de agua que enche lentamente por uma torneira. Cada vez que voce quer fazer uma operacao (mandar mensagem, executar tool, etc.), voce precisa tirar 1 litro do tanque.
Se o tanque estiver vazio, voce nao pode fazer nada — tem que esperar a torneira encher mais um pouco.
O tanque tem um tamanho maximo (capacidade). Mesmo que voce nao use, ele nao transborda — a torneira para quando ta cheio.
Isso permite rajadas curtas (usar varios litros de uma vez) mas impede abuso continuo (a torneira nao enche rapido o suficiente).
Mecanica passo a passo
- Cada tipo de operacao tem um balde com capacidade fixa (exemplo:
channel_message= 10) - O balde comeca cheio (10 fichas disponiveis)
- Cada request consome 1 ficha
- Fichas sao reabastecidas ao longo do tempo (exemplo: 10 fichas por minuto = 1 ficha a cada 6 segundos)
- Se o balde esta vazio, o request e negado (nao enfileirado, negado imediatamente)
- Cada sessao tem seus proprios baldes — o abuso de um usuario nao afeta os outros
Exemplo pratico
Operacao: channel_message (10/minuto)
Tempo: 00:00 Balde: [10 fichas]
00:01 Usuario manda msg → consome 1 → Balde: [9] OK
00:02 Usuario manda msg → consome 1 → Balde: [8] OK
00:03 Usuario manda msg → consome 1 → Balde: [7] OK
...
00:10 Usuario manda msg → consome 1 → Balde: [0] OK
00:11 Usuario manda msg → balde vazio → NEGADO
00:12 (6s se passaram) → +1 ficha → Balde: [1]
00:12 Usuario manda msg → consome 1 → Balde: [0] OK
Por que Token Bucket e nao uma janela simples?
Vantagem do Token Bucket
Com uma janela fixa (ex: "10 por minuto"), um usuario pode mandar 10 mensagens no segundo 59 e mais 10 no segundo 61 — totalizando 20 em 2 segundos. O Token Bucket evita isso porque as fichas gastam e demoram para reabastecer, distribuindo o uso de forma mais uniforme ao longo do tempo.
Todos os baldes configurados
| Operacao | Capacidade | Reabastecimento | Descricao |
|---|---|---|---|
channel_message |
10 | 10 por minuto | Mensagens recebidas de um usuario em um canal |
tool_exec |
5 | 5 por minuto | Execucoes de comandos no terminal |
web_fetch |
20 | 20 por minuto | Requisicoes HTTP feitas pelo agente |
file_write |
30 | 30 por minuto | Escritas em arquivo |
subagent_spawn |
3 | 3 a cada 5 minutos | Criacao de subagentes em background |
cron_job |
10 | 10 por hora | Execucoes de tarefas agendadas |
O que acontece quando negado?
Quando o rate limit nega uma operacao:
- Para mensagens de channel: a mensagem e silenciosamente ignorada. O usuario nao recebe aviso.
- Para tools: o resultado retornado ao LLM e uma mensagem de erro informando o rate limit. O LLM pode decidir esperar ou tentar outra abordagem.
- Para subagents: a tentativa de spawn falha e o agente e informado.
4. Ciclo de Vida de uma Mensagem
Passo a passo completo do que acontece desde o usuario digitar no Telegram ate receber a resposta, com todos os limites que se aplicam em cada etapa.
TelegramChannel detecta a mensagem nova. Primeira verificacao: o sender_id esta na lista allowFrom? Se nao, a mensagem e ignorada silenciosamente.
allowFrom: whitelist de IDs
channel_message consome 1 ficha. Se o balde estiver vazio, a mensagem e descartada.
channel_message: 10/minuto por sessao
InboundMessage padronizado e coloca na fila de entrada do MessageBus. A fila aceita no maximo 1000 mensagens. Se estiver cheia, espera ate 5 segundos. Se nao abrir espaco, a mensagem e perdida com warning de backpressure.
MAX_QUEUE_SIZE: 1000 | Timeout: 5s
AgentLoop esta em await bus.consume_inbound() e recebe a mensagem. Carrega a sessao correspondente (telegram:123456789). Se a sessao nao esta no cache LRU, carrega do disco. Se tem mais de 30 dias, deleta e cria nova.
MAX_CACHED_SESSIONS: 100 | SESSION_EXPIRY: 30 dias
ContextBuilder monta o sistema de prompt na ordem: identidade → bootstrap files (SOUL.md, AGENTS.md, USER.md, TOOLS.md, IDENTITY.md) → memoria → skills → ultimas 50 mensagens do historico → mensagem atual. Bootstrap files maiores que 500KB sao ignorados.
get_history: 50 msgs | MAX_BOOTSTRAP: 500KB
provider.chat() envia todo o contexto montado para a API do modelo (ex: OpenRouter → Qwen). Junto vai a lista de tools disponiveis no formato JSON Schema. O LLM processa e retorna uma resposta.
web_fetch("wttr.in/SP")), o agent verifica o rate limit da tool. Se aprovado, executa. O resultado (ex: "28C, parcialmente nublado") e adicionado ao contexto e o loop volta ao passo 7. Isso pode repetir ate 20 vezes numa unica mensagem.
tool_exec: 5/min | web_fetch: 20/min | file_write: 30/min | Max 20 iteracoes
OutboundMessage e colocada na fila de saida do MessageBus. Mesmas regras: max 1000, timeout 5s.
MAX_QUEUE_SIZE: 1000 | Timeout: 5s
TelegramChannel. O channel converte para o formato do Telegram (HTML) e envia via API. A mensagem do channel tem limite de tamanho (50KB base).
Channel message size: 50KB
.jsonl da sessao. Se a sessao exceder 1000 mensagens, as mais antigas sao descartadas. A escrita e atomica (temp + rename) para evitar corrupcao.
MAX_MESSAGES_PER_SESSION: 1000 | MAX_MESSAGE_SIZE: 100KB
Resumo dos limites no caminho
| Etapa | Limite | Se exceder |
|---|---|---|
| Channel recebe | allowFrom | Mensagem ignorada |
| Channel rate limit | 10/min | Mensagem descartada |
| Fila de entrada | 1000 (5s timeout) | Mensagem perdida |
| Cache de sessoes | 100 LRU | Sessao carregada do disco (mais lento) |
| Expiracao da sessao | 30 dias | Sessao deletada, nova criada |
| Historico para LLM | 50 mensagens | Mensagens antigas cortadas |
| Bootstrap files | 500KB cada | Arquivo ignorado |
| Tool rate limits | Varia (5-30/min) | Tool retorna erro ao LLM |
| Iteracoes do loop | 20 | Responde com o que tem |
| Fila de saida | 1000 (5s timeout) | Resposta perdida |
| Mensagem do channel | 50KB | Mensagem truncada |
| Sessao no disco | 1000 mensagens | Mais antigas removidas |
5. Fila Unica e Concorrencia
Duas perguntas frequentes sobre a arquitetura do nanobot: como funciona quando os dados chegam por mais de um canal ao mesmo tempo? E o agente pode processar varias mensagens em paralelo?
A fila e unica para todos os canais
Nao importa se a mensagem veio do Telegram, Discord, WhatsApp ou Feishu — todas vao para a mesma fila (inbound). O MessageBus tem exatamente duas filas:
| Fila | Direcao | Quem coloca | Quem retira |
|---|---|---|---|
inbound | Canal → Agente | Todos os canais (Telegram, Discord, etc.) | AgentLoop (1 consumidor) |
outbound | Agente → Canal | AgentLoop | Dispatcher (envia para o canal correto) |
Funciona como um funil: varios canais despejam mensagens na mesma entrada, e o agente puxa uma por uma.
Telegram ──┐
│
Discord ───┤ ┌─────────────────────┐ ┌───────────┐
├──────►│ FILA INBOUND (1000) │───────►│ AgentLoop │
WhatsApp ──┤ └─────────────────────┘ │ (1 por vez)│
│ └─────┬─────┘
Feishu ────┘ │
▼
Telegram ◄──┐ ┌──────────────────────┐ ┌─────┴─────┐
├──────│ FILA OUTBOUND (1000) │◄─────│ Resposta │
Discord ◄───┘ └──────────────────────┘ └───────────┘
(dispatcher envia para o canal certo)
O processamento e sequencial (uma mensagem por vez)
O AgentLoop funciona com um loop simples:
while self._running:
msg = await bus.consume_inbound() # 1. Pega UMA mensagem da fila
response = await _process_message(msg) # 2. Processa ATE O FIM (inclui tools)
await bus.publish_outbound(response) # 3. Coloca resposta na fila de saida
# so agora volta ao passo 1
Enquanto o agente esta processando a mensagem A (chamando LLM, executando tools, esperando APIs), as mensagens B, C e D ficam esperando na fila. Nao ha processamento paralelo de mensagens.
Consequencias praticas
| Situacao | O que acontece |
|---|---|
| Voce manda 3 mensagens rapidas pelo Telegram | A 1a e processada imediatamente. A 2a e 3a ficam na fila. Quando a 1a terminar, o agente pega a 2a, e depois a 3a. Voce recebe 3 respostas, em ordem. |
| Voce manda pelo Telegram e outra pessoa manda pelo Discord ao mesmo tempo | Ambas entram na mesma fila. A que chegar primeiro e processada primeiro. A outra espera. Nao ha prioridade por canal. |
| O agente esta usando tools (web_fetch, exec) que demoram | Tudo faz parte do processamento da mensagem. As tools rodam dentro do _process_message(). Novas mensagens continuam esperando na fila. |
O agente usa spawn para criar subagente |
O subagente roda em background (asyncio task separada). O agente pode terminar a mensagem atual e pegar a proxima. Quando o subagente termina, envia o resultado como mensagem "system" na fila. |
| 100 pessoas mandam mensagens ao mesmo tempo | Todas entram na fila (cabe 1000). Sao processadas uma por uma, na ordem de chegada. As ultimas vao esperar bastante. Se a fila encher (1000), novas mensagens sao descartadas. |
Por que nao e paralelo?
O nanobot e um assistente pessoal, projetado para poucos usuarios (normalmente so voce). Processamento paralelo traria complexidade desnecessaria:
- Cada sessao compartilha o mesmo workspace — tools paralelos poderiam conflitar (dois escrevendo no mesmo arquivo)
- O contexto do LLM depende do historico — processar em paralelo poderia misturar conversas
- O rate limiter e por sessao — concorrencia exigiria locks extras
- Para 1-5 usuarios, a fila sequencial funciona bem. Cada mensagem leva segundos, nao minutos
A unica forma de paralelismo real e o subagente (spawn), que roda em background com seu proprio loop de tools (max 5 simultaneos).
6. Seguranca em Camadas
A seguranca do nanobot segue o principio de defesa em profundidade (defense in depth): multiplas camadas independentes, cada uma capaz de bloquear ameacas por conta propria. Se uma camada falhar, as proximas ainda protegem.
+-------------------------------------------------------+
| CAMADA 1: allowFrom |
| (whitelist de usuarios) |
+-------------------------------------------------------+
|
+-------------------------------------------------------+
| CAMADA 2: Rate Limiting |
| (token bucket por sessao) |
+-------------------------------------------------------+
|
+-------------------------------------------------------+
| CAMADA 3: Restricao de Workspace |
| (tools so acessam ~/.nanobot/workspace/) |
+-------------------------------------------------------+
|
+-------------------------------------------------------+
| CAMADA 4: Validacao de Input |
| (paths, filenames, tamanhos, comandos) |
+-------------------------------------------------------+
|
+-------------------------------------------------------+
| CAMADA 5: Sanitizacao de Output |
| (erros, resultados de tools, stack traces) |
+-------------------------------------------------------+
|
+-------------------------------------------------------+
| CAMADA 6: Limites de Tamanho |
| (mensagens, arquivos, filas, sessoes) |
+-------------------------------------------------------+
|
+-------------------------------------------------------+
| CAMADA 7: Isolamento (Docker) |
| (non-root, rede restrita, volumes) |
+-------------------------------------------------------+
Camada 1: allowFrom (Whitelist de Usuarios)
O que faz
Cada channel tem uma lista allowFrom que define quais user IDs podem interagir com o bot. Se a lista estiver vazia, ninguem e aceito (deny all por padrao).
{
"channels": {
"telegram": {
"token": "...",
"allowFrom": ["123456789", "987654321"]
}
}
}
Mensagens de IDs fora da lista sao descartadas silenciosamente — sem resposta, sem log detalhado, sem confirmacao.
Camada 2: Rate Limiting
O que faz
Token bucket por sessao, impedindo abuso mesmo de usuarios autorizados. Cada operacao tem um balde independente. Veja a secao 3 para detalhes completos.
channel_message: 10/mintool_exec: 5/minweb_fetch: 20/minfile_write: 30/minsubagent_spawn: 3/5mincron_job: 10/hora
Camada 3: Restricao de Workspace
O que faz
Quando restrictToWorkspace esta ativo (padrao), todos os tools de filesystem (read_file, write_file, edit_file, list_dir) so podem acessar arquivos dentro de ~/.nanobot/workspace/. Tentativas de acessar /etc/passwd, ~/.ssh/, ou qualquer coisa fora do workspace sao bloqueadas.
O tool exec tambem roda com o diretorio de trabalho setado para o workspace.
Camada 4: Validacao de Input
O que faz
O modulo security/validators.py valida:
- Paths: bloqueia path traversal (
../../etc/passwd), symlinks maliciosos, paths absolutos fora do workspace - Filenames: bloqueia caracteres especiais, nomes reservados do sistema, nomes muito longos
- Comandos shell: bloqueia padroes perigosos (
rm -rf /,dd if=, fork bombs:(){ :|:,mkfs,> /dev/) e detecta injecao de comandos (;,&&,||,|, backticks,$()) - Tamanhos: verifica limites de mensagens (100KB), arquivos (10MB), imagens (5MB)
Camada 5: Sanitizacao de Output
O que faz
O modulo security/sanitize.py limpa resultados antes de enviá-los de volta:
- Erros: stack traces completos sao removidos e substituidos por mensagens genericas (nao vaza paths internos, nomes de modulos, etc.)
- Resultados de tools: saida de shell truncada em 10.000 caracteres
- Informacoes sensiveis: tokens, keys e passwords sao filtrados dos resultados
Camada 6: Limites de Tamanho
O que faz
Limites em todas as "bordas" do sistema para evitar abuso de recursos:
- Fila: 1000 mensagens max (RAM)
- Sessao: 1000 mensagens max (disco)
- Mensagem individual: 100KB
- Mensagem do channel: 50KB
- Arquivo read/write: 10MB
- Bootstrap file: 500KB
- Imagem: 5MB, max 5 por mensagem
- Anexo Discord: 20MB
- Saida shell: 10.000 chars
Camada 7: Isolamento Docker
O que faz
Quando rodando em Docker (recomendado para producao):
- Non-root: o container roda com um usuario sem privilegios
- Volumes restritos: apenas
~/.nanobot/e montado - Sem capabilities extras: nao tem acesso ao host
- Rede controlada: so precisa de acesso HTTPS de saida (APIs de LLM e channels)
Escrita atomica de arquivos
Como funciona a escrita atomica
Para evitar corrupcao de dados (ex: queda de energia no meio de uma escrita), o nanobot usa o padrao temp + rename:
1. Escreve o conteudo em um arquivo temporario (.tmp)
2. Faz fsync() para garantir que esta no disco
3. Renomeia o .tmp para o arquivo final (operacao atomica no filesystem)
Se der erro no passo 1 ou 2, o arquivo original nao e afetado.
O rename (passo 3) e atomico no Linux/macOS — ou o arquivo
antigo existe ou o novo, nunca um estado intermediario.
Isso e usado em: sessoes (.jsonl), config, cron jobs, e qualquer escrita do tool write_file.
7. Armazenamento
Todo o estado do nanobot vive dentro de ~/.nanobot/. Nao usa banco de dados, nao usa Redis, nao usa nada externo. Apenas arquivos no disco.
Estrutura de diretorios
~/.nanobot/
+-- config.json ← Configuracao principal
+-- gateway.log ← Log do gateway
+-- sessions/ ← Historico de conversas
| +-- telegram_123456789.jsonl
| +-- discord_987654321.jsonl
| +-- whatsapp_5511999998888.jsonl
+-- workspace/ ← Area de trabalho do agente
| +-- SOUL.md ← Personalidade do agente
| +-- AGENTS.md ← Instrucoes para agentes
| +-- USER.md ← Informacoes sobre o usuario
| +-- TOOLS.md ← Config de ferramentas
| +-- IDENTITY.md ← Identidade do agente
| +-- memory/
| | +-- MEMORY.md ← Memoria permanente
| | +-- 2026-02-10.md ← Notas diarias
| +-- ... (outros arquivos)
+-- cron/ ← Tarefas agendadas
| +-- jobs.json
+-- skills/ ← Skills instalados
+-- remotion/
| +-- SKILL.md
+-- github/
+-- SKILL.md
Detalhes de cada tipo de armazenamento
Sessoes (~/.nanobot/sessions/*.jsonl)
Formato JSONL
Cada arquivo de sessao usa o formato JSONL (JSON Lines) — uma linha JSON por mensagem. Isso permite append eficiente (adicionar sem reescrever o arquivo todo) e leitura linha por linha (sem carregar tudo na memoria).
{"_type":"metadata","created_at":"2026-02-10T05:30:00","session_key":"telegram:123456789"}
{"role":"user","content":"Oi!","timestamp":"2026-02-10T05:31:00"}
{"role":"assistant","content":"Ola! Como posso ajudar?","timestamp":"2026-02-10T05:31:05"}
{"role":"user","content":"Qual o clima em SP?","timestamp":"2026-02-10T05:32:00"}
{"role":"assistant","content":"28C, parcialmente nublado","timestamp":"2026-02-10T05:32:15"}
- Max 1000 mensagens por arquivo
- Max 100KB por mensagem individual
- Escrita atomica (temp + rename)
- Auto-deletado apos 30 dias sem acesso
- 100 sessoes em cache LRU na memoria
Configuracao (~/.nanobot/config.json)
Schema Pydantic
O arquivo de configuracao e validado pelo Pydantic v2 (config/schema.py). Se tiver campo errado, tipo errado, ou valor invalido, o nanobot recusa iniciar e mostra o erro exato.
{
"model": "openrouter/qwen/qwen3-coder-next",
"channels": {
"telegram": {
"token": "BOT_TOKEN_AQUI",
"allowFrom": ["123456789"]
}
},
"restrictToWorkspace": true
}
Workspace (~/.nanobot/workspace/)
Arquivos de bootstrap
Os arquivos na raiz do workspace sao os "bootstrap files" — carregados automaticamente no contexto do agente a cada mensagem. Sao eles que definem a personalidade, conhecimento e comportamento:
| Arquivo | Proposito |
|---|---|
SOUL.md | Personalidade principal, tom de voz, regras de comportamento |
AGENTS.md | Instrucoes especificas para agentes de IA |
USER.md | Informacoes sobre o usuario (nome, preferencias, contexto) |
TOOLS.md | Configuracao e restricoes de ferramentas |
IDENTITY.md | Nome e identidade visual do agente |
Limite: cada arquivo de bootstrap tem max 500KB. Arquivos maiores sao ignorados.
Cron (~/.nanobot/cron/)
Tarefas agendadas
Os jobs do cron sao salvos em jobs.json e persistidos no disco. O servico de cron (cron/service.py, 346 linhas) verifica a cada 10-30 segundos se ha tarefas para executar. Suporta tres tipos: every (intervalo), cron (expressao cron), e at (data/hora unica).
Log do gateway (~/.nanobot/gateway.log)
Log de operacao
Registra eventos do gateway: startup, shutdown, erros de channels, erros de provider, rate limits ativados, etc. Util para debug. Nao tem rotacao automatica — o usuario precisa gerenciar o tamanho.
Memoria (~/.nanobot/workspace/memory/)
Memoria persistente do agente
O agente pode gravar informacoes que quer lembrar entre sessoes:
MEMORY.md— Memoria permanente. O agente pode ler e editar livremente. Ideal para informacoes que nunca mudam (nome do usuario, preferencias, etc.)YYYY-MM-DD.md— Notas diarias. O contexto builder inclui automaticamente as notas dos ultimos 7 dias. Ideal para anotacoes temporarias, TODOs, lembretes.
O agente acessa a memoria via tools (read_file, write_file). Na proxima conversa, o context builder carrega a memoria automaticamente no prompt do sistema.
Nada de banco de dados
Por que so arquivos?
O nanobot e um assistente pessoal, nao um servidor de producao com milhares de usuarios. Para um unico usuario (ou um punhado), arquivos no disco sao:
- Simples: nao precisa instalar/configurar nada
- Transparentes: voce pode abrir e ler qualquer arquivo num editor de texto
- Portaveis: copiar
~/.nanobot/para outra maquina e voce tem tudo - Confiaveis: escrita atomica previne corrupcao
- Leves: sem dependencia externa, sem processo separado