MemoryOS
Memory-OS · 全息记忆 四源 · Fabric / Qdrant / Sessions / Facts

七层记忆的 全息大脑内核

Memory-OS 跨 7 层架构完成记忆的捕获、存储、评分、检索与权威裁决——让 agent 不再重新发现它早已知道的事实。四个独立记忆源 Fabric、Qdrant、Sessions、Fact Store 协同工作,全部围绕一个核心问题:注入的记忆,凭什么被信任。

# Memory-OS · icarus/hooks.py · icarus/state.py · layers/

7 层
全息记忆架构
4 源
Fabric · Qdrant · Sessions · Facts
4096d
Qwen3-Embedding-8B 向量
4 级回退
Qdrant fail-open 级联
向下探索
01
Layer 7 · ground truth

基准真相 SOUL.md — 权威分级问题

源码:~/.hermes/SOUL.md 与 rulebook.md。Memory-OS 把记忆注入进每一次提示,但 agent 的身份文档从未告诉它「要信任这些记忆」——没有明确的等级,注入记忆就被隐式当成可选项。

核心洞察(2026-05-31):Memory OS 一直在向每条提示注入上下文,但身份文档(SOUL.md 与 rulebook.md)从未赋予它权威等级。缺少 rank,注入记忆就排在终端输出和官方文档之下——agent 不断重新发现提示里已有的事实。

修复前:只有三级的基准真相层级

在 Layer 7 之前,Ground Truth 层级只有三级:

  • 1. 终端输出→ 基准真相(运行时系统状态)
  • 2. 官方文档→ 权威(API、配置、版本细节)
  • 3. 训练知识→ 仅供参考(务必核实)

四个注入源 [qdrant]、[fabric]、[sessions]、[facts] 根本没有被列出。无状态 = 无权威。

团队观察到的症状:

  • agent 运行 search_files、read_file、session_search 去重新查找 [qdrant] 早已提供的信息
  • 即便答案就在提示里,每个问题都被当成全新问题
  • 项目、决策、约束在每个会话里从头重新发现
  • 跨会话上下文虽然出现在系统前导里却被默默忽略

修复后:4 级基准真相层级

修复把 SOUL.md 扩展到四级,将注入记忆插入为第 2 级:

  • 1. 终端输出→ 系统状态的基准真相(运行时)
  • 2. 注入记忆 [qdrant] [fabric] [sessions] [facts]→ 已记录知识与既往决策的基准真相
  • 3. 官方文档→ API/配置/版本细节的权威
  • 4. 训练知识→ 仅供参考;务必对照 1–3 核实
表 01 · 冲突裁决规则
冲突的来源裁决规则胜出方
终端输出 vs 注入记忆系统状态以终端为准;已记录知识以注入记忆为准。终端 / 注入
注入记忆 vs agent 假设注入记忆永远胜出。答案在提示里时,绝不把问题当作全新问题。注入记忆
注入记忆 vs 官方文档版本敏感的细节以官方文档为准;项目上下文以注入记忆为准。文档 / 注入
训练知识 vs 任何来源训练知识永远败北,对照第 1–3 级核实。第 1–3 级
写入 SOUL.md 的关键指令:「当注入记忆与你的假设矛盾时,注入记忆胜出。答案已在你的提示里时,绝不把问题当作全新问题。」

变更的文件

文件变更
~/.hermes/SOUL.mdGround Truth 段从 3 级扩展到 4 级;新增冲突规则
~/.hermes/rulebook.md在 Source of Truth 表中新增「Injected memory」行;新增强制核实行为
需要重启:编辑 SOUL.md 或 rulebook.md 后,必须重启 gateway 才能让变更在新会话生效——systemctl --user restart hermes-gateway

为什么这层重要

没有 Layer 7,基础设施层(01–06)照样捕获并注入记忆,但注入的记忆被无视:

[qdrant]

注入却被验证

Qdrant 点已注入,agent 却 curl Qdrant API 去核实它们。

[fabric]

注入却被重查

