Files
aiagent/docs/M2_control_plan.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

6.9 KiB
Raw Permalink Blame History

虚拟公司「运行时控制」(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(),yield paused/resumed);跳过闸(就绪选取处拦 skip_depts,标记 completed_names 让下游推进,yield dept_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(镜像 recoverProject POST)。
  • stores/companyExecution.ts:state paused/controlPending;action stop/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 一致)。

验证(端到端)

  1. 重启后端 + npm run build(= vite)。
  2. 起一次 async 运行。
  3. 暂停/继续:/pause → status=paused、下一波不启动、paused 事件;/resume → resumed 且继续。
  4. 跳过:对 waiting 部门 /skip → dept_skipped、下游依赖照常推进、该部门不产生 agent 事件。
  5. 停止:/stop → 取消、落 company_terminated、status=stopped、WS _stream_end。
  6. 前端按钮可用(停止有确认)、断线重连控制态从事件流恢复。
  7. 回归:不点任何控制的普通运行仍正常完成(检查点在未暂停/无跳过时是 no-op)。