Robôs de Automação
Nível de acesso requerido
Admin ou Super Admin
O que é um robô?
Um robô é um programa automatizado que executa tarefas sem intervenção humana. Robôs podem:
- Iniciar processos automaticamente em horários agendados
- Buscar dados de sistemas externos e alimentar o Flowi Agentic
- Executar etapas que não dependem de uma pessoa
- Integrar com ERPs, sistemas fiscais e outras plataformas
Cada robô é um arquivo JAR compilado previamente enviado para a plataforma, operando mediante agendamento CRON.
Atenção: Robôs ≠ Agentes
Os "Robôs" tratam de integração sistêmica rígida (código). Se o que você precisa é de uma automação cognitiva (ler PDFs, interpretar formulários livres ou decidir caminhos flexíveis com base em IA), acesse o manual de Agentes de IA.
Listando robôs
Acesse Automação → Robôs na barra lateral.
A lista responde de relance a pergunta que importa — algo quebrou? — com uma linha por robô:
| Coluna | O que mostra |
|---|---|
| Nome | nome e a chave do robô, com a etiqueta Global quando o robô é da plataforma |
| Situação | ativo ou inativo |
| Agendamento | a expressão cron, ou sob demanda quando não há |
| Última execução | o resultado e quando foi |
| JAR | a versão ativa, ou o aviso de que falta enviar |
Robô que nunca rodou aparece como Nunca executou, e não em branco: ausência de resultado e resultado bom não devem ter a mesma aparência.
Clicar na linha abre a página do robô.
Cadastrando um novo robô
- Clique em Novo robô
- Preencha:
- Nome — identificação do robô
- Agendamento — expressão cron (ex.:
0 8 * * MON= toda segunda às 8h) - Arquivo JAR — faça o upload do arquivo compilado
- Parâmetros — configurações opcionais em formato JSON (a coluna é
jsonb: um objeto malformado é recusado ao salvar, e não na primeira execução agendada)
- Clique em Salvar
Expressão cron
Formato: minuto hora dia-do-mês mês dia-da-semana
Exemplos:
0 8 * * *— todo dia às 8h0 8 * * MON— toda segunda às 8h*/30 * * * *— a cada 30 minutos
Um tique só dispara se o anterior já terminou
Um robô de 1 em 1 minuto que leva 5 minutos não roda cinco vezes ao mesmo tempo, e também não acumula fila: o tique é pulado enquanto a execução anterior daquele robô, naquele tenant, ainda não terminou. O motivo fica no log da aplicação.
O disparo manual não é a exceção: Executar agora com uma execução em andamento é recusado, com a mensagem dizendo isso — veja Executando manualmente. A diferença entre os dois é só o que se vê: o tique some no log, o clique recebe a recusa na tela.
O bloqueio é por tenant: um robô global lento num assinante não segura o tique dos outros.
O JAR é conferido no envio
Ao enviar o JAR, a plataforma o varre e verifica se a classe principal cadastrada existe dentro dele e implementa FlowIaRobot. Se não existir, o envio é recusado na hora e a mensagem lista as classes de robô que o arquivo realmente contém — normalmente é só copiar o nome certo para o campo.
A varredura lê os metadados do bytecode e não carrega as classes no processo do servidor. Carregar código enviado por um tenant executaria o inicializador estático dele dentro da plataforma, que é justamente o que a sandbox existe para impedir.
Sem essa conferência, uma classe principal digitada errado só apareceria quando o robô executasse — no caso de um robô agendado, horas depois, como falha de execução em vez de erro de cadastro.
No mesmo envio a plataforma calcula a impressão digital (SHA-256) do arquivo e a guarda junto da versão. Toda vez que o JAR precisa ser recuperado — depois de um deploy, por exemplo, quando o arquivo já não está no disco do servidor — a plataforma confere essa impressão antes de executar. Se não bater, a execução é recusada com uma mensagem dizendo que o arquivo não está íntegro, em vez de falhar adiante como "classe principal não encontrada", que apontaria para o lado errado.
Parâmetros declarados pelo JAR
O robô pode declarar quais configurações precisa, direto na classe:
@RobotParameter(name = "erpUrl", label = "URL do ERP", required = true)
@RobotParameter(name = "erpToken", label = "Token", required = true, secret = true)
public class SincronizaCadastros implements FlowIaRobot { … }A plataforma lê essa declaração no envio do JAR e monta o formulário a partir dela — em vez de pedir um JSON livre onde uma chave digitada errada só aparece quando o robô executa.
Preencher e salvar esse formulário — e mesmo abri-lo para ver o que está gravado — é ação de Admin do tenant ou Super Admin, o mesmo papel exigido pelo resto do cadastro do robô. Um gerente ou um usuário comum recebe recusa: a configuração nomeia cada parâmetro declarado e mostra para onde a referência de um parâmetro secreto aponta, o que já é meio caminho para o segredo.
Um parâmetro secreto aponta para onde o segredo mora
Um campo marcado secret = true não deve receber o valor. Escreva nele uma referência, e o segredo continua morando na variável do tenant que já foi feita para guardá-lo:
| Referência | De onde o valor vem |
|---|---|
${var.erpToken} | uma variável do tenant, inclusive as marcadas como secretas |
${config.erp.baseUrl} | a configuração do tenant, por caminho pontuado |
A referência é resolvida no momento em que o runner pede os parâmetros, dentro do contexto do tenant. O que fica gravado no cadastro do robô é ${var.erpToken} — que é inofensivo num backup, na tela e no log de auditoria. Os parâmetros também não viajam mais na linha de comando do processo isolado: o sandbox os busca pelo mesmo canal autenticado por execução que já carrega os logs.
Uma referência que não resolve para a execução e diz qual referência era. Ela nunca é trocada por vazio: um token em branco faria o robô autenticar e reportar o que o outro lado dissesse sobre isso, em vez de falhar onde o problema está.
O que continua sem proteção
Uma vez resolvido, o valor está na memória do robô — e o robô é código que o próprio tenant enviou. Ele pode registrar o valor no log, mandá-lo para qualquer lugar ou gravá-lo em arquivo, e nenhum desenho impede isso a não ser não entregar o segredo ao robô.
O que este desenho garante é mais estreito: o segredo não está na linha de comando, não está no cadastro do robô, não sai em GET /a/robots e não é escrito pela plataforma no log da execução. Use mesmo assim credenciais de escopo mínimo e rotacionáveis, nunca a senha principal de um sistema — e, ao escrever um robô, nunca registre o valor de um parâmetro que possa ser um token.
O token da execução também saiu da linha de comando: o agente o entrega ao sandbox pela entrada padrão. Isso vale a partir do agente atualizado — um agente antigo ainda passa o token como argumento, e nesse caso o log da execução traz um aviso dizendo exatamente isso. Se você vir esse aviso, atualize o runner-agent daquela máquina.
O que o robô alcança também deixou de ser tudo. Cada robô declara suas capacidades — ler os próprios parâmetros, escrever o próprio log e ler e gravar o próprio estado valem sempre, e por isso não aparecem como caixa na tela; ler ou escrever no CMS, iniciar processo, consultar instâncias, listar membros e subir arquivo são marcadas uma a uma. O token da execução carrega só o que foi marcado, e a rota recusa o resto com 403, nomeando a capacidade que falta e dizendo onde marcá-la.
Robô que já existia recebeu todas as capacidades na atualização, para nenhuma automação parar de funcionar sem aviso. Robô novo nasce com o mínimo e você marca o que ele precisa — na tela de cadastro, ao criar, e depois na tela do robô, em Salvar acessos. Se uma execução falhar dizendo que falta capacidade, é aí que se resolve, e a mensagem diz qual falta.
Robô global não é editável pelo tenant: quem o mantém é a plataforma, e as capacidades dele vêm junto.
De todo modo, trate a máquina do runner como máquina de confiança: quem tem conta nela lê a memória do processo, e nenhum desenho impede isso.
Robôs globais e a configuração de cada tenant
Um robô da plataforma aparece para todos os tenants, mas o JAR é um só e a configuração não pode ser. Por isso a divisão: o JAR declara a forma, cada tenant fornece os valores.
Na página do robô global, um administrador de tenant preenche os campos declarados e salva. Esses valores valem só para o tenant dele.
O mesmo formulário serve o robô do próprio tenant — a diferença está em onde o valor é gravado: no robô, porque ele tem um dono só. Quem usa a tela não precisa saber a diferença.
Alterar o robô em si — nome, agendamento, classe principal, enviar novo JAR, apagar — é do administrador da plataforma. Para um administrador de tenant esses comandos não aparecem na página do robô global: no lugar deles fica um aviso dizendo de quem é a alteração e apontando para o formulário de assinatura logo abaixo. Antes eles apareciam e devolviam uma recusa; oferecer um botão que só sabe recusar é pior do que não oferecer.
O que continua disponível é Executar agora, e não por descuido: um robô global só roda pela assinatura do tenant, então executar é justamente o que o administrador do tenant pode fazer com ele. Para o administrador da plataforma, nada muda — ele vê todos os comandos.
Na lista e no topo da página, a etiqueta Global identifica o robô da plataforma, do mesmo jeito que os processos globais já são marcados.
Um robô global só executa dentro de um tenant
O robô global é o registro do JAR, não algo que roda por si. Não existe execução "global": toda execução acontece dentro de um tenant que assinou o robô, com os parâmetros daquele tenant.
Por isso, um tenant que nunca preencheu a configuração não executa o robô — nem pelo botão, nem no agendamento. A recusa diz qual tenant é e onde configurar. Um tenant que desligou a própria assinatura também não executa, mesmo que alguém aperte o botão.
Os valores padrão que aparecem no robô global são um modelo oferecido a quem for assinar, nunca o que roda. Se fossem usados na execução, todo assinante rodaria com a configuração da plataforma — e, no caso de um campo secreto, com a credencial dela.
Quando uma versão nova pede mais do que a anterior
Se o administrador da plataforma envia um JAR que declara um obrigatório novo, todas as assinaturas que não têm esse valor ficam incompletas. O envio funciona — a versão nova precisa poder ser publicada — mas o robô não executa naquele tenant até alguém preencher, nem no agendamento nem pelo botão.
Isso é proposital, e é a escolha entre duas coisas ruins: um robô parado com o motivo escrito, ou um robô rodando com metade da configuração e relatando sucesso. A segunda é pior porque ninguém procura.
Agendamento de robô global
O agendamento pertence ao robô, e cada tenant pode desligar o dele. No horário marcado a plataforma dispara uma execução por tenant assinante — dez assinantes, dez execuções, cada uma no contexto e com os parâmetros do seu tenant. A falha de um não interrompe os demais, e assinatura incompleta é pulada com o motivo no log.
Ativando e desativando
Use o toggle de ativo/inativo na listagem para controlar se o agendamento está em execução. Um robô desativado não é executado, mas permanece cadastrado.
Executando manualmente
Clique em Executar agora para disparar o robô fora do agendamento. Útil para testes. O botão fica desabilitado enquanto o robô não tem JAR ativo — sem ele não há o que executar.
Uma execução por vez, por robô e por empresa
Se já existe execução em andamento deste robô nesta empresa, o disparo manual é recusado com essa mensagem. Até 2026-09-02 ele começava assim mesmo, e isso fazia sentido enquanto uma execução não lembrava de nada. Com o Estado guardado entre execuções (abaixo), duas execuções simultâneas leem a mesma posição, processam o mesmo trecho e a última a gravar vence — registros somem sem erro nenhum. Espere a atual terminar ou cancele-a.
Estado guardado entre execuções
Um robô que percorre um feed incremental — a próxima página de uma API, o último id já importado — precisa lembrar onde parou. Esse é o Estado: um conjunto de pares chave/valor que o robô grava durante a execução e lê na execução seguinte.
O estado é por robô e por empresa. Num robô global, cada empresa assinante tem o seu: dez assinantes não dividem uma posição de paginação — cada um retoma de onde ele mesmo parou.
Ele é diferente dos parâmetros declarados, e a diferença importa: parâmetro é o que uma pessoa decidiu e digitou no formulário; estado é o que o robô descobriu sozinho. Por isso são duas coisas separadas na tela — um robô não escreve no formulário do administrador, e o administrador editando o formulário não sobrescreve o cursor do robô.
Cada gravação do robô vale na hora. Se ele grava a página depois de processar cada uma e cai na página 7 de 20, a próxima execução começa na 8 — e não da 0.
Na página do robô, o cartão Estado guardado entre execuções mostra o conteúdo atual em JSON. Você pode editá-lo e salvar (o dia em que o sistema de origem renumerar as páginas, é aqui que se corrige) ou usar Limpar estado para zerar. Sem isso, um valor errado só sairia com acesso ao banco.
Dois limites, ditos de propósito:
- 64 KB no estado inteiro, chaves e valores somados — não é teto por chave. Estado é onde o robô anota onde parou, não onde guarda o resultado. A gravação que faria o conjunto passar do teto é recusada, com o tamanho e o limite na mensagem, e o valor anterior continua valendo. Volume vai para o CMS ou para o armazenamento; aqui fica o ponteiro.
- Não é lugar de segredo. O estado não é mascarado e aparece nesta tela. Token e senha vão para uma variável do tenant, referenciada por um parâmetro secreto.
Apagar o robô apaga o estado dele junto.
A página do robô
Abrindo um robô você vê a classe principal, o agendamento, os parâmetros e a versão do JAR ativo, e é ali que o upload do JAR acontece — depois que o robô existe, porque o arquivo é versionado por robô.
Abaixo fica a tabela de execuções: situação, tipo de disparo, início, duração e o erro quando houve. Enquanto houver execução em aberto — PENDENTE ou EXECUTANDO — a tabela se atualiza sozinha a cada poucos segundos, e para de perguntar quando todas terminam. Não é preciso recarregar a página depois de clicar em Executar agora. Clicar numa execução abre o log dela, e o diálogo tem Baixar .log: um arquivo de texto com o log inteiro, na ordem em que aconteceu, com a origem de cada linha marcada. Vale para grep, para anexar num chamado e para ler fora da tela.
O arquivo completo é selado no armazenamento de objetos quando a execução termina — é o runner que o envia, e a plataforma confere que ele chegou antes de enxugar o banco, que passa a guardar só as últimas linhas para a tela. Uma execução que ainda não selou baixa direto do banco: o botão funciona nos dois casos. As linhas de log são buscadas só nesse momento; a lista não as carrega, para não trazer todas as linhas de todas as execuções a cada abertura da página.
Cancelando uma execução
Toda execução ainda em aberto — sem hora de término, seja PENDENTE ou EXECUTANDO — traz um botão Cancelar execução na própria linha da tabela. Execução já terminada não oferece o botão: não há o que cancelar numa linha fechada, e reabri-la apagaria o desfecho que o runner reportou.
A confirmação diz qual dos dois casos é o seu, porque eles não são a mesma coisa:
| Situação | O que acontece ao confirmar |
|---|---|
PENDENTE — nenhum runner coletou ainda | Resolve na hora. A execução sai da fila, fica como CANCELADO e não chega a rodar |
EXECUTANDO — um runner já a coletou | O pedido é registrado agora, a linha ganha a marca Cancelamento pedido e continua EXECUTANDO. O agente instalado na máquina descobre no próximo heartbeat, encerra o robô e reporta CANCELADO |
Como a tabela já se atualiza sozinha enquanto houver execução em aberto, a mudança de estado aparece na tela sem recarregar. Fica registrado quem cancelou e quando o pedido foi feito.
Cancelado não é falha. A execução termina com o status CANCELADO, sem mensagem de erro e sem aviso de falha para quem a disparou. Ler um cancelamento como falha manda alguém depurar um robô que fez exatamente o que mandaram.
O limite: a plataforma não alcança a máquina do cliente por dentro
Cancelar uma execução em curso é um pedido que o agente precisa vir buscar. Se aquele agente nunca mais chamar — máquina desligada, rede cortada, processo morto —, o robô pode continuar rodando numa máquina que a plataforma não administra.
O que a plataforma faz, e faz sempre: para de despachar aquela execução e para de contar com ela. O lease vence, a linha é fechada como CANCELADO com a explicação escrita nela, e o runner deixa de ser considerado ocupado por causa dela. O que ela não faz é encerrar um processo no servidor de outra pessoa — para isso não existe caminho, porque a plataforma nunca abre conexão para dentro da rede do cliente; é sempre o runner que liga para ela. Quem administra aquela máquina é quem encerra o processo.
Monitorando execuções
O histórico de execuções de cada robô exibe:
- Data e hora de início e fim
- Status:
SUCESSO,FALHAouCANCELADO - Log de saída ou mensagem de erro
Em caso de falha
Falhas de robôs são registradas no log mas não interrompem outros processos. Verifique o log para diagnosticar o problema e corrija o JAR antes de reativar.
Quando a máquina do runner some no meio
Duas coisas diferentes podem acontecer, e a plataforma trata cada uma de um jeito — porque o risco não é o mesmo.
A máquina parou de responder antes de buscar o trabalho. O robô não chegou a começar. A execução volta para a fila e a plataforma a entrega a outro runner elegível assim que houver um. Nada a fazer.
A máquina parou de responder com o robô já rodando. Aqui a plataforma não redespacha, e é deliberado: ela não alcança o processo por dentro e não tem como saber se ele ainda está de pé na máquina do cliente. Rodar o mesmo robô de novo faria as duas execuções lerem a mesma posição guardada em Estado, processarem o mesmo trecho, e a última a gravar venceria — registros somem sem erro nenhum. Então a execução é encerrada como FALHA, com o motivo escrito nela, e o robô fica liberado para a próxima. Se o trabalho precisa mesmo ser refeito, dispare o robô pela tela depois de conferir o que a execução interrompida chegou a gravar.
O log tem duas origens, e a tela separa as duas
[INFO],[WARN],[ERROR]— o que o robô escolheu registrar, chamandocontext.log(...).[processo]— o que a JVM imprimiu:System.out,System.err, rastreamento de exceção. Aparece recuado e em cinza, porque ler um rastreamento de pilha como se o robô o tivesse escrito de propósito leva a conclusão errada.
As duas sobem juntas, em lotes durante a execução e não só no fim, e por isso ficam na tela na ordem exata em que aconteceram: o context.log("vou baixar o arquivo") aparece imediatamente antes do rastreamento de pilha que explica por que o download falhou.
O nível de um context.log(...) é um entre TRACE, DEBUG, INFO, WARN e ERROR. Qualquer outra coisa é registrada como INFO.
Linhas seguidas de mesma origem e mesmo nível aparecem sob um único [INFO], e as quebras de linha são preservadas: um context.log(...) com um rastreamento de pilha dentro continua legível linha a linha, em vez de virar um parágrafo só.
Um robô que imprime demais tem o começo e o fim guardados, com uma linha no meio dizendo quantas foram descartadas. O começo explica a configuração; o fim explica a falha. O teto vale para as duas origens somadas — assim um robô que registra muito não empurra para fora o rastreamento de pilha que explica a própria falha.
O que o robô imprime vai para a tela como está
Nada mascara essa saída. Um robô que imprimir uma senha coloca a senha no log da execução e no banco. Registre o que aconteceu, nunca a credencial usada.
context.log(...) não depende da rede
O registro de log sai pela saída padrão do processo do robô, e quem o entrega à plataforma é o agente do runner, no mesmo lote da saída do processo. Duas consequências práticas:
- Um robô encerrado à força no timeout não leva o log junto. Quem tem as linhas nesse momento é o agente, não o processo que acabou de ser morto.
context.log(...)não falha por causa da rede, e não tenta reenviar. A garantia que ele dá é "o agente entregou o lote", não "a plataforma confirmou esta linha".
As outras chamadas do RobotContext falam com a plataforma e derrubam a execução quando não conseguem: gravar no CMS, abrir processo, anexar arquivo. Um robô que não conseguiu fazer o que foi mandado fazer não termina como Sucesso.
Runners — a máquina onde o robô roda
Um runner é a máquina que de fato executa um robô. A plataforma agenda, atribui e mostra o resultado; quem abre o JAR e roda o código é sempre um runner.
Todo robô roda num runner. A aplicação agenda e mostra o resultado; quem abre o JAR e executa o código é sempre uma máquina registrada como runner. É isso que mantém o código de um cliente fora do disco que guarda a configuração, as credenciais e os anexos dos outros.
A consequência prática é direta: sem nenhum runner cadastrado e respondendo, as execuções ficam PENDENTE e a tela diz por quê, em vez de rodarem em algum lugar que ninguém escolheu.
Numa instalação por Helm já existe runner. Desde 2026-08-29 o chart sobe runner.replicaCount runners (1 por padrão) que geram a própria chave e se inscrevem sozinhos como compartilhados — runner.replicaCount é também o botão de paralelismo, porque um runner compartilhado roda uma execução por vez. Fora do Helm, registrar ao menos um runner faz parte de subir a instalação.
Por que cadastrar um runner próprio
Para alcançar um sistema dentro da rede do cliente. Um robô existe, quase sempre, para falar com o ERP, o sistema fiscal ou uma API interna. Sem runner próprio, é a plataforma que precisa chegar até essa rede: regra de firewall de entrada, túnel ou ERP exposto. Um runner instalado dentro da rede do cliente só faz conexões de saída, e a pergunta deixa de existir em vez de ser respondida.
Para não misturar o código de um cliente com o de outro. No runner compartilhado, o JAR de um tenant roda na mesma máquina que o de outro — é exatamente a exposição descrita no aviso O que a sandbox ainda não isola: o disco, mais abaixo. Um runner próprio tira o código daquele cliente de lá. Se cada tenant da sua instalação é um cliente diferente, esse é o motivo mais forte da lista.
Um robô pode recusar o runner compartilhado
Na página do robô, o campo Runner diz onde aquele robô aceita rodar:
| Valor | O que significa |
|---|---|
| Qualquer runner disponível | o padrão — aceita inclusive o runner compartilhado da instalação |
| Dedicado — recusa o runner compartilhado | só roda em runner do próprio tenant |
Marcar um robô como dedicado é uma decisão de segurança, não de desempenho: é o que impede o JAR daquele cliente de rodar na mesma máquina que o de outro. O preço é que, sem runner do tenant disponível, a execução fica pendente em vez de cair no compartilhado — e a tela de execuções diz exatamente isso, em vez de silenciosamente rodar em outro lugar.
Cadastrando um runner
Acesse Automação → Runners na barra lateral — é uma tela irmã da lista de robôs, e não da área da plataforma: quem cuida dos robôs de um tenant cuida também dos runners dele.
Na máquina, gere o par de chaves:
java -jar flowia-runner-agent.jar --initO comando imprime a chave pública e o fingerprint dela. A chave privada nunca sai daquele disco.
Clique em Novo Runner, dê um nome (ex.:
erp-fiscal-01), cole a chave pública e escolha o escopo.Na máquina, suba o agente:
java -jar flowia-runner-agent.jar --url https://<sua-instalacao> --name erp-fiscal-01
A tela tem botão de copiar em cada um dos dois comandos.
O agente confere, ao subir, se alcança a plataforma. Antes de reivindicar qualquer execução ele tenta o endereço de RPC que a plataforma informou. Se não alcançar, ele para ali e diz qual endereço tentou. É quase sempre configuração da plataforma: app.api.base-url precisa ser o endereço da plataforma visto de fora dela — localhost ali é a própria plataforma, nunca a máquina do runner.
A chave nasce na máquina, e por isso nada secreto viaja
A plataforma guarda apenas a chave pública — que é pública por definição. Esta tela não exibe, não gera e não entrega token nenhum: se você estiver procurando onde copiar um segredo, é porque o sentido é o contrário do esperado. Uma chave pública colada no lugar errado não vaza nada.
Escopo — o que aquela máquina aceita rodar
| Escopo | Aceita |
|---|---|
| Todos os robôs deste tenant | qualquer robô do tenant dono do runner |
| Apenas os robôs selecionados | só os robôs escolhidos na hora do cadastro |
Todos os robôs deste tenant é o caso comum: uma máquina dentro da rede do cliente que pode rodar o que for daquele cliente. Apenas os robôs selecionados é para a máquina que é especial — a que alcança o sistema fiscal, ou a que tem um dongle de licença espetado — onde "roda tudo" é errado e a lista é curta e deliberada. Ao escolher esse escopo, a tela pede quais robôs.
O log do próprio runner
Cada linha da lista tem Log: o que o agente daquela máquina relatou sobre si mesmo — aperto de mão recusado, digest de JAR que não bateu, execução recebida, código de saída do filho, reconexão depois de uma queda. É o que responde "por que meu runner não está pegando trabalho" sem precisar de acesso à máquina.
Ele guarda evento, não rotina: um agente parado, perguntando por trabalho a cada 25 segundos, não escreve nada. E o que aconteceu enquanto a plataforma estava fora do ar não chega — o agente segura as últimas linhas e manda quando volta, mas um silêncio longo aparece como silêncio.
O log do runner compartilhado é da plataforma
Ele nomeia execuções e classes principais de todos os tenants que atendeu, então só o administrador da plataforma lê. O log das execuções do seu tenant continua na tela do robô.
A lista e o estado de cada runner
| Coluna | O que mostra |
|---|---|
| Nome | o nome dado no cadastro, e a versão do Java da máquina |
| Escopo | o que aquele runner aceita, e quais robôs quando a lista é explícita |
| Fingerprint | a impressão digital da chave pública, para conferir contra o que o --init imprimiu |
| Visto por último | quando o agente fez o último poll |
| Situação | online, offline, aguardando a primeira conexão ou revogado |
"Aguardando a primeira conexão" é normal, não é falha
O cadastro vem antes da máquina: você registra a chave pública e só depois sobe o agente — às vezes dias depois, às vezes em uma máquina que ainda nem foi provisionada. Enquanto isso o runner aparece como Aguardando a primeira conexão, e some desse estado sozinho assim que o agente fizer o primeiro poll. Não há nada a corrigir.
Trocando a chave e revogando
Máquina reinstalada — rode o --init de novo na máquina nova e cole a chave nova em Editar runner. Preencher a chave revoga a antiga por construção; não é preciso apagar e recadastrar.
Revogar — o botão Revogar pede confirmação e diz o que acontece: o runner falha no próximo poll e para de receber trabalho, e uma execução em curso não é interrompida. Revogar não mata nada no meio do caminho; ele só deixa de ser escolhido daí em diante.
Desenvolvimento do JAR (Native API)
O JAR carrega num URLClassLoader próprio, dentro do processo filho, e conversa com a plataforma apenas por RPC autenticado com um token efêmero daquela execução. Se o robô travar, consumir toda a memória ou chamar System.exit, quem cai é o processo dele; a plataforma registra a falha e segue.
O que a sandbox limita:
| Limite | O que significa para o código do tenant |
|---|---|
| Variáveis de ambiente | O processo recebe só o mínimo para a JVM subir. Credencial de banco e chave de provedor de IA não são visíveis |
| Memória | Sobe com -Xmx (padrão 512 MB, configurável); um robô desgovernado morre sozinho |
| Tempo | Ao estourar o limite o processo é encerrado à força, não apenas marcado como expirado |
| Disco de trabalho | Diretório de trabalho e de temporários só daquela execução, apagado ao terminar, inclusive em falha. Um arquivo em /tmp não sobrevive de uma execução para a seguinte |
| Alcance | Só o que o RobotContext expõe, sempre dentro do tenant dono do robô |
O que a sandbox ainda não isola: o disco
O processo do robô roda com o mesmo usuário de sistema operacional da plataforma, e portanto com as mesmas permissões de arquivo. Ambiente, memória e tempo de execução são limitados; o sistema de arquivos não é.
Se os tenants da instalação representam partes diferentes — os clientes da sua empresa, por exemplo, um tenant por cliente —, isso significa que o código enviado por um tenant pode ler arquivos que a plataforma alcança, incluindo os de outro tenant. Não é uma limitação teórica e não há configuração que a desligue.
Vale reforçar porque a leitura natural é a oposta: o fato de a instalação ser de uma empresa só não diminui isso. Quando cada tenant é um cliente seu, a fronteira entre eles é um compromisso de confidencialidade entre partes distintas — e aí ela é mais estrita, não menos.
O isolamento de verdade exige que a execução do robô rode no próprio contêiner — as outras três saídas não funcionam neste modelo de implantação (dropar privilégio exige rodar como root, sandbox de sistema operacional exige capacidades que o runtime gerenciado não concede, e o SecurityManager foi removido no Java 21). Esse trabalho ainda não foi implementado.
Até lá: trate o envio de JAR como uma permissão de confiança. Só não há exposição entre tenants quando os tenants são divisões da mesma organização. Se cada tenant é um cliente diferente, conceda ADMIN de tenant apenas a quem você confiaria com acesso de leitura ao servidor — e, na prática, apenas à sua própria equipe.
Acessando Dados, CMS e Variáveis de Processo (JSONB)
Os robôs podem interagir de forma rica com os dados de instâncias de processos (utilizando variáveis estruturadas em JSON/JSONB), e também com o banco de dados sem esquema do Flow.IA (CMS).
Para criar o seu robô, implemente a interface FlowIaRobot:
import ia.flow.engine.FlowIaRobot;
import ia.flow.engine.RobotContext;
import java.util.Map;
public class MeuRobo implements FlowIaRobot {
@Override
public void execute(RobotContext context) {
context.log("Iniciando varredura no CMS...");
// 1. Acesso aos parâmetros estáticos configurados na tela do robô
Map<String, String> params = context.getParameters();
// 2. Acesso Native API: Consumir ou escrever no CMS do Tenant
context.getCms().getRecordsByCollectionSlug("faturas")
.forEach(record -> {
context.log("Processando fatura: " + record);
});
// 3. Acesso Native API: Iniciar novos processos
// É possível passar variáveis complexas (JSONB) diretamente no mapa de inicialização.
Map<String, Object> processVariables = Map.of(
"origem", "robo_agendado",
"detalhesProcessamento", Map.of("urgente", true, "categoria", "A") // Variável que será convertida em JSONB
);
context.getProcesses().startProcess("faturamento-workflow", processVariables);
// 4. Acesso Native API: Consultar membros do tenant e dados gerais
var members = context.getTenant().getMemberEmails();
context.log("Membros notificados: " + members.size());
// 5. Acesso Native API: Fazer upload de arquivos
// Ideal para casos em que o robô baixa arquivos externos e inicia processos com anexos
byte[] pdfContent = new byte[]{ /* ... bytes do arquivo baixado ... */ };
java.util.UUID attachmentId = context.getStorage().uploadAttachment(
"instance", "uuid-da-instancia-criada", "fatura.pdf", "application/pdf", pdfContent
);
context.log("Fatura anexada com sucesso: " + attachmentId);
}
}O RobotContext é sua fachada isolada para o Flow.IA. Ele garante que qualquer operação (como getCms(), getProcesses() ou getStorage()) esteja restrita apenas aos dados pertencentes ao Tenant do robô ativo, prevenindo que um robô vaze dados de empresas vizinhas na plataforma. Você tem autonomia para consultar processos públicos do tenant, membros, etc., com segurança e agilidade.
De onde vem a anotação
@RobotParameter não é publicada em repositório Maven: declare a sua própria cópia, com o mesmo pacote e o mesmo nome. O código da anotação e o porquê estão em Java Delegates → De onde vem a anotação — a única diferença é que @RobotParameter tem secret e @DelegateParameter não.