|
|
f2ec46480e
|
refactor(core)!: N0 无状态化 —— 删除 Agent.currentOutputChannel,通道只跟输入事件/帧走
驻留式子 agent 设计(docs/zh/resident-subagent-design.md)的里程碑 N0。
## 问题
`a.currentOutputChannel` 是 **agent 级可变字段**,只在 prepare 段写入,而被打断任务
恢复时**不重新 prepare**(resumeTask 只 rebase 前缀)。于是中断任务 prepare 时把它
覆盖成自己的通道,被恢复的任务再把回复发到**中断任务的通道**上——两个任务串台。
后果不只是标签错:工具提示词里那句"当前输入来源通道是 X,对应输出门工具是
output_send__X"会诱导模型**把回复主动发到错误的通道**。
## 两处一起改(用户指出的两件事)
1. **内核不应持有"当前通道"**:通道是随输入事件带进来的,路由发生在**进内核之前**,
输出是 agent 的**主动调用**。删除该字段,改为一律从输入事件推导
(`outputChannelOf(evt)`)或读本任务的帧(`f.OutputChannel`)。
2. **提示词不应预设 outputch**:删掉"当前输入来源通道是 X → 用 output_send__X"那两行,
改为"不要假设当前通道是固定值;先看消息本身与上下文的来源信息,不确定时先调
output_list_channels"。
## 改动面(把通道一路显式传下去,而不是读共享状态)
- `agent.go`:删字段
- `task.go`:新增 `outputChannelOf` / `isCriticalChannel`;帧记录通道;
安全点与 setCritical 用帧/事件推导;步骤内事件标签改用 `f.OutputChannel`;
`executeToolCall(f.CurTool, f.OutputChannel)`;`callLLMWithFallback(..., f.OutputChannel)`
- `process.go`:`chatStreamWithFallback` / `accumulateStream` 增加 channel 参数
(增量事件的 channel 标签由此而来)
- `stage.go`:`runStage` 从 `ctx.Extra["output_channel"]` 读(发起方写入)
- `eventloop.go`:`emitResponse` 用 `outputChannelOf(evt)`;stageCtx 带上通道
- `spawn.go` / `toolcall.go`:`executeSpawnChild` 的 parentChannel 由调用方(帧)传入
(子任务完成通知要回到**发起这次 spawn 的那个任务**的通道)
- `distill.go`:删掉 consolidation 路径里的赋值
- `tooldefs.go`:删掉提示词里的通道预设
## 验收
- `scheduler_channel_routing_test.go`(N0 守卫):中断任务跑过之后,被恢复任务的
输出通道仍是它自己的(改前实测为 cli,期望 qq)
- `TestCriticalSection_ConsolidationMarked`:补上推导链
「输入事件 → 通道 → isCriticalChannel → scheduler.critical」的集成断言
- 全仓 `go test ./...` 37 包 ok / 0 FAIL;`-race ./internal/agent/...` 干净
- 残留 `currentOutputChannel` 引用为 0(只剩描述历史的注释)
|
2026-09-13 08:59:15 +08:00 |
|
|
|
cb32032f76
|
feat(scheduler)!: 中断/排队两类别模型 + 插件声明 L1-L3、L4 内核独占
用户澄清推翻了早期设计的三处前提,本提交按新模型重做调度核心(行为有意变化):
1) 类别由注入 API 决定,与通道名无关
- InjectInterrupt* -> TaskInterrupt(带级别,可被严格更高级中断打断)
- InjectText*/InjectInputSync*/内核自循环 -> TaskQueued(无级别,可被任何中断打断)
- 删除按通道名推断的 taskLevel():qq 走 InjectInterruptTextOpts,本就是中断
2) 级别只属于中断
- 插件在 InjectOptions.Priority 声明 L1-L3(空/非法降级 L1,声明 L4 夹到 L3)
- L4 内核独占:新增 raiseKernelInterrupt(panic / selfip);requestKernelPreempt 不夹取
- panic 现在产生一条带 kernel 标记的 L4 中断;L4 自身 panic 不再产生新 L4(防自我放大)
3) 选择结构:四容器固定次序,删除统一比较器
- immediate(抢占者立即运行)-> 中断队列 L4..L1 -> 栈顶(与队头比级别) -> 排队 FIFO
- 删除 pickTaskIndex/taskBefore 与“同级 pending 优先”补丁(根因是抢占者进了队列)
- 中断栈上界改为结构推论 = 4(= 中断级数);删除“超限转 pendingInterrupts”降级
公开 SDK(feature 分支有意新增,纯追加):InjectOptions.Priority + PriorityL1/2/3;
内核 io / proc 桥 / 插件模板同步透传。
设计稿 §2/§3/§4.1/§6.3/§9/§11/§12/§13/§15 按新模型重写。
验收:go build/vet 干净;go test ./... 37 包 ok 0 FAIL;-race 全绿;
e2e(抢占-挂起-恢复)+ 压力(200 排队 + 50 中断,L1/L2/L3 轮转)通过。
|
2026-09-13 07:00:51 +08:00 |
|
|
|
86a702b3b0
|
fix(scheduler)!: 中断栈语义(嵌套抢占 LIFO),并修掉抢占空转
用户指正:存在**中断被中断**的场景,所以被打断的现场要进**中断栈**。
我此前把 suspendPool 明确写成“不是栈、按优先级取”,是错的。
改动:
- suspendPool 改名 suspendStack,恢复纪律改为**严格 LIFO(只比栈顶)**;
栈内不做优先级重排——嵌套抢占天然使栈自底向上基础级递增,
且“后被打断的先恢复”才是栈语义。取出即弹栈。
- 修掉一个由此暴露的真 bug(抢占空转):一次抢占生效后,被挂起的原任务
会因饥饿防护提升有效级,与抢占者同级;此时若按“先到先服务”,原任务
(入队更早)会被立刻选回,抢占者永远排不到 —— 抢占等于没发生。
现在**同级时 pendingInterrupts 优先于其它两类**,保证抢占必然生效。
- 状态 DTO:SuspendPool/suspend_pool → SuspendStack/suspend_stack
- 设计稿:§2 用语更正(它**就是**中断栈)、§4.1 选择函数(候选只含栈顶 +
pending 同级优先,并说明为何必需)、§6.2/§6.3/§9/§11 用例同步
测试新增 scheduler_stack_test.go 3 项:
- 嵌套 L1→L2→L3,恢复严格 LIFO(B 先于 A)
- 只比栈顶:人为构造“栈底 L3、栈顶 L2”,必须取栈顶(区分两种实现)
- 嵌套下的深度上限
验收:agent 全量 + -race;全仓 build/vet 通过
|
2026-09-13 06:30:23 +08:00 |
|
|
|
a971fc8877
|
feat(scheduler): M5 饥饿防护(抢占计数提升有效级 + 抢占冷却)
设计依据 docs/zh/input-scheduler-design.md §9、§11.5(G1/G2)。
- Task 增加 PreemptCount / LastPreemptAt;effectiveLevel(t) =
min(L4, Level + min(PreemptCount, 2)):被抢占越多越“值钱”,
逐步追上抢占它的流,但封顶 L4 因而抢不过真正的紧急输入
- 选择函数 taskBefore 改用有效级;requestPreempt 与 preemptGrantedFor
同样以有效级比较
- 抢占冷却 preemptCooldown=2s:刚被抢占的任务期内不再被抢占,
避免同一任务被反复打断到永不完结
- 新增 scheduler_starvation_test.go 4 项:提升与封顶、冷却期内不得再抢占、
提升后同级不得抢占而更高可、选择函数确实用有效级
- 验收:agent 全量 + -race;全仓 build/vet 通过
|
2026-09-13 00:38:30 +08:00 |
|
|
|
7565248f61
|
feat(scheduler): M4 临界区显式化 + 清掉被 pendingInterrupts 取代的 interceptCh
设计依据 docs/zh/input-scheduler-design.md §4.3、§11.1(P5/P6)。
- 临界区语义显式化:让位检查**只在 step 之间**做,执行中的 step
(工具 RPC / ONNX / CAS 落盘)天然不可抢占;_consolidation_ 整任务
经 inCriticalSection() 判为不可抢占(它直接改图库)
- 删除 interceptCh 与 drainInterrupts:M3b 起中断一律走 pendingInterrupts,
旧的「同行注入 + 三处 drain + 批次放弃」已无写入者,属死代码
- 新增 scheduler_critical_test.go 3 项:
P5/P6 工具执行中 arm 了让位信号也不得挂起、必须等工具返回后的安全点;
_consolidation_ 判为临界区;无抢占时同批工具必须全部执行(新语义回归)
- 验收:agent 全量 + -race;全仓 build/vet 通过
|
2026-09-13 00:34:39 +08:00 |
|