# Memory Storage Management

> Hermes 记忆治理：Use when 调用 memory() 工具写入/增删改记忆或讨论记忆价值判断（该不该存）决策——判据四问/85%阈值/溢出决策树/扩容判定/分流策略。

- Skill: `yyyyyhhhhh0639/memory-storage-management` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add yyyyyhhhhh0639/memory-storage-management`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yyyyyhhhhh0639/memory-storage-management/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: yyyyyhhhhh0639 (https://skillmd.com/u/yyyyyhhhhh0639)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/yyyyyhhhhh0639/memory-storage-management

---


# Hermes 记忆存储管理

> **核心原则：记忆空间的稀缺性是设计特征，不是缺陷——它强制信息蒸馏。扩容是最后手段，不是第一反应。**

### 🔗 本 skill 的关联文件

| 文件 | 内容 |
|:--|:--|
| `references/source-code-defaults.md` | memory 段默认值从源码 define 到生效路径的完整链路 |
| `references/context-engine-vs-memory-provider.md` | `context.engine` vs `memory.provider` 架构区分、Desktop UI 覆盖范围 |
| `references/authorization-mechanisms.md` | 授权边界三层机制详解（write_approval 强制 / smart_policy 提示 / 规则兜底） |

> scope-recall 运维细节（升级/补丁/journal recovery/embedder）已统一到 **`scope-recall-maintenance`** skill，不再在此重复维护。

### 快速入口

| 你的场景 | 直接跳转到 |
|:--|:--|
| memory() 写入被拒绝 | → 溢出处理决策树 |
| 考虑是否扩容 | → 扩容的判定标准 |
| 想知道扩容的代价 | → Token 税 |
| 为什么不能自动扩容 | → 自动扩容论证 |

---

## 双存储架构

Hermes 内置记忆分为**两个独立文件**：

| 存储 | 文件 | 默认上限 | 调用方式 |
|:--|:--:|:--:|:--|
| 用户档案 | `USER.md` | **1,375 字符**（~500 tokens） | `memory(target="user", ...)` |
| 个人笔记 | `MEMORY.md` | **2,200 字符**（~800 tokens） | `memory(target="memory", ...)` |

**来源：** 源码 `hermes_cli/config.py` 的 `DEFAULT_CONFIG` 中定义，`agent_init.py` 在 Agent 启动时读取，会话期间固定。

两个限额各自独立，互不占用。

---

## 适用边界（源码限定）

限额仅在一个入口生效：**`memory()` 工具 → `MemoryStore._char_limit()`**。
其他所有写入 USER.md / MEMORY.md 的路径都不受限额约束。

### 受限额约束的路径 ✅

| 调用者 | 入口 | 为何受限 |
|:--|:--|:--|
| **对话中的 LLM** | `agent_runtime_helpers.py:1958` → `memory_tool()` → `MemoryStore._char_limit()` | 走 MemoryStore |
| **Gateway / 工具调度器** | `tool_executor.py:1111` → 同上路径 | 同上 |
| **斜杠命令 `/memory`** | `cli_commands_mixin.py:1520` → `load_on_disk_store()` | 读取同一限额 |

### 不受限额约束的路径 ❌

| 调用者 | 写入方式 | 为何不受限 |
|:--|:--|:--|
| **自改进（curator）** | `write_file` / `patch` 直接写文件系统 | `curator.py:1893` 创建 review agent 时传 `skip_memory=True` → 无 MemoryStore → 无 `memory()` 工具 |
| **用户手动编辑** | 编辑器直接修改文件 | 文件系统无限额概念 |
| **其他 profile / 进程** | 任意文件写入方式 | 非本 MemoryStore 实例 |

### 对本 skill 的含义

- 本 skill 的**溢出处理决策树**、**85% 阈值**、**3 次溢出规则**只对走 `memory()` 工具的写入有效
- 自改进的写入行为不由本 skill 约束——它的溢出处理取决于自身的 LLM 推理，以及 `write_file` / `patch` 工具的固有特性（全量覆写、无原子批处理、无冲突检测）
- 这意味着：**USER.md 的实际大小可能超过限额**（如自改进写入时），但对话 LLM 通过 `memory()` 写入时仍然受限额约束

## USER.md 单条目结构陷阱（2026-08-04 发现）

USER.md 磁盘上**没有 `\n§\n` 条目分隔符**（唯一的 § 在标题 `## § 行为约束` 里，前后是空格）——按 `_parse_entries`（`raw.split("\n§\n")`）切分，**整个 USER.md 是 1 个条目**。MEMORY.md 是正常多条目（`\n§\n` 分隔），不受影响。含义：

