--- name: clip-agent description: Gerir agente Paperclip individual — estado, config, AGENTS.md, histórico runs, issues. Aceita nome como argumento. Usar quando "clip agent", "agente clip", "ver agente", "estado do CTO". context: fork --- # /clip-agent — Gerir Agente Paperclip Aceita argumento: nome do agente (ex: `/clip-agent CTO`). ## Constantes ``` BD: PGPASSWORD="paperclip" psql -h localhost -p 54329 -U paperclip -d paperclip COMPANY_ID: ebe10308-efd7-453f-86ab-13e6fe84004f AGENTS_PATH: /media/ealmeida/Dados/Hub/04-Stack/02.06-Clip/agents/ ``` ## Procedimento ### Passo 1: Contexto completo do agente Invocar tool MCP: `mcp__paperclip__diag_agent_full_context(agent_name="{{NOME}}")` Esta tool retorna config, últimas runs, membership, permissões e tokens — cobre os Passos 1, 3, 6, 7 e 7b de uma só vez. Se multiplos resultados, mostrar lista e pedir clarificacao. ### Passo 2: Hierarquia Quem lhe reporta: ```sql SELECT name, role, status FROM agents WHERE reports_to = '{{AGENT_ID}}' AND company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' ORDER BY role, name; ``` A quem reporta: ```sql SELECT name, role FROM agents WHERE id = '{{REPORTS_TO_ID}}'; ``` ### Passo 3: Ultimas 5 runs ```sql SELECT hr.status, hr.started_at, hr.finished_at, LEFT(hr.error, 80) as erro FROM heartbeat_runs hr WHERE hr.agent_id = '{{AGENT_ID}}' ORDER BY hr.started_at DESC LIMIT 5; ``` ### Passo 4: Issues atribuidas ```sql SELECT title, status, priority FROM issues WHERE assignee_agent_id = '{{AGENT_ID}}' AND status NOT IN ('done','cancelled') ORDER BY priority, status; ``` ### Passo 4b: Verificar falsos blockers (INC-07) Se o agente tem issues `blocked`, invocar tool MCP: `mcp__paperclip__diag_false_blockers` e filtrar pelo agente. TODO: criar diag_* tool per-agent se uso recorrente. Se `sub_activas > 0` e o agente está operacional → falso blocker. Alertar: "Issue {{ID}} marcada como blocked mas tem sub-tasks activas — deveria ser in_progress." ### Passo 5: AGENTS.md Procurar em `AGENTS_PATH`: ```bash find /media/ealmeida/Dados/Hub/04-Stack/02.06-Clip/agents/ -name "AGENTS.md" -exec grep -l "{{NOME}}" {} \; ``` Se encontrado, ler e apresentar resumo (primeiras 30 linhas). ## Formato de output ``` ## Agente: {{NOME}} **Role:** {{role}} | **Status:** {{status}} | **Ultimo heartbeat:** {{last_heartbeat_at}} **Reporta a:** {{reports_to_name}} | **Budget:** {{spent}}/{{budget}} ({{pct}}%) **Tokens cached:** {{tokens_M}}M | **CWD:** {{cwd}} | **Model:** {{model}} ### Equipa (reportam a este agente) | Nome | Role | Status | ... ### Ultimas 5 runs | Status | Início | Fim | Erro | ... ### Issues atribuidas | Titulo | Status | Prioridade | ... ### Skills atribuidas [lista de desiredSkills ou "Sem skills (apenas built-in paperclip)"] ### AGENTS.md [resumo ou path] ``` ### Passo 6: Skills do agente ```sql SELECT adapter_config->'paperclipSkillSync'->'desiredSkills' as desired_skills FROM agents WHERE id = '{{AGENT_ID}}'; ``` Se nao NULL, listar as skills. Se NULL, indicar "Sem skills atribuidas (apenas built-in paperclip)". Para ver detalhes das skills disponiveis na empresa: ```sql SELECT name, slug, key, source_type FROM company_skills WHERE company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' ORDER BY name; ``` ### Skills atribuídas ao agente ```sql -- Ver skills desejadas (configuradas via API) SELECT a.name as agente, a.adapter_config->'paperclipSkillSync'->'desiredSkills' as desired_skills FROM agents a WHERE a.id = '{{AGENT_ID}}'; ``` **Acções disponíveis:** - Ver skills da empresa: `SELECT name, slug FROM company_skills WHERE company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f'` - Atribuir skills via API (fetch no browser): `POST /api/agents/{{AGENT_ID}}/skills/sync` com `{ "desiredSkills": ["slug1", "slug2"] }` ### Membership (OBRIGATÓRIO — sem isto, permissões não funcionam) A função `hasPermission()` verifica **primeiro** se o agente tem membership activa em `company_memberships`. Sem membership → permissão negada mesmo com grants correctos. ```sql -- Verificar membership SELECT status, membership_role FROM company_memberships WHERE company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' AND principal_id = '{{AGENT_ID}}'; ``` Se **0 rows** → agente não tem membership. Criar: ```sql INSERT INTO company_memberships (id, company_id, principal_type, principal_id, status, membership_role, created_at, updated_at) VALUES (gen_random_uuid(), 'ebe10308-efd7-453f-86ab-13e6fe84004f', 'agent', '{{AGENT_ID}}', 'active', 'member', NOW(), NOW()); ``` **Nota:** Agentes criados via SQL directo NÃO recebem membership automaticamente — apenas os onboarded via fluxo OpenClaw/hiring. Sempre criar membership ao adicionar agentes manualmente. ### Permissões do agente ```sql SELECT permission_key FROM principal_permission_grants WHERE company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f' AND principal_id = '{{AGENT_ID}}'; ``` Para adicionar `tasks:assign` (necessário para delegação): ```sql INSERT INTO principal_permission_grants (company_id, principal_type, principal_id, permission_key, granted_by_user_id) VALUES ('ebe10308-efd7-453f-86ab-13e6fe84004f', 'agent', '{{AGENT_ID}}', 'tasks:assign', 'local-board'); ``` **ATENÇÃO:** Este grant só funciona se o agente tiver membership activa (ver secção acima). ### Passo 7: Verificar adapter_config (CRITICO) Agentes sem `dangerouslySkipPermissions: true` ficam bloqueados — todos os comandos bash sao rejeitados. ```sql SELECT name, adapter_config->>'dangerouslySkipPermissions' as skip_perms, adapter_config->>'cwd' as cwd, adapter_config->>'model' as model, adapter_config->>'timeoutSec' as timeout, adapter_config->>'maxTurnsPerRun' as max_turns FROM agents WHERE id = '{{AGENT_ID}}'; ``` **Alertar se:** - `skip_perms` != `true` → agente vai ficar bloqueado em todos os comandos bash - `cwd` = NULL → agente corre em directório temporário sem acesso ao Hub - `cwd` = `/media/ealmeida/Dados/Hub` → **CRITICO: CWD aponta para Hub raiz (6.4GB), Claude Code indexa tudo, causa token burn massivo** — usar `/media/ealmeida/Dados/Hub/04-Stack/02.06-Clip` - `model` = NULL → usa modelo default (pode não ser o desejado) **Corrigir agente (CWD correcto para Clip):** ```sql UPDATE agents SET adapter_config = adapter_config || '{"dangerouslySkipPermissions": true, "cwd": "/media/ealmeida/Dados/Hub/04-Stack/02.06-Clip", "model": "gemini-2.5-flash", "graceSec": 20, "timeoutSec": 900}'::jsonb WHERE id = '{{AGENT_ID}}'; ``` **Nota sobre modelos e adapters:** O adapter `claude_local` já não é usado para agentes heartbeat. Distribuição actual: - 1 agente (CEO) usa `gemini_local` com `gemini-2.5-pro` - 50 agentes usam `gemini_local` com `gemini-2.5-flash` - 13 agentes usam `opencode_local` com `openrouter/x-ai/grok-4.1-fast` **Agentes analyst/read-only (ex: Reality Checker):** usar `adapter_type: process` com `adapter_config.model: "claude-sonnet-4-6"` e `instructionsBundleMode: "external"`. Estes agentes são invocados manualmente (heartbeat desactivado), não consomem budget em modo autónomo. ### Passo 7b: Sessão e tokens (INC-12) `agent_task_sessions` não tem campo de tokens — o consumo está em `heartbeat_runs.usage_json`. Verificar erros "Prompt is too long" e consumo nas últimas runs. ```sql -- Erros "Prompt is too long" recentes SELECT hr.status, LEFT(hr.error, 80) as erro, hr.started_at::timestamp(0) FROM heartbeat_runs hr WHERE hr.agent_id = '{{AGENT_ID}}' AND hr.status IN ('failed','error') AND hr.error ILIKE '%too long%' AND hr.started_at > NOW() - INTERVAL '48 hours' ORDER BY hr.started_at DESC LIMIT 5; -- Consumo token nas últimas runs bem-sucedidas SELECT ROUND(COALESCE((hr.usage_json->>'cache_read_input_tokens')::numeric,0)/1000,0) as cache_read_k, ROUND(COALESCE((hr.usage_json->>'input_tokens')::numeric,0)/1000,0) as input_k, ROUND(COALESCE((hr.usage_json->>'output_tokens')::numeric,0)/1000,0) as output_k, hr.started_at::timestamp(0) FROM heartbeat_runs hr WHERE hr.agent_id = '{{AGENT_ID}}' AND hr.status = 'succeeded' AND hr.usage_json IS NOT NULL ORDER BY hr.started_at DESC LIMIT 3; ``` Se existirem erros "too long" → forçar rotação: ```sql DELETE FROM agent_task_sessions WHERE agent_id = '{{AGENT_ID}}' AND company_id = 'ebe10308-efd7-453f-86ab-13e6fe84004f'; ``` ### Passo 7c: Validar instructionsFilePath (INC-07) O Paperclip **não usa `cwd` para resolver `instructionsFilePath`** — o path é sempre relativo ao processo do servidor, não ao CWD do agente. Deve ser **sempre absoluto**. ```sql SELECT adapter_config->>'instructionsFilePath' as instructions_path FROM agents WHERE id = '{{AGENT_ID}}'; ``` **Alertar se:** - `instructionsFilePath` não começa com `/` → **CRITICO: path relativo, AGENTS.md não carrega** - Path absoluto mas ficheiro não existe → CRITICO **Corrigir path relativo:** ```sql UPDATE agents SET adapter_config = jsonb_set( adapter_config, '{instructionsFilePath}', to_jsonb('/media/ealmeida/Dados/Hub/04-Stack/02.06-Clip/' || (adapter_config->>'instructionsFilePath')) ) WHERE id = '{{AGENT_ID}}' AND adapter_config->>'instructionsFilePath' NOT LIKE '/%'; ``` ## Wakeup manual de agente Para forcar um agente a acordar imediatamente (ex: testar, desbloquear): ```sql DO $$ DECLARE wakeup_id uuid; BEGIN INSERT INTO agent_wakeup_requests (company_id, agent_id, source, trigger_detail, reason, payload, status, requested_at, created_at, updated_at) VALUES ('ebe10308-efd7-453f-86ab-13e6fe84004f', '{{AGENT_ID}}', 'on_demand', 'manual', '{{RAZAO}}', '{}'::jsonb, 'queued', NOW(), NOW(), NOW()) RETURNING id INTO wakeup_id; INSERT INTO heartbeat_runs (company_id, agent_id, invocation_source, trigger_detail, status, wakeup_request_id, context_snapshot, created_at, updated_at) VALUES ('ebe10308-efd7-453f-86ab-13e6fe84004f', '{{AGENT_ID}}', 'on_demand', 'manual', 'queued', wakeup_id, '{"wakeReason": "manual_wakeup"}'::jsonb, NOW(), NOW()); END $$; ``` **Nota:** UPDATE directo em issues nao dispara wakeOnDemand — e preciso o par wakeup_request + heartbeat_run. ## Criar agente novo via SQL (checklist OBRIGATÓRIA) Ao criar agente directamente na BD (fora do fluxo OpenClaw), executar TODOS os passos: 1. `INSERT INTO agents (...)` — dados do agente, incluindo: - `adapter_type: 'process'` - `adapter_config`: campos obrigatórios: `model`, `instructionsFilePath` (absoluto!), `instructionsRootPath`, `instructionsEntryFile: "AGENTS.md"`, `instructionsBundleMode: "external"` - Para agentes autónomos: adicionar `dangerouslySkipPermissions: true`, `cwd`, `timeoutSec` 2. `INSERT INTO company_memberships (...)` — **sem isto, permissões não funcionam** (agentes criados via SQL não recebem membership automática) 3. `INSERT INTO principal_permission_grants (...)` — se precisa de `tasks:assign` (C-Level, Directores) 4. Criar `AGENTS.md` no path absoluto definido em `instructionsFilePath` 5. Verificar: `instructionsFilePath` não começa com `/` → AGENTS.md não carrega (ver Passo 7c) ## Acções disponíveis Se o utilizador pedir: - **Editar AGENTS.md:** Abrir ficheiro com Read/Edit - **Alterar config:** `UPDATE agents SET runtime_config = '...' WHERE id = '{{AGENT_ID}}';` - **Pausar:** `UPDATE agents SET status = 'paused', pause_reason = '...', paused_at = NOW() WHERE id = '{{AGENT_ID}}';` - **Despausar:** `UPDATE agents SET status = 'idle', pause_reason = NULL, paused_at = NULL WHERE id = '{{AGENT_ID}}';` - **Wakeup:** Usar bloco SQL acima (wakeup_request + heartbeat_run) - **Corrigir permissoes:** Usar UPDATE adapter_config acima - **Atribuir skill:** `curl -s -X POST http://localhost:3100/api/agents/{{AGENT_ID}}/skills/sync -H "Content-Type: application/json" -H "Authorization: Bearer $PAPERCLIP_API_KEY" -d '{"desiredSkills": ["key1", "key2"]}'` - **Ver skills empresa:** query company_skills acima ### Safety gate: verificacao de dependencias (OBRIGATORIO antes de DELETE/remocao) Antes de apagar ou remover qualquer agente, executar SEMPRE: ```sql SELECT 'budget_policies' as tabela, COUNT(*) as refs FROM budget_policies WHERE scope_type='agent' AND scope_id='{{AGENT_ID}}' UNION ALL SELECT 'budget_incidents', COUNT(*) FROM budget_incidents WHERE scope_type='agent' AND scope_id='{{AGENT_ID}}' UNION ALL SELECT 'heartbeat_runs', COUNT(*) FROM heartbeat_runs WHERE agent_id='{{AGENT_ID}}' UNION ALL SELECT 'issues', COUNT(*) FROM issues WHERE assignee_agent_id='{{AGENT_ID}}' UNION ALL SELECT 'issue_comments', COUNT(*) FROM issue_comments WHERE author_agent_id='{{AGENT_ID}}'; ``` - Se refs > 0 em budget_policies ou budget_incidents: apagar essas refs PRIMEIRO, senao o dashboard fica com "Agent not found" - Se refs > 0 em issues: reatribuir ou fechar as issues antes - Mostrar resultado ao utilizador e confirmar antes de prosseguir - NUNCA apagar agente sem verificar e limpar dependencias Confirmar sempre antes de executar accoes destrutivas. --- ## Healing Log Registo de erros conhecidos e como evitá-los. Lido automaticamente antes de executar. ```jsonl {"date":"2026-04-07","issue":"API skill attribution sem Authorization header — chamada rejeitada com 401","fix":"Adicionar -H 'Authorization: Bearer $PAPERCLIP_API_KEY' ao curl POST /api/agents/:id/skills/sync","source":"auto"} {"date":"2026-04-07","issue":"Agente criado via SQL sem membership ficou com Board access required em todos os endpoints","fix":"Sempre inserir em company_memberships após INSERT em agents. Agentes via SQL não recebem membership automática — só via fluxo OpenClaw/hiring","source":"auto"} {"date":"2026-04-07","issue":"instructionsBundleMode em falta na adapter_config — AGENTS.md não carregava","fix":"Incluir instructionsBundleMode: 'external' + instructionsRootPath + instructionsEntryFile: 'AGENTS.md' na adapter_config","source":"auto"} ``` *Adicionar nova linha após cada erro corrigido.*