Back to portfolioSource

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. No server, no database, no human editor.

SYSTEM DATASHEETSTATUS
Status
Live. Publishes daily at 08:00 IST, unattended, building since June 2026.
Shipped
Autonomy
No human editor. Every skip-or-publish call, every relevance judgment, is machine-made.
Shipped
Infrastructure
One GitHub Actions job. No server, no database, no hosting bill.
Shipped
Cost per run
~$0.008 USD, across a seven-stage pipeline.
Measured
Intake
61 usable candidates per run, up from ~15, across 8 RSS feeds plus GitHub trending.
Measured
Triage accuracy
81.9%, up from 73.8% after one prompt fix — scored against 1,505 hand-labeled rows in three golden sets.
Measured
Known weakness
26.3% of generated claims are unsupported by the source on the faithfulness baseline. Reported as-is, not tuned away.
Measured
Tiered research routing
Built, verified, and kept in the codebase — switched off for production runs on cost and build time.
Off in prod

A pipeline that works once is a demo. These are the totals from a month of it running on a schedule, unattended — same code every morning, whether or not anyone is watching it. Read straight out of the GitHub Actions run logs and the traces the pipeline commits about itself, not from a benchmark harness.

AT SCALE · 2 JUL → 2 AUG 2026FROM RUN LOGS
Operating window
39 scheduled runs across 31 days, unattended.
Compute
848 min (14.1 hours) of Actions runtime, averaging 23.6 minutes a completed run.
Published, to date
860 articles in the store — 588 from RSS, 192 from GitHub trending, 80 synthesized.
Hindi prose written
2,262,387 characters, averaging 2,980 per full three-stage article.
Source text read (traced subset only — a floor, not a total)
2,027,544 characters scraped and processed, roughly 338,000 words.
Pipeline throughput (traced subset only — a floor, not a total)
731 articles reached the pipeline after clustering — 635 published, 63 rejected by the relevance gate, 8 too thin to be worth writing.
Research dispatched (traced subset only — a floor, not a total)
2,774 search queries — 1,812 identity lookups, 962 gap and context questions.
Entity knowledge base
2,272 entities cached to date.
Cache effectiveness (traced subset only — a floor, not a total)
30% of 2,584 extractions answered from cache: no search, no model call, no cost.
Under failure (traced subset only — a floor, not a total)
3.4% of Stage 2 calls failed; each degraded to a fallback rather than dropping the article.

† Covers the 28 runs that committed machine-readable traces. Trace committing was added on 7 July 2026, so the eight runs before it left none behind: those rows are floors, not totals. The 860-article store is the only figure spanning the full lifetime. Per-call LLM volume is recorded nowhere, so it is not estimated here.

LLM systems
Multi-stage prompt pipelinesPlan-execute decompositionCost-tiered model routingAgent-to-agent callsStructured JSON extraction
Evaluation
Hand-labeled golden setsLLM-as-judge calibrationBefore/after prompt A/BFailure taxonomies
Local models
sentence-transformersEmbedding-based dedupLanguage detection
Engineering
PythonGitHub Actions CI/CDTTL cachingBeautifulSouppytest

Everything below is the log of that build: the architecture in one figure, why the workflow exists, what each stage does, and nine real bugs, each with what was claimed, what broke, what fixed it, and how it was verified. Nothing below is hypothetical.

01

The whole system, in one figure

Seven stages, read downward. The left rail is the part that matters most to how this was engineered: a filled square is a metered model call, a hollow one is free. Deduplication and language detection were pushed onto local models specifically so the paid budget is spent on judgment — deciding what's worth writing, and what the source article left out — rather than on bookkeeping a library could do.

WHAT IT COSTSWHAT IT DROPS, OR ADDSGITHUB ACTIONS · DAILY CRONone job, 08:00 IST, nobody watchingsarvam-105bbatched editorCOLLECT8 RSS feeds + GitHub trending✕ job ads, listicle spam, low-traction repos— dropped by regex before any model runslangdetectlocal · freeNORMALIZEdetect language, translate to Englishnon-English sources are translated first,so the entities extracted are the real onesmpnet-768dlocal · freeCLUSTERsame-story detection by embedding✕ five outlets, one event — cosine ≥ 0.44folds them into one story, written oncesarvam-30b× 3 callsGATEscrape, then decide if it's worth writing✕ not worth writing — the gate states itsREASON before its VERDICT (0/5 → 10/10)sarvam-30b × 345-day cache 1stRESEARCHentity + gap analysis, tiered searcha 45-day entity cache is checked first —a hit costs nothing at all: no search, no callsarvam-105b× 2 callsPLANeditorial strategy, then a paragraph planthe thinking is written out as plain textfirst, then transcribed into strict JSONsarvam-105b× 1 + one retryWRITEoriginal Hindi prose, six sectionssix labeled Hindi sections — 2.3× longeronce the plan was fixed, not the token capRESEARCH AGENTa second project of mine, calledas a paid API from this oneCOMMITTED BACK TO THE REPOa static Hindi edition · ~$0.008 per runa metered model callruns locally, costs nothingrejected here, and why
Three stages exist to throw things away. Collect, cluster and gate all reject — by regex, by embedding distance, and by model judgment respectively, in that order, because each one is more expensive than the last and there's no reason to pay for a verdict on something a regex can drop for free. What survives all three is the only thing the writing stages ever see.
02

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.

NO HUMAN EDITOR
Every relevance call is machine-made

Skip or publish, which entities matter, what still needs researching: all model judgment. Nobody reviews a draft before it goes out at 8 AM.

HARD TOKEN CAP
4,096 completion tokens per call, no exceptions

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.

03

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.

STEP 1 · ENTITIES
Find the nouns, resolve the ambiguous ones

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.

STEP 2 · COMPLEX QUESTIONS
Send what a keyword search can't answer to a real research agent

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.

04

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.

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

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

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

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

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

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

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

05

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.

CUSTOM BUILD · 45-DAY ENTITY CACHE

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.

Live demo of the research agent Why Sarvam over DeepSeek for this stage: a 74-call side-by-side →

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.

PendingRESEARCH-MODE FLOW · DIALED BACK FOR PRODUCTION
./deploy_pipeline.sh --research-mode

Reverted the RESEARCH stage to DuckDuckGo-only search. Tiered research-agent routing stays in the codebase, just switched off for production runs.

Current build · DDG-only
~$0.008 USD/run
9–20 min
Retained · search-agent build
~$0.17–0.20 USD/run
1 hr 20+ min

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.

06

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.

Read all nine field reports →

Claim, what broke, the fix, and how it was verified, each with the real captured evidence.

07

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.

81.9%
triage accuracy, up from 73.8% after a prompt fix
74.7%
judge-vs-human agreement on entity extraction
26.3%
faithfulness baseline: claims unsupported by the source
Read the full evaluation report →

Golden sets, judge calibration, the two verified prompt iterations, and real misclassifications before and after each fix.

08

Why Sarvam, and what it costs

Model choice was a cost decision, checked against the obvious alternative rather than assumed:

Per 1M tokensInputCachedDiscountOutput
sarvam-30b$0.029$0.01740%$0.115
sarvam-105b$0.046$0.02937.5%$0.184
deepseek-v4-flash$0.140$0.002898%$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.

Full quality comparison: Sarvam vs DeepSeek →

74 live calls, judged head to head.