refactor(schedule): keep reminder delivery conversational
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/simplification/2026-07-27-intent-named-subagent-continuation-operations.md
|
||||
2026-07-27-intent-named-subagent-continuation-operations.md: 4175e6d593e066033f3796357c6f0aefd767c588
|
||||
2026-07-27-intent-named-subagent-continuation-operations.zh.md: 6aeb036153a7d32ee61f2c08caf56273aa062ade
|
||||
2026-07-27-intent-named-subagent-continuation-operations.md: e74d62b7582e92f8e5ce68327a677259c8453d24
|
||||
2026-07-27-intent-named-subagent-continuation-operations.zh.md: ae7b370441d8e0ee045f4d0fcf851d28d055b295
|
||||
|
||||
@@ -18,7 +18,7 @@ The durability boundary also exposed both `SessionStore.flush()` and `flushRequi
|
||||
|
||||
Caller and provider requests are distinct. `SubagentStartRequest` contains caller-supplied one-shot data; `ResolvedSubagentStartRequest` adds the service-resolved descriptor before `SubagentProvider.start()`. For continuable creation, the manager passes a `ContinuableCreateRequest` to optional `SubagentProvider.prepareContinuable()` and receives detached creation data only. `SubagentService.resume()` and provider resume dispatch are absent: the continuation manager loads the descriptor, authorizes the parent, and owns Agent materialization, prompt delivery, cold resume, and teardown.
|
||||
|
||||
`SessionStore.flush(session)` is the single durability barrier and returns `Promise<boolean>`. Every scoped listener settles; a listener returns literal `true` only when it completed durability work. The call resolves `true` when at least one listener gives that acknowledgement, resolves `false` when none does, and rejects with the first registered listener failure after all listeners settle. The acknowledgement does not identify a selected persistence backend when several listeners are present. Ordinary checkpoints may ignore the boolean; the continuation manager also treats its final flush as a best-effort barrier, deliberately ignores it, logs rejection, and still disposes the child and releases ownership.
|
||||
`SessionStore.flush(session)` is the single durability barrier and returns `Promise<boolean>`. It resolves `true` after at least one scoped listener participates successfully, resolves `false` for an empty listener snapshot, and rejects with the first registered listener failure after all listeners settle. Participation cannot identify whether a selected persistence backend stored the state. Ordinary checkpoints may ignore the boolean; the continuation manager also treats its final flush as a best-effort barrier, deliberately ignores participation, logs rejection, and still disposes the child and releases ownership.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
@@ -26,7 +26,7 @@ Caller and provider requests are distinct. `SubagentStartRequest` contains calle
|
||||
|
||||
**Keep `sendMessage` on the service.** The model tool sends a message, but the service operation represents a follow-up that may steer or cold-resume. `followup` aligns with the structural `Agent` interface and does not promise a particular route.
|
||||
|
||||
**Keep `flushRequired()`.** A second method hides only a missing-durability-acknowledgement check. Returning that acknowledgement from the existing barrier keeps dispatch in one implementation and lets each caller state whether absence is acceptable.
|
||||
**Keep `flushRequired()`.** A second method hides only an empty-listener check. Returning participation from the existing barrier keeps dispatch in one implementation and lets each caller state whether absence is acceptable.
|
||||
|
||||
**Fold ordinary and continuable starts together.** A flag would make one method return either an awaited holder-owned one-shot run or immediate durable child and message identities. Separate intent methods preserve the ownership and timing distinction without a return union.
|
||||
|
||||
@@ -34,5 +34,5 @@ Caller and provider requests are distinct. `SubagentStartRequest` contains calle
|
||||
|
||||
- The Cordis service catalog contains only caller operations; a provider can opt into continuable first creation through `SubagentProvider.prepareContinuable?()` without receiving Agent lifecycle authority or a public resume operation.
|
||||
- Follow-up source and cancellation travel in one options object, matching the intent-helper shape on `Agent` while retaining the existing live-delivery and cold-resume semantics.
|
||||
- Session durability has one barrier operation. Its explicit durability acknowledgement remains observable, but no continuable-child path depends on which backend supplied it.
|
||||
- Session durability has one barrier operation. Its participation result remains observable, but no continuable-child path treats arbitrary listener participation as proof that a persistence backend stored the state.
|
||||
- The `send_message` and `report` schemas, accepted message identities, `AgentHandle` ownership, durable event vocabulary, and model-visible transcript follow the activation-based realization linked above.
|
||||
|
||||
@@ -18,7 +18,7 @@ Status: implemented
|
||||
|
||||
调用方请求与提供方请求相互分离。`SubagentStartRequest` 包含调用方提供的 one-shot 数据;`ResolvedSubagentStartRequest` 会在调用 `SubagentProvider.start()` 前加入由服务解析的描述符。创建可继续 child 时,管理器将 `ContinuableCreateRequest` 传给可选的 `SubagentProvider.prepareContinuable()`,且只接收分离的创建数据。`SubagentService.resume()` 与提供方恢复分发均不存在:继续执行管理器加载描述符、对 parent 进行鉴权,并负责 Agent 实体化、提示词投递、冷恢复与 teardown。
|
||||
|
||||
`SessionStore.flush(session)` 是唯一的持久性屏障,并返回 `Promise<boolean>`。所有作用域内 listener 都会结算;只有在完成持久化工作后,listener 才返回字面量 `true`。至少一个 listener 给出该确认时,调用解析为 `true`;没有 listener 确认时解析为 `false`;所有 listener 结算后,如有失败,则以注册顺序最靠前的错误拒绝。当存在多个 listener 时,该确认不会标识具体由哪个持久化后端提供。普通检查点可以忽略该布尔值;继续执行管理器同样将最终 flush 视为 best-effort 屏障,有意忽略它,记录拒绝日志,并仍会对 child 执行 dispose(资源释放)并释放所有权。
|
||||
`SessionStore.flush(session)` 是唯一的持久性屏障,并返回 `Promise<boolean>`。至少一个作用域内监听器成功参与后,它解析为 `true`;监听器快照为空时解析为 `false`;所有监听器结算后,如有失败,则以注册顺序最靠前的监听器错误拒绝。参与结果无法表明所选的持久化后端是否已经存储状态。普通检查点可以忽略该布尔值;继续执行管理器同样将最终 flush 视为 best-effort 屏障,有意忽略参与结果,记录拒绝日志,并仍会对 child 执行 dispose(资源释放)并释放所有权。
|
||||
|
||||
## 已考虑的替代方案
|
||||
|
||||
@@ -26,7 +26,7 @@ Status: implemented
|
||||
|
||||
**在服务上保留 `sendMessage`。** 面向模型的工具发送消息,但服务操作表达的是后续操作,既可能对运行中的激活执行 steering,也可能从持久化存储恢复。`followup` 与结构化 `Agent` 接口保持一致,也不承诺特定路由。
|
||||
|
||||
**保留 `flushRequired()`。** 第二个方法只封装了缺少持久化确认的检查。由现有屏障返回该确认,可以让分发只保留一套实现,并让每个调用方自行判定缺少确认是否可接受。
|
||||
**保留 `flushRequired()`。** 第二个方法只封装了空监听器检查。由现有屏障返回是否有监听器参与,可以让分发只保留一套实现,并让每个调用方自行判定缺少监听器是否可接受。
|
||||
|
||||
**合并普通启动与可继续启动。** 一个标志会让同一方法要么等待由持有方负责的 one-shot run 就绪后返回,要么立即返回持久化 child 与消息标识。按意图拆分的方法无需返回值联合类型即可保留所有权与时序差异。
|
||||
|
||||
@@ -34,5 +34,5 @@ Status: implemented
|
||||
|
||||
- Cordis 服务目录只包含调用方操作;提供方可以通过 `SubagentProvider.prepareContinuable?()` 选择参与可继续 child 的首次创建,但不会获得 Agent 生命周期权限或公开恢复操作。
|
||||
- 后续操作的来源与取消信号通过同一个选项对象传递,与 `Agent` 上按意图命名的辅助方法形态一致,同时保留在线投递与从持久化存储恢复的语义。
|
||||
- 会话持久性只有一个屏障操作。显式持久化确认仍可观测,但任何可继续 child 路径都不依赖由哪个后端提供确认。
|
||||
- 会话持久性只有一个屏障操作。参与结果仍可观测,但任何可继续 child 路径都不会将任意监听器参与视为持久化后端已存储状态的证明。
|
||||
- `send_message` 与 `report` schema、已接受的消息标识、`AgentHandle` 所有权、持久化事件词汇与模型可见的 transcript(文本记录)遵循上文链接的基于 Activation 的实现。
|
||||
|
||||
@@ -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/simplification/2026-08-09-conversational-schedule-delivery.md
|
||||
2026-08-09-conversational-schedule-delivery.md: ee58ae25abf125ed5507f3cd27ee2ba09b1711ec
|
||||
2026-08-09-conversational-schedule-delivery.zh.md: 15fe0d1bba2590119b1457fc0a8437a40f7d75d2
|
||||
@@ -0,0 +1,39 @@
|
||||
# Agent Note: Conversational Schedule delivery
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-08-09-conversational-schedule-delivery.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
Schedule already delivers a due reminder by queuing a normal Agent follow-up. A second durable Web receipt represented the same occurrence through a Schedule projection, a persistence-success event, Host history and live sidecars, client same-sequence upgrades, a generic event-view slot, and a dedicated renderer. That path spread one feature's confirmation UI across Session, persistence, Host, client runtime, conversation UI, and an extra package.
|
||||
|
||||
The receipt also created a second meaning of delivery. It remained visible when the model turn failed, while the conversation itself contained no successful reminder answer. Users need the scheduled conversation to continue; they do not need a separate durable badge proving that an internal dispatch was attempted.
|
||||
|
||||
## Decision
|
||||
|
||||
A due reminder waits for the Agent's idle maintenance phase and calls `followup()`. The follow-up starts a normal later turn and appears through the ordinary conversation transcript; Schedule never calls `steer()` and never interrupts the current turn.
|
||||
|
||||
`schedule/change` remains the only durable Schedule state. Its dispatch operation records that the follow-up was synchronously queued, which prevents ordinary restart replay after the dispatch is durable. Dispatch does not claim model success, user acknowledgement, or an external notification. The narrow crash interval between enqueue and durable dispatch remains at-least-once.
|
||||
|
||||
Schedule exposes no presentation projection, Host sidecar, browser event node, keyed event slot, or client renderer. Session persistence retains its shared `flush()` contract and has no Schedule-driven success event. The opt-in Web overlay loads only `@deepseek-ai/dsh-tool-schedule`.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the commit-aware receipt.** It could prove that a dispatch reached persistence even when the model failed, but that is an implementation outcome rather than the user's reminder. Its cross-component protocol and late same-sequence merge logic are disproportionate to that value.
|
||||
|
||||
**Render raw `schedule/change` events in the conversation.** This avoids a domain card but still exposes internal state transitions as user-facing messages and requires generic non-surface event presentation machinery solely for Schedule.
|
||||
|
||||
**Treat dispatch as successful reminder delivery.** The dispatch precedes the model request and cannot establish that an assistant answer exists or was read. Naming it delivery would overstate the durable fact.
|
||||
|
||||
**Steer the current turn when a reminder becomes due.** Steering changes the in-progress request path and lets timing interrupt unrelated work. Waiting for full idle and using `followup()` preserves one reminder per ordinary later turn.
|
||||
|
||||
## Verification
|
||||
|
||||
Package lifecycle tests pin idle waiting, maintenance ownership, follow-up-before-dispatch ordering, synchronous enqueue failure, model-independent dispatch, and restart replay. The assembled Web scenario snapshots the resulting assistant row and asserts that a persisted Schedule dispatch has no special history view. Source and dependency audits reject the removed presentation symbols, event, sidecar, slot, renderer package, and overlay entry.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Schedule is contained in its package plus ordinary composition and catalog wiring; Session, persistence, Host, client runtime, and conversation UI carry no Schedule-specific behavior.
|
||||
- Users see the reminder only through the conversation's normal model response. A failed model turn remains a failed turn rather than a contradictory success receipt.
|
||||
- Consumers that need external or acknowledged delivery require a different product boundary with its own notification and acknowledgement semantics.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Agent Note: 对话式 Schedule 交付
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-08-09-conversational-schedule-delivery.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
Schedule 已经通过将普通的 agent(智能体)后续轮次排入队列来交付到期提醒。第二条持久 Web 回执通过 Schedule 投影、持久化成功事件、Host 历史记录与 live 伴随数据、客户端同序号升级、通用事件视图 slot 和专用渲染器表示同一次提醒触发。这条路径把一项功能的确认 UI 分散到会话、持久化、Host、客户端运行时、对话 UI 和一个额外包中。
|
||||
|
||||
该回执还让「交付」有了第二种含义。即使模型轮次失败,它仍然可见,而对话本身没有成功的提醒答复。用户需要定时对话继续进行;他们不需要一枚单独的持久标记来证明内部 dispatch 已经尝试过。
|
||||
|
||||
## 决策
|
||||
|
||||
到期提醒会等待 agent 的 idle maintenance phase,再调用 `followup()`。该操作会在稍后开启一个普通轮次,并通过普通对话 transcript(文本记录)显示;Schedule 绝不会调用 `steer()`,也绝不会中断当前轮次。
|
||||
|
||||
`schedule/change` 仍是唯一持久 Schedule 状态。其 dispatch 操作记录后续轮次已同步入队,这会在 dispatch 持久化后阻止普通的重启回放。dispatch 不表示模型成功、用户确认或外部通知。入队与持久 dispatch 之间的狭窄崩溃窗口仍保留至少一次语义。
|
||||
|
||||
Schedule 不公开呈现投影、Host 伴随数据、浏览器事件节点、按事件键控的 slot 或客户端渲染器。会话持久化保留共享的 `flush()` 约定,且不存在由 Schedule 驱动的成功事件。显式启用的 Web overlay 只加载 `@deepseek-ai/dsh-tool-schedule`。
|
||||
|
||||
## 已考虑的替代方案
|
||||
|
||||
**保留提交感知回执。** 即使模型失败,它也可以证明 dispatch 已到达持久化,但这是实现结果,而不是用户的提醒。其跨组件协议与后到的同序号合并逻辑,与这点价值不成比例。
|
||||
|
||||
**在对话中渲染原始 `schedule/change` 事件。** 这样可以避免领域卡片,但仍会把内部状态转换暴露为面向用户的消息,而且仅为 Schedule 就需要通用的内部事件呈现机制。
|
||||
|
||||
**把 dispatch 当作提醒已成功交付。** dispatch 发生在模型请求之前,无法证明 assistant 答复存在或已被读取。将其称为交付会夸大持久事实。
|
||||
|
||||
**提醒到期时中途引导当前轮次。** 中途引导会改变进行中的请求路径,并让定时触发中断无关工作。等待完全 idle 后使用 `followup()`,可让每条提醒分别进入一个普通的后续轮次。
|
||||
|
||||
## 验证
|
||||
|
||||
包生命周期测试固定 idle 等待、maintenance 所有权、后续轮次先于 dispatch 的顺序、同步入队失败、与模型无关的 dispatch 和重启回放。组装后的 Web 场景为产生的 assistant 行生成快照,并断言已持久化的 Schedule dispatch 没有特殊 history view。源码与依赖审计会拒绝残留的已移除呈现符号、事件、sidecar、slot、渲染器包与 overlay 配置项。
|
||||
|
||||
## 后果
|
||||
|
||||
- Schedule 的实现仅涉及其自身包、常规组合与目录接线;会话、持久化、Host、客户端运行时和对话 UI 不携带 Schedule 专属行为。
|
||||
- 用户只能通过对话中的普通模型响应看到提醒。失败的模型轮次仍是失败轮次,不会出现与之矛盾的成功回执。
|
||||
- 需要外部交付或交付确认的消费方必须采用另一条产品边界,并由其拥有自己的通知和确认语义。
|
||||
Reference in New Issue
Block a user