| 操作 | 对 USER.md 的实际行为 | 危险 |
|------|----------------------|------|
| `replace(old_text, content)` | **整文件替换**（old_text 命中任意子串即可） | 中——整文件修改的唯一安全入口 |
| `remove(old_text)` | **删除整个文件** | 🔴 极高——误用即清空用户画像 |
| `add(content)` | 追加条目并**引入 § 分隔符**，文件从此变多条目 | 高——改变结构，后续 replace/remove 语义随之变化 |

**正确用法**：整文件修改用 `replace`（`old_text="# USER.md"` + 完整新全文），先 `.bak` 备份 + diff 审阅。不要对 USER.md 用 `add` / `remove`。

---

## memory() 溢出时的行为（源码事实）

当写入超过上限时，`memory()` 工具**不会静默失败或截断**：

1. 返回 `success: false`
2. 同时返回 `current_entries`（现有全部条目，可供查看和压缩重试）
3. 错误信息明确告诉你当前占用 / 上限 / 溢出量
4. 你可以**原地在同一轮中**执行压缩后重试

批量模式（`operations`）额外优势：同一批中先 remove 再 add，
即使中间步骤超出上限也没关系——只检查最终净结果。

---

## 什么适合放

**放** ✅：身份偏好、语言选择、兴趣领域、常用根路径、环境工具链
**锚定条目** ✅（用户裁决）：允许祈使句（"必须/禁止/一律"）——祈使句是触发保障的一部分，规范本体在技能/RULES；锚定 = 触发信号+动作+流程
**判据严格执行**（审计修订）：① 触发认知只是必要不充分条件——"反直觉教训必存"受"高频 ∨ 未第一时间加载→重大事故"约束；② 低频排障经验（一次事故、可 session_search/技能查证）**不常驻**，写入前先 grep 技能库确认承载位置，已承载则不写（或只留指针）；③ 排障叙事 → ops-lessons-archive；④ **混合条目逐段判定**：锚定条目可能混有机理/规范摘要段——逐段检查（锚定段=指向+触发信号，保留；机理摘要段已承载 → 删/指针），禁止用整体标签豁免局部冗余（实证：技能触发机制条目机理段冗余留存，整体标 anchor 被豁免）
**判别类不等式** ✅（必须带实例）：如"信息需求 ≠ 执行授权"、"索引可见 ≠ 规范已加载"——价值在决策时刻的分类边界，可靠性来自反复实例化；不能用"能否命令化"否定（2026-08-05，详见 rule-enforcement/references/l1-failure-patterns.md）
**不放** ❌：

| 内容 | 替代方案 |
|:--|:--|
| 构建步骤/命令行序列 | → 存为 **Skill**（无大小限制） |
| 临时 TODO / 进度 | → `session_search` 即可 |
| 项目详细配置 | → **Scope Recall** 的 `target="project"` |
| 代码片段 | → **Skill** 的 `references/` 目录 |

---

## 条目标签体系

MEMORY.md 条目使用 **4 个固定前缀标签**：`[base]` / `[env]` / `[ops]` / `[rule]`。标签是**注入层的索引前缀**（认知分组 + 治理维度），不是分类学——分析框架是五维 M1-M5，标签只是条目的注意力引导。

