Files
HomeAgent/internal/agent/api/device.go
JianFeeeee cc0eb51286 refactor(kernel): 统一挂载点 kstate + 插件声明式系统提示词段
## 起因(生产故障)

A2A 分诊助手(驻留子)把 terminal_create 当一次性命令执行器:

    terminal_create(command='cd /usr/lib/node_modules/@deepseek-ai && ls -d */ | head -60 && …')
    → read /dev/ptmx: input/output error
    → 再开 term_7、term_8、term_9 …

链路拆开四层:
  ① cmd 插件 auto_start=false ⇒ 工具表里没有 cmd_run
  ② 需要 cmd-tools skill 说明"先 plugin_start cmd"
  ③ 但 skill 索引没进那个 agent 的提示词
  ④ 因为 skillIndex 只是根 agent 的字段、由 bootstrap 手工接线

④ 才是总根。而它之所以必须手工接线:agent/core 读不到内核的共享数据。

## 环的精确形状(实测,非推测)

kernelboot 因为持有 KernelState 而依赖 19 个业务包:

    kernelboot ──▶ agent/core ──▶ …
               ──▶ plugin
               ──▶ security
               ──▶ memory / knowledge / text / coldcontext / …

而 kernelboot.go / scheduler.go / funcmodule.go / syncbus.go / lifecycle.go
**一行业务代码都不需要** —— 依赖全在 kernelstate.go 这一个数据文件里。

后果是四个底层包各自造通道:

    core      → Agent 字段(每种 agent 构造点都要记得传)
    plugin    → 12 个 SetXxx
    sdk       → SDKConfig 传参
    security  → 包级单例 Global() / RuntimeGlobal() / ConfigCodec()

四条通道是同一个根因的四个表现。

## 本次做的事

### 1. 抽出 internal/kstate(断环)

kernelstate.go / syncbus.go / lifecycle.go 搬出 kernelboot:

    internal/kstate     纯数据 + 唯一挂载点(叶子,谁都能 import)
    internal/kernelboot 装配引擎,零业务包

挂载点 API:Mount / Get(nil 安全)/ MountForTest(返回 restore,并行测试隔离)。

验证:core 现在能 import kstate(此前 import cycle not allowed)。

### 2. StageHost / Agent 字段存 any

它们是 core 自己的类型(core 是环的一极,这是真环)。按用户裁定的方向:

    > 是其他包在kstate挂共享内容啊

存 any,消费方断言回具体类型 —— 与本仓既有的 PluginCatalog /
PluginScheduler / UpdateCapabilities 同型。

★ 其余字段保持**具体类型**,没有一律改 any。判据是"它会不会反向依赖
agent/core":实测 plugin / security / sdk 都不依赖 core,所以它们作为
具体类型不构成环,而类型化字段让"拼错/取错类型"是编译错误(本结构体的
设计原则)。

### 3. 插件声明式系统提示词段

SDK 新增 SystemPromptAPI(见 submodule 提交)。内核侧:

    kstate.State.PromptSections *promptsec.Registry   ← 唯一事实来源
    Agent.renderSystemPrompt() 每轮重建时读它

skillmgr 从"实现 SkillIndexProvider 让内核来问"改为"声明自己的段":

    DeclarePromptSection{Section:"skillmgr.skills", Order:100, Volatile:true}

并挂到全部变更点(启动全扫后、移交事件后、load/unload/create/install/
enable/disable 后、Stop 时撤销)。

### 4. 内核自身的提示词段也走同一管理器

按用户裁定「把现在内核所有 systemprompt 的相关注入都接入到这个统一的
systemprompt 管理器上」,把 buildSystemPrompt 里硬拼的十几块
(人格卡/任务提示词/记忆说明/输出规则/工具目录…)改为声明式:
kernelSections() 当轮计算 → 与插件段按 Order 合并渲染。

顺序由 Order 常量决定,改结构变成调常量而不是挪代码块。

## 判据(均经变异验证)

  TestCoreCanImportKstate            真编译探针;环重现则红(实测)
  TestKstateDoesNotDependOnCore      静态版,指向具体字段
  TestKstateIsLeaf                   kstate 不得反依赖上层
  TestRTChildSeesPromptSections      子 agent 能看到插件段
  TestResidentSeesPromptSections     生产实际中招的那一类;变异后红(实测)
  TestRootAgentSeesPromptSections    确认没改坏根路径
  TestNoPromptSectionsMount          未装配时不 panic、不凭空出现段
  TestDeclareTwiceRendersOnce        ★ 回应"会不会多份":声明 5 次渲染 1 份
  TestSectionRemovalTakesEffect      Stop 后段消失
  TestRuntimeUpdateVisibleToExistingAgent  同一实例下一轮看到新内容
  TestSectionOrderIsRespected        Order 生效且稳定

