Practical guide

Your own AI workspace, private by design.

An analysis of the open-source Odysseus used as a practical example: where to run it (your computer or your own cloud), what it can still send out, how to block it with the network, how to reach it, and how to give agents access with the least power.

O que eu fiz e o que não fiz. Li o código e a documentação do Odysseus (commit 0f996f5, de 2026-10-29, ramo dev), baixei as imagens oficiais, preparei os modelos num volume do Ollama e validei com docker compose config que o arquivo de isolamento desta nota funde corretamente com o Compose do projeto. Não executei o Odysseus. O ambiente em que escrevi a nota bloqueou a execução de código de um repositório externo, e eu não contornei isso. Por isso tudo que descrevo sobre o comportamento do app vem da leitura do código (cada afirmação cita o arquivo), e as atividades do laboratório marcadas como “não executada” são derivadas desse código e precisam ser conferidas por você. Quando algo foi de fato rodado, a nota diz.What I did and did not do. I read Odysseus's code and documentation (commit 0f996f5, dated 2026-10-29, dev branch), pulled the official images, prepared the models in an Ollama volume and checked with docker compose config that this note's isolation file merges correctly with the project's Compose. I did not run Odysseus. The environment I wrote the note in blocked running code from an external repository, and I did not work around that. So everything I describe about the app's behaviour comes from reading the code (each claim names the file), and the lab activities marked “not run” are derived from that code and need to be checked by you. Where something was actually run, the note says so.

O que significa “uma IA que não envia dados”What “an AI that does not send data” means

Muita gente resume privacidade a “rodar local”. Mas os dados que importam saem por quatro caminhos diferentes, e só o primeiro é o modelo:Many people reduce privacy to “run it locally”. But the data that matters leaves through four different paths, and only the first is the model:

CaminhoPathO que saiWhat leavesExemploExample
1. Inferência1. InferenceSeu prompt, o histórico e os trechos de documentos e e-mails que o app coloca no contexto.Your prompt, the history and the document and email excerpts the app puts into the context.Usar uma chave da OpenAI em vez de um modelo seu.Using an OpenAI key instead of a model of your own.
2. Ferramentas2. ToolsO que o agente manda para serviços externos ao usar uma ferramenta.What the agent sends to external services when it uses a tool.Uma busca na web, a previsão do tempo de uma cidade, um download de modelo.A web search, a city's weather forecast, a model download.
3. Telemetria3. TelemetryUso, erros e identificadores que o software envia ao autor.Usage, errors and identifiers the software sends to its author.Analytics, relatórios de falha.Analytics, crash reports.
4. Operação4. OperationsBackups, logs, imagens e contas na nuvem onde tudo roda.Backups, logs, images and the cloud account where it all runs.Um snapshot do disco; um log com o conteúdo dos prompts.A disk snapshot; a log containing prompt contents.
A sua infraestrutura (máquina, VPS ou nuvem sua)Your infrastructure (machine, VPS or your own cloud) Você navegador, celularbrowser, phone Odysseus agente e toolsagent and tools modelo Ollama, vLLM Chroma, ntfy memória, avisosmemory, alerts SearXNG busca privadaprivate search Pode sair da sua redeCan leave your network API de modelo OpenAI, Anthropic… GitHub, Hugging Face downloads, skills buscadores, e-mail consultas, IMAP/SMTPqueries, IMAP/SMTP linha cheia = fica dentro · tracejada = só se você ligarsolid = stays inside · dashed = only if you turn it on
Por padrão o app, o modelo local, a memória e a busca ficam na sua infraestrutura. Cada seta tracejada é uma saída que existe por causa de uma função que você escolhe usar.By default the app, the local model, memory and search stay in your infrastructure. Each dashed arrow is an exit that exists because of a feature you choose to use.

Daí a definição prática que a nota usa: uma IA “que não envia dados” é aquela em que você consegue listar, por função, para onde cada dado pode ir, e a rede impede o resto. A palavra importante é impede. Uma promessa de “sem telemetria” no README é uma afirmação sobre o código de hoje; um firewall é uma garantia independente do código. As duas coisas somam, mas só a segunda vale quando o código muda ou é enganado por um texto malicioso (veja agentes).Hence the practical definition the note uses: an AI “that does not send data” is one where you can list, per feature, where each piece of data may go, and the network blocks the rest. The key word is blocks. A “no telemetry” promise in the README is a statement about today's code; a firewall is a guarantee independent of the code. The two add up, but only the second holds when the code changes or is tricked by malicious text (see agents).

Nuvem também conta. Rodar numa VM sua em um provedor de nuvem mantém os dados fora de qualquer API de IA de terceiros, mas o provedor continua hospedando o disco. “Não envia dados” aqui significa nenhum serviço de IA de terceiros recebe seus dados, não “ninguém além de mim consegue tecnicamente ver”. Para dados regulados ou muito sensíveis, essa diferença importa e foge do escopo desta nota.The cloud counts too. Running on a VM of yours at a cloud provider keeps data away from any third-party AI API, but the provider still hosts the disk. “Does not send data” here means no third-party AI service receives your data, not “nobody but me can technically see it”. For regulated or highly sensitive data that difference matters and is outside this note's scope.

O exemplo: Odysseus, um workspace de IA auto-hospedadoThe example: Odysseus, a self-hosted AI workspace

Odysseus é descrito pelo próprio projeto como “um workspace de IA auto-hospedado para chat, agentes, pesquisa, documentos, e-mail, notas, calendário e fluxos com modelos locais”. Ele não é um modelo: é o aplicativo em volta do modelo (a interface, o agente, as ferramentas, a memória), que você liga a um modelo que roda em outro lugar. Dados do repositório na data da análise: licença AGPL-3.0-or-later, escrito em Python (FastAPI) com front-end em módulos ES sem bundler, criado em 2026-05-31, cerca de 95 mil estrelas, ramo padrão dev (o ramo main é o mais curado).Odysseus is described by the project itself as “a self-hosted AI workspace for chat, agents, research, documents, email, notes, calendar and local model workflows”. It is not a model: it is the application around the model (the interface, the agent, the tools, the memory), which you connect to a model running somewhere else. Repository facts at the time of analysis: AGPL-3.0-or-later license, written in Python (FastAPI) with a front-end of ES modules and no bundler, created 2026-05-31, about 95 thousand stars, default branch dev (the main branch is the more curated one).

FunçãoFeatureO que fazWhat it does
Chat + AgentsModelos locais ou de API, ferramentas, MCP, arquivos, shell, skills e memória.Local or API models, tools, MCP, files, shell, skills and memory.
CookbookRecomenda modelos conforme o seu hardware, baixa e serve (vLLM, llama.cpp e outros), inclusive em servidores remotos por SSH.Recommends models for your hardware, downloads and serves them (vLLM, llama.cpp and others), including on remote servers over SSH.
Deep ResearchPesquisa na web em várias etapas, com leitura das fontes e relatório.Multi-step web research with source reading and a report.
CompareTeste lado a lado e às cegas entre modelos.Blind side-by-side model testing.
Documentos, e-mail, notas, calendárioDocuments, email, notes, calendarEditor com edições por IA; caixa IMAP/SMTP com triagem e rascunhos; tarefas agendadas e CalDAV.Editor with AI edits; IMAP/SMTP inbox with triage and drafts; scheduled tasks and CalDAV.
navegador 127.0.0.1:7011 odysseus FastAPI :7000 chat, agente, e-mail,chat, agent, email, notas, documentosnotes, documents chromadb vetores · :8100vectors · :8100 searxng busca · :8080search · :8080 ntfy push · :8091 endpoint do modelo OpenAI-compatívelOpenAI-compatible data/ SQLite, chavesSQLite, keys tudo em 127.0.0.1all on 127.0.0.1 por padrãoby default
O Compose do projeto sobe quatro contêineres. O modelo fica fora dele: é qualquer endpoint compatível com a API da OpenAI (Ollama, vLLM, llama.cpp) ou um provedor.The project's Compose starts four containers. The model lives outside it: any OpenAI-compatible endpoint (Ollama, vLLM, llama.cpp) or a provider.

O docker-compose.yml do projeto sobe quatro serviços e liga todos a 127.0.0.1 por padrão. A interface fica em http://localhost:7011 (a porta 7000 é a de dentro do contêiner), e a senha do primeiro admin aparece em docker compose logs odysseus:The project's docker-compose.yml starts four services and binds all of them to 127.0.0.1 by default. The interface is at http://localhost:7011 (port 7000 is the one inside the container), and the first admin's password shows in docker compose logs odysseus:

git clone https://github.com/odysseus-dev/odysseus.git
cd odysseus
cp .env.example .env
docker compose up -d --build
# abra http://localhost:7011 ; a senha do admin está em: docker compose logs odysseus
ServiçoServicePapelRoleTamanho da imagem (medido ao baixar)Image size (measured on pull)
odysseusA aplicação (FastAPI, agente, ferramentas). Dados em data/: banco SQLite, chaves, uploads, memória.The application (FastAPI, agent, tools). Data in data/: SQLite database, keys, uploads, memory.3,7 GB
chromadbBanco vetorial para memória e RAG (a telemetria do Chroma vem desligada: ANONYMIZED_TELEMETRY=FALSE).Vector database for memory and RAG (Chroma telemetry ships off: ANONYMIZED_TELEMETRY=FALSE).826 MB
searxngMeta-buscador que você hospeda; é o que dá busca na web ao agente sem uma chave de API de busca.A metasearch engine you host; it gives the agent web search without a search API key.383 MB
ntfyServidor de notificações push (lembretes no celular).Push notification server (phone reminders).123 MB

As imagens são fixadas por digest no Compose (chromadb/chroma:1.5.9@sha256:…, e o mesmo para SearXNG e ntfy), o que é a prática certa da nota de Docker. Para o app, o README manda fixar uma tag imutável (ghcr.io/odysseus-dev/odysseus:1.0.2-7c8070f, formato X.Y.Z-<sha>) em vez de :latest, que muda a cada push.The images are pinned by digest in Compose (chromadb/chroma:1.5.9@sha256:…, and the same for SearXNG and ntfy), which is the right practice from the Docker note. For the app, the README says to pin an immutable tag (ghcr.io/odysseus-dev/odysseus:1.0.2-7c8070f, format X.Y.Z-<sha>) instead of :latest, which moves on every push.