| 标签 | 语义 | 判定 | 负边界（不豁免什么） | 实证 |
|:--|:--|:--|:--|:--|
| `[env]` | Agent **运行环境**事实（M1） | 描述"Agent 在哪运行"：GPU/junction/venv/STT | ❌ 构建/排障环境（npm/electron → 技能）；❌ 低频环境事实（判据②③）；❌ 用户属性（→USER.md） | npm 条目删除 08-10 |
| `[ops]` | **高频**反直觉机理与操作注意（M2+M4） | 描述"如何工作/注意什么"且满足"高频 ∨ 未第一时间加载→重大事故" | ❌ 低频排障（→技能/archive）；❌ 技能已承载（→技能）；❌ 规范摘要（→压缩为指针） | M2 判据宽松 08-10；3 条冗余删除 |
| `[rule]` | 规则锚定：触发信号+动作+流程（**指向**技能/RULES） | 约束行为并指向规范本体位置 | ❌ 机理摘要/规范摘要（**逐段判定**，判据④）；❌ 无指向的规范复述（→AGENTS.md/RULES.md） | 技能触发机制机理冗余 08-10 |
| `[base]` | **元层**基础规则：规范执行与记忆治理的最高层锚定 | 规范执行失败时的第一动作（rule-enforcement 触发 / 记忆修改流程） | ❌ 领域规则（→[rule]）；❌ 流程细节（→技能本体）；❌ 自身同样受 L0/L1 归因约束 | 反思未加载 rule-enforcement 08-10 |

**系统设计五原则**：

1. **标签固定 4 个**——不随知识类别扩展；新增类别 → 技能/archive，不是新标签（防标签膨胀与豁免面扩大）
2. **标签与承载状态正交**——标签表示"条目性质"，不表示"是否已承载"（承载检查 = 判据①）
3. **标签与新鲜度正交**——标签不表示"是否过时"（过期风险 = 判据④）
4. **标签是注入层专有概念**——条目移出记忆（→技能/archive）时标签消亡
5. **混合条目取主导段**——一条可含多段，按判据④逐段判定；锚定段与机理段并存且机理冗余 → 删机理段、标签不变

**防豁免核心**：**标签决定"怎么读"（注意力分组），不决定"该不该存"（判据四问）——凡"贴签 = 合规"的用法都是豁免误用。** 防豁免靠三联动：判据④逐段判定（流程）+ memory-audit cron（审计）+ 用户 diff 审阅（审批），标签定义本身是必要非充分。

---

## 记忆价值判据（什么该留、什么该删）

### 写入前自检清单（操作化，按序执行）

任何 memory() 写入前按序执行 5 步，**任一步拦截即不写**（判据细节见下）：

```
□ 1. 承载检查：grep -ril "<核心关键词>" "$HERMES_HOME/skills" --include="SKILL.md"
     命中 → skill_view 确认已覆盖 → 不写（或只留指针），停止
□ 2. 具体操作：影响哪个高频操作？写不出具体操作 = 排障知识 → 技能/archive，停止
□ 3. 频率门槛：操作 ≥2 次/月？或未第一时间加载会致重大事故？否则 → 技能/archive，停止
□ 4. 后果评估：不知道它 → 犯错后果可恢复？可恢复 → 技能；不可逆 → 通过
□ 5. 四问终检：触发认知 × 提及频率 × 时机敏感 − 过期风险；历史状态不写
□ 6. 粒度检查：含多个独立语义单元？各自服务什么决策时刻？不同决策/频率/过期 → 拆开；
     单条 >200 字符 → 检查是否该分流而非合并
```

**粒度判定：决策时刻三问**（聚合单位 = **决策时刻**而非事件）：

| 问 | 判定 | 动作 |
|:--|:--|:--|
| ① 这些信息服务同一高频决策吗？ | 是 → 可合并；否 → 拆开 | 拆开标准 |
| ② 频率一致吗？ | 不一致 → 拆（高频留注入层，低频去技能） | 频率分层 |
| ③ 过期风险一致吗？ | 不一致 → 拆（各自更新，防耦合过时） | 生命周期分离 |

默认策略：**拆分优先**——宁可多一条独立条目（可合并），不可过度合并（耦合难拆）。

