DEV Community

Enjoy Kumawat
Enjoy Kumawat

Posted on

My Blog-Posting Agent Scores Every Trending Post It Reads. It Has Never Scored One of Its Own.

Every run of my scheduled dev.to publisher starts the same way: pull the top posts for six tags, score each one as positive_reactions_count + 3*comments_count, and pick whichever themes score highest and aren't already covered by my ~30 prior posts. That formula has picked every single topic I've written about for the last month.

I went looking for the equivalent call on my own output — something that scores my 30 published articles the same way, so a future run could see "the MCP-security angle keeps outperforming the debugging-story angle by 4x" and weight topic selection accordingly. There isn't one. The tool to do it has existed in my own MCP server since the day I built it.

the tool that was always there

server.py is an 8-tool FastMCP server I wrote to combine the GitHub and DEV.to APIs. Two of those tools are exactly what a feedback loop would need:

@mcp.tool()
def list_articles(per_page: int = 10) -> list:
    """List your published DEV.to articles."""
    articles = _dev(f"/articles/me?per_page={min(per_page, 30)}")
    return [
        {
            "id": a["id"],
            "title": a["title"],
            "published": a.get("published"),
            "url": a.get("url"),
            "reactions": a.get("positive_reactions_count", 0),
            "comments": a.get("comments_count", 0),
            "page_views": a.get("page_views_count", 0),
        }
        for a in articles
    ]


@mcp.tool()
def get_article_stats(article_id: int) -> dict:
    """Get reactions, comments, and page views for a DEV.to article."""
    a = _dev(f"/articles/{article_id}")
    return {
        "id": a["id"],
        "title": a["title"],
        "reactions": a.get("positive_reactions_count", 0),
        "comments": a.get("comments_count", 0),
        "page_views": a.get("page_views_count", 0),
        "published": a.get("published"),
    }
Enter fullscreen mode Exit fullscreen mode

I grepped the repo for callers. Zero. list_articles and get_article_stats are fully implemented, fully working, and have never been invoked by anything — not the publisher, not the comment-reply pipeline, not a cron job, nothing. They were built for a Claude Desktop chat session where a human would ask "how's my blog doing" and get a live answer. The scheduled publishing routine never grew that question into its own loop.

the asymmetry, stated plainly

The publishing task's Step 2 is: fetch trending posts, score them, rank them, use the ranking to decide what to write. That's a real feedback signal, applied to other people's content, every single run.

There is no Step 6 that says: fetch my own last N posts, score them the same way, and let a below-median score inform anything. The scoring formula is directional — it only ever looks outward. My own 30 articles have reaction and comment counts sitting in DEV.to's database right now, scoreable with the identical one-liner, and nothing has ever read them back.

This is a different gap from two things I've already written about here. It's not the "20 unanswered comments" problem — that was about responding to engagement that already arrived, a reactive gap. This is about never checking whether engagement arrived at all, which is a measurement gap, upstream of reacting to anything. It's also not the "memory file cost drift" problem — that was a file getting more expensive to read over time under a contract that already existed to read it. This is a contract that was never written: nobody ever told the routine to look at its own results.

why this kind of gap is easy to miss

The reason this sat unnoticed for 30+ articles isn't laziness, it's that the two code paths look unrelated at a glance. The scoring formula lives inline in the publishing task's own instructions, re-derived fresh on read each run. The get_article_stats tool lives in server.py, built for a totally different interaction (interactive chat, not scheduled automation). Nothing in either file points at the other. An agent re-reading the publishing task's Step 2 has no reason to also open server.py and notice the exact function it needs is sitting sixty lines down.

That's the general shape worth naming: a capability existing somewhere in a codebase does not mean it's reachable from the place that needs it, especially across a chat-tool/scheduled-task boundary where the two entry points were built months apart for different audiences. Grepping "do I already have a tool for this" only works if you think to grep in the first place, and nothing prompts that check by default.

closing the loop, minimally

I'm not rewiring topic selection around this yet — that's a bigger decision than a code change (do reaction counts on dev.to actually mean the writing is better, or just that the topic tag was more crowded that week?). But the smallest useful step is cheap: log the numbers so the question becomes answerable later instead of staying invisible.

import json
from datetime import datetime, timezone

def snapshot_performance():
    articles = list_articles(per_page=30)
    scored = [
        {**a, "score": a["reactions"] + 3 * a["comments"]}
        for a in articles
    ]
    scored.sort(key=lambda x: -x["score"])
    return {
        "checked_at": datetime.now(timezone.utc).isoformat(),
        "articles": scored,
    }
Enter fullscreen mode Exit fullscreen mode

Run that once a week, append the output to a file under docs/project_notes/, and after a couple months there's an actual answer to "does the MCP-security angle outperform the debugging-story angle" instead of a guess. The formula I've been applying to other people's trending posts for a month works exactly as well pointed at my own — I just never pointed it.

Top comments (0)