Maturidade e licença: leia antes de dependerMaturity and license: read before you depend on it

  • Projeto jovem e rápido. O próprio ROADMAP.md é franco: diz que o projeto funciona bem para o autor, que “o navio está andando rápido” e que há cantos do código que é melhor evitar. A prioridade alta inclui “matar bugs”, testes de instalação limpa em Linux, macOS e Windows, e confiabilidade do Cookbook em outras máquinas. Trate como software em evolução, não como produto estável.Young, fast-moving project. The ROADMAP.md itself is frank: it says the project works well for its author, that “the ship is moving fast” and that some corners of the code are best avoided. High priority includes “squash bugs”, clean-install tests on Linux, macOS and Windows, and Cookbook reliability on other machines. Treat it as evolving software, not a stable product.
  • Modelo de confiança declarado. O THREAT_MODEL.md diz que o Odysseus é feito para usuários confiáveis numa rede privada, não para exposição pública, e que deve ser tratado “como um console de administrador”. Concordo e a nota segue essa linha: nada de porta aberta na internet.Declared trust model. THREAT_MODEL.md says Odysseus is built for trusted users on a private network, not public exposure, and should be treated “like an admin console”. I agree, and the note follows that line: no port open to the internet.
  • Lacunas conhecidas, listadas pelo projeto. A primeira é a mais importante para esta nota: não há sandbox de shell nem de sistema de arquivos; as ferramentas bash e read_file/write_file rodam com o usuário do processo, sem filtro de saída de rede nem confinamento. O documento pode estar parcialmente desatualizado (por exemplo, diz que os escopos de token são grosseiros, mas o código atual tem escopos por capacidade, veja agentes); confirme no commit que você for usar.Known gaps, listed by the project. The first is the most important for this note: there is no shell or file-system sandbox; the bash and read_file/write_file tools run as the process user, with no network egress filtering or confinement. The document may be partly out of date (for example, it says token scopes are coarse, but the current code has per-capability scopes, see agents); confirm on the commit you use.
  • AGPL-3.0. Para uso próprio, sem modificar, nada muda. Se você modificar o código e oferecer o serviço a outras pessoas pela rede, a licença exige oferecer a elas o código-fonte modificado. (Não é aconselhamento jurídico; leia a licença.)AGPL-3.0. For your own use, unmodified, nothing changes. If you modify the code and offer the service to other people over a network, the license requires offering them the modified source. (Not legal advice; read the license.)

Onde rodar: no seu computador ou numa nuvem suaWhere to run it: on your computer or in a cloud of yours

A decisão central é separar duas peças com custos opostos. O app (workspace, Chroma, SearXNG, ntfy) é leve: a documentação diz que “o app em si é leve; servir modelo local é a parte pesada”, e que hosts pequenos podem se conectar a uma API ou a servidores de modelo remotos. O modelo é o que pede memória de GPU, e o cálculo de quanto cabe está na nota de LLMs open-source.The central decision is to separate two parts with opposite costs. The app (workspace, Chroma, SearXNG, ntfy) is light: the documentation says “the app itself is lightweight; local model serving is the heavy part”, and that small hosts can connect to an API or to remote model servers. The model is what needs GPU memory, and the math of how much fits is in the open-source LLMs note.

