DEV Community

Cover image for Looping VibeCoding Engineering: o grafo é a MELHOR abstração + Claude Dynamic Workflows
suissAI
suissAI

Posted on • Edited on

Looping VibeCoding Engineering: o grafo é a MELHOR abstração + Claude Dynamic Workflows

Este artigo é um thinking in public técnico. Não é uma especificação fechada, nem uma tentativa de vender uma arquitetura pronta. É o registro de como eu pego um conceito novo, debato com IA, comparo com padrões que já uso e tento transformar isso em DSL, grafo, agentes e behaviors reutilizáveis.

Esse print é deste vídeo: https://www.youtube.com/watch?v=ij9a55_rib4

Achei muito interessante o nome Loop Engineering e a ideia de que os prompts devem ser gerados dinamicamente.

Confesso que só comecei a testar agentes de codificação esta semana. Porém, há bastante tempo meu pipeline já funciona como um loop de prompts, onde eu debato com o ChatGPT Web minhas ideias de solução para funcionalidades arquiteturais, padrões de geração de sistemas, DSLs, funções atômicas e genéricas, além do grafo do fluxo do payload dentro de um módulo/agente.

Nesse grafo, eu defino quais funções existem, quais configurações específicas elas recebem e qual é a sequência de execução. Essa sequência também pode ter execução paralela.

Você já viu como algum Loop Engenieer declara seus grafos?

Bom, se ele usar JSON saiba que ele está usando o formato mais verboso, menos legível e provavelmente menos semântico.

2flow.Store

Eu criei uma notação para Grafos Acíclicos e Direcionados onde usei o mínimo possível de carateres e mantendo o máximo de expressividade semântica, por exemplo:

classify.intent -> 
  UserLoginIntent:execute.userLoginFlow,
  CreateOrderIntent:execute.createOrderFlow,
  ValidatePhoneIntent:execute.phoneValidationFlow,
  UnknownIntent:request.userClarification
Enter fullscreen mode Exit fullscreen mode

Isso é processamento sequencial porém o resultado para classify.intent vem apenas no último evento.

create.User -> [
  validate.UserPhone,
  validate.UserHash,
  validate.UserPassword,
  validate.UserEmail,
]
Enter fullscreen mode Exit fullscreen mode

Isso é processamento paralelo, pois 1 validação não interfere em outra.

Tentei usar da sintaxe para modificar o mínimo possível e diferenciar 2 comportamentos diferentes.

Usar [] remete a filas, array, ou seja, um tipo só. Já quando você usa somente a vírgula em uma frase é porque está separando as coisas e está lendo sequencialmente, pelo menos foi a forma mais simples que achei de declarar processamento sequencia e paralelo.

Se você tiver sugestões eu adoraria ler. <3

ps: com meu runtime em Zig eu consigo executar mais de 36.000 validates ao mesmo tempo com 1Gb usando 32Kb por Agent, mas meus payloads não passam de 2kb

Eu até voltei para uma versão anterior da minha notação, antiga Graflow, para utilizar uma declaração parecisa com o case da linguagem Gleam que só retorna 2 tipos de resultados:

  • Ok: na primeira linha
  • Error: na próxima

Você desenha, o sistema faz

Eu estou fazendo algo muito próximo disso, mas não apenas desenhando a solução para um problema específico.

Estou criando uma arquitetura altamente distribuída, ultra resiliente, escalável vertical e horizontalmente, usando Post-Quantum ZeroTrust Passwordless, seis linguagens diferentes e dez bancos de dados diferentes.

Mesmo assim, as mesmas funcionalidades também podem ser geradas em uma arquitetura mais simples, com apenas uma linguagem no backend e um único banco, como PostgreSQL.

Esse é um exemplo claro do motivo pelo qual eu chamo esse processo de:

VibeCoding State-of-the-Art-Driven Design/Development

Eu tento otimizar ao máximo cada aspecto da arquitetura. Pesquiso, debato com a IA e vou refinando até chegar ao ponto em que, normalmente, só existe no máximo uma técnica, tecnologia ou conceito melhor para aquele caso — e muitas vezes essa alternativa é inviável no contexto real.

Eu não escrevo uma linha de código diretamente. Meu trabalho principal é estudar as coisas novas que a IA me apresenta, entender os conceitos e transformar esse conhecimento em arquitetura reutilizável.

E eu realmente não quero deixar um agente programar tudo para mim, porque o que eu mais gosto nesse processo é aprender conceitos avançados que, há dois meses, eu nem imaginava que usaria.

Com a minha DSL, eu consigo gerar um sistema FullAgenticStack — ou apenas FullStack — em seis arquiteturas diferentes, porque criei padrões e convenções em cima de Semantic Behavior Types.

Com um valor semântico declarado, consigo gerar seu comportamento semântico em diferentes camadas, usando diferentes linguagens, todas executando a mesma intenção.

