首页
/ LobeHub Agent Signal 领域包深度解析:源事件、作用域键与信号节点的共享契约层

LobeHub Agent Signal 领域包深度解析:源事件、作用域键与信号节点的共享契约层

2026-09-07 23:41:12作者:温玫谨Lighthearted

Agent Signal(智能体信号)是 LobeHub 中把"发生了什么事"(用户发言、执行完成、网关事件)送入策略与运行时的统一事件化语义层。而 @lobechat/agent-signal 正是这一语义层的纯领域契约包:它只定义信号节点的形状、源事件的类型与载荷、以及作用域键(scope key)的推导规则,不含任何策略运行、模型调用、数据库读取或工作流触发逻辑。本文以 packages/agent-signal/README.md 为主体,结合包内源码与测试,梳理它的设计边界、公共 API、源事件目录、作用域键算法与新增源事件的完整流程,帮助你在运行时代码、调度器、策略或可观测性逻辑中正确消费与扩展这套共享契约。

一、包定位:只定义"语言",不执行"动作"

@lobechat/agent-signalpackage.json 中描述为 "Shared AgentSignal producer helpers and contracts",其核心职责边界非常清晰:

  • 它定义 Agent Signal 节点(source / signal / action / executor)的领域语言,以及源事件(source event)的类型、常量、载荷映射与作用域键工具;
  • 它不做:不运行策略(policies)、不调用模型运行时(model runtimes)、不读取数据库、不触发工作流,也不导入任何应用服务端模块。

因此它可以在浏览器端与服务端同时安全使用——这正是选择它承载共享类型与纯函数的原因。当代码需要"浏览器安全或服务器安全的源事件类型、源事件常量、作用域键工具,以及核心节点形状"时,就该引入此包。

设计上有一条铁律:不要在此包中做应用集成。副作用(side effects)应放在浏览器侧的服务门面或服务端的 agentSignal 服务实现中——在本文所基于的 monorepo 布局里,浏览器侧对应 src/services/agentSignal.ts 及其桥接层,服务端对应 apps/server 下的相关服务与路由。从包入口 src/index.ts 的导出可以看出,包同时再导出内置信号词汇(AGENT_SIGNAL_TYPESSignalPlanSignalActionApplied/Failed/Skipped 等,定义见 src/types/events.tssrc/types/builtin.ts),构成完整的"源 → 信号 → 动作"语义闭环。

二、包结构与双入口导出

包目录非常精简,源码全部集中在 src 下:

packages/agent-signal/
├── package.json          # exports: "." 与 "./source" 双入口
├── src/
│   ├── index.ts          # 包根入口:核心节点 API + 内置信号类型
│   ├── base/             # 核心节点:types / builders / guards / registries
│   ├── source/           # 源事件:sourceEvent / sourceTypes / scopeKey
│   └── types/            # 内置信号:events / builtin

package.jsonexports 暴露了两个语义不同的入口:

入口 目标文件 用途
@lobechat/agent-signal src/index.ts 构建或检查 source、signal、action、executor 节点
@lobechat/agent-signal/source src/source/index.ts 生产、类型化、校验或路由源事件

包根入口同时再导出 ./source,因此从包根也可拿到源事件 API。生产源事件时优先从 ./source 子路径导入,语义更精确、依赖面更小。

1. 核心节点 API(包根入口)

import type { AgentSignalSource, BaseAction, BaseSignal } from '@lobechat/agent-signal';
import { createAction, createSignal, createSource } from '@lobechat/agent-signal';

这些 API 面向运行时、调度器、策略与可观测性代码,用于获得归一化后的语义节点。核心节点形状定义在 src/base/types.ts

  • AgentSignalSource:源节点,必带 sourceIdsourceTypescopeKeytimestampchain(因果链元数据)与 payload
  • AgentSignalSignal(兼容别名 BaseSignal):信号节点,通过 source 引用(SourceRef)回指产生它的源,并带 signalId/signalType/chain
  • AgentSignalActionNode(兼容别名 BaseAction):动作节点,通过 sourcesignalSignalRef)双重引用回指因果来源;
  • AgentSignalScope:单条信号链的作用域元数据(topicId/taskId/botScopeKey/agentId/userId/workspaceId);
  • AgentSignalChainRef(别名 ChainRef):链元数据,记录 chainIdrootSourceId 与父节点引用(parentSourceId/parentSignalId/parentActionId)。

节点由 src/base/builders.ts 中的三个纯函数构造,构造过程会自动补全 ID 与链关系:

  • 节点 ID 优先使用 globalThis.crypto.randomUUID()(返回 source:<uuid> 形式),不可用时退化为 kind:Date.now 36 进制:递增种子
  • chainId 统一形如 chain:${rootSourceId}
  • createSignal 会把 parentNodeId 指向源节点,并把 SourceRef 固化进信号;createAction 会把父节点指向信号、parentSignalIdparentNodeId 均设为信号的 ID。

