objective03: My Laptop Eats the News and Talks Back
Author: Daniel Kliewer
Date: 2026-06-01
Tags: local AI, news aggregation, LLM, contradiction detection, TTS, llama.cpp, KuzuDB, Qdrant, Qwen3-TTS, sovereign AI
Description: An autonomous news ingestion, claim extraction, contradiction tracking, and TTS broadcast system that runs entirely locally. No API keys. No monthly bills. No vendor lock-in.
---
[Free Apple Silicon Download](https://6340588028610.gumroad.com/l/qkxkt)
# objective03: A Locally-Run Autonomous News Ingestion and Contradiction Tracking System
## Architecture Overview: From RSS Feeds to Audio Broadcasts on Consumer Hardware
---
objective03 is a Python daemon that performs autonomous news ingestion, atomic claim extraction, entity resolution, event clustering, contradiction detection, narrative analysis, and text-to-speech broadcast — all running on local hardware via llama.cpp (Metal GPU backend), KuzuDB (embedded temporal property graph), Qdrant (vector similarity search), and Qwen3-TTS (mlx_audio). The system ingests content from RSS feeds, Reddit subreddits, and YouTube channels, extracts structured factual claims with GBNF-enforced JSON schemas, detects typed contradictions across sources, clusters claims into events and narrative threads, and generates TTS-optimized audio broadcasts with voice cloning and procedural ambient audio. Zero cloud dependencies. Zero API calls.
---
## Pipeline Architecture
The system operates as a five-group task scheduler running on independent intervals. Each group contains a sequence of subprocesses with configurable timeouts, failure limits, and circuit-breakers.
### Task Group 1: Ingestion (default interval: 60s)
The ingestion module polls three source types:
- **RSS feeds** — HTTP GET with `If-None-Match` / `ETag` support for conditional requests. Parsed via `feedparser`. Documents normalized (HTML stripped, Unicode NFKC normalized, whitespace collapsed).
- **Reddit subreddits** — OAuth2 authenticated API calls. Posts and comments extracted, metadata preserved (author, subreddit, upvotes, timestamps).
- **YouTube channels** — `yt-dlp` for metadata extraction and audio transcription. Channel upload schedules polled on configurable intervals.
All documents undergo SHA-256 deduplication before graph insertion. The normalized document is stored as a `Document` node in KuzuDB with a `FROM_SOURCE` edge pointing to the originating `Source` node.
### Task Group 2: Analysis Pipeline (default interval: 120s)
This is the core processing pipeline, executing sequentially:
#### 2a. Claim Extraction
Each document is chunked and passed to a local LLM (llama.cpp, Metal backend). The model extracts atomic factual claims using a GBNF-defined grammar that enforces a strict JSON schema:
```json
{
"claim": "string",
"confidence": "float (0.0-1.0)",
"stance": "positive | negative | neutral",
"topic": "string (tag)",
"evidence": "string (verbatim text span)"
}
```
GBNF grammar enforcement ensures the model's output is structurally valid — no schema drift, no optional fields appearing as required. Each claim node in KuzuDB carries a `confidence` property and an `EXTRACTED_FROM` edge to its source document.
#### 2b. Entity Resolution
A second local LLM call extracts named entities (PERSON, ORG, LOC, EVENT) from each document. Extracted entities are resolved against existing graph nodes via:
1. **Exact match** on entity name
2. **Fuzzy matching** using Levenshtein distance with configurable threshold
3. **Alias tracking** — multiple names resolved to the same entity node over time
Resolved entities receive a `MENTIONS` edge to the source document and an `APPEARS_IN` edge to any event nodes they participate in.
#### 2c. Event Clustering
Claims are assigned to events based on entity overlap. The algorithm:
1. Extracts all entities from the new claim
2. Queries the graph for existing event nodes connected to any of those entities
3. If matches found, the claim's `ABOUT_EVENT` edge points to the existing event
4. If no matches, a new `Event` node is created with `emerging` status
Events track:
- `importance_score` — computed from entity frequency, claim count, and temporal recency
- `status` — `emerging` -> `active` -> `resolved`
- `temporal_start` / `temporal_end` — bounded by earliest and latest claim timestamps
#### 2d. Contradiction Detection
New claims are embedded using BGE-Small-EN-v1.5 (384-dimensional) and indexed in Qdrant. For each new claim:
1. **Vector search** — cosine similarity query against the Qdrant collection. Candidates with similarity > 0.75 are returned.
2. **LLM classification** — each candidate pair is passed to the local LLM with a prompt template that classifies the relationship into one of five typed categories:
| Type | Definition | Example |
|------|-----------|---------|
| `DIRECT_CONTRADICTION` | Same proposition, opposite truth value | "GDP grew 3%" vs "GDP shrank 3%" |
| `NUMERICAL_DISCREPANCY` | Same proposition, different values | "100 casualties" vs "200 casualties" |
| `FRAMING_DIFFERENCE` | Same event, different narrative lens | "Tax relief" vs "Tax cut for corporations" |
| `TEMPORAL_DISCREPANCY` | Same event, different timing | "Signed Monday" vs "Signed Tuesday" |
| `COMPATIBLE` | No contradiction; semantic overlap warrants review | — |
Contradictions are persisted as `CONTRADICTS` edges between claim nodes with a `type` property. Contradictions are **never auto-resolved** — the system preserves the raw disagreement for downstream consumption.
#### 2e. Narrative Analysis
Claims not assigned to events (i.e., no entity overlap with existing event nodes) are grouped into narrative threads via embedding cosine similarity clustering (>0.75 threshold). Each cluster receives an LLM-generated label. Active narratives track:
- `drift_score` — semantic shift over time within the narrative
- `framing_classification` — dominant narrative frame (e.g., "economic," "political," "social")
#### 2f. Source Reliability Scoring
Each `Source` node accumulates a reliability score based on historical claim accuracy — measured by the frequency of that source's claims being contradicted by other sources. Sources that consistently produce contradictory claims see their reliability scores degrade over time.
#### 2g. Graph Update
All extracted nodes and edges are committed to KuzuDB in a single transaction per batch.
### Task Group 3: Broadcast Generation (default interval: 90s)
A local LLM queries the KuzuDB graph via Cypher-like queries for:
- Top N events by `importance_score`
- Unresolved contradictions (all `CONTRADICTS` edges with no resolution)
- Active narratives (narratives with `status == "active"`)
- System metrics (sources ingested, claims extracted, contradictions detected)
The LLM produces an 800–1200 word broadcast script optimized for TTS. The script uses `` blocks for internal reasoning before the spoken output. The prompt template includes structural directives: opening summary, top events, contradiction deep-dives, narrative shifts, and closing metrics.
### Task Group 4: Audio Production (default interval: 90s)
The broadcast script undergoes preprocessing:
1. **Chunking** — split into ~100-word segments
2. **Abbreviation expansion** — "U.S." -> "United States", "Dr." -> "Doctor"
3. **Number normalization** — "3.5%" -> "three and a half percent", "$500M" -> "five hundred million dollars"
4. **Date formatting** — "Jan 15, 2026" -> "January fifteenth, twenty twenty-six"
5. **Punctuation normalization** — ellipses, em-dashes, and other TTS-sensitive characters
Preprocessed segments are synthesized via Qwen3-TTS using mlx_audio. Voice cloning is supported via reference audio input. Synthesized audio segments are crossfaded at boundaries and queued for playback via `afplay` on macOS.
### Task Group 5: Maintenance (default interval: 24h)
- **Memory consolidation** — low-importance events and old narratives pruned based on `importance_score` thresholds
- **Graph evaluation** — sample of claims re-verified against source documents for accuracy metrics
- **Embedding index rebuild** — Qdrant index refreshed with all current claim embeddings
---
## Storage Architecture
| Tier | Technology | Content |
|------|-----------|---------|
| **Graph** | KuzuDB (embedded) | 8 node types: Source, Document, Claim, Entity, Event, Narrative, Broadcast, ContradictionSummary. 10 edge types: FROM_SOURCE, EXTRACTED_FROM, MENTIONS, ABOUT_EVENT, CONTRADICTS, SUPPORTS, PART_OF_THREAD, APPEARS_IN, REFERENCES, PREVIOUS_VERSION |
| **Vector** | Qdrant | BGE-Small-EN-v1.5 claim embeddings (384-dim), cosine similarity search with 0.75 threshold |
| **Audio** | Local filesystem | Generated TTS WAV segments, crossfaded master tracks, procedural ambient drone |
| **Config** | YAML/JSON | Scheduler intervals, model paths, source lists, embedding thresholds |
| **State** | Local JSON | Scheduler state, circuit-breaker status, last-run timestamps |
KuzuDB serves as the single source of truth. The temporal graph preserves full provenance: every claim points to its source document, every contradiction points to both claims, every event points to its contributing claims. Queries traverse edges to reconstruct the full evidence chain.
---
## Node and Edge Schema
### Node Types
| Node | Key Properties |
|------|---------------|
| `Source` | name, url, type (rss|reddit|youtube), reliability_score |
| `Document` | sha256, title, url, ingest_timestamp, source_type |
| `Claim` | text, confidence, stance, topic, evidence_text, extraction_timestamp |
| `Entity` | name, type (person|org|loc|event), alias_list |
| `Event` | label, importance_score, status, temporal_start, temporal_end |
| `Narrative` | label, drift_score, framing_classification, status |
| `Broadcast` | script_path, duration, timestamp, event_count, contradiction_count |
| `ContradictionSummary` | type, claim_a_id, claim_b_id, resolution, resolution_timestamp |
### Edge Types
| Edge | From | To | Properties |
|------|------|-----|-----------|
| `FROM_SOURCE` | Source | Document | ingest_timestamp |
| `EXTRACTED_FROM` | Claim | Document | extraction_timestamp |
| `MENTIONS` | Entity | Document | context |
| `ABOUT_EVENT` | Claim | Event | temporal_timestamp |
| `CONTRADICTS` | Claim | Claim | type, detected_timestamp |
| `SUPPORTS` | Claim | Claim | type, detected_timestamp |
| `PART_OF_THREAD` | Claim | Narrative | confidence |
| `APPEARS_IN` | Entity | Event | role |
| `REFERENCES` | Event | Event | relation_type |
| `PREVIOUS_VERSION` | Claim | Claim | version_number |
---
## Inference Stack
| Component | Technology | Details |
|-----------|-----------|---------|
| **LLM Inference** | llama.cpp | Metal GPU backend (Apple Silicon). Quantized GGUF models. GBNF grammar enforcement for structured output. |
| **Embeddings** | BGE-Small-EN-v1.5 | 384-dimensional text embeddings. Indexed in Qdrant for cosine similarity search. Threshold: 0.75. |
| **TTS** | Qwen3-TTS via mlx_audio | Voice cloning from reference audio. Multi-lingual. Synthesized in ~100-word segments. |
| **Video Download** | yt-dlp | YouTube metadata and transcript extraction. |
| **RSS Parsing** | feedparser | RFC 4287 compliant RSS/Atom parsing with ETag support. |
---
## Scheduler Configuration
```yaml
scheduler:
ingestion:
interval_seconds: 60
max_runtime_seconds: 300
failure_limit: 5
circuit_breaker_timeout: 600
analysis_pipeline:
interval_seconds: 120
max_runtime_seconds: 600
failure_limit: 3
circuit_breaker_timeout: 1800
steps:
- claim_extraction
- entity_resolution
- event_clustering
- contradiction_detection
- narrative_analysis
- framing_analysis
- source_reliability
- graph_update
broadcast:
interval_seconds: 90
max_runtime_seconds: 120
failure_limit: 5
circuit_breaker_timeout: 600
audio_production:
interval_seconds: 90
max_runtime_seconds: 300
failure_limit: 5
circuit_breaker_timeout: 600
maintenance:
interval_seconds: 86400
max_runtime_seconds: 3600
failure_limit: 2
circuit_breaker_timeout: 7200
```
---
## Why This Stack
The current AI industry narrative centers on a "compute shortage" — a claimed physical limitation of GPU availability. objective03 demonstrates that this is primarily a **billing bottleneck**: paid AI providers extract wealth through API costs, inflating operational expenses while quantized models on consumer hardware deliver comparable performance for inference-heavy workloads.
BGE-Small-EN-v1.5 for embeddings runs on CPU in under 10ms per document. llama.cpp with Metal GPU quantizes 7B-parameter models to run at interactive speeds on Apple Silicon. Qwen3-TTS synthesizes speech at 2x real-time on an M-series chip. Qdrant runs embedded with minimal memory footprint. KuzuDB is embedded with zero external dependencies.
The total infrastructure cost: model download size (a few GB) plus disk space for the graph and audio. No monthly bills. No API rate limits. No vendor lock-in.
---
## The Repo
objective03 is MIT licensed, requires Python 3.11+, and is structured as:
- `backend/` — Python daemon, ingestion modules, analysis pipeline, scheduler
- `electron/` — Electron desktop wrapper with system tray integration
- `docs/` — Configuration examples, Cypher query patterns, schema diagrams
- Root — `requirements.txt`, `scheduler_config.yaml`, `.env.template`, startup scripts
Built by Daniel Kliewer (kliewerdaniel). Follows the local-first philosophy established in "mastering llama.cpp local LLM integration": a weak local model controlled by the user is superior to a powerful cloud model controlled by a vendor.
---
## Why "objective03"?
"objective" — the system ingests raw data, extracts claims, detects contradictions, and presents findings without preference. Objectivity as a system property, not a guarantee.
"03" — version three. Also: the third wave of local AI. Wave 1 was CPU inference (slow, universal). Wave 2 was GPU inference (fast, desktop-bound). Wave 3 is Metal/MLX inference on consumer SoCs (fast, portable, power-efficient).
---
## Roadmap
- [ ] Source reliability scoring (per-source accuracy tracking and degradation curves)
- [ ] Cross-platform audio output (ALSA/PulseAudio for Linux, WASAPI for Windows)
- [ ] Multi-model pipeline routing (different models for extraction vs. contradiction classification vs. broadcast generation)
- [ ] Web dashboard for graph inspection (networkX visualization, Cypher query console)
- [ ] Incremental graph updates via temporal snapshots
- [ ] Claim verification against external fact-checking APIs (optional, cloud-fallback)
---
**Get it at:** [github.com/kliewerdaniel/objective](https://github.com/kliewerdaniel/objective)
[Free Apple Silicon Download](https://6340588028610.gumroad.com/l/qkxkt)
*The compute shortage is a billing shortage. Your laptop already has the silicon.*