feat: 拆分 worker 架构并增强 assistant 安全保护

- 新增 daemon/channel/assistant/function/scheduler/logging worker 架构和 JetStream 消息协议

- 修复邮件通知误入 assistant、迁移 checksum、日志 subject 和工具调用无限循环风险

- 新增 assistant 工具轮次熔断、工具结果截断和提示词软收敛规则

- 补充邮件推送关键字、可信发件人配置、升级日志和验证测试
This commit is contained in:
2026-07-10 00:41:36 +08:00
parent 7deae286ca
commit af6cfdaa83
70 changed files with 5953 additions and 1608 deletions
+445
View File
@@ -0,0 +1,445 @@
# iAs 进程拆分设计方案
## 1. 设计结论
当前 `ias-service::run_service()` 同时承载微信长轮询、邮件轮询、AI 队列、HTTP 服务、control socket 和运行态快照,已经形成单进程强耦合。拆分后不再自研进程间队列,统一使用 **async-nats + NATS JetStream** 承担消息持久化、路由、Ack、重试和 DLQ。
目标边界:
- **Daemon Control Plane**:只负责 worker 启停、健康检查、control socket、JetStream 初始化和状态汇总。
- **JetStream 数据面**:承载所有业务消息,worker 直接 publish/consume。
- **Worker**:承载业务逻辑,包括渠道、AI、工具、日志、定时任务和 HTTP。
Daemon 不调用 AI、不读写上下文、不调用微信、不处理邮件业务、不决定 typing 生命周期。
## 2. 当前问题
- `core/queue.rs` 既是内存队列,又包含上下文记录、AI 调用、工具调用、typing 刷新和微信发送,职责过宽。
- 微信、邮件、AI、HTTP 共享同一 runtime,任何模块阻塞或崩溃都会影响整体。
- 邮件通知直接依赖微信 manager,缺少统一的消息路由。
- 现有 supervisor 只能管理单个后台服务进程,不能管理多个 worker。
- 内存 `mpsc` 不具备跨进程持久化、Ack、重试、DLQ 和恢复能力。
## 3. 目标架构
保留一张核心图,后续实现以这张图为准。
```mermaid
flowchart TB
User["微信用户"] --> WeChatAPI["WeChat API"]
MailSrv["IMAP / 邮件服务器"] --> MailWorker["Mail Worker"]
Browser["浏览器 / 管理页面"] --> HttpSvc["HTTP Service"]
subgraph Control["控制面"]
CLI["ias CLI"]
Daemon["Daemon Control Plane<br/>worker 启停 / health / control socket / JetStream bootstrap"]
Runtime["runtime files<br/>pid / state / logs"]
CLI --> Daemon
HttpSvc -- status --> Daemon
Daemon --> Runtime
end
subgraph MQ["数据面:NATS JetStream"]
Inbound["IA_INBOUND<br/>inbound.wechat.* / inbound.mail.*"]
Function["IA_FUNCTION<br/>function.call.* / function.result.*"]
Outbound["IA_OUTBOUND<br/>outbound.wechat.* / outbound.mail.*"]
Schedule["IA_SCHEDULE<br/>schedule.trigger.*"]
Logs["IA_LOGS<br/>logs.*"]
DLQ["IA_DLQ<br/>dlq.*"]
end
subgraph Workers["Worker"]
WeChatWorker["WeChat Worker"]
MailWorker["Mail Worker"]
Assistant["Assistant Worker"]
FunctionWorker["Function Worker"]
Scheduler["Scheduler Worker"]
Logging["Logging Worker"]
end
subgraph Storage["存储"]
Pg["PostgreSQL<br/>账号 / 邮件 / 上下文 / 日志 / 定时任务"]
JS["JetStream file store"]
end
WeChatAPI --> WeChatWorker
WeChatWorker -- publish inbound --> Inbound
MailWorker -- publish inbound/notification --> Inbound
Inbound -- consume --> Assistant
Assistant -- publish function call --> Function
Function -- consume --> FunctionWorker
FunctionWorker -- publish function result --> Function
Function -- consume result --> Assistant
Assistant -- publish outbound --> Outbound
Outbound -- consume --> WeChatWorker
WeChatWorker --> WeChatAPI
Scheduler -- publish trigger --> Schedule
Schedule --> Assistant
Schedule --> FunctionWorker
Daemon -. log .-> Logs
WeChatWorker -. log .-> Logs
MailWorker -. log .-> Logs
Assistant -. log .-> Logs
FunctionWorker -. log .-> Logs
Scheduler -. log .-> Logs
Logs --> Logging
Logging --> Pg
Inbound -. max deliver .-> DLQ
Function -. max deliver .-> DLQ
Outbound -. max deliver .-> DLQ
Schedule -. max deliver .-> DLQ
Inbound --- JS
Function --- JS
Outbound --- JS
Schedule --- JS
Logs --- JS
DLQ --- JS
Assistant <--> Pg
WeChatWorker <--> Pg
MailWorker <--> Pg
Scheduler <--> Pg
HttpSvc <--> Pg
```
## 4. 核心组件
### 4.1 Daemon Control Plane
职责:
- 启动、停止、重启 worker 和 HTTP 服务。
- 初始化 JetStream stream、consumer、DLQ。
- 提供 control socket 给 CLI 和 HTTP 状态页。
- 汇总 worker 心跳、JetStream consumer lag、DLQ 数量和最近错误。
- 输出本地运行日志,并发布关键控制事件到 `IA_LOGS`
禁止:
- 不调用 `ias-ai`
- 不调用 `ias-context`
- 不调用 `ias-wechat`
- 不解析业务消息 payload。
- 不直接消费或生产业务消息。
### 4.2 JetStream 数据面
固定使用 `async-nats + NATS JetStream`
Streams
- `IA_INBOUND`:渠道输入消息,例如 `inbound.wechat.*``inbound.mail.*`
- `IA_FUNCTION`:工具调用和工具结果,例如 `function.call.*``function.result.*`
- `IA_OUTBOUND`:渠道发送命令,例如 `outbound.wechat.*`
- `IA_SCHEDULE`:定时任务触发事件,例如 `schedule.trigger.*`
- `IA_LOGS`:结构化日志事件,例如 `logs.daemon.*``logs.worker.*``logs.audit.*`
- `IA_DLQ`:死信消息,例如 `dlq.*`
默认策略:
- 使用 file storage。
- 使用 durable pull consumer。
- 使用 explicit ack。
- 普通消息 `AckWait` 默认 60 秒,AI 和工具类消息可单独加长。
- `MaxDeliver` 默认 5 次,超过后进入 `IA_DLQ`
- 所有业务处理按 at-least-once 设计,下游必须幂等。
### 4.3 Worker
- **WeChat Worker**:微信长轮询、`updates_buf` 持久化、发布 inbound、消费 outbound、执行发送和 typing。
- **Mail Worker**:邮件轮询、邮件通知事件、邮件 inbound 事件。
- **Assistant Worker**:上下文、AI 调用、`/new`、active turn、typing 决策、工具编排、生成 outbound。
- **Function Worker**:执行工具、HTTP API、内部功能,返回 function result。
- **Scheduler Worker**:扫描定时任务,到期后发布 scheduled trigger。
- **Logging Worker**:消费 `IA_LOGS`,落库、写文件、聚合错误和审计记录。
- **HTTP Service**REST API、SPA、日志/更新查询、daemon 状态代理。
## 5. 消息标准
所有 JetStream payload 统一包一层业务消息信封,字段保持稳定:
- `message_id`:消息幂等键。
- `topic`:业务 topic,与 JetStream subject 对齐。
- `trace_id`:跨 worker 链路追踪。
- `created_at_ms`:生成时间。
- `attempt_hint`:业务层可读的尝试次数提示,真实投递次数以 JetStream 为准。
- `payload`:业务内容。
常用幂等键:
- 入站消息:`message_id = channel/account/external_message_id`
- outbound 命令:`command_id`
- function 调用:`call_id`
- 定时触发:`fire_id = schedule_id + fire_time`
- 日志事件:`event_id`
消息内容可以使用 `serde_json`;不要在 daemon 中解析业务 payload。
## 6. 日志设计
日志分两层:
- **运行日志**:所有进程继续用 `tracing` 输出到 stdout/file,方便 systemd 和本地排障。
- **结构化事件日志**:关键事件发布到 `IA_LOGS`,由 Logging Worker 落库和归档。
日志事件来源:
- daemon:进程启动/停止、worker 重启、JetStream 初始化、DLQ 操作。
- channel worker:长轮询错误、发送成功/失败、账号状态变化。
- assistant worker:AI 请求摘要、工具调用、上下文错误、`/new`
- function worker:工具执行耗时、结果摘要、错误。
- scheduler worker:任务触发、跳过、重复触发拦截。
- HTTP service:管理操作、配置变更、API 错误。
日志要求:
- 所有跨进程消息必须带 `trace_id`
- `message_id``command_id``call_id``fire_id` 必须进入日志字段。
- 默认不记录完整用户正文,只记录摘要、长度和哈希。
- Logging Worker 失败不能阻塞业务流。
- `IA_LOGS` 必须设置保留周期和最大容量。
## 7. 定时任务设计
Scheduler Worker 独立运行,不放在 daemon 内。
定时任务来源:
- 数据库表,例如 `ias_schedules`
- 少量系统级任务可来自配置文件或环境变量。
任务字段建议:
- `schedule_id`
- `name`
- `schedule_type``cron``interval`
- `expression`
- `target_topic`
- `payload`
- `enabled`
- `last_fired_at`
- `next_fire_at`
触发流程:
1. Scheduler Worker 周期扫描启用任务。
2. 到期后生成 `fire_id = schedule_id + fire_time`
3. 通过数据库唯一约束抢占触发权。
4. 发布 `ScheduledTrigger``IA_SCHEDULE`
5. 下游 worker 根据 `target_topic` 处理。
防重复要求:
- `fire_id` 必须唯一。
- JetStream publish 使用 `message_id` 去重窗口。
- 下游 worker 必须按 `fire_id` 幂等。
## 8. 模块拆分
目标 crate
- `ias-daemon`control daemon、worker registry、process manager、JetStream bootstrap。
- `ias-common`:消息类型、JetStream 薄封装、RuntimeSnapshot、control 协议。
- `ias-channel`:微信和邮件 worker。
- `ias-assistant`:AI、上下文、工具编排、typing 决策。
- `ias-function`:工具执行 worker。
- `ias-scheduler`:定时任务 worker。
- `ias-logging`:日志消费 worker。
- `ias-http`HTTP 服务和 daemon 状态代理。
依赖约束:
- `ias-daemon` 可以依赖 `ias-common``async-nats`
- `ias-daemon` 禁止依赖 `ias-ai``ias-context``ias-wechat``ias-mail`
- `ias-channel` 禁止依赖 `ias-ai``ias-context`
- `ias-assistant` 禁止依赖 `ias-wechat`
- `ias-logging``ias-scheduler` 不直接调用渠道 SDK 和 AI SDK。
## 9. CLI 入口
保留现有账号管理命令,不复用 `ias channel` 作为 worker 启动命令。
新增或调整入口:
- `ias service start|stop|restart|status|logs`
- `ias daemon run`
- `ias worker assistant`
- `ias worker wechat --account-ids ...`
- `ias worker mail`
- `ias worker function --name ...`
- `ias worker scheduler`
- `ias worker logging`
- `ias http --host 0.0.0.0:9003`
## 10. 部署配置
核心环境变量:
- `NATS_URL=nats://127.0.0.1:4222`
- `IAS_NATS_STREAM_PREFIX=IA`
- `IAS_NATS_ACK_WAIT_SECS=60`
- `IAS_NATS_MAX_DELIVER=5`
- `IAS_CONTROL_SOCKET=/run/user/1000/ias/ias.sock`
- `IAS_LOG_EVENT_ENABLED=true`
- `IAS_LOG_EVENT_RETENTION_DAYS=30`
- `IAS_SCHEDULER_ENABLED=true`
- `IAS_SCHEDULER_TICK_SECS=10`
- `HOST=0.0.0.0:9003`
JetStream 部署方式:
- NATS Server 作为独立基础设施部署,iAs 只通过 `NATS_URL` 连接。
- 开发和单机部署优先使用 Docker Compose,便于本地启动、清理和迁移。
- Docker Compose 中的 NATS 启动参数必须等价于 `nats-server --jetstream --store_dir=/data --http_port=8222`
- Docker Compose 必须挂载持久化目录,例如 `./data/nats:/data`
- 本机开发端口只绑定 `127.0.0.1:4222``127.0.0.1:8222`,不要暴露公网。
- 生产单机也可以用 systemd 管理 `nats-server`,推荐配置文件放在 `/etc/ias/nats-server.conf`JetStream 数据放在 `/var/lib/ias/nats`
- 跨主机或多节点 JetStream 集群不作为第一阶段要求;确实需要时必须补充鉴权、TLS、cluster routes、备份和监控设计。
- daemon 启动时只负责检查和创建 stream、consumer、DLQ,不负责启动 Docker 容器或 systemd 服务。
- NATS 不可用时,worker 必须退避重连;daemon 和 HTTP 状态接口显示 MQ 降级状态。
## 11. 施工计划
Phase 0:边界收敛
- 确认 daemon 只做 control plane。
- 确认 JetStream stream、subject、consumer 命名。
- 确认 `worker` CLI 命名不与现有账号管理命令冲突。
Phase 1:公共协议
-`ias-common` 定义业务消息信封、payload 类型、日志事件和定时触发事件。
- 封装 `JetStreamBus`,集中处理 publish、consumer、stream 初始化和错误映射。
- 增加消息序列化、幂等键生成和 subject 生成测试。
Phase 2:日志和定时基础
- 创建 `IA_LOGS``IA_SCHEDULE`
- 实现 Logging Worker 原型。
- 实现 Scheduler Worker 原型。
- 将关键操作日志改成发布 `LogEvent`,保留本地 `tracing` 输出。
Phase 3:队列解耦
-`core/queue.rs` 拆出 JetStream publish/consume 适配。
- 将 AI、上下文、工具编排、typing 决策迁移到 Assistant Worker。
- 将微信发送调用改成 `OutboundCommand`
- 先保持单进程兼容模式,验证行为不变。
Phase 4Daemon 和 Worker 提取
- 创建 `ias-daemon`,只迁移 control、worker registry、process manager、JetStream bootstrap。
- 创建 `ias-assistant``ias-channel``ias-scheduler``ias-logging`
- 实现 worker 注册、心跳、JetStream Ack/Nack、断线重连。
Phase 5HTTP 和部署
- 添加独立 `ias http` 入口。
- 添加 daemon control client。
- 更新 `service start/stop/status/logs`
- 增加 NATS JetStream Docker Compose 和 systemd 部署说明。
Phase 6:端到端验证
- 微信消息到回复链路。
- AI 工具调用链路。
- worker 崩溃后的 redelivery。
- 定时任务触发和去重。
- 结构化日志落库和查询。
- HTTP 独立运行和 daemon 状态降级。
## 12. 施工标准
### 12.1 架构标准
- daemon 不在业务消息数据路径中。
- 所有业务消息必须经过 JetStream。
- worker 之间不得直接调用彼此内部 API。
- 新增业务能力优先通过新增 subject 和 worker 实现,不扩展 daemon 职责。
### 12.2 依赖标准
- `ias-daemon` 只能依赖 `ias-common``async-nats`、日志和进程管理相关依赖。
- `ias-common` 可以定义协议和薄封装,但不能包含业务逻辑。
- 渠道 SDK 只允许出现在 channel worker。
- AI 和上下文依赖只允许出现在 assistant worker。
### 12.3 消息标准
- 所有消息必须有 `message_id``trace_id`
- 所有 outbound 必须有 `command_id`
- 所有 function call 必须有 `call_id`
- 所有定时触发必须有 `fire_id`
- 所有 worker 按 at-least-once 投递语义实现幂等。
### 12.4 日志标准
- 所有关键状态变化必须发 `LogEvent`
- 日志字段必须包含相关 ID`message_id``command_id``call_id``fire_id`
- 默认不得记录完整用户正文。
- 日志流必须设置保留周期和容量上限。
### 12.5 定时任务标准
- 定时任务触发权必须通过数据库唯一约束或等价机制保证。
- Scheduler Worker 可以多副本运行,但同一 `fire_id` 只能触发一次。
- 定时任务失败必须可重试或进入 DLQ。
### 12.6 测试标准
- 每个 worker 至少有消息序列化和幂等测试。
- JetStream consumer Ack/Nack、redelivery、DLQ 必须有集成测试。
- daemon 边界必须通过依赖检查验证。
- 关键端到端链路必须有人工或自动化验证记录。
### 12.7 部署标准
- NATS 必须作为独立进程或容器部署,不嵌入 iAs daemon。
- JetStream 必须开启 file storage,并配置持久化目录。
- 开发和单机默认使用 Docker Compose;生产单机可改用 systemd。
- NATS client 端口和监控端口默认只监听本机。
- NATS 跨主机访问必须启用鉴权;公网访问必须启用 TLS。
- iAs 代码只能负责连接、初始化和检查 JetStream,不能负责管理 Docker 或 systemd 生命周期。
## 13. 验收标准
架构验收:
- `ias-daemon` 编译依赖中不包含 `ias-ai``ias-context``ias-wechat``ias-mail`
- 不存在自研 `ias-queue.sock` 业务数据面。
- 所有 MQ 访问通过 `async-nats + JetStream``ias-common` 的 JetStream 薄封装完成。
消息验收:
- 没有 assistant worker 时,`IA_INBOUND` 能接收并保留消息。
- 没有 channel worker 时,`IA_OUTBOUND` 能保留 outbound 消息。
- worker 崩溃或断线后,未 Ack 消息能 redeliver。
- 超过最大投递次数的消息进入 `IA_DLQ`
业务验收:
- 微信消息能完成:inbound -> assistant -> outbound -> 微信回复。
- `/new` 仍能打断同一会话旧 turn。
- typing 由 assistant 生成命令,channel worker 执行。
- 邮件提醒不直接调用微信 manager,而是发布消息后由路由完成发送。
日志验收:
- Logging Worker 能消费 `IA_LOGS`
- 结构化日志能写入数据库或文件。
- 日志中能通过 `trace_id` 串起跨 worker 链路。
- 默认不会记录完整用户正文。
定时任务验收:
- Scheduler Worker 能按 cron/interval 发布 `ScheduledTrigger`
- 同一 `fire_id` 重复启动不会重复触发。
- 定时任务失败能重试或进入 DLQ。
运维验收:
- HTTP 可以在 daemon 不在线时启动,状态接口返回降级结果。
- daemon 重启不影响 JetStream 中未 Ack 消息。
- Docker Compose 或 systemd 重启 NATS 后,JetStream file store 中的未处理消息仍保留。
- NATS 重启后 worker 能退避重连并恢复消费。
- NATS 端口默认只绑定本机;跨主机部署时必须验证鉴权配置。
- `service status` 能看到 daemon、worker、stream、consumer lag 和 DLQ 摘要。