类型层面还提供了执行器与运行时结果契约:ExecutorResultapplied / skipped / failed 三态,见 src/base/types.ts)、RuntimeProcessorResult 判别联合(wait/dispatch/schedule/conclude)、SignalTriggerMetadata、以及去重结果 EmitSourceEventResultdeduped 时区分 duplicatescope_locked 两种原因)。

2. 源事件 API(/source 入口)

import {
  AGENT_SIGNAL_SOURCE_TYPES,
  createSourceEvent,
  type AgentSignalSourceEvent,
  type AgentSignalSourceEventInput,
} from '@lobechat/agent-signal/source';

const event = createSourceEvent({
  payload: {
    message: 'Please remember that I prefer concise answers.',
    messageId: 'msg_1',
    topicId: 'topic_1',
  },
  sourceId: 'source_1',
  sourceType: AGENT_SIGNAL_SOURCE_TYPES.agentUserMessage,
});

createSourceEvent 会补齐两个关键字段(见 src/source/sourceEvent.ts 的实现):

  • scopeKey:推导顺序为 topicId 优先 → bot 线程元数据 → fallback:global(详见下文作用域键一节);
  • timestamp:调用方未传入时使用 Date.now()(毫秒)。

两个类型参数有明确分工:

  • AgentSignalSourceEventInput<TSourceType>:面向生产者输入scopeKeytimestamp 可选;
  • AgentSignalSourceEvent<TSourceType>:归一化后的不可变事件scopeKeytimestamp 为必填,可直接进入持久化、去重或工作流交接。

类型层面,sourceTypepayload 通过 AgentSignalSourcePayloadMapsrc/source/sourceTypes.ts)做了一一对应的强类型约束——选择不同的源类型常量,payload 的必填/可选字段随之变化,从类型上杜绝生产出"字段与事件不匹配"的事件。

3. 作用域键 API

import { AgentSignalScopeKey, getSourceEventScopeKey } from '@lobechat/agent-signal/source';

const topicScopeKey = getSourceEventScopeKey({ topicId: 'topic_1' });
const botScopeKey = AgentSignalScopeKey.forBotThread({
  applicationId: 'discord-app',
  platform: 'discord',
  platformThreadId: 'thread_1',
});

一组实际推导结果如下:

输入 推导结果
{ topicId: 'topic-1' } topic:topic-1
{ taskId: 'task-1' } task:task-1
{ platform: 'discord', applicationId: 'discord-app', platformThreadId: 'thread-1' } bot:discord:discord-app:thread-1
{ agentId: 'agent-1', userId: 'user-1' } agent:agent-1:user:user-1
{ userId: 'user-1' } user:user-1
均无 fallback:global

三、作用域键:信号链的"分桶"算法

作用域键是 Agent Signal 架构里去重、工作流与运行时泳道协调的地基:同一条 topic、同一个 bot 线程、同一个任务或同一对 agent/user 下的事件必须能稳定地归入同一个桶。该逻辑集中实现在 src/source/scopeKey.tsAgentSignalScopeKey 命名空间对象中。

AgentSignalScopeKey 提供两类能力:

① 具名构建器,直接给定结构化元数据:

  • forTopic({ topicId })topic:{topicId}
  • forTask({ taskId })task:{taskId}
  • forBotThread({ platform, applicationId, platformThreadId })bot:{platform}:{applicationId}:{platformThreadId}
  • forAgentUser({ agentId, userId })agent:{agentId}:user:{userId}
  • forUser({ userId })user:{userId}

② 从输入元数据自动推导fromProducerInput 的判定优先级是

  1. topicIdtopic 作用域;
  2. taskIdtask 作用域;
  3. 同时具备 platform + applicationId + platformThreadIdbot 线程作用域;
  4. 同时具备 agentId + userIdagent/user 作用域;
  5. 仅有 userIduser 作用域;
  6. 全部缺失 → 兜底 fallback:global

另有 fromRuntimeScope 用于已持有结构化 AgentSignalScope 的场景:按 topicId → botScopeKey → taskId → agentId(+userId) → userId 的顺序映射。对于从任意 payload 取字符串字段的场景,getSourceEventScopeKeysrc/source/scopeKey.ts)会从 payload 中 pickStringagentId/applicationId/platform/platformThreadId/taskId/topicId/userId 再交给 fromProducerInput——这正是 createSourceEvent 补齐 scopeKey 时使用的默认逻辑。

以上推导规则都有测试用例背书:在 src/source/sourceEvent.test.ts 中,bot.message.merged 事件被断言为得到 bot:discord:discord-app:thread-1;同时携带 topicId 与 bot 元数据时断言优先使用 topic 作用域topic:topic-1);nightly review 事件断言为 agent:agent-1:user:user-1;self-reflection 事件在带 taskId 时断言进入 task 作用域。

