agent-Specialization/.astrion/memory/background_notification_pool.md

110 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
name: background_notification_pool
description: 当需要讨论或修复后台子智能体/后台 run_command 完成通知机制时,应该索引本记忆
---
# 后台子智能体 / 后台 run_command 通知机制问题(待修复)
## 问题描述
后台子智能体和后台 `run_command` 在“主智能体停止输出后完成”的场景下,通知机制存在串行化问题:
- 若同时有多个后台任务在运行,它们不会随完成逐个通知。
- 而是在所有后台任务全部完成后,按时间顺序一条一条地发送 user 消息。
- 且每发送一条 user 消息就要触发一次“工作 → 停止 → 再工作”的循环,导致通知被拆成多次停止输出。
## 当前机制
运行期间inline和停止输出后polling是两条链路
### 1. 运行期间inline当前正常
- 位置:`server/chat_flow_tool_loop.py` 中 `execute_tool_calls` 末尾。
- 调用:
- `process_sub_agent_updates(..., inline=True)`
- `process_background_command_updates(..., inline=True)`
- 行为:每次工具循环结束时,把**当前所有已完成且未通知**的任务一次性插入 `messages`,不触发新任务。
- 结论:运行期间的批量通知已经是“池化”的,暂无问题。
### 2. 停止输出后polling问题所在
- 位置:`server/chat_flow_task_main.py` 中 `handle_task_with_sender` 结尾。
- 调用:
- `poll_sub_agent_completion(...)`
- `poll_background_command_completion(...)`
- 行为:
- 两个轮询器各自由 `socketio.start_background_task(...)` 启动。
- 每轮循环只处理**一个**完成的通知。
- 处理完一个通知后,创建一个新的 chat task然后 `return`
- 下一个通知必须等待这个新 chat task 跑完,并再次走到 `handle_task_with_sender` 结尾重新 spawn 轮询器时才会发出。
## 根因
后台任务管理器本身已经扮演了“后台池 + 通知池”的角色(任务完成但 `notified=False` 就是待通知状态)。
但轮询器是 **one-shot** 的:每次只消费一条通知就退出,导致通知被串行化到多次“工作 → 停止 → 再工作”循环中。
## 讨论中的设计方案
统一为“通知池轮询器”:
1. 合并 `poll_sub_agent_completion``poll_background_command_completion` 为一个统一的 `poll_completion_notifications`
2. 每次轮询时,同时从以下两处取出**所有**待通知项:
- `sub_agent_manager.poll_updates()`
- `background_command_manager.poll_updates(conversation_id=...)`
3. 把所有待通知项作为 user 消息插入对话历史(倾向保留为多条消息,而非合并成一条)。
4. 只创建 **一个**后续 chat task 来触发继续工作。
5. 轮询器返回,由这个新任务在结束时继续下一轮轮询。
### 期望行为示例
- 3 个后台子智能体运行中,主智能体停止输出。
- 第 1 个完成 → 通知池有 1 条 → 插入 1 条 user 消息 → 触发 1 个新任务。
- 新任务处理期间,第 2、3 个完成 → 进入通知池。
- 新任务停止输出 → 轮询器发现通知池还有 2 条 → 一次性插入 2 条 user 消息 → 再触发 1 个新任务。
- 不再有后台任务时,轮询器直接退出。
## 涉及文件
| 文件 | 角色 |
|---|---|
| `server/chat_flow_task_support.py` | 新增统一轮询器 `poll_completion_notifications`;可废弃旧的两个轮询函数。 |
| `server/chat_flow_task_main.py` | 在 `handle_task_with_sender` 结尾,把分别 spawn 两个轮询器的逻辑替换为 spawn 一个统一轮询器。 |
| `modules/sub_agent/manager.py` | 提供 `poll_updates()`,基于 `notified` 标志表示待通知状态。 |
| `modules/background_command_manager.py` | 提供 `poll_updates()``has_pending_for_conversation()`。 |
## 待决策点
- 是否坚持把通知拆成多条 user 消息插入历史(推荐),还是合并为一条。
- 统一轮询器是否做成“持久单例”还是保持 one-shot batch当前讨论倾向 one-shot batch由新任务继续轮询
## 状态
已实施2026-06-24方案 A + one-shot batch
### 落地实现
- `server/chat_flow_task_support.py` 未改;新增逻辑集中在 `server/chat_flow_task_main.py`
- 新增统一轮询器 `poll_completion_notifications`,替换并删除旧的 `poll_sub_agent_completion` / `poll_background_command_completion`
- 新增 `_collect_pending_completion_notices`:每轮一次性从子智能体 + 后台命令两路取出所有待通知项(取出即就地标记 `notified` / `_announced_sub_agent_tasks`),按 `updated_at` 跨两路排序。
- 新增 `_has_pending_completion_work`:判断是否还有运行中/待通知的后台工作,决定轮询是否继续。
- 新增 `_build_sub_agent_notice_text`:抽出子智能体通知文本构建。
- `handle_task_with_sender` 结尾:保留 `sub_agent_waiting` 事件探测,但把「分别 spawn 两个轮询器」改为只 spawn **一个** `run_completion_poll`。**这也顺带消除了两个轮询器同时 `create_chat_task``create_chat_task` 单工作区互斥(`models.py` RuntimeError的隐患。**
### 方案 A 的「多条 + 单 task」如何落地
`create_chat_task` 只收单条 message`handle_task_with_sender` 开头无条件 `add_conversation`。因此:
- 一批取出 N 条:前 N-1 条为「前置通知」,最后 1 条作为后续 chat task 的触发消息。
- `_dispatch_completion_user_notice` 新增 `preceding_notices` 参数:
- 通过 `_persist_and_echo_preceding_notice` 把前 N-1 条**写入对话历史**`add_conversation`,落盘)+ **socketio 回显**(在线客户端即时可见),`starts_work=False`(渲染为完成态气泡,不起独立计时器)。
-`preceding_user_notices` 塞进 `session_data`,传给后续任务。
- `server/tasks/models.py``_run_chat_task`:在补发触发 user_message 事件**之前**,先按顺序把 `preceding_user_notices` 逐条 `_append_event("user_message", ...)` 注入任务事件流——保证 REST 轮询客户端按时间顺序看到所有完成通知。
- 触发消息(最后 1 条)走原有 `auto_user_message_event` 链路,正常 `starts_work=True` 启动一轮工作。
### 验证
- `python3 -m py_compile server/chat_flow_task_main.py server/tasks/models.py` 通过(注意本机 pycache 目录无写权限,需 `PYTHONPYCACHEPREFIX=$TMPDIR/pycache`)。
- `python3 -m unittest test.test_server_refactor_smoke` 6 用例通过。
- 前端未改:复用既有 `user_message` / `sub_agent_notice` 渲染链路(`messaging.ts` handleUserMessage + socketio `user_message`dedup 与单通知场景一致。