Live Apps: como a resposta do agente virou um painel
Por que, na OrganIA, a saída de um agente é um painel que acumula, se edita e aciona ferramentas, e o que aprendi construindo 21 tipos de bloco, um funil de escrita único e privacidade por linha.
Uma tabela que rola para cima custa caro
Medimos em produção, numa janela de 90 dias, o que as pessoas diziam logo depois de o agente mostrar uma tabela. Em 35% dos casos a fala seguinte pedia outro recorte da mesma lista. “Só os de São Paulo.” “Agora ordena por valor.” “E dos últimos 30 dias?”
Cada um desses pedidos virava um run completo do agente, de uns 23 segundos. O modelo relia o contexto, chamava de novo a mesma ferramenta, montava de novo a mesma tabela com uma linha a menos. Gastava token e tempo para fazer o que uma planilha faz com um clique.
Esse número mudou a forma como penso a saída de um agente. O chat é linear e efêmero. O trabalho de verdade se acumula e precisa ser operado. Quando o agente só responde em texto, a pessoa vira o banco de dados: precisa copiar o que importa para algum lugar antes que a mensagem suba e suma.
Live Apps é o nome que demos, no agent-builder da OrganIA, à resposta para isso.
A tese: o agente é a aplicação
Uma Live App é um painel persistente que o agente cria e alimenta. Tem abas, blocos (tabela, kanban, métricas, gráfico, formulário, botões), e fica ao lado da conversa. A pessoa consulta, corrige, arrasta um cartão, clica num botão que executa uma ferramenta. Outros agentes leem esse painel como fonte de verdade.
A diferença prática é entre um assistente que informa e um que produz. O primeiro te dá um parágrafo bem escrito sobre os prospects da semana. O segundo te dá um quadro com os prospects, onde você move “Empresa X” para “Negociando” e, amanhã, quando o agente refizer a busca, o cartão continua onde você deixou.
Dois perfis usam isso de jeitos diferentes. O Agent Boss cria e calibra os agentes e decide que painéis eles mantêm. O Business User não configura nada: pede, recebe, aprova e opera os painéis.
Regra que guia tudo: o que a pessoa lê enquanto conversa vai para o lado da conversa. O ato curto de configurar fica em diálogo. Um diálogo cobre o chat, e o que ela consulta no meio de uma conversa não pode cobrir justamente o que ela está lendo.
O transporte entre agente e interface é o AG-UI, protocolo aberto de eventos. É ele que faz os 44 tipos de bloco visual do chat funcionarem. Decidimos cedo não amarrar o núcleo a uma biblioteca de front específica e não criar dialeto próprio onde existe padrão: MCP para ferramentas, AG-UI para interface.
Duas metades: o que a app é e o que ela sabe
Toda Live App tem duas partes que mudam em ritmos diferentes. Entender essa separação explica quase todo o resto.
O template é o que a app é: as abas e a ordem delas, quais blocos e de que tipo, o papel de cada campo dos cartões, qual ferramenta atualiza cada aba, onde moram as linhas e quem pode ver. Muda quando alguém redesenha.
A instance é o que a app sabe: o dado atual de cada bloco, a hora exata da coleta, se veio completo, parcial ou com erro, qual ferramenta produziu e quem disparou (pessoa ou sistema). Muda a cada atualização.
O detalhe que mais me agrada aqui é que a hora da coleta viaja com o dado, não com a estrutura. Um painel com quatro abas pode ter quatro idades diferentes, e ele diz isso: “esta aba é de ontem às 18h, aquela é de agora”. Uma data única de “última modificação” engana. E o agente que lê o painel como contexto recebe a mesma informação, em vez de tratar tudo como igualmente atual.
O catálogo de blocos do painel é fechado: 21 tipos. Backend valida e frontend desenha a partir do mesmo registro.
| Grupo | Tipos | Observação |
|---|---|---|
| Linhas de trabalho | table, kanban, card_grid | As três são visões sobre as mesmas linhas |
| Números e forma | metric_cards, chart | Cada KPI leva a própria definição junto |
| Sequência e estrutura | list, timeline, mind_map, text, map, image, file_list | Conteúdo vivo, atualizável |
| Estáticos | callout, form, action_buttons | Vivem no template; o form recebe dado da pessoa |
| Desenhados pelo chat | área, funil, treemap, radar, dispersão, grafo de relações | Ainda em desenvolvimento |
São 15 tipos com desenho próprio do painel e 6 que usam o mesmo componente do chat. As linhas de uma tabela moram dentro do próprio painel (até 2 mil) ou numa tabela relacional separada (até 20 mil), para blocos que acumulam sem teto natural, como histórico ou biblioteca de atas. Quem consome não sabe nem precisa saber em qual modo o bloco está: pede as linhas e recebe as linhas. Cada agente pode criar até 50 apps.
Um funil para cinco escritores
Cinco atores escrevem num painel: o agente durante a conversa, a pessoa editando direto, a pessoa anotando uma linha, um webhook de sistema externo e uma ferramenta determinística que calcula o painel inteiro de uma vez.
Todos passam pelo mesmo caminho. Ninguém escreve no painel por fora dele.
agente na conversa ───┐
pessoa editando ──────┤ FUNIL DE ESCRITA
pessoa anotando ──────┼──► 1. trava o painel e relê o estado
webhook externo ──────┤ 2. guarda a versão anterior
ferramenta de cálculo ┘ 3. marca hora e origem, por aba
4. invalida o contexto dos agentes leitores
5. dispara as regras de alerta
O passo 1 parece detalhe e não é. Duas escritas simultâneas leem o estado, modificam e gravam. Sem a trava e sem reler o estado depois de obtê-la, a segunda sobrescreve a primeira e ninguém percebe. A linha simplesmente some.
O passo 5 é o que eu mais gosto. As regras de alerta (“me avise quando passar de 20 abertos”) escutam o funil. Resultado: qualquer coisa que escreva no painel, inclusive um webhook do Jira, passa a disparar alerta sem uma linha de código de integração entre os dois mecanismos. O mesmo webhook ganha versionamento, frescor declarado e invalidação de cache porque escreveu pelo caminho de todo mundo.
Passar pelo mesmo funil não quer dizer que todo escritor pode tudo. Duas regras dizem quem muda o quê.
A anotação só acrescenta. Tem um escritor só, que adiciona uma entrada e nada mais; a autoria é carimbada pelo servidor e o dono da linha recebe aviso. Antes, o navegador lia a lista, acrescentava e mandava a lista inteira de volta. A autoria vinha do cliente, dava para apagar a anotação de outra pessoa, e duas anotações simultâneas perdiam uma.
O valor pertence a quem o declarou. Um campo que vira fato para outro mecanismo guarda de onde veio, e essa origem sai da porta usada (a pessoa, o convite da agenda, o título do evento), nunca de quem escreve. A IA não sobrescreve esse campo: o palpite dela vai para um campo de sugestão. Aprendemos isso quando a transcrição de uma ata ouviu errado o nome de um cliente e a ata foi ancorada no caderno de outra empresa.
Atualizar sem chamar o modelo
A primeira pergunta ao criar um painel é qual é a natureza dele, porque isso decide o que “atualizar” significa.
| Semântica | O painel é | Atualizar significa | “Atualizar tudo” |
|---|---|---|---|
| Sincronizar | Espelho de algo que vive fora (Jira, ERP, anúncios) | Buscar de novo; rodar duas vezes dá o mesmo resultado | Ligado |
| Gerar | Conteúdo criado pelo agente (pauta, análise, plano) | Recriar, o que custa modelo e destrói parte do que havia | Desligado; só por aba |
Quando a aba declara no template qual ferramenta a atualiza, o clique vira uma chamada de função. O backend chama a ferramenta direto, sem passar pelo chat. Sem token, sem variação, sem o risco de o modelo escolher a ferramenta errada. Se a aba só tem caminho via agente, o botão diz “atualizar via agente”. Se não tem nenhum dos dois, é um painel de entrada de dados e não tem botão.
Uma salvaguarda explícita: ferramenta de escrita genérica nunca roda como atualizadora. Senão “atualizar” viraria “reescrever com o que estiver na mão”.
O chat ganhou um terceiro verbo, ainda em desenvolvimento: consultar de novo sem modelo. A tabela que o agente montou vira uma planilha leve, onde a pessoa filtra, agrupa, esconde coluna e baixa CSV. Se o servidor sabe de qual ferramenta a tabela veio, aparece “buscar de novo” com 7, 30 ou 90 dias. É exatamente o custo daqueles 35%. E quando a pessoa pergunta algo depois de filtrar, o recorte vai ao agente com o denominador (“6 de 22 desta resposta”), para ele não ler 6 linhas como se fossem o conjunto.
Quem carimba a origem é o servidor. O modelo só aponta a ferramenta; o servidor confere qual leitura do run contém as linhas. Na dúvida, fica sem origem, a tela diz “dados desta resposta” e o botão de buscar de novo não aparece.
Os botões do painel seguem a mesma lógica: executam uma ferramenta direto, sem LLM. Isso exige um portão bem desenhado, porque derivar autorização do template seria auto-autorizar qualquer ferramenta nomeada no payload. A regra: uma ferramenta só se auto-autoriza se o poder dela não exceder o que a interface já consegue fazer por outro caminho. Hoje só uma ferramenta passa nesse critério, a de atualizar uma linha. Ferramenta de domínio, que muda status num sistema externo ou envia algo, continua exigindo autorização explícita.
De quem é a etapa de um cartão
O kanban não é um tipo de dado. É uma visão sobre as mesmas linhas da tabela, em que um campo faz papel de etapa. Arrastar um cartão grava esse campo.
Isso cria a pergunta mais delicada da camada inteira: a pessoa move um cartão para “Negociando”, o agente reenvia a lista de prospects. O cartão volta para o começo?
Não volta. A regra cabe numa frase:
A etapa de um cartão pertence a quem a moveu, nunca a quem reenviou a lista.
Remessa parcial preserva a etapa. Lista nova completa preserva a etapa e marca quem sumiu como “não confirmado desde…”, sem apagar, porque a ausência pode ser do buscador e não do mundo. Só o arraste da pessoa muda a etapa. Existe um teste cujo nome é literalmente “mover um cartão MUDA a etapa”.
Mais dois detalhes. Cada cartão tem um identificador imutável, carimbado no nascimento; atualização sem ele é recusada com orientação, senão “atualizar o cartão X” viraria “criar outro parecido”. E cartão sem etapa válida vai para uma coluna “Sem etapa” visível, em vez de desaparecer.
Privacidade segue o mesmo cuidado. Um painel pode ser “só eu”, e isso é imposto no backend, não decorativo. Dentro de um painel compartilhado, cada linha pode ser do dono, e quem carimba o dono é o servidor. Três leitores veem a linha privada: o dono (tudo), o gestor do time (vê e anota, não edita nem exporta, e só se o departamento ligou essa opção) e o admin. A opção na tela diz quem vai ler, com nome, em vez de só “privado”.
A regra que dá valor a tudo isso: contexto dos agentes, chips de sugestão, o Cérebro e as automações sempre excluem o que é privado, inclusive do próprio dono. Parece errado e está certo. Essas superfícies são caches compartilhados por agente. Se a linha privada entrasse no contexto, vazaria para a próxima pessoa que conversasse com o mesmo agente, sem nenhuma falha de permissão, só por reuso de cache.
O que deu errado
A parte mais útil para quem vai construir algo parecido está aqui.
Conversão silenciosa de desenho. A primeira versão do “salvar no painel” convertia o bloco do chat para o que o painel sabia desenhar. Área virava linha, funil virava barras, dispersão sumia. Ninguém pediu essa troca. A pessoa salva o que viu; mudar o desenho no caminho é alterar o resultado sem avisar. Hoje a regra é o mesmo desenho, e o que ainda não tem destino aparece listado no diálogo de salvar. Dos 44 tipos de bloco do chat, 21 têm destino no painel, 7 não são painel por natureza (aprovação, evidência, anexo que vence em 30 dias) e 16 são dívida declarada. Um teste recusa bloco novo sem destino, e a lista da dívida só desce.
“Esta app não tem blocos” com o painel cheio. Temos dois formatos de template e três de instância, por evolução histórica. Três consumidores independentes, no mesmo dia, liam um formato só e devolveram vazio. Sem erro nenhum. A correção foi uma fachada única de leitura, e a regra que sobrou vale para qualquer sistema: resolver qual é o alvo antes de decidir qualquer coisa sobre ele, e na ambiguidade levantar erro instrutivo, nunca devolver vazio em silêncio.
Linha privada na tela de outra pessoa. Em setembro, num bloco com linhas guardadas no próprio painel, a linha privada de uma pessoa apareceu para outra. O filtro só existia para o outro modo de armazenamento. O conserto óbvio, filtrar no lugar, seria pior: as regras de alerta rodam dentro da escrita, e o filtro seria gravado junto, apagando as linhas privadas de todo mundo. Hoje a vista filtrada é sempre uma cópia, e o original nunca é tocado.
Botão que dava 403. O detector que autoriza a ferramenta de linha rodava só no assistente de configuração. Painel criado pelo chat respondia 403 no primeiro arraste de cartão. Medimos isso em produção em dois painéis reais antes de levar o detector para os dois caminhos.
Filtro sem quem preencha. Na biblioteca de atas, o campo cliente estava vazio em 15 de 17 registros, com filtro de cliente na tela. Filtro que ninguém preenche é decoração. Hoje um teste exige que todo filtro de texto tenha caminho humano de preenchimento.
“Aplicado”, mas a tela não sabia cumprir. A tabela do painel entendia link em markdown, a do chat mostrava o endereço cru. O dono aprovou uma regra para mostrar a chave como link, o sistema disse “aplicado” com razão, e a tela onde ele pediu não sabia obedecer. Antes de dar uma capacidade por pronta, confira se todas as superfícies onde ela é pedida a suportam.
Dessas histórias saiu uma regra de interface que hoje aplico em tudo: affordance de escrita só existe onde a escrita existe. O kanban que o agente mostra no chat é rascunho e não tem alça de arrastar. O gestor que vê a linha do time não ganha botão de editar. Mostrar um controle que não salva nada é pior que não mostrar.
Onde estamos, em números
| Item | Número |
|---|---|
| Tipos de bloco no painel | 21 (15 próprios + 6 desenhados pelo chat) |
| Tipos de bloco visual no chat | 44 |
| Origens de escrita | 5, por um funil só |
| Linhas por bloco | 2 mil dentro do painel, 20 mil em tabela própria |
| Apps por agente | até 50 |
| Leitores de linha privada | 3 (dono, gestor do time, admin) |
| Falas pós-tabela pedindo outro recorte | 35% em 90 dias, ~23 s por run |
Sendo honesto sobre o que falta. A ponte que salva no painel os blocos do chat com o mesmo desenho, e a tabela e o gráfico vivos do chat, estão prontos em desenvolvimento, não em produção. O funil sabe validar cada bloco contra o esquema do tipo, mas a validação está desligada por padrão; bloco com forma errada é aceito e o problema aparece na renderização. A heurística que sugere próximas ações num painel vazio cobre 5 dos 21 tipos. Parte do funil ainda lê blocos sem passar pela fachada única. E não existe forma de publicar um painel para quem não é usuário da plataforma; avaliamos e estacionamos de propósito.
Checklist para quem quer fazer o agente produzir em vez de responder
- Meça o que vem depois da resposta. Se uma fatia relevante das falas pede outro recorte do mesmo dado, você está pagando run de modelo para fazer trabalho de planilha.
- Separe estrutura de estado. Template e instância mudam em ritmos diferentes. Guarde a hora da coleta e o status (completo, parcial, erro) por bloco.
- Feche o catálogo de blocos. Um registro único que o backend valida e o frontend desenha. Tipo novo sem destino é recusado por teste.
- Um funil de escrita, sem exceções. Trava, releitura, versão anterior, origem, invalidação de contexto, alertas. Webhook e pessoa passam pelo mesmo lugar.
- Decida a semântica de atualização por painel. Sincronizar é idempotente e pode ser barato; gerar custa modelo e não deve ser “atualizar tudo”.
- Tire o modelo do caminho do clique. Declare no template que ferramenta atualiza cada aba, e chame direto.
- Diga de quem é cada valor. Etapa pertence a quem moveu. Campo âncora pertence à porta de origem. O palpite da IA vai para um campo de sugestão.
- Origem é carimbada pelo servidor. Se o servidor não sabe de onde veio o dado, a interface não oferece “buscar de novo”.
- Privado fica fora de tudo que é compartilhado, inclusive do cache de contexto do agente, inclusive para o próprio dono.
- Nunca mostre controle que não grava.
O chat continua sendo a porta de entrada. Só deixou de ser o lugar onde o resultado mora. Quando a pessoa volta no dia seguinte e encontra o quadro do jeito que deixou, com cada aba dizendo a própria idade, ela para de copiar resposta para planilha.
Um e-mail do lab a cada duas semanas.
O que testei, os números e o que deu errado. Sem spam. Dá para sair quando quiser.