O Herdr é o chão de fábrica.
A Workroom é o contrato social.
O relatório de pesquisa foi taxativo: “Herdr is the factory floor. It is not the foreman.” O inventário do sistema real confirmou o diagnóstico inverso: a Workroom atual construiu o chão de fábrica à mão — três vezes. A proposta é inverter isso, peça por peça.
✕ Eliminar / delegar ao Herdr
Spawn de processos com detach + PID files + guarda via lsof; heartbeat próprio de 30s + sweep de 90s; liveness via kill(pid,0); reconciliação por polling herdr agent list a cada 5s por worker; o dashboard React de 4,3 mil linhas. ≈7,7 mil LOC saem.
◐ Encolher drasticamente
O MCP (25 → ~18 tools), o tempo real (SSE + long-poll + heartbeat + polling → eventos Herdr + 1 stream de domínio), o dispatcher (fila em memória → fila durável na tabela; capacidade por regex em texto de erro → erro estruturado), a telemetria recomputada a cada snapshot.
✓ Manter — é o produto
O ledger SQLite com as 13 tabelas de domínio, as 11 invariantes de coordenação, obrigações de resposta, handoffs versionados, deliverables com hash do conteúdo real, review cross-provider, gate humano, memória da sala, idempotência. Nada disso existe no Herdr — nem deveria.
room/herdr-runtime.mjs, 136 LOC) que spawna workers de implementação via herdr agent start — ou seja, a “Fase 0” do plano do relatório já aconteceu organicamente. E já existe até um workspace Herdr chamado workroom-herdr-pivot com um pane rotulado workroom-hub: a tentativa anterior de migrar o hub para dentro do Herdr só parou num bloqueio de permissão de socket dentro do sandbox do worker. Esta proposta retoma exatamente esse fio.Um sistema operacional escrito à mão
A Collab Room real, mapeada read-only em …/2026-07-18/https-x-com-devjuninho-status-…/room/: 6.404 LOC em room/*.mjs + 4.343 LOC de dashboard React + hooks de lifecycle + 13 tabelas SQLite. O hub estava vivo na porta 4789 durante toda a inspeção — e continua intacto.
Onde estão as linhas
verde fica vermelho sai ou encolhe azul neutro
As três camadas que o Herdr já resolve — e a Workroom refez à mão
1 Runtime de processos
3 mecanismos de spawn coexistindo: Herdr para workers de implementação; spawn(detached) + PID file + guarda lsof para o hub e os reply runners; pipes com timeout SIGKILL para os CLIs dos providers. O próprio relatório de cutover proíbe o padrão detached para o hub — mas ele ainda é o padrão.
2 Estado & liveness
Heartbeat próprio de 30s no MCP bridge; sweep markStaleAgents a cada leitura com cutoff de 90s; liveness via kill(pid,0); SESSION_IDENTITY.json + hooks de cleanup por sessão; estado de stall derivado em três lugares que precisam concordar.
3 Tempo real & wake-up
Tabela events + SSE /events hand-rolled + long-poll /api/wait + polling de resposta a cada 20s + reconciliação Herdr por shell-out O(N) a cada 5s por dispatch — sem usar nenhum evento push que o Herdr já oferece.
Dores concretas encontradas no inventário (amostra das 14)
- Fila de dispatch volátil:
pendingDispatchesé umMapem memória do hub — restart perde a fila (durabilidade declarada: “re-despacha manual”). - Capacidade sinalizada por texto de erro: o dispatcher faz regex na mensagem
work capacity is exhaustedpara decidir enfileirar ou rejeitar. - Aprovação por regex em texto livre:
decideApprovalcasa padrões EN/PT (aprovo|autorizo…) no corpo de uma mensagem — frágil a paráfrase. - Migrações ad-hoc:
ALTER TABLEcondicional coluna a coluna, sem tabela de versão. - Estado sem retenção:
events(1.499 linhas),idempotency(996),agents(231) só crescem. - Identidade por heurística two-provider no MCP — quebra silenciosamente com uma terceira identidade.
Verificado no binário instalado — não na documentação
Cada capacidade abaixo foi confirmada por sondagem read-only do herdr 0.7.4 deste Mac (helps, api schema com 85 métodos, api snapshot, estado ao vivo: 7 workspaces, 2 agentes). Server rodando, socket ~/.config/herdr/herdr.sock, protocolo 16.
Spawn de agentes gerenciados
PTY persistente server-owned, com cwd, workspace, env e split — attachável depois.
herdr agent start <nome> --cwd … --workspace … --env K=V -- <argv>Estado semântico dos agentes
idle / working / blocked / done / unknown, com roll-up em workspace, tab e snapshot. Autoridade: hooks de integração > manifests de tela.
herdr agent list · agent get · pane.agent_status_changedStream de eventos push
26 tipos assináveis, incluindo os dois que hoje são polling na Workroom.
events.subscribe → pane.agent_status_changed · pane.output_matched · pane.exitedWaits bloqueantes
Espera nativa por texto/regex na saída ou por mudança de status — substitui loops de polling.
herdr wait output <pane> --match/--regex · wait agent-status · events.waitLeitura e envio de texto
Lê tela do agente (visible/recent) e envia texto ou teclas — base do “acordar” um agente.
herdr agent read/send <alvo> · pane send-text/send-keys/runWorktrees gerenciados
Cria checkout + workspace Herdr num passo; remove sem deletar a branch. Um worktree por task vira uma linha.
herdr worktree create/open/list/remove · [worktrees] directoryAttach humano estável
O humano entra em qualquer worker a qualquer momento — local ou via SSH. Hoje isso é impossível com runners detached.
herdr agent attach <alvo> [--takeover] · herdr --remote <ssh>Notificações de atenção
A “attention queue” que a pesquisa comunitária elegeu como O valor — nativa.
herdr notification show <título> [--sound done|request]Plugins out-of-process
11 métodos plugin.*, panes custom, event hooks — o caminho nativo para um painel Workroom dentro do Herdr.
plugin.link · plugin.pane.open · event hooks (herdr-plugin.toml)API completa + schema descobrível
85 métodos NDJSON sobre unix socket; snapshot do estado inteiro; schema JSON do binário.
herdr api snapshot · herdr api schema --jsonReport de estado/metadata
Pane/workspace reportam agente, estado, título e tokens — a Workroom pode anotar a frota sem tabela própria de presença.
pane report-agent/report-metadata · workspace report-metadataSessões nomeadas + live handoff
Isolamento hard por sessão quando preciso; panes sobrevivem a restart do server.
herdr --session workroom · server live-handoffFila / scheduler / DAG — NÃO existe
Confirmado: zero ocorrências de queue/schedul no schema de 235 KB. Leases, roteamento, capacidade, prioridade → ficam na Workroom. É exatamente o que o relatório manda construir por cima.
grep "queue|schedul" no schema → 0 · agent_panel_sort="priority" é só ordenação visualVerdade sobre tasks — NÃO é dele
Eventos Herdr são visibilidade e wake-up. Aceite, review, aprovação e memória ficam no ledger da Workroom (seção 06).
“Herdr is the factory floor. It is not the foreman.”0 integrações instaladas hoje
Status de agente hoje vem de manifests de tela (menos autoritativo). Quick win da Fase 1: instalar as integrações claude + codex para status por lifecycle hooks.
herdr integration status → 14 suportadas, 0 instaladasPeça por peça: o que o Herdr absorve
Cada linha: a peça custom de hoje (com evidência no código), o substituto nativo verificado, e o destino. eliminar encolher manter
Runtime & spawn
o fim dos PID fileslsof + retry 20×250msseam.mjs:56–89 · padrão proibido pelo próprio cutover reportherdr agent start workroom-hub -- node room/hub.mjs — supervisionado, attachável, PTY persistenteretoma o cutover bloqueado (workspace w2 “workroom-herdr-pivot”)kill(pid,0)seam.mjs:99–122, 382+herdr agent start sol-reply --env COLLAB_ROOM_* — estado semântico nativo e agent attach para o humano olharagent start --workspace --env · attach estávelcodex exec/claude -p com pipes, timeout SIGKILL, cwd sandbox, build de promptrunner.mjs · 368 LOCherdr agent list completo a cada 5s por dispatch ativowork-dispatch.mjs:165, 447–469events.subscribe em pane.agent_status_changed/pane.exited + events.wait — reconcilia dirigida a evento, não a timer26 tipos de evento · protocolo 16events.subscribe, worktree.*, notification.show; NDJSON direto no socket em vez de shell-out por chamadaherdr.sock · 85 métodos · schema descobrívelPresença & liveness
o fim do heartbeat de 30sagent list sob demanda + pane.exited = morte instantânea. Sem timer, sem sweeppane.exited · pane.agent_status_changedstale a cada snapshot por last_seen_atstore.mjs:1625–1628pane report-agent --agent "Sol (direct)" --state … + report-metadata com tokens — sem arquivo, sem cleanuppane report-agent/report-metadataherdr status + api snapshot + /health do hubherdr status [server|client]Tempo real & wake-up
4 mecanismos → 1pane.output_matched / agent_status_changed acordam na hora; ledger confirma a respostaget_answer funde em talk · −1 toolSuperfície — UI & MCP
a maior economia únicaplugin.pane.open + event hooks: o board mora no chão de fábrica, com attention queue nativa. Alternativa conservadora: congelar o app lendo só /api/snapshotplugin.* · padrão comunitário (Cmd+P command center)room_heartbeat, room_wait, get_answer, live_fleet_status; room_claim_reply/room_reply fundem em respond; identidade sem heurística two-providertools de domínio ficam intactasapi snapshot do Herdr cruzado com o ledger; detalhe de task permanece, mas simplesmenos código de paginação hand-rolledDomínio — fica e endurece
é o produto, não é plumbingqueued→leased→running na mesma tabela que já existe; Postgres + SKIP LOCKED só quando multi-hostexatamente o que o relatório prescreve (Fase 1)approval_decide direto do humano; a regex vira fallback legado, não o caminhohumano continua gate final — invariante #8A conta
Duas zonas, uma fronteira nítida
Em cima, o que é seu e fica: o protocolo. Em baixo, o que é produto e absorve: o runtime. A fronteira é um adapter único — o único processo que fala com o socket do Herdr.
As três regras da fronteira
1 Broker único
Só o HerdrAdapter (dentro do hub) fala com herdr.sock. Workers nunca recebem acesso ao socket — recebem comandos estreitos via MCP/brief. É o controle #1 do relatório (“broker the Herdr socket”) e hoje ele é impossível porque cada processo faz shell-out livre.
2 Eventos ≠ verdade
Eventos Herdr acordam e informam; quem decide é o ledger. pane.exited dispara reconciliação — mas é o hub que marca a task, aplica o lease e re-enfileira. Nuance verificada: done é estado de atenção da UI; para automação de conclusão, espera-se idle.
3 Um writer por worktree
Cada task de implementação: um worktree Herdr (worktree.create), uma branch, um dono, um lease — exatamente o isolamento que o relatório e o padrão comunitário (worktrees por milestone/epic) recomendam.
O ciclo de uma task na v2
- 1. Orquestrador:
task_create→task_dispatch(MCP fino). O hub clama atomicamente e gravaqueuedna tabela — durável desde o nascimento. - 2. Hub → HerdrAdapter:
worktree.create+agent startcom brief canônico e envCOLLAB_ROOM_*. Lease abre no ledger. - 3. Worker checkpointa fases (
reading/editing/testing/submitting) via MCP; o Herdr reportaagent_statuspor eventos. Silêncio além do threshold →stalled(regra já existente, agora com detecção imediata). - 4. Entrega:
deliverable_submit(hash do conteúdo real) → review cross-provider → gate humano → merge. O pane do worker permanece attachável até o cleanup. - 5. Humano acompanha tudo pelo painel Herdr (plugin) + notificações de atenção — sem abrir um app web separado.
O Herdr não é o encarregado — e é por isso que estas peças ficam
O relatório verificou duas vezes: fila, scheduler, leases, autorização e memória não existem no Herdr (grep no schema de 235 KB: zero). É exatamente aí que a Workroom é um produto, não plumbing.
◆ Ledger SQLite canônico
As 13 tabelas de domínio continuam a única verdade. SQLite é suficiente para single-host (veredito do relatório); Postgres + SKIP LOCKED só quando houver controllers concorrentes/multi-host.
◆ As 11 invariantes
Claim atômico; dono responsável até handoff aceito; efeitos idempotentes; review target = hash do conteúdo real; stale handoff não sobrescreve versão nova. Nada disso é terceirizável.
◆ Review cross-provider + gate humano
Anti-self-review, família diferente aprovando, humano como gate final. Um gerenciador de terminal não decide aceite — nem deve.
◆ Memória da sala
Decisões, constraints e procedimentos com supersession e proveniência. “Novas sessões não são novos contratados com amnésia.”
◆ Fila, leases, roteamento, capacidade
O scheduler que o Herdr deliberadamente não tem: rubrica R1/R2, lanes reply/work, prioridade, backpressure. Endurece: a fila sai do Map em memória para a tabela.
◆ Envelopes causais + idempotência
Hops, depth, visited, request hash contra replay alterado. At-least-once por construção, efeitos observáveis idempotentes.
Quatro fases — e a primeira já aconteceu
Mapeado sobre as fases do relatório (adapter spike → fila local segura → handoffs → failure drills), ajustado à realidade encontrada: o spike já está em produção.
Fase 0 · Adapter spike já existe
O herdr-runtime.mjs (136 LOC) já provisiona workspace, spawna workers e reconcilia estado — com testes. O que falta é disciplina de versão:
- Pinnar
herdr 0.7.4 / protocolo 16 / schema_version 1e gravar oapi schemano repo (o relatório manda registrar o observado). - Gate de compatibilidade no boot do hub: protocolo inesperado → degraded mode explícito, nunca falha silenciosa.
Fase 1 · Tudo sob o Herdr ~1 semana
- Retomar o cutover bloqueado: hub como
herdr agent start workroom-hub— de um contexto privilegiado (a lição do cutover report: nunca de dentro de worker sandboxed). - Reply lanes como panes persistentes; morrem os PID files, o detached spawn, o lsof e o kill(pid,0).
herdr integration install claude+codex→ status por lifecycle hooks (autoridade maior que manifest de tela).- Fila de dispatch durável na tabela
work_dispatches; capacidade com erro estruturado (fim da regex em mensagem).
herdr agent list mostra hub + lanes; restart do hub não perde um único dispatch enfileirado; zero PID files no repo.Fase 2 · Eventos nativos ~1 semana
- Adapter assina
pane.agent_status_changed,pane.exited,pane.output_matched; reconciliação vira dirigida a evento. - Apagar: heartbeat de 30s, sweep de 90s, polling de 5s por dispatch, polling de 20s do
get_answer(funde emtalkcom wait híbrido). notification.showpara obrigação de resposta vencendo e para approval pendente — a attention queue trabalha para o humano.
Fase 3 · Superfície fina + drills ~1–2 semanas
- MCP 25 → ~18 tools; identidade sem heurística two-provider.
- Painel Workroom como plugin Herdr (
plugin.pane.open+ event hooks) — ou, na rota conservadora, dashboard congelado lendo só/api/snapshot. - Aprovação por ato explícito no painel; regex EN/PT vira fallback legado.
- Failure drills do relatório: restart do hub, restart do Herdr, worker órfão, quarentena de ambíguo, retry storm — com reconciliação antes de re-despachar.
O que pode dar errado — e a contenção
| Risco | Contenção proposta | Prioridade |
|---|---|---|
| Workers falando direto com o herdr.sockbypass do broker — controle #1 do relatório | Só o hub carrega o adapter; workers recebem env sem HERDR_SOCKET_PATH e comandos estreitos via MCP/brief; audit no ledger. | P0 |
| Restart do Herdr com workers vivosestado ambíguo pós-restart | Reconcile before dispatch: comparar leases do ledger × api snapshot; quarentenar o que for ambíguo antes de criar replacements. Live-handoff existe mas é opt-in. | P0 |
| Cutover executado de dentro de worker sandboxedfoi exatamente o blocker de 20 Jul: PermissionDenied no socket | Operações de cutover/migração só de contexto privilegiado (sessão interativa do humano), nunca despachadas como task. | P0 |
Confundir done com conclusãonuance verificada no binário | “done é estado de atenção da UI”: automação espera idle + evidência no ledger; nunca aceitar por status visual. | P1 |
| Drift de versão do Herdrupdate muda protocolo/schema | Pin 0.7.4/16 + gate no boot + schema gravado no repo; canal stable; update vira mudança revisada, não surpresa. | P1 |
| Plugin do painel é supply chainmarketplace herdr não é curado | Plugin próprio, revisado, sem auto-fetch de manifests remotos após baseline; roda com os mesmos privilégios do usuário — tratar como código de produção. | P1 |
| Herdr como SPOF do runtime | Degraded mode já existe no SKILL.md (trabalha desconectado, reporta backlog); fila durável sobrevive ao Herdr cair; hub responde “indisponível” honesto em vez de fingir. | P1 |
Antes → depois, medível
| Métrica | Hoje | Alvo v2 |
|---|---|---|
| LOC de infra para manter | 10.750 (room 6.404 + dashboard 4.343) | ≈3.000 (−70%) |
| Mecanismos de spawn | 3 (herdr, detached+pidfile, pipes) | 1 (herdr) |
| Mecanismos de tempo real | 4 (SSE, long-poll, heartbeat, polling) | eventos Herdr + 1 stream de domínio |
| Detecção de worker morto | sweep de 90s | evento pane.exited (<2s) |
| Wake de reply | polling 20s | push (output_matched / status) |
| Attach humano em worker | impossível (detached) | herdr agent attach |
| Tools MCP | 25 | ~18 |
| Integrações de status instaladas | 0 (manifest de tela) | 2+ (claude, codex — lifecycle) |
| Fila de dispatch | Map em memória (volátil) | durável na tabela |
| Supervisão de hub/runners | PID files + lsof + operador | herdr server |
As métricas de domínio não mudam: rework-rate por modo (R4), accept-rate e time-to-catch dos principals, reply P50/P95, queue age — agora derivadas sob demanda em vez de recomputadas a cada snapshot.
Como esta proposta foi construída
Três frentes de investigação em paralelo, todas 100% read-only — nenhum arquivo do sistema, processo ou banco foi modificado; o SQLite foi inspecionado numa cópia em /tmp.
Decisões que são suas (não ousei decidir por você)
? Painel: plugin Herdr ou web congelado?
Plugin é o padrão comunitário e vive no chão de fábrica, mas é supply chain e código novo. O web app existe hoje e funciona — congelá-lo lendo só /api/snapshot custa zero. Minha recomendação: começar congelado, plugin na Fase 3 se fizer falta.
? Reply lane: pane persistente ou runner sob Herdr?
Pane persistente por identidade é o ideal (attachável, observável); manter o runner.mjs apenas spawnado pelo Herdr é o passo intermediário seguro. Propus o intermediário na Fase 1 e o ideal como evolução.
? Sessão Herdr default ou nomeada “workroom”?
O relatório recomenda sessão compartilhada por default e nomeada só para isolamento hard. Como o hub+workers são infra, uma sessão workroom separada da sua sessão interativa pode ser mais limpa — mas custa um segundo socket.
? SQLite agora, Postgres quando?
O relatório: SQLite OK single-host; Postgres obrigatório para controllers concorrentes/multi-host. Hoje você é single-host — eu ficaria no SQLite e deixaria a migração como decisão registrada na memória da sala.