8 min de leitura

Tarefas SSH continuam após uma chamada bem-sucedida

Teste tarefas SSH em segundo plano, nohup, controle de jobs e processos separados para não encerrar a auditoria cedo demais.

Tarefas SSH continuam após uma chamada bem-sucedida

Um status de saída zero em uma chamada SSH prova que o comando remoto informou sucesso. Ele não prova que todos os processos causados pelo comando pararam, terminaram suas gravações ou chegaram a um resultado correto. Se o comando iniciou trabalho em segundo plano, a ação que realmente importa pode estar apenas começando quando a ferramenta registra o fim.

Trato essa diferença como um limite de auditoria, não como uma curiosidade do shell. Um agente pode executar um script de implantação, receber status 0 e seguir em frente enquanto uma migração separada continua alterando dados. O registro da ferramenta diz a verdade sobre o canal SSH, mas induz a uma conclusão errada sobre o trabalho remoto. A solução é nomear o ciclo de vida que se pretende observar, testá-lo nas mesmas condições de shell e terminal usadas pelo agente e coletar prova de conclusão do sistema responsável pelo processo duradouro.

O zero descreve o comando remoto, não seus descendentes

O OpenSSH retorna o status fornecido pelo comando remoto, ou 255 quando o próprio cliente SSH encontra um erro. A RFC 4254 é ainda mais precisa: quando o comando do outro lado termina, o servidor pode enviar uma solicitação de canal exit-status e depois fechar o canal. Nenhum dos documentos afirma que o servidor espera recursivamente todos os descendentes do comando.

Essa distinção importa sempre que um shell executa uma lista assíncrona. POSIX define um comando terminado por & como assíncrono: o shell o inicia e continua sem esperar. Se não houver mais nada a fazer, o shell pode sair com sucesso enquanto o filho assíncrono permanece vivo. O status SSH pertence a esse shell.

Há pelo menos quatro resultados que as pessoas chamam de sucesso:

  • A conexão SSH e a autenticação funcionaram.
  • O shell remoto aceitou e iniciou um comando.
  • A carga de trabalho iniciada terminou com status 0.
  • O efeito pretendido tornou-se durável e observável.

Um único inteiro não estabelece os quatro. Um registro de auditoria claro precisa dizer qual evento o produziu. Uso ssh_command_exit_status para o resultado do canal e reservo workload_result para a evidência informada pelo responsável remoto pelo trabalho.

O mesmo alerta vale quando o executável remoto se transforma em daemon. Um iniciador pode retornar 0 depois de um fork bem-sucedido, mesmo que o filho falhe segundos depois ao abrir um banco de dados, vincular uma porta ou ler uma configuração. O iniciador cumpriu seu contrato; o auditor escolheu o contrato errado.

Uma sonda de doze segundos expõe a diferença

É possível reproduzir esse sucesso enganoso sem daemon, acesso root ou configuração incomum do shell. Execute o teste com uma conta Unix descartável. Os redirecionamentos explícitos importam porque permitem que o processo em segundo plano libere o canal SSH enquanto continua em execução.

ssh testhost 'rm -f /tmp/ssh-bg.done /tmp/ssh-bg.log; (sleep 12; date -u +%FT%TZ > /tmp/ssh-bg.done) > /tmp/ssh-bg.log 2>&1 < /dev/null & printf "launcher_pid=%s\n" "$!"'
printf 'ssh_status=%s\n' "$?"
ssh testhost 'test -f /tmp/ssh-bg.done; printf "done_status=%s\n" "$?"'
sleep 13
ssh testhost 'cat /tmp/ssh-bg.done'

Um resultado imediato típico tem este formato:

launcher_pid=41872
ssh_status=0
done_status=1
2026-07-24T10:14:05Z

O PID e o horário serão diferentes. A contradição é o ponto: ssh_status=0 e done_status=1 coexistem porque respondem a perguntas diferentes. O shell iniciou a lista assíncrona com sucesso, mas o arquivo marcador ainda não existia.

Não transforme esse exemplo em orquestração de produção. Arquivos marcadores em /tmp podem colidir, desaparecer ou ser falsificados por outro processo com acesso suficiente. A sonda é útil porque torna o tempo visível. Um registro de conclusão em produção precisa de identificador único, armazenamento protegido, autor autenticado e estado de falha definido.

