hermes atlas
310·repos hermes·v0.21.5 ★ star this repo

tuancookiez-hub/HyAtlas-Memory

A plugin for Hermes to connect Hy-Memory as a memory provider as well as a dashboard view

★ 31 langGo licenseApache-2.0 updated2026-10-20

HyAtlas Memory is a Go-based memory server with an eight-layer memory model, in-process BGE embeddings, and an embedded vector store. It can extract facts, intentions, and entity pages through an OpenAI-compatible endpoint, then search and consolidate memories; retired memories can be restored.

  • Supports Lite, Pro, and Ultra extraction modes
  • Uses an embedded, disk-persisted vector store
  • Provides reversible memory retirement with reasons

Basic static screen: be1ca8f (November 9, 2026), not security certification. Pro/ultra modes send memory content to the configured model; lite mode avoids LLM calls. Requires a separate server and downloaded runtime/model assets. Keep the service local.

full readme from github

HyAtlas Memory 5.0: memory you can take back

HyAtlas Memory: memory you can take back

One binary. Eight layers. Three extraction modes. One Go binary (the embedded release build is about 160 MB because it carries the BGE model), no Python at runtime, in-process BGE embeddings. Release binaries: Linux amd64 · macOS arm64 · Windows amd64. Other platforms build from source.

HyAtlas is a complete rewrite of the HyAtlas memory system in pure Go (since v4.0). It replaces the Python floor (venv, zvec, Kuzu, FastAPI, HTTP embed subprocess) with a single binary: an embedded Chromem vector store, in-process BGE-small embeddings via onnxruntime-go, and LLM fact extraction, where HYATLAS_MODE sets how much the server reasons and HYATLAS_SYNC_EXTRACT sets whether a write waits for extraction. The 8-layer memory model (Profile · Raw · Fact · Summary · Knowledge · Schema · Intention · Entity) is implemented in full.

Current release: v5.0.0, the stable floor; what it changes is in CHANGELOG.md.

What it does, in short:

  • Writing. Each conversation turn goes through one LLM call that extracts dated facts, updates or replaces older ones, open intentions with due dates, entity pages and reusable task experiences.
  • Remembering in the background. Summaries are written per episode of work. A background pass merges and retires stale facts (with a reason, reversible), and builds a knowledge graph and patterns that record the facts behind them.
  • Recall. Search understands dates ("last week") and the entities you name, finds long facts by any of their sentences, and shows what a fact replaced.
  • Reversible by design. Replaced, merged, dropped and forgotten memories are retired with a reason, not erased, and can be restored; a note you replace or remove with Hermes's memory tool is forgotten here too. Rows are deleted only when you ask: a delete by id, a bulk delete with HYATLAS_ADMIN=on, or opt-in raw retention.
  • Credentials. HyAtlas uses only the extraction key you give it (llm_key, or HYATLAS_LLM_KEY); it never reads another application's credentials or takes Hermes's sign-in token. The last Python release was v3.5.0 (Zvec/Kuzu); it is no longer maintained. CHANGELOG.md has the full history.

What's new in HyAtlas 5.0

Three modes across an eight-layer memory: Lite 1/8, Pro 6/8, Ultra 8/8

How HyAtlas works: remember, reflect, remind


Architecture

flowchart TD
  H["Hermes turn (plugin)"] --> A["POST /api/v1/add"]
  A --> RAW["Embed + store L2 raw"]
  RAW --> CTX["Gather context: 5 related facts (>= 0.70),<br/>open intentions, entity pages"]
  CTX --> X["Extract (LLM, one call): facts with dates,<br/>replaces / updates, intention with due date,<br/>intentions done, entity pages, task experiences"]
  X -.-> RC["Reconcile (LLM, opt-in, local models):<br/>update / replace / link similar stored facts"]
  X --> P["Promote: L3 facts (0.92 restatement fold),<br/>L1 mirror, L7 intention, L8 pages"]
  P --> NOW["Now page (LLM),<br/>at most every 15 min"]
  P --> EP["Episode summary (LLM),<br/>after a 30-min gap"]
  P --> ST[("chromem vectors L1-L8,<br/>long-fact clauses, doc index")]
  EP --> ST
  KEY["Plugin hands over its own key<br/>(loopback only, memory only)"] -.-> X
  CU["Catch-up every 10 min:<br/>turns not yet extracted"] -.-> X
  FG["Hermes memory replace / remove<br/>-> POST /api/v1/forget"] --> RET["Retire matching rows<br/>(reversible, with a reason)"]
  RET --> ST

  T["Ticker: 6h, catch-up while due"] --> CL["Topic clusters (0.55),<br/>only changed clusters"]
  CL --> C["Consolidate (LLM, per cluster): merges,<br/>drops + reason, schemas, L5 edges, arc"]
  C --> G["Guards: merge >= 0.65, schema dup >= 0.92,<br/>schemas keep their evidence, restore"]
  G --> ST
  C --> KG["L5 graph with evidence"]
  C -.-> XS["Cross-topic pass (LLM, daily):<br/>patterns that span topics"]
  XS --> G

  Q["POST /api/v1/search"] --> F["Vector (+ long-fact clauses) and stemmed keyword,<br/>fused by rank, weighted by the kind of query"]
  F --> F2["Dates in the query, entity boost,<br/>graph-assisted recall, idle-time recency,<br/>usage feedback, near-duplicate demotion, MMR diversity"]
  F2 --> OUT["Now -> pages -> profile (+ schemas behind the facts)<br/>-> proactive -> experiences -> normal,<br/>with event dates and replaced versions"]

Lite mode keeps only the raw write and search: no LLM call at all. Pro adds extraction, episodes and the Now page. Ultra adds consolidation, which is what fills L5 and L6. Every LLM step goes to the endpoint you configure; nothing else leaves the machine.