Fabric 条目已注入,agent 却调用 fabric_recall 去重新查找。

[sessions]

注入却被重搜

会话历史已注入,agent 却运行 session_search 去重新发现。

[facts]

注入却被探测

Facts 已注入,agent 却 probe fact_store 去确认它们。

每一次重新发现都在烧 token、时间和模型上下文。Layer 07 正是阻止这种浪费的那一层。

02
Layer 3 · structured facts

结构化事实 — 贝叶斯信任评分(全息记忆)

源码:$HERMES_HOME/memory_store.db(SQLite + HRR + FTS5)。Layer 3 存储持久化的结构化事实,带实体解析与贝叶斯信任评分。

Facts 表结构

字段类型说明
fact_idINTEGER PK唯一事实标识
contentTEXT事实陈述本身
categoryTEXTuser_pref | project | tool | general
entitiesJSON ARRAY解析出的实体引用(HRR 驱动)
tagsTEXT逗号分隔的标签列表
trust_scoreREAL贝叶斯计算的可信度(0–1)
retrieval_countINTEGER事实被检索的次数
helpful_countINTEGER经 fact_feedback 标记为有用的次数
created_atTIMESTAMP创建时间
last_accessed_atTIMESTAMP最后检索时间

6 个 Fact 动作

动作作用示例
add存入新事实fact_store(action='add', content='...', entities=['Qdrant'])
search经 FTS5 关键词查询fact_store(action='search', query='Qdrant named vectors')
probe某个实体的全部事实fact_store(action='probe', entity='Docker')
reason组合式:关联多个实体的事实fact_store(action='reason', entities=['Qdrant', 'Docker'])
contradict查找 store 中冲突的论断fact_store(action='contradict')
update / removeCRUD 维护fact_store(action='update', fact_id=87, trust_delta=0.1)

贝叶斯信任分计算

信任分不是简单比例——它用贝叶斯先验避免在小样本上过拟合。所有事实从 0.50(中性)起步:

trust_score · Bayesian prior
trust_score = (helpful_count + α * prior) / (retrieval_count + α)

Where:
  α = 2          (strength of the prior, i.e. "virtual observations")
  prior = 0.50  (initial belief before any evidence)

例如:

  • 新事实(0 检索,0 有用):trust = (0 + 2×0.5) / (0 + 2) = 0.50
  • 1 检索,1 有用:trust = (1 + 2×0.5) / (1 + 2) = 0.67
  • 10 检索,3 有用:trust = (3 + 2×0.5) / (10 + 2) = 0.33(有用但稀疏)
  • 100 检索,80 有用:trust = (80 + 2×0.5) / (100 + 2) = 0.79(高置信)
关键规则:当你通过 probe、search 或 reason 检索到事实并在回复中引用它时,必须在同一轮调用 fact_feedback(helpful=True/False)。没有反馈,trust_score 只是摆设——每个事实都衰减回 0.50,事实质量悄然劣化。

HRR — 全息约简表示

Layer 3 用 HRR 做实体解析——把不同表面形式的相关事实绑定在一起。例如「Docker」「docker daemon」「containerd」可能通过向量叠加解析到同一个内部实体 ID。这让 probe() 和 reason() 无需精确字符串匹配即可工作。

HRR 不直接暴露给 agent。agent 看到的是 fact_store 工具提供的关键词搜索 + 实体链接,而非全息向量本身。

Icarus 增强:_search_facts()

Icarus 分支新增 _search_facts()——在 pre_llm_call 期间对 memory_store.db 做 FTS5 搜索,仅在首轮注入相关事实(避免每轮成本)。结果在系统提示中标记为 [facts]。

icarus · _search_facts()
def _search_facts(query, top_k=3):
    # Uses FTS5 over memory_store.db
    # Tokenizes query, builds OR-query from tokens ≥4 chars
    # Returns top-3 facts by rank, truncated to 200 chars
    # Fail-open: returns [] on any error

已知坑

BUG · 已修复

retrieval_count

