Como a stack se encaixa
Este capítulo explica a forma do sistema: qual peça faz o quê, por que as peças são separadas, e onde fica de fato a fronteira de segurança. Leia isto uma vez antes de qualquer outra coisa desta seção; todo capítulo posterior parte dele.
O problema que essa forma resolve
Um agente de IA lê e escreve arquivos, roda comandos e mantém memória de longa duração, tudo guiado por linguagem natural que ele não escreveu. Rode um agente para várias pessoas num processo só e basta uma injeção de prompt, um bug de path traversal ou uma ferramenta que vaza para que uma pessoa alcance as conversas, os arquivos e os segredos de outra.
Por isso a stack não compartilha um agente. Cada usuário ganha o próprio container, com o próprio diretório em disco, e a coisa que decide quem você é não é a mesma coisa que roda o seu agente.
Três camadas
A stack são três camadas, cada uma com exatamente um trabalho.
your browser
|
v
+--------------------------------------------+
| 1. EDGE -- mycelium gateway | the only thing exposed
| authenticates, enforces RBAC, |
| injects a verified account profile |
+--------------------------------------------+
| x-mycelium-service-name: alpha
| profile (accId, tenant, subscription)
v
+--------------------------------------------+
| 2. ORCHESTRATION -- crab-shell-proxy | holds the Docker socket
| resolves (tenant, subscription, | runs as root
| agent, user), starts that user's |
| container, proxies the turn |
+--------------------------------------------+
| docker.sock ^
v | HTTP / WebSocket on zombie_net
+--------------------------------------------+
| 3. AGENT -- one harness container | non-root, one per user
| per (tenant, subscription, agent, | own volume, own memory
| user); ganglion or picoclaw |
+--------------------------------------------+
Ao lado dessas três, mais dois serviços rodam mas não estão no caminho da requisição: chat-webapp, a UI de chat voltada ao membro, e harness-sphere, o watcher.
1. Mycelium, a borda
Mycelium é um gateway de API externo, desenvolvido separadamente deste projeto e construído ou baixado como parte da stack. É a única entrada para a API dos agentes: toda requisição que chega ao crab-shell-proxy passou por ele, e o proxy não confia em mais nada sobre quem está chamando. Ele verifica o token de quem chama, aplica o controle de acesso por papel e injeta um perfil de conta verificado na requisição antes de encaminhá-la.
O cliente de chat e a própria UI de administração do mycelium publicam portas próprias, porque são aplicações de navegador que uma pessoa abre. Isso não é uma segunda porta para os agentes — os dois chamam o gateway como qualquer outro cliente.
Essa última palavra é o ponto. Quem chama nunca diz ao proxy quem é; o mycelium diz ao proxy, no servidor, e a identidade flui de cima para baixo, a partir de uma fonte confiável, em vez de de baixo para cima, a partir do corpo da requisição. As rotas são protegidas por papel, então uma conta precisa ter o papel de convidado correspondente para sequer alcançar um agente.
Um tenant é uma organização no mycelium. Uma subscription é uma conta dentro de um tenant para a qual os membros são convidados. O par, mais o id de conta do próprio membro, é o que faz o agente de uma pessoa ser distinto do de outra.
O gateway do mycelium também expõe um endpoint JSON-RPC em POST /_adm/rpc que o chat webapp usa para operações de identidade e de participação. As requisições roteadas pelo crab-shell-proxy são a REST API do próprio proxy e são uma superfície separada.
2. crab-shell-proxy, o orquestrador
O proxy lê no nome de serviço injetado qual agente foi endereçado e no id de conta do perfil qual usuário está chamando, depois garante que o container daquele usuário está rodando — iniciando-o sob demanda, parando-o quando ocioso — e encaminha o turno.
Sua unidade de isolamento é uma chave de quatro partes: tenant, subscription, papel (a chave do agente, como alpha) e id de conta do usuário. Essa tupla é WorkspaceKey em internal/docker/manager.go, e ela nomeia tanto um diretório em disco quanto um container.
O nome do container é <prefix>-<role>-<hash>, onde o hash é um SHA-256 sobre os ids de tenant, subscription e usuário. A tupla completa carrega dois UUIDs e passaria do limite de 63 caracteres de um rótulo DNS, o que deixaria o container inacessível pelo próprio nome na rede Docker — então a identidade fica nos rótulos do container e em um marcador .crab-owner.json no diretório do usuário, não no nome.
3. O agente, atrás de um contrato de harness
A terceira camada não é um programa só. É um harness: um runtime de agente atrás de um contrato fixo, escolhido por agente. Dois são suportados — crab-ganglion-harness, que este projeto escreveu, e picoclaw, com o qual ele começou e que agora está sendo descontinuado. Qual deles um agente roda é declarado no config.yaml do proxy. Veja Harnesses para a escolha e suas consequências.
Quem detém o socket do Docker
Este é todo o argumento de segurança, então ele ganha uma seção própria.
O crab-shell-proxy detém o socket do Docker e roda como root. É o único componente que faz isso. O socket do Docker é o daemon do host: quem consegue escrever nele consegue iniciar, parar e executar comandos dentro de qualquer container, e daí chegar ao root do host. Isso faz do proxy a peça mais privilegiada da stack e seu plano de controle confiável.
Tudo que o proxy cria é o oposto. Os containers de agente rodam com um uid não-root (picoclawUser: "1000:1000" no config.yaml do proxy), ganham seus próprios namespaces de processo, rede e montagem, e ganham um bind do próprio diretório e de mais nada. Se o agente de um usuário for totalmente comprometido — levado por injeção de prompt a rodar código hostil — ele ainda assim não consegue ler os arquivos, a memória ou as conversas de outro usuário. Container diferente, diretório diferente, nenhuma superfície compartilhada. O isolamento é imposto pelo kernel, não por código de aplicação decidindo o que mostrar a quem.
O harness-sphere, o observador da stack, nunca recebe um socket do Docker, e isso é uma regra permanente e deliberada, não um descuido. Ele roda como root para percorrer a árvore de tenants criada pelo proxy, e três restrições mantêm isso estreito: seu bind /data é somente leitura, ele não publica portas e não recebe socket. Um segundo detentor de socket dobraria o raio de alcance do pior comprometimento da stack. Aquilo para que ele precisaria do socket — atribuir um container ao seu tenant — é atendido em vez disso por GET /v1/instances no proxy, atrás de um token próprio.
A stack é afinada para ser fácil de ler e de rodar localmente, não para ser endurecida. Antes de expô-la, isole o socket (um proxy de socket restrito, ou um host dedicado), termine o TLS na borda e troque os tokens e as chaves no
.env.
Pelo que cada repositório é responsável
Este repositório é um nível de topo fino — os arquivos de compose, os perfis de deploy, a documentação e os overlays de build em fungi/ para o lado do mycelium — mais quatro submódulos git em crab/.
| Repositório | Responsabilidade |
|---|---|
crab/crab-shell-proxy | O orquestrador. Detém o socket, é dono do layout em disco, serve a API HTTP. Go. |
crab/crab-ganglion-harness | O runtime de agente do próprio projeto. Um binário Go estático em Alpine. |
crab/crab-exoskeleton-webapp | O cliente de chat voltado ao membro. Next.js. Seu serviço no compose é chat-webapp, não o nome do repositório. |
crab/harness-sphere | O observador. Somente observabilidade, exclusivo desta stack. Rust. |
Mycelium não é um submódulo. O diretório fungi/ guarda Dockerfiles que buscam o mycelium e sua UI de administração no upstream na hora de construir a imagem.
Cada submódulo tem seu próprio remote, seus próprios pull requests e seu próprio branch padrão, e a cadeia é mesclada de baixo para cima: um ponteiro aqui só pode nomear um commit alcançável a partir do branch padrão daquele submódulo, e uma verificação de CI garante isso.
Duas coisas que quem vai fazer o deploy deve saber agora
Os containers de agente não são iniciados pelo compose. O proxy os cria pela API do Docker, um por membro por agente, fora de qualquer projeto do compose. O docker ps os mostra como crabshell-<agent>-<hash>, e docker compose down não os remove.
Produção não tem imagem publicada do ganglion. O docker-compose.prod.yaml baixa imagens publicadas do mycelium, do proxy, do chat webapp e do harness-sphere, e não define CRAB_GANGLION_IMAGE; nenhum workflow neste repositório publica uma. O compose de desenvolvimento constrói a imagem localmente sob a tag zombie-crab/crab-ganglion:dev, que existe só na máquina que a construiu. Um deploy de produção que rode agentes ganglion precisa fornecer essa imagem por conta própria.
Para onde ir agora
Harnesses explica a camada de agente e como um deles é escolhido. Agentes, workspaces e projetos cobre o que o proxy realmente escreve em disco. Para os componentes como componentes, veja crab-shell-proxy e harness-sphere.