Files
Agentswarm/docs/scheduling/dispatch-score-schema.md
T
Songhaoz666andClaude Opus 4.8 62610a7e3f 调度评分:多维可解释打分匹配 + dispatch.decision_made(Closes #9)
把派发从「能力子集 + 空闲」升级为任务为中心的多维可解释打分匹配。

新增/改动:
- orchestrator/dispatch_score.py(新):DispatchCandidate/DispatchScore + 加权掩码归一
  打分(score_candidate/rank_candidates)+ build_dispatch_decision_event。
- orchestrator/main.py:抽出三模式共用的 finalize_dispatch;新增 scored_matchmake
  (ENABLE_DISPATCH_SCORE,默认关,与 ACO 择一)——为每个就绪任务在有能力的空闲 Agent
  间按 capability/历史成功(τ)/负载/预算压力/权限择优,记录可解释决策;_run_budget_pressure
  计算真实预算占比。
- orchestrator/swarm_runtime.py:SwarmRun.dispatch_decisions + record_dispatch_decision
  (内部状态,非 Manager 事件)。
- 测试:scripts/test-dispatch-score.py(公式 24 项)+ scripts/test-dispatch-scored.py
  (集成 11 项:按 τ/负载多 Agent 择优 + 排除原因 + 可回放记录);CI 纳入两者。
- docs/scheduling/dispatch-score-schema.md、CLAUDE.md 同步。

诚实边界:risk_score/estimated_cost/estimated_time 本仓无来源 → None 并在 payload
uncollected_dimensions 披露(不伪造,规则 #9);dispatch.decision_made 暂为 Swarm 内部
记录,未进 Manager 事件契约(需 event-schema 注册,跨端)。flag 关闭时贪心/ACO 路径逐字节不变。

Closes #9

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 14:08:08 +08:00

12 KiB
Raw Blame History

Dispatch 评分模型与 dispatch.decision_made 事件(Issue #9)

让派发从「仅按能力集合匹配」升级为「多维、可解释、加权」的调度评分:在能力之外引入负载、预算压力、风险、权限契合、历史质量、预估成本/时间等维度,并把每次派发的候选列表、被选 Agent、逐维度评分细分与排除原因,通过 dispatch.decision_made 事件暴露出来。本文档是该评分层的唯一入口。

实现:orchestrator/dispatch_score.py(纯评分)+ orchestrator/main.py: scored_matchmake/finalize_dispatch(已接入派发环,门控 ENABLE_DISPATCH_SCORE,默认关)。测试:scripts/test-dispatch-score.py(公式)+ scripts/test-dispatch-scored.py(集成)。详见 §4。

1. 与 #10(τ/η/P 决策)的边界

#9 与 #10(orchestrator/decision_engine.py,已实现)耦合但分工明确:

Issue 产出 性质
#10 把历史归一为 τ、把先验归一为 η,按 P=τ^α·η^β/Σ 概率采样一个任务(ε-greedy),并产出可回放的内部 DecisionTrace 概率决策、含 RNG、内部状态(不进 Manager 事件流)
#9(本文) 候选 Agent×任务的可解释评分输入(capability_match / load / budget_pressure / risk_score / permission_fit / historical_success / estimated_cost / estimated_time)+ 每次派发的候选列表与排除原因(dispatch.decision_made 事件) 确定性(无 RNG)、可审计、面向 Manager
  • 共享输入:historical_success 直接复用 #10 的 τ trail(τ 是跨 run 学到的「实测声誉」,已在 [0.05, 1.0]),经 normalize_tau 归一到 [0,1]。capability_match、load 与 DecisionEngine.compute_eta 的 match/resource 同定义,保证两层对「能力契合」「负载」理解一致。
  • dispatch_score.py 模块本身是纯计算、无 RNG、不发事件。选择行为由 main.py 的集成提供:开 ENABLE_DISPATCH_SCORE 时,scored_matchmake 用本评分确定性地在多个候选 Agent 中择优(任务为中心,与 #10 的概率采样互斥、择一启用)。即 #9 = 确定性可解释打分匹配;#10 = 概率采样。两者都默认关,关闭时为原贪心派发。

2. 评分模型

2.1 维度与权重(DISPATCH_WEIGHTS)

维度 权重 方向 含义 / 归一来源
capability_match 0.30 收益 required ∩ caps 的 Jaccard(奖励专精);无 required → 1.0(任何人可做,对齐 can_agent_run_task)
historical_success 0.20 收益 复用 #10 的 τ trail,normalize_tau 映射到 [0,1]
load 0.15 收益 空闲槽位余量(越空越高),对齐 AGENT_SLOTS / RESOURCE_NORM_SLOTS
permission_fit 0.12 收益 Agent 是否被允许执行该任务(1.0 允许 / 0.0 不允许)
budget_pressure 0.10 成本 run 预算已消耗比例(越高越扣分)
risk_score 0.08 成本 任务风险(越高越扣分)
estimated_cost 0.03 成本 预估 $(越高越扣分)
estimated_time 0.02 成本 预估墙钟(越高越扣分)
  • 权重显式、可审计,不必和为 1.0——评分时按「实际有信号的维度」重归一(见 §2.2)。
  • 成本维度(budget_pressure/risk_score/estimated_cost/estimated_time)的贡献取 (1 − 值),即「越高越差」;其余为收益维度,贡献取原值。

2.2 打分公式(掩码 + 重归一)

total = Σ_{d∈present} w_d · contribution_d / Σ_{d∈present} w_d
contribution_d = (1 − x_d)  if d 是成本维度
               = x_d        否则           (x_d 夹紧到 [0,1])
  • 值为 None 的维度同时退出分子与权重归一(masked + renormalized),因此「未采集」的信号既不加分也不扣分,total 始终落在 [0,1]。这与 benchmark.metrics.quality_score 的掩码纪律一致(规则 #9:绝不为未采集量伪造 0/1)。
  • 权重为 0 的维度仍写入 breakdown(透明),但不参与计分。

2.3 数据结构

  • DispatchCandidate:一个 (agent, task) 配对的原始信号。除标识字段外每个字段都是评分输入;无信号的维度必须留 None,不得伪造。score 由 score_candidate() 回填。
  • DispatchScore:
    • total:重归一加权总分([0,1]);
    • breakdown:每个维度的原始值(或 None),供审计;
    • weights_used:实际施加到各「在场」维度的重归一权重;
    • missing:本次无信号的维度名列表(显式列出「未采集」集合,而非静默省略)。

2.4 函数

函数 职责
normalize_capability_match(required, caps) Jaccard;无 required → 1.0
normalize_tau(tau) τ → [0,1];None → None
normalize_load(free_slots) 空闲槽位 → [0,1];None → None
score_candidate(candidate) 纯函数打分(无 RNG),回填 candidate.score
rank_candidates(candidates) 全部打分并按 total 降序(稳定排序,决定性)
build_dispatch_decision_event(candidates, chosen, excluded_reasons, task_id=...) 构造 dispatch.decision_made 事件 payload

3. dispatch.decision_made 事件

3.1 payload 结构

{
  "task_id": "swarm-X-impl",          // 与 envelope 顶层 task_id 一致
  "chosen_agent_id": "agent-A",        // 被选 Agent(无派发时为 null)
  "chosen_task_id": "swarm-X-impl",
  "candidate_count": 3,
  "candidates": [                       // 全部候选 + 逐维度细分
    {
      "agent_id": "agent-A",
      "task_id": "swarm-X-impl",
      "agent_role": "implementation",
      "score": {
        "total": 0.83,
        "breakdown": {                  // 原始值(或 null)
          "capability_match": 1.0,
          "historical_success": 0.95,
          "load": 1.0,
          "permission_fit": 1.0,
          "budget_pressure": 0.10,
          "risk_score": null,
          "estimated_cost": null,
          "estimated_time": null
        },
        "weights_used": { "capability_match": 0.30, "historical_success": 0.20, "load": 0.15, "permission_fit": 0.12, "budget_pressure": 0.10 },
        "missing": ["risk_score", "estimated_cost", "estimated_time"]
      }
    }
    // ... 其余候选
  ],
  "excluded": {                         // agent_id → 排除原因(人读)
    "agent-B": "lower_score",
    "agent-C": "capability_mismatch"
  },
  "weights": { /* DISPATCH_WEIGHTS 快照 */ },
  "uncollected_dimensions": ["estimated_cost", "estimated_time", "risk_score"]
}

3.2 Envelope 与事件流约定

  • 本 payload 由 orchestrator 的 swarm_runtime.emit_event(run, "dispatch.decision_made", task_id=..., payload=...) 包裹,自动补齐 event_id/swarm_id/occurred_at/correlation_id/source 等标准 envelope 字段(见 docs/integration/event-schema.md §2)。
  • 该事件类型当前不在 event-schema.md §4 的 HM 注册表中,属于 Swarm 内部可观测扩展。是否需要 HM 入库/前端展示需与 Manager 侧对齐——在对齐前,事件只进 Swarm 本地事件流,不应宣称已被 HM 校验/消费(见 §5)。

4. 集成现状(已落地)

已接入 orchestrator/main.py(issue #9 关闭 PR)。下述为实际实现,非提案。

启用开关 ENABLE_DISPATCH_SCORE(默认关,与 ENABLE_ACO_DISPATCH 择一)。开启后,task_dispatch_loop 在每个 tick 走 scored_matchmake(...) 任务为中心的匹配,而非 Agent 拉取:

  1. 枚举所有就绪 PENDING 任务(按 created_at 稳定排序)。
  2. 对每个任务,遍历有能力且有容量的空闲 Agent,构造 DispatchCandidate,信号取自既有状态:
    • capability_match = normalize_capability_match(required, caps)(Jaccard)
    • historical_success = normalize_tau(decision_engine.get_tau(role, agent))(复用 #10 的 τ trail)
    • load = normalize_load(AGENT_SLOTS[agent])(空闲槽位)
    • permission_fit = 1.0(can_agent_run_task 通过;不通过的 Agent 直接以 capability_mismatch 进 excluded,不参与排序)
    • budget_pressure = 真实计算 _run_budget_pressure(task)(run 已消耗成本 / max_cost_usd;无预算或无用量 → None)
    • risk_score / estimated_cost / estimated_time = None(本仓无来源,见 §5,payload uncollected_dimensions 披露)
  3. rank_candidates(...) 确定性择优;落选 Agent 记 lower_score,无能力 Agent 记 capability_mismatch。
  4. 选中对经 finalize_dispatch(agent, task, dispatch_event=...)(三种派发模式共用的统一收尾)完成 assign + task.claimed + 派发上下文 + 下发;同一 tick 内被选 Agent 不重复分配。
  5. dispatch.decision_made payload(候选明细 + 排除原因)经 swarm_runtime.record_dispatch_decision 存入 SwarmRun.dispatch_decisions(内部状态,可审计/回放)。

刻意不发 Manager 事件:dispatch.decision_made 暂为 Swarm 内部可观测记录,未经 swarm_runtime.emit_event 进入回调流——避免污染未注册的 Manager 事件契约。若 HM 需消费,需先在 docs/integration/event-schema.md §4 注册(跨端,超出 #9 范围)。build_dispatch_decision_event 与 dispatch_score_event_enabled() 已备好,供注册后启用 Manager 侧发送。

行为隔离:ENABLE_DISPATCH_SCORE 关闭时,贪心/ACO 路径逐字节不变(共用的 finalize_dispatch 保持原语义)。

测试:scripts/test-dispatch-score.py(纯公式 24 项)+ scripts/test-dispatch-scored.py(集成:按 τ/负载在多 Agent 中择优 + 记录可回放,11 项)。运行命令见各测试 docstring 与 docs/TESTING.md。

5. 诚实边界(本仓未采集 / 未做的)

严格遵循规则 #9,以下维度在本仓无真实来源,一律以 None("not collected")表示,绝不伪造数值;并通过 payload 的 uncollected_dimensions 显式披露:

  • estimated_cost / estimated_time:本仓无「派发前」成本/时长预估器。模型成本与 runtime_seconds 只在任务完成后经 emit_usage_event 得到(事后量),派发时不可得 → None。
  • risk_score:本仓无每任务风险分类器。风险/审批(risk_level)归属 Manager 审批链,不在队列 Task 上 → None。
  • region / GPU / 硬件契合:AgentMetadata 无任何此类字段(仅 agent_id/status/last_heartbeat/capabilities/current_task_id)→ 不引入该维度(不造假维度)。
  • budget_pressure:理论上可由 run 预算与已消耗比例算出,但派发前缺少可靠的「run 级已消耗」累计来源(usage 事件按任务事后发出),故集成示例中留 None;若后续聚合 run 级已消耗,可填真实比例。
  • permission_fit:当前仅能用「能力子集」近似(can_agent_run_task)。真正的 RBAC/审批级权限校验在 Manager 侧,Swarm 派发面无此信号——本维度是「能力可行性」的近似,不是完整权限判定。
  • 决策质量未证:本评分维度/权重是否能产出更优派发是经验命题,需 Group C 的对比 harness 才能回答;在此之前,本评分层默认不发事件(ENABLE_DISPATCH_SCORE_EVENT 关)、不改派发行为。

6. 文件

文件 职责
orchestrator/dispatch_score.py DispatchCandidate/DispatchScore 数据结构、归一函数、score_candidate/rank_candidates、build_dispatch_decision_event
scripts/test-dispatch-score.py 24 项断言(归一器、同能力不同代价/负载/历史 → 不同分且可解释、≥4 个非能力维度各自影响评分、None 掩码不扣分、事件携带候选细分 + 排除原因 + 未采集披露)
docs/scheduling/dispatch-score-schema.md 本文档