Então eu não peço para a IA criar um sistema para mim.

Eu peço para ela criar:

- parsers
- converters
- compilers
- funções atômicas
- behaviors semânticos
- testes
- provas
- grafos
- convenções
Enter fullscreen mode Exit fullscreen mode

Eu prefiro código determinístico a um código diferente toda vez.

Faça um teste: crie um documento em Spec-Driven Development e peça para uma IA gerar o sistema. Depois entre na mesma IA com outra conta e peça exatamente a mesma coisa, mas sem nenhum contexto anterior, com uma IDE zerada e outra API key.

É altamente provável que o resultado não seja o mesmo.

O grafo é a nova abstração

A melhor parte do vídeo, para mim, é quando ele fala que:

O grafo é o novo nível de abstração.

Isso corrobora exatamente a forma como eu estou programando e como minha arquitetura funciona.

Tanto que, com as mesmas propriedades, eu criei a geração do mesmo sistema para seis arquiteturas diferentes apenas declarando a arquitetura como um grafo.

O pior é que eu estava achando que talvez estivesse sendo extremista demais no reuso. Mas, no vídeo, ele também mostra a orquestração de subagentes, que é exatamente o que eu faço, só que ainda sem IA na camada de agentes.

A diferença é que, no meu caso, o grafo não é apenas um desenho visual para organizar chamadas. Ele é a própria estrutura semântica de execução.

Eu uso o grafo para descrever:

- fluxo do payload
- sequência de behaviors
- execução paralela
- validação semântica
- self-healing
- emissão de eventos
- consumo linear
- orquestração entre módulos
- composição de subagentes
- equivalência entre arquiteturas
Enter fullscreen mode Exit fullscreen mode

Foi por isso que essa parte do vídeo bateu tão forte.

Eu já venho tratando propriedades, behaviors, módulos e agentes como nós de um grafo. A arquitetura não nasce como código. Ela nasce como uma declaração de relações, restrições e comportamentos.

O código é consequência.

Com as mesmas propriedades declaradas no grafo, eu consigo gerar diferentes versões da mesma arquitetura:

- uma versão simples com uma linguagem e PostgreSQL
- uma versão distribuída com múltiplos bancos
- uma versão com CQRS/Event Sourcing
- uma versão com agentes
- uma versão com ZeroTrust mais forte
- uma versão multi-runtime com várias linguagens
Enter fullscreen mode Exit fullscreen mode

Isso é muito diferente de pedir para a IA “criar um sistema”.

Eu não quero que a IA invente uma arquitetura nova a cada prompt. Eu quero declarar o grafo, definir os behaviors e gerar implementações consistentes a partir disso.

Praticamente nove de dez conceitos que ele explica no vídeo eu já venho usando, mas sem IA. Eu ainda não cheguei na camada de agentes de IA da minha arquitetura.

Por isso esse vídeo foi tão interessante para mim: ele não apenas explicou conceitos novos, mas confirmou que a direção que eu já estava seguindo faz sentido.

E, por sinal, o canal desse criador é um dos melhores que eu acompanho. É muito técnico, e a forma como ele fala, meio apavorando quem não faz do jeito certo, me lembra muito eu tentando fomentar o uso de técnicas, ferramentas e conceitos melhores do que os programadores normalmente estão acostumados.

Se você ainda não acompanha e quer entender melhor esses conceitos técnicos, vale a pena seguir o canal.

Semantic QuarkBehavior Type

Com as mesmas propriedades, eu já criei o graflow para seis arquiteturas diferentes e cerca de vinte frameworks/linguagens diferentes, usando três módulos de segurança que inauguram o conceito de Semantic QuarkBehavior Type.

Esse tipo não diz apenas o que o valor é.

Ele diz qual comportamento aquela variável deve executar.

Um exemplo é o comportamento LinearAutoDestroy, que segue a ideia de tipo linear, mas adiciona uma garantia semântica: depois de utilizado, o valor deve ser destruído da memória.

Para elevar o nível da abstração, também é necessário elevar o nível do reuso.

Quantos sistemas você já fez que tinham uma entidade User, login e CRUD?

Eu já fiz praticamente todos assim.

Quando você pede para a IA gerar código em cima de um documento, ela pode gerar códigos diferentes para fazer a mesma coisa. Mas se você transforma as propriedades em cidadãs de primeira classe, consegue reutilizar o comportamento semântico de cada uma delas em qualquer sistema.

Há cerca de dez anos eu criei uma prova de conceito para gerar um backend MVC usando apenas um JSON com rotas e propriedades da entidade. As propriedades eram apenas um array com seus nomes.

Desde aquela época eu já tratava propriedades como unidades reutilizáveis. Se eu tivesse uma pasta com o schema e a validação daquela propriedade, poderia reutilizá-la em qualquer sistema.