Repita a sonda pela rota exata usada pelo agente. Um comando direto em terminal, uma solicitação exec SSH não interativa, uma chamada com pseudoterminal e um gateway de ferramentas podem escolher arquivos de inicialização, shells e arranjos de descritores diferentes. Um teste que ignora esses detalhes testou um sistema vizinho.

Descritores abertos podem simular uma espera síncrona

Executar em segundo plano e fechar o canal são mecanismos distintos. Um filho que herda a saída ou o erro padrão do canal SSH pode mantê-lo legível depois que o shell remoto sai. O processo local ssh pode parecer esperar pelo filho porque o pipe ainda não chegou ao fim do arquivo, e não porque o SSH supervisiona o resultado do filho.

Compare estas duas chamadas e meça o tempo decorrido:

time ssh testhost 'sleep 12 &'
time ssh testhost 'sleep 12 > /tmp/sleep.log 2>&1 < /dev/null &'

Em combinações comuns de OpenSSH e shell, a primeira chamada pode permanecer aberta até sleep terminar, enquanto a segunda retorna rapidamente. Trate isso como observação a verificar, não como promessa portável. A implementação do shell, o servidor, a alocação de pseudoterminal e o tratamento de descritores pelo filho podem mudar o resultado.

Essa espera acidental é uma evidência fraca. O processo em segundo plano pode fechar os descritores cedo e continuar trabalhando. Pode criar um neto que os feche. Pode enviar saída por socket ou gravar direto no armazenamento. No sentido oposto, um auxiliar que apenas mantém stdout aberto pode fazer a chamada parecer ativa depois que o trabalho importante falhou.

Ainda vale inspecionar os descritores, pois eles explicam muitos testes inconsistentes. No Linux, capture o PID remoto e examine-os enquanto a chamada SSH estiver ativa:

pid=$(cat /run/user/$(id -u)/agent-job.pid)
ps -o pid=,ppid=,pgid=,sid=,stat=,etime=,args= -p "$pid"
ls -l "/proc/$pid/fd/0" "/proc/$pid/fd/1" "/proc/$pid/fd/2"

Registre PID pai, grupo de processos, ID de sessão, estado, tempo decorrido, comando e destinos dos descritores 0, 1 e 2. Se /proc não estiver disponível, use as ferramentas nativas do sistema. Não reduza o teste a pgrep name: nomes colidem, wrappers mudam nomes e um PID pode ser reutilizado depois da saída.

nohup resolve o desligamento, não a responsabilidade

nohup muda o tratamento de sinais para que o comando chamado ignore SIGHUP. Ele não coloca o comando em segundo plano. O manual do GNU Coreutils afirma isso diretamente e manda adicionar & para execução assíncrona. Essa condição costuma desaparecer de trechos copiados.

As regras de redirecionamento também surpreendem por SSH. O GNU nohup redireciona a entrada apenas quando ela é um terminal, envia a saída para nohup.out apenas quando stdout é um terminal e normalmente aplica a mesma escolha ao erro. Um comando SSH não interativo costuma usar pipes, então nohup pode deixar esses descritores ligados ao canal SSH.

Portanto, estes comandos fazem promessas diferentes:

ssh testhost 'nohup /opt/jobs/rebuild-index &'
ssh testhost 'nohup /opt/jobs/rebuild-index > /var/log/rebuild-index.log 2>&1 < /dev/null &'

A segunda forma desconecta explicitamente os descritores padrão. Ainda assim, não diz se rebuild-index terminou. nohup informa falhas de invocação, como comando ausente, e nos demais casos seu status acompanha o comando chamado. Depois que o shell coloca essa chamada em segundo plano, ele normalmente informa o início, não o resultado final.

Imunidade a SIGHUP é apenas parte da sobrevivência. O processo pode morrer porque um gerenciador de login remove a sessão, um gerenciador de serviços mata o grupo de controle, o kernel aplica uma política de falta de memória, um administrador revoga a conta ou o host reinicia. Também pode sobreviver e produzir o resultado errado. nohup não oferece identidade, repetição, limite de recursos, estado durável ou registro confiável de conclusão.

Ainda uso nohup para manutenção pequena e descartável quando perder o resultado é aceitável e estou observando o host. Não o uso para transformar a chamada SSH de um agente em tarefa de produção gerenciada. A recomendação é popular porque o trecho é curto e costuma sobreviver à perda do terminal. É errada quando alguém precisa provar depois o que terminou.

