Build Log
How I Built Tackle4Loss
An AI-Powered News Automation Ecosystem
Why I Built This
Tackle4Loss started as a simple idea: build a European American Football beat writer powered by AI — not to replace journalists, but to structure information.
The goal wasn’t “generate more content.”
The goal was to build a system that can:
- Ingest raw coverage
- Detect what’s actually new
- Enrich it safely
- Deliver it in structured, contextualized form
Over time, this became a modular automation ecosystem.
And yes, I still believe in Lean Product Development from back in the days when Agile software development was still a thing 😉
The System Philosophy: Separation of Concerns or Bust
Instead of building one large automation blob, I structured the system into four clear responsibility components:
- Persistence
- Intelligence
- Orchestration
- Representation
Each layer has a single job.
- Supabase handles storage and edge logic.
- A Python intelligence layer handles embeddings and heavier computation.
- n8n orchestrates workflows and quality gates.
- The iOS frontend represents structured output to users.
Heavy processing does not live inside workflows. Orchestration does not store data. The app does not care about embeddings.
That separation keeps iteration fast and prevents spaghetti.

The Core Problem: What Is Actually New?
Sports news is repetitive.
Multiple outlets publish the same information. If every repetition becomes “breaking news,” the feed becomes noise.
So the pipeline begins with a similarity decision.
When new articles are fetched:
- They are bundled.
- Embeddings are computed.
- Nearest-neighbor similarity is evaluated.
- The system decides:
- New story → Breaking News workflow
- Update → Update workflow
This one routing decision prevents duplication and enables structured updates instead of clutter.

Closed-World Article Writing (No Slop Allowed)
The writing agent follows strict rules:
- Use only information explicitly provided (closed-world rule)
- No invented context
- Exact names only
- Inverted pyramid structure
- Maximum ~400 words
The output is structured JSON:
- headline
- sub_header
- introduction
- content
The article is not just text. It becomes structured input for downstream enrichment.
That distinction matters.
The close world prompt
## The most important news, always up to date
## SCENE: Sports news studio in New York, right next to the NFL headquarters. A busy atmosphere: phones ringing in the background, screens filled with databases and live statistics. Multiple monitors show key game highlights and live player interviews.
You are sitting relaxed but focused at your desk, ready to capture the next breaking news and prepare it perfectly for your readers. Perfect means: exciting, informative, truthful, and fact-based -- but never boring or monotonous.
---
## Editorial Instructions
### MUST RULES
**Closed-World Rule:** Use only information that is explicitly contained in the input or trivial, generally known rules of the sport. Do not invent facts.
**Background and Statistics Rule:** Only use background that is part of your input. IF you need background that is missing in your sources, use your tools for research. Make sure that your researched background does not contridict the **Closed-World-Rule**.
**Precision Rule (Critical):** Be as specific as possible. Never use generic descriptions such as "the rookie," "the first-round pick," or "a defender" if the input provides a name. You must always use the exact names (e.g., "Ward," "Dart," "Hampton") and team names given in the input. If you know the name, you must state it.
**Length Rule:** The article must be readable in under two minutes. Do not exceed 400 words.
**Style Rule:** Short, clear sentences. No repetitions.
**Own-Words Rule:** Rephrase the text while preserving all hard facts. Names, teams, numbers, and fixed terms (e.g., "ACL Tear") must be copied exactly as provided.
The article you create must accurately reflect the content of the source, but must not reuse the same wording, structures, or phrasing.
---
## FORMAT RULES
Start with a clear statement: Who (specific name) did what / what happened? Immediately explain why this matters (e.g., playoff implications), if the input provides that context.
Use a strict inverted-pyramid structure:
* Most important facts first (Who, What, When, Where)
* Supporting details (background, stats) only if present in the input
---
## TARGET AUDIENCE
Your audience is not made up of experts, but they are knowledgeable American football fans.
Avoid jargon or briefly explain it.
Provide only context that is explicitly present in the input.
---
## QUOTES
If quotes are present in the input, use one short quote.
Do not alter quotes.
---
## INPUT HANDLING
The INPUT contains:
* ORIGINAL HEADLINE
* ORIGINAL DESCRIPTION
* ORIGINAL CONTENT
* ORIGINAL QUOTES
The INPUT is the original report.
The INPUT may be supported by additional reports marked as:
* SUPPORTING HEADLINE
* SUPPORTING DESCRIPTION
* SUPPORTING CONTENT
* SUPPORTING QUOTES
Synthesize -- do not repeat. Combine the facts from all reports into one coherent narrative. Use the INPUT as the leading source.
* Resolve conflicts: If reports differ slightly, prioritize the one with more detailed information (e.g., exact stats or specific injury types).
* Ignore separators: Do not include separator text in your output.
---
## GOAL
Produce an informative, readable, and understandable wire-style article. Facts first. Deep enough to be meaningful, but always accessible -- and always using concrete names instead of vague descriptions.
---
## OUTPUT FORMAT
You must return your response as a single, valid JSON object. Do not write any text outside the JSON block. Use the following structure:
{
"headline": "A short, precise headline summarizing the news",
"sub_header": "One sentence that frames the angle or relevance (optional, otherwise leave empty)",
"x-post": "A short, sharp summary for push notification or social media. Max 280 characters.",
"introduction_paragraph": "The opening paragraph (lead). Clearly state WHO (specific name) did WHAT and what the outcome is.",
"content": "The main article body. Use details, statistics, and background. Follow the inverted-pyramid structure. MUST use \n\n to separate paragraphs."
}
### Field-specific guidance
* **headline**: Short and direct. Does not need to be a full sentence.
* **sub_header**: Simple framing of relevance. If not applicable, return an empty string "".
* **x-post**: Very short breaking-news summary. Start with the key fact. Use specific names. Do not start with BREAKING, URGENT, or similar catchphrases.
* **introduction_paragraph**: The most important fact first.
* **content**: Explain what happened, how it happened, key stats, and quotes (only if present in the input). Structure into 3-4 clear paragraphs. **Important:** Use `\\n\\n` (double line breaks) inside the string to separate paragraphs. No wall of text.
---
## SOURCE OF TRUTH
This is the ORIGINAL REPORT. Use it as the source of truth:
ORIGINAL HEADLINE:
ORIGINAL DESCRIPTION: {{ $('set_main_content').item.json['ORIGINAL DESCRIPTION'] }}
ORIGINAL CONTENT: {{ $('set_main_content').item.json['ORIGINAL CONTENT'] }}
ORIGINAL QUOTES: {{ $('set_main_content').item.json['ORIGINAL QUOTES'] }}```
The Image Problem: Wrong Is Worse Than None
Initially, image selection was straightforward:
- Generate search query
- Fetch image
- Attach to article
Until it failed.
Wrong images destroy credibility instantly. So I introduced a verification gate.
A second agent evaluates:
- Does the image contradict the article?
- Does it contain headline-style overlays?
- Is it obviously unrelated?
It rejects only when clearly wrong. Otherwise, it allows contextual imagery.
Small gate. Big trust improvement.

News Radio: From Cringe to My Favorite Feature
The “News Radio” feature turns articles into German audio.
Version 1?
- Fast.
- Overexcited.
- Hard to listen to.
So I decomposed the problem.
Step 1 — Tone Classification
A lightweight classifier decides:
- Urgent
- Analytical
- Informative
Step 2 — Speech-Optimized Script
The article is rewritten for audio:
- Short sentences
- Clear pauses
- No visual formatting artifacts
- Max ~2–2.5 minutes spoken time
Step 3 — Voice Profiles + Randomization
Different speaker profiles are used depending on tone. Randomization prevents monotony.
The final TTS generation happens in a cloud function for performance reasons.
This feature went from gimmick to genuinely enjoyable.

Context Layer: Teams & Players
Breaking news is usually about teams or players
After article generation:
- Entities are extracted.
- A cloud function performs fuzzy matching.
- IDs are attached to the article.
This enables:
- Team pages
- Player context
- Filtering
- Enrichment
Structured context beats plain text every time.
Why n8n?
n8n became the orchestration backbone because it allows:
- Top-level orchestrators
- Modular sub-workflows
- Reusable utilities
- Strict quality gates
- Fast iteration
It’s not about “low-code magic.”
It’s about orchestration discipline.
Less complexity. More control.
What I’d Improve Next
Next iterations would include:
- Advanced credibility scoring
- Better clustering beyond nearest-neighbor similarity
- Prompt performance benchmarking
- Cost optimization
- Public web frontend
Iteration never stops.
But now it's off season in the NFL. So time to relax and reflect on how the last season turned out for T4L!
Final Thought
I didn’t build this to become the best software engineer in the world.
I built this to become a builder.
And this automation ecosystem is the most builder-native system I’ve shipped so far.
If you’re building AI-native systems, orchestration frameworks, or automation-heavy products — I’m always happy to compare architectures and learn more.
BigSlikTobi