# Ticket Manager 飞书 Bot 接入 Agent Gateway 设计

创建日期：2026-08-31

状态：Agent Gateway implemented；Ticket Manager implemented；旧同步对话服务已于 2026-09-02 移除

基线：`shiori/agent-gateway@0090b776`

## 1. 背景

Ticket Manager 当前直接接收飞书 Bot 消息，并在 Conversation Handler 中调用
Conversation Service。Conversation Service 再通过 OpenCode 执行 Agent，最后由 Ticket Manager
生成飞书文本或卡片并发送回飞书。

Agent Gateway 已经具备 Session、Task、Agent Runtime 和 Task Event Listener 能力，但当前
同时持有飞书 Bot 凭证、建立飞书长连接、解析飞书消息，并直接渲染和发送
飞书卡片。这使 Agent 执行与飞书渠道接入、展示逻辑耦合。

本设计将 Ticket Manager 飞书 Bot 的 Agent 执行切换到 Agent Gateway，同时保持
飞书接入和展示归 Ticket Manager 所有。

## 2. 设计结论

最终边界如下：

1. Ticket Manager 接收飞书事件，复用现有 Lark Middleware 和
   `LarkEventDispatcher`。
2. Ticket Manager 在调用 Agent Gateway 前完成 Bot 消息过滤，包括发送方、
   消息类型、群聊 mention、允许用户和最大消息长度。
3. Ticket Manager 通过 Agent Gateway 的 gRPC 或 gRPC-Gateway HTTP
   `GatewayService.ProcessLarkMessage` 提交已通过过滤的飞书消息。
4. `GatewayService` 从飞书消息中统一构造 `MessageSourceType`、
   `SessionSource`、`LarkSessionContext`、`source_session_key`、`TaskSource` 和
   Prompt，然后调用 `TaskService.Process`。
5. `TaskService.Process` 在 Task 持久化后立即返回，Agent 在 Agent Gateway 后台
   异步执行。
6. Agent Gateway 的 HTTP Event Listener 将 Task 开始、Agent Commentary 和 Task
   结束事件投递到 Ticket Manager。endpoint 由 Agent Gateway 配置文件按
   `MessageSourceRef` 配置。
7. Ticket Manager 处理事件，负责 render，并通过现有 Lark Client
   发送 card message、reply 或 reaction。

`GatewayService` 是飞书消息到 Task Service 的协议适配层，不负责飞书验签、
长连接、Webhook 接入或飞书 API 调用。

## 3. 范围

### 3.1 本期范围

- 使用 Agent Gateway 替换 Ticket Manager 飞书 Bot 当前的 OpenCode 调用流程。
- 新增 Agent Gateway `GatewayService.ProcessLarkMessage` gRPC 接口及
  gRPC-Gateway HTTP 路由。
- 将飞书 Session 和 Task 来源解析收敛在 Agent Gateway Gateway Service。
- 在 Agent Gateway 中使用 HTTP Event Listener 向 Ticket Manager 投递事件。
- 在 Ticket Manager 中新增 event handler，并基于共享 Card Render 组件和
  Lark Client 发送消息。
- 从 Agent Gateway 移除 Lark Client、飞书 Bot 连接和卡片渲染职责。

### 3.2 非目标

- 不替换 Ticket Manager 现有同步 `POST /api/v1/ticketmanager/conversations`
  API。该 API 继续使用现有 Conversation Service/OpenCode 流程并同步返回
  Answer。
- 不修改 Ticket Manager 中其他 `LarkEventDispatcher` consumer，例如
  Workspace Advice。
- 不实现 event delivery Outbox、持久化重试或死信队列。
- 不实现 Agent Gateway 与 Ticket Manager 之间的认证、授权、签名或 mTLS。
  本期将两个服务视为可信内网环境。
- 不优化中间 Agent Commentary 的卡片合并、单卡 patch 或消息降噪策略。
- 不接入 Slack、Web 或其他新渠道。
- 不改造 Agent Gateway Task 的跨副本 claim 和运行中 Task 恢复机制。

## 4. 职责边界

### 4.1 Ticket Manager

Ticket Manager 保持为飞书 Bot 的渠道所有者，负责：

- 飞书事件验签、解密和 HTTP Event Callback。
- 通过 `LarkEventDispatcher` 分发 `im.message.receive_v1`。
- 调用 Agent Gateway 前的 Bot 消息过滤。
- 根据飞书 sender user ID 查询 Core User email，并将 event 映射为
  `ProcessLarkMessageRequest`；不计算 session key，不构造 `SessionSource`。
