Skip to content

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:

TopicSourcesTabsTime
InvestingBloomberg, Reuters, WSJ, Finviz15-2020 min
SportsESPN, The Athletic, team sites10-1515 min
GeopoliticsBBC, Reuters, Foreign Affairs10-2020 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:

  1. Fetch news from multiple sources automatically
  2. Deduplicate overlapping stories
  3. Prioritize what actually matters to me
  4. 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:

news-topics.yaml
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: telegram

Step 2: Schedule the Delivery

schedule-agents.sh
# Investing briefing at 7 AM weekdays
openclaw 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 daily
openclaw cron add \
--schedule '0 7 * * *' \
--agent sports-briefing \
--prompt 'Create the daily sports summary.' \
--announce
# Geopolitics briefing at 7 AM weekdays
openclaw cron add \
--schedule '0 7 * * 1-5' \
--agent geopolitics-briefing \
--prompt 'Create the daily geopolitics summary.' \
--announce

That’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

topic_news_agent.py
from dataclasses import dataclass
from typing import List, Optional
from anthropic import Anthropic
import feedparser
@dataclass
class NewsItem:
title: str
source: str
url: str
summary: str
@dataclass
class 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 themes
2. Top 5 stories with 1-sentence summaries
3. 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

news_orchestrator.py
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated, Optional
import operator
# Define sources for each topic
INVESTING_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

telegram_delivery.py
import os
import requests
from 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 orchestrator
deliverer = 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.

deduplication.py
# Simple title-based deduplication
def 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 unique

Mistake 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.

correct-schedule.sh
# Daily at 7 AM is plenty
--schedule '0 7 * * 1-5' # Weekdays only for business news
--schedule '0 7 * * *' # Daily for sports

Model Selection: Don’t Overpay

Not every task needs Claude Sonnet. Here’s my cost optimization:

TaskModelWhy
RSS fetchingNo LLMJust parsing XML
DeduplicationNo LLMString matching
SummarizationClaude SonnetNeeds quality synthesis
Simple formattingClaude HaikuCheaper, 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 earnings
beat expectations; Oil prices stabilize.
**Top Stories:**
1. Fed signals rate pause through Q2 - markets rally
2. NVIDIA earnings beat by 12%, stock up 5%
3. Oil holds at $78/barrel after inventory data
4. Bitcoin tests $70K resistance level
5. European markets close mixed on ECB uncertainty
**Watch List:**
- Tomorrow's PCE inflation data
- Apple earnings next week

Similar 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):

openclaw-quickstart.sh
# 1. Create agent config (news-topics.yaml)
# 2. Schedule with cron
openclaw cron add --schedule '0 7 * * 1-5' --agent investing-briefing --prompt '...' --announce
# 3. Test immediately
openclaw cron run [job-id]

For Custom LangGraph (2-4 hour setup):

  1. Create TopicNewsAgent class
  2. Define sources per topic
  3. Build LangGraph orchestration
  4. Add Telegram/email delivery
  5. 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:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments