Kaynağa Gözat

docs: 手册补任务状态语义(Hermes turn 模型实证)

用户反馈:当前任务显示英文、没任务也显示"正在执行"。

准确性核实(全部准确,无需改):
- Token: input+output=total,差值 0
- CPU: top 真实占用率,与其他容器同量级
- 内存: 51.3% 正常

根因(容器自证 + 代码级):
容器脚本 build_values() 硬编码 "status":"running"、
"currentAction":"reporter heartbeat @ %s",全容器无任何人写
AGENT_CURRENT_TASK env → 三个字段全是上报器自身活动

关键认知(手册新增 §三):
Hermes 无独立"任务"对象,任务 = 一个 turn。
权威信号 state.db sessions 表:
  last_activity_at 保留 / last_activity_description turn 结束时清空
源码:activity_tracking.py:120-138 reset_activity_labels_after_turn()
上游自己就靠清空 description 表达"不忙了"

正确取值:
  status 从 DB 现场判定(desc 空 → idle,不是 running)
  currentTask = "处理{渠道}用户对话"
  currentAction = last_activity_description(上游原话,已是中文)
  progress 不填(无可靠数据,编数=伪造)

实测:15:22 running → 15:26 idle,正确翻转。

App 侧无需改动:AGENT_STATUS_TEXT 已含 idle → 空闲。

验证:verify:docs 49 文件

Co-Authored-By: Claude Code <noreply@anthropic.com>
Huccct 1 hafta önce
ebeveyn
işleme
c519c469ac

+ 16 - 0
docs/00-tracking/操作追踪.md

@@ -901,3 +901,19 @@
 | **✏️ 手册补「上线前自检」章节** | 《容器接入上线手册》新增 §一:**自检脚本(只读)** + 自检结果 → 四条分流路径。<br>核心原则「先自检 → 按结果选路径 → 才动手」,并标注最易误判的第 3 种情况(无进程但数据在跳 = 写入方在别处,别装第二份)|
 | 验证证据 | `ng test` **510**(+7 用例)/ `build` 通过 / `verify:ui` **11/11** / `verify:docs` **49 文件** |
 | 未做(需容器侧) | `currentTask` 报英文属容器数据问题,App 不伪造翻译 |
+
+### 2026-10-20 — 容器任务状态语义修正(Hermes turn 模型实证)+ 手册补 §三
+
+| 项 | 内容 |
+|---|---|
+| **用户反馈** | ① 当前任务显示英文 `periodic status reporter` ② Token/内存/CPU/成果是否准确 ③ 没任务也显示「正在执行」④ 先了解容器底层(Hermes)逻辑 |
+| **✅ 准确性核实(数据自洽性)** | · **Token**:`input(1856406) + output(381649) = total(2238055)`,**差值 0**,每会话均 37 万、每次调用 4596 → **准确**<br>· **CPU**:`cpuPercent=5.4`(`cpuPercentSource:"top"` 真实占用率),与其他 8 个活跃容器同量级(2.5%~9.3%)→ **准确**<br>· **内存**:51.3% 正常<br>· **成果**:容器 `15:16` 首次真实上报(`Qtd1VHvLHV`),此前只有 1 条 probe 测试记录 |
+| **🔴 根因(容器自证 + 代码级)** | 容器脚本 `build_values()`:<br>`"status": "running"` — **硬编码 L558**<br>`"currentAction": "reporter heartbeat @ %s"` — **硬编码 L559**<br>全容器 grep `AGENT_CURRENT_TASK` / `AGENT_STATUS_STATE` → **0 命中**(无人写 env)<br>→ 三个字段全在上报器自身活动,与业务无关 |
+| **🆕 Hermes 任务模型(关键认知,已写入手册 §三)** | **Hermes 没有独立"任务"对象,任务 = 一个 turn(回合)**<br>权威信号在 `state.db` 的 `sessions` 表:`last_activity_at`(保留)+ `last_activity_description`(**turn 结束时清空为 ""**)<br>源码依据:`/opt/hermes/agent/activity_tracking.py:120-138` `reset_activity_labels_after_turn()`<br>**上游自己就是靠「清空 description」表达「不忙了」** |
+| **正确取值** | `status` 从 DB 现场判定(desc 非空且 <180s → running;desc 为空 → **idle**;DB 不可读 → error)<br>`currentTask = "处理{渠道}用户对话"`(从 session_key 解析)<br>`currentAction = last_activity_description`(上游原话,本身就是中文)<br>`progress` **不填**(Hermes 只有 `iteration 3/50` 这种,不是用户可读进度,编数=伪造)|
+| **实测验证** | 15:22 生成回复时:`status=running / currentTask="处理企业微信用户对话" / currentAction="内容还在往外吐,接住中。"`<br>15:26 turn 结束后:**`status=idle`、`currentTask=""`、`currentAction=""`** ✅ 正确翻转 |
+| **App 侧核实** | 无需改动:`AGENT_STATUS_TEXT` 已含 `idle → 空闲`,状态徽标统一走 `agentStatusText()` |
+| **✏️ 手册新增 §三「任务状态字段怎么填」** | 语义定义表 / Hermes turn 模型(附源码)/ 正确取值代码 / 实测验证 / **禁止做法黑名单**(硬编码 running、填上报器活动、编 progress)|
+| **✏️ 检查表补两项** | 「status 处理器完后翻 idle」「currentTask 是业务任务而非上报器活动」|
+| 验证证据 | `verify:docs` **49 文件**通过 |
+

