mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-11-05 16:57:32 +00:00
## 1. 工作式子的工具禁令收成单一事实来源 禁令此前写了两遍:定义侧(把工具从 childTools 滤掉)与执行侧 (模型硬调时拒绝)。两处名单必须逐字一致,任何一边落后就是真实缺口: 定义侧漏滤 ⇒ 模型看得见但不该能用(白耗 token,还会反复试) 执行侧漏拒 ⇒ 模型绕过定义直接调用**就能生效** 现在只有 workerForbidden 一张表,每条禁令把"匹配方式 + 类别 + 解释" 绑在一个结构体里;定义侧 workerToolAllowed、执行侧 workerToolReject 都从它派生。 上一版我用 reason 字符串 + 下标猜分类 + hint 查表做了三处间接, 属于新造的技术债,已重写为单表直查。 判据(均经变异验证): TestWorkerToolDenyListIsSingleSource 长出第二份名单 ⇒ 红(实测确认) TestWorkerToolAllowedMatchesReject 两侧判据漂移 ⇒ 红 TestWorkerToolRejectExplainsWhy 拒绝文案不可执行 ⇒ 红 ## 2. 删除零使用的抽象(不留技术债) R1–R3 引入了 ChildKind / Host / Deliverer / DeviceResolver / ToolSpec / ToolResult / DeclaredKinds / KindWorker / State / SetState / BumpRound / ChannelDisposition,实现过程中发现它们是假抽象: · 各类型要用载体 Agent 的大半能力,要满足接口就得在实现里类型断言回 *Agent · 接口比被抽象的东西还大 ⇒ 只是把复杂度换个地方放 · 实测:26 个导出符号里 20 个零使用、零实现 已删除 host.go 与 kind.go,并把"四种类型清单"和"转投驻留子为何不是类型" 这两个有价值的结论移进 kinds.go 的文档注释(知识保留,死代码删除)。 child 包现在只剩实际被用的 9 个符号:Kind / Child / Entry / Registry / StopDeps / ErrChildExists / ErrChildNotFound(+ 2 个 Kind 常量)。 ## 3. 修掉误导性签名 Entry.Dispose(policy ResidualPolicy) 里 policy **两个实现都不读** —— 残余处置实际走 ApplyResidual / FinishResidentWith,在 Dispose 之前就 已经完成。看签名会以为这里处理残余,是误导。已去掉该参数, 并删掉 child 包里重复定义的 ResidualPolicy(core 包已有一份,且多一个 stash)。 ## 验证 go build ./... PASS go vet ./... PASS gofmt -l 干净 go test ./internal/agent/core/ -count=1 ok 15.300s go test ./internal/agent/core/ -count=1 -race ok 18.040s 干净检出(git worktree)单独 build + vet + test PASS
136 lines
4.7 KiB
Go
136 lines
4.7 KiB
Go
package child
|
||
|
||
// child.go 是子 agent 的**公共部分**与类型标签。
|
||
//
|
||
// # 这个包只做一件事
|
||
//
|
||
// 把所有类型的子 agent 共有的、**最容易"改一处漏一处"**的生命周期收成一处:
|
||
//
|
||
// 单一登记表(Registry) ← 替换掉 residents / rtChildren 两张表
|
||
// 单一清理路径(Cleanup) ← 单数与复数共用,防"复数漏一步"
|
||
// 公共字段(Child:id/ctx/IO/入站通道)
|
||
//
|
||
// # 为什么这里没有 ChildKind 接口
|
||
//
|
||
// 曾设计过 `ChildKind`(Deliver / BuildContext / Emit / RunTools / Dispose /
|
||
// ParentTools),但在实现过程中发现它是**假抽象**:
|
||
//
|
||
// · 各类型要用载体 Agent 的大半能力(provider / 工具执行 / 记忆 / 安全)
|
||
// · 要满足它就得以 `Entry` 为参数、在实现里做类型断言回 `*Agent`
|
||
// · 接口比被抽象的东西还大 ⇒ 只是把复杂度换个地方放
|
||
//
|
||
// 实测结果是它零实现、零调用(26 个导出符号里 20 个无人使用)。
|
||
// 按"不留技术债"的要求,接口与其配套的 Host / ToolSpec / ToolResult 已删除,
|
||
// 类型差异直接写在各自的文件里(rtchild.go / resident.go / spawn.go)。
|
||
//
|
||
// # 已确认的边界(不要为了"更统一"而抹掉)
|
||
//
|
||
// - 驻留子的 `DestroyResident` 有三种 ChannelDisposition(释放/挂起/接回)
|
||
// - rtchild 的 rt 通道**销毁后悬空**,不归还
|
||
// - 驻留子 `Start()` 起 7 个 goroutine;rtchild **只起 1 个** rtLoop
|
||
// - 工作式子**不进**登记表(无 io/通道,结果缓存与存活登记语义不同)
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"sync"
|
||
|
||
agentIO "github.com/JianFeeeee/HologramAgent/internal/agent/io"
|
||
)
|
||
|
||
// Kind 是子 agent 的类型标签。
|
||
//
|
||
// 用字符串而不是 iota:它要出现在状态面与日志里,字符串直接可读,
|
||
// 且新增类型不需要改一个中心 switch。
|
||
type Kind string
|
||
|
||
const (
|
||
// KindResident 是长期派驻的子 agent(任务队列、残余可回灌、不丢活)。
|
||
KindResident Kind = "resident"
|
||
// KindRT 是实时通话子 agent(单槽覆盖、最新者胜、可饿死、丢现场)。
|
||
KindRT Kind = "rt"
|
||
)
|
||
|
||
// Child 是一个**已建立**的子 agent 实例的公共部分。
|
||
//
|
||
// ★ 用法是**嵌入**:类型专属状态放在各实现自己的结构体里,
|
||
// 公共部分靠嵌入提升字段:
|
||
//
|
||
// type rtChild struct {
|
||
// *child.Child // ID / Kind / Ctx / IO / InboundCh / stopped
|
||
// agent *Agent // 载体(类型专属,不进公共结构)
|
||
// slot *rtSlot
|
||
// }
|
||
//
|
||
// 为什么不把 agent *Agent 放进本结构:本包不能 import core(否则成环),
|
||
// 而 core 可以 import 本包。嵌入让公共部分与载体指针各归各位。
|
||
type Child struct {
|
||
// ID 是登记表的键,也是通道 Owner 记的值。
|
||
//
|
||
// ★ 必须与载体 Agent 的 ID **逐字相同**:AssignRealtime 会把通道 Owner
|
||
// 记成 carrier.id,而路由按 entry.Owner 在本表里查 —— 两处各拼一次名字
|
||
// 就会永远查不到(曾因写成 "rt/"+id 而让插件输入全打到父身上)。
|
||
ID string
|
||
|
||
// Kind 是本实例的类型标签(Add 时由 Entry.Kind() 写入)。
|
||
Kind Kind
|
||
|
||
// IO 是本子自己的 IOManager(独立输入入口 + 共享通道登记表 + 父视图回退)。
|
||
IO *agentIO.IOManager
|
||
|
||
// Ctx / Cancel 是本子**自己的**取消域。
|
||
//
|
||
// 必须是 WithCancel(parentCtx) 派生:父停则子停(不留孤儿),
|
||
// 但单独 cancel 只影响本子,不波及父与兄弟。
|
||
Ctx context.Context
|
||
Cancel context.CancelFunc
|
||
|
||
// InboundCh 是本子在父侧注册的入站通道名("child/<id>"),
|
||
// Cleanup 负责注销它。空 = 本类型不需要入站通道。
|
||
InboundCh string
|
||
|
||
// ParentID 是父的 agent id(日志用)。
|
||
ParentID string
|
||
|
||
// CreatedAt 是 Unix 纳秒,供状态面排序。
|
||
CreatedAt int64
|
||
|
||
mu sync.Mutex
|
||
stopped bool
|
||
}
|
||
|
||
// Stop 原子地标记停止并取消本子的 ctx。可重复调用(幂等)。
|
||
func (c *Child) Stop() {
|
||
if c == nil {
|
||
return
|
||
}
|
||
c.mu.Lock()
|
||
c.stopped = true
|
||
c.mu.Unlock()
|
||
if c.Cancel != nil {
|
||
c.Cancel()
|
||
}
|
||
}
|
||
|
||
// Stopped 报告是否已被 Stop。
|
||
func (c *Child) Stopped() bool {
|
||
if c == nil {
|
||
return true
|
||
}
|
||
c.mu.Lock()
|
||
defer c.mu.Unlock()
|
||
return c.stopped
|
||
}
|
||
|
||
// ── 错误 ──
|
||
|
||
var (
|
||
// ErrChildExists 表示同名子已存在。
|
||
//
|
||
// 为什么不允许"同名覆盖":登记表与通道 Owner 都以 id 为键,
|
||
// 覆盖会让上一代的残留(通道归属、策略、留档)被当成这一代的。
|
||
ErrChildExists = errors.New("同名子 agent 已存在")
|
||
// ErrChildNotFound 表示 id 未登记。
|
||
ErrChildNotFound = errors.New("子 agent 不存在")
|
||
)
|