Habilidade: Aperture
Resumo — Uma habilidade para agentes de IA que desejam produzir relatórios diários melhores e descobrir mais do que seus usuários realmente querem. Aperture examina as páginas iniciais das fontes, compara-as com o dia anterior, pré-seleciona com um perfil ponderado, revisa com um LLM, deduplica, publica e evolui o perfil a partir do feedback do usuário. Todas as decisões vão para um log append-only (tape).
Esta habilidade é agnóstica em relação à implementação. Um agente pode executá-la lendo este arquivo, chamando a implementação de referência em Python incluída, ou portando os padrões para seu próprio runtime.
1. Propósito
Dar aos agentes uma habilidade determinística e auditável de curadoria de notícias que substitui leitores de RSS estáticos e sumarizadores únicos de LLM:
- Examinar a página inicial — tratar "ler a página inicial" como uma operação de primeira classe, não apenas como polling de feeds.
- Gravar cada decisão — registrar cada snapshot de fonte, item, motivo de rejeição, versão de perfil e relatório em um log append-only para auditoria e replay.
- Aprender com o feedback do usuário — atualizar o gosto do agente através de feedback explícito e manter cada alteração versionada e reversível.
Quando um agente executa esta habilidade, ele pode responder "por que isso foi selecionado?" e "o que teria sido diferente ontem?" a partir do tape.
2. Conceitos centrais
| Conceito | Significado |
|---|---|
| Vertical | Um segmento de notícias configurado (ex.: tecnologia, automóveis, política de IA). Cada vertical tem suas próprias fontes, perfil e tape. |
| Tape | Log JSONL append-only. Um arquivo por vertical. Cada snapshot de fonte, item, versão de perfil, feedback e relatório é um registro. |
| Perfil | O "gosto" da vertical: palavras-chave ponderadas, categorias com bônus e termos negativos. Versionado; cada alteração é registrada. |
| Varredura | Buscar a página de listagem de cada fonte, extrair títulos/links, normalizar URLs, comparar com o snapshot anterior. |
| Loop de reflexão | Feedback do usuário → operações de perfil analisadas → incremento de versão → registro de evolução → reavaliação de itens recentemente coletados. |
3. Pipeline de quatro estágios
┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ coleta │ -> │ edição │ -> │ revisão │ -> │ publicar│
│(varredura│ │(pré-triagem│ │ (LLM) │ │(dedup + │
│ │ │ regras) │ │ │ │ relatório│
└─────────┘ └──────────┘ └─────────┘ └──────────┘
│ │ │ │
└───────────────┴───────────────┴───────────────┘
│
TAPE append-only
3.1 Coleta — examinar a página inicial
Objetivo: produzir os itens candidatos de hoje de cada fonte configurada.
Passos:
- Para cada fonte, buscar
list_url. - Extrair pares
{titulo, url}usando oextract_profileda fonte:rss— analisar blocos<item>.generic_links— analisar tags<a href>.regex— aplicar uma regex com grupos nomeados para título/url/data.json_api— navegar por um caminho JSON e ler as chaves de título/url.
- Normalizar cada URL:
- Remover fragmentos e parâmetros de rastreamento (
utm_*,fbclid, etc.). - Remover a barra no final e o
www.inicial. - Forçar o esquema
https, host em minúsculas.
- Remover fragmentos e parâmetros de rastreamento (
- Comparar com o snapshot da página inicial anterior para esta fonte (lido do tape).
- Novos valores de
url_normtornam-se candidatos. - Salvar o snapshot completo atual como um novo registro de
frontpageno tape.
- Novos valores de
- Atualizar a saúde da fonte:
- Sucesso → resetar
fail_count. - Falha → incrementar
fail_count; alertar se ≥ 3 falhas consecutivas.
- Sucesso → resetar
Por que comparar com a página inicial em vez de analisar datas de publicação? Porque muitas fontes têm timestamps não confiáveis ou layouts anti-scraping. "Novo na página inicial" é um proxy robusto para "notícias na janela de hoje".
Guarda de janela de tempo (determinístico, não LLM):
Para fontes pull que expõem pubDate (RSS/Atom), o scanner aplica um corte rígido antes da comparação. A janela padrão é de 36 horas; substituível com extract_profile.window_hours. Itens mais antigos que a janela são descartados. Itens com datas ausentes ou não analisáveis são excluídos por padrão (missing_date_policy: exclude) porque uma data que o mecanismo não consegue verificar é uma data em que ele não pode confiar. Defina missing_date_policy: include apenas para fontes onde as datas são sabidamente não confiáveis e a comparação de página inicial é o sinal primário.
Esta regra existe porque a aplicação da janela de data é contabilidade, não julgamento. Ela deve ser determinística e auditável — nunca delegada a um LLM, que é propenso a ler datas incorretamente e misturar histórias antigas no relatório de hoje.
3.2 Edição — pré-selecionar com o perfil
Objetivo: pontuar candidatos com regras baratas; manter o funil amplo.
Fórmula:
score = soma(pesos de palavras-chave correspondentes)
+ soma(bônus de correspondência de categoria)
- soma(pesos negativos correspondentes)
Passos:
- Carregar o perfil mais recente da vertical.
- Para cada título candidato:
- Corresponder palavras-chave e categorias (limite de palavra inteira para termos ASCII).
- Subtrair termos negativos.
- Registrar
last_hitpara qualquer termo correspondente.
- Limiar: pontuação padrão
>= 2passa; pontuações menores são rejeitadas comreject_reason: low_score. - Escrever cada candidato no tape como um registro
item:- Aprovado →
stage: prescreened. - Rejeitado →
stage: rejected, comreject_reason.
- Aprovado →
Itens rejeitados são o combustível para calibração. Nunca os delete.
3.3 Revisão — segunda passagem estruturada
Objetivo: verificar se os itens pré-selecionados são notícias reais, no tópico e não anúncios/empregos.
Passos:
- Ordenar itens pré-selecionados por pontuação (decrescente).
- Limitar a representação por fonte (ex.: máx. 4 por fonte) para evitar monopólio.
- Para cada item, buscar o texto do artigo (primeiros ~1000 caracteres) e extrair uma data de publicação através de uma cadeia de fallback:
- JSON-LD
datePublished <meta article:published_time>
- JSON-LD
- Enviar um prompt em lote para o provedor de LLM configurado. Perguntar para cada item:
is_news— não é anúncio/emprego/comunicadois_ad,is_job— desqualificadoresvertical_fit— pontuação de relevância de 0 a 1summary— resumo de 2 a 3 frases
- Aplicar motivos de rejeição:
not_news,is_ad,is_job,low_fit.
- Escrever resultados no tape como registros
itemcomstage: verifiedoustage: rejected.
Se nenhum provedor de LLM estiver configurado: o pipeline executa no modo --dry apenas com regras e promove itens pré-selecionados diretamente.
3.4 Publicar — deduplicar e relatar
Objetivo: colapsar duplicatas e produzir um relatório diário legível.
Passos:
- Carregar itens recentemente agrupados (últimos 14 dias, excluindo hoje).
- Dedup de dois níveis para itens verificados:
- Correspondência exata de
url_norm→ pular. simhashde título com distância de Hamming ≤ 3 → mesmo evento; agrupar em um cluster.
- Correspondência exata de
- Para cada cluster, escolher o item com maior pontuação como item principal; os outros tornam-se fontes relacionadas.
- Ordenar clusters por contagem de fontes e pontuação.
- Formatir o relatório. Anexar um rodapé de status:
- fontes examinadas, itens pré-selecionados, rejeitados, agrupados, contagem de itens do relatório
- alertas de saúde da fonte
- tempos dos estágios
- Escrever um registro
reportno tape.
4. Loop de reflexão (auto-evolução)
Gatilho: o usuário lê um relatório e dá feedback, ex.:
"Mais histórias de segurança de IA, menos posts patrocinados e fique de olho na regulação da UE."
Passos:
- Analisar o feedback em operações de perfil:
add_keyword: {termo, peso}adjust_weight: {termo, delta}remove_keyword: {termo}add_negative: {termo, peso}adjust_negative_weight: {termo, delta}remove_negative: {termo}
- Aplicar operações ao perfil; incrementar versão.
- Escrever um registro
evolutionno tape contendo as operações e o texto de feedback original. - Reavaliar itens recentemente agrupados (últimos 7 dias) em relação aos novos termos negativos/pesos reduzidos.
- Se algum agora seria filtrado, relatar a contagem ao usuário.
- Retornar um resumo de confirmação.
Regra de decaimento (semanal):
- Termos com
origin: learnedque não tiveram hit por 30 dias perdem 1 de peso. - Peso 0 → mover para uma lista de exclusão pendente; perguntar ao usuário antes de remover.
- Termos com
origin: manualnunca são auto-excluídos, apenas avisam.
5. Taxonomia de aquisição de fontes
Uma fonte pode ser adquirida de cinco maneiras. O mecanismo as trata uniformemente uma vez que chegam ao tape. O registro de fontes do relatório renderiza cada fonte como status [nome](url) · método para que um leitor possa ver rapidamente como a fonte é adquirida.
| Tipo | Mecanismo | Quando usar | Exibição no registro |
|---|---|---|---|
| pull | RSS/API/curl em cron | Feeds e APIs estáveis | 🟢 [Hacker News](https://news.ycombinator.com/rss) · pull |
| scan | Comparação de página inicial + pré-seleção | SPAs, sites irregulares | 🟢 [TechCrunch](https://techcrunch.com) · scan |
| push | Webhook/SSE/bridge | Alertas internos ou ecossistemas fechados | 🟢 [Alertas Internos](...) · push |
| search | Busca ad-hoc | Descoberta de novas fontes ou atualização de grandes eventos | 🟢 [Busca](...) · search |
| human-feed | Usuário envia um link | Dicas exclusivas e sinais de primeira mão | 🟢 Dica Weixin · human-feed |
Adicione uma fonte registrando {id, nome, list_url, extract_profile} na configuração da vertical. Uma fonte human-feed define extract_profile = { method = "human_feed" } e pode omitir list_url.
Human-feed facilitado pelo agente
Uma variante útil é o human-feed facilitado pelo agente: o próprio agente realiza uma busca na plataforma (ex.: no X por posts sob uma hashtag de IA nas últimas 24–36 horas), seleciona ≥1 candidato e o injeta através do canal human-feed. O item injetado ainda passa pelos mesmos estágios de pré-seleção, revisão e dedup que os itens pull/scan — o human-feed garante entrada no pool de candidatos, não passagem livre para o relatório.
"Nunca pare na mensagem"
Um post de mídia social, manchete ou dica é um sinal, não uma citação. Cada item injetado deve resolver para a URL do artigo original antes de chegar ao tape. Se o agente só conseguir encontrar uma URL de post, scripts/x_hunt.py tenta uma busca de melhor esforço pelo artigo original; se falhar, o candidato é descartado e o motivo é registrado no tape. O relatório publicado nunca aponta os leitores para um post ou manchete como fonte final.
Implementação de referência (scripts/x_hunt.py)
scripts/x_hunt.py é contabilidade determinística: ele escreve um item já selecionado no tape. A busca, leitura e seleção são o trabalho de julgamento do agente, realizados com as próprias ferramentas de busca na web/ferramentas do agente.
# Caminho preferido: o agente já resolveu a manchete para a URL do artigo original.
python scripts/x_hunt.py --vertical ai-frontier --source-id ainativef_zh \
--title "OpenAI anuncia GPT-5" \
--url "https://openai.com/blog/introducing-gpt-5" \
--inject
# Se apenas uma URL de post do X estiver disponível, o script tenta resolver a URL original.
python scripts/x_hunt.py --vertical ai-frontier --source-id ainativef_zh \
--title "OpenAI anuncia GPT-5" \
--url "https://x.com/OpenAI/status/1234567890" \
--inject
# Fallback de busca automatizada de melhor esforço (frequentemente bloqueada; use com moderação).
python scripts/x_hunt.py --vertical ai-frontier \
--query "IA inteligência artificial site:x.com" --inject
# Revisar candidatos de fallback de busca sem injetar
python scripts/x_hunt.py --vertical ai-frontier \
--query "IA inteligência artificial site:x.com"
O registro injetado é escrito no tape como um item com:
source_id: o id da fonte registrada (padrãoowner_tips).stage:scanned— ele entra no pipeline na pré-seleção.facilitated_by:agent— proveniência auditável.
Registros descartados (URLs de post não resolvíveis, candidatos fora do tópico, etc.) também são anexados ao tape com stage: dropped e um drop_reason, para que a trilha de auditoria inclua o que não foi incluído.
Separação de responsabilidades: julgamento (busca/leitura/seleção/resolução de origem) pertence ao agente; contabilidade (normalizar URL, alocar id, anexar ao tape) pertence ao script. Isso mantém a habilidade determinística e deixa estratégias de recuperação específicas da plataforma para as próprias capacidades do agente.
Este modo transforma a plataforma de uma fonte de conteúdo passiva em uma caça ativa de descoberta de fontes: quando o agente repetidamente encontra boas histórias do mesmo veículo, o ECHO pode perguntar "Adicionar '<veículo>' como uma fonte rastreada?" e, em caso afirmativo, promover o veículo a uma fonte pull/scan formal. Esse loop fechado é a auto-evolução da habilidade em ação.
6. Tipos de registro do tape
Cada linha do tape é um objeto JSON com um campo type.
| type | campos |
|---|---|
source | id, name, vertical, list_url, extract_profile, health |
frontpage | source_id, date, items[], count |
profile | vertical, version, keywords[], negatives[], categories[], reason |
item | id, vertical, source_id, title, url, url_norm, stage, scores, reject_reason, simhash, cluster_id |
report | date, vertical, item_ids[], generated_at, stats |
feedback | date, vertical, text, applied_ops[], status |
evolution | vertical, from_version, to_version, ops[], reason, date |
7. Executando a implementação de referência
# Execução apenas com regras (sem LLM)
python -m engine.pipeline --dry --vertical tech --config config/example_vertical.toml
# Com revisão LLM
export APERTURE_LLM_API_KEY="sk-..."
python -m engine.pipeline --vertical tech --config config/example_vertical.toml
# Aplicar feedback
python -c "from engine.feedback import apply_feedback; apply_feedback('Mais segurança de IA, menos anúncios', 'tech')"
Após a publicação, opcionalmente execute o ECHO para fazer ao usuário até duas perguntas de esclarecimento:
from engine.echo import ask
questions = ask("tech")
for q in questions:
print(q["question"])
# Depois, aplicar uma resposta de uma palavra: yes / no / skip / silence
from engine.echo import apply_answer
apply_answer(question_id, "tech", "yes")
8. ECHO — esclarecimento proativo
O ECHO é uma camada proativa opcional que faz ao usuário até duas perguntas de esclarecimento de uma palavra após cada relatório. Ele transforma leitores passivos em treinadores ativos de perfil.
O ECHO é intencionalmente …
[truncado para tradução]