Skip to content

记忆架构

每个节点仅从其祖先链中读取:

root ──▶ memory["root"]
  ├─ node_A ──▶ memory["node_A"]
  │    ├─ node_A1  (读取:root + node_A)
  │    └─ node_A2  (读取:root + node_A,不读取 node_A1)
  └─ node_B  (仅读取 root,不读取 node_A 分支)

search_memoryquery = node.eval_summary 调用。在 Letta 0.16.7 上,本技能调用 passages.searchGET /archival-memory/searchembed_query=True),以 top_k = max(letta_overfetch, limit*40) 拉取,再按 ancestor_ids / ari_checkpoint / kind == "node_scope" 做本地 post-filter。服务端返回的 embedding 排序得以保留,子节点按其查询的语义相关度从高到低看到祖先条目。被刻意避开的 passages.list(search=q) 路由实际上是 SQL substring filter(LOWER(text) LIKE LOWER(%q%)),长的自然语言查询无法与 RESULT SUMMARY metrics=[...] 这类结构化条目子串匹配,会静默返回 0 条 —— 详见 ari-skill-memory/src/ari_skill_memory/backends/letta_backend.py 的 live verification。

v0.6.0:基于 Letta

两个层级共存于每个检查点的同一个 Letta 代理中:

  • ari_node_<ckpt_hash> — 节点作用域的 archival 集合,使用上述祖先作用域元数据过滤器。
  • ari_react_<ckpt_hash> — 每个检查点的扁平 ReAct 轨迹(LettaMemoryClient,不做祖先过滤)。

经由这两个集合的读写路径(HASH = 检查点哈希;保证祖先作用域的是写入守卫与 post-filter):

mermaid
flowchart LR
    node["执行中的节点<br/>(= ARI_CURRENT_NODE_ID)"]
    subgraph letta["每个检查点的 Letta 代理"]
        nodecol["ari_node_HASH<br/>祖先作用域 archival"]
        reactcol["ari_react_HASH<br/>扁平 ReAct 轨迹"]
    end
    node -->|"add_memory (CoW: node_id 必须 == current)"| nodecol
    node -->|"ReAct 步骤"| reactcol
    node -->|"search_memory(query, ancestor_ids)"| search["passages.search<br/>embed_query, top_k = max(overfetch, limit*40)"]
    search --> nodecol
    search -->|"post-filter: ancestor_ids + ari_checkpoint + kind == node_scope"| result["已排序、仅祖先的条目"]

代理还会向核心记忆块(persona + human + ari_context)种入实验目标、主要指标和硬件规格 ── 时机为首个节点的 generate_ideas 完成时(即 primary_metric 被确定的时刻)。技能可通过 get_experiment_context() 读取,无需付出搜索成本;在 seed 执行之前调用会返回 {}

Copy-on-Write:写端工具拒绝 node_id$ARI_CURRENT_NODE_ID 的写入,因此祖先条目在兄弟节点之间保持字节稳定;出于同样的原因,Letta 自编辑默认禁用。

可移植性:每个检查点都携带 memory_backup.jsonl.gz 快照,在 ari resume 时若目标 Letta 为空将自动恢复 ── 让 cp -r checkpoints/foo /elsewhere/ + ari resume 持续可用。


另请参阅

架构 · BFTS 算法 · 术语表