guide · computers

Hermes Memory Trim: A Proposal-Only Workflow

Operator playbook for the weekly memory trim: the proposal-only contract (never mutate), the 4 verdicts and 5 trim candidates, the diff-and-confirm UX, and the 3 verification checks.

July 15, 2026 · By Alastair Fraser

A retro robot assistant holding a clipboard titled TRIM PROPOSAL next to two large binders labeled MEMORY and USER, with a stack of marked-up memory cards and a glowing REQUIRES APPROVAL stamp pad on a workbench.

The nightly audit at 30 0 * * * writes a flag list each morning (Daily Memory Audit). The weekly trim is the second half: turning the flag list into a smaller, sharper MEMORY.md and USER.md. The defining property is that the proposal turn never mutates. Read, classify, show the diff, stop. The apply turn runs only after explicit human approval, entry by entry.

This playbook pairs with the memory-trim skill (version 1.3.0) and walks the 5 candidate categories and the 3 verification checks that gate the apply turn.

Why proposal-only

A trim that auto-deletes cannot be audited when it goes wrong. A “release preference: gate all GitHub pushes” entry drops silently; the next deploy pushes unredacted.

Two failure modes drove this design. First, the agent proposes to remove a load-bearing entry — it had not seen the failure mode the entry prevented. Second, an entry labeled “TRIM” turned out to be a +106 char addition. Skill 1.3.0 (logged 2026-07-14) added a per-entry len(replacement) < len(current) self-check. Both modes disappear when the proposal is full text, char-counted, human-approved.

The rule on one line: read live state, classify every entry, show the diff, stop.

The four verdicts

Every §-delimited entry is classified into exactly one of four verdicts. There is no fifth option. An unclassifiable entry remains KEEP until you can.

VerdictWhenWhat the proposal shows
KEEPNeeded verbatim next turn; the rule fails if removed.Exact replacement: unchanged. Delta: 0.
TRIMCore meaning survives; the rest is verbose or stale.Full proposed replacement and len(replacement) < len(current) self-check.
MOVEBelongs somewhere better — skill, topic file, fact_store, session handoff.Destination path that must exist before removal.
REMOVEOne-off, dated, or test scaffolding; safe to drop.Retrieval path showing where the original survives.

A TRIM that ends up longer than the original is not a TRIM — relabel as KEEP or rewrite. The 1.3.0 self-check catches this at proposal time instead of relying on the memory tool at apply time.

The proposal must cover every §-delimited entry. Never omit because an entry is unchanged, duplicated, long, or apparently unimportant. The user signs off on the absence of change as much as the presence of it.

The proposal turn — read order

The proposal turn is read-only and performs five actions in order:

  1. Load harness-self-maintenance and read references/memory-routing.md. Resolve active paths from $HERMES_HOME; defaults /opt/data/memories/MEMORY.md and /opt/data/memories/USER.md.
  2. Read both stores verbatim. No summaries, no yesterday’s snapshot. Only the live file is the contract.
  3. Segment into §-delimited entries. Two entries that look identical may differ on a path or SHA.
  4. Assign a verdict to each. Walk the 5 candidates below before settling on KEEP.
  5. Render the proposal. Stop. No memory tool calls. No file patches. Wait for explicit approval.

When classification is ambiguous, MiniMax M3 is consulted with structured output (verdict, tier, destination, reason, confidence). Low-confidence outcomes remain proposal-only.

The five trim candidates

Categories are not mutually exclusive — a 2026-07-12 14:03 debrief is both dated and test-scaffolding — but the strongest reason wins.

  1. Outdated facts. A rule, host, port, or path replaced by a newer one. The entry contradicts a current source of truth or duplicates a more recent log. Example: “Default branch is main.” — long since replaced by a rule in notes/git-workflow.md.
  2. Redundant duplicates. Two entries that say the same thing in different words, common after long sessions that pushed multiple times. The duplicate adds no retrieval surface. Example: “User prefers surgical execution.” vs “Release preference: gate noise; do only what was asked.”
  3. Procedural notes that became a skill. A multi-line procedure in memory that already exists as a SKILL.md. The skill is the canonical home and loads on demand. Example: “For Cloudflare alias redirects, use cloudflare-alias-domain-redirect skill.” — redundant when the skill exists.
  4. Dated events. Entries that name a specific date, hour, session, or commit SHA. They describe history, not durable state. The memory-hygiene litmus test catches this first: lines starting with yesterday, today I, or naming a SHA are not memory candidates. Example: “On 2026-07-08, migrated cron from host A to host B.” — git history is the source of truth.
  5. Test entries. Scratch entries written during skill validation, rollouts, or one-off experiments that were never durable; they reference a test ID, fixture, eval run, or sandbox path that has been torn down. Example: “Johnny5 test fixture: memories/TEST_MEMORY.md — delete after 2026-07-09.” — that date is past.