- 接收 Agent Gateway HTTP Task Event。
- 根据 Task event 渲染 Commentary、Result、Rejected 或 Failed 卡片。
- 使用原始 Task 的 `source_message_id` 调用 `ReplyMessage`、reaction 或后续
  card API。

Ticket Manager 不保存 Agent Session 和 Agent Turn 状态，不直接启动 Agent
Runtime。

### 4.2 Agent Gateway Gateway Service

Gateway Service 是 Task Service 的入口适配层，负责：

- 校验 `ProcessLarkMessageRequest` 的必要结构字段。
- 根据 RPC method 确定 `MessageSourceType=LARK`。
- 从飞书消息构造 `LarkSessionContext`。
- 统一计算 `source_session_key`。
- 构造 `TaskSource` 和 Prompt。
- 调用 `TaskService.Process`，并返回 Task 接收结果。

Gateway Service 不直接调用 Agent Runtime，不复制 Session/Task 持久化和幂等
逻辑。

### 4.3 Agent Gateway Task Service

Task Service 继续负责：

- 按 source session 查找或创建 Session。
- 以 `(session_id, source_message_id)` 幂等创建 Task。
- 在 Task 持久化后异步调度执行。
- 选择 Agent provider，处理 Exec 或 Steer。
- 产生 `OnTaskStarting`、`OnAgentCommentary` 和 `OnTaskFinished` 事件。

### 4.4 Agent Gateway HTTP Event Listener

HTTP Event Listener 实现 `TaskEventListener`，负责：

- 根据配置的 `MessageSourceRef.type + instance_id` 订阅 Task lifecycle event。
- 将 Task lifecycle 事件转换为稳定 HTTP JSON payload。
- 使用有界的 HTTP timeout 调用 endpoint。
- 在投递失败时返回 error，由 Task Service 按现有 listener 语义记录
  日志，但不改变 Task 执行结果。

HTTP Event Listener 不引用 Lark Client，不生成飞书 card JSON。

## 5. 端到端流程

```mermaid
sequenceDiagram
    participant Lark as "Feishu/Lark"
    participant Dispatcher as "Ticket Manager Lark Dispatcher"
    participant TM as "Ticket Manager Bot Adapter"
    participant Gateway as "Agent Gateway GatewayService"
    participant Task as "Agent Gateway TaskService"
    participant Agent as "Agent Runtime"
    participant Reactor as "Agent Gateway HTTP Event Listener"
    participant Endpoint as "Ticket Manager Event Endpoint"

    Lark->>Dispatcher: im.message.receive_v1
    Dispatcher->>TM: dispatch event
    TM->>TM: filter sender/type/mention/user/size
    alt ignored message
        TM-->>Dispatcher: return nil
    else accepted message
        TM->>Gateway: ProcessLarkMessage(message)
        Gateway->>Gateway: derive source/session/task/prompt
        Gateway->>Task: Process(ProcessRequest)
        Task->>Task: persist or find idempotent Task
        Task-->>Gateway: Task
        Gateway-->>TM: accepted Task
        Task->>Agent: async Exec or Steer
        Task->>Reactor: OnTaskStarting
        Reactor->>Endpoint: task_starting
        Endpoint->>Lark: reaction or card
        Agent-->>Task: agent commentary
        Task->>Reactor: OnAgentCommentary
        Reactor->>Endpoint: agent_commentary
        Endpoint->>Lark: render and reply card
        Agent-->>Task: completed result
        Task->>Reactor: OnTaskFinished
        Reactor->>Endpoint: task_finished
        Endpoint->>Lark: render and reply card
    end
```

Ticket Manager 的飞书 Event HTTP Handler 仍按现有 Dispatcher 语义快速返回。
`ProcessLarkMessage` 不等待 Agent 完成，只等待 Session/Task 幂等创建结束。

## 6. Ticket Manager 调用前过滤

Ticket Manager Bot Adapter 在调用 RPC 前执行当前 Bot 的渠道策略：

1. `sender_type == app` 时忽略；
2. 非 `text` 消息忽略；
3. 私聊消息允许处理；
4. 群聊消息只在允许群聊且明确 mention 当前 Bot 时处理；
5. 配置了 allowed users 时，发送人必须在允许列表中；
6. 解析后的文本不得超过最大长度。

过滤属于 Ticket Manager Bot 的产品与渠道策略。Gateway Service 仍对 RPC
必要字段做结构校验，但不重复维护 allowed users 或群聊策略。

