← All posts

October 3, 2026 · 12 min read

Hermes Agent: one Telegram message a day

ai-agentsself-hostingautomation
Blue-and-white ink illustration of a girl with headphones looking down at a small glowing moon floating above her open hand

I have a Notion database called Watch & Read List. It has a title, a type, a link, some tags, and a status column with three values: Not started, In progress, Done. The Done column is mostly decorative.

The idea was simple. Every morning, something picks one item that isn't done and sends it to my phone. One message. Once a day. The kind of thing a cron job and forty lines of Python could do before breakfast.

I did not write forty lines of Python. I spent the weekend with Hermes Agent, tried two machines, around ten local models and three LLM providers, and switched Notion auth once. This is the whole story, wrong turns included, because the wrong turns were where I actually learned something.

In fairness to past me, the reading list was never the whole plan. Next I want the same agent to pull my daily grocery spend from the Instamart, Blinkit and Zepto MCP servers into Notion, and maybe track stocks after that. For that I need an agent that can talk to MCP servers on a schedule, so I wanted the plumbing to work first.

“Hermes” is two different things

My first plan was to self-host “Hermes” on a GCP VM. I was confusing two Nous Research projects with the same name:

I needed the second one. Before I figured that out, I spent a while reading about GCP GPU machine types: G2 with L4s for small models, A2 with A100s for 70B-class models, A3/A4 for the very large ones. Then I did the obvious arithmetic. A GPU running 24/7 to do a few seconds of work a day is a space heater with a monthly bill. For low-volume inference, a hosted API with a free tier wins on both cost and effort.

Two more name mix-ups happened along the way, and I'm including them because I'm probably not the only one:

The 2019 MacBook Pro

Instead of a GPU VM, I used the laptop I had lying around: a 2019 MacBook Pro with 16GB of RAM and Intel UHD 630 integrated graphics. I didn't look up the CPU myself. Ollama printed it in its startup log the first time it loaded a model: an i9-9880H. Ollama didn't report a discrete AMD GPU, so I think it's the 13-inch model, but I never actually checked.

So everything runs on the CPU. That matters more than it sounds, because the 16GB is shared between macOS, the model weights and the context cache. When a model card says a model “fits in 16GB,” it usually means 16GB of GPU VRAM that nothing else is using. On a laptop with shared RAM, “fits” and “usable” are different things.

Picking a model

I went through more candidates than a one-message-a-day job deserves:

ModelVerdictWhy
Qwen2.5-7B-InstructEarly candidateGood tool calling, fits in 16GB
Qwen3-8BInitial pickNative tool calling, thinking and non-thinking modes, ~5.2GB
gpt-oss:20bPick for agentic workMoE with 3.6B of 20B params active, so fast for its size; built by OpenAI for tool use
gpt-oss-safeguardRuled outA different model, tuned for content moderation, not agents
Llama 4 Scout / MaverickRuled outScout alone needs ~65GB at Q4
Llama 3.1 / 3.2Ruled outTool calling is prompt-wrapped rather than trained in; hallucinates more
Gemma4 31b / 26b MoERuled outNeeds 20–32GB, and I don't need vision or audio
Gemma4:e4bFits, technicallyNot built for agents; its memory figures assume Apple Silicon
LFM2.5-8B-A1BInteresting, riskyBuilt for on-device MCP agents, but independent tests found quantized tool calling got much less reliable
llama-prompt-guard-2-22mAbsolutely notNot a chat model. It's a prompt-injection classifier.

What I took from this: newer isn't automatically better, and vendor benchmarks and independent community testing often disagree. The only benchmark that matters is your own tool schemas run against the model.

Setting up Hermes on the Mac

I installed Ollama and pulled gpt-oss:20b. The first surprise: Hermes needs a context window of at least 64,000 tokens, and Ollama's default is much smaller. You raise it inside the model and save the result under a new tag:

brew install ollama
ollama pull gpt-oss:20b
ollama run gpt-oss:20b
>>> /set parameter num_ctx 65536
>>> /save gpt-oss:20b-64k

Then I ran the official Hermes installer script, which brought its own problems:

“What's 2+2” took ten minutes

My first test was what's 2+2. It seemed to hang forever.

Running ollama serve in the foreground showed why. Before generating anything, the model had to read a 13,586-token prompt. My question was a few of those tokens. The rest was Hermes's system prompt plus the schemas for 40+ default tools: every tool the agent might use, sent with every request.

Prompt processing on this CPU ran at about 20–29 tokens per second. At that speed, 13K tokens takes more than ten minutes before the first word of the answer appears. That's a very slow way to find out it's four.

The fix was to cut Hermes's toolsets config down to what the job needs: cronjob and message delivery. That took the prompt from 13,586 to 2,855 tokens, about 80% smaller. Most of what was left came from Notion MCP's own tool schemas, which the toolsets setting doesn't control.

This was the biggest lesson of the weekend. In an agent framework, the size of the tool list sent with every request can matter more than the model's raw speed. Trimming toolsets was the single most effective performance fix I made.

