AGENTIC AI ENGINEERING CASE STUDY
TechDrishti
An autonomous AI news agent
PUBLISHES DAILY AT 8:00 AM IST · BUILDING SINCE JUNE 2026
Every morning, before most of India is awake, an agentic AI workflow running unattended on GitHub Actions reads the day's English tech news, decides for itself which stories are worth covering, calls out to its own research agent when a fact needs real investigation, and writes each one from scratch, in original Hindi prose rather than machine translation, through a multi-stage LLM pipeline it has to be caught arguing with itself along the way.
This page is the log of that build: why the agentic workflow exists, what the AI architecture does today, and nine real bugs found along the way, each with what was claimed, what broke, what fixed it, and how it was verified. Nothing below is hypothetical.
Why this exists
There's no shortage of Hindi tech coverage, but almost all of it is either wire-service translation or a human editor's fast rewrite. The bet here was different: could an agentic AI workflow make its own editorial calls, autonomously research what it doesn't know by calling out to a second AI agent, and write original Hindi prose, at a running cost of fractions of a cent per article, with no server or hosting bill beyond a GitHub Actions runner? That framing is what makes the field reports below worth reading: each one is the AI agent being asked to make a judgment call a human editor makes without thinking twice, getting it wrong the first time, in a way specific to how it was asked.
Skip or publish, which entities matter, what still needs researching: all model judgment. Nobody reviews a draft before it goes out at 8 AM.
The starter-tier ceiling is fixed. Reasoning and output share the same budget, so a call that reasons too long can return nothing at all.
Research, not translation
The differentiator isn't the writing, it's what feeds it. The pipeline doesn't rephrase the source article: it researches the topic the way a reporter would, and hands the writer real, grounded facts the source article never mentioned. That's also a deliberate copyright and originality choice, not just a quality one: a published piece is meaningfully distinct from what it started from because it adds real reporting on top, not because it's reworded.
Stage 1 reads the article and title and produces two things: research questions that would fill real gaps in the story, and a list of named entities, the people, products, and companies worth knowing about.
Most entities are unambiguous (“OpenAI,” “Google”) and get a plain, direct definition search, cheap and fast, since there's only one real answer. Some aren't: “Python” (the language or the snake), “Elastic” (the company or the physics concept). For these the model is asked to flag the entity as ambiguous and write its best guess at which sense applies, from context alone, before any search runs. That guess is what disambiguates the search, instead of search having to guess blind. This exists because of two separate failures: first, ambiguous entities weren't being classified at all, so searches came back confidently wrong (a project called “Tom Riddle” resolved to a mobile game, “xovi” resolved to NASCAR); second, even once flagged, a plain keyword search still couldn't reliably resolve the ambiguity on its own. Classifying, then searching with that context attached, is what fixed both, and it's also the cheapest path: most entities are unambiguous and need nothing more than a plain search, no extra API calls.
Some questions a news article raises don't have one searchable fact behind them at all, e.g. “What are the geopolitical implications of the US blocking Fable and Mythos models, and how does Z.ai benefit?”
Early versions of the pipeline just skipped questions like this, since there was no tool that could actually investigate one; a keyword search returns a page that mentions the same words, not an answer that weighs several facts together. That's what the self-built research agent is for: routed only the queries that genuinely need reasoning across multiple sources, not the entity lookups from Step 1, which stay on free search since they don't need it.
Built like a newsroom, not a translation
The whole thing starts from one decision: this is a newsroom, not a translate button. A translator takes one article and rewords it. A newsroom is slower and does something different — it reads everything coming in, argues about what actually matters, sends a reporter to fill the gaps, agrees on the angle, and only then writes. Each of those is a separate job, done by a separate desk, and keeping them apart is exactly how a real newsroom avoids mistakes. This system copies that structure on purpose: every stage is one desk with one job, here for one reason.
- The wire desk — reads everything, keeps almost nothing
A mountain of tech news lands every morning and most of it is noise. The first job is simply to scan all of it and throw the junk back, so the rest of the newsroom only ever looks at things that might be real stories.
- The foreign desk — makes sure a foreign story is actually understood first
If something breaks in another language, you don't report on your rough guess of what it says. You get it properly understood before anyone writes a word, because every step after this is built on top of it.
- The news editor — notices when five outlets are telling one story
One event covered by five sites is still one event. Someone has to catch that and say “we cover this once, and cover it well,” instead of publishing the same thing five times over.
- The gut check — decides whether it's even worth a story
Not everything that's true is worth writing. This is the editor's “so what?” — a plain yes or no, made before anyone spends effort on a piece that shouldn't exist.
- The reporter — does the legwork the original article didn't
This is the part that makes it reporting and not rewording: chasing down the facts the source left out or took for granted, so the finished piece knows things the article it started from never said.
- The editorial meeting — agrees what the story is actually about
Before a word is written, the desk settles the angle — what the story is really about, what to lead with, what to cut. Skip this and you get a pile of facts instead of a story.
- The writer — writes it fresh, in the paper's own voice
Only now does anyone write — an original Hindi article built from the plan, not a translation of the English source. A brand-new piece, not a reworded one.
None of these desks is doing anything magic on its own. The whole idea is the order and the separation — the same reason newsrooms have desks in the first place. What each one actually does under the hood is next.
How the agentic workflow thinks today
No server, no database, no standing infrastructure: the entire agentic AI workflow runs inside a daily GitHub Actions job, chains through collection, an LLM editorial gate, a self-built research agent, and a two-stage writing pipeline, then pushes its own output back to the repo, unattended, end to end.
Click a step for how it actually works.
8 RSS feeds (Hacker News, MIT Technology Review, TechCrunch, The Verge, Ars Technica, VentureBeat AI, OpenAI News, DeepMind Blog) plus GitHub trending across the topic queries in config/github.yaml. Two filter layers run before a model ever sees a GitHub item: a denylist for abuse and job-listing terms plus a regex that flags listicle-style spam (a year stamp next to a marketing word like “ultimate” or “guide”), then a batched sarvam-105b call acting as an editor, approving only genuine AI/ML developments. That editorial verdict is known to be batch-context-dependent, the same repo can be approved or rejected depending on what else is in its batch of 15, an accepted limitation rather than a solved one.
langdetect samples up to 500 characters of the scraped body locally, no API call, to guess the source language. Anything not English gets the title, summary, and full scraped text run through Google Translate before Stage 1 ever reads them. This step didn't exist originally: a Chinese-sourced article that Sarvam synthesized from went straight into Hindi generation completely untranslated. Verified end-to-end on a real Chinese article afterward: a person's name was correctly translated and the full pipeline still produced a complete, well-formed Hindi article.
Titles are embedded with all-mpnet-base-v2 (768 dimensions, local, no API) and compared pairwise by cosine similarity at a 0.44 threshold, tuned against a known set of real true and false positive pairs. This replaced two earlier approaches, word-overlap counting and then TF-IDF, both of which missed genuine duplicates worded differently. The accepted tradeoff: two different Anthropic stories (a product launch, a funding round) can occasionally score high enough to fold together. Judged better than the alternative failure mode: the same story published twice.
The only free step here is a length check on the scraped text (reject anything too thin to be a real article) before any model is called. Everything after that costs a real, metered API call: a skip gate that writes its stated REASON before its SKIP verdict (with reasoning off, word order is the only lever, reversing it took the gate from 0/5 correct to 10/10 across real articles); an entity and research-gap analysis call reading the first 2,000 characters of the source (raised from 800 after that shorter window cut off real entity names and left only generic nouns to extract); and a JSON-extraction call that transcribes that analysis instead of re-deciding anything. Three sarvam-30b calls per article that reaches this stage, not one.
Every extracted entity checks a 45-day knowledge cache before anything is searched, a genuine cost saving since a cache hit skips both the search and the model call that would otherwise follow it. On a cache miss, retrieval itself is free (DuckDuckGo or Google News, no API key), but the raw snippets are then distilled into a direct answer by a real sarvam-30b call, that synthesis step is not free, it's what keeps entity_context from being unusable snippet soup. Ambiguous entities and comparison or why-now questions skip DDG entirely and go to a separate research agent, a second project of mine that actually reads multiple sources and reasons about conflicting claims instead of keyword-matching a snippet, called here as a paid API. A code-level filter drops any comparison query that names a competitor never mentioned in the article, since prompt wording alone didn't stop the model from inventing one. Any later query still saying “this” or “it” with no named entity gets the article's own opening question attached as background context before it's dispatched, since the agent answering it has no memory of the sibling query it's implicitly relying on.
A self-built local cache keyed by resolved entity, not raw string, so "Python (language)" and "Python (snake)" never collide. Every entity checks the cache before a single search or model call fires: a hit costs nothing at all, no DDG fetch, no sarvam-30b synthesis call. Only a miss pays for search + model. Because most entities recur across runs (the same companies, people, and products keep showing up in tech news), this alone is what keeps the RESEARCH stage's real per-article API spend a fraction of what it would be if every entity were re-researched from scratch, on every run, forever.
The first call writes its editorial thinking out in plain text (core narrative, key facts and quotes, disambiguation targets, paragraph plan) with reasoning explicitly off, so the visible output is the actual thinking, not a summary competing with hidden reasoning tokens for the same completion budget. The second call transcribes that plain text into the strict JSON schema, told not to re-analyze or invent anything not already written. Before this split, one combined call occasionally burned its entire budget on invisible reasoning and returned JSON cut off mid-object, which silently fell through to a bare translated stub.
Output is plain, labeled Hindi text (headline, a short concept explainer, intro, main body, analysis, conclusion), each section with its own word-count target and an overall target for the piece, not JSON: an earlier version of this stage did output JSON, adopted specifically to stop labels drifting into Hindi or markdown mid-response, but the current prompt reverted to labeled plain text as the primary format, JSON is kept only as a fallback parser in case the model reverts to its old habit. Explicitly told to write comprehensively rather than a short gist, since real completion usage was measured at only ~11% of the token cap. A separate fix stops the model from copying the plan's own instruction wording (“explain the technology behind X”) into the article instead of actually explaining it. One automatic retry absorbs a rare transient API failure, since a real production run once failed twice for a reason that reproduced cleanly 3 out of 3 times when retried in isolation, before falling back to a plain-translated stub.
./deploy_pipeline.sh --research-modeReverted the RESEARCH stage to DuckDuckGo-only search. Tiered research-agent routing stays in the codebase, just switched off for production runs.
Reason: build time and cost cut took priority over the marginal research-quality gain. Tiered routing kept in the codebase for future use, not deleted.
Nine bugs that shaped the evolution
Every fix was reproduced against real articles, not synthetic test cases, the honest failure rate included alongside the fix. Real before/after artifacts where they survive: an empty API response, a wrong query the model actually sent, a Hindi sentence it actually wrote.
Claim, what broke, the fix, and how it was verified, each with the real captured evidence.
Measuring what it gets wrong
The field reports above are qualitative: one bug, one fix, verified once. On top of that sits a quantitative evaluation layer, three hand-labeled golden sets (1,505 rows) with judge-validated harnesses, that puts real numbers on how often the pipeline's own judgment is wrong, and measures two prompt fixes as direct before/after. Held to the same honesty standard: the one metric that regressed is reported as-is, not tuned away.
Golden sets, judge calibration, the two verified prompt iterations, and real misclassifications before and after each fix.
By the numbers
Model choice was a cost decision, checked against the obvious alternative rather than assumed:
| Per 1M tokens | Input | Cached | Discount | Output |
|---|---|---|---|---|
| sarvam-30b | $0.029 | $0.017 | 40% | $0.115 |
| sarvam-105b | $0.046 | $0.029 | 37.5% | $0.184 |
| deepseek-v4-flash | $0.140 | $0.0028 | 98% | $0.280 |
| deepseek-v4-pro | $0.435 | $0.0036 | ~99% | $0.870 |
Sarvam runs 5–9× cheaper per raw token than the closest DeepSeek equivalent: the reason it was kept even though its own cache discount is far shallower than DeepSeek's.
74 live calls, judged head to head.