Hoje, esse conceito evoluiu para propriedades com SemanticType único.

Por exemplo:

UserId é diferente de ProductId,
mesmo que os dois sejam representados pelo mesmo tipo primitivo.
Enter fullscreen mode Exit fullscreen mode

Toda propriedade precisa ter sua própria função validate, porque é essa função que cria a identidade semântica da propriedade.

Aposto que você nunca criou uma função para validar nome, certo?

Eu pelo menos nunca tinha criado. No máximo fazia um replace para remover caracteres que não eram letras.

Sabe quem não valida nome direito? O Gmail. E como ele tem campo para sobrenome, então ele claramente não está pedindo um username.

Veja se você concorda comigo:

sanitize -> removeAllNonLetters -> trim
normalize -> capitalize

PersonName -> sanitize -> normalize -> mustHaveTwoWords -> [name, lastName]

name -> lengthIsBetween(2, 50)
lastName -> lengthIsBetween(2, 50)
Enter fullscreen mode Exit fullscreen mode

Minha DSL é um formato que lembra uma mistura de classe + JSON, eliminando boa parte da sintaxe. Ela é baseada em Semantic Behavior Types e compilada com Haskell + Prolog.

Em outro artigo eu explico isso melhor.

Esse vídeo abriu minha mente sobre como eu devo usar agentes corretamente: fazendo eles executarem a mesma forma como eu já trabalho manualmente.

Como eu li sobre os Dynamic Workflows do Claude e percebi que já uso cinco dos seis sem IA, quero fazer um exercício de imaginação.

Estou escrevendo este artigo enquanto vejo o vídeo, então vou pensar agora em como implementar os seis Dynamic Workflows para que meus agentes façam VibeCoding como eu faço.

Talvez o nome certo para isso seja:

Looping VibeCoding Engineering

1. Classify-And-Act

Esse padrão eu uso desde o meu primeiro chatbot com IA, no início de 2025.

Eu nem sei fazer chatbot sem isso. Acredito que talvez seja o padrão mais básico e comum em sistemas com IA.

A ideia é simples: o sistema primeiro classifica a intenção ou o tipo do payload, depois executa a ação correspondente.

No meu caso, isso combina naturalmente com Semantic Behaviors.

O sistema recebe um payload, identifica qual comportamento semântico deve ser aplicado e então envia esse payload para o fluxo correto.

received.payload -> classify.intent
classify.intent -> 
  UserLoginIntent:execute.userLoginFlow,
  CreateOrderIntent:execute.createOrderFlow,
  ValidatePhoneIntent:execute.phoneValidationFlow,
  UnknownIntent:request.userClarification

Enter fullscreen mode Exit fullscreen mode

Isso evita que o sistema tente resolver tudo com uma única função gigante.

Cada intenção vira uma entrada para um grafo específico.

2. Fanout-And-Synthesize

Por padrão, quando um payload chega na função sub, meu runtime executa automaticamente a função forge do Semantic QuarkBehavior Type daquele subagent, função ou QuarkBehavior.

Essa função chama internamente a função validate, que recebe qualquer tipo de valor.

A primeira coisa que ela faz é passar o payload por um pipeline de extração do valor primitivo. Se esse tipo não for aceitável, o sistema tenta converter para o tipo primitivo esperado.

Somente quando todas as formas de transformar o payload para o formato desejado se esgotam, o subagent emite automaticamente um evento de error.

Esse evento é recebido por ele mesmo, iniciando seu pipeline de self-healing.

O payload de erro contém:

- payload de entrada
- último estado do payload
- valor invalidado
- comportamento semântico que invalidou o valor
- trace/span das execuções internas
Enter fullscreen mode Exit fullscreen mode

Como eu não conheço muitos programadores que programam com o conceito de Intent-based Healing, eu mesmo me responsabilizo por criar e atualizar um pipeline de self-healing para as linguagens que já tenho código gerado com base na minha DSL.

Erros de valor são universalmente corrigidos de forma parecida: basta transformar os valores até ficarem dentro do esperado.

Erros de tipo são diferentes em cada linguagem, mas ainda conseguimos agrupar os comportamentos dos tipos por categorias.

O Fanout-And-Synthesize entra quando vários agentes ou behaviors analisam o mesmo payload sob perspectivas diferentes.

received.payload -> fanout(
  TypeAnalysisAgent,
  SecurityAnalysisAgent,
  SemanticAnalysisAgent,
  RuntimeAnalysisAgent
)

TypeAnalysisAgent -> pub("analyzed.type")
SecurityAnalysisAgent -> pub("analyzed.security")
SemanticAnalysisAgent -> pub("analyzed.semantic")
RuntimeAnalysisAgent -> pub("analyzed.runtime")

analyzed.type, analyzed.security, analyzed.semantic, analyzed.runtime
  -> synthesize.finalDecision
