DEV Community

zishuowang696
zishuowang696

Posted on Originally published at embedai.com.cn

I gave my blog an agent: auto topic ideas every week

The annoying part of running a blog isn't writing — it's picking topics. What do I write today? It's guesswork, and it eats time.

So I gave it an agent: every Monday it reads my published posts (to avoid repeats) and proposes 5 new topics. It runs on GitHub Actions — free, scheduled, no server.

And the essence never changes: an agent is a loop — call the API to ask the model → run the tool → feed the result back.

What it actually does

One loop, one tool:

TOOLS = [{
    "type": "function",
    "function": {
        "name": "list_existing_titles",
        "description": "read existing post titles to avoid duplicates",
        "parameters": {"type": "object", "properties": {}},
    },
}]

def run_tool(name, args):
    if name == "list_existing_titles":
        xml = urlopen(FEED).read().decode("utf-8", "ignore")   # read the site RSS
        titles = re.findall(r"<title>(.*?)</title>", xml, re.S)
        return "\n".join(titles[1:])
Enter fullscreen mode Exit fullscreen mode

Model is DeepSeek (OpenAI-compatible). The loop:

def agent(user_input):
    messages = [{"role": "user", "content": user_input}]
    while True:
        msg = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS).choices[0].message
        messages.append(msg)
        if not msg.tool_calls:
            return msg.content                     # no more tools -> answer
        for call in msg.tool_calls:                # run the tool -> feed it back
            args = json.loads(call.function.arguments or "{}")
            messages.append({"role": "tool", "tool_call_id": call.id,
                             "content": run_tool(call.function.name, args)})
Enter fullscreen mode Exit fullscreen mode

Output goes to weekly/<date>.md. That's it — no framework, just a loop.

Why GitHub Actions

To me this is the neat part:

  • Free: public repo, unlimited minutes;
  • Scheduled: on: schedule: cron, runs weekly;
  • No server: nothing to host;
  • Internet access: runners are in the US, reaching DeepSeek / Google APIs directly;
  • Repo access: it can commit the result back.
on:
  schedule: [{ cron: '0 1 * * 1' }]
  workflow_dispatch: {}
permissions: { contents: write }
# ... pip install -> python agent/topics.py -> git commit weekly/
Enter fullscreen mode Exit fullscreen mode

Secrets go in Secrets (DEEPSEEK_API_KEY); site config in Variables (SITE_FEED/SITE_BRIEF) — never in code.

It actually delivered

On its first run it read 17 published posts and replied (excerpt):

The existing content is mostly low-level build / framework internals, but "running an agent on an edge device" is almost a blank spot. The 5 topics below all land in that gap…

Then it gave 5 topics plus a scheduling plan (lead-gen → conversion → moat). Honestly, better than my gut.

My principle: the agent does the dirty work, the human judges

That's also why I kept it small and open-sourced only the framework:

  • Agent does the dirty / half-done work: topics, drafts, retros, distribution copy;
  • Human judges and publishes: which one, how to write, whether to ship;
  • Open architecture, private data: the framework is open; but data, keys, keyword lists, business strategy stay private.

For instance, my real prompt, keyword list, and GSC data live in a private repo — what's public is only how it's built.

Next

  • Add a tool to read a GSC export (topics with impressions but no clicks first);
  • Add a "draft agent" (first-pass drafts);
  • But publishing always stays with the human.

📦 Reusable template (private bits removed): github.com/zishuowang696/weekly-topics-agent

Questions or feedback? Open an issue on the repo above.

Top comments (0)