## 7. RPC Contract

### 7.1 位置

新增 protobuf contract：

```text
packages/contracts/agent-gateway/api/agent-gateway/gateway/v1/gateway_service.proto
packages/contracts/agent-gateway/entity/agent-gateway/gateway/v1/gateway.proto
```

对应 package：

```proto
// gateway_service.proto
package aisupport.agent_gateway.api.gateway.v1;

// gateway.proto
package aisupport.agent_gateway.entity.gateway.v1;
```

该 Service 同时注册到 Agent Gateway gRPC Server 和 gRPC-Gateway。HTTP 路由为：

```http
POST /api/v1/agent-gateway/lark/messages:process
Content-Type: application/json
```

### 7.2 Service

```proto
service GatewayService {
  rpc ProcessLarkMessage(ProcessLarkMessageRequest) returns (ProcessLarkMessageResponse) {
    option (google.api.http) = {
      post: "/api/v1/agent-gateway/lark/messages:process"
      body: "*"
    };
  }
}
```

### 7.3 Request

RPC contract 定义稳定的最小 Lark Message 字段，不直接传递 Go Lark SDK
对象，也不传递整个原始 webhook JSON。

```proto
message ProcessLarkMessageRequest {
  string instance_id = 1;
  aisupport.agent_gateway.entity.gateway.v1.LarkMessage message = 2;
}
```

`LarkMessage` 和 `LarkMention` 位于 Gateway entity contract：

```proto
package aisupport.agent_gateway.entity.gateway.v1;

message LarkMessage {
  string message_id = 1;
  string root_id = 2;
  string thread_id = 3;
  string chat_id = 4;
  string chat_type = 5;
  string message_type = 6;
  string content = 7;
  string sender_type = 8;
  string sender_email = 9;
  repeated LarkMention mentions = 10;
}

message LarkMention {
  string key = 1;
  string open_id = 2;
}
```

除 `sender_email` 由 Ticket Manager 通过 Core User 解析外，其余字段直接来自
event。`content` 保留飞书 text message 的 JSON content，由 Gateway Service
解析 Prompt 并移除 mention key。

`instance_id` 标识调用方使用的 Lark Bot instance，同时用于：

- 构造 `MessageSourceRef.instance_id`；
- 隔离不同 Bot 的 source session；
- 选择匹配的 Task Event Listener 配置。

### 7.4 Response

```proto
enum ProcessLarkMessageDisposition {
  PROCESS_LARK_MESSAGE_DISPOSITION_UNSPECIFIED = 0;
  PROCESS_LARK_MESSAGE_DISPOSITION_ACCEPTED = 1;
  PROCESS_LARK_MESSAGE_DISPOSITION_DUPLICATED = 2;
}

message ProcessLarkMessageResponse {
  ProcessLarkMessageDisposition disposition = 1;
  aisupport.agent_gateway.entity.session.v1.Task task = 2;
}
```

Ticket Manager 已经在调用前过滤不需要处理的消息，因此 RPC 不返回
`IGNORED`。缺少必要字段、无法解析 text content 等输入问题返回
`InvalidArgument`。Task/Session 持久化错误返回对应 gRPC error。

为区分 `ACCEPTED` 和 `DUPLICATED`，Task Service 内部 Process 结果需要显式暴露
Task 是否在本次调用中创建，但不改变数据库幂等语义。

## 8. Lark Session 和 Task 映射

### 8.1 Message Source

`ProcessLarkMessage` 固定构造：

```text
MessageSourceRef.type        = MESSAGE_SOURCE_TYPE_LARK
MessageSourceRef.instance_id = request.instance_id
```

RPC 调用方不需要传入 source type。method 本身即确定渠道类型。

### 8.2 Session Key

Lark session anchor 使用：

```text
anchor_message_id = message.root_id
anchor_message_id = message.message_id, when root_id is empty

source_session_key = message.chat_id + ":" + anchor_message_id
```

`thread_id` 保留在 Lark Context 中供查询和诊断，不作为通用 Session Key。

该规则保证：

- 顶层消息使用自身 message ID 建立新 Session；
- 对顶层消息的回复通过 root ID 复用同一 Session；
- 不同 chat 中相同 message ID 不会冲突。

### 8.3 Session Source

```text
SessionSource.source             = MessageSourceRef(LARK, instance_id)
SessionSource.source_session_key = derived session key
SessionSource.initiator_email    = message.sender_email

LarkSessionContext.chat_id           = message.chat_id
LarkSessionContext.thread_id         = message.thread_id
LarkSessionContext.anchor_message_id = derived anchor message ID
LarkSessionContext.entry_message_id  = message.message_id
```

