--- name: clip-routine description: Gerir routines Paperclip — listar crons activos, ver execuções, criar/editar routines. Usar quando "clip routine", "routines clip", "crons paperclip", "automações clip". context: fork --- # /clip-routine — Gerir Routines Paperclip ## Modelo de governance (desde 29-03-2026) **Todas as routines são atribuídas ao CEO.** O CEO delega pela cadeia hierárquica: ``` Routine (cron trigger) → CEO avalia e delega → C-Level adiciona contexto departamental → Director coordena e atribui → Especialista executa → Resultado sobe a cadeia com síntese progressiva ``` Cada routine tem no campo `description` a cadeia de delegação (ex: `CADEIA: CEO → CTO → Dir. Infraestrutura → Backup Specialist`). **Regras:** - Novas routines devem ser SEMPRE atribuídas ao CEO - A cadeia de delegação deve estar explícita na descrição - Nunca atribuir routines directamente a especialistas — quebra a visibilidade hierárquica ## Dois tipos de periodicidade 1. **Heartbeats** — intervalos configurados via `runtime_config` dos agentes (quando acordam) 2. **Routines** — tarefas periódicas via tabela `routines` + `routine_triggers` com cron (o que fazem ao acordar) ## Constantes ``` BD: PGPASSWORD="paperclip" psql -h localhost -p 54329 -U paperclip -d paperclip COMPANY_ID: ebe10308-efd7-453f-86ab-13e6fe84004f ``` ## Tiers de heartbeat | Tier | Intervalo | Uso | |------|-----------|-----| | 1 | 1h (3600s) | CEO, agentes criticos | | 2 | 2h (7200s) | C-Level activos | | 3 | 4h (14400s) | Directores | | 4 | 6h (21600s) | Especialistas com rotina | | 5 | on-demand | Especialistas reactivos (sem timer) | ## Modo lista (sem argumentos) Listar agentes com heartbeat configurado: ```sql SELECT name, role, status, runtime_config->'heartbeat'->>'enabled' as hb_enabled, runtime_config->'heartbeat'->>'intervalSec' as hb_interval, last_heartbeat_at FROM agents WHERE company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' AND runtime_config::text != '{}' AND runtime_config->'heartbeat' IS NOT NULL ORDER BY (runtime_config->'heartbeat'->>'intervalSec')::int ASC NULLS LAST; ``` Complementar com contagem de execucoes recentes: ```sql SELECT a.name, COUNT(CASE WHEN hr.status = 'succeeded' THEN 1 END) as ok_24h, COUNT(CASE WHEN hr.status = 'failed' THEN 1 END) as fail_24h FROM agents a LEFT JOIN heartbeat_runs hr ON hr.agent_id = a.id AND hr.started_at > NOW() - INTERVAL '24 hours' WHERE a.company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' AND a.runtime_config->'heartbeat' IS NOT NULL GROUP BY a.name ORDER BY a.name; ``` ## Modo ver (com nome de agente) Ultimas 10 execucoes de um agente: ```sql SELECT hr.status, hr.started_at, hr.finished_at, LEFT(hr.error, 80) as erro FROM heartbeat_runs hr JOIN agents a ON hr.agent_id = a.id WHERE a.company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' AND LOWER(a.name) LIKE LOWER('%{{NOME}}%') ORDER BY hr.started_at DESC LIMIT 10; ``` ## Modo criar/editar (configurar heartbeat) ### Activar heartbeat ```sql UPDATE agents SET runtime_config = jsonb_set( COALESCE(runtime_config, '{}'::jsonb), '{heartbeat}', '{"enabled": true, "intervalSec": {{INTERVALO}}, "cooldownSec": 10, "wakeOnDemand": true, "maxConcurrentRuns": 1}'::jsonb ) WHERE name = '{{NOME}}' AND company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' RETURNING name, runtime_config; ``` Confirmar sempre com o utilizador: - Nome do agente - Intervalo (sugerir tier adequado ao role) - wakeOnDemand (true para a maioria) ### Desactivar heartbeat ```sql UPDATE agents SET runtime_config = jsonb_set( runtime_config, '{heartbeat,enabled}', 'false'::jsonb ) WHERE name = '{{NOME}}' AND company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' RETURNING name, runtime_config; ``` ### Alterar intervalo ```sql UPDATE agents SET runtime_config = jsonb_set( runtime_config, '{heartbeat,intervalSec}', '{{INTERVALO}}'::jsonb ) WHERE name = '{{NOME}}' AND company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' RETURNING name, runtime_config; ``` ## Routines do projecto (tabela routines) Alem dos heartbeats por agente, existem routines organizadas por projecto com cron triggers. **Estado actual:** 5 routines activas no Paperclip. 9 routines anteriores foram migradas para workflows n8n em https://automator.descomplicar.pt. **5 routines Paperclip:** - Auditoria processos e compliance (0 10 * * *) - Execução tarefas AikTop (*/30 * * * *) - Reconciliação financeira diária (0 8 * * *) - Varredura inteligência competitiva (0 11 * * *) - Monitorização workflows n8n (0 10,18 * * *) — atribuída ao COO ### Listar routines activas ```sql SELECT r.title, r.status, r.priority, a.name as assignee, rt.cron_expression, rt.enabled, rt.next_run_at FROM routines r LEFT JOIN agents a ON r.assignee_agent_id = a.id LEFT JOIN routine_triggers rt ON rt.routine_id = r.id WHERE r.company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' ORDER BY r.title; ``` ### Ver execucoes de uma routine ```sql SELECT rr.status, rr.triggered_at, rr.completed_at, LEFT(rr.error, 80) as erro FROM routine_runs rr JOIN routines r ON rr.routine_id = r.id WHERE r.company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' AND LOWER(r.title) LIKE LOWER('%{{TITULO}}%') ORDER BY rr.triggered_at DESC LIMIT 10; ``` ## Modo criar routine (nova) ### Passo 1: Obter CEO ID ```sql SELECT id FROM agents WHERE name = 'CEO' AND company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f'; ``` ### Passo 2: Obter projecto ```sql SELECT id, name FROM projects WHERE company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f'; ``` ### Passo 3: Criar routine (SEMPRE atribuída ao CEO) ```sql INSERT INTO routines (id, company_id, project_id, title, description, assignee_agent_id, priority, status, created_at, updated_at) VALUES ( gen_random_uuid(), 'ebe10308-efd7-453f-86ab-13e6fe84004f', '{{PROJECT_ID}}', '{{TITULO}}', 'CADEIA: CEO → {{C_LEVEL}} → {{DIRECTOR}} → {{ESPECIALISTA}}. {{DESCRICAO_TAREFA}}', '{{CEO_ID}}', '{{PRIORIDADE}}', 'active', NOW(), NOW() ) RETURNING id, title; ``` ### Passo 4: Criar cron trigger **CRITICO:** O kind DEVE ser `'schedule'` (nao `'cron'`). O scheduler do Paperclip so processa triggers com `kind = 'schedule'`. Tambem e OBRIGATORIO popular `next_run_at` — sem ele o scheduler ignora o trigger. ```sql -- Calcular next_run_at antes de inserir -- Para crons diarios (ex: 0 8 * * *), usar: -- (CURRENT_DATE + 1) AT TIME ZONE 'Europe/Lisbon' + INTERVAL '{{HORA}} hours' -- Para crons recorrentes (ex: 0 */4 * * *), calcular proximo slot INSERT INTO routine_triggers (id, company_id, routine_id, kind, label, enabled, cron_expression, timezone, next_run_at, created_at, updated_at) VALUES ( gen_random_uuid(), 'ebe10308-efd7-453f-86ab-13e6fe84004f', '{{ROUTINE_ID}}', 'schedule', '{{LABEL}}', true, '{{CRON_EXPRESSION}}', 'Europe/Lisbon', {{NEXT_RUN_AT}}, NOW(), NOW() ) RETURNING id, cron_expression, next_run_at; ``` **Exemplos de next_run_at:** - `0 8 * * *` → `(CURRENT_DATE + 1) AT TIME ZONE 'Europe/Lisbon' + INTERVAL '8 hours'` - `0 */6 * * *` → `date_trunc('hour', NOW()) + INTERVAL '6 hours'` - `0 9 * * 1` → proxima segunda-feira as 09h Confirmar sempre com o utilizador: título, cadeia, cron, projecto. ### Diagnostico: routines que nao disparam Se routines nao disparam, verificar: Invocar tool MCP: `mcp__paperclip__diag_routine_triggers_broken` Corrigir com: ```sql UPDATE routine_triggers SET kind = 'schedule', next_run_at = {{NEXT_RUN}}, updated_at = NOW() WHERE id = '{{TRIGGER_ID}}'; ``` ### Diagnostico: pipeline noticias Se noticias nao estao a ser publicadas, verificar esta cadeia: ``` 1. Routine triggers → kind='schedule'? next_run_at populado? last_fired_at recente? 2. CEO heartbeat → apanha a issue da routine? delega ao CGO? 3. CGO → delega ao Intelligence Researcher? 4. Intelligence Researcher → cria issues PUBLICAR NOTICIA? (assignee fica NULL — normal) 5. Cron assign-copywriter.sh → atribui ao Copywriter a cada 5 min? 6. Copywriter → acorda com wakeOnDemand? publica no WordPress? 7. Artigo publicado → acentuacao OK? links com aspas? titulo normalizado? ``` Verificar ponto a ponto com: ```sql -- Ponto 1: triggers SELECT r.title, rt.kind, rt.next_run_at, rt.last_fired_at FROM routine_triggers rt JOIN routines r ON rt.routine_id = r.id WHERE r.title LIKE 'Pesquisa diaria%noticias%' ORDER BY rt.cron_expression; -- Ponto 4-5: issues PUBLICAR NOTICIA SELECT i.title, i.status, a.name as assignee, i.created_at FROM issues i LEFT JOIN agents a ON i.assignee_agent_id = a.id WHERE i.title LIKE 'PUBLICAR NOTICIA%' AND i.created_at > NOW() - INTERVAL '24 hours' ORDER BY i.created_at DESC; -- Ponto 6: Copywriter runs SELECT hr.status, hr.started_at FROM heartbeat_runs hr JOIN agents a ON hr.agent_id = a.id WHERE a.name = 'Copywriter' AND hr.started_at > NOW() - INTERVAL '24 hours' ORDER BY hr.started_at DESC; -- Ponto 7: artigos publicados (via SSH server) -- wp --allow-root post list --category=noticias --date_query='{"after":"1 day ago"}' --fields=ID,post_title ``` ## Cadeias de delegação por área | Área | Cadeia | |------|--------| | Infraestrutura/WP/Backups/SSL | CEO → CTO → Dir. Infraestrutura → Especialista | | Desenvolvimento/MCPs/N8N | CEO → CTO → Dir. Desenvolvimento/Automação → Especialista | | Email/Tickets/Processos | CEO → COO → Dir. Suporte/Operações → Especialista | | Financeiro/Facturação | CEO → CFO → Dir. Financeiro → Finance Manager | | Marketing/SEO/Conteúdo/Ads | CEO → CMO → Dir. relevante → Especialista | | Vendas/Leads/Propostas | CEO → CRO → Dir. Comercial → Especialista | | Inteligência/Pesquisa | CEO → CGO → Dir. IA/Estratégia → Especialista | | Analytics/Dados | CEO → CDO → Analytics Agent | ## Formato de output Para modo lista (heartbeats): ``` ## Heartbeats Clip — [data] | Agente | Tier | Intervalo | Enabled | Último HB | OK 24h | Fail 24h | ... ``` Para modo lista (routines): ``` ## Routines Clip — [data] | Título | Cron | Assignee | Cadeia | Próximo run | Enabled | ... Total: 5 routines activas no Paperclip (4 atribuídas ao CEO, 1 ao COO) + 9 migradas para n8n ``` Para modo ver: ``` ## Routine: {{NOME}} **Cadeia:** {{cadeia_delegação}} **Cron:** {{cron}} | **Enabled:** {{enabled}} **Último trigger:** {{last_triggered_at}} ### Últimas 10 execuções | Status | Triggered | Completed | Erro | ... Taxa sucesso 24h: N/N (N%) ``` --- ## Healing Log Registo de erros conhecidos e como evitá-los. Lido automaticamente antes de executar. ```jsonl {"date":"","issue":"","fix":"","source":"user|auto"} ``` *Adicionar nova linha após cada erro corrigido.*