跳到主要内容

记忆、主动性与多 agent 工作区

30 秒导读: 大模型本身没有跨会话的记忆,每次对话都从零开始。QwenPaw 把"记住你、主动帮你、按人隔离"这三件事补在模型之外——记忆靠把工作目录当成硬盘(markdown 日记 + 向量检索);主动性靠一个空闲检测循环让 agent 在你没说话时先开口;多 agent 靠给每个 agent 一个独立、可热重载的 Workspace。本章讲清这三块怎么搭起来的。

本章是 QwenPaw 系列的第 6 章。运行时怎么把一次请求跑完(8 阶段编排),见 01-request-lifecycle;ReActAgent 与模型组装见 02-agent-and-model;技能系统见 03-skills


1. 这是什么(零基础也能懂)

一句话定义

这一章覆盖 QwenPaw 里让 agent「有记性、会主动、能分身」的那部分子系统:记忆管理器、主动服务、定时任务、以及承载它们的多 agent 工作区。

它解决什么问题

大模型(LLM)是无状态的:你这次告诉它"我叫 taiyou、习惯用 zsh",下次开新会话它完全不记得。要做一个"越用越懂你"的私人助理,就得自己在模型外面补三样东西。

缺的能力白话QwenPaw 的补法
记性跨会话记住事实、偏好、决策记忆管理器:日记 + 长期记忆 + 语义检索
主动你不开口它也能提醒/帮忙主动循环:空闲检测 → 从记忆抽任务 → 主动发消息
分身多个性格/配置的 agent 各管各的多 agent 工作区:每个 agent 一套独立运行时

用起来什么样

  • 你在对话里说"我下周三要去北京出差"。agent 先把这条写进当天的日记文件,再回答你。
  • 半小时你都没理它。它自己查了下天气,主动发来一句"北京周三有雨,记得带伞"。
  • 你在控制台建了两个 agent:一个"工作助理"、一个"生活管家"。它们的记忆、渠道、定时任务互不干扰,改其中一个的配置不会打断另一个正在跑的对话。

一句话直觉

把工作目录当磁盘、把上下文窗口当内存。 每个 agent 有自己的一间"工作室"(workspace 目录):MEMORY.md 是它精挑细选的长期记忆,memory/2026-07-23.md 是当天的流水日记,向量索引是"能按意思翻找这些文件"的检索层。会话结束、内存清空,但工作室里的文件还在——下次进来接着用。

本节到此不碰代码。记住这张心智图,后面每一层都在把它做实。


2. 顶层全景(三条暗线怎么拧在一起)

先给全景,再逐块下钻。QwenPaw 的这部分可以看成三条并行的暗线,都挂在同一个 Workspace 上。

怎么读这张图

从左到右是"谁属于谁":一个 MultiAgentManager 管多个 Workspace,每个 Workspace 内部挂着记忆、主动、定时三套东西,它们都读写同一个工作目录。

┌──────────────────────────┐
│ MultiAgentManager │ 懒加载 / 热重载 / 并行启动
│ (WorkspaceRegistry) │
└───────────┬──────────────┘
│ 每个 agent 一个
┌─────────────────┼─────────────────┐
▼ ▼ ▼
Workspace(A) Workspace(B) Workspace(...)

┌──────────┼───────────┬────────────┬───────────┐
▼ ▼ ▼ ▼ ▼
记忆管理器 主动循环 Cron管理器 渠道/驱动 插件注册表
(memory) (proactive) (crons) (见04章) (per-workspace)
│ │ │
└──────────┴─────┬─────┘

