Swarm 负责拉起 agent + 执行限额(team 决议,反转 AM 拉起)(Refs #16)

团队决议:由 Swarm 运行时(非 AM)拉起专家 agent 池并执行每用户限额。

代码:
- orchestrator/agent_launcher.py(新):plan_launch_specs(纯,按 min(池大小, MAX_AGENTS_PER_USER
  −已连) 限额 + 组装每 agent env)、resolve_model_key(override→azkv secret_ref 解析(部署
  SecretResolver/dev HEICODE_SECRET_<name>)→OPENAI_API_KEY 兜底,解析不到不伪造)、可插拔后端
  launch()(none 默认/subprocess/command 模板,fail-soft)、stop_launched。
- orchestrator/main.py:create 播种后调 launch_swarm_agents(仅去中心化、非 Manager 显式 agent;
  从 create x-user-id 取 user_id;key 服务端解析,不入 create 体);stop_swarm_run 调 stop_launched。

文档:runtime-contract §3.3 由「AM 拉起(提案待确认)」改为「Swarm 拉起 + 限额(已定)」,
更新 env 来源列(key=Swarm 从 secret_ref 解析、AGENT_ID/CAPABILITIES=Swarm launcher、
HEICODE_USER_ID=从 create 透传)+ 后端/限额/解析约束;security-boundary §6 增 Swarm 拉起 +
服务端解析 key(不上 argv/日志)说明。

测试:scripts/test-agent-launcher.py(限额封顶、env 组装、key 解析优先级、command 模板、
none no-op)接入 CI。e2e/contract 回归通过(默认 backend=none,行为不变)。

影响范围:仅 agent_swarm(orchestrator + docs + 测试 + CI)。默认 backend=none 不自动拉起、
向后兼容;密钥仅服务端 env 注入、不入 create 体/回调/日志/argv(满足 §3.1 + security-boundary)。
不改 Manager↔Swarm 契约鉴权/计费账本/审批链。

Refs #16

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Songhaoz666
2026-06-11 15:07:23 +08:00
co-authored by Claude Opus 4.8
parent fb367c4373
commit 9516b5c696
6 changed files with 413 additions and 15 deletions
+19 -15
View File
@@ -58,30 +58,34 @@ create 响应 `data`:`deployment_id`、`runtime_deployment_id`、`manager_depl
### 3.3 蜂群专家 agent 拉起环境契约(agent launch env,回应 agent_swarm#16)
> **冻结状态**:下表的 **env 字段清单本身已稳定**(`agent/main.py` 实读,无异议);但**「拉起触发链」(谁调 AM、何时拉起、agent 池由谁提供)仍为提案,待 @azgy(AM) + @zsbgnw12(HM) 确认**(见 #16),**未冻结**。在触发链敲定前,本节按「env 契约已定、触发流程待定」对待。
> **状态(团队决议)**:**Swarm 运行时负责拉起专家 agent 并执行每用户限额**(不再由 AM 拉起)。下表 env 字段 + 拉起方/限额口径**已定**。实现:`orchestrator/agent_launcher.py` + `main.launch_swarm_agents`,测试 `scripts/test-agent-launcher.py`。
**关键拓扑事实**:编排器(swarm runtime)**不拉起 agent、无 AM 客户端、不持有模型 key**。专家 agent 是**外部进程主动出站**连编排器 WS(`/ws/{agent_id}`,见 §5/security-boundary §6)。注意 §1「分解→派发→执行」里的「派发」= 把任务指派给**已连入**的 agent,**不是**编排器拉起 agent pod——§1 与本节不矛盾,但二者都未定义「谁拉起 pod」(即 #16 的缺口)。
**拓扑**:专家 agent 仍是**独立进程**,主动出站连编排器 WS(`/ws/{agent_id}`,见 §5/security-boundary §6)。**拉起方 = Swarm 运行时**:编排器在 create 播种后由 `agent_launcher` 按 provisioning 策略拉起一个能力多样的 agent 池。去中心化下**无「按 run 分解算出 agent 数/角色」这一步**(种子任务无 required caps、任意 agent 可认领,子任务按能力自路由),故 agent 池**按用户**拉起、为固定能力集。§1 的「派发」= 把任务指派给**已连入**的 agent;**拉起 agent 进程**是本节定义的 Swarm 新职责。
**拉起触发链(提案,待确认,见 #16)**:去中心化模型下编排器只播种单一种子任务、agent 自选+自主分解,**无「按 run 分解算出 agent 数/角色」这一步**;故 agent 是**按用户的常驻池**(能力多样),**由 HM 触发 AM 拉起**(HM 持 per-user `sk-` + 已有 AM 客户端;若 AM 无批量接口则 HM 循环调模板 `POST /agents`)。模型 key 全程 **HM→AM**,**不经编排器 create 请求体**(满足 §3.1)。`ORCHESTRATOR_URL` 为部署期常量,由 HM 注入。
**拉起后端**(`AGENT_LAUNCH_BACKEND`,fail-soft——拉起失败不影响 create):
- `none`(默认):不自动拉起,agent 由外部供给(保留 CI/e2e 与「外部拉起」部署);
- `subprocess`:本地起 `python -m agent.main`(dev);
- `command`:执行部署注入的模板 `AGENT_LAUNCH_CMD`(env 经进程环境传入,模板包装真实拉起如 kubectl/pod-create;**密钥不上 argv**)。
AM 拉起每个蜂群专家 agent 时,**须注入以下进程环境变量**(本仓 `agent/main.py` 实读,为接线清单——此部分无异议):
**限额(Swarm 执行)**:池大小 `AGENT_LAUNCH_POOL_SIZE`(默认 3);**实际拉起数 = min(池大小, `MAX_AGENTS_PER_USER` − 该用户已连接数)**——Swarm 在**拉起时**限额,并在 **agent 注册时**兜底硬拒(security-boundary §6 / PR#32)。
Swarm 拉起每个专家 agent 时注入以下进程环境变量(`agent/main.py` 实读):
| env | 含义 | 来源 |
|---|---|---|
| `OPENAI_API_KEY` | 模型调用凭据 = **HM 为该用户现签的 per-user `sk-`**(NewAPI token),扣发起用户 `user.Quota` | HM 现签 |
| `OPENAI_API_BASE` | **HM 模型网关 `/v1`**(OpenAI 兼容) | HM |
| `OPENAI_MODEL` | 所选模型 id | HM/计划 |
| `ORCHESTRATOR_URL` | 编排器 WS 基址(如 `ws://<swarm-runtime>`);agent 据此回连、自选任务 | 部署 |
| `AGENT_ID` | agent 实例 id(唯一) | AM |
| `AGENT_CAPABILITIES` | 能力集合(逗号分隔) | 计划/角色 |
| `HEICODE_USER_ID` | 发起用户;用于**每用户并发 Agent 上限** `MAX_AGENTS_PER_USER`(注册时强制,见 security-boundary §6) | HM |
| `OPENAI_API_KEY` | 模型调用凭据 = 该用户 per-user `sk-`(NewAPI token),扣发起用户 `user.Quota` | **Swarm 从 `billing_context.secret_ref` 解析**(HM 现签写入 KV) |
| `OPENAI_API_BASE` | HM 模型网关 `/v1`(OpenAI 兼容) | 部署(`AGENT_OPENAI_API_BASE`/`OPENAI_API_BASE`) |
| `OPENAI_MODEL` | 所选模型 id | `billing_context.default_model_id`/计划 |
| `ORCHESTRATOR_URL` | 编排器 WS 基址;agent 据此回连、自选任务 | 部署(`ORCHESTRATOR_PUBLIC_URL`/`AGENT_RUNTIME_WS_URL`) |
| `AGENT_ID` | agent 实例 id(唯一) | **Swarm launcher** |
| `AGENT_CAPABILITIES` | 能力集合(逗号分隔) | **Swarm launcher**(`AGENT_LAUNCH_CAPABILITIES` 池策略) |
| `HEICODE_USER_ID` | 发起用户;用于每用户并发上限 `MAX_AGENTS_PER_USER`(注册时强制) | **Swarm 从 create `x-user-id` 透传** |
| `WORKSPACE_DIR` / `GIT_REPO_URL` | 可选:工作区 / 代码仓 | 资源授权 |
**约束**:
- key 注入是 **server→server env**,**不入** create 请求 / 回调 / 日志 / 事件(与模板 Agent 一致);编排器 create 的 `billing_context` 仅为归因元数据(`newapi_user_ref`/`quota_ref`),**不承载 key**。见 [usage-billing-schema.md §2](./usage-billing-schema.md)。
- token 生命周期 = **agent 部署生命周期**(stop/delete 时由 HM 吊销),**非 run 结束**——专家 agent 长驻、跨 run 自选任务。
- 所有专家 agent 注入**同一把**该用户的 `sk-` + `OPENAI_API_BASE=HM/v1`,保证计费归一到发起用户;`task_executor` 随模型请求带 `X-Agent-*` 归因头供 HM/NewAPI 关联。
- **运行时事件前置**:只有 (i) HM 真把 create 派发到编排器(`SWARM_RUNTIME_ENABLED=true`,非 manager-local 适配器)且 (ii) 专家 agent 已按上表拉起连入,编排器才会回推 `task.*`/`swarm.*` 等运行时事件(否则 events feed 仅有 HM 控制面的 `deployment.status_changed`)。
- 模型 key 由 **Swarm 服务端从 `billing_context.secret_ref`(`azkv://`)解析**(`resolve_model_key`:override → azkv(部署 SecretResolver / dev `HEICODE_SECRET_<name>`)→ 编排器 `OPENAI_API_KEY` 兜底)后注入被拉起 agent 的 env。key **不入** create 请求体 / 回调 / 日志 / argv(满足 §3.1);解析不到则 agent keyless 启动并明确报错(不伪造)。`billing_context` 的 `newapi_user_ref`/`quota_ref` 仍仅为归因元数据。见 [usage-billing-schema.md §2](./usage-billing-schema.md)。
- 同一用户的专家 agent 注入**同一把** `sk-` + `OPENAI_API_BASE=HM/v1`,计费归一到发起用户;`task_executor` 随模型请求带 `X-Agent-*` 归因头供 HM/NewAPI 关联。token 由 HM 按 agent 生命周期吊销(stop/delete)。
- **运行时事件前置**:只有 (i) HM 真把 create 派发到编排器(`SWARM_RUNTIME_ENABLED=true`,非 manager-local 适配器)且 (ii) Swarm 已按上表拉起 agent 并连入,编排器才回推 `task.*`/`swarm.*` 运行时事件(否则 events feed 仅有 HM 控制面 `deployment.status_changed`,见 #39)。
## 4. 状态机