refactor(agent): clarify turn lifecycle event names

This commit is contained in:
_Kerman
2026-07-27 17:38:42 +08:00
parent 7d5cc498d3
commit 52174e32cb
67 changed files with 180 additions and 176 deletions

View File

@@ -51,7 +51,7 @@ sequenceDiagram
end
Driver->>Session: post-tool context and steering (no prompt-submit)
Driver->>Session: <code>step/end</code>
Driver->>Hooks: <code>agent/stopping</code> serial terminal checkpoint
Driver->>Hooks: <code>agent/turn-stopping</code> serial terminal checkpoint
end
Driver->>Session: <code>turn/end</code>
Driver-->>SDK: <code>agent/status</code> idle

View File

@@ -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 docs/architecture.md
architecture.md: 16565546387882aa9af34beb1e275e3177460161
architecture.zh.md: ce5cb31dd8fceb9c6a6bb91ebb0ebfafadd273f7
architecture.md: e293d3f3996210fd6b4d9784379eef58cd6c9f0b
architecture.zh.md: 9760511e979fa9bb20b812fbbbefcefa09565c55

View File

@@ -99,8 +99,8 @@ forever:
drain accepted tool context and steering
'step/end'
continue for tools or steering unless a result concluded the turn
otherwise agent/stopping -> drain -> continue only for steering
'turn/end' -> agent/idle
otherwise agent/turn-stopping -> drain -> continue only for steering
'turn/end' -> agent/settled
start the next waking queued message, or emit agent/status(idle)
idle inject:
@@ -176,7 +176,7 @@ New behavior attaches to a documented extension point; a loop change updates thi
| Add background work | register on `ctx.tasks`; generic `task_*` tools collect or stop it |
| Add filesystem access or policy | implement a `ctx.fs` provider or listen to `fs/*` policy events |
| Confine spawned processes | use a `ctx.sandbox` backend; consumers wrap argv before spawning |
| Intercept a request, tool, or turn | use its `agent/*` or `tools/*` event; `agent/stopping` is the stop boundary |
| Intercept a request, tool, or turn | use its `agent/*` or `tools/*` event; `agent/turn-stopping` is the stop boundary |
| Add model-facing context | call `agent.inject()` to append a sourced `user/message` without a turn |
| Add UI or editor integration | drive `ctx.agents`, render from `session/event`; terminal-only overlays use `ctx.tui` |
| Add durable session state | extend `SessionEventMap`; render and replay from the log |

View File

@@ -99,8 +99,8 @@ forever:
drain accepted tool context and steering
'step/end'
continue for tools or steering unless a result concluded the turn
otherwise agent/stopping -> drain -> continue only for steering
'turn/end' -> agent/idle
otherwise agent/turn-stopping -> drain -> continue only for steering
'turn/end' -> agent/settled
start the next waking queued message, or emit agent/status(idle)
idle inject:
@@ -176,7 +176,7 @@ idle inject:
| 添加后台工作 | 在 `ctx.tasks` 上注册;通用 `task_*` 工具负责收集或停止 |
| 添加文件系统访问或策略 | 实现 `ctx.fs` 提供方,或监听 `fs/*` 策略事件 |
| 限制生成的进程 | 使用 `ctx.sandbox` 后端;消费方在生成前包装 argv |
| 拦截请求、工具或轮次 | 使用相应的 `agent/*``tools/*` 事件;`agent/stopping` 是停止边界 |
| 拦截请求、工具或轮次 | 使用相应的 `agent/*``tools/*` 事件;`agent/turn-stopping` 是停止边界 |
| 添加模型可见上下文 | 调用 `agent.inject()`,追加带来源的 `user/message`,但不创建轮次 |
| 添加 UI 或编辑器集成 | 驱动 `ctx.agents`,从 `session/event` 渲染;仅终端浮层使用 `ctx.tui` |
| 添加持久会话状态 | 扩展 `SessionEventMap`;从日志渲染和回放 |

View File

@@ -1,6 +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
extension-cookbook.md: 3ef9e6e7d7117d76f20da274a0f1d689497df441
extension-cookbook.zh.md: 575c8d5e543fe578feb925cec41f6704dbc2797c
# pnpm run verify-translation-pairing --write docs/cookbook/extension-cookbook.md
extension-cookbook.md: 51a87be037ddbf6d031d3607da7b70470087334c
extension-cookbook.zh.md: 389ac87be0a1cd14a7646374909d79e6e00d8b56

View File

@@ -100,7 +100,7 @@ Every product feature maps to a listener on a documented extension seam — the
| Product feature | Plugin mechanism |
|---|---|
| Hook system (user + project level) | listeners on `agent/session-start`, `agent/prompt-submit`, `agent/request`, `tools/pre-execute`, `tools/post-execute`, and `agent/stopping`; the waterfall seams return typed decisions, while `agent/stopping` may steer another step; the `dsh-hooks-claude` / `dsh-hooks-codex` bridges map hook config files onto these seams |
| Hook system (user + project level) | listeners on `agent/session-start`, `agent/prompt-submit`, `agent/request`, `tools/pre-execute`, `tools/post-execute`, and `agent/turn-stopping`; the waterfall seams return typed decisions, while `agent/turn-stopping` may steer another step; the `dsh-hooks-claude` / `dsh-hooks-codex` bridges map hook config files onto these seams |
| `/goal` | `ctx.goals` owns durable state, `dsh-goal-session` schedules same-session rounds through the public `Agent`, and separate command/tool producers expose human/model control |
| `/loop` | on the `turn/end` session event, `followup()` the next iteration; or force-continue |
| Dynamic workflow | `ctx.workflows` + the worker-thread engine + the `workflow` tool; structured in-process children enforce output with scoped prompt/tool registrations, a monotonic tool guard, final `tools/result` commit (including enclosing `run_code`), and the structured-output execution's monotonic `concludeTurn()` marker |

View File