Layer What it holds
L1 Profile User preferences, style, constraints (mirrors L3 facts labelled user_preferences)
L2 Raw Every incoming memory as-is
L3 Fact LLM-extracted factual atoms
L4 Summary One summary per episode of work (writes with no gap over HYATLAS_EPISODE_GAP), plus one rolling arc per owner from consolidation
L5 Knowledge Entity/relation graph (JSON-persisted). Built by the ultra slow path from relations corroborated across at least two turns, each with evidence citations
L6 Schema Recurring patterns generalised by the ultra slow path
L7 Intention LLM-extracted current goal / next step
L8 Entity One page per thing that matters (a project, tool, person, place): its current state plus a dated history. Written by each extraction; a changed value moves the old one into history

Stack

  • Vector store: Chromem-go v0.7.0 — embedded, disk-persisted, no server process
  • Embeddings: bge/bge.go — BGE-small-en-v1.5 (384-dim) via onnxruntime-go (cgo). WordPiece tokenizer, mean-pool, L2-normalize. No Python.
  • LLM extraction: Via any OpenAI-compatible endpoint. One structured call per write (pro, ultra) fills L3 Fact, L7 Intention, L8 entity pages and, for user preferences, L1 Profile from the L2 Raw entry. The call is given today's date and shown the owner's few nearest existing facts and open intentions, so it can date a fact (event_at) or an intention (due_at), mark a fact whose value changed as replaced, fold new detail or a repeat of the same event into an existing fact as updated (with a count), and mark an intention as moved on or done; those rows are superseded, not deleted, and record how (superseded_how). An intention is written only for a goal beyond the one exchange, and not again for a turn that just continues an open one. The plugin shows each recalled memory with its date. Blocking or background follows HYATLAS_SYNC_EXTRACT. L5 and L6 come from the separate ultra consolidation pass.
  • Graph: graph/graph.go — exact-match traversal over JSON-persisted nodes and relations, scoped per owner
  • HTTP: Standard Go net/http. No framework.

Differences from v3.5 (historical)

This table lists differences that the code in this repository shows. It does not include benchmark numbers.

v3.5 (Python) v4 and later (Go)
Runtime venv, zvec, Kuzu, FastAPI, HTTP embed subprocess One binary, no Python
Release binary — About 160 MB, embedded (model included). A plain build is small and needs a model folder
Listening port 19527 19528 by default
L4 Summary Dormant Active
Restart — Reloads the doc index and graph from the data directory

Repository layout

cmd/hyatlas-go/   the server (package main): HTTP API, extraction, consolidation, search;
                  dashboard/dist is the embedded web dashboard
bge/              in-process BGE-small embedder (onnxruntime-go)
graph/            the L5 knowledge graph store
memory/           layer and mode definitions
plugins/hyatlas/  the Hermes plugin (memory provider, CLI, Desktop pane, dashboard tab)
scripts/          the one-line installer and the ONNX export script
docs/             images and infographic sources

Quick start (Linux / macOS / Windows)

The one-liner (recommended)

curl -fsSL https://raw.githubusercontent.com/tuancookiez-hub/HyAtlas-Memory/main/scripts/install.sh | bash

The script looks for a prebuilt release asset for your platform (hyatlas-go-<tag>-<os>-<arch>). Release assets exist for Linux amd64, macOS arm64 and Windows amd64. On any other platform it builds from source, which needs Go 1.26+ and a C compiler. The installer refuses an older go instead of switching toolchains. It installs the binary to a directory on your PATH, and verifies the install by starting the server and probing /healthz.

  • Release binaries are embedded builds and already carry the BGE-small model and onnxruntime, so the script downloads nothing else for them.

  • Source builds fetch the model (~133 MB) and the onnxruntime 1.28.1 library that matches your CPU (Linux x64 or aarch64, macOS arm64, Windows x64 or arm64). Intel macOS has no onnxruntime package for this version, so the installer stops there with a clear error.

  • If the model fetch fails (for example HuggingFace is blocked), the binary is still installed, the manual steps are printed, and the script exits with status 1. A missing Go or C compiler, or a failed source build, stops the script before anything is installed.

  • Source builds clone the tag named by HYATLAS_VERSION. The default branch is built instead only when that tag is missing and HYATLAS_VERSION is not set, and the installer warns about it. If you set HYATLAS_VERSION and the tag is missing, the install fails.

  • Go 1.26 or newer is required for source builds. A plain go build with an older Go and the default GOTOOLCHAIN=auto fetches the toolchain that go.mod names (1.26.5). The installer does not do that: it stops on an older go.

  • Verification starts the server on a free loopback port and waits about 15 seconds for /healthz. The last line says which of four outcomes happened:

    Final line Exit Meaning
    installed and verified 0 The server answered /healthz with the BGE embedder (an embedded release binary, or a source build with its model).
    binary verified, model missing 1 The server answered /healthz only with the stub embedder (HYATLAS_EMBED_BASE=local). That shows the binary runs, not that embeddings work. The exit code is the model step's.
    NOT verified 1 The server did not answer /healthz. Start hyatlas-go by hand to see its error.
    skipped, not verified 0 A source build with HYATLAS_NO_MODEL=1. Without the model the server cannot start, so nothing was probed.
  • Setup questions. The installer asks for the mode, the LLM endpoint and the key on the terminal. Under curl | bash it asks on /dev/tty. With no terminal at all it asks nothing. Any HYATLAS_MODE, HYATLAS_LLM_BASE, HYATLAS_LLM_MODEL or HYATLAS_LLM_KEY already exported are saved to $HERMES_HOME/.env (default ~/.hermes/.env), which is created with mode 0600.

Useful env vars (installer only):

