refactor agent pre-step inbox lifecycle

This commit is contained in:
_Kerman
2026-07-31 19:21:16 +08:00
parent c2ff9ddec8
commit fcc2b5e282
267 changed files with 2052 additions and 1546 deletions

View File

@@ -17,20 +17,22 @@ sequenceDiagram
participant Session
participant SDK as UI or SDK listener
User->>Agent: followup(content)
Agent-->>SDK: <code>agent/inbox/enqueue</code>
Agent-->>SDK: <code>agent/inbox/spliced</code>
Agent-->>SDK: <code>agent/inbox/inserted</code> { message }
Agent->>Driver: queued work wakes driver
Driver-->>SDK: <code>agent/status</code> running
Note over Agent,Driver: next-step acceptance window opens
Driver->>Hooks: <code>agent/prompt-submit</code> waterfall
Hooks-->>Driver: authoritative allow, block, or add context
alt prompt blocked or admission failed
Driver-->>Driver: append context-only batch or keep steering boundary pending
else prompt allowed
Note over Agent,Driver: claim pending next-step input plus one queued prompt
Driver-->>SDK: <code>agent/inbox/spliced</code> pure deletion
Driver-->>SDK: <code>agent/inbox/claimed</code> { message, turn } per message
Driver->>Hooks: <code>agent/pre-step</code> waterfall
Hooks-->>Driver: authoritative reject or enter(messages)
alt proposed step rejected or pre-step failed
Driver-->>Driver: claimed batch stays removed; no turn opens
else enter proposed step
Driver->>Session: <code>turn/start</code>
Driver->>Session: <code>user/message</code>
Driver->>Prompt: <code>system-prompt/assemble</code> waterfall
Driver-->>Driver: <code>agent/step</code> serial checkpoint
Driver->>Session: <code>step/start</code>
Driver->>Session: <code>user/message</code> per entered message
Driver->>Prompt: <code>system-prompt/assemble</code> waterfall
Driver->>LLM: <code>agent/request</code> waterfall, then <code>llm/stream</code> waterfall
LLM-->>Driver: StreamChunk*
Driver->>Session: <code>assistant/chunk</code>*
@@ -53,11 +55,17 @@ sequenceDiagram
Driver->>Session: <code>tool/result</code>
end
end
Driver->>Session: post-tool context and steering (no prompt-submit)
Driver->>Session: <code>step/end</code>
Driver->>Hooks: <code>agent/turn-stopping</code> serial terminal checkpoint
opt natural stop and next-step inbox empty
Driver->>Hooks: <code>agent/turn-stopping</code> serial terminal checkpoint
end
opt next-step input is pending
Driver-->>Driver: claim pending next-step input
Driver-->>SDK: <code>agent/inbox/claimed</code> { message, turn } per message
Driver->>Hooks: <code>agent/pre-step</code> waterfall
Hooks-->>Driver: authoritative reject or enter(messages)
end
end
Note over Agent,Driver: next-step acceptance window closes
Driver->>Session: <code>turn/end</code>
end
Driver-->>SDK: <code>agent/status</code> idle
@@ -65,9 +73,9 @@ sequenceDiagram
The `assistant/message` edge records every successful provider call, including content-less and `max-tokens` finishes. Empty content stays out of derived history while the durable anchor retains usage and exact chunk provenance, including an explicit empty source set.
`dsh-compact-basic` uses `agent/step` for pressure before request derivation and `agent/request-error` only for canonical context overflow. Once either trigger qualifies, optional tool-result pruning runs before summary selection. Recovery works between the closed failed step and failed turn close, and opens a fresh retry turn only when pruning or summarization advances the surface replacement generation; otherwise the original request error remains authoritative.
`dsh-compact-basic` uses `agent/pre-step` for pressure before request derivation and `agent/request-error` only for canonical context overflow. Once either trigger qualifies, optional tool-result pruning runs before summary selection. Recovery works between the closed failed step and failed turn close, and opens a fresh retry turn only when pruning or summarization advances the surface replacement generation; otherwise the original request error remains authoritative.
The returned `agent/prompt-submit` allow is authoritative; listeners wrapping `next()` preserve downstream content and additional contexts unless replacement is intentional. Steering bypasses that waterfall and joins at its durable checkpoint.
The returned `agent/pre-step` decision is authoritative; listeners wrapping `next()` preserve downstream messages unless replacement is intentional. Steering and injected context pass through the same waterfall after a later boundary claims their next-step batch.
SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors.

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: e0cd8c8e95151734fc97e8b8ce775af918db82c9
architecture.zh.md: 096278ceb39de2851d259b0a1c0dfab4751bd437
architecture.md: 38f889b32a75d945876c1174b1673ff741960c10
architecture.zh.md: 919b7cf4efb5f562b2d3c27335126ed101771211

View File

@@ -59,7 +59,7 @@ Events are the service extension API ([catalog](cordis-catalog/events.md), [prod
### Event Domains
- **Session events** are durable log facts emitted through `session/event`.
- **Agent events** carry live `Agent` for status, prompt admission, request shaping, validation, and continuation.
- **Agent events** carry live `Agent` for inbox notifications, step entry, status, request shaping, validation, and continuation.
- **Capability events** let owning seams attach policy and adapters without a loop import.
### Interception Semantics
@@ -80,22 +80,19 @@ choose declarative identity and fresh/resume path
-> enter session + agent -> session/created -> agent/created
-> enable driving -> agent/session-start(source) -> start driver
forever:
wait for queued occurrence
claim (edit/remove end) -> emit agent/status(running) if starting an interval
open the next-step acceptance window
-> agent/prompt-submit
blocked or failed prompt -> close the window without opening a turn
append a context-only caller batch immediately
keep steering and context staged beside it pending for a later admitted turn
allowed prompt:
wait for waking inbox work
claim next-step input plus one next-turn message with a pure deletion splice
-> 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'
append prompt + additional contexts as separate 'user/message' events
STEP loop:
agent/step
drain injected context and steering (steering bypasses prompt-submit)
'step/start'
append the returned batch as separate 'user/message' events
assemble system prompt and tool schemas
snapshot the derived messages (the reconstruction boundary)
'step/start'
agent/request (config only) -> prepare adapter defaults/provenance under turn signal -> log request/header -> llm/stream (frozen, registration-bound)
'assistant/chunk'
'assistant/message'
@@ -104,22 +101,25 @@ forever:
parallel -> rolling pool, <= maxParallelToolCalls; reclassify-at-start; scheduler failure -> stop starts, drain dispatches
start -> 'tool/call' -> ordered tools/pre-execute -> concurrent tools/execute
model-order result -> ordered tools/post-execute -> 'tool/result'
drain accepted tool context and steering
'step/end'
continue for tools or steering unless a result concluded the turn
otherwise agent/turn-stopping -> drain -> continue only for steering
close the next-step acceptance window
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
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)
idle inject:
append 'user/message'
do not open a turn or run the model
queue non-waking next-step context
leave it pending until followup or steer wakes the driver
```
Each step assembles ordered prompt sections, tool schemas, and variables; unknown references fail the turn. `dsh-system-prompt` owns identity and persona; the loop supplies `provider`, `model`, and `cwd` ([prompt ownership](../.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md)).
Admission-time and active-turn `inject()` stage for the next step; post-tool `additionalContexts` settles after results. Steering shares that staging boundary and requests another step. Idle `inject()` appends immediately without changing turn numbers; persistence drains eagerly.
`inject()` always queues non-waking `next-step` context. A collecting or running driver claims it at the nearest later prompt boundary; an idle driver leaves it pending until `followup()` or `steer()` wakes the driver. Post-tool `additionalContexts` enter the same next-step inbox after their tool results.
`agent/pre-step` receives the exclusive batch already removed from the inbox and finalizes whether the loop enters the proposed step. Its `PreStepContext` carries the exact upcoming turn and step plus the cancellation signal. `{ kind: 'reject' }` opens no step; `{ kind: 'enter', messages }` supplies the complete batch appended after `step/start`. A tool continuation with no newly claimed inbox input submits an empty batch so listeners can still contribute current-step context. Waterfall rewrites settle only in the final returned `messages` value.
Pruning precedes summaries; overflow retries require durable progress. `agent/request-error` may authorize one retry turn between failed-step and turn close; cancellation wins. Adapter-owned `retryPolicy` makes normal mode bounded; always mode delegates specialized recovery before retrying until success or cancellation ([compaction](../.agents/notes/implemented/architecture/2026-07-10-after-call-compaction-pressure-and-overflow-recovery.md), [retry foundation](../.agents/notes/implemented/architecture/2026-06-21-bounded-llm-request-recovery.md), [provider policy](../.agents/notes/implemented/feature/2026-07-24-provider-retry-policies.md)).
@@ -129,11 +129,11 @@ Final-adapter selection, dispatch, and iteration failures become terminal `finis
Other failures use `agent/error`. Cancellation and disposal beat recovery. Before request-header commit, the turn signal cancels asynchronous model-capability preparation; undispatched tools get synthetic `tool/call`/`ABORTED_BEFORE_DISPATCH` pairs. Effective `cancel(cause)` emits its cause before queue clearing and abort; observers cannot veto; idle calls emit nothing. Durability records user or parent cancellation as `aborted`, teardown as `disposed`; teardown awaits quiescence. The cause affects reporting, not late result-context handling ([decision](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md)).
Turn and step events are turn-enclosed; idle injected `user/message` events may sit between turns. Reload closes an interrupted tail with a synthetic turn end. After close, only `agent/error` reports failures. Each turn has one [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap).
Turn and step events are turn-enclosed; the loop appends injected `user/message` events only from entered batches inside a turn. Reload closes an interrupted tail with a synthetic turn end. After close, only `agent/error` reports failures. Each turn has one [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap).
### Agent Handles
`ctx.agents` returns `AgentHandle { agent, dispose() }`. Plugins drive agents with `followup()`, `steer()`, and `inject()`; `cancel()` stops work, while the awaited disposer owns teardown. `followup()` only queues an identified message: its `MessageId` follows durable inbox admission, not a prompt-specific output or turn ending. `agent/status` and `whenIdle()` describe whole-agent activity; only a caller that explicitly owns an activity interval may summarize that interval as a run result ([decision](../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md)).
`ctx.agents` returns `AgentHandle { agent, dispose() }`. Plugins drive agents with `followup()`, `steer()`, and `inject()`; `cancel()` stops work, while the awaited disposer owns teardown. `followup()` only queues an identified message: its `MessageId` follows durable inbox insertion, claiming, and discard notifications, not a prompt-specific output or turn ending. `agent/status` and `whenIdle()` describe whole-agent activity; only a caller that explicitly owns an activity interval may summarize that interval as a run result ([decision](../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md)).
### Agent Scope
@@ -165,7 +165,7 @@ A swappable capability usually has **interface / implementation / consumer** lay
Exceptions combine LLM interface/consumer, filesystem policy, web registries, and named skill/subagent providers. Subagents spawn fresh, fork a completed-turn prefix, or use ACP children ([subagent.md](core-data-structures/subagent.md)).
`dsh-workspace-context` injects baseline at the first `agent/step` and appends `ctx.fs`-discovered changes through `tools/post-execute`; its [decision](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md) records isolation. `dsh-paths` owns shared paths.
`dsh-workspace-context` uses the first `agent/pre-step` to prepend its baseline directly to the next-step inbox, replacing any still-pending predecessor, and appends `ctx.fs`-discovered changes through `tools/post-execute`; its [decision](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md) records isolation. Because claim precedes pre-step, that baseline may miss the current request. `dsh-paths` owns shared paths.
### Bundles And Apps
@@ -186,7 +186,7 @@ New behavior attaches to a documented extension point; a loop change updates thi
| 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/turn-stopping` is the stop boundary |
| Add model-facing context | call `agent.inject()` to append a sourced `user/message` without a turn |
| Add model-facing context | call `agent.inject()` to queue sourced context for the next admitted request |
| 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 |
| Add asynchronous session-title generation | register the sole `ctx.sessionTitle` provider |

View File

@@ -59,7 +59,7 @@
### 事件域
- **会话事件**是通过 `session/event` 发出的持久日志事实。
- **Agent 事件**携带活跃 `Agent`,用于状态、提示词准入、请求塑形、验证和续跑。
- **Agent 事件**携带活跃 `Agent`,用于 inbox 通知、步骤进入、状态、请求塑形、验证和续跑。
- **功能事件**让所属服务边界无需导入循环即可附加策略和适配器。
### 拦截语义
@@ -80,22 +80,19 @@ choose declarative identity and fresh/resume path
-> enter session + agent -> session/created -> agent/created
-> enable driving -> agent/session-start(source) -> start driver
forever:
wait for queued occurrence
claim (edit/remove end) -> emit agent/status(running) if starting an interval
open the next-step acceptance window
-> agent/prompt-submit
blocked or failed prompt -> close the window without opening a turn
append a context-only caller batch immediately
keep steering and context staged beside it pending for a later admitted turn
allowed prompt:
wait for waking inbox work
claim next-step input plus one next-turn message with a pure deletion splice
-> 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'
append prompt + additional contexts as separate 'user/message' events
STEP loop:
agent/step
drain injected context and steering (steering bypasses prompt-submit)
'step/start'
append the returned batch as separate 'user/message' events
assemble system prompt and tool schemas
snapshot the derived messages (the reconstruction boundary)
'step/start'
agent/request (config only) -> prepare adapter defaults/provenance under turn signal -> log request/header -> llm/stream (frozen, registration-bound)
'assistant/chunk'
'assistant/message'
@@ -104,22 +101,25 @@ forever:
parallel -> rolling pool, <= maxParallelToolCalls; reclassify-at-start; scheduler failure -> stop starts, drain dispatches
start -> 'tool/call' -> ordered tools/pre-execute -> concurrent tools/execute
model-order result -> ordered tools/post-execute -> 'tool/result'
drain accepted tool context and steering
'step/end'
continue for tools or steering unless a result concluded the turn
otherwise agent/turn-stopping -> drain -> continue only for steering
close the next-step acceptance window
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
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)
idle inject:
append 'user/message'
do not open a turn or run the model
queue non-waking next-step context
leave it pending until followup or steer wakes the driver
```
每个步骤都会组装有序提示词片段、工具 schema 和变量;未知引用会使该轮次失败。`dsh-system-prompt` 负责身份和角色设定;循环提供 `provider``model``cwd`[提示词归属](../.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md))。
接纳期间和活跃轮次内的 `inject()` 会为下一步骤暂存;工具执行后的 `additionalContexts` 会在结果记录完毕后落定。steering 与其共用这一暂存边界,并请求再执行一个步骤。空闲状态下的 `inject()` 会立即追加,且不改变轮次编号;持久化层会尽快排空
`inject()` 始终将不会唤醒的上下文排入 `next-step`。collecting 或 running 驱动器会在最近的后续提示词边界领取它idle 驱动器则会让它保持待处理,直至 `followup()``steer()` 唤醒驱动器。工具执行后的 `additionalContexts` 会在对应工具结果之后进入同一个 next-step inbox
`agent/pre-step` 接收已经从 inbox 删除的独占批次,并最终决定循环是否进入拟议步骤。它的 `PreStepContext` 携带准确的 turn、step 与取消 signal。`{ kind: 'reject' }` 不会打开步骤;`{ kind: 'enter', messages }` 提供在 `step/start` 后追加的完整批次。当工具 continuation 没有新领取的 inbox 输入时批次为空listener 仍可为当前步骤贡献上下文。waterfall 的全部改写只在最终返回的 `messages` 中一次性结算。
裁剪先于摘要;溢出重试必须取得持久进展。`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))。
@@ -129,11 +129,11 @@ idle inject:
其他故障使用 `agent/error`。取消和资源释放优先于恢复。在提交请求头之前,轮次信号会取消异步模型能力准备;尚未分派的工具会得到合成的 `tool/call`/`ABORTED_BEFORE_DISPATCH` 对。实际生效的 `cancel(cause)` 在清空队列和中止前发出原因;观察方不能否决;空闲调用不发事件。持久化层将用户或父级取消记录为 `aborted`,拆卸记录为 `disposed`;拆卸会等待完全停稳。原因只影响报告方式,不影响延迟完成的结果上下文处理([决策](../.agents/notes/implemented/architecture/2026-07-16-explicit-turn-cancellation.md))。
轮次和步骤事件均位于轮次边界内;空闲时注入的 `user/message` 可以位于两个轮次之间。重新加载会用合成的轮次结束事件闭合中断尾部。关闭后仅由 `agent/error` 报告故障。每个轮次有一个 [TurnEndReason](core-data-structures/session.md#why-a-turn-ended-turnendreasonmap)。
轮次和步骤事件均位于轮次边界内;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 负责。`followup()` 只会将一条带标识的消息排队:其 `MessageId` 跟踪持久 inbox 的插入、领取与丢弃通知,而不标识某个提示词特有的输出或轮次结束。`agent/status``whenIdle()` 描述整个 agent 的活动;只有显式拥有某个活动区间的调用方才能将该区间概括为一次运行的结果([决策](../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。
### Agent 作用域
@@ -165,7 +165,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/step` 注入基线,并通过 `tools/post-execute` 追加 `ctx.fs` 发现的变更其[决策](../.agents/notes/implemented/feature/2026-06-24-workspace-context.md)记录隔离方式。`dsh-paths` 负责共享路径。
`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` 负责共享路径。
### 组合包与应用
@@ -186,7 +186,7 @@ idle inject:
| 添加文件系统访问或策略 | 实现 `ctx.fs` 提供方,或监听 `fs/*` 策略事件 |
| 限制生成的进程 | 使用 `ctx.sandbox` 后端;消费方在生成前包装 argv |
| 拦截请求、工具或轮次 | 使用相应的 `agent/*``tools/*` 事件;`agent/turn-stopping` 是停止边界 |
| 添加模型可见上下文 | 调用 `agent.inject()`追加带来源的 `user/message`,但不创建轮次 |
| 添加模型可见上下文 | 调用 `agent.inject()`带来源的上下文排入下一次获准请求 |
| 添加 UI 或编辑器集成 | 驱动 `ctx.agents`,从 `session/event` 渲染;仅终端浮层使用 `ctx.tui` |
| 添加持久会话状态 | 扩展 `SessionEventMap`;从日志渲染和回放 |
| 添加异步会话标题生成 | 注册唯一的 `ctx.sessionTitle` 提供方 |

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/cookbook/extension-cookbook.md
extension-cookbook.md: ebcd2eff53cbffa86328e1992baf409bce643cc2
extension-cookbook.zh.md: 05bac113fceff54bde8e1f27d89484e20c2dd0da
extension-cookbook.md: fffb15ef1d51cd32aae8a6ee1a1620905263a277
extension-cookbook.zh.md: e01814bdef1cbf9566a01f0ae42aa2c166d0fd93