Enter fullscreen mode Exit fullscreen mode

A síntese não é simplesmente juntar textos.

Ela precisa produzir uma decisão executável:

- aceitar
- corrigir
- pedir colaboração
- rejeitar
- gerar novo behavior
- iniciar self-healing
Enter fullscreen mode Exit fullscreen mode

3. Adversarial Verification

Esse é o padrão que eu provavelmente já faço de forma artesanal quando fico debatendo com o ChatGPT Web, mas ainda não tinha transformado em uma etapa formal do meu runtime.

Quando peço para a IA pensar em uma solução arquitetural, normalmente eu não aceito a primeira resposta.

Eu começo a questionar:

- Isso é realmente seguro?
- Isso escala?
- Funciona em ambiente distribuído?
- Quebra com concorrência?
- Funciona offline?
- Continua correto se eu trocar a linguagem?
- Continua correto se eu trocar o banco?
- Respeita ZeroTrust?
- Respeita LinearAutoDestroy?
- Consegue ser provado com Haskell + Prolog?
Enter fullscreen mode Exit fullscreen mode

Isso, na prática, já é uma forma manual de Adversarial Verification.

A diferença é que agora eu quero transformar esse debate em agentes especializados.

Em vez de eu sozinho tentar derrubar a solução, cada agente teria a função explícita de atacar uma parte diferente do resultado.

Por exemplo, quando minha DSL gerar um novo QuarkBehavior, ela publica o fato de que um candidato foi gerado.

A partir desse fato, o fluxo pode solicitar a verificação adversarial daquele candidato:

DSL.pub("generated.candidate")

generated.candidate -> request.adversarialVerification.for.candidate
Enter fullscreen mode Exit fullscreen mode

Vários agentes podem receber esse evento de forma coreografada:

generated.candidate -> verify.candidate.with.TypeAdversaryAgent
generated.candidate -> verify.candidate.with.SecurityAdversaryAgent
generated.candidate -> verify.candidate.with.SemanticEquivalenceAdversaryAgent
generated.candidate -> verify.candidate.with.LinearUsageAdversaryAgent
generated.candidate -> verify.candidate.with.RuntimeCrashAdversaryAgent
generated.candidate -> verify.candidate.with.PayloadCorruptionAdversaryAgent
generated.candidate -> verify.candidate.with.ZeroTrustPolicyAdversaryAgent
Enter fullscreen mode Exit fullscreen mode

Cada agente não existe para “melhorar” o código.

Ele existe para tentar provar que o código está errado.

O TypeAdversaryAgent tenta quebrar o comportamento usando tipos errados, objetos aninhados, valores nulos, arrays inesperados, strings vazias, valores parcialmente válidos e payloads que parecem corretos, mas semanticamente não são.

O SecurityAdversaryAgent tenta encontrar vazamento de chave, replay, bypass de DPoP, quebra de mTLS, payload reaproveitado, ausência de destruição linear ou qualquer falha que viole Post-Quantum ZeroTrust Passwordless.

O SemanticEquivalenceAdversaryAgent tenta provar que a implementação em TypeScript, Zig, Haskell, Prolog, Rust ou qualquer outra linguagem não está executando exatamente o mesmo comportamento semântico.

O LinearUsageAdversaryAgent tenta descobrir se algum valor que deveria ser consumido uma única vez está sendo reutilizado, copiado, logado, cacheado ou mantido na memória depois do uso.

Isso é muito importante para mim porque meu objetivo não é gerar código parecido em várias linguagens.

Meu objetivo é gerar o mesmo comportamento semântico em várias camadas diferentes.

Então, para mim, Adversarial Verification não é apenas revisão de código.

É uma etapa de guerra contra a própria solução gerada.

O fluxo seria mais ou menos assim:

generated.candidate -> request.adversarialVerification.for.candidate

request.adversarialVerification.for.candidate
  -> fanout(TypeAdversaryAgent, SecurityAdversaryAgent, SemanticEquivalenceAdversaryAgent, LinearUsageAdversaryAgent)

TypeAdversaryAgent -> [
  Ok:pub("passed.verification.by.TypeAdversaryAgent"),
  Error:pub("found.counterexample.by.TypeAdversaryAgent")
]

SecurityAdversaryAgent -> [
  Ok:pub("passed.verification.by.SecurityAdversaryAgent"),
  Error:pub("found.securityViolation.by.SecurityAdversaryAgent")
]

SemanticEquivalenceAdversaryAgent -> [
  Ok:pub("passed.verification.by.SemanticEquivalenceAdversaryAgent"),
  Error:pub("found.semanticDrift.by.SemanticEquivalenceAdversaryAgent")
]

LinearUsageAdversaryAgent -> [
  Ok:pub("passed.verification.by.LinearUsageAdversaryAgent"),
  Error:pub("found.linearViolation.by.LinearUsageAdversaryAgent")
]
Enter fullscreen mode Exit fullscreen mode

Quando um desses agentes encontra um erro, ele não deve apenas retornar “falhou”.

Ele deve retornar o contraexemplo.

Esse contraexemplo vira entrada para o meu pipeline de Intent-based Healing.

found.counterexample -> execute.selfHealingPipeline
found.securityViolation -> execute.selfHealingPipeline
found.semanticDrift -> execute.selfHealingPipeline
found.linearViolation -> execute.selfHealingPipeline
Enter fullscreen mode Exit fullscreen mode

Esse é o ponto em que o workflow deixa de ser apenas orquestrado e passa a ser coreografado.

O agente gerador não precisa saber quem vai verificar. Ele apenas publica o candidato. Todos os agentes interessados em provar que aquele candidato está errado se inscrevem nesse evento.

No fim, o código só deveria ser aceito quando sobreviver aos agentes adversários.

4. Generate-And-Filter

Esse é o padrão que mais combina com a forma como eu uso VibeCoding hoje.

Eu não quero que a IA gere um sistema inteiro diretamente.

Eu quero que ela gere:

- parser
- converter
- compiler
- função atômica
- comportamento semântico
- teste
- prova
- grafo
- convenção
Enter fullscreen mode Exit fullscreen mode

Meu objetivo é que a parte criativa fique no nível da arquitetura, mas que a geração final seja determinística.

Então, em vez de pedir:

Crie um sistema de login.
Enter fullscreen mode Exit fullscreen mode

Eu prefiro definir:

Crie o parser da minha DSL para gerar o comportamento semântico PhoneNumber,
MagicLinkToken, PasskeyChallenge, DPoPProof, LinearAutoDestroy
e o grafo de execução do payload.
Enter fullscreen mode Exit fullscreen mode

A partir disso, o sistema pode gerar várias versões candidatas de uma mesma coisa.

Por exemplo, para um Semantic QuarkBehavior Type, o agente poderia gerar:

- implementação mínima
- implementação strict
- implementação otimizada para performance
- implementação otimizada para segurança
- implementação com foco em prova formal
- implementação com foco em portabilidade entre linguagens
- implementação com LinearAutoDestroy explícito
- implementação com self-healing embutido
Enter fullscreen mode Exit fullscreen mode

Depois disso, entra a etapa de filtro.

O filtro não é gosto pessoal.

O filtro precisa ser determinístico.

Um candidato só passa se ele:

- compila
- passa nos testes unitários
- passa nos testes de propriedade
- preserva a semântica declarada
- respeita o SemanticType
- respeita ZeroTrust
- respeita LinearAutoDestroy
- não reutiliza payload consumido
- não quebra o grafo do fluxo
- não introduz comportamento implícito
- não depende de contexto invisível
- não muda o contrato da DSL
Enter fullscreen mode Exit fullscreen mode

Esse padrão é fundamental porque IA é ótima para gerar possibilidades, mas não deve ser a autoridade final sobre o que entra no sistema.

A autoridade final deve ser o filtro.

accepted.intent -> generate.candidates

generate.candidates -> generated.candidate.1
generate.candidates -> generated.candidate.2
generate.candidates -> generated.candidate.3

generated.candidate.1 -> execute.filterPipeline
generated.candidate.2 -> execute.filterPipeline
generated.candidate.3 -> execute.filterPipeline

execute.filterPipeline -> [
  Ok:pub("accepted.candidate"),
  Error:pub("rejected.candidate")
]
Enter fullscreen mode Exit fullscreen mode

No meu caso, o filterPipeline pode ser uma composição de vários agentes:

execute.filterPipeline -> execute.compileCheck
execute.compileCheck -> execute.unitTests
execute.unitTests -> execute.propertyTests
execute.propertyTests -> execute.semanticEquivalenceCheck
execute.semanticEquivalenceCheck -> execute.securityPolicyCheck
execute.securityPolicyCheck -> execute.linearUsageCheck
execute.linearUsageCheck -> execute.benchmarkCheck
execute.benchmarkCheck -> pub("accepted.candidate")
Enter fullscreen mode Exit fullscreen mode

O mais interessante é que isso também pode ser coreografado.

O candidato não precisa passar por uma função gigante chamada validateEverything.

Ele pode emitir candidate.readyForFiltering, e cada filtro se inscreve nesse evento.

ready.candidate.for.filtering -> filter.candidate.with.CompileFilterAgent
ready.candidate.for.filtering -> filter.candidate.with.TestFilterAgent
ready.candidate.for.filtering -> filter.candidate.with.SecurityFilterAgent
ready.candidate.for.filtering -> filter.candidate.with.LinearFilterAgent
ready.candidate.for.filtering -> filter.candidate.with.SemanticFilterAgent
ready.candidate.for.filtering -> filter.candidate.with.BenchmarkFilterAgent
Enter fullscreen mode Exit fullscreen mode

