feat(agent): add addressable queue operations
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# 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/implemented/architecture/2026-07-22-unified-send-and-coalesced-user-messages.md
|
||||
2026-07-22-unified-send-and-coalesced-user-messages.md: ed171735cf483938c70291963a6e68dc02d7bde2
|
||||
2026-07-22-unified-send-and-coalesced-user-messages.zh.md: 8b2a3ebabb493954e653255e876255b9c0810c19
|
||||
2026-07-22-unified-send-and-coalesced-user-messages.md: 3b2187f9e9ae24f3a03c1418daf1c0aec255b314
|
||||
2026-07-22-unified-send-and-coalesced-user-messages.zh.md: 02a21193ce63926b6f04a02cb7ea8a64fe6603cb
|
||||
|
||||
@@ -22,7 +22,7 @@ Separately, `context/message` and `user/message` had converged: the surface proj
|
||||
|
||||
**`send` does not return identity.** Callers already own the complete message and its opaque `MessageId`; creation and freezing are owned by the [identified immutable message decision](2026-07-28-identified-immutable-message-values.md), not by routing.
|
||||
|
||||
**Three inbox events replace agent/queued.** `agent/inbox/enqueue` (an item entered a FIFO), `agent/inbox/dequeue` (the driver claimed one), and `agent/inbox/discard` (`cancel()` dropped pending items) carry the accepted `UserMessage`. Enqueue and dequeue also carry the resolved `queued | steering` placement captured at acceptance, so observers and reconnect mirrors retire repeated message identities from the correct FIFO without reconstructing routing from later status or session history. Injection never touches a FIFO and emits none of these. Every FIFO entry publishes an enqueue, including steering submitted by an `agent/turn-stopping` listener, so the ledger stays balanced with its later dequeue or discard. The `dsh-agent` invariant companion asserts FIFO conservation: a per-agent outstanding count that dequeue and discard can never drive negative.
|
||||
**Inbox lifecycle events carry occurrence identities.** `agent/inbox/enqueue` (an item entered a FIFO), `agent/inbox/update` (a pending item was edited or promoted), `agent/inbox/dequeue` (the driver claimed one), and `agent/inbox/discard` (pending items were dropped) carry an `InboxItem`: an occurrence-local `InboxItemId`, the accepted `UserMessage`, and the resolved `queued | steering` placement captured at acceptance. The occurrence identity lets observers and reconnect mirrors distinguish repeated sends of the same `MessageId` without reconstructing routing from later status or session history. Injection never touches a FIFO and emits none of these. Every FIFO entry publishes one enqueue and exactly one terminal dequeue or discard; updates are non-terminal. The `dsh-agent` invariant companion asserts this FIFO conservation.
|
||||
|
||||
**Admission accepts next-step input without becoming a turn.** The loop opens a private next-step acceptance window before `agent/prompt-submit`, keeps it open through the turn, and closes it before `turn/end`. Steering and injection received during admission therefore remain together in the outbox and join an allowed turn. If admission blocks or fails, a context-only caller batch takes idle injection's immediate append, while steering and context staged beside it remain available to retry; neither path writes the rejected prompt. When a later prompt is admitted, retained outbox input enters its turn before that prompt, while input accepted during the current admission remains after the prompt. Closing the window before `turn/end` preserves the rule that reentrant late steering becomes an independent queued turn. `Agent.acceptsNextStep` exposes whether a `next-step` send would currently join this window; `status` remains the broader activity signal rather than a routing predicate.
|
||||
|
||||
@@ -43,7 +43,7 @@ Separately, `context/message` and `user/message` had converged: the surface proj
|
||||
|
||||
The delivery surface is now one primitive plus three self-documenting presets, and the (`target` × `wakeup`) matrix makes previously-unreachable combinations explicit. One durable message type serves prompts, injected context, and goal rounds, so the surface projection and every "human prompt?" check simplify to a `source` test. The `Agent` contract remains an interface, so alternate implementations and object-literal test fakes implement the same minimal structural surface. The goal fold's channel split moved from event type to `source.round`, and every consumer that filtered `context/message` now filters `user/message` by source. An idle injection appends `user/message` between turns without opening a turn or running the model.
|
||||
|
||||
`wakeup` is the "should the model run" signal, so the inbox distinguishes waking queued work from anything available to dequeue: a lone `next-turn`/no-wakeup item stays parked at idle and rides along the next waking send, and `whenIdle`/`cancel` settle quiescence off the waking signal. Every FIFO exit publishes exactly one lifecycle event, while domain-specific durable facts travel in typed message sources rather than a parallel metadata channel. The direct pending-item representation keeps public lifecycle events correlated without maintaining a second steering wrapper or allowing its durable data to diverge.
|
||||
`wakeup` is the "should the model run" signal, so the inbox distinguishes waking queued work from anything available to dequeue: a lone `next-turn`/no-wakeup item stays parked at idle and rides along the next waking send, and `whenIdle`/`cancel` settle quiescence off the waking signal. Every FIFO exit publishes exactly one lifecycle event, while domain-specific durable facts travel in typed message sources rather than a parallel metadata channel. The direct pending-item representation keeps public lifecycle events correlated without maintaining a second steering wrapper or allowing its durable data to diverge. The later [addressable queue operations](../feature/2026-07-29-addressable-queue-operations.md) decision adds live mutations over that occurrence identity without changing the one-message-per-turn or durable-message contracts.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ agent 的对外驱动接口逐渐长出三个近乎平行的动词——`send`
|
||||
|
||||
**`send` 不返回标识。** 调用方已经持有完整消息及其不透明的 `MessageId`;消息的创建与冻结由[带标识的不可变消息值决策](2026-07-28-identified-immutable-message-values.md)负责,而不是由路由负责。
|
||||
|
||||
**三个 inbox 事件取代 agent/queued。** `agent/inbox/enqueue`(一个队列项进入某个 FIFO)、`agent/inbox/dequeue`(驱动器认领了一个)和 `agent/inbox/discard`(`cancel()` 丢弃了待处理项)都会携带已接受的 `UserMessage`。enqueue 和 dequeue 还会携带生产方在接受消息时捕获的已解析 `queued | steering` 放置方式,因此观察方和重连镜像可以从正确的 FIFO 中结算重复出现的消息标识,无需根据后续状态或会话历史重建路由。注入从不触及 FIFO,也不发出这些事件中的任何一个。每一次 FIFO 入队都会发布一个 enqueue 事件,包括 `agent/turn-stopping` 监听器提交的 steering,因此账目会与其后的 dequeue 或 discard 保持平衡。`dsh-agent` 的不变量配套断言 FIFO 守恒:一个按 agent 计的未结算计数,dequeue 和 discard 永远无法把它压到负数。
|
||||
**Inbox 生命周期事件携带单次入队标识。** `agent/inbox/enqueue`(一个队列项进入某个 FIFO)、`agent/inbox/update`(待处理项被编辑或前移)、`agent/inbox/dequeue`(驱动器认领一个项)和 `agent/inbox/discard`(待处理项被丢弃)都会携带一个 `InboxItem`:仅属于本次入队的 `InboxItemId`、已接受的 `UserMessage`,以及生产方在接受消息时捕获的已解析 `queued | steering` 放置方式。单次入队标识让观察方和重连镜像能够区分同一 `MessageId` 的多次发送,无需根据后续状态或会话历史重建路由。注入从不触及 FIFO,也不发出这些事件中的任何一个。每次 FIFO 入队都会发布一个 enqueue,并且恰好发布一个终态 dequeue 或 discard;update 不是终态。`dsh-agent` 的不变量配套断言这种 FIFO 守恒。
|
||||
|
||||
**准入接受 next-step 输入,但不会因此成为一个轮次。** 循环会在 `agent/prompt-submit` 前打开一个私有的 next-step 接受窗口,使其贯穿整个轮次,并在 `turn/end` 前关闭。因此,在准入期间收到的 steering 和注入会一起留在 outbox 中并加入获准轮次。如果准入被阻止或失败,仅含调用方上下文的批次会采用空闲注入的立即追加行为,而 steering 及与其一同暂存的上下文仍可重试;两种路径都不会写入被拒绝的提示词。后续提示词获准时,保留在 outbox 中的输入会先于该提示词进入其轮次,而当前准入期间接受的输入则留在提示词之后。在 `turn/end` 前关闭窗口,可以保留这样的规则:可重入的晚到 steering 会成为一个独立的排队轮次。`Agent.acceptsNextStep` 会公开一次 `next-step` 发送当前是否会加入该窗口;`status` 仍是更宽泛的活动信号,而非路由判据。
|
||||
|
||||
@@ -43,7 +43,7 @@ agent 的对外驱动接口逐渐长出三个近乎平行的动词——`send`
|
||||
|
||||
投递接口现在是一个原语加三个自解释的预设,(`target` × `wakeup`) 矩阵把此前无法表达的组合显式化。一种持久消息类型同时服务提示词、注入的上下文和 goal 轮次,因此对外接口的投影和每一处“是否人类提示词?”检查都简化为一次 `source` 判断。`Agent` 契约仍是接口,因此其他实现和对象字面量形式的测试替身只需实现同一个最小结构接口。goal 折叠的通道区分从事件类型改到了 `source.round`;此前过滤 `context/message` 的每个消费方现在改为按来源过滤 `user/message`。空闲状态下的注入会在两个轮次之间追加 `user/message`,既不打开轮次,也不运行模型。
|
||||
|
||||
`wakeup` 是“模型是否应当运行”的信号,因此 inbox 会区分能唤醒的排队工作与任何可 dequeue 的项:一个孤立的 `next-turn`/no-wakeup 队列项会停泊在空闲状态,并随下一次唤醒 send 一同带出,而 `whenIdle`/`cancel` 依据唤醒信号来结算静默。每一次 FIFO 退出都恰好发布一个生命周期事件,特定于领域的持久事实则通过类型化消息 source 传递,而非通过平行的元数据通道。直接使用待处理项的表示方式,使公开生命周期事件保持可关联,既无需维护第二个 steering 包装层,也避免其持久数据发生分歧。
|
||||
`wakeup` 是“模型是否应当运行”的信号,因此 inbox 会区分能唤醒的排队工作与任何可 dequeue 的项:一个孤立的 `next-turn`/no-wakeup 队列项会停泊在空闲状态,并随下一次唤醒 send 一同带出,而 `whenIdle`/`cancel` 依据唤醒信号来结算完全停稳。每一次 FIFO 退出都恰好发布一个生命周期事件,特定于领域的持久事实则通过类型化消息 source 传递,而非通过平行的元数据通道。直接使用待处理项的表示方式,使公开生命周期事件保持可关联,既无需维护第二个 steering 包装层,也避免其持久数据发生分歧。后续的[可寻址队列操作](../feature/2026-07-29-addressable-queue-operations.md)决策在该单次入队标识上增加了实时变更,但不改变单消息单轮次或持久消息契约。
|
||||
|
||||
## 相关
|
||||
|
||||
|
||||
@@ -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/implemented/feature/2026-07-29-addressable-queue-operations.md
|
||||
2026-07-29-addressable-queue-operations.md: 7462b882dde0c3b25ddfb321ab339b6cd51bd170
|
||||
2026-07-29-addressable-queue-operations.zh.md: ac442421a1e21b2e09bb003ca9be1a0412374d7f
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note: Address pending queue occurrences for edit, remove, and promotion
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-07-29-addressable-queue-operations.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The Web queue rendered pending messages but could not act on one row. `MessageId` was insufficient as an address because callers may enqueue the same immutable message more than once. The browser also inferred queue retirement from turn and status events, so a row operation racing with driver claim had no authoritative outcome.
|
||||
|
||||
“Send now” introduced a separate semantic choice: it could mean reorder the next independent turn, interrupt the current turn as steering, or cancel current work. Only the first interpretation preserves the queue row’s original delivery contract.
|
||||
|
||||
## Decision
|
||||
|
||||
**Each accepted FIFO occurrence has its own identity.** AgentLoop mints an opaque `InboxItemId` and publishes an `InboxItem` containing that id, the identified `UserMessage`, and its acceptance-time `queued | steering` placement. Reusing one `MessageId` creates distinct inbox identities. Injection bypasses the FIFOs and receives no inbox identity.
|
||||
|
||||
**Mutation ends at driver claim.** `Agent.updateInbox(id, action)` synchronously searches the pending queued and steering FIFOs. Edit replaces frozen content while preserving `InboxItemId`, `MessageId`, source, placement, wake policy, and position. Remove emits the occurrence’s terminal discard. Promote moves it to the front of its current FIFO; an ordinary queued item also becomes waking. The driver removes an occurrence before prompt admission or steering drain, so a later mutation returns `not-found` and never rewrites durable history.
|
||||
|
||||
**The live ledger is authoritative.** `agent/inbox/enqueue`, `update`, `dequeue`, and `discard` maintain a Host mirror. The wire sends complete `session/queue` snapshots rather than incremental guesses. Reconnect sends the current baseline, and every live mutation or terminal event replaces it. The client applies no optimistic edit and never retires a row from `turn/start`, `steering/message`, or status changes.
|
||||
|
||||
**Web actions preserve delivery kind.** QueueDock projects only `queued` occurrences; pending `steering` occurrences remain in the authoritative snapshot but wait for a dedicated Web interaction. It exposes edit and delete, but no send-now control. Edit is available only when all content blocks are text; the editor cannot silently drop non-text blocks. An editing row exposes only save and cancel, with Enter and Escape as their keyboard equivalents. Delete removes the exact occurrence. Protocol-level promotion remains available without being presented as a Web interaction; it never converts queued work into steering or cancels active work.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Address rows by `MessageId`.** Rejected because one immutable message may be sent repeatedly; editing or deleting by message identity would affect an ambiguous occurrence.
|
||||
|
||||
**Apply optimistic browser mutations.** Rejected because driver claim and another client can win before the Host action. Waiting for the authoritative snapshot makes the ownership boundary visible and lets `queue-item-not-found` report a real race.
|
||||
|
||||
**Treat send-now as steering.** Rejected because it would change a queued independent turn into current-turn context, bypass ordinary prompt admission, and alter the one-send-one-turn guarantee. Promotion changes priority, not delivery semantics.
|
||||
|
||||
**Cancel the active turn before promotion.** Rejected because a row-local action must not destroy unrelated in-flight work.
|
||||
|
||||
## Verification
|
||||
|
||||
AgentLoop contract tests hold prompt admission while editing, removing, and promoting exact occurrences, then verify the resulting independent-turn order and terminal lifecycle events. Host schema and proxy tests cover authoritative snapshots, reconnect, typed not-found errors, and the RPC transport. Client runtime and QueueDock tests cover non-optimistic projection, queued-only Web projection, text-only editing, save and cancel affordances, removal, retirement races, disabled mixed-content editing, and the absent send-now control. Keyless browser scenarios drive the exposed edit and delete actions and keep accepted pending steering hidden until it becomes a durable transcript event through the built Web composition and real HTTP/SSE wire.
|
||||
|
||||
## Consequences
|
||||
|
||||
Pending work gains precise row operations without becoming durable session history. Occurrence identity is a live process-local capability and disappears at claim, cancellation, disposal, or restart; reconnect recovers only items still held by the live Agent. Send-now is intentionally weaker than interruption, and editing intentionally excludes mixed content until an editor can preserve every block.
|
||||
|
||||
The protocol now carries full queue snapshots on each change. Queues are expected to remain short, so deterministic recovery and multi-client convergence are preferred over an incremental mutation protocol.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Agent Note(agent 决策记录):为待处理队列项提供编辑、移除与前移操作
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-07-29-addressable-queue-operations.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
Web 队列能够渲染待处理消息,但无法操作其中某一行。`MessageId` 不足以充当寻址标识,因为调用方可以多次将同一条不可变消息加入队列。浏览器还会根据轮次和状态事件推断队列项已退役,因此当行操作与驱动器认领发生竞态时,系统无法给出权威结果。
|
||||
|
||||
“立即发送”还引入了另一项语义选择:它可以表示重新排序下一个独立轮次、以 steering(中途引导)方式打断当前轮次,或取消当前工作。只有第一种解释能够保留该队列行原有的投递契约。
|
||||
|
||||
## 决策
|
||||
|
||||
**每次获准进入 FIFO 的项都有独立标识。** AgentLoop 会铸造不透明的 `InboxItemId`,并发布一个 `InboxItem`,其中包含该 id、已有标识的 `UserMessage`,以及接受时确定的 `queued | steering` 放置方式。复用同一个 `MessageId` 会创建不同的 inbox 标识。注入绕过 FIFO,因此不会获得 inbox 标识。
|
||||
|
||||
**变更边界止于驱动器认领。** `Agent.updateInbox(id, action)` 会同步搜索仍处于待处理状态的 queued 和 steering FIFO。编辑会替换已冻结的内容,同时保留 `InboxItemId`、`MessageId`、来源、放置方式、唤醒策略和位置。移除会发出该次入队项的终态 discard。前移会把它移至当前 FIFO 的队首;普通 queued 项还会变为可唤醒。驱动器会在提示词接纳或排空 steering 之前移除该项,因此之后的变更会返回 `not-found`,绝不会改写持久历史。
|
||||
|
||||
**实时账本是权威状态。** `agent/inbox/enqueue`、`update`、`dequeue` 和 `discard` 共同维护 Host 镜像。协议发送完整的 `session/queue` 快照,而非增量猜测。重连会发送当前基线,每次实时变更或终态事件都会整体替换它。客户端不会进行乐观编辑,也绝不根据 `turn/start`、`steering/message` 或状态变化退役队列行。
|
||||
|
||||
**Web 操作保持投递类型。** QueueDock 只投影 `queued` 入队项;待处理的 `steering` 入队项仍保留在权威快照中,等待 Web 提供专用交互。它只暴露编辑和删除,不提供立即发送控件。仅当所有内容块都是文本时才提供编辑功能;编辑器不能静默丢弃非文本块。编辑中的行只展示保存和取消操作,对应的键盘操作分别是 Enter 和 Escape。删除会移除对应的精确入队项。协议层仍保留前移操作,但不会把它呈现为 Web 交互;该操作绝不会把 queued 工作转换为 steering,也不会取消活动工作。
|
||||
|
||||
## 考虑过的替代方案
|
||||
|
||||
**通过 `MessageId` 寻址行。** 不予采纳,因为同一条不可变消息可以重复发送;按消息标识编辑或删除会无法确定应操作哪一次入队。
|
||||
|
||||
**在浏览器中进行乐观变更。** 不予采纳,因为驱动器认领或另一个客户端可能先于 Host 操作完成。等待权威快照可以显式呈现所有权边界,并让 `queue-item-not-found` 报告真实竞态。
|
||||
|
||||
**把立即发送视为 steering。** 不予采纳,因为这会把一个独立的排队轮次变成当前轮次的上下文,绕过普通提示词接纳,并改变单次 send 单轮次保证。前移只改变优先级,不改变投递语义。
|
||||
|
||||
**前移前取消活动轮次。** 不予采纳,因为仅影响某一行的操作不应破坏无关的进行中工作。
|
||||
|
||||
## 验证
|
||||
|
||||
AgentLoop 契约测试会在编辑、移除和前移对应的精确入队项时阻塞提示词接纳,随后验证所得独立轮次顺序及终态生命周期事件。Host schema 与代理测试覆盖权威快照、重连、类型化 not-found 错误和 RPC 传输。客户端运行时和 QueueDock 测试覆盖非乐观投影、Web 仅投影 queued 项、仅文本编辑、保存与取消入口、移除、退役竞态、禁用混合内容编辑,以及不提供立即发送控件。无密钥浏览器场景会通过构建后的 Web 组合和真实 HTTP/SSE 协议操作公开的编辑和删除,并使已接受的待处理 steering 在成为持久 transcript(文本记录)事件之前保持隐藏。
|
||||
|
||||
## 后果
|
||||
|
||||
待处理工作获得精确的行操作,但不会因此成为持久会话历史。单次入队标识是进程本地的实时寻址凭据,会在认领、取消、dispose 或重启时消失;重连只能恢复仍由活跃 Agent 持有的项。立即发送有意弱于打断,而编辑也有意排除混合内容,直至编辑器能够保留每个块。
|
||||
|
||||
现在,协议会在每次变更时携带完整队列快照。队列预期保持较短,因此系统优先选择确定性恢复和多客户端收敛,而非增量变更协议。
|
||||
@@ -2,5 +2,5 @@
|
||||
# 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/implemented/simplification/2026-07-17-one-send-one-turn.md
|
||||
2026-07-17-one-send-one-turn.md: dcc6c0aa483a0e53205dbaeef4e2b903f5f6a215
|
||||
2026-07-17-one-send-one-turn.zh.md: 8c12481defe6608c13ee81132b432b4d8b17b681
|
||||
2026-07-17-one-send-one-turn.md: 3ae43f137206f25bdbc563875c17e24211f17d6b
|
||||
2026-07-17-one-send-one-turn.zh.md: 5ccdb2192048ecf795415bcd427f967df6a609fb
|
||||
|
||||
@@ -16,7 +16,7 @@ This grouping changes behavior, not just the number of model calls. One ordinary
|
||||
|
||||
The rule is simple: each successful `send()` creates one independent FIFO queue item. If that item runs, it is the only ordinary message in its turn. An item can be dropped before it starts, so the precise guarantee is at most one turn rather than exactly one; two sends are never silently combined.
|
||||
|
||||
Before enqueueing an item, `send()` checks the agent state and makes a detached, deeply frozen snapshot of the content and resolved source. After enqueueing it, `send()` publishes `agent/queued`.
|
||||
Before enqueueing an item, `send()` checks the agent state and accepts an already identified, deeply frozen message. It mints an occurrence-local `InboxItemId` and publishes `agent/inbox/enqueue`; the pending occurrence remains addressable under the [addressable queue operations](../feature/2026-07-29-addressable-queue-operations.md) decision until the driver claims or discards it.
|
||||
|
||||
If messages A and B are both processed, B's turn starts only after A records `turn/end` and A's durability checkpoint settles. B's request therefore sees whatever closed result A left in the same session log. A checkpoint error is reported, but settlement only releases this ordering barrier; it does not make a failed write durable. Broad `cancel()`, disposal, or a failure before `turn/start` can instead discard an unstarted item without opening an empty turn.
|
||||
|
||||
@@ -40,6 +40,6 @@ The no-batching rule applies only to ordinary `send()`. Running `steer()` puts i
|
||||
|
||||
## Consequences
|
||||
|
||||
Ordinary turn boundaries are predictable: messages A and B stay separate, and B runs only after A has closed and reached its checkpoint. Callers still do not receive a per-send completion or cancellation handle; broad cancellation can discard the entire unstarted tail, while status and quiescence remain agent-wide observations.
|
||||
Ordinary turn boundaries are predictable: messages A and B stay separate, and B runs only after A has closed and reached its checkpoint. Callers still do not receive a per-send completion handle; a pending occurrence can be removed through its live `InboxItemId`, broad cancellation can discard the entire unstarted tail, and status and quiescence remain agent-wide observations.
|
||||
|
||||
The trade-off is more model requests and more checkpoints. A busy queue can take longer to drain and can grow under sustained producers. Ordinary-send batching returns only through an explicit, measured contract.
|
||||
|
||||
@@ -16,7 +16,7 @@ Status: implemented
|
||||
|
||||
规则很简单:一次成功的 `send()` 创建一个独立的 FIFO 队列项。该队列项如果运行,就是所在轮次中唯一的普通消息。队列项可能在启动前被丢弃,因此精确保证是最多一个轮次,而不是必定一个轮次;两次 send 绝不会被悄悄合并。
|
||||
|
||||
队列项入队之前,`send()` 会检查 agent 状态,并为内容和解析后的来源创建一份脱离调用方对象、经过深度冻结的快照。队列项入队之后,`send()` 发布 `agent/queued`。
|
||||
队列项入队之前,`send()` 会检查 agent 状态,并接受已有标识且经过深度冻结的消息。它会铸造一个仅属于本次入队的 `InboxItemId`,并发布 `agent/inbox/enqueue`;根据[可寻址队列操作](../feature/2026-07-29-addressable-queue-operations.md)决策,在驱动器认领或丢弃该项之前,这次待处理入队始终可以被寻址。
|
||||
|
||||
如果消息 A、B 都进入处理,B 的轮次只能在 A 记录 `turn/end` 且 A 的持久性检查点处理结束后开始。因此,B 的请求能看到 A 在同一会话日志中留下的已关闭结果。检查点错误会照常报告,但处理结束只表示解除这道顺序屏障,不表示失败的写入已经持久化。广义 `cancel()`、dispose(资源释放)或 `turn/start` 之前的失败也可能丢弃尚未启动的队列项,而不打开一个空轮次。
|
||||
|
||||
@@ -40,6 +40,6 @@ Status: implemented
|
||||
|
||||
## 后果
|
||||
|
||||
普通轮次的边界可预测:消息 A、B 始终分开,B 只能在 A 关闭并到达检查点后运行。调用方仍然拿不到逐次 send 的完成或取消句柄;广义取消可以丢弃整个尚未启动的队尾,状态和静止性也仍是面向整个 agent 的观察。
|
||||
普通轮次的边界可预测:消息 A、B 始终分开,B 只能在 A 关闭并到达检查点后运行。调用方仍然拿不到逐次 send 的完成句柄;待处理项可通过其仍有效的 `InboxItemId` 移除,广义取消可以丢弃整个尚未启动的队尾,而状态与完全停稳仍是面向整个 agent 的观察。
|
||||
|
||||
代价是模型请求和检查点都会增加。繁忙队列可能需要更长时间才能清空;如果生产方持续提交消息,队列也可能增长。只有建立显式且经过测量的契约后,才能重新引入普通 send 批处理。
|
||||
|
||||
Reference in New Issue
Block a user