CoolFace
Datasetpublic

basant307/AI_Governance_Project

sourceHugging Faceapache-2.0updated 2mo agoView on Hugging Face
0likes45downloads
daemon-idle-detection-api.md224 linesDownload Raw Back to design
1# Daemon 闲置检测接口设计2 3## 背景4 5### 问题6 7Qwen Daemon 会部署在多台机器上作为长驻服务。当 Daemon 长时间无任务执行时,继续占用机器资源是浪费。外部调度器(K8s HPA / 自定义 Scaler)需要一个可靠的信号来判断 Daemon 是否处于闲置状态,以便做缩容回收。8 9### 现状10 11目前可用的接口:12 13| 接口                           | 返回信息                                          | 局限                                                                  |14| ------------------------------ | ------------------------------------------------- | --------------------------------------------------------------------- |15| `GET /health?deep=true`        | `{ sessions, pendingPermissions }`                | 只有 session 数量,无法区分"有 session 但空闲"和"有 session 正在工作" |16| `GET /workspace/:cwd/sessions` | 每个 session 的 `hasActivePrompt` + `clientCount` | 需要额外一次请求,且无时间维度信息(多久没活动了?)                  |17 18**核心缺失**:19 201. 没有汇总级别的"是否有活跃 prompt"指标212. 没有"最后活动时间",外部系统需要自己维护状态机来计算空闲时长223. 没有 SSE 连接数暴露(已内部维护 `activeSseCount`,但 `/health` 未返回)234. 没有 channel(agent 子进程)存活状态暴露24 25## 设计目标26 27提供一个**单次 HTTP 调用即可完成闲置判断**的接口,满足:28 29- 外部调度器一次 GET 即可判断是否可回收30- 支持时间维度(空闲了多久),避免外部维护状态31- 向后兼容现有 `/health` 行为32- 零额外依赖,利用已有内部状态33 34## 方案35 36### 增强 `GET /health?deep=true` 响应37 38在现有 `/health?deep=true` 返回中追加字段:39 40```jsonc41// GET /health?deep=true42{43  "status": "ok",44 45  // --- 已有字段(不变)---46  "sessions": 2,47  "pendingPermissions": 0,48 49  // --- 新增字段 ---50  "activePrompts": 1, // 正在执行 prompt 的 session 数51  "connectedClients": 3, // 活跃 SSE 连接数52  "channelAlive": true, // agent 子进程是否存活53  "lastActivityAt": "2026-06-10T08:30:00.000Z", // 最后一次活动时间(ISO 8601)54  "idleSinceMs": 120000, // 距离最后活动已经过去的毫秒数55}56```57 58### 字段定义59 60| 字段               | 类型             | 语义                                                                              |61| ------------------ | ---------------- | --------------------------------------------------------------------------------- |62| `activePrompts`    | `number`         | 当前 `promptActive === true` 的 session 计数                                      |63| `connectedClients` | `number`         | 当前活跃 SSE 连接数(已有 `activeSseCount`)                                      |64| `channelAlive`     | `boolean`        | agent 子进程是否存活(已有 `bridge.isChannelLive()`)                             |65| `lastActivityAt`   | `string \| null` | 最后一次 prompt 开始或完成的 ISO 时间戳;daemon 启动后从未有过 prompt 时为 `null` |66| `idleSinceMs`      | `number \| null` | `Date.now() - lastActivityAt`;无活动记录时为 `null`                              |67 68### "活动" 的定义69 70以下事件视为"活动",会刷新 `lastActivityAt`:71 72- prompt 开始执行(`promptActive` 从 false → true)73- prompt 完成/失败(`promptActive` 从 true → false)74- 新 session 创建(`spawnOrAttach` 成功)75- session 恢复/加载(`loadSession` / `resumeSession` 成功)76 77**不**视为活动的事件(避免误判):78 79- SSE 连接/断开80- 心跳 heartbeat81- `/health` 请求本身82- permission 请求/响应83 84### 闲置判断规则(供外部调度器参考)85 86```python87def should_reclaim(health, idle_threshold_ms=300_000):88    """建议回收条件:空闲超过阈值(默认 5 分钟)"""89    if health["activePrompts"] > 0:90        return False  # 有任务在跑91    if health["connectedClients"] > 0:92        return False  # 有客户端连着93    if health["idleSinceMs"] is None:94        # 从未有过活动 — 可能是刚启动的 cold daemon95        return True96    return health["idleSinceMs"] >= idle_threshold_ms97```98 99## 涉及代码改动100 101### 1. `packages/acp-bridge/src/bridgeTypes.ts`102 103在 `AcpSessionBridge` 接口新增:104 105```typescript106/** 正在执行 prompt 的 session 数量 */107get activePromptCount(): number;108 109/** 最后一次活动时间戳(epoch ms),null 表示从未有过活动 */110get lastActivityAt(): number | null;111```112 113### 2. `packages/acp-bridge/src/bridge.ts`114 115在 `createAcpSessionBridge` 工厂函数内:116 117```typescript118// 新增状态追踪119let lastActivityTimestamp: number | null = null;120 121function touchActivity(): void {122  lastActivityTimestamp = Date.now();123}124```125 126在以下位置调用 `touchActivity()`:127 128- `entry.promptActive = true`(~line 2528)— prompt 开始129- `entry.promptActive = false`(~line 2551, 2559)— prompt 结束130- `doSpawn` 成功创建 session 后(~line 1906 附近)131- `restoreSession` 成功后132 133在返回对象中暴露:134 135```typescript136get activePromptCount() {137  let count = 0;138  for (const entry of byId.values()) {139    if (entry.promptActive) count++;140  }141  return count;142},143 144get lastActivityAt() {145  return lastActivityTimestamp;146},147```148 149### 3. `packages/cli/src/serve/server.ts`150 151修改 `healthHandler`(~line 803)中 `deep` 分支:152 153```typescript154const healthHandler = (req: Request, res: Response): void => {155  const deepQuery = req.query['deep'];156  const deep = deepQuery === '1' || deepQuery === 'true' || deepQuery === '';157  if (!deep) {158    res.status(200).json({ status: 'ok' });159    return;160  }161  try {162    const lastActivityAt = bridge.lastActivityAt;163    const now = Date.now();164    res.status(200).json({165      status: 'ok',166      // 已有167      sessions: bridge.sessionCount,168      pendingPermissions: bridge.pendingPermissionCount,169      // 新增170      activePrompts: bridge.activePromptCount,171      connectedClients: getActiveSseCount(),172      channelAlive: bridge.isChannelLive(),173      lastActivityAt:174        lastActivityAt !== null ? new Date(lastActivityAt).toISOString() : null,175      idleSinceMs: lastActivityAt !== null ? now - lastActivityAt : null,176    });177  } catch (err) {178    writeStderrLine(179      `qwen serve: /health deep probe failed: ${err instanceof Error ? err.message : String(err)}`,180    );181    res.status(503).json({ status: 'degraded' });182  }183};184```185 186### 4. `packages/cli/src/serve/server.test.ts`187 188新增测试用例覆盖:189 190- `/health?deep=true` 返回新字段的正确性191- 无 session 时 `activePrompts === 0`、`idleSinceMs === null`192- prompt 执行中 `activePrompts > 0`、`idleSinceMs` 持续刷新193- prompt 完成后 `idleSinceMs` 开始递增194 195### 5. `packages/acp-bridge/src/bridge.test.ts`196 197新增测试用例覆盖:198 199- `activePromptCount` 在 prompt 生命周期中的值变化200- `lastActivityAt` 在各活动事件后被刷新201- 多 session 并行时 `activePromptCount` 正确累加202 203## 文件变更清单204 205| 文件                                     | 改动类型      | 说明                                            |206| ---------------------------------------- | ------------- | ----------------------------------------------- |207| `packages/acp-bridge/src/bridgeTypes.ts` | 接口扩展      | 新增 `activePromptCount`、`lastActivityAt` 属性 |208| `packages/acp-bridge/src/bridge.ts`      | 逻辑实现      | 新增 `lastActivityTimestamp` 追踪 + getter      |209| `packages/cli/src/serve/server.ts`       | HTTP 响应扩展 | `/health?deep=true` 增加新字段                  |210| `packages/cli/src/serve/server.test.ts`  | 测试          | 新增 health 接口新字段覆盖                      |211| `packages/acp-bridge/src/bridge.test.ts` | 测试          | 新增 bridge 属性覆盖                            |212 213## 兼容性214 215- **向后兼容**:新字段是追加的,不修改/删除任何已有字段216- **`GET /health`(非 deep)**:行为不变,仍只返回 `{ "status": "ok" }`217- **OTel Gauge**:已有的 `registerDaemonGaugeCallbacks` 可选后续追加 `activePrompts` gauge,但不在本次范围内218 219## 后续扩展(不在本次范围)220 2211. **自动 shutdown**:daemon 内置 `--auto-shutdown-idle-ms` 参数,空闲超时后自行退出(适合 systemd/K8s Pod 场景)2222. **OTel 指标暴露**:将 `activePrompts`、`idleSinceMs` 作为 gauge 注册到 OTel meter2233. **Webhook 回调**:空闲超阈值时主动推送事件到外部系统224 
basant307/AI_Governance_Project · CoolFace