Cada filtro emite sua decisão.

CompileFilterAgent -> pub("passed.filter.compile")
TestFilterAgent -> pub("passed.filter.tests")
SecurityFilterAgent -> pub("passed.filter.security")
LinearFilterAgent -> pub("passed.filter.linear")
SemanticFilterAgent -> pub("passed.filter.semantic")
BenchmarkFilterAgent -> pub("passed.filter.benchmark")
Enter fullscreen mode Exit fullscreen mode

Quando todos os filtros obrigatórios passam, o Governor pode aceitar o candidato.

passed.allRequiredFilters -> accept.candidate.with.Governor
accept.candidate.with.Governor -> pub("promoted.artifact")
Enter fullscreen mode Exit fullscreen mode

Essa é a diferença entre VibeCoding comum e o que eu estou chamando de Looping VibeCoding Engineering.

No VibeCoding comum, a IA gera o código e o programador tenta fazer funcionar.

No meu caso, a IA gera candidatos, mas o runtime filtra, prova, testa, ataca, mede e só depois promove aquilo para comportamento reutilizável.

5. Tournament

Esse padrão é provavelmente o que mais pode elevar o nível dos meus agentes de codificação.

Hoje, quando converso com a IA, eu já faço uma espécie de torneio mental.

Eu peço uma solução, depois peço outra, depois pergunto se existe uma técnica mais moderna, comparo com outra tecnologia e questiono se aquilo escala melhor com QUIC, NATS, gRPC, WebSocket, eBPF, EventStore, PostgreSQL, MongoDB, Qdrant, Neo4j, Redis ou qualquer outra parte da arquitetura.

Só que eu faço isso manualmente.

O Tournament seria transformar esse processo em um workflow formal.

Em vez de pedir para um agente gerar “a melhor solução”, eu posso pedir para vários agentes gerarem soluções concorrentes para o mesmo problema.

Por exemplo:

defined.problem -> open.tournament
open.tournament -> generate.candidate.with.TypeScriptAgent
open.tournament -> generate.candidate.with.ZigAgent
open.tournament -> generate.candidate.with.HaskellAgent
open.tournament -> generate.candidate.with.PrologAgent
open.tournament -> generate.candidate.with.RustAgent
Enter fullscreen mode Exit fullscreen mode

Ou então, para o mesmo problema na mesma linguagem:

defined.problem -> generate.candidate.with.minimalStrategy
defined.problem -> generate.candidate.with.strictStrategy
defined.problem -> generate.candidate.with.fastStrategy
defined.problem -> generate.candidate.with.safeStrategy
defined.problem -> generate.candidate.with.provableStrategy
defined.problem -> generate.candidate.with.distributedStrategy
Enter fullscreen mode Exit fullscreen mode

Cada candidato entra no torneio com a mesma intenção, mas com estratégias diferentes.

O vencedor não é escolhido porque “parece melhor”.

Ele é escolhido por pontuação.

A pontuação pode considerar:

- compila?
- passa nos testes?
- passa nos testes de propriedade?
- preserva a semântica?
- é mais rápido?
- usa menos memória?
- aloca menos?
- é mais fácil de provar?
- é mais fácil de converter para outra linguagem?
- respeita LinearAutoDestroy?
- respeita ZeroTrust?
- gera menos eventos?
- tem melhor rastreabilidade?
- tem menor custo de self-healing?
- tem menor complexidade operacional?
- encaixa melhor no graflow?
Enter fullscreen mode Exit fullscreen mode

O torneio não precisa escolher apenas um vencedor absoluto.

Ele pode escolher vencedores por categoria:

- melhor implementação para produção
- melhor implementação para prova formal
- melhor implementação para benchmark
- melhor implementação para documentação
- melhor implementação para portabilidade
- melhor implementação para segurança
Enter fullscreen mode Exit fullscreen mode

Isso faz muito sentido para minha arquitetura porque nem sempre a solução mais rápida é a solução correta.

Às vezes, a solução mais fácil de provar formalmente vale mais.

Às vezes, a mais segura vale mais.

Às vezes, a mais simples é melhor porque vira padrão reutilizável para várias linguagens.

O fluxo poderia ser:

opened.tournament -> generate.candidates
generated.candidates -> request.benchmark.for.candidates
generated.candidates -> request.proof.for.candidates
generated.candidates -> request.adversarialVerification.for.candidates
generated.candidates -> request.semanticEquivalence.for.candidates

request.benchmark.for.candidates -> pub("scored.benchmark")
request.proof.for.candidates -> pub("scored.proof")
request.adversarialVerification.for.candidates -> pub("scored.adversarialVerification")
request.semanticEquivalence.for.candidates -> pub("scored.semanticEquivalence")