O controle de jobs muda quando entra um terminal

Passe SSH pelo gateway
O shim sp mcp envia trabalho SSH pelo Sallyport e seu auxiliar sem estado.

O controle de jobs do shell agrupa processos para que um usuário interativo possa suspender, retomar e mover pipelines entre primeiro e segundo plano. Um shell não interativo geralmente roda sem modo monitor, e uma solicitação exec SSH normalmente não tem pseudoterminal, salvo pedido do cliente. Scripts que dependem de jobs, %1, disown ou sinais do terminal podem se comportar de outro modo quando um agente os executa.

POSIX vincula identificadores de job e PIDs de segundo plano conhecidos ao ambiente do shell atual. O utilitário wait pode esperar esses processos conhecidos, mas um wait iniciado em outro shell não herda a tabela. Isto falha como técnica de auditoria:

ssh testhost 'long_task & printf "%s\n" "$!"'
ssh testhost 'wait 41872; printf "wait_status=%s\n" "$?"'

A segunda chamada inicia um shell novo. Mesmo que 41872 esteja vivo, esse shell não o conhece como filho. POSIX especifica 127 para um PID desconhecido passado a wait. Permissões e reutilização de PID tornam qualquer reconstrução ainda menos confiável.

Mantenha início e espera no mesmo shell quando o contrato exigir conclusão síncrona:

ssh testhost 'long_task > /tmp/long-task.log 2>&1 < /dev/null & pid=$!; printf "pid=%s\n" "$pid"; wait "$pid"; rc=$?; printf "workload_status=%s\n" "$rc"; exit "$rc"'

Esse padrão retorna o status do filho e mantém a ação SSH aberta. Funciona para um filho que continua ligado ao shell. Se long_task fizer fork e o processo original sair, wait pode terminar antes do trabalhador real. Teste o executável real, não um sleep substituto, antes de aceitar o contrato.

Pseudoterminais acrescentam sinais e mudam buffers. Um terminal pode enviar SIGHUP quando a sessão termina, e grupos em segundo plano que leem do terminal de controle podem receber SIGTTIN e parar. Alguns programas mudam para buffer por linha ou emitem outra saída quando detectam terminal. A menos que o comando realmente precise dessa semântica, a automação deve evitar alocá-lo e configurar deliberadamente os três descritores padrão.

setsid separa o processo, mas não produz prova

setsid cria nova sessão e novo grupo de processos, inicialmente sem terminal de controle. É uma separação mais forte do que apenas ignorar SIGHUP. Isso explica por que um filho pode sobreviver ao shell e por que sinais do terminal deixam de segui-lo.

Isso não coloca o processo sob supervisão. Quando o pai original sai, outro processo pode adotar o descendente. Em um host convencional pode ser PID 1; em um contêiner ou árvore de serviços, um subreaper. A nova relação parental não diz nada sobre o sucesso e pode apagar o vínculo mais simples com a ação que iniciou tudo.

Um teste útil captura a identidade antes do shell desaparecer:

ssh testhost 'run_id=agent-probe-20260724-1014; setsid sh -c '\''printf "%s\n" "$$" > /tmp/'"$run_id"'.pid; sleep 12; printf "complete\n" > /tmp/'"$run_id"'.state'\'' > /tmp/'"$run_id"'.log 2>&1 < /dev/null & printf "run_id=%s\n" "$run_id"'

Depois consulte pelo ID retornado, trate o PID gravado apenas como pista e verifique horário de início e comando antes de agir. Um PID isolado não é identidade durável. Se o processo sair e o kernel reutilizar o número, uma limpeza posterior pode atingir trabalho não relacionado.

Fork duplo, setsid, disown e fechamento de descritores são técnicas de implementação. Equipes frequentemente os confundem com protocolo de jobs porque fazem o terminal retornar. Um protocolo responde outras perguntas: quem é responsável agora, como consultar, quais estados terminais existem, onde fica a causa da saída, como cancelar toda a árvore e qual ID liga solicitação, logs, efeitos e auditoria?

Se a resposta de início não responde a essas perguntas, registre um início separado, não uma ação concluída.

Defina três eventos de ciclo de vida no contrato

Revogue a execução do agente
Sessions journal bloqueia novas ações do gateway após uma submissão suspeita.

