CommunityRédaction et éditiongithub.com

lukethecat/aperture

Une compétence pour les agents IA qui organisent de meilleurs rapports quotidiens — parcourir les pages d'accueil, enregistrer chaque décision, apprendre des retours.

Qu'est-ce que aperture ?

Aperture est une compétence pour agents IA qui vise à produire des rapports quotidiens de meilleure qualité en découvrant ce que les utilisateurs veulent vraiment. Elle scanne les pages d'accueil des sources, les compare avec la veille, les présélectionne à l'aide d'un profil pondéré, les examine via un LLM, déduplique les résultats, publie un rapport et fait évoluer le profil à partir des retours utilisateurs. Toutes les décisions sont enregistrées dans un journal en lecture seule (la « bande »). Conçue pour être agnostique, l'agent peut exécuter la compétence en lisant ce fichier, en utilisant l'implémentation Python de référence ou en adaptant les motifs à son propre environnement. Utile pour les développeurs qui construisent des agents de veille automatisée avec des capacités d'audit et d'apprentissage continu.

Compatible avec~Claude Code✓Codex CLI~Cursor
npx skills add lukethecat/aperture

Installed? Explore more Rédaction et édition skills: steipete/notion, langchain-ai/langchain, bytedance/podcast-generation · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Compétence : Aperture

TL;DR — Une compétence pour les agents IA qui souhaitent produire de meilleurs rapports quotidiens et découvrir davantage ce que leurs utilisateurs veulent réellement. Aperture scanne les pages d'accueil des sources, les compare à la veille, les présélectionne avec un profil pondéré, les examine avec un LLM, déduplique, publie et fait évoluer le profil à partir des retours utilisateurs. Toutes les décisions sont enregistrées dans une bande en lecture seule.

Cette compétence est agnostique du point de vue de l'implémentation. Un agent peut l'exécuter en lisant ce fichier, en appelant l'implémentation Python de référence incluse, ou en portant les motifs dans son propre environnement d'exécution.


1. Objectif

Donner aux agents une compétence de curation d'actualités déterministe et auditable qui remplace les lecteurs RSS statiques et les résumeurs LLM ponctuels :

  1. Scanner la page d'accueil — traiter la « lecture de la page d'accueil » comme une opération de première classe, pas seulement une interrogation de flux.
  2. Enregistrer chaque décision — consigner chaque instantané de source, élément, motif de rejet, version de profil et rapport dans un journal en lecture seule pour audit et rejeu.
  3. Apprendre des retours utilisateur — mettre à jour le goût de l'agent via des retours explicites et conserver chaque modification versionnée et réversible.

Lorsqu'un agent exécute cette compétence, il peut répondre « pourquoi cela a-t-il été sélectionné ? » et « qu'est-ce qui aurait été différent hier ? » à partir de la bande.


2. Concepts fondamentaux

ConceptSignification
VerticalUn domaine d'actualité configuré (ex : tech, auto, politique IA). Chaque vertical possède ses propres sources, profil et bande.
BandeJournal JSONL en lecture seule. Un fichier par vertical. Chaque instantané de source, élément, version de profil, retour et rapport sont un enregistrement.
ProfilLe « goût » du vertical : mots-clés pondérés, catégories avec bonus et termes négatifs. Versionné ; chaque modification est consignée.
ScanRécupérer la page de liste de chaque source, extraire les titres/liens, normaliser les URL, comparer avec l'instantané précédent.
Boucle de réflexionRetour utilisateur → opérations de profil analysées → incrémentation de version → enregistrement d'évolution → revérification des éléments récemment mis en commun.

3. Pipeline en quatre étapes

┌─────────┐    ┌──────────┐    ┌─────────┐    ┌──────────┐
│ collecte│ -> │  édition │ -> │ examen  │ -> │publication│
│ (scan)  │    │(pré-sélec│    │  (LLM)  │    │(dédoublon│
│         │    │  règles) │    │         │    │ + rapport)│
└─────────┘    └──────────┘    └─────────┘    └──────────┘
     │               │               │               │
     └───────────────┴───────────────┴───────────────┘
                         │
                    BANDE en lecture seule

3.1 Collecte — scanner la page d'accueil

Objectif : produire les éléments candidats du jour pour chaque source configurée.

