Transformando Manutenção de Repositório em Markdown: Mantendo uma Codebase Rust Viva com Workflows Agênticos
Repositórios de vida longa sofrem drift: componentes depreciados persistem, camadas sangram e testes não cobrem as funções que de fato quebram. No meu próprio estudo, transformei três tarefas recorrentes em workflows markdown agendados que o próprio repositório roda sobre si mesmo, e depois escrevi o que aprendi sobre limitar o raio de impacto, parear checagens LLM com scans determinísticos, e deixar os agentes rascunharem formatos enquanto eu escrevo a substância.
Codebases de vida longa nunca falham em um único dia. Elas sofrem drift. Um componente de substituição é entregue e metade do projeto ainda chama o depreciado. Um handler desce três camadas porque esse era o caminho mais curto sob prazo. Testes existem, mas não nas funções que de fato quebraram no último trimestre. Nada disso bloqueia uma release, nada disso merece um ticket, e tudo isso onera toda mudança futura. O problema que eu continuava enfrentando em meus próprios projetos paralelos não era falta de consciência — era falta de um loop que faria a consciência agir sobre si mesma.
O artigo de Kief Morris de março de 2026 no martinfowler.com me deu a moldura que me faltava: a alavanca produtiva com agentes de código é o loop de trabalho, não o prompt. Ele chama isso de estar "on the loop" — humanos projetam e mantêm o harness, agentes o rodam. Eu estava oscilando entre copiar-colar prompts em uma janela de chat e rodar scripts ad-hoc que só eu lembrava de rodar. Ambos são padrões in-the-loop que quebram no momento em que eu olho para o outro lado.
O que eu queria era um harness que o repositório pudesse carregar nas próprias costas. O GitHub lançou GitHub Agentic Workflows (gh aw) como technical preview em 13 de fevereiro de 2026, e o formato encaixou: cada workflow é um arquivo markdown sob .github/aw/, o agente roda com um token read-only por default, e qualquer escrita de volta no repositório deve passar por um bloco declarado safe-outputs que abre um pull request em um job separado e com escopo. O markdown é o harness. Isso mudou como eu pensei sobre tarefas de manutenção.
As três tarefas que tiro da minha cabeça
Em um serviço Rust que mantenho para estudo, três tarefas recorrentes continuavam aparecendo como itens "algum dia" nas minhas notas. Transformei cada uma em um workflow agendado.
A primeira é migração de componente depreciado. Um módulo de UI havia se movido de ui::button::Button para ui::v2::Button, e vinte e oito callers ainda usavam o antigo. O arquivo de workflow:
---
description: Migrate ui::button::Button to ui::v2::Button
on:
schedule:
- cron: "0 4 * * 2"
permissions: read-all
tools:
- rg
- cargo
safe-outputs:
create-pull-request:
max: 1
labels: [chore, auto-migration]
---
Find up to 20 call sites of `ui::button::Button` that are not inside
`src/ui/button/`. Replace each with `ui::v2::Button`, preserving the
prop names listed in `docs/migration-button-v2.md`. Run
`cargo check --all-targets` and stop if it fails. Open one pull
request titled `chore(ui): migrate Button v1 -> v2 (N/28)`.Dois detalhes importam. O max: 1 em create-pull-request limita o raio de impacto — se o agente interpretar mal o guia de migração, eu ganho um PR barulhento, não vinte. O limite de "até 20 call sites" mantém qualquer PR individual revisável; eu prefiro fazer merge de quinze PRs pequenos do que tentar ler um enorme.
A segunda tarefa é imposição de fronteiras arquiteturais. O serviço tem um layering limpo — domain, application, infrastructure — e a regra é que domain não deve importar de infrastructure. Um scan determinístico pega os casos fáceis; uma análise por LLM é o que finalmente pega os casos espertos, em que um módulo de domínio importa um helper cuja dependência transitiva alcança a infraestrutura. O Thoughtworks Technology Radar Volume 34 (abril de 2026) colocou "redução de drift arquitetural com LLMs" em Assess — promissor, ainda não um default. Meu workflow espelha esse padrão: rode o scan determinístico primeiro, entregue os casos ambíguos remanescentes para o agente.
---
description: Flag and propose fixes for domain -> infrastructure leaks
on:
schedule:
- cron: "0 5 * * 4"
permissions: read-all
tools:
- rg
- cargo
safe-outputs:
create-issue:
max: 3
labels: [architecture, drift]
---
Scan `src/domain/**/*.rs`. For each module that directly or
transitively depends on a type from `src/infrastructure/**`,
propose a trait in `src/domain` that narrows the dependency.
Open at most three issues; each must cite the offending import
path and a minimal trait sketch. Do not open pull requests.A terceira é fechamento de lacuna de cobertura. cargo tarpaulin --print-summary reporta cobertura por arquivo; o workflow escolhe as três funções públicas com zero cobertura de branch em arquivos abaixo de 60 por cento e propõe testes determinísticos — table-driven, sem I/O, sem clock. A saída vai para um PR com max: 1 para que eu possa rejeitar um teste ruim sem vadear por um backlog.
O caso que me fez confiar no loop
O workflow de arquitetura expôs um vazamento real que eu continuaria perdendo. Uma função de domínio dependia de uma struct de infrastructure::postgres::rows para moldar seu valor de retorno, e o scan determinístico perdeu porque a cadeia de imports passava por um re-export. O agente sinalizou o caminho ofensor e rascunhou um sketch de trait. Eu mantive o formato e escrevi o código final eu mesmo. Aqui está o exemplo mínimo que puxei das minhas notas para capturar o padrão para um eu-futuro:
// src/domain/pricing.rs
// Run with: cargo test
pub struct Money { pub cents: i64 }
pub trait PriceSource {
fn unit_price(&self, sku: &str) -> Option<Money>;
}
pub fn total(src: &dyn PriceSource, cart: &[(&str, u32)]) -> Money {
let cents = cart
.iter()
.filter_map(|(sku, qty)| src.unit_price(sku).map(|p| p.cents * *qty as i64))
.sum();
Money { cents }
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashMap;
struct Stub(HashMap<&'static str, i64>);
impl PriceSource for Stub {
fn unit_price(&self, sku: &str) -> Option<Money> {
self.0.get(sku).map(|c| Money { cents: *c })
}
}
#[test]
fn totals_known_skus_and_ignores_unknown() {
let src = Stub(HashMap::from([("A", 100), ("B", 250)]));
let t = total(&src, &[("A", 2), ("B", 1), ("C", 5)]);
assert_eq!(t.cents, 450);
}
}cargo test exercita o contrato inteiro sem tocar em Postgres, sem um runtime tokio, e sem a dependência transitiva chrono que o formato antigo arrastava. A trait PriceSource é a costura estreita. Antes da extração, cada mudança na struct de row da infraestrutura ressoava nos testes de domínio; depois, o domínio é dono do próprio vocabulário e o adapter Postgres implementa a trait em src/infrastructure/pricing_pg.rs. O agente não escreveu este código. Ele rascunhou a assinatura da trait em um comentário de issue e deixou a implementação para um PR humano. Essa divisão — agente propõe forma, eu escrevo substância — é a divisão que quero que o harness imponha.
O que errei na primeira passada
Algumas armadilhas que sofri nos meus próprios testes e corrigi:
- Comecei com
max: 5no PR de migração. A atenção de revisão colapsou no PR três. Reduzir paramax: 1com um limite por execução de 20 arquivos fez com que cada PR ficasse pequeno o suficiente para ler em cinco minutos. - Dei ao workflow de arquitetura
write-all"só por garantia". Previsivelmente, o agente propôs uma reescrita abrangente. Mudar para read-only maissafe-outputs: create-issueforçou a divisão rascunho-depois-humano e cortou o ruído por uma ordem de magnitude. - Agendei todos os três workflows no mesmo minuto. A feature de fuzzy-schedule do gh-aw distribui horários de início de forma determinística entre workflows para evitar picos de cron; sair de um horário exato compartilhado me fez parar de brigar comigo mesmo por minutos de runner.
- Assumi que HTTP outbound do agente era irrestrito. Não é — o runtime roteia tráfego outbound através de um proxy com uma allowlist explícita de domínios, e qualquer coisa fora dela é descartada no kernel. Isso é uma feature; parei de escrever workflows que tentavam chamar analisadores SaaS arbitrários.
Quando usar isso, quando deixar de lado
Esse formato se paga quando a tarefa é recorrente, estreita e fácil de revisar. Migrações de símbolo depreciado, drift de fronteiras de import, lacunas de cobertura em funções puras, freshness de doc-comment — todas encaixam. O custo é um punhado de PRs pequenos por semana, e cada um carrega a mesma carga mental de uma limpeza escrita por humano.
É o formato errado quando a tarefa é pontual, quando requer gosto ("deixe essa API mais bonita"), ou quando o modo de falha é sutil o bastante para que um PR barulhento possa passar despercebido na revisão. A imposição de arquitetura guiada por LLM ainda está em Assess no Radar da Thoughtworks por boa razão — eu a pareio com regras determinísticas e trato a saída do agente como rascunho, nunca veredito.
O único takeaway ao qual fico voltando: um repositório que se mantém sob supervisão, não por interrupção, devolve a largura de banda que eu costumava gastar lembrando de agendar sprints de limpeza.
Notas para um eu-futuro:
- mantenha
safe-outputs.*.maxpequeno (1–3), e limite o escopo por execução dentro do próprio prompt. - use permissões read-only; nunca defina
write-allpara economizar um minuto de debug. - use fuzzy schedules para que workflows paralelos não colidam em runners.
- pareie qualquer passo de imposição via LLM com um scan determinístico que roda primeiro.
- deixe o agente propor formatos em issues; escreva a substância em PRs.
Use quando: tarefas recorrentes e estreitas com revisão barata. Evite quando: julgamento de gosto, pontuais, ou qualquer lugar em que uma resposta errada silenciosa possa aterrissar.
Leitura adicional: Kief Morris, "Humans and Agents in Software Engineering Loops", martinfowler.com, 4 de março de 2026; a visão geral do gh-aw e a referência de safe-outputs em github.github.com/gh-aw; Thoughtworks Technology Radar Volume 34, abril de 2026.
Curtindo? Talvez goste disso aqui.
Nada parecido — quer tentar outro ângulo?
Posts Relacionados
Auditando um serviço Scala contra as quatro restrições regenerativas de Chad Fowler
Levei um serviço Scala de processamento de pedidos das minhas anotações pelas quatro restrições regenerativas de Chad Fowler. Duas passaram de graça, duas forçariam um redesign de verdade. Aqui está o que aprendi sobre onde "módulo fracamente acoplado" termina e "componente regenerativo" começa, e quais partes do redesign eu de fato pagaria.
Execução Durável Não É Sobre Agentes — É Sobre Workflows de Backend com Replay
Cheguei aos runtimes de execução durável pela hype dos agentes, mas a restrição que surpreende todo mundo é o determinismo no replay. Estas são minhas anotações trabalhando uma reconciliação de pagamentos de seis passos como um workflow do Restate em TypeScript — a linha que quebrou o replay, o modelo mental que consertou, e os trade-offs que vêm com o padrão.
A Espinha Dorsal Determinística: Por Que Sistemas de IA em Produção Estão Se Afastando de Agentes Totalmente Autônomos
Agentes totalmente autônomos são difíceis de limitar, difíceis de testar e caros de operar. Uma espinha dorsal determinística com etapas de agente estreitas devolve o controle de fluxo a você enquanto mantém a inteligência onde ela importa. Veja como projetar, testar e migrar nessa direção.
