How I Built Multi-Topic News Summarization Agents in 30 Minutes
Every morning I’d open Chrome and start my ritual: Bloomberg for markets, Reuters for business news, ESPN for sports scores, BBC for world news. By the time I finished, I had 50+ tabs open and had spent nearly an hour just collecting information.
I didn’t need to read everything. I just needed to know what mattered.
That’s when I found a Reddit post that changed my approach:
“I set up three different agents, one for each topic, that summarize everything for me. Read through them in the morning, and I’m good to go. The whole setup took 30 minutes.”
Thirty minutes to reclaim an hour every day? I had to try it.
The Problem: Information Overload
My morning news consumption looked like this:
| Topic | Sources | Tabs | Time |
|---|---|---|---|
| Investing | Bloomberg, Reuters, WSJ, Finviz | 15-20 | 20 min |
| Sports | ESPN, The Athletic, team sites | 10-15 | 15 min |
| Geopolitics | BBC, Reuters, Foreign Affairs | 10-20 | 20 min |
The problems were obvious:
- I’d read the same story across multiple sources
- Everything got equal attention, regardless of importance
- Constant tab-switching broke my focus
- Important news got lost in the noise
I needed a system that would:
- Fetch news from multiple sources automatically
- Deduplicate overlapping stories
- Prioritize what actually matters to me
- Deliver a concise summary I could read in 5 minutes
Why Separate Agents Per Topic?
My first instinct was to build one giant news reader. But that’s wrong.
Investing news needs different analysis than sports news. A market-moving Fed announcement requires different treatment than an NBA trade rumor. A single agent can’t optimize for all these contexts simultaneously.
The insight from the Reddit discussion: specialized agents outperform monolithic ones.
Each topic gets:
- Its own source list (no ESPN in geopolitics)
- Its own prioritization logic (earnings matter for investing, scores matter for sports)
- Its own summary format (technical for finance, narrative for geopolitics)
Here’s the architecture:
+------------------+ +------------------+ +------------------+| Investing Agent | | Sports Agent | | Geopolitics Agent|+--------+---------+ +--------+---------+ +--------+---------+ | | | v v v+------------------+ +------------------+ +------------------+| Sources: | | Sources: | | Sources: || - Bloomberg RSS | | - ESPN RSS | | - BBC RSS || - Reuters RSS | | - The Athletic | | - Reuters RSS || - Finviz | | - Team feeds | | - Foreign Policy|+--------+---------+ +--------+---------+ +--------+---------+ | | | v v v+------------------+ +------------------+ +------------------+| Summarization | | Summarization | | Summarization || (Claude) | | (Claude) | | (Claude) |+--------+---------+ +--------+---------+ +--------+---------+ | | | +------------+-----------+-----------+------------+ | | v v +---------------+ +---------------+ | Morning Digest| | Telegram | | (Consolidated)| | Delivery | +---------------+ +---------------+Option A: Quick Setup with OpenClaw
If you want to get running in 30 minutes, use OpenClaw’s managed platform.
Step 1: Define Your Topics
Create a configuration file for your news agents:
agents: - id: investing-briefing name: "Investing News Agent" sources: - type: rss url: "https://feeds.bloomberg.com/markets/news.rss" - type: rss url: "https://www.reutersagency.com/feed/?taxonomy=best-topics&post_type=best" prompt: | Summarize today's investing news: 1. Market Overview (2 sentences) 2. Top 5 Stories (headline + 1 sentence each) 3. Watch List (emerging trends) output: type: telegram
- id: sports-briefing name: "Sports News Agent" sources: - type: rss url: "https://www.espn.com/espn/rss/news" prompt: | Summarize sports news: - Scores and key plays - Injury updates - Upcoming games to watch output: type: telegram
- id: geopolitics-briefing name: "Geopolitics News Agent" sources: - type: rss url: "https://feeds.bbci.co.uk/news/world/rss.xml" prompt: | Summarize geopolitics developments: 1. Major conflicts 2. Diplomatic news 3. Elections and political changes output: type: telegramStep 2: Schedule the Delivery
# Investing briefing at 7 AM weekdaysopenclaw cron add \ --schedule '0 7 * * 1-5' \ --agent investing-briefing \ --prompt 'Check all configured sources and create the daily investing summary.' \ --announce
# Sports briefing at 7 AM dailyopenclaw cron add \ --schedule '0 7 * * *' \ --agent sports-briefing \ --prompt 'Create the daily sports summary.' \ --announce
# Geopolitics briefing at 7 AM weekdaysopenclaw cron add \ --schedule '0 7 * * 1-5' \ --agent geopolitics-briefing \ --prompt 'Create the daily geopolitics summary.' \ --announceThat’s it. The next morning, you’ll wake up to three briefings in your Telegram.
Option B: Custom LangGraph Agent
If you want more control, build it yourself with LangGraph. This takes 2-4 hours but gives you full customization.
Step 1: Create the Topic Agent
from dataclasses import dataclassfrom typing import List, Optionalfrom anthropic import Anthropicimport feedparser
@dataclassclass NewsItem: title: str source: str url: str summary: str
@dataclassclass TopicDigest: topic: str synthesized_summary: str key_articles: List[NewsItem]
class TopicNewsAgent: def __init__(self, topic_name: str, sources: List[dict], focus_areas: List[str]): self.topic_name = topic_name self.sources = sources self.focus_areas = focus_areas self.client = Anthropic()
def fetch_from_rss(self, url: str, max_items: int = 15) -> List[dict]: feed = feedparser.parse(url) articles = [] for entry in feed.entries[:max_items]: articles.append({ "title": entry.get("title", "Untitled"), "url": entry.get("link", ""), "summary": entry.get("summary", ""), "source": feed.feed.get("title", "Unknown") }) return articles
def synthesize(self, articles: List[dict]) -> TopicDigest: articles_text = "\n\n".join([ f"[{a['source']}] {a['title']}\n{a['summary'][:400]}" for a in articles[:15] ])
prompt = f"""You are a {self.topic_name} news analyst.
Focus areas: {', '.join(self.focus_areas)}
Articles:{articles_text}
Create a digest with:1. A 2-3 sentence overview of key themes2. Top 5 stories with 1-sentence summaries3. Key developments to watch
Format in clean markdown."""
response = self.client.messages.create( model="claude-sonnet-4-20250514", max_tokens=1200, messages=[{"role": "user", "content": prompt}] )
return TopicDigest( topic=self.topic_name, synthesized_summary=response.content[0].text, key_articles=[NewsItem(**a) for a in articles[:5]] )
def run(self) -> TopicDigest: all_articles = [] for source in self.sources: if source["type"] == "rss": all_articles.extend(self.fetch_from_rss(source["url"]))
# Deduplicate by title similarity (simplified) seen_titles = set() unique_articles = [] for a in all_articles: title_key = a["title"][:50].lower() if title_key not in seen_titles: seen_titles.add(title_key) unique_articles.append(a)
return self.synthesize(unique_articles)Step 2: Orchestrate with LangGraph
from langgraph.graph import StateGraph, ENDfrom typing import TypedDict, Annotated, Optionalimport operator
# Define sources for each topicINVESTING_SOURCES = [ {"type": "rss", "url": "https://feeds.bloomberg.com/markets/news.rss"}, {"type": "rss", "url": "https://www.reutersagency.com/feed/?taxonomy=best-topics&post_type=best"}]
SPORTS_SOURCES = [ {"type": "rss", "url": "https://www.espn.com/espn/rss/news"}]
GEOPOLITICS_SOURCES = [ {"type": "rss", "url": "https://feeds.bbci.co.uk/news/world/rss.xml"}]
class NewsState(TypedDict): investing_digest: Optional[dict] sports_digest: Optional[dict] geopolitics_digest: Optional[dict] errors: Annotated[list, operator.add]
def create_news_graph(): # Initialize agents investing_agent = TopicNewsAgent( topic_name="investing", sources=INVESTING_SOURCES, focus_areas=["market movements", "earnings", "Fed policy"] )
sports_agent = TopicNewsAgent( topic_name="sports", sources=SPORTS_SOURCES, focus_areas=["NFL", "NBA", "Premier League"] )
geopolitics_agent = TopicNewsAgent( topic_name="geopolitics", sources=GEOPOLITICS_SOURCES, focus_areas=["conflicts", "diplomacy", "elections"] )
def fetch_investing(state: NewsState) -> NewsState: try: state["investing_digest"] = investing_agent.run() except Exception as e: state["errors"] = [f"Investing: {e}"] return state
def fetch_sports(state: NewsState) -> NewsState: try: state["sports_digest"] = sports_agent.run() except Exception as e: state["errors"] = state.get("errors", []) + [f"Sports: {e}"] return state
def fetch_geopolitics(state: NewsState) -> NewsState: try: state["geopolitics_digest"] = geopolitics_agent.run() except Exception as e: state["errors"] = state.get("errors", []) + [f"Geopolitics: {e}"] return state
# Build graph graph = StateGraph(NewsState) graph.add_node("investing", fetch_investing) graph.add_node("sports", fetch_sports) graph.add_node("geopolitics", fetch_geopolitics)
graph.set_entry_point("investing") graph.add_edge("investing", "sports") graph.add_edge("sports", "geopolitics") graph.add_edge("geopolitics", END)
return graph.compile()
if __name__ == "__main__": graph = create_news_graph() result = graph.invoke({ "investing_digest": None, "sports_digest": None, "geopolitics_digest": None, "errors": [] })
if result.get("investing_digest"): print("## INVESTING\n" + result["investing_digest"].synthesized_summary)
if result.get("sports_digest"): print("\n## SPORTS\n" + result["sports_digest"].synthesized_summary)
if result.get("geopolitics_digest"): print("\n## GEOPOLITICS\n" + result["geopolitics_digest"].synthesized_summary)Step 3: Add Telegram Delivery
import osimport requestsfrom datetime import datetime
class TelegramDeliverer: def __init__(self): self.bot_token = os.environ["TELEGRAM_BOT_TOKEN"] self.chat_id = os.environ["TELEGRAM_CHAT_ID"]
def send_digest(self, topic: str, digest: str): header = f"*{topic.upper()} - {datetime.now().strftime('%A, %B %d')}*\n\n" url = f"https://api.telegram.org/bot{self.bot_token}/sendMessage" data = { "chat_id": self.chat_id, "text": header + digest, "parse_mode": "Markdown" } requests.post(url, data=data)
# Use in your orchestratordeliverer = TelegramDeliverer()deliverer.send_digest("investing", result["investing_digest"].synthesized_summary)What I Got Wrong
Mistake 1: Using One Agent for Everything
I tried to make a single “news agent” that understood both stock market movements and NFL trades. The summaries were generic and missed nuance.
Fix: Separate agents per topic. Each has its own system prompt and source list.
Mistake 2: Skipping Deduplication
Without deduplication, I’d get the same story from Bloomberg and Reuters. The summary became repetitive.
# Simple title-based deduplicationdef deduplicate(articles: List[dict]) -> List[dict]: seen = set() unique = [] for a in articles: key = a["title"][:50].lower() if key not in seen: seen.add(key) unique.append(a) return uniqueMistake 3: Over-Scheduling
I ran my agents every hour at first. That’s unnecessary for news that updates daily.
Fix: Run once in the morning. That’s when I actually consume the news.
# Daily at 7 AM is plenty--schedule '0 7 * * 1-5' # Weekdays only for business news--schedule '0 7 * * *' # Daily for sportsModel Selection: Don’t Overpay
Not every task needs Claude Sonnet. Here’s my cost optimization:
| Task | Model | Why |
|---|---|---|
| RSS fetching | No LLM | Just parsing XML |
| Deduplication | No LLM | String matching |
| Summarization | Claude Sonnet | Needs quality synthesis |
| Simple formatting | Claude Haiku | Cheaper, good enough |
For a daily briefing of ~50 articles across 3 topics, I spend about $0.10 per day using Sonnet for synthesis only.
The Results
Before: 55 minutes, 50+ tabs, information overload.
After: 5 minutes, 3 concise briefings delivered to Telegram.
The setup took 32 minutes (close to the promised 30). And now every morning I wake up to:
INVESTING - Thursday, March 27
**Key Themes:** Markets cautious ahead of Fed minutes; Tech sector earningsbeat expectations; Oil prices stabilize.
**Top Stories:**1. Fed signals rate pause through Q2 - markets rally2. NVIDIA earnings beat by 12%, stock up 5%3. Oil holds at $78/barrel after inventory data4. Bitcoin tests $70K resistance level5. European markets close mixed on ECB uncertainty
**Watch List:**- Tomorrow's PCE inflation data- Apple earnings next weekSimilar briefings arrive for sports and geopolitics. I read them with my coffee and I’m done.
When This Approach Works
This setup is ideal if you:
- Check multiple news sources daily
- Follow distinct topics with different analysis needs
- Want to reclaim morning time
- Prefer summaries over full articles
It’s not for you if you:
- Need real-time breaking news alerts
- Want to read every article in full
- Follow niche topics without RSS feeds
Quick Reference
For OpenClaw (30 min setup):
# 1. Create agent config (news-topics.yaml)# 2. Schedule with cronopenclaw cron add --schedule '0 7 * * 1-5' --agent investing-briefing --prompt '...' --announce
# 3. Test immediatelyopenclaw cron run [job-id]For Custom LangGraph (2-4 hour setup):
- Create
TopicNewsAgentclass - Define sources per topic
- Build LangGraph orchestration
- Add Telegram/email delivery
- Schedule with cron or cloud scheduler
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
- 👨💻 OpenClaw Documentation
- 👨💻 LangGraph Documentation
- 👨💻 Claude API Documentation
- 👨💻 Reddit Discussion: Best Use Cases of OpenClaw
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments