fix inbox lifecycle downstream contracts

This commit is contained in:
_Kerman
2026-07-31 22:00:39 +08:00
parent 8e88b17c9f
commit afedf18ccf
219 changed files with 5660 additions and 4556 deletions
+22 -30
View File
@@ -6,9 +6,7 @@
## 概览
每个 harness 都是 [Cordis](cordis-primer.md) 上下文;各包(package)贡献服务、类型化事件和可释放的注册项。
`packages/core/` 汇集默认的 agent(智能体)流程;各项功能仍以插件形式存在。
每个 harness 都是 [Cordis](cordis-primer.md) 上下文;各包(package)贡献服务、类型化事件和可释放的注册项。`packages/core/` 汇集默认流程;各项功能仍以插件形式存在。
### 默认服务
@@ -18,7 +16,7 @@
| `ctx.sessions` | `dsh-session` | 内存中的事件溯源会话 |
| `ctx.systemPrompt` | `dsh-system-prompt` | 有序提示词片段、工具 schema 和变量 |
| `ctx.tools` | `dsh-tools` | 工具注册表和[执行流水线](tool-execution-pipeline.md) |
| `ctx.agents` | `dsh-agent` | 活跃 agent、委托创建、`agent/*` 事件、进程内发起方作用域 |
| `ctx.agents` | `dsh-agent` | 活跃 agent(智能体)、委托创建、`agent/*` 事件、进程内发起方作用域 |
| `ctx.agentLoop` | `dsh-agent-loop` | 实体 `Agent` 驱动器 |
### 功能服务
@@ -59,8 +57,8 @@
### 事件域
- **会话事件**是通过 `session/event` 发出的持久日志事实。
- **Agent 事件**携带活跃 `Agent`,用于 inbox 通知、步骤进入、状态、请求塑形、验证和续跑。
- **功能事件**让所属服务边界无需导入循环即可附加策略和适配器。
- **Agent 事件**携带活跃 `Agent`,用于 inbox、步骤、状态、请求、验证和续跑。
- **功能事件**无需导入循环即可附加策略和适配器。
### 拦截语义
@@ -68,9 +66,9 @@ waterfall(瀑布式事件)是环绕中间件:监听器通过 `next()` 委
## 默认循环生命周期
**会话**采用仅追加方式。普通**轮次**领取一条已排队的后续消息;注入不领取输入。后续轮次会等待前一轮次的检查点,但可以与其共用同一个 `running` 区间([决策](../.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md)。模型或插件停止轮次时,该轮次结束;一个**步骤**包含一次模型请求及其工具。[下文时序](agent-lifecycle.md)中的引号标记持久事件。
**会话**采用仅追加方式。一个**轮次**领取一条已排队的后续消息等待前一轮次的检查点,并可与其共用 `running` 区间([决策](../.agents/notes/implemented/simplification/2026-07-17-one-send-one-turn.md);注入不领取输入。一个**步骤**包含一次模型请求及其工具。[时序](agent-lifecycle.md)中的引号标记持久事件。
创建时若未提供 id,流程会生成 `<config-id>-session-<uuid>``sessionId` 用于恢复或创建会话,而 `resumeSessionId` 要求已有历史。恢复流程在发布前还原沿袭关系和委托深度初始化失败会发出 `agent-loop/config-start-failed`;拆卸过程保持静默
创建时若未提供 id,流程会生成 `<config-id>-session-<uuid>``sessionId` 用于恢复或创建会话,而 `resumeSessionId` 要求已有历史。恢复流程在发布前还原沿袭关系和委托深度初始化失败会发出 `agent-loop/config-start-failed`
### 轮次流程
@@ -81,31 +79,27 @@ choose declarative identity and fresh/resume path
-> enable driving -> agent/session-start(source) -> start driver
forever:
wait for waking inbox work
claim next-step input plus one next-turn message with a pure deletion splice
claim next-step input plus one next-turn message
-> emit agent/inbox/claimed({ message, turn }) for each claimed message
-> emit agent/status(running) if starting an interval
-> agent/pre-step(messages, { turn, step, signal })
reject or listener failure -> the claimed batch stays removed; stop the driver
enter:
'turn/start'
STEP loop:
step loop:
'step/start'
append the returned batch as separate 'user/message' events
assemble system prompt and tool schemas
snapshot the derived messages (the reconstruction boundary)
agent/request (config only) -> prepare adapter defaults/provenance under turn signal -> log request/header -> llm/stream (frozen, registration-bound)
'assistant/chunk'
'assistant/message'
assemble prompt and schemas -> snapshot derived messages
agent/request -> prepare adapter defaults/provenance -> request/header -> llm/stream
'assistant/chunk' -> 'assistant/message'
schedule tool calls by ctx.tools.executionMode:
exclusive -> barrier
parallel -> rolling pool, <= maxParallelToolCalls; reclassify-at-start; scheduler failure -> stop starts, drain dispatches
start -> 'tool/call' -> ordered tools/pre-execute -> concurrent tools/execute
parallel -> rolling pool, <= maxParallelToolCalls; reclassify at start
start -> 'tool/call' -> tools/pre-execute -> concurrent tools/execute
model-order result -> ordered tools/post-execute -> 'tool/result'
'step/end'
tools owe another request or next-step inbox is nonempty
-> claim next-step messages
-> agent/pre-step (messages may be empty for a tool continuation)
-> append the entered batch and continue
-> claim -> agent/pre-step -> append entered batch -> continue
otherwise agent/turn-stopping -> re-check the next-step inbox
'turn/end' -> agent/settled
start the next waking queued message, or emit agent/status(idle)
@@ -117,23 +111,21 @@ idle inject:
每个步骤都会组装有序提示词片段、工具 schema 和变量;未知引用会使该轮次失败。`dsh-system-prompt` 负责身份和角色设定;循环提供 `provider``model``cwd`[提示词归属](../.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md))。
`inject()` 始终将不会唤醒的上下文排入 `next-step`。collecting 或 running 驱动器会在最近的后续提示词边界领取它;idle 驱动器会让它保持待处理,直至 `followup()``steer()` 唤醒驱动器。工具执行后的 `additionalContexts` 会在对应工具结果之后进入同一个 next-step inbox
`inject()` 将不会唤醒驱动器的上下文排入 `next-step`;空闲驱动器会让它保持待处理,直至 `followup()``steer()` 唤醒。工具执行后的 `additionalContexts` 使用同一个 inbox。`agent/pre-step` 接收独占的已领取批次,以及即将使用的轮次、步骤和信号。拒绝则不进入步骤;进入则提供在 `step/start` 后追加的完整批次。空的工具续跑仍会经过 waterfall,其最终值一次性结算所有改写
`agent/pre-step` 接收已经从 inbox 删除的独占批次,并最终决定循环是否进入拟议步骤。它`PreStepContext` 携带准确的 turn、step 与取消 signal。`{ kind: 'reject' }` 不会打开步骤;`{ kind: 'enter', messages }` 提供在 `step/start` 后追加的完整批次。当工具 continuation 没有新领取的 inbox 输入时,批次为空,listener 仍可为当前步骤贡献上下文。waterfall 的全部改写只在最终返回的 `messages` 中一次性结算
裁剪先于摘要;溢出重试必须取得持久进展。`agent/request-error` 可以在步骤关闭前授权一次同步骤重试;取消优先,且重试会复用该步骤已冻结的 prompt assembly。适配器拥有的 `retryPolicy` 使 normal mode 保持有界;always mode 先委托专门恢复,再持续重试直至成功或取消([压缩](../.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md)、[重试基础](../.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md)、[提供方策略](../.agents/notes/implemented/feature/2026-07-24-provider-retry-policies.md))。
裁剪先于摘要;溢出重试必须取得持久进展。`agent/request-error` 可以授权使用冻结提示词进行同步骤重试;取消优先。适配器`retryPolicy` 使 normal mode 保持有界,always mode 则在专门恢复后重试([压缩](../.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md)、[重试基础](../.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md)、[提供方策略](../.agents/notes/implemented/feature/2026-07-24-provider-retry-policies.md)
### 失败边界
最终适配器选择、分发与迭代失败会在 loop 处理前成为终止 `finish { kind: 'error' | 'aborted', failure }` chunk`agent/request-error` 接收请求坐标、标准化 `LlmFailure`、可用时已准备注册项的重试策略以及信号;middleware 与消费方错误仍在请求恢复之外抛出。失败分片既不提交消息,也不提交工具调用。
适配器选择、分发与迭代失败会成为 error 或 aborted 类型的终止 `finish` 分片`agent/request-error` 接收请求坐标、标准化 `LlmFailure`、可用的重试策略信号;middleware 与消费方错误仍在恢复之外。失败分片既不提交消息,也不提交工具调用。
其他故障使用 `agent/error`取消和资源释放优先于恢复。在提交请求头之前,轮次信号会取消异步模型能力准备;尚未分派的工具会得到合成的 `tool/call`/`ABORTED_BEFORE_DISPATCH` 对。实际生效的 `cancel(cause)` 在清空队列和中止前发出原因;观察方不能否决;空闲调用不发事件。持久化层将用户或父级取消记录为 `aborted`,拆卸记录为 `disposed`;拆卸会等待完全停稳。原因只影响报告方式,不影响延迟完成的结果上下文处理[决策](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md))。
其他故障使用 `agent/error`取消和资源释放优先于恢复。在提交请求头之前,轮次信号会取消功能准备;尚未分派的工具会得到合成的 `tool/call`/`ABORTED_BEFORE_DISPATCH` 对。实际生效的 `cancel(cause)` 在清空队列和中止前报告原因;空闲调用不发事件。持久化层 `aborted` 区分取消,以 `disposed` 区分会等待完全停稳的拆卸[决策](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md))。
轮次和步骤事件均位于轮次边界内;loop 只会在轮次内从进入步骤的批次追加注入的 `user/message`。重新加载会用合成的轮次结束事件闭合中断尾部。关闭后仅由 `agent/error` 报告故障。每个轮次有一个 [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap)。
### Agent 句柄
`ctx.agents` 返回 `AgentHandle { agent, dispose() }`。插件用 `followup()``steer()``inject()` 驱动 agent`cancel()` 停止工作,而拆卸由需等待完成的 disposer 负责`followup()` 只会将一条带标识的消息排队:其 `MessageId` 跟踪持久 inbox 的插入、领取与丢弃通知,而不标识某个提示词特有的输出或轮次结束。`agent/status``whenIdle()` 描述整个 agent 的活动;只有显式拥有某个活动区间的调用方才能将该区间概括为一次运行结果([决策](../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。
`ctx.agents` 返回 `AgentHandle { agent, dispose() }`。插件用 `followup()``steer()``inject()` 驱动 agent`cancel()` 停止工作,需等待完成的 disposer 负责拆卸。后续消息的 `MessageId` 跟踪持久 inbox 的插入、领取与丢弃通知,而不标识提示词输出或轮次结束。`agent/status``whenIdle()` 描述整个 agent 的活动;只有区间所有方才能将概括为一次运行结果([决策](../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。
### Agent 作用域
@@ -147,9 +139,9 @@ idle inject:
**模型可见 ⟺ 已记录**`step/start` 时的消息与折叠后的 `request/header` 可以重建每个请求;该 header 还会标记适配器填入的默认值,使下一次提议可以丢弃这些值并解析所选路由,同时不丢失显式对话设置。该包的 `dsh-agent-loop/invariant` 可通过 `ctx.invariants` 断言可重建性([可重建性](../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md))。
持久性由插件负责。后端会尽快排空同步的 `session/event` 通知。`session/flush` 屏障位于每次请求与顶层工具分发之前,并在 `turn/end` 之后、处理另一个已排队轮次或观察到空闲状态之前执行。`SessionPersistence` 直接存储 `SessionEvent`,并将元数据存入 `SessionHeader`;JSONL 默认采用带校验和的 Zstandard,SQLite 遵循同一契约([决策](../.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md))。
持久性由插件负责。后端会尽快排空同步的 `session/event` 通知。`session/flush` 位于请求与顶层工具分发之前,并在 `turn/end` 之后、另一个轮次或空闲状态之前执行。`SessionPersistence` 存储事件和 header 元数据;JSONL 默认采用带校验和的 Zstandard,SQLite 遵循同一契约([决策](../.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md))。
纯日志事件可以位于轮次之间。事件所有方通过 `Session` 追加,仅为持久性而刷写。`session/title` 依赖尽快持久化与生命周期排空。最新标题按后写覆盖并携带来源信息;回退与提供方工作绝不会延迟响应。这类记录可作为 fork 边界,因此 fork 会继承标题[决策](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md))。
纯日志事件可以位于轮次之间。事件所有方通过 `Session` 追加,仅为持久性而刷写。最新的 `session/title` 携带来源信息并按后写覆盖,且不会延迟响应;标题记录可作为 fork 边界([决策](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md))。
### 模型内容
@@ -165,7 +157,7 @@ idle inject:
例外情况包括 LLM(大语言模型)合并接口和消费方、文件系统整合策略、web 使用注册表、skill 和 subagent 使用具名提供方。subagent 可以通过 spawn 创建全新实例、fork 一个已完成轮次的前缀,或使用 ACPAgent Client Protocol)子 agent[subagent.md](core-data-structures/subagent.md))。
`dsh-workspace-context` 在第一次 `agent/pre-step` 将基线直接 prepend 到 next-step inbox替换仍在等待的前序消息;它通过 `tools/post-execute` 追加 `ctx.fs` 发现的变更。其[决策](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)记录隔离方式。由于领取发生在 pre-step 之前,该基线可能赶不上当前请求`dsh-paths` 负责共享路径。
`dsh-workspace-context` 在第一次 `agent/pre-step` 将基线 prepend 到 next-step inbox,替换仍在等待的前序消息,并在工具执行后添加 `ctx.fs` 变更。由于领取发生在 pre-step 之前,该基线可能赶不上当前请求([决策](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)`dsh-paths` 负责共享路径。
### 组合包与应用