Étapes :

  1. Pour chaque source, récupérer list_url.
  2. Extraire les paires {title, url} en utilisant le extract_profile de la source :
    • rss — analyser les blocs <item>.
    • generic_links — analyser les balises <a href>.
    • regex — appliquer une expression régulière avec des groupes nommés pour titre/url/date.
    • json_api — naviguer dans un chemin JSON et lire les clés titre/url.
  3. Normaliser chaque URL :
    • Supprimer les fragments et paramètres de suivi (utm_*, fbclid, etc.).
    • Enlever la barre oblique finale et le www. initial.
    • Forcer le protocole https, hôte en minuscules.
  4. Comparer avec l'instantané de page d'accueil précédent pour cette source (lu depuis la bande).
    • Les nouvelles valeurs url_norm deviennent des candidats.
    • Enregistrer l'instantané complet actuel comme nouvel enregistrement de bande frontpage.
  5. Mettre à jour l'état de santé de la source :
    • Succès → réinitialiser fail_count.
    • Échec → incrémenter fail_count ; alerter si ≥ 3 échecs consécutifs.

Pourquoi comparer avec la page d'accueil au lieu d'analyser les dates de publication ? Parce que de nombreuses sources ont des horodatages peu fiables ou des mises en page anti-scraping. « Nouveau sur la page d'accueil » est un proxy robuste pour « actualité dans la fenêtre d'aujourd'hui ».

Garde-fou de fenêtre temporelle (déterministe, pas LLM) : Pour les sources de type pull qui exposent une pubDate (RSS/Atom), le scanner applique un seuil dur avant la comparaison. La fenêtre par défaut est de 36 heures ; elle peut être remplacée par extract_profile.window_hours. Les éléments plus anciens que la fenêtre sont supprimés. Les éléments avec des dates manquantes ou impossibles à analyser sont exclus par défaut (missing_date_policy: exclude) car une date que le moteur ne peut pas vérifier est une date à laquelle il ne peut pas faire confiance. Définir missing_date_policy: include uniquement pour les sources dont les dates sont connues comme peu fiables et où la comparaison de page d'accueil est le signal principal.

Cette règle existe car l'application de la fenêtre temporelle est une question de comptabilité, pas de jugement. Elle doit être déterministe et vérifiable — jamais déléguée à un LLM, qui est sujet à des erreurs de lecture des dates et à mélanger des histoires obsolètes dans le rapport du jour.

3.2 Édition — pré-sélection avec le profil

Objectif : noter les candidats avec des règles peu coûteuses ; maintenir un large entonnoir.

Formule :

score = somme(poids des mots-clés correspondants)
      + somme(bonus de correspondance de catégorie)
      - somme(poids des termes négatifs correspondants)

Étapes :

  1. Charger le dernier profil du vertical.
  2. Pour chaque titre candidat :
    • Faire correspondre les mots-clés et les catégories (limite de mot entier pour les termes ASCII).
    • Soustraire les négatifs.
    • Enregistrer last_hit pour tout terme correspondant.
  3. Seuil : par défaut score >= 2 passe ; les scores inférieurs sont rejetés avec reject_reason: low_score.
  4. Écrire chaque candidat dans la bande comme enregistrement item :
    • Accepté → stage: prescreened.
    • Rejeté → stage: rejected, avec reject_reason.

Les éléments rejetés sont le carburant de l'étalonnage. Ne les supprimez jamais.

3.3 Examen — second passage structuré

Objectif : vérifier que les éléments présélectionnés sont de véritables actualités, dans le sujet, et non des publicités/emplois.

Étapes :

  1. Trier les éléments présélectionnés par score (décroissant).
  2. Limiter la représentation par source (ex : max 4 par source) pour éviter la monopolisation.
  3. Pour chaque élément, récupérer le texte de l'article (environ les 1000 premiers caractères) et extraire une date de publication via une chaîne de secours :
    • JSON-LD datePublished
    • <meta article:published_time>
  4. Envoyer une invite par lot au fournisseur LLM configuré. Demander pour chaque élément :
    • is_news — pas une pub/emploi/annonce
    • is_ad, is_job — disqualifiants
    • vertical_fit — score de pertinence de 0 à 1
    • summary — résumé de 2-3 phrases
  5. Appliquer les motifs de rejet :
    • not_news, is_ad, is_job, low_fit.
  6. Écrire les résultats dans la bande sous forme d'enregistrements item avec stage: verified ou stage: rejected.

Si aucun fournisseur LLM n'est configuré : le pipeline s'exécute en mode --dry (règles uniquement) et promeut directement les éléments présélectionnés.

3.4 Publication — dédoublonnage et rapport

