Objetivos de aprendizagem
Ao final deste capítulo, você deve ser capaz de:
- Explicar por que extensibilidade é "aberto para extensão, fechado para modificação": extension points em vez de fork;
- Distinguir os quatro eixos de extensão (hooks · comandos/skills · plugins · provedores) e o que cada um resolve;
- Comparar as três estratégias de ecossistema: profundidade, empacotamento, interoperabilidade;
- Avaliar o código de extensão como superfície de ataque (o trust triangle) e as defesas (scan, trust envelope, managed settings, least-privilege);
- Implementar um subsistema de hooks pre/post-tool com o retorno do hook como canal de controle no harness-zero (etapa 11).
O fork que ficou três versões atrás
A equipe queria duas coisas simples. Que toda edição passasse pelo formatador antes de gravar, e que ninguém rodasse terraform apply a partir do agente.
O harness não tinha como fazer nenhuma das duas. Alguém então fez o óbvio: forkou.
Funcionou por três semanas. No segundo mês, o upstream lançou a correção de uma fuga de sandbox, e o merge deu conflito em quatro arquivos. Alguém resolveu na pressa. No terceiro mês veio a mudança do formato de sessão, e o merge deu conflito em onze. Ninguém resolveu.
Hoje o fork está três versões atrás, com uma correção de segurança que nunca chegou. As duas regras que motivaram tudo continuam lá, funcionando, presas a um harness que ninguém consegue mais atualizar.
O erro não foi forkar. Foi o harness não ter oferecido uma fronteira estável onde aquelas duas regras coubessem. Extensibilidade é isso: o conjunto de pontos em que terceiros mudam o comportamento sem tocar no código, e sem os quais o fork é a única saída.
O problema
Nenhum harness cobre todos os fluxos de trabalho; a extensibilidade decide se o usuário adapta o harness ou o abandona. Os eixos consagrados:
- Hooks: código do usuário interceptando o ciclo de vida (antes/depois de tool, compactação, sessão).
- Skills / comandos custom: capacidades empacotadas como markdown/config, carregadas sob demanda.
- Plugins / extensions: pacotes distribuíveis agregando tools, comandos, hooks e config.
- Provedores de modelo: a extensão mais estratégica: o harness funciona com qualquer modelo, ou é vitrine de um?
A regra que une os quatro é antiga: aberto para extensão, fechado para modificação, o usuário estende sem editar (nem forkar) o core.
Fundamentos científicos
Registro editorial honesto (Princípio I): não existe canon acadêmico de "extensibilidade de harness de agente", é uma lacuna real. As citações duráveis vêm da engenharia de software clássica de arquiteturas extensíveis e da segurança de ecossistemas de plugin, que transferem diretamente.
- Extension points, não fork: o princípio aberto-fechado (Meyer, 1988. Martin, 1996) e a arquitetura de plug-ins do Eclipse (Birsan, ACM Queue 2005) dão a fundação, e a advertência do "plug-in hell": pontos de extensão mal desenhados viram dívida. Decisão: exponha seams explícitos (eventos, diretórios conhecidos), não pontos ad-hoc.
- Núcleo mínimo, extensões plugáveis: o padrão Microkernel (Buschmann et al., POSA v.1, 1996) e sua encarnação agêntica, AIOS, arXiv 2403.16971 (um kernel que isola escalonamento/memória/tools das aplicações-agente), sustentam a postura "harness como microkernel": um core pequeno que serve de soquete.
- Mecanismo × política: Hydra (Levin et al., SOSP '75) é a origem de "separar mecanismo de política". Traduzido: o harness fornece o mecanismo (invocar tool, despachar hook, carregar provedor); a extensão fornece a política. É por isso que adicionar um provedor de modelo pode ser "escrever um arquivo".
- Extensão de terceiros não é confiável: a melhor citação on-topic é LLM (Large Language Model) Platform Security: ChatGPT Plugins, arXiv 2309.10254 (AIES '24): um trust triangle plataforma/plugin/usuário com exploits concretos (sequestro de sessão via plugin malicioso). E a base empírica de over-privilege vem da segurança de extensões de browser (Barth et al., NDSS '10: 88% das extensões pedem mais poder do que precisam). Decisão: least-privilege + isolamento + verificação, o mesmo argumento do tool poisoning do cap. 06.
(Bibliografia completa e ponteiros: livro/bibliografia.md.)
Fontes da indústria
- Hooks: exit code como canal de controle: os hooks do Claude Code expõem ~31 eventos de ciclo de vida (
PreToolUse,PostToolUse,Stop,SessionStart,UserPromptSubmit,PreCompact,SubagentStop…) onde o harness executa comandos do usuário. O exit code é o canal (0 = segue / JSON no stdout com allow-deny-ask; 2 = bloqueia com stderr realimentado ao modelo). Decisão: times impõem política (bloquearrm, redigir.env, auto-lint) de forma determinística e sem patchar o harness. E o Codex implementa o mesmo padrão de forma independente (hooks +allow_managed_hooks_onlypara empresas), hooks são padrão cross-vendor, não peculiaridade de um fornecedor. - Plugin = unidade de empacotamento. Marketplace = catálogo: o modelo de plugins do Claude Code: um plugin agrega skills, subagentes, hooks, MCP (Model Context Protocol) e LSP (Language Server Protocol) num pacote instalável (
/plugin install nome@marketplace). Um marketplace é um repo git com.claude-plugin/marketplace.json. Instala em escopo user/project/local/managed, com pin a SHAs e um modelo de confiança em dois níveis (marketplace oficial curado + comunidade com triagem de segurança). Decisão: extensão de terceiros vira distribuível e governável sem fork. - Comandos custom viraram file-drop (e AGENTS.md é o padrão aberto): no Claude Code, os comandos slash foram absorvidos pelas skills: largar um arquivo em
.claude/commands/ou.claude/skills/cria o comando, sem registro nem build. E o AGENTS.md virou o formato de config aberto e multi-tool, lido por Codex, Cursor, Cline, Windsurf, Gemini CLI e Claude Code. Decisão: o ponto de extensão é "largue um arquivo num diretório conhecido", e o formato é portável entre harnesses. - Settings como superfície de enforcement: a config do Claude Code é uma pilha de precedência (Managed >. CLI > local > project > user); a maioria das chaves sobrescreve, mas regras de permissão fazem merge, e as managed settings não podem ser sobrescritas (uma equipe de segurança nega tools/marketplaces para toda a empresa). Decisão: config é enforcement, não preferência (liga ao cap. 07).
- Extensibilidade é também orçamento de contexto: o advanced tool use (Anthropic) reenquadra: com bibliotecas ilimitadas de tools, a extensão precisa ser carregada sob demanda, não registrada de antemão. E plugins se ligam/desligam para controlar o custo de system prompt. Decisão: um ponto de extensão que sempre injeta contexto não escala, o carregamento tardio é parte do design (liga aos caps. 03 e 05).
- Consulte também: a coleção viva Awesome Harness Engineering: Debugging & Developer Experience reúne mais recursos consultáveis desta dimensão (padrões, artigos e implementações), curados por problema.
Na prática: o hook cujo retorno é o canal de controle
Um hook pobre é um observador: recebe o evento, faz o que quiser, e o harness segue. Serve para log e para nada mais.
Um hook útil decide, e o mecanismo que o torna útil é o retorno:
@hooks.pre_tool
def formatar_antes_de_gravar(chamada: Chamada) -> Chamada | str | None:
"""Contrato do retorno:
None -> segue como está
Chamada -> segue com os argumentos REESCRITOS
"block:motivo" -> não executa, e o motivo volta ao modelo como dado
"""
if chamada.nome != "editar":
return None
if chamada.args["path"].endswith(".py"):
formatado = black.format_str(chamada.args["conteudo"], mode=black.Mode())
return chamada.com(args={**chamada.args, "conteudo": formatado})
return None
@hooks.pre_tool
def proibir_terraform(chamada: Chamada) -> str | None:
if chamada.nome == "shell" and "terraform apply" in chamada.args["comando"]:
return "block:terraform apply é feito por pipeline, não pelo agente"
As duas regras da cena da abertura, em quinze linhas, sem fork.
Repare no que o block: faz: o motivo volta ao modelo como dado, exatamente como o erro de tool do cap. 05 e a recusa de plan mode do cap. 09. Um hook que bloqueia em silêncio produz um agente que tenta de novo com aspas diferentes.
E repare em onde o hook mora: na fronteira entre o loop e a execução da tool. Não é um patch dentro do loop, é um ponto declarado. Por isso o autor do hook não precisa saber como o loop funciona, e o autor do loop pode reescrevê-lo sem quebrar o hook.
Falta a conta a pagar, e ela é a razão de este capítulo terminar no cap. 07:
# O hook roda com a autoridade do harness. Ele pode ler o argumento de
# qualquer tool -- inclusive o conteúdo de arquivos e as credenciais que
# passarem por ali -- e pode reescrevê-lo antes da execução.
Um plugin de terceiro instalado por uma linha de configuração tem, por construção, o mesmo alcance que o código que você escreveu. É a mesma tese do apêndice de supply chain, e é por isso que o registro de plugins é vetor de ataque e não detalhe de empacotamento.
# LACUNA (etapa 11): escreva o auditor. Ele registra em auditoria.jsonl toda
# chamada e todo veredito de hook, com timestamp e origem do plugin -- porque
# extensibilidade sem trilha é a superfície do cap. 07 sem sensor.
@hooks.post_tool
def auditar(chamada: Chamada, resultado: str) -> None:
...
O estado da arte
1. Três estratégias de ecossistema
A moldura da rodada 1 persiste e ganhou reforço. Profundidade: os hooks alcançam pontos que os outros não expõem, o opencode transforma mensagens e system prompt antes do envio, intercepta permission.ask e registra provedores de auth. Empacotamento: a extension como unidade de distribuição completa (gemini-cli agrega MCP+comandos+hooks+políticas num pacote; Codex com manifest + marketplace + App Server JSON-RPC (Remote Procedure Call)). Interoperabilidade: adotar os formatos do líder em vez de inventar os próprios (OpenHarness com SKILL.md/.claude-plugin; IronClaw com SKILL.md compatível).
2. A aposta da interoperabilidade está vencendo, o "MCP da extensibilidade"
O que na rodada 1 era o eixo mais subestimado virou tendência dominante: os formatos de extensão estão convergindo em padrões portáveis entre harnesses. SKILL.md/AgentSkills (o OpenClaw usa o padrão agentskills.io. O IronClaw declara compatibilidade com OpenClaw/Claude), .claude-plugin (adotado pelo OpenHarness) e sobretudo o AGENTS.md (lido por seis harnesses diferentes) estão fazendo pela extensibilidade o que o MCP fez pela integração. Até o vocabulário de hooks convergiu, o conjunto de eventos do Codex é praticamente o do OpenHarness e o do Claude Code (PreToolUse/PostToolUse/… com decisões Approve/Block/Deny/Ask). A extensibilidade está deixando de ser silo por harness.
3. Marketplaces e scan de segurança, a lacuna da rodada 1 fechou
Na rodada 1, só o gemini-cli tratava código de extensão como superfície de ataque. Na rodada 2 isso virou norma, exatamente como o trust triangle de plugins previa: o OpenClaw tem o registry ClawHub com trust envelope + scan (VirusTotal/ClawScan). O Claude Code tem marketplace oficial curado + comunidade com triagem de segurança e pin a SHA; o n8n roda scan-community-package; o Goose verifica malware de extensões antes de carregar. Somado às managed settings que negam marketplaces enterprise-wide, a distribuição de extensões virou infraestrutura com contenção, o least-privilege que a literatura de over-privilege pede.
4. Provider-agnosticism virou config declarativa
A separação mecanismo × política aplicada ao modelo: adicionar um provedor deixou de ser código e virou arquivo. O Goose tem 37 provedores declarativos por JSON (um provider OpenAI-compatible = um arquivo). O opencode tem ~26 loaders + centenas de modelos via models.dev; o Hermes tem ProviderProfile subclassável (Nous Portal com 300+ modelos). O harness agnóstico de modelo (que trata o provedor como política plugável) venceu a vitrine de um fornecedor só.
5. A próxima fronteira: o harness que se estende sozinho
O embrião da auto-extensão já aparece: o IronClaw tem extração automática de skills (learning.rs) com métricas de uso e confiança, o harness observa o próprio trabalho e escreve skills novas. É a ponte com o cap. 16 (aprendizado) e com a linhagem Voyager/ToolMaker: extensibilidade que não espera o usuário.
Leitura executiva
O que está mais moderno: a convergência de formatos (SKILL.md/.claude-plugin/AGENTS.md como padrões portáveis). Marketplaces com scan de segurança e managed settings; hooks com exit-code como canal cross-vendor; provider-agnosticism declarativo; e o começo da auto-extensão. O que roubar: exponha seams explícitos (eventos nomeados, diretórios conhecidos) em vez de pontos ad-hoc. Adote formatos portáveis em vez de inventar os seus. Trate extensão de terceiros como não-confiável (scan + least-privilege + managed deny); e faça o carregamento ser tardio para não estourar o contexto.
Mão na massa, harness-zero, etapa 11
A etapa 11 (harness-zero/etapas/11-hooks/) dá ao harness-zero um subsistema de hooks pre/post-tool: antes de cada chamada de tool, hooks são funções registradas (@hooks.pre_tool/@hooks.post_tool) e o retorno do hook é o canal de controle ("block:motivo" bloqueia e realimenta o motivo ao modelo. Um dict ajusta os argumentos), o exercício de completude propõe a variante externa dos produtos: executar um comando do usuário e ler o exit code (0 segue; não-zero bloqueia com o stderr). É o mecanismo (o harness despacha o hook) separado da política (o usuário decide o que o hook faz), a tese do capítulo em ~40 linhas. Exercício de completude: você adiciona um PostToolUse que roda um linter e devolve os erros ao modelo. Um gate de confiança mínimo (o hook só roda se o diretório for confiável).
Verificação
- Por que "aberto para extensão, fechado para modificação" leva a hooks e plugins em vez de instruir o usuário a forkar o harness?
- Você vai permitir um marketplace de plugins de terceiros. Cite o risco central (com o nome da literatura) e duas defesas concretas. (Trust triangle / over-privilege; defesas: scan de segurança + pin a SHA + managed settings que negam + least-privilege.)
- Seu harness precisa suportar um novo provedor de modelo sem release. Que princípio de design torna isso "escrever um arquivo"?
Apêndice A — Como cada repositório trata a extensibilidade
Evidência por harness, com paths — complementação online, expandida a cada rodada.
opencode (rodada 1) — hooks profundos e agnosticismo radical de provedor
Plugins são funções que retornam Hooks (packages/plugin/): ~15 pontos, incluindo raros — transformar mensagens/system prompt antes do envio (experimental.chat.messages.transform), interceptar permission.ask, customizar compactação e registrar provedores de auth (auth). Tools custom auto-carregadas de tool/. E ~26 loaders de provedor + centenas de modelos via models.dev, sobre o Vercel AI SDK (Software Development Kit) — o mais agnóstico de modelo em produção.
gemini-cli (rodada 1) — o pacote tudo-em-um
Extensions (gemini-extension.json): um pacote instalável agrega MCP servers, comandos custom, hooks, políticas de permissão, skills e temas. Comandos custom em TOML (FileCommandLoader). Hooks como subsistema (packages/core/src/hooks/) com gate de confiança (trustedHooks.ts — só rodam em pastas confiáveis). Provedores: ecossistema Google.
OpenHarness (rodada 1) — compatibilidade como estratégia
Skills em markdown carregadas também de ~/.claude/skills e ~/.agents/skills (layout SKILL.md); plugins no formato .claude-plugin/plugin.json (12 plugins reais testados); hooks cobrem 10 eventos com hot-reload. Provedores como "workflows" nomeados (Anthropic/OpenAI-compatible, Copilot, Kimi, GLM, Ollama…).
Codex CLI (rodada 2) — hooks completos + marketplace + App Server
Hooks completos (hooks/: PreToolUse/PostToolUse/PreCompact/SessionStart-End/UserPromptSubmit/Stop/SubagentStart-Stop, decisões Approve/Block/Deny/Ask) e knob enterprise allow_managed_hooks_only; plugins com manifest e marketplace; skills; provedores configuráveis; profiles; SDKs Python/TS; App Server JSON-RPC como espinha dorsal programática.
OpenClaw (rodada 2) ⭐ — registry com scan de segurança
Skills no padrão AgentSkills (agentskills.io) com 6 níveis de precedência e registry público ClawHub com trust envelope + scan (VirusTotal/ClawScan); 159 plugins (tools, canais, provedores, hooks, mídia) com Plugin SDK; dezenas de provedores LLM com failover e rotação de auth.
IronClaw (rodada 2) ⭐ — compatível e auto-extensível
Formato SKILL.md compatível com OpenClaw/Claude; skills v2 com snippets executáveis, métricas de uso/confiança e extração automática de skills (learning.rs); extensões via WASM/MCP/first-party sem restart; providers configuráveis (NEAR AI, Gemini OAuth…).
Goose (rodada 2) — provedores declarativos e distros brandeadas
Três eixos: extensões MCP (6 tipos de transporte/origem); recipes/skills; e provedores — nativos + 37 provedores declarativos por JSON (adicionar um provider OpenAI-compatible = criar um arquivo). CUSTOM_DISTROS.md (distros brandeadas); goose-sdk para embutir; verificação de malware de extensões antes do carregamento.
Hermes (rodada 2) — ProviderProfile e plugins
ProviderProfile subclassável (Nous Portal com 300+ modelos sob assinatura, OpenRouter, endpoint próprio); sistema de plugins (20 diretórios, registry de toolsets, hooks de sessão); adaptadores Anthropic/Bedrock/Codex/ACP (Agent Client Protocol).
OpenHands (rodada 2) — marketplaces e injeção de dependência
Marketplaces de skills/plugins (instance/org/personal); LLM + agent profiles; camada de integrações Git plugável; agentes de terceiros via ACP; backends de sandbox/event-store trocáveis por injeção de dependências; litellm para provedores.
n8n (rodada 2) ⭐ — o catálogo como extensibilidade
O ponto mais forte: os 400+ nós de integração viram pool de tools sem escrever código (via usableAsTool + $fromAI); community nodes com scanner de segurança (scan-community-package); ~20 providers de modelo (LmChat*).
ohmo (rodada 2) — raízes extras
~/.ohmo/skills e ~/.ohmo/plugins como raízes coexistindo com as do projeto; plugins carregam tools, slash commands e servidores MCP; skills viram comandos no canal; channel_configs arbitrário por canal.
Frameworks (rodada frameworks)
Os frameworks expõem extensibilidade como API: registro de tools/@tool, callbacks/hooks de ciclo de vida, adaptadores de provedor (litellm/model providers), e — cada vez mais — leitura do AGENTS.md. O formato portável (AGENTS.md, SKILL.md) é o que aproxima frameworks e harnesses de código num ecossistema comum.
Respostas da verificação
1. Porque um hook que só observa não resolve nenhum dos dois problemas que levam ao fork. Quem quer formatar antes de gravar precisa reescrever o argumento; quem quer proibir um comando precisa impedir a execução. Um observador consegue registrar que aconteceu, e é tarde. Fazer do retorno o canal de controle dá as duas capacidades sem inventar API nova: None segue, um objeto reescreve, uma string com prefixo bloqueia. E o bloqueio precisa carregar o motivo, porque o motivo volta ao modelo como dado — sem ele, o agente recebe uma recusa sem explicação e repete a tentativa, que é o mesmo erro de tratar erro de tool como exceção.
2. A fronteira estável é o contrato entre quem estende e quem mantém. Ela precisa de três propriedades. Ser declarada, com nome e assinatura, e não um ponto arbitrário do código. Ser poucos pontos, porque cada ponto de extensão é um compromisso que trava refatoração futura. E não vazar a implementação: o hook recebe a chamada e devolve uma decisão, não recebe o objeto interno do loop. Sem isso, o autor do plugin acopla-se ao seu código, e você volta ao problema do fork com um nome mais bonito. O sinal de que a fronteira está certa é poder reescrever o loop inteiro sem quebrar nenhum hook existente.
3. Que o plugin roda com a autoridade do harness, e não com uma autoridade menor. Ele lê os argumentos de todas as tools, o que inclui conteúdo de arquivo e qualquer credencial que passe por ali, e pode reescrevê-los antes da execução. Instalar um plugin é, portanto, uma decisão da mesma classe que instalar uma dependência de código, e não uma preferência de interface. As defesas são as do cap. 07 aplicadas ao próprio mecanismo de extensão: isolar o plugin quando a linguagem permitir, mediar as credenciais para que ele nunca as veja, fixar a versão em vez de aceitar atualização automática, e auditar — porque extensibilidade sem trilha é superfície de ataque sem sensor.