# Building a Hermes Fleet Zero To Hero! Reproducing Moshe's Self-Hosted Agent Stack
**作者**: Sigrid Jin
**日期**: 2026-04-16T03:04:09.000Z
**来源**: [https://x.com/realsigridjin/status/2044612784584048788](https://x.com/realsigridjin/status/2044612784584048788)
---

TL;DR
> Yesterday I read Moshe's thread (@Mosescreates) about his Hermes setup. Six agent profiles, one shared self-hosted memory store, Claude Code hooked in so coding sessions write into the same memory the agents read from. I spent some days building a stripped-down version. Two profiles instead of six, one machine instead of two, but the core architecture holds. The interesting piece isn't Hermes or mem0 individually. It's the shared memory layer that lets Claude Code sessions feed directly into agent context.

Commands to bring up
> With those three services up and a single config, any agent and any Claude Code session can share context through one mem0 client pointed at the same Qdrant collection.
One machine, not two. I don't have a Mac Studio and a MacBook both sitting idle, and I wanted to see if the architecture survived collapsing onto a single box. Two profiles, not six. One CLI coder profile and one agent profile reachable over Telegram. Offline fallback from day one, because Moshe's notes about Happy Eyeballs and silent failover told me that's where I'd lose time if I deferred it. And every component self-hosted or at least key-managed by me. No platform lock-in.
Here's what the weekend actually looked like.
## The memory layer
This is the core of the system and also the easiest part to get subtly wrong, because mem0 is a thin wrapper and most of the failure modes live in the layers underneath it.
I started with Qdrant running in Docker. I bind-mounted the storage directory to my home folder so the data would survive container rebuilds. Ollama running locally with nomic-embed-text pulled. mem0 installed into a fresh Python 3.11 venv.
The first thing I tripped over is mem0's default config uses OpenAI embeddings. If you don't explicitly set the embedder to Ollama, mem0 will silently try to call OpenAI and fail with a confusing auth error. The config block in the TL;DR is the minimum you need to stay offline for embeddings.
The second thing. Ollama on my machine kept timing out on batches of embedding requests. Setting OLLAMA_NUM_PARALLEL=4 in the environment helped. Setting OLLAMA_MAX_LOADED_MODELS=1 helped more, because I had a chat model also loaded and the embedder was getting starved for GPU time.
And the same data reads back from any other process pointing at the same Qdrant instance. That's the entire shared memory store. Everything else in the system is ways of writing into it and reading out of it.
## The Claude Code hook
Moshe's Stop hook was the piece I was most curious about, because if it worked, every coding session I ran would contribute to the memory the agents use. The Stop hook fires at the end of every Claude Code turn. You wire it up in .claude/settings.json:
The script reads the session transcript from stdin, extracts the user turn and the assistant turn, and writes a single memory entry.
This worked on first try, which was the most satisfying moment of the weekend. After ten minutes of coding with Claude Code, I could go to my Telegram agent and ask "what was I working on?" and get a coherent summary back, because the agent's memory search pulled the last handful of hook writes.
Two things to flag for anyone trying this. The script needs to be fast. Anything past a few hundred milliseconds and you'll feel it between turns. The Ollama embedding call is the bottleneck, so keep nomic-embed-text warm. And a redactor matters more than you'd think. Claude Code sessions sometimes contain API keys in tool call output, and those will get written straight into your memory store if you don't strip them. Moshe's note about a redactor script scanning every turn-end for secret patterns isn't optional. I wrote a minimal version that matches sk-, ghp_, Bearer , and a few private patterns of my own, and replaces matches before the mem0 write happens.
## Agent profiles and model routing
Moshe runs five profiles on Qwen3.6-plus via OpenRouter and his coder on GLM-5.1 via Z.AI. I followed the same pattern, one agent profile on Qwen3.6-plus through OpenRouter, and my coder on GLM-5.1 through the Z.AI coding plan. The $45-per-quarter price Moshe mentioned is what I paid, and native routing through Z.AI's own endpoint felt noticeably snappier than going through an aggregator.
The one setting that matters more than any other is native-only provider routing. OpenRouter by default will fall back to cheaper or faster providers if the primary is slow. For an agent that writes to a shared memory store, you do not want that. A silently swapped provider means the behavior your memory entries were built against is no longer the behavior producing the output going back into memory. Pin it.
OpenRouter respects provider.allow_fallbacks: false when you pass it through on the request. The patch Moshe mentioned in Hermes is presumably doing exactly this, hard-coding the routing so it can't be accidentally loosened by a config change upstream.
## Local fallback
I kept this simple. Gemma 2 9B 4-bit on Ollama, same machine as Qdrant. The profile config has a fallback block that triggers on primary-provider error:
Moshe runs a 31B model on an M4 Max. I don't have that hardware. 9B is enough for what fallback actually has to do, which is keep the conversation alive while the primary provider recovers. The fallback doesn't have to match primary quality. It has to be running when the primary isn't.
I tested this by killing my network for thirty seconds mid-conversation. The failover was silent. The user side noticed nothing beyond a small latency bump on the turn where the switch happened. Memory writes continued against local inference.
## What I skipped
I skipped the second machine. I skipped launchd service definitions because everything was on one OS and brew services plus docker was enough for my two services. I skipped the daily backup cron because I'm already backing up to Time Machine and Qdrant's storage sits on the backed-up volume. I skipped the fleet status command because with two services on one machine, brew services list and docker ps together cover it.
Moshe's stack includes those pieces because it's a real production setup across two machines with six agents and live customer conversations. Mine is a weekend reproduction to validate the architecture. Anyone reading this will be somewhere on that spectrum. The load-bearing pieces are shared Qdrant, Ollama for embeddings, mem0 as the abstraction, the Claude Code Stop hook, native-only provider pinning, and some kind of local fallback. Everything else is operational scale.
## Things I learned the hard way
The Happy Eyeballs issue Moshe flagged is real and it will bite you the moment you try to talk to services over a mix of IPv6 and IPv4-only paths on macOS. Forcing IPv4 with -4 on curl commands, and setting AddressFamily inet in ~/.ssh/config, saved me an afternoon of random hangs once I figured out what was happening.
Idempotency keys on every write matter more than I expected. I skipped them on day one because my system only had two profiles and retries felt unlikely enough to defer. By Sunday afternoon I had two memory entries for the same conversation turn, because a mem0 write had retried after a transient Qdrant timeout and gone through on both attempts. Fixing this properly meant adding an Idempotency-Key header derived from session_id plus turn index on every write path, the same pattern Moshe credits @brian_cheong for pushing into task_server v1.1.3. If you skip one thing from his stack, do not skip this one.
Self-hosted embeddings are cheap enough that there's no strong reason to call out to a cloud provider for them. Running nomic-embed-text locally for a weekend of heavy use cost me measurable amounts of electricity and nothing else. The privacy and reliability upside is real. Every memory entry lives on my disk. Every embedding is computed on my machine. If OpenRouter disappears tomorrow, my memory layer keeps working.
The biggest lesson was qualitative. The shared memory architecture feels different from running individual tool sessions. I kept catching myself switching contexts without having to re-explain anything. The Telegram agent knew what I'd just been working on in Claude Code. The next Claude Code session knew what I would asked the Telegram agent about over lunch. None of this is novel technology on its own. mem0 exists. Qdrant exists. The Stop hook API exists. The work is in wiring them together carefully and making every tool a reader and writer of the same store, with enough discipline around idempotency and redaction that you trust what's in there.
## Closing
Moshe's point about ownership hits harder after you've built a version of it. When something broke in my weekend setup I fixed it. When I wanted to swap models I swapped them. When I wanted to add a new profile I copied a YAML file and edited four lines. No platform decided anything for me.
Credit to Moshe for the thread, and to the Hermes, mem0, Qdrant, and Ollama maintainers for making the pieces assemblable in a weekend. If you've been eyeing the thread and wondering whether any of this is actually buildable, it is. Start with Qdrant plus Ollama plus mem0. Get the Claude Code Stop hook writing. Everything else builds out from there.
## 相关链接
- [Teknium (e/λ) reposted](https://x.com/Teknium)
- [Sigrid Jin](https://x.com/realsigridjin)
- [@realsigridjin](https://x.com/realsigridjin)
- [Moshe's thread](https://x.com/Mosescreates/status/2044230599448129996?s=20)
- [@Mosescreates](https://x.com/@Mosescreates)
- [Z.AI](https://z.ai/)
- [Z.AI](https://z.ai/)
- [Z.AI](https://z.ai/)
- [@brian_cheong](https://x.com/@brian_cheong)
- [Upgrade to Premium](https://x.com/i/premium_sign_up)
- [11:04 AM · Apr 16, 2026](https://x.com/realsigridjin/status/2044612784584048788)
- [5,077 Views](https://x.com/realsigridjin/status/2044612784584048788/analytics)
---
*导出时间: 2026/4/16 17:09:01*