Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Arquivos e entrega

Arquivos viajam nas duas direções. Um membro anexa algo para o agente trabalhar; o agente produz algo que o membro tem de conseguir baixar. Os dois usam um diretório, e este capítulo é sobre esse diretório e as regras em volta dele.

Um diretório: public/

Todo workspace tem um diretório public/, e ele é o único diretório que a interface do membro lista. Essa única frase é todo o desenho. Um arquivo escrito em qualquer outro lugar do workspace existe, é perfeitamente legível pelo agente, e é invisível para a pessoa para quem o agente está trabalhando.

workspace/public/                 what the member sees
workspace/public/attachments/     where the agent delivers

O workspace de um projeto tem seu próprio public/, irmão do principal, então um arquivo enviado para dentro de um projeto fica nesse projeto. Veja Agentes, workspaces e projetos.

Enviando um arquivo para um agente

No compositor do cliente de chat, anexe um arquivo. O cliente o envia para a rota de mídia do proxy, que:

  • limita o corpo da requisição — o limite padrão é 10 MiB, configurável em mediaMaxBytes, e um envio grande demais é recusado com 413 sem ser bufferizado por inteiro;
  • confere o tipo com uma lista de extensões permitidas, recusando qualquer outra coisa com 400;
  • reduz o nome do arquivo a um nome-base seguro — diretórios removidos, caracteres inseguros trocados por _, pontos iniciais descartados — e recusa um que acabaria vazio ou que contivesse travessia de caminho;
  • escreve os bytes no diretório public/ do seu workspace e faz chown nele para que o agente não-root consiga ler o que o proxy root escreveu.

A resposta é o caminho relativo ao workspace, public/<name>, e é esse o caminho que o turno referencia, então o agente consegue abrir o arquivo exatamente pelo nome que lhe foi dito.

Dois comportamentos valem ser conhecidos porque são deliberados:

  • Reenviar o mesmo nome sobrescreve. Um arquivo por nome, em vez de uma pilha crescente de report(1).pdf.
  • Enviar para dentro de um projeto exige que o projeto viaje junto com o arquivo. A rota de upload é a única rota multipart da superfície de mídia, então o projeto chega como um campo de formulário em vez de um parâmetro de query. Mandar sem ele costumava deixar o arquivo no workspace principal, onde o agente do projeto nunca conseguiria abri-lo.

A escrita passa por um handle confinado pelo kernel no diretório public/, então um nome que resolve para um symlink apontando para fora da árvore faz a syscall falhar em vez de escrever onde quer que ele apontasse. Isso importa aqui mais do que importaria normalmente: o proxy roda como root, a árvore é gravável pelo agente dentro do container, e o conteúdo vem da rede.

Recebendo um arquivo de volta de um agente

Há dois caminhos pelos quais um arquivo chega a public/attachments/, e o membro não consegue distingui-los — o que é a intenção.

O próprio agente escreve o arquivo ali. Esse é o caminho comum no ganglion. O diretório de trabalho da ferramenta de shell é o workspace do turno, então public/attachments/report.pdf é um caminho relativo que cai onde o membro vai encontrar.

O harness entrega o arquivo por fora. O picoclaw responde a um pedido de “me manda o arquivo” com uma frase curta e empurra o arquivo ele mesmo pelo seu próprio canal de mídia. O proxy busca esses bytes — com um timeout de 60 segundos e um limite de 64 MiB — e os escreve em public/attachments/<name> sob a mesma sanitização de nome que um envio pelo navegador recebe. Copiar em vez de fazer proxy sob demanda é deliberado: o armazenamento de mídia do harness é um cache próprio com tempo de vida próprio, e um arquivo em public/ já é listável e baixável por tudo que serve um arquivo enviado pelo membro.

A lista de extensões permitidas que vale para os envios não vale para as entregas. Essa lista restringe o que um chamador de fora pode empurrar para dentro de um container; um arquivo entregue foi escrito pelo agente dentro do próprio workspace, então recusá-lo ali descartaria trabalho legítimo sem acrescentar nenhuma fronteira que o workspace já não tenha.

attachments/ é reservado