> 2026-08-03 会话教训固化（来源 `20260803_105311_eebf93`）：**"可一步推断" ≠ "冗余"**——Agent 是问题驱动（reactive），存在"未知的未知"（不知道要查 = 永远查不到）。判断一条环境事实是否值得占用记忆：

**价值 = 触发认知 × 提及频率 × 时机敏感度 − 过期风险**

**写入前自检（⓪ 内容级查重 + 四问）**：任何 memory() 写入前**先执行 ⓪**——对技能库做内容级 grep：`grep -ril "<关键词>" "$HERMES_HOME/skills" --include="SKILL.md"`，命中即 `skill_view` 确认是否已覆盖；已覆盖 → 不写入，改 patch 技能。**名称/描述比对会漏**"知识在内容里但描述不含关键词"的存量技能（2026-08-05 stash 教训；规范出处 hermes-agent-skill-authoring §258）。再输出四维对照执行——

| 问 | 判定 | 不过关的处置 |
|----|------|-------------|
| ① 触发认知 | 不知道要查会不会永远查不到？ | 否 → 可查证知识，压缩留引用 |
| ② 提及频率 | 用户常问/常用吗？ | 低频 → 考虑删 |
| ③ 时机敏感 | 提问瞬间需要秒答？ | 否 → 可放文档 |
| ④ 过期风险 | 环境会变吗？ | 会 → 带日期；变更时更新而非删除 |

四问过完才写；历史状态（已修复的过去式）一律不写。
符合高频使用、未在第一时间加载就会导致重大事故等任意一项条件的信息都必须加入MEMORY.md。经常未在预期时机加载的技能也可通过记忆来锚定。

| 判据 | 核心问题 | 处置 |
|:--|:--|:--|
| 触发认知 | Agent 不知道要查就永远不会查？（junction 映射 / pwsh 存在 / 工具链事实） | 缺失 → 必须存 |
| 提及频率 | 用户是否常问？（GPU 兼容性、环境版本） | 高频存；低频可删 |
| 时机敏感度 | 用户提问瞬间需要秒答？ | 是 → 存（消除查询延迟） |
| 过期风险 | 环境变更会失效？ | **MEMORY.md：用时效语义表述（如"IP 为 DHCP 动态分配"），不带日期戳**；存档/日志类文档（ops-lessons-archive、provenance、备份命名）**带日期**。变更时更新而非删除 |

**四类处置：**

| 类别 | 特征 | 处置 |
|:--|:--|:--|
| 反直觉教训 | 环境推不出，踩坑才知道 | 必存（含触发信号四要素） |
| 环境事实+触发认知 | 可本地查询但 Agent 不会主动查 | 存（带日期） |
| 可查证知识 | 文档/help 可得 | 压缩留引用（"详见存档"） |
| 历史状态 | 已修复的过去式 | 删 |

**实证（2026-08-03）**：无 pwsh 记忆时 Agent 默认选 `powershell.exe` 5.1（"不会主动查询"活证据）；junction 条目防止"同一物理目录当两份"的错误推断。

---

## 分流正确出口（合并不是容量问题的解，分流才是）

**命题**：容量压力下"合并条目"是错误方向——省字符的代价是维护性（replace 难定位）、可读性（长条难秒读）、耦合性（频率/过期绑死）；真正的解是**分流**（不占注入层）。

**与既有"压缩合并"的关系（术语区分）**：
- "合并"的两义：① **精简合并**（同归属层内去重/压缩——正面，既有"方案一：压缩合并"继续有效且优先）② **信息合并**（跨归属层把应分流内容塞进 MEMORY——负面，本命题反对的）
- **操作序列：先分流（归属决策）→ 后精简（表述压缩）**——互补非冲突
- "合并不是容量问题的解"仅指 ②；① 仍然有效且优先（压缩冗余零成本）