View File

@@ -102,12 +102,12 @@ 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/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 |
| Hook system (user + project level) | listeners on `agent/session-start`, `agent/pre-step`, `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 |
| Queued + steering messages | core `Agent.followup()` / `Agent.steer()` |
| Context compaction (auto + manual) | the `ctx.compact` seam + `dsh-compact-basic`; automatic pressure runs on serial `agent/step`, canonical overflow recovery runs on `agent/request-error`, and manual callers use the same compact service ([compaction Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md) — the model-facing `/compact` consumer tool is deferred) |
| Context compaction (auto + manual) | the `ctx.compact` seam + `dsh-compact-basic`; automatic pressure runs on serial `agent/pre-step`, canonical overflow recovery runs on `agent/request-error`, and manual callers use the same compact service ([compaction Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md) — the model-facing `/compact` consumer tool is deferred) |
| System prompt configurability | `ctx.systemPrompt.section()` with ordering and scope-local shadowing |
| AGENTS.md (root) | a section provider reading the file |
| AGENTS.md (subdir, on-touch) + file-change notices | `agent.inject()` from a watcher / tool-result listener |

View File

@@ -102,12 +102,12 @@ export function apply(ctx: Context) {
| 产品功能 | 插件机制 |
|---|---|
| 钩子系统(用户级 + 项目级) | `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 上 |
| 钩子系统(用户级 + 项目级) | `agent/session-start``agent/pre-step``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()` 标记来强制输出 |
| 排队消息 + steering中途引导 | 核心 `Agent.followup()` / `Agent.steer()` |
| 上下文压缩context compaction自动 + 手动) | `ctx.compact` seam + `dsh-compact-basic`;自动压力检查运行在串行 `agent/step`,规范化溢出恢复运行在 `agent/request-error`,手动调用方使用同一个压缩服务([压缩 Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)——面向模型的 `/compact` 消费方工具已推迟) |
| 上下文压缩context compaction自动 + 手动) | `ctx.compact` seam + `dsh-compact-basic`;自动压力检查运行在串行 `agent/pre-step`,规范化溢出恢复运行在 `agent/request-error`,手动调用方使用同一个压缩服务([压缩 Agent Note](../../.agents/notes/implemented/feature/2026-06-18-compaction-capability-seam.md)——面向模型的 `/compact` 消费方工具已推迟) |
| 系统提示词可配置性 | `ctx.systemPrompt.section()`,支持排序与作用域局部覆盖 |
| AGENTS.md根目录 | 一个读取该文件的 section provider |
| AGENTS.md子目录按需触发+ 文件变更通知 | 从 watcher / tool-result 监听器调用 `agent.inject()` |

View File

