Hermes Agent

Type: ../types/agent-memory-system-review.md · Tags: trace-learning

Hermes Agent is a personal agent harness built by Nous Research for terminal, desktop, and messaging use. Its built-in memory design combines small profile-scoped files for durable facts and user preferences, reusable skill packages for procedures, a searchable SQLite session record, automatic post-turn review that can learn from the live trace, and an optional external memory-provider interface. This review covers the main branch source identity supplied for the prepared checkout.

Source: NousResearch/hermes-agent

Reviewed commit: d59b79fadd1e9edd7afc5c679cc3b143838e7c01

Core Ideas

Separate factual and procedural memory by disclosure cost. Hermes keeps durable environment facts in MEMORY.md and user facts or preferences in USER.md; both are injected into the system prompt. Procedures live as agent-skills packages: the prompt carries a catalogue of names and descriptions, while the agent pulls the full SKILL.md and linked support files only when relevant. The two profile files default to hard character limits of 2,200 and 1,375, respectively; exceeding a limit asks the agent to replace or remove content rather than silently compacting it. This makes a small knowledge-artifact tier coarse and persistent while keeping larger system-definition artifacts progressively disclosed (tools/memory_tool.py, agent/prompt_builder.py, tools/skills_tool.py).

Learn in a fork, outside the foreground prompt cache. Separate user-turn and tool-iteration counters can request memory review or skill review after a completed turn. A daemon-thread fork replays a snapshot of the conversation, restricts itself to memory and skill-management tools, and uses the configured model as the judge of whether a durable fact, preference, correction, or procedure should be retained. The main session does not record the review prompt or inherit the fork's messages; successful writes are instead surfaced as a notification. If write approval is enabled, background changes are staged for human approval, but both memory and skill approval gates are off by default (agent/turn_context.py, agent/turn_finalizer.py, hermes_cli/config.py).

the conversation snapshot in a forked :class:AIAgent and asks itself "should any skill/memory be saved or updated?". Writes go straight to the memory + skill stores. Main conversation and prompt cache are never touched. --- agent/background_review.py

Preserve raw sessions as evidence instead of making summaries the only recall surface. Each profile has a SQLite/WAL session database containing sessions, messages, lineage, and FTS5 plus trigram indexes. session_search supports discovery, anchored windows around a match, paging, browsing, and bounded whole-session reads; it returns actual messages, so a user or agent can inspect the evidence behind a remembered episode. Compression summaries are a separate context-management product, not the search result (hermes_state.py, tools/session_search_tool.py).

Treat context efficiency as tiering, bounded windows, and cache stability. The small profile memory is always loaded; skill descriptions are frontloaded but full skill bodies and support files are pulled progressively; session search defaults to a few results and bounded surrounding messages; and compression keeps a recent tail plus an LLM-generated summary of the middle. Subagents and background review forks have isolated histories, while the latter normally reuses the parent's cached system prefix. The design controls volume well, although the full skill catalogue has no semantic top-k budget, and the default compressor can drop the middle without a summary if the selected summarization model cannot accept the input (agent/system_prompt.py, agent/conversation_compression.py, agent/background_review.py).

Freeze prompt memory within a cache epoch, not absolutely for the session. MemoryStore snapshots the two files for prompt use, while tool writes update the live files immediately; ordinary later turns keep the cached snapshot. Context compression explicitly invalidates the system prompt, reloads the files, and rebuilds the prompt, so a same-session memory write can become visible after compression even though the user documentation describes changes as taking effect next session (tools/memory_tool.py, agent/system_prompt.py, agent/conversation_compression.py).

Favor inspectable local adoption, with optional service-backed recall. The default durable surfaces are ordinary Markdown skill/memory files and SQLite, with atomic writes, file locks, drift backups, recoverable curator archives, exact-duplicate rejection, and strict injection scanning for profile-memory writes. Users can edit or back up these artifacts without a metered service. One external memory plugin may additionally receive completed turns and prefetch query-relevant context before a model call. Trust is uneven: profile memory fails closed on detected injection content, but loading a skill only logs suspicious patterns and still serves it, and the stronger scanner for agent-created skills is disabled by default (tools/memory_tool.py, agent/memory_provider.py, tools/skills_tool.py, hermes_cli/config.py).

Artifact analysis

Storage substrate: files sqlite vector service-object — Built-in facts, preferences, skill packages, usage sidecars, curator state, archives, and reports are profile files; raw messages and search indexes are SQLite. Optional bundled providers can instead retain facts in local vectors or external provider objects, but neither is required for the default path.

Representational form: natural-language symbolic parametric — Memory entries, user-profile entries, session messages, compression summaries, and skill instructions are natural-language. Skill frontmatter, support scripts, usage state, approval records, FTS indexes, and curator state are symbolic. Optional Mem0-style providers add learned embeddings/vector indexes; Hermes' built-in Holographic provider also stores fixed-width distributed vectors (plugins/memory/mem0/_oss_providers.py, plugins/memory/holographic/store.py).