Uma ação remota auditável precisa de eventos separados para aceitação, conclusão do canal e conclusão da carga. Colapsá-los em um booleano success cria certeza falsa e faz a investigação depender do histórico do shell.

Uso um registro com esta forma conceitual:

{
  "action_id": "act_01J3M8Q4",
  "remote_host": "worker-07",
  "launch": {"state": "accepted", "at": "2026-07-24T10:14:00Z"},
  "ssh_command": {"state": "exited", "status": 0, "at": "2026-07-24T10:14:01Z"},
  "workload": {"id": "job_8931", "state": "running", "result": null},
  "completion_source": "remote-job-manager"
}

Os nomes importam menos que a separação. accepted significa que o responsável remoto validou e assumiu a solicitação. exited indica que o comando SSH terminou. running indica que o trabalho duradouro ainda não chegou ao estado terminal. Somente o componente responsável deve escrever succeeded, failed ou cancelled.

Torne explícitas as regras de transição. Um início pode falhar antes de criar o ID. O canal pode quebrar depois da aceitação remota, deixando o chamador em estado incerto, não falho. A carga pode falhar após uma saída limpa do canal. O cancelamento pode ter sido solicitado, mas estar pendente. Um modelo que não representa unknown acabará registrando um palpite como fato.

Idempotência pertence ao contrato. Se o cliente perde o canal após enviar, deve tentar novamente com o mesmo ID de ação e perguntar se o responsável já aceitou. Iniciar uma segunda migração porque a primeira resposta sumiu é pior que um log bagunçado.

A evidência de conclusão deve incluir ID, estado terminal, causa de saída, horários de início e fim e identidade do gerenciador que observou o estado. Acrescente prova específica do efeito quando o risco justificar, como revisão implantada, manifesto de backup completo ou versão de schema. Não use uma linha com done como única autoridade, a menos que registrador e armazenamento façam parte do protocolo confiável.

Teste as janelas de falha, não só o caminho feliz

Uma matriz útil varia como o processo se separa, o destino dos descritores e o momento em que conexão ou processo falha. Execute em cada classe de host, pois gerenciadores de login, shells e serviços mudam o comportamento.

Cubra pelo menos estes casos:

  • Comando em primeiro plano, job de shell em segundo plano, nohup com segundo plano, nova sessão por setsid e programa que se transforma em daemon.
  • Sem terminal e com pseudoterminal.
  • Descritores herdados, redirecionados a arquivos e fechados pelo filho.
  • Desconexão antes da aceitação, depois da aceitação e antes da resposta, e depois da saída SSH.
  • Filho que sai com erro, recebe sinal, trava, cria neto e sobrevive até o cancelamento explícito.

Para cada caso, capture quatro relógios: início do cliente, confirmação do lançamento, fechamento do canal e estado terminal remoto. Guarde separadamente status SSH e resultado da carga. Inspecione grupo e sessão durante a execução e prove se o cancelamento alcança todos os descendentes.

Um harness compacto pode falhar quando status 0 chega sem evidência terminal:

result=$(ssh testhost '/usr/local/bin/job-submit agent-probe-42')
ssh_rc=$?
printf 'ssh_rc=%s response=%s\n' "$ssh_rc" "$result"
job_id=$(printf '%s\n' "$result" | sed -n 's/^job_id=//p')
test "$ssh_rc" -eq 0 && test -n "$job_id" || exit 1
/usr/local/bin/poll-job "$job_id" || exit 1

O exemplo supõe que job-submit retorna exatamente uma linha job_id= e que poll-job autentica a consulta, espera um estado terminal e sai com o resultado real. Esses são requisitos do contrato, não propriedades do SSH. Em produção, rejeite saída extra, imponha prazo, preserve o estado incerto no timeout e armazene a resposta bruta.

Teste também o observador. Pare o agente depois do início. Reinicie o cliente. Troque a credencial SSH. Reinicie o host remoto se a tarefa deve sobreviver. Se a única cópia do ID vive na janela de contexto do agente, o sistema não é auditável.

Um gerenciador de serviços costuma ser o responsável certo

Aprove cada chave sensível
Exija clique ou Touch ID para cada chamada com uma chave SSH marcada.

Quando o trabalho precisa sobreviver ao comando SSH, entregue-o a um serviço ou gerenciador remoto e retorne seu identificador durável. O gerenciador deve possuir o grupo, coletar saída, aplicar recursos e cancelamento, manter estado e expor uma consulta que diferencie execução de estado terminal.