## 验证

  go build ./...                   PASS
  go vet ./...                     PASS
  gofmt -l internal/ cmd/ pkg/     干净
  go test ./... -count=1           63 包全过,0 FAIL
  干净检出(git worktree)           build + 4 包测试全绿

## 未做(见 kernelplan.md)

  T3 security 三单例收敛(含 Store 复用 Codec 的惰性迁移)
  T5 plugin Setter 收敛
  T6 防腐判据(部分已随本次落地)
2026-11-04 11:23:05 +08:00

96 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
// Device 数据面的**公共契约**(低层包,避免 import 环)。
//
// ## 为什么这些类型必须住在这里
//
// 依赖图上有两个方向的需求:
//
// internal/kernelmod/device → kernelboot → internal/agent/core
// internal/agent/core → 需要满足 device 的 ToolHost 接口
//
// 如果接口定义在 kernelmod/device,agent/core 就得 import 它 ⇒ **循环**。
// 所以契约下沉到 agent/api(core 早已依赖它,而它不依赖任何内核模块)。
// ToolDefinition 是工具定义的数据面投影(与具体 schema 表示解耦)。
type ToolDefinition struct {
Name string
Description string
// ParallelSafe 声明该工具可与同批其他工具并发执行。
ParallelSafe bool
// Timeout 是声明的超时(空 = 用内核默认)。
Timeout string
}
// ToolHost 是 device 需要 agent 提供的那部分能力。
//
// ★ 用接口而不是具体类型:第 2 步把工具实现迁进 device 时,agent 只需
// 继续满足这个接口,双方都不必互相 import。
type ToolHost interface {
// ToolDefinitions 返回当前 agent 可见的工具定义(含运行期门控)。
ToolDefinitions() []ToolDefinition
// ExecuteTool 执行一个工具调用(已含参数校验、授权闸、超时、脱敏)。
ExecuteTool(name string, args map[string]interface{}) (string, error)
}
// DeviceSurface 是 device 数据面暴露给内核的最小面(KernelState.Device 的类型)。
//
// 只放"别的模块真的要读"的东西:诊断与状态面用工具清单与数量;
// 执行只经 Agent(安全闸门在那边),**不**从这里暴露任意执行入口。
type DeviceSurface interface {
Tools() []ToolDefinition
ToolCount() int
}
// BuiltinToolHandlers 是**内置工具的族处理器**(每个族一个方法)。
//
// ## 为什么用族的粒度而不是逐工具
//
// 族内工具共享同一批依赖(memory 族要 graph、knowledge 族要 store、
// coldcontext 族要 coldcontextStore…)。按族传接口,device 的分发层就能
// 在不认识 Agent 内部字段的前提下完整接管**路由**:
//
// device.Dispatch(tc) → 选族 → handler.ExecuteMemoryTool(tc)
//
// 第二阶段再把族实现搬进 device(那时依赖改为显式 Deps 结构体),
// 路由这一段不用再改。
type BuiltinToolHandlers interface {
ExecutePersonaTool(tc ToolCall) string
ExecuteMemoryTool(tc ToolCall, turnScenes []string) string
ExecuteSocialTool(tc ToolCall) string
ExecuteKnowledgeTool(tc ToolCall) string
ExecuteColdContextTool(tc ToolCall) string
ExecuteSecretTool(tc ToolCall) string
ExecuteOutputSendHelp(tc ToolCall) string
ExecuteOutputSend(tc ToolCall) string
ExecuteOutputListChannels() string
ExecuteInputChannels(tc ToolCall) string
ExecuteResidentAgents(tc ToolCall) string
ExecuteNotifyParent(tc ToolCall) string
ExecuteInputchNote(tc ToolCall) string
ExecutePluginReload() string
ExecuteGetPluginTools(pluginName string) string
}
// DeviceMounter 是"能把数据面挂到自己身上"的最小面。
//
// ## 为什么需要它(2026-11-04)
//
// `kstate.State.Agent` 存 `any`(挂载点不能依赖 agent/core,否则成环)。
// 消费方要调 `MountDevice` 就必须断言回具体类型 —— 但 device 模块断言成
// `*agentCore.Agent` 会让 device 依赖 core 的全部实现细节,而它真正需要
// 的只有这一个方法。
//
// 用一个单方法接口把需求显式化:
//
// · device 只依赖 api.DeviceMounter(叶子包,零环)
// · core 的 *Agent 天然实现它(方法名与签名一致)
// · 谁想提供"可挂载数据面的宿主",实现这一个方法即可
//
// ★ 与 ToolHost 的分工:ToolHost 是**执行面**(工具定义与执行),
// DeviceMounter 是**装配面**(挂上数据面)。两者用途不同,不合并。
type DeviceMounter interface {
// MountDevice 挂载工具/通道数据面(装配期调用一次)。
MountDevice(d DeviceSurface)
}