← Writing

Hermes Agent

As a Conversation Grows, How Does Hermes Know What Matters Now?

How does “don't change it yet” survive a long agent session? Follow Hermes through stored history, memory prefetch, usage anchors and compaction boundaries.

You ask an agent to investigate a production error. It reads logs, opens several files, and runs a series of tools. At last it narrows the problem down to a configuration setting. You add: “Don't change it yet. Check the blast radius first.” Then the agent appears to forget that instruction and carries on with its earlier plan.

We might call this a memory failure, but several different failures can look the same. The record may still exist while the model cannot see it in this request. Old tool output may have crowded a new instruction out of the useful window. A summary may preserve the error but turn “don't change it yet” into a completed or obsolete task. Or a memory provider may retrieve a similar incident that distracts from the current request.

Hermes Agent does not treat remembering as one switch. The stored conversation, long-term memory, the request sent to a model, and a handoff summary after compaction have different lifetimes. Getting one layer right cannot compensate for the wrong material reaching the model this turn. Follow a growing debugging session through those layers and the design becomes much more concrete.

A stored record is not the same as the current model request

Start with the session from the previous article. A message reaches a conversation, and the history can be stored and reloaded next turn. But a model has a context window. The system prompt, tool schemas, conversation messages, and tool responses all consume space in each request. “It is in the database” does not mean “the model sees the whole thing right now.”

Hermes does not rebuild every piece of context from disk on every request. Identity, tool guidance, project context, the skills index, and snapshots of MEMORY.md and USER.md are assembled into a system prompt. That prompt is kept stable over the conversation where possible, allowing the provider to reuse its cached prefix. Existing sessions can restore their stored prompt. Editing a memory file on disk may therefore not immediately change the system prompt of a live conversation. That is a lifecycle decision, not evidence that the file failed to save.

Long-term memory can arrive through another channel. An optional memory provider can prefetch material relevant to the current user turn. Rather than dumping the entire memory store into the system prompt, Hermes wraps a recall result in <memory-context> and attaches it to the model-visible content of this user message. It preserves the user's original text separately from the api_content actually sent to the model. When replayed later, the turn can retain the same sent content. New recall thus enters the current turn without silently rewriting the old system-prompt prefix.

A durable session, frozen system prompt, current-turn memory prefetch and model request are separate layers
Separate what is stored from what this request sends before diagnosing an apparent loss of memory.

There is a cost to this design. A recalled result attached to a user turn can also be replayed on later turns, adding to their size. Hermes bounds external-provider prefetch by a timeout; oversized output can be spilled to a file, leaving a preview and path in context. If a provider stalls, the current turn skips it and later turns do not repeatedly start new copies while that call is still running. Memory is not a free annex outside the context budget. It has both time and space costs.

The code paths fit together in system-prompt assembly, turn prefetch and api_content, and the memory manager. External providers are optional; their presence should not be assumed in every chat.

Ask what the model used before deciding to clean up

Return to the investigation. There is a long log, several file contents, and a run of tool responses. The conversation looks close to the model's limit. An easy approach would be to divide character count by a constant and compact immediately.

That estimate is useful, but the system prompt, tool schemas, images, and provider-specific payloads can distort it. Hermes prefers an anchor based on actual usage returned by the model. It records the prompt and completion tokens for that request and roughly estimates only the messages appended afterward. The anchor includes a fingerprint of the priced message boundary. A later turn can reload the history from storage and reuse the reading if that boundary still matches. Editing the boundary message invalidates it.

The anchor does not pretend to know everything. New tool results after it are still estimated. A matching boundary is not a cryptographic proof of the entire transcript. When there is no trustworthy anchor, some threshold gates wait for one real model response rather than compressing solely on an inflated first estimate. A provider-confirmed context overflow instead leads into bounded recovery. “Compacts at 50%” describes the ordinary default agent layer only roughly; the model window, absolute cap, configuration overrides, and provider route all matter.

The Gateway has a separate, higher pre-agent hygiene check, around 85% by default. It catches sessions that grow between turns; the in-loop ContextCompressor remains the normal manager. Their trigger timing and evidence differ. They do not add up to one simple threshold.

See the usage anchor, request assembly, and Gateway turn handling.

Circle what cannot be lost before clearing the desk

Once compaction is needed, Hermes does not simply send the entire chat to another model with “summarize this.” It first reduces stale tool results: duplicate outputs can be replaced, old long results become short stubs, large tool arguments shrink while remaining valid JSON, and older images may be retired. This can reclaim substantial space before a summary model is called.