| 内容 | 出口 | 容量 |
|:--|:--|:--|
| 事件完整叙事 | ops-lessons-archive（"MEMORY 压缩的叙事完整版"） | 不占注入层 |
| 过程知识/步骤 | 技能（无大小限制） | 不占注入层 |
| 项目细节/笔记 | scope-recall（独立存储池） | 不占注入层 |
| 临时进度 | session_search | 不占注入层 |
| 高频决策事实 | MEMORY.md 独立条目 | 唯一该占注入层的 |

**"全塞一条" = 不知道往哪分流的产物**——分流四出口先行，注入层只留高频决策事实。

---

## 不够用时怎么办（优先级排序）

### 方案一：压缩合并（零成本，优先级最高）

```python
memory(target="user", operations=[
    {"action": "remove", "old_text": "第一条旧内容的唯一识别子串"},
    {"action": "remove", "old_text": "第二条旧内容的唯一识别子串"},
    {"action": "add", "content": "合并版：偏好A／项目B／工具C"}
])
```

> ⚠️ `operations` 数组中的多个操作是**原子提交**的——要么全成功，要么全不生效。不是三次独立调用。

典型效果：8 条（510 字符）→ 3 条（~350 字符），省 30%。

### 方案二：存为 Skill（过程性知识）

过程性知识 → `skill_manage(action="create", name="xxx", category="xxx", content="...")`，记忆只记引用名。

### 方案三：Scope Recall 分流

`scope_recall_store(target="memory"|"project"|"ops")` — 独立存储池，**不占用记忆字符**。需 scope-recall 插件已安装且启用（配置 `memory.provider: scope-recall` + `plugins.enabled: [scope-recall]`）。

> ⚠️ `scope_recall_profile()` 只能查看 scope-recall 插件中的记忆，**不能** 查看内置记忆内容。内置记忆的内容通过 `memory()` 工具直接管理。

### 方案四：定期清理（每 2~4 周）

```python
memory(target="user", operations=[
    {"action": "remove", "old_text": "过期项目配置"},
])
```

---

## 溢出处理决策树

当 `memory()` 写入触发上限拒绝时，按此流程处理：

```
          memory() 操作溢出
               │
               ▼
        ┌──────────────┐
        │ 执行压缩     │
        │（去重/合并）  │
        └──────┬───────┘
               │
               ▼
        ┌──────────────────────────────┐
        │ 压缩后占用 < 上限×85%？      │
        └──────────┬───────────────────┘
                   │
      ┌────────────┼────────────┐
      ▼ 是          ▼ 否
  问题在冗余，      ┌────────────────┐
  压缩解决         │ 这是连续第几次？ │
  写入成功         └───────┬────────┘
                          │
               ┌──────────┼──────────┐
               ▼ 第1~2次   ▼ 第3+次
           注意观察       ┌────────────────────┐
                          │ 扩容信号触发       │
                          │ → 走扩容判定标准   │
                          └────────────────────┘
```

**85% 阈值的来源：** 压缩后如果仍 >85%，再写入新信息时几乎必然再次溢出——这本身就是"空间不够"的指标。以 1,375 为例，85% = ~1,169 字符。

---

## 压缩损失的两种类型

| 类型 | 定义 | 举例 | 是否可接受 |
|:--|:--|:--|:--:|
| **冗余损失** | 删除重复、过时的信息（⚠️ "可推导"需先过"记忆价值判据"——本地可查询 ≠ 应删除，见下文） | "User is Chinese" + "用户是中文使用者" → 保留一条 | ✅ 可接受，压缩的目标 |
| **实质损失** | 删除不可还原的独立信息单元 | 删掉一个独立的偏好判断或事实记录 | ❌ 不可接受，应触发扩容评估 |

### 判断当前属于哪种损失

```
压缩省下的空间 > 30%  → 损失的主要是冗余（可接受）
压缩省下的空间 < 10%  → 接近实质损失边界（应评估扩容）
连续3次压缩收益递减   → 扩容信号
```

---

## 扩容的判定标准

### 触发扩容评估的条件（三个条件同时满足才触发）

1. **压缩收益持续递减：** 第一次压缩省 80%，第二次省 30%，第三次省 5% —— 冗余已基本榨干
2. **压缩后仍接近上限：** 占用持续 >85%（1,169/1,375）
3. **模式重复 3 次以上：** 不是偶发事件

