docs(agent-loop): align series-start contracts with shipped behavior
Clarify the SystemPromptDecisionInput.startsSeries contract (a new request series no longer implies consolidation), record the tool-schema series-start exception consistently in the decision and consequences, and correct the complete-declaration attribution to the unsupported-route projection.
This commit is contained in:
1 parent
b65da56f6e
commit
d5ee15acf2
4 files changed
+19
-17
No files matched your search
+8
-8
@@ -6,17 +6,17 @@
|
||||
en: f62e2980951e0cb3
|
||||
zh: 3cd4fd49a89fae40
|
||||
/agent-note-a-series-start-with-an-unchanged-prompt-keeps-the-surviving-system-node/problem:
|
||||
en: 8e0ce5b72de9a0ea
|
||||
zh: 83376482c18fe44b
|
||||
en: b2b7d5e9a4850ece
|
||||
zh: cbd91b67969bca02
|
||||
/agent-note-a-series-start-with-an-unchanged-prompt-keeps-the-surviving-system-node/decision:
|
||||
en: 922ec89abd884d4a
|
||||
zh: 26e62f1b7f489fbb
|
||||
en: 8a3404207a36ecfb
|
||||
zh: d8bc1decefa60a20
|
||||
/agent-note-a-series-start-with-an-unchanged-prompt-keeps-the-surviving-system-node/verification:
|
||||
en: 3b81cbf6c2c8f017
|
||||
zh: d79242b84132f3cf
|
||||
en: 1970ea84337ce9b5
|
||||
zh: da5b9928606c07c6
|
||||
/agent-note-a-series-start-with-an-unchanged-prompt-keeps-the-surviving-system-node/alternatives-considered:
|
||||
en: 2e87f55619f04607
|
||||
zh: 7ca5a8e9a42a799a
|
||||
/agent-note-a-series-start-with-an-unchanged-prompt-keeps-the-surviving-system-node/consequences:
|
||||
en: 909a86c92a205d2a
|
||||
zh: 6826df609193e103
|
||||
en: bf8a9d6c56722efd
|
||||
zh: 18dfdf8a7b9bcb5c
|
||||
+4
-3
@@ -6,13 +6,13 @@ English | [中文](2026-10-22-series-start-unchanged-prompt-prefix.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
On an in-history route, a prompt change is appended as a later `system/message`, so node 0 stays stale while the latest surviving node carries the rendering. A surface replacement (compaction, tool-result prune, image offload) starts a new request series. The projection used to re-baseline node 0 at every series start even when the rendering already matched that latest node: it emptied the later node and wrote the identical text into node 0. The wire request then differed at its first message, so the provider prefix cache missed from the first token although no prompt text changed.
|
||||
On an in-history route, a prompt change is appended as a later `system/message`, so node 0 stays stale while the latest surviving node carries the rendering. A surface replacement (compaction, tool-result prune, image offload) starts a new request series. Re-baselining node 0 at a series start while the rendering already matched that latest node emptied the later node and wrote the identical text into node 0. The wire request then differed at its first message, so the provider prefix cache missed from the first token although no prompt text changed.
|
||||
|
||||
Real session `session-da55bbc9-…`, turn 710 step 2: `compaction/prune` replaced one tool result (seq 34669, 34670); the next request wrote 7,618 characters identical to the node it emptied (seq 34672, 34673), `request/header` logged reason `series` (seq 34674), and the step billed 722,332 uncached input tokens with 1,152 cache-read tokens (0.16% hit) against about 99.9% on the neighboring steps. A build update at turn 707 had appended the prompt node; nothing changed the rendering at turn 710.
|
||||
|
||||
## Decision
|
||||
|
||||
`SystemPromptProjection.project` re-baselines node 0 only when the effective text must move: an incapable route, an empty rendering, or a series start whose latest surviving non-empty node text differs from the rendering. When the route is in-history, the rendering is non-empty, and the latest surviving node already holds it, the projection returns no updates and the node stays where the cached history carried it. The loop still records the request series through `request/header` (`series`, or `change` with `startsSeries: true`) and rebuilds declarations as before.
|
||||
`SystemPromptProjection.project` re-baselines node 0 only when the effective text must move: an incapable route, an empty rendering, or a series start whose latest surviving non-empty node text differs from the rendering. When the route is in-history, the rendering is non-empty, and the latest surviving node already holds it, the projection returns no updates and the node stays where the cached history carried it. The loop still records the request series through `request/header` (`series`, or `change` with `startsSeries: true`) and rebuilds declarations as before; when the unchanged-rendering skip coincides with a tool-schema series start, the header records the declaration change without `startsSeries`.
|
||||
|
||||
Retry recovery keeps its guarantee: when a retry compaction shadows the latest node, the effective text falls back to an older node, differs from the rendering, and the head is rewritten before users are admitted again. The projection never appends an update after already-admitted users.
|
||||
|
||||
@@ -20,6 +20,7 @@ Retry recovery keeps its guarantee: when a retry compaction shadows the latest n
|
||||
|
||||
- [`system-prompt-projection.spec.ts`](../../../../packages/core/agent-loop/tests/system-prompt-projection.spec.ts) pins the no-operation decision for a capable series start whose surviving node already matches.
|
||||
- [`system-prompt-admission.spec.ts`](../../../../packages/core/agent-loop/tests/system-prompt-admission.spec.ts) pins the wire request: a compaction replacement with an unchanged rendering keeps both system messages, the previous request messages remain a prefix, and the neighboring steps regain the provider prefix.
|
||||
- The [`in-history-series-start`](../../../../snapshots/session/in-history-series-start/) recorded session replays the shipped headless profile through a prompt change and a later surface replacement, pinning the unchanged-rendering series start to two retained prompt versions and no `system/message` event.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -34,5 +35,5 @@ Retry recovery keeps its guarantee: when a retry compaction shadows the latest n
|
||||
## Consequences
|
||||
|
||||
- A capable route can retain earlier prompt versions in model history after a series start; they cost input tokens until a later consolidation or compaction shadows them.
|
||||
- A tool-schema series start on a route without tool updates no longer writes a `system/message` event, so the header records the declaration change without `startsSeries`; tool history reconstructs the full active declaration set for that request.
|
||||
- A tool-schema series start on a route without tool updates no longer writes a `system/message` event, so its header records the declaration change without `startsSeries`. The request still carries the complete active declaration set through the unsupported-route projection (`projectToolUpdates`), while `Session.toolHistory()` keeps its incremental baseline instead of resetting.
|
||||
- Consolidation still applies when the rendering actually differs: route switches, prompt updates concurrent with compaction, and retries that shadow the latest node.
|
||||
+4
-3
@@ -6,13 +6,13 @@ Status: implemented
|
||||
|
||||
## Problem
|
||||
|
||||
在 in-history 路由上,提示词变化会追加为后续的 `system/message`,因此 node 0 保持陈旧,由最新存活节点承载渲染文本。surface 替换(压缩、工具结果剪枝、图片省略)会开启新的请求序列。投影过去会在每次新序列开始时无条件重写 node 0,即使渲染文本已经与该最新节点一致:它清空后续节点,再把完全相同的文本写进 node 0。于是 wire 请求的第一条消息发生变化,提供方前缀缓存从第一个 token 起未命中,尽管提示词文本没有任何变化。
|
||||
在 in-history 路由上,提示词变化会追加为后续的 `system/message`,因此 node 0 保持陈旧,由最新存活节点承载渲染文本。surface 替换(压缩、工具结果剪枝、图片省略)会开启新的请求序列。在渲染文本已经与该最新节点一致时重写 node 0,会清空该节点并把完全相同的文本写进 node 0。于是 wire 请求的第一条消息发生变化,提供方前缀缓存从第一个 token 起未命中,尽管提示词文本没有任何变化。
|
||||
|
||||
真实会话 `session-da55bbc9-…` 第 710 轮第 2 步:`compaction/prune` 替换了一条工具结果(seq 34669、34670);下一次请求写入的 7,618 个字符与它清空的节点完全相同(seq 34672、34673),`request/header` 记录 reason `series`(seq 34674),该步计费 722,332 个未命中输入 token、1,152 个缓存读取 token(命中率 0.16%),而相邻步骤约为 99.9%。第 707 轮的构建更新追加了提示词节点;第 710 轮没有任何东西改变渲染文本。
|
||||
|
||||
## Decision
|
||||
|
||||
`SystemPromptProjection.project` 只在有效文本必须移动时重写 node 0:不具备能力的路由、空渲染文本,或最新存活非空节点文本与渲染文本不同的新序列开始。当路由为 in-history、渲染文本非空、且最新存活节点已经持有它时,投影不产生任何更新,节点保持在已缓存历史携带它的位置。loop 仍通过 `request/header` 记录请求序列(`series`,或带 `startsSeries: true` 的 `change`),并照旧重建声明。
|
||||
`SystemPromptProjection.project` 只在有效文本必须移动时重写 node 0:不具备能力的路由、空渲染文本,或最新存活非空节点文本与渲染文本不同的新序列开始。当路由为 in-history、渲染文本非空、且最新存活节点已经持有它时,投影不产生任何更新,节点保持在已缓存历史携带它的位置。loop 仍通过 `request/header` 记录请求序列(`series`,或带 `startsSeries: true` 的 `change`),并照旧重建声明;当「渲染未变则跳过」与工具 schema 起因的新序列重合时,header 记录声明变化但不带 `startsSeries`。
|
||||
|
||||
重试恢复保持原有保证:重试压缩遮蔽最新节点后,有效文本回退到更旧的节点,与渲染文本不同,于是头节点在用户消息再次被接纳之前重写。投影从不会在已接纳的用户消息之后追加更新。
|
||||
|
||||
@@ -20,6 +20,7 @@ Status: implemented
|
||||
|
||||
- [`system-prompt-projection.spec.ts`](../../../../packages/core/agent-loop/tests/system-prompt-projection.spec.ts) 固定「能力路由的新序列开始时存活节点文本已一致则不产生操作」的决策。
|
||||
- [`system-prompt-admission.spec.ts`](../../../../packages/core/agent-loop/tests/system-prompt-admission.spec.ts) 固定 wire 请求:渲染文本未变的压缩替换保留两条系统消息,上一次请求的消息仍构成前缀,相邻步骤重新获得提供方前缀。
|
||||
- [`in-history-series-start`](../../../../snapshots/session/in-history-series-start/) 录制会话通过 shipped headless profile 回放「提示词变化 + 后续 surface 替换」,固定渲染未变的新序列保留两份提示词版本且不写 `system/message` 事件。
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -34,5 +35,5 @@ Status: implemented
|
||||
## Consequences
|
||||
|
||||
- 具备能力的路由在新序列开始后可能在模型历史中保留更早的提示词版本;在被后续归并或压缩遮蔽之前,它们会计入输入 token。
|
||||
- 不支持工具更新的路由上,工具 schema 变化开启的新序列不再写出 `system/message` 事件,因此 header 记录声明变化但不带 `startsSeries`;工具历史会为该请求重建完整的生效声明集合。
|
||||
- 不支持工具更新的路由上,工具 schema 变化开启的新序列不再写出 `system/message` 事件,因此其 header 记录声明变化但不带 `startsSeries`。请求仍通过不支持更新的路由投影(`projectToolUpdates`)携带完整的生效声明集合,而 `Session.toolHistory()` 保留其增量基线、不再重置。
|
||||
- 渲染文本确实不同时归并仍然生效:路由切换、与压缩同时发生的提示词更新,以及遮蔽最新节点的重试。
|
||||
@@ -41,9 +41,9 @@ export interface SystemPromptDecisionInput {
|
||||
/** Whether the prepared route for this attempt reads a later `system` message as the effective prompt. */
|
||||
inHistory: boolean
|
||||
/**
|
||||
* Whether prompt admission must consolidate: a pre-step listener declared a
|
||||
* new series, the surface changed since the last request, or assembled tools
|
||||
* changed on a route without tool-update support.
|
||||
* Whether this step starts a new request series: a pre-step listener declared
|
||||
* one, the surface changed since the last request, or assembled tools changed
|
||||
* on a route without tool-update support.
|
||||
*/
|
||||
startsSeries: boolean
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user