Files
aiagent/docs/虚拟公司白盒控制室实现方案.md
renjianbo 495eafcd78 feat: virtual company M2 control + tier-3/4 capabilities + per-dept session fix
- 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>
2026-07-26 18:16:39 +08:00

11 KiB
Raw Blame History

虚拟公司「白盒控制室」实现方案

面向 /companies(CompanyBuilder.vue)虚拟公司功能,将其从"黑盒执行"升级为"可观测 + 可控 + 可恢复"的控制室。 分支:rjb_tiangong_debug | 编写日期:2026-07-11


一、背景(为什么做)

当前 /companies 执行一个公司级项目时是黑盒:

  • 看不清:SSE 事件其实已深到每个 Agent 的 ReAct think/工具调用,但前端把工具入参出参、完整推理链、产出文件、真实 token 全丢弃或截断;CEO 规划/评分阶段无过程;依赖关系只有文字箭头。
  • 控不了:执行是"一条 async 请求内跑完",无任务态、无停止/暂停、循环里无取消检查点。客户端断开只停止消费,后端继续跑 → 产生僵尸项目。
  • 断即失:中间态不落库,刷新页面/断线后正在跑的项目无法恢复查看。

目标范围(已确认):

  1. 观测 + 完整控制:实时全量可观测 + 停止/暂停/恢复 + 跳过部门/手动返工。
  2. 独立控制室页面:新建路由页,不塞进已 1600 行的 CompanyBuilder.vue。
  3. 可恢复:刷新/断线后能重连查看正在跑的项目并回放历史。

关键结论:可观测数据源已具备(改造小),可控 + 可恢复需要架构升级(把执行从"请求协程内跑完"解耦成"后台任务 + 事件落库 + 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,该迁移含危险删表操作(drop tools/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=now
    
    末尾 except CancelledError / stop-flag → 落 stopped/failed 终态并发终止事件。

3. 控制信号 + 协作式检查点

  • 控制端点(companies.py,均校验归属):
    • POST /{cid}/projects/{pid}/stop → Redis company:control:{pid} = {action:"stop"}
    • POST .../pause / .../resume → {action:"pause"|"run"}
    • POST .../skip body {department_name} → {action:"skip", dept}(当前波跳过该部门)
    • POST .../rework body {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.py run_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/sub company: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.routeStreamEvent CB:1294 的分发逻辑)、stop/pause/resume/skip/rework actions。
  • WS 客户端:优先 WebSocket;失败降级到现有 SSE fetch+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 状态机补齐

六、分期落地(里程碑)

  1. M1 可观测白盒(无控制):事件落库 + WS 通道 + 控制室页面 + Pinia store + 工具入参出参/文件/真实 token/原始事件流展示 + updated_at 心跳修复。→ 立刻把黑盒变可回放白盒。
  2. M2 停止 + 边界暂停/恢复:执行解耦为后台任务 + Redis 控制 flag + 编排循环检查点 + 控制条按钮。
  3. M3 跳过部门 / 手动返工:skip/rework 端点 + DAG 节点操作。
  4. 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:编排默认自动放行文件写入,控制室应显式展示文件写入动作供审计。