mirror of
https://gitcode.com/JianFeeeee/HomeAgent.git
synced 2026-09-23 02:18:06 +00:00
用户澄清(重要定性):这不是「内核替父决定」,而是**及时反馈** —— 主 agent 忙时不该让用户干等十几分钟。子 agent 是**分诊助手**: 简单的直接处理并回复,需要主 agent 的立刻回「忙碌中,请稍候」、不勉强作答。 三处补齐: 1. 分诊助手的职责提示词(之前完全没给 ⇒ 子不知道自己为什么存在): 两条路(直接办 / 报忙碌)、拿不准时报忙碌、必须 output_send 到原通道。 2. 驻留子继承父的 SystemPrompt(之前没传 ⇒ 子只用一句兜底文案, 拿不到「异步通道必须显式 output_send,否则回复被静默丢弃」这条铁律。 webui 这类同步通道能回是因为走 ResponseCh,掩盖了这个缺陷)。 3. 残余任务由父显式决定(用户要求):reclaim/destroy 时子手头未处理的消息 不再由内核悄悄处置 —— 内核只负责列清楚,父用 residual=keep/drop 决定。 之前 pendingEvents 只收带 ResponseCh 的,异步(qq)残余任务完全不在内, 被销毁时静默消失、用户零反馈且日志无痕。 配套: - scheduler.takeAllPendingEvents:取走全部未执行事件(不筛通道) - ApplyResidual(keep|drop):keep 转回父队列(保留 ResponseCh), drop 逐条记日志 + 给同步调用方补终态(否则 cli/a2a 永久挂起) - 状态面暴露 offload_owned,让父分清「我建的子」与「内核临时拉的助手」 - 工具 schema 加 residual 参数并说明 drop 的代价 这也是用户观察到的「机制很自然」的落点:分诊助手就在同一张登记表里, 父能 inspect/send/compress/reclaim/destroy,控制面 6 动作按 id 生效不区分来源。 测试 +7(残余 keep 转回且保留 ResponseCh / drop 通知同步调用方 / 空残余如实报告 / 分诊提示词 / 继承 SystemPrompt), 其中 drop 那条已实测「对着静默丢弃的旧实现会失败」。全套绿。
274 lines
12 KiB
Go
274 lines
12 KiB
Go
package sdk
|
||
|
||
// StatusAPI provides a snapshot of the kernel runtime status.
|
||
type StatusAPI interface {
|
||
GetKernelStatus() *KernelStatus
|
||
}
|
||
|
||
// KernelStatus is the aggregated runtime snapshot of all kernel subsystems.
|
||
type KernelStatus struct {
|
||
Uptime string `json:"uptime"`
|
||
StartTime string `json:"start_time"`
|
||
|
||
// Build 是内核自身的版本与构建信息。
|
||
//
|
||
// 为何需要:此前 WebUI 展示的“版本”取自 `sdk.SDKVersion`,那是 **SDK 仓
|
||
// meta.Version 的硬编码值**;而 `-ldflags` 注入的是内核的
|
||
// `internal/meta.Version`——两条链完全不相交。于是:
|
||
// - 构建时注入的真实版本号、commit、构建时间全部丢失;
|
||
// - 两仲版本号碰巧相等时看不出问题,一旦不等就报错版本;
|
||
// - 无法回答“现网跑的是哪个 commit 构出来的”。
|
||
Build BuildStatus `json:"build"`
|
||
|
||
AgentID string `json:"agent_id"`
|
||
|
||
Plugins []PluginInfo `json:"plugins"`
|
||
Tools []ToolDef `json:"tools"`
|
||
Channels []ChannelInfo `json:"channels"`
|
||
// InputChannels 是 inputch 的**登记与归属**视图(谁注册、划给了哪个 agent、
|
||
// 容量、默认回程输出)。
|
||
//
|
||
// 为何单列:Channels 只描述设备侧能力,回答不了「这条输入归谁」。驻留子 agent
|
||
// 出现之后,归属才是运行态里最该看见的东西——只画设备能力,等于把
|
||
// 「输入路由发生在进内核之前」这条设计事实藏了起来。
|
||
InputChannels []InputChannelInfo `json:"input_channels"`
|
||
|
||
Memory MemoryStatus `json:"memory"`
|
||
Knowledge KnowledgeStatus `json:"knowledge"`
|
||
Documents DocumentStatus `json:"documents"`
|
||
TextMemory TextMemoryStatus `json:"text_memory"`
|
||
Social SocialStatus `json:"social"`
|
||
|
||
LLM LLMStatus `json:"llm"`
|
||
|
||
Context ContextStatus `json:"context"`
|
||
|
||
Runtime RuntimeStatus `json:"runtime"`
|
||
|
||
// ONNX 报告统一多模态向量空间(ONNX 模型)是否**真的在用**。
|
||
//
|
||
// 为何单列:内核的向量能力是三层降级(统一多模态空间 → 词嵌入 → TF-IDF),
|
||
// 只报「向量可用/不可用」分不清「ONNX 模型已加载」与「退回了纯文本路径」。
|
||
// 模型缺失 / 运行时缺失 / provider 打开失败时这里是 enabled=false + reason。
|
||
ONNX ONNXStatus `json:"onnx"`
|
||
|
||
Tracker TrackerStatus `json:"tracker"`
|
||
|
||
// Residents 是驻留式子 agent 的运行时视图(数量 = len(Residents))。
|
||
Residents []ResidentStatus `json:"residents"`
|
||
|
||
// Scheduler 是输入调度器的运行时快照(可观测性,设计文档 §11 O1/O2)。
|
||
// M2 起输入不再直接排队在 channel 上,而是经 readyQueue/pendingInterrupts/
|
||
// suspendStack 三集合按优先级调度;这里把这些状态暴露出来。
|
||
Scheduler SchedulerStatus `json:"scheduler"`
|
||
}
|
||
|
||
// SchedulerStatus 是调度器的原子快照 DTO。
|
||
type SchedulerStatus struct {
|
||
// Running 是当前执行的任务(空表示空闲)。
|
||
Running *SchedulerTask `json:"running,omitempty"`
|
||
// Immediate 是刚抢占成功、将在下一个安全点立即运行的中断(最多一个)。
|
||
Immediate *SchedulerTask `json:"immediate,omitempty"`
|
||
// ReadyQueueDepth / PendingInterrupts / SuspendStack 是三个集合的深度。
|
||
ReadyQueueDepth int `json:"ready_queue_depth"`
|
||
PendingInterrupts int `json:"pending_interrupts"`
|
||
SuspendStack int `json:"suspend_stack"`
|
||
MaxSuspendDepth int `json:"max_suspend_depth"`
|
||
|
||
// InterruptQueues 是**四条中断队列各自的深度**,下标即中断级别(1..4);
|
||
// 下标 0 恒为 0,这样 level 可以直接当数组下标用,省掉调用方 ±1 的翻译。
|
||
//
|
||
// 为什么单列:PendingInterrupts 只是总数,看不清"堵在哪一级"——
|
||
// 四级中断是抢占优先级,堵在 L1 还是 L4 是完全不同的运行状态。
|
||
InterruptQueues [5]int `json:"interrupt_queues"`
|
||
|
||
// SuspendFrames 是中断栈的帧,**栈底 → 栈顶**(只暴露任务标识,不含帧内容)。
|
||
// 深度见 SuspendStack;帧的顺序回答了"谁被谁打断"。
|
||
SuspendFrames []SchedulerFrame `json:"suspend_frames,omitempty"`
|
||
|
||
// InterruptsByLevel / PreemptsByLevel 是各级中断的累计计数(下标 1..4):
|
||
// 前者=被登记次数(含没抢成的),后者=判定可抢占并进入 immediate 的次数。
|
||
InterruptsByLevel [5]uint64 `json:"interrupts_by_level"`
|
||
PreemptsByLevel [5]uint64 `json:"preempts_by_level"`
|
||
|
||
Enqueued uint64 `json:"enqueued"`
|
||
Executed uint64 `json:"executed"`
|
||
Rejected uint64 `json:"rejected"`
|
||
Suspended uint64 `json:"suspended"`
|
||
Resumed uint64 `json:"resumed"`
|
||
// Preempted = Σ PreemptsByLevel[1..4],即「真正抢占成功」的次数。
|
||
// 它与 Suspended 不等价(受害者可能先自行结束),因此不是 Suspended 的别名。
|
||
Preempted uint64 `json:"preempted"`
|
||
// Backpressure 是就绪队列满、输入被挡回 channel 的次数(暂时不收,不是丢弃)。
|
||
Backpressure uint64 `json:"backpressure"`
|
||
}
|
||
|
||
// SchedulerFrame 是中断栈里的一帧(供图形化展示"压了几层现场")。
|
||
type SchedulerFrame struct {
|
||
Task SchedulerTask `json:"task"`
|
||
}
|
||
|
||
// ResidentStatus 是驻留式子 agent 的运行时视图。
|
||
//
|
||
// 为什么进状态面:驻留子是"常驻的独立 agent",它们的数量、轮次与上下文占用
|
||
// 是运行态里最需要一眼看到的东西(此前只在日志里,WebUI 只能显示文字)。
|
||
type ResidentStatus struct {
|
||
ID string `json:"id"`
|
||
State string `json:"state"`
|
||
Rounds int `json:"rounds"`
|
||
ContextFull bool `json:"context_full"`
|
||
InputChs []string `json:"input_channels,omitempty"`
|
||
AllowedOutputs []string `json:"allowed_outputs,omitempty"`
|
||
InputChTable int `json:"input_ch_table"`
|
||
CreatedAt string `json:"created_at,omitempty"`
|
||
|
||
// OffloadOwned 标记这是**内核为承接积压而拉起**的临时助手(见 core/offload.go)。
|
||
//
|
||
// 为什么要暴露给状态面/工具面:父的模型需要分清"我建的子"与
|
||
// "内核临时拉的分诊助手" —— 前者该按需回收,后者在空闲、无积压时
|
||
// 可以安全回收,而**正忙时不该回收**(会让用户的消息再次无声丢失)。
|
||
OffloadOwned bool `json:"offload_owned,omitempty"`
|
||
|
||
// 以下四项是该驻留子**自己的**调度器积压摘要,用于 per-agent 负载环形图。
|
||
//
|
||
// 根 agent 的积压看 KernelStatus.Scheduler;每个驻留子是独立 agent、
|
||
// 各跑各的调度器,必须分别给,否则「哪个子忙」在界面无从判断。
|
||
ReadyQueueDepth int `json:"ready_queue_depth"`
|
||
PendingInterrupts int `json:"pending_interrupts"`
|
||
SuspendStack int `json:"suspend_stack"`
|
||
InterruptQueues [5]int `json:"interrupt_queues"`
|
||
}
|
||
|
||
// SchedulerTask 是任务的最小标识(不暴露帧内容)。
|
||
type SchedulerTask struct {
|
||
ID uint64 `json:"id"`
|
||
Level int `json:"level"`
|
||
Kind string `json:"kind"`
|
||
}
|
||
|
||
// ONNXStatus 是统一多模态向量空间(ONNX 模型)的启用状态与身份。
|
||
type ONNXStatus struct {
|
||
// Enabled 是 provider 真正打开且元数据合法(不是「配置里写了 provider」)。
|
||
Enabled bool `json:"enabled"`
|
||
// Provider 是配置指定的 provider 名(如 chineseclip / qwen3vl / http)。
|
||
Provider string `json:"provider,omitempty"`
|
||
Dim int `json:"dim,omitempty"`
|
||
Fingerprint string `json:"fingerprint,omitempty"`
|
||
Modalities []string `json:"modalities,omitempty"`
|
||
// Reason 是未启用时的原因(未配置 / 打开失败的具体错误 / 其它)。
|
||
Reason string `json:"reason,omitempty"`
|
||
}
|
||
|
||
type PluginInfo struct {
|
||
Name string `json:"name"`
|
||
Loaded bool `json:"loaded"`
|
||
}
|
||
|
||
type ChannelInfo struct {
|
||
Name string `json:"name"`
|
||
// Type 保持为 DeviceType 的数字字符串(历史字段,别改语义)。
|
||
Type string `json:"type"`
|
||
// Direction 是方向的可读名:in(只进)/ out(只出)/ io(双向)。
|
||
Direction string `json:"direction"`
|
||
Ready bool `json:"ready"`
|
||
// 以下三项供通道拓扑展示:此前 collectKernelStatus 只透传了 Name/Type,
|
||
// 把 Description/Tools/OutputCaps 全丢了,前端只能画出一排光秃秃的名字。
|
||
Description string `json:"description,omitempty"`
|
||
Tools []string `json:"tools,omitempty"`
|
||
OutputCaps int `json:"output_caps"`
|
||
CapsText string `json:"caps_text,omitempty"`
|
||
}
|
||
|
||
// InputChannelInfo 是一条 inputch 的登记与归属视图。
|
||
//
|
||
// 字段取自 agent/io 的 ChannelRegistry(根 agent 与驻留子**共用同一份**登记表),
|
||
// 因此它天然覆盖驻留子的通道分配。
|
||
type InputChannelInfo struct {
|
||
Name string `json:"name"`
|
||
// Plugin 是注册它的插件名(归属可追溯)。
|
||
Plugin string `json:"plugin,omitempty"`
|
||
// Owner 是被划给的 agent id;"" = 未分配,归根 agent / 内核默认。
|
||
Owner string `json:"owner,omitempty"`
|
||
// Capacity 是该 inputch 的队列容量;0 = 用内核默认值。
|
||
Capacity int `json:"capacity,omitempty"`
|
||
// Output 是该 inputch 的默认回程输出通道;"" = 由来源决定。
|
||
Output string `json:"output,omitempty"`
|
||
}
|
||
|
||
type MemoryStatus struct {
|
||
Available bool `json:"available"`
|
||
EntityCount int `json:"entity_count"`
|
||
RelationCount int `json:"relation_count"`
|
||
EntityTypes int `json:"entity_types"`
|
||
}
|
||
|
||
type KnowledgeStatus struct {
|
||
Available bool `json:"available"`
|
||
ItemCount int `json:"item_count"`
|
||
Items []string `json:"items,omitempty"`
|
||
}
|
||
|
||
type DocumentStatus struct {
|
||
Available bool `json:"available"`
|
||
DocCount int `json:"doc_count"`
|
||
VectorCount int `json:"vector_count"`
|
||
}
|
||
|
||
type TextMemoryStatus struct {
|
||
Available bool `json:"available"`
|
||
FileCount int `json:"file_count"`
|
||
}
|
||
|
||
type SocialStatus struct {
|
||
Available bool `json:"available"`
|
||
PersonCount int `json:"person_count"`
|
||
}
|
||
|
||
type LLMStatus struct {
|
||
Available bool `json:"available"`
|
||
Provider string `json:"provider,omitempty"`
|
||
Sources int `json:"sources,omitempty"`
|
||
}
|
||
|
||
type ContextStatus struct {
|
||
EventCount int `json:"event_count,omitempty"`
|
||
}
|
||
|
||
type RuntimeStatus struct {
|
||
Goroutines int `json:"goroutines"`
|
||
MemoryMB int64 `json:"memory_mb"`
|
||
GoVersion string `json:"go_version"`
|
||
}
|
||
|
||
// BuildStatus 是内核二进制的构建身份,全部来自 `internal/meta`
|
||
// (由 -ldflags 在构建时注入,未注入时为源码默认值/unknown)。
|
||
type BuildStatus struct {
|
||
// Version 是内核语义版本(如 1.0.0)。
|
||
Version string `json:"version"`
|
||
// Commit 是构建时的 Git 短 hash;未注入为 unknown。
|
||
Commit string `json:"commit"`
|
||
// BuildTime 是 UTC 构建时间;未注入为 unknown。
|
||
BuildTime string `json:"build_time"`
|
||
// SDKCompatible 是本内核可兼容的最高 SDK 版本。
|
||
// 插件用旧 SDK 编译时靠它判断能不能加载。
|
||
SDKCompatible string `json:"sdk_compatible"`
|
||
// KernelName 是内核名(HomeAgent)。
|
||
KernelName string `json:"kernel_name"`
|
||
// SourceURL 是本次构建对应的源码地址。AGPL-3.0 §13 要求:向使用者提供网络
|
||
// 服务时,要给他们拿到 Corresponding Source 的机会——WebUI 状态页会把它渲染成
|
||
// 可见链接,所以**修改后对外部署的分支必须把这个值改指向自己的源码仓库**。
|
||
SourceURL string `json:"source_url,omitempty"`
|
||
// License 是开源许可标识(SPDX,如 AGPL-3.0-only)。
|
||
//
|
||
// 为何与 SourceURL 分开:只看一个源码链接,使用者没法从界面上看出这是什么
|
||
// 许可、网络服务场景下有哪些义务。许可名与许可全文地址是两个独立事实。
|
||
License string `json:"license,omitempty"`
|
||
// LicenseURL 是 License 对应的全文地址。
|
||
LicenseURL string `json:"license_url,omitempty"`
|
||
}
|
||
|
||
type TrackerStatus struct {
|
||
Available bool `json:"available"`
|
||
Dir string `json:"dir,omitempty"`
|
||
}
|