@@ -100,7 +100,7 @@ export function apply(ctx: Context) {
| 产品功能 | 插件机制 |
|---|---|
| 钩子系统(用户级 + 项目级) | `agent/session-start``agent/prompt-submit``agent/request``tools/pre-execute``tools/post-execute``agent/stopping` 上的监听器waterfall seam 返回类型化决策,`agent/stopping` 则可通过 steering 触发下一步;`dsh-hooks-claude` / `dsh-hooks-codex` 桥接器将钩子配置文件映射到这些 seam 上 |
| 钩子系统(用户级 + 项目级) | `agent/session-start``agent/prompt-submit``agent/request``tools/pre-execute``tools/post-execute``agent/turn-stopping` 上的监听器waterfall seam 返回类型化决策,`agent/turn-stopping` 则可通过 steering 触发下一步;`dsh-hooks-claude` / `dsh-hooks-codex` 桥接器将钩子配置文件映射到这些 seam 上 |
| `/goal` | `ctx.goals` 管理持久状态,`dsh-goal-session` 通过公共 `Agent` 调度同会话回合,独立的命令/工具生产方分别提供人类/模型控制 |
| `/loop` | 在 `turn/end` 会话事件上 `followup()` 下一次迭代;或强制继续 |
| 动态工作流 | `ctx.workflows` + worker-thread 引擎 + `workflow` 工具;结构化的进程内子任务通过作用域化的 prompt/工具注册、单调工具守卫、最终 `tools/result` 提交(包括外层 `run_code`)和结构化输出执行的单调 `concludeTurn()` 标记来强制输出 |

View File

@@ -98,31 +98,6 @@ Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structu
Source: [`packages/core/agent/src/types.ts:405`](../../packages/core/agent/src/types.ts)
### `agent/idle` — emit
One drain chain reached its terminal turn: that turn's `turn/end` is already committed. Automatically recovered failed turns do not emit this notification, and neither does a run that aborts or fails before its `turn/start` commits — there is no durable turn to settle against. `reason` says why; model-request recovery is exhausted when an error reaches it.
```ts cordis-catalog
/**
* One drain chain reached its terminal turn: that turn's `turn/end` is
* already committed. Automatically recovered failed turns do not emit this
* notification, and neither does a run that aborts or fails before its
* `turn/start` commits — there is no durable turn to settle against.
* `reason` says why; model-request recovery is exhausted when an error
* reaches it.
* @param agent - the agent whose turn closed.
* @param turn - the terminal turn number.
* @param reason - why the terminal turn ended, with live error facts when it failed.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode emit
*/
'agent/idle'(this: Scoped<Agent>, agent: Agent, turn: number, reason: IdleReason): void
```
Types: [Agent](../core-data-structures/core.md) · [IdleReason](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:392`](../../packages/core/agent/src/types.ts)
### `agent/inbox/dequeue` — emit
The driver claimed one item out of the inbox: a queued item at a turn boundary, or steering drained between steps. Fires after the item leaves its FIFO and before it becomes a durable message.
@@ -282,6 +257,31 @@ Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structu
Source: [`packages/core/agent/src/types.ts:307`](../../packages/core/agent/src/types.ts)
### `agent/settled` — emit
One drain chain reached its terminal turn: that turn's `turn/end` is already committed. Automatically recovered failed turns do not emit this notification, and neither does a run that aborts or fails before its `turn/start` commits — there is no durable turn to settle against. `reason` says why; model-request recovery is exhausted when an error reaches it.
```ts cordis-catalog
/**
* One drain chain reached its terminal turn: that turn's `turn/end` is
* already committed. Automatically recovered failed turns do not emit this
* notification, and neither does a run that aborts or fails before its
* `turn/start` commits — there is no durable turn to settle against.
* `reason` says why; model-request recovery is exhausted when an error
* reaches it.
* @param agent - the agent whose turn closed.
* @param turn - the terminal turn number.
* @param reason - why the terminal turn ended, with live error facts when it failed.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode emit
*/
'agent/settled'(this: Scoped<Agent>, agent: Agent, turn: number, reason: SettleReason): void
```
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [SettleReason](../core-data-structures/core.md)
Source: [`packages/core/agent/src/types.ts:392`](../../packages/core/agent/src/types.ts)
### `agent/status` — emit
Agent status changed (`idle` ⇄ `running`). `send()` does not enter `running` synchronously; drive lifecycle from this event.
@@ -326,7 +326,7 @@ Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structu
Source: [`packages/core/agent/src/types.ts:335`](../../packages/core/agent/src/types.ts)
### `agent/stopping` — serial
### `agent/turn-stopping` — serial
The turn is about to close: the model owes no response (no live tool calls, no fresh steering). Awaited before the boundary commits — a listener that objects steers (`agent.steer(...)`) and the machine re-reads its inbox: fresh steering runs another step, none closes the turn. Data decides, so listener order cannot change the outcome. The inverse control (stop a tool loop early) is data too: a tool result carrying `concludesTurn` ends the turn at its step.
@@ -345,7 +345,7 @@ The turn is about to close: the model owes no response (no live tool calls, no f
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode serial
*/
'agent/stopping'(this: Scoped<Agent>, agent: Agent, turn: number, signal: AbortSignal): Promise<void> | void
'agent/turn-stopping'(this: Scoped<Agent>, agent: Agent, turn: number, signal: AbortSignal): Promise<void> | void
```
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)

View File

@@ -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 docs/core-data-structures/core.md
core.md: 8dcc08b0ca4524c8cc18c8585ce07701de17cc93
core.zh.md: ec2c2462446d1df8f286b7bacebff394c5e80638
core.md: bc4288872a9b1e47a82c86b095c13ae9defc957b
core.zh.md: abd1b7bd0aa5563e86cf652db354ad30e4fb74cc

View File

@@ -615,7 +615,7 @@ type PromptDecision =
type RequestError = Error & { code?: string }
```
`agent/step` is the single serial boundary before request derivation. `agent/stopping` runs when a turn has no tool or steering continuation, before one final steering drain.
`agent/step` is the single serial boundary before request derivation. `agent/turn-stopping` runs when a turn has no tool or steering continuation, before one final steering drain.
`agent/session-start` carries a `SessionStartSource` (why the session lifecycle began; a bridge keys its SessionStart matcher on it):

View File

@@ -623,7 +623,7 @@ type PromptDecision =
type RequestError = Error & { code?: string }
```
`agent/step` 是请求推导前唯一的串行边界。`agent/stopping` 在轮次没有工具或 steering中途引导后续时运行先于最后一次 steering 排空。
`agent/step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering中途引导后续时运行先于最后一次 steering 排空。
`agent/session-start` 携带 `SessionStartSource`(会话生命周期为何开始;桥接层据此匹配其 SessionStart

View File

@@ -12,7 +12,6 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:236`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`goal-session`](../packages/goal/goal-session), [`tui`](../packages/ui/tui) |
| `agent/disposed` | `emit` | [`packages/core/agent/src/types.ts:245`](../packages/core/agent/src/types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`agent-loop`](../packages/core/agent-loop), [`goal-session`](../packages/goal/goal-session), [`tui`](../packages/ui/tui) |
| `agent/error` | `emit` | [`packages/core/agent/src/types.ts:405`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `apiproxy`, [`goal-session`](../packages/goal/goal-session), [`tui`](../packages/ui/tui) |
| `agent/idle` | `emit` | [`packages/core/agent/src/types.ts:392`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`compact-basic`](../packages/compact/compact-basic), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/inbox/dequeue` | `emit` | [`packages/core/agent/src/types.ts:272`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`agent`](../packages/core/agent), `apiproxy`, [`tui`](../packages/ui/tui) |
| `agent/inbox/discard` | `emit` | [`packages/core/agent/src/types.ts:284`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`agent`](../packages/core/agent), `apiproxy`, [`tui`](../packages/ui/tui) |
| `agent/inbox/enqueue` | `emit` | [`packages/core/agent/src/types.ts:262`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`agent`](../packages/core/agent), `apiproxy`, [`goal-session`](../packages/goal/goal-session) |
@@ -20,9 +19,10 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:348`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`agent`](../packages/core/agent) |
| `agent/request-error` | `waterfall` | [`packages/core/agent/src/types.ts:363`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/session-start` | `emit` | [`packages/core/agent/src/types.ts:307`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal`](../packages/goal/goal), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`workspace-context`](../packages/context/workspace-context) |
| `agent/settled` | `emit` | [`packages/core/agent/src/types.ts:392`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`compact-basic`](../packages/compact/compact-basic), [`llm-retry`](../packages/llm/llm-retry) |
| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:254`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`agent`](../packages/core/agent), `apiproxy`, [`goal-session`](../packages/goal/goal-session), [`tui`](../packages/ui/tui) |
| `agent/step` | `serial` | [`packages/core/agent/src/types.ts:335`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`compact-basic`](../packages/compact/compact-basic), [`plan-mode`](../packages/plan/plan-mode), [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy), [`time-context`](../packages/context/time-context), [`tool-skill`](../packages/skill/tool-skill), [`user-approval`](../packages/ui/user-approval), [`workspace-context`](../packages/context/workspace-context) |
| `agent/stopping` | `serial` | [`packages/core/agent/src/types.ts:378`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/types.ts:378`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex) |
| `approval/request` | `waterfall` | [`packages/ui/user-approval/src/index.ts:30`](../packages/ui/user-approval/src/index.ts) | [`user-approval`](../packages/ui/user-approval) (`waterfall`) | [`acp`](../packages/acp/acp) |
| `commands/change` | `emit` | [`packages/ui/commands/src/index.ts:103`](../packages/ui/commands/src/index.ts) | [`commands`](../packages/ui/commands) (`events.dispatch`) | `apiproxy`, [`tui`](../packages/ui/tui) |
| `domain/changed` | `emit` | [`packages/storage/storage-domain/src/events.ts:46`](../packages/storage/storage-domain/src/events.ts) | [`storage-domain`](../packages/storage/storage-domain) (`emit`) | `apiproxy`, [`storage-domain`](../packages/storage/storage-domain), [`workspace`](../packages/workspace/workspace) |