A. Tudo na sua máquinaA. All on your machine notebook ou desktoplaptop or desktop B. App no VPS, modelo na GPUB. App on a VPS, model on a GPU dois servidores seustwo servers of yours C. Tudo numa VM com GPUC. All on one GPU VM um servidor na nuvemone server in the cloud Odysseus + serviços Chroma, SearXNG, ntfy Ollama no host (ou Docker)on the host (or Docker) simples · limitada pela GPUsimple · limited by GPU VPS pequeno: Odysseus sem GPUno GPU rede privadaprivate network GPU: vLLM ou Ollama só aceita o VPSaccepts the VPS only app 24h · GPU sob demandaapp 24/7 · GPU on demand Odysseus + serviços Chroma, SearXNG, ntfy vLLM / llama.cpp Cookbook, GPU overlay tudo junto · GPU cobra ligadaall in one · GPU bills while on
O app é leve; o modelo é o que pesa. Separar os dois permite um servidor barato para o workspace e uma GPU só quando precisar.The app is light; the model is the heavy part. Splitting them allows a cheap server for the workspace and a GPU only when needed.
Como ficaHow it looksCuidados específicosSpecific cautions
ACompose do projeto + Ollama no host. O .env.example sugere OLLAMA_BASE_URL=http://host.docker.internal:11434/v1.Project Compose + Ollama on the host. .env.example suggests OLLAMA_BASE_URL=http://host.docker.internal:11434/v1.Para o contêiner alcançar o Ollama do host, o Ollama precisa escutar em 0.0.0.0 (o próprio .env.example diz isso), o que o expõe à sua rede local: feche a porta 11434 no firewall. Em Mac com Apple Silicon o Docker não enxerga a GPU Metal; para acelerar, rode o Odysseus nativo (./start-macos.sh, porta 7860).For the container to reach the host's Ollama, Ollama must listen on 0.0.0.0 (.env.example itself says so), which exposes it to your local network: close port 11434 in the firewall. On an Apple Silicon Mac Docker cannot see the Metal GPU; to accelerate, run Odysseus natively (./start-macos.sh, port 7860).
BUm VPS barato roda o app e fala com o modelo numa GPU alugada por uma rede privada. A receita da GPU com túnel SSH está na nota de LLMs.A cheap VPS runs the app and talks to the model on a rented GPU over a private network. The GPU recipe with an SSH tunnel is in the LLMs note.O modelo não pode ter porta pública: aceite só o IP do VPS (ou use a rede privada do provedor, ou Tailscale). Você paga o VPS 24 h e a GPU só quando liga. O Cookbook também sabe controlar servidores remotos por SSH, com uma chave que o próprio app gera.The model must not have a public port: accept only the VPS IP (or use the provider's private network, or Tailscale). You pay for the VPS 24/7 and the GPU only when it is on. Cookbook can also drive remote servers over SSH, with a key the app itself generates.
CUma VM com GPU roda tudo. O projeto traz overlays docker-compose.gpu-nvidia.yml e docker-compose.gpu-amd.yml.One GPU VM runs everything. The project ships docker-compose.gpu-nvidia.yml and docker-compose.gpu-amd.yml overlays.Precisa do NVIDIA Container Toolkit no host. O overlay só expõe os dispositivos: a imagem enxuta ainda precisa instalar CUDA/ROCm via Cookbook → Dependencies antes de servir um modelo na GPU. Esqueça a GPU ligada e a fatura sobe.Needs the NVIDIA Container Toolkit on the host. The overlay only exposes the devices: the slim image still needs CUDA/ROCm installed through Cookbook → Dependencies before serving a model on the GPU. Forget the GPU on and the bill climbs.

Em qualquer das três, o Odysseus fala com o modelo por um endpoint: qualquer servidor com API compatível com a da OpenAI (Ollama, vLLM, llama.cpp, LM Studio) ou um provedor. Você cadastra o endpoint em Settings, ou o app o descobre sozinho: LLM_HOSTS lista hosts para ele varrer em portas comuns (11434 do Ollama, 8000 a 8020). Aqui está o ponto em que a privacidade se decide: um endpoint apontando para ollama:11434 mantém o prompt na sua infraestrutura; um endpoint de api.openai.com o envia para fora. O app trata os dois igual, e quem diferencia é você.In any of the three, Odysseus talks to the model through an endpoint: any server with an OpenAI-compatible API (Ollama, vLLM, llama.cpp, LM Studio) or a provider. You register the endpoint in Settings, or the app discovers it: LLM_HOSTS lists hosts for it to scan on common ports (Ollama's 11434, 8000 to 8020). This is where privacy is decided: an endpoint pointing to ollama:11434 keeps the prompt in your infrastructure; an endpoint at api.openai.com sends it out. The app treats both the same, and you are the one who tells them apart.

Modelo pequeno, agente pesado. O próprio roadmap lista como problema aberto que “o modo agente é pesado demais para modelos locais pequenos”: esquemas de ferramentas, skills, memória e instruções consomem o contexto antes de a sua pergunta chegar, principalmente em janelas de 4k a 16k. Um modelo de 0,5B serve para testar a ligação (é o que o laboratório usa), não para trabalho de agente. Para isso, escolha um modelo maior e com bom suporte a ferramentas.Small model, heavy agent. The roadmap itself lists as an open problem that “agent mode is too heavy for smaller local models”: tool schemas, skills, memory and instructions eat the context before your question arrives, especially on 4k to 16k windows. A 0.5B model is fine for testing the plumbing (it is what the lab uses), not for agent work. For that, pick a larger model with good tool-calling support.

O mapa de saída: o que o Odysseus pode mandar para foraThe egress map: what Odysseus can send out

Para responder “ele envia dados?” sem confiar em slogan, procurei no código do commit analisado (1) clientes de telemetria e (2) todos os endereços externos escritos nos arquivos Python do app. O resultado, por função:To answer “does it send data?” without trusting a slogan, I searched the analysed commit's code for (1) telemetry clients and (2) every external address written into the app's Python files. The result, per feature:

FunçãoFeaturePara onde vaiWhere it goesEvidênciaEvidenceComo conterHow to contain
Chat e agenteChat and agentPara o endpoint de modelo que você cadastrou. Local: fica. Provedor: sai.To the model endpoint you registered. Local: stays. Provider: leaves.src/llm_core.py: trata OpenAI-compatível, Ollama, Anthropic, Copilot e ChatGPT (assinatura)handles OpenAI-compatible, Ollama, Anthropic, Copilot and ChatGPT (subscription)Cadastre só endpoints seus e deixe OPENAI_API_KEY vazio.Register only endpoints of yours and leave OPENAI_API_KEY empty.
Busca na web, Deep ResearchWeb search, Deep ResearchO SearXNG repassa a consulta a buscadores públicos; as ferramentas web baixam as páginas. O Compose usa as configurações padrão do SearXNG.SearXNG forwards the query to public search engines; the web tools download the pages. The Compose uses SearXNG's default settings.config/searxng/settings.yml (use_default_settings: true); src/agent_tools/web_tools.pyDeixe a função desligada, ou aceite que a consulta (texto que o modelo escreve) sai. Veja o aviso abaixo.Leave the feature off, or accept that the query (text the model writes) leaves. See the warning below.
TempoWeatherA ferramenta de clima consulta geocoding-api.open-meteo.com e api.open-meteo.com com o nome do lugar.The weather tool queries geocoding-api.open-meteo.com and api.open-meteo.com with the place name.src/agent_tools/weather_tools.pyBloqueio de rede; ou não use a ferramenta.Network block; or do not use the tool.
Seletor de emojiEmoji pickerO servidor busca os SVGs em cdn.jsdelivr.net/npm/openmoji; a CSP também libera esse domínio para o navegador.The server fetches SVGs from cdn.jsdelivr.net/npm/openmoji; the CSP also allows that domain for the browser.routes/emoji_routes.py, core/middleware.pyUma saída “escondida” num detalhe de interface: só um bloqueio de rede a pega com certeza.An exit “hidden” in an interface detail: only a network block reliably catches it.
CookbookHugging Face (modelos), GitHub (releases do llama.cpp, receitas do vLLM).Hugging Face (models), GitHub (llama.cpp releases, vLLM recipes).routes/cookbook_routes.py, routes/cookbook_helpers.pySó quando você baixa ou serve um modelo. Baixe os pesos antes e depois corte a saída (veja o isolamento).Only when you download or serve a model. Download the weights beforehand and then cut egress (see isolation).
SkillsImportar uma skill de um repositório busca em github.com, api.github.com e raw.githubusercontent.com.Importing a skill from a repository fetches from github.com, api.github.com and raw.githubusercontent.com.services/memory/skill_importer.pySó na importação. Skills importadas são texto que o agente lê: trate como código não confiável.Only on import. Imported skills are text the agent reads: treat them as untrusted code.
EmbeddingsEmbeddingsSem EMBEDDING_URL, cai num modelo local fastembed que, segundo o .env.example, “baixa o modelo na primeira execução (~50 MB)”.Without EMBEDDING_URL, it falls back to a local fastembed model that, per .env.example, “downloads the model on first run (~50 MB)”..env.example, src/embedding_lanes.pyAponte EMBEDDING_URL para o seu Ollama (modelo all-minilm) e nada é baixado em tempo de execução.Point EMBEDDING_URL at your Ollama (the all-minilm model) and nothing is downloaded at run time.
E-mail e calendárioEmail and calendarIMAP/SMTP para o seu provedor; OAuth do Google se você conectar uma conta Google; CalDAV para o seu servidor.IMAP/SMTP to your provider; Google OAuth if you connect a Google account; CalDAV to your server.routes/email_routes.py, .env.exampleÉ o seu e-mail, que já está “fora”. O risco novo é o agente ler e escrever nele (veja agentes). E exige uma saída de rede para o provedor.It is your email, which is already “out there”. The new risk is the agent reading and writing it (see agents). It also needs an outbound path to the provider.
WebhooksVocê cadastra a URL; o app envia eventos para ela.You register the URL; the app sends events to it.routes/webhook/webhook_routes.pySó para onde você mandar.Only where you point it.
TelemetriaTelemetryNão encontrei nenhum cliente de analytics ou relatório de erros no código. O único ajuste relacionado é o ANONYMIZED_TELEMETRY=FALSE do Chroma, e a página do projeto afirma “no telemetry”.I found no analytics or error-reporting client in the code. The only related setting is Chroma's ANONYMIZED_TELEMETRY=FALSE, and the project page states “no telemetry”.Busca por sentry, posthog, mixpanel, gtag, amplitude, telemetry: sem clientesSearch for sentry, posthog, mixpanel, gtag, amplitude, telemetry: no clientsÉ leitura de código, não captura de tráfego. Confirme na atividade de captura.This is code reading, not traffic capture. Confirm it in the capture activity.

Os provedores de modelo que o código sabe chamar incluem OpenAI, Anthropic, Google (Gemini), DeepSeek, Groq, Together, Fireworks, Mistral, OpenRouter, Venice, Kimi, GitHub Copilot e a assinatura do ChatGPT. Nenhum é chamado sem você cadastrar uma chave ou conta; a lista mostra só o que fica a um clique de distância quando você abre a tela de endpoints.The model providers the code knows how to call include OpenAI, Anthropic, Google (Gemini), DeepSeek, Groq, Together, Fireworks, Mistral, OpenRouter, Venice, Kimi, GitHub Copilot and the ChatGPT subscription. None is called until you register a key or account; the list only shows what is one click away when you open the endpoints screen.

A busca na web é um canal de saída controlado pelo modelo. O texto da consulta é escrito pelo modelo, e o modelo lê conteúdo que pode conter instruções maliciosas (uma página, um e-mail). Um atacante pode induzi-lo a pôr dados seus na “busca”. É a razão de o projeto ter o portão de aprovação descrito na seção de agentes, e de eu tratar a busca como uma função que você liga de propósito, não como algo “privado porque o SearXNG é meu”: o SearXNG é seu, mas as consultas dele vão para os buscadores.Web search is a model-controlled exit channel. The query text is written by the model, and the model reads content that may contain malicious instructions (a page, an email). An attacker can induce it to put your data into the “search”. That is why the project has the approval gate described in the agents section, and why I treat search as a feature you turn on deliberately, not as “private because SearXNG is mine”: SearXNG is yours, but its queries go to the search engines.

Audite você mesmoAudit it yourself

Rodei versões destes comandos no clone do repositório; refaça no commit que você for usar, porque o projeto muda todo dia:I ran versions of these commands on the repository clone; redo them on the commit you will use, because the project changes every day:

# 1) clientes de telemetria no código do app
grep -rniE "sentry|posthog|mixpanel|google-analytics|gtag|amplitude" \
  --include=*.py --include=*.js src services routes core static | head

# 2) todos os hosts externos escritos nos arquivos Python do app
grep -rhoE "https?://[a-zA-Z0-9.-]+\.[a-z]{2,}" --include=*.py src services routes core app.py \
  | grep -vE "example\.|localhost|127\.0|w3\.org|schemas\." | sort | uniq -c | sort -rn | head -40

# 3) o portão que impede o agente de agir depois de ler conteúdo externo
grep -n "ODYSSEUS_TOOL_APPROVAL_GATE" -r src | head

Garantir pela rede: o isolamentoGuaranteeing it with the network: isolation

A tabela acima é o que o código faz. Para impedir, a camada certa é a rede. O Docker oferece isso de graça: uma rede marcada como internal: true não tem rota para fora. A ideia é a mesma da nota de Docker: todos os serviços numa rede interna e um único proxy, ligado às duas redes, como porta de entrada.The table above is what the code does. To prevent, the right layer is the network. Docker offers it for free: a network marked internal: true has no route out. The idea is the same as the Docker note: all services on an internal network and a single proxy, joined to both networks, as the front door.

internet ✗ sem rota✗ no route rede backend (internal: true)backend network (internal: true) você 127.0.0.1:7011 proxy nginx único com portaonly one with a port odysseus sem saídano outbound ollama chromadb searxng · ntfy o proxy também participa da rede frontend, a única com acesso ao hostthe proxy also joins the frontend network, the only one with host access
Todos os serviços ficam numa rede sem rota para fora. Só o proxy tem porta publicada, e só em 127.0.0.1.Every service sits on a network with no route out. Only the proxy has a published port, and only on 127.0.0.1.

O arquivo abaixo é um overlay do Compose: o Compose do projeto continua intacto e este arquivo, passado com um segundo -f, muda só o que precisa. Eu o validei com docker compose config (a fusão resulta em portas publicadas apenas no proxy, em 127.0.0.1:7011, e todos os outros serviços só na rede backend, que é internal).The file below is a Compose overlay: the project's Compose stays intact and this file, passed with a second -f, changes only what it must. I validated it with docker compose config (the merge results in ports published only on the proxy, at 127.0.0.1:7011, and every other service only on the backend network, which is internal).

# Overlay: everything on an internal network (no route to the internet),
# a local Ollama, and one reverse proxy as the only way in.
services:
  ollama:
    image: ollama/ollama:latest
    volumes:
      - ollama-data:/root/.ollama
    networks: [backend]
    restart: unless-stopped

  odysseus:
    ports: !reset []
    networks: !override [backend]
    environment:
      - OLLAMA_BASE_URL=http://ollama:11434/v1
      - EMBEDDING_URL=http://ollama:11434/v1/embeddings
      - EMBEDDING_MODEL=all-minilm
      - ALLOWED_ORIGINS=http://localhost:7011,http://127.0.0.1:7011

  chromadb:
    ports: !reset []
    networks: !override [backend]

  searxng:
    ports: !reset []
    networks: !override [backend]

  ntfy:
    ports: !reset []
    networks: !override [backend]

  proxy:
    image: nginx:alpine
    ports:
      - "127.0.0.1:7011:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    networks: [backend, frontend]
    depends_on: [odysseus]
    restart: unless-stopped

networks:
  backend:
    internal: true
  frontend: {}

volumes:
  ollama-data:
server {
  listen 80;
  client_max_body_size 50m;
  location / {
    proxy_pass http://odysseus:7000;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_buffering off;
    proxy_read_timeout 3600s;
  }
}
  • ports: !reset [] e networks: !override [backend] são marcadores do Compose: o primeiro apaga a lista de portas herdada do arquivo do projeto, o segundo substitui (em vez de somar) a lista de redes. Sem eles, a fusão manteria as portas publicadas e a rede padrão, que tem saída.ports: !reset [] and networks: !override [backend] are Compose markers: the first clears the port list inherited from the project's file, the second replaces (instead of adding to) the network list. Without them the merge would keep the published ports and the default network, which has egress.
  • Um contêiner que está só numa rede interna não consegue publicar porta. Eu testei isso com um nginx genérico: o -p não cria nenhum mapeamento e o curl falha (código 000). Por isso o proxy precisa estar na rede backend e na frontend; nesse arranjo o curl pelo proxy deu 200 e o contêiner interno não resolveu nenhum nome externo.A container that is only on an internal network cannot publish a port. I tested it with a generic nginx: -p creates no mapping and curl fails (code 000). That is why the proxy must be on the backend network and the frontend one; in that arrangement curl through the proxy gave 200 and the internal container resolved no external name.
  • O proxy tem saída. Por estar na rede frontend, ele alcança a internet. Mantenha-o um repassador simples, sem executar nada que venha do modelo. A garantia vale para o Odysseus, o Chroma, o SearXNG, o ntfy e o Ollama, não para o proxy.The proxy has egress. Being on the frontend network, it reaches the internet. Keep it a plain forwarder that runs nothing that comes from the model. The guarantee holds for Odysseus, Chroma, SearXNG, ntfy and Ollama, not for the proxy.
  • Baixe os pesos antes de cortar a rede. Rede interna significa que o ollama pull falha dentro dela. O laboratório baixa os modelos num volume com um contêiner temporário e só depois sobe o stack isolado. O EMBEDDING_URL aponta para o Ollama pelo mesmo motivo: sem ele o app tentaria baixar o modelo fastembed na primeira execução.Download the weights before cutting the network. An internal network means ollama pull fails inside it. The lab downloads the models into a volume with a temporary container and only then starts the isolated stack. EMBEDDING_URL points at Ollama for the same reason: without it the app would try to download the fastembed model on first run.
  • O que quebra, de propósito: busca na web e Deep Research (o SearXNG não alcança os buscadores), clima, emoji, Cookbook (downloads), importação de skills, o servidor MCP de navegador (instalado por npx), e e-mail com um provedor externo. Cada item é uma saída da tabela anterior que agora está impedida.What breaks, on purpose: web search and Deep Research (SearXNG cannot reach the search engines), weather, emoji, Cookbook (downloads), skill import, the browser MCP server (installed through npx) and email with an external provider. Each item is an exit from the previous table that is now blocked.
  • Quer busca? Ponha só o SearXNG também na rede frontend (ele ganha saída; o app continua sem). Assim a única saída do sistema é a consulta de busca, o que é fácil de auditar, e você sabe exatamente o que está aceitando.Want search? Put only SearXNG on the frontend network as well (it gains egress; the app still has none). Then the system's only exit is the search query, which is easy to audit, and you know exactly what you are accepting.

Outras camadas de bloqueioOther blocking layers

CamadaLayerComoHowQuando usarWhen to use
Rede do DockerDocker networkinternal: true + proxyO caso desta nota: tudo no mesmo host.This note's case: everything on one host.
Firewall do hostHost firewallRegras de saída para a ponte do Docker (cadeia DOCKER-USER do iptables), liberando só os destinos permitidos.Outbound rules for the Docker bridge (iptables DOCKER-USER chain), allowing only permitted destinations.Quando precisa de uma lista de permissão (por exemplo, só o seu servidor de e-mail).When you need an allowlist (for example, only your mail server).
NuvemCloudRegras de saída do security group / firewall da VPC; sub-rede privada sem NAT para o servidor do modelo.Outbound rules on the security group / VPC firewall; a private subnet with no NAT for the model server.Nas opções B e C da seção “onde rodar”.In options B and C of the “where to run” section.
Proxy de saídaEgress proxyUm proxy HTTP com lista de domínios; o app só conhece o proxy.An HTTP proxy with a domain list; the app only knows the proxy.Quando precisa de poucos destinos HTTP e quer registro de cada um.When you need a few HTTP destinations and want each one logged.
Por que a rede e não só a configuração. O THREAT_MODEL.md do projeto admite que as ferramentas de shell e de arquivos do agente rodam sem filtro de saída. Um admin com o shell ligado, enganado por um texto malicioso, pode fazer qualquer requisição que o processo consiga. Nenhum interruptor de configuração protege disso; um contêiner sem rota para fora, sim.Why the network and not just configuration. The project's THREAT_MODEL.md admits that the agent's shell and file tools run without egress filtering. An admin with the shell on, tricked by malicious text, can make any request the process can. No configuration switch protects against that; a container with no route out does.

Como se comunicar com elaHow to talk to it

A regra do projeto, repetida no README, no SECURITY.md e no guia de instalação: não exponha a porta do app à internet. Mantenha-a em 127.0.0.1 e chegue até ela por uma camada privada. As formas, da mais simples à mais completa:The project's rule, repeated in the README, in SECURITY.md and in the setup guide: do not expose the app's port to the internet. Keep it on 127.0.0.1 and reach it through a private layer. The ways, from the simplest to the most complete:

Quem falaWho talksComoHowDetalhesDetails
Você, no navegador, de longeYou, in a browser, remotelyTúnel SSH: ssh -N -L 7011:127.0.0.1:7011 usuario@servidor e abra http://localhost:7011.SSH tunnel: ssh -N -L 7011:127.0.0.1:7011 user@server and open http://localhost:7011.Nenhuma porta aberta além do SSH, e localhost conta como origem segura (o botão de copiar funciona).No open port besides SSH, and localhost counts as a secure origin (the copy button works).
Você e outros usuáriosYou and other usersTailscale, Cloudflare Access, VPN, ou um proxy reverso (Caddy, nginx, Traefik) com HTTPS na frente de 127.0.0.1:7011.Tailscale, Cloudflare Access, a VPN, or a reverse proxy (Caddy, nginx, Traefik) with HTTPS in front of 127.0.0.1:7011.Mantenha AUTH_ENABLED=true e LOCALHOST_BYPASS=false. Em HTTP puro por LAN, o navegador bloqueia o botão de copiar (só HTTPS e localhost são “origem segura”); o guia sugere mkcert para certificado local. Para redes lentas, HTTP/2 no proxy (Caddy) acelera o carregamento, porque o front-end faz centenas de requisições pequenas. Veja a nota de Cloudflare.Keep AUTH_ENABLED=true and LOCALHOST_BYPASS=false. Over plain HTTP on a LAN the browser blocks the copy button (only HTTPS and localhost are “secure origins”); the guide suggests mkcert for a local certificate. For slow networks, HTTP/2 at the proxy (Caddy) speeds up loading, because the front-end makes hundreds of small requests. See the Cloudflare note.
Seu celularYour phoneApp companion: o admin gera um token de pareamento de uso único (POST /api/companion/pair, só com cookie de admin).Companion app: the admin mints a one-time pairing token (POST /api/companion/pair, admin cookie only).O COMPANION_BASE_URL anunciado precisa ser um IP de LAN ou Tailscale. O cliente atual aceita só HTTP, sem nome público nem HTTPS.The advertised COMPANION_BASE_URL must be a LAN or Tailscale IP. The current client accepts only HTTP, with no public name and no HTTPS.
Um script ou outro programaA script or another programToken de API com escopo chat e POST /api/v1/chat.API token with the chat scope and POST /api/v1/chat.Veja abaixo.See below.
Alertas para vocêAlerts to yountfy (push) e webhooks (POST para uma URL sua).ntfy (push) and webhooks (POST to a URL of yours).Para o ntfy chegar ao celular, mude NTFY_BIND para o IP do host ou do Tailscale e acerte NTFY_BASE_URL.For ntfy to reach the phone, change NTFY_BIND to the host or Tailscale IP and set NTFY_BASE_URL accordingly.

Pela API: login, endpoint de modelo, token e chatThrough the API: login, model endpoint, token and chat

O fluxo abaixo vem da leitura das rotas (routes/auth_routes.py, routes/model_routes.py, routes/api_token_routes.py, routes/webhook/webhook_routes.py). Não foi executado por mim; confira cada resposta.The flow below comes from reading the routes (routes/auth_routes.py, routes/model_routes.py, routes/api_token_routes.py, routes/webhook/webhook_routes.py). I did not run it; check each response.

BASE=http://127.0.0.1:7011

# 1) login como admin (a senha inicial está em: docker compose logs odysseus)
curl -s -c cookies.txt -X POST $BASE/api/auth/login \
  -H 'content-type: application/json' \
  -d '{"username":"admin","password":"SENHA_DO_LOG"}'

# 2) cadastrar o Ollama como endpoint de modelo (só admin)
curl -s -b cookies.txt -X POST $BASE/api/model-endpoints \
  -d 'name=ollama' -d 'base_url=http://ollama:11434/v1'

# 3) criar um token com escopo "chat" (a resposta traz o token UMA vez: ody_...)
curl -s -b cookies.txt -X POST $BASE/api/tokens -d 'name=meu-script' -d 'scopes=chat'

# 4) conversar com o token, sem cookie
curl -s -X POST $BASE/api/v1/chat \
  -H "Authorization: Bearer $ODY_TOKEN" -H 'content-type: application/json' \
  -d '{"message":"Diga olá em cinco palavras","model":"qwen2.5:0.5b"}'
# -> {"response": "...", "session_id": "...", "model": "qwen2.5:0.5b"}
  • O token é gerado como ody_ + 32 bytes aleatórios, e o servidor guarda só o hash bcrypt: a resposta do passo 3 é a única vez que você vê o valor. Perdeu, crie outro e apague o antigo (DELETE /api/tokens/{id}).The token is generated as ody_ + 32 random bytes, and the server stores only the bcrypt hash: the step 3 response is the only time you see the value. Lost it, create another and delete the old one (DELETE /api/tokens/{id}).
  • O /api/v1/chat exige um token com escopo chat (um cookie de navegador não serve) e usa o primeiro endpoint habilitado do dono do token. Passar session retoma uma conversa; o código só deixa retomar sessões do próprio dono./api/v1/chat requires a token with the chat scope (a browser cookie will not do) and uses the first enabled endpoint of the token's owner. Passing session resumes a conversation; the code only lets you resume sessions of the owner.
  • O corpo aceita também base_url, api_key e provider, o que deixaria o chamador apontar o servidor para qualquer endpoint. O THREAT_MODEL.md lista isso como uma lacuna (SSRF) com correção em andamento; num setup privado, dê esse token só a quem você confiaria com o servidor, e confira no seu commit se a validação já entrou.The body also accepts base_url, api_key and provider, which would let the caller point the server at any endpoint. THREAT_MODEL.md lists this as a gap (SSRF) with a fix in progress; in a private setup, give that token only to whoever you would trust with the server, and check on your commit whether the validation has landed.

Dar acesso a agentes sem entregar a casaGiving agents access without handing over the house

“Agente” aqui tem dois sentidos, e o Odysseus trata os dois: o agente de dentro (o modo agente do chat, que usa as ferramentas do app) e os agentes de fora (Claude Code, Codex, o seu script) que usam o Odysseus como fonte de dados e ferramenta. Em ambos, a pergunta é a mesma: qual é o menor poder que resolve a tarefa? O projeto responde com camadas:“Agent” here has two meanings, and Odysseus handles both: the inside agent (the chat's agent mode, which uses the app's tools) and the outside agents (Claude Code, Codex, your script) that use Odysseus as a data source and tool. In both, the question is the same: what is the least power that does the job? The project answers with layers:

1 Rede privadaPrivate network SSH, Tailscale ou Cloudflare Access; nada exposto à internetSSH, Tailscale or Cloudflare Access; nothing exposed to the internet 2 Login e 2FALogin and 2FA AUTH_ENABLED=true, senha com bcrypt, TOTP, cadastro aberto desligadoAUTH_ENABLED=true, bcrypt password, TOTP, open signup off 3 PapelRole admin tem shell, arquivos, e-mail, MCP; usuário comum nãoadmin gets shell, files, email, MCP; a regular user does not 4 Token com escopoScoped token agentes externos entram por ody_…, com escopos como todos:readexternal agents come in with ody_… and scopes like todos:read 5 Contrato do turnoTurn contract só as ferramentas pedidas e permitidas são oferecidas ao modeloonly the requested and allowed tools are offered to the model 6 Portão de aprovaçãoApproval gate depois de conteúdo externo, ações com efeito pedem aprovaçãoafter external content, actions with effects need approval 7 Usuário do processoProcess user PUID/PGID no contêiner; docker.sock e pasta do host só por opt-inPUID/PGID in the container; docker.sock and host folder opt-in only
Cada camada reduz o que um agente (ou quem o enganar) consegue fazer. As duas destacadas são as específicas de agentes.Each layer reduces what an agent (or whoever tricks it) can do. The two highlighted ones are specific to agents.

Camada 3: papéisLayer 3: roles

CapacidadeCapabilityAdminUsuário comum (padrão)Regular user (default)
Chat com o agente, navegador, documentos, pesquisa, imagem, memóriaAgent chat, browser, documents, research, images, memory✓✓
Shell e Python, leitura e escrita de arquivosShell and Python, file read and write✓✗
Ler e enviar e-mail, calendárioRead and send email, calendar✓✗
Ferramentas MCP (mcp__*)MCP tools (mcp__*)✓✗
Tokens, webhooks, servir modelo, cofre, configuraçõesTokens, webhooks, model serving, vault, settings✓✗

Esta tabela é a do THREAT_MODEL.md, e o código a aplica por uma lista de bloqueio (NON_ADMIN_BLOCKED_TOOLS em src/tool_security.py) que inclui bash, python, read_file, write_file, manage_tokens, manage_mcp, serve_model e qualquer nome que comece com mcp__. Os comentários do arquivo explicam até as exclusões que parecem inocentes: ferramentas “somente leitura” do Cookbook também são bloqueadas, porque chamam rotas de admin com o token interno e dariam alcance de admin a um usuário comum. Regra prática: use sua conta de admin só para administrar, e uma conta comum para o trabalho diário com o agente.This table is the one in THREAT_MODEL.md, and the code applies it through a blocklist (NON_ADMIN_BLOCKED_TOOLS in src/tool_security.py) that includes bash, python, read_file, write_file, manage_tokens, manage_mcp, serve_model and any name starting with mcp__. The file's comments explain even the exclusions that look harmless: “read-only” Cookbook tools are blocked too, because they call admin routes with the internal token and would give a regular user admin reach. Practical rule: use your admin account only to administer, and a regular account for day-to-day agent work.

Camada 5: o contrato do turnoLayer 5: the turn contract

Para o agente de dentro, o docs/AGENT_TURN_CONTRACT.md descreve que, a cada turno, o servidor congela o conjunto de ferramentas oferecido ao modelo e confere que exigido ⊆ oferecido ⊆ executável: ligar o interruptor “Web” sozinho não seleciona as ferramentas web, e uma ferramenta oferecida ainda passa pelas políticas de execução e de dono. É o princípio de quanto menos ferramentas visíveis, menos o modelo consegue usar, aplicado turno a turno (e ainda ajuda a poupar contexto de modelos pequenos).For the inside agent, docs/AGENT_TURN_CONTRACT.md describes that, on each turn, the server freezes the set of tools offered to the model and checks that required ⊆ offered ⊆ executable: turning on the “Web” switch alone does not select the web tools, and an offered tool still passes through the execution and owner policies. It is the principle of the fewer tools visible, the less the model can use, applied turn by turn (and it also saves context on small models).

Camada 6: o portão depois de conteúdo externoLayer 6: the gate after external content

É a defesa mais específica contra o risco central dos agentes. Em src/tool_capabilities.py, cada ferramenta declara seus efeitos. Quando conteúdo não confiável entra na conversa (resultado de busca, página baixada, transcrição do YouTube, pesquisa), o servidor passa a bloquear as ferramentas com estes efeitos até o usuário autorizar à parte:It is the most specific defence against the central risk of agents. In src/tool_capabilities.py, each tool declares its effects. When untrusted content enters the conversation (a search result, a downloaded page, a YouTube transcript, research), the server starts blocking tools with these effects until the user authorises separately:

POST_EXTERNAL_BLOCKED_EFFECTS = frozenset({
    ToolEffect.READ_PRIVATE,      ToolEffect.WRITE_WORKSPACE,
    ToolEffect.WRITE_PRIVATE,     ToolEffect.EXECUTE_CODE,
    ToolEffect.NETWORK_EGRESS,    ToolEffect.EXTERNAL_SIDE_EFFECT,
    ToolEffect.UI_SIDE_EFFECT,    ToolEffect.ADMIN_CHANGE,
    ToolEffect.DESTRUCTIVE,
})

Em termos da nota de harness, isso quebra a “trifeta letal” (dados privados + conteúdo não confiável + saída para fora) no servidor, e não pedindo gentilmente ao modelo. Está ligado por padrão (ODYSSEUS_TOOL_APPROVAL_GATE=1); o comentário do código diz que, enquanto os processos do agente não rodarem sem rede, ele é “a única verificação” entre conteúdo injetado e essas ferramentas, e que desligar só é razoável em instalações de um usuário só que aceitam o risco. Há exceções deliberadas: metadados do próprio sistema (skills, o documento aberto, o e-mail aberto, anexos do chat) ainda permitem ferramentas de leitura. Não desligue para “o agente parar de perguntar”. Se ele pergunta demais, restrinja as ferramentas.In terms of the harness note, this breaks the “lethal trifecta” (private data + untrusted content + an outbound channel) in the server, rather than politely asking the model. It is on by default (ODYSSEUS_TOOL_APPROVAL_GATE=1); the code comment says that until agent processes run without network it is “the only check” between injected content and those tools, and that turning it off is only reasonable on single-user installs that accept the risk. There are deliberate exceptions: the system's own metadata (skills, the open document, the open email, chat attachments) still permits read tools. Do not turn it off to “make the agent stop asking”. If it asks too much, restrict the tools.

Camada 4: agentes de fora, com token de escopoLayer 4: outside agents, with a scoped token

Para o Claude Code ou o Codex usarem seu workspace, o Odysseus traz um fluxo pronto em Settings → Integrations: você adiciona um “Claude Agent” (ou Codex), o app gera um token, você liga só as ferramentas que esse agente pode usar e copia os comandos de configuração. O agente então usa duas variáveis de ambiente e uma skill baixada do próprio servidor:For Claude Code or Codex to use your workspace, Odysseus ships a ready flow in Settings → Integrations: you add a “Claude Agent” (or Codex), the app generates a token, you turn on only the tools that agent may use and copy the setup commands. The agent then uses two environment variables and a skill downloaded from the server itself:

export ODYSSEUS_URL=http://seu-servidor:7000
export ODYSSEUS_API_TOKEN=ody_token_gerado
curl -fsSL -H "Authorization: Bearer $ODYSSEUS_API_TOKEN" "$ODYSSEUS_URL/api/claude/plugin.zip" -o /tmp/odysseus-claude-skill.zip
python3 -m zipfile -e /tmp/odysseus-claude-skill.zip ~/.claude/
python3 ~/.claude/skills/odysseus/scripts/odysseus_api.py capabilities

O token carrega escopos. Em routes/api_token_routes.py a lista permitida é fechada, e o servidor recusa qualquer escopo fora dela:The token carries scopes. In routes/api_token_routes.py the allowed list is closed, and the server rejects any scope outside it:

ÁreaAreaEscoposScopes
Chatchat (o padrão)(the default)
Todostodos:read, todos:write
DocumentosDocumentsdocuments:read, documents:write
E-mailemail:read, email:draft, email:send
CalendárioCalendarcalendar:read, calendar:write
MemóriaMemorymemory:read, memory:write
Cookbookcookbook:read, cookbook:launch
  • Escrever implica ler. Pedir todos:write acrescenta todos:read sozinho (o mesmo para documentos, calendário, memória, rascunho de e-mail e cookbook:launch). Há perfis prontos, como codex_email_drafts (ler e-mail, rascunhar, documentos), que deliberadamente não inclui email:send: o agente prepara o rascunho e uma pessoa envia.Writing implies reading. Asking for todos:write adds todos:read by itself (the same for documents, calendar, memory, email draft and cookbook:launch). There are ready profiles, like codex_email_drafts (read email, draft, documents), which deliberately does not include email:send: the agent prepares the draft and a person sends it.
  • O servidor confere no caminho de cada chamada. O agente começa por GET /api/codex/capabilities para saber o que pode e, se tentar algo fora do escopo, recebe 403. O SKILL.md que o Odysseus entrega manda tratar o 403 como restrição intencional, sem contorná-la. (O nome /api/codex/* é histórico: é a API de agentes de todas as integrações.)The server checks on every call's path. The agent starts with GET /api/codex/capabilities to learn what it may do and, if it tries something outside the scope, gets 403. The SKILL.md Odysseus delivers says to treat 403 as an intentional restriction, without working around it. (The /api/codex/* name is historical: it is the agent API for all integrations.)
  • Token ≠ sessão de pessoa. Quando uma execução é dirigida por um token, o servidor a marca como “credencial delegada”: ferramentas privilegiadas são recusadas (“não está disponível para chamadores com token de API; exige uma sessão interativa”) e nenhuma aprovação levanta isso. As rotas de admin do Cookbook também não aceitam token (um token alcança só a superfície /api/codex/cookbook/*, com os escopos cookbook:*).Token ≠ a person's session. When a run is driven by a token, the server marks it a “delegated credential”: privileged tools are refused (“not available to API-token callers; requires an interactive session”) and no approval lifts that. The admin Cookbook routes do not accept tokens either (a token reaches only the /api/codex/cookbook/* surface, with the cookbook:* scopes).
  • Um token por integração, e apague os que sobram. É a recomendação do projeto, e permite revogar um agente sem afetar os outros. Se o usuário dono for apagado, os tokens dele são revogados junto.One token per integration, and delete the leftovers. It is the project's recommendation, and it lets you revoke one agent without affecting the others. If the owning user is deleted, their tokens are revoked with them.
  • O escopo protege a API, não o disco. O SKILL.md proíbe o agente de usar SSH, Docker, SQLite ou arquivos locais para ler dados do Odysseus, mas isso é uma instrução ao agente. Se o Claude Code roda na mesma máquina e consegue ler data/, o escopo do token não impede nada. Rode o agente externo em outro usuário ou outra máquina, sem acesso ao diretório de dados.The scope protects the API, not the disk. The SKILL.md forbids the agent from using SSH, Docker, SQLite or local files to read Odysseus data, but that is an instruction to the agent. If Claude Code runs on the same machine and can read data/, the token scope prevents nothing. Run the outside agent as another user or on another machine, without access to the data directory.

MCP: o Odysseus como clienteMCP: Odysseus as a client

O Odysseus também é um cliente MCP (veja a nota de MCP): o admin cadastra servidores MCP e as ferramentas deles aparecem para o agente com o prefixo mcp__, que só admins usam. O app registra servidores internos (mcp_servers/: e-mail, geração de imagem, memória e RAG), e o servidor MCP de navegador (@playwright/mcp) só entra se o pacote já estiver no cache do npx, o que exige um npx -y @playwright/mcp@latest --version prévio (cerca de 300 MB). Servidores MCP remotos com OAuth precisam do OAUTH_REDIRECT_BASE_URL correto atrás de proxy. Cada servidor MCP que você acrescenta é código com acesso: aplique à ferramenta dele o mesmo teste de menor poder.Odysseus is also an MCP client (see the MCP note): the admin registers MCP servers and their tools appear to the agent with the mcp__ prefix, which only admins use. The app registers internal servers (mcp_servers/: email, image generation, memory and RAG), and the browser MCP server (@playwright/mcp) only joins if the package is already in the npx cache, which needs a prior npx -y @playwright/mcp@latest --version (about 300 MB). Remote MCP servers with OAuth need the right OAUTH_REDIRECT_BASE_URL behind a proxy. Every MCP server you add is code with access: apply the same least-power test to its tools.

Camada 7: o que o agente toca no computadorLayer 7: what the agent touches on the computer

Por padrão o contêiner do Odysseus enxerga só seus volumes. Três sobreposições do Compose, todas desligadas por padrão e descritas no .env.example como “alta confiança”, ampliam o alcance. Você as liga com a variável COMPOSE_FILE:By default the Odysseus container sees only its volumes. Three Compose overlays, all off by default and described in .env.example as “high-trust”, widen the reach. You turn them on with the COMPOSE_FILE variable:

OverlayOverlayO que concedeWhat it grantsCuidadoCaution
docker/host-workspace.ymlMonta uma pasta do host, gravável, para o agente editar arquivos reais “como o Codex”.Mounts a host folder, writable, so the agent edits real files “like Codex”.O exemplo do .env.example usa ODYSSEUS_HOST_WORKSPACE_DIR=/home/you, a casa inteira. Use uma pasta de projeto, de preferência um clone descartável.The .env.example example uses ODYSSEUS_HOST_WORKSPACE_DIR=/home/you, the whole home. Use a project folder, preferably a disposable clone.
docker/host-docker.ymlMonta o docker.sock para o Cookbook gerenciar o Docker do host.Mounts docker.sock so Cookbook can manage the host's Docker.Quem controla o socket controla o host (veja a nota de Docker). O projeto prefere servidores remotos por SSH. Só ative com um motivo claro.Whoever controls the socket controls the host (see the Docker note). The project prefers remote servers over SSH. Enable only with a clear reason.
docker/host-network.ymlFaz o contêiner usar a rede do host (descoberta de LAN, estado da VPN).Makes the container use the host network (LAN discovery, VPN state).Desfaz o isolamento de rede da seção anterior. Incompatível com o objetivo desta nota.Undoes the previous section's network isolation. Incompatible with this note's goal.
# exemplo (do .env.example): o agente passa a editar uma pasta real do host
COMPOSE_FILE=docker-compose.yml:docker/host-workspace.yml
ODYSSEUS_HOST_WORKSPACE_DIR=/caminho/da/pasta-do-projeto   # NÃO a sua home
ODYSSEUS_HOST_WORKSPACE_MOUNT=/host/workspace

Tarefas agendadas e jobs em segundo planoScheduled tasks and background jobs

O Odysseus pode rodar tarefas agendadas de agente e jobs em segundo plano (um bloco bash que começa com #!bg vira um job destacado, com estado em data/bg_jobs/). Isso é o que faz um agente “trabalhar sozinho”, e também o que tira a pessoa do laço. A ação run_script usa o host em ODYSSEUS_SCRIPT_HOST (padrão: o próprio host do app). Para autonomia segura, combine três coisas: contas sem shell, o portão de aprovação ligado e a rede isolada, de modo que a pior tarefa agendada ainda não tenha para onde mandar dados.Odysseus can run scheduled agent tasks and background jobs (a bash block that starts with #!bg becomes a detached job, with state in data/bg_jobs/). That is what makes an agent “work on its own”, and also what takes the person out of the loop. The run_script action uses the host in ODYSSEUS_SCRIPT_HOST (default: the app's own host). For safe autonomy, combine three things: accounts without shell, the approval gate on and the isolated network, so that even the worst scheduled task has nowhere to send data.

Praticar na sua máquinaPractise on your machine

Pré-requisitos: Docker com Compose v2.24 ou mais (para os marcadores !reset e !override; testei com o Compose v5) e uns 15 GB livres: as imagens medidas foram 3,7 GB (app), 826 MB (Chroma), 383 MB (SearXNG), 123 MB (ntfy) e 8,5 GB (a imagem do Ollama, que já vem com CUDA). Cada atividade diz se eu a executei. As executadas têm a saída real; as outras trazem o resultado esperado segundo o código e valem como roteiro para você conferir.Prerequisites: Docker with Compose v2.24 or newer (for the !reset and !override markers; I tested on Compose v5) and about 15 GB free: the measured images were 3.7 GB (app), 826 MB (Chroma), 383 MB (SearXNG), 123 MB (ntfy) and 8.5 GB (the Ollama image, which ships with CUDA). Each activity says whether I ran it. The ones I ran show real output; the others give the expected result according to the code and serve as a script for you to check.

#AtividadeActivityExecutei?Did I run it?
0Montar a pasta do laboratórioSet up the lab folderSimYes
1Baixar os modelos para um volumeDownload the models into a volumeSimYes
2Validar a fusão dos arquivos do ComposeValidate the Compose file mergeSimYes
3Provar a rede isolada com um contêiner qualquerProve the isolated network with any containerSimYes
4Subir o Odysseus isolado e entrarStart the isolated Odysseus and sign inNãoNo
5Conversar pela API com um tokenChat through the API with a tokenNãoNo
6Um token com escopo mínimoA minimal-scope tokenNãoNo
7Capturar o que o app tenta alcançarCapture what the app tries to reachNãoNo
8Auditar o códigoAudit the codeSimYes
9LimparClean up—

Atividade 0: montar a pasta do laboratório (executada)Activity 0: set up the lab folder (run)

git clone --depth 1 https://github.com/odysseus-dev/odysseus.git src
mkdir -p lab/config/searxng lab/scripts && cd lab
cp ../src/docker-compose.yml .
cp ../src/config/searxng/settings.yml config/searxng/
cp ../src/scripts/migrate_searxng_settings.py scripts/
# crie aqui: compose.private.yml e nginx.conf (os da seção de isolamento)
printf 'COMPOSE_PROJECT_NAME=odylab\nAPP_PORT=7011\n' > .env

O projeto odylab dá nome aos volumes e redes (odylab_ollama-data, odylab_backend). Leia os arquivos que você copiou antes de subir qualquer coisa: é código de terceiros.The odylab project names the volumes and networks (odylab_ollama-data, odylab_backend). Read the files you copied before starting anything: it is third-party code.

Atividade 1: baixar os modelos para um volume (executada)Activity 1: download the models into a volume (run)

docker volume create odylab_ollama-data
docker run -d --name ollama-prep -v odylab_ollama-data:/root/.ollama ollama/ollama
docker exec ollama-prep ollama pull qwen2.5:0.5b     # modelo de chat minúsculo (397 MB)
docker exec ollama-prep ollama pull all-minilm       # embeddings (45 MB)
docker exec ollama-prep ollama list
# NAME                 ID              SIZE
# all-minilm:latest    1b226e2802db    45 MB
# qwen2.5:0.5b         a8b0c5157701    397 MB
docker rm -f ollama-prep

O que isso mostra: o contêiner temporário está na rede padrão, com internet, e só existe para encher o volume. Depois o Ollama do stack isolado monta o mesmo volume e nunca precisa de internet. É o padrão “baixe, depois corte a rede”.What this shows: the temporary container is on the default network, with internet, and exists only to fill the volume. Afterwards the isolated stack's Ollama mounts the same volume and never needs the internet. It is the “download, then cut the network” pattern.

Atividade 2: validar a fusão dos arquivos do Compose (executada)Activity 2: validate the Compose file merge (run)

export ODYSSEUS_TTS_CACHE_MAX_BYTES=524288000   # o Compose do projeto lê essa variável sem padrão
docker compose -f docker-compose.yml -f compose.private.yml config \
 | python3 -c "
import sys,yaml
d=yaml.safe_load(sys.stdin)
for n,s in d['services'].items():
    print(n, 'ports=',[(p.get('host_ip'),p.get('published'),p['target']) for p in s.get('ports',[])], 'networks=',list(s.get('networks',{})))
print(d['networks'])"
# chromadb ports= [] networks= ['backend']
# ntfy ports= [] networks= ['backend']
# odysseus ports= [] networks= ['backend']
# ollama ports= [] networks= ['backend']
# proxy ports= [('127.0.0.1', '7011', 80)] networks= ['backend', 'frontend']
# searxng ports= [] networks= ['backend']
# {'backend': {'name': 'odylab_backend', 'internal': True}, 'frontend': {'name': 'odylab_frontend'}}

Resultado: o config só lê e funde os arquivos, sem iniciar nada. Ele mostra o que importa: uma única porta publicada (a do proxy, em 127.0.0.1) e todos os serviços na rede interna. Se aparecer porta em outro serviço, o overlay não está agindo.Result: config only reads and merges the files, starting nothing. It shows what matters: a single published port (the proxy's, on 127.0.0.1) and every service on the internal network. If a port shows on another service, the overlay is not taking effect.

Atividade 3: provar a rede isolada com um contêiner qualquer (executada)Activity 3: prove the isolated network with any container (run)

docker network create --internal t-int ; docker network create t-front

# (a) só na rede interna: -p não publica nada
docker run -d --name t-only --network t-int -p 127.0.0.1:18081:80 nginx:alpine
curl -s -o /dev/null -m 3 -w "%{http_code}\n" http://127.0.0.1:18081/      # 000

# (b) proxy nas duas redes, app só na interna
docker run -d --name t-app   --network t-int nginx:alpine
docker run -d --name t-proxy --network t-int -p 127.0.0.1:18082:80 nginx:alpine
docker network connect t-front t-proxy
# (configure o proxy_pass para t-app, como no nginx.conf do overlay)
curl -s -o /dev/null -w "via proxy: %{http_code}\n" http://127.0.0.1:18082/ # 200

# (c) quem tem saída
docker exec t-app   wget -q -T3 -O- http://example.com   # wget: bad address 'example.com'
docker exec t-proxy wget -q -T4 -O- http://example.com   # <!doctype html>...

docker rm -f t-only t-app t-proxy ; docker network rm t-int t-front

Resultado real: 000 no caso (a), 200 pelo proxy e “bad address” no app, enquanto o proxy alcançou a internet. É a mesma topologia do overlay, com contêineres que você pode confiar.Real result: 000 in case (a), 200 through the proxy and “bad address” on the app, while the proxy reached the internet. It is the same topology as the overlay, with containers you can trust.

Atividade 4: subir o Odysseus isolado e entrar (não executada)Activity 4: start the isolated Odysseus and sign in (not run)

docker compose -f docker-compose.yml -f compose.private.yml up -d
docker compose -f docker-compose.yml -f compose.private.yml ps
docker compose -f docker-compose.yml -f compose.private.yml logs odysseus | grep -i password
# abra http://localhost:7011 e entre como admin com a senha do log

Esperado: o app depende do SearXNG saudável (depends_on: service_healthy) e do Chroma; o SearXNG tem healthcheck local, então deve ficar saudável mesmo sem internet. Em Settings, cadastre o endpoint http://ollama:11434/v1 e escolha qwen2.5:0.5b. Se o endpoint não aparecer sozinho, é o passo 2 da seção da API. Troque a senha inicial e ligue o 2FA.Expected: the app depends on a healthy SearXNG (depends_on: service_healthy) and on Chroma; SearXNG has a local healthcheck, so it should become healthy even with no internet. In Settings, register the endpoint http://ollama:11434/v1 and pick qwen2.5:0.5b. If the endpoint does not show up by itself, that is step 2 of the API section. Change the initial password and turn on 2FA.

Atividade 5: conversar pela API com um token (não executada)Activity 5: chat through the API with a token (not run)

Faça os quatro curl da seção “Pela API”. Esperado: o passo 3 devolve um ody_… uma única vez; o passo 4 devolve {"response": …, "session_id": …, "model": "qwen2.5:0.5b"}, com a resposta de um modelo que roda no seu volume, sem nenhuma saída de rede. Repita o passo 4 sem o cabeçalho Authorization e confirme o 401, e com um token sem o escopo chat e confirme o 403.Run the four curl calls in the “Through the API” section. Expected: step 3 returns an ody_… once; step 4 returns {"response": …, "session_id": …, "model": "qwen2.5:0.5b"}, with the answer from a model running in your volume, with no network egress. Repeat step 4 without the Authorization header and confirm 401, and with a token without the chat scope and confirm 403.

Atividade 6: um token com escopo mínimo (não executada)Activity 6: a minimal-scope token (not run)

curl -s -b cookies.txt -X POST $BASE/api/tokens -d 'name=so-todos' -d 'scopes=todos:read'
export ODY_READ=ody_...
curl -s -H "Authorization: Bearer $ODY_READ" $BASE/api/codex/capabilities
curl -s -H "Authorization: Bearer $ODY_READ" $BASE/api/codex/todos            # permitido
curl -s -o /dev/null -w "%{http_code}\n" -H "Authorization: Bearer $ODY_READ" \
  "$BASE/api/codex/emails?folder=INBOX&limit=5"                               # esperado: 403

Esperado: as duas primeiras respondem, a terceira devolve 403 (o token não tem email:read). Depois acrescente email:read com PATCH /api/tokens/{id} e veja o 403 sumir. É o ciclo “comece com o mínimo e conceda sob demanda”.Expected: the first two answer, the third returns 403 (the token lacks email:read). Then add email:read with PATCH /api/tokens/{id} and watch the 403 go away. It is the “start with the minimum and grant on demand” cycle.

Atividade 7: capturar o que o app tenta alcançar (não executada)Activity 7: capture what the app tries to reach (not run)

docker pull nicolaka/netshoot          # antes: o contêiner do passo seguinte compartilha a rede do alvo

# anexa um capturador ao namespace de rede do contêiner do app e lista DNS e novas conexões
docker run --rm --net container:odylab-odysseus-1 --cap-add NET_RAW nicolaka/netshoot \
  tcpdump -ni any -c 300 'udp port 53 or (tcp[tcpflags] & tcp-syn != 0 and not dst net 172.16.0.0/12)'

Esperado: no stack isolado, o app só tenta alcançar nomes da rede interna (ollama, chromadb, searxng) e as tentativas externas aparecem como consultas DNS sem resposta. Rode o mesmo comando no Compose padrão do projeto (sem o overlay) e anote cada nome externo que aparecer em repouso e durante um chat com o modelo local: é a sua medição da telemetria e das saídas “escondidas”, que a leitura de código só sugere (a lista de hosts da tabela de saída é o que você deve esperar ver, ou não ver).Expected: on the isolated stack, the app only tries to reach internal-network names (ollama, chromadb, searxng) and external attempts show up as unanswered DNS queries. Run the same command on the project's default Compose (without the overlay) and note every external name that appears at idle and during a chat with the local model: that is your measurement of telemetry and “hidden” exits, which code reading only suggests (the host list in the egress table is what you should expect to see, or not see).

Atividade 8: auditar o código (executada)Activity 8: audit the code (run)

Rode os três grep da seção do mapa de saída no clone. Resultado real que obtive: nenhum cliente de analytics; o ANONYMIZED_TELEMETRY=FALSE do Chroma nos três Compose; e a lista de hosts externos com, no topo, huggingface.co (14 menções), www.youtube.com (12), github.com (12), ollama.com (8), chatgpt.com (7) e os provedores de API, além de api.open-meteo.com e cdn.jsdelivr.net. Cada um corresponde a uma linha da tabela. Pergunte-se de novo, para cada um: eu uso essa função?Run the three grep commands from the egress map section on the clone. The real result I got: no analytics client; Chroma's ANONYMIZED_TELEMETRY=FALSE in all three Compose files; and the list of external hosts with, at the top, huggingface.co (14 mentions), www.youtube.com (12), github.com (12), ollama.com (8), chatgpt.com (7) and the API providers, plus api.open-meteo.com and cdn.jsdelivr.net. Each one matches a row of the table. Ask yourself again, for each: do I use that feature?

Atividade 9: limparActivity 9: clean up

docker compose -f docker-compose.yml -f compose.private.yml down -v   # contêineres, redes e volumes do stack
docker volume rm odylab_ollama-data                                    # os modelos
docker rmi ghcr.io/odysseus-dev/odysseus:latest ollama/ollama:latest  # imagens grandes, se não vai reutilizar
rm -rf src lab                                                         # o clone e os arquivos

Checklist: uma IA privada de verdadeChecklist: a truly private AI

ÁreaAreaFaçaDo
ModeloModelSó endpoints seus (Ollama, vLLM, llama.cpp). Nenhuma chave de provedor preenchida. Porta do modelo fechada ao mundo (só o app ou o túnel alcançam).Only endpoints of yours (Ollama, vLLM, llama.cpp). No provider key filled in. Model port closed to the world (only the app or the tunnel reach it).
RedeNetworkServiços em rede internal; um proxy como única porta; APP_BIND=127.0.0.1; acesso por SSH, Tailscale ou Cloudflare Access; na nuvem, saída negada por padrão no security group.Services on an internal network; one proxy as the only port; APP_BIND=127.0.0.1; access through SSH, Tailscale or Cloudflare Access; in the cloud, outbound denied by default on the security group.
Funções com saídaFeatures with egressListe as que você usa (busca, e-mail, clima, downloads) e dê saída só a elas, uma a uma. O resto fica fechado.List the ones you use (search, email, weather, downloads) and give egress to those only, one by one. The rest stays closed.
ContasAccountsAUTH_ENABLED=true, LOCALHOST_BYPASS=false, senha inicial trocada, 2FA no admin, cadastro aberto desligado, conta comum para o trabalho diário.AUTH_ENABLED=true, LOCALHOST_BYPASS=false, initial password changed, 2FA on the admin, open signup off, a regular account for daily work.
AgentesAgentsUm token por integração, escopos mínimos, sem email:send; portão de aprovação ligado; sem overlays de host; agente externo em outro usuário ou máquina.One token per integration, minimal scopes, no email:send; approval gate on; no host overlays; outside agent on another user or machine.
DadosDatadata/ guarda o banco, a chave de criptografia (.app_key) e o cofre. O snapshot do odysseus-backup inclui tudo isso: trate-o como senha e guarde-o cifrado. Deixe ODYSSEUS_CAPTURE_MODEL_REQUESTS=0 (o padrão do Compose), para o app não gravar os pedidos ao modelo.data/ holds the database, the encryption key (.app_key) and the vault. The odysseus-backup snapshot includes all of it: treat it like a password and store it encrypted. Keep ODYSSEUS_CAPTURE_MODEL_REQUESTS=0 (the Compose default) so the app does not record the requests to the model.
VersãoVersionTag imutável da imagem (X.Y.Z-<sha>), digests nos outros serviços, requirements sem versão fixa só em instalação nativa (use uv pip compile para travar). Leia o histórico antes de atualizar: o ramo dev muda todo dia. Teste a restauração do backup uma vez.Immutable image tag (X.Y.Z-<sha>), digests on the other services, unpinned requirements only on native installs (use uv pip compile to lock). Read the history before upgrading: the dev branch changes every day. Test restoring the backup once.

Quando não usarWhen not to use it

  • Se você precisa de um produto estável, com suporte e SLA, e não de software em evolução rápida.If you need a stable, supported product with an SLA, not fast-moving software.
  • Se o servidor tem que ficar aberto à internet, para pessoas que você não controla: o projeto diz, com todas as letras, que não foi feito para isso.If the server must be open to the internet, for people you do not control: the project says outright that it was not built for that.
  • Se você precisa de confinamento forte do agente com shell: o projeto admite que não há sandbox. Use uma VM descartável ou desligue o shell.If you need strong confinement of a shell-enabled agent: the project admits there is no sandbox. Use a disposable VM or turn the shell off.
  • Se o seu hardware só roda modelos pequenos demais para o modo agente: um bom chat local pode ser tudo de que você precisa, e então um front-end mais simples (por exemplo, o do próprio Ollama) é suficiente.If your hardware only runs models too small for agent mode: a good local chat may be all you need, and then a simpler front-end (for example, Ollama's own) is enough.

Quando algo dá erradoWhen something goes wrong

SintomaSymptomCausa provávelLikely causeO que fazerWhat to do
O app não acha o Ollama do hostThe app cannot find the host's OllamaO Ollama escuta só em 127.0.0.1; de dentro do contêiner, o host é host.docker.internalOllama listens only on 127.0.0.1; from inside the container, the host is host.docker.internalSuba o Ollama com OLLAMA_HOST=0.0.0.0:11434 e feche a porta no firewall; ou rode o Ollama como serviço do próprio Compose (como no overlay)Start Ollama with OLLAMA_HOST=0.0.0.0:11434 and close the port in the firewall; or run Ollama as a service in the same Compose (as in the overlay)
O app não sobe e fica esperandoThe app does not start and keeps waitingO SearXNG não ficou saudável (o app depende dele); o Compose do projeto já teve uma imagem latest quebrada por issoSearXNG did not become healthy (the app depends on it); the project's Compose once had a broken latest image because of thisdocker compose logs searxng; use os digests fixados do Composeuse the Compose's pinned digests
Login falha ou volta ao formulário atrás de proxyLogin fails or returns to the form behind a proxyALLOWED_ORIGINS não inclui a origem do navegador, ou o cookie Secure não combina com o esquema que o app enxergaALLOWED_ORIGINS does not include the browser's origin, or the Secure cookie does not match the scheme the app seesListe a origem exata em ALLOWED_ORIGINS; ajuste SECURE_COOKIES só se o proxy esconde o HTTPSList the exact origin in ALLOWED_ORIGINS; adjust SECURE_COOKIES only if the proxy hides the HTTPS
403 de um agente externofrom an outside agentO escopo do token não cobre a ferramenta (é o comportamento esperado)The token scope does not cover the tool (the expected behaviour)Confira /api/codex/capabilities; conceda o escopo mínimo necessário, nunca contorneCheck /api/codex/capabilities; grant the minimum scope needed, never work around it
O agente “pede aprovação” o tempo todoThe agent “asks for approval” all the timeO portão depois de conteúdo externo está agindo, e a conversa tem busca ou páginasThe post-external-content gate is working, and the conversation has search or pagesRestrinja as ferramentas do turno em vez de desligar o portãoRestrict the turn's tools instead of turning the gate off
Resposta ruim ou agente “perdido”Poor answers or a “lost” agentModelo pequeno demais; contexto consumido pelos esquemas de ferramentasModel too small; context eaten by tool schemasModelo maior, menos ferramentas por turno, janela de contexto maiorLarger model, fewer tools per turn, larger context window

Cola e revisãoCheat sheet and review

ColaCheat sheet

AssuntoTopicRegraRule
“Não envia dados”“Does not send data”Quatro caminhos: inferência, ferramentas, telemetria e operação. Liste cada um por função e deixe a rede impedir o resto.Four paths: inference, tools, telemetry and operations. List each per feature and let the network block the rest.
OdysseusWorkspace em volta do modelo: app + Chroma + SearXNG + ntfy; modelo é um endpoint à parte; AGPL-3.0; jovem e rápido; “console de admin”, não para a internet.Workspace around the model: app + Chroma + SearXNG + ntfy; the model is a separate endpoint; AGPL-3.0; young and fast; an “admin console”, not for the internet.
Onde rodarWhere to runApp leve, modelo pesado: separe. Notebook, VPS + GPU sob demanda, ou VM com GPU. Modelo sem porta pública.Light app, heavy model: split them. Laptop, VPS + on-demand GPU, or a GPU VM. Model with no public port.
SaídasExitsProvedor de modelo, busca, clima, emoji (CDN), Cookbook, skills, e-mail, webhooks. Telemetria: não encontrada no código (confirme por captura).Model provider, search, weather, emoji (CDN), Cookbook, skills, email, webhooks. Telemetry: not found in the code (confirm by capture).
IsolamentoIsolationinternal: true + proxy nas duas redes; !reset/!override no overlay; baixe os pesos antes; o proxy tem saída.internal: true + proxy on both networks; !reset/!override in the overlay; download weights first; the proxy has egress.
AcessoAccessLoopback + túnel SSH, Tailscale ou Cloudflare Access; HTTPS na frente; app de celular por pareamento (LAN/Tailscale); scripts por token chat + POST /api/v1/chat.Loopback + SSH tunnel, Tailscale or Cloudflare Access; HTTPS in front; phone app by pairing (LAN/Tailscale); scripts by chat token + POST /api/v1/chat.
AgentesAgentsPapéis (admin × comum), contrato do turno, token com escopo (ody_, bcrypt, 403 intencional), portão pós-conteúdo-externo, overlays de host só por opt-in.Roles (admin × regular), turn contract, scoped token (ody_, bcrypt, intentional 403), post-external-content gate, host overlays opt-in only.
Limite honestoHonest limitEscopo protege a API, não o disco. Sem sandbox de shell. A rede é a garantia final.A scope protects the API, not the disk. No shell sandbox. The network is the final guarantee.

Perguntas de revisãoReview questions

  • Quais são os quatro caminhos pelos quais dados saem de um workspace de IA? Em qual deles “rodar local” ajuda e em quais não?What are the four paths through which data leaves an AI workspace? In which does “running locally” help and in which does it not?
  • Por que o Odysseus é dividido em app e modelo, e como isso muda onde cada um pode rodar (notebook, VPS, GPU na nuvem)?Why is Odysseus split into app and model, and how does that change where each can run (laptop, VPS, cloud GPU)?
  • Cite três saídas de rede do Odysseus que um “modo local” de modelo não elimina. Como cada uma é contida?Name three network exits of Odysseus that a local-model setup does not eliminate. How is each contained?
  • Por que a busca na web, mesmo com um SearXNG seu, pode ser um canal de vazamento controlado pelo modelo?Why can web search, even with a SearXNG of your own, be a model-controlled leak channel?
  • Por que um contêiner só numa rede internal não publica porta, e como o proxy nas duas redes resolve? O que o proxy continua podendo fazer?Why does a container only on an internal network not publish a port, and how does a proxy on both networks solve it? What can the proxy still do?
  • Descreva as sete camadas de autoridade. Quais são as duas específicas de agentes e o que cada uma impede?Describe the seven layers of authority. Which two are specific to agents and what does each prevent?
  • O que o portão “depois de conteúdo externo” bloqueia e por que isso responde à “trifeta letal”? Por que não desligá-lo?What does the “after external content” gate block and why does that answer the “lethal trifecta”? Why not turn it off?
  • Um token tem escopo todos:read. O que o servidor faz se o agente tentar ler e-mail? E se o agente roda na mesma máquina e lê data/ direto?A token has the todos:read scope. What does the server do if the agent tries to read email? And if the agent runs on the same machine and reads data/ directly?
  • Por que a nota diz que “sem telemetria” no README é uma afirmação sobre o código e a rede é uma garantia? Como você mediria o tráfego real?Why does the note say “no telemetry” in the README is a statement about the code while the network is a guarantee? How would you measure the real traffic?
FontesSources