工作目录 workspace_dir/
MEMORY.md · memory/*.md · digest/ · jobs.json · agent.json

部件一句话职责

部件干什么在哪个文件
MultiAgentManager按 agent_id 懒加载 Workspace,负责热重载src/qwenpaw/app/multi_agent_manager.py:23
WorkspaceRegistry给新 Workspace 注入内置插件 + 共享服务src/qwenpaw/app/workspace_registry.py:24
Workspace单个 agent 的完整运行时容器src/qwenpaw/app/workspace/workspace.py:39
BaseMemoryManager记忆后端的抽象契约src/qwenpaw/agents/memory/base_memory_manager.py:19
ReMeLightMemoryManager默认记忆后端(工作目录即记忆库)src/qwenpaw/agents/memory/reme_light_memory_manager.py:55
ADBPGMemoryManager向量库长期记忆后端src/qwenpaw/agents/memory/adbpg_memory_manager.py:33
proactive 三件套空闲触发 + 从记忆抽任务 + 主动发消息src/qwenpaw/agents/memory/proactive/
CronManager定时任务调度 + 内置 heartbeat/dream 作业src/qwenpaw/app/crons/manager.py:52
ServiceManager按优先级启停 Workspace 内所有服务src/qwenpaw/app/workspace/service_manager.py:79
两个 ConfigWatcher监控 agent.json / 驱动卡片,触发热更新agent_config_watcher.py:44driver_config_watcher.py:26

主线走一遍(高层,不进代码)

  1. 请求进来 → MultiAgentManager.get_agent(agent_id) 拿到(或懒创建)对应 Workspace
  2. Workspace 里的记忆管理器在系统提示里注入"你有记忆、该怎么用",在回答前/后择机检索与抽取(时机由 Ch01 的中间件编排)。
  3. 你不理它时,主动循环每 30 秒探一次;空闲够久就从记忆里抽任务、执行、主动发消息。
  4. 到点(cron)时,heartbeat/dream 作业触发——heartbeat 把 HEARTBEAT.md 当查询跑一遍,dream 让记忆管理器做一次"整理归档"。

三条暗线的共同落点都是那个工作目录。下面逐条讲。


3. 记忆:从"每次会话都是全新"到"越用越懂你"

本节讲 QwenPaw 怎么给无记性的模型补上记性。先讲抽象契约,再讲两个真实后端,再讲"markdown 即记忆"的巧思。

3.1 抽象层:BaseMemoryManager 契约

它要解决的小问题: 记忆后端可能是本地文件、也可能是云端向量库,运行时不想为此写两套代码。所以先定一份契约。

BaseMemoryManagerbase_memory_manager.py:19)是抽象基类,规定了每个后端必须实现的四个方法,外加几个可选钩子:

方法什么时候被调用作用
start() / close()Workspace 启停初始化 / 释放后端
get_memory_prompt()组装系统提示时返回"你有记忆、怎么用"的引导语
list_memory_tools()构建工具箱时暴露给 agent 的记忆工具(如 memory_search
get_auto_memory_interval()中间件判断抽取节奏每 N 轮自动抽一次记忆,0 表示关
summarize() / auto_memory() / auto_memory_search() / dream()回答前后 / 定时抽取、检索、整理(可选,基类默认空实现)

一个巧思——串行化的后台抽取队列。 记忆抽取要调 LLM,慢且不能阻塞回答。基类内置一个 FIFO 队列 + 单 worker:

# base_memory_manager.py:186 add_summarize_task(示意精简,非逐字源码)
def add_summarize_task(self, messages, **kwargs):
if self._worker_task is None or self._worker_task.done():
self._worker_task = asyncio.create_task(self._summarize_worker())
task_id = f"task_{self._task_counter}"
self._task_queue.put_nowait((task_id, messages, kwargs)) # 入队即返回,不阻塞

重点看:入队即返回,真正的 summarize()_summarize_workerbase_memory_manager.py:162)串行取出执行——保证多次抽取不会并发打架、也不拖慢主线回答。

后端注册与回退。 后端用装饰器注册进 memory_registrybase_memory_manager.py:271)。get_memory_manager_backend():274)按名字取类,取不到就回退到第一个注册的后端并告警。由于 __init__.py 先导入 ReMe、后导入 ADBPG(memory/__init__.py:8-11),第一个注册的是 "remelight",所以未知后端名会回退到 ReMe。

3.2 ReMe 后端(默认):把工作目录当记忆库

思路: 默认后端 ReMeLightMemoryManager(注册名仍叫 "remelight"reme_light_memory_manager.py:55)把 QwenPaw 的工作目录直接当成 ReMe(一个记忆库框架)的"vault",把"写日记、检索、抽取、做梦整理"都委托给 ReMe 的 job 框架。

它长这样(数据流):

用户消息 ──► auto_memory job ──► 写入 memory/YYYY-MM-DD.md(当天日记)

后台 index_update_loop 监控文件变化 ──► 更新混合索引(向量 + BM25)

memory_search 工具 ──► search job ──► 向量+BM25 RRF 融合排序 ──► 命中片段

几个关键实现点:

  • 嵌入式而非独立进程。 ReMe 正常靠 YAML 起独立 CLI;QwenPaw 把它当进程内应用,直接把等价配置字典塞给它(reme_config.py:16 build_reme_app_config)。工作目录、各子目录、语言、时区都在这里映射进去。
  • 复用 QwenPaw 的模型。 ReMe 需要 LLM 做抽取。_update_qwenpaw_model()reme_light_memory_manager.py:164)在启动/跑 job 前,把 QwenPaw 当前激活的模型对象注入 ReMe 的 as_llm 组件——记忆抽取和主对话用同一个模型,不用另配。
  • 混合检索。 search job 配的是 vector_weight: 0.7 的向量 + BM25 RRF 融合(reme_config.py:135-169)。这也是 memory_search 工具(reme_light_memory_manager.py:329)为什么把 min_score 默认设 0、并在文档里提醒别乱调高的原因——两种分数量纲不同,调高会误杀关键词命中。
  • 抽取即入队。 auto_memory():470)不直接抽,而是走 3.1 的 add_summarize_task,把真正的 auto_memory job(summarize(), :375)丢进串行队列。
  • 结果回执进收件箱。 auto_memory/auto_dream/auto_resource 这类后台 job 跑完,会把结果推进 QwenPaw 的 inbox(_append_reme_job_result_to_inbox, :227)——而且"没改动记忆"(modified is False)时故意不推,避免刷屏。

3.3 ADBPG 后端:向量库长期记忆

思路:memory_manager_backend="adbpg" 时启用 ADBPGMemoryManageradbpg_memory_manager.py:33),把长期记忆落到阿里云 AnalyticDB for PostgreSQL(向量库)。适合多端共享、规模化。

和 ReMe 的三点不同:

  1. 每轮都存。 get_auto_memory_interval() 直接返回 1:158)——因为事实抽取在数据库服务端做,客户端只管每轮把用户消息推过去。
  2. 发射即忘 + 守护线程。 存储走 _fire_and_forget_add:324),在 daemon 线程里调 client.add_memory,不阻塞事件循环。
  3. 检索是双源合并。 memory_search:224同时查两处:ADBPG 语义检索,加上本地 MEMORY.md / memory/*.md 的关键词匹配(_search_local_memory_files, :347)。即便向量库暂时不可用,本地文件仍兜底。

隔离维度由 memory_isolation 决定:开则每个 agent 用自己的 agent_id 作命名空间,关则所有 agent 共享 "shared":75)。连接细节在 adbpg_client.py(支持 sql/rest 两种 api_mode)。

3.4 markdown 即记忆:AGENTS.md / MEMORY.md 与路径安全

这是 QwenPaw 记忆观里最"人味"的一层。 它不把记忆藏在数据库里,而是就用给 agent 看的 markdown 文件本身当记忆——人能直接读、直接改。

记忆引导提示(prompts.py:8 MEMORY_GUIDANCE_ZH_TEMPLATE)会告诉 agent 一套明确的分工:

文件角色白话
memory/YYYY-MM-DD.md每日笔记当天发生了什么的原始流水
MEMORY.md长期记忆精选提炼的精华,像人的长期记忆
PROFILE.md用户资料名字、偏好、习惯
AGENTS.md经验教训犯过的错、学到的规矩,写给未来的自己

提示里反复强调两条行为准则(prompts.py:27-47):"写下来,别只记在脑子里"(会话重启内存就没了),以及**"主动记录——先记下再回答"**(别总等用户说"记住这个")。这正是"越用越懂你"的行为学基础。

文件读写靠 AgentMdManageragent_md_manager.py:11,它区分工作目录 md 和记忆目录 md,并把路径安全做得很硬——因为这些路径可能来自模型输出,必须防目录穿越:

  • _sanitize_md_name:51):拒绝含 .. 或路径分隔符的文件名。
  • _normalize_md_path:81):归一化并强制 .md 后缀。
  • _assert_within_dir:92):用 Path.resolve() 跟随符号链接后,断言最终路径仍在允许目录内,否则抛错。

三道关卡叠起来,保证"AI 说要写 ../../etc/passwd"这类输入落不到目录外。

3.5 注入与抽取的时机(转指 Ch01)

记忆不是自己动的,是被中间件在请求生命周期里择机触发的。记忆管理器通过 build_middlewares()base_memory_manager.py:76)贡献一个 MemoryMiddleware,它挂在四个钩子上:

钩子干的事对应记忆方法
on_system_prompt注入记忆引导语get_memory_prompt()
on_model_call回答前自动检索auto_memory_search()
on_reply攒够 N 轮就抽取一次auto_memory()
on_compress_context压缩上下文前顺手抽summarize_when_compact

这几个钩子在 8 阶段编排里的确切位置、以及 POST_RESPONSE 阶段怎么串联,属于运行时编排,详见 01-request-lifecycle。本章只需知道:记忆的"注入"和"抽取"是中间件驱动的,不是记忆管理器自己定时跑的(定时那条线是下面的 cron)。


4. 主动性:agent 何时不等用户先开口

本节讲 agents/memory/proactive/ 那套东西——让 agent 在你沉默时,基于记忆主动做点事、再发消息。

4.1 触发:一个空闲检测循环

它要解决的小问题: "主动"不能瞎主动,得等你真的空闲、且上一条主动消息你没回,才值得再开口。

会话开启主动模式后(proactive_trigger.py:27 enable_proactive_for_session,默认 idle_minutes=30),起一个后台循环 proactive_trigger_loop:218每 30 秒探一次

每 30s:
agent 在忙?(has_active_tasks) ── 是 → 跳过
距上次用户消息 < idle_minutes ? ── 是 → 跳过
上条已是未回复的主动消息 ? ── 是 → 跳过(别追问)
60s 冷却内 ? ── 是 → 跳过
── 全部通过 → 触发一次主动响应

判据分散在三处:_should_trigger_proactive:125,算空闲时长)、_handle_proactive_trigger:148,管冷却和"上条是不是没回的主动消息")、is_last_message_proactive:61,靠消息里的 [PROACTIVE] 标记识别)。设计上处处从严跳过——宁可不打扰。

4.2 响应:从记忆抽任务 → 执行 → 回话

触发后 generate_proactive_responseproactive_responder.py:41)走一条完整小流水线:

① 起一个独立的 proactive agent(自带 browser_use/read_file/shell 工具,max_iters=50)
② 建记忆上下文:近 7 天会话 + 可选桌面截图分析
③ 从上下文抽出「用户可能想要的任务」(JSON)
④ 对每个任务用工具去查/去做,自检以 [SUCCESS]/[FAILURE] 结尾
⑤ 命中一个成功的 → 生成一句面向用户的话
⑥ 通过 HTTP 自调用把这句话作为一条新消息发进会话
每一步前都检查「是否被用户新活动打断」,被打断就立刻放弃

几个要点:

  • 它用的是独立 agent,不是主对话 agent_initialize_single_proactive_agent, :101)——避免污染主会话上下文,也能配独立的迭代上限。
  • 任务抽取有结构约束_extract_tasks_from_memory:150)要求模型吐 {task, query, why} 的 JSON,解析失败还会用正则兜底捞 JSON。
  • 执行结果靠尾标判定_execute_query:192)强制回答"最后一个 token 必须是 [SUCCESS][FAILURE]",用正则 \[(SUCCESS)\]\s*$ 判成败——一种朴素但稳的自检协议。
  • 打断优先_was_interrupted:323)同时看"agent 是否变忙"和"会话是否在基准时间后被更新",任一为真就中止——用户随时能"抢回"主动权。
  • 回话是 HTTP 自调用send_proactive_message_via_http:250)用 session_id="proactive_mode:{agent_id}" POST 到 /console/chat,让主动消息走正常的回答管道(复用 Ch01 的编排),而不是绕过去凭空塞一条。

4.3 dream / interests 与 proactive 的关系

记忆管理器里还有个 auto_dream job(reme_config.py:424):扫近几天的日记,全局抽取合并"兴趣单元/话题",写进 interests.yaml。ReMe 另有 proactive job(reme_config.py:463)暴露最新兴趣话题。二者为主动服务提供"该关心什么"的素材层——dream 沉淀兴趣,proactive 循环把兴趣变成行动。dream 由 cron 触发(见 5.2),闭环就此合上。


5. 定时任务:cron 作为写回的触发器

本节讲 app/crons/。它表面是"定时跑任务",实质是把记忆整理、心跳巡检这些"写回"动作挂到时钟上——呼应 Ch01 里 POST_RESPONSE 之外的另一条触发线。

5.1 CronManager 与两个内置作业

CronManagercrons/manager.py:52)基于 APScheduler,管两类作业:

  1. 用户作业:从 jobs.json 加载的 CronJobSpeccrons/models.py:183)。任务类型分 text(定时发固定文本)和 agent(定时让 agent 跑一段查询再把回复发到渠道),由 CronExecutor.executecrons/executor.py:26)分派。
  2. 两个内置系统作业,在 start()manager.py:80)里按配置装上:
内置作业job_id触发源回调干什么
心跳_heartbeatheartbeat.every(间隔或 cron)run_heartbeat_once
做梦_dreamreme_light_memory_config.dream_cron(默认 0 23 * * *memory_manager.dream()

_dream_callbackmanager.py:621)就一行关键逻辑:

# manager.py:621 _dream_callback(示意)
async def _dream_callback(self) -> None:
await self._workspace.memory_manager.dream() # 每晚 23:00 让记忆做一次整理

这就是"cron 作为内置技能触发的写回"最直白的样子——定时器直接回接到第 3 节的记忆整理

调度细节上还有几处稳健设计:_build_trigger:510)支持 cron/一次性/间隔三种触发;_execute_once:633)用每作业信号量控并发、跑完把结果按需推 inbox;_handle_job_missed/_handle_job_max_instances 处理错过与堆积并记为 skipped

5.2 heartbeat 如何回接记忆与巡检

心跳 run_heartbeat_oncecrons/heartbeat.py:169)把工作目录里的 HEARTBEAT.md 当成一条用户查询喂给 agent,跑一遍。它先查活跃时段 _in_active_hours:95,比如只在 08:00–22:00 巡检),再按 target 决定结果去哪:

target结果去向
last发到最近一次对话的渠道
inbox只跑,把最后输出摘要推进收件箱
main/其它只跑不派发

配合 AGENTS.md/MEMORY.md 记忆,heartbeat 让 agent 能"定期自己看一眼待办、有需要就动手"——是主动性的另一条、更受控的实现路径(proactive 是空闲驱动,heartbeat 是时钟驱动)。


6. 多 agent 工作区:按 workspace 隔离与热加载

前面三条线都挂在 Workspace 上。本节讲这些 Workspace 怎么被创建、隔离、热重载。

6.1 MultiAgentManager:懒加载与并发去重

MultiAgentManagermulti_agent_manager.py:23)按 agent_id 管一堆 Workspace,核心是 get_agent():54):

  • 懒加载:第一次请求某 agent 才创建并启动它的 workspace。
  • 并发去重:多个请求同时要同一个 agent,只让第一个真去创建,其余在一个 asyncio.Event 上等(_pending_starts, :88)。
  • 细粒度锁:管理锁只在改字典时短暂持有,慢的 workspace 启动放在锁外:119)——所以 start_all_configured_agents:540)能真正并行启动多个 agent。

6.2 零停机热重载

改了某个 agent 的配置,不该打断它正在跑的对话。reload_agent()multi_agent_manager.py:321)用"先建后换"实现零停机:

① 锁外:创建并完整启动「新实例」(慢,但不挡别的 agent)
└ 先从旧实例取「可复用组件」搬进新实例(如 memory_manager/chat_manager)
② 锁内:原子替换 —— self.agents[id] = 新实例(极短临界区)
③ 锁外:优雅停「旧实例」
├ 有在跑的任务 → 后台延迟清理,最多等 60s(_graceful_stop_old_instance:204)
└ 没有 → 立即停

新请求立刻打到新实例,老的 SSE/流式任务在旧实例上跑完再回收。哪些组件能"搬过去不重建"由服务描述符的 reusable 标志决定(见 6.3)。

6.3 Workspace 与"per-workspace 归属"

Workspaceworkspace/workspace.py:39)是单个 agent 的完整运行时容器。它的服务不是硬编码 new 出来的,而是声明式注册进 ServiceManager,按优先级分组启停(_register_services, :269):

优先级服务并发?可复用/可选
5local_workspace(工具路由)
10session(会话存储)
20memory_manager / driver_manager / chat_managermemory 可复用+可选
30channel_manager(渠道)
40cron_manager(定时)
50/51agent_config_watcher / driver_config_watcher

ServiceManager.start_allservice_manager.py:176)按优先级从小到大启动,同优先级可并发;stop_all:372)反序关闭,且热重载时跳过 reusable 服务:425)——这正是 6.2 能"搬组件"的机制。注意 memory_manager 标了 optional=Trueworkspace.py:334):ReMe 依赖缺失时它可缺席,workspace 仍能启动,只是没记忆。

"归属"的关键点:per-workspace 的东西绝不跨 workspace 共享。 WorkspacePluginsworkspace/workspace_plugins.py:31)把这几样注册表都做成每个 workspace 一份:

  • slash_command_registry(斜杠命令)
  • hook_registry(8 阶段钩子)
  • tool_registry(工具)
  • prompt_manager(提示贡献者)
  • modes(agent 模式)

内置类由 WorkspaceRegistry._create_workspaceworkspace_registry.py:37)在创建后立刻 bootstrap_plugins 注入。这样两个 agent 即使加载同名插件,各自的钩子/命令也互不串味——多 agent 隔离的边界就落在这层。

6.4 配置热更新:两个 watcher

改配置文件后不用重启进程。两个轮询 watcher 负责"磁盘编辑 → 热更新":

Watcher盯什么变了怎么办
AgentConfigWatcheragent.json 的 mtime只在 channels/heartbeat 段的哈希变了时,调 reload_agent 整体热重载
DriverConfigWatcherdrivers/ 卡片快照逐个 reload_driver/delete_driver(见 04 章)

AgentConfigWatcheragent_config_watcher.py:44)有个巧思:它按段哈希判断(_channels_hash/_heartbeat_hash),像 last_dispatch 这类运行时回写不触发重载,避免"自己改自己→无限重载";触发前先 self._disabled = True 自禁用,重载后换上的是新实例带的新 watcher。


7. 配置体系:三层落盘

记忆、心跳、后端选择这些"越用越懂你"的旋钮,最终都落在配置里。QwenPaw 的配置分三层:

config.json(根) 每个 agent 只存「引用」:id + workspace_dir + enabled
└─ AgentProfileRef (config.py:1191)
│ 指向

workspace/agent.json 单个 agent 的完整配置
└─ AgentProfileConfig (config.py:1238)
└─ running: AgentsRunningConfig (config.py:978)
├─ memory_manager_backend 默认 "remelight"
├─ reme_light_memory_config (config.py:666) daily_dir/digest_dir/auto_memory_interval=5/dream_cron
└─ adbpg_memory_config (config.py:625) 向量库连接

加载靠两个带 mtime 缓存的函数:load_config()utils.py:586,根配置)和 load_agent_config()config.py:2104,agent 配置)——文件没改就直接返回缓存,避免每次读盘。save_agent_config()config.py:2238)写盘后主动失效缓存。心跳与做梦的配置读取则封装在 get_heartbeat_config()utils.py:688)和 get_dream_cron()utils.py:714)。

多 agent 下的"当前是谁"靠 contextvar 传递。 get_current_agent_id()app/agent_context.py:175)从上下文变量取当前 agent,取不到回退到激活 agent。工具函数解析相对路径要知道"我在哪个工作目录",也靠一组 contextvar(config/context.pycurrent_workspace_dir/current_session_id/current_toolkit)——因为异步并发下不能用全局变量区分 agent。时区归一化(cron 调度器要用)在 config/timezone.pynormalize_tz/detect_system_timezone)。


8. 巧妙之处(可借鉴的技术)

  • 工作目录即记忆库、markdown 即记忆。 记忆不是黑盒数据库,而是人能直接读改的 MEMORY.md/日记文件;检索层只是"能按意思翻找这些文件"。可解释、可迁移、可手改。依据:prompts.py:8agent_md_manager.py:11
  • 抽取入队、串行执行。 慢的 LLM 抽取一律进 FIFO 队列由单 worker 跑,主线回答零阻塞、也不会并发打架。依据:base_memory_manager.py:186
  • "先建后换"零停机热重载 + 组件搬运。 新实例完全起好才原子替换,旧实例带着在跑的流式任务后台回收;标了 reusable 的组件直接搬过去不重建。依据:multi_agent_manager.py:321service_manager.py:425
  • 按段哈希防重载风暴。 只有真正影响运行的配置段变了才热重载,运行时回写不触发。依据:agent_config_watcher.py:182
  • 主动性处处从严跳过。 忙/没空够久/上条没回/冷却内,任一命中就不打扰;执行中被用户新活动打断就立刻放弃。依据:proactive_trigger.py:148proactive_responder.py:323
  • 主动消息走正常回答管道。 不凭空塞消息,而是 HTTP 自调用 /console/chat,复用完整编排。依据:proactive_responder.py:250
  • cron 直接回接记忆整理。 dream 作业一行 memory_manager.dream(),把"每晚归档"挂到时钟上。依据:crons/manager.py:621

9. 边界与局限(诚实)

  • 默认记忆后端有硬依赖。 ReMe 依赖 agentscope.token 等,缺失时 memory_manager 会作为 optional 服务静默缺席——workspace 照常启动,但没有记忆。代码注释明说了这点(workspace.py:332-336)。
  • ADBPG 需要外部基础设施。 没配 host/rest_api_key 时长期记忆直接 DISABLED(adbpg_memory_manager.py:64),只剩本地文件关键词兜底。
  • 主动性是轮询,不是事件驱动。 30 秒一探、60 秒冷却,对"秒级"主动不适用;且依赖 [PROACTIVE]/[SUCCESS] 这类字符串标记,模型不守约定时判定会退化。
  • watcher 是轮询 mtime/快照,默认 2 秒间隔。 极短时间内的连续编辑可能被合并成一次重载。
  • 配置缓存按 mtime。 某些文件系统 mtime 粒度粗或被外部工具"原地"改动而 mtime 不变时,缓存可能读到旧值(load_agent_config 明确以 mtime 相等为缓存命中,config.py:2157)。
  • 记忆隔离取决于配置。 ADBPG memory_isolation=False 时所有 agent 共享 "shared" 命名空间,多 agent 场景要留意串味。

10. 代码地图(导航索引)

主题文件路径关键符号
记忆后端契约 + 串行抽取队列 + 注册表src/qwenpaw/agents/memory/base_memory_manager.pyBaseMemoryManageradd_summarize_taskget_memory_manager_backend
默认后端(ReMe,工作目录即记忆库)src/qwenpaw/agents/memory/reme_light_memory_manager.pyReMeLightMemoryManagermemory_searchsummarizedream_update_qwenpaw_model
ReMe 嵌入式配置(jobs/组件/混合检索)src/qwenpaw/agents/memory/reme_config.pybuild_reme_app_config_base_config_base_components
向量库后端src/qwenpaw/agents/memory/adbpg_memory_manager.pyadbpg_client.pyADBPGMemoryManagermemory_search_fire_and_forget_addADBPGMemoryClient
markdown 记忆 + 路径安全src/qwenpaw/agents/memory/agent_md_manager.pyAgentMdManager_sanitize_md_name_assert_within_dir
记忆引导提示(AGENTS.md/MEMORY.md 规矩)src/qwenpaw/agents/memory/prompts.pyMEMORY_GUIDANCE_ZH_TEMPLATEbuild_memory_guidance_prompt
记忆注入/抽取的中间件时机src/qwenpaw/agents/middlewares.pyMemoryMiddlewareon_system_prompt/on_model_call/on_reply/on_compress_context
主动触发循环src/qwenpaw/agents/memory/proactive/proactive_trigger.pyenable_proactive_for_sessionproactive_trigger_loop_handle_proactive_trigger
主动响应流水线src/qwenpaw/agents/memory/proactive/proactive_responder.pygenerate_proactive_response_execute_querysend_proactive_message_via_http
主动辅助(上下文/打断/类型)proactive/proactive_utils.pyproactive_types.pybuild_proactive_memory_contextis_agent_busyProactiveConfig
Cron 调度 + 内置 heartbeat/dreamsrc/qwenpaw/app/crons/manager.pyCronManager_dream_callback_heartbeat_callback_execute_once
Cron 数据模型src/qwenpaw/app/crons/models.pyCronJobSpecScheduleSpec
Cron 执行器 / 心跳crons/executor.pycrons/heartbeat.pyCronExecutor.executerun_heartbeat_once_in_active_hours
多 agent 管理(懒加载/热重载)src/qwenpaw/app/multi_agent_manager.pyMultiAgentManagerget_agentreload_agent_graceful_stop_old_instance
Workspace 容器 + 服务注册src/qwenpaw/app/workspace/workspace.pyWorkspace_register_servicesset_reusable_components
服务生命周期管理src/qwenpaw/app/workspace/service_manager.pyServiceManagerstart_allstop_allServiceDescriptor
per-workspace 注册表归属workspace/workspace_plugins.pyworkspace_registry.pyWorkspacePluginsWorkspaceRegistry
配置热更新 watcherapp/agent_config_watcher.pyapp/driver_config_watcher.pyAgentConfigWatcherDriverConfigWatcher
配置模型与加载src/qwenpaw/config/config.pyconfig/utils.pyAgentProfileConfigAgentsRunningConfigReMeLightMemoryConfigload_agent_configget_dream_cron
多 agent 上下文变量config/context.pyapp/agent_context.pycurrent_workspace_dirget_current_agent_id