Then it divides messages into three regions: a protected head, a summarizable middle, and a recent tail. The tail is budgeted from newest to oldest, with boundaries aligned so tool calls are not detached from their results. It normally tries to preserve the latest real user request and the latest visible agent reply; a setting can protect more real user turns. “Real” matters here. A blank platform echo or synthetic handoff must not impersonate a fresh user command.

In the default lean mode, the verbatim tail does not grow without bound. A smaller raw tail is paired with a more deliberate handoff: task progress, important identifiers, quoted real user turns, and a session_search recovery pointer. Material omitted from the active model window is not necessarily deleted from storage. With default in-place compaction, the session ID stays the same and older turns remain stored with compacted status so they can be searched or inspected.

Hermes first reduces stale tool output, protects a head and current request, summarizes the middle, and leaves searchable history
Compaction solves model-request pressure; evidence retention, recovery, and the user's current intent are separate concerns.

The summary carrier explicitly warns that earlier work is reference material, not a new user request. Otherwise the model might read “next, change the configuration” in an old handoff and treat it as more urgent than today's “don't change it yet.” A useful handoff says what happened; it should not issue a new instruction on the user's behalf.

This logic lives in the compressor. Compaction intentionally changes the cached context prefix. It is a carefully bounded exception to the stable-prompt rule, not a general mechanism for refreshing every piece of memory whenever a file changes.

What if the latest turn is itself too large?

The protection rule has a conflict hidden inside it. Imagine that after “don't change it yet,” the agent runs many tools within the same turn. Their combined output becomes enormous. If compaction insists on keeping that opening user request and every subsequent tool group verbatim, the context can remain too large. But cutting halfway through a turn risks losing the user's current objective.

The pinned implementation contains a constrained exception. A backward tail walk applies a soft ceiling to optional material; genuine user requests, recent replies, and atomic tool groups can still exceed it. Only if the anchored active region is truly above that ceiling, the opening request itself is reasonably sized, and real tool calls exist in the relevant span, can it keep a tool-group-aligned mid-turn cut and carry the opening request through the handoff. A configuration that promises to retain multiple real user messages still takes precedence over this exception.

The measurement of “truly above” is subtle. The code reuses the same tail-walk accounting. On routes that do not replay old reasoning text, only the newest assistant turn's reasoning is charged. A different, more pessimistic count could falsely label a fitting active turn as oversized and move the current request out of the verbatim tail. The September 19 official commit tightens precisely this decision.

That is the interesting architecture lesson: “keep recent content” becomes a set of checkable constraints. Compaction must actually free space while keeping the latest “don't change it yet” as close to its original wording and priority as the available window permits.

A failed summary is not a successful handoff

The auxiliary model generating a handoff can time out, reject the request, or return incomplete content. There is no universal “on failure, just continue” rule. Some terminal failures keep the original messages and abort; other allowed degradation can use a deterministic fallback handoff. Repeated failures have cooldown and anti-thrash guards to prevent burning time every turn. Manual compaction and provider-confirmed overflow have their own bounded retry behavior.

An external memory provider can also receive a pre-compression notification to extract useful material. That hook is best-effort by default. Only an explicitly enabled checkpoint_required setting, together with a provider that implements and successfully commits the checkpoint contract, makes durable archiving a precondition for a lossy rewrite. Installing a memory plugin by itself is no guarantee that each compaction reliably archived the original evidence. Nor can a summary model with insufficient capacity guarantee a good account of everything in the middle.

To check key branches, I executed 21 behavioral checks against pure functions extracted from the pinned source. They cover invalidating a usage anchor after editing its boundary, adding recalled memory only to the user turn's model-facing copy, and changing a tail cut based on whether older reasoning is replayed. The checks substitute a memory wrapper, budget estimator, and compressor host. They do not run a real model, SQLite commit, Gateway, or the complete compression procedure. Claims about commits, cooldown, and searchable history come from tracing source paths, not from presenting these local checks as end-to-end evidence.

Return to “don't change it yet; check the blast radius.” A good memory system first recognizes what that sentence is: a current user instruction, not recalled historical reference or a plan to replay from a summary. Session storage keeps evidence, long-term memory supplies leads, and usage accounting plus compaction decide how much the model can see now. Those boundaries determine whether an agent can still hear what you mean in the middle of a long task.

继续阅读