Artigo
Compozy: o orquestrador que transforma o Spec-Driven Development em código real
O que é Spec-Driven Development e como o Compozy torna isso prático: council de ideias, PRD, execução de tasks em daemon e rounds de review. Tudo num exemplo real, construir um jogo da velha em React, do primeiro prompt até os fixes.
- Publicado em
- 11 min de leitura
- 2 visualizações
O jeito rápido de construir uma feature com um agente de IA é abrir um chat, descrever o que você quer e deixar ele escrever código até a coisa mais ou menos funcionar. Parece produtivo. E é, por mais ou menos um dia. Aí você volta depois, não lembra por que o agente tomou metade das decisões, não acha registro nenhum delas, e descobre que a feature "pronta" quebrou silenciosamente uma regra que ninguém chegou a escrever.
Tem um nome pro caminho oposto: Spec-Driven Development (SDD). E tem uma ferramenta que torna esse caminho prático sem virar burocracia: o Compozy. Conheci o Compozy no curso IA para Devs, do Pedro Nauck e do Rodrigo Branas, dois caras que valem a pena acompanhar. Este blog inteiro nasceu desse fluxo, mas em vez de te mostrar o blog, vou construir uma coisa do zero na sua frente: um jogo da velha em React, do primeiro prompt até os fixes do review.
O que é Spec-Driven Development
A ideia central é uma só: a especificação é a fonte da verdade, não o código. Você decide o quê e o porquê antes do como, registra isso num documento durável, e só então deixa o agente implementar contra uma spec aprovada. O código vira a saída do processo, não o processo.
Na prática isso vira um pipeline curto, onde cada fase produz um artefato que a próxima consome:
ideia -> PRD -> tech spec -> tasks -> execução -> reviewA diferença pro prompt-and-pray não está na primeira hora, quando os dois cospem código parecido. Está duas semanas depois, quando você precisa lembrar por que algo foi feito daquele jeito. No SDD a resposta está num arquivo ao lado do código. No outro, está numa janela de chat que você já fechou.
O Compozy é um CLI em Go que orquestra esse pipeline inteiro e dispara o agente de código que você usa (Claude, Codex, Copilot, Cursor, Gemini) através de um runtime comum. Os artefatos de planejamento ficam como markdown em .compozy/tasks/<slug>/, no disco, ao lado do código e fora de qualquer transcript de chat. Commitar esses arquivos é opcional: você versiona no git pra ter o histórico, ou deixa no .gitignore e confia no catálogo que o daemon mantém em global.db. (Aqui no blog, eles são gitignored: o que guia o build são as specs, não o fato de elas estarem no repo.)
Instalando o Compozy
Escolha um dos métodos. Homebrew é o mais direto:
brew install compozy/compozy/compozynpm install -g @compozy/cli # via npmgo install github.com/compozy/compozy/cmd/compozy@latest # via GoConfira a versão (vai importar daqui a pouco):
compozy version
# compozy version 0.2.7 (commit=7363565 date=2026-05-27T...)Depois rode o setup uma vez. Ele instala as skills core do Compozy no seu agente:
compozy setupO Compozy não roda o modelo sozinho. Ele dirige um runtime ACP que você instala à parte (o Claude Agent, por exemplo). É esse runtime que escreve o código; o Compozy decide o que mandar e quando.
A extensão cy-idea-factory (e o tal do <tag>)
A fase de ideação, com o council de advisors, vem numa extensão opcional: a cy-idea-factory. Instalação em três comandos:
compozy ext install --yes compozy/compozy --remote github --ref v0.2.7 --subdir extensions/cy-idea-factory
compozy ext enable cy-idea-factory
compozy setupRepara no --ref v0.2.7. Esse foi exatamente o ponto que me travou na primeira vez, então vale parar aqui.
O <tag> da documentação não é uma versão qualquer: é uma git tag real do repositório compozy/compozy, e as tags de release são prefixadas com v. A armadilha é óbvia depois que você vê: eu rodei com --ref 0.2.7 e o install falhou, porque a tag no GitHub é v0.2.7, com o v. O número sem o v não existe como ref.
A regra prática: pegue a sua versão com compozy version, prefixe com v, e use isso no --ref. Se você está no Compozy 0.2.7, a tag é v0.2.7. Casar a tag da extensão com a versão do seu CLI também evita instalar uma extensão de uma versão incompatível.
Confirma que ficou ativa:
compozy ext list
# cy-idea-factory enabledMãos à obra: um jogo da velha
Daqui pra frente é o fluxo SDD completo. O escopo que vou seguir é o MVP: dois jogadores no mesmo dispositivo, detecção de vitória e empate, e um botão de reiniciar. Sem IA, sem online, sem placar. (Spoiler: o council vai tentar me empurrar o online, e eu vou recusar.)
1. Ideação: o council entra em campo
Começo com a ideia crua:
/cy-idea-factory um jogo da velha em React pro blogA skill não sai escrevendo. Primeiro ela faz de 3 a 6 perguntas, uma por mensagem, pra refinar escopo e intenção. As minhas vieram mais ou menos assim:
P1 — Qual o tamanho ideal do V1?
A) MVP — só 2 jogadores no mesmo dispositivo
B) Completo — com IA pra jogar sozinho
C) Plataforma — partidas online, salas, ranking
> A
P2 — Quão ambicioso isso deve ser?
A) Quick win — esforço pequeno, valida numa tarde
B) Strategic bet — esforço maior, abre caminho pra mais
C) Compounding — fica mais valioso com o tempo
> A
P3 — Onde ele roda?
> Numa rota do blog, /jogo-da-velhaCom o escopo refinado, ela faz pesquisa em paralelo (codebase + web) e então convoca o council: um grupo de advisors, cada um um subagente com uma perspectiva própria. O roster vem do compozy setup, e a skill seleciona de 3 a 5 conforme a complexidade do dilema. Pro jogo da velha, três bastaram:
Council (3 advisors): pragmatic-engineer, architect-advisor, devils-advocate
devils-advocate — "Jogo da velha sem oponente cansa em dois minutos.
Se o objetivo é alguém *jogar*, o online ou a IA não são V2, são o produto."
pragmatic-engineer — "O MVP local valida o tabuleiro, o turno e a
detecção de vitória numa tarde. Online arrasta WebSocket, salas e estado
de servidor pra dentro de um exemplo de tutorial. Fora de escopo."
architect-advisor — "Se a chance de virar IA depois é real, modele o estado
desde já como uma função pura (board, jogada) -> board. A IA só lê esse
estado. Não acopla a lógica de jogo ao componente."A tensão é real e o council não a varre pra baixo do tapete: ela vira uma exclusão explícita do V1 e uma oportunidade de V2. Eu fico com o MVP local (era o objetivo: praticar SDD num exemplo pequeno), mas levo o conselho do architect pra dentro: a lógica de jogo vai ser uma função pura, fácil de plugar numa IA depois. Essa decisão, com o trade-off inteiro, é escrita no _idea.md e num ADR. A discordância fica registrada, não evapora.
2. Requisitos: do _idea.md ao PRD
/cy-create-prd jogo-da-velhaA skill lê o _idea.md como contexto e faz mais algumas perguntas, todas focadas no quê e no porquê, nunca no como (o protocolo dela proíbe falar de React, estado ou testes nessa fase). As perguntas são do tipo "o leitor pode desfazer uma jogada, ou ela é final?" e "o que conta como sucesso aqui?". No fim ela apresenta de 2 a 3 abordagens de produto com trade-offs (só o tabuleiro, tabuleiro + histórico de partidas, ou tabuleiro + níveis de dificuldade) e registra a escolhida, só o tabuleiro, num ADR. O _prd.md que sai é focado em negócio:
## Goals
- G1 — Dois jogadores alternam X e O clicando nas casas.
- G2 — O jogo detecta vitória (8 linhas) e empate (tabuleiro cheio).
- G3 — Um botão reinicia a partida a qualquer momento.
## Non-goals
- Oponente por IA (V2)
- Partidas online (fora de escopo)
- Placar persistente entre partidasGoals falsificáveis. Cada um dá pra conferir contra a tela depois. Isso vale dez instruções de "deixa bom" pra um modelo.
3. Tech spec e tasks
O /cy-create-techspec traduz o PRD pro como, e é aqui que as perguntas viram técnicas: o estado é Array(9) de 'X' | 'O' | null, o jogador atual é derivado da contagem de jogadas, e o vencedor sai de uma função pura sobre as 8 linhas vencedoras (3 linhas, 3 colunas, 2 diagonais). Componentes: Board, Square, Status. É também aqui que cai a pergunta de integração específica deste blog: como o MDX é compilado pra HTML estático, um componente dentro do .mdx viraria markup morto e sem interatividade. A decisão (que vira um ADR) é montar o componente na rota, anexado ao fim do post. É o tabuleiro que você joga logo abaixo.
O /cy-create-tasks quebra isso em arquivos de task implementáveis sozinhos:
_tasks.md
task_01 — rota /jogo-da-velha + componente Board vazio
task_02 — estado com useReducer (board, jogada)
task_03 — clique na casa + alternância de turno
task_04 — função pura calculateWinner + detecção de empate
task_05 — Status (vez de quem / vencedor) + botão reset
task_06 — testes com VitestSeis tasks pequenas. Você enxerga o formato do trabalho inteiro antes de uma linha ser escrita.
4. Execução em daemon
Aqui está a parte que mais me convenceu do Compozy. A execução não roda no seu chat: ela roda num daemon.
Daemon é um processo que fica rodando em segundo plano, desacoplado do seu terminal. Sobe uma vez, se mantém de pé sozinho e atende trabalho sob demanda, mesmo depois que você fecha a janela. É o mesmo modelo do dockerd ou de um Postgres rodando local: você não "abre" o banco toda vez, ele já está lá. O Compozy mantém um desses com escopo no seu home (~/.compozy/), um por máquina, e é ele que segura o estado entre as tasks.
compozy tasks run jogo-da-velha --ide claudeEsse comando sobe o daemon (se ainda não estiver de pé) e entrega as tasks, uma de cada vez, pro agente configurado. O daemon é dono do estado do run: ele sequencia as tasks na ordem, mantém a memória por task como contexto, grava snapshots e expõe um stream do progresso. Com auto_commit ligado no config.toml, cada task vira seu próprio commit.
Como vive fora da sessão, você não fica refém do terminal. Dá pra soltar e voltar depois:
compozy tasks run jogo-da-velha --ide claude --detach # roda em background
compozy runs attach <run-id> # reata e assiste
compozy runs watch # lista o que está rolandoO stream fica mais ou menos assim:
[jogo-da-velha] task_01 scaffold rota + Board ......... done (commit a1b2c3d)
[jogo-da-velha] task_02 useReducer ................... done (commit e4f5a6b)
[jogo-da-velha] task_03 turno ........................ done (commit 7c8d9e0)
[jogo-da-velha] task_04 calculateWinner + empate ..... done (commit 1f2a3b4)
[jogo-da-velha] task_05 status + reset ............... done (commit 5c6d7e8)
[jogo-da-velha] task_06 testes ....................... done (commit 9a0b1c2)Seis commits, um por task, cada um rastreável. Se a task_04 tivesse quebrado, o daemon pararia ali, com o estado preservado, e eu retomaria do ponto certo em vez de recomeçar do zero.
5. Review e os fixes
Código que um agente escreveu ainda passa por review. O /cy-review-round faz um passe crítico e joga os problemas como arquivos numerados em reviews-001/:
reviews-001/
issue_001.md (major) calculateWinner não cobre a anti-diagonal [2,4,6]
issue_002.md (major) empate é checado antes da vitória: última jogada
vencedora num tabuleiro cheio reporta "empate"
issue_003.md (minor) os botões Square não têm aria-labelO issue_001 é o bug clássico do jogo da velha. A lista de linhas vencedoras saiu faltando uma diagonal:
// errado — só uma diagonal
const LINES = [
[0, 1, 2], [3, 4, 5], [6, 7, 8], // linhas
[0, 3, 6], [1, 4, 7], [2, 5, 8], // colunas
[0, 4, 8], // diagonal principal (faltou [2,4,6])
];Mando o Compozy corrigir. Esse passo também roda no daemon, igual à execução das tasks:
compozy reviews fix jogo-da-velhaO daemon despacha os issues pro agente em lotes (dá pra ajustar com --concurrent e --batch-size), e cada um é triado, corrigido e verificado. O issue_001 vira a linha que faltava; o issue_002 vira uma troca de ordem (checar vitória primeiro, empate só se não houve vencedor); o issue_003 ganha um aria-label por casa:
// certo
const LINES = [
[0, 1, 2], [3, 4, 5], [6, 7, 8],
[0, 3, 6], [1, 4, 7], [2, 5, 8],
[0, 4, 8], [2, 4, 6], // as duas diagonais
];
function status(board: Board) {
const winner = calculateWinner(board); // vitória primeiro
if (winner) return `Vencedor: ${winner}`;
if (board.every(Boolean)) return "Empate"; // empate só depois
return `Vez de: ${currentPlayer(board)}`;
}Cada round vira uma rodada: quando o review volta vazio, a branch está pronta pro merge. No blog, é aí que os guardrails do repo assumem (branches tipadas, commits convencionais, make test && make lint && make check no CI, e o deploy automático no merge), mas isso é assunto de outro post.
E este blog?
Nada disso é hipotético. Este blog foi construído assim do início ao fim, com SDD e o Compozy: a escolha do stack (TanStack Start, Drizzle, Better Auth), onde hospedar, o pipeline de CI/CD que builda e faz deploy na VPS, e cada feature de bastidor. As decisões grandes ganharam um _idea.md, um _prd.md, ADRs, tasks e rounds de review antes de virar código. O jogo da velha é só um exemplo pequeno o bastante pra caber num post; a mecânica é a mesma pras decisões grandes.
O modelo mental
A versão de uma linha: pare de pedir código, comece a rodar um workflow que produz specs e depois cobra o agente em cima delas.
Um agente de IA é rápido, incansável e não tem memória nenhuma de por que fez qualquer coisa. O SDD compensa exatamente isso: o council estressa a ideia antes de ela virar plano, o PRD fixa o quê num documento que você confere, o daemon executa o plano fora da sua sessão e em commits rastreáveis, e o round de review caça o que escapou. O Compozy costura essas quatro coisas num CLI só. O jogo da velha levou uma tarde, mas saiu com o raciocínio em disco, e é isso que ainda vai estar lá no dia trinta.
E pra não ficar só na conversa: o tabuleiro logo abaixo é esse componente rodando ao vivo. O mesmo calculateWinner com as duas diagonais, a vitória checada antes do empate. Joga aí.