Em um host systemd, um serviço transitório ou de modelo oferece grupo de controle e identidade no journal. O manual de systemd-run distingue início assíncrono de espera pelo término e alerta que um serviço simples pode considerar o início bem-sucedido depois do fork, antes de executar o programa. Prefiro Type=exec quando a falha de execução precisa aparecer, mas isso ainda prova início, não sucesso final.

Uma unidade de modelo pode estabelecer o limite assim:

[Unit]
Description=Agent job %i

[Service]
Type=exec
ExecStart=/usr/local/libexec/agent-job %i
StandardOutput=journal
StandardError=journal
KillMode=control-group
TimeoutStopSec=30s

Envie um ID único e validado e consulte a unidade até um estado terminal. Registre ActiveState, SubState, Result, ExecMainStatus, horários e ID. Confirme o comportamento quando o programa faz fork, pois tipo de serviço e programa precisam concordar. Não transforme entrada arbitrária em nome de unidade ou argumento sem validação rigorosa.

Uma fila, agendador em lote, orquestrador de contêineres ou tabela da aplicação pode oferecer o mesmo limite. Escolha o responsável que já gerencia recursos e recuperação. SSH deve enviar e consultar, não imitar um agendador por uma cadeia elaborada de operadores de shell.

Para trabalho curto, manter o comando em primeiro plano e retornar o status real é mais simples. A separação custa outro armazenamento, outra identidade, semântica de cancelamento, retenção e reconciliação. Pague esse custo apenas se o trabalho realmente precisa sobreviver à chamada.

A auditoria termina no estado terminal remoto

Um gateway de ações pode registrar a chamada SSH com precisão sem saber que existe um descendente remoto. Sallyport registra a ação SSH no Activity journal e a execução do agente no Sessions journal, então a entrada prova o resultado do canal, não faz um censo de processos do host. O ID remoto e o evento terminal precisam voltar por uma ação explícita e auditável.

Essa divisão mantém os registros honestos. O gateway prova qual execução invocou SSH, qual chave protegida autorizou, qual chamada ocorreu e qual resultado voltou. O gerenciador remoto prova o que aconteceu depois. Una ambos com um ID de ação que o agente não possa trocar entre início e consulta.

Não marque um início separado como completed. Use submitted ou detached, mostre o ID e mantenha a ação pai aberta ou claramente pendente até um observador confiável registrar o estado terminal. Se a observação expirar, mostre unknown e exija reconciliação. Um status vermelho pode incomodar, mas um verde baseado no processo errado é perigoso.

O momento da aprovação exige a mesma precisão. Aprovar o uso de uma chave SSH autoriza uma tentativa sob as informações mostradas naquele instante. O clique não aprova toda ação futura de descendentes sem limite nem certifica o efeito final. Se uma tarefa pode durar horas ou criar processos, mostre isso antes e vincule a aprovação ao ID, host, intenção e tipo remoto. Aprovação e conclusão pertencem à mesma cadeia, mas descrevem decisões diferentes.

Preserve a saída bruta em cada limite. Salve a resposta antes de analisá-la, capture stderr separadamente quando o protocolo permitir e registre se um pseudoterminal misturou os fluxos. O analisador deve rejeitar IDs duplicados, caracteres de controle, respostas truncadas e linhas extras que confundam respostas. Campos analisados servem à automação; bytes brutos ajudam a rever falhas do analisador, das aspas do shell ou do programa. Nenhuma forma deve conter segredos.

Um marcador de conclusão também precisa de publicação atômica. O trabalhador deve escrever em arquivo temporário protegido, descarregar os dados quando a durabilidade importar e renomear somente quando o registro estiver completo. A consulta deve validar ID, proprietário esperado, tipo e estado. Melhor ainda, um gerenciador ou banco deve expor o estado por interface autenticada. Um marcador gravável por todos em /tmp mostra tempo, mas não decide um incidente.

Planeje entrega incerta no envio e no cancelamento. Se a conexão desaparece após a aceitação e antes do recebimento do ID, o estado local correto é unknown. Reconecte com a chave idempotente original e procure a submissão. Não repita em silêncio. Se a resposta de cancelamento se perder, consulte até o gerenciador informar estado terminal e grupo vazio. Enviar um sinal é tentativa, não prova de parada.

