环境
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_tokens 与 data.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 一致)
src/context/mod.rs:165-167 — reproject_work_item_scoped_with_limit() 在 reprojection.current_work_item 为 None 时直接 返回 NoCurrentWorkItem,没有次级作用域。
src/runtime/turn/execution.rs:1685-1698 — 任何 fallback_reason 都统一退化到 HistorySelector::RecentTurns + history_window_scope="agent_recent" + history_window_limit=context_config.recent_messages(=12)。
「改成 owner 作用域」在本场景几乎无效 :TurnOwner(src/types.rs:2309)只有 WorkItem / Conversation / AgentLifecycle / Command;无 WI 轮的 owner 恰恰是 AgentLifecycle{agent_id},用 read_recent_turns_for_owner()(src/storage/mod.rs:709,fix: 深化 work_item_scoped 历史窗口 #2845 已接入 WI 分支)去查同一 agent 的 lifecycle 轮次 ≈ 等价于 agent_recent。
调度侧对这些激活是要求模型重入 的:src/runtime/scheduler.rs:944 EmitSystemTick("wake_hint") + .model_reentry(true);因此每个 webhook 事件都会拉起一个可能长达数十轮的 turn,而该 turn 从第一轮起就没有可裁剪的作用域。
放大器 :turn 内 CreateWorkItem 不会让新 WI 成为 current focus(实测返回 is_current=false / focus=queued,必须再 PickWorkItem)。trace-reporter 在这些 turn 内建了 5 个 WI,364 轮却全部 仍是 no_current_work_item。
已有修复不覆盖本问题: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 直接生效;
A2 在 TurnOwner 增加 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_tokens、data.output_tokens、data.turn_index、data.tool_call_count、data.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,输出本文全部表格)贴到评论里。
环境
HOLON_CONTEXT_HISTORY_SELECTOR=work_item_scoped(全局启用),HOLON_CONTEXT_WINDOW_MESSAGES=12(默认)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):no_current_work_itemno_current_work_itemno_current_work_itemno_owned_turn_recordsno_owned_turn_recordsno_current_work_item合计:
no_current_work_item1465 轮(83.5%),真正用上work_item_scoped只有 159 轮(9.1%)。token 口径(刻意分开陈述,不相加当计费)
data.input_tokens与data.provider_cache_usage.read_input_tokens是分开计量的(本窗口后者是前者的 5–13 倍),所以:no_current_work_item的 1465 轮)history_projection.input_estimated_tokens合计history_projection.input_chars合计data.input_tokens合计provider_cache_usage.read_input_tokens合计provider_cache_usage.creation_input_tokens合计data.output_tokens合计即:每一轮都被重新灌入约 25.9k token 的跨工作历史,其中绝大部分只能靠 prompt cache 兜住;
history_window_budget_tokens=64000并没有成为约束,绑定的是「12 条」这个条数上限。极端个例:一次 wake_hint 激活 = 52 轮真实工作,全程无作用域
trace-reporter 的 364 轮里 417 次是
ExecCommand,只有 11 轮零工具调用;这些 turn 内它还调用了CreateWorkItem5 次、CompleteWorkItem6 次、PickWorkItem2 次。所以这不是「空转 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一致)src/context/mod.rs:165-167—reproject_work_item_scoped_with_limit()在reprojection.current_work_item为None时直接返回NoCurrentWorkItem,没有次级作用域。src/runtime/turn/execution.rs:1685-1698— 任何fallback_reason都统一退化到HistorySelector::RecentTurns+history_window_scope="agent_recent"+history_window_limit=context_config.recent_messages(=12)。TurnOwner(src/types.rs:2309)只有WorkItem / Conversation / AgentLifecycle / Command;无 WI 轮的 owner 恰恰是AgentLifecycle{agent_id},用read_recent_turns_for_owner()(src/storage/mod.rs:709,fix: 深化 work_item_scoped 历史窗口 #2845 已接入 WI 分支)去查同一 agent 的 lifecycle 轮次 ≈ 等价于agent_recent。src/runtime/scheduler.rs:944EmitSystemTick("wake_hint")+.model_reentry(true);因此每个 webhook 事件都会拉起一个可能长达数十轮的 turn,而该 turn 从第一轮起就没有可裁剪的作用域。CreateWorkItem不会让新 WI 成为 current focus(实测返回is_current=false / focus=queued,必须再PickWorkItem)。trace-reporter 在这些 turn 内建了 5 个 WI,364 轮却全部仍是no_current_work_item。work_item_scoped_projection_unavailable(本机 689→0),但采纳率反被no_current_work_item压在 9.1%;fix: 删除周期性 WorkItem 进度催更 #2879(移除周期性 WI 进度提醒)也没改变这一分布。建议(按性价比排序)
A(首选,语义层)给「带持久外部载荷的激活」一个可裁剪的作用域。 两种等价做法:
activationId(agentinbox activation)时,为该激活自动建立/绑定一个 WorkItem(或等价的轻量 activation scope),让既有的work_item_scoped直接生效;TurnOwner增加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 会超时):字段路径:
data.input_tokens、data.output_tokens、data.turn_index、data.tool_call_count、data.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,输出本文全部表格)贴到评论里。