Around the same time, my config.yaml broke. Earlier hand edits had left an api_key: / api_mode: pair with no parent key. Patching it piece by piece didn't work, so I rewrote the whole file from scratch, which did.

The mystery 500

Even with the smaller prompt, Ollama's log showed the Chat Completions endpoint returning HTTP 500 whenever Hermes sent its full payload with tool schemas. A plain curl to the same endpoint worked.

My best guess is a compatibility gap between gpt-oss's tool-call output format and Ollama's partial OpenAI compatibility layer. Reasoning-tuned models on self-hosted servers have this kind of problem fairly often. I didn't dig further. I switched to qwen3:8b, which didn't hit the bug.

Giving up on the Mac

Qwen3 worked, slowly. Several things added up to me moving off the laptop:

  1. Keeping a six-year-old laptop awake permanently for a daily cron job is a reliability risk, and it takes up desk space.
  2. CPU-only inference on 16GB of shared RAM is slow. Even after the trimming, responses took minutes.
  3. Ollama unloads a model after 5 idle minutes, so any test after a coffee break paid the full cold start again.
  4. Eventually I want the agent busy 14–15 hours a day. On a 13-inch chassis, if that's what it is, that's a heat problem waiting to happen.

New plan: run Hermes on a small cloud VM and use a hosted model API instead of local weights.

Which provider gets my data

DeepSeek was the obvious cheap option. Its own privacy policy says prompts and chat history are stored on servers in mainland China. Under Chinese law, the company has to hand data to the government on request, and there's no independent court to challenge that in.

The obvious counterpoint, which I raised with myself: any hosted API means sending data to a third party, Groq included. True. But the difference isn't US vs. China as a vibe. It's legal accountability. US and EU companies can be sued and are bound by privacy laws that get enforced. A foreign user of a Chinese provider has no real recourse.

If you want DeepSeek's model quality specifically, Western providers like Groq, AWS Bedrock and Together AI host the open weights, so your data never goes to DeepSeek the company.

I started with Groq: US-based, a published data-retention policy, a free tier. If the grocery-spend data needs tighter control later, I can move that part somewhere more locked down.

Groq's free tier, in three problems

Problem one: a 404. I asked for llama-3.3-70b-versatile and got HTTP 404. openai/gpt-oss-20b worked fine over curl, so the key was good. A screenshot of my account's rate-limits page later explained it: the account's list had gpt-oss-120b, gpt-oss-20b, gpt-oss-safeguard-20b, qwen/qwen3.8-27b, and some non-chat models (prompt guards, Whisper, Orpheus TTS). Llama 3.3 was never on it. Check your own account's current model list, not the general docs. Free-tier availability changes, and it differs between accounts.

Problem two: reasoning_effort. On a fresh GCP e2-medium VM, with a clean config pointing at Groq, every gpt-oss request was rejected with:

reasoning_effort must be one of low, medium, or high

I tried the plain string "low", the dict form {enabled: true, effort: low}, and a per-model override. All three failed the same way, even though grep showed the config file was correct. As far as I can tell, it's a real compatibility bug between Hermes and Groq for gpt-oss through the generic provider: custom endpoint, not something I misconfigured. I dropped gpt-oss on Groq instead of fighting it.

Problem three: rate limits and retries. Qwen3.8-27B worked, but then hit two problems that made each other worse. The free tier has a shared input limit per minute. The dashboard showed “8K,” but the 429 errors consistently reported 7,000 tokens per minute. The limit was the same for every chat model on the free tier, so switching models wouldn't help.

Qwen also sometimes produced badly formatted tool calls (“Failed to call a function”). Each failure triggered a retry, and each retry sent the whole prompt again, which used up more of the 7K/minute budget, which caused more failures. A model that fumbles tool calls doesn't just fail once. It burns through your quota while it keeps failing.

One useful thing from this stretch: hermes fallback add lets you set a backup provider that Hermes switches to automatically when the main one is rate-limited or returning errors.

Switching to Gemini

At a handful of calls a day, any frontier model costs close to nothing. So price stopped mattering, and tool-calling reliability was the only real criterion. I compared DeepSeek V4 (cheap, but the same data-residency issue), Gemini 2.5 Flash-Lite and Flash, and Claude Haiku 4.5.

One finding nearly changed my choice: an independent benchmark showed Gemini 2.5 Flash-Lite's tool calling gets much less reliable beyond about 13 registered tools. Hermes's own tools plus a few MCP servers would go past that quickly.

I went with Gemini 3.1 Flash-Lite instead. It's the newer generation, with documented “more stable function calling” and native parallel tool calls, and it's pitched at production agent pipelines. The price is about the same as 2.5 Flash-Lite. I connected it through Hermes's native provider: "gemini" integration rather than the generic OpenAI-compatible endpoint I'd used for Groq, which means less translation between formats and fewer places for things to break.

It worked on the first real test. No retry storms, no rate-limit errors, no reasoning_effort bug. After two days of debugging, that was almost a letdown.

What it cost

Gemini's console showed about 100K tokens used across 27 test calls. That looks like a lot until you do the math: the whole debugging session cost about $0.01.

