Por que o T25 orquestra CLIs de agente em vez de ser um framework em processo
A decisão de arquitetura que define o T25: cada papel roda um CLI real, headless, em worktree isolada, e a fábrica governa o que acontece entre os estágios.
A decisão em uma frase
Quando começamos a construir o T25, a pergunta de arquitetura não era "qual modelo usar". Era: o agente é código dentro do nosso processo, ou um processo que nós governamos? Escolhemos a segunda opção, e ela explica quase tudo no produto, do jeito que isolamos tarefas ao jeito que auditamos decisões.
Uma biblioteca de agentes em processo roda dentro da sua aplicação: você chama uma função, o loop do agente executa na sua memória, e o resultado volta como valor de retorno. Uma fábrica que orquestra CLIs faz o contrário: cada papel do pipeline, research, planner, dev, QA, reviewer, security, é um CLI real, autenticado com a assinatura do usuário, spawnado como processo separado, recebendo um prompt montado pela fábrica e devolvendo saída que a fábrica parseia antes de decidir qualquer coisa.
Entre os dois modelos há uma diferença de postura. No primeiro, o agente é uma peça sua. No segundo, o agente é um fornecedor com contrato, e o contrato é o que permite governança.
O que acontece entre os estágios ao orquestrar CLIs de agente
O ponto central é quem controla o que acontece entre uma saída de agente e o próximo dispatch. No T25, a resposta é o FactoryService (src/factory/service.ts): um loop sobre task.state em que cada iteração escolhe o adapter, executa o papel, parseia o artefato e chama transition(). A máquina de estados (src/core/state-machine.ts) centraliza as transições legais via assertTransition, o serviço nunca atribui estado diretamente.
Isso quer dizer que o modelo não anda sozinho pelo pipeline. Ele produz texto num estágio; o código decide se aquele texto é um artefato válido, o que ele autoriza e qual o próximo estado. Um parse failure não é retentado em silêncio: falha a tarefa com erro descritivo. Um diff que estoura limits.max_files_changed ou limits.max_diff_lines é parado por checkDiffLimits() depois da implementação. Um plano só pede aprovação humana se requiresPlanApproval() disser que pede, com base no risco configurado no t25.yaml.
Esse desenho é o que nos permite afirmar, num post anterior, que o APPROVE do modelo não é o merge: o veredito do reviewer é recalculado por evaluateReview() a partir dos findings, e o merge continua sendo uma ação humana depois dos checks obrigatórios.
Isolamento por worktree, isolamento por processo
Cada tarefa roda numa Git worktree própria, em .factory/worktrees/<taskId>, no branch factory/<taskId>, nunca no checkout principal. Já contamos os detalhes noutro post: guards de path contra symlink e .., verificação de ownership via .factory-metadata, locks por PID e um cleanup() que recusa worktree suja e nunca faz force-remove.
A decisão de orquestrar CLIs soma a isso um segundo isolamento: o do processo. O adapter de cada CLI (src/adapters/base.ts) faz spawn com argv puro, sem shell, nomes de branch e caminhos nunca são interpolados numa string de comando. Prompts acima de 100KB são materializados em arquivo temporário em vez de irem por argv, para não estourar ARG_MAX. Há timeout, abort e detecção de quota, e um provider de sandbox Docker opcional executa comandos com network none.
O efeito prático sobre o raio de explosão: quando um agente se comporta mal, loop de edição, diff gigante, tentativa de escrever fora do diretório, o estrago está contido num diretório descartável, num branch que ninguém fez merge, num processo que pode ser morto. Nada disso depende do modelo estar de bom humor.
Auditabilidade como consequência
Processos que terminam deixam rastro. Cada run de agente no T25 grava log, eventos SSE e artefato persistido no store endereçado por SHA-256; o Markdown solto em .factory/artifacts/ não é mais a fonte de verdade. As decisões do operador, aprovar, mergear, cancelar, reexecutar, arquivar, vão para um audit.jsonl append-only com ator e timestamp, exposto via GET /audit no dashboard. Em produção, tarefas, runs e leases vivem no Postgres, com leasing atômico via FOR UPDATE SKIP LOCKED e heartbeat de worker.
Quando a auditoria de um incidente pede "quem escreveu isso, com qual prompt, a partir de qual estado", a resposta é uma consulta, não uma reconstrução de memória. Em uma arquitetura em processo, obter o equivalente exige instrumentar você mesmo cada chamada, o que a maioria dos times adia para sempre.
Troca de agente sem reescrita
O t25.yaml define, por papel, uma lista ordenada de adapters, uma cadeia de fallback, não um adapter fixo. Se o Claude Code não está no PATH, ou responde com quota esgotada, o selectAdapter() tenta o próximo da lista. Um adapter novo é um CliAdapterSpec fino (nome do binário e buildArgs), não uma reimplementação de runtime: a infraestrutura de spawn, timeout e log é compartilhada em src/adapters/base.ts.
Esse é um benefício que só existe porque o agente é um processo externo.
Custo e credencial ficam com você
Cada CLI roda autenticado com a sessão e a assinatura do usuário, na máquina dele. O T25 não pede chave de API de provedor, não proxy o tráfego dos modelos e não vê o conteúdo das conversas além da saída que parseia. Os eventos de usage que os adapters emitem no fim de cada run alimentam uma estimativa de custo por run (src/core/cost.ts), com taxas configuráveis, para visibilidade, não para cobrança.
Para um time que já paga pelos CLIs, isso muda a conta do projeto: a fábrica soma o custo de orquestração, não substitui o contrato dos agentes. E para segurança, significa que credencial nenhuma nova é criada, armazenada ou transmitida.
O que essa escolha custa
Nenhuma arquitetura é de graça. Três custos são reais e convivem com a gente:
- Latência de spawn. Cada estágio paga o boot de um processo CLI. Para tarefas curtas, isso é proporcionalmente caro. Nossa resposta é empacotar trabalho por estágio, o pipeline inteiro roda poucos dispatches por tarefa, e tratar o tempo de máquina como barato comparado ao tempo de revisão humana.
- Dependência de CLIs de terceiros. Mudanças de versão, flags e formatos de saída quebram adapters. Mitigamos com parsers tolerantes (compatibilidade com saídas legadas), detecção de versão no boot e testes de contrato, mas a fragilidade existe e volta de tempos em tempos.
- Menos controle sobre o loop do modelo. Quando o agente é uma biblioteca no seu processo, você enxerga cada chamada de tool, cada token, e pode intervir no meio do raciocínio. Quando é um processo, você governa as bordas, prompt que entra, saída e logs que saem, e o meio é território do CLI. Para governança isso chega; para pesquisa em comportamento de modelo, não.
Há casos em que o framework em processo é a escolha certa: protótipos, experimentos de orquestração, produtos cujo core é o próprio agente. Se o seu problema é "preciso de um loop de agente customizado dentro do meu serviço", uma biblioteca serve melhor. Se o problema é "preciso que uma equipe entregue software com agentes sem abrir mão de revisão, escopo, isolamento e merge humano", a fábrica processo-externo é o desenho que sustenta isso.
Perguntas frequentes
Orquestrar CLIs não é mais lento que chamar uma API diretamente?
É, por estágio: há boot de processo e materialização de prompt. A comparação justa não é por chamada, é por tarefa entregue. Uma chamada de API barata que produz código sem spec, sem worktree isolado e sem veredito auditável sai mais cara na revisão do que um dispatch com overhead de spawn.
E se o CLI que eu uso sair do ar ou mudar radicalmente?
A cadeia de fallback por papel existe exatamente para isso. Como o contrato entre a fábrica e o agente é a saída parseada, trocar o executor de um papel não muda a política, o mesmo argumento do post sobre o que é uma software factory: a fábrica decide o que acontece com o código, quem escreve é substituível.
O T25 roda os agentes com as minhas credenciais. Isso é seguro?
O T25 roda local, na sua máquina, com os CLIs que você já autenticou. Nenhuma credencial é enviada a um servidor nosso, o control plane é local, o Postgres é seu. O que a fábrica centraliza é política, não segredo. O isolamento por worktree e as regras de workspace detalham a parte defensiva.
Dá para usar o T25 como biblioteca dentro do meu serviço?
Não é o desenho. O T25 é um harness standalone com API REST/SSE, CLI e dashboard sobre o mesmo FactoryService. Se você precisa embutir um loop de agente num processo seu, o que recomendamos é usar esse loop onde ele brilha e deixar com a fábrica o que exige governança, worktree, gates e merge.
Onde leio mais sobre a arquitetura?
A documentação tem o guia de arquitetura e o modelo de domínio, e o blog já cobriu o isolamento por worktree e o gate de aprovação em profundidade. O código do T25 é fechado; a documentação descreve o comportamento que citamos aqui.