乐于分享
好东西不私藏

【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory

【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory

【OpenClaw】通过 Nanobot 源码学习架构---(7)Memory

  • 0x00 概要
  • 0x01 OpenClaw
    • 1.1 核心设计理念
    • 1.2 双层存储架构(核心)
    • 1.3 记忆写入规则(何时写、写哪里)
    • 1.4 检索机制(混合搜索)
    • 1.5 关键流程与特性
    • 1.6 Claw0
    • 1.7 ZeroClaw
  • 0x02 会话消息
    • 2.1 消息记录结构
    • 2.2 存储位置
    • 2.3 会话状态更新
    • 2.4 重要特性
    • 2.5 Claw0
  • 0x03 MemoryStore
    • 3.1 两层记忆结构
    • 3.2 与其他组件的协作
    • 3.3 记忆管理方式
    • 3.4 记忆整合/会话压缩
    • 3.5 代码
  • 0x04 SKILL.md
    • 4.1 文件内容
    • 4.2 文件结构
    • 4.3 核心作用
    • 4.4 使用流程
    • 4.5 memory 技能用法拆解
    • 4.6 关键补充
  • 0x05 相关工具
    • 5.1 文件系统工具
    • 5.2 _SAVE_MEMORY_TOOL
  • 0xFF 参考

0x00 概要

OpenClaw 应该有40万行代码,阅读理解起来难度过大,因此,本系列通过Nanobot来学习 OpenClaw 的特色。

Nanobot是由香港大学数据科学实验室(HKUDS)开源的超轻量级个人 AI 助手框架,定位为"Ultra-Lightweight OpenClaw"。非常适合学习Agent架构。

Memory 是构建复杂 Agent 的关键基础。如果仅依赖 LLM 自带的有限上下文窗口作为短期记忆,Agent 将在跨会话中处于 stateless 的状态,难以维持连贯性。同时,缺乏长期记忆机制也限制了 Agent 自主管理和执行持续性任务的能力。引入额外的 Memory 可以让 Agent 做到存储、检索,并利用历史交互与经验,从而实现更智能和持续的行为。

Nanobot 的记忆系统提供了两层存储方案:频繁访问的重要信息保存在 MEMORY.md 中并始终加载到上下文,而详细的历史事件保存在 HISTORY.md 中可通过搜索访问。然而,实际上的记忆系统还应该包括会话消息,因此记忆系统总体包括如下:

  • Session messages:当前会话临时存储

  • HISTORY.md:历史日志文件,记录会话历史事件,用于搜索查询

  • MEMORY.md:长期记忆文件,存储偏好、项目上下文、关系等重要信息

0x01 OpenClaw

OpenClaw 的记忆机制以 文件即真相(File-First) 为核心,采用 双层 Markdown 落盘存储 + 混合向量检索 + 自动刷新 的工程化设计,彻底解决 Agent 跨会话失忆问题,同时保证透明、可控、可治理。

1.1 核心设计理念

  • 文件是唯一真相源:所有记忆以纯 Markdown 文件存储在本地工作区,模型只 “记住” 写入磁盘的内容,无黑盒向量库。
  • 透明可控:可直接用编辑器查看、编辑、Git 版本管理记忆文件。
  • 持久优先:重要信息必须落盘,不依赖内存,跨会话、重启后不丢失。
  • 分层治理:短期临时信息与长期重要知识分离,避免记忆污染。

模型不会因为你用得更久而变聪明。但围绕它的文件会变得更丰富、更精准、更贴合你的具体需求。

1.2 双层存储架构(核心)

默认工作区路径:~/.openclaw/workspace/

1.2.1. 短期记忆(每日日志)

  • 文件:memory/YYYY-MM-DD.md(按天归档,仅追加)
  • 内容:日常对话、临时上下文、即时想法、待办、会话摘要
  • 加载:会话启动时自动加载 今天 + 昨天 的日志,提供近期上下文
  • 生命周期:按天滚动,保留历史,不主动清理

1.2.2. 长期记忆(精选持久)

  • 文件:MEMORY.md(可选,仅主会话加载,群组上下文不加载)
  • 内容:用户偏好、关键决策、项目元数据、持久事实、长期目标
  • 特性:结构化整理,人工 / 模型可主动提炼写入
  • 隔离:仅在私密会话可见,保护敏感信息

1.3 记忆写入规则(何时写、写哪里)

  • 写入 MEMORY.md:决策、偏好、持久事实、长期知识
  • 写入 memory/YYYY-MM-DD.md:日常笔记、运行上下文、临时信息
  • 显式触发:用户说 “记住这个” 时,强制写入文件,不留在内存
  • 自动刷新(Pre-compaction Flush):会话接近上下文压缩阈值时,触发静默轮次,提醒模型将重要内容写入持久记忆(默认 NO_REPLY,用户无感知)
  • sessionFlush: /new 会构建新session,此时保存旧 session到memory/-slug.md

