Skip to content

docs(agent-engine): Use Agent Engine to decouple components - #438

Draft
xxx7xxxx wants to merge 10 commits into
OpenCSGs:mainfrom
xxx7xxxx:refactoring
Draft

docs(agent-engine): Use Agent Engine to decouple components#438
xxx7xxxx wants to merge 10 commits into
OpenCSGs:mainfrom
xxx7xxxx:refactoring

Conversation

@xxx7xxxx

@xxx7xxxx xxx7xxxx commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

@xxx7xxxx xxx7xxxx changed the title doc(agent-engine): Use Agent Engine to decouple components docs(agent-engine): Use Agent Engine to decouple components Aug 4, 2026
@GatewayJ

GatewayJ commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

CSG channel 和 Feishu 不需要在接入、存储、鉴权上完全对称,只应在各自 Channel Adapter 完成事件校验、身份识别、去重、会话键构造和渲染之后,以统一的 turn 进入 Agent Engine,再由 Runtime Adapter 对接 Codex 等运行时。

对 Codex 而言,这条路可行,因为它已有直接的 Session/Prompt/Event/Cancel 能力;但 OpenClaw、PicoClaw 目前仍由 sandbox 内部的 Feishu channel 消费消息并注入 App 凭据,若后续再启用 host 侧 Feishu Adapter,必须通过 Binding 明确 runtime_owned 或 host_direct,保证一个机器人只有一个消费者,否则会出现重复和竞争消费。

三个契约需要考虑:

  1. turn 必须有 ExecutionID,否则 Feishu 连续消息排队时无法准确取消被新消息淘汰的旧 turn;
  2. Feishu Adapter 要保留 sender open_id 并按 binding 执行用户/群聊白名单和机器人身份校验,不能只靠 @ 提及;
  3. codex等原生不带飞书channle 如何获取飞书 机器人的appinfo

)