scored.benchmark, scored.proof, scored.adversarialVerification, scored.semanticEquivalence
  -> build.tournamentScoreboard

build.tournamentScoreboard -> select.tournamentWinner
select.tournamentWinner -> pub("selected.tournamentWinner")
Enter fullscreen mode Exit fullscreen mode

O mais importante é que o torneio transforma opinião em medição.

E isso é exatamente o que eu quero para o meu VibeCoding.

Eu quero continuar debatendo com IA, estudando conceitos novos e entendendo tecnologias avançadas, mas quando chegar a hora de transformar isso em código, eu quero que o sistema consiga competir soluções e provar qual delas é a mais adequada para aquele contexto.

Esse padrão também evita uma armadilha comum: achar que a primeira resposta boa da IA é a melhor resposta possível.

Para mim, isso é perigoso.

A primeira resposta pode funcionar, mas talvez exista uma solução muito mais segura, simples, performática ou compatível com a minha DSL.

Então o Tournament seria o agente dizendo:

Não vou aceitar uma resposta.
Vou criar várias respostas, colocar todas para competir
e deixar testes, provas, políticas e benchmarks decidirem.
Enter fullscreen mode Exit fullscreen mode

Esse é um dos pontos em que o grafo realmente vira o novo nível de abstração, porque o torneio inteiro pode ser descrito como um grafo de geração, validação, pontuação e promoção.

6. Loop-Until-Done

Esse padrão é o exemplo mais claro de como eu programo usando o conceito de Intent-based Healing.

A maioria dos sistemas trata erro como fim do fluxo.

No meu caso, erro é só mais um evento.

Quando um QuarkBehavior recebe um payload inválido, ele não deveria simplesmente retornar erro para o usuário.

Ele deve emitir um evento de erro para ele mesmo, contendo todo o contexto necessário para tentar corrigir o problema.

Esse payload de erro precisa ter:

- payload original
- último estado conhecido do payload
- valor invalidado
- tipo primitivo esperado
- SemanticType esperado
- comportamento semântico que falhou
- trace/span da execução interna
- função que falhou
- tentativa de conversão já realizada
- histórico de healing
- motivo da rejeição
Enter fullscreen mode Exit fullscreen mode

A partir disso, o próprio subagent pode tentar se curar.

AtomicBehavior.pub("execute.{QuarkBehavior}")

execute.{QuarkBehavior}
  -> QuarkBehavior.forge
  -> [
    Ok:pub("ended.{QuarkBehavior}.in.success"),
    Error:pub("failed.{QuarkBehavior}.at.validation")
  ]

failed.{QuarkBehavior}.at.validation
  -> execute.selfHealingPipeline
  -> [
    Ok:pub("ended.{QuarkBehavior}.in.success"),
    Error:pub("failed.{QuarkBehavior}.at.selfHealingPipeline")
  ]

failed.{QuarkBehavior}.at.selfHealingPipeline
  -> unwrap.recursive.to.primitive
  -> [
    Ok:pub("ended.{QuarkBehavior}.in.success"),
    Error:pub("failed.{QuarkBehavior}.at.unwrapRecursiveToPrimitive")
  ]

failed.{QuarkBehavior}.at.unwrapRecursiveToPrimitive
  -> convert.type.with.pipeline
  -> [
    Ok:pub("ended.{QuarkBehavior}.in.success"),
    Error:pub("failed.{QuarkBehavior}.at.convertTypePipeline")
  ]

failed.{QuarkBehavior}.at.convertTypePipeline
  -> convert.semanticData
  -> [
    Ok:pub("ended.{QuarkBehavior}.in.success"),
    Error:pub("ended.{QuarkBehavior}.in.error")
  ]

AtomicBehavior.sub("ended.{QuarkBehavior}.in.success")
AtomicBehavior.sub("ended.{QuarkBehavior}.in.error")
Enter fullscreen mode Exit fullscreen mode

Esse loop não é um retry burro.

Retry burro é repetir a mesma coisa esperando um resultado diferente.

Intent-based Healing é diferente: cada nova tentativa precisa adicionar conhecimento ao estado.

Se o payload veio como objeto, o sistema tenta extrair o valor primitivo.

Se o valor primitivo veio como string, o sistema tenta converter para o tipo esperado.

Se o tipo foi convertido, mas a semântica falhou, o sistema tenta corrigir o valor.

Se o valor não pode ser corrigido automaticamente, o sistema pode pedir colaboração ao usuário.

Se o usuário colabora, o sistema aprende uma nova estratégia de correção.

Se a correção for recorrente, ela vira uma nova função no pipeline de self-healing.

Então o fluxo real é mais próximo disso:

detected.error -> enrich.error
enrich.error -> request.primitiveExtraction -> [
  Ok:request.typeConversion,
  Error:pub("ended.healing.in.error")
]

