10-runtime-architecture.md 10.9 KB

10. 运行时架构与并发模型

一个主进程 + N 个子进程。主进程是"中枢神经",子进程是独立运行的 Agent。


10.1 并发模型

┌──────────────────────────────────────────────────────────────────┐
│                 LemonBoss Server (主进程)                         │
│                 Node.js 单进程 + EventEmitter                     │
│                                                                   │
│  ┌─────────────────────────────────────────────────────────┐     │
│  │                Agent 进程管理器                          │     │
│  │                                                         │     │
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐              │     │
│  │  │ ACP #1   │  │ ACP #2   │  │ ACP #3   │  ...         │     │
│  │  │ Master   │  │ CEO      │  │ 编剧部    │              │     │
│  │  │          │  │          │  │          │              │     │
│  │  │ 子进程   │  │ 子进程    │  │ 子进程    │              │     │
│  │  │ PID 1001 │  │ PID 1002 │  │ PID 1003 │              │     │
│  │  └────┬─────┘  └────┬─────┘  └────┬─────┘              │     │
│  │       │             │             │                      │     │
│  │       └─────────────┼─────────────┘                      │     │
│  │            stdin / stdout pipe                            │     │
│  │            (JSON-RPC 双向通讯)                            │     │
│  └─────────────────────┼────────────────────────────────────┘     │
│                        │                                          │
│  ┌─────────────────────┼────────────────────────────────────┐     │
│  │              工具拦截器 (Tool Interceptor)                  │     │
│  │                                                           │     │
│  │  Agent stdout 输出工具调用 → 主进程拦截:                    │     │
│  │                                                           │     │
│  │    task.assign   → 路由消息到目标 Agent 的 stdin            │     │
│  │    task.report   → 路由消息到上级 Agent 的 stdin            │     │
│  │    role.create   → AgentScaffold 创建目录 → 启动子进程     │     │
│  │    pipeline.set  → 存储 DAG 到 SQLite                      │     │
│  │    org.status    → 查询 SQLite 返回全局状态                │     │
│  │    skill.reuse   → 复制 Skill 引用                         │     │
│  └──────────────────────────────────────────────────────────┘     │
│                                                                   │
│  ┌──────────────────────────────────────────────────────────┐     │
│  │                SQLite (唯一共享存储)                       │     │
│  │                                                           │     │
│  │  agents 表    ← Agent 注册信息和状态                       │     │
│  │  messages 表  ← Agent 间所有通讯记录                       │     │
│  │  tasks 表     ← 任务队列和进度                             │     │
│  │  goals 表     ← 目标定义与追踪                             │     │
│  │  loop_runs 表 ← Loop 运行记录                              │     │
│  │  memories 表  ← Agent 共享记忆                             │     │
│  └──────────────────────────────────────────────────────────┘     │
│                                                                   │
│  ┌─────────────┐  ┌─────────────┐  ┌──────────────────┐         │
│  │ Cron 调度器 │  │ Gateway Hub │  │ HTTP/WS API 层   │         │
│  │ node-cron   │  │ 飞书/WA/钉钉│  │ Express + WS     │         │
│  └─────────────┘  └─────────────┘  └──────────────────┘         │
│                                                                   │
└──────────────────────────────────────────────────────────────────┘

并发方式

维度 方案 说明
进程级并发 每个 Agent = 独立子进程 操作系统级别并行,互不阻塞
通讯方式 stdin/stdout pipe JSON-RPC over stdio(ACP 标准协议)
共享存储 SQLite (WAL 模式) 主进程单写,子进程通过工具请求读取
调度方式 异步事件驱动 Agent A 发任务后不等待,Agent B 完成后异步回报

子进程的启动方式

// 主进程启动一个 Agent 的 ACP 子进程
const child = spawn('claude', ['--agent', agentDir], {
  stdio: ['pipe', 'pipe', 'pipe'],   // stdin, stdout, stderr 全部管道化
  cwd: path.join(agentDir, 'workspace'),
  env: { ...process.env, ...agentEnv },
});

// 注入 System Prompt (agent.md + soul.md + ... 拼接)
child.stdin.write(systemPrompt);

// 监听 Agent 的输出 (工具调用)
child.stdout.on('data', (data) => {
  const toolCall = parseJSON(data);
  toolInterceptor.handle(toolCall, agentId);
});