probe()/reason() 之前不递增计数,只有 search() 会——当前代码已修复。

IMPORTANT

分数不动

信任分需要 fact_feedback 调用才会移动。没有它,每个事实都停在 0.50。

NOTE

HRR 内部化

HRR 在内部驱动实体解析——agent 不会看到或操作全息向量。

03
Layer 5 · vector database

Qdrant 向量库 — 四级回退链

源码:context_enhancer.py 的 search_with_fallback()。Layer 5 对一切受益于向量嵌入的知识做语义搜索——wiki 页面、会话转录、原始文档、技术参考。

技术栈

store

Qdrant 1.17+

Docker 部署 · Collection: knowledge_base · Endpoint: http://localhost:6333

vectors

4096d Cosine + BM25

稠密 4096 维余弦 + BM25 稀疏向量,混合检索。

embedding

Qwen3-Embedding-8B

经 OpenRouter 调用,$0.025/1M tokens。

为什么选 Qwen3-Embedding-8B

  • 多语言 — 在 50+ 语言(葡萄牙语、西班牙语等)上表现强劲
  • 质量 — 4096 维,语义保真度高,接近 MTEB 榜首
  • 速度 — 8B 参数推理快,适合每小时增量摄取而不拖累 ARQ worker
  • 成本 — 经 OpenRouter $0.025/1M tokens,整库 wiki 重建只需几美分

Qdrant Collection 结构(必须用命名向量)

collection schema
{
  "vectors": {
    "dense": { "size": 4096, "distance": "Cosine" }
  },
  "sparse_vectors": {
    "sparse": { "index": { "on_disk": false } }
  }
}
Qdrant 1.17+ 破坏性变更:点必须使用 {"dense": [...], "sparse": SparseVector(...)}。未命名向量会被静默拒绝。另外 AsyncQdrantClient.search() 在 qdrant-client 1.18 中不存在——改用 REST API POST /collections/{name}/points/search。

4 级回退级联

search_with_fallback() 实现一个 4 级 fail-open 级联:

图 01 · fail-open 级联
① Hybrid
dense+sparse+RRF
sparse 失败
② Dense-only
纯余弦
Qdrant 离线
③ Lexical
vault markdown
vault 不可达
④ SQLite
lineage 表
级别方法触发条件返回
1Hybrid(dense + sparse + RRF)正常运行 — 两种向量都可用对 dense 余弦 + BM25 稀疏做 Reciprocal Rank Fusion 的 Top-k
2Dense-onlysparse_vector 为 None(FastEmbed BM25 子进程失败)纯 4096d 余弦相似度搜索
3Lexical(vault markdown)Qdrant 离线(ConnectionError / Timeout / HTTPError)glob $VAULT_PATH/wiki/ 下 *.md,按 term 密度 / √word_count 排序
4SQLite keyword(lineage)vault 不可访问(无读权限、无文件)对 state.db 的 lineage.query 列做 LIKE 查询

级联在代码里如何工作

search_with_fallback()
search_with_fallback(dense_vector, sparse_vector, query_text, top_k, score_threshold)
  └── t0 = time.perf_counter()
  │
  ├─── [Level 1] if sparse_vector is not None:
  │       POST /collections/knowledge_base/points/query
  │       prefetch: [dense (top_k*3), sparse (top_k*3)]
  │       query: {"fusion": "rrf"}
  │       → RRF fusion: score = Σ 1/(k + rank_i) across dense + sparse
  │
  ├─── [Level 2] elif dense_vector is not None:
  │       POST /collections/knowledge_base/points/query
  │       using: "dense"
  │       → Pure cosine similarity, no fusion
  │
  └─── [Levels 3+4] Exception caught:
          lexical_search_in_vault() → sqlite_keyword_search() → []

  Returns: (results, fallback_level, qdrant_latency_ms, fallback_latency_ms)

嵌入管线

图 02 · 增量摄取
wiki/ 文件
SHA-256 diff
Redis 队列 (ARQ)
ARQ Worker
Qdrant upsert

