Skip to main content
O Park & Resume transforma o agent loop em algo que pode esperar sem travar o terminal. Em vez do antigo padrão bash sleep 300 que bloqueia 5 minutos a tela inteira, o agent emite um único tool call @park que:
  1. Snapshota o estado do loop (history, counters, modo) em disco.
  2. Libera o terminal — você pode chatar, listar jobs, abrir outro /coder.
  3. Agenda o resume no scheduler durável (sobrevive a crash/restart).
  4. Auto-retoma sozinho quando o timer/polling completa, sem você apertar Enter.
Disponível desde chatcli 1.111.x (PR #879). Funciona em modo /coder, /agent e /run. Auto-resume via TIOCSTI no Unix e WriteConsoleInputW no Windows; fallback transparente quando o sistema operacional restringe a injeção.

Visão geral do fluxo


Por que isso é necessário

Antes do park, esperar dentro de um /coder significava:
Problemas:
  • Terminal bloqueado os 300 s inteiros.
  • Cada bash consome um turn do orçamento de turns do agent (default 100).
  • Crash do CLI = perde o sleep e o estado.
  • Sem audit trail — só fica no histórico do shell.
Com @park:
  • Terminal liberado imediatamente; você usa o CLI normalmente.
  • Park ocupa um único turn, independente da duração (10 s ou 14 dias).
  • Crash-safe — snapshot em disco + scheduler WAL replay no boot.
  • Audit completo via /jobs logs e /parked.

Quatro modos do @park

Timer fixo. Single-shot. Ideal para “espere antes de checar”.

Matchers do success_when

DSL livre. Vazio assume “sucesso default” (HTTP 2xx ou exit 0). Múltiplos matchers em um único spec não são suportados; combine via custom body matches: se precisar de lógica.

Comandos de gerenciamento

Lista todos os parks pendentes em disco com cross-check do scheduler job.
Subcomandos:

Diretivas mid-park

Enquanto um agent está estacionado o terminal é seu: texto plano vai para o chat normal, todos os comandos funcionam, e você pode até rodar outra tarefa /coder em paralelo. Para falar com o agent estacionado, use os canais explícitos: Isso torna loops estacionados componíveis: um segundo agent investigando um incidente pode dar @park list, ver o loop de monitoramento estacionado por 10 minutos, e mandar @park note“o deploy terminou, valide e encerre o monitoramento” ou “mude seu ciclo para 5m”. Mudanças de cadência são aplicadas pelo próprio agent estacionado no ciclo seguinte, então a posse do scheduler nunca sai do loop que criou o park. No resume, o agent recebe as diretivas em bloco:

Auto-resume — como o terminal “acorda sozinho”

Aqui está a parte que diferencia park de uma simples scheduled-task: quando o wait completa, o agent volta ao foreground sem você fazer nada.

Por que TIOCSTI

TIOCSTI é um ioctl POSIX que injeta bytes no input buffer da TTY como se o usuário tivesse digitado. Funciona com qualquer aplicação que lê stdin no controlling tty — não precisa modificar o go-prompt.

Por que dois bursts (body + 15ms + \r)

go-prompt v0.2.6 usa bytes.Equal para classificar keys (input.go:24). Um buffer multi-byte como /resume abc\r não casa com nenhuma sequência da tabela ASCII e cai no branch default que insere como texto — incluindo o \r final, que vira literal e nunca submete. Solução: split.
  1. Body do comando vai num burst (multi-byte → text insert).
  2. Pause de 15 ms (acima do poll de 10 ms do readBuffer).
  3. \r solo num segundo burst (1 byte → matcheia ControlM → submete).

Windows usa WriteConsoleInputW

Sem TIOCSTI no Windows — o kernel32.dll expõe WriteConsoleInputW que aceita INPUT_RECORD estruturados. Cada char vira um par key-down/key-up; o Enter final usa VirtualKeyCode=VK_RETURN para go-prompt classificar como Enter nativo.

Parks no ACP, MCP server e gateway

A maquinaria de auto-resume acima foi construída em torno de um terminal interativo. Superfícies unattended não têm loop de prompt para acordar — então um park feito nelas se comporta diferente, de propósito:
  • O turno fica aberto. Via ACP (IDE) ou MCP server, o @park não encerra o run. O turno bloqueia in-process, o scheduler entrega o wake diretamente a ele, e cada ciclo de monitoração streama na mesma request do cliente — pensamentos, tool calls e o relatório do ciclo continuam chegando na sua IDE exatamente como qualquer outro turno. Re-parks continuam no lugar, então uma request cobre a conversa de monitoração inteira.
  • Stop cancela tudo. Cancelar o turno (Stop da IDE, session/cancel, desconexão do cliente) retira o park por completo: o job do scheduler é cancelado, o snapshot apagado, nada continua pollando em background.
  • Sem tomada do terminal. Banners e a injeção de TTY nunca disparam nessas superfícies — o stdout ali é canal de protocolo, e não há prompt para redesenhar.
No REPL nada muda: parks encerram o run, você recebe o prompt de volta, e o auto-resume acorda o terminal como documentado acima.

Matriz de plataformas

TIOCSTI com gate /proc/sys/dev/tty/legacy_tiocsti:Para reabilitar (root):
Trade-off: re-habilita uma feature que distros desabilitaram por causa de CVE-2017-5226 (escape de sandboxes via injeção). Em ambiente dev pessoal é seguro; em servidores compartilhados, considere o fallback.

Fallback quando TIOCSTI/WriteConsoleInput não disponível

Quando a injeção é rejeitada, o 🔔 park ready ainda aparece e o token entra na pendingResumeQueue. Você precisa digitar qualquer caractere + Enter no prompt — o executor consome a queue antes de processar seu input. UX equivalente, com um clique extra. O prompt prefix mostra [🅿️ resume ready: N] ❯ enquanto há resume pendente, então é difícil esquecer.

Exemplos práticos

CI do GitHub Actions

Agent emite (resumido):
Terminal volta. Você abre outro /coder pra refatorar testes em paralelo. ~15 minutos depois:

Terraform apply lento

Detalhe importante: terraform plan -detailed-exitcode retorna 0 se não há diff, 2 se há diff, 1 em erro. Aqui esperamos 0 (convergência). Para esperar “diff aplicado”, troque pra success_when:exit=2.

Janela de deploy noturna

Agent dorme até 02:00, retoma e executa o deploy.

Health check pós-rollout


Modelo de segurança

Aprovação no @park = aprovação do polling

Quando o agent emite @park for_cmd cmd="echo done", o /coder mostra a security check com os args completos, incluindo o cmd embedded:
Quando você responde [y], está pré-autorizando o polling shell rodar aquele cmd específico que você acabou de ver. O ChatCLI propaga DangerousConfirmed=true no scheduler job, então o poll fire não tropeça em ShellPolicyAsk (não há humano no fire-time pra aprovar de novo).
Denylist sempre vence. Mesmo com a aprovação interativa, comandos que casam com regras Deny na sua coder policy são rejeitados no fire-time. Veja Coder Security.

Snapshots têm permissão 0o600

~/.chatcli/parked/<token>.json contém o histórico de chat completo do park. Arquivos são criados com 0o600 (owner-only) e o diretório com 0o700. Snapshots nunca vazam pra outros usuários do host.

Token não permite path traversal

Tokens são gerados com crypto/rand (16 bytes hex = 32 chars) e validados contra regex [a-zA-Z0-9._-]{8,128}. Não há como /resume ../etc/passwd escapar do diretório.

Variáveis de ambiente

A maioria do comportamento é controlada pelo scheduler subjacente — veja Scheduler env vars.

Internos — para quem quer hackear

Snapshot format

JSON serializado com json.MarshalIndent. Schema versionado (SchemaVersion = 1). Campos principais:
pending_tool_call_id é o native tool_use ID da Anthropic — preservado pra reconstruir o pairing tool_use/tool_result no resume (caso contrário a próxima request à API rejeita por unmatched tool_call).

Action types do scheduler

Park introduz 2 action types: park_poll se auto-reschedula a cada interval até casar ou deadline elapsar. Crash-safe via WAL replay — uma iteração interrompida volta no boot.

Idempotência do /resume

O auto-resume injeta /resume <token>\r via TIOCSTI. Mas o executor já fez o resume na primeira linha (drainPendingResumes), então quando o /resume <token> chega no command handler o snapshot já foi deletado. Solução: markRecentlyResumed(token) no drain (TTL 30s) e wasRecentlyResumed(token) no handleResumeCommand → no-op silencioso. Tokens realmente inválidos (typos do user) ainda surgem como erro porque o TTL é curto.

Troubleshooting

Causa mais comum: legacy_tiocsti=1 no Linux ou kern.tiocsti_disable=1 no macOS. O banner é printado pela bridge mas a injeção foi rejeitada pelo kernel.Verificação:
Se retorna 1, ou habilita (vide Matriz de plataformas) ou usa o fallback: digite qualquer caractere + Enter no prompt e o drain consome o resume.
Você está usando o job ID (segunda coluna do /parked) em vez do token (primeira coluna). Tokens têm 8 chars visíveis no /parked; job IDs são do scheduler interno.Cole sempre da primeira coluna do /parked ou use auto-complete (Tab).
Veja /jobs show <job_id>. Causas comuns:
  • echo done (ou cmd qualquer) com policy Ask + sem DangerousConfirmed: deveria ser propagado automaticamente; reporte como bug.
  • HTTP 5xx persistente em for_url: cada poll falha, scheduler pode marcar job como failed depois de N retries. Aumente interval ou deadline.
  • Comando shell denylist: regra Deny na coder policy bate sempre, mesmo com aprovação. Veja /config security rules.
Use /parked prune para remover snapshots cujo job está terminal (completed/failed/cancelled/timed_out). Em sistemas longevos, considere /parked gc 24h periódico.
Mostra /dev/pts/N (Linux) ou /dev/ttysNNN (macOS). É essa fd que injectTTYLine abre via /dev/tty.

Reference rápida

Pra contexto da feature de scheduler que sustenta o park, veja Scheduler. Para a security policy do @coder exec, veja Coder Security.