`LarkSessionContext` 继续由 Session 持有并持久化，避免调用方重复实现
飞书 Session 解析。

已存在 Session 不会因后续 Task 而更换初始 `entry_message_id`。因此
`entry_message_id` 只表示 Session 入口，不能作为所有 Task 的回复目标。

### 8.4 Task Source

```text
TaskSource.source_message_id = message.message_id
TaskSource.sender_email       = message.sender_email
```

每个 Task 的事件投递和飞书回复必须使用当前 Task 的
`source_message_id`，而不是 Session 的 `entry_message_id`。

### 8.5 Prompt

Gateway Service 将 `message.content` 按飞书 text content JSON 解析为文本，并移除
`mentions[].key`。移除后的文本作为 `ProcessRequest.Prompt`。

Gateway Service 不解析 Jira Issue Key，不调用 Ticket Manager Conversation Service。

## 9. Task Event Contract

### 9.1 Endpoint

Ticket Manager 新增内部业务接口：

```http
POST /api/v1/ticketmanager/agent-gateway/events
Content-Type: application/json
```

DTO 与 Gateway Service 位于同一 Agent Gateway contract：

```text
packages/contracts/agent-gateway/entity/agent-gateway/gateway/v1/gateway.proto
```

Agent Gateway 使用生成的 `TaskEvent` protobuf DTO 和 `protojson` 投递，字段名使用
snake_case。Ticket Manager 后续直接消费共享 SDK contract。

### 9.2 Event

```text
TaskEvent
  type                 starting | agent_commentary | finished
  task_id              Agent Gateway Task ID
  session_id           Agent Gateway Session ID
  session_source       SessionSource(source + session key + context)
  task_source          TaskSource(source_message_id + sender_email)
  status               Task status
  content              Agent commentary 或终态内容
```

事件映射：

| Task listener | event type | status/content |
| --- | --- | --- |
| `OnTaskStarting` | `task_starting` | processing，content 为空 |
| `OnAgentCommentary` | `agent_commentary` | processing，content 为 Agent commentary |
| `OnTaskFinished` | `task_finished` | succeeded/failed/timeout/rejected 及最终 content |

event payload 不包含飞书 card JSON，也不要求 Ticket Manager 使用某一种
具体卡片样式。

### 9.3 Delivery 语义

本期 event delivery 为 best effort：

- HTTP Event Listener 同步发起 HTTP 请求，并配置短 timeout；
- HTTP 2xx 表示 Ticket Manager 已成功处理本次 event；
- 网络错误、超时或非 2xx 返回 listener error；
- listener error 只记录 Agent Gateway 日志，不将已成功的 Agent Task
  改为 failed；
- 不持久化 event，不在进程重启后补发；
- 本期不承诺 event 的 at-least-once 或 exactly-once delivery。

该限制必须通过日志和指标可观测，后续可以在不改变 event payload 的
前提下增加 Outbox、重试和去重。

## 10. Ticket Manager Event 处理

Ticket Manager 根据 event type 调用现有 Lark Client：

- `task_starting`：保留当前 processing reaction 体验；
- `agent_commentary`：渲染 Commentary card 并回复当前 `source_message_id`；
- `task_finished`：按 Task status 渲染 Result、Rejected 或 Failed card，并清理
  processing reaction。

为保留 processing reaction，Ticket Manager event component 使用进程内
`task_id -> reaction_id` 映射。`task_starting` 写入映射，`task_finished` 删除
映射并清理 reaction。本期不持久化该映射；Ticket Manager 重启后可能
遗留无法自动清理的 processing reaction。

本期可继续为每个 Agent Commentary 发送独立 Commentary card。将多个 Agent
Commentary 合并到一张卡片，或使用 `PatchMessage` 更新单卡，作为后续展示优化。

飞书 Card 的通用数据模型与 `RenderCard` 序列化函数位于
`packages/sdk-go/infra/lark/client/util`。共享组件不包含 Commentary、Result、
Rejected 或 Failed 等 Agent Task 业务预设；这些样式由 Ticket Manager 组合。

event handler 对于不支持的 event type 返回 bad request；飞书 render/send
失败返回非 2xx，使 Agent Gateway 记录 event delivery failure。

## 11. Ticket Manager 现有流程调整

### 11.1 飞书 Bot

