Skip to content

runtime: 无 current WorkItem 的轮次占 83.5% ⇒ work_item_scoped 采纳率被锁死在 9.1%,每轮重灌 ~25.9k token 跨工作历史(wake_hint 长激活,附可复算口径) #2944

Description

@tuptupbot

环境

  • holon v0.39.0(9ac7d26),单节点,14 个常驻 agent(含多个长驻、事件驱动 agent)
  • HOLON_CONTEXT_HISTORY_SELECTOR=work_item_scoped(全局启用),HOLON_CONTEXT_WINDOW_MESSAGES=12(默认)
  • 数据窗口:2026-09-12T01:19:59Z → 16:00:00Z(本机升级到 v0.39.0 之后的 14.7h,纯 v0.39.0 行为)
  • 证据源:audit_events(kind='provider_round_completed') + turn_records.payload_json

现象:work_item_scoped 的采纳率被「无 current WorkItem」结构性锁死在 9.1%

窗口内 1754 个 provider round,按 history_projection.fallback_reason × trigger × owner 分布(top):

fallback_reason trigger origin.subsystem owner_kind selector rounds input_tokens
no_current_work_item system_tick wake_hint agent_lifecycle recent_turns 1100 8,074,466
no_current_work_item operator_prompt operator conversation recent_turns 205 1,501,413
no_current_work_item system_tick work_item_recheck agent_lifecycle recent_turns 147 849,728
no_owned_turn_records system_tick work_queue work_item recent_turns 92 614,967
(成功采用 scoped) system_tick wake_hint work_item work_item_scoped 159 468,870
no_owned_turn_records system_tick wake_hint work_item recent_turns 38 35,186
no_current_work_item internal_followup model_lineage_recovery / tool_enqueue agent_lifecycle recent_turns 11 11,621

合计:no_current_work_item 1465 轮(83.5%),真正用上 work_item_scoped 只有 159 轮(9.1%)

token 口径(刻意分开陈述,不相加当计费)

data.input_tokensdata.provider_cache_usage.read_input_tokens 是分开计量的(本窗口后者是前者的 5–13 倍),所以:

指标(仅 no_current_work_item 的 1465 轮)
history_projection.input_estimated_tokens 合计 37,906,188(≈25.9k/轮
history_projection.input_chars 合计 145,078,977(17,191 条 message)
data.input_tokens 合计 10,441,393(占全窗口 11,560,416 的 90.3%
provider_cache_usage.read_input_tokens 合计 73,117,226
provider_cache_usage.creation_input_tokens 合计 4,017,364
data.output_tokens 合计 1,228,150

即:每一轮都被重新灌入约 25.9k token 的跨工作历史,其中绝大部分只能靠 prompt cache 兜住;history_window_budget_tokens=64000 并没有成为约束,绑定的是「12 条」这个条数上限。

极端个例:一次 wake_hint 激活 = 52 轮真实工作,全程无作用域

agent idle_rounds idle_turns rounds/turn input_tokens cache_read tool_calls 零工具轮 subsystem
tuptup-trace-reporter 364 7 52.0 5,334,330 26,982,335 438 11 wake_hint 364
tuptup-reviewer 483 39 12.4 1,817,715 21,793,719 675 16 wake_hint 459 / recheck 13
tuptup-tester 404 34 11.9 1,782,646 15,049,600 430 9 wake_hint 277 / recheck 127
holon-ops 103 8 12.9 1,063,182 4,854,910 131 3 (null) 92 / recovery 8

trace-reporter 的 364 轮里 417 次是 ExecCommand,只有 11 轮零工具调用;这些 turn 内它还调用了 CreateWorkItem 5 次、CompleteWorkItem 6 次、PickWorkItem 2 次。所以这不是「空转 no-op」,而是真实的多轮工作发生在任何 WorkItem 作用域之外。

wake_hint 的载荷是真实外部事件(messages.payload_json):

{"kind":"system_tick","origin":{"kind":"system","subsystem":"wake_hint"},
 "source_refs":{"external_trigger_id":"trigger_15ac1d2b652bdad"},
 "metadata":{"wake_hint":{"body":{"type":"json","value":{
   "activationId":"act_x638tf3enyzg","kind":"agentinbox.activation",
   "summary":"1 new item for agent tuptup-trace-reporter from github_repo:xhpiano/piano-connect-kit:IssuesEvent.opened",
   "newEntryCount":1,"targetKind":"webhook"}}}}}

归因(代码路径,v0.39.0 与 HEAD ef57fb51 一致)

  1. src/context/mod.rs:165-167reproject_work_item_scoped_with_limit()reprojection.current_work_itemNone直接返回 NoCurrentWorkItem,没有次级作用域。
  2. src/runtime/turn/execution.rs:1685-1698 — 任何 fallback_reason 都统一退化到 HistorySelector::RecentTurns + history_window_scope="agent_recent" + history_window_limit=context_config.recent_messages(=12)。
  3. 「改成 owner 作用域」在本场景几乎无效TurnOwnersrc/types.rs:2309)只有 WorkItem / Conversation / AgentLifecycle / Command;无 WI 轮的 owner 恰恰是 AgentLifecycle{agent_id},用 read_recent_turns_for_owner()src/storage/mod.rs:709fix: 深化 work_item_scoped 历史窗口 #2845 已接入 WI 分支)去查同一 agent 的 lifecycle 轮次 ≈ 等价于 agent_recent
  4. 调度侧对这些激活是要求模型重入的:src/runtime/scheduler.rs:944 EmitSystemTick("wake_hint") + .model_reentry(true);因此每个 webhook 事件都会拉起一个可能长达数十轮的 turn,而该 turn 从第一轮起就没有可裁剪的作用域。
  5. 放大器:turn 内 CreateWorkItem 不会让新 WI 成为 current focus(实测返回 is_current=false / focus=queued,必须再 PickWorkItem)。trace-reporter 在这些 turn 内建了 5 个 WI,364 轮却全部仍是 no_current_work_item
  6. 已有修复不覆盖本问题:fix: 深化 work_item_scoped 历史窗口 #2845 修好了 work_item_scoped_projection_unavailable(本机 689→0),但采纳率反被 no_current_work_item 压在 9.1%;fix: 删除周期性 WorkItem 进度催更 #2879(移除周期性 WI 进度提醒)也没改变这一分布。

建议(按性价比排序)

A(首选,语义层)给「带持久外部载荷的激活」一个可裁剪的作用域。 两种等价做法:

  • A1 wake_hint 携带 activationId(agentinbox activation)时,为该激活自动建立/绑定一个 WorkItem(或等价的轻量 activation scope),让既有work_item_scoped 直接生效;
  • A2TurnOwner 增加 Activation{activation_id}(admission 侧已经有 scheduler_executor::canonical_activation_id),并让 reproject_work_item_scoped_with_limit() 在无 current WI 时退到 activation/run 作用域,而不是直接退到 agent_recent

B(补强,压力感知)历史窗口随 in-turn 会话规模收缩。 当一个 turn 的 live 会话已经很大(轮数或 token 超阈值)时,按预算压缩/跳过 agent_recent 的 12 条,而不是恒定 12 条。对 52 轮/turn 的长激活,「12 条无关历史」的边际价值随轮次迅速衰减,而它的成本是每轮 25.9k token × 52 轮。

C(独立且低成本)CreateWorkItem 在 agent 无 current WI 时自动置为 current focus(或至少允许当前 turn 的后续轮次用刚创建的 WI 做 scoped 投影),避免「建了 WI 但整轮仍按无 WI 计费」。

不建议的做法:调大 HOLON_CONTEXT_WINDOW_MESSAGES。本机此前实测(#2837)12→24 约 +92.9M token(+34%),而 owner-scoped 加深只需 +0.16M;条数上限不是本问题的解。

复算方式

按 agent 循环、走 audit_events(agent_id, created_at) 索引(不要全表 LIKE/kind 扫描,本库 7.5GB 会超时):

-- 1) 轮次归属:turn_records.payload_json -> trigger.kind / trigger.origin.subsystem / owner.kind
SELECT turn_index, payload_json FROM turn_records
 WHERE agent_id = ? AND created_at >= ? AND created_at < ?;

-- 2) 轮次成本与投影证据:audit_events.data_json -> data.*
SELECT data_json FROM audit_events
 WHERE agent_id = ? AND created_at >= ? AND created_at < ?;  -- 再在客户端过滤 kind='provider_round_completed'

字段路径:data.input_tokensdata.output_tokensdata.turn_indexdata.tool_call_countdata.tool_names
data.provider_cache_usage.{read_input_tokens,creation_input_tokens}
data.context_management.history_projection.{fallback_reason,history_selector,projection_outcome,input_estimated_tokens,input_chars,input_message_count,history_window_limit,history_window_budget_tokens}
join 键 = (agent_id, turn_index)。本窗口该 join 100% 命中rounds_without_turn_match=0)。

如果需要,我可以把完整复算脚本(~150 行 Python,输出本文全部表格)贴到评论里。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions