Skill: Aperture
TL;DR — Eine Fähigkeit für KI-Agenten, die bessere tägliche Berichte erstellen und mehr darüber erfahren möchten, was ihre Nutzer wirklich wollen. Aperture scannt Quellen-Startseiten, vergleicht sie mit dem Vortag, filtert sie mit einem gewichteten Profil vor, prüft sie mit einem LLM, entfernt Duplikate, veröffentlicht und entwickelt das Profil auf Basis von Nutzer-Feedback weiter. Alle Entscheidungen werden in einem nur-anhängbaren Log (Tape) festgehalten.
Diese Fähigkeit ist implementierungsunabhängig. Ein Agent kann sie ausführen, indem er diese Datei liest, die mitgelieferte Python-Referenzimplementierung aufruft oder die Muster in seine eigene Laufzeitumgebung portiert.
1. Zweck
Agenten eine deterministische, prüfbare Nachrichten-Kurationsfähigkeit zu geben, die statische RSS-Reader und einmalige LLM-Zusammenfasser ersetzt:
- Die Startseite scannen — das „Lesen der Startseite“ als erstklassige Operation behandeln, nicht nur als Feed-Polling.
- Jede Entscheidung aufzeichnen — jede Quellen-Schnappschuss, jedes Element, jeden Ablehnungsgrund, jede Profilversion und jeden Bericht in einem nur-anhängbaren Log für Prüfung und Wiederholung speichern.
- Aus Nutzer-Feedback lernen — den Geschmack des Agenten durch explizites Feedback aktualisieren und jede Änderung versioniert und rückgängig machbar halten.
Wenn ein Agent diese Fähigkeit ausführt, kann er die Fragen „warum wurde dies ausgewählt?“ und „was wäre gestern anders gewesen?“ aus dem Tape beantworten.
2. Kernkonzepte
| Konzept | Bedeutung |
|---|---|
| Ressort (Vertical) | Ein konfiguriertes Nachrichtengebiet (z.B. Technik, Auto, KI-Politik). Jedes Ressort hat seine eigenen Quellen, sein Profil und sein Tape. |
| Tape | Nur-anhängbares JSONL-Log. Eine Datei pro Ressort. Jeder Quellen-Schnappschuss, jedes Element, jede Profilversion, jedes Feedback und jeder Bericht ist ein Datensatz. |
| Profil | Der „Geschmack“ des Ressorts: gewichtete Schlüsselwörter, Kategorien mit Boni und negative Begriffe. Versioniert; jede Änderung wird protokolliert. |
| Scan | Die Listenseite jeder Quelle abrufen, Titel/Links extrahieren, URLs normalisieren, mit dem vorherigen Schnappschuss vergleichen (diff). |
| Reflexionsschleife | Nutzer-Feedback → analysierte Profiloperationen → Versionserhöhung → Evolutionsdatensatz → erneute Überprüfung kürzlich gepoolter Elemente. |
3. Vierstufige Pipeline
┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────┐
│ sammeln │ -> │ bearbeiten│ -> │ prüfen │ -> │ veröffent-│
│ (scan) │ │(Vorfilter│ │ (LLM) │ │ lichen │
│ │ │ regeln) │ │ │ │ (Dedup + │
│ │ │ │ │ │ │ Bericht)│
└─────────┘ └──────────┘ └─────────┘ └──────────┘
│ │ │ │
└───────────────┴───────────────┴───────────────┘
│
NUR-ANHÄNGBARES TAPE
3.1 Sammeln — Die Startseite scannen
Ziel: Die heutigen Kandidaten-Elemente aus jeder konfigurierten Quelle produzieren.
Schritte:
- Für jede Quelle
list_urlabrufen. {title, url}-Paare mit demextract_profileder Quelle extrahieren:rss—<item>-Blöcke analysieren.generic_links—<a href>-Tags analysieren.regex— Einen regulären Ausdruck mit benannten Gruppen für Titel/URL/Datum anwenden.json_api— Einen JSON-Pfad navigieren und Titel/URL-Schlüssel lesen.
- Jede URL normalisieren:
- Fragmente und Tracking-Parameter entfernen (
utm_*,fbclid, etc.). - Nachgestellten Schrägstrich und führendes
www.entfernen. https-Schema erzwingen, Host in Kleinbuchstaben.
- Fragmente und Tracking-Parameter entfernen (
- Gegen den vorherigen Startseiten-Schnappschuss für diese Quelle differenzieren (aus Tape gelesen).
- Neue
url_norm-Werte werden zu Kandidaten. - Den vollständigen aktuellen Schnappschuss als neuen
frontpage-Tape-Datensatz speichern.
- Neue
- Quellengesundheit aktualisieren:
- Erfolg →
fail_countzurücksetzen. - Fehlschlag →
fail_counterhöhen; bei ≥ 3 aufeinanderfolgenden Fehlschlägen warnen.
- Erfolg →
Warum gegen die Startseite differenzieren, anstatt Veröffentlichungsdaten zu analysieren? Weil viele Quellen unzuverlässige Zeitstempel oder Anti-Scraping-Layouts haben. „Neu auf der Startseite“ ist ein robuster Indikator für „Nachrichten im heutigen Zeitfenster“.
Zeitfenster-Wächter (deterministisch, nicht LLM):
Für Pull-Quellen, die tatsächlich ein pubDate (RSS/Atom) bereitstellen, wendet der Scanner vor dem Diff einen harten Cutoff an. Das Standardfenster beträgt 36 Stunden; mit extract_profile.window_hours überschreibbar. Elemente, die älter als das Fenster sind, werden verworfen. Elemente mit fehlendem oder nicht analysierbarem Datum werden standardmäßig ausgeschlossen (missing_date_policy: exclude), da ein Datum, das die Engine nicht überprüfen kann, ein Datum ist, dem sie nicht vertrauen kann. Setze missing_date_policy: include nur für Quellen, bei denen bekannt ist, dass die Daten unzuverlässig sind und der Startseiten-Diff das primäre Signal ist.
Diese Regel existiert, weil die Durchsetzung des Datumsfensters Buchhaltung ist, kein Urteil. Sie muss deterministisch und prüfbar sein – niemals an ein LLM delegiert werden, das dazu neigt, Daten falsch zu lesen und alte Geschichten in den heutigen Bericht zu mischen.
3.2 Bearbeiten — Vorfilter mit dem Profil
Ziel: Kandidaten mit kostengünstigen Regeln bewerten; den breiten Trichter beibehalten.
Formel:
score = Summe(gewichtete Schlüsselwort-Treffer)
+ Summe(Kategorie-Übereinstimmungsboni)
- Summe(gewichtete negative Treffer)
Schritte:
- Das neueste Profil des Ressorts laden.
- Für jeden Kandidatentitel:
- Schlüsselwörter und Kategorien abgleichen (Wortgrenze für ASCII-Begriffe).
- Negative abziehen.
last_hitfür jeden passenden Begriff aufzeichnen.
- Schwellwert: Standardmäßig bestehen
score >= 2; niedrigere Bewertungen werden mitreject_reason: low_scoreabgelehnt. - Jeden Kandidaten als
item-Datensatz auf das Tape schreiben:- Bestanden →
stage: prescreened. - Abgelehnt →
stage: rejected, mitreject_reason.
- Bestanden →
Abgelehnte Elemente sind der Treibstoff für die Kalibrierung. Lösche sie niemals.
3.3 Prüfen — Strukturierter zweiter Durchlauf
Ziel: Sicherstellen, dass vorgefilterte Elemente echte Nachrichten, themenrelevant und keine Werbung/Jobs sind.
Schritte:
- Vorgefilterte Elemente nach Punktzahl sortieren (absteigend).
- Die Darstellung pro Quelle begrenzen (z.B. max. 4 pro Quelle), um eine Monopolstellung zu vermeiden.
- Für jedes Element den Artikeltext abrufen (erste ~1000 Zeichen) und ein Veröffentlichungsdatum über eine Fallback-Kette extrahieren:
- JSON-LD
datePublished <meta article:published_time>
- JSON-LD
- Einen Batch-Prompt an den konfigurierten LLM-Anbieter senden. Für jedes Element abfragen:
is_news— keine Anzeige/Job/Ankündigungis_ad,is_job— Ausschlusskriterienvertical_fit— Relevanzbewertung von 0 bis 1summary— Zusammenfassung in 2-3 Sätzen
- Ablehnungsgründe anwenden:
not_news,is_ad,is_job,low_fit.
- Ergebnisse als
item-Datensätze mitstage: verifiedoderstage: rejectedauf das Tape schreiben.
Wenn kein LLM-Anbieter konfiguriert ist: Die Pipeline läuft im --dry Nur-Regel-Modus und befördert vorgefilterte Elemente direkt.
3.4 Veröffentlichen — Deduplizieren und Berichten
Ziel: Duplikate entfernen und einen lesbaren täglichen Bericht erstellen.
Schritte:
- Kürzlich gepoolte Elemente laden (letzte 14 Tage, heute ausgenommen).
- Zweistufige Deduplizierung für geprüfte Elemente:
- Exakte
url_norm-Übereinstimmung → überspringen. - Titel-
simhashmit Hamming-Distanz ≤ 3 → gleiches Ereignis; zu einem Cluster gruppieren.
- Exakte
- Für jedes Cluster das Element mit der höchsten Punktzahl als Hauptelement auswählen; andere werden zu verwandten Quellen.
- Cluster nach Quellenanzahl und Punktzahl sortieren.
- Den Bericht formatieren. Eine Statusfußzeile anhängen:
- gescannte Quellen, vorgefilterte, abgelehnte, gepoolte Elemente, Anzahl der Berichtselemente
- Warnungen zur Quellengesundheit
- Phasenzeitmessungen
- Einen
report-Datensatz auf das Tape schreiben.
4. Reflexionsschleife (Selbstentwicklung)
Auslöser: Ein Nutzer liest einen Bericht und gibt Feedback, z.B.:
„Mehr KI-Sicherheitsgeschichten, weniger gesponserte Beiträge, und behalte die EU-Regulierung im Auge.“
Schritte:
- Feedback in Profiloperationen parsen:
add_keyword: {term, weight}adjust_weight: {term, delta}remove_keyword: {term}add_negative: {term, weight}adjust_negative_weight: {term, delta}remove_negative: {term}
- Operationen auf das Profil anwenden; Version erhöhen.
- Einen
evolution-Tape-Datensatz mit den Operationen und dem ursprünglichen Feedbacktext schreiben. - Kürzlich gepoolte Elemente (letzte 7 Tage) gegen die neuen Negativ-/niedrigeren Gewichtungen erneut prüfen.
- Falls jetzt gefiltert würden, die Anzahl dem Nutzer melden.
- Eine Bestätigungszusammenfassung zurückgeben.
Verfallsregel (wöchentlich):
origin: learned-Begriffe, die 30 Tage lang nicht getroffen haben, verlieren 1 Gewichtungspunkt.- Gewichtung 0 → in eine Löschvorschlagsliste verschieben; Nutzer vor Entfernung fragen.
origin: manual-Begriffe werden niemals automatisch gelöscht, nur gewarnt.
5. Taxonomie der Quellenakquisition
Eine Quelle kann auf fünf Arten erworben werden. Die Engine behandelt sie einheitlich, sobald sie das Tape erreichen. Das Quellenregister des Berichts stellt jede Quelle als status [name](url) · method dar, damit ein Leser auf einen Blick sehen kann, wie die Quelle erworben wurde.
| Typ | Mechanismus | Wann verwenden | Registeranzeige |
|---|---|---|---|
| pull | RSS/API/curl per Cron | Stabile Feeds und APIs | 🟢 [Hacker News](https://news.ycombinator.com/rss) · pull |
| scan | Startseiten-Diff + Vorfilter | SPAs, unregelmäßige Seiten | 🟢 [TechCrunch](https://techcrunch.com) · scan |
| push | Webhook/SSE/Bridge | Interne Warnungen oder geschlossene Ökosysteme | 🟢 [Internal Alerts](...) · push |
| search | Ad-hoc-Suche | Neuquellen-Entdeckung oder Aufholen bei Großereignissen | 🟢 [Search](...) · search |
| human-feed | Nutzer gibt einen Link | Exklusive Tipps und Signale aus erster Hand | 🟢 Weixin Tip · human-feed |
Füge eine Quelle hinzu, indem du {id, name, list_url, extract_profile} in der Ressortkonfiguration registrierst. Eine Human-Feed-Quelle setzt extract_profile = { method = "human_feed" } und kann list_url weglassen.
Agent-unterstützter Human-Feed
Eine nützliche Variante ist der Agent-unterstützte Human-Feed: Der Agent selbst führt eine Plattform-Suche durch (z.B. auf X nach Beiträgen unter einem KI-Hashtag in den letzten 24–36 Stunden), kuratiert ≥1 Kandidaten und injiziert ihn über den Human-Feed-Kanal. Das injizierte Element durchläuft dennoch dieselben Phasen der Vorfilterung, Prüfung und Deduplizierung wie Pull/Scan-Elemente – der Human-Feed garantiert den Eintritt in den Kandidatenpool, keinen Freifahrtschein in den Bericht.
„Hör niemals bei der Nachricht auf“
Ein Social-Media-Beitrag, eine Schlagzeile oder ein Tipp ist ein Signal, kein Zitat. Jedes injizierte Element muss die ursprüngliche Artikel-URL auflösen, bevor es das Tape erreicht. Wenn der Agent nur eine Beitrags-URL finden kann, versucht scripts/x_hunt.py eine Suche nach bestem Wissen und Gewissen nach dem Originalartikel; falls dies fehlschlägt, wird der Kandidat verworfen und der Grund auf dem Tape aufgezeichnet. Der veröffentlichte Bericht verweist den Leser niemals auf einen Beitrag oder eine Schlagzeile als endgültige Quelle.
Referenzimplementierung (scripts/x_hunt.py)
scripts/x_hunt.py ist deterministische Buchhaltung: es schreibt ein bereits kuratiertes Element auf das Tape. Die Suche, das Lesen und die Auswahl sind die Beurteilungsarbeit des Agenten, die mit den eigenen WebSearch-/Tools des Agenten durchgeführt wird.
# Bevorzugter Pfad: Der Agent hat die Schlagzeile bereits zur ursprünglichen Artikel-URL aufgelöst.
python scripts/x_hunt.py --vertical ai-frontier --source-id ainativef_zh \
--title "OpenAI kündigt GPT-5 an" \
--url "https://openai.com/blog/introducing-gpt-5" \
--inject
# Wenn nur eine X-Beitrags-URL verfügbar ist, versucht das Skript, die Original-URL aufzulösen.
python scripts/x_hunt.py --vertical ai-frontier --source-id ainativef_zh \
--title "OpenAI kündigt GPT-5 an" \
--url "https://x.com/OpenAI/status/1234567890" \
--inject
# Automatisierte Suche nach bestem Wissen und Gewissen als Fallback (oft blockiert; sparsam verwenden).
python scripts/x_hunt.py --vertical ai-frontier \
--query "KI künstliche Intelligenz site:x.com" --inject
# Kandidaten des Such-Fallbacks überprüfen, ohne zu injizieren
python scripts/x_hunt.py --vertical ai-frontier \
--query "KI künstliche Intelligenz site:x.com"
Der injizierte Datensatz wird als item auf das Tape geschrieben mit:
source_id: die registrierte Quellen-ID (Standardowner_tips).stage:scanned— es tritt in die Pipeline bei der Vorfilterung ein.facilitated_by:agent— prüfbare Herkunft.
Verworfene Datensätze (nicht auflösbare Beitrags-URLs, unpassende Kandidaten usw.) werden ebenfalls mit stage: dropped und einem drop_reason an das Tape angehängt, sodass die Prüfspur auch das enthält, was es nicht geschafft hat.
Trennung der Verantwortlichkeiten: Beurteilung (Suche/Lesen/Auswahl/Quellenauflösung) gehört dem Agenten; Buchhaltung (URL normalisieren, ID zuweisen, an Tape anhängen) gehört dem Skript. Dies hält die Fähigkeit deterministisch und überlässt plattformspezifische Abrufstrategien den eigenen Fähigkeiten des Agenten.
Dieser Modus verwandelt die Plattform von einer passiven Inhaltsquelle in eine aktive Quellenjagd: wenn der Agent wiederholt gute Geschichten von derselben Publikation findet, kann ECHO fragen „„‘ als verfolgte Quelle hinzufügen?“ und bei Zustimmung die Publikation zu einer formellen Pull/Scan-Quelle erheben. Diese geschlossene Schleife ist die Selbstentwicklung der Fähigkeit in Aktion.
6. Tape-Datensatztypen
Jede Tape-Zeile ist ein JSON-Objekt mit einem type-Feld.
| Typ | Felder |
|---|---|
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. Ausführen der Referenzimplementierung
# Nur-Regel-Testlauf (kein LLM erforderlich)
python -m engine.pipeline --dry --vertical tech --config config/example_vertical.toml
# Mit LLM-Prüfung
export APERTURE_LLM_API_KEY="sk-..."
python -m engine.pipeline --vertical tech --config config/example_vertical.toml
# Feedback anwenden
python -c "from engine.feedback import apply_feedback; apply_feedback('Mehr KI-Sicherheit, weniger Werbung', 'tech')"
Nach der Veröffentlichung optional ECHO ausführen, um dem Nutzer bis zu zwei Klarstellungsfragen zu stellen:
from engine.echo import ask
questions = ask("tech")
for q in questions:
print(q["question"])
# Später eine Ein-Wort-Antwort anwenden: yes / no / skip / silence
from engine.echo import apply_answer
apply_answer(question_id, "tech", "yes")
8. ECHO — Proaktive Klarstellung
ECHO ist eine optionale proaktive Schicht, die dem Nutzer nach jedem Bericht bis zu zwei Ein-Wort-Klarstellungsfragen stellt. Es verwandelt passive Leser in aktive Profil-Trainer.
ECHO ist absichtlich
…
[für die Übersetzung gekürzt]