**反例（不应扩容）：** 一次溢出后，一次压缩就省了 90% 空间（如 1,513→130）——问题在表达方式，不在容量。

### 建议值的误区

此 skill 不提供通用的"场景→建议值"映射表，原因是：

- 你的实际占用取决于信息密度，不是语言数量或领域数量
- 双语言 + 多领域完全可能在 1,375 内（实际案例：中英文共 784 字符，仅占 57%）
- 错误建议值会导致"看表→直接扩容→跳过压缩"的非必要扩容

**唯一可靠的扩容判定方式：走上面的溢出处理决策树。**

---

## 扩容的操作方法

### 扩容（提高上限）

```yaml
# config.yaml memory 段
memory:
  user_char_limit: 1375     # USER.md 上限
  memory_char_limit: 2200   # MEMORY.md 上限
```

```bash
hermes config set memory.user_char_limit 2000
# 或
hermes config set memory.memory_char_limit 5000
```

**⚠️ 生效时机：** 修改后需 `/reset` 或重启 Hermes。
**原因：** 限额在 Agent 启动时由 `agent_init.py` 一次性读入 `MemoryStore`，会话期间固定。`/reset` 新建 Agent 实例，重新读取配置。
**注意：** 修改配置不会删除超过新限额的已有内容——已有条目不受影响。

### 缩容（改回较低上限）

如果想改回默认或更低的值：

```bash
hermes config set memory.user_char_limit 1375  # 改回默认
# /reset 生效
```

**⚠️ 超过新上限的内容不会被自动截断。** 先手动压缩到低于新上限，然后改配置——或者先改配置再清理，系统都不会自动删除。

### 验证方法

```bash
hermes config show  # 查看 memory. 段的当前值
```

---

## Token 税：扩容的真实成本

1,375 字符 ≈ 500 tokens（以 2.75 chars/token 估算），这个值写入**每轮对话的系统提示词中**。扩容的代价是**每轮 × 每一字节**：

| 上限 | 每轮 tokens | 50 轮对话 | 100 轮对话 |
|:--|:--:|:--:|:--:|
| 1,375（当前） | ~500 | 25,000 | 50,000 |
| 2,000 | ~730 | 36,500 | 73,000 |
| 3,000 | ~1,090 | 54,500 | 109,000 |

从 1,375 扩到 2,000，50 轮多消耗 **11,500 tokens**——足够模型执行 5~10 次工具调用，或多回答 3~5 个复杂问题。

**结论：扩容不是零成本——每扩一个字符，都在用未来的上下文窗口买单。**

---

## 为什么不设计自动扩容（双向棘轮论证）

### 提议的方案

```
触及上限 → 压缩 → 自动扩容到 max(实际占用, 旧上限)
         ↑ 每次 / 固定N次 / 动态
```

### 反对论据

| 论据 | 解释 |
|:--|:--|
| **单向棘轮** | 扩了就不缩回。即使后期内容已精简，限额不会降。单调递增，最终收敛到无限制 |
| **Token 税不可逆** | 每轮对话持续消耗额外 token，但扩容的触发可能是一次性的写作啰嗦 |
| **激励扭曲** | 啰嗦写作能获得"奖励"（更多空间），精确简洁反而让限额不变——与系统设计目标相反 |
| **模型差异** | 不同 LLM 啰嗦度不同，限额会反映写入者的写作风格而非用户的真实需求 |

### 手动扩容的优势

| 维度 | 手动 | 自动 |
|:--|:--:|:--:|
| 决策者有意识 | ✅ 你知道为什么扩 | ❌ 你不知道触发过 |
| 可逆 | ✅ 随时能改回来 | ❌ 只增不减 |
| 边界明确 | ✅ 2000 就是 2000 | ❌ 边界模糊 |
| 适配不同场景 | ✅ 工作/个人不同配置 | ❌ 一刀切 |

---

## 推荐策略