// AgentInterface manages persisted Agent resources and their Runtime lifecycle.
type AgentInterface interface {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is AgentInterface intended to represent a single Agent or the Agent collection? It includes List, and every instance-level operation accepts an agentID, so it currently behaves as a collection-scoped interface; however, the singular name makes the scope unclear. Please make this explicit: if it represents the collection, consider AgentCollectionInterface or AgentsInterface; if it represents one Agent, select the Agent before returning the interface, remove the repeated agentID parameters, and keep List on a separate collection interface.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming AgentInterface is collection-scoped, which component is responsible for storing the Agent collection and serving Get/List? The document assigns Agent persistence to the existing Agent Service and states that Agent Engine must not copy durable Agent state, while agentengine.Interface exposes Agents(). How are these ownership boundaries intended to fit together?

@xxx7xxxx xxx7xxxx Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this interface is collection-scoped, but I see naming and storage ownership as separate concerns.

The singular name intentionally follows the Kubernetes typed-client convention. For example, Pods(namespace) PodInterface returns a resource interface that supports both List and name-addressed operations such as Get and Delete. Likewise, Agents() AgentInterface returns the interface for Agent resources, while agentID selects an individual Agent. Splitting collection and instance operations into separate interfaces would diverge from that convention without making the API materially simpler.

Exposing Agents() also does not mean that Agent Engine stores or owns the Agent collection. It is the public resource-oriented facade, while the existing Agent Service remains the single source of truth for Agent persistence, desired state, Runtime lifecycle, and Get/List (Of course, we need to refactor it for the new architecture). The eventual AgentInterface implementation delegates those operations to the Agent Service. Agent Engine adds only the coordination needed where lifecycle operations interact with active Turns, such as ensuring that Recreate or Delete does not race with ongoing execution.
In short, Agent Engine owns the contract and execution coordination; Agent Service owns the durable Agent state behind that contract.

@xxx7xxxx xxx7xxxx Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6516072 has added the explanation and incremental solution; see if it has fixed your concern.

@xxx7xxxx

xxx7xxxx commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@GatewayJ 感谢。我们的目标也是只统一 Channel Adapter 之后的 Turn 边界,不要求 CSGClaw Channel 和 Feishu 在接入、存储或鉴权上对称。

  1. turn 必须有 ExecutionID,否则 Feishu 连续消息排队时无法准确取消被新消息淘汰的旧 turn;

关于 ExecutionID,你说得对。ConversationKey 只能标识 Conversation(各个 Channel Adapter自己觉得如何拼接,最佳实践是都在 ConversationKey 上加上自己 channel 的前缀,例如 feishu,csgclaw-im)。在存在 Running Turn 和 Queued Turn 时,无法精确表达 superseding 要取消哪一个 Turn。这里会恢复一个 Turn 级 ID,可能命名为 TurnID,用于 TurnRequest、Cancel 和 Engine 内部的 Turn Registry,不再把它扩散到不需要的类型。

已在 85f9be9 更新,看看是否能解决你的 concern。

  1. Feishu Adapter 要保留 sender open_id 并按 binding 执行用户/群聊白名单和机器人身份校验,不能只靠 @ 提及;

同意。sender open_id、chat identity、Binding、用户/群聊授权和机器人身份校验都应由 Feishu Adapter 在进入 Agent Engine 前完成,@ 只作为触发条件,不能作为授权。Agent Engine 只接收已经验证的统一 Turn。

  1. codex等原生不带飞书channle 如何获取飞书 机器人的appinfo

Codex Runtime 不需要获取 Feishu AppInfo。App ID 和 App Secret 由 Host Feishu Adapter 通过 Binding 持有;Adapter 负责收发 Feishu 消息,Codex 只处理规范化 Input 并返回 Event/Result,凭据不会进入 Runtime。

@wanghaojie124

Copy link
Copy Markdown
Collaborator

结合当前文档,我建议在结束 Phase 0 前再明确下面两个契约:

  1. 把 Agent 生命周期与 Turn 的协调从原则落实为可验证的协议。

    文档已经明确 Agent Service 持有持久状态和 Runtime lifecycle,Agent Engine 负责 lifecycle change 与 active Turn 的协调,但目前还没有规定竞态下的具体语义。例如:Run 读取到 ready Runtime 后,Recreate/Delete/Stop 恰好开始时,如何保证 Turn 不会 dispatch 到正在被替换或删除的 Runtime?

    建议至少在设计和验收测试中明确:

    • Run 获取包含 RuntimeID 和 generation 的 execution target snapshot/lease;
    • Stop/Recreate/Delete 先关闭新 admission,再处理 queued Turn,并等待 running Turn drain;
    • 明确 drain timeout、queued Turn 取消/失败以及生命周期操作失败时的结果;
    • Recreate 后旧 generation 的 conversation mapping 不能被新 Runtime 继续使用;
    • 明确锁顺序,避免 Agent Service 与 Engine 互相等待。

    这个 lease 不一定需要进入公共 API,但内部协议和状态转换需要成为可测试的契约。

  2. Named Session 的 ready 条件不应只依赖 Dispatched=true

    Dispatched=true 只能说明请求已进入 Runtime Adapter,不一定证明 Runtime-native conversation mapping 已成功创建并持久化。如果 Adapter 在创建 mapping 时失败,Named Session 仍可能被标记为 ready,下一次使用 require_existing 时只能得到 conversation_not_resumable

    建议:

    • 由 Runtime Adapter/TurnResult 明确返回 mapping 已建立的状态(例如 ConversationEstablished),只有确认持久化成功后才把 Named Session 改为 ready
    • 明确 Named Session 以 (agentID, externalSessionID) 为唯一作用域;
    • 定义进程在 initializing 状态崩溃后的恢复规则;
    • 考虑记录 Runtime generation,确保 Recreate/Delete 后能识别过期 binding,而不是把它误认为仍可严格续接。

@xxx7xxxx

xxx7xxxx commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@GatewayJ 已在 7ec761a 更新我们讨论的共识

@RussellLuo

Copy link
Copy Markdown
Collaborator

基于现有实现和这份新架构,我觉得 Phase 2 还需要补充 Channel consumer 生命周期如何迁移 的说明。

先澄清一下现状:当前 internal/agent.Service 并不直接持有或写入 IM 消息存储;消息实际由 internal/im.Service 持久化,并由 Session、API 或 Channel 路径调用。当前真正存在的是行为和控制流层面的耦合:internal/agent.Service.syncLifecycleForAgent 会调用 LifecycleObserver.EnsureAgent/StopAgentcli/serve 又把 LifecycleObserverBindingActivator 都接到了 codexBridgeMgr,而后者负责启动和停止 CSGClaw、Feishu 的 bridge worker。也就是说,目前 Agent 的生命周期变化会间接控制 Channel consumer,consumer 再通过各自的 client 发送和持久化消息。

按照本文描述的目标边界,我的理解是:

  • Agent resource implementation 负责 Agent 持久化、期望状态、Runtime 生命周期和 execution target 协调;
  • Agent Engine 只负责 admission、同一 Conversation 的串行化、dispatch、active Turn 和交互协调,不持有 IM 状态;
  • Channel/Binding 侧负责 ingress、binding、consumer 生命周期、鉴权与去重、transcript 持久化、渲染和投递。Built-in IM 应在调用 Run 前持久化用户消息,并在 Run 返回后渲染和持久化结果;Anonymous Session 则不再创建任何 IM 实体。

但是目前 Phase 2 只写了“Move built-in IM execution behind Agent Engine”,还没有解释现有 LifecycleObserver / BindingActivator -> codexBridgeMgr 这条控制链如何被移除或迁移。这里是否可以在文档中进一步说明:

  1. 迁移后由哪个组件启动和停止 Channel consumer?它是否应由 Binding/Channel 生命周期驱动,而不是由 Agent Stop 或 Runtime restart 驱动?
  2. 当前 Agent Service 被重构或替换后,LifecycleObserverBindingActivator 是否会移出 Agent 资源实现;如果会,新的唯一 owner 是谁?
  3. Agent Stop、Recreate、Delete 分别应该如何影响已有 Binding、正在运行的 consumer 和已保存的 transcript?
  4. 如何保证 Runtime restart 不会重建或重复启动 Channel consumer,从而避免同一机器人出现多个消费者?
  5. 验收测试如何证明只有 Channel Adapter 会读写 IM 消息,而 Agent resource implementation 和 Agent Engine 既不持有 IM 状态,也不控制 Channel consumer?

如果缺少这部分迁移契约,最终可能只是包依赖上看起来完成了解耦,但 Agent 生命周期仍然在行为上与 Built-in IM 或 Feishu 的 Channel 生命周期绑定在一起。

@xxx7xxxx

xxx7xxxx commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@wanghaojie124

3fe09f2 已补全生命周期和 Session Binding 契约。生命周期现在明确了 Admission 关闭、Queued Turn 取消、Running Turn Drain、超时处理及 Runtime Pinning。
Dispatched=true 现在保证 Mapping 已持久化且 Turn 已提交,因此不再增加 ConversationEstablished。同时将 Named Session 更名为更准确的 Session Binding,明确它只是 (agentID, externalSessionID) 到 ConversationKey 的绑定。Recreate/Delete 会删除 Mapping,所以暂不需要 Runtime generation。

@RussellLuo

RussellLuo commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

结合当前的分阶段计划,我觉得这里需要区分两个不同的问题:一是 Agent Engine 最终的职责,二是这些职责在每个阶段如何接入现有实现。

按照当前接口,Agent Engine 最终同时通过 Agents() 提供 Agent 创建、删除、启动、停止等控制面操作,并通过 Conversations(agentID) 提供对话执行的数据面操作。因此疑问并不是 Engine 是否应该负责控制面——它应该负责。真正需要确认的是:Engine 在承载控制面契约之后,是否必须重新实现或替换现有 Agent Service,还是可以把现有 Agent Service 作为控制面的后端实现,由 Engine 负责统一编排和生命周期协调?

从当前实现看,internal/agent.Service 已经是整个应用中稳定且广泛使用的 Agent 能力入口,并不只是一个可以轻易替换的存储 adapter:

  • internal/api.Handler 直接持有 *agent.Service,Agent CRUD、Profile、Skills、MCP、Workspace、Runtime Start/Stop/Recreate、Binding 等 API 都直接调用它;
  • cli/serve、Participant、AgentTask、Team、LLM、Feishu binding 和 Runtime wiring 等模块也直接依赖 *agent.Service
  • Agent Service 当前同时实现了持久化 Agent registry、Profile/config、Workspace、Runtime selection 和 Runtime lifecycle。

不过,这个较大的迁移问题不一定需要阻塞只改 Anonymous Session 的第一阶段。如果 Phase 1 的实际目标只是把 Session API 切到 Conversations(agentID),那么这条数据面路径只需要通过一个窄的 ExecutionTargetProvider 获取当前可执行的 Agent/Runtime,并不需要同时把现有 Agent CRUD API 和其他调用方切换到 Agents() facade,也不需要立刻替换 Agent Service。

这里仍有一个 Phase 1 必须处理的最小控制面交点:现有 Agent API 仍可能在 Session Turn 执行期间调用 Stop、Runtime-affecting Update、Recreate 或 Delete。即使这些 API 暂时继续调用 Agent Service,它们也必须与 Engine 共享同一个 Agent-scoped coordinator,才能关闭 admission、处理 queued Turn 并 drain running Turn。这个协调可以通过窄接口或 composition-root wiring 完成,不要求第一阶段就把整个控制面迁移到 Engine。

因此是否可以采用下面这种渐进结构:

Phase 1: Anonymous Session

Session API
  -> Agent Engine.Conversations()
      -> ExecutionTargetProvider adapter
          -> existing Agent Service / Runtime owner
      -> Runtime Adapter

existing Agent lifecycle API
  -> existing Agent Service
      -> shared Agent-scoped coordinator


Later control-plane integration

Agent CRUD / lifecycle CLI and API
  -> Agent Engine.Agents()                  # control-plane contract and coordination
      -> AgentControlBackend adapter
          -> existing Agent Service         # persistence and Runtime lifecycle implementation


Phase 2/3: Built-in IM and Feishu

Channel Adapter
  -> Agent Engine.Conversations()
  -> IM Service / Participant and Binding Service

在这个结构下:

  1. Agent Engine 最终仍然是统一的公共 facade,同时负责控制面和数据面,并没有被收窄成只处理 Conversation。
  2. Phase 1 可以只落地 Conversations()、Codex Runtime Adapter、Session Binding 和 Session execution path;现有 Agent Service 继续作为唯一的 persistence/Runtime lifecycle implementation,只增加与 Engine 共享的 lifecycle coordinator。
  3. Agents() 控制面正式接入时,Stop/Recreate/Delete 等操作先由 Engine 协调 active Turn,再通过 AgentControlBackend 委托现有 Agent Service 完成实际的持久化和 Runtime lifecycle 变更。Engine 拥有对外契约和跨控制面/数据面的协调,但不需要因此复制底层状态或重写全部实现。
  4. 现有 CLI/API 中属于 Agents() 公共控制面的入口可以在该阶段逐步切到 Engine;Participant、Team、Task、LLM 等内部模块如果只需要 Agent 查询或特定能力,则可以继续使用现有 Service,或者逐步改为更窄的 capability interface,不需要一次性全部迁移。
  5. Built-in IM 和 Feishu 迁移时,再重点处理现有 Channel Bridge 与 Agent Service 的关系。最新文档已经把 Channel Event Worker 放到 Binding-driven Channel ownership,并将移除 LifecycleObserver / BindingActivator -> codexBridgeMgr 控制链列入 Phase 2/3,这与上述分阶段方式是一致的。

当前文档的 Phase 1 同时写了“实现 AgentInterface,并按需重构或替换现有 Agent Service”和“迁移 Anonymous Session”。如果第一阶段实际上只需要迁移 Session API,是否可以把这两件事进一步拆开,避免为了数据面迁移提前引入整个控制面的实现替换?

所以这里想进一步确认:

  1. Phase 1 是否只要求 Session API 通过 Conversations() 进入 Engine?如果是,是否可以暂不迁移现有 Agent CRUD/lifecycle facade?
  2. Phase 1 中,现有 Agent Service 的生命周期操作计划如何接入共享 coordinator,以保证它们不会与 Session active Turn 竞态?
  3. AgentInterface 不作为现有 Agent Service 的 adapter,而要重构或替换 Agent Service,是这套架构的必要目标,还是后续控制面落地时的一种可选实现?
  4. 当 Built-in IM、Feishu 和 Agents() 控制面逐步接入后,是否可以由 Agent Engine 拥有统一契约和协调,而现有 Agent Service 继续作为唯一的 persistence/Runtime lifecycle backend?

我认为这里的核心不是 Engine 是否负责控制面,而是 控制面契约、底层状态 owner 和分阶段迁移不必在 Phase 1 同时完成。第一阶段只接入 Session 数据面并补上必要的 lifecycle coordination;在后续改造 Built-in IM、Feishu 或正式迁移 Agents() 时,再处理现有调用面和 Agent Service 后端复用,会更符合“每个阶段可独立验证和发布”的目标。

@xxx7xxxx

xxx7xxxx commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@RussellLuo

fe06c20 已补充相关迁移契约。

为避免误解,文档将 Channel Consumer 统一命名为 Channel Event Worker,指现有负责监听 Channel Event 的后台 bridge worker。Event Worker 由 Channel/Binding 层按稳定的 Binding ID 管理,不再由 Agent 生命周期或 RuntimeID/SessionID 驱动。Agent Stop、Recreate 和 Runtime restart 不影响 Worker、Binding 或 transcript;Agent Delete 由应用和 Binding 层完成清理。

@RussellLuo

Copy link
Copy Markdown
Collaborator

@xxx7xxxx 感谢,fe06c20 已经回答了我前一条关于 Channel Event Worker 生命周期归属的问题。

补充说明一下:你回复时,我后面那条评论还是旧版本;我刚刚已经更新了它:更新后的评论

更新后的 concern 不再是 Channel Event Worker,而是分阶段迁移边界:

  • Phase 1 如果只迁移 Session API,是否可以只落地 Conversations(),暂不迁移或替换现有 Agent CRUD/lifecycle facade;
  • 现有 Agent Service 的生命周期操作如何通过共享的 Agent-scoped coordinator 与 Session Turn 协调;
  • 后续接入 Agents()、Built-in IM 和 Feishu 时,Engine 是否可以拥有控制面契约和协调,而现有 Agent Service 继续作为 persistence/Runtime lifecycle backend。

麻烦再基于更新后的版本看看这部分是否与你的实施计划一致。

@xxx7xxxx

xxx7xxxx commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator Author

@RussellLuo

可以让 Phrase 1 步幅更小一些,文档已按这个边界调整 8693547

  1. Phase 1 只实现 Conversations() 并迁移 Anonymous Session,现有 Agent CRUD/lifecycle API 暂时不变。
  2. Conversation 通过私有 Adapter 复用现有 Agent Service,不增加公共 ExecutionTargetProvider。
  3. Phase 1 会扩展现有 internal/agent.agentLifecycleGate。它目前负责串行化生命周期操作,后续增加 admission、queued Turn 和 active Turn 协调,不再引入第二个 Coordinator。
  4. Phase 2 再实现 Agents() 并迁移控制面,同时删除临时 Adapter。现有 Store、数据格式和底层 Runtime 代码可以复用,但当前职责宽泛的 Agent Service 不作为永久 Backend,不能一直在这个依赖巨多的包里重构打转。

因此 Phase 1 不会被完整控制面改造阻塞,但扩展现有 lifecycle gate(coordinator) 是该阶段必须完成的。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants