Un bot (inemaccbot) que no sabe nada sobre video, y dos dominios (promoavatar, promoavatar3) que no saben nada sobre colas. Esta página muestra el diagrama de cada uno, el mapa de los archivos de configuración y la tabla de «quiero cambiar X, ¿dónde lo modifico?».
La regla que sostiene el diseño está escrita en la parte superior del src/fluxos/runtime.ts: quien orquesta no trabaja; quien trabaja no decide. El worker ejecuta un job y lo marca done/failed; quien lee esto y elige la siguiente fase es el runtime — y lo hace dentro de la misma transacción del ack, para que «fase completada» y «siguiente fase encolada» nunca existan por separado.
Se mide en segundos. Clasifica el mensaje, lo valida contra el registry y lo pone en cola. Si el agente de interpret si alucina un comando que no existe, la respuesta es rechazarlo: catálogo cerrado.
SQLite en WAL. Cinco clases de recursos con concurrencia propia, para que un render no le quite el turno a una descarga. Lease con heartbeat: si el proceso falla, el job vuelve a la cola en vez de desaparecer.
Dos tipos de trabajo. kind: agent llama a un modelo con un prompt; kind: function es código TypeScript determinista. Hoy solo la fase de texto usa un modelo — avatar y reel son funciones.
Esta es la frontera que permite que el sistema crezca sin volverse pesado. El bot conoce las colas, las puertas de control, los reintentos y la entrega. El dominio conoce los públicos, los prompts, las plantillas y el motor de renderizado. Ambos se conectan en exactamente dos archivos.
config/fluxos.json indica qué carpeta cargar; el flow.ts valida el flow.json que vino de allí y rechaza lo que esté malformado antes de que exista cualquier job.lives2), nunca por ruta. El bot es el único que sabe dónde está eso en el disco, en un solo lugar (src/dominio/destinos.ts). Un canal nuevo aparece con solo crear la carpeta yt-pub-livesN — sin editar ni recompilar el bot.
| Archivo | Se lee cuando | ¿Hay que reiniciar? |
|---|---|---|
config/fluxos.json | al arrancar el proceso | Sí — y comprueba que la cola esté vacía antes: restart mata los renders en curso |
config/skills.json | al arrancar el proceso | Sí |
flow.json del dominio | al crear cada flujo | No |
prompts/*.md del dominio | al crear — el texto es congelado dentro del flujo | No (pero un flujo ya creado mantiene el texto anterior) |
templates/*.json | al momento del render, por medio de preparar.py | No |
.env | al arrancar | Sí |
Un flujo no es un script que se ejecuta de principio a fin. Es una definición que congelada al crear y luego avanza fase a fase, con el estado en la base de datos. Esto es lo que permite reiniciar el proceso a mitad de camino y continuar desde donde se quedó.
flow.json| Campo | Qué hace | Valores |
|---|---|---|
id | nombre de la fase: aparece en /status | texto, gerar, baixar, reel… |
escopo | 1 job por flujo, o 1 job por objetivo | fluxo · alvo |
fila | clase de recurso — define la concurrencia | texto io render navegador cpu |
kind | llama a un modelo o ejecuta código | agent · function |
tarefa | qué tarea ejecuta el bot | fluxo-agente, heygen.gerar, reel.montar… |
prompt | archivo del dominio (solo en kind: agent) | prompts/fase1-3versoes.md |
variantes | prompts alternativos, intercambiados por | prompt=<nome> | {"viral": "prompts/fase1-viral.md"} |
opcional | la fase solo se ejecuta si se solicitó la flag | api estudio creditos navega |
pausa_apos | puerta humana después de la fase | true |
espera | poll de trabajo externo | {intervalo, timeout} en segundos |
max_tentativas | reintento antes de marcar fallo | 2, 3 |
El dominio original. 12 objetivos (uno por público), prefijo A, siete fases, de las cuales cuatro son rutas opcionales alternativas para producir el mismo avatar.
texto → estudio → baixar → reel.El mismo esqueleto, con el objetivo convertido en <publico>-<tipo>: 12 públicos × 3 tipos = 36 objetivos, prefijo C. Ganó variantes de prompt, CTA por variante y el mapa de layout — y perdió la ruta navega-avatar.
| promoavatar | promoavatar3 | |
|---|---|---|
| prefijo | A | C |
| objetivo | <publico> — 12 | <publico>-<tipo> — 36 |
| fases | 7 (4 rutas opcionales) | 6 (3 rutas opcionales) |
ruta | navega | tiene (el agente pilota el navegador) | no tiene |
| variantes de prompt | no | manifesto, viral |
| CTA | valor predeterminado del motor | cta: {padrao, viral} por variante |
| modelo de la fase de texto | opus fijado en el flow.json | perfil predeterminado del bot |
| timeout del reel | 10800s (3h) | 21600s (6h) |
SOBREPOSIÇÕES e IMAGENS), hay que replicarlo en todos los prompts de los dos repos. Nada en el sistema lo impone.
La fase reel no renderiza nada: arma una línea de comandos y ejecuta un proceso en segundo plano. Todo el render se hace en Python, en el repo de dominio; y un dominio puede apuntar al motor de otro con motor_repo, en vez de copiar los scripts.
La tabla que responde el 90% de las preguntas de quienes llegan al sistema. La columna de la derecha indica si hace falta reiniciar el proceso.
| Quiero… | Cambio | ¿Restart? |
|---|---|---|
| Cambiar el texto que escribe la IA | <dominio>/prompts/fase1-*.md | no |
| Agregar un público / objetivo | <dominio>/flow.json → alvos | no |
| Cambiar el canal de un público | <dominio>/flow.json → alvos.<alvo>.canal | no |
Crear un canal nuevo (lives7) | crear la carpeta ~/projetos/yt-pub-lives7/imports/videos | no |
| Cambiar el avatar o la voz | <dominio>/flow.json → avatar_id, voice_id, engine | no |
| Cambiar el aspecto del reel | <dominio>/templates/*.json | no |
| Cambiar el layout que usa cada formato | <dominio>/templates/mapa.json | no |
| Definir un diseño para un público | flow.json → alvos.<alvo>.template | no |
| Cambiar el clip de cierre | <dominio>/cta/*.mp4 + flow.json → cta | no |
| Crear una variante de prompt | flow.json → fases[texto].variantes + el nuevo .md | no |
| Agregar una fase al pipeline | flow.json → fases (la tarea ya debe existir en el bot) | no |
| Agregar una tarea nueva | src/fila/tarefas/ + registrar en tarefas/index.ts | sí |
| Registrar un dominio nuevo | config/fluxos.json | sí |
| Registrar una skill de una etapa | config/skills.json + prompts/<nome>.md | sí |
| Cambiar la concurrencia de una cola | src/fila/filas.ts → CONCORRENCIAS | sí |
| Cambiar la clave de HeyGen / token | .env | sí |
| Cambiar la ayuda que aparece en el chat | <dominio>/HELP.md | no |
Esta es la prueba de que la frontera está en el lugar correcto. Si tu nuevo dominio exige editar TypeScript, necesita una tarea que no existe o la frontera se filtró.
# 1. el repo del dominio, junto al bot mkdir ~/projetos/meudominio && cd ~/projetos/meudominio # 2. flow.json — objetivos, fases y el motor de otro 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 en el bot — ÚNICO punto que requiere reiniciar # config/fluxos.json: {"command":"meudominio","repo":"meudominio",...} # 4. probar SIN gastar avatar ni render /meudominio assunto de teste | sombra
/fila y comprueba si hay un job running. Restart termina el render en curso — y un reel con 6h de timeout vuelve a empezar desde cero.
No son detalles de implementación: son las decisiones que evitaron clases enteras de errores y que sirven para cualquier sistema de colas con agentes.
El worker ejecuta y marca done/failed. Quien decide la siguiente fase es el runtime, dentro de la misma transacción del ack. Separar eso fue lo que acabó con el dispatch duplicado de la versión anterior.
El texto de los prompts se incorpora al flujo al crearlo. Editar el prompt hoy no altera un flujo de ayer, y /refazer reproduce exactamente lo que se ejecutó.
El agente que interpreta texto libre puede alucinar un comando. Su salida se valida contra el registry antes de convertirse en job: un comando que no existe es un rechazo, no un intento.
Pedirlo en el prompt no garantiza nada. Cuando el agente del reel ignoró la sección IMAGENS e inventó sus propios prompts, la corrección no fue escribir la regla en negrita: fue mover la generación de imágenes dentro del preparar.py. Solo el script, el exit code o la ruta eliminada cambian el comportamiento.
pausa_apos existe donde el siguiente paso cuesta dinero u horas. Revisar 36 guiones en el chat es barato; rehacer 36 avatares no lo es.
El dominio dice lives2; solo el bot sabe que esto es ~/projetos/yt-pub-lives2/imports/videos. Una lista de canales en N copias diverge; en un solo lugar, no.
Barrera entre fases, preempción de jobs, límite global de agentes y multiusuario. Cada ausencia tiene un detonante documentado para reconsiderarla. Un sistema que nace con todo es un sistema que nadie termina.