Lição 19 · Curso de Fusão · Parte 3 · A arquitetura · O swarm
Alembic × Hermes · O Curso de Fusão · Parte 3 — A arquitetura

O swarm: orchestrator → lead → worker

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/.

Leia primeiro (fonte primária)
packages/swarm/src/orchestrator.ts + packages/swarm/src/types.ts (lidos verbatim)

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.

Leia a versão simples, ou abra a camada técnica em qualquer seção.
Ao fim desta lição você vai saber
  • Por que a hierarquia orchestrator → lead → worker tem profundidade travada em MAX_DEPTH = 2 — e o que isso impede.
  • O que torna uma tarefa ready, e os três tipos de worker (model, command, background).
  • Como o resume recupera uma tarefa running órfã de um worker morto — execução at-least-once.
  • Por que isolate: true sem worktree é um erro (fail-closed), nunca uma execução silenciosa.
  • Por que o park T4 nunca auto-executa, e por que o computeReward não é aprendizado por reforço.
Suposições tolas (assumimos pouco de você)
  • Você já viu a ideia de uma fila de tarefas com dependências (um item espera outro terminar).
  • Você sabe o que é um processo que pode morrer no meio do caminho.
  • Você lembra das lições anteriores que Result nunca lança e que um gate falha fechado. Se não, tudo bem — recapitulamos aqui.
1

A grande ideia

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.

Analogia: o swarm é esse canteiro com uma planta de segurança embutida. Tudo que importa fica anotado num caderno à prova de fogo (o disco). Se um operário desmaia no meio de uma tarefa, ao reabrir o caderno o mestre vê a tarefa pela metade e manda refazê-la — nada se perde. E nenhuma tarefa perigosa (mexer em algo irreversível) é executada sem alguém apertar o botão.

É 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.

O que o pacote diz de si mesmo

@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.

Cinco propriedades, uma fila no centro fila dependency-gated 3 tiers · profundidade ≤ 2 git-worktree (isolamento) resume à prova de crash park T4 inviolável
A fila é o coração; as quatro propriedades em volta a tornam paralela, isolada, recuperável e segura.
2

Três tiers, um nível de aninhamento

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.

Infográfico em árvore: orchestrator na profundidade 0 no topo, dois leads na profundidade 1 no meio, dois workers na profundidade 2 embaixo; sobre os workers um selo vermelho 'canSpawn = false (leaf)' e uma seta de spawn bloqueada por um X; legenda com três chips orchestrator/lead/worker.

A profundidade é um invariante, não um ajuste: na folha, canSpawn é falso e o spawn é estruturalmente impossível.

A hierarquia, com a profundidade marcada
orchestrator · depth 0 lead · depth 1 lead · depth 1 worker · depth 2 worker · depth 2 canSpawn = false em MAX_DEPTH (folha) um worker é uma folha — ele executa, nunca gera; o aninhamento é de um nível, estruturalmente
Preveja antes de continuar

Um lead na profundidade 1 cria workers. Um desses workers pode criar seus próprios sub-workers?

Não. Um worker está em 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.

Onde mora a trava

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).

Guarde isto Profundidade é invariante. "três tiers, um nível de aninhamento" não é uma recomendação de estilo — é uma propriedade estrutural que o código garante. É o que mata o medo do fan-out recursivo.
3

A fila — dependency-gated, ready quer dizer pronto

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:

blocked ready running done failed

Três tipos de worker

Tipo de workerO que roda
model workeruma chamada de adapter (o run() da lição 14, a cintura estreita)
command workertaskSpec.command → um subprocesso real; exit 0 → done, qualquer outro → failed
background workertaskSpec.background → um filho desacoplado que sobrevive à morte do pai e se re-conecta ao seu report no resume (exige command, proíbe isolate)
Exemplo guiado — quando esta tarefa fica pronta?
1
Tarefa u3 declara dependsOn: ['u1','u2']. No início, u1 e u2 ainda não terminaram → u3 fica blocked.
2
u1 chega a done. Ainda falta u2u3 continua blocked (o gating exige todas).
3
u2 chega a done. Agora todas as dependências estão em sucesso terminal → u3 vira ready e entra na corrida.
4
Agora você: se 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 contrato da tarefa

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).

4

Resume à prova de crash — o filesystem é a verdade

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.

Infográfico em quatro etapas da esquerda para a direita: 1) disco com events.jsonl append-only e checkpoint.json; 2) replayInto reaplica checkpoint e cada task-state, latest-per-id vence; 3) cartão de alerta com tarefa 'running' (worker morreu) rebaixada a 'ready'; 4) cartão verde 're-tentada — at-least-once'; um cadeado 'Zod valida cada linha na borda de durabilidade'.

Um crash no meio da declaração não perde nenhuma tarefa: a órfã running vira ready e o enqueue idempotente cobre toda tarefa.

O fluxo do replayInto
events.jsonl + checkpoint.json replayInto latest-per-id · idempotente running (órfã) worker morreu ready (re-tentada) at-least-once

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
append-only
o journal events.jsonl nunca reescreve linhas
latest-per-id
o replay é idempotente: o último estado por id vence
at-least-once
a tarefa órfã é re-executada, nunca perdida
Por que "at-least-once" e não "exactly-once"? Porque rebaixar a órfã e re-tentar pode rodar a tarefa uma segunda vez se ela já tivesse efeito antes de morrer. O swarm escolhe nunca perder trabalho (re-tentar) em vez de arriscar pular uma tarefa — e conta com o enqueue idempotente para manter o denominador do monitor consistente.