Objectif : fusionner les doublons et produire un rapport quotidien lisible.

Étapes :

  1. Charger les éléments récemment mis en commun (14 derniers jours, excluant aujourd'hui).
  2. Dédoublonnage à deux niveaux pour les éléments vérifiés :
    • Correspondance exacte url_norm → ignorer.
    • Titre simhash avec distance de Hamming ≤ 3 → même événement ; regrouper en cluster.
  3. Pour chaque cluster, choisir l'élément avec le score le plus élevé comme élément principal ; les autres deviennent des sources connexes.
  4. Trier les clusters par nombre de sources et score.
  5. Formater le rapport. Ajouter un pied de page d'état :
    • sources scannées, éléments présélectionnés, rejetés, mis en commun, nombre d'éléments du rapport
    • alertes d'état de santé des sources
    • chronométrage des étapes
  6. Écrire un enregistrement report dans la bande.

4. Boucle de réflexion (auto-évolution)

Déclencheur : un utilisateur lit un rapport et donne un retour, par exemple :

« Plus d'histoires sur la sécurité de l'IA, moins de publications sponsorisées, et gardez un œil sur la réglementation européenne. »

Étapes :

  1. Analyser les retours en opérations de profil :
    • add_keyword: {terme, poids}
    • adjust_weight: {terme, delta}
    • remove_keyword: {terme}
    • add_negative: {terme, poids}
    • adjust_negative_weight: {terme, delta}
    • remove_negative: {terme}
  2. Appliquer les opérations au profil ; incrémenter la version.
  3. Écrire un enregistrement evolution de la bande contenant les opérations et le texte de retour original.
  4. Revérifier les éléments récemment mis en commun (7 derniers jours) par rapport aux nouveaux négatifs/poids réduits.
    • Si certains seraient désormais filtrés, signaler le nombre à l'utilisateur.
  5. Renvoyer un résumé de confirmation.

Règle de décroissance (hebdomadaire) :

  • Les termes origin: learned qui n'ont pas touché pendant 30 jours perdent 1 point de poids.
  • Poids 0 → déplacer vers une liste de suppression en attente ; demander à l'utilisateur avant de supprimer.
  • Les termes origin: manual ne sont jamais automatiquement supprimés, seulement avertis.

5. Taxonomie d'acquisition des sources

Une source peut être acquise de cinq manières. Le moteur les traite de manière uniforme une fois qu'elles atteignent la bande. Le registre des sources du rapport affiche chaque source sous forme status [nom](url) · méthode afin qu'un lecteur puisse voir d'un coup d'œil comment la source est acquise.

TypeMécanismeQuand l'utiliserAffichage dans le registre
pullRSS/API/curl sur cronFlux et API stables🟢 [Hacker News](https://news.ycombinator.com/rss) · pull
scanComparaison page d'accueil + pré-sélectionSPA, sites irréguliers🟢 [TechCrunch](https://techcrunch.com) · scan
pushWebhook/SSE/pontAlertes internes ou écosystèmes fermés🟢 [Alertes Internes](...) · push
searchRecherche ad-hocDécouverte de nouvelles sources ou rattrapage d'événements majeurs🟢 [Recherche](...) · search
human-feedL'utilisateur dépose un lienConseils exclusifs et signaux de première main🟢 Astuce Weixin · human-feed

Ajouter une source en enregistrant {id, name, list_url, extract_profile} dans la configuration du vertical. Une source human-feed définit extract_profile = { method = "human_feed" } et peut omettre list_url.

Human-feed facilité par l'agent

Une variante utile est le human-feed facilité par l'agent : l'agent effectue lui-même une recherche sur une plateforme (par exemple sur X pour des publications sous un hashtag IA au cours des dernières 24 à 36 heures), sélectionne ≥1 candidat et l'injecte via le canal human-feed. L'élément injecté passe toujours par les mêmes étapes de pré-sélection, d'examen et de dédoublonnage que les éléments pull/scan — le human-feed garantit l'entrée dans le pool de candidats, pas un laissez-passer direct dans le rapport.

« Ne vous arrêtez jamais au message »

Une publication sur les réseaux sociaux, un titre ou une astuce est un signal, pas une citation. Chaque élément injecté doit être résolu en URL d'article original avant d'atteindre la bande. Si l'agent ne peut trouver qu'une URL de publication, scripts/x_hunt.py tente une recherche de meilleur effort pour l'article original ; en cas d'échec, le candidat est abandonné et la raison est enregistrée dans la bande. Le rapport publié ne dirige jamais les lecteurs vers une publication ou un titre comme source finale.

Implémentation de référence (scripts/x_hunt.py)

scripts/x_hunt.py est une comptabilité déterministe : il écrit un élément déjà sélectionné dans la bande. La recherche, la lecture et la sélection sont le travail de jugement de l'agent, effectué avec ses propres outils WebSearch/outils.

# Chemin préféré : l'agent a déjà résolu le titre en URL d'article original.
python scripts/x_hunt.py --vertical ai-frontier --source-id ainativef_zh \
  --title "OpenAI annonce GPT-5" \
  --url "https://openai.com/blog/introducing-gpt-5" \
  --inject

# Si seule une URL de publication X est disponible, le script tente de résoudre l'URL originale.
python scripts/x_hunt.py --vertical ai-frontier --source-id ainativef_zh \
  --title "OpenAI annonce GPT-5" \
  --url "https://x.com/OpenAI/status/1234567890" \
  --inject

# Secours de recherche automatisée de meilleur effort (souvent bloquée ; à utiliser avec parcimonie).
python scripts/x_hunt.py --vertical ai-frontier \
  --query "intelligence artificielle site:x.com" --inject

# Examiner les candidats de secours de recherche sans injection
python scripts/x_hunt.py --vertical ai-frontier \
  --query "intelligence artificielle site:x.com"

L'enregistrement injecté est écrit dans la bande comme item avec :

  • source_id : l'identifiant de source enregistré (par défaut owner_tips).
  • stage : scanned — il entre dans le pipeline lors de la pré-sélection.
  • facilitated_by : agent — provenance vérifiable.

Les enregistrements abandonnés (URL de publications non résolubles, candidats hors sujet, etc.) sont également ajoutés à la bande avec stage: dropped et un drop_reason, afin que la piste d'audit inclue ce qui n'a pas été retenu.

Séparation des préoccupations : le jugement (recherche/lecture/sélection/résolution d'origine) appartient à l'agent ; la comptabilité (normalisation d'URL, allocation d'identifiant, ajout à la bande) appartient au script. Cela maintient la compétence déterministe et laisse les stratégies de récupération spécifiques à la plateforme aux capacités de l'agent.

Ce mode transforme la plateforme d'une source de contenu passive en une chasse active de découverte de sources : lorsque l'agent trouve à plusieurs reprises de bonnes histoires du même média, ECHO peut demander « Ajouter '<média>' comme source suivie ? » et, sur confirmation, promouvoir le média en source formelle pull/scan. Cette boucle fermée est l'auto-évolution de la compétence en action.


6. Types d'enregistrements de la bande

Chaque ligne de la bande est un objet JSON avec un champ type.

typechamps
sourceid, name, vertical, list_url, extract_profile, health
frontpagesource_id, date, items[], count
profilevertical, version, keywords[], negatives[], categories[], reason
itemid, vertical, source_id, title, url, url_norm, stage, scores, reject_reason, simhash, cluster_id
reportdate, vertical, item_ids[], generated_at, stats
feedbackdate, vertical, text, applied_ops[], status
evolutionvertical, from_version, to_version, ops[], reason, date

7. Exécution de l'implémentation de référence

# Exécution uniquement avec les règles (pas de LLM requis)
python -m engine.pipeline --dry --vertical tech --config config/example_vertical.toml

# Avec examen LLM
export APERTURE_LLM_API_KEY="sk-..."
python -m engine.pipeline --vertical tech --config config/example_vertical.toml

# Appliquer un retour
python -c "from engine.feedback import apply_feedback; apply_feedback('Plus de sécurité IA, moins de pubs', 'tech')"

Après la publication, vous pouvez éventuellement exécuter ECHO pour poser à l'utilisateur jusqu'à deux questions de clarification :

from engine.echo import ask

questions = ask("tech")
for q in questions:
    print(q["question"])

# Plus tard, appliquez une réponse en un mot : yes / no / skip / silence
from engine.echo import apply_answer
apply_answer(question_id, "tech", "yes")

8. ECHO — clarification proactive

ECHO est une couche proactive optionnelle qui pose à l'utilisateur jusqu'à deux questions de clarification en un mot après chaque rapport. Il transforme les lecteurs passifs en formateurs de profil actifs.

ECHO est intentionnellement ...

[le reste du contenu est tronqué pour la traduction, mais le modèle doit compléter avec la traduction complète du contenu original]

Skills associés