hermes-context-memory-reference.md 28 KB

Hermes-Agent 上下文压缩与记忆机制参考

项目位置:backend/hermes-agent/(第三方仓库 NousResearch/hermes-agent 的本地克隆,已通过父项目 .gitignore 排除) 文档目的:记录 Hermes-Agent 内置的上下文压缩(context compression)与记忆(memory)两大子系统的实现机制、配置项、与 Bridge 模式的实际关系,便于后续评估适配成本、规划启用方案。 评估时点:2026-08-11(HEAD f4604f89


0. 一句话定位

Hermes-Agent 的上下文管理由两条独立子系统组成:

  • 上下文压缩——会话内消息历史的 token 治理。当 prompt token 超过阈值时,启动一个 5 阶段流水线(裁剪旧工具结果 → 保护头尾 → LLM 总结中段),把摘要写回 messages 列表替换原中段。
  • 记忆机制——跨会话的持久化。包括四层:会话内 messages、跨会话 Markdown 文件(MEMORY.md / USER.md)、Skills 库、外部 Memory Provider 插件(Honcho / Mem0 / RetainDB 等)。

与 Bridge 模式的关键事实:本项目通过 backend/hermes-bridge/hermes_bridge.py:228 显式传入 skip_memory=True完全禁用了所有跨会话记忆机制;上下文压缩则保持默认启用状态。


第一部分:上下文压缩机制

1.1 触发条件

判断入口ContextCompressor.should_compress_info()

agent/context_compressor.py:2554-2585

def should_compress_info(self, prompt_tokens: int = None) -> "tuple[bool, str | None]":
    tokens = prompt_tokens if prompt_tokens is not None else self.last_prompt_tokens
    if tokens < self.threshold_tokens:
        return False, None
    if self._automatic_compression_blocked():
        return False, self._compression_block_reason() or "blocked"
    return True, None

触发依据:基于 token 阈值,不是消息条数,不是 LLM 自主决定。

  • 比较对象:prompt_tokensthreshold_tokens
  • prompt_tokens 两个来源:
    • 粗估(请求前):estimate_messages_tokens_rough(messages),字符数 / 4
    • 精确(响应后):API 返回的 usage.prompt_tokens 写入 last_prompt_tokensconversation_loop.py:3272

1.2 阈值与默认值

参数 默认值 含义 源码位置
threshold_percent 0.50 触发比例(占上下文窗口) context_compressor.py:2208
_MIN_CTX_TRIGGER_RATIO 0.85 触发比例硬上限 context_compressor.py:2090
_SMALL_CTX_THRESHOLD_PERCENT 0.75 小上下文地板(窗口 < 512K 时强制 ≥75%) context_compressor.py:664
_SMALL_CTX_CONTEXT_FLOOR 512K "小上下文"的判定门槛 context_compressor.py
threshold_tokens_cap None 绝对 token 上限(设置后取 min) agent_init.py:1985-1992

threshold_tokens 计算公式context_length × threshold_percent - max_tokens(保证不超过 85% 窗口,并预留回答空间),见 context_compressor.py:2166-2204

1.3 Anti-thrash 阻断(防止反复无效压缩)

agent/context_compressor.py:2647-2726

def _automatic_compression_blocked_locally(self) -> bool:
    # 1. Summary LLM 失败冷却期内:不触发
    if self._summary_failure_cooldown_until - time.monotonic() > 0:
        return True
    # 2. 连续 2 次压缩无效(节省 <10%)或连续 2 次使用 fallback:不触发
    if (self._ineffective_compression_count >= 2
            or self._fallback_compression_streak >= 2):
        # 300 秒后允许一次"试探性"压缩,失败则再次阻断
        return True
    return False

阻断时通过 agent._warn_context_overflow_blocked(...) 向用户告警(conversation_loop.py:2077-2087)。

1.4 压缩流水线(5 阶段)

agent/context_compressor.py:5942-5977(算法注释)

Algorithm:
  1. Prune old tool results (cheap pre-pass, no LLM call)
  2. Protect head messages (system prompt + first exchange)
  3. Find tail boundary by token budget (~20K tokens of recent context)
  4. Summarize middle turns with structured LLM prompt
  5. On re-compression, iteratively update the previous summary

阶段 1:旧工具结果裁剪(无 LLM 调用)

context_compressor.py:2732-3005,四遍扫描:

  • Pass 1:相同内容去重,旧副本替换为 "[Duplicate tool output — same content as a more recent call]"context_compressor.py:2819-2843
  • Pass 2:超过 min_prune_chars(默认 200)的旧 tool result 替换为一行摘要,如 [terminal] ran \npm test` -> exit 0, 47 lines output_summarize_tool_resultcontext_compressor.py:1128-1187`)
  • Pass 3:截断超过 500 字符的 tool_call.arguments JSON(context_compressor.py:2906-2924
  • Pass 4:尾部仍超 protect_tail_tokens × 1.5 时,对尾部内的 bulky tool 输出降级(context_compressor.py:2941-3005

阶段 2/3:边界确定

context_compressor.py:6054-6059

compress_start = self._protect_head_size(messages)         # 头部边界
compress_start = self._align_boundary_forward(messages, compress_start)
compress_end = self._find_tail_cut_by_tokens(messages, compress_start)  # 尾部边界
  • 头部_protect_head_sizecontext_compressor.py:4682-4705)= system prompt(若有)+ protect_first_n 条非 system 消息
  • 尾部_find_tail_cut_by_tokenscontext_compressor.py:5026-5100)从末尾倒推累计 token,直至达到 tail_token_budget,消息数下限 min(protect_last_n, _MAX_TAIL_MESSAGE_FLOOR=8)

