Fetch News Skill
Use this skill at the start of every sweep. It queries two public sources —
no account, credentials, or API key required for either — and owns the
cross-run dedupe state:
- Google News RSS (
news.google.com) — a search feed per topic plus a
headline feed per genre (WORLD, NATION, BUSINESS, TECHNOLOGY,
ENTERTAINMENT, SCIENCE, SPORTS, HEALTH)
- Hacker News via the Algolia search API (
hn.algolia.com) — stories and
comments per topic, for the community-buzz angle
- Medium RSS (
medium.com) — long-form posts per tag from
interests.json's medium_tags, for the practitioner-writing angle
Usage
python3 skills/fetch-news/scripts/fetch_news.py
Topics, genres, and the per-topic cap all come from
.agents/workspace/interests.json — change that file, not the script.
The script:
- Reads the interests profile.
- Fetches every topic (both sources) and every genre (Google News). If a
source fails for one topic, the sweep continues and records the error in
source_errors.
- Drops every item whose id is already in
.agents/workspace/seen_items.json,
then adds the new ids to that file (bootstrapping it on the first run).
- Writes the fresh items to
.agents/workspace/new_items.json and prints them
to stdout, with a one-line summary on stderr.
Output format (new_items.json)
{
"topics": ["Gemini API"],
"genres": ["TECHNOLOGY"],
"fetched_at": "2026-07-15T09:00:03Z",
"source_errors": [],
"new_count": 42,
"items": [
{
"id": "gn:CBMiXyz…",
"source": "news",
"outlet": "The Verge",
"title": "…headline…",
"context": null,
"url": "https://news.google.com/rss/articles/…",
"published": "2026-07-15T08:12:44Z",
"engagement": null,
"topic": "Gemini API"
},
{
"id": "hn:44581234",
"source": "community",
"outlet": "Hacker News (comment by example_dev)",
"title": "…comment text…",
"context": "Title of the story the comment is on",
"url": "https://news.ycombinator.com/item?id=44581234",
"published": "2026-07-15T07:03:10Z",
"engagement": 12,
"topic": "Gemini API"
}
]
}
source is news (Google News), community (Hacker News), or longform
(Medium, with ids prefixed md: and topic: "medium:<tag>"). engagement
is only present for community items (points or comment count). Genre items
carry topic: "genre:<name>".
Instructions for the Agent
- Run this exactly once per sweep. Running it twice in one sweep marks items
as seen without you having curated them.
- Treat
items as the complete candidate pool for the sweep. new_count: 0
means a quiet interval — still write the briefing.
- If
source_errors is non-empty, name the degraded source in the briefing —
the counts only cover the sources that responded.
1---2name: fetch-news3description: Pulls the latest Google News and Hacker News items for every topic and genre in the reader's interests, deduped against every item shown in previous runs.4---56# Fetch News Skill78Use this skill at the start of every sweep. It queries two public sources —9no account, credentials, or API key required for either — and owns the10cross-run dedupe state:1112- **Google News RSS** (`news.google.com`) — a search feed per topic plus a13 headline feed per genre (`WORLD`, `NATION`, `BUSINESS`, `TECHNOLOGY`,14 `ENTERTAINMENT`, `SCIENCE`, `SPORTS`, `HEALTH`)15- **Hacker News** via the Algolia search API (`hn.algolia.com`) — stories and16 comments per topic, for the community-buzz angle17- **Medium RSS** (`medium.com`) — long-form posts per tag from18 `interests.json`'s `medium_tags`, for the practitioner-writing angle1920## Usage2122```bash23python3 skills/fetch-news/scripts/fetch_news.py24```2526Topics, genres, and the per-topic cap all come from27`.agents/workspace/interests.json` — change that file, not the script.2829The script:301. Reads the interests profile.312. Fetches every topic (both sources) and every genre (Google News). If a32 source fails for one topic, the sweep continues and records the error in33 `source_errors`.343. Drops every item whose id is already in `.agents/workspace/seen_items.json`,35 then adds the new ids to that file (bootstrapping it on the first run).364. Writes the fresh items to `.agents/workspace/new_items.json` and prints them37 to stdout, with a one-line summary on stderr.3839## Output format (`new_items.json`)4041```json42{43 "topics": ["Gemini API"],44 "genres": ["TECHNOLOGY"],45 "fetched_at": "2026-07-15T09:00:03Z",46 "source_errors": [],47 "new_count": 42,48 "items": [49 {50 "id": "gn:CBMiXyz…",51 "source": "news",52 "outlet": "The Verge",53 "title": "…headline…",54 "context": null,55 "url": "https://news.google.com/rss/articles/…",56 "published": "2026-07-15T08:12:44Z",57 "engagement": null,58 "topic": "Gemini API"59 },60 {61 "id": "hn:44581234",62 "source": "community",63 "outlet": "Hacker News (comment by example_dev)",64 "title": "…comment text…",65 "context": "Title of the story the comment is on",66 "url": "https://news.ycombinator.com/item?id=44581234",67 "published": "2026-07-15T07:03:10Z",68 "engagement": 12,69 "topic": "Gemini API"70 }71 ]72}73```7475`source` is `news` (Google News), `community` (Hacker News), or `longform`76(Medium, with ids prefixed `md:` and `topic: "medium:<tag>"`). `engagement`77is only present for community items (points or comment count). Genre items78carry `topic: "genre:<name>"`.7980## Instructions for the Agent8182- Run this exactly once per sweep. Running it twice in one sweep marks items83 as seen without you having curated them.84- Treat `items` as the complete candidate pool for the sweep. `new_count: 0`85 means a quiet interval — still write the briefing.86- If `source_errors` is non-empty, name the degraded source in the briefing —87 the counts only cover the sources that responded.