O swarm L3 é como o Alembic roda muitas unidades de trabalho ao mesmo tempo e sobrevive a crashes. Um orquestrador de 3 tiers com profundidade limitada, sobre uma fila de tarefas dependency-gated, com estado filesystem-as-truth, isolamento por git-worktree, um park T4 inviolável e resume à prova de crash. É o pacote que a matriz de fusão usa para classificar o delegate_tool.py do Hermes como IGNORE — o Alembic já delega, nativamente e melhor. Fonte: packages/swarm/.
Esta lição destila o orquestrador real: MAX_DEPTH = 2 e ROLE_DEPTH em types.ts; canSpawn, replayInto, o park T4 e o worktree fail-closed em orchestrator.ts. Tudo aqui é citado de arquivo real do monorepo — nenhuma capacidade é inventada, e os gaps conhecidos são ditos como gaps.
MAX_DEPTH = 2 — e o que isso impede.ready, e os três tipos de worker (model, command, background).running órfã de um worker morto — execução at-least-once.isolate: true sem worktree é um erro (fail-closed), nunca uma execução silenciosa.computeReward não é aprendizado por reforço.Result nunca lança e que um gate falha fechado. Se não, tudo bem — recapitulamos aqui.Imagine um canteiro de obras. Há um mestre de obras (o orchestrator), alguns encarregados (os leads) e os operários (os workers) que de fato martelam pregos. O mestre não martela; ele divide o trabalho. Os encarregados distribuem; os operários executam. E há uma regra rígida: um operário não vira encarregado — ele não pode montar sua própria equipe. Isso mantém a obra sob controle.
É exatamente isso que o swarm faz para agentes de software: paraleliza unidades de trabalho, respeita as dependências entre elas, sobrevive a crashes lendo o estado do disco, e se recusa a tomar o passo perigoso sozinho.
@alembic/swarm é a orquestração L3: um orquestrador de 3 tiers com profundidade limitada (orchestrator → lead → worker) sobre uma fila de tarefas dependency-gated, com estado durável filesystem-as-truth (JSONL append-only + índice opcional via node:sqlite), um hook de reward PARL HITL-gated, isolamento por git-worktree com portas determinísticas, runs resumíveis à prova de crash endereçadas por conteúdo (re-rodar reaplica o journal e recupera tarefas em voo, em vez de reiniciar), um monitor de progresso read-only por polling de arquivo para observabilidade AFK, e um park T4 inviolável para trabalho irreversível/legal/segurança que nunca pode auto-executar.
packages/swarm/src/index.ts (6–21) — o resumo do próprio pacote.
A hierarquia de papéis é orchestrator → lead → worker, e ela é limitada: MAX_DEPTH = 2. Um nó na profundidade máxima é uma folha e não pode gerar filhos; subtarefas são tarefas-folha, então o aninhamento é estruturalmente de um nível só. É uma trava deliberada contra o fan-out recursivo descontrolado — uma equipe que cria equipes que criam equipes ao infinito.
A profundidade é um invariante, não um ajuste: na folha, canSpawn é falso e o spawn é estruturalmente impossível.
Um lead na profundidade 1 cria workers. Um desses workers pode criar seus próprios sub-workers?
MAX_DEPTH = 2, então canSpawn retorna falso. Ele é uma folha: só executa. Subtarefas declaradas a partir dele são tarefas-folha. Por isso o aninhamento é, por construção, de um único nível — e o fan-out recursivo é impossível, não apenas desencorajado.ROLE_DEPTH mapeia cada papel a um número (orchestrator 0, lead 1, worker 2) e MAX_DEPTH = 2 é a constante. O teto de profundidade é também forçado em tempo de execução por canSpawn no orquestrador: na profundidade máxima ela retorna falso e nenhuma declaração de subtarefa é aceita. O ordenamento dos papéis é o que limita a recursão — não há flag para destravá-la.
packages/swarm/src/types.ts — MAX_DEPTH(32)/ROLE_DEPTH(25+); orchestrator.ts — canSpawn (107).
Só tarefas ready rodam. Uma tarefa vira ready quando todas as suas dependências (dependsOn) chegam ao sucesso terminal (done). O orquestrador nunca roda uma tarefa cujos pré-requisitos não tenham de fato terminado com sucesso — o grafo de dependências é honrado, não torcido para que dê certo.
Explore a máquina de estados de uma tarefa. Clique em um estado para ver o que ele significa:
| Tipo de worker | O que roda |
|---|---|
| model worker | uma chamada de adapter (o run() da lição 14, a cintura estreita) |
| command worker | taskSpec.command → um subprocesso real; exit 0 → done, qualquer outro → failed |
| background worker | taskSpec.background → um filho desacoplado que sobrevive à morte do pai e se re-conecta ao seu report no resume (exige command, proíbe isolate) |
u3 declara dependsOn: ['u1','u2']. No início, u1 e u2 ainda não terminaram → u3 fica blocked.u1 chega a done. Ainda falta u2 → u3 continua blocked (o gating exige todas).u2 chega a done. Agora todas as dependências estão em sucesso terminal → u3 vira ready e entra na corrida.u2 tivesse terminado em failed em vez de done, o que aconteceria com u3? (Resposta: permanece bloqueada — o gating exige sucesso terminal done, não apenas "terminou".)O taskSpec é uma especificação imutável. dependsOn lista ids de tarefas que devem chegar a um estado terminal de sucesso; o prompt é o texto entregue ao adapter; command liga o command worker (exit 0 → done); background liga o filho desacoplado (e proíbe isolate, porque um filho desacoplado não compartilha o ciclo de vida do worktree do pai). Cada tipo é mutuamente coerente por schema.
packages/swarm/src/types.ts — dependsOn (82), command (95), background (100–107).
O estado é durável no disco: um journal append-only events.jsonl mais um checkpoint.json. O resume reaplica o último checkpoint e cada evento task-state (idempotente, o mais recente por id vence). A parte engenhosa é como ele trata um worker que morreu no meio: a tarefa fica órfã em running, e o resume a rebaixa para ready e a re-tenta.
Um crash no meio da declaração não perde nenhuma tarefa: a órfã running vira ready e o enqueue idempotente cobre toda tarefa.
Cada valor que cruza uma borda de durabilidade é validado por Zod na leitura, então uma linha de journal corrompida ou editada à mão é rejeitada na borda em vez de silenciosamente confiada (filesystem-as-truth, a invariante ③ da lição 16). E a unicidade global de id por toda a árvore é validada antes de qualquer coisa ser journaled, então um id duplicado não consegue envenenar um resume.
// packages/swarm/src/orchestrator.ts (condensado) — replayInto // reaplica checkpoint + cada evento task-state (latest-per-id vence, idempotente) // uma tarefa 'running' órfã (worker morreu) é rebaixada a 'ready' e re-tentada // ⇒ execução at-least-once; o enqueue é idempotente, então o denominador // do monitor cobre toda tarefa mesmo após um crash no meio da declaração
A run-id é endereçada por conteúdo (runIdFor): re-rodar a mesma declaração resolve para o mesmo diretório de run, então o resume é automático — não há "modo resume" separado, re-rodar É o resume. O orquestrador nunca lança (todos os caminhos retornam Result); um crash não corrompe o estado porque o estado vive no disco e é reaplicado, não mantido só em memória.
packages/swarm/src/orchestrator.ts — replayInto, unicidade global de id, runIdFor; types.ts — reatividade da run-id endereçada por conteúdo.
isolate: true sem configuração de worktree é um erro, nunca uma execução não-isolada silenciosa. Se você pediu isolamento e o ambiente não consegue provê-lo, a run para — ela não executa caladamente sua tarefa contra a working tree compartilhada. Um command worker que não pode tocar o checkout principal ganha um git worktree via withWorktree, ou não roda de jeito nenhum.
Tarefas irreversíveis / legais / de segurança / T4 são roteadas para o ledger do park e nunca auto-executadas. O park é idempotente entre resumes e estruturalmente protegido — uma tarefa parkeada é imóvel. As razões do park são um conjunto fechado: tier-t4 / irreversible / legal / security / manual. É a expressão, no nível do swarm, do princípio do human-gate: o motor se recusa a dar o passo perigoso por conta própria, e você o reabre com alembic approve/reject/propose.
alembic approve, reject ou propose.
O parkReasonSchema é um z.enum(['tier-t4','irreversible','legal','security','manual']) em types.ts. No orquestrador, o park é aplicado antes de qualquer execução e é idempotente no replay (re-parkear a mesma tarefa não a duplica). O worktree fail-closed e o park compartilham a mesma filosofia: o caminho perigoso ou impossível para a run em vez de degradar silenciosamente.
packages/swarm/src/types.ts — parkReasonSchema (57–64); orchestrator.ts — park T4 + worktree fail-closed.
O sinal de reward (computeReward) é uma escalar moldada heurística — estilo PARL, não aprendizado por reforço (RL) — e é HITL-gated por requiresApproval. O nome evoca RL; a implementação é uma heurística determinística com um humano no loop. Não há gradiente, não há treino.
Por padrão requiresApproval é true: o sinal não influencia o roteamento futuro sem que um humano aprove. O reward existe para moldar a priorização de tarefas — uma pista, não uma política autônoma.
computeReward é uma escalar heurística moldada, explicitamente não RL, e HITL-gated por requiresApproval (default true). Sem gradiente, sem treino — só priorização com humano no loop.O reward é um hook: computeReward produz uma escalar a partir de sinais observáveis (sucesso, custo, proximidade do objetivo) de forma determinística. O campo requiresApproval (default true) controla se a escalar pode influenciar o roteamento futuro. "PARL-style" descreve a forma (uma escalar moldada por feedback) sem nenhuma das mecânicas de RL — é a escolha de nomenclatura mais honesta que o pacote pôde fazer, e o curso a repete em vez de superdimensionar.
packages/swarm/src/types.ts — computeReward (168–184), requiresApproval (181, default true).
computeReward é uma escalar heurística moldada, explicitamente não RL, e é HITL-gated por requiresApproval. O reward molda a priorização com um humano no loop; não há gradiente, não há treino.Em uma frase: o swarm é um time de software que nunca cria times recursivamente sem fim, nunca perde trabalho num crash, nunca executa caladamente algo que pediu isolamento, e nunca toma o passo perigoso sozinho.
Em uma frase: orquestração L3 com MAX_DEPTH = 2, fila dependency-gated, replayInto at-least-once endereçado por conteúdo, withWorktree fail-closed e um parkReasonSchema fechado HITL-gated — tudo retornando Result, nunca lançando.
replayInto reaplica o checkpoint mais os eventos task-state; uma tarefa deixada em running por um worker morto é rebaixada a ready e re-rodada. Combinado com o enqueue idempotente, o resume cobre toda tarefa mesmo após um crash no meio da declaração.isolate: true mas nenhuma config de worktree é fornecida. O que o orquestrador faz?orchestrator → lead → worker e o aninhamento é de um nível: MAX_DEPTH = 2.ready rodam; ready exige todas as dependsOn em done.events.jsonl + checkpoint.json); re-rodar É o resume, at-least-once.isolate sem worktree é erro; T4/irreversível/legal/segurança/manual é park.computeReward é heurística HITL-gated, não RL — e é por isso que o delegate_tool.py do Hermes é IGNORE.