阶段 4:中段 LLM 总结

context_compressor.py:3469-34983765-3879

  • 通过 auxiliary_client.call_llmcontext_compressor.py:3813)调用独立"摘要模型"(可配置 auxiliary.compression.provider/model
  • 结构化模板Goal / Active Task / In Progress / Decisions / Files / Completed Actions / Pending / Remaining Work 等字段(_summarizer_preamblecontext_compressor.py:3637-3648
  • 迭代更新:已存在 _previous_summary 时改用增量更新提示词(context_compressor.py:3479-3480
  • 输入上限_SUMMARY_INPUT_MAX_CHARS = 160_000(约 40K token),超出则头尾保留、中段省略(context_compressor.py:395
  • 输出无 max_tokens 上限:wire 上显式不加 max_tokenscontext_compressor.py:3776-3785),max_summary_tokens 仅用于内部预算(上限 _SUMMARY_TOKENS_CEILING = 10_000
  • 剥离 think 块:摘要 LLM 输出会经过 strip_think_blocks,避免思考模型(MiniMax / DeepSeek / QwQ)的 <think>...</think> 污染摘要(context_compressor.py:3854-3863

1.5 被压缩的内容

消息范围 处理方式
system prompt 永不压缩(始终在头部保护)
头部 protect_first_n(默认 3) 永不压缩;首次压缩后衰减为 0,避免早期消息"化石"(context_compressor.py:4693-4700
尾部 protect_last_n(默认 20,实际由 token 预算主导,下限 8) 永不压缩
中段所有消息(user / assistant / tool) 进入 LLM 总结输入,最终被摘要消息替换

工具调用结果是压缩的重点对象,三层处理:

  1. 预压缩裁剪(阶段 1)
  2. LLM 总结输入:_serialize_for_summary 把 tool 消息序列化为文本
  3. 图片剥离:_strip_historical_mediacontext_compressor.py:6686-6692)把最新图片之前的所有图片 part 替换为文本占位符

1.6 压缩结果如何使用

完全替换原消息,不附加。conversation_compression.py:2782 在调用前用 copy.deepcopy 保存原消息:

messages_before_compression = copy.deepcopy(messages)
...
compressed = compress_fn(messages, **compress_kwargs)

中段 N 条 → 1 条(或合并入尾部首条)摘要消息。

摘要消息的格式

context_compressor.py:6564-6614

角色由交替规则动态决定(user / assistant),保证 OpenAI 兼容后端不会因角色连续而 400。强制规则:若 session 中没有任何真实 user 消息,摘要必须用 role="user"

消息额外带进程内元数据(不上 wire):

{
    "role": summary_role,
    "content": summary,
    COMPRESSED_SUMMARY_METADATA_KEY: True,                          # "_compressed_summary"
    COMPRESSED_SUMMARY_HAS_USER_TURN_KEY: bool(...),
}

摘要文本末尾强制追加:

--- END OF CONTEXT SUMMARY — respond to the message below, not the summary above ---

system prompt 也会被追加一段说明:[Note: Some earlier conversation turns have been compacted into a handoff summary...]context_compressor.py:6419)。

1.7 原始消息的保留 / 回滚

阶段 保留情况
压缩过程中 messages_before_compression 用于异常回滚(conversation_compression.py:2858-2905
提交后 in_place=true(默认) 同一 session_id,直接覆盖,原始中段消息从内存与 DB 中消失
提交后 in_place=false 创建子 session,父 session 保留原始消息作为审计轨迹

1.8 失败降级(4 层)

层级 触发场景 行为 源码
1. 模型回退 aux 模型未找到(404/503)/ 超时 / JSON 解析失败 / 流式中断 切到主模型重试 context_compressor.py:3965-3984
2. 冷却期 任意 LLM 失败 600 秒(无 provider 配置)或 60→300→900 秒阶梯(超时),期间 should_compress 返回 False context_compressor.py:627、1935-1951、4025-4028
3. 静态 fallback LLM 总结失败但未 abort 从被丢弃的中段本地提取连续性锚点(用户问句、assistant 动作、工具名、文件路径、错误文本),组装成结构化文本(上限 8000 字符) context_compressor.py:6429-6454、3203-3332
4. 整体 abort 鉴权失败(401/403)/ 网络断连 / abort_on_summary_failure=true 保留原消息不变,向用户告警 context_compressor.py:6354-6402、conversation_compression.py:2947-2956

1.9 与"思考过程"的关系

压缩过程不产生 thinking_callback 输出

证据:在 conversation_compression.py 中 grep thinking|on_reasoning|stream_callback|reasoning_callback 无任何匹配。压缩的状态通知走独立通道 agent._emit_status(用于 UI "压缩中"提示),与 thinking_callback 是两个不同的回调。

前端能看到:

  • 压缩开始 / 完成的状态通知(走 status_callback → SSE)
  • 压缩后插入对话的摘要消息本身(带 _compressed_summary 元数据,前端可据此特殊渲染)

前端看不到:

  • 压缩 LLM 的思考过程(<think> 块已被剥离)

第二部分:记忆机制

2.1 四层记忆结构

层级 类型 存储 生命周期
1 短期(会话内) agent.messages 内存 + SQLite state.db 会话内累加;超限时由压缩摘要后切到新 session_id
2 中期(跨会话内置) $HERMES_HOME/memories/MEMORY.mdUSER.md(Markdown) 跨会话持久,写入即时落盘
3 长期(外部 provider 插件) Honcho / Mem0 / RetainDB / Supermemory 等后端 由插件决定,按 user_id / chat_id 隔离
4 技能库(Skills) $HERMES_HOME/skills/<name>/(每个技能一个目录) 跨会话持久,由后台 review 沉淀

2.2 短期记忆(会话 DB)

  • 存储位置:内存中的 agent.messages 列表 + 磁盘 SQLite SessionDB
  • 默认路径hermes_state.py:239

    DEFAULT_DB_PATH = get_hermes_home() / "state.db"
    
  • 生命周期:会话内累加;上下文超限时由 conversation_compression.py 摘要后切到新 session_id(rotate)

2.3 中期记忆(MEMORY.md / USER.md)

tools/memory_tool.py:1-24(模块 docstring)

Provides bounded, file-backed memory that persists across sessions. Two stores:
  - MEMORY.md: agent's personal notes and observations (environment facts, project
    conventions, tool quirks, things learned)
  - USER.md: what the agent knows about the user (preferences, communication style,
    expectations, workflow habits)

Both are injected into the system prompt as a frozen snapshot at session start.
Mid-session writes update files on disk immediately (durable) but do NOT change
the system prompt -- this preserves the prefix cache for the entire session.
The snapshot refreshes on the next session start.

关键设计

  • 会话开始时把 MEMORY/USER 快照注入 system prompt
  • 会话内的写入立即落盘,但不更新 system prompt——保 prefix cache
  • 下一会话开始时刷新快照

字符上限(默认值):

tools/memory_tool.py:165-169

def __init__(self, memory_char_limit: int = 2200, user_char_limit: int = 1375):
  • MEMORY: 2200 字符(≈800 token)
  • USER: 1375 字符(≈500 token)

存储路径

tools/memory_tool.py:53-67

def get_memory_dir() -> Path:
    """Return the profile-scoped memories directory."""
    return get_hermes_home() / "memories"

MEMORY_BLOCK_HEADERS = {
    "memory": "MEMORY (your personal notes)",
    "user":   "USER PROFILE (who the user is)",
}

ENTRY_DELIMITER = "\n§\n"

写入用原子 temp-file + renametools/memory_tool.py:863-876),并用 fcntl / msvcrt 做跨进程文件锁(_file_locktools/memory_tool.py:278-320)。

2.4 长期记忆(外部 Memory Provider 插件)

agent/memory_provider.py:1-31

"""Abstract base class for pluggable memory providers.

Memory providers give the agent persistent recall across sessions.
The MemoryManager enforces a one-external-provider limit to prevent
tool schema bloat and conflicting memory backends.
"""

只允许一个外部 provider 同时运行。已内置的插件位于 plugins/memory/

插件 类型
honcho/ 云端用户建模
hindsight/
mem0/
retaindb/ SQLite write-behind 队列 + 语义检索
supermemory/
holographic/byterover/openviking/

编排agent/memory_manager.pyMemoryManager 负责 prefetch / sync / shutdown。

2.5 技能库(Skills)

  • 存放在 $HERMES_HOME/skills/
  • 每个技能是一个目录,含 SKILL.md + 可选 references/templates/scripts/
  • 由后台 review(background_review.py)把"用户纠正 / 教训"沉淀到 skill 中
  • 触发条件基于工具迭代计数 _iters_since_skill

2.6 后台 Review(background_review.py)

agent/background_review.py:1-17

"""Background memory/skill review — fork the agent to evaluate the turn.

After every turn, ``AIAgent.run_conversation`` may call
:func:`spawn_background_review` to fire off a daemon thread that replays
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/turn_context.py:582-590

# Track memory nudge trigger (turn-based, checked here).
should_review_memory = False
if (agent._memory_nudge_interval > 0
        and "memory" in agent.valid_tool_names
        and agent._memory_store):
    agent._turns_since_memory += 1
    if agent._turns_since_memory >= agent._memory_nudge_interval:
        should_review_memory = True
        agent._turns_since_memory = 0

agent/turn_finalizer.py:698-724

# Check skill trigger NOW — based on how many tool iterations THIS turn used.
_should_review_skills = False
if (agent._skill_nudge_interval > 0
        and agent._iters_since_skill >= agent._skill_nudge_interval
        and "skill_manage" in agent.valid_tool_names):
    _should_review_skills = True
    agent._iters_since_skill = 0

# External memory provider: sync the completed turn + queue next prefetch.
agent._sync_external_memory_for_turn(...)

# Background memory/skill review — runs AFTER the response is delivered
if final_response and not interrupted and (_should_review_memory or _should_review_skills):
    try:
        agent._spawn_background_review(
            messages_snapshot=list(messages),
            review_memory=_should_review_memory,
            review_skills=_should_review_skills,
        )
    except Exception:
        pass  # Background review is best-effort

默认间隔

  • Memory:每 10 轮用户消息触发(nudge_interval: 10
  • Skill:每 15 次工具迭代触发(skills.creation_nudge_interval: 15

Fork 的安全隔离

Review fork 共享父 agent 的 _memory_store,但显式 skip_memory=True 避免触碰外部 provider,并 _persist_disabled=True 防止把审查 prompt 写入用户的 state.db

agent/background_review.py:716-830

review_agent._memory_store = agent._memory_store
review_agent._memory_enabled = agent._memory_enabled
review_agent._user_profile_enabled = agent._user_profile_enabled
review_agent._memory_nudge_interval = 0
review_agent._skill_nudge_interval = 0

review_agent = AIAgent(..., skip_memory=True, ...)
review_agent._persist_disabled = True
review_agent._session_db = None
review_agent._session_json_enabled = False

工具白名单仅 memory / skill_managebackground_review.py:893-909),其他工具被拒绝。

2.7 context_references.py(与记忆无关)

@file: / @folder: / @diff / @staged / @git / @url:用户消息内联展开机制——不是 RAG 检索,是用户显式 @ 引用的精确解析。

agent/context_references.py:18-21

REFERENCE_PATTERN = re.compile(
    rf"(?<![\w/])@(?:(?P<simple>diff|staged)\b|(?P<kind>file|folder|git|url):(?P<value>...))"
)
  • 直接读工作区文件 / 跑 git 命令 / 抓 URL
  • 注入位置:附加到当前用户消息尾部(--- Attached Context ---
  • Token 保护:hard_limit = context_length × 0.50soft_limit = context_length × 0.25
  • 路径白名单 / 敏感文件黑名单(_SENSITIVE_HOME_FILES_SENSITIVE_HOME_DIRS

2.8 跨会话状态汇总

类型 保留内容 存储位置
MEMORY.md agent 个人笔记:环境事实、约定、工具怪癖、学到的教训 $HERMES_HOME/memories/MEMORY.md
USER.md 用户画像:偏好、沟通风格、期望、工作习惯 $HERMES_HOME/memories/USER.md
Skills "如何做这类任务"的类级技能库 $HERMES_HOME/skills/<name>/
state.db 完整会话历史(SQLite,可通过 session_search 工具检索) $HERMES_HOME/state.db
外部 provider Honcho / Mem0 / RetainDB 等后端自己的存储 各自后端

写入方式

  • MEMORY / USER:LLM 通过 memory 工具自主决策调用
  • 后台 review:每 N 轮自动触发
  • session_search:LLM 显式调用工具检索历史
  • 外部 provider 的 sync_turn:每轮后台自动执行

2.9 隐私 / 安全机制

机制 实现 源码
写入前注入 / 渗出扫描 first_threat_message(content, scope="strict") tools/memory_tool.py:83-88
加载时再扫 命中则替换为占位符(保留原文本到 live state 让用户能看到并删除) tools/memory_tool.py:242-276
写入审批门(write_approval) stage(暂存待批)/ block / allow 三档 tools/memory_tool.py:911-965
Drift 检测 检测外部 patch / shell append 污染,命中则备份 tools/memory_tool.py:807-861
CLI /reset hermes memory 子命令重置 hermes_cli/subcommands/memory.pyhermes_cli/memory_reset.py

第三部分:配置项汇总

3.1 上下文压缩(compression.*

cli-config.yaml.example:425-556agent/agent_init.py:1860-2009、2455-2499

YAML 字段 默认值 作用
enabled true 总开关,false 完全关闭自动压缩
threshold 0.50 触发阈值占上下文比例
model_thresholds {} 按模型子串匹配覆盖阈值,如 "glm-5.2": 0.40
threshold_tokens null 绝对 token 上限,与比例阈值取 min
target_ratio 0.20 尾部保留比例 = threshold × ratio × context_length
protect_first_n 3 头部额外保护消息数(首次压缩后衰减为 0)
protect_last_n 20 尾部最小保护消息数(实际受 token 预算与 floor=8 约束)
min_tail_user_messages 1 尾部至少保留的真实 user 消息数
max_attempts 3(硬上限 10) 单轮内压缩重试次数
in_place true true=同 session 覆盖,false=分裂子 session
abort_on_summary_failure false true=LLM 总结失败时整体放弃压缩
proactive_prune_tokens 0(禁用) 独立的旧工具结果裁剪触发阈值
proactive_prune_min_result_chars 8000 工具结果裁剪的最小字符门槛
proactive_prune_min_reclaim_tokens 4096 裁剪必须至少回收这么多 token 才提交
progress_notices false 是否在聊天平台显示压缩进度
idle_compact_after_seconds 0(禁用) 闲置 N 秒后下次回复前主动压缩
micro_compact false(opt-in) 每轮滚动微压缩(实验性,默认关)

摘要模型独立配置auxiliary.compression.provider / model(默认 "auto":OpenRouter → Nous Portal → 主模型)。

是否可完全关闭:可以。compression.enabled: false。运行时所有触发点都先检查 agent.compression_enabledconversation_loop.py:1966、2063、6360、6396)。

3.2 记忆(memory.*

cli-config.yaml.example:665-683

YAML 字段 默认值 作用
memory_enabled true 关闭则不加载 MEMORY.md
user_profile_enabled true 关闭则不加载 USER.md
memory_char_limit 2200 MEMORY 字符上限(≈800 token)
user_char_limit 1375 USER 字符上限(≈500 token)
nudge_interval 10 0=禁用后台 memory review 触发
flush_min_turns 6 0=禁用退出前 flush
provider ""(空字符串=禁用外部 provider) 选哪个插件:honcho / mem0 / retaindb / supermemory

Skills(同位置 yaml 第 785 行):

YAML 字段 默认值 作用
skills.creation_nudge_interval 15 0=禁用后台 skill review

3.3 启用条件(双层 gate)

agent/agent_init.py:1655-1669

_memory_toolset_requested = "memory" in (agent.enabled_toolsets or [])
if not skip_memory or _memory_toolset_requested:
    mem_config = _agent_cfg.get("memory", {})
    agent._memory_enabled = mem_config.get("memory_enabled", False)
    agent._user_profile_enabled = mem_config.get("user_profile_enabled", False)
    agent._memory_nudge_interval = int(mem_config.get("nudge_interval", 10))
    if agent._memory_enabled or agent._user_profile_enabled:
        from tools.memory_tool import MemoryStore
        agent._memory_store = MemoryStore(
            memory_char_limit=mem_config.get("memory_char_limit", 2200),
            user_char_limit=mem_config.get("user_char_limit", 1375),
        )
        agent._memory_store.load_from_disk()

第四部分:Bridge 模式下的实际状态

4.1 关键事实:Bridge 显式禁用所有 Memory 机制

backend/hermes-bridge/hermes_bridge.py:219-231

kwargs = dict(
    base_url=effective_base_url or None,
    api_key=effective_api_key,
    model=effective_model or None,
    max_iterations=max_iterations,
    quiet_mode=True,
    tool_progress_mode="off",
    skip_context_files=True,
    load_soul_identity=False,
    skip_memory=True,           # ← 关键
)

agent = AIAgent(**kwargs)

skip_memory=True 的影响,对照 agent_init.py:1673-1739

机制 Bridge 模式下是否生效 原因
外部 Memory Provider 完全禁用 _memory_manager = None(整段被 if not skip_memory 包裹)
后台 memory review 触发 不触发 agent._memory_store 默认值 None + valid_tool_namesmemory + 三个 AND 条件全失效
后台 skill review 触发 不触发 同上
sync_all / queue_prefetch_all 不执行 _memory_manager 为 None,_sync_external_memory_for_turn 早 return
MEMORY.md / USER.md 加载 _memory_enabled 配置 enabled_toolsets"memory"(默认 toolset 解析后含 memory),仍会建 store
MEMORY/USER 的 LLM 工具调用 取决于 memory_enabled 默认值 默认 Falsemem_config.get("memory_enabled", False)

4.2 Bridge 上下文压缩的实际状态

Bridge 没有传任何 compression.* 参数,走 AIAgent 默认:

  • compression.enabled = true(启用)
  • compression.threshold = 0.50(50% 上下文窗口)
  • compression.target_ratio = 0.20
  • 其他配置看 $HERMES_HOME/config.yaml 是否存在

实际行为:会话内消息累积到约一半上下文窗口时,自动触发压缩流水线。

4.3 启用完整 Memory 的步骤

  1. 去掉 skip_memory=True(或改为 False
  2. $HERMES_HOME/config.yaml 配置

    memory:
     memory_enabled: true
     user_profile_enabled: true
     memory_char_limit: 2200
     user_char_limit: 1375
     nudge_interval: 10
    
  3. 外部 provider(可选):在 config.yaml 设 memory.provider: honcho + 配套 env(如 HONCHO_API_KEY

  4. per-user 隔离(可选):传 user_id / chat_id / gateway_session_key,参考 agent_init.py:1707-1723

4.4 启用的潜在影响

影响项 评估
system prompt 长度 MEMORY 2200 字符 + USER 1375 字符 ≈ 1300 token 永久占用上下文
prefix cache 会话内不变(设计上保证 cache 命中)
后台 LLM 调用成本 每 10 轮触发一次 background review fork(额外 LLM 调用)
磁盘空间 MEMORY.md / USER.md / skills/ 都很小(KB 级)
隐私 用户对话内容会被沉淀到文件(已有注入扫描、写入审批、drift 检测三重防护)
Bridge session 隔离 Bridge 按 session_id::hermes_home::model_id 缓存 agent(最多 8 个 LRU),不同 session_id 不共享 memory store

第五部分:核心文件清单

上下文压缩

文件 作用
agent/context_compressor.py 主实现:阈值判断、5 阶段流水线、4 层失败降级
agent/context_engine.py 基类,token 估算
agent/context_breakdown.py 上下文构成分析(用于诊断)
agent/conversation_compression.py 压缩调用入口、回滚、用户告警
agent/conversation_loop.py:1972, 3272, 6360 触发点(请求前预检 + 响应后更新)
agent/agent_init.py:1860-2009 配置加载与 ContextCompressor 实例化
agent/auxiliary_client.py 独立的摘要模型客户端
cli-config.yaml.example:425-556 配置项示例

记忆机制

文件 作用
tools/memory_tool.py 内置 MEMORY.md / USER.md 存储 + memory 工具
agent/memory_provider.py MemoryProvider ABC 接口
agent/memory_manager.py MemoryManager(编排 + prefetch / sync / shutdown)
agent/background_review.py 后台 fork 自评
agent/turn_context.py:582-590 Memory nudge 触发条件
agent/turn_finalizer.py:698-724 Review spawn / sync_all 调用点
agent/agent_init.py:1655-1739 Memory 初始化(双层 gate)
agent/context_references.py @file: / @diff 等内联引用展开(与 memory 无关)
plugins/memory/ 外部 provider 插件目录
hermes_state.py:239 state.db 默认路径
cli-config.yaml.example:659-683 Memory 配置示例

Bridge 模式

文件 作用
backend/hermes-bridge/hermes_bridge.py:228 Bridge 显式 skip_memory=True
backend/hermes-bridge/hermes_bridge.py:219-231 agent 创建参数