bash sleep 300 que bloqueia 5 minutos a tela inteira, o agent emite um único tool call @park que:
- Snapshota o estado do loop (history, counters, modo) em disco.
- Libera o terminal — você pode chatar, listar jobs, abrir outro
/coder. - Agenda o resume no scheduler durável (sobrevive a crash/restart).
- 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:
- Terminal bloqueado os 300 s inteiros.
- Cada
bashconsome 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.
@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 logse/parked.
Quatro modos do @park
- delay
- until
- for_url
- for_cmd
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
- /parked
- /resume
- /cancel-park
- /park-note
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 usabytes.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.
- Body do comando vai num burst (multi-byte → text insert).
- Pause de 15 ms (acima do poll de 10 ms do
readBuffer). \rsolo num segundo burst (1 byte → matcheia ControlM → submete).
Windows usa WriteConsoleInputW
Sem TIOCSTI no Windows — o kernel32.dll expõeWriteConsoleInputW 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
@parknã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.
Matriz de plataformas
- Linux
- macOS
- Windows
- BSDs
TIOCSTI com gate 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.
/proc/sys/dev/tty/legacy_tiocsti:Para reabilitar (root):
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
/coder pra refatorar testes em paralelo. ~15 minutos depois:
Terraform apply lento
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
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:
[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).
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 comcrypto/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 comjson.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
park: nenhum snapshot bate com '<token>'
park: nenhum snapshot bate com '<token>'
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).Park ficou em (failed) no /parked
Park ficou em (failed) no /parked
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
intervaloudeadline. - Comando shell denylist: regra
Denyna coder policy bate sempre, mesmo com aprovação. Veja/config security rules.
Snapshot acumulando em ~/.chatcli/parked/
Snapshot acumulando em ~/.chatcli/parked/
Use
/parked prune para remover snapshots cujo job está terminal (completed/failed/cancelled/timed_out). Em sistemas longevos, considere /parked gc 24h periódico.Como saber qual é o controlling TTY que recebe a injeção?
Como saber qual é o controlling TTY que recebe a injeção?
/dev/pts/N (Linux) ou /dev/ttysNNN (macOS). É essa fd que injectTTYLine abre via /dev/tty.Reference rápida
@coder exec, veja Coder Security.