+ 102 - 7
docs/03-interface/容器接入上线手册-20261020.md

@@ -203,8 +203,100 @@ sqlite3 <hermes>/executions.db "SELECT * FROM executions ORDER BY started_at DES
 
 ---
 
-## 三、验收清单(9 条,缺一不可)
+## 三、任务状态字段怎么填(Hermes 容器必读)
 
+> 🔴 这一节解决的是:**「当前任务」和「容器状态」该从哪来。**
+> 填错会让用户看到"永远正在执行",或看到上报器自己的活动被当成业务任务。
+
+### 3.1 语义定义(先分清两件事)
+
+| 字段 | 语义 | ❌ 常见错误 |
+|---|---|---|
+| `status` | 容器**有没有在为用户干活** | 硬编码 `running`,永远显示"正在执行" |
+| `currentTask` | **为用户**正在执行的任务 | 填上报器自己的活动(`periodic status reporter`)|
+| `currentAction` | 该任务的具体动作 | 填 `reporter heartbeat @ <时间>` |
+| `progress` | 任务进度 0~100 | 没有真实数据时编一个数 |
+
+**判据**:这三个字段描述的是**用户的业务**,不是**上报进程自己**。
+
+### 3.2 Hermes 的任务模型(代码级实证)
+
+**Hermes 没有独立"任务"对象,任务 = 一个 turn(回合)。**
+
+权威信号在 `state.db` 的 `sessions` 表:
+
+| 字段 | 语义 | turn 结束时 |
+|---|---|---|
+| `last_activity_at` | 最近活动时间戳 | 保留(时钟连续)|
+| `last_activity_description` | 当前活动描述(**中文**)| **清空为 `""`** |
+
+源码依据:`/opt/hermes/agent/activity_tracking.py:120-138`
+
+```python
+def reset_activity_labels_after_turn(self):
+    """Drop mid-turn activity labels once the turn is no longer running.
+    Keeps last_activity_ts so idle/watchdog clocks stay continuous across turns;
+    clears description + provenance so idle agents / SessionDB listings
+    stop advertising the last mid-turn stamp."""
+    self.last_activity_desc = ""
+    self.last_activity_provenance = ActivityProvenance.UNKNOWN
+```
+
+**上游自己就是靠「清空 description」来表达「不忙了」。**
+
+### 3.3 正确取值方式
+
+```python
+# status:从 state.db 现场判定,不读 env、不硬编码
+desc = sessions.last_activity_description
+age  = now - sessions.last_activity_at
+
+if desc and age < 180:      # 有描述且新鲜 → 在干活
+    status = "running"
+elif desc == "":            # 已被上游清空 → 不忙了
+    status = "idle"
+else:                       # DB 不可读 → 诚实报错,不伪造
+    status = "error"
+
+# currentTask:从 session_key 解析渠道,不硬编码
+currentTask = "处理{渠道}用户对话"        # 如「处理企业微信用户对话」
+
+# currentAction:直接取上游原话,不加工
+currentAction = sessions.last_activity_description   # 本身就是中文
+
+# progress:没有可靠数据 → 不填
+#   Hermes 的 format_iteration_progress() 给的是 "iteration 3/50",
+#   不是用户可读的进度百分比 → 编一个数等于伪造
+```
+
+**窗口为什么取 180s**:上游心跳最小间隔 60s,长回合中间可能无写入。
+宁短时误报 `running`,也不把正在跑的报成 `idle`。
+
+### 3.4 实测验证(2026-10-20 15:22~15:26)
+
+| 时刻 | 场景 | status | currentTask |
+|---|---|---|---|
+| 15:22 | 容器正在生成回复 | `running` | `处理企业微信用户对话` |
+| 15:26 | 回复结束、turn 关闭 | **`idle`** | `""`(已清空)|
+
+✅ 正确翻转,与 `reset_activity_labels_after_turn()` 的行为一致。
+
+### 3.5 🔴 禁止的做法
+
+| 做法 | 为什么不行 |
+|---|---|
+| 硬编码 `status = "running"` | 永远显示"正在执行",用户无法判断是否空闲 |
+| 把上报器自己的活动写进 `currentTask` | 用户看到的是监控程序在跑,不是他的任务 |
+| 让上报器往 `AGENT_CURRENT_TASK` env 里写 | 等于伪造业务状态;且原版脚本是**读**这个 env,不是写 |
+| 编一个 `progress` 数字 | 进度必须来自真实数据源 |
+
+> **实测**:某容器曾上报 `currentTask = "periodic status reporter (xlosi3m46q)"`、
+> `currentAction = "reporter heartbeat @ ..."` —— 三个字段全是上报器自己的活动,
+> 用户看到"正在执行"但实际什么都没做。
+
+---
+
+## 四、验收清单(9 条,缺一不可)
 ```bash
 curl -s 'https://server.fmode.cn/parse/classes/FmodeAgent?where={"agentId":"<容器名>"}' \
   -H 'X-Parse-Application-Id: ncloudmaster'
@@ -224,7 +316,7 @@ curl -s 'https://server.fmode.cn/parse/classes/FmodeAgent?where={"agentId":"<容
 
 ---
 
-## 四、🔴 四条铁律(本轮全部踩过)
+## 五、🔴 四条铁律(本轮全部踩过)
 
 ### 铁律 1 · 查运行态,不查路径
 
@@ -291,7 +383,7 @@ lastInboundAgeSecs  = 业务活跃度(只展示,不参与健康判断)
 
 ---
 
-## 五、故障速查表
+## 六、故障速查表
 
 ### 4.1 上报返回 `agent not found` / 数据不落库
 
@@ -370,7 +462,7 @@ grep -iE "inbound message" /opt/data/logs/gateway.log | tail -10
 
 ---
 
-## 六、给容器的标准操作提示词
+## 七、给容器的标准操作提示词
 
 ```markdown
 【容器状态上报 · 标准接入】
