From fc41e160dbd5cc2e8a33e3e7eb58b499fb951450 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Sun, 13 Sep 2026 08:39:40 +0800 Subject: [PATCH] =?UTF-8?q?docs(resident-subagent):=20=E9=A9=BB=E7=95=99?= =?UTF-8?q?=E5=BC=8F=E5=AD=90=20agent=20=E8=AE=BE=E8=AE=A1=E7=A8=BF?= =?UTF-8?q?=EF=BC=88=E8=BD=BB=E9=87=8F=E5=86=85=E6=A0=B8=20=C2=B7=20?= =?UTF-8?q?=E4=B8=A4=E7=BA=A7=E8=AE=B0=E5=BF=86=20=C2=B7=20=E7=88=B6?= =?UTF-8?q?=E5=AD=90=E4=B8=AD=E6=96=AD=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把用户口述的设计固化成文档,与已落地的 input-scheduler-design.md 配套。 定下来的核心: - inputch = 对「中断输入/排队输入」两者的高层抽象,是**路由与分配**单位 (路由发生在进内核之前;类别与级别是每条输入的属性,不是 inputch 的属性) - 输出是 agent 的**主动调用**(output_send__{通道});内核不持「当前通道」可变状态、 提示词不预设 outputch - 通道不配对:父**划入若干输入通道** + 授权**一组可用输出通道**;输出可寻址到具体 agent - 驻留子 = **独立轻量内核**:读 temp∪main、写 temp(记忆层按 agent 作用域化 —— 这才是「轻量」的真正理由,不是砍功能) - 两条独立中断阶梯:父侧 子的主动消息=L3 / contextfull=L4;子侧 **父的消息=L4** (L4 通则:某 agent 的 L4 只属于它的内核 ⇒ isKernelLevelSource 泛化为「该 agent 的上级」; 子内部一切来源被夹到 ≤L3,所以父的消息确定能打断子) - 父对子 6 个动作:创建/发送消息/查看/压缩/回收/销毁(原语在内核、决策在父的模型); 插件与工具由父授权,**默认完整授权** - contextfull 三处置:压缩=保留(清处理表)/ 回收=取消(promote 选中的 temp 进 main)/ 销毁=立刻销毁并移除 - inputch 处理表:子持有、父 pull;主动写入优先,否则系统自动写;生命周期 = 当前上下文窗口 - 登记表 + 硬约束:父可随时销毁,父退出必须销毁全部,子不得比父活得久 - 工作式轻量子 agent **原样保留**,两类并存 含 20 个测试点(S1–S20)与 8 个里程碑(N0–N7,每步独立可验收)、 12 条待确认决策(全部带可逆默认值)。 --- docs/zh/resident-subagent-design.md | 422 ++++++++++++++++++++++++++++ 1 file changed, 422 insertions(+) create mode 100644 docs/zh/resident-subagent-design.md diff --git a/docs/zh/resident-subagent-design.md b/docs/zh/resident-subagent-design.md new file mode 100644 index 0000000..f35312a --- /dev/null +++ b/docs/zh/resident-subagent-design.md @@ -0,0 +1,422 @@ +# 驻留式子 Agent 设计(轻量内核 · 两级记忆 · 父子中断) + +> **前置**:本文建立在《输入调度器设计》(`docs/zh/input-scheduler-design.md`)之上。 +> 那里已经落地了:两类别输入(中断 / 排队)、四级中断优先级、可抢占、 +> 现场保存/恢复、中断栈(LIFO,结构上界 4 帧)、任务级回执、panic→L4。 +> 本文只描述**驻留式子 agent** 这一新能力,以及它对既有实现的改动。 +> +> 标记:**[已定]**= 用户明确拍板;**[默认]**= 本文给出的可逆默认取值,实现时在提交信息里标注。 + +--- + +## 1. 背景与目标 + +现状的子 agent 是**工作式轻量子**:`spawn_child` 起一个一次性 goroutine(临时 `msgs`), +跑完把结果经 `selfInputCh` 回投父,然后销毁。它**没有**自己的中断机制、没有通道分配、 +没有可查询的状态面,父也无法在它运行途中干预它。 + +要让父 agent 能**长期派驻**一个下属去持续处理某类工作(一个 inputch 上的来源、一段长期目标), +就需要一种新的子 agent:**驻留子**。它必须满足: + +- 有自己的**轻量内核**(自己的调度器、中断机制、上下文),所以父能"打断它"、"查它"、"回收它"; +- 有自己的**记忆空间**(可写),但**改不了父的主记忆** —— 记忆入库的决策权留在父手里; +- 与父之间有**双向、可寻址**的通道(子→父、父→指定子),且父对子的消息是**最高级中断**; +- 有一张**可被父查询的状态面**(inputch 处理表),使父不打断它也能知道它做到哪; +- 生命周期由父掌握:父可随时**销毁**它,父退出时**必须**销毁全部。 + +**两类子 agent 并存**[已定]:工作式轻量子**原样保留**(它是"内核自循环"的一部分, +不是对等 agent);驻留子是新增的第二类。 + +--- + +## 2. 术语 + +| 术语 | 含义 | +|---|---| +| **根 agent** | 进程级主 agent,拥有**完整内核**(含记忆读写、全部通道) | +| **驻留子** | 父创建、长期驻留的子 agent,拥有**轻量内核**与**临时记忆空间** | +| **工作式轻量子** | 现状 `spawn_child` 的一次性子任务(**不是内核实例**) | +| **inputch** | 对"中断输入 / 排队输入"两者的高层抽象,是**路由与分配**的单位 | +| **outputch** | 输出通道;输出是 agent 的**主动调用**,并**可寻址到具体 agent** | +| **main 空间** | 主记忆空间:父读写、所有子只读、全部子共享 | +| **temp 空间** | 子临时记忆空间:该子读写、子之间互不可见 | +| **状态面** | 子对外(对父)可查询的状态:含 **inputch 处理表**与产出 | +| **处理表** | 按 inputch 记录每轮处理信息的状态表(子持有,父 pull) | +| **登记表** | 父持有的全部子 agent 名录(id / 状态 / 通道 / 状态面句柄) | +| **contextfull** | 子的上下文窗口满,产生 L4 中断通知父 | +| **压缩 / 回收 / 销毁** | 父对 contextfull 的三种处置(见 §9) | + +--- + +## 3. 角色与内核形态 + +| | **根 agent** | **驻留子** | **工作式轻量子**(保留不动) | +|---|---|---|---| +| 内核 | 完整 | **轻量** | 无(不是内核实例) | +| 调度器 / 四级中断 / 中断栈 | ✅ | ✅(**L4 只来自父**) | ❌ | +| 上下文 | 完整(记忆介导) | 传统上下文(消息序列) | 临时 `msgs` | +| 记忆 | main 读写 | **读 temp ∪ main,写 temp** | 无 | +| 通道 | 全部 | 父**划入**的输入通道 + **授权**的输出通道 | 无(结果回投父) | +| 插件与工具 | 全部 | **父授权,默认完整授权**[已定] | 现有剔除规则不变 | +| 状态面 / 处理表 | — | ✅(父 pull) | ❌ | +| 子→父 | — | 主动消息 = **L3 中断** | `injectSelfChannel` → `selfInputCh`(排队) | +| 主→子 | — | **L4 中断**(取消当前状态 + 插入新消息) | 无 | +| 生命周期 | 进程级 | 父持登记表;父可随时销毁;**父退出必须全部销毁** | 跑完即销毁 | + +--- + +## 4. 通道模型 + +### 4.1 `inputch` 的定义 [已定] + +`inputch` **不是**通道名标签,也**不是**与 outputch 配对的东西。它是: + +1. **对"中断输入"与"排队输入"两者的高层抽象** —— 两类输入都从 inputch 进出; +2. **路由与分配的单位**: + - **路由**:一条输入投给哪个 inputch,就是"该由谁处理"的**既定事实** + ⇒ **路由发生在进内核之前**; + - **分配**:inputch 是**可分配资源**(父把输入通道划给子)。 + +「中断 vs 排队」是**每条输入自己的类别**(由注入 API 决定),级别(L1–L4)也是 +**每条输入的属性**,都不是 inputch 的属性。 + +### 4.2 三个入场(沿用现有实现) + +| 入口 | 承载 | +|---|---| +| `inputCh` | 外部客户端 / 插件的**排队与中断**输入(已预寻址) | +| `interruptCh` | 中断入站(外部 / 插件 / 内核自身) | +| `selfInputCh` | **内核自循环**:consolidation、工作式轻量子回投 | + +### 4.3 输出是**主动调用** [已定] + +- 异步通道(qq / 微信 / 群聊…):必须显式调用 `output_send__{通道名}` 才真正送达; +- 同步通道(webui / cli / 终端):返回纯文本,内核把文本交给等待方 —— + 走输入事件自带的 `ResponseCh`,这是**事前定好的回程**。 + +⇒ **内核不持有"当前通道"可变状态**,**提示词也不预设 outputch**。 +(要删 `Agent.currentOutputChannel`;要删 `tooldefs.go` 里"当前输入来源通道是 X, +对应输出门工具是 output_send__X"那两行。) + +> 之前把这件事说成"内核路由"是错的:内核只负责**投递到既定的回程**与**执行显式的输出调用**。 + +### 4.4 通道分配:不对称 [已定] + +- **不按对划分**。父为子:**划入若干输入通道** + 授权**一组可用输出通道**(授权集合,不是一对一)。 +- **输出通道可寻址到具体 agent**: + - **子 → 主**:子直接打到主(经输出通道投进主的 inputch); + - **主 → 指定某个子**:父经输出通道投进**指定子**的 inputch。 + +### 4.5 通道的一等化(实现要求) + +现在 `Source` / `OutputChannel` 只是字符串标签,`IOManager.inputCh` 是**一条全局 channel**, +`inputChannels` 只是策略表(`ChannelDef`:NoMemory / Cleaner / ContextPolicy), +**没有归属、没有绑定、没有容量**。要实现 §4.4 需要新增: + +- **通道注册层**:`inputch` 成为一等对象(归属 agent / 容量 / 可接收类别 / 输出目标解析); +- **输出通道 → 目标 agent 的 inputch** 的解析; +- **授权过滤**:`output_list_channels` 只列该 agent 被授权的通道。 + +--- + +## 5. 记忆模型:两级空间 [已定] + +``` +主记忆空间(main) ← 子【只读,可看到全部】;父【读写】;所有子共享 +子临时记忆空间(temp) ← 该子【读写】;每个子独立、互不可见 + +子的记忆查询 = temp ∪ main 子的记忆写入 → 只落 temp +``` + +| | 根 agent | 驻留子 | +|---|---|---| +| main | **读写** | **只读**(可见全部) | +| temp | —(它自己就是 main 的所有者) | **读写**(自己的空间) | +| 查询范围 | main | **temp ∪ main** | + +### 5.1 "轻量内核"的真正理由 + +不是砍功能,而是**记忆层被作用域化**: + +- **写目标**被限定到 `temp`(子自己的命名空间); +- **读视图**被扩成 `temp ∪ main`(两个空间的并集)。 + +完整内核的记忆层**硬绑定在单一 main 空间**上。要让每个 agent 都有自己的空间 + +并集读视图,记忆层就必须接受**每个 agent 一份的作用域参数** —— 这就是轻量内核存在的理由。 + +### 5.2 推论:记忆写工具不禁用,而是**重定向** + +子是**能写**的(写自己的 temp)。因此: + +- 记忆写工具(图记忆写、文本记忆写、**文档记忆写**、**向量索引写**、**媒体落盘**) + **不禁用,而是重定向到 temp 命名空间**; +- 检索注入算"读",范围 `temp ∪ main`; +- 子**不能**写 main ⇒ "哪些内容进入主记忆"的决策**结构上**只在父手里(§9.2)。 + +### 5.3 三种处置对记忆的作用 + +| 动作 | temp | main | 子 | +|---|---|---|---| +| **压缩** | 保留 | 不写 | 继续 | +| **回收** | 父读 → **选中的 promote 进 main** → 丢弃 temp | 父写 | 取消 | +| **销毁** | 直接丢弃 | 不写 | 立刻移除 | + +### 5.4 待钉的边界 [默认] + +- temp 与 main 是**同一套记忆子系统里的命名空间**(同一批表/索引 + 一个 space 维度), + 不是独立存储 ⇒ "并集读"就是一次查询里的两个 space 条件; +- 父**可读**子的 temp(要决定 promote 什么),属"查看状态面"的一部分; +- temp **与子同生共死**;子之间 temp **互不可见**。 + +--- + +## 6. 中断模型:两条独立阶梯 [已定] + +``` +在【父的】中断阶梯上: + 子的主动消息 = L3 中断 (子主动汇报,带子标识) + 子的 contextfull = L4 中断 (资源耗尽,需父立即决策,带子标识) + +在【子的】中断阶梯上: + 父的消息(发送消息) = L4 中断 ← 子的 L4 归父独占 +``` + +### 6.1 L4 归属通则 + +> **某个 agent 的 L4 只属于它的"内核"。** + +- 根 agent 的内核 = 内核自身(panic / 内核事件 selfip)+ 内核级插件(WebUI 终止按钮); +- 驻留子的内核 = **父 agent**。 + +⇒ 现有 `isKernelLevelSource`(只认编译期内置插件)**泛化为"该 agent 的上级"**,不为子开特例。 + +### 6.1.1 **父消息 = 子的 L4**(落地机制,钉死) + +父 → 子的"发送消息"是一条 **L4 中断**,它是**子的阶梯上唯一的 L4 来源**。具体落地: + +``` +父【发送消息】到指定子 + └─ 经输出通道寻址到该子的某个 inputch + └─ 在该子的调度器里按 L4 登记(子的内核级来源 = 父) + └─ 子的 L4 > 子内部一切 ⇒ 立即打断子的当前任务 +``` + +- **子内部任何来源都够不到 L4**:子自己的输入注入、工具、定时器、插件…… + 一律被夹到 **≤ L3**(与根 agent 里"外部插件被夹到 L3"完全同一条规则, + 只是"内核级来源"从"编译期内置插件"变成了"父 agent")。 +- 后果一:**子内部任何东西都压不过父**(父的话是子的最高级输入)。 +- 后果二:父的"取消当前状态 + 插入新消息"因此是**确定能生效**的 + —— 不会因子内部正在跑什么而被挡住(除非子处于不可抢占临界区, + 此时按调度器既有规则在安全点生效)。 + +**子侧内核级事件如何上报**(推论,待确认):子的 panic / contextfull 属于**子侧的内核级事件**, +但在**子的**阶梯上 L4 归父独占,所以它们不能作为"子自己的 L4";应当**上报给父**, +在**父的阶梯上以 L4(带子标识)**出现 —— 与 §9 的 contextfull 同一条通路。 +(即:父侧 L4 = 子侧内核级事件的接收位;子侧 L4 = 父控制语的发射位。) + +### 6.2 为什么 contextfull 是 L4 而不是 L3 + +它是**必须由父立刻决策**的场合(三个处置都与子的存续有关),且决策要读**一整个状态面** ⇒ +只能"**推信号 + 拉状态**",不能把状态塞进中断消息里。 + +--- + +## 7. 父对子的控制面(6 个动作)[已定:原语在内核,决策在父的模型] + +| 动作 | 语义 | 子是否继续存在 | 走哪条路 | +|---|---|---|---| +| **创建** | 划入输入通道 + 授权输出通道 + 授权插件/工具(默认完整)+ 在固定提示词之上注入任务提示词 | — | 内核原语 | +| **发送消息** | 经输出通道寻址到子的 inputch:**取消当前状态 + 插入新消息** | 是 | 对子 = **L4 中断** | +| **查看** | **pull** 子的状态面(处理表 + temp 产出),**不打断**子 | 是 | 纯查询,**不走中断** | +| **压缩** | **保留语义**:压上下文 + **清理处理表** | **是**(同一驻留子) | 见 §9 | +| **回收** | **取消语义**:父看状态面 → 选择哪些 **promote 进 main** → **取消**该驻留子 | 否 | 见 §9 | +| **销毁** | **立刻销毁并从登记表移除**(不收割) | 否 | 立即 | + +- **创建/销毁/回收/查看/发送**是**父可调用的原语(工具)**;**决策**(压还是收、收哪些) + 在父的模型手里 —— 内核不替父决定。 +- **默认完整授权**[已定]:子默认拿到全部插件与工具(含输出门); + 父可在创建时**收窄**(收窄工具子集、收窄可用输出通道集合)。 + ⚠️ 默认含输出门意味着**子可以直接对用户通道发消息**;若要默认收窄,改一处默认即可。 + +--- + +## 8. inputch 处理表 + +### 8.1 归属与方向 + +- **子是持有者**;父**主动查看(pull)**,**不是**推给父。 +- **内容**:按 inputch 记录**每一轮**子对该 inputch 的处理信息。 +- **存在意义**:长期驻留子的**进度可见性** —— 父不必打断它就能知道它做到哪。 + +### 8.2 写入规则 + +- 子**主动写入**时,系统**不**自动写; +- 子**未主动写入**时,系统**自动**把该轮 inputch 对应的信息写进去; +- ⇒ **每一轮必有记录**,父不会看到空洞。 + +[默认]"主动写入"的动作形态 = 子调用一个 `inputch_note` 类**工具**; +自动写入在轮次结束时由内核兜底。 + +### 8.3 生命周期 = 上下文窗口 + +处理表记的是"**当前这段上下文窗口**里每轮 inputch 做了什么"。 +因此: + +- **压缩必须清表**(窗口被压成摘要后,逐轮记录被摘要取代;留着会让父看到与当前窗口 + 不对应的陈旧状态); +- **回收不必清表**(表就是父刚读过的收割材料,子都没了,表自然作废); +- ⇒ 处理表天然有**大小上界**(窗口多大、表最长多长),不需要额外容量策略。 + +--- + +## 9. contextfull 的处置 + +``` +子的上下文窗口满 + └─ 产生 contextfull → **L4 中断**通知父(中断信息里标明是哪个子)—— 只推信号 + └─ 父【查看】子的状态面(处理表 + temp 产出) + ├─ 【压缩】压成摘要 → 清处理表 → 子续用 (保留语义) + ├─ 【回收】选择 temp 中哪些 promote 进 main (取消语义) + │ → 丢弃 temp → 取消该驻留子 + └─ 【销毁】立刻销毁并移除 (不收割) +``` + +| 动作 | 语义 | 子上下文/成果 | 子 agent | 处理表 | +|---|---|---|---|---| +| **压缩** | **保留** | 压成摘要 | **继续存在** | **必须清理** | +| **回收** | **取消** | 选中的 promote 进 main | **取消** | 不必清 | +| **销毁** | 立刻销毁并移除 | 不收割 | 立刻销毁 + 出登记表 | 无关 | + +[默认]压缩由**子的轻量内核自己执行**(它拥有自己的上下文与 LLM)。 + +--- + +## 10. 登记表与生命周期硬约束 + +- 父持 **agent 登记表**,记录全部子:`id / 状态 / 划入的输入通道 / 授权的输出通道 / + 授权的插件与工具 / 状态面句柄`。 +- 它是**查看 · 发送 · 压缩 · 回收 · 销毁**的寻址依据。 +- **硬约束(必须写成测试)**: + 1. 父 `Stop()` ⇒ 销毁全部子(取消运行中的任务、停轻量内核、释放其通道), + **登记表清空、不留孤儿**; + 2. **子不得比父活得久**(无孤儿 goroutine / 无悬空通道 / 无残留 temp)。 + +--- + +## 11. 并发与不变量 + +沿用输入调度器的并发模型,并按多 agent 扩展: + +- **每个 agent 一个调度器 goroutine**(根 agent 与每个驻留子各一个), + 它**独占**自己的队列 / running / 中断栈 / 帧。 +- 跨 agent 投递只经**通道**(值传递),**不共享帧**;父**永远不能**直接改子的帧。 +- **子不得比父活得久**(§10)。 +- 父的"查看"是**只读快照**,不阻塞子、不参与子的调度决策。 +- **L4 独占**:子的调度器只接受来自父的 L4(§6.1)。 + +--- + +## 12. 与现有实现的接合点(差距清单) + +| 设计项 | 现状 | 要做 | +|---|---|---| +| 两类别 + 四级中断 + 抢占/挂起/恢复/中断栈 | ✅ 已落地(见 input-scheduler-design.md) | 复用 | +| inputch 一等化(归属/容量/授权) | ❌ `inputCh` 是全局单 channel;`ChannelDef` 只是策略表 | **新增通道注册与分配层** | +| 输出通道可寻址到 agent | ❌ 只有字符串标签;`output_list_channels` 列全部 | 通道解析表 + 授权过滤 | +| 子的 L4 = 父 | ⚠️ `isKernelLevelSource` 只认内置插件 | 泛化为"该 agent 的上级"(分层) | +| 轻量内核(记忆作用域化) | ❌ 记忆层绑定单一 main 空间 | 记忆子系统加 **space 维度**;`AgentConfig` 加**作用域参数** | +| 记忆写路径重定向到 temp | ❌ 写路径无空间概念 | 所有写入口带 space;子的一切写 → temp | +| 驻留子生命周期 + 登记表 | ❌ 只有一次性 `runChildTask` | 驻留子 + 父的登记表 + 退出清理 | +| 跨 agent 投递(子→父 L3 / 父→子 L4) | ❌ 无 | 投递原语(复用注入层 + 通道寻址) | +| inputch 处理表 | ❌ 无 | 新数据结构 + 主动写入工具 + 自动写兜底 | +| contextfull 检测 | ❌ **完全没有** | 检测 + L4 通知(带子标识)+ 三处置 | +| 内核不持有"当前通道" | ❌ `currentOutputChannel` + 提示词预设 | 删字段、删预设 | +| 工作式轻量子 | ✅ | **不动** | + +--- + +## 13. 非目标(本文明确不做) + +1. 跨进程 / 跨主机的驻留子(v1 只在同进程内)。 +2. 子的**子**(驻留子再创建驻留子)——先不做,保留扩展位。 +3. main 空间的**多写者**(父是唯一写者,不做并发合并)。 +4. temp 空间的持久化(跟子同生共死,不落盘)。 +5. 工作式轻量子的任何行为变更。 + +--- + +## 14. 测试点、方式与预期 + +| 编号 | 测试点 | 方式 | 预期 | +|---|---|---|---| +| S1 | 创建:划入输入通道 + 授权输出通道 | 创建子,向划入的 inputch 投输入 | 子处理它;未划入的 inputch 投不进(或报错) | +| S2 | 授权收窄 | 创建时只授权部分工具/插件 | 子的工具表恰为该子集;`output_list_channels` 只列授权的 | +| S3 | 默认完整授权 | 不传授权参数创建 | 子拿到全部插件/工具 | +| S4 | 子→父 L3 | 子在工作中主动发消息 | 父侧收到 **L3 中断**且带子标识;父可被打断(非临界区时) | +| S5 | contextfull → L4 | 灌满子的上下文 | 父侧收到 **L4 中断**、带子标识,且**只推信号** | +| S6 | 父→子 L4 | 父"发送消息"到指定子 | 子在收到时被打断(L4 > 子内部一切),按 §15 的默认挂起/恢复 | +| S7 | 查看(pull 不打断) | 子在跑长任务时父"查看" | 返回状态面快照;**子的 step/帧不变**、未被抢占 | +| S8 | 处理表:自动写兜底 | 子一轮不主动写 | 该轮仍有记录(系统自动写) | +| S9 | 处理表:主动写优先 | 子主动写 `inputch_note` | 该轮只有主动写的内容,无自动写 | +| S10 | 压缩(保留语义) | 父选压缩 | 上下文变短;**处理表被清空**;**子继续存在**且能继续干活 | +| S11 | 回收(取消语义) | 父选回收并挑若干条 promote | 选中内容进 **main**、其余丢弃;**temp 被丢弃**;**子被取消** | +| S12 | 销毁(立刻) | 父销毁(含子正在跑工具/LLM 时) | 子立刻消失、出登记表、其 temp 丢弃、通道释放 | +| S13 | 子不得写 main | 子调记忆写工具 | 落在 **temp**;main 无新增 | +| S14 | 子查询范围 = temp ∪ main | 子查只在 main 里的内容 / 只在 temp 里的内容 | 两者都能查到 | +| S15 | 子之间 temp 隔离 | 两个子各写 temp,互相查 | 查不到对方的 temp | +| S16 | 父退出清理 | 父 `Stop()`(多个子、有子在工作中) | 全部子被销毁;登记表空;**无孤儿 goroutine / 无悬空通道 / 无残留 temp** | +| S17 | L4 独占 | 子内部(子自己的输入/工具/定时器)试图产生 L4 | 被夹到 **≤L3**;只有父的消息是 L4 | +| S19 | 父消息必能打断子 | 子在长任务中(LLM 流式段)时父发送消息 | 子按 L4 被打断;若子在不可抢占临界区,则在安全点生效 | +| S20 | 子的内核级事件上报 | 子 panic / 子 contextfull | 在**父的阶梯上以 L4(带子标识)**出现;子侧不自己产生 L4 | +| S18 | 内核不持有"当前通道" | 抢占/中断后被打断任务恢复并发响应 | 提示词与事件标签都**只来自输入事件**(不再有被覆盖的字段) | + +--- + +## 15. 待确认决策(含默认取值) + +| 编号 | 问题 | 取值 | +|---|---|---| +| **R1** | 驻留子的内核形态 | **[已定]独立轻量内核**(自己的调度器/中断栈/上下文/记忆作用域) | +| **R2** | 插件与工具 | **[已定]父授权,默认完整授权**(可收窄) | +| **R3** | 记忆模型 | **[已定]两级空间**:读 temp∪main,写 temp | +| **R11** | 父消息的级别 | **[已定]对子 = L4**(子的阶梯上唯一 L4 来源;子内部一律 ≤L3) | +| **R12** | 子的内核级事件(panic / contextfull)上报级别 | **[默认/推论]在父的阶梯上以 L4(带子标识)上报** | +| **R4** | 父→子消息落地 | **[默认]挂起/恢复**(现场不丢);一处开关可改"直接丢弃" | +| **R5** | "主动写入处理表"的形态 | **[默认]子调用 `inputch_note` 类工具**;未调用则轮末自动写 | +| **R6** | 输入通道"划入"的语义与容量 | **[默认]读写授权(不转移所有权)+ 创建时给定容量** | +| **R7** | 压缩由谁执行 | **[默认]子的轻量内核自己压**(它有自己的上下文与 LLM),压缩后清表 | +| **R8** | 压缩前父是否"查看后决定" | **[默认]纯机械压缩**(父只在选"压缩 vs 回收"时决策) | +| **R9** | 回收时处理表 | **[默认]不必清**(子都没了);若日后要留作审计需单独策略 | +| **R10** | 默认授权是否含输出门 | **[默认]含**("默认完整授权"的字面含义);若嫌宽,改默认即可 | + +--- + +## 16. 实现里程碑(每步 = 一个可独立验收的提交) + +| 里程碑 | 内容 | 验收 | +|---|---|---| +| **N0** | **无状态化**:删 `Agent.currentOutputChannel`、删提示词里的通道预设 | S18;既有全部测试通过(这是纯收敛,不含新能力) | +| **N1** | **通道层**:inputch 一等化(归属 / 容量 / 授权 / 目标解析)+ 输出通道授权过滤 | S1–S3 | +| **N2** | **轻量内核 + 记忆作用域**:记忆子系统加 space 维度;`AgentConfig` 加作用域参数(写 temp、读 temp∪main) | S13–S15 | +| **N3** | **驻留子生命周期**:创建 / 销毁 / 登记表 / 父退出清理 | S12、S16 | +| **N4** | **跨 agent 投递**:子→父 L3、父→子 L4(+ `isKernelLevelSource` 分层) | S4、S6、S17 | +| **N5** | **inputch 处理表**:主动写入工具 + 自动写兜底 + 生命周期 | S8、S9 | +| **N6** | **contextfull**:检测 + L4 通知(带子标识)+ 三处置(压缩/回收/销毁) | S5、S10、S11 | +| **N7** | **e2e + 压力**:父子互中断、contextfull 处置矩阵、多子并发、父退出清理 | S1–S18 全绿;`-race` 干净 | + +每步收尾命令: + +```bash +export GOCACHE=/tmp/gocache GOPATH=/tmp/gopath TMPDIR=/var/tmp/gotmp +gofmt -l internal/agent internal/plugin internal/sdk # 本步新增文件必须为空 +go build ./... && go vet ./... +go test -count=1 ./... && go test -race -count=1 ./internal/agent/... ./internal/plugin/... +``` + +--- + +## 17. 与发布纪律的关系 + +- 本设计在 `feature/input-semantics` 之后的特性分支上开发,完成后合回 `main`。 +- 若需要动公开 SDK(例如新增 `agent_*` 控制面原语、通道授权字段),按"**只增不减、签名不改**" + 追加,并同步 `docs/zh/plugin-interface-matrix.md` 与 SDK 仓版本。