70 lines
6.9 KiB
Markdown
70 lines
6.9 KiB
Markdown
|
|
# 虚拟公司「运行时控制」(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)。
|