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 / 数据部 可能在同时做其他事 (心跳、其他任务)
所有步骤全异步, 不阻塞