A reconciliação precisa sobreviver ao agente. Armazene IDs não resolvidos fora da conversa, atribua um responsável e consulte periodicamente para fechar ou escalar registros antigos. Defina prazo e falha separadamente: uma tarefa pode exceder a espera e continuar saudável e gerenciada. O registro deve mostrar quando o cliente parou de esperar, quem observa e se a conclusão chegou. Caso contrário, timeout vira outro estado terminal falso.

Revisores precisam do mesmo vocabulário do runtime. Procure ações com ssh_command.status zero e workload.state ausente, atrasado ou desconhecido. Procure jobs terminais sem aprovação de início e submissões repetidas com a mesma chave idempotente. Essas consultas transformam a distinção em um controle que encontra lacunas.

Dê ao operador uma ação para reabrir o registro remoto. A tela deve mostrar última observação, componente que a forneceu e se veio de consulta ativa ou cache. Nunca mude unknown para failed só para limpar uma fila. Preserve a incerteza até o responsável responder ou um revisor autorizado resolver com evidência documentada. Registros abertos após uma falha são desconfortáveis e corretos.

Teste também a retenção. Os registros precisam ficar consultáveis por mais tempo que a maior tarefa esperada e durante auditorias ou incidentes. Se o gerenciador descarta logo uma unidade transitória, copie o resultado terminal para o registro durável antes da coleta. Guardar um ID que nenhum sistema consegue resolver na semana seguinte cria correlação sem responsabilidade.

Execute a sonda de doze segundos pela rota real do agente e repita com a carga importante. Se o cartão SSH ficar verde antes de existir o marcador remoto, você encontrou uma lacuna de auditoria. Mantenha o zero, pois ele é evidência válida sobre o comando. Pare de pedir que ele testemunhe sobre trabalho que nunca observou.

FAQ

O SSH espera processos em segundo plano terminarem?

SSH espera o comando remoto e o fechamento dos dados do canal, não uma árvore abstrata de descendentes. Um processo que mantém um descritor aberto pode atrasar o cliente, mas isso é incidental e não equivale a supervisão.

Por que ssh retorna 0 enquanto a tarefa remota ainda roda?

O shell remoto pode iniciar corretamente um comando assíncrono e sair com status 0. SSH informa esse status, enquanto o job em segundo plano terá seu próprio resultado depois.

nohup coloca um comando SSH em segundo plano?

Não. nohup muda o tratamento de SIGHUP; o operador & do shell executa em segundo plano. Também é preciso decidir os destinos de entrada, saída e erro.

Por que nohup às vezes faz o SSH parecer travado?

Em uma sessão não interativa, stdout e stderr podem ser pipes, então nohup pode mantê-los iguais. Um descendente que os deixa abertos atrasa o fechamento do canal.

Posso esperar um PID remoto em outra chamada SSH?

Um shell novo não pode usar wait para um processo que não é seu filho nem está em sua tabela. Consulte um gerenciador por ID durável, sem reconstruir parentesco.

setsid basta para um trabalho separado confiável?

setsid cria uma nova sessão e remove a relação com o terminal de controle. Não acrescenta identidade durável, estado, repetição, cancelamento completo ou prova de sucesso.

Um comando SSH automatizado deve alocar pseudoterminal?

Normalmente não, salvo se precisar do comportamento de terminal. O pseudoterminal muda sinais, buffers, controle de jobs e entrada, podendo esconder diferenças com o agente.

O que registrar ao auditar um job SSH em segundo plano?

Registre separadamente aceitação, saída do canal e conclusão da carga. Guarde ID remoto, estado terminal, motivo, horários, fonte de conclusão e o ID de ação comum.

Como cancelar com segurança um processo remoto separado?

Peça ao gerenciador responsável para cancelar e verifique um estado cancelled ou failed. Matar um PID lembrado é inseguro porque descendentes podem escapar e o PID pode ser reutilizado.

Quando um comando SSH em primeiro plano é melhor?

Use-o quando o trabalho for limitado, a conexão puder ficar aberta e o comando retornar o resultado real. Use gerenciador quando precisar sobreviver, ser consultado depois ou cancelado em grupo.

Sallyport

O Sallyport executa chamadas de API e comandos SSH pelo seu agente de IA. As chaves ficam em um cofre local no seu Mac; você aprova cada execução e toda ação vai para um registro selado.

© 2026 Sallyport · Código aberto sob Apache-2.0 · Oleg Sotnikov