Variable Purpose Default
HYATLAS_VERSION Release tag to install. A source build clones this tag (see above) v5.0.0
HYATLAS_INSTALL_DIR Where the binary goes ~/.local/bin (Windows: %LOCALAPPDATA%\hyatlas)
HYATLAS_MODEL_DIR Where the installer caches the model (source builds) ~/.hyatlas/models (Windows: %LOCALAPPDATA%\hyatlas\models)
HYATLAS_MODE Extraction mode written to the Hermes .env: lite | pro | ultra. Any other exported value stops the installer with an error and nothing is written ultra (asked interactively)
HYATLAS_LLM_BASE, HYATLAS_LLM_MODEL, HYATLAS_LLM_KEY LLM endpoint, model and key (asked interactively if unset) (none)
HYATLAS_NO_MODEL=1 Skip the model download on source builds (downloads)

The server's own variables (HYATLAS_SYNC_EXTRACT, HYATLAS_CONSOLIDATE_*, HYATLAS_RAW_RETENTION, and the rest) are listed in the configuration table below.


Manual install (3 steps)

Step 1 — Prerequisites (click to expand)
OS What's needed Install command
Linux Go 1.26+, gcc (for cgo) apt install golang-go gcc (or distro equivalent)
macOS Go 1.26+, Xcode CLI tools xcode-select --install + install Go 1.26+ from go.dev
Windows Go 1.26+, MinGW-W64 (for cgo) winget install BrechtSanders.WinLibs.POSIX.UCRT then restart terminal

cgo is required — onnxruntime-go links against the platform's C runtime via cgo. You need a C compiler (gcc, clang, or MinGW-W64).

Step 2 — Build
git clone https://github.com/tuancookiez-hub/HyAtlas-Memory.git
cd HyAtlas-Memory
go build -o hyatlas-go ./cmd/hyatlas-go                     # plain build (small; reads the model from a models/ folder at runtime)
go build -tags embedded -o hyatlas-go ./cmd/hyatlas-go       # embedded build (about 160 MB, model and onnxruntime bundled in)

For the embedded build to compile, cmd/hyatlas-go/models/ must contain four files, because go:embed lists them by name: bge-small-en-v1.5.onnx, bge-small-en-v1.5.onnx.data (an empty file is fine), vocab.txt, and the onnxruntime library for your platform: libonnxruntime.so on Linux, libonnxruntime.dylib on macOS, onnxruntime.dll on Windows. See Model assets for where to get them.

Step 3 — Run
# Local BGE embeddings (the "no Python" path) are the default. Set HYATLAS_MODEL_DIR
# only for a plain build whose model is not in one of the search-order folders in
# "Model assets" below. An embedded build ignores it.
export HYATLAS_EMBED_BASE=bge
export HYATLAS_MODEL_DIR=/path/to/models

# Required for LLM extraction in pro/ultra. No endpoint is assumed: without all
# three the server stores the raw trace only and reports llm=unconfigured.
# Any OpenAI-compatible API works, including a local one.
export HYATLAS_LLM_BASE="https://inference-api.nousresearch.com/v1"   # example
export HYATLAS_LLM_MODEL="poolside/laguna-s-2.1:free"                 # example
export HYATLAS_LLM_KEY="your-nous-agent-key"

./hyatlas-go

The server listens on 127.0.0.1:19528 (loopback only by default). It has no authentication, so keep it on loopback. Use HYATLAS_GO_HOST to change the bind address only if you understand the exposure.

Who may talk to it. The server refuses requests that a web page could make on your behalf, on every bind address:

  • A Host header that names a DNS name is refused with 403, including on /healthz. Only localhost and IP literals pass. This blocks DNS rebinding. To accept a hostname, such as a LAN name or a reverse proxy, list it in HYATLAS_ALLOWED_HOSTS. Entries are hostnames (no scheme, and any port in an entry is ignored). Then the server accepts that name.
  • An Origin header that is not loopback, not the request's own host, and not in the allowlist is refused with 403. A null or non-http(s) origin is refused too.
  • A request with Sec-Fetch-Site: cross-site is refused with 403.
  • Requests with no Origin header (curl, the Hermes plugin) are not affected by the Origin rule, and the server's own /dashboard/ pages are not affected.

Each write returns an extraction_status: pending (extraction runs in the background), done or failed (the write waited for extraction), unconfigured (no LLM endpoint, model or key), or skipped (lite mode).

Privacy — what leaves your machine

The server binds loopback only, but loopback is not the whole story, and the default configuration is not fully local:

What Goes where Default How to keep it local
Memory text (each write's text, sent for extraction) The extraction LLM. Each write is one request, and a failed call or unparseable reply is retried once. From the Hermes plugin, a write is one turn: the user's message and the assistant's text only. Tool calls, tool output, system messages and Hermes compaction summaries are not sent. Each message is capped at 4,000 characters and the turn at 12,000. One extraction call sends at most 16,000 bytes of the write's text (the stored raw row keeps the full text) Nowhere — no endpoint is shipped, so an unconfigured server makes no LLM call and reports unconfigured Already opt-in: set HYATLAS_LLM_BASE/_MODEL/_KEY to choose where it goes. Point them at a local OpenAI-compatible server to keep it on-machine, or use HYATLAS_MODE=lite
Stored facts and schema patterns (ultra only) The same LLM, by the consolidation pass. One request per owner (user_id/agent_id pair) that is due: one of its topic clusters has changed members since the cluster's last successful call. An owner with one fact, or with nothing due, makes no request. Each request covers one cluster and carries up to HYATLAS_CONSOLIDATE_BATCH (50) facts of that owner, each cut to 400 characters with its fact and turn IDs, plus up to 20 existing schema patterns cut to 200 characters. Facts that a pass merges or drops are superseded: kept and hidden from search, not deleted. Each drop is stored with its reason. Once a day at most per owner (HYATLAS_CROSS_SCHEMA_EVERY, off to disable), one more request sends that owner's schema texts (up to 40, each cut to 300 characters) to look for patterns that span topics Nowhere until an endpoint is configured Use pro or lite
Embeddings (each memory's text, and each search query) In-process BGE-small (onnxruntime-go) Local — HYATLAS_EMBED_BASE=bge, no network Already local. If you set HYATLAS_EMBED_BASE to a URL, the text goes there instead, in every mode, including lite
Stored memories, vector index, graph HYATLAS_GO_DATA (default ./data) Local Already local
Telemetry / usage reporting — None. The only outbound HTTP the server makes is to the LLM and embedding endpoints you configure —

So out of the box: embeddings and storage are local, and extraction stays off until you configure an endpoint. Once HYATLAS_LLM_BASE, HYATLAS_LLM_MODEL and HYATLAS_LLM_KEY are all set, every write in pro or ultra is sent to that endpoint to derive facts, summaries and intentions. That is the point of the feature, and it means choosing the endpoint is choosing where the text goes. The endpoint is yours to choose: any OpenAI-compatible API, including one on your own machine. To keep conversation text on the machine, set HYATLAS_MODE=lite: no LLM call is made at all, so only the raw trace and local embeddings are stored. (lite keeps text local only while HYATLAS_EMBED_BASE is bge or local; an HTTP embedder receives the text too.)

The three modes form a ladder of reasoning scope, not of latency:

Mode LLM calls Reasoning scope Layers Consolidation
lite none — 1 / 8 — L2 Raw only no
pro one per write, plus one per episode summary within one turn 6 / 8 — L1, L2, L3, L4, L7, L8 no
ultra (default) one per write + periodic batch across memories and time 8 / 8 at steady state (7 / 8 with HYATLAS_CONSOLIDATE_GRAPH=off) yes

The two systems own disjoint layers:

  • System1 (per turn) — L1 Profile, L2 Raw, L3 Fact, L7 Intention, L8 Entity pages. What one turn can actually evidence. L4 Summary comes from episodes: the writes of one owner with no gap longer than HYATLAS_EPISODE_GAP (30 minutes) form one stretch of work, which gets one summary when it ends. That costs one LLM call per episode, not per write.
  • System2 (slow path) — L5 Knowledge, L6 Schema. A relation worth keeping is corroborated by more than one turn, and a schema is a recurring pattern, so neither can come from a single turn. Ultra is the only mode that runs System2, which is why it is the only one that fills L5 and L6. HYATLAS_CONSOLIDATE_GRAPH=off stops L5 (and the arc).

Whether a write blocks on its extraction is a separate knob (HYATLAS_SYNC_EXTRACT=on|off), not part of the mode. Pro blocks by default and ultra does not, but either can be overridden — capability follows the mode, latency follows the knob.

HYATLAS_SYNC_EXTRACT Effect
(unset) follow the mode: pro blocks, ultra returns immediately
on the write waits for extraction and reports done / failed
off the write returns pending; extraction runs behind it

Ultra-only tuning:

Variable Default Meaning
HYATLAS_CONSOLIDATE_EVERY 6h how often the slow path runs
HYATLAS_CONSOLIDATE_BATCH 50 max facts per owner per consolidation call
HYATLAS_CONSOLIDATE_GRAPH on off (also false, 0, no) stops consolidation writing L5 knowledge edges and the cross-session arc; the prompt then does not ask for them. Each edge is also stored as a searchable L5 memory ("Foxtrot uses Postgres 16")
HYATLAS_CONSOLIDATE_PARALLEL 2 calls run at once (1 to 4)
HYATLAS_CONSOLIDATE_CATCHUP 20m interval between passes while clusters are still due
HYATLAS_CONSOLIDATE_RECLUSTER (unset = never) rebuild an owner's clusters after this long
HYATLAS_RAW_RETENTION (unset = never delete) age after which uncited L2 Raw is deleted

Durations are Go durations (30m, 6h, 2160h) or a plain number of seconds. Go has no d unit, so write 90 days as 2160h. A value that does not parse is logged and treated as unset, so raw decay stays off.

HYATLAS_CONSOLIDATE_BATCH caps the facts one owner sends per call. An owner with more facts than the cap is split into several clusters, each its own call.

HYATLAS_RAW_RETENTION is opt-in because it deletes. An L2 raw row is kept if a live graph edge cites it (as its primary source or any of its sources), or if a live fact (L1 or L3) was extracted from it. Only raw rows that are old and cited by nothing are removed. The decay pass runs once per consolidation pass, so raw rows can age out only when a pass runs.

Facts that the ultra pass merges or drops are superseded, not deleted: they keep invalid_at and superseded_by, and GET /api/v1/list returns them only with include_superseded=true.

The 8 / 8 above is steady state, not the first minute. A fresh ultra install sits at 6/8 until the slow path first runs, which is up to 6h away by default. L5 is filled only when a pass finds relations corroborated by at least two turns, and L6 only when a pass finds recurring patterns. To run a pass now, instead of waiting for the tick:

curl -X POST http://127.0.0.1:19528/api/v1/digest

The response wraps the pass in report: digest_ok: true, then report with facts_in, owners_run, owners_unchanged, skipped_owners, clusters_run, clusters_pending, merged, edges, dropped, schemas, arc, pruned_raw, protected_raw, duration_ms and errors. edges counts distinct L5 edges created. Re-citing an existing edge adds evidence and is not counted.

/api/v1/status carries consolidations (the number of completed passes) and last_consolidated (the last report), so "the slow path ran" is checkable rather than something you have to take on faith. Both reset when the server restarts, so they count passes since the current process started. consolidations is -1 when the mode has no slow path (lite and pro). A pass that finds nothing to reconcile still reports as run with zero changes, which is what distinguishes it from a pass that never happened. An owner whose facts have not changed since its last successful pass counts under owners_unchanged and is not sent to the LLM again.

At the end of a pass, an owner with at least 4 live schemas from at least 2 topics also gets a cross-topic pass, at most once per HYATLAS_CROSS_SCHEMA_EVERY (24h; the last run is kept in consolidate_state.json, so a restart does not repeat it). One extra LLM call, at most once a day per owner, sends that owner's schema texts (newest 40) and asks for up to 3 patterns that hold across two or more of them. Each is written as a new L6 schema marked cross_topic, with the facts behind the schemas it generalises as evidence, unless the owner already has a schema that says the same (the 0.92 duplicate guard). Existing schemas are never retired or edited by it. The report counts them under cross_schemas.

Facts are consolidated in topic clusters of up to HYATLAS_CONSOLIDATE_BATCH facts, built once per owner and kept in consolidate_state.json. An owner here is a person: user IDs grouped by HYATLAS_USER_ALIASES are consolidated together, so a contradiction between facts written under two of their IDs meets in one call. A new fact joins the cluster of its nearest neighbour (similarity 0.55 and up) when that cluster has room, otherwise the newest time-ordered cluster. A cluster is due only when its members changed since its last successful call, so after the first full coverage a pass handles just the clusters that received new facts. A pass runs due clusters across owners, up to HYATLAS_CONSOLIDATE_PARALLEL (2) calls at once, and starts no call when less time is left in its 10-minute budget than a call takes: 1.25 times the median of recent successful calls, at least 3 minutes. While clusters are still due (clusters_pending > 0) the next pass runs after HYATLAS_CONSOLIDATE_CATCHUP (20m) instead of HYATLAS_CONSOLIDATE_EVERY, and a server started with work pending begins with a catch-up pass. How long the first coverage takes depends on the model: on a free model with 25-fact clusters a pass ran 7 clusters in 9 minutes, about half a day for ~150 clusters. Progress is saved after every cluster. A cluster that fails three passes in a row is skipped until its members change or 24 hours pass, and the skip is listed in errors; when two or more calls of a pass fail and none succeeds, the provider is taken to be down and no cluster is charged. A failed LLM call, merge or supersede makes the cluster retry on the next pass. A failed arc, schema, L5 edge or L1 mirror write is listed in errors but does not cause a retry, because the facts were already reconciled.

Otherwise the endpoint is yours to choose per the tier you are on — set HYATLAS_LLM_BASE, HYATLAS_LLM_MODEL and HYATLAS_LLM_KEY to any OpenAI-compatible API, or point HYATLAS_LLM_BASE at a server you host (ollama, vLLM, llama.cpp, LM Studio) and leave HYATLAS_EMBED_BASE=bge.

Nothing is transmitted until all three are set. There is no default endpoint, so a fresh install makes no LLM call at all: writes store the raw trace plus local embeddings, status reports llm: "unconfigured", and each write reports extraction_status: "unconfigured". Once you configure an endpoint, extraction runs on every write in pro and ultra and your turn text goes there — so choosing the endpoint is choosing where memory text leaves the machine. Set HYATLAS_MODE=lite to opt out entirely regardless of configuration.

When the Hermes plugin spawns this server it passes an explicitly allowlisted environment — OS essentials plus HYATLAS_* only — rather than a copy of the agent's environment, so provider API keys the agent holds do not reach the server process. The plugin forwards only the non-secret LLM endpoint and model settings, and only when you have set them in the plugin config. It forwards no LLM key at spawn time. A key reaches the server only if you provide it: your own HYATLAS_LLM_KEY, a plain-text HYATLAS_LLM_KEY_FILE, the llm_key secret saved by hermes memory setup, or a launcher or tool you run that posts it to POST /api/v1/llm/key from the same machine (kept in memory only). A Nous Portal API key, or the key of any OpenAI-compatible provider, works. Until a key arrives, /api/v1/status reports llm: "awaiting_key" and writes are stored without extraction; once a key arrives the catch-up loop extracts them (see HYATLAS_CATCHUP_EVERY), and /api/v1/status shows how many are still waiting as unextracted.

Neither the server nor its launcher reads another program's credential file. Earlier versions of the sample launcher read Hermes' auth.json; a program that opens another application's credentials and then calls the internet is what credential stealers do, and behaviour-based antivirus (Bitdefender, for one) can quarantine it together with every file it wrote. The extraction key is your own. HYATLAS_LLM_KEY_FILE takes a plain-text token file only, so it cannot be pointed at a JSON credential file.

Optional: a local memory model (Hy-Memory-30B-A3B). If you want extraction and consolidation to stay on your machine, Tencent's Hy-Memory-30B-A3B (Apache-2.0) is a good fit. It is trained for exactly this job, writing long-term memory: deciding what to keep and how new facts update old ones. Tencent's model card scores it level with large general models as a memory writer. It is a mixture-of-experts model with about 30B parameters in total and about 3B active per token, so it runs faster than its size suggests. It is heavy, though:

Setup Build Needs
Minimum Q4_0 / Q4_K_M GGUF (17–18 GB) 24 GB of RAM and a modern 6-core CPU with AVX2. No GPU needed: with about 3B parameters active per token it runs on the CPU, roughly 8–15 tokens/s on DDR4
Comfortable Q4_K_M GGUF 32 GB of RAM plus a 6–8 GB GPU, with the experts kept in system RAM (about 1.5–2× faster)
Full speed Q4_K_M GGUF 24 GB+ of VRAM, with the whole model on the GPU
Higher precision Q8_0 GGUF (about 32 GB) 48 GB of VRAM, or 64 GB of RAM
Full weights BF16 (vLLM, SGLang), about 60 GB Data-centre GPUs

The model stays loaded the whole time, so on a 24–32 GB machine it takes most of the memory. Builds below 4 bits (Q2, Q1, TQ1, 4–9 GB) load on 12–16 GB of RAM, but they lose too much accuracy for memory writing: wrong facts and broken JSON.

Serve it from any OpenAI-compatible server and point HyAtlas at it. For example, with llama.cpp and a community GGUF:

llama-server -m Hy-Memory-30B-A3B-Q4_K_M.gguf --port 8080 -c 32768 --n-cpu-moe 48 --jinja --chat-template-kwargs '{"reasoning_effort":"no_think"}'
export HYATLAS_LLM_BASE="http://127.0.0.1:8080/v1"
export HYATLAS_LLM_MODEL="hy-memory"
export HYATLAS_LLM_KEY="local"   # any non-empty value; a local server ignores it

Notes:

  • --n-cpu-moe keeps that many layers' experts in system RAM. Raise it until the model fits your VRAM, and lower it for speed.
  • The model card runs it with reasoning_effort: "no_think". Set that on the server as shown, because HyAtlas does not send it per request.
  • To try it without giving up a hosted model, set it as the fallback instead (HYATLAS_LLM_FALLBACK_BASE, HYATLAS_LLM_FALLBACK_MODEL). It then answers only when the primary fails.
  • Consolidation prompts are several thousand tokens. On a mid-range desktop with the experts in RAM, expect minutes per consolidation call. That is fine for a background pass, but slower than a hosted model.
  • It was trained on the Mem0, MemOS and Hy-Memory prompt formats. HyAtlas uses its own, so check a few consolidation passes before relying on it.

All configuration is via environment variables — the binary reads no settings from its command line:

Variable Default Purpose
HYATLAS_GO_PORT 19528 HTTP listen port
HYATLAS_GO_HOST 127.0.0.1 Bind address (loopback only by default). IPv6 literals work with or without brackets
HYATLAS_ALLOWED_HOSTS (empty) Comma-separated hostnames the server accepts in Host and Origin, in addition to localhost and IP literals. A DNS name not listed here gets 403 on every route, including /healthz. Entries are hostnames; a port in an entry is ignored. Add a name only if you reach the server through it, such as a reverse proxy or a LAN hostname.
HYATLAS_GO_DATA ./data Data directory (relative paths resolve against the working directory): chromem collections, doc_index.json, consolidate_state.json, and by default graph.json
HYATLAS_GRAPH_PATH <data>/graph.json L5 graph store location
HYATLAS_EMBED_BASE bge bge = local in-process BGE embedder (no network). Set to a URL for an OpenAI-compatible embedder, or local for a deterministic stub that is not semantic.
HYATLAS_EMBED_MODEL text-embedding-3-small Model name sent to an OpenAI-compatible embedder. Used only when HYATLAS_EMBED_BASE is a URL.
HYATLAS_EMBED_KEY (empty) Bearer token for that embedder. Used only when HYATLAS_EMBED_BASE is a URL.
HYATLAS_MODEL_DIR (unset: search order below) Where the BGE model lives, for a plain build. When set, it is the only directory searched. When unset, the server looks in <cwd>/models, then <exe dir>/models, then ~/.hyatlas/models (Windows %LOCALAPPDATA%\hyatlas\models). An embedded build ignores this variable.
HYATLAS_MODE ultra lite | pro | ultra. An unrecognised value is fatal at startup. See the mode table above.
HYATLAS_SYNC_EXTRACT (follows the mode) on | off. Whether a write waits for extraction. An unrecognised value is fatal at startup.
HYATLAS_CONSOLIDATE_EVERY 6h Ultra only: interval between slow-path passes. Must be positive in ultra: 0, off, false, no or a negative value is fatal at startup; use pro to run without the pass.
HYATLAS_CONSOLIDATE_BATCH 50 Ultra only: max facts per owner per consolidation call. Larger owners are split into several clusters, one call each.
HYATLAS_CONSOLIDATE_GRAPH on Ultra only: off (also false, 0, no) stops consolidation writing L5 knowledge edges and the cross-session arc, and the prompt then does not ask for them. Each edge is stored in the graph (drawn by the dashboard and the Desktop starmap) and as a searchable L5 memory; edges from older versions are indexed for search at startup.
HYATLAS_CONSOLIDATE_PARALLEL 2 Ultra only: how many consolidation calls run at once (1 to 4)
HYATLAS_CONSOLIDATE_CATCHUP 20m Ultra only: the interval between passes while clusters are still due
HYATLAS_CONSOLIDATE_RECLUSTER (unset = never) Ultra only: rebuild an owner's topic clusters after this long; clusters whose members are unchanged keep their progress
HYATLAS_CROSS_SCHEMA_EVERY 24h Ultra only: the least time between two cross-topic schema passes for one owner; 0 or off disables it. The pass makes one extra LLM call, sending that owner's schema texts, only for an owner with at least 4 schemas from at least 2 topics
HYATLAS_RAW_RETENTION (unset = never delete) Ultra only: age after which an L2 raw row cited by no live edge or fact is deleted
HYATLAS_USER_ALIASES (unset = no aliases) Groups of user IDs that are one person, for search: groups separated by ;, IDs in a group by ,, e.g. "221727702992945152,default,hermes-memory-archive". A search for any ID in a group covers the whole group, and so do list, write-time de-duplication, the related facts shown to extraction, consolidation, forget, episodes and the Now page. Episodes and Now pages are kept under the first ID of the group
HYATLAS_MIN_SCORE 0.60 Search drops vector hits below this cosine similarity. 0 disables the floor. A request may override it with min_score
HYATLAS_DEDUPE_SCORE 0.92 Write-time de-duplication: a new L3 fact at or above this similarity to the owner's nearest live fact supersedes it. 0 disables it
HYATLAS_ADMIN off on enables the two /api/v1/admin/* maintenance endpoints (see Maintenance (admin) in the API Reference), and bulk delete (/api/v1/delete_all by layer, user, agent or all). Otherwise they return 403. Deleting one memory by id needs no switch
HYATLAS_CATCHUP_MAX_AGE 168h (7 days) Pro and ultra: how old a raw memory may be and still be caught up. Older rows, and rows written in lite mode, are never re-extracted automatically (/api/v1/reprocess still can). 0 or off means no age limit
HYATLAS_CATCHUP_EVERY 10m Pro and ultra: how often the server re-extracts raw memories that extraction never reached (written while no key was set, or whose extraction call failed). Up to 20 per run, oldest first, one at a time; a row under 2 minutes old is left alone, and the run stops at the first model error. A reply that cannot be parsed counts a try on that row and the run goes on; after 3 tries the row is left alone. Only rows written in pro or ultra, and younger than HYATLAS_CATCHUP_MAX_AGE, are taken. A key arriving also triggers a run. 0, off, false or no turns it off
HYATLAS_LLM_BASE (unset) OpenAI-compatible LLM endpoint. Memory text is sent here once you set it — see Privacy above. Unset means no LLM call at all.
HYATLAS_LLM_MODEL (unset) LLM model name. Base, model and key must all be set for extraction to run.
HYATLAS_LLM_KEY (empty) LLM bearer token
HYATLAS_LLM_KEY_FILE (empty) Read the key live from this file on every call, for a rotating key file of your own. The file must hold the token as plain text. A file whose content starts with { (JSON, such as another program's credential file) is refused with one warning in the log, and the key from HYATLAS_LLM_KEY is used instead. Wins over HYATLAS_LLM_KEY, which is the fallback if the file cannot be read or is refused. A key posted to POST /api/v1/llm/key wins over both.
HYATLAS_RECENCY on off stops newer memories winning close calls in search (see Search)
HYATLAS_MMR_LAMBDA 0.75 Relevance weight of the diversity pass in search, in (0, 1]; 1 turns it off (see Search)
HYATLAS_INTENTION_TTL 168h (7 days) How long an L7 intention without a due date stays in search results. Older intentions are kept but not returned. 0, off, false or no means no limit: they are all returned. An intention with a due date stays until a day after it is due, then drops out
HYATLAS_SNAPSHOT_EVERY 24h How often the server zips its data directory (first one a minute after start). 0 or off turns snapshots off
HYATLAS_SNAPSHOT_KEEP 7 How many snapshots to keep; older ones are deleted
HYATLAS_SNAPSHOT_DIR <data>-snapshots Where snapshots go: a folder next to the data directory by default; do not point it inside the data directory
HYATLAS_RECONCILE off on: after extraction, one more LLM call compares the new facts with similar stored ones (cosine 0.70 or more) and merges repeats (UPDATE, which keeps a recurring event as a series), retires facts that are no longer true (SUPERSEDE), or links related facts without retiring them (LINK). Doubles the LLM calls per write: meant for a local model, not a hosted free tier
HYATLAS_EPISODE_GAP 30m pro and ultra: a gap between an owner's writes longer than this ends an episode of work, which then gets one L4 summary. 0 or off writes no episode summaries
HYATLAS_NOW_EVERY 15m How often an owner's Now page may be rebuilt after writes (pro and ultra only). 0 or off rebuilds after every write
HYATLAS_USAGE_LEARNING on off stops search learning from which recalled memories replies actually used
HYATLAS_LLM_FALLBACK_MODEL (unset) A second model, tried only when a call to the primary fails (error, empty reply or timeout). The primary then gets two thirds of the call's time. Unset means no fallback
HYATLAS_LLM_FALLBACK_BASE (the primary's) OpenAI-compatible endpoint for the fallback model
HYATLAS_LLM_FALLBACK_KEY (see purpose) Key for the fallback endpoint. Unset reuses the primary key or key file only when the fallback is on the primary's host; a fallback on another host gets no key rather than the primary's
HYATLAS_EXTRACT_TIMEOUT 300s How long one extraction call may take

The binary reads no settings from the command line. It answers only --version and --help; any other argument is refused instead of starting a second server.

Durations. Every duration setting (the HYATLAS_CONSOLIDATE_* intervals, HYATLAS_RAW_RETENTION, HYATLAS_CATCHUP_EVERY, HYATLAS_EPISODE_GAP, HYATLAS_NOW_EVERY, HYATLAS_INTENTION_TTL, HYATLAS_SNAPSHOT_EVERY) reads a value the same way. 15m or 6h is a Go duration, a bare number is seconds, and 0, off, false or no turns the feature off. An empty value uses the default. A value that is none of these logs a line and uses the default, so a typo never changes the schedule silently.

Snapshots. The server backs itself up: every HYATLAS_SNAPSHOT_EVERY it writes hyatlas-<UTC time>.zip of the whole data directory to HYATLAS_SNAPSHOT_DIR, pausing writes while it copies, and keeps the newest HYATLAS_SNAPSHOT_KEEP. To restore, stop the server, replace the data directory with a snapshot's contents, and start it again.

Stopping. Ctrl-C or SIGTERM stops the server cleanly: it finishes the requests in progress, waits up to 10 seconds for background extractions that writes just served, then writes the doc index and usage counts. Whatever extraction is still left is picked up by the catch-up loop on the next start. A hard kill skips all of this, so the Windows launcher (hyatlas-go.ps1 stop) and the Hermes plugin's stop on Windows call POST /api/v1/flush before they stop the process.


Model assets

The BGE-small model + the platform-matching onnxruntime shared library live in models/ (gitignored). For a plain build, the server reads them at runtime. For an embedded build, go:embed bundles them into the binary at compile time from cmd/hyatlas-go/models/ (copy the same files there before building), so the release binary (built with -tags embedded) needs no model folder at all.

Where a plain build looks (when HYATLAS_MODEL_DIR is unset), in order:

  1. <cwd>/models (a checkout run from the repo root)
  2. <exe dir>/models (the folder the binary sits in)
  3. the installer's default: ~/.hyatlas/models, or %LOCALAPPDATA%\hyatlas\models on Windows

If HYATLAS_MODEL_DIR is set, that directory is the only one searched. A plain build without a model in the searched places refuses to start and names every directory it checked. An embedded build never searches these places: it unpacks the bundled model into a temporary directory at startup.

Directory layout (per platform):

OS Models dir contents
Windows bge-small-en-v1.5.onnx + .onnx.data + vocab.txt + onnxruntime.dll
Linux bge-small-en-v1.5.onnx + .onnx.data + vocab.txt + libonnxruntime.so
macOS bge-small-en-v1.5.onnx + .onnx.data + vocab.txt + libonnxruntime.dylib

Where to get them:

  • Model: https://huggingface.co/Xenova/bge-small-en-v1.5/resolve/main/onnx/model.onnx (saved as bge-small-en-v1.5.onnx) and .../resolve/main/vocab.txt. The release workflow and the installer use these URLs.
  • onnxruntime 1.28.1: the release archives that .github/workflows/release.yml downloads, onnxruntime-<platform>-1.28.1 from microsoft/onnxruntime releases. Take the lib/ file for your platform (onnxruntime.dll, libonnxruntime.so or libonnxruntime.dylib).

To regenerate the ONNX from the HuggingFace model, see scripts/export_bge_onnx.py.

Version pins (critical)

  • onnxruntime_go v1.32.0 — declares ONNX API 28
  • onnxruntime 1.28.1 shared library — must expose API 28
  • The .data external weights resolve relative to the process CWD. The embedder chdirs to the model dir while it loads the model, then changes back.

What changed since v3.5.0

  • Pure Go rewrite — single binary (about 160 MB embedded release), no Python venv, no zvec, no Kuzu, no FastAPI
  • In-process BGE embeddings via onnxruntime-go (cgo) — no HTTP embed subprocess
  • L4 Summary enabled — was dormant in v3.5
  • L5 bitemporal graph (v4.1.0+) — every L5 relation carries its source L2 memory IDs plus a bitemporal timestamp; the /api/v1/graph-as-of?ts=<unix> endpoint lets you rewind the graph to any past moment.
  • Mind Palace (v4.1.0+) — a temporal visualization of the L5 knowledge graph in the Hermes Desktop hyatlas pane. Its design is in plugins/hyatlas/desktop/SPEC.md. The Desktop app itself is not part of this repository.
  • Living memory (v4.6.0–v4.7.x) — the Now page, L8 entity pages, usage learning, topic-clustered consolidation with drop reasons, guards and restore.
  • Precision per fact (v4.8.0) — event dates and date-aware search, updates with counts, intention due dates, episode summaries, history on read, near-duplicate demotion, idle-time recency, and the localhost key hand-over that replaced reading auth.json.
  • A stable floor (v5.0.0) — turns that were never extracted are caught up, forgetting mirrored from Hermes's memory tool, linked facts and recurring events under HYATLAS_RECONCILE, the current session's intentions first, one alias rule everywhere, a clean shutdown, and a faster, inverted-index search.
  • Smarter recall (v4.9.0) — task experiences, schemas with evidence, long facts found by their sentences, an entity boost and graph-assisted recall, query-kind weighting, and an opt-in second reconcile call for local models.

API Reference

Base URL: http://127.0.0.1:19528

Method notes: the handlers read their parameters from the query string, the JSON body, or both. Where a method is shown, that is the one the Hermes plugin and the docs use. Some handlers also accept other methods, so check routes() in cmd/hyatlas-go/server.go if you depend on that. Every route is behind the Host/Origin guard described above.

Method Path Parameters and response
GET /healthz Liveness. Returns {"status":"ok"}.
GET /api/v1/status status, version, vdb, embed, llm (ok | unconfigured | unused), llm_model, llm_base, mode, mode_detail, uses_llm, extract_sync, write_pipeline (ok or degraded: …), writes, searches, layers (per layer, live rows; L5 = graph node count), graph_nodes, graph_edges, unextracted (live raw rows that extraction has not reached; the catch-up loop works through them, and lite always reports 0), consolidations (-1 without a slow path; resets on restart), last_consolidated (last report; omitted until a pass has run). graph_nodes and graph_edges count all owners.
GET /api/v1/metrics layers, total, graph_nodes, graph_edges (all owners).
POST /api/v1/add Body: text (or data, required), user_id, agent_id, session_id, metadata. Returns success, memory_id, extraction_status (done | failed | pending | unconfigured | skipped). A missing text returns 400.
POST /api/v1/search Body: query (required), limit, layer, user_id and agent_id (strings, as the Hermes plugin sends them), user_ids and agent_ids (arrays; only the first entry of each is used), min_score (float, overrides HYATLAS_MIN_SCORE; 0 disables the floor), reader (ranking: legacy, hybrid_tag or hybrid by default; see Search), session_id (the caller's session: its own L7 intentions rank above other sessions'). Returns memories: pages, profile, proactive, experiences and normal lists, each item with memory_id, content, score, layer, gmt_created, user_id, agent_id; order, the channel names in the order to show them (now first, then the channels); and now when the owner has a Now page.
GET or POST /api/v1/list Query or body: layer, user_id, agent_id, limit (default 20), offset, include_raw (default on; false hides L2 rows when no layer is given), include_superseded (true also returns rows the ultra pass merged or dropped, with invalid_at, superseded_by and superseded_how, how the row was retired: replaced, updated, merged, restated, dropped, done or forgotten; drop_reason is added when there is one). A user_id lists the rows of every ID in its alias group. The string all is not special here. Returns total, offset, limit, memories, layers, graph_nodes, graph_edges.
GET or POST /api/v1/graph node (the L5 node ID), user_id, agent_id. Read from the query string or the JSON body; the body wins when both are set. all or empty means no owner filter. Returns node, neighbors, node_count, edge_count (for that owner), extract_err.
GET /api/v1/edges Query: n (default 500), k_semantic (default 3), user_id, agent_id (all = no filter). Returns nodes, knowledge (L5 relations, each with sources), co_session, semantic, total_edges.
GET /api/v1/learning/graph Query: n, k_semantic, user_id, agent_id. Returns the starmap feed: nodes, edges, memory, stats.
GET /api/v1/graph-as-of Query: ts (unix seconds, default now), n (default 500), user_id, agent_id. Returns `no

README truncated. Continue reading on GitHub