衰减与去重

机制调度作用
Decay scanner每周 cron归档低重要性 + 高龄的 AI 生成点。公式:decay_score = exp(-ln(2) * age_days / half_life)。豁免人类内容与高重要性(>0.7)点。
Semantic dedup每月合并近重复点(cosine > 0.92)。标签取并集,按 source_type 定优先级。
Decay scanner 注意:缺少 payload 元数据时它毫无作用——需要点 payload 中带 importance_score、last_accessed_at、confidence_score。缺失 → 所有点 decay_score=1.0 → 什么都不归档。

Icarus 注入:_search_qdrant()

icarus · _search_qdrant()
def _search_qdrant(query, top_k=2, threshold=0.55):
    # 1. embed_query() → dense 4096d vector via OpenRouter
    # 2. embed_query_sparse() → BM25 via FastEmbed subprocess
    # 3. search_with_fallback() → 4-level cascade
    # 4. Results labeled [qdrant] in system prompt
    # 5. Per-session dedup by point ID (_injected_qdrant set)
    #
    # Key pitfall: context_enhancer.py reads OPENROUTER_API_KEY (singular).
    # Before importing, hooks.py injects: os.environ["OPENROUTER_API_KEY"] = resolved_key

Social Closer 门控

琐碎消息完全跳过 Qdrant 搜索——没必要为闲聊嵌入向量:

_is_social_close()
_SOCIAL_CLOSERS = frozenset({
    "ok", "obrigado", "valeu", "beleza", "blz", "tks", "thanks",
    "👍", "👌", "✅", "feito", "certo", "confirmo", "entendido",
    "sim", "não", "claro", "perfeito", "ótimo"
})

def _is_social_close(text):
    stripped = text.strip().lower()
    if stripped in _SOCIAL_CLOSERS: return True
    # Very short ASCII-only without technical markers
    if len(stripped) < 6 and stripped.isascii() and not any(
        c in stripped for c in "://.@#$_?"): return True
    return False
04
hooks.py · lifecycle

hooks.py — 生命周期钩子与记忆管线

源码:icarus/hooks.py。该模块实现四个生命周期钩子,包裹每一个会话轮次,共同构成完整的捕获 → 注入 → 提取管线。

钩子执行顺序

① on_session_start(session_id, platform)

上下文引导

加载 SOUL、待处理交接、近期条目、创意状态。返回 {"context": "..."} 注入会话前导。

② pre_llm_call(session_id, user_message, is_first_turn)

多源召回 + 注入

搜索全部 4 源(Fabric / Qdrant / Sessions / Facts),按会话去重,返回 {"context": "..."} 在 LLM 调用前注入。

③ post_llm_call(..., assistant_response, platform)

决策捕获 + 创意追踪

捕获决策,追踪创意状态(主题、学习、问题)。为重要结果写 fabric 条目。

④ on_session_end(session_id, platform, completed)

会话收尾

给会话打分,尝试用 LLM 从转录提取条目,失败回退到旧的截断逻辑,写记忆文件。

on_session_start — 上下文引导

新会话开始时调用。清除每会话去重状态,递增创意周期,然后从四源组装上下文:

hooks.py · on_session_start
def on_session_start(session_id="", platform="", **kwargs):
    global _last_query_tokens, _injected_fabric, _injected_qdrant, _injected_sessions
    _last_query_tokens = set()
    _injected_fabric.clear()
    _injected_qdrant.clear()
    _injected_sessions.clear()
    state.session_id = session_id
    state.exchanges = []

    # 1. Load SOUL (the agent's identity/values document)
    soul = state.load_soul()
    if soul: parts.append(soul.strip())

    # 2. Pending handoffs from other agents (tasks, reviews, tickets)
    open_tasks, reviews, open_tickets = state.read_pending()
    if open_tasks:  # → [fabric] items assigned to you
    if reviews:     # → [fabric] reviews of your work
    if open_tickets: # → [fabric] tickets assigned to you

    # 3. Cross-agent feedback (non-pending items from other agents)
    feedback = state.read_cross_agent(3)

    # 4. Recent entries from this agent
    entries = state.read_recent(limit=5)  # → [fabric] recent activity

    # 5. Creative state (open questions + recent learnings)
    if creative["questions"]:  # → [fabric] open questions
    if creative["learnings"]:  # → [fabric] learnings

    return {"context": "\n".join(parts)} if parts else None