@@ -33,7 +33,7 @@ A fully configured agent and live session were published. Setup is composition-o
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:160`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:171`](../../packages/core/agent/src/types.ts)
### `agent/disposed` — emit
@@ -53,7 +53,7 @@ An agent left the registry; AgentLoop emits this after driver quiescence and sco
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:169`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:180`](../../packages/core/agent/src/types.ts)
### `agent/error` — emit
@@ -75,30 +75,85 @@ A step or turn errored. The machine reports a failure here (plus the logger) eve
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:270`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:281`](../../packages/core/agent/src/types.ts)
### `agent/prompt-submit` — waterfall
### `agent/inbox/claimed` — emit
Allow, rewrite, or block one claimed inbox batch before it becomes model-visible or opens a turn. Call `next()` for the unchanged default. The signal controls only this admission attempt; listeners may cooperate with it but must not retain it for a later attempt or turn.
One message left the inbox for a turn.
```ts cordis-catalog
/**
* Allow, rewrite, or block one claimed inbox batch before it becomes
* model-visible or opens a turn. Call `next()` for the unchanged default. The
* signal controls only this admission attempt; listeners may cooperate with
* it but must not retain it for a later attempt or turn.
* @param agent - the agent whose driver claimed the batch.
* @param messages - the claimed messages.
* @param signal - the current turn's explicit abort signal.
* One message left the inbox for a turn.
* @param agent - the agent whose inbox changed.
* @param event - the claimed message and owning turn.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode emit
*/
'agent/inbox/claimed'(this: Scoped<Agent>, agent: Agent, event: { message: UserMessage; turn: number }): void
```
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md)
Source: [`packages/core/agent/src/types.ts:201`](../../packages/core/agent/src/types.ts)
### `agent/inbox/discarded` — emit
One message was discarded from the live inbox.
```ts cordis-catalog
/**
* One message was discarded from the live inbox.
* @param agent - the agent whose inbox changed.
* @param event - the discarded message.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode emit
*/
'agent/inbox/discarded'(this: Scoped<Agent>, agent: Agent, event: { message: UserMessage }): void
```
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md)
Source: [`packages/core/agent/src/types.ts:209`](../../packages/core/agent/src/types.ts)
### `agent/inbox/inserted` — emit
One message entered the live inbox.
```ts cordis-catalog
/**
* One message entered the live inbox.
* @param agent - the agent whose inbox changed.
* @param event - the inserted message.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode emit
*/
'agent/inbox/inserted'(this: Scoped<Agent>, agent: Agent, event: { message: UserMessage }): void
```
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md)
Source: [`packages/core/agent/src/types.ts:193`](../../packages/core/agent/src/types.ts)
### `agent/pre-step` — waterfall
Reject a proposed step or replace the messages that enter it. Calling `next()` preserves the current messages.
```ts cordis-catalog
/**
* Reject a proposed step or replace the messages that enter it. Calling
* `next()` preserves the current messages.
* @param agent - the agent proposing the step.
* @param messages - messages removed from the inbox for this step.
* @param context - proposed turn and step coordinates plus cancellation.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode waterfall
*/
'agent/prompt-submit'(this: Scoped<Agent>, agent: Agent, messages: UserMessage[], signal: AbortSignal, next: () => Promise<PromptDecision>): Promise<PromptDecision>
'agent/pre-step'(this: Scoped<Agent>, agent: Agent, messages: UserMessage[], context: PreStepContext, next: () => Promise<PreStepDecision>): Promise<PreStepDecision>
```
Types: [Agent](../core-data-structures/core.md) · [PromptDecision](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md)
Types: [Agent](../core-data-structures/core.md) · [PreStepDecision](../core-data-structures/core.md) · [PreStepContext](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [UserMessage](../core-data-structures/session.md)
Source: [`packages/core/agent/src/types.ts:205`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:216`](../../packages/core/agent/src/types.ts)
### `agent/request` — waterfall
@@ -122,7 +177,7 @@ Replace the frozen call configuration. `await next()` yields the config the mach
Types: [Agent](../core-data-structures/core.md) · [LlmCallConfig](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:231`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:242`](../../packages/core/agent/src/types.ts)
### `agent/request-error` — waterfall
@@ -145,7 +200,7 @@ Handle one failed model-request attempt before the loop retries or closes its st
Types: [Agent](../core-data-structures/core.md) · [RequestErrorAction](../core-data-structures/core.md) · [RequestFailureContext](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:243`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:254`](../../packages/core/agent/src/types.ts)
### `agent/session-start` — emit
@@ -167,7 +222,7 @@ The session lifecycle began, once before the first turn. Use `agent.inject()` to
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md) · [SessionStartSource](../core-data-structures/core.md)
Source: [`packages/core/agent/src/types.ts:191`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:202`](../../packages/core/agent/src/types.ts)
### `agent/status` — emit
@@ -188,31 +243,7 @@ Agent status changed (`idle` ⇄ `running`). A waking delivery enters `running`
Types: [Agent](../core-data-structures/core.md) · [AgentStatus](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:179`](../../packages/core/agent/src/types.ts)
### `agent/step` — serial
Awaited serial checkpoint before EVERY request of a turn is built (the first as well as each post-tools continuation). The single "between steps" extension point: inject context, steer, or edit the session log here — the request's history derives from the log right after this settles.
```ts cordis-catalog
/**
* Awaited serial checkpoint before EVERY request of a turn is built (the
* first as well as each post-tools continuation). The single "between
* steps" extension point: inject context, steer, or edit the session log
* here — the request's history derives from the log right after this settles.
* @param agent - the agent about to send a request.
* @param turn - the open turn number.
* @param step - the step number about to open.
* @param signal - the turn abort signal.
* Scope-filtered dispatch (`@deepseek-ai/dsh-scope`): agent-scoped listeners receive only that agent.
* @mode serial
*/
'agent/step'(this: Scoped<Agent>, agent: Agent, turn: number, step: number, signal: AbortSignal): Promise<void> | void
```
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:218`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:190`](../../packages/core/agent/src/types.ts)
### `agent/turn-stopping` — serial
@@ -238,7 +269,7 @@ The turn is about to close: the model owes no response (no live tool calls, no f
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:258`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:269`](../../packages/core/agent/src/types.ts)
## `agent-loop/*`

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/compaction.md
compaction.md: 911b71d00fa4b42e9cdfa67f67d4e9b29e354a4a
compaction.zh.md: 643a116ff2edbbb53d300b4f5ff0ad36d401130b
compaction.md: 9e5b9f0da8be68d2205f022e7d9e59ebb7c1f6dc
compaction.zh.md: c5c70f2cf71b53e2caddca78986b371cf877ae64

View File

@@ -62,7 +62,7 @@ type CompactionTrigger = 'pressure' | 'context-overflow'
`CompactService` exposes `compactIfNeeded(agent, trigger, signal)` for automatic `pressure` or `context-overflow` policy, returning `null` when no safe work exists, and `compactRegion(...)` for an explicit inclusive surface range. Every backend marks its replacement `user/message` with the package-exported `COMPACT_CHECKPOINT_SOURCE`; consumers call `isCompactCheckpointSource()` instead of coupling checkpoint recognition to one backend. Implementations must forward the supplied signal to summarization. The seam owns no pricing API: the singleton [`ctx.tokenMeter`](token-meter.md) directly owns estimation and replay, while `dsh-compact-basic` owns retention, event sequencing, routed summarization calls, and their configuration.
Pressure compaction runs at serial `agent/step` before request derivation. Once pressure or canonical overflow qualifies, compact-basic invokes optional [`ctx.toolResultPrune`](../../packages/compact/compact-tool-result-prune/README.md) before range selection, remeasures through `ctx.tokenMeter`, and can advance the surface without a summary. Failed-request recovery runs through `agent/request-error` after the failed step closes and returns a retry action only when the surface replacement generation advances, even if later summary work throws after pruning; cancellation still wins. Region boundaries preserve tool-call/result pairing but not whole turns, allowing early closed steps of one oversized turn to compact. `dsh-compact-basic` owns thresholds, retained-tail policy, overflow caps, and failure handling.
Pressure compaction runs at serial `agent/pre-step` before request derivation. Once pressure or canonical overflow qualifies, compact-basic invokes optional [`ctx.toolResultPrune`](../../packages/compact/compact-tool-result-prune/README.md) before range selection, remeasures through `ctx.tokenMeter`, and can advance the surface without a summary. Failed-request recovery runs through `agent/request-error` after the failed step closes and returns a retry action only when the surface replacement generation advances, even if later summary work throws after pruning; cancellation still wins. Region boundaries preserve tool-call/result pairing but not whole turns, allowing early closed steps of one oversized turn to compact. `dsh-compact-basic` owns thresholds, retained-tail policy, overflow caps, and failure handling.
The seam exports `toolPairingBalancedBefore(session, seq)` and `toolPairingBalancedAfter(session, seq)` for those edge checks. Both validate current surface membership and reject missing seqs and orphan results; the [package contract](../../packages/compact/compact/README.md#tool-pairing-boundaries) owns their cache semantics.

View File

@@ -62,7 +62,7 @@ type CompactionTrigger = 'pressure' | 'context-overflow'
`CompactService` 暴露 `compactIfNeeded(agent, trigger, signal)` 以执行自动 `pressure` 或 `context-overflow` 策略;没有可安全执行的工作时返回 `null`。它还针对显式、两端均包含的 surface 范围暴露 `compactRegion(...)`。每个后端都使用包导出的 `COMPACT_CHECKPOINT_SOURCE` 标记其替换用的 `user/message`;消费方调用 `isCompactCheckpointSource()`,而不是把检查点识别逻辑耦合到某一个后端。实现必须把传入的 signal 转发给摘要流程。该 seam 不拥有计价 API单例 [`ctx.tokenMeter`](token-meter.md) 直接拥有估算与回放,而 `dsh-compact-basic` 拥有保留策略、事件排序、按路由执行的摘要调用及其配置。
压力压缩在串行 `agent/step` 中运行先于请求推导。一旦压力或规范化溢出满足条件compact-basic 会在选择范围前调用可选的 [`ctx.toolResultPrune`](../../packages/compact/compact-tool-result-prune/README.md),再通过 `ctx.tokenMeter` 重新测量,并且可以在不生成摘要的情况下推进 surface。失败请求的恢复在失败的步骤关闭后通过 `agent/request-error` 运行;仅当 surface replacement generation 前进时才返回重试动作,即便后续摘要工作在剪枝后抛异常亦如此;取消仍然优先。区域边界保持工具调用/结果配对,但不保持整个轮次,因此一个过大轮次中较早关闭的步骤可以被压缩。`dsh-compact-basic` 拥有阈值、保留尾部策略、溢出上限与失败处理。
压力压缩在串行 `agent/pre-step` 中运行先于请求推导。一旦压力或规范化溢出满足条件compact-basic 会在选择范围前调用可选的 [`ctx.toolResultPrune`](../../packages/compact/compact-tool-result-prune/README.md),再通过 `ctx.tokenMeter` 重新测量,并且可以在不生成摘要的情况下推进 surface。失败请求的恢复在失败的步骤关闭后通过 `agent/request-error` 运行;仅当 surface replacement generation 前进时才返回重试动作,即便后续摘要工作在剪枝后抛异常亦如此;取消仍然优先。区域边界保持工具调用/结果配对,但不保持整个轮次,因此一个过大轮次中较早关闭的步骤可以被压缩。`dsh-compact-basic` 拥有阈值、保留尾部策略、溢出上限与失败处理。
该 seam 导出 `toolPairingBalancedBefore(session, seq)` 与 `toolPairingBalancedAfter(session, seq)`,用于这些边缘检查。两者都会验证当前 surface 成员关系,并拒绝缺失的 seq 与遗留结果;其缓存语义由[包契约](../../packages/compact/compact/README.md#tool-pairing-boundaries)规定。

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: 1a0f6d0cc7604a4fbbc4ffb302a20d3895997bf4
core.zh.md: 28d1b57f4844a532804906f92032ccadad503fd3
core.md: d5fb4b1efb4a74d7a01ee09e4086863e981e2122
core.zh.md: 2cb15e20a87160e0ff0bc375752282969d77124b

View File

@@ -485,7 +485,7 @@ Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types
type InboxTarget = 'next-turn' | 'next-step'
```
Every pending occurrence is its `UserMessage`; `MessageId` is the sole identity. `Inbox.splice(target, start, deleteCount, inserted, outcome?)` uses standard splice coordinates, rejects duplicate pending message ids, and records the normalized mutation as durable `agent/inbox/spliced`. Replaying those events reconstructs both `nextTurn` and `nextStep`, including edits, insertion, admission, and cancellation.
Every pending occurrence is its `UserMessage`; `MessageId` is the sole identity. `Inbox.append`, `prepend`, `update`, `remove`, and `splice` record normalized durable `agent/inbox/spliced` mutations and reject duplicate pending ids. Ordinary removals are cancellations. `claim(target)` atomically removes the proposed step batch through pure deletion splices; the loop separately emits per-message claimed notifications. Whole-queue consumers such as UI projections reconstruct `nextTurn` and `nextStep` from the durable splices, while consumers following one message use the exact `agent/inbox/inserted`, `claimed`, and `discarded` notifications.
```ts type-equiv
/** Options for {@link Agent.cancel}. */
@@ -568,18 +568,18 @@ interface Agent {
steer(message: UserMessage): void
/**
* Append model-facing context without running the model. Admission or an
* open turn stages it at the next safe log position; outside that window it
* appends immediately without opening a turn. If admission closes without a
* turn, a context-only boundary appends immediately; context staged beside
* steering remains pending with it.
* Queue model-facing context for the next pre-step without waking the
* driver. Collecting and running drivers claim it at the nearest later
* step boundary; idle drivers leave it pending until follow-up or steering
* wakes them. It may miss a request whose pre-step already claimed its
* batch. Cancellation or disposal may discard pending context.
* @param message - identified injected context and its producer provenance.
*/
inject(message: UserMessage): void
}
```
`AgentStatus` is `'idle' | 'running'`, and `SessionId` is branded. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. `followup()` returns no handle: its `MessageId` identifies durable inbox and admission facts, not a later assistant output or turn ending. `whenIdle()` observes the whole agent, so callers may call a receipt-to-idle interval a run only when they explicitly own that interval ([decision](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md)). `AgentOptions` is merge-extensible: core declares `provider?`, `model?`, and `maxTokens?` (dispatch requires provider and model after `agent/request`). When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default.
`AgentStatus` is `'idle' | 'running'`, and `SessionId` is branded. Disposal removes the agent from the registry and emits `agent/disposed`; it is not a terminal status value. `running` describes the driver-wide drain interval and may span consecutive queued turns; it does not prove a turn is still open. `followup()` returns no handle: its `MessageId` identifies durable inbox insertion, claim, and discard facts, not a later assistant output or turn ending. `whenIdle()` observes the whole agent, so callers may call a receipt-to-idle interval a run only when they explicitly own that interval ([decision](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md)). `AgentOptions` is merge-extensible: core declares `provider?`, `model?`, and `maxTokens?` (dispatch requires provider and model after `agent/request`). When present, `maxTokens` must be a positive safe integer and caps every conversation-model request; omission allows the exact-model adapter default to materialize before the request header, or otherwise leaves provider behavior unchanged. Persona belongs to `dsh-system-prompt`: an agent-scoped `deployment:persona` may shadow the global default.
The cause is a TypeScript-enforced same-process input. An active cancellation holder copies it into the runtime-only `AbortSignal.reason`; a signal grants cooperating listeners no classification authority. Durable `turn/end` retains the coarse `{ kind: 'aborted' }` outcome; request provenance would require a separate durable event rather than overloading the terminal result.
@@ -591,22 +591,31 @@ The process-local initiator carried by `ctx.agents` is the exact `Agent` above,
## Interception decisions
Prompt decisions use the same identified `UserMessage` shape as durable user-role input. The allowed batch is authoritative and preserves every message's identity and provenance. Hook bridges map their native decision fields onto this typed result.
Pre-step decisions use the same identified `UserMessage` shape as durable user-role input. The entered batch is authoritative and preserves every message's identity and provenance. Hook bridges map their native decision fields onto this typed result.
Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts)
`agent/prompt-submit` returns a `PromptDecision` before a turn opens. Allow supplies the complete admitted batch; block rejects admission without creating turn events and must choose whether to discard the claimed messages. Messages not claimed by that admission remain pending:
`agent/pre-step` receives the exclusive claimed batch and the proposed step's coordinates and cancellation signal. The initial proposal runs before its turn opens; a tool continuation may submit an empty claimed batch between steps:
```ts type-equiv
/**
* Prompt interception result. An allowed batch replaces the submitted
* messages; a listener wrapping `next()` preserves that batch unless it
* intentionally replaces it. A blocked batch explicitly chooses whether to
* discard the claimed messages; unclaimed work remains pending.
*/
type PromptDecision =
| { kind: 'allow'; messages: UserMessage[] }
| { kind: 'block'; reason: string; discardClaimed: boolean }
/** Coordinates and cancellation for a proposed step. */
interface PreStepContext {
/** Turn that will own the step. */
readonly turn: number
/** Step proposed by the loop. */
readonly step: number
/** Current turn cancellation signal. */
readonly signal: AbortSignal
}
```
It returns a `PreStepDecision`. Reject opens no step. Enter supplies the complete message batch appended after `step/start`; claimed messages omitted by the final decision remain removed, while input inserted after the claim stays pending:
```ts type-equiv
/** Whether and with which messages the loop enters a proposed step. */
type PreStepDecision =
| { kind: 'reject' }
| { kind: 'enter'; messages: UserMessage[] }
```
`agent/request-error` runs after a failed model step closes and before its turn closes. Listeners can repair durable state or await policy work while the failed turn's signal is still live. A handling listener returns `{ kind: 'retry' }` without calling `next()`; the default `undefined` leaves the failure terminal.
@@ -616,7 +625,7 @@ type PromptDecision =
type RequestErrorAction = { kind: 'retry' } | undefined
```
`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/pre-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

@@ -493,7 +493,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
type InboxTarget = 'next-turn' | 'next-step'
```
每个待处理入队项就是其 `UserMessage``MessageId` 是唯一标识。`Inbox.splice(target, start, deleteCount, inserted, outcome?)` 使用标准 splice 坐标,拒绝重复的待处理消息 id并将规范化变更记录为持久 `agent/inbox/spliced`。回放这些事件可以重建 `nextTurn` `nextStep`包括编辑、插入、准入与取消
每个待处理入队项就是其 `UserMessage``MessageId` 是唯一标识。`Inbox.append`、`prepend`、`update`、`remove` 与 `splice` 会记录规范化的持久 `agent/inbox/spliced` 变更,并拒绝重复的待处理 id。普通删除表示取消。`claim(target)` 通过纯删除 splice 原子移除拟进入步骤的批次;循环另行逐条发出 claimed 通知。UI 投影等整体队列消费方通过持久 splice 重建 `nextTurn` `nextStep`而跟踪单条消息的消费方使用精确的 `agent/inbox/inserted`、`claimed` 与 `discarded` 通知
```ts type-equiv
/** Options for {@link Agent.cancel}. */
@@ -576,18 +576,18 @@ interface Agent {
steer(message: UserMessage): void
/**
* Append model-facing context without running the model. Admission or an
* open turn stages it at the next safe log position; outside that window it
* appends immediately without opening a turn. If admission closes without a
* turn, a context-only boundary appends immediately; context staged beside
* steering remains pending with it.
* Queue model-facing context for the next pre-step without waking the
* driver. Collecting and running drivers claim it at the nearest later
* step boundary; idle drivers leave it pending until follow-up or steering
* wakes them. It may miss a request whose pre-step already claimed its
* batch. Cancellation or disposal may discard pending context.
* @param message - identified injected context and its producer provenance.
*/
inject(message: UserMessage): void
}
```
`AgentStatus` 为 `'idle' | 'running'``SessionId` 是品牌类型。dispose资源释放会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。`followup()` 不返回 handle其 `MessageId` 标识持久 inbox 与准入事实,而不标识之后的助手输出或轮次结束。`whenIdle()` 观察整个 agent因此只有显式拥有从回执到 idle 这一完整区间的调用方才能将其称为一次运行([决策](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。`AgentOptions` 可合并扩展core 声明 `provider?`、`model?` 与 `maxTokens?`(在 `agent/request` 后,分发要求 provider 与 model 都存在)。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值否则提供方行为保持不变。Persona 归 `dsh-system-prompt` 所有agent 作用域的 `deployment:persona` 可以遮蔽全局默认值。
`AgentStatus` 为 `'idle' | 'running'``SessionId` 是品牌类型。dispose资源释放会把 agent 从注册表移除并发出 `agent/disposed`;它不是一个终态 status 值。`running` 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。`followup()` 不返回 handle其 `MessageId` 标识持久 inbox 的插入、领取与丢弃事实,而不标识之后的助手输出或轮次结束。`whenIdle()` 观察整个 agent因此只有显式拥有从回执到 idle 这一完整区间的调用方才能将其称为一次运行([决策](../../.agents/notes/implemented/architecture/2026-07-30-followup-enqueue-and-owned-runs.md))。`AgentOptions` 可合并扩展core 声明 `provider?`、`model?` 与 `maxTokens?`(在 `agent/request` 后,分发要求 provider 与 model 都存在)。提供 `maxTokens` 时,它必须是正安全整数,并限制每次对话模型请求的输出;省略时,系统会在写入请求 header 前填入确切模型的适配器默认值否则提供方行为保持不变。Persona 归 `dsh-system-prompt` 所有agent 作用域的 `deployment:persona` 可以遮蔽全局默认值。
cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有者会将它复制到仅运行时的 `AbortSignal.reason`signal 不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求 provenance应使用单独的持久事件而不是让终态结果承担额外含义。
@@ -599,22 +599,31 @@ cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有
## 拦截决策
提示词决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。获准批次具有权威性,并保留每条消息的标识与 provenance。钩子桥接层把其原生决策字段映射到这一类型化结果上。
pre-step 决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。进入步骤的批次具有权威性,并保留每条消息的标识与 provenance。钩子桥接层把其原生决策字段映射到这一类型化结果上。
源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts)
`agent/prompt-submit` 在轮次打开前返回 `PromptDecision`。allow 提供完整的准入批次block 拒绝准入且不产生任何轮次事件,并且必须选择是否丢弃已领取的消息。未被此次接纳领取的消息会继续保持待处理
`agent/pre-step` 接收独占的已领取批次,以及拟进入步骤的坐标与取消 signal。首次提案发生在轮次打开前工具 continuation 可以在步骤之间提交空的已领取批次
```ts type-equiv
/**
* Prompt interception result. An allowed batch replaces the submitted
* messages; a listener wrapping `next()` preserves that batch unless it
* intentionally replaces it. A blocked batch explicitly chooses whether to
* discard the claimed messages; unclaimed work remains pending.
*/
type PromptDecision =
| { kind: 'allow'; messages: UserMessage[] }
| { kind: 'block'; reason: string; discardClaimed: boolean }
/** Coordinates and cancellation for a proposed step. */
interface PreStepContext {
/** Turn that will own the step. */
readonly turn: number
/** Step proposed by the loop. */
readonly step: number
/** Current turn cancellation signal. */
readonly signal: AbortSignal
}
```
它返回 `PreStepDecision`。reject 不会打开步骤。enter 提供在 `step/start` 后追加的完整消息批次;最终决策省略的已领取消息保持已删除,而领取后插入的输入仍留待后续处理:
```ts type-equiv
/** Whether and with which messages the loop enters a proposed step. */
type PreStepDecision =
| { kind: 'reject' }
| { kind: 'enter'; messages: UserMessage[] }
```
`agent/request-error` 在失败的模型步骤关闭之后、其轮次关闭之前运行。listener 可以在失败轮次的 signal 仍然存活时修复持久状态或 await 策略工作。处理该错误的 listener 返回 `{ kind: 'retry' }` 且不调用 `next()`;默认的 `undefined` 会让失败保持终态。
@@ -624,7 +633,7 @@ type PromptDecision =
type RequestErrorAction = { kind: 'retry' } | undefined
```
`agent/step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering中途引导后续时运行先于最后一次 steering 排空。
`agent/pre-step` 是请求推导前唯一的串行边界。`agent/turn-stopping` 在轮次没有工具或 steering中途引导后续时运行先于最后一次 steering 排空。
`agent/session-start` 携带 `SessionStartSource`(会话生命周期为何开始;桥接层据此匹配其 SessionStart

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/session.md
session.md: 436ee6ea5530e5b4baf832ba446e09019f5c08d4
session.zh.md: 7d2b762e9332299ea71c0e3b1902d1bc35d0db2a
session.md: 05c993755cc50fe9f1c6fe8836fc6a6fb4196ed2
session.zh.md: ac7e59c8563a0319609557c7346919b6e1d78ad2

View File

@@ -47,8 +47,7 @@ interface SessionEventMap {
* (the queued message claimed for this turn), a synthetic `agent.inject()`
* context (file-change notices, subdir AGENTS.md, skill content, cron
* notifications, …), or an admitted goal continuation round. All three
* project their `content` verbatim; `source` tells them apart. An idle
* injection may append this event between turns without running the model.
* project their `content` verbatim; `source` tells them apart.
*/
'user/message': UserMessage
/** Raw stream chunk — token-level replay fidelity. */
@@ -485,7 +484,7 @@ An explicit `boundary` lets callers fork from any stable between-turn position,
## Why a turn ended: `TurnEndReasonMap`
`turn/start` has no trigger field. The admitted `user/message` batch records what entered the turn, `llm/retry` records request recovery, and idle injection opens no turn. `aborted.reason` retains the typed [`AgentCancelCause`](core.md#the-agent-handle) that stopped the driver.
`turn/start` has no trigger field. The entered `user/message` batch records what entered each step, `llm/retry` records request recovery, and idle injection remains pending until a waking delivery reaches a later pre-step. `aborted.reason` retains the typed [`AgentCancelCause`](core.md#the-agent-handle) that stopped the driver.
```ts type-equiv
/**
@@ -513,7 +512,7 @@ interface TurnEndReasonMap {
## Execution enclosure and standalone events
A turn encloses one model-loop execution, not the whole session log. Idle injected `user/message` events and plugin-owned log-only events may appear between `turn/end` and the next `turn/start`; they consume event seqs without incrementing turn numbers. Persistence eagerly records every contiguous accepted event, while crash repair closes only a genuinely open trailing turn. A producer that needs a durability barrier explicitly awaits `ctx.sessions.flush(session)`.
A turn encloses one model-loop execution, not the whole session log. AgentLoop records injected `user/message` events only from entering pre-step batches inside a turn; plugin-owned log-only events may still appear between `turn/end` and the next `turn/start`, consuming event seqs without incrementing turn numbers. Persistence eagerly records every contiguous accepted event, while crash repair closes only a genuinely open trailing turn. A producer that needs a durability barrier explicitly awaits `ctx.sessions.flush(session)`.
The optional `dsh-session/invariant` companion enforces the relations owned by core: turn and step numbering, execution-event enclosure, and same-step tool call/result pairing. Merge-extensible event relations belong to the plugin that declares them, so core does not reject an unknown event merely because no turn is open. See [the standalone-event decision](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md).
@@ -531,7 +530,7 @@ Activity ordering excludes the boundary through `lastActivityTime(events)`: pick
A plugin may declaration-merge extra `SessionEventMap` types. These are **log-only**: NOT `SurfaceEventType`s (they carry no `surfaceOp` and contribute nothing to derived history). Their owner decides whether they belong to an open execution turn or may stand between turns, and enforces any relation in its own invariant companion. The full per-event enumeration — core and plugin-contributed alike, with payloads and provenance — is the generated [persistence log event catalog](../persistence-catalog.md); the compaction seam's `compact/*` semantics are discussed on [compaction.md](compaction.md).
The hook bridges' `hook/invoked` / `hook/result` provenance pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. The mid-turn hook points (`PreToolUse`/`PostToolUse`/`Stop`) fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` and the pre-turn `UserPromptSubmit` admission seam get no `hook/*` record because neither has an open turn to enclose one; allowed context is instead evidenced by its sourced `user/message` (see [the hook-bridges Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md)).
The hook bridges' `hook/invoked` / `hook/result` provenance pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. The mid-turn hook points (`PreToolUse`/`PostToolUse`/`Stop`) fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` and the pre-turn `UserPromptSubmit` pre-step seam get no `hook/*` record because neither has an open turn to enclose one; entered context is instead evidenced by its sourced `user/message` (see [the hook-bridges Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md)).
## Durability contract

View File

@@ -47,8 +47,7 @@ interface SessionEventMap {
* (the queued message claimed for this turn), a synthetic `agent.inject()`
* context (file-change notices, subdir AGENTS.md, skill content, cron
* notifications, …), or an admitted goal continuation round. All three
* project their `content` verbatim; `source` tells them apart. An idle
* injection may append this event between turns without running the model.
* project their `content` verbatim; `source` tells them apart.
*/
'user/message': UserMessage
/** Raw stream chunk — token-level replay fidelity. */
@@ -489,7 +488,7 @@ declare class Session {
## 轮次的结束原因:`TurnEndReasonMap`
`turn/start` 没有 trigger 字段。已准入的 `user/message` 批次记录进入轮次的内容,`llm/retry` 记录请求恢复idle 注入则不会打开轮次。`aborted.reason` 保留停止驱动器的类型化 [`AgentCancelCause`](core.md#the-agent-handle)。
`turn/start` 没有 trigger 字段。返回 enter 的 pre-step 所产生的 `user/message` 批次记录进入轮次的内容,`llm/retry` 记录请求恢复idle 注入则保持待处理,直到后续边界领取并让它进入步骤。`aborted.reason` 保留停止驱动器的类型化 [`AgentCancelCause`](core.md#the-agent-handle)。
```ts type-equiv
/**
@@ -517,7 +516,7 @@ interface TurnEndReasonMap {
## 执行封闭与独立事件
一个轮次包围一次模型循环执行,而不是整个会话日志。空闲注入的 `user/message` 事件插件所属的纯日志事件可出现在 `turn/end` 与下一个 `turn/start` 之间;它们占用事件 seq但不递增轮次编号。持久化会尽快记录每个连续且已接受的事件,而崩溃修复只关闭确实仍处于开放状态的尾部轮次。需要持久性屏障的生产方会显式等待 `ctx.sessions.flush(session)`。
一个轮次包围一次模型循环执行,而不是整个会话日志。AgentLoop 只会从轮次内返回 enter 的 pre-step 批次记录注入的 `user/message` 事件插件所属的纯日志事件可出现在 `turn/end` 与下一个 `turn/start` 之间占用事件 seq 但不递增轮次编号。持久化会尽快记录每个连续且已接受的事件,而崩溃修复只关闭确实仍处于开放状态的尾部轮次。需要持久性屏障的生产方会显式等待 `ctx.sessions.flush(session)`。
可选的 `dsh-session/invariant` 配套插件会强制核心拥有的关系:轮次与步骤编号、执行事件封闭,以及同一步骤内的工具调用/结果配对。可合并扩展事件的关系由声明它的插件拥有,因此核心不会仅因没有开放轮次就拒绝未知事件。见[独立事件决策](../../.agents/notes/implemented/simplification/2026-07-28-remove-synthetic-log-only-turns.md)。
@@ -535,7 +534,7 @@ interface TurnEndReasonMap {
插件可以通过 declaration merging 添加额外的 `SessionEventMap` 类型。这些是**仅日志**事件:不是 `SurfaceEventType`(不携带 `surfaceOp`,不参与派生历史)。事件所有方决定它们属于一个开放的执行轮次,还是可以独立位于轮次之间,并在自己的不变量配套插件中强制所需关系。完整的逐事件枚举(核心与插件贡献的,含 payload 与溯源信息)见生成的[持久化日志事件目录](../persistence-catalog.md);压缩 seam 的 `compact/*` 语义在 [compaction.md](compaction.md) 中讨论。
钩子桥接层的 `hook/invoked` / `hook/result` 溯源对(来自 `@deepseek-ai/dsh-hook-protocol`)通过 `handlerId` 关联。轮次中间的钩子点(`PreToolUse`/`PostToolUse`/`Stop`)在 loop 已打开的轮次内触发,因此其 `hook/*` 记录天然位于轮次之内。`SessionStart` 与轮次开始前的 `UserPromptSubmit` 准入 seam 都不生成 `hook/*` 记录,因为者都没有已打开的轮次可容纳该记录;被放行的上下文改由其带来源的 `user/message` 作为持久证据(见[钩子桥接 Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md))。
钩子桥接层的 `hook/invoked` / `hook/result` 溯源对(来自 `@deepseek-ai/dsh-hook-protocol`)通过 `handlerId` 关联。轮次中间的钩子点(`PreToolUse`/`PostToolUse`/`Stop`)在 loop 已打开的轮次内触发,因此其 `hook/*` 记录天然位于轮次之内。`SessionStart` 与初始 follow-up 在轮次开始前运行的 `UserPromptSubmit` pre-step 都不生成 `hook/*` 记录,因为者都没有已打开的轮次可容纳该记录;进入步骤的上下文改由其带来源的 `user/message` 作为持久证据(见[钩子桥接 Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.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/skills.md
skills.md: d4b41845bea009444653739abad712e9ce3afb13
skills.zh.md: 8d6793129080487836b2e2471b8659df5a402974
skills.md: 16e6fb649f9db4d7be47468adf0bf8a00df428c3
skills.zh.md: 2237e291b437043b25b3016c25d061bac51fa860

View File

@@ -211,7 +211,7 @@ interface Config {
## Session catalog and tool contract
`dsh-tool-skill` injects the initial durable user-role `<system-reminder>` at the first `agent/step` of a live session that observes a non-empty complete view. The catalog contains sorted skill `name` and normalized, XML-escaped `description` only; it omits bodies, paths, sources, providers, and routing hints. Discovery forwards the step's abort signal through `SkillLookupOptions`. `catalogDescriptionMaxLength` is the consumer config for the description bound, with default `500` and integer minimum `3`.
`dsh-tool-skill` injects the initial durable user-role `<system-reminder>` at the first `agent/pre-step` of a live session that observes a non-empty complete view. The catalog contains sorted skill `name` and normalized, XML-escaped `description` only; it omits bodies, paths, sources, providers, and routing hints. Discovery forwards the step's abort signal through `SkillLookupOptions`. `catalogDescriptionMaxLength` is the consumer config for the description bound, with default `500` and integer minimum `3`.
Before each later model step, the consumer applies exact tool visibility and digests the exact rendered entries between the `<available_skills>` tags from a complete snapshot. It derives the comparison baseline from the same entries in the newest recognizable visible catalog message sourced by the plugin. A changed digest appends a durable full replacement through `agent.inject()`; deleting every skill appends an explicit empty replacement. Incomplete snapshots preserve the last-good model view. If compaction hides every historical catalog message, the next complete snapshot re-establishes the current catalog; an empty view with no prior catalog emits nothing. These catalog messages are session history, not World State.

View File

@@ -211,7 +211,7 @@ interface Config {
## 会话目录与工具契约
`dsh-tool-skill` 在存活会话中第一个观察到非空完整视图的 `agent/step` 注入初始的持久 user-role `<system-reminder>`。目录只包含已排序的 skill `name` 和规范化、经 XML 转义的 `description`;不包含正文、路径、来源、提供方或路由提示。发现通过 `SkillLookupOptions` 转发该步骤的 abort signal。`catalogDescriptionMaxLength` 是消费方用于 description 上限的配置,默认值为 `500`,整数最小值为 `3`。
`dsh-tool-skill` 在存活会话中第一个观察到非空完整视图的 `agent/pre-step` 注入初始的持久 user-role `<system-reminder>`。目录只包含已排序的 skill `name` 和规范化、经 XML 转义的 `description`;不包含正文、路径、来源、提供方或路由提示。发现通过 `SkillLookupOptions` 转发该步骤的 abort signal。`catalogDescriptionMaxLength` 是消费方用于 description 上限的配置,默认值为 `500`,整数最小值为 `3`。
在后续每个模型步骤之前,消费方都会应用精确的工具可见性,并对完整快照中 `<available_skills>` 标签之间精确渲染的条目计算 digest。它以该插件所发布、最新一条可识别且仍可见的目录消息中的相同条目作为比较基线。digest 发生变化时,会通过 `agent.inject()` 追加一条持久的完整目录替换;删除所有 skill 时会追加一条显式的空替换。不完整快照会保留上一份可用模型视图。如果压缩compaction隐藏了所有历史目录消息下一份完整快照会重新建立当前目录如果视图为空且从未发布目录则不发送任何内容。这些目录消息属于会话历史而非 World State。

View File

@@ -8,16 +8,18 @@ This matrix shows which packages dispatch each harness-owned event and which pac
| Event | Mode | Declared in | Dispatchers | Listeners |
| --- | --- | --- | --- | --- |
| `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:157`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | [`tui`](../packages/ui/tui) |
| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:160`](../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:169`](../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:270`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `apiproxy`, [`goal-session`](../packages/goal/goal-session), [`session-telemetry`](../packages/telemetry/session-telemetry), [`tui`](../packages/ui/tui) |
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:205`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`tui`](../packages/ui/tui) |
| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:231`](../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:243`](../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:191`](../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/status` | `emit` | [`packages/core/agent/src/types.ts:179`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`agent`](../packages/core/agent), `apiproxy`, [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`jsonrpc`](../packages/ui/jsonrpc), [`tui`](../packages/ui/tui) |
| `agent/step` | `serial` | [`packages/core/agent/src/types.ts:218`](../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), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`user-approval`](../packages/ui/user-approval), [`workspace-context`](../packages/context/workspace-context) |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/types.ts:258`](../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/created` | `emit` | [`packages/core/agent/src/types.ts:171`](../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:180`](../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:281`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `apiproxy`, [`goal-session`](../packages/goal/goal-session), [`session-telemetry`](../packages/telemetry/session-telemetry), [`tui`](../packages/ui/tui) |
| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/types.ts:201`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`tui`](../packages/ui/tui) |
| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/types.ts:209`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`tui`](../packages/ui/tui) |
| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/types.ts:193`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | — |
| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/types.ts:225`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`hooks-claude`](../packages/hooks/hooks-claude), [`hooks-codex`](../packages/hooks/hooks-codex), [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-guard`](../packages/guard/repeat-tool-guard), [`session-checkpoint-policy`](../packages/session-persistence/session-checkpoint-policy), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`tui`](../packages/ui/tui), [`user-approval`](../packages/ui/user-approval), [`workspace-context`](../packages/context/workspace-context) |
| `agent/request` | `waterfall` | [`packages/core/agent/src/types.ts:242`](../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:254`](../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:202`](../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/status` | `emit` | [`packages/core/agent/src/types.ts:190`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`agent`](../packages/core/agent), `apiproxy`, [`compact-basic`](../packages/compact/compact-basic), [`goal-session`](../packages/goal/goal-session), [`jsonrpc`](../packages/ui/jsonrpc), [`tui`](../packages/ui/tui) |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/types.ts:269`](../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), `apiproxy` |
| `commands/change` | `emit` | [`packages/ui/commands/src/index.ts:154`](../packages/ui/commands/src/index.ts) | [`commands`](../packages/ui/commands) (`events.dispatch`) | `apiproxy`, [`tui`](../packages/ui/tui) |
| `credentials/updated` | `emit` | [`packages/credentials/credentials/src/index.ts:67`](../packages/credentials/credentials/src/index.ts) | [`credentials`](../packages/credentials/credentials) (`events.dispatch`) | `apiproxy`, [`credentials`](../packages/credentials/credentials) |

View File

@@ -78,7 +78,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
}[T]
```
Sources: [`packages/core/session/src/types.ts:261`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:268`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:297`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:329`](../packages/core/session/src/types.ts)
Sources: [`packages/core/session/src/types.ts:262`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:269`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:298`](../packages/core/session/src/types.ts) · [`packages/core/session/src/types.ts:330`](../packages/core/session/src/types.ts)
## Events
@@ -101,7 +101,7 @@ Sources: [`packages/core/session/src/types.ts:261`](../packages/core/session/src
}
```
Source: [`packages/core/agent/src/types.ts:281`](../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:292`](../packages/core/agent/src/types.ts)
### `approval/*`
@@ -175,7 +175,7 @@ Source: [`packages/ui/user-approval/src/index.ts:67`](../packages/ui/user-approv
Types: [StreamChunk](core-data-structures/llm-streaming.md)
Source: [`packages/core/session/src/types.ts:194`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:195`](../packages/core/session/src/types.ts)
#### `assistant/message` — surface
@@ -191,7 +191,7 @@ Source: [`packages/core/session/src/types.ts:194`](../packages/core/session/src/
Types: [TokenUsage](core-data-structures/llm-streaming.md)
Source: [`packages/core/session/src/types.ts:201`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:202`](../packages/core/session/src/types.ts)
### `command/*`
@@ -400,7 +400,7 @@ Source: [`packages/plan/plan-mode/src/index.ts:51`](../packages/plan/plan-mode/s
'request/header': { header: EpochHeader; reason: RequestHeaderReason }
```
Source: [`packages/core/session/src/types.ts:234`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:235`](../packages/core/session/src/types.ts)
### `sandbox/*`
@@ -453,7 +453,7 @@ Source: [`packages/sandbox/sandbox-policy/src/session-mode.ts:33`](../packages/s
'session/end-seed': Record<string, never>
```
Source: [`packages/core/session/src/types.ts:257`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:258`](../packages/core/session/src/types.ts)
#### `session/title` — log-only
@@ -489,7 +489,7 @@ Source: [`packages/session-title/session-title-llm/src/index.ts:43`](../packages
'steering/message': { turn: number; message: UserMessage }
```
Source: [`packages/core/session/src/types.ts:227`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:228`](../packages/core/session/src/types.ts)
### `step/*`
@@ -500,7 +500,7 @@ Source: [`packages/core/session/src/types.ts:227`](../packages/core/session/src/
'step/end': { turn: number; step: number }
```
Source: [`packages/core/session/src/types.ts:183`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:185`](../packages/core/session/src/types.ts)
#### `step/start` — log-only
@@ -509,7 +509,7 @@ Source: [`packages/core/session/src/types.ts:183`](../packages/core/session/src/
'step/start': { turn: number; step: number }
```
Source: [`packages/core/session/src/types.ts:181`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:183`](../packages/core/session/src/types.ts)
### `todo/*`
@@ -522,7 +522,7 @@ Source: [`packages/core/session/src/types.ts:181`](../packages/core/session/src/
Types: [TodoItem](core-data-structures/session.md)
Source: [`packages/core/session/src/types.ts:229`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:230`](../packages/core/session/src/types.ts)
### `tool/*`
@@ -539,7 +539,7 @@ Source: [`packages/core/session/src/types.ts:229`](../packages/core/session/src/
Types: [CallId](core-data-structures/core.md)
Source: [`packages/core/session/src/types.ts:207`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:208`](../packages/core/session/src/types.ts)
#### `tool/code-dispatch` — log-only
@@ -612,7 +612,7 @@ Source: [`packages/core/tools/src/code-mode.ts:33`](../packages/core/tools/src/c
}
```
Source: [`packages/core/session/src/types.ts:219`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:220`](../packages/core/session/src/types.ts)
### `turn/*`
@@ -630,7 +630,7 @@ Source: [`packages/core/session/src/types.ts:219`](../packages/core/session/src/
Types: [TurnEndReason](core-data-structures/session.md)
Source: [`packages/core/session/src/types.ts:179`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:181`](../packages/core/session/src/types.ts)
#### `turn/start` — log-only
@@ -643,7 +643,7 @@ Source: [`packages/core/session/src/types.ts:179`](../packages/core/session/src/
'turn/start': { turn: number }
```
Source: [`packages/core/session/src/types.ts:172`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:174`](../packages/core/session/src/types.ts)
### `user/*`
@@ -655,10 +655,9 @@ Source: [`packages/core/session/src/types.ts:172`](../packages/core/session/src/
* (the queued message claimed for this turn), a synthetic `agent.inject()`
* context (file-change notices, subdir AGENTS.md, skill content, cron
* notifications, …), or an admitted goal continuation round. All three
* project their `content` verbatim; `source` tells them apart. An idle
* injection may append this event between turns without running the model.
* project their `content` verbatim; `source` tells them apart.
*/
'user/message': UserMessage
```
Source: [`packages/core/session/src/types.ts:192`](../packages/core/session/src/types.ts)
Source: [`packages/core/session/src/types.ts:193`](../packages/core/session/src/types.ts)

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
events.md: 5cd5d22f854d0b4e271e892cbdb1ccebe687ae49
events.zh.md: 5fd4d5de53897e32523ab478626965ad7c9602ba
events.md: fcbdb5f39bf2078032affbc6469f7eecc795d3ba
events.zh.md: 8b96f27fd6930ca9a481be60f93bcd834901d5dc

View File

@@ -101,7 +101,7 @@ declare module 'cordis' {
## Cordis events and session records
Harness Cordis events use `namespace/action` names, including `agent/step`, `agent/request`, `agent/request-error`, `tools/result`, and `session/event`. The generated [event catalog](../../../cordis-catalog/events.md) records complete signatures and modes.
Harness Cordis events use `namespace/action` names, including `agent/pre-step`, `agent/request`, `agent/request-error`, `tools/result`, and `session/event`. The generated [event catalog](../../../cordis-catalog/events.md) records complete signatures and modes.
`turn/*`, `step/*`, `tool/call`, `tool/result`, and `compact/*` are durable session-event types, not same-named Cordis events. To observe them, listen to `session/event` and inspect `event.type`.

View File

@@ -101,7 +101,7 @@ declare module 'cordis' {
## Cordis 事件与会话记录
Harness 的 Cordis 事件遵循 `namespace/action` 命名,例如 `agent/step`、`agent/request`、`agent/request-error`、`tools/result` 和 `session/event`。完整签名与触发模式见[Events 目录](../../../cordis-catalog/events.md)。
Harness 的 Cordis 事件遵循 `namespace/action` 命名,例如 `agent/pre-step`、`agent/request`、`agent/request-error`、`tools/result` 和 `session/event`。完整签名与触发模式见[Events 目录](../../../cordis-catalog/events.md)。
`turn/*`、`step/*`、`tool/call`、`tool/result` 和 `compact/*` 是持久化的会话事件类型,不是同名 Cordis 事件。需要观察它们时,监听 `session/event` 并检查 `event.type`。