worker/src/resource_sync.c init_builtin_load_indices registers 11 load indices (r15s/r1m/r15m/ut/pg/ls/it/swp/mem/tmp/io) but resources.yaml only defined r1m/r15s/mem/tmp. The 7 missing ones (r15m/ut/pg/ls/it/swp/io) caused controller "update_resval: Success" ERRORs and scheduler "unknown resource in per-worker resvals update" warnings, leaving the worker resource view incomplete. Add the 7 definitions matching the existing r1m/r15s pattern. Docs: log bug-1494 (all jobs PEND, scheduler never dispatches; after this config fix jobs still PEND — remaining suspect is the scheduler→controller join path after restart), update design doc + verification manual known-issue tables and root CLAUDE.md cluster-verification status. Co-Authored-By: Claude <noreply@anthropic.com>
604 lines
31 KiB
Markdown
604 lines
31 KiB
Markdown
# Unified Scheduler Agent Framework — 设计文档
|
||
|
||
> **项目代号**:scheduler-agent
|
||
> **文档版本**:v1.2
|
||
> **最近更新**:2026-08-01
|
||
> **文档状态**:实施中(Phase 0-1 代码完成;集群验证:读 + submit ✅,control 有 apiserver 预存 bug 待修)
|
||
|
||
---
|
||
|
||
## 1. 文档目的
|
||
|
||
本文档定义"自然语言调度器管理 Agent 框架"的完整设计——一套架构同时适配自研调度器、LSF、Slurm、Kubernetes,让用户通过自然语言完成集群管理操作。
|
||
|
||
本文档是**设计权威来源(source of truth)**,实施进度、架构变更、决策记录均在此维护。
|
||
|
||
---
|
||
|
||
## 2. 背景与项目定位
|
||
|
||
### 2.1 为什么做
|
||
|
||
HPC 集群(调度器)的使用者——研究员、运维——操作门槛高。`bjobs`/`squeue`/`bsub` 等命令行语法复杂,跨系统(LSF/Slurm/K8s)命令不互通。AI Agent 可以将这些操作抽象为自然语言。
|
||
|
||
### 2.2 核心定位
|
||
|
||
> **我们是调度器开发者,不是壳。**
|
||
|
||
与 iris-mcp、srunx 等项目的结构性差异:
|
||
|
||
| 对比维度 | 其他项目 | 本项目 |
|
||
|----------|---------|--------|
|
||
| 交互方式 | CLI 文本解析 | 内核级嵌入 Agent 原生接口 |
|
||
| 事件能力 | 无,只能轮询 | **实时事件流**(自研独有) |
|
||
| 授权粒度 | 粗粒度 | 细粒度 RBAC |
|
||
| API 能力 | 受限 | 高级 API 原生支持 |
|
||
|
||
### 2.3 目标用户
|
||
|
||
- **研究员**:提交作业、查状态、诊断失败原因,用自然语言
|
||
- **运维**:集群监控、资源管理、问题排查,用自然语言
|
||
|
||
### 2.4 市场叙事
|
||
|
||
- Phase 0-2:自研调度器体验碾压 LSF/Slurm(实时 + 自然语言)
|
||
- Phase 3-4:"Any cluster, natural language"
|
||
|
||
---
|
||
|
||
## 3. 总体架构
|
||
|
||
### 3.1 分层架构图
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Layer 0 · 用户界面层 │
|
||
│ 🧑 用户 ──→ 💻 CLI (Claude Code) │
|
||
│ → 🌐 Web Chat (自建 Chat UI) │
|
||
│ → 🔌 IDE Plugin (Cursor / VS Code) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│ 自然语言
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Layer 1 · 推理规划层 │
|
||
│ 🧠 Orchestrator LLM │
|
||
│ 意图识别 → 规划 → 工具调用 → 纠错 │
|
||
│ (Claude / GPT / 本地模型) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│ Tool Call (JSON-RPC)
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Layer 2 · Agent 核心层 │
|
||
│ 🔌 MCP 协议 (stdio/SSH/HTTP) │
|
||
│ ⚙️ 核心引擎 (Tool 路由 · Session · 多后端调度) │
|
||
│ 📦 统一资源模型 URM (Job/Queue/Node) │
|
||
│ 🔐 鉴权 + 审计 (RBAC · 审计日志) │
|
||
│ 📋 Session 上下文管理 │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│ Adapter 接口
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Layer 3 · 后端适配器层 │
|
||
│ 🏆 Native Adapter (自研调度器) ★ 实时事件流 │
|
||
│ 📟 LSF Adapter (CLI/Python SDK) │
|
||
│ 📡 Slurm Adapter (REST API/CLI) │
|
||
│ ☸️ K8s Adapter (client-go/Watch) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
│
|
||
┌─────────────────────────────────────────────────────────────────┐
|
||
│ Layer 4 · 调度器层 │
|
||
│ 🚀 自研调度器 (事件总线 · RBAC · 高级 API) │
|
||
│ 📡 LSF 集群 (9.x/10.x) · 📡 Slurm 集群 (23.x+) │
|
||
│ ☸️ Kubernetes (1.28+) │
|
||
└─────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
### 3.2 数据流
|
||
|
||
```
|
||
用户自然语言 → LLM → MCP Tool Call → Agent 核心 → Adapter → 调度器
|
||
↑
|
||
事件流实时回传(自研独有)
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 核心设计
|
||
|
||
### 4.1 统一资源模型(URM)
|
||
|
||
每个调度器的概念不同。URM 提供通用抽象,同时通过 `extensions` 暴露后端专有能力。
|
||
|
||
#### 4.1.1 核心类型
|
||
|
||
```go
|
||
type URMJob struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Status JobStatus `json:"status"` // pending | running | succeeded | failed | cancelled | suspended | unknown
|
||
RawStatus string `json:"raw_status"` // 原始状态(PEND / RUNNING / Suspended)
|
||
Queue string `json:"queue"` // 队列 / partition / namespace
|
||
Nodes int `json:"nodes"`
|
||
Cores int `json:"cores"`
|
||
GPUs int `json:"gpus"`
|
||
MemoryBytes int64 `json:"memory_bytes"`
|
||
WalltimeSeconds int `json:"walltime_seconds"`
|
||
Dependencies []JobRef `json:"dependencies"`
|
||
SubmissionTime time.Time `json:"submission_time"`
|
||
StartTime *time.Time `json:"start_time,omitempty"`
|
||
ExitCode *int `json:"exit_code,omitempty"`
|
||
Owner string `json:"owner"`
|
||
LogsURL string `json:"logs_url,omitempty"`
|
||
Extensions map[string]any `json:"extensions,omitempty"` // 后端专有能力
|
||
}
|
||
|
||
type URMQueue struct {
|
||
Name string `json:"name"`
|
||
Status string `json:"status"` // active | inactive | draining
|
||
NodeCount int `json:"node_count"`
|
||
RunningJobs int `json:"running_jobs"`
|
||
PendingJobs int `json:"pending_jobs"`
|
||
MaxWalltimeSeconds int `json:"max_walltime_seconds"`
|
||
DefaultResources *ResourceSpec `json:"default_resources,omitempty"`
|
||
Extensions map[string]any `json:"extensions,omitempty"`
|
||
}
|
||
|
||
type URMNode struct {
|
||
Name string `json:"name"`
|
||
Status string `json:"status"` // idle | busy | down | drain | unknown
|
||
CoresTotal int `json:"cores_total"`
|
||
CoresUsed int `json:"cores_used"`
|
||
MemoryBytesTotal int64 `json:"memory_bytes_total"`
|
||
MemoryBytesUsed int64 `json:"memory_bytes_used"`
|
||
GPUCount int `json:"gpu_count"`
|
||
Features []string `json:"features"`
|
||
Extensions map[string]any `json:"extensions,omitempty"`
|
||
}
|
||
```
|
||
|
||
#### 4.1.2 设计原则
|
||
|
||
- **核心字段** = 所有调度器都有,LLM 使用同一套 prompt 理解
|
||
- **扩展字段** = `Extensions` map 兜底后端专有能力(fairshare / GRES / tolerations)
|
||
- **能力文档动态注入** = LLM prompt 中动态注入当前后端的能力文档
|
||
|
||
### 4.2 后端适配器层
|
||
|
||
#### 4.2.1 SchedulerAdapter 接口
|
||
|
||
```go
|
||
type SchedulerAdapter interface {
|
||
// ── 读操作 ──
|
||
ListJobs(ctx context.Context, filter JobFilter) ([]*URMJob, error)
|
||
GetJob(ctx context.Context, id string) (*URMJob, error)
|
||
ListQueues(ctx context.Context) ([]*URMQueue, error)
|
||
ListNodes(ctx context.Context) ([]*URMNode, error)
|
||
GetMetrics(ctx context.Context) (*Metrics, error)
|
||
GetJobLogs(ctx context.Context, id string, opts LogOpts) ([]string, error)
|
||
|
||
// ── 写操作 ──
|
||
SubmitJob(ctx context.Context, spec *JobSpec) (*URMJob, error)
|
||
CancelJob(ctx context.Context, id string) (*CancelResult, error)
|
||
HoldJob(ctx context.Context, id string) error
|
||
ReleaseJob(ctx context.Context, id string) error
|
||
ModifyJob(ctx context.Context, id string, changes *JobModification) (*URMJob, error)
|
||
|
||
// ── 高级(可选实现) ──
|
||
CreateReservation(ctx context.Context, spec *ReservationSpec) (*Reservation, error)
|
||
AnalyzeFailure(ctx context.Context, jobID string) (*FailureAnalysis, error)
|
||
|
||
// ── 事件流 ──
|
||
WatchJobs(ctx context.Context, filter EventFilter) (<-chan JobEvent, error)
|
||
}
|
||
```
|
||
|
||
#### 4.2.2 各后端实现策略
|
||
|
||
| 后端 | 适配器语言 | 实现方式 | 事件流 |
|
||
|------|-----------|---------|--------|
|
||
| **自研调度器** | **Go** | 调用 apiserver REST API(cgo 桥接 C backend) | ✅ 原生实时事件推送 |
|
||
| **LSF** | **Go + Python sidecar** | Phase 0-1: `bjobs`/`bsub` CLI 解析;Phase 2+: Python SDK | ⚠️ 轮询 |
|
||
| **Slurm** | **Go** | Phase 0-1: `squeue`/`sbatch` CLI 解析;Phase 2+: slurmrestd | ⚠️ 轮询 |
|
||
| **Kubernetes** | **Go** | client-go 直接调用 K8s API Server | ✅ Informer/Watch |
|
||
|
||
> **⚠️ 已修正(2026-07-26)**:myxce 调度内核是 C(Cap'n Proto + Raft HA),Go 只存在于 `apiserver`(cgo 桥接 `libxce`)和 `xcecli`。因此 Native Adapter 的实现路径是 **mcp-server 调用 apiserver 现成 REST API**,而非进程内 import Go package。
|
||
|
||
### 4.3 语言选择策略
|
||
|
||
**主体语言:Go**。
|
||
|
||
1. **自研调度器是 Go 栈**——Adapter 可 import 内部 client 包(性能最优)
|
||
2. **单二进制分发**——ssh 到 login node 不需要装运行时
|
||
3. **`os/exec` + stdout 解析**天然适配 CLI-based adapter(LSF/Slurm)
|
||
4. **并发/事件流**——goroutine + channel 完美适配 WatchJobs
|
||
|
||
**分阶段语言演进**:
|
||
|
||
```
|
||
Phase 0-1: 纯 Go (MCP Server + 所有 adapter)
|
||
↓ 当需要 LSF C 库 / Python SDK 深度集成时
|
||
Phase 2+: Go 主体 + LSF 侧 Python Sidecar(UDS/gRPC 通信)
|
||
```
|
||
|
||
### 4.4 MCP 协议接口(对外暴露的工具集)
|
||
|
||
通过 MCP Server 暴露给 Claude Code、Cursor 等 MCP 客户端,或自建的 Chat UI。
|
||
|
||
#### 4.4.1 读工具
|
||
|
||
| Tool | 说明 |
|
||
|------|------|
|
||
| `scheduler_list_jobs(filter)` | 列出作业,支持按状态/队列/owner 过滤 |
|
||
| `scheduler_get_job(id)` | 获取作业详情 |
|
||
| `scheduler_list_queues()` | 列出队列及容量、运行/排队数 |
|
||
| `scheduler_list_nodes()` | 列出节点及资源使用情况 |
|
||
| `scheduler_job_logs(id, tail?, since?)` | 获取作业输出日志 |
|
||
| `scheduler_cluster_metrics()` | 集群整体负载、使用率概览 |
|
||
|
||
#### 4.4.2 写工具(默认 require confirm)
|
||
|
||
**Phase 1 已实现(2026-08-01)**:
|
||
|
||
| Tool | 说明 | confirm 机制 |
|
||
|------|------|--------------|
|
||
| `scheduler_submit(template, command, ...)` | 提交作业,支持模板(`general_serial` 通用串行脚本)+ 格式化参数;`dry_run=true` 返回 review card | `dry_run` review 模式 + destructive 注解 |
|
||
| `scheduler_cancel(id)` | 取消作业(→ action `kill`,默认 SIGTERM) | destructive 注解,客户端确认 |
|
||
| `scheduler_hold(id)` | 暂停止作业(→ action `stop`,默认 SIGTSTP) | destructive 注解,客户端确认 |
|
||
| `scheduler_release(id)` | 恢复暂停的作业(→ action `resume`,默认 SIGCONT) | destructive 注解,客户端确认 |
|
||
|
||
> **Phase 1 未做**:`scheduler_modify(id, changes)` —— apiserver 当前无 modify 端点(job control 仅支持 kill/stop/resume/checkpoint),需新增后端端点,推迟。
|
||
>
|
||
> **confirm 实现说明**:MCP 协议(mcp-go v0.57.0)无原生 `requiresConfirmation` 字段,只有 `ToolAnnotation{ReadOnlyHint, DestructiveHint}`。实现采用两层:① `scheduler_submit` 用 `dry_run` 参数作为协议内 review 门(先返回 resolved JobSpec 供用户确认,再以 `dry_run=false` 真正提交);② cancel/hold/release 标记 `DestructiveHint: true`,由 MCP 客户端(Claude Code)在执行前弹出确认。这与设计 §4.5"写操作默认 require confirm"一致。
|
||
|
||
#### 4.4.3 高级工具
|
||
|
||
| Tool | 说明 |
|
||
|------|------|
|
||
| `scheduler_analyze_failure(id)` | 自动诊断作业失败原因(日志分析 + 退出码 + 资源分析) |
|
||
| `scheduler_suggest_resources(description)` | 根据自然语言任务描述推荐资源配置 |
|
||
| `scheduler_estimate_wait_time(queue_spec)` | 估算排队时间 |
|
||
|
||
### 4.5 安全模型
|
||
|
||
这是作为调度器厂商**最重要的护城河**。Agent 的错误操作会直接侵蚀对产品的信任。
|
||
|
||
#### 4.5.1 安全链路
|
||
|
||
```
|
||
┌──────┐ ┌──────┐ ┌───────────┐ ┌────────────┐ ┌─────────┐
|
||
│ 用户 │ → │ LLM │ → │ Agent 层 │ → │ Audit Proxy│ → │ 调度器 │
|
||
└──────┘ └──────┘ └───────────┘ └────────────┘ └─────────┘
|
||
↑ │
|
||
│ └→ 审计日志
|
||
└── 写操作 require confirm
|
||
Autopilot 需用户显式启用
|
||
```
|
||
|
||
#### 4.5.2 安全原则
|
||
|
||
1. **所有写操作默认 require confirm**:LLM 生成命令/参数,展示给用户确认后再执行
|
||
2. **Autopilot 模式**:用户通过 `--autopilot` 显式启用后,Agent 可在授权范围内自主执行
|
||
3. **完整审计日志**:每条 AI 操作记录 `[timestamp] [user] [action] [params] [confirmed?]`
|
||
4. **拒绝不安全操作**:直接修改调度器配置、操作他人作业(无授权时)、sudo/root 操作
|
||
5. **操作范围绑定**:用户可通过 prompt 限定 Agent 作用域("只操作我的作业" / "只在这个队列")
|
||
|
||
#### 4.5.3 凭证模型
|
||
|
||
- **mcp-server 用服务账号 API Key**(`xce_ak_` 前缀)调用 apiserver,而非透传终端用户 JWT
|
||
- API Key 不做 IP 绑定(面向长期运行的自动化客户端,源 IP 不稳定)
|
||
- Phase 0 用 `mcp_readonly` 角色(仅 `:view` 权限)—— ✅ 已实施(2026-07-26)
|
||
- **Phase 1 引入 `mcp_operator` 角色** —— ✅ 已实施(2026-08-01):5 个 `*:view`(domain all)+ `queue:submit` + `job:control`(domain all)。覆盖 `scheduler_submit` 和 cancel/hold/release 的全部写端点权限。**不含** host:control / cluster:control / rbac:* / file:* / user:*——MCP 服务账号仍不能管理集群配置或其他用户/角色。
|
||
- 生成方式:管理员用 `POST /xce/v1/apikeys`(`rbac:control`,仅 cluster_admin)创建,`role_name: "mcp_operator"`。handler 同时注册 key 和 `AddRoleForUser(username, role)`。
|
||
|
||
### 4.6 事件流设计
|
||
|
||
#### 4.6.1 当前调研结论(2026-07-30)
|
||
|
||
**XCE 产品目前零对外实时推送实现。** 所有客户端通信均为 100% 请求/响应模式。
|
||
|
||
已有(进程内):
|
||
- C Controller → `NotificationHandler`(`OnJobCreated`/`OnJobUpdated`/`OnJobFinished`...)→ `DefaultHandler` → 写入内存 `DataStore`
|
||
- **事件到进程就停了,没有对外通道**
|
||
|
||
确认没有的:
|
||
- SSE endpoint、WebSocket、gRPC streaming、长轮询、Subscribe/Watch API、Pub/Sub 均不存在
|
||
- Gin 已带 `github.com/gin-contrib/sse`(间接依赖),无需额外引入
|
||
|
||
#### 4.6.2 设计方向
|
||
|
||
在 apiserver 侧加 **SSE endpoint** + EventBroker,把 `DefaultHandler` 收到的事件同时广播给订阅者。
|
||
|
||
```
|
||
C Controller ──OPCode_push──→ apiserver ──SSE──→ mcp-server / 浏览器
|
||
NotificationHandler
|
||
↓ 仅需在 handler 加一行
|
||
broker.Publish(event)
|
||
```
|
||
|
||
#### 4.6.3 工作量估算
|
||
|
||
| 任务 | 工作量 | 位置 |
|
||
|------|--------|------|
|
||
| apiserver 加 SSE endpoint + EventBroker | 1 天 | `controllers/events.go`(新建)|
|
||
| NotificationHandler → Broker 转发 | 0.5 天 | `handler.go` 加 publish |
|
||
| RBAC `event:subscribe` 权限 | 0.5 天 | `config/rbac.yaml` + middleware |
|
||
| schedulerclient 加 `WatchJobs()` | 0.5 天 | `httpclient.go` SSE 解析 |
|
||
| mcp-server 集成 | 0.5 天 | Agent 工作流:提交→监控→自动处理 |
|
||
|
||
**总计 2-3 天。** 决策:Phase 1 走通写操作后再接入。
|
||
|
||
---
|
||
|
||
## 5. 实施路线图
|
||
|
||
### 5.1 Phase 0 — 只读 MCP Server(✅ 已完成代码,待集群验证)
|
||
|
||
- 实现自研调度器的只读 adapter(ListJobs / GetJob / ListQueues / ListNodes / GetJobLogs)
|
||
- 暴露 6 个 MCP 读工具
|
||
- 验证:自然语言问"我的作业状态"、"队列里有什么"
|
||
|
||
**输入**:`schedulerclient` Go package(只读接口)
|
||
**输出**:可运行的 MCP Server,连接 Claude Code 后可以用自然语言查集群状态
|
||
|
||
### 5.2 Phase 1 — 作业提交 + 模板(✅ 已完成代码,待集群验证;2026-08-01)
|
||
|
||
- **Submit / Cancel / Hold / Release 写操作工具**(✅ 4 个已实现;Modify 因 apiserver 无端点推迟)
|
||
- 作业模板(✅ `general_serial` 通用串行脚本已实现;registry 机制支持后续扩展 GPU/array/dependency)
|
||
- 严格的参数校验 + review 模式(✅ `scheduler_submit` 的 `dry_run=true` 返回 resolved JobSpec 供用户确认)
|
||
- 安全性审查(✅ destructive 注解 + `mcp_operator` RBAC 角色)
|
||
- **待集群验证**:运行中的 apiserver + 生成 `mcp_operator` API Key + Claude Code MCP 配置(同 Phase 0 阻塞项)
|
||
|
||
### 5.3 Phase 2 — 监控 + 纠错 loop(+1 周)
|
||
|
||
- Agent 工作流:提交 → WatchJobs 接收事件 → 发现失败 → 分析日志 → 建议修复
|
||
- **自研调度器的实时事件流是核心差异点**:作业状态变化时 adapter 直接推送事件,LLM 自动触发处理
|
||
- 与 LSF/Slurm 适配器的"轮询"模式形成体验代差
|
||
|
||
### 5.4 Phase 3 — LSF 适配器(+1 月)
|
||
|
||
- LSF adapter:先用 CLI 解析(`bjobs -o "id stat queue slots" -noheader`),需要时加 Python Sidecar
|
||
- 目标:LSF 用户迁移到自研调度器时体验零摩擦
|
||
|
||
### 5.5 Phase 4 — Slurm + K8s 适配器(+1 月)
|
||
|
||
- Slurm adapter:slurmrestd REST API + CLI 回退
|
||
- K8s adapter:client-go
|
||
- 市场叙事:"Any cluster, natural language"
|
||
|
||
---
|
||
|
||
## 6. 代码仓库结构
|
||
|
||
### 6.1 目标结构(scheduler-agent 全量)
|
||
|
||
```
|
||
scheduler-agent/
|
||
├── cmd/
|
||
│ └── mcp-server/ # MCP Server 入口
|
||
├── internal/
|
||
│ ├── server/ # MCP Server 核心
|
||
│ │ ├── server.go # JSON-RPC 处理
|
||
│ │ ├── tools.go # Tool 定义注册
|
||
│ │ └── session.go # Agent session 状态管理
|
||
│ ├── urm/ # 统一资源模型
|
||
│ │ ├── job.go
|
||
│ │ ├── queue.go
|
||
│ │ └── node.go
|
||
│ └── adapters/ # 后端适配器
|
||
│ ├── native/ # 自研调度器
|
||
│ │ └── adapter.go
|
||
│ ├── lsf/ # LSF
|
||
│ │ ├── adapter.go
|
||
│ │ └── parser.go # CLI 输出解析
|
||
│ ├── slurm/ # Slurm
|
||
│ │ ├── adapter.go
|
||
│ │ └── parser.go
|
||
│ └── k8s/ # Kubernetes
|
||
│ └── adapter.go
|
||
├── test/
|
||
│ └── golden/ # Golden test
|
||
```
|
||
|
||
### 6.2 当前落地位置(myxce 仓库内)
|
||
|
||
```
|
||
myxce/src/frontend/
|
||
├── apiserver/ # Go REST API server(cgo 桥接 C backend)
|
||
│ └── config/rbac.yaml # 含 mcp_readonly(Phase 0)+ mcp_operator(Phase 1)角色
|
||
├── xcecli/ # Go CLI 工具
|
||
├── schedulerclient/ # ← Phase 0-1 ✅ scheduler-agent Go client(读 + 写)
|
||
│ ├── client.go # Client interface(6 只读 + 4 写方法)
|
||
│ ├── types.go # URMJob / URMQueue / URMNode
|
||
│ ├── statusmap.go # 状态映射(含单测)
|
||
│ ├── httpclient.go # resty HTTP 实现(GET)
|
||
│ └── write.go # ← Phase 1 写接口:JobSpec / SubmitJobResult / doPost /
|
||
│ # SubmitJob / CancelJob / HoldJob / ReleaseJob(含单测)
|
||
└── mcp-server/ # ← Phase 0-1 ✅ MCP Server 二进制
|
||
├── cmd/main.go # 入口,注册 10 个 MCP Tool(6 只读 + 4 写),stdio server
|
||
└── internal/
|
||
├── app.go # Tool handlers 连接 schedulerclient(读 + 写)
|
||
└── templates.go # ← Phase 1 作业模板 registry + general_serial(含单测)
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 测试策略
|
||
|
||
测试是商业化的真正护城河。模型不重要,**验证套件才重要**。
|
||
|
||
### 7.1 测试分层
|
||
|
||
```
|
||
test/
|
||
├── backends/
|
||
│ ├── native/ # 自研调度器 mock
|
||
│ ├── lsf/ # 需要 LSF 测试集群
|
||
│ ├── slurm/ # 需要 Slurm 测试集群
|
||
│ └── k8s/ # kind/minikube
|
||
├── scenarios/
|
||
│ ├── simple_submit.yaml # "帮我跑这个脚本"
|
||
│ ├── job_dependency.yaml # "按 A→B→C 顺序提交"
|
||
│ ├── job_array.yaml # "做 100 个参数的扫描"
|
||
│ ├── failure_diagnosis.yaml # "为什么失败了"
|
||
│ ├── multi_step_pipeline.yaml # "提交→监控→分析→重试"
|
||
│ └── cross_platform.yaml # 同场景在不同后端的表现对比
|
||
├── golden/
|
||
│ └── expected_tool_calls/ # 每个 scenario 期望的 tool call 链
|
||
└── integration/
|
||
└── agent_e2e_test.go # 端到端:自然语言输入 → 验收结果
|
||
```
|
||
|
||
### 7.2 Golden Test 回归
|
||
|
||
每次 adapter 改动用 golden test 回归——**同样的自然语言输入,产生同样的 tool call 序列**。
|
||
|
||
---
|
||
|
||
## 8. 设计原则
|
||
|
||
### 8.1 Narrow First(先窄后宽)
|
||
|
||
先吃透自研调度器,再扩展。自研调度器适配器的体验必须显著优于第三方适配器,形成竞争壁垒。
|
||
|
||
### 8.2 功能 vs 兼容的取舍
|
||
|
||
URM 不做最小公分母。核心字段通用,扩展字段用 `extensions` 兜底。LLM prompt 动态注入后端能力文档。
|
||
- 通用能力:所有后端一致
|
||
- 专有能力:extensions 暴露,模型自动识别是否可用
|
||
|
||
### 8.3 Write-Protected by Default
|
||
|
||
所有写操作默认 require confirm。Audit Log 是产品的一等公民,不是后加的功能。
|
||
|
||
### 8.4 LLM 是路由器,不是执行器
|
||
|
||
LLM 负责意图识别、规划、纠错。调度器交互由 adapter 的确定性代码执行。
|
||
- 调度器升级只需更新 adapter,不需调优模型
|
||
- 错误路径清晰:LLM 幻觉不会传递到调度器操作层
|
||
|
||
### 8.5 渐进的上下文窗口管理
|
||
|
||
Agent loop 跨多轮调度器轮次保持上下文:
|
||
- 作业 <= 60s:一步到位(提交→等待→返回)
|
||
- 作业 > 60s:事件流异步通知,状态持久化,Agent 通过 `GetJob()` 恢复
|
||
- 上下文满:auto-compact 历史轮次,保留最新状态摘要
|
||
|
||
---
|
||
|
||
## 9. 决策记录(ADR)
|
||
|
||
### 9.1 ADR-001:mcp-server 用服务账号 API Key 鉴权(已确认)
|
||
|
||
- **状态**:✅ 已实施(2026-07-26)
|
||
- **决策**:mcp-server 用服务账号 API Key(`xce_ak_` 前缀)调用 apiserver,而非透传终端用户 JWT
|
||
- **理由**:API Key 面向长期运行的自动化客户端,源 IP 不一定稳定,不做 IP 绑定
|
||
- **实现**:`store/apikey.go` + `models/apikey.go` + `controllers/auth.go` + `middleware/validatehandler.go` + `router/routers.go` + `config/rbac.yaml`
|
||
|
||
### 9.2 ADR-002:schedulerclient 放在 myxce 仓库内(已确认)
|
||
|
||
- **状态**:✅ 已实施
|
||
- **决策**:代码放在 `myxce/` 仓库内新增目录(与 `apiserver`/`xcecli` 同级),不另起独立仓库
|
||
|
||
### 9.3 ADR-003:Native Adapter 走 apiserver REST API(已确认)
|
||
|
||
- **状态**:✅ 已确认(2026-07-26)
|
||
- **决策**:mcp-server 调用 apiserver 现成 REST API,不是进程内 import Go package
|
||
- **理由**:myxce 调度内核是 C,Go 只存在于 apiserver(cgo 桥接)和 xcecli。直接 cgo 链接 `libxce` 绕开 apiserver 复杂度接近重写一次 apiserver
|
||
|
||
### 9.4 ADR-004:事件流对外通道用 SSE(待实施)
|
||
|
||
- **状态**:🕐 设计完成,Phase 1 后实施
|
||
- **决策**:apiserver 加 SSE endpoint + EventBroker,复用已有 `NotificationHandler` 进程内事件
|
||
|
||
---
|
||
|
||
## 10. 当前进度状态(2026-08-01)
|
||
|
||
### 10.1 已完成
|
||
|
||
**Phase 0(只读链路)**:
|
||
|
||
| 任务 | 内容 | 状态 |
|
||
|------|------|------|
|
||
| Task #1 | 架构与设计文档 | ✅ |
|
||
| Task #2 | API Key 鉴权机制(TDD) | ✅ 全绿 |
|
||
| Task #3 | `schedulerclient` 只读客户端 | ✅ 33 单测 |
|
||
| Task #4 | `mcp-server` MCP 二进制 | ✅ 构建通过 |
|
||
|
||
**Phase 1(写操作链路,2026-08-01)**:
|
||
|
||
| 任务 | 内容 | 状态 |
|
||
|------|------|------|
|
||
| Task #5 | `schedulerclient` 写接口(Submit/Cancel/Hold/Release + JobSpec + doPost) | ✅ 12 单测 |
|
||
| Task #6 | `mcp-server` 4 个写工具 + `general_serial` 模板 + `dry_run` review 模式 | ✅ 14 单测 |
|
||
| Task #7 | 修复 6 个只读工具注解(原被默认误标 destructive) | ✅ |
|
||
| Task #8 | `mcp_operator` RBAC 角色(`queue:submit` + `job:control`) | ✅ 测试全绿 |
|
||
|
||
**Phase 0-1 完整链路(代码层面就绪)**:
|
||
|
||
```
|
||
用户自然语言 → Claude Code → MCP stdio → mcp-server → schedulerclient → apiserver REST API → 调度器
|
||
(读:6 只读工具 + 写:4 写工具)
|
||
```
|
||
|
||
### 10.2 待办(阻塞项)
|
||
|
||
| # | 阻塞项 | 状态 | 责任方 |
|
||
|---|--------|------|--------|
|
||
| 1 | 运行中的 apiserver + C 编译环境 | ✅ t1 单节点 controller + apiserver + scheduler + worker 已启动(2026-08-01) | 已完成 |
|
||
| 2 | 生成 API Key | ✅ `mcp_operator` key 已生成并验证读 + submit | 已完成 |
|
||
| 3 | 配置 Claude Code MCP | 🚫 拿到集群 API 后配置 | 用户 |
|
||
| 4 | Phase 1 端到端验证:提交→列表→hold→release→cancel | ⚠️ 部分完成:读 + submit ✅;**hold/release/cancel 返回 HTTP 500**(apiserver control 链路预存 bug,见 bug-1490) | 待修 apiserver |
|
||
|
||
### 10.2b 本次集群验证发现并修复的 apiserver bug(2026-08-01)
|
||
|
||
| Bug | 现象 | 修复 |
|
||
|-----|------|------|
|
||
| whitelist 缺 `/xce/v1/apikeys` | API Key 生成端点 403 "URL is not allowed" | `middleware/whitelist.go` 加 `/xce/v1/apikeys`(bug-1489,已修) |
|
||
| control 的 `job_status` int64 断言 | control 返回 "Invalid job_status" | `controllers/jobs.go` 加 `normalizeJobStatus()` 兼容 int/int64/float64(已修) |
|
||
| control 链路 500(未修) | hold/release/cancel 返回 500,请求未达 controller | 待排查(bug-1490) |
|
||
| 读接口 OrderMap 解析(已修) | `scheduler_list_nodes/queues/jobs` 实体数量对但字段全空 | `httpclient.go` 用 `apiRow` 解析列式格式(bug-1491,已修 + 真实验证) |
|
||
| 作业全部 PEND(未修) | 提交的作业从不被调度(scheduler 日志 0 job 行;新作业也 PEND) | 已补 resources.yaml 缺失的 worker 负载指标 + 重建 cluster.bin;剩余疑 scheduler→controller join 链路(bug-1494) |
|
||
|
||
### 10.3 未决决策项
|
||
|
||
- [ ] 事件流接口(`SubscribeJobEvents`)对外通道设计 —— 已验证前提成立,待 Phase 2 实施
|
||
- [ ] LLM 选型:Claude API / 自部署本地模型 / 混合策略
|
||
- [ ] 多集群场景:URM 如何标识作业来源(集群 ID 字段)
|
||
- [ ] on-prem vs cloud:客户私有化部署时能否访问 LLM API
|
||
|
||
---
|
||
|
||
## 11. 风险与缓解
|
||
|
||
| 风险 | 影响 | 缓解 |
|
||
|------|------|------|
|
||
| LLM 幻觉导致错误操作 | 作业丢失/资源浪费 | 写操作 require confirm;LLM 只做路由不做执行 |
|
||
| 自研调度器事件流未打通 | Phase 2 体验差 | 已确认进程内事件存在,SSE 通道工作量 2-3 天 |
|
||
| LSF/Slurm 轮询延迟 | 体验代差 | 通过 agent 工作流 + 模板缓解 |
|
||
| 上下文窗口膨胀 | 长任务监控失效 | auto-compact + 状态摘要策略 |
|
||
|
||
---
|
||
|
||
## 12. 附录
|
||
|
||
### 12.1 相关文档索引
|
||
|
||
| 文档 | 位置 | 说明 |
|
||
|------|------|------|
|
||
| 本文档 | `doc/Scheduler-Agent-设计文档.md` | 设计权威来源 |
|
||
| 集群部署与验证手册 | `src/docs/chatbot-verification.md` | t1/t2/t3 集群启动 + Phase 0-1 验证步骤(2026-08-01) |
|
||
| CLAUDE.md(根目录) | `CLAUDE.md` | AI 实施向导(含最新进度) |
|
||
| 架构图源文件 | `architecture_overview` | Graphviz DOT |
|
||
| 架构图渲染 | `architecture_overview.svg` | 可视化架构图 |
|
||
| 架构图生成脚本 | `generate_diagram.py` | diagrams 库 |
|
||
| myxce 产品文档 | `myxce/.kiro/steering/` | 产品/技术/结构/安全等 |
|
||
| feature 规格 | `myxce/.kiro/specs/` | 各功能 spec |
|
||
|
||
### 12.2 关键实现细节备忘
|
||
|
||
- **认证**:apiserver 的 `GetUsernameFromToken`/`isValidToken` 已支持 `xce_ak_` 前缀,JWT 和 API Key 透明共存(27 处调用点零改动)
|
||
- **schedulerclient 内存单位**:假设 apiserver 返回 MB(HPC 惯例),转 bytes 存入 URM(`mbToBytes = 1_000_000` decimal)
|
||
- **schedulerclient 认证**:Authorization header 直接传 token(不拼 Bearer,服务端会 TrimPrefix)
|
||
- **mcp-server 依赖**:`github.com/mark3labs/mcp-go@v0.57.0`,`replace schedulerclient => ../schedulerclient`
|
||
- **写端点 HTTP 状态陷阱(Phase 1)**:`ReturnSuccess` 恒返回 HTTP 200,逻辑状态在 envelope `code` 字段(CreateJob→201,ControlJob→202);只有 `ReturnError` 才把 HTTP 状态设为错误码。`doPost` 检查 `resp.StatusCode()==200` 再 `env.Code==expectedCode`,**不要**要求 HTTP 201/202
|
||
- **`run_limit` 分钟陷阱(Phase 1)**:apiserver `CustomInt` 解析裸整数为**分钟**(`minutes*60`),`"HH:MM"` 才是秒。schedulerclient 只发 `"HH:MM"` 字符串;模板 `parseWalltimeSeconds` 拒绝裸整数避免歧义
|
||
- **`mem_limit` wire 格式(Phase 1)**:`CustomLimit` 只接受 `"<number> <unit>"` 字符串(`"4096 MB"`),GB=1024MB 二进制;客户端通过 `formatMemory` 发 MB 字符串,模板解析用 decimal 与 `mbToBytes` 保持一致
|
||
- **confirm 无原生支持(Phase 1)**:mcp-go v0.57.0 无 `RequiresConfirmation`,仅 `ToolAnnotation` 且 `NewTool` 默认 `destructive=true`(Phase 0 只读工具因此被误标,已在 Phase 1 修复);写操作 confirm 用 `dry_run` review 参数 + destructive 注解实现
|