让几个子代理同时开口、接龙干活、互相来往。
AstrBot 多子代理并行调度插件(原名 parallel_handoff)
| 信息 | 值 |
|---|---|
| 版本 | 3.0 |
| 更新说明 | CHANGELOG.md |
| 作者 | hypxtmc |
| 许可 | MIT |
| 要求 | AstrBot ≥ 4.26.0 |
| 工具 | parallel_handoff call_subagent task_status task_result task_stop |
你在 AstrBot 里配了好几个子代理,人格写得很细,用起来却永远是一个一个来。想让两个人在同一件事上表态,得手打两遍。A 的结论要接着给 B 用,也只能自己复制过去。至于让她们互相来往,翻遍了,没有。
缺的不是人格,是调度层。
| 想做的事 | 没有复调 | 有了复调 |
|---|---|---|
| 一件事听三个人的看法 | 挨个问三遍 | 一次调用,三段同时到 |
| A 的结论接着给 B 用 | 手动复制粘贴 | chained 接龙自动传棒 |
| 让 A 记得三天前聊过什么 | 全靠人格硬写 | 独立会话档案 + 跨轮上下文 |
一次提问,多个声部同时开口,各自成段落进聊天窗口:
你:这个方案你们怎么看,各自说说
【助手A】:可行。但第二阶段迁移没有回滚路径,我不建议一次上。
【助手B】:我只问排期。这周就要看到东西,范围砍一半,你选。
【助手C】:(从后面凑过来)那我先把文档框架搭了?反正早晚要写,省得你到时候又熬夜赶。
同一段上下文,几个人接出来不一样。每人有独立人格、记忆线和会话档案,跨轮记得自己说过什么,各说各的。
再往前一步,她们之间能过自己的日子:今天谁跟谁聊过什么、谁心情不好、谁手头压着事。你随时可以插话。
这段是我自己跑过的,名字和内容做了泛化。
21:47 你随手扔一件事进去
你:帮我把上周那份数据核对一遍,顺便看看有没有更省事的写法
没点任何人的名字。
21:47 路由层开始工作
「核对数据」命中助手A,「更省事的写法」落在助手B 的能力圈。路由层不做取舍,两个都进调度指令,relay 模式并行。
21:48 两段回复先后落到窗口
【助手A】:核对完了。第 3 张表有 14 处日期格式不一致,其余三张没问题,异常清单列在最后。
【助手B】:你那三个
for循环可以并成一个,顺便省掉两次全表扫描。改法写在下面,动手前先备份。
21:52 你补了一句
你:助手A 的异常清单,助手B 你按这个顺手把脚本改一下
链式接龙启动,助手A 的产出作为输入递给助手B。不用复制粘贴,也不用再解释「哪个清单」。
我在 AstrBot 里养了几个子代理。时间久了发现一件怪事:她们的人格越来越细,我的用法却一直停在最原始的形态,点一个,等回答,再点下一个,来回倒腾。
复调就是为这个写的,也是我自己每天在用的那一套。
适合:已经在 AstrBot 里配了两个以上子代理,希望她们能同时开口、能接龙干活、能互相来往。
不太适合:只有一个 bot、单人格用着。装了跟没装差不多,帮不上什么。
请保持「流式输出」关闭
复调的分段转发与 LLM 流式输出在 QQ REST API 上物理冲突(没有消息编辑接口),表现为卡顿后吐一大段、分段错乱、重复。想要「逐句蹦出」的效果,靠复调的分段配置实现,别开流式。
- 插件目录放进 AstrBot 的
data/plugins/ - 重启 AstrBot,或控制台热重载
- 确认 AstrBot ≥ 4.26.0
可选增强:livingmemory(不装也能完整运行)
装了 livingmemory,子代理获得跨会话长期记忆(召回、存储、主动提炼)。没装则记忆链路静默跳过,零报错,其余功能照常。
WebUI 插件配置里至少设两项:
name_display_map:子代理英文 id → 中文名,如{"agent_a": "张三", "agent_b": "李四"}- 主代理路由规则:把想交给子代理的请求导向
parallel_handoff或call_subagent
其余全有默认值。
// 并行齐奏:同时问两个人
{"calls": [{"agent_name": "agent_a", "input": "帮我看下这个函数"}, {"agent_name": "agent_b", "input": "顺便评估下性能"}], "mode": "tech"}
// 接龙:A 说完 B 接着
{"calls": [{"agent_name": "agent_a", "input": "先起个头"}, {"agent_name": "agent_b", "input": "接力"}], "call_mode": "chained"}
// 消歧:不点名,找最近说过话的人
{"message": "刚才那个问题你再说细点"}- 主代理:执棒者。负责汇总、转述(relay)或放手(direct),也独占所有写操作。
- 子代理:声部。各有人格、记忆线、会话档案;默认只带只读工具和网页搜索。
- 直发(direct):子代理的回复不经主代理转述,直接以
【名字】前缀发给用户。 - 收谱(relay):子代理回复交回主代理,由主代理统一发声。
- 双投递(both):直发的同时把完整回复也回传主代理。用户看得见子代理对主代理说了什么,主代理知情但不复述,只做增量(决策、下一步、风险提示)。
- 场景(scene):转发时附带的一句场景说明(如「深夜,书房」),让子代理知道此刻在哪、跟谁说话。
- 在场(presence):读空气仲裁记录「谁在场、该谁接话」的状态机基础。
| 调度 | 行为 | 适合 |
|---|---|---|
relay × parallel |
各声部同时开跑,结果交回主代理汇总 | 多方案对比、并行调研 |
relay × chained |
依次接力,前一位的回复作为后一位的上下文 | 流水线(查资料 → 写初稿 → 审校) |
direct × parallel |
各自直接对用户说话,分条转发 | 群聊氛围 |
direct × chained |
依次对用户说话,后面的人听得到前面的 | 日常接龙、多角色对话 |
both × parallel |
既直发用户、又把完整回复回传主代理 | 技术干活,要看见子代理对主代理说了什么 |
both × chained |
同上,按接龙顺序串行 | 接力干活,全程留痕 |
调用时传 mode: "tech"(relay+parallel)或 mode: "affection"(direct+chained)可整体切换。模式命中时以模式配置为准,显式传参不覆盖已配置的策略。
both 与 relay 的区别:relay 只回传,用户看到的是主代理的转述;both 两份都给——用户看到原文,主代理也拿到全文。主代理因此不需要也不应该再复述一遍,它的发言应当是增量。
标【实验性】的功能未经长期运行验证,行为可能随版本调整。涉及:读空气仲裁(
enable_read_air_arbitrate)、多人接龙记忆沉淀(enable_chain_memory_persist)。
命令式点名(T0 强锁):消息以 /、/、#、!、!、、 打头直接叫名字,如 /张三、/张三+李四。锁定后持续生效,之后无需重复点名。主代理忙碌时同样有效。
消息消歧:不传 calls 时只传 message,插件自动路由到最近对话中出场的子代理。
路由强制指令:LLM 请求前按配置算好路由路径并注入执行指令,工具全保留、不做软硬拦截。任务分类(plan / exec / chat / weak)随指令注入。
主代理前缀 + 分段转发:转发时自动带 【名字】 前缀;长回复自动分段;前缀可按子代理粒度开关(name_prefix_overrides);QQ 平台自带 markdown 降级(qq_md_plainify)。
跨轮上下文(ctx_engine):子代理记得跨轮聊过什么,历史以结构化 messages 注入(前缀缓存友好),超窗口按 subagent_context_max_turns 纯截断。
常驻会话落盘(session_store):每个子代理的对话线独立成档、实时落盘到 subagent_sessions/,重启不丢,保留天数可配。
后台任务(task_runner):子代理长任务不阻塞总线,并发上限、单会话限量、单轮超时全可配,配 task_status / task_result / task_stop 回收。
会话柜台三件套:/谁在 查锁、/复位 放锁回自动分派、/列表 看全部可点名成员。整句判定,主代理忙碌时可用。
读空气仲裁(arbitrate):在场状态机判断哪些子代理在场、该谁接话。二级闸门 read_air_enforce 默认关闭(observe-only);开启后执行「宁静权」真实拦截。
智能路由(router,默认关):enable_smart_router 开启后由独立小模型预判该不该转子代理,置信度阈值和超时可调。
livingmemory 记忆集成:调用子代理时自动召回相关记忆片段(recall_enabled),并为子代理过滤记忆工具,防止跨人格记忆污染。私有路径访问通过防腐层 _lm_bridge 隔离。
子代理工具循环 + 只读白名单:子代理可带工具干活(受 subagent_max_steps / subagent_tool_call_timeout 约束)。默认只读档 26 项,写和执行类工具全留在主代理。
| 分类 | 工具 |
|---|---|
| 文件读取 | safe_read dir_list dir_tree es_search rg_search text_filter file_hash file_diff file_preview safe_backups |
| 代码理解 | code_explore code_status |
| 知识网页 | astr_kb_search web_search web_fetch web_search_tavily tavily_extract_web_page |
| 只读检查 | syntax_check lint_runner config_diff |
| 只读 git | git_status git_diff git_log git_branch git_remote git_changelog |
白名单可由 subagent_tools 调整(留空回落内置默认,兼容旧键 subagent_readonly_tools)。
关系档案自动注入:子代理的 system 提示带上她与家中每个成员的关系档案(亲密度、基调、最近互动,数据源 relationships.json)。档案放在 system 固定段,逐字节确定、无时间戳,跨调用命中前缀缓存;按亲密度降序;文件缺失时退化为空段,不阻塞对话。
接龙摘要(chain_summary):chained 长接龙自动生成摘要传给下一棒,阈值和保留首尾策略可调。
个体状态随机演化(random_state):纯规则状态机。每个子代理的日常话题、关注度随机演化,同一个问题今天和明天可能由不同的人接。
离线心情注入(daily_life):用 GLM-4-Flash 离线读取近期对话,为每个子代理注入今日心情、手头事、话题域。
旁听窗:子代理直发的内容会被记录(1000 字窗口),主代理下次开口时把最近 10 分钟内子代理说过的话附进上下文。
| 工具 | 作用 | 关键参数 |
|---|---|---|
parallel_handoff |
并行/接龙调用多个子代理 | calls · mode(tech/affection)· route_mode / call_mode · background |
call_subagent |
调用单个子代理并转发回复 | agent_name · input |
task_status |
查后台任务状态 | task_id(不传列全部) |
task_result |
取后台任务结果 | task_id · timeout |
task_stop |
取消后台任务 | task_id |
| 命令 | 作用 |
|---|---|
/名字、/名字+名字2 |
点名锁定,持续生效(前缀兼容 / / # ! ! 、) |
/谁在 |
看当前锁着谁 |
/复位 |
放开锁定,回到自动分派 |
/列表 |
列出全部可点名成员 |
/热重载并行插件、/reload_parallel、/重载插件 |
热重载本插件 |
/(某某)的前缀关了、/(某某)的前缀开了 |
按子代理开关姓名前缀 |
共 84 项,按功能分组(完整定义见 WebUI 配置面板)。
核心调度:user_address · main_agent_name · route_mode · call_mode · tech_mode_config / affection_mode_config · handoff_blacklist_agents · direct_delivery_agents
前缀与转发:enable_subagent_name_prefix · enable_mainagent_name_prefix · enable_mainagent_segmented · enable_segmented_forward · min_fragment_length · fragment_interval · allow_mainagent_after_direct · forbid_pre_tool_mainagent_talk · mainagent_disable_md_split · mainagent_md_split_max_chars · mainagent_md_split_progress · name_display_map · name_prefix_overrides · qq_md_plainify
路由与指令:enable_route_directive · subagent_visibility_inject · directive_inject_mode · enable_smart_router · enable_disambiguation · router_provider_id · router_confidence_threshold · router_timeout · subagent_reply_timeout
子代理工具循环:subagent_tools · subagent_readonly_tools(旧键)· subagent_max_steps · subagent_tool_call_timeout · subagent_response_preview_chars · subagent_prefetch_enabled
跨轮上下文:subagent_context_enabled · subagent_context_max_turns
常驻会话:subagent_session_persist · subagent_session_retention_days
后台任务:subagent_task_max_concurrent · subagent_task_max_per_session · subagent_task_turn_timeout
记忆召回:recall_enabled · recall_default_k / recall_max_k · exclude_agents
场景与生活:enable_scene_inject · enable_daily_random_life · daily_life_provider_id · enable_read_air_arbitrate · read_air_enforce · read_air_presence_window · arbitrate_old_grudge_agent · persona_suffix_agents · chain_summary_enabled · chain_summary_model · chain_summary_threshold · chain_summary_keep_head_tail · enable_chain_memory_persist
计量:metrics_enabled · metrics_path
astrbot_plugin_parallel_handoff/
├── main.py # 入口 + 事件注册
├── dispatch.py # 核心调度:主流程、工具循环、关系档案
├── router.py # 路由层:T0 强锁 / T1 规则 / T2 小模型 / T3 兜底、消歧
├── forward.py # 分段转发、前缀注入、markdown 降级、旁听窗
├── memory.py # livingmemory 集成、工具白名单、时间感知
├── random_state.py # 个体状态随机演化
├── daily_life.py # 离线心情注入
├── directive.py # 路由强制指令
├── arbitrate.py # 读空气仲裁
├── ctx_engine.py # 跨轮上下文
├── session_store.py # 常驻会话落盘
├── task_runner.py # 后台任务
├── _lm_bridge.py # livingmemory 防腐层
├── config.py # 配置读取
├── _conf_schema.json # 配置面板定义
└── data/ # 插件自带数据
├── display_names.json # 英文 id → 中文名
├── random_state_data.json # 状态机种子
└── router_tables.json # 路由规则表
运行时数据(自动创建):
data/plugin_data/astrbot_plugin_parallel_handoff/subagent_sessions/— 会话落盘data/relationships/relationships.json— 家庭关系网(关系档案数据源)
复调开箱即用,以下三层全部可选。
第一层:自动发现(默认行为)
装完不用配。路由池自动发现,显示名未配时直接用英文 id。
第二层:词表定制(子代理较多时建议配)
data/display_names.json,英文 id → 中文显示名:
{"agent_a": "张三", "agent_b": "李四"}data/router_tables.json,路由规则表,字段全可选,缺省即安全降级:
| 字段 | 作用 | 缺省行为 |
|---|---|---|
main_token / main_token_set |
主代理专属入口词 | 仅通用词 /主代理、/主agent 生效 |
aliases |
子代理爱称映射 | 爱称不触发,全名与命令正常 |
keywords |
领域关键词 → 子代理 | 领域词不触发 |
t2_brief |
子代理职责简介(判向参考) | 自动发现兜底(用子代理公开描述) |
{
"main_token_set": [],
"aliases": {"阿张": "agent_a", "老四": "agent_b"},
"keywords": {"做饭": "agent_a", "写画": "agent_b"},
"t2_brief": {"agent_a": "日常照顾、做饭", "agent_b": "画画、创作"}
}第三层:主代理人格配合(进阶,可选)
插件自身已注入路由规范,不写人格也能工作。想让主代理更主动地路由,可在人格末尾追加:
## 路由规则
收到消息先判断意图:
- 属于某子代理职责的 → 用 parallel_handoff 路由(插件会注入执行规范)
- 用户点名的("让XX看看"或 /XX)→ 路由给对应子代理
- 自己就能答的(闲聊、常识)→ 直接回复三层是叠加的:不配靠自动发现保底,配词表命名更准,配人格路由意识更强。
| 模块 | 规模 | 职责 |
|---|---|---|
main.py |
366 行 | 插件入口、事件注册 |
dispatch.py |
1849 行 | 核心调度主流程、去重守卫、工具循环、关系档案注入 |
router.py |
1779 行 | 四层判向、消歧、场景判定 |
forward.py |
1155 行 | 分段转发、主代理前缀、markdown 降级、旁听窗记录 |
memory.py |
667 行 | livingmemory 集成、工具白名单、时间感知 |
random_state.py |
428 行 | 个体状态随机演化 |
directive.py |
373 行 | 路由强制指令构建与注入 |
arbitrate.py |
328 行 | 读空气仲裁 |
task_runner.py |
295 行 | 后台任务执行 |
session_store.py |
224 行 | 会话落盘 |
_lm_bridge.py |
225 行 | livingmemory 防腐隔离 |
config.py |
206 行 | 配置读取与默认值 |
daily_life.py |
174 行 | 离线心情注入 |
ctx_engine.py |
116 行 | 跨轮上下文 |
一次 direct × parallel 调用的数据流:
用户消息
→ router:T0 强锁?T1 规则命中?T2 小模型?T3 兜底主代理
→ dispatch:构建 calls、并发派发
→ 每声部:system(人格 + 关系档案 + 纪律 + 任务卡)
+ 上下文(ctx_engine / session_store)
+ 工具循环(只读白名单)
→ forward:前缀注入 → 分段转发 → 旁听窗记录
→ 用户看到多声部发言
python3 test_plugin.py # 全量测试(需要 AstrBot 的 venv 环境)
<astrbot>/venv/bin/python3 test_plugin.py # 或指定解释器- 测试文件:
test_plugin.py(主套件)、test_lm_bridge.py、test_task_runner.py、test_session_store.py、test_task_integration.py - 改完代码:先
python3 -m py_compile自查,再热重载插件,用plugin_list+ 日志验证 - 插件级热重载:命令
/热重载并行插件,或控制台插件管理 - 提交前跑全量测试确认零回归
主代理和子代理什么区别? 主代理负责调度、汇总、对外发声,独占所有写权限(改文件、跑命令、提交代码)。子代理有人格、记忆线、自己的会话档案,能看能查能搜,不能动手写。
为什么子代理默认没有写权限?
子代理是并行执行的,写操作容易互相踩踏;写权集中在主代理,责任链清晰。确实需要时可通过 subagent_tools 单项授权。
默认为什么是 direct 路由?
日常陪伴场景下子代理直接对用户说话更自然。技术干活时调用传 mode: "tech" 切到 relay。
改了代码怎么生效?
插件级热重载即可(/热重载并行插件)。全局配置变更需重启 AstrBot。
会消耗很多 token 吗? 调度本身开销很小,主要消耗在各子代理的对话与工具循环。关系档案放在 system 稳定层、逐字节确定,跨调用命中前缀缓存,不逐轮重复计费。
子代理能互相聊天吗? 能。主代理用 chained 接龙就能让子代理接力对话。
怎么做多人群聊氛围?
direct 路由 + parallel 调用,多个子代理同时直发,各有前缀不串音。配合 affection 模式更好。
开了流式输出后分段失效、消息乱序? 关掉流式。这是已知限制,流式与分段转发在 QQ REST API 上物理冲突。保持流式关闭即可。
没装 livingmemory 会怎样? 什么都不用做。记忆功能自动降级:子代理不召回也不存储长期记忆,对话功能完好,其余全部正常。装上后自动启用。
复调的好几处设计站在了社区前辈的肩膀上。按 AstrBot 官方插件开发指南 的要求,在此逐一鸣谢:
- astrbot_plugin_custome_segment_reply(作者 LinJohn8):复调的流式守卫源自该插件的「回放不抢发」思想。流式通道已逐 token 发出文本时,分段转发必须让位,否则必然重复或乱序。
- astrbot_plugin_maid_agent · 代理女仆(作者 Kalo / @Kalospacer):复调的后台任务体系借鉴了它的「前台阈值 → 超时原地转后台」非阻塞派活模型。
steer(运行中追加要求)因复调一次性工具循环的架构限制暂未实现,此处如实注明差异。 - astrbot_plugin_livingmemory(作者 lxfight):复调与它深度集成(记忆召回、存储、工具过滤、私有路径防腐层
_lm_bridge)。 - AstrBot(AstrBotDevs 团队与全体贡献者):复调首先是 AstrBot 的插件,感谢框架、官方文档与社区生态。
若清单有所疏漏,或某处借鉴的边界描述不够准确,欢迎联系作者补充修正。
license: MIT · author: hypxtmc · 原名 parallel_handoff,v2.7.1 起以「polyphony · 复调」示人