Um bot (inemaccbot) que não sabe nada sobre vídeo, e dois domínios (promoavatar, promoavatar3) que não sabem nada sobre filas. Esta página tem o diagrama de cada um, o mapa dos arquivos de configuração e a tabela de "quero mudar X, mexo onde".
A regra que sustenta o desenho está escrita no topo do src/fluxos/runtime.ts: quem orquestra não trabalha, quem trabalha não decide. O worker executa um job e o marca done/failed; quem lê isso e escolhe a próxima fase é o runtime — e faz isso dentro da mesma transação do ack, para que "fase feita" e "próxima fase enfileirada" nunca existam separadas.
Mede-se em segundos. Classifica a mensagem, valida contra o registry e enfileira. Se o agente do interpret alucinar um comando que não existe, a resposta é recusar — catálogo fechado.
SQLite em WAL. Cinco classes de recurso com concorrência própria, para que um render não roube a vez de um download. Lease com heartbeat: se o processo cair, o job volta para a fila em vez de sumir.
Duas naturezas de job. kind: agent chama um modelo com um prompt; kind: function é código TypeScript determinístico. Hoje só a fase de texto usa modelo — avatar e reel são função.
Essa é a fronteira que faz o sistema crescer sem inchar. O bot conhece filas, portões, retentativa e entrega. O domínio conhece públicos, prompts, templates e o motor de render. Os dois se encontram em exatamente dois arquivos.
config/fluxos.json diz qual pasta carregar; o flow.ts valida o flow.json que veio de lá e recusa o que estiver malformado antes de qualquer job existir.lives2), nunca por caminho. Quem sabe onde isso fica no disco é o bot, num lugar só (src/dominio/destinos.ts). Um canal novo aparece só de criar a pasta yt-pub-livesN — sem editar nem recompilar o bot.
| Arquivo | Lido quando | Precisa reiniciar? |
|---|---|---|
config/fluxos.json | no boot do processo | Sim — e confira a fila vazia antes: restart mata render em voo |
config/skills.json | no boot do processo | Sim |
flow.json do domínio | na criação de cada fluxo | Não |
prompts/*.md do domínio | na criação — o texto é congelado dentro do fluxo | Não (mas fluxo já criado mantém o texto antigo) |
templates/*.json | na hora do render, pelo preparar.py | Não |
.env | no boot | Sim |
Um fluxo não é um script que roda de cima a baixo. Ele é uma definição que é congelada na criação e depois avança fase a fase, com o estado no banco. Isso é o que permite reiniciar o processo no meio e continuar de onde parou.
flow.json| Campo | O que faz | Valores |
|---|---|---|
id | nome da fase — aparece no /status | texto, gerar, baixar, reel… |
escopo | 1 job por fluxo, ou 1 job por alvo | fluxo · alvo |
fila | classe de recurso — define a concorrência | texto io render navegador cpu |
kind | chama um modelo, ou roda código | agent · function |
tarefa | qual tarefa do bot executa | fluxo-agente, heygen.gerar, reel.montar… |
prompt | arquivo do domínio (só em kind: agent) | prompts/fase1-3versoes.md |
variantes | prompts alternativos, trocados por | prompt=<nome> | {"viral": "prompts/fase1-viral.md"} |
opcional | a fase só entra se a flag foi pedida | api estudio creditos navega |
pausa_apos | portão humano depois da fase | true |
espera | poll de trabalho externo | {intervalo, timeout} em segundos |
max_tentativas | retentativa antes de marcar falha | 2, 3 |
O domínio original. 12 alvos (um por público), prefixo A, sete fases das quais quatro são rotas opcionais alternativas para produzir o mesmo avatar.
texto → estudio → baixar → reel.O mesmo esqueleto, com o alvo virando <publico>-<tipo>: 12 públicos × 3 tipos = 36 alvos, prefixo C. Ganhou variantes de prompt, CTA por variante e o mapa de layout — e perdeu a rota navega-avatar.
| promoavatar | promoavatar3 | |
|---|---|---|
| prefixo | A | C |
| alvo | <publico> — 12 | <publico>-<tipo> — 36 |
| fases | 7 (4 rotas opcionais) | 6 (3 rotas opcionais) |
rota | navega | tem (agente pilota o navegador) | não tem |
| variantes de prompt | não | manifesto, viral |
| CTA | default do motor | cta: {padrao, viral} por variante |
| modelo da fase texto | opus fixado no flow.json | perfil padrão do bot |
| timeout do reel | 10800s (3h) | 21600s (6h) |
SOBREPOSIÇÕES e IMAGENS), tem que replicar em todos os prompts dos dois repos. Nada no sistema força isso.
A fase reel não renderiza nada: ela monta uma linha de comando e dispara um processo destacado. Todo o render mora em Python, no repo de domínio — e um domínio pode apontar para o motor de outro com motor_repo, em vez de copiar os scripts.
A tabela que responde 90% das perguntas de quem chega no sistema. A coluna da direita diz se é preciso reiniciar o processo.
| Quero… | Mexo em | Restart? |
|---|---|---|
| Mudar o texto que a IA escreve | <dominio>/prompts/fase1-*.md | não |
| Acrescentar um público / alvo | <dominio>/flow.json → alvos | não |
| Trocar o canal de um público | <dominio>/flow.json → alvos.<alvo>.canal | não |
Criar um canal novo (lives7) | criar a pasta ~/projetos/yt-pub-lives7/imports/videos | não |
| Trocar o avatar ou a voz | <dominio>/flow.json → avatar_id, voice_id, engine | não |
| Mudar o visual do reel | <dominio>/templates/*.json | não |
| Mudar qual layout cada formato usa | <dominio>/templates/mapa.json | não |
| Cravar um layout para um público | flow.json → alvos.<alvo>.template | não |
| Trocar o clipe de encerramento | <dominio>/cta/*.mp4 + flow.json → cta | não |
| Criar uma variante de prompt | flow.json → fases[texto].variantes + o novo .md | não |
| Acrescentar uma fase ao pipeline | flow.json → fases (a tarefa já tem que existir no bot) | não |
| Acrescentar uma tarefa nova | src/fila/tarefas/ + registrar em tarefas/index.ts | sim |
| Registrar um domínio novo | config/fluxos.json | sim |
| Registrar uma skill de uma etapa | config/skills.json + prompts/<nome>.md | sim |
| Mudar a concorrência de uma fila | src/fila/filas.ts → CONCORRENCIAS | sim |
| Trocar chave da HeyGen / token | .env | sim |
| Mudar a ajuda que aparece no chat | <dominio>/HELP.md | não |
Este é o teste de que a fronteira está no lugar certo. Se o seu domínio novo exigir editar TypeScript, ou ele precisa de uma tarefa que não existe, ou a fronteira vazou.
# 1. o repo do domínio, irmão do bot mkdir ~/projetos/meudominio && cd ~/projetos/meudominio # 2. flow.json — alvos, fases e o motor de outro repo cat > flow.json <<'JSON' { "nome": "meudominio", "prefixo": "M", "versao_def": 1, "motor_repo": "promoavatar3", "templates_dir": "templates", "template": "empilhado-capa", "alvos": { "ep01": { "canal": "lives2", "fecho": "..." } }, "fases": [ { "id": "texto", "escopo": "fluxo", "fila": "texto", "kind": "agent", "tarefa": "fluxo-agente", "prompt": "prompts/fase1.md", "pausa_apos": true }, { "id": "reel", "escopo": "alvo", "fila": "render", "kind": "function", "tarefa": "reel.montar" } ] } JSON # 3. registrar no bot — ÚNICO ponto que exige restart # config/fluxos.json: {"command":"meudominio","repo":"meudominio",...} # 4. testar SEM gastar avatar nem render /meudominio assunto de teste | sombra
/fila e veja se há job running. Restart mata render em voo — e um reel de 6h de timeout recomeça do zero.
Não são detalhes de implementação — são as escolhas que evitaram classes inteiras de bug, e que valem para qualquer sistema de fila com agentes.
O worker executa e marca done/failed. Quem decide a próxima fase é o runtime, dentro da mesma transação do ack. Foi separar isso que acabou com o dispatch duplicado da versão anterior.
O texto dos prompts é embutido no fluxo na criação. Editar o prompt hoje não altera um fluxo de ontem — e /refazer reproduz exatamente o que rodou.
O agente que interpreta texto livre pode alucinar um comando. A saída dele é validada contra o registry antes de virar job: comando que não existe é recusa, não tentativa.
Pedir no prompt não garante nada. Quando o agente do reel ignorou a seção IMAGENS e inventou os próprios prompts, a correção não foi escrever a regra em negrito — foi mover a geração das imagens para dentro do preparar.py. Só script, exit code ou caminho removido mudam comportamento.
pausa_apos existe onde o próximo passo custa dinheiro ou horas. Revisar 36 roteiros no chat é barato; refazer 36 avatares não é.
O domínio diz lives2; só o bot sabe que isso é ~/projetos/yt-pub-lives2/imports/videos. Uma lista de canais em N cópias diverge; em um lugar só, não.
Barreira entre fases, preempção de job, teto global de agentes e multiusuário. Cada ausência tem um gatilho documentado para ser reconsiderada. Um sistema que já nasce com tudo é um sistema que ninguém termina.