10.2 Agent 生命周期

                ┌─── create ───┐
                ▼               │
  ┌─────────────────────────────────────┐
  │            AgentRegistry             │
  │                                     │
  │  ┌───────┐    ┌─────────┐    ┌───┐ │
  │  │ idle  │───→│ running │───→│stop│ │
  │  │       │    │         │    │   │ │
  │  │ 目录已│    │ ACP 子  │    │ACP│ │
  │  │ 创建  │    │ 进程运行│    │关闭│ │
  │  └───────┘    └─────────┘    └───┘ │
  │      │              │              │
  │      └──── error ───┘              │
  │                                     │
  │         destroy → 删除目录 + 注销    │
  └─────────────────────────────────────┘

  create:  AgentScaffold 生成目录 → 注册到 SQLite → 注册到 Bus
  start:   加载 Bootstrap Files → 启动 ACP 子进程 → 注入 System Prompt
  stop:    关闭 ACP 子进程 → 状态改为 idle
  destroy: 停止 → 从 Bus 注销 → 删除目录 → 从 SQLite 删除

10.3 框架核心服务

服务 职责
AgentRegistry Agent CRUD + 生命周期管理 (create/list/get/update/destroy)
AgentBus 消息路由 + 持久化 + 广播
AgentProcessManager ACP 子进程启动/停止/监控/重启
ToolInterceptor 拦截 Agent 的工具调用,执行框架级操作
CronScheduler 解析 heartbeat.md,按时触发 Agent
GatewayHub 外部 IM 消息接入 (飞书/WhatsApp/钉钉)
GoalTracker 目标进度追踪与评估
PipelineRouter 按 DAG 自动流转 Pipeline 节点

10.4 技术选型

组件 技术方案 理由
AI 内核 Claude Code ACP 已验证的四维架构 (Protocol + Transport + Launcher + Runtime)
后端主进程 Node.js + Express 事件驱动,天然适合管理多子进程
子进程通讯 stdio pipe (JSON-RPC) ACP 标准协议,零依赖
共享存储 SQLite (better-sqlite3, WAL) 轻量级,主进程单写多读,无外部依赖
实时推送 WebSocket + SSE 前端仪表盘实时监控
定时任务 node-cron 解析 heartbeat.md 的 cron 表达式
进程管理 Node.js child_process 子进程启动/停止/信号管理
前端 Vue 3 + Vite 控制面板和监控仪表盘
日志 Winston + JSONL 结构化日志 + Agent 对话记录
网关 适配器模式 统一不同 IM 的消息格式
包管理 pnpm workspace Monorepo 管理

10.5 一个完整请求的生命周期

用户在飞书里说: "最近短剧市场怎么样?"
    │
    ▼
[1] Gateway Hub (飞书适配器)
    │ 消息格式化为标准格式
    ▼
[2] AgentProcessManager
    │ 找到 Master ACP 子进程
    │ 通过 stdin 注入消息
    ▼
[3] Master ACP 进程 (子进程 PID 1001)
    │ 读取 agent.md + soul.md → 推理
    │ 决定: 让 CEO 调数据部去分析
    │ stdout: task.assign({ to: "cxo-ceo-001", ... })
    ▼
[4] ToolInterceptor (主进程)
    │ 拦截 task.assign
    │ 写入 SQLite messages 表
    │ 注入到 CEO 的 stdin
    ▼
[5] CEO ACP 进程 (子进程 PID 1002)    ← 与 Master 并行运行
    │ 收到任务 → 推理
    │ stdout: task.assign({ to: "role-data-001", ... })
    ▼
[6] ToolInterceptor → 路由到数据部
    ▼
[7] 数据部 ACP 进程 (子进程 PID 1005)  ← 与 CEO 并行运行
    │ 调用爬虫 Skill → 采集数据
    │ 分析完成
    │ stdout: task.report({ to: "cxo-ceo-001", ... })
    ▼
[8] ToolInterceptor → 路由回 CEO
    ▼
[9] CEO 收到数据 → 汇总
    │ stdout: task.report({ to: "master-001", ... })
    ▼
[10] ToolInterceptor → 路由回 Master
    ▼
[11] Master 收到汇总 → 组织回复
    │ stdout: reply({ content: "最近市场分析..." })
    ▼
[12] Gateway Hub → 飞书 → 用户

整个过程中 Master / CEO / 数据部 可能在同时做其他事 (心跳、其他任务)
所有步骤全异步, 不阻塞