四、支持的源事件目录

源事件是 Agent Signal 的最外层输入,由各生产者(服务端运行时、浏览器网关、Bot 路由等)发出。AGENT_SIGNAL_SOURCE_TYPESsrc/source/sourceTypes.ts 中定义,README 的目录表给出主要事件如下(文件名与主要生产者的仓库路径已按当前 monorepo 布局标注):

源事件 常量 主要生产者 Payload 意图
agent.execution.completed agentExecutionCompleted Agent Runtime 服务端 agent 执行完成,携带 operation、step、topic 与上下文元数据
agent.execution.failed agentExecutionFailed Agent Runtime 服务端 agent 执行失败,携带 operation、reason、error、topic 与上下文
agent.user.message agentUserMessage 服务端工作流桥接与 Bot 路由 用户反馈/消息内容,供策略分析记忆、prompt、文档或技能变更
bot.message.merged botMessageMerged Bot 路由 Bot 平台消息内容并入某个会话作用域
client.gateway.error clientGatewayError 浏览器网关事件处理器 客户端运行时操作的浏览器网关错误
client.gateway.runtime_end clientGatewayRuntimeEnd 浏览器网关事件处理器 客户端操作的浏览器网关运行时结束
client.gateway.step_complete clientGatewayStepComplete 浏览器网关事件处理器 浏览器网关步骤完成,携带 operation 与 step 序号
client.gateway.stream_start clientGatewayStreamStart 浏览器网关事件处理器 浏览器网关流开始,携带 operation 与首个 step 元数据
client.runtime.complete clientRuntimeComplete 聊天流式执行器 浏览器聊天运行时完成,携带 operation、topic、thread 与状态
client.runtime.start clientRuntimeStart 聊天流式执行器 浏览器聊天运行时启动;工作流可将其桥接为 agent.user.message 并携带序列化上下文
runtime.after_step runtimeAfterStep Agent Runtime 服务端运行时步骤结束
runtime.before_step runtimeBeforeStep Agent Runtime 服务端运行时步骤即将执行

浏览器侧两个主要生产者的入口源码在 src/store/chat/slices/agentRun/actions/lifecycle/agentSignalBridge.ts(特性开关门控的非阻塞发射桥)与其配套测试 agentSignalBridge.test.ts;服务端侧 Agent Runtime 的发射钩子集成测试见 apps/server/src/services/agentRuntime/tests/agentSignalHooks.integration.test.ts,Bot 路由测试见 apps/server/src/services/bot/tests/BotMessageRouter.test.ts

除目录表外,实际的类型目录还内置了更多源类型常量(同样定义于 src/source/sourceTypes.ts),包括 agent.nightly_review.requestedagent.self_reflection.requestedagent.self_feedback_intent.declaredtool.outcome.completedtool.outcome.failed,它们服务于夜间回顾、自我反思、自我反馈意图与工具结果等闭环场景——服务端实现可参见 apps/server/src/services/toolExecution/serverRuntimes/ 下的 agentSignalReview.tsagentSignalReflection.tsagentSignalFeedbackIntent.tsagentSignalSkillManagement.ts。从源码结构看,这是一个会持续扩充的目录,新增事件需要遵循第五节的可扩展性约定。

客户端白名单AGENT_SIGNAL_CLIENT_SOURCE_TYPES 是浏览器来源事件的允许清单,目前只包含所有 client.* 事件(见 src/source/sourceTypes.ts,其类型上用 Extract<AgentSignalSourceType, \client.${string}`>做了约束)。该清单由认证后的服务端入口消费:在apps/server/src/routers/lambda/agentSignal.ts(含配套测试 agentSignal.test.ts`)中校验浏览器来源事件的类型是否合法。

事件守卫(类型收窄):包内还提供了一组类型守卫(同样在 src/source/sourceTypes.ts),用于在运行时中间件或工作流入口处把"泛化节点"收窄为强类型的特定源变体:isAgentSignalKnownSource(是否属于内置目录)、isClientRuntimeStartSource(是否为 client.runtime.start,工作流入口用它识别待桥接的浏览器运行时启动)、isAgentUserMessageSource(反馈分类器只处理用户消息源)、isNightlyReviewSourceisSelfReflectionSourceisSelfFeedbackIntentSourceisToolOutcomeSource。配合 AgentSignalSourceVariant<TSourceType> 与成组的具名变体类型(SourceAgentUserMessageSourceAgentExecutionCompletedSourceEventClientRuntimeStart 等),可让调用点在收窄后直接获得按源类型精确化的 payload 类型。

五、分层使用指南:浏览器、包、服务端各司其职

README 对"该在哪一层使用什么"给出了明确分界,这直接影响你写业务代码时 import 哪一侧:

在此包中做(纯契约):

  • 浏览器/服务端共享的源事件类型
  • 源类型常量与载荷映射
  • 作用域键推导
  • 纯 source / signal / action / result / trigger 类型
  • 纯节点构建器

在浏览器服务门面做(副作用):

浏览器侧的"门控 + 非阻塞"发射由桥接层承担:src/store/chat/slices/agentRun/actions/lifecycle/agentSignalBridge.ts,其配合测试 agentSignalBridge.test.ts 位于同目录。它会先检查特性开关,再决定真正把事件送出还是静默跳过,避免 UI 主链路被旁路信号阻塞。

在服务端服务做(副作用与执行):

  • 特性门控(feature gating)
  • 数据库访问
  • 工作流交接(workflow handoff)
  • 策略/运行时执行
  • Redis 或内存去重
  • 可观测性持久化

服务端对外入口即上面提到的 Lambda 路由 apps/server/src/routers/lambda/agentSignal.ts:它负责认证浏览器来源的 ingress,并基于 AGENT_SIGNAL_CLIENT_SOURCE_TYPES 做客户端源类型校验;服务端产源侧的代表性实现则是 AgentRuntimeServiceBotMessageRouter。可观测性消费侧可参考 packages/agent-tracing/src/viewer/agentSignal.ts,它把同一套语义节点落到追踪视图中。

从仓库布局看,README 中 src/server/... 前缀所指的服务端集成点(发射器、编排器、源去重存储、异步工作流触发等)对应当前 monorepo 下 apps/server 一侧的服务实现;浏览器侧 src/servicessrc/store/... 路径则与仓库布局一致。整体链路可以概括为:浏览器/运行时/机器人生产者 → createSourceEvent 归一化(本包)→ 经桥接与 tRPC 或服务内直连进入服务端 → 服务端校验白名单 → 按 scopeKey 去重与分桶 → 编排器执行策略与运行时 → 可观测性与持久化

六、如何新增一个源事件:五步扩展流程

当出现一种新的、需要被策略感知的"发生了什么事"时,README 给出了标准五步流程:

  1. src/source/sourceTypes.ts 添加常量与 payload 形状:同时把新类型写进 AGENT_SIGNAL_SOURCE_TYPESAgentSignalSourcePayloadMap,从而获得端到端强类型;
  2. 在服务端源渲染层添加渲染器:如果泛化 payload 在成为 AgentSignalSource 之前需要归一化(例如把浏览器侧的扁平字段映射为服务端规范结构),则为其编写 renderer;
  3. 把渲染器注册到源构建路径(buildSource)中,保证新事件在入口处被正确归一化;
  4. 仅当浏览器代码应被允许经 Lambda 路由发射该事件时,才把它加入 AGENT_SIGNAL_CLIENT_SOURCE_TYPES——所有非 client.* 服务端事件默认不应出现在白名单中;
  5. 依据新的生产路径补充聚焦测试:源事件创建(本包内,参照 src/source/sourceEvent.test.ts)、路由白名单校验(参照 apps/server/src/routers/lambda/agentSignal.test.ts)、渲染器归一化或工作流桥接(参照 apps/server/src/workflows/agentSignal.test.ts)。

这条流程把"加常量"到"加测试"串成了一条可回溯的扩展链路:核心契约层只做类型与归一化,是否允许浏览器直发、如何渲染进服务端、如何被工作流桥接,都留给了各自的层去决定与验证。

稳定性约定:新增事件时必须保持既有源事件稳定。因为源类型字符串会被持久化到**追踪记录(traces)、去重键(dedupe keys)与工作流载荷(workflow payloads)**中——一旦线上已有历史数据携带旧字符串,改名或删除将造成去重失效与历史追踪断链。扩展时应只增不改,并通过第 5 步的测试锁住字符串常量与 scopeKey 派生结果。

七、小结

@lobechat/agent-signal 的价值在于把 Agent Signal 的全链路词汇沉淀为一份不依赖任何运行时的共享契约:核心节点类型与纯构建器让"源 → 信号 → 动作"的因果链在任何端侧都能被一致地构造与检查;createSourceEvent 让所有生产者产出同构事件;作用域键算法让去重、工作流与运行时按稳定语义分桶;客户端白名单与守卫函数让浏览器侧在受控范围内安全发声。当你在 LobeHub 中扩展一个新的策略入口或新的运行时事件时,最合理的做法就是先回到这里定义你的源事件契约,再交给上层去执行——这也正是 README 反复强调的分层纪律:契约留在包内,副作用回归宿主。

延伸阅读:包完整文档见 packages/agent-signal/README.md;想深入类型系统,建议按 src/base/types.tssrc/source/sourceTypes.tssrc/source/scopeKey.ts 的顺序阅读,并结合 src/source/sourceEvent.test.ts 验证行为预期。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388