```text
记忆（memory）     → 高频稳定的偏好/事实
技能（Skill）      → 过程性知识（步骤/配置/工作流）
Scope Recall      → 笔记/日志/项目细节（不占记忆限额）
会话搜索          → 临时 TODO / 进度

条目长度：
  理想值：50~100 字符（方便单条替换和精确匹配）
  弹性：如果信息是关联偏好的连贯描述，
       单条 500~700 字符也是合理的——
       优先保证语义完整性，而非机械地切成碎片。
  判断标准：能拆成独立语义单元 → 拆开；
           拆开会丢失上下文关联 → 合并为一条

扩容优先顺序：
① 压缩合并（90% 的情况在此解决）
② 分流到 Skill / Scope Recall
③ 手动扩容（仅当压缩收益趋零 + 连续 3 次溢出后）

扩容走 hermes config set + /reset
```

---

## 记忆文件修改流程规范

修改 MEMORY.md / USER.md 必须按序执行：

1. **备份**：`cp <file> <file>.bak.$(date +%s)`（修改前必做）
2. **通道**：通过 `memory()` 工具写入（有 write_approval/限额/漂移检测），禁用 write_file/patch 直接写
3. **审阅**：先给出完整 diff，等用户明确批准
4. **执行**：批准后执行；变更后验证字符数 vs 限额

系统级保障（config.yaml）：
- `memory.write_approval: true` → memory() 写入先 staged，`/memory approve` 才提交
- `approvals.smart_policy` → 模型侧显式提示（"修改 memories/ 前必须备份+diff+批准"）

常见错误（反例）：
- 用 write_file 直接覆盖记忆文件（绕过备份/限额/审批）
- 把"同意方案方向"当作"授权跳过审阅"
- 修改前不备份

### 版本管理

数据版本仓库位于 **`F:\AI\Hermes\hermes-state-git\.git`**（hermes 目录内**无 .git**——避免 `_is_repo_junk` 把会话 git 探测拉回 Home；`core.worktree=F:/AI/Hermes/hermes`）。无 remote，禁止 push。跟踪 `memories/` + `skills/` + 身份文档 + `config.yaml`。`.gitignore` 排除敏感文件（auth.json / .env / *.db / *.bak.*）与框架数据目录。

**批量 commit 约定**（每次知识落盘/治理批次结束时，在 hermes 目录下执行）：

```bash
cd F:/AI/Hermes/hermes
git --git-dir=../hermes-state-git/.git --work-tree=. add memories/ skills/
git --git-dir=../hermes-state-git/.git --work-tree=. commit -m "desc (source session <会话ID>)"
```

**陷阱**：hermes 目录内直接 `git add` 报 `not a git repository`；`git -C ../hermes-state-git add memories/` **exit 0 但静默不生效**（相对路径解析到仓库目录而非工作树）——前面成功 ≠ 安全。

**禁止**：`git remote add` / `git push`（本地私有是安全边界，push 即泄露面）；`git add -A`（纳入范围外文件）。

### git 管理全景

**受管范围（跟踪）**：`memories/`（MEMORY.md/USER.md/USER - log.md）、`skills/`、`config.yaml`、`scripts/`、`patches/`、身份文档（SOUL.md/RULES.md/README.md/CAPABILITIES.md/CONSTITUTION.md）、`.gitignore`。
**排除**：`*.bak` / `*.bak.*`（watchdog 曾误纳 config.yaml.bak——白名单必须路径段级匹配）、`auth.json`、`.env`、`*.db`、`hermes-agent/`（源码）、运行产物（gateway.pid/hermes-setup.exe 等）。

**机制特征（事实）**：无 git hooks、无 watcher、无自动提交——git 只记录"执行 add+commit 的时点"，**用户/任何进程手动编辑文件不触发任何 git 记录**（变更静默悬空直到批次 commit 碰巧带上）。"git 已记录时间线"指 commit 时点，非编辑时点。

