- M2-a/b/c: stop/pause/resume/skip dept, plan/rework approval gates, budget & timeout circuit breakers, per-dept cancel and on-demand rework - tier-3: batched event persistence (_EventBuffer), project/dept concurrency semaphores, company_ws event streaming - tier-4: cross-dept deliverable handoff, CEO memory feedback loop, per-dept model override, one-click Markdown report export (RFC5987) - fix: per-department independent SQLAlchemy session in parallel waves (shared Session race across asyncio tasks in company_orchestrator) - fix: rename process_open_fds gauge to app_process_open_fds (collides with prometheus_client ProcessCollector on Linux) - fix: dept role resolution ladder, real token usage & cost tracking, deliverable file capture (mtime scan of dept workspace) - tests: test_company_orchestrator.py 22 cases with mocked LLM - frontend: CompanyControlRoom, companyExecution store, EChart, dept model config dialog, ws proxy, build/typecheck decoupling - docs: virtual company design/usage docs, M2 control plan, deepseek4pro handover docs Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
11 KiB
11 KiB
虚拟公司「白盒控制室」实现方案
面向
/companies(CompanyBuilder.vue)虚拟公司功能,将其从"黑盒执行"升级为"可观测 + 可控 + 可恢复"的控制室。 分支:rjb_tiangong_debug | 编写日期:2026-07-11
一、背景(为什么做)
当前 /companies 执行一个公司级项目时是黑盒:
- 看不清:SSE 事件其实已深到每个 Agent 的 ReAct think/工具调用,但前端把工具入参出参、完整推理链、产出文件、真实 token 全丢弃或截断;CEO 规划/评分阶段无过程;依赖关系只有文字箭头。
- 控不了:执行是"一条 async 请求内跑完",无任务态、无停止/暂停、循环里无取消检查点。客户端断开只停止消费,后端继续跑 → 产生僵尸项目。
- 断即失:中间态不落库,刷新页面/断线后正在跑的项目无法恢复查看。
目标范围(已确认):
- 观测 + 完整控制:实时全量可观测 + 停止/暂停/恢复 + 跳过部门/手动返工。
- 独立控制室页面:新建路由页,不塞进已 1600 行的
CompanyBuilder.vue。 - 可恢复:刷新/断线后能重连查看正在跑的项目并回放历史。
关键结论:可观测数据源已具备(改造小),可控 + 可恢复需要架构升级(把执行从"请求协程内跑完"解耦成"后台任务 + 事件落库 + Redis 实时通道 + 协作式取消")。
二、架构总览
[控制室页面 CompanyControlRoom.vue]
│ ①启动执行 POST /execute/async → 立即拿 project_id
│ ②WS 订阅 /ws/company-projects/{pid}
│ ├─ 连接即回放:DB company_project_events 历史事件(恢复)
│ └─ 增量实时:Redis pub/sub company:events:{pid}
│ ③控制 POST .../{stop|pause|resume|skip|rework}
▼
[后台执行任务 asyncio.create_task] [Redis]
orchestrator.execute_stream() ──发布──▶ pub/sub: company:events:{pid}
每个事件: ──写库──▶ DB: company_project_events
循环检查点 ◀──读取──────────────────── flag: company:control:{pid}
心跳 updated_at
- 执行与请求解耦:
/execute/async创建项目 →asyncio.create_task起后台任务 → 立即返回project_id。SSE 断开不再杀执行(解决僵尸 + 支持恢复)。 - 双写:每个编排事件同时 (a) 发 Redis pub/sub 供实时,(b) 落
company_project_events供回放。 - 协作式取消:控制信号写 Redis flag
company:control:{pid};执行在循环检查点读取,实现跨 worker 的停止/暂停(无需进程内任务注册表)。
三、后端改动
1. 事件持久化(新表 + 迁移)
- 新增模型
backend/app/models/company_project_event.py→ 表company_project_events:id, project_id(FK,index), seq(int 递增), type, payload(JSON), created_at。 - 新增 alembic 迁移。⚠️ 迁移雷区:当前
alembic heads是d684c9b30c1c,该迁移含危险删表操作(droptools/api_keys/fcm_tokens等,tools有真实数据)。生成新迁移前必须先alembic stamp对齐真实 DB 状态,再基于其生成新迁移;绝不能触发d684c9b30c1c的 drop_table。 - 用途:WS 连接时按
seq回放;执行结束保留作审计/回放。
2. 执行解耦为后台任务
backend/app/api/companies.py:387现有/execute/stream保留(向后兼容)。新增POST /{company_id}/execute/async:- 校验公司 +
current_user归属(顺带修当前越权问题)。 - 创建
CompanyProject(status=queued)。 asyncio.create_task(_run_company_project(project_id, ...)),返回{project_id}。
- 校验公司 +
- 新增执行包装器(
company_orchestrator.py或新company_runner.py):末尾async for event in orchestrator.execute_stream(...): _publish(pid, event) # Redis pub/sub _persist(pid, seq, event) # DB append _heartbeat(project) # 周期 updated_at=nowexcept CancelledError/ stop-flag → 落stopped/failed终态并发终止事件。
3. 控制信号 + 协作式检查点
- 控制端点(
companies.py,均校验归属):POST /{cid}/projects/{pid}/stop→ Rediscompany:control:{pid}={action:"stop"}POST .../pause/.../resume→{action:"pause"|"run"}POST .../skipbody{department_name}→{action:"skip", dept}(当前波跳过该部门)POST .../reworkbody{department_names[]}→ 手动触发指定部门返工(下一轮)
- 检查点插入位置(读
_should_stop()/_should_pause()):company_orchestrator.py:615轮次 while 循环头company_orchestrator.py:650部门 wave while 循环头(stop→break;pause→await asyncio.sleep轮询直到 resume;skip→从 ready 移除)- 深度停止(可选,二期):
backend/app/agent_runtime/core.pyrun_stream ReAct 循环(约 :893)每轮读一次 stop-flag,才能"立即停当前 LLM 调用";一期先做部门/阶段边界级控制,不改 core.py。
- 语义:pause = 边界暂停(不打断进行中的 LLM 调用,在部门/阶段边界卡住),实现简单且够用;真正的挂起-快照-重入(复用 workflow
pause_state那套)列为后续增强。
4. 实时推送通道(复用现有 WS 基建)
- 新增
backend/app/api/company_ws.py:WS /api/v1/ws/company-projects/{project_id}- 复用
app/websocket/manager.py的websocket_manager与websocket.py:35的_validate_ws_token鉴权范式(改为校验 project→company→workspace 归属)。 - 连接流程:① 按
seq从 DB 回放历史事件 → ② 订阅 Redis pub/subcompany:events:{pid}推增量 → ③ 项目终态后推 final 并关闭。
- 复用
- 复用 Redis 客户端
app/core/redis_client.py:get_redis_client(websocket.py:24 已在用)。
5. 数据质量修复(顺带)
- updated_at 心跳:执行循环周期性
project.updated_at=now(); commit()→ 修ProjectSupervisor(project_supervisor.py:41)对长任务的误杀。 - 真实 token:把部门 phase 的
final.token_usage(core.py:1020 真实 TokenBudget)透传到公司层事件,替换现在"事件计数冒充 token"(company_orchestrator.py:712)。 - dept_done duration bug(company_orchestrator.py:726):修正计时起点。
四、前端改动
1. 路由 + 页面
frontend/src/router/index.ts:新增/companies/:companyId/projects/:projectId/monitor→CompanyControlRoom.vue(requiresAuth)。CompanyBuilder.vue:项目历史/执行处加"进入控制室"入口;"流式执行"改为调用/execute/async后跳控制室(保留旧内联视图作降级)。
2. 状态管理(新 Pinia store)
frontend/src/stores/companyExecution.ts:承载deptPanels / timeline / rawEvents / ceoPlan / ceoScores / rounds / tokens / status / controlState;提供connect(pid)(WS+回放)、applyEvent(evt)(迁移自CompanyBuilder.routeStreamEventCB:1294 的分发逻辑)、stop/pause/resume/skip/reworkactions。- WS 客户端:优先
WebSocket;失败降级到现有 SSEfetch+getReader(CB:1262 模式),并加AbortController。
3. 控制室布局(CompanyControlRoom.vue)
- 顶部控制条:状态徽标 +
停止/暂停/恢复按钮(复用 ExecutionDetail.vue:90 的 resume 交互范式);轮次指示 el-steps。 - 左:组织/依赖 DAG:用已引入的
@vue-flow/core(package.json 已有,WorkflowDesigner 在用)画 CEO→部门→依赖的真实有向图,节点实时染色(等待/运行/通过/失败);节点上可"跳过/返工"。 - 中:部门实时卡片 + 焦点区:每部门展开 → phase → Agent → 每次迭代的 think/act/observe;新增展示工具入参出参(数据在
dept_agent_event.data.data的tool_call.input/tool_result.result,core.py:1124/1187,现被丢弃)。 - 右(tabs):
- 产出文件树 + 内容预览(
dept_done.result.files)。 - 统计:真实 token / 阶段耗时 / 工具调用次数。
- 原始事件流查看器:复用
SystemLogs.vue的表格+级别/来源/关键词筛选范式,展示rawEvents(调试白盒)。 - CEO 规划全文 + 评分卡(
ceo_plan.analysis/ceo_score_done,现被截断)。
- 产出文件树 + 内容预览(
4. API 封装
frontend/src/api/companies.ts:新增executeAsync / getProjectEvents(pid, sinceSeq) / stopProject / pauseProject / resumeProject / skipDepartment / reworkDepartments及 WS URL 构造。
五、数据模型新增汇总
| 表 / Key | 关键列 | 用途 |
|---|---|---|
company_project_events(表) |
project_id, seq, type, payload(JSON), created_at | 事件落库 → 回放/恢复/审计 |
Redis company:events:{pid}(pub/sub) |
— | 实时增量推送 |
Redis company:control:{pid}(key) |
{action, dept?} |
停止/暂停/跳过/返工信号 |
company_projects 新增状态 |
queued/paused/stopping/stopped |
状态机补齐 |
六、分期落地(里程碑)
- M1 可观测白盒(无控制):事件落库 + WS 通道 + 控制室页面 + Pinia store + 工具入参出参/文件/真实 token/原始事件流展示 + updated_at 心跳修复。→ 立刻把黑盒变可回放白盒。
- M2 停止 + 边界暂停/恢复:执行解耦为后台任务 + Redis 控制 flag + 编排循环检查点 + 控制条按钮。
- M3 跳过部门 / 手动返工:skip/rework 端点 + DAG 节点操作。
- M4(可选增强):core.py 深度停止检查点(立即停 LLM)+ 快照-重入式真暂停。
七、验证(端到端)
- 迁移安全:
alembic current/heads确认;新迁移upgrade后核对company_project_events存在,且 tools/api_keys 等表未被删。 - 可观测:启动一个公司项目 → 控制室能看到 CEO 规划全文、部门 ReAct 逐迭代、工具入参出参、产出文件、真实 token;
GET /projects/{pid}/events有序落库。 - 可恢复:项目运行中刷新页面 / 断网重连 → 按 seq 回放 + 继续实时。
- 可控:
暂停→部门在边界卡住、恢复→继续、停止→在检查点内终止且状态落stopped(后台无残留 asyncio 任务);跳过/返工生效。 - 回归:旧
/execute/stream与CompanyBuilder原视图仍可用;restart_backend_celery.ps1+manage.ps1 status全绿;登录 admin/123456 冒烟。
八、风险 / 注意
- 迁移雷区:head
d684c9b30c1c含删表;生成新迁移前必须先 stamp 对齐真实 DB,避免误跑删表。 - 多 worker:控制走 Redis flag(跨 worker 生效);后台任务在接收请求的 worker 内,靠协作式 flag 自取消,无需跨进程注册表。若将来多副本部署,建议迁 Celery 以获得 revoke。
- 一期不改 core.py:只能做到部门/阶段边界级停止/暂停,无法"立即中断进行中的单次 LLM 调用"——M4 再做。
- XSS:部门产出走
v-html(CB:395),控制室渲染 LLM 输出需做 sanitize。 - auto_approve_files=True:编排默认自动放行文件写入,控制室应显式展示文件写入动作供审计。