crab-shell-proxy
Esta página descreve o orquestrador como componente: do que ele é dono, do que ele deliberadamente não é, e como o código dele está organizado. Leia antes de abrir o repositório pela primeira vez.
O que é
crab-shell-proxy é um pequeno serviço em Go que fica entre o gateway Mycelium e os containers dos agentes. Ele lê para qual agente é a requisição e qual membro a fez, garante que o container daquele membro está rodando, e repassa a conversa para ele. O repositório é crab/crab-shell-proxy, o módulo Go dele é github.com/LepistaBioinformatics/crab-shell-proxy, e o serviço no compose tem o mesmo nome do diretório.
É o componente que segura o socket do Docker, e ele roda como root. O estágio de runtime do Dockerfile diz o porquê com todas as letras: ele precisa alcançar o socket (root:docker, modo 0660), ler arquivos de template pertencentes ao root, e escrever diretórios de dados por usuário. Todo o resto da stack está arranjado para não precisar desses privilégios, porque este componente já os tem. A nota de segurança do README diz sem rodeios: o proxy é o plano de controle confiável, e os agentes que ele cria são a parte sem root, em sandbox.
Um harness é o programa dentro do container de um agente que de fato conversa com o modelo e roda ferramentas. O proxy orquestra harnesses; ele não é um. Veja Harnesses.
Pelo que ele é responsável
Resolver identidade em container. O gateway verifica o token de quem chama e injeta um cabeçalho de perfil. O proxy tira o agente do nome de serviço injetado e o membro do accId do perfil, e garante que existe um container e um diretório para a tupla (tenant, subscription, agent, user) resultante. Usa-se o id da conta em vez do e-mail porque um e-mail pode mudar; o e-mail fica só como marcador legível em .crab-owner.json.
Ciclo de vida. Um agente é declarado como scale-to-zero, em que o container sobe do zero na primeira requisição do membro e é parado depois de uma janela de ociosidade, ou como continuous, em que ele nunca é parado automaticamente. Os dois são configurados por agente no config.yaml.
Escolher o harness. Cada agente declara qual runtime responde por ele:
agents:
alpha:
serviceName: "alpha"
harness: "ganglion"
template: "alpha"
mode: "scale-to-zero"
idleTimeout: 30s
internal/pico roda um turno contra o picoclaw pelo protocolo WebSocket dele; internal/ganglion roda um contra o ganglion por HTTP com SSE. Qual dos dois é usado é decidido por essa única chave. Um agente que não declara a chave harness: recebe o ganglion: o carregador de configuração em internal/config/config.go preenche um harness: vazio com DefaultHarness antes de validar qualquer coisa, e requireHarnessFeature em internal/httpapi/harness_gate.go lê a mesma constante. Ainda assim, declare a chave explicitamente em todo agente que você escrever — um agente ganglion sem imagem é desabilitado em vez de iniciado, então herdar o padrão em um host despreparado tira esse agente de serviço.
Dizer a verdade sobre o que um harness não consegue fazer. harness_gate.go mantém uma tabela de recursos que não são universais, e um recurso que o harness do agente não consegue atender responde 501 nomeando o harness, em vez de fingir sucesso. O arquivo registra o incidente que gerou a regra: um harness certa vez aceitou uma criação de projeto que ele não implementava, então um projeto podia ser criado, guardado e listado sem mudar nada no agente que respondia.
Tudo que é feito com um container. Provisionamento e posse de volumes, materialização de segredos, o registro de modelos, o servidor MCP do grafo de memória, as tarefas agendadas e a API de administração vivem todos aqui. A especificação do harness coloca isso como uma fronteira permanente: um harness dentro de um container não tem nada que iniciar, parar ou provisionar coisa alguma, nem a si mesmo.
A superfície HTTP. A parte voltada ao membro tem o formato da OpenAI — POST /v1/chat/completions, GET /v1/models, GET /v1/sessions/history — com GET /healthz para liveness e GET /doc/openapi.json para o documento OpenAPI embutido no binário. A administração fica em /v1/admin/..., e GET /v1/instances é um inventário somente leitura das instâncias em execução, que existe para que nada mais na stack precise perguntar ao próprio Docker.
Pelo que ele não é responsável
Ele não autentica ninguém. A identidade chega já verificada do gateway, e o trabalho do proxy é confiar nesse cabeçalho, não repetir a verificação.
Ele não roda o laço do agente. Decidir qual ferramenta chamar, quando parar e qual é a resposta pertence ao harness.
Ele não desenha nada. A interface voltada ao membro é o crab-exoskeleton-webapp, que chega ao proxy através do gateway e nunca fala direto com um container de agente.
Ele não coleta métricas sobre a stack. Isso é o harness-sphere, e a divisão sustenta peso: como o proxy já segura um socket do Docker, nada mais na stack ganha um.
Como é construído e testado
O build é o portão de testes. O estágio de build do Dockerfile roda go vet ./... e go test ./... antes de linkar o binário, então um teste que falha significa que nenhuma imagem é produzida e, portanto, nada é publicado. release-image.yml é o único workflow do repositório; ele constrói e publica essa imagem em um push para main ou em uma tag de versão. Não há um workflow separado de pull request, o que significa que as verificações que um contribuidor roda localmente são as mesmas que barram a imagem:
go vet ./... && go test ./...
Uma segunda suíte conversa com um daemon Docker de verdade e fica atrás de uma build tag, então ela não roda no comando acima e também não roda no build da imagem:
go test -tags integration ./internal/docker -run TestIntegration -v
config.yaml é embutido na imagem em /etc/crab-shell-proxy/config.yaml e guarda nomes de variáveis de ambiente, não valores, de modo que uma mesma imagem continua utilizável em vários deploys. Veja Configuração.
Como o código está organizado
cmd/crab-shell-proxy/main.go é o ponto de entrada; todo o resto está em internal/. Os pacotes que vale conhecer antes de começar a ler:
| Pacote | O que vive ali |
|---|---|
config | o catálogo de agentes, os padrões e a validação, e os helpers de caminho para o diretório de um membro |
httpapi | toda rota, incluindo o portão de recursos do harness |
docker | o cliente da API do Docker Engine escrito à mão e tudo que é feito com um container ou com o volume dele |
pico | rodar um turno contra o picoclaw |
ganglion | rodar um turno contra o crab-ganglion-harness |
registry | os modelos, a cascata deles, e quem pode usar qual |
history | ler as transcrições de volta do diretório de um membro |
memgraph, mcpserver, mcptoken | o grafo de memória e o endpoint MCP pelo qual os agentes chegam nele |
cron, projects, restart, authz, identity, turn | tarefas agendadas, projetos, controle de restart, autorização, o cabeçalho de perfil, e os tipos de turno compartilhados |
internal/docker é, com larga margem, o maior pacote, o que é um bom sinal de onde está o trabalho: a maior parte do que este serviço faz é manipulação cuidadosa de sistema de arquivos e de containers em nome de alguém cuja requisição ele já decidiu confiar.
Para onde ir agora
Harnesses explica os dois runtimes e como um deles é escolhido. Agentes, workspaces e projetos descreve o layout de diretórios que este serviço lê e escreve. Se você está prestes a mexer no código, Trabalhando na stack tem os comandos de build e de teste de cada repositório em um só lugar.