**看门狗**：`~/AppData/Local/hermes/scripts/git-watchdog.py` + cron `git-state-watchdog`（每日 23:55，no_agent 零 token）。白名单路径段级匹配 + 敏感路径拒绝 + 干净静默；未提交变更自动快照 commit。**手工批次 commit 仍是首选**（有意义的 message），看门狗是兜底（防悬空丢失），不是替代。

**执行纪律（防 L1 遗忘）**：
1. 任何受管文件变更（含 config.yaml 配置、scripts/ 补丁）**变更完成即 commit**，不等批次
2. 手动编辑记忆/技能后，下次会话主动检查 `git status` 并补 commit
3. 看门狗日志：`cronjob(action='list')` 查 `git-state-watchdog` last_status

**风险备忘**：A2A 配置（gateway.platforms.a2a）、fvm repo_scan 排除、0.20.0 升级路径曾悬空 21h+ 未提交（2026-08-05 发现）——配置变更是最易漏 commit 的类型。

**与既有流程的关系**：步骤①的 `.bak` 快照**仍保留**——git 历史提供"逐次 diff 追溯 + 精确回滚"，`.bak` 提供"修改前瞬间快照"，两者互补；`hermes backup` 全量与云备份（gpg+rclone 123pan，**已含 hermes-state-git**）为第三、四层。保护链：① .bak 快照 → ② git 历史 → ③ hermes backup → ④ 云备份。

### 备份保留策略

`.bak` 快照**无上限累积**——实测 MEMORY.md.bak 55 份、单技能 SKILL.md.bak 10 份（每次写前备份从不清理）。策略：

- **保留量**：每个文件保留最近 **2 份**（MEMORY.md / USER.md / SKILL.md 同理）——第 1 份回滚当前修改，第 2 份兜底上一版本；更早版本由 git 历史（hermes-state-git）与 `hermes backup` 全量兜底，`.bak` 无需多存
- **清理时机**：每次写前备份时顺手清（超过 2 份即删最旧）；或每月治理批次（与 hermes-state-git 批量 commit 同批）
- **清理方式**：memories/ 与 skills/ 是**受保护路径**——裸 `rm` 会被 block-hermes-assets hook 拦截；一律用 safe-rm（转 `F:/AI/Hermes/trash/`，可恢复，cron `trash-cleanup` 30 天自动清）：
  ```bash
  PY="$HERMES_HOME/hermes-agent/venv/Scripts/python.exe"
  SRM="$HERMES_HOME/scripts/safe-rm.py"
  "$PY" "$SRM" <memories|skills>/.../FILE.bak.<ts>   # 每文件保留最新 2 份，其余删除
  ```
- **实证**：一次清理 55 份 MEMORY.md.bak + 29 份 SKILL.md.bak（memory-storage-management 独占 10 份、rule-enforcement 3 份）→ 释放 ~800KB；备份机制本身的累积不产生价值，只产生索引噪音

---

## Scope-Recall 运维

> **编排式运维** — scope-recall 的完整运维手册已统一到 **`scope-recall-maintenance`** skill（安装/升级/补丁/journal recovery/embedder 配置/doctor 解读）。此处仅保留与记忆治理相关的要点，避免双份维护。

### 与记忆治理相关要点

1. **scope_recall 与内置记忆分工**：`scope_recall_store(target="memory"|"project"|"ops")` 是独立存储池，**不占用记忆字符**；`scope_recall_profile()` 只能看 scope-recall 记忆，不能看内置 USER.md/MEMORY.md
2. **升级/维护操作** → 加载 `scope-recall-maintenance` skill 执行（含 1.8.7 升级前置条件、5 个补丁、doctor 全绿红线）
3. **journal 积压消化** → 见 `scope-recall-maintenance` 的 `references/journal-recovery.md`（heuristic 快速消化法）
4. **UA 修复** → `scope-recall-maintenance` 的 `nightly-llm-ua.patch`（浏览器 UA，非自定义字符串）

### Schema 迁移

- 自动时机：provider 初始化（Hermes 完整重启）
- 验证：doctor 输出 `sqlite.schema_migrations.current: true`
- 注意：`/new` / `/reset` 不足，需完整重启 Hermes

