Open source · MIT licence · Python 3.10+

    Hermes Fleet Memory

    One private memory for the AI agents on your work laptop, your home PC and your Telegram bot. They look things up only when a question needs it, an update replaces the old fact instead of piling up next to it, and work data never reaches your personal machine.

    memory tokens in prompts that don’t need it
    0
    memory domains, gated on each machine
    3
    INT8-quantised vectors in Qdrant
    384-d
    Each node's FLEET_HARD_DOMAIN is checked on the host before a request leaves it: the work PC reads work and shared, the home rig personal and shared, the cloud hub all three, in one Qdrant store (384-d embeddings, INT8). Writes go to UUID5 slots with last-write-wins, so an update replaces a memory in place. The hub also sends tasks to the home rig's desktop bridge over a reverse WSTunnel and gets Ed25519-signed receipts back.

    Your agents forget between machines

    Every machine starts from zero, and the usual fix costs tokens on every message.

    Run Claude Desktop on a work laptop, Cursor on a home PC and a Telegram bot on a server, and each one starts from nothing. What you set up on one machine is unknown to the others.

    The usual fix is a memory plugin that pastes stored notes into the system prompt on every turn. That spends tokens on messages that never needed the memory, and syncing a flat memory between work and personal machines can leak company details into personal chats.

    Fleet Memory is an MCP server instead. Your agent calls fleet_memory_search when a question needs context, and gets back only the cards that answer it.

    Memory in every prompt compared with memory on demand Three chat turns. With ambient memory, each turn carries the same large memory block. With Fleet Memory, the first and last turns carry only the message, and the second turn fetches one memory card from Qdrant because the question needs it. Memory in every prompt Fleet Memory “Hi” memory block “Hi” “Which Caddy header?” “Which Caddy header?” “Thanks!” “Thanks!” search() Qdrant same block, every turn one card, only when asked
    Ambient memory plugins resend the same block with every message. Fleet Memory sends nothing until a question needs it, then fetches the one card that answers it.

    Memory that updates instead of piling up

    Change a fact once and every machine sees the new version, not both.

    Deterministic slot writes Three writes target the slot hardware ram. Each is hashed with UUID5 over domain, client id and slot name, so all three map to the same point. 32 GB creates revision 1, 64 GB replaces it as revision 2, and a late write of 16 GB with an older timestamp is rejected by last-write-wins. store hardware / ram “32 GB” t = 1 store hardware / ram “64 GB” t = 2 store hardware / ram “16 GB” t = 0 (late) uuid5( dom:client :slot) same slot, same id hardware:ram 32 GB revision 1 64 GB revision 2 replaced in place stale write rejected last-write-wins
    A slot write is hashed to the same point every time, so an update replaces the old answer instead of adding a second one. A write that arrives late with an older timestamp is dropped.
    • Slots with fixed addresses. A write to hardware / ram always lands on the same point: its id is a UUID5 of domain, client id and slot name.
    • Last write wins. Each slot keeps a revision number. A write carrying an older timestamp than the stored one is rejected.
    • Near-duplicate check. A note without a slot that is at least 0.95 cosine-similar to an existing note in the same domain replaces it instead of stacking up.
    • Pinned or expiring. Slot cards are pinned. Other notes expire after 90 days unless pinned, and a cleaner archives them off the index.

    Work stays at work

    The boundary is enforced by the machine, not by asking the model nicely.

    The domain gate Three machines above one Qdrant store with work, shared and personal vaults. The work PC queries work and shared. When it asks for personal, the request is rejected with a PermissionError on the work PC and never sent. The cloud hub, set to all, queries every vault. Work PC DOMAIN=work Cloud hub DOMAIN=all Home rig DOMAIN=personal Qdrant · one collection, three vaults work shared personal PermissionError on the host the request is never sent
    Each machine is locked to a domain in its environment. The work PC can read work and shared memory. Asking for personal fails on the work PC itself, before anything goes over the network. The cloud hub, set to all, can read every vault.

    Each machine sets FLEET_HARD_DOMAIN to work, personal or all. The check runs inside the MCP server on that machine before any request is built, so a confused agent or a crafted prompt cannot reach another domain. Knowledge every machine should share goes in shared.

    Relations, not only vectors

    Ask what connects to a component, not only what sounds similar.

    Knowledge graph from memory cards Three memory cards say Caddy connects to Qdrant, FastMCP uses Qdrant, and Qdrant runs on the Oracle host. The entities and their relations are extracted into a graph. A graph query for Qdrant returns its direct connections, Caddy, FastMCP and Oracle, plus the three memories they came from. [[Caddy]] connects to `Qdrant` on the VPS. `FastMCP` uses `Qdrant` for memory search. `Qdrant` runs on the Oracle host. extract connects_to uses runs_on Caddy FastMCP Qdrant Oracle fleet_graph_query(“Qdrant”) → Caddy, FastMCP, Oracle + 3 memories
    Stored memories are scanned for entities: [[wikilinks]], `backticked` identifiers and capitalised names. Sentences like “Caddy connects to Qdrant” become typed relations. A graph query returns an entity’s direct connections and the memories they came from, with no separate graph database to run.

    Reach the PC at home from anywhere

    Check a GPU, fetch a file or put the machine to sleep from your phone.

    Reaching a home PC behind NAT The home PC dials out to the cloud hub over a reverse WSTunnel on TLS port 443, so the home router accepts no inbound connections. A status request from the phone goes to the hub, through the tunnel to the desktop bridge on port 8099, passes three checks, bridge key, allowlisted binary and home-folder jail, and the answer returns the same way. home router · NAT Phone Telegram Cloud hub Hermes · MCP WSTunnel · TLS 443 Home PC desktop bridge :8099 PC dials out “GPU status?” desktop_status() bridge key allowlisted binary home-folder jail GPU temp · VRAM
    The home PC opens the tunnel from the inside, so the home router never accepts an incoming connection. Requests reach the bridge through that tunnel and must present the bridge key, name an allowlisted program (no shell), and stay inside the home folder.

    Six tools reach the home PC through the bridge: desktop_status, desktop_exec, desktop_read_file, desktop_download_file, desktop_archive_folder and desktop_power. If the PC is off, the agent says so within seconds and answers from the last stored hardware card instead of hanging.

    Hand work to a machine that is asleep

    Queue a job now; the workstation runs it when it wakes and proves it did.

    Delegating a task with a signed receipt A node delegates a gpu_batch task. It is queued in SQLite on the hub, claimed by a worker under a lease, and completed. The worker signs a receipt with Ed25519, and the delegating node verifies the signature. Failed tasks retry with backoff and end in a dead-letter state. Any node fleet_task_delegate Task plane SQLite WAL, on hub fleet_health_ping telegram_notify Worker claims a lease lease · heartbeat gpu_batch Ed25519 receipt signature verified If an action fails: failed retry · backoff dead letter
    Tasks are queued in SQLite on the hub, so they survive a machine that is asleep or offline. A worker claims a task under a lease, runs one of a closed list of actions, and signs a receipt with Ed25519. Whoever asked can check the signature without trusting the server that relayed it.

    What your agent gets

    MCP tools, available to Claude Desktop, Claude Code, Cursor and Hermes Agent.

    Memory

    • fleet_memory_search
    • fleet_memory_store
    • fleet_graph_query

    Desktop bridge

    • desktop_status
    • desktop_exec
    • desktop_read_file
    • desktop_download_file
    • desktop_archive_folder
    • desktop_power

    Task plane

    • fleet_task_delegate
    • fleet_task_status

    Try it in 60 seconds

    Standalone mode runs on one machine, with no VPS or Qdrant cluster to set up first.

    git clone https://github.com/amrlazw/hermes-fleet-memory.git
    cd hermes-fleet-memory
    python setup.py --role standalone --non-interactive
    python client/fleet_memory.py --test
    

    Then register it with your agent. For Claude Code:

    python client/fleet_memory.py --doctor
    python client/fleet_memory.py --init --domain personal
    claude mcp add fleet-memory -- python /absolute/path/to/hermes-fleet-memory/client/fleet_memory.py
    

    For Hermes Agent, add it to ~/.hermes/config.yaml:

    mcp_servers:
      fleet-memory:
        command: python3
        args:
          - /absolute/path/to/hermes-fleet-memory/client/fleet_memory.py
        env:
          FLEET_HARD_DOMAIN: all
    

    When a second machine joins, choose where the memory lives:

    Option You need Good for
    Managed cloud A Qdrant Cloud free-tier cluster Two or more machines, no server to run
    Self-hosted VPS Any Linux VPS Full control of the data and TLS
    Full fleet mesh A home PC and a relay VPS Commands and files on a PC behind NAT
    Local task plane The machines you already have Delegated tasks with signed receipts

    Run it on the machines you already have

    MIT licensed. Start with one laptop in standalone mode, then point a second machine at a Qdrant Cloud free tier or your own VPS.