Criando agents customizados
Personas customizadas estendem o Ongrid com seus próprios specialists. Elas vivem em disco como arquivos <name>.md com YAML frontmatter, exatamente como os built-ins — mesmo loader, mesmo registry, mesmo caminho de dispatch. Escreva uma, monte, e o coordinator pode despachar para ela.
Esta página é o contrato.
Estrutura do arquivo
Uma persona é um único arquivo Markdown com YAML frontmatter:
---
name: specialist-clickhouse
description: ClickHouse 查询性能 / 分区健康 / mutation backlog 专家
when_to_use: |
When the user asks about:
- ClickHouse query plan / scan / shuffle slow
- Partition merges / mutation backlog
- Replication lag between replicas
- System.parts / system.mutations inspection
tools:
- query_knowledge
- query_clickhouse_system # custom BaseTool you registered
- query_promql # for clickhouse_* metrics
- host_bash
- get_edge_summary
disallowed_tools:
- host_restart_service
permission_mode: read-only
max_turns: 12
model: anthropic/claude-sonnet-4-7
critical_reminder: |
You're read-only. Never propose direct ALTER / OPTIMIZE without
citing the system.mutations evidence first. Always check the
replication lag before recommending any maintenance command.
---
# specialist-clickhouse
You are Ongrid's ClickHouse specialist.
## Step 0: knowledge base check (mandatory)
Before any inspection, call `query_knowledge` once with a natural-
language description of the question. Hit (score >= 0.6) → follow
the playbook. Cite as `(参考 KB: <title>)` in your final reply.
## Working style
1. Start with `query_clickhouse_system` for system.parts /
system.mutations / system.replication_queue. One call, broad
snapshot.
2. If a specific table is suspect, drill into `system.parts` for
that table with bytes / rows / merge_state.
3. For replication: `system.replication_queue` for failures,
`clickhouse_replica_delay_seconds` PromQL series for trend.
4. For query perf: `system.query_log` with `query_duration_ms`
sort + `read_rows` to find the heavy query.
## Output
- 现状 (1-2 sentences): which table, which metric, what's wrong.
- 证据 (2-3 lines): system.* row excerpts + PromQL value.
- 建议 (1 line): observation only, or "recommend dispatching
specialist-ops to run OPTIMIZE/ALTER under reviewer".Referência do frontmatter
Os campos que o parser entende (ParseAgentMd):
| Campo | Obrigatório | Tipo | Propósito |
|---|---|---|---|
name | sim | string | Chave de spawn. Deve ser única. snake_case ou kebab-case. |
description | sim | string | Aparece no catálogo de agents do coordinator. |
when_to_use | sim | string | A primeira linha aparece no catálogo. Estritamente obrigatório porque o coordinator não consegue escolher uma persona sem isso. |
tools | não | []string | Whitelist de nomes de BaseTool. Vazio = não herda nada. |
disallowed_tools | não | []string | Blacklist. Vence sobre whitelist; suporta wildcards (*_skill). |
permission_mode | não | string | read-only / mutating-with-confirm / dual-sign-required. Hoje é informativo; versões futuras podem auto-conectar decorators baseando-se nisso. |
max_turns | não | int | Cap rígido do loop ReAct. Padrão 15. |
model | não | string | Identificador do LLM (ex.: anthropic/claude-sonnet-4-7). Cai para o padrão da org. |
critical_reminder | não | string | Envolvido em <critical-reminder>...</critical-reminder> no system prompt. Também re-injetado por turno pela camada de grafo. |
initial_prompt | não | string | Prepended ao primeiro turno de usuário do worker. Raramente usado. |
background | não | bool | true = spawn assíncrono (UI não bloqueia). Usado pelo reviewer. |
omit_claude_md | não | bool | Suprime o prompt base do runtime para essa persona. |
metadata | não | map | Forma livre. metadata.ongrid.{scope, min_ongrid_version} é lido pelo registry; o resto é pass-through. |
Campos desconhecidos são preservados em Agent.UnknownFields para que futuras adições ao formato de persona do Claude Code (effort, isolation, mcp_servers, hooks, …) não quebrem o load.
tools vs disallowed_tools
Whitelist + blacklist, blacklist vence. Então:
tools: ["query_*", "host_bash"] # tudo começando com query_, mais bash
disallowed_tools: ["query_devices"] # mas não essedeixa query_promql, query_logql, query_traceql, query_knowledge, … e host_bash, menos query_devices.
Wildcards: *_skill casa com todo nome de tool terminando em _skill. É assim que o reviewer bloqueia todas as execuções de skill em uma única linha.
O AgentTool também é removido automaticamente da bag de qualquer worker — workers não podem dar spawn em workers. Você não precisa listá-lo em disallowed_tools.
Onde as personas vivem
O runtime percorre duas raízes:
- A raiz da imagem —
/app/agents/dentro do container do manager. Contém as seis personas que vêm de fábrica. Read-only dentro da imagem; sobrevive a restart do container mas não a código custom. - A raiz do marketplace —
/var/lib/ongrid/agents/(volume montado). Personas escritas pelo usuário pousam aqui via a UI Settings → Agents ou via o caminho de instalação do marketplace.
Ambas são mescladas no mesmo AgentRegistry. Em colisão de nome o loader registra um warning e mantém o primeiro load. Para sobrescrever uma persona built-in, salve sua versão com o mesmo name via a UI Settings — AgentRegistry.Replace faz upsert in place.
Por onde começar
O caminho mais rápido é copiar agents/specialist-disk.md no seu editor, renomear, e ajustar a tool bag. O formato carrega todas as convenções (KB-first, receita em 4 passos, formato de saída) que funcionam bem com o coordinator.
Hot reload vs restart
| Ação | Hot-reloadable? | Como |
|---|---|---|
| Editar corpo da persona (system prompt) | Sim | Settings → Agents → Save |
| Mudar whitelist de tools | Sim | Idem. Filtro é aplicado por spawn. |
Mudar model / max_turns | Sim | Idem. Novos spawns pegam os novos valores. |
| Adicionar uma nova persona | Sim | Settings → Agents → New, ou drop do arquivo + Reload |
| Deletar uma persona | Sim | Settings → Agents → Delete, ou remover arquivo + Reload |
Sobrescrever uma built-in (mesmo name) | Sim | Replace faz upsert; coordinator usa a nova. |
| Mudar quais tools existem na bag | Não | Registro de BaseTool é binary-side. |
| Adicionar uma nova BaseTool | Não | Exige mudança de código + restart do manager. |
Mudar semântica de default_locale | Não | Isso é código de runtime. |
O lock em volta do AgentRegistry é um sync.RWMutex. Um turno de coordinator em andamento que já obteve um ponteiro de persona segue usando o snapshot; o próximo turno do coordinator vê a nova persona.
Debugando
"Coordinator nunca despacha para minha persona"
- Cheque o catálogo de agents no system prompt do coordinator (o manager loga o prompt renderizado no startup com
--log-level=debug). Sua persona deve aparecer com seudescriptione a primeira linha dewhen_to_use. - Se o catálogo não a tem: o loader registrou um warning. Cheque
AgentRegistry.Warnings()via API (GET /api/v1/agents/warnings) ou procure linhaschatruntime: parse <path>no log do manager. - Se o catálogo a tem mas o LLM não a escolhe: aperte o
when_to_use. Comece com um padrão concreto de trigger; o LLM é instruído a ler a primeira linha como a dica de match.
"Worker spawna mas falha imediatamente"
Causas comuns:
- Uma tool whitelisted não está na bag. O runtime filtra e silenciosamente descarta qualquer coisa não presente; o worker não consegue chamar o que não existe. Cheque
GET /api/v1/skillspara a bag ativa. - O identificador do modelo está errado. O resolver de chat model mapeia
anthropic/<x>paradefault_providerse não estiver configurado. Definadefault_providercomoanthropicem Settings → LLM ou fixe um provider+model concreto na persona. max_turnsmuito baixo. Um worker que esgota os turns antes de produzir uma mensagem assistant final retorna comofailed. Aumente para 15+ em qualquer persona não-trivial.
"Worker retorna OK mas a saída é lixo"
O corpo da persona é seu system prompt. Aperte:
- Comece com Step 0: uma única chamada KB forçada. Ancora o worker.
- Especifique o formato de saída verbatim no corpo. O coordinator faz parse nesse formato.
- Use
critical_reminderpara restrições duras (read-only, sem PII, idioma de saída). É envolvido em<critical-reminder>E re-injetado por turno — o LLM o vê em cada iteração.
Testando sua persona
Dois pontos de integração:
Pela superfície de chat
Abra /chat, faça uma pergunta que case com o when_to_use da sua persona. Observe o SPA — se o coordinator despachar, um "Agent tile" aparece com o name da sua persona + a description do AgentTool. Clique para o transcript do worker.
Pela API
curl -X POST http://localhost:8080/api/v1/chat \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{"prompt": "<the question that should trigger your persona>"}'A resposta streaming traz à tona:
- deltas de
text— a prosa do coordinator. - envelopes
agent_tile— cada dispatch de AgentTool. - envelopes
task_notification— conclusão de worker.
Se sua persona for despachada, o agent_tile.persona correspondente será o seu name.
Quando NÃO escrever uma persona custom
- A tarefa é responder com 1 tool. Não embrulhe "consultar meu Prometheus customizado" em uma persona. Registre um BaseTool custom e deixe o coordinator chamar.
- A tarefa é one-off. Personas são para padrões repetidos. Para uma investigação one-shot, basta perguntar ao coordinator diretamente.
- A tarefa precisa chamar todos os 5 specialists. Isso é exatamente para o que serve o coordinator; não escreva um meta-specialist que recrie o comportamento do coordinator.
Regra boa: escreva uma persona quando a mesma forma de pergunta recorre, a resposta exige 5+ chamadas de tool, e a tool bag é mais estreita do que a que o coordinator carrega.
Compartilhando personas
- Coloque o arquivo
.mdno seu repoops. Monte-o no container do manager sob/var/lib/ongrid/agents/. O registry pega no startup (ou em uma chamada de Reload). - Para rollout em toda a org, distribua pelo marketplace de skills — a instalação pelo marketplace empacota personas + skills juntos e dispara um
Reloadautomaticamente.
Relacionado
- Visão geral dos agents — o panorama completo.
- Formato de persona — todo campo, sem prosa.
- Manifesto de skill — formato irmão para distribuir personas junto com skills customizadas.