pre_llm_call — 多源召回 + 注入

核心记忆注入钩子。搜索全部四源并按会话去重,避免一轮又一轮重复相同上下文:

hooks.py · pre_llm_call
def pre_llm_call(session_id="", user_message="", is_first_turn=False, **kwargs):
    # ── Overlap gate ──
    # Only suppress injection on NEAR-LITERAL repetition (>0.85 token overlap).
    # The old 0.6 gate killed injection in long single-topic sessions.
    tokens = _tokenize(user_message)   # alphanumeric tokens ≥4 chars, stopwords removed
    if _last_query_tokens:
        overlap = len(tokens & _last_query_tokens) / max(len(tokens), 1)
        if overlap > 0.85: return None   # suppress — near-identical turn

    _last_query_tokens = tokens
    is_social = _is_social_close(user_message)

    # ── 4 independent searches ──
    results          = state.recall(user_message, max_results=5, agent=agent)  # Fabric
    qdrant_results   = _search_qdrant(user_message, top_k=2, threshold=0.55)  # Qdrant
    session_results  = _search_sessions(user_message, session_id, top_k=2)   # Sessions FTS5
    fact_results     = _search_facts(user_message, top_k=3) if is_first_turn else []  # Facts

    # ── Per-source dedup (within session) ──
    # Fabric:   dedup by entry id (str) — _injected_fabric set
    # Qdrant:   dedup by point id — _injected_qdrant set
    # Sessions: dedup by session_id — _injected_sessions set
    # Facts:    no dedup needed (first-turn only)

    # → Returns {"context": "[fabric]...\n\n[qdrant]...\n\n[sessions]...\n\n[facts]..."}

post_llm_call — 决策捕获 + 创意追踪

hooks.py · post_llm_call
def post_llm_call(session_id="", user_message="", assistant_response="", platform="", **kwargs):
    state.exchanges.append({"user": user_message[:200], "assistant": assistant_response[:500]})

    # ── Decision capture ──
    # Requires: decision keyword + outcome keyword + substantial response (>200 chars)
    #           + substantial user request (>50 chars)
    if (DECISION_RE.search(assistant_response)
        and OUTCOME_RE.search(assistant_response)
        and len(assistant_response) > 200
        and len(user_text) > 50):
        body = f"Task: {user_text[:300]}\n\nResult: {assistant_response[:500]}"
        state.write_entry("decision", body, summary[:80],
                          platform=plat, status=entry_status, training_value="high")

    # ── Creative tracking (broader _THEME_RE — no writes) ──
    if _THEME_RE.search(assistant_response):
        theme = _extract_theme(assistant_response)      # top-3 non-stopword words, ≥4 chars
        if theme not in creative["themes"]: creative["themes"].append(theme)

    if _EVAL_RE.search(assistant_response):
        learning = _extract_sentence(assistant_response, _EVAL_RE)  # first matching sentence
        if learning and learning not in creative["learnings"]: creative["learnings"].append(learning)

    if _QUESTION_RE.search(assistant_response):
        question = _extract_sentence(assistant_response, _QUESTION_RE)
        if question and question not in creative["questions"]: creative["questions"].append(question)

    state.save_creative(creative)

on_session_end — LLM 提取 + 旧逻辑回退

