- 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>
6.9 KiB
6.9 KiB
虚拟公司「运行时控制」(M2) 规划文档
状态:M2-a(能力 1–4)已排期实施;M2-b/c 登记待办。 关联:M1 白盒控制室(可观测)已完成;一档(部门角色匹配 + 可信真实 token/成本¥)已完成并验证。 更新:2026-07-12
背景与动机
白盒控制室(M1)解决了「看得见」,一档解决了「数据可信」(真实 token + 成本¥,实测一次运行约 ¥9.88、耗时 ~60 分钟)。但目前只能看不能动:一次运行跑歪了,只能干等到结束或直接杀进程。M2 给控制室补上「运行时控制」,让人能在长运行过程中干预。
M2 拆成 10 项可独立验收的原子能力,按依赖与价值分三片交付。一次全做改动面广、验证成本高(单次运行 ~1 小时),故分片。
架构基线(现状,已逐点核实)
- 同进程模型:
company_runner.spawn()在 API(uvicorn)进程内asyncio.create_task起编排任务,句柄存_RUNNING: Dict[str, asyncio.Task]。API handler 与编排任务同进程 → 控制用「进程内注册表 + 协作检查点」即可,无需 DB/消息队列。 - 停止半成品:已有
CancelledError → status=stopped+ 落company_terminated事件的雏形,缺「按 project_id 取消」的 helper 与对外端点。 - 编排检查点:
company_orchestrator.execute_stream有干净的注入点——轮次顶、就绪部门选取、事件抽取循环、波次边界。 - 事件单写者:编排器
yield→ runner_persist_event落库并分配单调seq。控制确认事件必须走编排器 yield,避免 API 直接写库造成 seq 竞争。 - 可复用件:
approval_manager.py(asyncio.Event 式 create/wait/resolve,供审批);round-to-round rework 过滤(供单部门返工);一档已累计的company_cost_yuan(供预算熔断)。
10 项能力总览
| # | 能力 | 片 | 机制 / 复用点 | 关键取舍 |
|---|---|---|---|---|
| 1 | 停止(硬取消整公司运行) | M2-a | company_runner.stop(pid) = _RUNNING[pid].cancel(),复用 CancelledError→stopped 雏形 |
打断在飞 LLM,立即生效 |
| 2 | 暂停(hold,不开新波次) | M2-a | ProjectControl.pause_event.clear();编排器波次/轮次边界 await wait() |
在飞部门跑完才 hold(部门粒度 ~15min 延迟) |
| 3 | 继续(resume) | M2-a | pause_event.set() |
— |
| 4 | 跳过部门(skip 未开始部门) | M2-a | control.skip_depts;就绪选取处拦截并 completed_names.add 让下游推进 |
仅未开始部门;在跑部门不停 |
| 5 | 单部门返工(对已完成项目只重跑一个部门,不重跑整公司) | M2-c | 复用 rework 过滤(failed_names→filtered_plans→current_plans),单部门子集外部驱动;新 POST …/rework {department_name} |
需读回历史 ceo_plan/产出做上下文 |
| 6 | 审批·CEO 计划(花钱前把关) | M2-b | 复用 approval_manager.py;CEO 规划后 yield awaiting_approval → await → 放行/改计划 |
阻塞式,带 timeout 兜底 |
| 7 | 审批·部门评分/返工(返工前把关) | M2-b | 同上,插在 CEO 打分后、决定返工前 | — |
| 8 | 预算熔断(成本超 ¥X 自动停) | M2-b | 把 advisory 预算检查改强制:读运行中 company_cost_yuan,超阈值 → budget_tripped + 触发停止 |
阈值入 company.config["budget"];按 ¥ 而非 token |
| 9 | 超时熔断(总耗时超 N 分钟自动停) | M2-b | 新增 deadline:编排器起始记 t0,检查点比对(当前无任何 timeout,仅事后判僵尸) |
与预算熔断共用「自动停」出口 |
| 10 | 单部门取消(取消在跑的某部门,非停整公司) | M2-c | 为每个 _run_department_stream task 建 per-dept 句柄表,cancel 单个;team 层需容错半途取消 |
比 skip 复杂,需 team_orchestrator 配合 |
分片与依赖
- M2-a(本片)· 核心四控:1 停止、2 暂停、3 继续、4 跳过。共用「进程内控制注册表 + 编排检查点」,自成闭环、最易验证。
- M2-b · 把关与自动停:6/7 人在环审批(
approval_manager)+ 8 预算熔断 + 9 超时熔断(8/9 共用「自动停」出口,与一档成本累计联动)。 - M2-c · 部门粒度:5 单部门返工 + 10 单部门取消(都要 per-dept 粒度的重跑/取消,最深,放最后)。
依赖顺序:8/9 熔断依赖 1 停止的出口;5 返工依赖一档产出落库;10 取消依赖 per-dept task 句柄。故 1–4 必须先行。
M2-a 设计要点(本片实施)
后端
company_runner.py:ProjectControl{pause_event, skip_depts}+_CONTROL注册表 +get_control/clear_control+stop(pid)(取消_RUNNING[pid],复用 stopped 雏形);done-callback 清理控制态。company_orchestrator.execute_stream:惰性import company_runner,起始取control;暂停闸(轮次顶 + 波次边界,await pause_event.wait(),yieldpaused/resumed);跳过闸(就绪选取处拦skip_depts,标记completed_names让下游推进,yielddept_skipped)。companies.py:POST …/projects/{pid}/{stop|pause|resume|skip}四端点,_assert_company_owner;pause/resume/skip 需is_running(否则 409),pause/resume 同步project.status。
前端
api/companies.ts:stopProject/pauseProject/resumeProject/skipDepartment(镜像recoverProjectPOST)。stores/companyExecution.ts:statepaused/controlPending;actionstop/pause/resume/skipDept;applyEvent加paused/resumed/dept_skipped归并。views/CompanyControlRoom.vue:控制栏(暂停/继续/停止,停止带ElMessageBox.confirm)+ 每部门waiting时显「跳过」;状态标签显「已暂停」。
语义取舍(写进 UI 提示)
- 停止 = 硬取消,立即 → stopped(打断在飞 LLM)。
- 暂停 = 只拦新波次/新轮次,在飞部门跑完才 hold(部门粒度延迟);
/pause即时置status=paused缓解 UI 反馈。 - 跳过 = 仅未开始部门;在跑部门跳过留 M2-c(能力 10)。
- 进程内状态:进程重启则控制态随运行任务一起消失(任务本就死),无持久化需求;单 worker 假设(与
_RUNNING一致)。
验证(端到端)
- 重启后端 +
npm run build(= vite)。 - 起一次 async 运行。
- 暂停/继续:
/pause→ status=paused、下一波不启动、paused事件;/resume→resumed且继续。 - 跳过:对
waiting部门/skip→dept_skipped、下游依赖照常推进、该部门不产生 agent 事件。 - 停止:
/stop→ 取消、落company_terminated、status=stopped、WS_stream_end。 - 前端按钮可用(停止有确认)、断线重连控制态从事件流恢复。
- 回归:不点任何控制的普通运行仍正常完成(检查点在未暂停/无跳过时是 no-op)。