O endereçamento por conteúdo fecha o ciclo

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.

5

Isolamento e o park T4 — ambos fail-closed

Isolamento por worktree, fail-closed

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.

A bifurcação fail-closed do isolamento
isolate: true ? worktree existe sem worktree withWorktree → roda isolado branch dedicado + portas determinísticas err — a run para nunca uma execução não-isolada
O park T4 inviolável

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.

As cinco razões de park (conjunto fechado)
tier-t4 irreversible legal security manual um conjunto fechado (z.enum) — nada fora desta lista pode ser razão de park
Cuidado Parkeado não é "falhou". Uma tarefa no park não é um erro — é trabalho deliberadamente segurado para um humano decidir. Ela não some e não roda sozinha no próximo resume; espera alembic approve, reject ou propose.

Onde isso é forçado

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.

6

O reward PARL — heurística, não aprendizado por reforço

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.

O que NÃO é
RL: gradiente que atualiza pesos sem treino, sem gradiente, sem rede que aprende
O que É
escalar heurística determinística HITL-gated por requiresApproval molda a priorização, com humano no loop

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.

Flashcard · recуperação
PARL no swarm significa que ele "aprende por reforço"?
clique para virar
Resposta
Não. 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.

A assinatura honesta

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).

7

Confusões comuns

"O reward PARL quer dizer que ele aprende por reforço." Não — 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.

"Background workers rodam realmente assíncronos, liberando o slot." Ainda não — é um gap conhecido: background workers ainda bloqueiam o slot da tarefa enquanto fazem polling do report; o aprimoramento de async-drain é uma fatia futura (o seam do dispatcher já a acomoda). O curso declara os gaps honestamente, em vez de superdimensionar.

"delegate_tool.py do Hermes deveria ser portado." Não — a matriz de fusão o classifica como IGNORE justamente porque o swarm já existe e delega nativamente, com profundidade limitada, resume e park T4. Portar seria duplicar um subsistema inferior.

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.

8

Recapitulando em 6 slides

Slide 1 · O papel

orchestrator → lead → worker

Três tiers, e o aninhamento é de um nível só: MAX_DEPTH = 2. Um worker é uma folha.

orchestratorleadleadworker
01
Slide 2 · A fila

ready quer dizer pronto

Uma tarefa só roda quando todas as dependsOn chegam a done. O grafo é honrado, não torcido.

02
Slide 3 · Os workers

model · command · background

Chamada de adapter, subprocesso (exit 0 → done) ou filho desacoplado que sobrevive ao pai e proíbe isolate.

03
Slide 4 · O resume

at-least-once, do disco

events.jsonl + checkpoint.jsonreplayInto. A órfã running vira ready e é re-tentada.

04
Slide 5 · As duas travas

fail-closed em dose dupla

isolate sem worktree = erro. T4/irreversível/legal/segurança/manual = park, nunca auto-executado.

05
Slide 6 · O reward

PARL ≠ RL

computeReward é heurística determinística, HITL-gated por requiresApproval. Sem gradiente, sem treino.

06
Slide 1 / 6 use
Recall ativo: feche os olhos e tente recitar as cinco propriedades do swarm — tiers limitados, fila dependency-gated, resume at-least-once, isolamento fail-closed, park T4. Depois confira na seção 1.
9

Verifique seu entendimento

Três perguntas — a pontuação corre conforme você responde
1. Um lead na profundidade 1 cria workers. Um desses workers pode criar seus próprios sub-workers?
Correto: c. A profundidade é travada em 2. Um worker é uma folha; subtarefas são tarefas-folha, então o aninhamento é estruturalmente de um nível. Isso impede o fan-out recursivo descontrolado — o limite de profundidade é um invariante, não um ajuste.
2. Um processo worker é morto no meio da tarefa e a run é depois resumida. O que acontece com essa tarefa?
Correto: b. 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.
3. Uma spec de tarefa define isolate: true mas nenhuma config de worktree é fornecida. O que o orquestrador faz?
Correto: d. O isolamento por worktree é fail-closed: pedir um isolamento que você não pode ter para a run. O motor nunca executa caladamente uma tarefa contra o checkout compartilhado quando você pediu separação explicitamente.
Acertos: 0/3
As cinco verdades do swarm
  1. A hierarquia é orchestrator → lead → worker e o aninhamento é de um nível: MAX_DEPTH = 2.
  2. Só tarefas ready rodam; ready exige todas as dependsOn em done.
  3. O estado vive no disco (events.jsonl + checkpoint.json); re-rodar É o resume, at-least-once.
  4. Fail-closed em dose dupla: isolate sem worktree é erro; T4/irreversível/legal/segurança/manual é park.
  5. computeReward é heurística HITL-gated, não RL — e é por isso que o delegate_tool.py do Hermes é IGNORE.
Pergunta para levar adiante: se o swarm já delega com profundidade limitada, resume e park, o que ainda falta nele? (Pista: a seção 7 cita um gap real — o async-drain dos background workers. A próxima lição, "O método de reverse-engineering", mostra como caçar e declarar gaps assim.)