diff --git a/src/agents/researcher.py b/src/agents/researcher.py
index 5d60c8a..9f990b4 100644
--- a/src/agents/researcher.py
+++ b/src/agents/researcher.py
@@ -8,5 +8,233 @@ from config import CLAUDE_MODEL_FAST
 logger = logging.getLogger("osint.researcher")
 
 RESEARCH_PROMPT_TEMPLATE = """Du bist ein OSINT-Recherche-Agent fÃ¼r ein Lagemonitoring-System.
-AUSPS‘ÑNˆÈÝ]]Û[™ÝXYÙ_B‚�UQ•�QÎˆÝXÚH˜XÚZÝY[[ˆ[™›Ü›X][Û™[ˆ�H›ÛÙ[™[H›Ü™˜[‚•][ˆÝ]_B’ÛÛ�^ˆÙ\ØÜš\[ÛŸB‚”‘QÑS‚‹HÝXÚH�\ˆ™ZHÙ\špíœÙ[ˆ˜XÚšXÚ[œ]Y[[ˆ
-˜XÚšXÚ[˜YÙ[�\™[‹]X[]0éÞ™Z][™Ù[‹0í™™™[�XÚ2â\™XÚXÚHYYY[‹™pí±œ¡•¹(¤(´�-%8�M½�¥…°�5•‘¥„€¡QÝ¥ÑÑ•È½`°�…�•‰½½¬°�%¹ÍÑ…�É…´°�Q¥­Q½¬°�I•‘‘¥Ð¤(´�-%9�	½Õ±•Ù…É‘µ•‘¥•¸€¡	¥±�°�MÕ¸°�…¥±ä�5…¥°�•ÑŒ¸¤)í±…¹�Õ…�•}¥¹ÍÑÉÕ�Ñ¥½¹ô(´�…­Ñ•¹‰…Í¥•ÉÐ�Õ¹��¹•ÕÑÉ…°€´�­•¥¹”�MÁ•­Õ±…Ñ¥½¹•¸(´�9ÕÑé”�]•‰•Ñ� �Õ´�‘¥”€Ì´Ô�Ý¥�¡Ñ¥�ÍÑ•¸�ÉÑ¥­•°�Ù½±±ÍÓ‘¹‘¥œ�…‰éÕÉÕ™•¸�Õ¹��…ÕÍ›ñ¡É±¥�¡•É”�iÕÍ…µµ•¹™…ÍÍÕ¹¡•¸�éÔ�•ÉÍÑ•±±•¸(´�9ÕÑé”�É•µ½Ù•Á…åÝ…±±Ì¹�½´�›ñÈ�A…åÝ…±°µ�•Í�£ñÑé±¥�¡”�ÉÑ¥­•°€¡è¹¸�MÁ¥•�•°¬°�i•¥Ð¬°�Mh¬§Žˆ�¡ÑÑÁÌè¼½ÝÝÜ¹É•µ½Ù•Á…åÝ…±±Ì¹�½´½Í•…É� ýÕÉ°õIQ%-1}UI0()¥ˆ�‘¥”�É�•‰¹¥ÍÍ”�UMM
!1'e1%
 �…±Ì�)M=8µÉÉ…ä�éÕËñ�¬°�½¡¹”�É­³‰ÉÕ¹¡•¸�‘…Ù½È€m‘•È�‘…¹…� ¸))•‘•Ì�±•µ•¹Ð�¡…Ð�‘¥•Í”�•±‘•Èè(´€‰¡•…‘±¥¹”ˆè�=É¥�¥¹…±”ƒq‰•ÉÍ�¡É¥™Ð(´€‰¡•…‘±¥¹•}‘”ˆè�U‰•ÉÍ•ÑéÕ¹œ�¥¸�ÕÍ�…‰•ÍÁÉ…�¡”€¡™…±±Ì�=É¥�¥¹…±ÍÁÉ…�¡”�…‰Ý•¥�¡Ð¤(´€‰Í½ÕÉ�”ˆè�9…µ”�‘•È�EÕ•±±”€¡è¹¸€‰I•ÕÑ•ÉÌˆ°€‰Ñ…�•ÍÍ�¡…Ôˆ¤(´€‰Í½ÕÉ�•}ÕÉ°ˆè�UI0�‘•Ì�ÉÑ¥­•±Ì(´€‰�½¹Ñ•¹Ñ}ÍÕµµ…Éäˆè�iÕÍ…µµ•¹™…ÍÍÕ¹œ�‘•Ì�%¹¡…±ÑÌ€ Ì´Ô�O‘Ñé”°�¥¸�ÕÍ�…‰•ÍÍÁÉ…�¡”¤¸�	•¤�Á•È�]•‰•Ñ� �…‰�•ÉÕ™•¹•¸�ÉÑ¥­•±¸è�…ÕÍ›ñ¡É±¥�¡•É”�iÕÍ…µµ•¹™…ÍÍÕ¹œ€ Ô´à�O‘Ñé”¤¸(´€‰±…¹�Õ…�”ˆè�MÁÉ…�¡”�‘•Ì�=É¥�¥¹…±Ì€¡è¹¸€‰‘”ˆ°€‰•¸ˆ°€‰™Èˆ¤(´€‰ÁÕ‰±¥Í¡•‘}…Ðˆè�Y•ËÙ™™•¹Ñ±¥�¢éà½±¥�¡Õ¹�Í‘…ÑÕ´�™…±±Ì�‰•­…¹¹Ð€¡%M<µ½Éµ…Ð¤()¹ÑÝ½ÉÑ”�9UH�µ¥Ð�‘•´�)M=8µÉÉ…ä¸�-•¥¹”�¥¹±•¥ÑÕ¹œ°�­•¥¹”�É­«¬ÉÕ¹œ¸ˆˆˆ()A}IMI
!}AI=5AQ}Q5A1Q€ô€ˆˆ‰Ô�‰¥ÍÐ�•¥¸�=M%9PµQ¥•™•¹É•�¡•É�¡”µ�•¹Ð�›ñÈ�•¤�¸�1…�•µ½¹¥Ñ½É¥¹œµMåÍÑ•´¸)UMQAIM!�UIcÎ”ç¬Èø„
\ No newline at end of file
+AUSGABESPRACHE: {output_language}
+
+AUFTRAG: Suche nach aktuellen Informationen zu folgendem Vorfall:
+Titel: {title}
+Kontext: {description}
+
+REGELN:
+- Suche nur bei seriÃ¶sen Nachrichtenquellen (Nachrichtenagenturen, QualitÃ¤tszeitungen, Ã¶ffentlich-rechtliche Medien, BehÃ¶rden)
+- KEIN Social Media (Twitter/X, Facebook, Instagram, TikTok, Reddit)
+- KEINE Boulevardmedien (Bild, Sun, Daily Mail etc.)
+{language_instruction}
+- Faktenbasiert und neutral - keine Spekulationen
+- Nutze removepaywalls.com fÃ¼r Paywall-geschÃ¼tzte Artikel (z.B. Spiegel+, Zeit+, SZ+): https://www.removepaywalls.com/search?url=ARTIKEL_URL
+- Nutze WebFetch um die 3-5 wichtigsten Artikel vollstÃ¤ndig abzurufen und zusammenzufassen
+
+Gib die Ergebnisse AUSSCHLIESSLICH als JSON-Array zurÃ¼ck, ohne ErklÃ¤rungen davor oder danach.
+Jedes Element hat diese Felder:
+- "headline": Originale Ãœberschrift
+- "headline_de": Ãœbersetzung in Ausgabesprache (falls Originalsprache abweicht)
+- "source": Name der Quelle (z.B. "Reuters", "tagesschau")
+- "source_url": URL des Artikels
+- "content_summary": Zusammenfassung des Inhalts (3-5 SÃ¤tze, in Ausgabesprache)
+- "language": Sprache des Originals (z.B. "de", "en", "fr")
+- "published_at": VerÃ¶ffentlichungsdatum falls bekannt (ISO-Format)
+
+Antworte NUR mit dem JSON-Array. Keine Einleitung, keine ErklÃ¤rung."""
+
+DEEP_RESEARCH_PROMPT_TEMPLATE = """Du bist ein OSINT-Tiefenrecherche-Agent fÃ¼r ein Lagemonitoring-System.
+AUSGABESPRACHE: {output_language}
+
+AUFTRAG: FÃ¼hre eine umfassende Hintergrundrecherche durch zu:
+Titel: {title}
+Kontext: {description}
+
+RECHERCHE-STRATEGIE:
+- Breite Suche: Hintergrundberichte, Analysen, Expertenmeinungen, Think-Tank-Publikationen
+- Suche nach: Akteuren, ZusammenhÃ¤ngen, historischem Kontext, rechtlichen Rahmenbedingungen
+- Akademische und Fachquellen zusÃ¤tzlich zu Nachrichtenquellen
+- Nutze removepaywalls.com fÃ¼r Paywall-geschÃ¼tzte Artikel (z.B. https://www.removepaywalls.com/search?url=ARTIKEL_URL)
+- Nutze WebFetch um die 3-5 wichtigsten Artikel vollstÃ¤ndig abzurufen und zusammenzufassen
+{language_instruction}
+- Ziel: 8-15 hochwertige Quellen
+
+QUELLENTYPEN (priorisiert):
+1. Fachzeitschriften und Branchenmedien
+2. QualitÃ¤tszeitungen (Hintergrundberichte, Dossiers)
+3. Think Tanks und Forschungsinstitute
+4. Offizielle Dokumente und Pressemitteilungen
+5. Nachrichtenagenturen (fÃ¼r Faktengrundlage)
+
+AUSSCHLUSS:
+- KEIN Social Media (Twitter/X, Facebook, Instagram, TikTok, Reddit)
+- KEINE Boulevardmedien
+- KEINE Meinungsblogs ohne Quellenbelege
+
+Gib die Ergebnisse AUSSCHLIESSLICH als JSON-Array zurÃ¼ck, ohne ErklÃ¤rungen davor oder danach.
+Jedes Element hat diese Felder:
+- "headline": Originale Ãœberschrift
+- "headline_de": Ãœbersetzung in Ausgabesprache (falls Originalsprache abweicht)
+- "source": Name der Quelle (z.B. "netzpolitik.org", "Handelsblatt")
+- "source_url": URL des Artikels
+- "content_summary": AusfÃ¼hrliche Zusammenfassung des Inhalts (5-8 SÃ¤tze, in Ausgabesprache)
+- "language": Sprache des Originals (z.B. "de", "en", "fr")
+- "published_at": VerÃ¶ffentlichungsdatum falls bekannt (ISO-Format)
+
+Antworte NUR mit dem JSON-Array. Keine Einleitung, keine ErklÃ¤rung."""
+
+# Sprach-Anweisungen
+LANG_INTERNATIONAL = "- Suche in Deutsch UND Englisch fÃ¼r internationale Abdeckung"
+LANG_GERMAN_ONLY = "- Suche NUR auf Deutsch bei deutschsprachigen Quellen (Deutschland, Ã–sterreich, Schweiz)\n- KEINE englischsprachigen oder anderssprachigen Quellen"
+
+LANG_DEEP_INTERNATIONAL = "- Suche in Deutsch, Englisch und weiteren relevanten Sprachen"
+LANG_DEEP_GERMAN_ONLY = "- Suche NUR auf Deutsch bei deutschsprachigen Quellen (Deutschland, Ã–sterreich, Schweiz)\n- KEINE englischsprachigen oder anderssprachigen Quellen"
+
+
+FEED_SELECTION_PROMPT_TEMPLATE = """Du bist ein OSINT-Analyst. WÃ¤hle aus dieser Feed-Liste die Feeds aus, die fÃ¼r die Lage relevant sein kÃ¶nnten.
+
+LAGE: {title}
+KONTEXT: {description}
+INTERNATIONALE QUELLEN: {international}
+
+FEEDS:
+{feed_list}
+
+REGELN:
+- WÃ¤hle alle Feeds die thematisch oder regional relevant sein kÃ¶nnten
+- Lieber einen Feed zu viel als zu wenig auswÃ¤hlen
+- Bei "Internationale Quellen: Nein": Keine internationalen Feeds auswÃ¤hlen
+- Allgemeine Nachrichtenfeeds (tagesschau, Spiegel etc.) sind fast immer relevant
+- Antworte NUR mit einem JSON-Array der Nummern, z.B. [1, 2, 5, 12]"""
+
+
+class ResearcherAgent:
+    """FÃ¼hrt OSINT-Recherchen Ã¼ber Claude CLI WebSearch durch."""
+
+    async def select_relevant_feeds(
+        self,
+        title: str,
+        description: str,
+        international: bool,
+        feeds_metadata: list[dict],
+    ) -> tuple[list[dict], ClaudeUsage | None]:
+        """LÃ¤sst Claude die relevanten Feeds fÃ¼r eine Lage vorauswÃ¤hlen.
+
+        Nutzt Haiku (CLAUDE_MODEL_FAST) fÃ¼r diese einfache Aufgabe.
+
+        Returns:
+            (ausgewÃ¤hlte Feeds, usage) â€” Bei Fehler: (alle Feeds, None)
+        """
+        # Feed-Liste als nummerierte Ãœbersicht formatieren
+        feed_lines = []
+        for i, feed in enumerate(feeds_metadata, 1):
+            feed_lines.append(
+                f"{i}. {feed['name']} ({feed['domain']}) [{feed['category']}]"
+            )
+
+        prompt = FEED_SELECTION_PROMPT_TEMPLATE.format(
+            title=title,
+            description=description or "Keine weitere Beschreibung",
+            international="Ja" if international else "Nein",
+            feed_list="\n".join(feed_lines),
+        )
+
+        try:
+            result, usage = await call_claude(prompt, tools=None, model=CLAUDE_MODEL_FAST)
+
+            # JSON-Array aus Antwort extrahieren
+            match = re.search(r'\[[\d\s,]+\]', result)
+            if not match:
+                logger.warning("Feed-Selektion: Kein JSON-Array in Antwort, nutze alle Feeds")
+                return feeds_metadata, usage
+
+            indices = json.loads(match.group())
+            selected = []
+            for idx in indices:
+                if isinstance(idx, int) and 1 <= idx <= len(feeds_metadata):
+                    selected.append(feeds_metadata[idx - 1])
+
+            if not selected:
+                logger.warning("Feed-Selektion: Keine gÃ¼ltigen Indizes, nutze alle Feeds")
+                return feeds_metadata, usage
+
+            logger.info(
+                f"Feed-Selektion: {len(selected)} von {len(feeds_metadata)} Feeds ausgewÃ¤hlt"
+            )
+            return selected, usage
+
+        except Exception as e:
+            logger.warning(f"Feed-Selektion fehlgeschlagen ({e}), nutze alle Feeds")
+            return feeds_metadata, None
+
+    async def search(self, title: str, description: str = "", incident_type: str = "adhoc", international: bool = True) -> tuple[list[dict], ClaudeUsage | None]:
+        """Sucht nach Informationen zu einem Vorfall."""
+        from config import OUTPUT_LANGUAGE
+        if incident_type == "research":
+            lang_instruction = LANG_DEEP_INTERNATIONAL if international else LANG_DEEP_GERMAN_ONLY
+            prompt = DEEP_RESEARCH_PROMPT_TEMPLATE.format(
+                title=title, description=description, language_instruction=lang_instruction,
+                output_language=OUTPUT_LANGUAGE,
+            )
+        else:
+            lang_instruction = LANG_INTERNATIONAL if international else LANG_GERMAN_ONLY
+            prompt = RESEARCH_PROMPT_TEMPLATE.format(
+                title=title, description=description, language_instruction=lang_instruction,
+                output_language=OUTPUT_LANGUAGE,
+            )
+
+        try:
+            result, usage = await call_claude(prompt)
+            articles = self._parse_response(result)
+
+            # Ausgeschlossene Quellen dynamisch aus DB laden
+            excluded_sources = await self._get_excluded_sources()
+
+            # Ausgeschlossene Quellen filtern
+            filtered = []
+            for article in articles:
+                source = article.get("source", "").lower()
+                source_url = article.get("source_url", "").lower()
+                excluded = False
+                for excl in excluded_sources:
+                    if excl in source or excl in source_url:
+                        excluded = True
+                        break
+                if not excluded:
+                    # Bei nur-deutsch: nicht-deutsche Ergebnisse nachfiltern
+                    if not international and article.get("language", "de") != "de":
+                        continue
+                    filtered.append(article)
+
+            logger.info(f"Recherche ergab {len(filtered)} Artikel (von {len(articles)} gefundenen, international={international})")
+            return filtered, usage
+
+        except Exception as e:
+            logger.error(f"Recherche-Fehler: {e}")
+            return [], None
+
+    async def _get_excluded_sources(self) -> list[str]:
+        """LÃ¤dt ausgeschlossene Quellen aus der Datenbank."""
+        try:
+            from source_rules import get_source_rules
+            rules = await get_source_rules()
+            return rules.get("excluded_domains", [])
+        except Exception as e:
+            logger.warning(f"Fallback auf config.py fÃ¼r Excluded Sources: {e}")
+            from config import EXCLUDED_SOURCES
+            return list(EXCLUDED_SOURCES)
+
+    def _parse_response(self, response: str) -> list[dict]:
+        """Parst die Claude-Antwort als JSON-Array."""
+        # Versuche JSON direkt zu parsen
+        try:
+            data = json.loads(response)
+            if isinstance(data, list):
+                return data
+        except json.JSONDecodeError:
+            pass
+
+        # Versuche JSON aus der Antwort zu extrahieren (zwischen [ und ])
+        match = re.search(r'\[.*\]', response, re.DOTALL)
+        if match:
+            try:
+                data = json.loads(match.group())
+                if isinstance(data, list):
+                    return data
+            except json.JSONDecodeError:
+                pass
+
+        logger.warning("Konnte Claude-Antwort nicht als JSON parsen")
+        return []
