refactor: remove per-followup result attribution
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/proposed/architecture/2026-07-30-followup-enqueue-and-owned-runs.md
|
||||
2026-07-30-followup-enqueue-and-owned-runs.md: 73dfb501cb5c18a7a9219861eba37e73499af5e0
|
||||
2026-07-30-followup-enqueue-and-owned-runs.zh.md: 03a321c761eda385acb665d26a33ef618c70dee5
|
||||
@@ -0,0 +1,43 @@
|
||||
# Agent Note: Follow-up enqueue and owned run boundaries
|
||||
|
||||
Status: proposed
|
||||
|
||||
English | [中文](2026-07-30-followup-enqueue-and-owned-runs.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
`Agent.followup()` identifies and queues a user message, but one follow-up does not own the activity that follows it. Steering, injected context, tool continuations, recovery, and later queued messages can all contribute before the agent next becomes idle. A `MessageId` can therefore prove inbox admission, but it cannot identify which assistant message or `turn/end` is the result of that input.
|
||||
|
||||
The [one-send-one-turn decision](../../implemented/simplification/2026-07-17-one-send-one-turn.md) already rejects a per-send completion handle at the core seam. Protocol and SDK layers currently manufacture that missing relationship downstream by pairing one prompt request with a turn result. The pairing becomes ambiguous as soon as activity admits more input, and it exposes turn mechanics as if they were a prompt-level outcome.
|
||||
|
||||
## Proposal
|
||||
|
||||
Keep `Agent.followup(message): void` as an enqueue-only operation. `Agent.whenIdle()` and `agent/status` remain whole-agent lifecycle observations; neither settles an individual message. Inbox durability records the identified message and its admission or cancellation, without assigning later output to it.
|
||||
|
||||
The low-level SDK protocol will answer `session/prompt` as soon as enqueue succeeds with `{ messageId }`. It will stream durable facts through `session.event`, publish whole-agent transitions through `session.status`, and remove `session.finished`. A low-level client may observe that receipt and later idleness, but receives no prompt result.
|
||||
|
||||
High-level automation APIs may return a `RunResult` only when they explicitly own an activity interval. The TypeScript and Python SDK `run()` methods will collect from the submitted message's durable inbox receipt through the next whole-agent `idle`; their `finalResponse` is the last committed assistant message in that interval, not a response causally attributed to the submitted prompt. The one-shot CLI owns the analogous idle-to-idle interval. An isolated child-agent run may still report a result because its caller owns the complete child lifecycle and any steering belongs to that run.
|
||||
|
||||
ACP must still return a protocol `stopReason`. Its bridge will serialize one in-flight prompt per ACP session, wait for whole-agent idle, report `cancelled` only for explicit ACP cancellation or disposal, and otherwise report the generic `end_turn`. It will not infer token-limit or error attribution for the prompt.
|
||||
|
||||
Goal continuation will retain `MessageId` only to recognize its durable queued and admitted goal message. It will advance from durable goal state at whole-agent idle, without mapping the message to a turn result.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Map `MessageId` to the turn that admits it.** A turn may consume steering and injected context and may continue through multiple model/tool steps. The mapping identifies admission, not causal ownership of the resulting output or stop reason.
|
||||
|
||||
**Return a per-follow-up completion handle.** A handle would imply a result boundary that the shared agent lifecycle does not have. It would either omit work that influenced the activity or silently absorb unrelated later input.
|
||||
|
||||
**Use the last `turn/end` observed before idle.** This is a useful run-level observation for an explicitly owned interval, but naming it as the submitted message's outcome recreates the false causal claim.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- `Agent.followup()` remains enqueue-only, and its documentation promises no per-message completion or result.
|
||||
- The SDK wire protocol returns `MessageId` from `session/prompt`, publishes `session.status`, and has no `session.finished`.
|
||||
- TypeScript and Python high-level SDKs expose `RunResult` without prompt-level `status` or `reason`, and define the receipt-to-idle collection window.
|
||||
- ACP, the one-shot CLI, goal continuation, and subagent providers document the distinct activity ownership they actually possess.
|
||||
- No production consumer derives a follow-up result by correlating `MessageId` with `turn/end`.
|
||||
|
||||
## Risks
|
||||
|
||||
An owned activity interval can include steering, injected context, or other work submitted before idleness, so its final response and events are deliberately broader than the initiating message. Prompt-level model error and token-limit classifications disappear from SDK and ACP results; callers that need those facts must inspect the durable event stream without claiming causal attribution. Concurrent automation on one session requires an explicit serialization or ownership policy rather than an implicit per-prompt result.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Agent Note: follow-up 入队与自有运行边界
|
||||
|
||||
Status: proposed
|
||||
|
||||
[English](2026-07-30-followup-enqueue-and-owned-runs.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
`Agent.followup()` 会标识一条用户消息并将其排入队列,但单次 follow-up 并不拥有随后发生的活动。在 agent(智能体)下一次进入 idle 前,steering(中途引导)、注入的上下文、工具续行、恢复和后续排队消息都可能参与活动。因此,`MessageId` 可以证明 inbox 已准入,但不能标识哪一条 assistant 消息或哪一个 `turn/end` 是该输入的结果。
|
||||
|
||||
[one-send-one-turn 决策](../../implemented/simplification/2026-07-17-one-send-one-turn.md) 已经在核心 seam 中排除了按 send 返回完成句柄的设计。协议层和 SDK 层仍会在下游配对一项提示词请求与一个轮次结果,人为构造这一缺失的关系。一旦活动准入更多输入,该配对就会产生歧义,还会把轮次机制暴露为提示词级结果。
|
||||
|
||||
## 提案
|
||||
|
||||
保留 `Agent.followup(message): void`,使其仅执行入队。`Agent.whenIdle()` 和 `agent/status` 仍用于观察整个 agent 的生命周期;二者都不结算单条消息。Inbox 持久性会记录已标识消息及其准入或取消,但不会把后续输出归属于该消息。
|
||||
|
||||
底层 SDK 协议在入队成功后立即以 `{ messageId }` 响应 `session/prompt`。它通过 `session.event` 传输持久事实,通过 `session.status` 发布整个 agent 的状态转换,并删除 `session.finished`。底层客户端可以观察该回执和之后的 idle,但不会收到提示词结果。
|
||||
|
||||
只有明确拥有一个活动区间时,高层自动化 API 才可以返回 `RunResult`。TypeScript 和 Python SDK 的 `run()` 方法会从已提交消息的持久 inbox 回执开始收集,直至整个 agent 下一次进入 `idle`;其 `finalResponse` 是该区间内最后一条已提交的 assistant 消息,而不是按因果关系归属于已提交提示词的响应。单次 CLI(命令行界面)拥有相应的 idle 到 idle 区间。隔离的子 agent 运行仍可报告结果,因为调用方拥有完整的子级生命周期,任何 steering 都属于该运行。
|
||||
|
||||
ACP(Agent Client Protocol)仍必须返回协议规定的 `stopReason`。其桥接层会串行处理每个 ACP 会话中唯一一个正在处理的提示词,等待整个 agent 进入 idle,仅在显式 ACP 取消或资源释放时报告 `cancelled`,其他情况均报告通用的 `end_turn`。它不会推断 token 上限或错误是否归属于该提示词。
|
||||
|
||||
Goal 续行只会保留 `MessageId`,用于识别持久排队和已准入的 goal 消息。它会在整个 agent 进入 idle 时根据持久 goal 状态推进,不把消息映射到轮次结果。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**将 `MessageId` 映射到准入它的轮次。** 一个轮次可能使用 steering 和注入的上下文,还可能经过多个模型/工具步骤继续执行。该映射只能标识准入,不能确立结果输出或停止原因的因果归属。
|
||||
|
||||
**返回按 follow-up 区分的完成句柄。** 这样的句柄暗示共享 agent 生命周期中存在并不实际成立的结果边界。它要么遗漏影响活动的工作,要么在不作说明的情况下吸收后续无关输入。
|
||||
|
||||
**使用进入 idle 前观察到的最后一个 `turn/end`。** 对于明确拥有的区间,这是一项有用的运行级观测;但如果将其命名为已提交消息的结果,就会再次作出错误的因果声明。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- `Agent.followup()` 仍仅执行入队,其文档不承诺单条消息的完成状态或结果。
|
||||
- SDK 协议格式(wire format)由 `session/prompt` 返回 `MessageId`、发布 `session.status`,且不包含 `session.finished`。
|
||||
- TypeScript 和 Python 高层 SDK 公开不带提示词级 `status` 或 `reason` 的 `RunResult`,并定义从回执到 idle 的收集窗口。
|
||||
- ACP、单次 CLI、goal 续行和 subagent 提供方分别记录自己实际拥有的活动边界。
|
||||
- 生产消费方都不会通过关联 `MessageId` 与 `turn/end` 来推导 follow-up 结果。
|
||||
|
||||
## 风险
|
||||
|
||||
自有活动区间可以包含进入 idle 前提交的 steering、注入上下文或其他工作,因此其最终响应和事件有意比初始消息涵盖更广。SDK 和 ACP 结果不再包含提示词级模型错误和 token 上限分类;需要这些事实的调用方必须检查持久事件流,但不能声称这些事实具有因果归属。在同一会话上并发执行自动化操作时,必须采用显式串行或所有权策略,不能依赖隐式的按提示词结果。
|
||||
Reference in New Issue
Block a user