@@ -422,7 +514,7 @@ heartbeatAt=<回读> usage.totalTokens=<回读> balance.remainCny=<回读>
 
 ---
 
-## 七、上线检查表(复制给执行人)
+## 八、上线检查表(复制给执行人)
 
 ```markdown
 □ 存活三查(ps / proc / 平台回读)已做
@@ -432,14 +524,17 @@ heartbeatAt=<回读> usage.totalTokens=<回读> balance.remainCny=<回读>
 □ 上报走 xlosi3m46q(返回不是 "key":"user/.../log/...json")
 □ 常驻用 cron 或 s6(不是 while true)
 □ 平台回读 updatedAt 在 5 分钟内
-□ 9 条验收断言全过
+□ 9 条验收断言全过(§四)
+□ 任务状态语义正确(§三)
 □ 机器人在 App 会话里能实际回复消息
+□ status 在消息处理完后从 running 翻成 idle(不是恒 running)
+□ currentTask 是业务任务而非上报器自身活动
 □ 重启 gateway 后上报能自动恢复
 ```
 
 ---
 
-## 八、关联文档
+## 九、关联文档
 
 - `docs/03-interface/reporter字段规范-20261020.md` —— 逐字段契约(改脚本时查)
 - `docs/03-interface/容器初始化流程优化建议-20261020.md` —— workshop 流程优化 + 方法论