harness-sphere
harness-sphere é o observador. Esta página o descreve como componente: o que ele observa, as três coisas que ele nunca vai fazer, e como o seu código está organizado.
O que é
O HarnessSphere é um único binário Rust autocontido que observa uma stack — a máquina em que roda, ele mesmo, e os serviços que o zombie-crab executa — e transforma o que encontra em métricas OpenTelemetry padrão, enviadas para o backend que você apontar. O binário se chama harnesssphere, com três s, e o repositório é crab/harness-sphere.
Ele existe porque antes dele não havia nada. Não “não havia o bastante”: a stack não emitia métricas, não tinha coletor nem dashboard, então a primeira pergunta que qualquer um faz durante um incidente — a máquina estava sob pressão quando aquilo ficou lento — não tinha resposta nenhuma. Porque o observador fica no mesmo host e na mesma linha do tempo que todo o resto, ele consegue responder a única coisa que ferramentas separadas não conseguem: se um agente ficou lento por causa do agente, ou por causa da máquina embaixo dele.
Ele é exclusivo desta stack. Ele começou como um observador de propósito geral e agora é o componente de observabilidade do zombie-crab-project. Os coletores sem fonte aqui foram apagados em vez de deixados configuráveis, e não há flag que os traga de volta. A publicação no crates.io está desativada; os releases de binário continuam.
Pelo que ele é responsável
A stack é modelada como exatamente seis camadas: o host, o próprio observador, o gateway, o proxy, o webapp e os containers de agente. Deliberadamente não existe uma variante coringa — um sétimo tipo de coisa aparecendo deve quebrar o build em vez de se arquivar sozinho sob “outros”.
Duas camadas são Critical e o resto é Optional, e a distinção é todo o modelo de resiliência. Host e Watcher são Critical: se uma delas falha de forma persistente, além de um limite configurável para que um soluço isolado seja perdoado, o observador descarrega o que consegue e sai com código diferente de zero, em alto e bom som. Todo o resto degrada, recua e tenta de novo, e nunca derruba o processo. Um alvo que não está respondendo lê um zero honesto em vez de sumir, e é por isso que o observador não precisa de ordem de inicialização e sobrevive a subir antes das coisas que observa.
Tudo é pull. Não há caminho de recepção, porque nada nesta stack empurra telemetria para ele e um receptor sem emissor é código que só pode estar errado. As métricas do host e as dele mesmo vêm do sysinfo; a liveness vem de uma conexão TCP ativa a cada tick; a CPU e a memória de container vêm da leitura direta dos arquivos de kernel do cgroup v2; e a atividade por membro vem de percorrer a árvore de tenants com glob e ler os transcritos JSONL em disco de forma incremental.
Essa última é a parte interessante, e ela tem quatro correções dentro, cada uma com um teste. As conversas de um projeto vivem em um diretório irmão workspace-<id>, e deixar de considerá-lo derrubava 42% das conversas no workspace que foi medido. Um diretório sessions/durable/ espelha os arquivos vivos um a um, então contá-lo dobra exatamente todo número. Cada execução de tarefa agendada escreve o seu próprio arquivo de sessão, o que desvia uma contagem de conversas para uma contagem de conversas mais cada execução de cron desde o provisionamento. E um transcrito que encolheu é lido de novo do zero em vez de tratado como um delta negativo.
O conteúdo das mensagens nunca é lido para virar um sinal. Só contagens, nomes e tamanhos.
Pelo que ele não é responsável
Ele nunca recebe um socket do Docker, por design. O proxy já tem um e roda como root; um segundo serviço com socket dobraria o raio de destruição do pior comprometimento possível da stack. Um socket é start, stop e exec em qualquer container e um caminho até o root do host, e tudo para o que o observador precisaria de um — mapear um container ao seu tenant — é servido pelo inventário somente leitura GET /v1/instances do proxy.
Ele não reporta custo de token. O picoclaw não escreve contagens de token em disco e não expõe endpoint de métricas, e o único caminho que já existiu era raspar um endpoint que esta stack não roda. Esse raspador foi apagado. Isso não está adiado; acabou. A contabilidade de tokens é uma das duas capacidades que justificaram escrever o crab-ganglion-harness, e ela vai chegar por aquele lado, não por este.
Ele não lê o conteúdo dos transcritos. O corpo das mensagens é dado do membro.
Ele não muda nada. Todo coletor puxa, o serviço não publica portas, e não há superfície de entrada nenhuma.
O
telemetryTokendo proxy, fornecido comoCRAB_TELEMETRY_TOKEN, protege a rota de inventário e não é um token de agente — o token bearer de um agente nunca deve ser usado no lugar dele. Com ele não definido, a rota nem chega a ser registrada, respondendo 404 em vez de 401, e esse é o padrão seguro, não um descuido.
Como ele roda nesta stack
O arquivo compose de desenvolvimento constrói o serviço a partir do submódulo e o roda com um bind somente leitura da raiz de dados do proxy em /data, um hostname estável e nenhuma porta publicada. Três detalhes ali são discutidos em detalhe no arquivo compose e valem a leitura antes de você mudar qualquer um deles.
O serviço recebe user: "0:0" nesta stack, mesmo que o Dockerfile dele rode como uid 10001. O motivo é que o proxy cria a árvore de tenants como root:root modo 0700, e a barreira fica no topo da árvore em vez de nas folhas, então um observador não-root não consegue atravessá-la de jeito nenhum. Três restrições mantêm essa permissão estreita e se sustentam juntas: o bind de /data continua somente leitura, nunca há um socket do Docker, e não há portas publicadas.
O hostname é fixado porque host.name é um rótulo. O hostname de um container assume por padrão o próprio id, que muda a cada recriação, então todo redeploy abriria uma série temporal nova — cinco tinham se acumulado em uma única noite de iteração. Um rótulo errado-mas-constante agrupa corretamente; um certo-mas-mutável nunca agrupa.
Deliberadamente não há depends_on. Colocar o observador atrás da saúde das coisas que ele observa suprimiria exatamente o sinal que ele existe para produzir.
Para de fato olhar os números, o repositório do produto carrega um overlay opcional com um OpenTelemetry Collector, Prometheus e Grafana:
docker compose -f docker-compose.yaml -f docker-compose.observability.yaml up -d
Quando você escrever queries, lembre que os nomes neste livro são os nomes OTel emitidos. A tradução de OTLP para Prometheus troca pontos por underscores e acrescenta um sufixo de unidade, então o que o observador emite como
system.memory.usageé coletado comosystem_memory_usage_bytes.
Como é construído e testado
Rust, um workspace do Cargo, edition 2024. rust-toolchain.toml pede o stable com rustfmt e clippy.
cargo build --release
./target/release/harnesssphere config.example.toml # imprime os sinais no seu terminal
O exportador de stdout é o padrão e prova a pipeline inteira sem nenhum backend para subir. Para um backend de verdade, compile com o adaptador OTLP — otlp é a única feature que o binário declara:
cargo build --release --features otlp
Saiba o que o CI verifica e o que não verifica aqui. audit.yml roda cargo audit --deny warnings quando um manifesto ou lockfile muda, semanalmente por agendamento, e sob demanda. deepseek-pr-review.yml posta um comentário de revisão automatizado em cada pull request. release-image.yml constrói e publica a imagem. Nenhum deles roda cargo test ou cargo clippy, e o Dockerfile compila o binário sem rodar a suíte. Os testes existem — crates/runtime/tests/ e harnesssphere/tests/ — mas rodá-los é com você.
O perfil de release é ajustado para ficar pequeno, com opt-level = "z", otimização em tempo de link e stripping. O unwinding de panic é mantido de propósito, porque o modelo de resiliência depende de capturar um panic dentro de um coletor antes que ele escape.
Como o código está organizado
O repositório é hexagonal, e as regras de arquitetura do ganglion foram modeladas nesta divisão de crates.
crates/domain/ canonical signal model, ports, pure policies — no I/O, no OpenTelemetry
crates/runtime/ supervisor, scheduler, circuit breaker, batching drain
crates/collectors/ host and self (Critical); process, endpoint probe, session, container (Optional)
crates/export/ stdout by default, OTLP behind the `otlp` feature
harnesssphere/ the binary: config, wiring, run
O domínio não ter nenhuma dependência de OpenTelemetry não é arrumação. Isso mantém a lógica importante — o circuit breaker, a política de criticidade, o enriquecimento — testável em unidade sem rede, e mantém um SDK pré-1.0 que ainda está mudando fora do núcleo.
A configuração é um arquivo TOML passado como primeiro argumento, com alguns overrides por variável de ambiente: HARNESSSPHERE_EXPORTER, OTEL_EXPORTER_OTLP_ENDPOINT e RUST_LOG. Todo coletor Optional fica desligado até ser configurado, então uma execução nova mostra só Host e Self. A configuração desta stack é config.zombie-crab.toml, que sonda o gateway, o proxy e o webapp, e deliberadamente deixa vazias as chaves de container de valor único — apontá-las para uma instância arbitrária produziria uma métrica que descreve um tenant e parece descrever a stack.
Para onde ir agora
Observabilidade cobre o que os números significam e como lê-los. Deploy cobre rodar a stack com o overlay de observabilidade acoplado.