agent-fleet/openspec/changes/dynamic-execution-mode/design.md
Zer4tul 48c93e2ce9 feat: dynamic execution mode — Undecided tasks, two-phase dispatch, assign API
- ExecutionMode enum adds Undecided variant (default for new tasks)
- Webhook creates tasks as Undecided instead of hardcoded SshCli
- Dispatch loop: Phase 1 matches ssh_cli hosts, Phase 2 marks remaining as HttpPull
- Dequeue now returns http_pull AND undecided tasks (atomic claim)
- New endpoint: POST /api/v1/tasks/{id}/assign for coordinator explicit assignment
- Backward compatible: existing SshCli/HttpPull tasks unaffected
- 37 tests passing (6 new)
2026-05-13 05:29:12 +08:00

75 lines
2.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## Context
当前任务创建时硬编码 `execution_mode = SshCli`。这在只有 ssh_cli agent 时可行但在混合环境ssh_cli + http_pull中会阻塞 http_pull agent 接任务。
核心洞察:**执行模式是调度决策,不是任务属性**。任务只描述"需要什么能力",由调度器决定"怎么执行"。
## Goals / Non-Goals
**Goals:**
- 任务创建时不预设执行模式
- Dispatch loop 根据注册的 agent 动态决定
- Coordinator 可以显式指派任务给特定 agent
- http_pull agent 能 dequeue 到未被 ssh_cli 认领的任务
**Non-Goals:**
- 不实现智能调度负载均衡、亲和性等——Phase 2 再考虑
- 不改变 receipt 验证流程
- 不改变 Forgejo webhook 格式
## Decisions
### Decision 1: ExecutionMode 新增 Undecided
**选择**: 新增 `Undecided` 变体作为默认值
**理由**:
- 向后兼容:已有的 `SshCli`/`HttpPull` 任务不受影响
- 语义清晰:`Undecided` 表示"等待调度器决定"
- dispatch loop 只处理 `Undecided` 任务,已决定的不再改变
**替代方案**:
-`Option<ExecutionMode>`None 表示未决定)—— 语义等价但 enum 更明确
- 去掉 execution_mode 字段,纯靠 runtime 状态—— 太激进,改太大
### Decision 2: 两阶段 dispatch
**选择**: dispatch loop 分两阶段:
1. **ssh_cli 阶段**:扫描 `Undecided` 任务,查找匹配的 ssh_cli host → 找到则标记 `SshCli` 并执行
2. **http_pull 阶段**:剩余的 `Undecided` 任务标记为 `HttpPull`,等待 agent dequeue
**理由**:
- ssh_cli 是主动调度orchestrator 控制),优先级高于被动等待
- http_pull agent 通过 dequeue 自行认领,不需要 orchestrator 主动分配
- 两阶段简单清晰,不需要复杂的调度算法
### Decision 3: Coordinator 显式指派
**选择**: 新增 `POST /api/v1/tasks/{id}/assign` 端点
```json
{
"agent_id": "hermes-worker-01",
"execution_mode": "http_pull" // optional, auto-detect if omitted
}
```
**理由**:
- coordinatorJeeves可能比自动调度更了解哪个 agent 适合
- 支持跨任务指派(如"这个文档任务给 Hermes"
- 指派后任务不再是 `Undecided`,直接进入执行
### Decision 4: Dequeue 查询条件
**选择**: dequeue 查询改为 `execution_mode IN ('http_pull', 'undecided')`
**理由**:
- 纯 http_pull 任务直接匹配
- 被自动标记为 http_pull 的任务也能匹配
- 如果调度器还没来得及处理 `Undecided`agent 也能直接拉走(降级为 http_pull
## Risks / Trade-offs
- **[竞争条件]** ssh_cli dispatch 和 http_pull dequeue 可能同时抢同一个 `Undecided` 任务 → 用 SQLite 事务保证原子性dequeue 用 `UPDATE ... RETURNING` 原子操作
- **[调度延迟]** Undecided 任务可能等一个 dispatch cycle 才被标记为 http_pull → dequeue 直接查 Undecided 可以缓解