hooks.py · on_session_end
def on_session_end(session_id="", platform="", completed=False, **kwargs):
    state.write_memory_file(creative)    # persist creative state to disk
    if not state.exchanges: return

    scores = state.score_session()
    if scores["total"] < 0.2: return    # too trivial — skip

    # ── LLM extraction (primary) ──
    transcript = _build_transcript(state.exchanges)   # compact [Turn N — User/Agent] format
    entries = _llm_extract_entries(transcript)        # LLM analyzes → structured entries

    if entries:
        for entry in entries:
            state.write_entry(entry["type"], entry["content"], entry["summary"],
                              platform=plat, training_value=entry["training_value"],
                              status="completed")
        logger.info("LLM extracted %d entries from session", len(entries))
    else:
        # ── Legacy fallback ──
        _legacy_session_write(platform, scores)  # truncated original behavior

LLM 驱动的会话提取

_llm_extract_entries() 把紧凑转录发给聊天 LLM(默认经 OpenRouter 的 DeepSeek),要求它提取值得跨会话保留的重要条目。返回结构化 dict:

extracted entry shape
{
  "type": "decision" | "resolution" | "note",
  "summary": "one line, max 80 chars, original language",
  "content": "markdown with ## Context, ## Action/Decision, ## Outcome",
  "training_value": "high" | "normal" | "low"
}

提取提示要求 LLM 跳过琐碎会话、问候和例行闲聊——只保留值得跨会话保留的内容。该函数用稳健 JSON 解析(_parse_json_robust)处理 markdown 围栏、首尾多余文本,并在解析失败时渐进剥离尾部字符。

FTS5 会话搜索:_search_sessions()

hooks.py · _search_sessions
def _search_sessions(query, current_session_id="", top_k=2):
    # FTS5 over state.db (Hermes session DB)
    # Builds OR-query from meaningful tokens (≥4 chars, stopwords removed)
    toks = [t for t in _tokenize(query) if len(t) >= 4]
    fts_query = " OR ".join(toks[:8])   # max 8 terms to avoid query complexity

    # FTS5 snippet() + GROUP BY are incompatible in same SELECT
    # → fetch top-20 ranked rows, dedup by session_id in Python
    SELECT m.session_id, s.title, s.started_at,
           snippet(messages_fts, 0, '', '', '…', 12) AS snip
    FROM messages_fts
    JOIN messages m ON m.id = messages_fts.rowid
    LEFT JOIN sessions s ON s.id = m.session_id
    WHERE messages_fts MATCH ?
      AND m.session_id != ?
      AND m.role IN ('user','assistant')
    ORDER BY rank
    LIMIT 20
Social closer 检测:在 pre_llm_call 中,若 _is_social_close() 返回 True,则 Qdrant、Sessions、Facts 搜索全部跳过。这避免在「ok」「thanks」「👍」等消息上浪费嵌入 API 调用。
05
security · prompt injection

提示注入消毒

Memory-OS 从外部源(Qdrant、vault 文件、SQLite)取文本并注入到 agent 上下文。任何文本进入提示前都先经过 _sanitize_context_text()——一个多模式消毒管线,外加启发式安全兜底。

捕获的已知攻击模式