Lineage: authored imported trace-extracted other-compiled — Users or the foreground agent author profile memory and skills; bundled, hub, and external-directory skills are imported. Session rows are trace-extracted, as are memory and skills produced by background review. FTS/trigram indexes, skill catalogues, usage-derived lifecycle state, and compression summaries are compiled from retained source artifacts; message changes invalidate search indexes, skill changes regenerate the catalogue, and history changes regenerate a later compression summary.

Behavioral authority: knowledge instruction enforcement routing validation ranking learning — Profile memory, prefetched provider recall, and session hits serve as knowledge artifacts. Skill natural-language content is a system-definition artifact consumed as instruction; manifests and catalogue entries route disclosure; parsers, size limits, injection guards, read-before-write requirements, pins, approval state, and consolidation-delete guards validate or enforce mutations. Search scores and skill usage state rank or tier candidates, while background review and curator prompts define learning and maintenance behavior. Whether natural-language content is actually obeyed and whether retrieval is precise are not verified from static code.

The operative paths are distinct:

  • MEMORY.md and USER.md are bounded natural-language knowledge artifacts, written manually or distilled from traces, then copied into the cached system prompt. The snapshot is regenerated at a new cache epoch.
  • A skill package combines natural-language instruction (SKILL.md) with symbolic frontmatter and optional scripts, references, templates, and assets. The prompt-level catalogue routes the agent to skill_view; support files are disclosed separately. Background review can promote a trace lesson directly into this stronger instruction surface.
  • The session database is a source-preserving knowledge-artifact layer. Messages are the raw trace; FTS5 and trigram structures are disposable ranking artifacts rebuilt from them. Session search does not itself promote a hit into a rule.
  • Curator and skill-usage sidecars are symbolic learning, ranking, and enforcement state. They drive active/stale/archive transitions and protect pinned or cron-referenced skills; optional model curation can consolidate several stored skills into a new umbrella instruction.
  • Compression summaries are other-compiled natural-language used to preserve selected knowledge inside the active context, while optional trajectories export raw or compressed traces for external training. Trajectory saving is disabled by default and no core loop trains Hermes from those exports (agent/conversation_compression.py, website/docs/developer-guide/trajectory-format.md).

The principal promotion path is conversation and tool trace → background-review judgment → bounded knowledge entry or skill package → frozen prompt block or catalogue entry → future read-back. Human approval can interpose a staged candidate, and later usage can protect or reactivate a skill; optional curator consolidation can move several narrow instruction artifacts into an umbrella skill. There is no observed promotion from a learned natural-language instruction into a tested validator, enforced gate, or model update, and the reviewing model is the default acquisition oracle rather than a task-success evaluator.

Comparison with Our System

Hermes and Commonplace both favor inspectable files, reusable Markdown instructions, progressive disclosure, cheap lexical routing, and validation at write boundaries. Both distinguish a compact navigation surface from larger artifacts and retain source-like material that can be inspected rather than trusting only an opaque model state.

Their operating centers differ. Hermes is a profile-scoped runtime memory for one agent: it automatically records sessions, pushes a small personal memory into every prompt, and can turn recent interaction traces directly into instructions. Commonplace is a typed, linked, repository-backed knowledge base whose retained claims and system definitions have collection contracts, explicit citation and lineage conventions, deterministic validation, and review workflows. Hermes therefore closes the acquisition/read-back loop faster and with less authoring friction; Commonplace has stronger semantic accountability, graph navigation, review separation, and artifact-level contracts. Hermes' fixed fact-memory cap gives it a sharper always-load budget, while Commonplace keeps most knowledge outside the prompt and relies more heavily on routing.