public/attachments/ é criado, nomeado e preenchido pelo proxy, então o membro não pode renomeá-lo, movê-lo, apagá-lo nem criar uma pasta própria com esse nome — a API recusa com um erro de “gerenciada pelo sistema”. Renomeá-lo desconectaria silenciosamente toda entrega futura.

Só o nível de cima é reservado. reports/attachments é uma pasta comum que um membro pode legitimamente querer; proibir a palavra em todo lugar seria uma regra sobre vocabulário, não sobre propriedade. E a recusa vive na API, não só na interface, porque esconder um botão não é uma permissão.

O que o membro vê

O arquivo aparece no painel Arquivos do workspace, na mesma lista dos arquivos que ele mesmo enviou, com clique para baixar. Pastas aninhadas são listadas, então um agente que organizou sua saída em subdiretórios é mostrado como ele organizou.

Na própria conversa, o proxy acrescenta um aviso curto à resposta:

📎 report.pdf — public/attachments/report.pdf

Esse aviso só existe no stream. Ele é injetado pela camada entre o agente e o membro e não faz parte da mensagem que é salva, então depois de recarregar a página ele some. O único registro durável de um arquivo entregue é o painel Arquivos — e o que o agente escreveu com as próprias palavras.

É por isso que o documento de memória FILE_DELIVERY.md entregue, lido a cada turno, manda o agente nomear o caminho na resposta:

Salvei o relatório em public/attachments/relatorio-q2.pdf.

Esse exemplo é citado literalmente do documento que a plataforma entrega, escrito para um deploy cujos membros escrevem em português. O que importa nele é a instrução, não o idioma: diga no texto da resposta o caminho em que você escreveu.

e o proíbe de anunciar um arquivo que não escreveu de fato, ou um caminho que não usou de fato. A frase padrão do próprio picoclaw — “Requested output delivered via tool attachment.” — não nomeia nada, e uma resposta que diz só isso vira, depois de um reload, uma mensagem sobre um arquivo sem jeito nenhum de achá-lo.

O mesmo documento traça a linha para o agente em uma frase: tudo que o membro deve receber vai em public/attachments/; os arquivos de trabalho de que o agente precisa só para si vão em qualquer outro lugar. Na dúvida, entregue — um arquivo visível que ele ignora não custa nada, um invisível que ele queria custa a ele o pedido inteiro.

uploads/ é o nome legado

public/ se chamava uploads/. A renomeação aconteceu, os arquivos existentes foram junto, e nada mais deve ser escrito em uploads/.

O nome antigo sobrevive em três lugares, e em todos eles está tudo bem:

  • Conversas antigas e referências antigas podem dizer uploads/.... Ler um caminho desses ainda funciona onde o diretório ainda existe; o agente é explicitamente instruído a não criar uma pasta uploads/ para corresponder a um deles.
  • Uma migração única. O proxy roteia todo acesso ao diretório público do membro — uma listagem, um envio, uma entrega — por um único acessor que primeiro migra para o lugar um uploads/ anterior à renomeação. Enganchar o acessor em vez da etapa de provisionamento é o que faz isso alcançar workspaces criados antes da renomeação, já que esses nunca são reprovisionados. Se os dois diretórios existirem de algum jeito, eles são mesclados arquivo a arquivo e o arquivo mais novo vence uma colisão, sendo o mtime a única evidência disponível sobre qual cópia o membro quis guardar.
  • Comentários e identificadores no código do proxy ainda dizem “uploads dir” em alguns lugares. As constantes são o que vale: PublicDirName é public e LegacyPublicDirName é uploads, e a única coisa que deveria referenciar a segunda é a migração.

Um teste de regressão garante que toda skill e todo documento de memória entregues nomeiem public/attachments e que nenhum deles aponte uma escrita para o diretório legado. Ele existe porque um deles já apontou: a skill shared-content mandava o agente escrever as entregas em uploads/attachments enquanto o FILE_DELIVERY.md, montado no mesmo workspace e lido a cada turno, dizia o contrário.

Para onde ir agora

O cliente de chat para o painel Arquivos em contexto, Skills e memória para os documentos citados aqui, e Solução de problemas quando um arquivo que o agente diz ter escrito não aparece.