1.4 检索机制(混合搜索)

  • 索引层:基于 SQLite 构建向量索引,默认启用
  • 嵌入支持:OpenAI、Gemini、Voyage、本地模型等多种嵌入提供商
  • 混合检索:向量相似度 + BM25 关键词匹配,支持 MMR 重排序(提升多样性)
  • 检索范围:覆盖 MEMORY.md 与所有 memory/*.md 文件
  • CLI 工具openclaw memory search 执行语义检索

1.5 关键流程与特性

  1. 会话启动:加载今日 / 昨日日志 + 主会话加载 MEMORY.md,构建初始上下文
  2. 运行时:对话与操作实时写入当日日志;重要信息由模型 / 用户主动提炼到 MEMORY.md
  3. 压缩保护:上下文快满时,自动触发记忆刷新,确保重要信息不被压缩丢失
  4. 跨会话:重启 / 新会话自动加载历史记忆,实现真正的长期记忆
  5. 工作区隔离:每个 Agent 独立工作区,记忆不互通(可显式配置共享)

1.6 Claw0

Claw0 关于 memory 的特色如下:

1.6.1 混合搜索管道 -- 向量 + 关键词 + MMR

完整的搜索管道串联五个阶段:

  1. 关键词搜索 (TF-IDF): 与上面相同的算法, 按余弦相似度返回 top-10
  2. 向量搜索 (哈希投影): 通过基于哈希的随机投影模拟嵌入向量, 返回 top-10
  3. 合并: 按文本前缀取并集, 加权组合 (vector_weight=0.7, text_weight=0.3)
  4. 时间衰减score *= exp(-decay_rate * age_days), 越近的记忆得分越高
  5. MMR 重排序MMR = lambda * relevance - (1-lambda) * max_similarity_to_selected, 用 token 集合的 Jaccard 相似度保证多样性

基于哈希的向量嵌入展示了双通道搜索的模式, 不需要外部嵌入 API.

1.6.2 _auto_recall() -- 自动记忆注入

每次 LLM 调用之前, 自动搜索相关记忆并注入到系统提示词中. 用户不需要显式请求.

def _auto_recall(user_message: str) -> str:    results = memory_store.search_memory(user_message, top_k=3)    if not results:        return ""    return "\n".join(f"- [{r['path']}{r['snippet']}" for r in results)# 在 agent 循环中, 每轮:memory_context = _auto_recall(user_input)system_prompt = build_system_prompt(    mode="full", bootstrap=bootstrap_data,    skills_block=skills_block, memory_context=memory_context,)

1.7 ZeroClaw

下图是来自其官方文档的“Storage backends and data flow”。

Memory System Architecture

1.7.1 记忆系统架构

层级
组件
说明
Memory Frontends
Auto-save hooks
user_msg, assistant_resp
memory_store tool
存储记忆
memory_recall tool
回忆记忆
memory_forget tool
遗忘记忆
memory_get tool
获取记忆
memory_list tool
列出记忆
memory_count tool
计数记忆
Memory Backends
sqlite
默认,本地文件
markdown
每日 .md 文件
lucid
云端同步
none
仅内存
Memory Categories
Conversation
聊天记录
Daily
会话摘要
Core
长期事实

1.7.2 数据流向

Frontend (AutoSave/Tools) → Memory trait → create_memory factory                                              │                                              ▼                                    Backend? (config.memory.backend)                                              │                    ┌─────────────────────────┼─────────────────────────┐                    │                         │                         │                    ▼                         ▼                         ▼                  sqlite                   markdown                    lucid                    │                         │                         │                    └─────────────────────────┼─────────────────────────┘                                              │                                              ▼                                    Memory Categories                                    (Conversation / Daily / Core)                                              │                                              ▼                                    Persistent Storage

1.7.3 RAG 集成

Hardware RAG -.-> load_chunks -> Markdown (每日 .md 文件)

0x02 会话消息

会话消息的定位是“会话级运行事实”。它不等同于长期记忆,也不应该直接替代长期记忆文件。

2.1 消息记录结构

每个消息字典包含以下字段:

  • role:消息角色(user,assistant,tool)
  • content:消息内容
  • timestamp:时间戳
  • tool_calls:工具调用信息(如果有)
  • tool_call_id:工具调用ID(如果是工具结果)
  • name:工具名称

2.2 存储位置

会话消息记录主要存储在以下两个地方:

  • 内存缓存(Session对象)
    • 数据结构:Session 类中的 messages 字段类型:list[dict[str,Any]]
    • 内容:包含角色、内容、时间戳等信息的消息列表
  • 磁盘文件(JSONL格式文件)
    • 文件格式:JSONL(每行一个JSON对象)
    • 文件位置:/.nanobot/workspace/sessions/(session_key).jsonl
    • 文件命名:将会话键中的冒号替换为下划线 3存储管理组件
    • 它记录会话头信息、消息、工具调用与结果等内容,主要用于会话连续性、恢复、压缩、导出和调试。

存储流程如下:

  • 创建/获取会话:通过get_orcreate()方法从缓存或磁盘加载
  • 添加消息:添加到内存中的messages列表
  • 保存会话:调用save()方法同步到磁盘

2.3 会话状态更新

主流程表示

Session.messages → Filter & Format → [MemoryStore.consolidate()] → LLM Request → [_SAVE_MEMORY_TOOL] → Parse Result → Update Memory Files → Update Session State

详细流程

  1. 会话消息积累 └─ Session.messages[](包含多个消息对象)

  2. 触发条件满足 └─ unconsolidated >= memory_window

  3. 提取待归档消息 └─ old_messages = session.messages[last_consolidated:-keep_count]

  4. 格式化消息 └─ lines[](时间戳+角色+内容格式)

  5. 读取当前记忆 └─ current_memory = MEMORY.md 内容

  6. 构建 LLM 提示 └─ 包含当前记忆和待处理对话

  7. LLM 处理请求 └─ 使用 _SAVE_MEMORY_TOOL 工具定义

  8. LLM 返回工具调用 └─ 包含 history_entry 和 memory_update 参数

  9. 更新记忆文件 └─ HISTORY.md(追加 history_entry)  └─ MEMORY.md(写入 memory_update)

  10. 更新会话状态 └─ last_consolidated 指针更新,指向已归档的消息位置

    └─ 对于完全归档(archive_all),设置为 0;否则设置为 len(session.messages) - keep_count

2.4 重要特性

  • 追加模式:消息记录只增不减,用于LLM缓存效率
  • 合并机制:长会话会触发合并过程,将旧消息归档到MEMORY.md/HISTORY.md
  • 缓存优化:使用内存缓存避免频繁磁盘读取
  • 持久化:所有消息最终都会保存到JSONL文件中

这种设计确保了消息记录的可靠性和访问效率,同时支持大规模会话的管理。

2.5 Claw0

我们再用Claw0来进行比对。

在 Claw0 中,会话就是 JSONL 文件. 追加写入, 重放恢复, 太大就摘要压缩.

架构如下:

    User Input        |        v    SessionStore.load_session()  --> rebuild messages[] from JSONL        |        v    ContextGuard.guard_api_call()        |        +-- Attempt 0: normal call        |       |        |   overflow? --no--> success        |       |yes        +-- Attempt 1: truncate oversized tool results        |       |        |   overflow? --no--> success        |       |yes        +-- Attempt 2: compact history via LLM summary        |       |        |   overflow? --yes--> raise        |    SessionStore.save_turn()  --> append to JSONL        |        v    Print response    File layout:    workspace/.sessions/agents/{agent_id}/sessions/{session_id}.jsonl    workspace/.sessions/agents/{agent_id}/sessions.json  (index)

其要点如下:

  • SessionStore: JSONL 持久化. 写入时追加, 读取时重放.
  • _rebuild_history(): 将扁平的 JSONL 转换回 API 兼容的 messages[].
  • ContextGuard: 3 阶段溢出重试 (正常 -> 截断 -> 压缩 -> 失败).
  • compact_history(): LLM 生成摘要替换旧消息.
  • REPL 命令/new/switch/context/compact 用于会话管理.

其历史压缩机制如下:

  • 将最早 50% 的消息序列化为纯文本, 让 LLM 生成摘要。
  • 用摘要 + 近期消息替换
def compact_history(self, messages, api_client, model):    keep_count = max(4int(len(messages) * 0.2))    compress_count = max(2int(len(messages) * 0.5))    compress_count = min(compress_count, len(messages) - keep_count)    old_text = _serialize_messages_for_summary(messages[:compress_count])    summary_resp = api_client.messages.create(        model=model, max_tokens=2048,        system="You are a conversation summarizer. Be concise and factual.",        messages=[{"role""user""content": summary_prompt}],    )    # 用摘要 + "Understood" 对替换旧消息    compacted = [        {"role""user""content""[Previous conversation summary]\n" + summary},        {"role""assistant""content": [{"type""text",         "text""Understood, I have the context."}]},    ]    compacted.extend(messages[compress_count:])    return compacted

0x03 MemoryStore

MemoryStore 是 Nanobot 框架中实现智能体双层记忆体系的核心组件,核心职责是管理智能体的「长期记忆(MEMORY.md)」和「历史日志(HISTORY.md)」,通过 LLM 自动整合对话记录,将临时会话信息提炼为结构化的长期记忆,避免消息无限累积,同时保留可检索的历史日志,复刻了 OpenCLAW 的核心记忆管理能力,是 Agent 具备「长期记忆能力」的核心实现。

3.1 两层记忆结构

记忆是智能体跨会话持久化知识的方式。在nanobot中,记忆存在Markdown 文件中。Markdown 记忆的妙处在于人类可读、人类可编辑。你可以用文本编辑器打开 MEMORY.md,进行调整,立即看到变化。没有数据库迁移,没有管理后台,就一个文件。

MemoryStore 模块通过「长期记忆 + 历史日志」双层结构实现 Agent 的记忆管理,核心是 consolidate 方法通过 LLM 自动提炼会话信息,避免消息无限累积;

  • 长期记忆(MEMORY.md):存储提炼后的核心事实、结论,轻量化且可直接用于后续对话上下文;
  • 历史日志(HISTORY.md):存储对话记录和交互历史,支持 grep 检索;

3.1.1 HISTORY.md

历史日志:存储在memory/HISTORY.md文件中,所有原始交互——用户输入、模型输出、工具调用结果——全部追加写入 History。不可修改,不可删除。

  • 追加式事件日志,不可变
  • 支持文本搜索,便于查找过往事件。
  • 可以通过grep命令搜索
  • 不会直接加载到LLM上下文,按需查询

3.1.2 MEMORY.md

长期事实记忆:存储在memory/MEMORY.md文件中,即Memory 是从 History 里提炼出来的结构化知识。和 History 的”原始全量”不同,Memory 是摘要过的、索引好的、可以快速检索的

  • 用户偏好:"I prefer dark mode"
  • 项目上下文:"The API uses OAuth2"
  • 关系信息:"Alice is the project lead"

这些信息会在每次会话开始时加载到上下文中。

3.2 与其他组件的协作

与 AgentLoop 的协作。

  • AgentLoop 负责触发和协调记忆整合
  • AgentLoop 在处理消息时调用 MemoryStore.get_memory_context() 获取长期记忆。
  • 当会话消息数量达到阈值时,AgentLoop 触发记忆整合过程。

与 ContextBuilder 的协作

  • ContextBuilder 负责将记忆注入系统提示

  • ContextBuilder 使用 MemoryStore.get_memory_context() 将长期记忆整合到系统提示中。

与 SessionManager 的协作

  • MemoryStore.consolidate() 接收来自 SessionManager 的 Session 对象。
  • 整合完成后更新 Session.last_consolidated 指针,避免重复处理。

3.3 记忆管理方式

有两种记忆管理方式

  • 自动管理

    • 通过MemoryStore.consolidate()自动进行
    • 使用_SAVE_MEMORY_TOOL专用工具
    • LLM在此过程中不使用edit_file或者 write_file
  • 手动管理

    • 用户可以主动请求AI更新 MEMORY.md
    • AI可以使用在常规工具集中的edit_file或者 write_file工具

3.3.1 记忆存储的时机

记忆存储的时机有两种。

会话压缩(系统驱动/自动写记忆)

会话压缩会将历史对话压缩到HISTORY.md和MEMORY.md ,其核心目的为:

  • 上下文管理:防止会话过长导致上下文溢出
  • 知识沉淀:将临时信息转化为持久记忆

入口:

  • MemoryStore.consolidate()函数

具体函数:

  • MemoryStore.write_long_term():写入长期记忆到MEMORY.md
  • MemoryStore.append_history():追加事件摘要到HISTORY.md

触发条件:

  • 系统自动调用,通过_SAVE_MEMORY_TOOL工具
手动记忆管理

手动记忆管理指的是:用户使用 write_file 和 edit_file 工具直接编辑记忆文件。

入口文件:

  • nanobot/skills/memory/SKILL.md
## When to Update MEMORY.mdWrite important facts immediately using `edit_file` or `write_file`:- User preferences ("I prefer dark mode")- Project context ("The API uses OAuth2")- Relationships ("Alice is the project lead")

工具:

  • write_file工具:直接写入完整内容到memory/MEMORY.md
  • edit_file工具:编辑现有内容,替换指定文本

具体函数:

  • WriteFileTool.execute():在 nanobot/agent/tools/filesystem.py 中实现
  • EditFileTool.execute():在 nanobot/agent/tools/filesystem.py 中实现

操作的两个示例如下:

  • 主动要求记录
用户输入:"记住,我的名字是张三" → AI 分析请求意图 → 识别需要更新长期记忆 → 选择适当的文件操作工具([EditFileTool.execute() → edit_file] 或 [WriteFileTool.execute() → write_file] → 构造工具调用参数 → 执行文件操作(更新 MEMORY.md) → 返回执行结果给 LLM → 更新 MEMORY.md 文件
  • 手动合并(/new命令)
# /new命令 pythonif cmd == "/new":    #档案全部消息    if notawait self._consolidate_memory(temp,archive_all=True):    #处理错误    session.clear)#清空会话

3.3.2 记忆读取的时机

构建上下文(系统提示构建时)

此处是长期记忆读取。

[ContextBuilder] 初始化 → 加载 MEMORY.md → 注入系统提示 → LLM 会话中持续访问记忆
  • 位置:memory.py中的MemoryStore.get_memory_context()
  • 具体函数:MemoryStore.read_long_term()在nanobot/agent/memory.py中
  • 调用时机:构建系统提示时(context.py中的build_system_prompt())
  • 目的:将长期记忆加入到系统提示中,使LLM在每次对话中都能看到关键信息

检索历史

此处是历史日志读取,即用户主动查询,Agent通过exec工具手动搜索历史事件 。

  • 入口:exec工具执行grep命令
  • 命令格式:grep -i“keyword"memory/HISTORY.md
  • 具体实现:ExecTool.execute() 在nanobot/agent/tools/shell.py 中
  • 文件路径:memory/HISTORY.md
  • 安全限制:通过命令过滤和路径限制确保安全

搜索执行流程如下:

用户输入:"查找上次会议记录" → AI 识别搜索意图 → 构造 grep 命令 → exec 工具调用:{"command""grep -i '会议' memory/HISTORY.md"} → [ExecTool.execute()] → 执行 grep 命令 → 返回搜索结果给 LLM → LLM 整理结果并回应用户

会话历史读取

  • 入口: Session.get_history()
  • 具体实现:在会话管理器中维护的消息列表

3.4 记忆整合/会话压缩

记忆整合会定期清理,在会话过长时自动合并内存限制:限制历史消息数量,这个记忆系统的设计旨在平衡信息保留与性能,通过自动合并减少上下文长度,同时保持重要的长期记忆。

会话增长 → 达到阈值 → 触发合并 → LLM 处理 → [save_memory / _SAVE_MEMORY_TOOL] 工具调用 → 更新 MEMORY.md 和 HISTORY.md

3.4.1 机制

MemoryStore.consolidate() 执行主要合并逻辑。

总体流程

整合过程:从会话中提取旧消息 → 构建记忆合并提示 → LLM处理 → 保存到记忆文件。

具体来说,当会话token使用量接近上下文窗口限制时,即AgentLoop._process_message() 中的条件判断。

Gateway会:

  • 调用AgentLoop._consolidate_memory()
  • 提取会话数据:从Session.messages中提取需要整合的消息(session.messages 中从 last_consolidated 到倒数第50条之间的消息)
  • 构建提示:创建包含当前记忆(当前MEMORY.md内容)和待处理对话的提示
  • LLM处理:模型根据提示词判断并写入重要信息,使用 _SAVE_MEMORY_TOOL 工具调用LLM进行记忆整理
  • 工具调用:LLM必须调用save_memory工具,提供:
    • history_entry:摘要事件,追加至 HISTORY.md
    • memory_update:更新后的长期记忆,覆盖MEMORY.md
  • 更新记忆:
    • 将重要事件摘要追加到HISTORY.md
    • 将长期事实更新到 MEMORY.md
    • 更新 Session.last_consolidated 指针
  • 使用 NO_REPLY 标记避免用户看到输出
具体流程

具体流程如下:

记忆系统初始化

ContextBuilder 初始化 → 加载 MemoryStore → 注册记忆相关工具

记忆合并流程

  • 在 MemoryStore.consolidate() 方法中使用。

合并触发条件

  • 会话消息数量超过 memory_window 阈值,达到内存窗口大小限制时自动触发
  • 执行 /new 命令时

条件判断

# [AgentLoop._process_message()]) → 检查 unconsolidated 消息数量 → 判断是否超过 [memory_window]阈值 → 触发记忆合并任务unconsolidated = len(session.messages) - session.last_consolidatedif (unconsolidated >= self.memory_window and session.key not in self._consolidating):

数据准备阶段

用户消息→_process_message()→检查是否需要整合  →[AgentLoop._consolidate_memory()] → [MemoryStore.consolidate()] → 提取需要归档的消息(old_messages)→ 读取当前长期记忆(current_memory,作为合并过程的上下文)→ 构建 LLM 提示(prompt)

提取消息数据信息如下:

  • 从 session.messages 中获取需要归档的消息
  • 过滤掉内容为空的消息
  • 格式化为时间戳 + 角色 + 内容的格式

LLM 处理阶段

待整合消息 +当前记忆 → 调用 LLM with [_SAVE_MEMORY_TOOL] → 系统提示:"You are a memory consolidation agent..." → 用户提示:包含当前记忆和待处理对话 → 工具定义:[_SAVE_MEMORY_TOOL]
  • 使用系统提示:"You are a memory consolidation agent. Call the save_memory tool with your consolidation of the conversation."
  • 传递 _SAVE_MEMORY_TOOL 作为可用工具定义
  • 模型:指定的 LLM 模型
        current_memory = self.read_long_term()        # 构建用户消息         prompt = f"""Process this conversation and call the save_memory tool with your consolidation.## Current Long-term Memory{current_memory or"(empty)"}## Conversation to Process{chr(10).join(lines)}"""        try:            response = await provider.chat(                messages=[ # 系统消息如下                    {"role""system""content""You are a memory consolidation agent. Call the save_memory tool with your consolidation of the conversation."},                    {"role""user""content": prompt}, # 用户消息                ],                tools=_SAVE_MEMORY_TOOL,                model=model,            )

工具调用阶段

LLM 必须调用 save_memory 工具 → 参数:{history_entry, memory_update} → [MemoryStore.consolidate()] 解析工具调用结果 → 更新 HISTORY.md 和 MEMORY.md

3.4.2 示例图

下图给出了记忆归档流程,用文字解释如下:

  • 用户持续对话 → Session.messages增长

  • 检查阅值  → len(session.messages)-session.last_consolidated>=memory_window

  • 异步整合 → 启动_consolidateand_unlock()协程

  • 获取待整合数据 → 提取session.messages[session.last_consolidated:-keep_count]

  • 构建LLM提示 → 包含当前记忆和待整合对话

  • LLM处理 → 调用带有save_memory工具的聊天

  • 工具执行  → LLM返回history_entry和 memory_update

  • 写入文件 → 更新HISTORY.md 和MEMORY.md

  • 更新状态  → 设置session.last_consolidated

MemoryStore 记忆归档流程

3.5 代码

# ===================== 核心类定义 =====================class MemoryStore:    """Two-layer memory: MEMORY.md (long-term facts) + HISTORY.md (grep-searchable log)."""    def __init__(self, workspace: Path):        # 初始化记忆目录(确保目录存在,不存在则创建)        self.memory_dir = ensure_dir(workspace / "memory")        # 定义长期记忆文件路径(MEMORY.md)        self.memory_file = self.memory_dir / "MEMORY.md"        # 定义历史日志文件路径(HISTORY.md)        self.history_file = self.memory_dir / "HISTORY.md"    def read_long_term(self) -> str:        """读取长期记忆文件内容"""        # 检查文件是否存在        if self.memory_file.exists():            # 读取文件内容(UTF-8编码保证中文等字符正常)            return self.memory_file.read_text(encoding="utf-8")        # 文件不存在时返回空字符串        return ""    def write_long_term(self, content: str) -> None:        """写入内容到长期记忆文件(覆盖写入)"""        self.memory_file.write_text(content, encoding="utf-8")    def append_history(self, entry: str) -> None:        """追加内容到历史日志文件(不会覆盖原有内容)"""        # 以追加模式打开文件(a),UTF-8编码        with open(self.history_file, "a", encoding="utf-8"as f:            # 去除entry末尾的空白符,添加两个换行(分隔不同条目)后写入            f.write(entry.rstrip() + "\n\n")    def get_memory_context(self) -> str:        """生成格式化的长期记忆上下文(供Agent对话使用)"""        # 读取当前长期记忆内容        long_term = self.read_long_term()        # 有内容时返回格式化字符串,无内容时返回空字符串        return f"## Long-term Memory\n{long_term}" if long_term else ""    async def consolidate(        self,        session: Session,        provider: LLMProvider,        model: str,        *,        archive_all: bool = False,        memory_window: int = 50,    ) -> bool:        """Consolidate old messages into MEMORY.md + HISTORY.md via LLM tool call.        Returns True on success (including no-op), False on failure.        """        # 分支1:全量归档模式(archive_all=True)        if archive_all:            # 取所有会话消息作为待整合的旧消息            old_messages = session.messages            # 全量归档后不保留旧消息(keep_count=0)            keep_count = 0            # 记录日志:全量归档的消息数量            logger.info("Memory consolidation (archive_all): {} messages"len(session.messages))        # 分支2:增量归档模式(默认)        else:            # 计算保留的消息数量(记忆窗口的一半,避免频繁整合)            keep_count = memory_window // 2            # 若会话消息总数 ≤ 保留数量,无需整合,直接返回成功            if len(session.messages) <= keep_count:                return True            # 若自上次整合后无新消息,无需整合,直接返回成功            if len(session.messages) - session.last_consolidated <= 0:                return True            # 提取待整合的旧消息:从上次整合位置到倒数keep_count条            old_messages = session.messages[session.last_consolidated:-keep_count]            # 若待整合消息为空,直接返回成功            if not old_messages:                return True            # 记录日志:待整合消息数量、保留消息数量            logger.info("Memory consolidation: {} to consolidate, {} keep"len(old_messages), keep_count)        # 构建待整合的对话文本(带时间戳、角色、工具使用信息)        lines = []        for m in old_messages:            # 跳过无内容的消息            if not m.get("content"):                continue            # 拼接工具使用信息(若有)            tools = f" [tools: {', '.join(m['tools_used'])}]" if m.get("tools_used"else ""            # 格式化每行内容:[时间戳] 角色(大写) [工具信息]: 消息内容            lines.append(f"[{m.get('timestamp''?')[:16]}{m['role'].upper()}{tools}{m['content']}")        # 读取当前长期记忆内容        current_memory = self.read_long_term()        # 构建LLM提示词:告知LLM需要整合对话并调用save_memory工具        prompt = f"""Process this conversation and call the save_memory tool with your consolidation.## Current Long-term Memory{current_memory or"(empty)"}## Conversation to Process{chr(10).join(lines)}"""        try:            # 调用LLM接口,触发记忆整合            response = await provider.chat(                messages=[                    # 系统提示:定义LLM的角色为记忆整合代理                    {"role""system""content""You are a memory consolidation agent. Call the save_memory tool with your consolidation of the conversation."},                    # 用户提示:传入待整合的对话和当前记忆                    {"role""user""content": prompt},                ],                tools=_SAVE_MEMORY_TOOL,  # 传入save_memory工具描述                model=model,             # 指定使用的LLM模型            )            # 检查LLM是否调用了save_memory工具            if not response.has_tool_calls:                # 未调用工具时记录警告日志,返回失败                logger.warning("Memory consolidation: LLM did not call save_memory, skipping")                return False            # 提取工具调用的参数(取第一个工具调用的参数)            args = response.tool_calls[0].arguments            # 兼容处理:部分LLM返回的参数是JSON字符串,需解析为字典            if isinstance(args, str):                args = json.loads(args)            # 校验参数类型:必须是字典,否则返回失败            if not isinstance(args, dict):                logger.warning("Memory consolidation: unexpected arguments type {}"type(args).__name__)                return False            # 处理历史条目:追加到HISTORY.md            if entry := args.get("history_entry"):                # 确保entry是字符串类型(非字符串则转为JSON字符串)                if not isinstance(entry, str):                    entry = json.dumps(entry, ensure_ascii=False)                # 追加到历史日志文件                self.append_history(entry)            # 处理记忆更新:写入MEMORY.md(仅当内容变化时)            if update := args.get("memory_update"):                # 确保update是字符串类型(非字符串则转为JSON字符串)                if not isinstance(update, str):                    update = json.dumps(update, ensure_ascii=False)                # 仅当内容与当前记忆不同时才写入(避免无意义的写入)                if update != current_memory:                    self.write_long_term(update)            # 更新会话的最后整合位置:            # - 全量归档:重置为0            # - 增量归档:设为当前消息总数 - 保留数量            session.last_consolidated = 0 if archive_all else len(session.messages) - keep_count            # 记录日志:记忆整合完成            logger.info("Memory consolidation done: {} messages, last_consolidated={}"len(session.messages), session.last_consolidated)            # 返回成功            return True        except Exception:            # 捕获所有异常,记录错误日志            logger.exception("Memory consolidation failed")            # 返回失败            return False

0x04 SKILL.md

SKILL.md 是 Nanobot 中技能的标准化定义文件,存放在对应技能的目录下(workspace/skills/{skill-name}/SKILL.md 或内置技能目录),是 Agent 识别、理解、使用技能的唯一依据,核心作用是为 LLM 提供技能的使用说明、规则、操作方法,无需训练,靠 LLM 阅读理解即可直接使用。

memory/SKILL.md 这个文件是给agent(LLM)看的技能指南,告诉 agent 如何使用记忆系统,指导用户如何主动更新记忆文件的。

4.1 文件内容

---name: memorydescription: Two-layer memory system with grep-based recall.always: true---# Memory## Structure- `memory/MEMORY.md` — Long-term facts (preferences, project context, relationships). Always loaded into your context.- `memory/HISTORY.md` — Append-only event log. NOT loaded into context. Search it with grep.## Search Past Events```bashgrep -i "keyword" memory/HISTORY.md```Use the `exec` tool to run grep. Combine patterns: `grep -iE "meeting|deadline" memory/HISTORY.md`## When to Update MEMORY.mdWrite important facts immediately using `edit_file` or `write_file`:- User preferences ("I prefer dark mode")- Project context ("The API uses OAuth2")- Relationships ("Alice is the project lead")## Auto-consolidationOld conversations are automatically summarized and appended to HISTORY.md when the session grows large. Long-term facts are extracted to MEMORY.md. You don't need to manage this.

4.2 文件结构

SKILL.md 是 Nanobot 技能的 “标准化配置 + 使用手册”,开发者按规范编写即可实现技能的即插即用,Agent 靠阅读理解即可使用,是 Nanobot 实现轻量化、插件化技能体系的核心载体。

SKILL.md 采用Markdown 格式,分为顶部YAML 前置元数据(用---包裹)和下方正文技能说明,是固定规范,Agent 会自动解析这两部分内容。

前置元数据

是技能的 “配置信息”,定义技能的基础属性(Agent 自动解析,用于技能管理),Nanobot 的 SkillsLoader 会自动提取、校验,核心必填 / 常用字段(示例中为memory技能的元数据):

字段
类型
作用
示例值
name
字符串
技能唯一标识(与技能目录名一致),Agent 靠此识别技能
memory
description
字符串
技能功能简述,用于生成技能摘要,方便 Agent 快速判断技能用途
双层记忆系统,支持 grep 检索
always
布尔值
是否为常驻技能true则直接嵌入 Agent 系统提示,无需手动加载;false则按需调用
true
requires
字典
(可选)技能依赖:指定bins(CLI 工具)、env(环境变量),缺失则技能标记为不可用
{bins: [grep]}

正文使用说明

用 Markdown 格式写清技能的核心功能、结构、操作命令、使用规则,是 LLM 实际使用技能的 “说明书”,需简洁、清晰、可执行,核心包含技能结构、操作方法、使用场景 / 规则三部分(示例为memory技能的说明)。

4.3 核心作用

这是Agent 技能的「身份标识 + 使用手册」。

  1. 对 SkillsLoader:是技能加载、校验、分类的依据,自动解析元数据,判断技能是否可用、是否为常驻技能,生成技能摘要;
  2. 对 Agent(LLM):是技能的唯一使用手册,LLM 通过阅读正文,掌握技能的操作方法、命令、规则,无需训练,直接在推理时使用;
  3. 对开发者:是技能的标准化开发规范,只需按「元数据 + 使用说明」编写,即可实现技能的 “即插即用”,无需修改 Agent 核心代码。

4.4 使用流程

SKILL.md 的使用完全由 Nanobot 框架自动化处理,被Agent 侧自动加载 + 按需调用,LLM 仅需负责 “阅读理解并执行”,核心流程:

  1. 加载SkillsLoader 启动时扫描技能目录,自动识别 SKILL.md,解析元数据,将技能加入注册表;
  2. 分类:元数据中always: true的技能(如示例memory),会被自动加载到 Agent 系统提示中,成为常驻技能,Agent 随时可用;always: false的技能仅生成摘要,不直接加载;
  3. 调用:Agent 需使用非常驻技能时,会先通过read_file工具读取对应技能的 SKILL.md,阅读使用说明后,按文档中的方法执行;
  4. 执行:Agent 严格按照 SKILL.md 正文的说明操作(如示例中用exec工具执行grep命令检索记忆),无需额外逻辑。

4.5 memory 技能用法拆解

  • 元数据always: true → 该技能直接嵌入 Agent 系统提示,Agent 无需手动读取,随时可使用记忆功能;
  • 结构说明:清晰区分MEMORY.md(长期记忆,加载到上下文)和HISTORY.md(事件日志,不加载),让 Agent 明确记忆系统的组成;
  • 操作方法:明确检索HISTORY.md需用exec工具执行grep命令,并给出基础用法和组合模式,Agent 可直接复制命令执行;
  • 使用规则:讲清MEMORY.md的更新场景(用户偏好、项目上下文等)和工具(edit_file/write_file),以及自动整合规则(无需 Agent 管理),让 Agent 知道 “何时做、怎么做、不用做什么”。比如:当对话中出现重要信息时,AI 可以使用 edit_file 或 write_file 工具将其保存到 MEMORY.md 文件中。

4.6 关键补充

我们接下来看看与 Nanobot 框架的联动细节。

  1. 优先级:工作空间(workspace/skills)的 SKILL.md 优先级高于内置技能目录,同名技能会覆盖内置技能;
  2. 可用性:若元数据中requires指定的依赖(如 CLI 工具、环境变量)缺失,SkillsLoader会将技能标记为available: false,并在技能摘要中注明缺失依赖,Agent 会提示安装后再使用;
  3. 轻量化:非常驻技能的 SKILL.md 不会直接加载到上下文,仅生成 XML 摘要,Agent 需用时再通过read_file工具读取,减少上下文冗余,提升推理效率。

0x05 相关工具

与记忆相关的工具如下:

  • 文件系统工具

    • LLM 会主动调用 WriteFileTool 和 EditFileTool
    • 用户可以显式调用 write_file 或 edit_file 工具来更新 MEMORY.md 文件
    • ReadFileTool、WriteFileTool、EditFileTool:用于直接访问记忆文件,用户可以通过标准文件操作技能间接管理记忆。这些工具对应了相关函数,比如edit_file 和 write_file。

      Write important facts immediately using `edit_file` or `write_file`:User preferences ("I prefer dark mode")Project context ("The API uses OAuth2")Relationships ("Alice is the project lead")
    • exec 工具:用于运行 grep 搜索历史记录

  • _SAVE_MEMORY_TOOL:后台自动执行的 记忆归档/整合 机制
    • _SAVE_MEMORY_TOOL 是内部使用的工具,专门用于记忆合并
    • 当会话达到 memory_window 阈值时,系统自动调用 MemoryStore.consolidate() 此方法内部调用 LLM,传递 _SAVE_MEMORY_TOOL 工具定义

我们举例如下。

5.1 文件系统工具

class WriteFileTool(Tool):    """Tool to write content to a file."""    def __init__(self, workspace: Path | None = None, allowed_dir: Path | None = None):        self._workspace = workspace        self._allowed_dir = allowed_dir    @property    def name(self) -> str:        return "write_file"    @property    def description(self) -> str:        return "Write content to a file at the given path. Creates parent directories if needed."    @property    def parameters(self) -> dict[strAny]:        return {            "type""object",            "properties": {                "path": {                    "type""string",                    "description""The file path to write to"                },                "content": {                    "type""string",                    "description""The content to write"                }            },            "required": ["path""content"]        }    async def execute(self, path: str, content: str, **kwargs: Any) -> str:        try:            file_path = _resolve_path(path, self._workspace, self._allowed_dir)            file_path.parent.mkdir(parents=True, exist_ok=True)            file_path.write_text(content, encoding="utf-8")            return f"Successfully wrote {len(content)} bytes to {file_path}"        except PermissionError as e:            return f"Error: {e}"        except Exception as e:            return f"Error writing file: {str(e)}"class EditFileTool(Tool):    """Tool to edit a file by replacing text."""    def __init__(self, workspace: Path | None = None, allowed_dir: Path | None = None):        self._workspace = workspace        self._allowed_dir = allowed_dir    @property    def name(self) -> str:        return "edit_file"    @property    def description(self) -> str:        return "Edit a file by replacing old_text with new_text. The old_text must exist exactly in the file."    @property    def parameters(self) -> dict[strAny]:        return {            "type""object",            "properties": {                "path": {                    "type""string",                    "description""The file path to edit"                },                "old_text": {                    "type""string",                    "description""The exact text to find and replace"                },                "new_text": {                    "type""string",                    "description""The text to replace with"                }            },            "required": ["path""old_text""new_text"]        }    async def execute(self, path: str, old_text: str, new_text: str, **kwargs: Any) -> str:        try:            file_path = _resolve_path(path, self._workspace, self._allowed_dir)            if not file_path.exists():                return f"Error: File not found: {path}"            content = file_path.read_text(encoding="utf-8")            if old_text not in content:                return self._not_found_message(old_text, content, path)            # Count occurrences            count = content.count(old_text)            if count > 1:                return f"Warning: old_text appears {count} times. Please provide more context to make it unique."            new_content = content.replace(old_text, new_text, 1)            file_path.write_text(new_content, encoding="utf-8")            return f"Successfully edited {file_path}"        except PermissionError as e:            return f"Error: {e}"        except Exception as e:            return f"Error editing file: {str(e)}"    @staticmethod    def _not_found_message(old_text: str, content: str, path: str) -> str:        """Build a helpful error when old_text is not found."""        lines = content.splitlines(keepends=True)        old_lines = old_text.splitlines(keepends=True)        window = len(old_lines)        best_ratio, best_start = 0.00        for i in range(max(1len(lines) - window + 1)):            ratio = difflib.SequenceMatcher(None, old_lines, lines[i : i + window]).ratio()            if ratio > best_ratio:                best_ratio, best_start = ratio, i        if best_ratio > 0.5:            diff = "\n".join(difflib.unified_diff(                old_lines, lines[best_start : best_start + window],                fromfile="old_text (provided)", tofile=f"{path} (actual, line {best_start + 1})",                lineterm="",            ))            return f"Error: old_text not found in {path}.\nBest match ({best_ratio:.0%} similar) at line {best_start + 1}:\n{diff}"        return f"Error: old_text not found in {path}. No similar text found. Verify the file content."

5.2 _SAVE_MEMORY_TOOL

LLM 执行 _SAVE_MEMORY_TOOL 时,必须返回 history_entry 和 memory_update,分别由程序来更新  HISTORY.md 和 MEMORY.md。

# ===================== 核心常量定义 =====================# 定义save_memory工具的元数据(供LLM调用的工具描述)_SAVE_MEMORY_TOOL = [    {        "type""function",  # 工具类型(固定为function)        "function": {            "name""save_memory",  # 工具名称(唯一标识)            # 工具描述:告知LLM该工具的作用是将记忆整合结果保存到持久化存储            "description""Save the memory consolidation result to persistent storage.",            # 工具参数定义(JSON Schema格式)            "parameters": {                "type""object",  # 参数整体为对象类型                "properties": {                    # 第一个参数:历史条目(会话摘要)                    "history_entry": {                        "type""string",  # 参数类型为字符串                        "description""A paragraph (2-5 sentences) summarizing key events/decisions/topics. "                        "Start with [YYYY-MM-DD HH:MM]. Include detail useful for grep search.",                    },                    # 第二个参数:记忆更新(完整的长期记忆内容)                    "memory_update": {                        "type""string",  # 参数类型为字符串                        "description""Full updated long-term memory as markdown. Include all existing "                        "facts plus new ones. Return unchanged if nothing new.",                    },                },                "required": ["history_entry""memory_update"],  # 必填参数列表            },        },    }]

0xEE 带货

带个货,是我的图书编辑 黄爱萍 老师的最新书籍

0xFF 参考

AI Infra:多 Agent 场景需要什么样的 Context Infra 层

打造你的 Claw 帝国:OpenClaw底层原理揭秘!

OpenClaw的Memory是怎么实现的

OpenClaw 进阶指南:从 SOUL.md 到 MEMORY.md,逐层拆解智能体的"操作系统"

万字】带你实现一个Agent(上),从Tools、MCP到Skills

3500 行代码打造轻量级AI Agent:Nanobot 架构深度解析

Kimi Agent产品很厉害,然后呢?

OpenClaw真完整解说:架构与智能体内核

https://github.com/shareAI-lab/learn-claude-code

深入理解OpenClaw技术架构与实现原理(上)

深度解析:一张图拆解OpenClaw的Agent核心设计

OpenClaw小龙虾架构全面解析

OpenClaw架构-Agent Runtime 运行时深度拆解

OpenClaw 架构详解 · 第一部分:控制平面、会话管理与事件循环

从回答问题到替你做事,AI Agent 为什么突然火了?

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-04-13 23:50:52 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/524793.html
  2. 运行时间 : 0.108254s [ 吞吐率:9.24req/s ] 内存消耗:4,926.95kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=36e1d30165babd3e91dc1b9c45a2820b
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.80 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000644s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000829s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000305s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000239s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000460s ]
  6. SELECT * FROM `set` [ RunTime:0.000206s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000534s ]
  8. SELECT * FROM `article` WHERE `id` = 524793 LIMIT 1 [ RunTime:0.000580s ]
  9. UPDATE `article` SET `lasttime` = 1776095452 WHERE `id` = 524793 [ RunTime:0.002659s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000305s ]
  11. SELECT * FROM `article` WHERE `id` < 524793 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000466s ]
  12. SELECT * FROM `article` WHERE `id` > 524793 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000430s ]
  13. SELECT * FROM `article` WHERE `id` < 524793 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.006679s ]
  14. SELECT * FROM `article` WHERE `id` < 524793 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.007855s ]
  15. SELECT * FROM `article` WHERE `id` < 524793 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.005847s ]
0.110205s