Guia de Agendamento¶
Outros idiomas¶
O guia de execução governada descreve o comportamento de
runtime desta branch: o registro armazena a configuração sem executar um agente;
invocações via API, manuais e por temporizador recebem IDs de execução distintos e
resultados terminais reais. O histórico atinge succeeded após a execução e a
limpeza, e inclui saída, erros e referências de auditoria protegidas. Agendamento
gerenciado via CLI, modelos roteados e transporte de agentes externos permanecem
indisponíveis no serviço padrão. O limite de comando selecionado pode usar
Firecracker para ferramentas oneshot, parsers, MCP stdio e
sessões PTY quando os artefatos de guest correspondentes estiverem provisionados.
Consulte o guia da branch para as diferenças visíveis
ao operador e a cobertura restante.
O sistema de agendamento do Symbiont oferece execução de tarefas baseada em cron de nível de produção para agentes de IA. O sistema suporta:
- Agendamentos cron: Sintaxe cron tradicional para tarefas recorrentes
- Tarefas únicas: Execução única em um horário específico
- Padrão heartbeat: Ciclos contínuos de avaliação-ação-espera para agentes de monitoramento
- Isolamento de sessão: Contextos de agente efêmeros, compartilhados ou totalmente isolados
- Roteamento de entrega: Múltiplos canais de saída (Stdout, LogFile, Webhook, Slack, Email, Custom)
- Aplicação de políticas: Verificações de segurança e conformidade antes da execução
- Robustez para produção: Jitter, limites de concorrência, filas de dead-letter e verificação AgentPin
Arquitetura¶
O sistema de agendamento é construído sobre três componentes principais:
┌─────────────────────┐
│ CronScheduler │ Loop de tick em segundo plano (intervalos de 1 segundo)
│ (Tick Loop) │ Seleção de tarefas e orquestração de execução
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│ SqliteJobStore │ Armazenamento persistente de tarefas
│ (Job Storage) │ Suporte a transações, gerenciamento de estado
└──────────┬──────────┘
│
▼
┌─────────────────────┐
│DefaultAgentScheduler│ Runtime de execução de agentes
│ (Execution Engine) │ Gerenciamento do ciclo de vida do AgentContext
└─────────────────────┘
CronScheduler¶
O CronScheduler é o ponto de entrada principal. Ele gerencia:
- Loop de tick em segundo plano executando em intervalos de 1 segundo
- Seleção de tarefas baseada no próximo horário de execução
- Controle de concorrência e injeção de jitter
- Coleta de métricas e monitoramento de saúde
- Encerramento gracioso com rastreamento de tarefas em andamento
SqliteJobStore¶
O SqliteJobStore fornece persistência durável de tarefas com:
- Transações ACID para atualizações de estado das tarefas
- Rastreamento do ciclo de vida das tarefas (Active, Paused, Completed, Failed, DeadLetter)
- Histórico de execuções com trilha de auditoria
- Capacidades de consulta para filtragem por status, ID do agente, etc.
DefaultAgentScheduler¶
O DefaultAgentScheduler executa agentes agendados:
- Cria instâncias de
AgentContextisoladas ou compartilhadas - Gerencia o ciclo de vida da sessão (criar, executar, destruir)
- Roteia entregas para os canais configurados
- Aplica portões de política antes da execução
Sintaxe DSL¶
Estrutura do Bloco Schedule¶
Blocos de agendamento são definidos em arquivos DSL do Symbiont:
schedule {
name: "daily-report"
agent: "reporter-agent"
cron: "0 0 9 * * *"
session_mode: "ephemeral_with_summary"
delivery: ["stdout", "log_file"]
policy {
require_approval: false
max_runtime: "5m"
}
}
Sintaxe Cron¶
Sintaxe cron estendida com seis campos (segundos primeiro, campo opcional sétimo para ano):
┌─────────────── segundo (0-59)
│ ┌───────────── minuto (0-59)
│ │ ┌─────────── hora (0-23)
│ │ │ ┌───────── dia do mês (1-31)
│ │ │ │ ┌─────── mês (1-12)
│ │ │ │ │ ┌───── dia da semana (0-6, Domingo = 0)
│ │ │ │ │ │
* * * * * *
Exemplos:
# Todo dia às 9h
cron: "0 0 9 * * *"
# Toda segunda-feira às 18h
cron: "0 0 18 * * 1"
# A cada 15 minutos
cron: "0 */15 * * * *"
# Primeiro dia de cada mês à meia-noite
cron: "0 0 0 1 * *"
Tarefas Únicas (Sintaxe At)¶
Para tarefas que executam uma única vez em um horário específico:
schedule {
name: "deployment-check"
agent: "health-checker"
at: "2026-02-15T14:30:00Z" # Timestamp ISO 8601
delivery: ["webhook"]
webhook_url: "https://ops.example.com/hooks/deployment"
}
Padrão Heartbeat¶
Para agentes de monitoramento contínuo que avaliam -> agem -> dormem:
schedule {
name: "system-monitor"
agent: "heartbeat-agent"
cron: "0 */5 * * * *" # Acordar a cada 5 minutos
heartbeat: {
enabled: true
context_mode: "ephemeral_with_summary"
max_iterations: 100 # Limite de segurança
}
}
O agente heartbeat segue este ciclo:
- Avaliação: Avaliar o estado do sistema (ex.: verificar métricas, logs)
- Ação: Tomar ação corretiva se necessário (ex.: reiniciar serviço, alertar operações)
- Espera: Aguardar até o próximo tick agendado
Comandos CLI¶
O symbi cron gerencia o ciclo de vida das tarefas agendadas. Cada subcomando
abaixo é mostrado com as opções que ele realmente aceita; execute
symbi cron <subcomando> --help para confirmar no seu build.
Listar Tarefas¶
O list não aceita opções. Use symbi cron status <job-id> para o detalhe de
uma tarefa, ou symbi cron history para filtrar registros de execução.
Adicionar Tarefa¶
--name, --cron e --agent são obrigatórios. O agente já deve estar
registrado; o add não lê um arquivo DSL.
symbi cron add --name "daily-report" --cron "0 0 9 * * * *" --agent reporter
# Fixar um fuso horário (padrão UTC) e uma política, e desativar após uma execução
symbi cron add --name "one-off" --cron "0 */5 * * * * *" --agent reporter \
--tz "America/New_York" --policy strict --one-shot
| Opção | Obrigatória | Significado |
|---|---|---|
--name <NAME> |
sim | Nome da tarefa |
--cron <EXPR> |
sim | Expressão cron, sete campos (segundos primeiro) |
--agent <AGENT> |
sim | Nome do agente registrado que será executado |
--tz <TIMEZONE> |
não | Fuso horário IANA, padrão UTC |
--policy <POLICY> |
não | Política a aplicar à execução |
--one-shot |
não | Executar uma vez e depois desativar a tarefa |
Remover, Pausar, Retomar¶
Cada um recebe um único UUID de tarefa e nenhuma opção.
symbi cron remove <job-id>
symbi cron pause <job-id> # para o agendamento, preserva o estado
symbi cron resume <job-id> # ocorrências não resolvidas bloqueiam a retomada
Status¶
O status recebe um UUID de tarefa e nenhuma opção. Ele reporta o detalhe da
tarefa e as execuções recentes, incluindo um status Reconciled com o erro
original e a resolução do operador quando uma invocação foi reconciliada.
Executar Agora¶
Dispara a tarefa imediatamente, à força. Um disparo manual exige um token
administrativo e um UUID em Idempotency-Key na superfície REST; reutilize o
mesmo UUID para repetir a tentativa em vez de executar duas vezes. Veja
estados de repetição do agendador e
recuperação do cron.
Histórico¶
# Execuções recentes de todas as tarefas (20 por padrão)
symbi cron history
# Uma única tarefa, com um limite explícito de registros
symbi cron history --job <job-id> --limit 50
| Opção | Significado |
|---|---|
--job <JOB_ID> |
Filtrar para uma única tarefa |
--limit <N> |
Máximo de registros a exibir, padrão 20 |
Padrão Heartbeat¶
HeartbeatContextMode¶
Controla como o contexto persiste entre iterações do heartbeat:
pub enum HeartbeatContextMode {
/// Fresh context each iteration, append summary to run history
EphemeralWithSummary,
/// Shared context across all iterations (memory accumulates)
SharedPersistent,
/// Fresh context each iteration, no summary (stateless)
FullyEphemeral,
}
EphemeralWithSummary (padrão):
- Novo AgentContext por iteração
- Resumo da iteração anterior anexado ao contexto
- Previne crescimento ilimitado de memória
- Mantém continuidade para ações relacionadas
SharedPersistent:
- Um único AgentContext reutilizado em todas as iterações
- Histórico completo de conversação preservado
- Maior uso de memória
- Melhor para agentes que precisam de contexto profundo (ex.: sessões de depuração)
FullyEphemeral:
- Novo AgentContext por iteração, sem transferência
- Menor consumo de memória
- Melhor para verificações independentes (ex.: probes de saúde de API)
Exemplo de Agente Heartbeat¶
agent heartbeat_monitor {
model: "claude-sonnet-4.5"
system_prompt: """
You are a system monitoring agent. On each heartbeat:
1. Check system metrics (CPU, memory, disk)
2. Review recent error logs
3. If issues detected, take action:
- Restart services if safe
- Alert ops team via Slack
- Log incident details
4. Summarize findings
5. Return 'sleep' when done
"""
}
schedule {
name: "heartbeat-monitor"
agent: "heartbeat_monitor"
cron: "0 */10 * * * *" # A cada 10 minutos
heartbeat: {
enabled: true
context_mode: "ephemeral_with_summary"
max_iterations: 50
}
delivery: ["log_file", "slack"]
slack_channel: "#ops-alerts"
}
Isolamento de Sessão¶
Modos de Sessão¶
pub enum HeartbeatContextMode {
/// Ephemeral context with summary carryover (default)
EphemeralWithSummary,
/// Shared persistent context across all runs
SharedPersistent,
/// Fully ephemeral, no state carryover
FullyEphemeral,
}
Configuração:
schedule {
name: "data-pipeline"
agent: "etl-agent"
cron: "0 0 2 * * *"
# Contexto novo por execução, resumo da execução anterior incluído
session_mode: "ephemeral_with_summary"
}
Ciclo de Vida da Sessão¶
Para cada execução agendada:
- Pré-execução: Verificar limites de concorrência, aplicar jitter
- Criação de sessão: Criar
AgentContextbaseado nosession_mode - Portão de política: Avaliar condições de política
- Execução: Executar agente com entrada e contexto
- Entrega: Rotear saída para os canais configurados
- Limpeza de sessão: Destruir ou persistir contexto baseado no modo
- Pós-execução: Atualizar registro de execução, coletar métricas
Roteamento de Entrega¶
Canais Suportados¶
pub enum DeliveryChannel {
Stdout, // Print to console
LogFile, // Append to job-specific log file
Webhook, // HTTP POST to URL
Slack, // Slack webhook or API
Email, // SMTP email
Custom(String), // User-defined channel
}
Exemplos de Configuração¶
Canal único:
Múltiplos canais:
schedule {
name: "security-scan"
agent: "scanner"
cron: "0 0 1 * * *"
delivery: ["log_file", "slack", "email"]
slack_channel: "#security"
email_recipients: ["ops@example.com", "security@example.com"]
}
Entrega via webhook:
schedule {
name: "metrics-report"
agent: "metrics-agent"
cron: "0 */30 * * * *"
delivery: ["webhook"]
webhook_url: "https://metrics.example.com/ingest"
webhook_headers: {
"Authorization": "Bearer ${METRICS_API_KEY}"
"Content-Type": "application/json"
}
}
Trait DeliveryRouter¶
Canais de entrega personalizados implementam:
#[async_trait]
pub trait DeliveryRouter: Send + Sync {
async fn route(
&self,
channel: &DeliveryChannel,
job: &CronJobDefinition,
run: &JobRunRecord,
output: &str,
) -> Result<(), SchedulerError>;
}
Aplicação de Políticas¶
PolicyGate¶
O PolicyGate avalia políticas específicas de agendamento antes da execução:
pub struct PolicyGate {
policy_engine: Arc<RealPolicyParser>,
}
impl PolicyGate {
pub fn evaluate(
&self,
job: &CronJobDefinition,
context: &AgentContext,
) -> Result<SchedulePolicyDecision, SchedulerError>;
}
Condições de Política¶
schedule {
name: "production-deploy"
agent: "deploy-agent"
cron: "0 0 0 * * 0" # Domingo à meia-noite
policy {
# Exigir aprovação humana antes da execução
require_approval: true
# Tempo máximo de execução antes do encerramento forçado
max_runtime: "30m"
# Exigir capacidades específicas
require_capabilities: ["deployment", "production_write"]
# Aplicação de janela de horário (UTC)
allowed_hours: {
start: "00:00"
end: "04:00"
}
# Restrições de ambiente
allowed_environments: ["staging", "production"]
# Verificação AgentPin obrigatória
require_agent_pin: true
}
}
SchedulePolicyDecision¶
pub enum SchedulePolicyDecision {
Allow,
Deny { reason: String },
RequiresApproval { approver: String, reason: String, policy_id: String },
}
Robustez para Produção¶
Jitter¶
Previne efeito manada (thundering herd) quando múltiplas tarefas compartilham um agendamento:
Exemplo:
Concorrência Por Tarefa¶
Limitar execuções concorrentes por tarefa para prevenir esgotamento de recursos:
schedule {
name: "data-sync"
agent: "sync-agent"
cron: "0 */5 * * * *"
max_concurrent: 2 # Permitir no máximo 2 execuções concorrentes
}
Se uma tarefa já está em execução na concorrência máxima, o agendador pula o tick.
Fila de Dead-Letter¶
Tarefas que excedem max_retries são movidas para o status DeadLetter para revisão manual:
schedule {
name: "flaky-job"
agent: "unreliable-agent"
cron: "0 0 * * * *"
max_retries: 3 # Após 3 falhas, mover para dead-letter
}
Recuperação:
# Listar as tarefas e encontrar a que foi desativada
symbi cron list
# Revisar os registros de execução dela, incluindo as falhas
symbi cron history --job <job-id>
# Reativar após corrigir a causa
symbi cron resume <job-id>
O cron list e o cron history não filtram por status; leia a coluna de status
do list, ou os registros de execução do history. Não existe o subcomando
cron reset -- o resume reativa uma tarefa, e uma ocorrência não resolvida
bloqueia a retomada até que um operador a reconcilie.
Verificação AgentPin¶
Verificar criptograficamente a identidade do agente antes da execução:
schedule {
name: "secure-task"
agent: "trusted-agent"
cron: "0 0 * * * *"
agent_pin_jwt: "${AGENT_PIN_JWT}" # JWT ES256 do agentpin-cli
policy {
require_agent_pin: true
}
}
O agendador verifica:
1. Assinatura JWT usando ES256 (ECDSA P-256)
2. ID do agente corresponde à claim iss
3. Âncora de domínio corresponde à origem esperada
4. Expiração (exp) é válida
Falhas disparam o evento de auditoria SecurityEventType::AgentPinVerificationFailed.
Endpoints da API HTTP¶
Gerenciamento de Agendamento¶
POST /api/v1/schedule Criar uma nova tarefa agendada.
curl -X POST http://localhost:8080/api/v1/schedule \
-H "Content-Type: application/json" \
-d '{
"name": "hourly-report",
"agent_id": "reporter",
"cron_expr": "0 0 * * * *",
"session_mode": "ephemeral_with_summary",
"delivery": ["stdout"]
}'
GET /api/v1/schedule Listar todas as tarefas (filtrável por status, ID do agente).
GET /api/v1/schedule/{job_id} Obter detalhes da tarefa.
PUT /api/v1/schedule/{job_id} Atualizar tarefa (expressão cron, entrega, etc.).
curl -X PUT http://localhost:8080/api/v1/schedule/job-123 \
-H "Content-Type: application/json" \
-d '{"cron_expr": "0 0 */2 * * *"}'
DELETE /api/v1/schedule/{job_id} Excluir tarefa.
POST /api/v1/schedule/{job_id}/pause Pausar tarefa.
POST /api/v1/schedule/{job_id}/resume Retomar tarefa pausada.
POST /api/v1/schedule/{job_id}/run Disparar execução imediata.
curl -X POST http://localhost:8080/api/v1/schedule/job-123/run \
-H "Content-Type: application/json" \
-d '{"input": "Run with custom input"}'
GET /api/v1/schedule/{job_id}/history Obter histórico de execução.
GET /api/v1/schedule/{job_id}/next_run Obter próximo horário de execução agendado.
Monitoramento de Saúde¶
GET /api/v1/health/scheduler Saúde e métricas do agendador.
Resposta:
{
"status": "healthy",
"active_jobs": 15,
"paused_jobs": 3,
"in_flight_jobs": 2,
"metrics": {
"runs_total": 1234,
"runs_succeeded": 1180,
"runs_failed": 54,
"avg_execution_time_ms": 850
}
}
Exemplos de SDK¶
SDK JavaScript¶
import { SymbiontClient } from '@symbiont/sdk-js';
const client = new SymbiontClient({
baseUrl: 'http://localhost:8080',
apiKey: process.env.SYMBI_API_KEY
});
// Criar tarefa agendada
const job = await client.schedule.create({
name: 'daily-backup',
agentId: 'backup-agent',
cronExpr: '0 0 2 * * *',
sessionMode: 'ephemeral_with_summary',
delivery: ['webhook'],
webhookUrl: 'https://backup.example.com/notify'
});
console.log(`Created job: ${job.id}`);
// Listar tarefas ativas
const activeJobs = await client.schedule.list({ status: 'active' });
console.log(`Active jobs: ${activeJobs.length}`);
// Obter status da tarefa
const status = await client.schedule.getStatus(job.id);
console.log(`Next run: ${status.next_run}`);
// Disparar execução imediata
await client.schedule.runNow(job.id, { input: 'Backup database' });
// Pausar tarefa
await client.schedule.pause(job.id);
// Ver histórico
const history = await client.schedule.getHistory(job.id, { limit: 10 });
history.forEach(run => {
console.log(`Run ${run.id}: ${run.status} (${run.execution_time_ms}ms)`);
});
// Retomar tarefa
await client.schedule.resume(job.id);
// Excluir tarefa
await client.schedule.delete(job.id);
SDK Python¶
from symbiont import SymbiontClient
client = SymbiontClient(
base_url='http://localhost:8080',
api_key=os.environ['SYMBI_API_KEY']
)
# Criar tarefa agendada
job = client.schedule.create(
name='hourly-metrics',
agent_id='metrics-agent',
cron_expr='0 0 * * * *',
session_mode='ephemeral_with_summary',
delivery=['slack', 'log_file'],
slack_channel='#metrics'
)
print(f"Created job: {job.id}")
# Listar tarefas para agente específico
jobs = client.schedule.list(agent_id='metrics-agent')
print(f"Found {len(jobs)} jobs for metrics-agent")
# Obter detalhes da tarefa
details = client.schedule.get(job.id)
print(f"Cron: {details.cron_expr}")
print(f"Next run: {details.next_run}")
# Atualizar expressão cron
client.schedule.update(job.id, cron_expr='0 */30 * * * *')
# Disparar execução imediata
run = client.schedule.run_now(job.id, input='Generate metrics report')
print(f"Run ID: {run.id}")
# Pausar durante manutenção
client.schedule.pause(job.id)
print("Job paused for maintenance")
# Ver falhas recentes
history = client.schedule.get_history(
job.id,
status='failed',
limit=5
)
for run in history:
print(f"Failed run {run.id}: {run.error_message}")
# Retomar após manutenção
client.schedule.resume(job.id)
# Verificar saúde do agendador
health = client.schedule.health()
print(f"Scheduler status: {health.status}")
print(f"Active jobs: {health.active_jobs}")
print(f"In-flight jobs: {health.in_flight_jobs}")
Configuração¶
CronSchedulerConfig¶
pub struct CronSchedulerConfig {
/// Tick interval (default: 1 second)
pub tick_interval: Duration,
/// Global concurrency limit (default: 100)
pub max_concurrent_cron_jobs: usize,
/// Persistent job store path (default: None)
pub job_store_path: Option<PathBuf>,
/// Catch up missed runs on startup (default: true)
pub enable_missed_run_catchup: bool,
}
Configuração TOML¶
[scheduler]
tick_interval_seconds = 1
max_jitter_seconds = 30
max_concurrent_jobs = 20
enable_metrics = true
default_max_retries = 3
shutdown_timeout_seconds = 60
Canais de entrega¶
Os canais de entrega (stdout, log_file, webhook, slack, email, …) são configurados por agendamento no bloco delivery daquele agendamento (veja os exemplos acima), e não por meio de uma seção global de configuração [scheduler.delivery].
A entrega via
log_filerequerSYMBIONT_LOG_DIR. O caminho de destino fica confinado a esse diretório da lista de permissão — caminhos que escapam dele via.., caminhos absolutos ou symlinks são rejeitados, e a entrega via log_file é fail-closed (desativada) quandoSYMBIONT_LOG_DIRnão está definido.
Observabilidade¶
Métricas (compatíveis com Prometheus)¶
# Total de execuções
symbiont_cron_runs_total{job_name="daily-report",status="succeeded"} 450
# Execuções falhas
symbiont_cron_runs_total{job_name="daily-report",status="failed"} 5
# Histograma de tempo de execução
symbiont_cron_execution_duration_seconds{job_name="daily-report"} 1.234
# Gauge de tarefas em andamento
symbiont_cron_in_flight_jobs 3
# Tarefas em dead-letter
symbiont_cron_dead_letter_total{job_name="flaky-job"} 2
Eventos de Auditoria¶
Todas as ações do agendador emitem eventos de segurança:
pub enum SecurityEventType {
CronJobCreated,
CronJobUpdated,
CronJobDeleted,
CronJobPaused,
CronJobResumed,
CronJobExecuted,
CronJobFailed,
CronJobDeadLettered,
AgentPinVerificationFailed,
}
Consultar log de auditoria a partir do shell interativo:
Ou programaticamente via API HTTP do runtime — veja Referência da API para os endpoints /api/v1/audit.
Melhores Práticas¶
- Use jitter para agendamentos compartilhados: Previne que múltiplas tarefas iniciem simultaneamente
- Defina limites de concorrência: Protege contra esgotamento de recursos
- Monitore a fila de dead-letter: Revise e corrija tarefas com falha regularmente
- Use EphemeralWithSummary: Previne crescimento ilimitado de memória em heartbeats de longa duração
- Habilite verificação AgentPin: Verifique criptograficamente a identidade do agente
- Configure roteamento de entrega: Use canais apropriados para diferentes tipos de tarefa
- Defina portões de política: Aplique janelas de horário, aprovações e verificações de capacidade
- Use o padrão heartbeat para monitoramento: Ciclos contínuos de avaliação-ação-espera
- Teste agendamentos em staging: Valide expressões cron e lógica de tarefas antes da produção
- Exporte métricas: Integre com Prometheus/Grafana para visibilidade operacional
Solução de Problemas¶
Tarefa Não Executando¶
- Verifique o status da tarefa:
symbi cron status <job-id> - Verifique a expressão cron: Use crontab.guru
- Verifique a saúde do agendador:
curl http://localhost:8080/api/v1/health/scheduler - Revise os logs:
symbi logs --lines 200(adicione--followpara acompanhar em tempo real)
Tarefa Falhando Repetidamente¶
- Veja o histórico:
symbi cron history --job <job-id> - Verifique mensagens de erro nos registros de execução
- Verifique a configuração e capacidades do agente
- Teste o agente fora do agendador:
symbi run <agent-id> --input "test" - Verifique portões de política: Assegure que janelas de horário e capacidades correspondam
Tarefa em Dead-Letter¶
- Liste as tarefas e encontre a que foi desativada:
symbi cron list - Revise o padrão de falha:
symbi cron history --job <job-id> - Corrija a causa raiz (código do agente, permissões, dependências externas)
- Reative a tarefa:
symbi cron resume <job-id>
Alto Uso de Memória¶
- Verifique o modo de sessão: Mude para
ephemeral_with_summaryoufully_ephemeral - Reduza iterações do heartbeat: Diminua
max_iterations - Monitore o tamanho do contexto: Revise a verbosidade da saída do agente
- Habilite arquivamento de contexto: Configure políticas de retenção
Migração do v0.9.0¶
A versão v1.0.0 adiciona recursos de robustez para produção. Atualize suas definições de tarefa:
schedule {
name: "my-job"
agent: "my-agent"
cron: "0 0 * * * *"
+
+ # Adicionar limite de concorrência
+ max_concurrent: 2
+
+ # Adicionar AgentPin para verificação de identidade
+ agent_pin_jwt: "${AGENT_PIN_JWT}"
+
+ policy {
+ require_agent_pin: true
+ }
}
Atualize a configuração:
[scheduler]
tick_interval_seconds = 1
+ max_jitter_seconds = 30
+ default_max_retries = 3
+ shutdown_timeout_seconds = 60
Sem alterações de API que quebrem compatibilidade. Todas as tarefas da v0.9.0 continuam funcionando.