mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-23 10:28:06 +00:00
feat(scheduler): 主 agent 忙时把积压任务自动转投给驻留子
问题(2026-09-19 线上实测):主 agent 被长任务占住时(现场:12 分 8 秒、69 次 工具调用),后来到达的消息全部以 level insufficient 排进中断队列干等 —— 同级 中断不能抢占同级运行任务(canPreempt),只能等前一个跑完。而内核本有驻留子 (独立 agent + 独立调度器)可并行干活。 行为(用户 2026-09-19 明确要求): - 触发:运行任务持续 > offload_busy_after(5m) 且积压 >= offload_min_pending(3) - 拉起/复用「转投专用」驻留子,把积压的纯排队输入转投过去 - 在原队列位置留下说明「[系统] N 条积压任务已转投给驻留子 agent X 处理…」 通道配置(按用户口径,与人工创建的子刻意不同): - 不配 inputch(内核的干活 agent,不接收插件用户输入) - 持有全部输出通道(结果要能发回 qq/webui 等正确通道) 三个设计要点(都是实测撞出来的,写进代码注释与设计文档 §7.1): 1. 检查必须在**独立 goroutine**:schedulerLoop 同步执行任务,放它里面在 「正忙」期间根本回不到循环顶部 ⇒ 永不触发(我第一版就写错了,测试才发现)。 2. 只转投 TaskQueued 纯排队输入:中断任务带级别语义、self 任务与父的记忆面绑定。 3. 转投失败/关闭时必须把任务**放回队列前端**:吞一条输入比多处理一条更糟。 这是设计 §7「决策在父的模型手里」的**刻意例外**(父正忙、物理上无法决策, 而积压任务本来就是空的),已在文档中显式记录,且默认关闭、由部署方显式打开。 测试 11 条:只取排队输入 / 不足量不取 / 放回不丢任务 / 说明自解释 / 默认关闭 / 空闲不触发 / 端到端转投 / 上限不增殖 / 独立 goroutine 确实会触发。
This commit is contained in:
@ -340,6 +340,40 @@
|
||||
|
||||
- **创建/销毁/回收/查看/发送**是**父可调用的原语(工具)**;**决策**(压还是收、收哪些)
|
||||
在父的模型手里 —— 内核不替父决定。
|
||||
|
||||
### 7.1 例外:积压任务自动转投(内核主动拉起)[已定 · 唯一例外]
|
||||
|
||||
**背景(2026-09-19 线上实测)**:主 agent 被一条长任务占住时(当天现场:12 分 8 秒、
|
||||
69 次工具调用),后来的 QQ 消息全部以 `level insufficient` 排进中断队列干等 ——
|
||||
同级中断不能抢占同级运行任务(`scheduler.canPreempt`),只能等前一个跑完。
|
||||
而内核明明有驻留子(独立 agent + 独立调度器 + 共享输出通道视图)可以并行干活。
|
||||
|
||||
**行为**:当运行任务已持续超过 `core.agent.offload_busy_after`(默认 5m)
|
||||
**且**排队输入积到 `offload_min_pending`(默认 3)条时,内核:
|
||||
1. 拉起(或在 `offload_max_residents` 内复用一个)**转投专用驻留子**;
|
||||
2. 把积压的**纯排队输入**转投给它;
|
||||
3. 在原队列位置留下一条说明:`[系统] N 条积压任务已转投给驻留子 agent X 处理…`。
|
||||
|
||||
**转投驻留子的通道配置(刻意与人工创建的子不同)**:
|
||||
- **不配 inputch**:它是内核的干活 agent,不接收任何插件的用户输入;
|
||||
- **持有全部输出通道**(`AllowedOutputs` 为空 = 完整授权):它必须能把结果发回
|
||||
qq/webui 等正确通道(否则干活结果无处可去)。
|
||||
|
||||
**为什么这是对上述原则的例外,且可接受**:父此刻正忙(物理上无法做决策),
|
||||
而积压任务**本来就是空的** —— 转投只是把「排队干等」换成「有人在做」,
|
||||
不改变任何已提交决策的语义。若不做例外,这个能力就只能由父的模型发起,
|
||||
而它恰恰是忙不过来的那个。
|
||||
|
||||
**默认关闭**(`core.agent.offload_enabled=false`):它改变的是系统行为而非修 bug,
|
||||
按「显式才是特权」(与 `scheduler.DefaultLevel` 同一条理由)由部署方打开。
|
||||
|
||||
**不做的事(边界)**:
|
||||
- 只转投 `TaskQueued` 纯排队输入。中断任务带级别语义(转投会打乱中断阶梯)、
|
||||
self 任务是内核内部记账(与父的记忆面绑定)—— 两者都不动。
|
||||
- 只对**根 agent** 生效:子再去拉孙子会形成无界增殖,而积压的源头是根那条链。
|
||||
- 实现上检查跑在**独立 goroutine**:`schedulerLoop` 是同步执行的,
|
||||
放在那里在「正忙」期间根本不会回到循环顶部(等于永不触发)。
|
||||
|
||||
- **默认完整授权**[已定]:子默认拿到全部插件与工具(含输出门);
|
||||
父可在创建时**收窄**(收窄工具子集、收窄可用输出通道集合)。
|
||||
⚠️ 默认含输出门意味着**子可以直接对用户通道发消息**;若要默认收窄,改一处默认即可。
|
||||
|
||||
Reference in New Issue
Block a user