request.typeConversion -> [
  Ok:request.semanticValidation,
  Error:request.semanticConversion
]

request.semanticConversion -> [
  Ok:request.semanticValidation,
  Error:request.userCollaboration
]

request.semanticValidation -> [
  Ok:pub("ended.healing.in.success"),
  Error:execute.selfHealingPipeline
]

request.userCollaboration -> [
  Ok:pub("created.learningEvent"),
  Error:pub("ended.intent.in.error")
]

created.learningEvent -> create.healingFunction
create.healingFunction -> execute.selfHealingPipeline
Enter fullscreen mode Exit fullscreen mode

Esse é o ponto em que eu digo que meu sistema não retorna erro como comportamento padrão.

Ele só deveria retornar erro quando não existe caminho seguro, válido ou cooperativo para continuar.

Se existe uma forma segura de inferir, converter, normalizar, corrigir ou pedir colaboração, então não é erro final.

É uma intenção incompleta.

Por exemplo:

"gmail.con" -> corrigir para "gmail.com"
telefone sem DDD -> inferir DDD pelo contexto
"amanhã às 14h" -> converter para timestamp
nome em caixa baixa -> normalizar capitalização
payload aninhado -> extrair valor primitivo
ID externo errado -> buscar por canonical_label
grupo escrito errado -> buscar semanticamente os 3 mais próximos
Enter fullscreen mode Exit fullscreen mode

O Loop-Until-Done também explica por que eu gosto tanto de VibeCoding.

Eu faço isso comigo mesmo.

Eu tenho uma intenção arquitetural, gero uma hipótese, debato com a IA, descubro uma falha, estudo uma técnica nova, corrijo a hipótese, testo contra outro cenário, encontro outra falha, aprendo outro conceito, atualizo o padrão e continuo até a solução ficar boa o suficiente para virar DSL, parser, compiler, graflow ou Semantic QuarkBehavior Type.

Então o meu VibeCoding não é “a IA programa por mim”.

É um loop de aprendizado e refinamento arquitetural.

defined.idea -> generate.prompt -> pub("generated.answer")
generated.answer -> pub("discovered.newConcept")
discovered.newConcept -> promote.reusablePattern -> pub("promoted.reusablePattern")
Enter fullscreen mode Exit fullscreen mode

Quando esse loop vira agente, ele deixa de ser apenas conversa e passa a ser engenharia.

Por isso eu gostei tanto do nome Loop Engineering.

Eu chamaria meu caso de Looping VibeCoding Engineering, porque é exatamente o que eu faço: uso IA para manter um loop contínuo entre intenção, pesquisa, arquitetura, geração, verificação, erro, healing e reuso.

Eu não quero perder esse loop, porque é nele que está a parte mais divertida: aprender técnicas e conceitos avançados todos os dias e transformar esse aprendizado em arquitetura reutilizável.

Se eu deixasse um agente programar tudo sozinho, talvez ganhasse velocidade, mas perderia o principal valor do processo: entender por que a solução existe, qual problema ela resolve, qual técnica ela usa, qual trade-off ela cria e como ela pode virar um padrão semântico reutilizável.

Por isso, para mim, o futuro dos agentes de codificação não é substituir meu processo.

É executar o meu processo junto comigo.

Conclusão

Eu acho que nunca vou deixar de fazer Looping VibeCoding Engineering, porque, se eu fizesse isso, perderia a parte que mais gosto: aprender técnicas e conceitos atuais e avançados todos os dias em que sento no computador.

Tirando frontend, 99% da minha arquitetura foi aprendida nos últimos dois meses.

Se não fosse o VibeCoding, eu provavelmente nunca usaria Haskell + Prolog em um sistema web, talvez nunca tivesse conhecido tipos lineares e dificilmente teria pensado em usar Zig para partes críticas de uma arquitetura web.

Mas o ponto principal não é a quantidade de tecnologias.

O ponto principal é o método.

Eu não quero usar IA para gerar código aleatório mais rápido.

Eu quero usar IA para acelerar o ciclo entre:

intenção -> pesquisa -> arquitetura -> grafo -> comportamento -> prova -> geração -> validação -> healing -> reuso
Enter fullscreen mode Exit fullscreen mode

Quando o grafo vira a nova abstração, o código deixa de ser o centro do sistema.

O centro passa a ser o comportamento semântico declarado.

E, para mim, esse é o próximo passo natural do desenvolvimento de software: sair de prompts soltos e código gerado sem consistência para grafos semânticos, workflows coreografados, agentes especializados e behaviors reutilizáveis.

É por isso que esse vídeo me pegou tanto.

Ele praticamente descreve, com IA e agentes, a direção que eu já venho seguindo sem IA.

Agora falta transformar esse processo em runtime.

Top comments (0)