现有 Conversation Handler 不再直接注册为通用飞书消息 consumer。Agent Service
在构造时作为 Bot Adapter 注册到 `LarkEventDispatcher`，执行过滤并调用
`GatewayService.ProcessLarkMessage`。

这保证同一条 Bot 对话消息只会进入一个通用对话 consumer，不会同时
调用 Conversation/OpenCode 和 Agent Gateway。其他特定功能的 Dispatcher consumer 保持
现状。

### 11.2 旧同步对话 API（后续已移除）

本设计落地后保留过一段时间的旧同步对话入口，现已随对应的 handler、service、
持久化模型和客户端契约一并移除。飞书 Bot 对话统一经 Agent Gateway 的异步
Task/event delivery 流程处理。

## 12. 配置

### 12.1 Ticket Manager

Ticket Manager 新增 Agent Gateway gRPC Client 配置，并保留现有 Lark Client
配置：

```yaml
clients:
  agentGateway:
    address: agent-gateway:9090
    timeout: 5s
  lark:
    # existing Ticket Manager bot credentials
```

Bot 过滤配置归 Ticket Manager，包括：

```yaml
services:
  agent:
    instanceId: ticket-manager
    allowGroupChat: true
    maxMessageSize: 8000
```

具体 YAML 字段应遵循 Ticket Manager 现有 config 分类：连接信息放在
`clients`，Bot 行为策略放在 `services`。

### 12.2 Agent Gateway

Agent Gateway 移除 Bot 凭证、allowed users、group chat 和 message size 配置，
改为按 source 配置通用 HTTP Event Listener：

```yaml
gateway:
  eventListeners:
    ticket-manager:
      sources:
        - type: lark
          instanceID: ticket-manager
      endpoint: http://ticket-manager:8080/api/v1/ticketmanager/agent-gateway/events
      timeout: 5s
```

map key 是 listener 名称，不是 Lark instance ID。同一 listener 可配置多个 source，
同一 source 也可配置多个 listener。RPC request 中的 `instance_id` 必须至少匹配一个 Lark listener source；
未匹配时返回 `InvalidArgument`，避免创建没有 event listener 的 Task。

## 13. 代码结构

### 13.1 Agent Gateway

```text
packages/contracts/agent-gateway/
  api/agent-gateway/gateway/v1/
    gateway_service.proto

services/agent-gateway/internal/
  app/handlers/gateway/
    handler.go                 # register GatewayService RPC and HTTP gateway
  services/gateway/
    gateway.go                 # Gateway Service implementation
    http_event_listener.go     # HTTP TaskEventListener
```

现有 `services/gateway/lark` 中的飞书长连接、Lark Client 和 renderer 移除。
`services/gateway` 保留为协议适配和 event delivery 所有者。

```text
packages/sdk-go/infra/lark/client/util/
  card.go                      # generic Lark Card models and RenderCard
```

### 13.2 Ticket Manager

```text
services/ticket-manager/internal/
  integrations/agentgateway/
    client.go                  # generated gRPC client wrapper
  services/agent/
    service.go                 # dispatcher registration, Bot filtering, RPC and Task Event handling
    renderer.go                # Ticket Manager-owned task card presets
  app/handlers/lark/
    handler.go                 # Lark HTTP events, cards and login
  app/handlers/agent/
    handler.go                 # Agent Gateway HTTP Task Event route
    agent_gateway_event.go     # Task Event decoding and dispatch
```

实现时可根据 handler 职责进一步拆分 service，但不应将 Lark Client 重新
注入 Agent Gateway。Ticket Manager 新增的 HTTP endpoint 必须注册在
`/api/v1/ticketmanager` 业务 route 下。

## 14. 错误语义

### 14.1 Ticket Manager -> Agent Gateway

- 过滤后的飞书消息调用 RPC 成功：Ticket Manager 不等待 Agent 结果。
- 重复 `source_message_id`：返回现有 Task 和 `DUPLICATED`，不再次执行
  Agent，也不重新发送历史 event。
- `InvalidArgument`：记录结构化日志，本期不向飞书发送额外错误消息。
- RPC 不可用或内部错误：记录日志；由于现有 Dispatcher 在 goroutine
  中处理 consumer，不依赖飞书 webhook 重试。

### 14.2 Agent Gateway -> Ticket Manager

- event endpoint 返回 2xx：当次事件处理成功。
- event delivery timeout/网络错误/非 2xx：记录 task ID、event type、instance ID 和
  endpoint，但不改变 Task 状态。
- Ticket Manager 飞书发送失败：event endpoint 返回非 2xx，Agent Gateway 记录
  listener error。

