Guia de administração
Este capítulo é para quem cuida de um deploy no dia a dia: adicionar pessoas, dar material de trabalho aos agentes delas, decidir qual modelo elas recebem e consertar a configuração de um membro quando ela dá errado. Ele supõe que a stack já está instalada — veja instalação se não estiver.
Tenants, subscriptions e escopos
A identidade nesta stack vem do Mycelium, um gateway de API externo. Um tenant é uma organização; uma subscription é uma conta sob um tenant, e é o único nível em que existe uma lista de membros. Um tenant pode ter muitas subscriptions.
Tudo o que um administrador publica é publicado em um escopo, que é ou um tenant ou uma subscription. Um escopo de tenant alcança todo mundo abaixo dele; um escopo de subscription alcança apenas aquela subscription. Onde os dois têm algo a dizer, o mais estreito vence.
O que você pode administrar depende do seu tier, resolvido a partir do perfil Mycelium a cada requisição (CallerTier em crab/crab-shell-proxy/internal/authz/authz.go). A equipe da instância tem autoridade em todo lugar; um tenant-owner ou tenant-manager, sobre um tenant e todas as subscriptions abaixo dele; um subscriptions-manager, sobre exatamente uma subscription. A área administrativa lista apenas os escopos que você pode gerenciar, e o proxy confere de novo a cada escrita — a interface se estreitar sozinha é uma conveniência, não o portão.
O formato da área administrativa
A área administrativa fica no webapp de chat, em /admin. Ela tem dois itens de primeiro nível: Workspaces, que é tudo o que tem escopo, e Branding, que vale para a instância inteira (railItems em crab/crab-exoskeleton-webapp/app/admin/admin-nav.ts).
Em Workspaces você escolhe primeiro um agente, depois um escopo. Essa ordem é deliberada: os agentes vêm da configuração do proxy e existem antes de qualquer tenant, então pedir o escopo primeiro dava a entender que os agentes eram uma propriedade de uma subscription. Cada seção então age sobre esse par (agente, escopo).
As seções são Files, Secrets, Skills, Persona, Model, Config e Members, nessa ordem (SECTION_TABS em crab/crab-exoskeleton-webapp/app/admin/tabs.ts). Quais delas um dado agente oferece é decidido em agent-scope.ts, e hoje um agente ganglion e um agente picoclaw oferecem as sete. Há também um endereço legado “all agents” que guarda conteúdo compartilhado escrito antes de o conteúdo passar a ser por agente; ele oferece só as seções de conteúdo, porque escrever uma persona, atribuir um modelo e convidar alguém precisam de um agente real a que se prender.
Vale dizer o modelo mental uma vez: você edita material compartilhado em um escopo, o proxy mescla os escopos em uma visão efetiva no disco, e o container de cada membro monta essa visão somente leitura. Um membro nunca vê o workspace privado de outro.
Arquivos
Os arquivos enviados aqui chegam ao agente de cada membro dentro do escopo, como bind mounts somente leitura em workspace/.shared/<layer> dentro do container — um diretório por camada, chamados tenant, subscription, tenant-agent e subscription-agent (sharedFileBinds em crab/crab-shell-proxy/internal/docker/shared.go). O agente pode lê-los; não pode alterá-los.
Files é a única seção cujas escritas não precisam reiniciar o container. O mount é ao vivo, então um arquivo novo aparece imediatamente em todos os containers em execução, e o proxy deliberadamente não recria o container — fazer isso costumava truncar a conversa de um membro no meio do turno.
Secrets
Secrets são credenciais de que os agentes precisam sem que elas fiquem gravadas em uma imagem ou em um template. Quatro formatos são suportados (crab/crab-shell-proxy/internal/docker/secrets.go):
| Formato | Onde ele aparece |
|---|---|
dotenv | um arquivo .env com linhas NAME=value |
json | um objeto secrets.json |
file | um arquivo por secret, o conteúdo é o valor |
native | um slot nomeado na configuração do próprio harness |
Os três primeiros servem para uma skill que lê uma credencial de algum lugar convencional. O formato native preenche um slot que o próprio runtime conhece, e só dois formatos de slot são aceitos: web.<provider> para a chave de um provedor de busca, e model_list.<model>.api_keys para um modelo que o inventário já conhece (validateNativeSlot). Qualquer outra coisa é recusada.
Um secret web.<provider> é o que liga a busca na web de um agente. Os nomes de provedor que os dois harnesses aceitam não são idênticos, então leia modelos e provedores antes de registrar um.
Os secrets são somente escrita pela API. Você pode listar os nomes que existem; nada lê um valor de volta.
Skills
Uma skill é uma pasta que contém um SKILL.md — um front matter YAML com um name e uma description, depois um corpo em Markdown — mais os arquivos de apoio de que ela precisar. Skills e memória explica para que elas servem.
Publique uma em um escopo escrevendo o SKILL.md dela ali mesmo ou enviando um zip da pasta; o proxy aceita uma parte file ou uma parte body e recusa uma requisição que não traga nenhuma das duas. Você pode baixar de volta como zip qualquer skill publicada.
As skills se mesclam em quatro camadas, em precedência crescente: tenant, tenant+agente, subscription, subscription+agente (syncEffectiveSkills em crab/crab-shell-proxy/internal/docker/skills.go). Uma pasta de skill presente em mais de uma camada é tomada inteira da camada mais específica — as camadas não se mesclam arquivo a arquivo. O resultado mesclado é escrito em um diretório cujo inode é mantido estável, então uma edição chega aos containers em execução sem recriá-los.
Persona
Quatro arquivos definem a identidade de um agente, e o conjunto é fixo e fechado (PersonaFiles em crab/crab-shell-proxy/internal/docker/persona.go):
| Arquivo | O que é | Como é entregue |
|---|---|---|
AGENT.md | o que o agente faz e como ele se comporta | mount somente leitura |
SOUL.md | sua voz | mount somente leitura |
HEARTBEAT.md | sua lista de tarefas recorrentes | mount somente leitura |
USER.md | o que se sabe sobre o membro | apenas semeado |
O conjunto é fechado porque esses endpoints escrevem na raiz de um workspace: um nome de arquivo arbitrário aqui seria uma escrita de arquivo arbitrária alcançando todos os containers dentro do escopo.
Os três primeiros são resolvidos por workspace em ordem de precedência — subscription+agente, depois tenant+agente, depois o template do agente (resolvePersonaSources) — e montados somente leitura. Isso é precedência, não mescla: dois arquivos AGENT.md não podem ser combinados em uma identidade só. Um membro não pode editá-los, e uma edição feita dentro do container nunca sobrevive a um restart.
USER.md é a exceção, porque o agente escreve nele: é onde o agente acumula o que aprende sobre o membro. Defini-lo aqui determina de onde um workspace novo começa, e nunca sobrescreve um que já existe.
Cada linha diz se o arquivo está definido neste escopo ou herdado. O editor pré-carrega o que o agente de fato executa, resolvido cascata abaixo, para que você edite uma identidade real em vez de uma página em branco; salvar é o que a torna deste escopo. Limpá-la deixa o workspace voltar para o escopo mais amplo, ou para o template.
Model
Um administrador controla duas coisas separadas aqui: um inventário de modelos que o deploy pode servir, e uma cascata que decide para qual deles um dado workspace é resolvido.
O inventário é uma lista só para o proxy inteiro, guardada em um único arquivo — model-registry.db na raiz de dados do proxy. Um modelo é registrado uma vez, com as credenciais dele, e cada escopo aponta para esse registro em vez de guardar uma cópia. Um registro carrega um provedor, um nome de modelo usado como identificador em todo o resto, o identificador de fato enviado ao endpoint, uma URL base de API, uma chave de API somente escrita e a cadeia de fallback ordenada do próprio modelo.
A cadeia de fallback é uma propriedade do modelo, não de um escopo: ela nomeia outros modelos registrados para tentar depois deste. O editor oferece só modelos ativos como candidatos, porque o resolvedor pula um fallback não ativo de qualquer jeito. A ordem em que a lista do inventário é exibida é só apresentação e não tem efeito nenhum na resolução.
Um modelo tem um de três status. Active é oferecido normalmente. Disabled é reversível e exige que nada referencie o modelo — nenhum workspace, nenhum padrão de escopo, nenhuma cadeia de outro modelo — então o proxy recusa com a lista de quem o referencia quando algo o faz, e a mesma checagem impede apagar um modelo. Deprecated é para um modelo que as pessoas ainda estão usando: exige que você nomeie um substituto, e esse substituto só é seguido para workspaces que ainda não materializaram o modelo depreciado. Essa única condição é o que produz “membros novos ganham o sucessor, membros existentes ficam com o que têm” sem um segundo caminho de código.
A cascata
Seis níveis decidem qual modelo um workspace recebe. Leia de cima para baixo; cada um cobre menos gente que o de cima e o sobrepõe (Resolve em crab/crab-shell-proxy/internal/registry/resolve.go):
global the whole instance
agent every tenant running this agent
tenant one tenant
subscription one subscription
user (a pin) one person, set by an administrator
the member's own one person, registered by themselves
O nível mais estreito que nomeia um modelo vence, e é nele que um workspace recém-provisionado cai. Os níveis abaixo de um que foi limpo continuam definidos e assumem, e é por isso que o painel desenha a escada inteira em vez de um nível por vez: você vê o que a sua escrita sobrepõe e para onde limpá-la cairia. Um nível que você não tem autoridade para ler é desenhado como ilegível, e não como não definido — um nível de instância inteira que você não pode ver pode ainda estar cobrindo o seu escopo.
Você só pode escrever no nível em que o seu escopo está. Com uma subscription selecionada você pode editar o nível da subscription e pins individuais; com um tenant selecionado você pode editar o nível do tenant e mais nada (editableLevels em crab/crab-exoskeleton-webapp/lib/models.ts). Os níveis de agente e global são legíveis aqui e não graváveis aqui — o nível de agente é rotulado com o nome do agente selecionado, mas alcança todos os tenants que rodam aquele agente, o que, dentro de uma tela cuja barra diz “esta subscription”, soa como algo bem mais estreito do que é. O primeiro padrão global ou de agente tem de ser escrito por fora.
Um pin é uma pessoa só, e supera todo nível definido por administrador acima dele. Use-o para um indivíduo; para mover um grupo, defina o nível do escopo dele. Os pins vivem dentro de uma subscription, e uma pessoa só aparece depois que tem um workspace — isto é, depois do primeiro chat dela.
Um padrão de escopo que nomeia um modelo que não existe mais é pulado e a cascata segue para o próximo nível, porque um padrão desatualizado importado no boot recusaria todo workspace daquele tenant. Um pin que nomeia um modelo ausente não é pulado; é uma falha dura, porque foi definido de propósito.
Se nada resolve, o que acontece depende do harness. Um workspace picoclaw é recusado e nada é escrito, porque o picoclaw falha na inicialização quando o padrão dele nomeia um modelo ausente da sua lista de modelos, então um padrão silencioso produziria um container permanentemente sem boot. Um workspace ganglion cai para o modelo declarado na entrada de catálogo do próprio agente, e esse fallback deliberadamente não é registrado como uma atribuição.
Os modelos dos próprios membros
Um membro pode registrar modelos próprios, com as chaves de API dele, e um modelo pessoal supera todo nível acima dele, incluindo o pin de um administrador. A chave é dele, a escolha é dele — o controle do administrador aqui é uma trava de escopo, não um veto pessoa a pessoa.
Essa trava tem duas opções independentes, e cada uma pode ser ligada, desligada ou deixada herdando de um escopo mais amplo (ScopePolicy em crab/crab-shell-proxy/internal/registry/usermodels.go):
| Opção | Não definida em lugar nenhum significa |
|---|---|
| os membros podem usar modelos próprios | permitido |
| eles podem nomear um endpoint fora do catálogo | negado |
Os padrões são deliberadamente opostos. Os modelos pessoais são o ponto do recurso, então são permitidos a menos que alguém se oponha; um membro que não pode nomear um endpoint não pode apontar a instância para um, o que é uma classe inteira de risco em vez de um caso governado dela. A política tem uma cascata própria — subscription, tenant, agente, global — e o primeiro nível que define uma opção decide. Mudar uma política não reinicia nada; ela vale a partir da próxima vez que cada workspace for preparado.
Separadamente, a lista User models deixa você desabilitar um modelo de um membro. Ele começa habilitado: a opção é uma intervenção, não um portão a passar. Desabilitá-lo re-prepara todo workspace que o estava usando e levanta um aviso de restart em vez de forçar um. Os membros não têm uma opção dessas — o jeito deles de parar de usar um modelo é parar de selecioná-lo.
Um membro pode registrar no máximo dez modelos, precisa fornecer um endpoint e uma chave para cada um, e só pode escolher de uma lista de provedores mais estreita que a do inventário do administrador: a família compatível com OpenAI, com os provedores OAuth e os específicos de fornecedor deliberadamente ausentes.
O que acontece depois que um workspace tem um modelo — a cadeia de fallback na hora do turno, a cadeia de visão, a geração de imagens e quais ferramentas aparecem — está em modelos e provedores.
Config
Esta seção alcança a configuração de runtime da instância de um membro, ou de uma chave em uma subscription inteira.
Bulk trabalha em uma subscription por vez. Você nomeia um caminho pontilhado até um único valor, lê primeiro a distribuição atual — o que cada membro tem agora — e só então escreve. As instâncias cuja chave está ausente, bloqueada por um caminho conflitante, ou cuja configuração não pode ser lida, ficam de fora da escrita e são listadas para que você as conserte uma a uma.
As chaves que o proxy possui são recusadas, porque uma mudança ali não sobreviveria — o proxy as reescreve toda vez que prepara um workspace. ManagedConfigPaths em crab/crab-shell-proxy/internal/docker/instance_config.go é a lista: a lista de modelos, o provedor padrão, o nome do modelo e os fallbacks, o caminho do workspace, o gerenciador de contexto, a opção do canal pico e a entrada MCP do próprio grafo de memória do proxy.
Single instance abre a configuração de um membro como JSON formatado ou como uma árvore, valida e escreve de volta.
Como essa escrita chega a um agente ganglion é diferente do picoclaw. O config.json de um workspace picoclaw é semeado uma vez e depois editado no lugar, então uma edição simplesmente fica. A configuração de um workspace ganglion é renderizada inteira toda vez que o container é preparado, então uma edição escrita no arquivo sumiria no turno seguinte. Por isso o proxy guarda a edição em um overlay por instância ao lado do arquivo e a reaplica a cada renderização (crab/crab-shell-proxy/internal/docker/ganglion_overlay.go). Um overlay quebrado é registrado no log e pulado, em vez de tratado como fatal.
O overlay é também o único caminho pelo qual
agents.defaults.image_modelouagents.defaults.image_gen_modelpodem chegar a um agente ganglion. O renderizador do proxy nunca emite nenhuma das duas chaves, então nenhuma aparece no seletor de chaves do bulk — que é gerado a partir desse renderizador — e as duas têm de ser adicionadas à mão no editor de instância única. O editor compara o documento inteiro e registra cada folha alterada que não seja malformada nem de propriedade do proxy, e estas duas não são nem uma coisa nem outra. Veja modelos e provedores.
Uma mudança de configuração vale a partir do próximo start da instância. O controle de restart no menu decide como esse reinício acontece: now, o padrão; schedule, que reinicia o escopo na hora que você escolher; ou notice, que deixa o momento para o membro, que vê um aviso de restart pendente no chat (crab/crab-shell-proxy/internal/httpapi/restart_policy.go). A mudança em si sempre se propaga na hora — só o reinício do container é adiado.
Uma instância também pode ser fixada em um modo de ciclo de vida diferente do que o agente dela declara. A razão de isso existir são as tarefas agendadas que rodam a partir de timers dentro de um container: um membro que precisa de um relatório diário é motivo para manter um container rodando sem forçar o agente inteiro a isso.
Members
A seção Members lista as pessoas da subscription selecionada, com as instâncias e os arquivos guardados que cada uma tem. Um escopo de tenant não tem lista de membros, porque pertencer é um fato de nível de subscription.
Convidar é o maquinário de convidados do Mycelium, alcançado por JSON-RPC; o webapp não guarda estado de convite nenhum. Dois fatos fazem isso funcionar (crab/crab-exoskeleton-webapp/lib/invitations.ts):
- O nome de um papel de convidado é a chave do agente. O gateway declara
protectedByRoles = [{ name = "alpha", permission = "write" }]e o Mycelium cria esses papéis no boot, então “convidar alguém para o agentealpha” é literalmente “dar a essa pessoa o papel de convidado chamadoalpha”. - A permissão vive no papel, não na chamada de convite. Escolher leitura ou escrita é escolher qual id de papel enviar. Escrita é o que conversar exige; leitura é só visualizar.
O formulário não pergunta qual agente, e isso é uma correção, não um esquecimento: ele usa o agente que você selecionou no portão. Ele já teve um seletor de agente próprio, três campos abaixo, o que significava duas seleções de agente em uma tela só, e a que decidia o acesso era a invisível. Revogar se faz pela linha da própria pessoa na lista, não pelo formulário de convite.
Você pode ver os metadados dos arquivos privados de um membro — nome, tamanho, hora de modificação — e apagar um deles. Deliberadamente não há como abrir, baixar ou pré-visualizar daqui o arquivo privado de um membro, em nenhum tier. O painel não expõe nada disso e o proxy não tem endpoint nenhum para isso.
Branding
Branding vale para a instância inteira e não tem escopo de tenant, agente ou subscription. Ele define o nome da aplicação e deixa você enviar ou redefinir um logo claro, um logo escuro e um ícone da aplicação. Ele aparece só para quem pode editá-lo, porque um console que oferece algo que quem chamou não pode usar soa como uma tela quebrada, e não como uma resposta sobre a autoridade dessa pessoa. O proxy é o portão de verdade.
Para onde ir agora
Modelos e provedores é a outra metade da seção Model. Criando um agente customizado cobre adicionar um agente para estas seções administrarem. Resolução de problemas reúne as falhas que as pessoas de fato relatam.