The diff-and-confirm UX

The output is the artifact the human signs off on. Each entry appears under a heading like MEMORY §3 or USER §2 and lists, in order:

  1. Current text (verbatim) — full reproduction. No summaries, no ..., no “unchanged.” Credentials become [REDACTED SECRET] and are flagged for immediate removal.
  2. Verdict — exactly one of the four.
  3. Why — what the entry preserves or loses.
  4. Destination — mandatory for MOVE; the exact skill, project file, topic file, session store, or todo path. Must exist before removal.
  5. Exact replacement — mandatory for TRIM, and for KEEP / REMOVE too (KEEP: Exact replacement: unchanged. REMOVE: Exact replacement: none.).
  6. Character delta — current → proposed chars for every entry, including KEEP and REMOVE.
  7. Risk and retrieval test — how the dropped detail survives elsewhere and can be re-read on demand.

The proposal closes with separate current / projected totals for MEMORY and USER against their own ceilings (2,200 / 1,375 on this profile), projected savings, and an entry-specific approval menu: apply MEMORY §2 and §3, keep USER §1, rewrite §4 as <text>, or reject all.

A proposal that contains only recommendations, omits current full text, collapses several entries together, or fails to provide exact TRIM replacements is incomplete and must not be delivered.

The apply turn — gated by three checks

The apply turn runs only after explicit approval in the form apply §N per entry — not a batch dump. Three checks gate the mutation, in order.

Check 1 — Size under ceiling

After every approved operation, re-read live state and confirm len(new) ≤ ceiling for each modified store. The projected total is a forecast; the apply turn reports actual characters. If approved TRIMs pushed a store over its ceiling, the apply turn halts and surfaces the new diff for a second approval round.

  • MEMORY.md ceiling: 2,200 chars.
  • USER.md ceiling: 1,375 chars.

Check 2 — No live fact dropped

Every approved REMOVE and MOVE is re-checked against the live source of truth before mutation. A postmortem path is verified, a destination file ls-checked, a git URL fetched for current HEAD. Anything that fails is reported and held — not applied.

Check 3 — Audit trail confirmed

Every applied change writes a governor event with hashes and proposal/approval identifiers. Hook loaded: immutable event log. Hook not loaded: metadata-only fallback event with hashes, never raw memory text. The trail is what makes the next proposal turn trustworthy.

Apply operations are atomic per target store. MEMORY.md and USER.md are separate batches when both change. A KEEP entry requires no operation. A partial failure halts the store batch and rolls back via the backup written at the start of the apply turn.

Failure modes to design against

Three failure modes have actually bitten this workflow: a TRIM longer than the original (caught by the 1.3.0 self-check at proposal time); a removal destination that does not exist (caught by Check 2 at apply time; an ls is cheaper than a failed WRITE); and a human approving an entry they did not understand (caught by the verbatim-full-text requirement — “unchanged” for a 600-character entry does not let them review what unchanged actually contains).

When not to trim

Skip the weekly trim when both stores are under half their ceiling (audit still flags, but turn cost outweighs the savings); when a migration is in flight and memory is the temporary store (trimming destroys the rollback surface); or when the no_agent cron fired in the last 24 hours with an identical flag list. Repetition is not progress. In each case, skip the turn rather than weaken the proposal contract. A skipped trim is recoverable; a weakened contract is not.

The short form

Every entry, every week, with full text reproduced. Four verdicts, no fifth. Apply turn only after apply §N. Three checks guard the mutation. Skill memory-trim 1.3.0; the audit that feeds this turn is the daily 0:30 UTC cron; the gate that catches “TRIM is actually a +106 addition” is the per-entry len(replacement) < len(current) self-check.

One line: propose, never mutate; verify, then apply.

Sources

#hermes-agent#memory#trim#proposal-only#governance#operator-rubric

Submit a take

Have a different read on this? Drop a comment below — your email isn't published, and I read every one. Nothing leaves the site until I approve it.

Your email address will not be published. Required fields are marked.