The real job is one call a day, which should come to well under $0.02 a month even without optimizing. I made one small change anyway: turning off auxiliary.title_generation. Every session was making a second API call just to give itself a name, which I don't need for a cron job.

Notion on a server with no browser

On the Mac, I'd connected the hosted Notion MCP at https://mcp.notion.com/mcp through OAuth in a browser. On the VM, it returned 401 Unauthorized every time. That OAuth login belonged to the Mac's browser session and doesn't carry over to a fresh headless machine.

So I switched to token auth, which is a better fit for a server anyway:

  1. Created a Notion integration token under Developer Tools → Personal access tokens.
  2. Shared the page with the token. This is the step everyone misses. A new token can't see anything until you add it to each page or database through the page's ••• menu → Connections → Add connection.
  3. Replaced the hosted OAuth URL in the MCP config with the local token-based package, @notionhq/notion-mcp-server, run via npx.
  4. Found that the docs disagree on the environment variable name: NOTION_API_KEY in some places, NOTION_TOKEN in others. I set both to the same token instead of checking which one the package actually reads.
  5. Ran cat -A on the .env file to rule out a hidden line break corrupting the token.

To be honest about where this stands: I never re-tested Notion access after the last fix. I moved on to Telegram and systemd, things started working, and I mentally marked everything as done. Something else succeeding later doesn't prove an earlier, untested fix worked. Status: fix applied, not confirmed.

Also unverified: whether sharing a top-level Notion page gives access to all the pages nested under it. I read two sources that directly contradict each other on this. I only ever connected one specific page, so I haven't tested it either way. Try it before you rely on it.

Telegram, twice

I picked Telegram for delivery without much debate. Bot setup takes two minutes through BotFather, the bot API is rich (you can edit messages and send files), and there's no business verification. The others lost on paperwork or structure: WhatsApp's Business API involves a lot of bureaucracy, Discord wants servers and channels, and Slack assumes a company workspace. Signal is the most private option, and I'd reconsider it if privacy became the top priority, but its bot ecosystem is still immature.

Setup:

  1. Message @BotFather, send /newbot, and pick a display name and a username ending in bot. You get a token.
  2. Put the token in Hermes's .env.
  3. Message your bot. It replies with a pairing code, which you approve on the server. This stops strangers from controlling your agent:
    hermes pairing approve telegram <CODE>
  4. Send /sethome to make that chat the home channel. That's where cron results and other unprompted messages go, since nobody is chatting with the bot at 8 AM.

I did all of this twice. Hermes stores pairing in a local state.db, and the fresh VM had no record of the Mac's pairing, even though it used the same bot token.

The systemd unit I didn't need

Last problem: keeping the gateway running on the VM. I ran sudo systemctl start hermes-gateway and got “Unit not found.” Fair, since I hadn't created one.

So I wrote one. It failed with status=75/TEMPFAIL, and Hermes logged:

the host gateway already serves profile 'default' — nothing to start

It pointed at a different PID. The first culprit was a gateway I'd started earlier with nohup ... & during testing and forgotten about. Hermes allows one gateway per host, and that process had the slot. I killed it.

It still failed, because of the real culprit: the Hermes installer already sets up its own user-level systemd service (systemctl --user) to supervise the gateway. My system-level unit was a duplicate from the start, competing with a service that was already working.

I deleted my unit, and that was the fix. Before writing your own process management for a tool, check what its installer already set up:

systemctl --user list-units

Where it ended up

What I'd tell myself on Friday

  1. The agent framework and the model are separate decisions. Mixing up the two Hermeses cost me most of Saturday morning.
  2. GPU memory specs don't carry over to shared RAM. “Fits in 16GB” on a model card is not the same as usable on a laptop.
  3. Tool-list size is often the real bottleneck. Cutting toolsets shrank the prompt by about 80%, more than any model change did.
  4. Test against your own tool schemas before trusting a leaderboard. Vendor and independent numbers disagree.
  5. Free tiers differ by provider and by account. Check your actual model list and the limits in the actual 429 errors.
  6. For privacy, ask about legal recourse, not the country. Every hosted API is a third party. What differs is whether you can do anything when something goes wrong.
  7. Retries make rate limits worse. A flaky tool-caller spends your quota several times over.
  8. Check what a tool already manages before adding your own layer. Duplicate systemd units, env vars and auth setups all cause quiet conflicts.
  9. Personal automations cost almost nothing. 100K tokens sounds like a lot. It was one cent.
  10. OAuth logins don't move between machines. On a headless server, token auth is usually the better choice anyway.
  11. Re-test each fix before calling it done. Something else working later doesn't prove an earlier, untested fix worked. I'm saying this partly to myself.

Next

First, actually confirm that Notion access works. Then connect the Instamart, Blinkit and Zepto MCP servers so the same agent can tell me each evening how much I spent on groceries and snacks that day. I already know roughly how that report will look, and I'm not looking forward to it.

If you've run Hermes Agent with a smaller model and got reliable tool calls, or you know which Notion env var the MCP server actually reads, please tell me. Both questions are still open on my end.