Borrowable Ideas

  • Use frozen prompt snapshots with explicit refresh epochs — ready where Commonplace gains a long-lived runtime. Cache a small generated context block for a run, keep its source artifacts live and inspectable, then rebuild only at a declared boundary such as compaction. The boundary must be visible because Hermes shows that “next session” and “next cache epoch” are not equivalent.
  • Keep raw trace recall separate from distilled guidance — ready now for review and execution logs. A bounded lexical search returning source messages and anchored neighbors would let Commonplace agents recover evidence without treating a generated summary as ground truth; promotion into a note or instruction should remain a separate reviewed action.
  • Adopt read-before-write, recoverable archival, and a fail-closed verified-absorption guard for autonomous instruction maintenance — ready now. Before an automated reviewer patches a skill, require it to load the exact target; before consolidation removes a source instruction, require an existing, verified absorbed_into forwarding target and archive the original, and keep any deterministic no-target prune path as a structurally separate operation so it cannot reuse the consolidation call to bypass the check. Hermes added exactly this guard after an incident (#29912) where an earlier consolidation pass archived whole clusters of active skills with zero verified absorptions (tools/skill_manager_tool.py) — worth pre-adopting in any future Commonplace automated note-consolidation tooling rather than waiting for the equivalent incident.
  • Stage trace-derived proposals with provenance — needs a concrete Commonplace operator workflow first. A background pass could propose memories or instruction patches after a review run, but it should write a report or pending diff linked to the source trace by default, not mutate library artifacts silently.
  • Add usage-driven decay only after defining what “unused” means, and pair it with automatic reference-exemption — needs a use case first. Hermes' stale/archive lifecycle gives a never-used skill a grace floor before it counts as stale, reasoning explicitly that absence of evidence is not evidence of staleness, and it exempts any skill a scheduled job still depends on — including paused jobs — from decay entirely, so a job firing less often than the archive window doesn't lose its dependency between runs (agent/curator.py). Commonplace notes can remain valuable as evidence without direct invocation, so age or low read counts alone would be a poor eviction oracle; if Commonplace ever adds decay, an inbound-reference exemption check is the safeguard that would make it safe.

Write side

Write agency: manual automatic — Users and foreground agents can add, replace, remove, create, patch, import, approve, reject, pin, archive, restore, or consolidate retained material. Automatic paths record every session, let configured external providers ingest completed turns, distill qualifying live traces through background review, rebuild access structures, summarize compressed history, and run the skill lifecycle curator.

Curation operations: consolidate evolve decay promote — The opt-in model curator can merge overlapping stored skills into an umbrella and archive absorbed sources, which is reductive consolidation. Background review can patch an existing skill in light of a newly completed trace, which evolves that retained instruction in place. The default deterministic curator marks unused skills stale and later archives them by inactivity, which is decay; it reactivates a stale skill after renewed use, which promotes its lifecycle tier. Exact duplicate rejection during memory acquisition is validation, not curation (agent/background_review.py, agent/curator.py, tools/skill_manager_tool.py).

Trace-learning

Trace source: session-logs tool-traces — The learning fork consumes an in-memory snapshot of user, assistant, tool-call, and tool-result messages. Memory review is triggered by a configurable user-turn cadence and skill review by a configurable tool-loop cadence; interrupted turns do not spawn the post-turn review.

Extraction is an LLM judgment over the trace. The memory prompt asks for durable preferences, facts, decisions, corrections, and environment knowledge; the skill prompt asks about user corrections, overcome errors and dead ends, non-trivial workflows, and reusable procedures. The fork uses the main model and full warm snapshot by default; if routed to a different auxiliary model, it receives a terse digest plus the most recent 24 messages. There is no task-success score, external judge, or stored source link attached to each extracted entry. The write tool's schema, character caps, target-read guard, pinned/protected-skill rules, and optional human approval constrain what the model can land, but they do not establish semantic correctness (agent/background_review.py, tools/memory_tool.py, tools/skill_manager_tool.py).

Learning scope: cross-task — Learned memory and skills are profile-scoped and available across later sessions and tasks. Their content can concern one project, but the automatic path has no separate project-local namespace.

Learning timing: online staged — The review runs asynchronously after a qualifying live turn, so learning can affect a later turn or session without an offline batch. Counter thresholds make it periodic, and approval mode inserts a staged pending-diff cycle before persistence.

Distilled form: natural-language symbolic — The fork writes natural-language facts/preferences or natural-language skill instructions; skills may also include symbolic YAML frontmatter, scripts, and other support files. Hermes does not update model weights from this loop.

On the survey axes, Hermes is framework-integrated, single-session trace learning whose readable artifact output shapes later inference without changing weights. It combines two techniques the survey should keep separate: raw trace-to-recall indexing in SQLite and model-mediated trace-to-artifact distillation into memory or skills. It strengthens the claim that useful online adaptation can remain inspectable and non-parametric, while splitting the “governance is absent” pattern: optional approval, read-before-write, pins, backups, and recoverable archives govern mutation, but there is still no behavioral outcome oracle or faithfulness test for the learned content.

Read-back

Read-back: both — Hermes pushes profile memory and the skill catalogue into the agent's context, can push a user-selected skill or provider-prefetched recall, and also gives the agent deliberate skill_view and session-search pull tools.

Read-back signal: coarse identifier inferred / embeddingMEMORY.md, USER.md, and skill descriptions are coarse always-load context. A user invoking a named skill pushes identifier-selected instruction to the receiving agent. A configured Mem0 provider can search vector memory from the current message and inject its result; other provider plugins implement their own query relevance, so their exact signal is provider-specific (agent/memory_manager.py, plugins/memory/mem0/__init__.py, plugins/memory/mem0/_oss_providers.py). FTS5 session matching is inferred / lexical, but it is agent-initiated pull and therefore is not included in the push-signal verdict.

Faithfulness tested: no — The inspected tests cover storage, triggering, prompt-cache isolation, write guards, provider injection, and search mechanics, but no WITH/WITHOUT ablation, perturbation, or post-action audit tests whether served memory changes behavior as intended.

All read-back happens pre-invocation. The cached system prompt carries the bounded profile memory and skill index; the full skill body arrives as a tool or slash-command result; external prefetch is wrapped as a <memory-context> block and ephemerally appended to the current user message without persisting that augmentation; session-search results arrive as ordinary tool output. The provider wrapper calls recalled material “authoritative reference data,” the prompt tells the model to load relevant skills, and profile memory sits in the system channel, but effective obedience remains unverified from code (agent/conversation_loop.py, agent/memory_manager.py, agent/system_prompt.py).

Selection costs vary by tier: profile memory consumes its full fixed cap; every visible skill contributes at least catalogue metadata, then selected skills add full bodies and linked files; session discovery defaults to three results, scans a bounded candidate pool, deduplicates session lineage, and returns a bounded neighborhood with bookends; whole-session reads truncate large histories. External-provider top-k and budgets are provider-defined. Runtime precision, recall, and context dilution are not established by the source.

Humans consume the same inspectable files, pending diffs, session views, curator reports, and archives. The curator and background reviewer are also consumers: they read usage state or a trace snapshot to decide later write-side maintenance, but those post-turn decisions are not additional read-back into the foreground action.

Curiosity Pass

  • The README says session search uses LLM summarization, while the current tool explicitly guarantees the opposite. The source-preserving implementation is simpler and more trustworthy than the product claim:

FTS5 session search with LLM summarization for cross-session recall. --- README.md

No LLM calls anywhere — every shape returns actual messages from the DB. --- tools/session_search_tool.py

  • “Self-improvement” is artifact acquisition and maintenance by the same model family that performed the task. The trigger counts turns or iterations, not success; the judge can notice correction language and tool failures, but cannot tell from code whether the retained lesson caused the eventual success or merely co-occurred with it.
  • The memory documentation's next-session visibility model is only approximately true: compression reloads the memory files and rebuilds the cached system prompt during the same logical conversation. A clearer abstraction is a frozen cache epoch.
  • Memory and skills have asymmetric injection defenses. Memory content matching suspicious patterns is blocked or sanitized before prompt use; skill content matching similar patterns is logged and still returned, while agent-created-skill write scanning is opt-in. This matters because a learned skill carries instruction authority and may include executable support files.
  • Optional trajectory output is research infrastructure, not evidence of a deployed parametric learning loop. The core agent does not train on its own JSONL exports, so counting trajectory generation as self-improvement would conflate trace retention with learning.
  • Background maintenance runs on two independent triggers, not just the post-turn counters described above. maybe_run_curator also fires on agent idleness — past min_idle_hours and a stale last-run older than interval_hours — with no cron daemon and no turn count involved (agent/curator.py).
  • The consolidation-delete guard was hardened by a specific incident, not written as a generic best practice. _curator_consolidation_delete_guard's docstring cites issue #29912: an earlier consolidation pass archived whole clusters of active skills with zero verified absorptions. The guard now fails closed on any curator-fork delete that doesn't declare and verify an existing absorbed_into target, while the deterministic inactivity-prune path stays a structurally separate call so it cannot reuse the same tool call to bypass the check (tools/skill_manager_tool.py).
  • Staleness has a grace floor for skills that have simply never fired: a skill with zero uses is exempt from staleness until it is at least stale_after_days old, on the code's explicit reasoning that absence of evidence is not evidence of staleness (agent/curator.py).
  • Decay exempts referenced artifacts automatically: any skill a scheduled cron job depends on — including paused or disabled jobs — is treated as pinned, because a job firing less often than the archive window would otherwise lose its dependency before it runs again (agent/curator.py).

What to Watch

  • Whether background-review writes gain per-entry links to the source session/messages or a task-outcome signal. That would let Commonplace evaluate trace-derived proposals as evidence-backed candidates instead of model-authored assertions.
  • Whether write approval or a semantic skill guard becomes the safe default, and whether suspicious skills are quarantined rather than merely logged. This determines whether automatic procedural memory can be trusted as system-definition material.
  • Whether the always-loaded skill catalogue receives a hard token budget or relevance selection. Its current progressive disclosure bounds skill bodies but not the growth of catalogue descriptions.
  • Whether compression fails safely when its summary model cannot accept the middle history, and whether memory-refresh epochs become explicit in documentation. Both affect whether a retained fact can unexpectedly disappear or appear mid-session.
  • Whether the common provider interface standardizes top-k, provenance, targeting signal, and faithfulness telemetry. Without that contract, “external memory prefetch” spans materially different context costs and trust properties.

Relevant Notes: