Configuração
Quatro arquivos decidem como esta stack se comporta. Este capítulo diz qual deles é dono do quê, percorre o catálogo de agentes em detalhe — porque é ali que você define um agente — e lista as variáveis de ambiente que vale conhecer.
As quatro superfícies
| Arquivo | É dono de | Como chega até a stack em execução |
|---|---|---|
.env na raiz do repositório | segredos, portas publicadas, referências de imagem | lido pelo compose |
crab/crab-shell-proxy/config.yaml | o catálogo de agentes: quais agentes existem, seu harness, modelo e ciclo de vida | copiado para a imagem do proxy no momento do build |
deploy/<mode>/config.*.toml | o gateway: quais rotas existem, qual papel protege cada uma, qual token é injetado | montado por bind no gateway |
docker-compose*.yaml | quais serviços rodam, o que eles montam, que ambiente recebem | o próprio comando compose |
A única coisa a internalizar: o catálogo de agentes é embutido na imagem do proxy, então adicionar ou remover um agente significa reconstruir o proxy. A configuração do gateway é montada, então uma mudança ali precisa apenas de um restart daquele serviço. Os dois têm que concordar — um agente que existe em um e não no outro é ou uma rota anunciada sem nada atrás, ou um agente que ninguém consegue alcançar.
O catálogo de agentes: config.yaml
Este é o arquivo que você edita para definir um agente. Todo o resto dele tem um padrão que funciona; o mapa agents: não.
Aqui está um agente completo, com as linhas que importam:
agents:
alpha:
serviceName: "alpha" # must match the gateway's service key
harness: "ganglion" # which runtime answers
token: { env: "MYC_PICOCLAW_ALPHA_TOKEN" } # read from the environment, never inline
template: "alpha" # <dataRoot>/templates/alpha
mode: "scale-to-zero" # or "continuous"
idleTimeout: 30s
model:
provider: "deepseek"
name: "deepseek-chat"
apiKeyEnv: "PICOCLAW_ALPHA_API_KEY" # the key lives in the environment
serviceName é o valor que o Mycelium injeta como x-mycelium-service-name quando encaminha uma requisição, e é a única coisa que diz ao proxy qual agente foi endereçado. O Mycelium pega o primeiro segmento de caminho da URL que chega como nome do serviço e o remove antes de encaminhar, então um membro que chama /alpha/v1/... alcança o agente cujo serviceName é alpha. Uma requisição que carrega um nome de serviço que nenhum agente reivindica é respondida com 404 e uma mensagem dizendo para você passar pelo gateway.
harness seleciona o runtime. Os dois valores aceitos são "ganglion" e "picoclaw"; qualquer outro faz o carregamento falhar com uma mensagem que nomeia os dois. Um agente que não declara harness nenhum recebe o ganglion, que é o que diz config.DefaultHarness. Declare o harness explicitamente em todo agente mesmo assim, como faz todo agente no catálogo deste repositório: um runtime não é algo que uma configuração deva escolher por omissão, e uma omissão aqui não degrada com elegância. Um agente ganglion sem imagem é desabilitado em vez de iniciado, então um agente que herda o padrão num host sem CRAB_GANGLION_IMAGE para de responder em vez de rodar outra coisa em silêncio. O log de boot diz isso, e nomeia as duas saídas. Veja harnesses para saber como os dois runtimes diferem.
token mostra o padrão que este arquivo usa para todo segredo: { env: "NAME" } lê o valor do ambiente do proxy no momento do carregamento, então nada confidencial é escrito aqui ou embutido na imagem. Uma string simples também é aceita, e é a escolha errada fora de um teste.
template nomeia um subdiretório de <dataRoot>/templates/. Para um agente picoclaw o proxy cria um template ausente a partir de um padrão compilado no binário. Um agente ganglion não tem template em disco: ele é configurado pelo ambiente e por arquivos escritos por usuário.
mode e idleTimeout são o ciclo de vida. scale-to-zero para o container depois da janela de ociosidade e o inicia de novo na próxima mensagem, com tudo preservado. continuous nunca o para, que é o que os conectores nativos do picoclaw precisam — eles discam para fora de dentro do container, então o proxy não enxerga essa atividade para manter o agente vivo. Para um agente ganglion não existe essa porta lateral e o modo é uma simples decisão de custo. idleTimeout precisa ser maior que zero quando o modo é scale-to-zero; nos outros casos é ignorado.
model fixa o provedor e o nome do modelo e, o que é crucial, nomeia a variável de ambiente que guarda a chave em vez da chave em si. Cada agente tem a sua, então dois agentes podem usar provedores diferentes e credenciais independentes. Um baseUrl opcional sobrescreve o endpoint que o proxy resolveria a partir do provedor — o que você quer quando roda um gateway ou um endpoint regional na frente do provedor.
O que acontece quando um agente ganglion não está totalmente configurado
Este é o comportamento com maior chance de surpreender você, e ele é deliberado.
Um agente ganglion se remove do catálogo, no boot, quando este ambiente não consegue rodá-lo: sem referência de imagem, sem token, ou com um valor vazio atrás do seu model.apiKeyEnv. Isso não derruba o proxy, e não é silencioso — o log de boot traz uma linha nomeando o agente e a configuração que falta, e as rotas do agente passam a responder 404:
agent "alpha" disabled: PICOCLAW_ALPHA_API_KEY is unset (the agent's model apiKeyEnv) — its routes will answer 404
O raciocínio é que um arquivo de configuração deve conseguir descrever vários deploys. Um agente ganglion que chega a um host sem chave para ele degrada para “esse agente não existe” em vez de “o proxy não vai subir”, o que derrubaria todos os outros agentes junto.
Um agente picoclaw se comporta de outro jeito de propósito: um token que ele não consegue resolver é fatal, porque descartá-lo em silêncio removeria o acesso de um membro sem nenhum sinal além de uma linha de log que ninguém lê até ficar trancado do lado de fora. A chave de modelo dele, em contraste, pode estar vazia — ela é escrita na configuração do próprio membro no momento do provisionamento e aparece como um erro de autenticação na primeira mensagem.
As configurações em volta do catálogo
O resto do config.yaml tem forma de máquina e em geral é fornecido pelo compose. Os valores que vale conhecer:
hostDataRooté o caminho absoluto no host da árvore de dados. O proxy o entrega ao daemon do Docker como origem do bind-mount dos containers que cria, então um caminho que só existe dentro do proxy não vai resolver. É isso queCRAB_HOST_DATA_ROOTsobrescreve.containerDataRooté onde essa mesma árvore é montada dentro do proxy. O padrão é/data.networké a rede Docker em que os containers criados entram. O arquivo compose fixa o nome real da rede emzombie_netpara que ele fique estável independentemente do nome do projeto compose.startupDeadline(padrão 35 segundos) limita uma partida a frio.turnIdleTimeout(padrão 120 segundos, definido como 600 no arquivo que vem pronto) limita quanto tempo o harness pode ficar em silêncio — não quanto tempo um turno pode levar. Um turno longo que usa ferramentas narra o tempo todo e o reinicia a cada frame.containerPrefix(padrãocrabshell) prefixa todo container que o proxy gerencia. O nome é<prefix>-<agent>-<hash>; o harness é registrado nas labels do container, não no nome dele.mediaMaxBytes(padrão 10 MiB) é a única coisa contra a qual um upload é checado.
Vários campos podem ser sobrescritos pelo ambiente para que o arquivo versionado continue portátil: CRAB_HOST_DATA_ROOT, CRAB_CONTAINER_DATA_ROOT, CRAB_NETWORK, CRAB_LISTEN, CRAB_PICOCLAW_IMAGE, CRAB_PICOCLAW_USER, CRAB_PICOCLAW_HOME, CRAB_GANGLION_IMAGE, CRAB_MCP_BASE_URL e GANGLION_OTLP_ENDPOINT.
A configuração do gateway
deploy/standalone/config.standalone.toml e deploy/prod/config.base.toml configuram o Mycelium. Por agente, o formato é um bloco de serviço nomeando o downstream, um bloco de segredo guardando o token bearer, e um bloco de caminho por rota:
[[alpha]]
host = "crab-shell-proxy:8080"
healthCheckPath = "/healthz"
[[alpha.secret]]
name = "alpha-authorization-header"
authorizationHeader = { headerName = "Authorization", prefix = "Bearer", token = { env = "MYC_PICOCLAW_ALPHA_TOKEN" } }
[[alpha.path]]
group = { protectedByRoles = [{ name = "alpha", permission = "write" }] }
path = "/v1/chat/completions"
secretName = "alpha-authorization-header"
methods = ["POST"]
Três coisas decorrem desse bloco. A chave de serviço (alpha) é ao mesmo tempo o primeiro segmento de caminho que quem chama usa e o serviceName que o proxy compara. O papel nomeado em protectedByRoles é criado automaticamente no boot a partir dessa declaração — mas conceder o papel a uma conta é uma ação humana, e até lá toda requisição é recusada. E o token é resolvido do ambiente a cada requisição, então o arquivo versionado não guarda segredo nenhum.
Adicionar uma rota significa adicionar um bloco de caminho. Uma rota que o gateway não conhece é recusada antes de o proxy sequer vê-la, com uma mensagem sobre o caminho não corresponder a nenhum serviço — o que parece um bug de roteamento e é na verdade um bloco faltando.
Variáveis de ambiente
A lista completa e comentada é deploy/standalone/.env.example; copie esse arquivo em vez de escrever um do zero. Estas são aquelas cujo comportamento não é óbvio pelo nome:
| Variável | Efeito |
|---|---|
MYC_PICOCLAW_<AGENT>_TOKEN | O bearer que o gateway injeta e o proxy verifica, por agente. |
PICOCLAW_<AGENT>_API_KEY | A chave de LLM do próprio agente, referenciada pelo nome a partir do config.yaml. |
MYC_STANDALONE_BOOTSTRAP_SECRET | Controla a reivindicação única da conta Staff. Vazio deixa esses endpoints respondendo 404. |
CRAB_WEBHOOK_SECRET | Autentica no proxy o webhook de conta criada do Mycelium. |
CRAB_MCP_TOKEN_SECRET | Vazio desabilita o grafo de memória — o endpoint não é registrado e nenhum bloco de servidor é escrito em workspace nenhum. Nada avisa você; as telas de memória simplesmente ficam vazias. |
CRAB_TELEMETRY_TOKEN | Vazio significa que a rota de inventário de workspaces não é registrada de jeito nenhum — 404, não 401. Nunca reutilize um token de agente aqui: um token de agente libera conversar como qualquer membro, e um componente de monitoramento não pode ter um. |
CRAB_GANGLION_IMAGE | A imagem do ganglion. Ela não tem padrão no proxy, de propósito; o arquivo compose de desenvolvimento fornece uma que ele mesmo constrói. |
CRAB_HOST_DATA_ROOT | O caminho no host da árvore de dados. Precisa ser absoluto e precisa ser um caminho que o daemon do Docker consiga enxergar. |
START_AT_SIGNIN | Defina como 1 e a landing page do cliente de chat nunca é servida: / vira a tela de login. |
COMPOSE_FILE | Faz de um overlay o padrão para todo comando compose. O overlay de observabilidade na prática exige isso — veja observabilidade. |
Duas dessas merecem ser repetidas como regra, porque compartilham uma: um segredo não definido significa que a funcionalidade está ausente, não desprotegida. Um deploy que esqueceu um valor não ganha superfície nova, em vez de ganhar um endpoint atrás de uma guarda adivinhável.
Um workspace ganglion não tem diretório
.secrets/. As credenciais chegam a esse harness como variáveis de ambiente no container, e é por isso que o proxy faz bind de cada workspace separadamente e de nada acima dele. Sob o picoclaw a visão mesclada de segredos é montada somente leitura emworkspace/.secrets. Veja agentes e workspaces.
Para onde ir agora
Criando um agente customizado percorre de ponta a ponta a edição do catálogo e o bloco correspondente no gateway. Modelos e provedores cobre cadeias de modelos e sobrescritas por membro. Deploy cobre o que muda em produção.