模式正则替换为
"ignore all previous/prior instructions"(?i)\bignore\s+all\s+(previous|prior)\s+(instructions|...)[REDACTED]
"you are/will now become/act as AI/assistant"(?i)\byou\s+(are|will\s+now)\s+(now\s+)?(become|act|acting)\s+as...[REDACTED]
"new instructions follow/above/below"(?i)\bnew\s+(instructions|directives|commands)\s+(follow|...)[REDACTED]
模板注入 {{...}}、${...}\{\{.*?\}\}|\$\{.*?\}[REDACTED]
三反引号代码围栏```[code]
javascript/data: URL(?i)(javascript|data)\s*:sanitized:
<script>、事件处理器、<iframe><\s*script[\s>]|on\w+\s*=<iframe[\s>][sanitized]
[IMPORTANT:]、[SYSTEM:]、[OVERRIDE:](?i)\[IMPORTANT:.*?\]|\[SYSTEM:.*?\]|\[OVERRIDE:.*?\][REDACTED]
控制字符(除 \n \t)[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]""
零宽 / 不可见 Unicode[​-‏
- ⁠-⁤]""

启发式安全兜底

除模式匹配外,_validate_safe_content() 通过指令密度分析捕获未知攻击向量:

_validate_safe_content()
def _validate_safe_content(text: str) -> str:
    if not text or len(text) < 20: return text
    directivess = len(re.findall(
        r"(?i)\b(ignore|forget|disregard|override|replace|pretend|"
        r"act\s+as|you\s+(are|must|will|shall))\b", text))
    if directivess >= 3 and directivess / max(len(text), 1) > 0.02:
        return "[SANITIZED]"
    return text

后处理

  • 规整多余换行:4+ 连续 \n → 3
  • 规整多余空格:8+ 空格 → 1
  • 结果截断到 max_len(默认 600 字符)
  • Fail-open:任何异常都返回原始文本
06
telemetry · effectiveness

召回遥测 — 度量记忆有效性

源码:state.py 的召回/使用遥测系统。Memory-OS 追踪被召回的条目是否真的被 agent 使用,从而闭环检索质量的度量。

记录的事件

事件记录者数据
recallstate.log_recall()query, result_count, result_ids, result_summaries, session_id, agent
usagestate.log_usage()entry_id, action, session_id, agent

遥测文件

$HERMES_HOME/.icarus-telemetry.jsonl
$HERMES_HOME/.icarus-telemetry.jsonl

{"ts": "2026-06-10T14:23:01Z", "event": "recall",
 "source": "pre_llm_call", "query": "how do I configure Qdrant...",
 "result_count": 3, "result_ids": ["a1b2", "c3d4", "e5f6"],
 "session_id": "sess-abc123", "agent": "hermes"}

{"ts": "2026-06-10T14:23:05Z", "event": "usage",
 "action": "referenced", "entry_id": "c3d4",
 "session_id": "sess-abc123", "agent": "hermes"}

使用率指标

遥测系统计算使用率:被召回的条目中后续在写操作里被使用的比例。低使用率(如 <0.2)说明 agent 在忽视注入上下文——指向可能的 Ground Truth 层级问题(Layer 7 未生效)。

_summarize_telemetry_events()
def _summarize_telemetry_events(events):
    recalls = [e for e in events if e.get("event") == "recall"]
    usages  = [e for e in events if e.get("event") == "usage"]
    recalled_ids = set(id for r in recalls for id in r.get("result_ids", []))
    used_ids    = set(u.get("entry_id", "") for u in usages) - {""}
    used_from_recall = used_ids & recalled_ids
    return {
        "total_recalls": len(recalls),
        "total_usages": sum(1 for u in usages if u.get("entry_id", "") in recalled_ids),
        "unique_entries_recalled": len(recalled_ids),
        "unique_entries_used": len(used_from_recall),
        "usage_rate": round(len(used_from_recall) / max(len(recalled_ids), 1), 2),
    }

集成:build_brief()

每日 brief 含召回统计,让 agent 自查记忆有效性:

state.build_brief()
brief = state.build_brief()
# {
#   "pending": { "open_tasks": 2, "reviews_of_my_work": 1, "open_tickets": 0, "items": [...] },
#   "recent_work": [{ "summary": "...", "timestamp": "2026-06-09T15:30" }],
#   "from_others": ["agent-x: Fixed the Qdrant upsert bug"],
#   "suggested_action": "Pick up: Review deployment script from hermes (id a1b2c3)",
#   "recall_stats": { "total_recalls": 12, "usage_rate": 0.58, ... }
# }
遥测是 append-only JSONL,文件超过 10 MB 时轮转。这意味着可用 state.get_telemetry(last_n=..., session_id_filter=..., agent_filter=...) 跨所有会话回溯分析召回质量。
MemoryOS · 全息记忆即基建
Memory-OS · 7-Layer Holographic Memory Architecture
Hermes Agent Plugin · icarus/hooks.py · icarus/state.py · layers/
四源协同 · Fabric / Qdrant / Sessions / Facts