Visao Geral
O nanobot funciona como um telefone com secretaria inteligente:
- Voce manda uma mensagem pelo Telegram (ou Discord, WhatsApp...)
- A mensagem entra numa fila de espera
- Um agente pega a mensagem, pensa (usando uma IA como GPT/Claude/Qwen), e pode usar ferramentas (ler arquivos, executar comandos, buscar na web)
- A resposta volta pela mesma fila e chega no seu Telegram
O codigo todo tem ~3.400 linhas e e dividido em 7 pecas principais que se conectam como engrenagens.
As 7 Pecas do Nanobot
+-----------------------------------------------------------+
| VOCE (Telegram, Discord...) |
+-----------------------------+-----------------------------+
|
(1) CHANNELS
(recebe e envia)
|
(2) MESSAGE BUS
(fila de espera)
|
(3) AGENT LOOP
(cerebro principal)
|
+-----------+-----------+
| | |
(4) PROVIDER (5) TOOLS (6) CONTEXT
(chama a IA) (faz coisas) (memoria)
| | |
+-----------+-----------+
|
(7) SESSIONS
(historico salvo)
1. Channels — A porta de entrada
Arquivo principal: nanobot/channels/telegram.py (e discord.py, whatsapp.py, etc.)
Pense no channel como um porteiro. Cada plataforma (Telegram, Discord, WhatsApp) tem seu porteiro, e todos fazem a mesma coisa:
- Ficam escutando — o Telegram usa "polling" (a cada poucos segundos pergunta "tem mensagem nova?")
- Verificam seguranca — confere se o remetente esta na lista
allowFrom. Se nao esta, ignora - Limitam velocidade — se alguem mandar 100 mensagens por segundo, bloqueia (rate limit)
- Padronizam — converte a mensagem de cada plataforma para um formato unico interno
O que o channel cria
InboundMessage(
channel = "telegram", # de onde veio
sender_id = "123456789", # quem mandou
chat_id = "123456789", # qual chat
content = "Oi, tudo bem?", # a mensagem
media = [], # fotos, audios...
)
Todos os channels herdam de BaseChannel — um "molde" que ja traz seguranca e rate limit prontos. Cada plataforma so precisa implementar como conectar e como enviar mensagem de volta.
Channels implementados
| Channel | Como recebe | Biblioteca |
|---|---|---|
| Telegram | Polling (pergunta a cada X segundos) | python-telegram-bot |
| WebSocket para bridge Node.js | baileys (JS) | |
| Discord | Gateway WebSocket | discord.py |
| Feishu | WebSocket longa conexao | SDK proprio |
| DingTalk | Stream mode | SDK proprio |
2. Message Bus — A fila de espera
Arquivo: nanobot/bus/queue.py (~60 linhas)
O bus e a peca mais simples: sao duas filas (como a fila do banco).
FILA DE ENTRADA (inbound): Channels colocam → Agent retira
FILA DE SAIDA (outbound): Agent coloca → Channels retiram
class MessageBus:
inbound = asyncio.Queue(maxsize=1000) # fila de entrada
outbound = asyncio.Queue(maxsize=1000) # fila de saida
Por que o bus existe?
Desacoplamento. O Telegram nao precisa esperar o agente responder. Ele joga a mensagem na fila e volta a escutar. O agente processa quando puder. Se chegarem 10 mensagens ao mesmo tempo, ficam na fila esperando a vez.
Se a fila encher (1000 mensagens), as novas sao descartadas com um aviso no log.
3. Agent Loop — O cerebro
Arquivo: nanobot/agent/loop.py
Este e o coracao do nanobot. Funciona assim:
LOOP INFINITO:
1. Pega proxima mensagem da fila de entrada
2. Busca o historico da conversa (sessao)
3. Monta o "contexto" (personalidade + memoria + skills)
4. Manda tudo para a IA (provider)
5. A IA responde com TEXTO ou pedido de FERRAMENTA
6. Se pediu ferramenta → executa → volta pro passo 4 com o resultado
7. Se respondeu com texto → salva no historico → coloca na fila de saida
Codigo simplificado
async def run(self):
while True:
msg = await bus.consume_inbound() # 1. espera mensagem
await self._process_message(msg) # 2-7. processa
async def _process_message(self, msg):
session = session_manager.get_or_create(msg.session_key)
messages = context_builder.build(session, msg)
for iteration in range(20): # max 20 rodadas
response = await provider.chat(messages, tools)
if response.tool_calls: # IA quer ferramenta?
for tool_call in response.tool_calls:
result = await tools.execute(
tool_call.name, tool_call.args
)
messages.append({
"role": "tool", "content": result
})
else: # IA respondeu texto
await bus.publish_outbound(OutboundMessage(
channel=msg.channel,
chat_id=msg.chat_id,
content=response.content
))
break
session.save()
Ponto-chave
A IA pode pedir para usar ferramentas, e o loop executa e devolve o resultado. Isso pode acontecer ate 20 vezes numa unica mensagem (por exemplo: a IA le um arquivo, depois executa um comando, depois busca na web, e finalmente responde).
4. Provider — A conexao com a IA
Arquivo: nanobot/providers/litellm_provider.py
O provider e quem faz a chamada para a API do modelo de linguagem (GPT, Claude, Qwen, etc.).
O nanobot usa uma biblioteca chamada LiteLLM que funciona como um "tradutor universal" — voce manda no formato OpenAI e ele traduz para qualquer provider:
class LiteLLMProvider:
async def chat(self, messages, tools, model):
response = await litellm.acompletion(
model = "openrouter/qwen/qwen3-coder-next",
messages = messages, # conversa inteira
tools = tools, # ferramentas disponiveis
temperature = 0.7,
max_tokens = 8192,
)
return LLMResponse(
content = response.content,
tool_calls = response.tool_calls,
)
O Registry de Providers
Arquivo: nanobot/providers/registry.py
Cada provider tem uma "spec" que diz como configurar:
ProviderSpec(
name="openrouter",
env_key="OPENROUTER_API_KEY",
keywords=("openrouter",),
is_gateway=True, # aceita qualquer modelo
detect_by_key_prefix="sk-or-", # detecta pelo prefixo da chave
)
Isso permite que adicionar um provider novo precise de apenas 2 mudancas (a spec + um campo na config).
Providers suportados
| Provider | Tipo | Modelos |
|---|---|---|
| OpenRouter | Gateway | Todos (Claude, GPT, Qwen, Llama...) |
| AiHubMix | Gateway | Todos |
| Anthropic | Direto | Claude |
| OpenAI | Direto | GPT |
| DeepSeek | Direto | DeepSeek |
| Groq | Direto | Llama, Whisper (voz) |
| Gemini | Direto | Gemini |
| Dashscope | Direto | Qwen |
| Moonshot | Direto | Kimi |
| vLLM | Local | Qualquer (servidor local) |
5. Tools — As ferramentas do agente
Arquivos: nanobot/agent/tools/ (6 arquivos)
Tools sao as "maos" do agente. Sem elas, ele so fala. Com elas, ele faz coisas.
Cada tool e uma classe Python:
class ReadFileTool(Tool):
name = "read_file"
description = "Read contents of a file"
parameters = {
"path": {"type": "string", "description": "File path"}
}
async def execute(self, path):
return open(path).read()
Tools disponiveis
| Tool | O que faz | Protecoes |
|---|---|---|
read_file | Le um arquivo (max 10MB) | Restrito ao workspace |
write_file | Cria/escreve arquivo | Restrito ao workspace |
edit_file | Edita linhas especificas | Restrito ao workspace |
list_dir | Lista diretorio | Restrito ao workspace |
exec | Executa comando no terminal | Bloqueia rm -rf, dd, fork bombs |
web_search | Busca na web (Brave Search) | Precisa de API key |
web_fetch | Le uma pagina web | Protecao SSRF |
message | Envia mensagem ao usuario | — |
spawn | Cria sub-agente background | Max 5 simultaneos |
cron | Agenda tarefas recorrentes | — |
Como a IA usa as tools
A IA recebe uma lista de "funcoes disponiveis" no formato JSON Schema. Quando decide usar uma, responde assim:
{
"tool_calls": [{
"id": "call_abc123",
"function": {
"name": "read_file",
"arguments": "{\"path\": \"/workspace/arquivo.txt\"}"
}
}]
}
O agent loop executa, pega o resultado, e manda de volta pra IA pra ela continuar pensando.
Seguranca do exec
O tool de terminal tem a protecao mais pesada:
# Padroes bloqueados:
BLOCKED = ["rm -rf /", "dd if=", ":(){ :|:", "mkfs", "> /dev/"]
# Injecao de comandos detectada:
INJECTION = [";", "&&", "||", "|", "`", "$("]
# Se restrict_to_workspace = true:
# Todo comando roda dentro de ~/.nanobot/workspace/ apenas
6. Context — Personalidade e Memoria
Arquivo: nanobot/agent/context.py
Quando o agente vai responder, ele precisa de contexto — quem ele e, o que sabe, o que o usuario ja disse. O ContextBuilder monta tudo isso num "system prompt".
O que vai no contexto (nesta ordem)
| # | Conteudo | Origem |
|---|---|---|
| 1 | Identidade — "Voce e o nanobot, assistente pessoal" | Hardcoded + data/hora |
| 2 | Bootstrap files — AGENTS.md, SOUL.md, USER.md | ~/.nanobot/workspace/ |
| 3 | Memoria — MEMORY.md + notas dos ultimos 7 dias | ~/.nanobot/workspace/memory/ |
| 4 | Skills — lista resumida ou conteudo completo | nanobot/skills/ |
| 5 | Historico — ultimas 50 mensagens deste chat | Sessao (.jsonl) |
| 6 | Mensagem atual do usuario | InboundMessage |
Memoria persistente
Arquivo: nanobot/agent/memory.py
~/.nanobot/workspace/memory/
+-- MEMORY.md ← Memoria permanente (agente pode editar)
+-- 2026-02-10.md ← Notas de hoje (agente pode adicionar)
O agente pode escrever na memoria usando o tool write_file. Na proxima conversa, o contexto inclui essas memorias automaticamente.
7. Sessions — Historico Salvo
Arquivo: nanobot/session/manager.py
Cada conversa e uma sessao identificada por channel:chat_id (ex: telegram:123456789).
As sessoes ficam salvas como arquivos .jsonl (uma linha JSON por mensagem):
~/.nanobot/sessions/
+-- telegram_123456789.jsonl
Conteudo do arquivo:
{"_type":"metadata","created_at":"2026-02-10T05:30:00"}
{"role":"user","content":"Oi!","timestamp":"2026-02-10T05:31:00"}
{"role":"assistant","content":"Ola! Como posso ajudar?","timestamp":"2026-02-10T05:31:05"}
Limites
| Limite | Valor |
|---|---|
| Mensagens por sessao | 1000 (descarta as mais antigas) |
| Tamanho max por mensagem | 100KB |
| Sessoes em cache na RAM | 100 (LRU — remove as menos usadas) |
| Validade | 30 dias (auto-deletadas) |
| Gravacao | Atomica (temp → rename, nunca corrompe) |
Pecas Extras
Cron — Tarefas agendadas
Arquivo: nanobot/cron/service.py
Um timer que roda a cada 10-30 segundos verificando se tem tarefa pra executar:
CronJob(
name = "bom dia",
schedule = CronSchedule(kind="cron", expr="0 9 * * *"), # todo dia 9h
payload = CronPayload(message="Diga bom dia!"),
)
Quando chega a hora, o cron manda a mensagem do job como se fosse um usuario → o agente processa normalmente → a resposta vai pro channel configurado.
Tipos de agendamento:
| Tipo | Exemplo | Descricao |
|---|---|---|
every | every_ms: 3600000 | Repete a cada N milissegundos (min 60s) |
cron | "0 9 * * *" | Expressao cron padrao |
at | at_ms: 1739145600000 | Uma vez na data/hora especifica |
Heartbeat — Batimento cardiaco
Arquivo: nanobot/heartbeat/service.py
A cada 30 minutos, o heartbeat "cutuca" o agente: "leia o HEARTBEAT.md e faca o que tiver la". Se nao tem nada, o agente responde HEARTBEAT_OK e volta a dormir.
Subagent — Tarefas em background
Arquivo: nanobot/agent/subagent.py
O agente pode "se clonar" pra fazer tarefas demoradas em paralelo:
Agente recebe: "pesquise sobre X e me mande um relatorio"
→ Cria subagent com a tarefa
→ Responde imediatamente: "Estou pesquisando em background!"
→ Subagent roda independente (com acesso a tools)
→ Quando termina, manda o resultado pro chat do usuario
Maximo 5 subagents simultaneos.
Skills — Habilidades do agente
Arquivo: nanobot/agent/skills.py
Skills sao pacotes de conhecimento em Markdown (SKILL.md) que o agente carrega conforme necessario:
- Skills always-on → conteudo completo no prompt
- Skills disponiveis → resumo no prompt, agente carrega via
read_filese precisar - Skills indisponiveis → listados com o que falta instalar
| Skill | Descricao | Requer |
|---|---|---|
| remotion | Criacao de videos em React (35 regras + 3 exemplos) | npx |
| agent-browser | Automacao de browser | agent-browser |
| github | GitHub via gh CLI | gh |
| summarize | Resumir URLs e videos | summarize |
| weather | Clima e previsao | — |
| tmux | Sessoes tmux | tmux |
| cron | Agendar lembretes | — |
| skill-creator | Criar novos skills | — |
Fluxo Completo: Da mensagem a resposta
Exemplo: voce digita "Qual o clima em SP?" no Telegram.
allowFrom? Sim. Cria InboundMessage.
bus.publish_inbound(msg) coloca na fila de entrada.
bus.consume_inbound() retira. Busca sessao telegram:123456789.
provider.chat() → API OpenRouter → qwen3-coder-next. IA responde: tool_call("web_fetch", url="wttr.in/Sao+Paulo")
web_fetch busca a pagina do wttr.in. Resultado: "Sao Paulo: 28 C, parcialmente nublado..."
.jsonl
bus.publish_outbound() coloca a resposta na fila de saida.
TelegramChannel.send() converte para HTML e envia via API do Telegram.
Tempo total
5-30 segundos dependendo da velocidade da IA e dos tools usados.
Tecnologias Utilizadas
| Parte | Tecnologia | Por que |
|---|---|---|
| Linguagem | Python 3.11+ | Async/await nativo, ecossistema IA |
| CLI | Typer + Rich | Interface bonita com cores |
| Async | asyncio | Multiplos channels simultaneos sem threads |
| LLM | LiteLLM | Suporta 100+ providers com uma API so |
| Config | Pydantic v2 | Validacao automatica do JSON |
| Sessoes | JSONL | Um arquivo por conversa, append-only |
| Telegram | python-telegram-bot | Oficial, bem mantida |
| Baileys (Node.js) | Unica opcao gratuita | |
| Seguranca | Rate limit + allowlist + workspace restriction | Defense in depth |
Por que so ~3.400 linhas?
O projeto faz escolhas minimalistas: usa LiteLLM em vez de implementar cada provider, usa asyncio.Queue em vez de Redis/RabbitMQ, e mantem cada componente com responsabilidade unica. Nada de frameworks pesados — so Python puro com algumas bibliotecas bem escolhidas.
Diagrama Completo
+-------------------------------------------------------------+
| PLATAFORMAS DO USUARIO |
| (Telegram, Discord, WhatsApp, Feishu, DingTalk) |
+-----------------------------+-------------------------------+
|
v
+---------------------------+
| CHANNEL LAYER |
| TelegramChannel |
| WhatsAppChannel |
| DiscordChannel |
| |
| - Polling / WebSockets |
| - Conversao de formato |
| - Seguranca (allowFrom) |
+------------+--------------+
|
+------------v--------------+
| MESSAGE BUS |
| |
| inbound.Queue ← channels |
| outbound.Queue → channels |
| |
+------------+--------------+
|
+------------v--------------+
| AGENT LOOP |
| |
| 1. consume_inbound() |
| 2. build_messages() |
| 3. while iteration < 20: |
| - LLM.chat() |
| - if tool_calls: |
| execute_tools() |
| - else: break |
| 4. save_session() |
| 5. publish_outbound() |
+------------+--------------+
|
+----------+-----------+-----------+----------+
| | | | |
+-----v----+ +--v------+ +-v--------+ +v-------+ +v---------+
| Provider | | Tools | | Context | |Sessions| | Skills |
| (LiteLLM)| | (6 tipos)| | Builder | |(.jsonl)| | (.md) |
+----------+ +---------+ +----------+ +--------+ +----------+
| | |
+-----v----+ +--v------+ +-v--------+
| OpenAI | | exec | | SOUL.md |
| Claude | | files | | AGENTS.md|
| Qwen | | web | | MEMORY.md|
| Llama | | message | | USER.md |
| ... | | spawn | | Skills |
+----------+ | cron | +----------+
+---------+
Mapa de Arquivos
Estrutura de diretorios do codigo-fonte
nanobot/ ← Pacote principal Python
+-- __init__.py
+-- __main__.py ← Entry point (python -m nanobot)
|
+-- cli/
| +-- commands.py ← Todos os comandos CLI (gateway, agent, status...)
|
+-- agent/ ← Cerebro do sistema
| +-- loop.py ← Motor principal (recebe msg, chama LLM, executa tools)
| +-- context.py ← Monta o prompt (bootstrap + memoria + skills + historico)
| +-- memory.py ← Memoria de longo prazo e notas diarias
| +-- skills.py ← Descoberta e carregamento de skills
| +-- subagent.py ← Gerencia subagentes em background (max 5)
| +-- tools/ ← Ferramentas do agente
| +-- registry.py ← Registro central de tools
| +-- base.py ← Classe base Tool
| +-- filesystem.py ← read_file, write_file, edit_file, list_dir
| +-- shell.py ← exec (executa comandos no terminal)
| +-- web.py ← web_search, web_fetch
| +-- message.py ← message (envia msg ao usuario)
| +-- spawn.py ← spawn (cria subagentes)
| +-- cron.py ← cron (agenda tarefas)
|
+-- bus/ ← Fila de mensagens
| +-- queue.py ← MessageBus (asyncio.Queue, max 1000)
| +-- events.py ← InboundMessage, OutboundMessage
|
+-- channels/ ← Portas de entrada (plataformas)
| +-- base.py ← BaseChannel (seguranca, rate limit)
| +-- manager.py ← Gerencia todos os channels ativos
| +-- telegram.py ← Canal Telegram (polling)
| +-- discord.py ← Canal Discord (WebSocket)
| +-- whatsapp.py ← Canal WhatsApp (bridge Node.js)
| +-- feishu.py ← Canal Feishu (WebSocket)
| +-- dingtalk.py ← Canal DingTalk (Stream)
|
+-- providers/ ← Conexao com modelos de IA
| +-- base.py ← Interface base do provider
| +-- registry.py ← Registro de providers (auto-detect)
| +-- litellm_provider.py ← Provider principal (LiteLLM)
| +-- transcription.py ← Transcricao de audio (Whisper/Groq)
|
+-- config/ ← Configuracao
| +-- schema.py ← Schema Pydantic v2
| +-- loader.py ← Carrega e valida config.json
|
+-- session/ ← Historico de conversas
| +-- manager.py ← SessionManager (JSONL, cache LRU)
|
+-- cron/ ← Tarefas agendadas
| +-- service.py ← Servico de cron (every, cron, at)
| +-- types.py ← Tipos: CronJob, CronSchedule, CronPayload
|
+-- heartbeat/ ← Wake-up periodico
| +-- service.py ← Heartbeat a cada 30 min
|
+-- security/ ← Seguranca
| +-- ratelimit.py ← Token bucket por sessao
| +-- validators.py ← Valida paths, filenames, comandos
| +-- sanitize.py ← Sanitiza erros e resultados
|
+-- utils/
+-- helpers.py ← Funcoes auxiliares
Tabela resumo por componente
| Componente | Arquivo | Responsabilidade |
|---|---|---|
| CLI / Entry | cli/commands.py | Todos os comandos, startup do gateway |
| Agent Core | agent/loop.py | Processamento de mensagens, loop de tools |
| Bus | bus/queue.py | Filas async de mensagens |
| Channels | channels/base.py, telegram.py... | Integracao com plataformas |
| LLM | providers/litellm_provider.py | Chamadas API, multi-provider |
| Sessions | session/manager.py | Persistencia de historico |
| Context | agent/context.py | Montagem do system prompt |
| Memory | agent/memory.py | Memoria de longo prazo e notas diarias |
| Tools | agent/tools/*.py | Capacidades do agente (6 arquivos) |
| Skills | agent/skills.py | Descoberta e carregamento |
| Config | config/schema.py, loader.py | Gerenciamento de configuracao |
| Cron | cron/service.py | Agendamento de tarefas |
| Heartbeat | heartbeat/service.py | Wake-up periodico |
| Subagents | agent/subagent.py | Tarefas em background |
| Seguranca | security/ratelimit.py, validators.py | Rate limit, validacao, sanitizacao |