## 15. 可观测性

本期至少记录：

- Ticket Manager 收到和过滤飞书消息的原因，不记录完整敏感 content；
- `ProcessLarkMessage` 的 instance ID、source message ID、Task ID 和 disposition；
- event delivery 的 Task ID、event type、HTTP status 和 latency；
- Ticket Manager render/send 成功或失败；
- Task 终态与 event delivery 结果的区分，避免将 Agent 执行成功误判为消息
  已成功送达。

建议指标：

```text
agent_gateway_process_lark_message_total{instance,disposition}
agent_gateway_event_delivery_total{instance,event_type,result}
agent_gateway_event_delivery_duration_seconds{instance,event_type}
ticket_manager_agent_gateway_rpc_total{instance,result}
ticket_manager_agent_gateway_event_total{event_type,result}
ticket_manager_agent_gateway_lark_send_total{event_type,result}
```

## 16. 迁移步骤

1. 新增 `GatewayService` protobuf，生成 Go gRPC contract。
2. 在 Agent Gateway 实现 `ProcessLarkMessage` parser 和 RPC handler，复用现有
   `TaskService.Process`。
3. 在 Agent Gateway 实现 HTTP Event Listener 和 endpoint 配置，用它替换当前
   Lark Reactor。
4. 在 Ticket Manager 新增 Agent Gateway gRPC client、Bot Adapter、event endpoint 和
   renderer/send 逻辑。
5. 移除 Conversation Handler 对通用飞书消息的订阅，由新 Bot Adapter
   成为唯一通用 Bot 对话 consumer。
6. 确认同步 `/conversations` API 仍调用 Conversation Service。
7. 移除 Agent Gateway 的飞书凭证、长连接、Lark Client 和 renderer。
8. 生成 contract/mock，执行 Agent Gateway 和 Ticket Manager 测试。

切换时不同时保留两个通用飞书 Bot consumer，避免同一条消息同时进入
OpenCode 和 Agent Gateway。

## 17. 验证

### 17.1 Agent Gateway

- parser：顶层消息、root reply、thread ID 保留、mention 移除、必要字段校验；
- RPC handler：正确构造 `ProcessRequest`，区分 accepted/duplicated；
- Session/Task：相同 source message 只创建一个 Task，同 root 回复复用
  Session；
- HTTP Event Listener：三种 Task event 映射、timeout、非 2xx，以及失败不修改
  Task 终态；
- 生成与构建：`make generate`、focused tests、module tests。

### 17.2 Ticket Manager

- Bot filter：app sender、非 text、群聊未 mention、未授权用户、超长消息均不
  调用 RPC；
- accepted event：只调用一次 `ProcessLarkMessage`；
- event handler：三种 event 都使用 payload 中的 `source_message_id` 发送；
- renderer：Commentary、Result、Rejected、Failed 卡片内容；
- regression：同步 `/conversations` API 仍调用 Conversation Service，其他 Lark
  Dispatcher consumer 仍正常注册；
- module tests：从 `services/ticket-manager` 执行 focused tests 和 `make test`。

### 17.3 端到端

最小 E2E 覆盖：

1. Ticket Manager 收到一条通过过滤的飞书 text event；
2. Agent Gateway 返回 accepted Task；
3. Agent Runtime 产生中间 Agent Commentary 和最终 Result；
4. Ticket Manager 收到 Task event 并通过 mock Lark Client 回复正确 message ID；
5. 重复投递同一飞书 message 不会创建第二个 Agent Task。

## 18. 已知限制与后续方向

- Ticket Manager Dispatcher 与 Agent Gateway event delivery 都没有持久化 delivery guarantee；
- event 丢失后不会自动重放；
- listener callback 完全异步执行，starting、commentary 和 finished event 不保证到达顺序；
- 中间 Agent Commentary 可能产生多张 Commentary card；
- Ticket Manager 重启会丢失进程内 reaction ID，可能遗留 processing reaction；
- 尚未设计 event endpoint 认证和授权；
- Agent Gateway 对运行中 Task 的宕机恢复和跨副本 claim 仍使用现有语义。

后续可以独立引入：

1. event delivery Outbox、指数退避和 dead-letter；
2. event `event_id/sequence` 与 Ticket Manager 幂等消费；
3. HMAC、service token 或 mTLS；
4. 持久化 card message ID，将多个 Agent Commentary patch 到同一张卡片；
5. Task result 持久化和 event 手动重放；
6. 其他渠道的 Gateway Service method 和 Event Listener。
