fix(subagent): confirm steering request admission

This commit is contained in:
Dudu-0223
2026-07-24 14:32:19 +08:00
committed by imccyu
parent 189502e4ac
commit e1f7eeeb95
58 changed files with 565 additions and 480 deletions

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: c5788ad33dc87e104dbdf0f420ac937af9ff2662
architecture.zh.md: db98ead01d5bcb689a2cfd199eaae059763ad19e
architecture.md: d7beb60baac3c550eb008d414158d9a05181337a
architecture.zh.md: 200f82df3d936b45f4aeef0cb080c55483af602a

View File

@@ -93,12 +93,12 @@ forever:
append prompt + additional contexts as separate 'user/message' events
STEP loop:
agent/step
drain injected context and steering (steering bypasses prompt-submit)
assemble system prompt and tools
materialize changed runtime context as sourced 'user/message'
drain injected context and provisional steering (steering bypasses prompt-submit)
snapshot the derived messages (the reconstruction boundary)
'step/start'
open strict-steering acceptance
admit the drained steering receipts
agent/request (config only) -> prepare adapter defaults/provenance + context capacity under turn signal -> log request/header (+ request/context on route change) -> llm/stream (frozen, registration-bound)
'assistant/chunk'
'assistant/message'
@@ -107,10 +107,10 @@ 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'
close strict-steering acceptance, then drain accepted tool context and steering
drain accepted tool context after all results; keep steering provisional
'step/end'
continue for tools or steering unless a result concluded the turn
otherwise agent/turn-stopping -> drain -> continue only for steering
continue for tools or steering unless a result concluded the turn and rejects pending steering
otherwise agent/turn-stopping -> drain context -> continue only for steering
close the next-step acceptance window
'turn/end' -> agent/settled
start the next waking queued message, or emit agent/status(idle)
@@ -122,7 +122,7 @@ idle inject:
Each step assembles ordered stable system sections, cache-safe dynamic contexts, 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. The default loop closes its optional `trySteer()` acceptance immediately before the final steering drain; ordinary `steer()` keeps its best-effort routing semantics. Idle `inject()` appends immediately without changing turn numbers; persistence drains eagerly.
Admission-time and active-turn `inject()` stage for the next step; tool-time injection and post-tool `additionalContexts` settle after results. Steering shares the outbox but remains provisional until a request admits it. `steer()` returns a message-owned receipt: after `agent/step` and asynchronous prompt assembly succeed, the loop commits the stable batch, snapshots request history, opens `step/start`, then resolves its receipts as admitted with the turn and step; later arrivals wait. A turn-concluding tool result, broad cancellation, disposal, or a claimed idle-steering turn that never opens a step rejects affected receipts, while `cancel(..., { keepInbox: true })` and non-terminal routing preserve pending delivery. Idle `inject()` appends immediately without changing turn numbers; persistence drains eagerly.
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)).
@@ -136,7 +136,7 @@ Turn and step events are turn-enclosed. Idle `user/message` and standalone `comp
### Agent Handles
`ctx.agents` owns agents, returning `AgentHandle { agent, dispose() }`. Plugins use `send()` or `followup()`, `steer()`, optional `trySteer()`, and `inject()` presets; [`reserveTurnAdmission()`](../packages/core/agent/README.md#agent-interface-typests) synchronously reserves idle for durable work without changing queued prompt identity. The default loop's `trySteer()` atomically rejects after the current step's final steering drain begins, while ordinary `steer()` retains best-effort routing. `cancel()` and `whenIdle()` control lifecycle. Awaited disposal owns teardown.
`ctx.agents` owns agents, returning `AgentHandle { agent, dispose() }`. Plugins use `send()` or `followup()`, receipt-bearing `steer()`, and `inject()` presets; [`reserveTurnAdmission()`](../packages/core/agent/README.md#agent-interface-typests) synchronously reserves idle for durable work without changing queued prompt identity. Await a steering receipt when request admission matters; best-effort UI steering may ignore it. `cancel()` and `whenIdle()` control lifecycle. Caller, factory, and consumer co-own teardown through one awaited disposer.
### Agent Scope

View File

@@ -93,12 +93,12 @@ forever:
append prompt + additional contexts as separate 'user/message' events
STEP loop:
agent/step
drain injected context and steering (steering bypasses prompt-submit)
assemble system prompt and tools
materialize changed runtime context as sourced 'user/message'
drain injected context and provisional steering (steering bypasses prompt-submit)
snapshot the derived messages (the reconstruction boundary)
'step/start'
open strict-steering acceptance
admit the drained steering receipts
agent/request (config only) -> prepare adapter defaults/provenance + context capacity under turn signal -> log request/header (+ request/context on route change) -> llm/stream (frozen, registration-bound)
'assistant/chunk'
'assistant/message'
@@ -107,10 +107,10 @@ 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'
close strict-steering acceptance, then drain accepted tool context and steering
drain accepted tool context after all results; keep steering provisional
'step/end'
continue for tools or steering unless a result concluded the turn
otherwise agent/turn-stopping -> drain -> continue only for steering
continue for tools or steering unless a result concluded the turn and rejects pending steering
otherwise agent/turn-stopping -> drain context -> continue only for steering
close the next-step acceptance window
'turn/end' -> agent/settled
start the next waking queued message, or emit agent/status(idle)
@@ -122,7 +122,7 @@ idle inject:
每个步骤都会组装有序的稳定系统提示词片段、缓存安全的动态上下文、工具 schema 和变量;未知引用会使该轮次失败。`dsh-system-prompt` 负责身份和角色设定;循环提供 `provider``model``cwd`[提示词归属](../.agents/notes/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md))。
接纳期间和活跃轮次内的 `inject()` 会为下一步骤暂存;工具执行后的 `additionalContexts` 会在结果记录完毕后落定。steering 与其共用这一暂存边界,并请求再执行一个步骤。默认循环会在最后一次排空 steering 前立即关闭其可选 `trySteer()` 的准入;普通 `steer()` 保留尽力路由语义。空闲状态下的 `inject()` 会立即追加,且不改变轮次编号;持久化层会尽快排空。
接纳期间和活跃轮次内的 `inject()` 会为下一步骤暂存;工具执行期间的注入和工具执行后的 `additionalContexts` 会在结果记录完毕后落定。steering 与其共用 outbox但在请求接纳前始终处于待准入状态。`steer()` 会返回归属于该消息的回执:`agent/step` 和异步提示词组装成功后,循环提交稳定批次、捕获请求历史并开启 `step/start`再将其回执解析为已准入并附带轮次与步骤后续消息继续等待。结束轮次的工具结果、广义取消、dispose资源释放以及已领取 idle-steering 消息却从未开启步骤的轮次,都会拒绝受影响的回执;`cancel(..., { keepInbox: true })` 和非终止型路由则保留待处理投递。空闲状态下的 `inject()` 会立即追加,且不改变轮次编号;持久化层会尽快排空。
裁剪先于摘要;溢出重试必须取得持久进展。`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))。
@@ -136,7 +136,7 @@ idle inject:
### Agent 句柄
`ctx.agents` 拥有 agent返回 `AgentHandle { agent, dispose() }`。插件使用 `send()`,或使用 `followup()``steer()`、可选的 `trySteer()``inject()` 预设;[`reserveTurnAdmission()`](../packages/core/agent/README.md#agent-interface-typests) 为持久工作同步预留空闲状态,同时不改变排队提示词身份。当前步骤开始最后一次排空 steering 后,默认循环的 `trySteer()` 会原子地拒绝调用,而普通 `steer()` 保留尽力路由语义。`cancel()``whenIdle()` 控制生命周期。需等待完成的资源释放负责拆卸
`ctx.agents` 拥有 agent返回 `AgentHandle { agent, dispose() }`。插件使用 `send()` `followup()`带回执的 `steer()``inject()` 预设;[`reserveTurnAdmission()`](../packages/core/agent/README.md#agent-interface-typests) 为持久工作同步预留空闲状态,同时不改变排队提示词身份。需要确认请求准入时应等待 steering 回执;尽力执行的 UI steering 可以忽略它。`cancel()``whenIdle()` 控制生命周期。调用方、工厂和消费方通过同一个需等待完成的 disposer 共同拥有拆卸过程
### Agent 作用域

View File

@@ -32,7 +32,7 @@ Effective broad cancellation was requested, before queued/outbox work is cleared
Types: [Agent](../core-data-structures/core.md) · [AgentCancelCause](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:343`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:349`](../../packages/core/agent/src/types.ts)
### `agent/created` — emit
@@ -54,7 +54,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:274`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:280`](../../packages/core/agent/src/types.ts)
### `agent/disposed` — emit
@@ -74,7 +74,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:283`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:289`](../../packages/core/agent/src/types.ts)
### `agent/error` — emit
@@ -96,7 +96,7 @@ 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:457`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:463`](../../packages/core/agent/src/types.ts)
### `agent/inbox/dequeue` — emit
@@ -117,7 +117,7 @@ The driver claimed one item out of the inbox: a queued item at a turn boundary,
Types: [Agent](../core-data-structures/core.md) · [InboxItem](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:321`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:327`](../../packages/core/agent/src/types.ts)
### `agent/inbox/discard` — emit
@@ -140,7 +140,7 @@ Pending inbox items were dropped without delivering them, so every enqueue occur
Types: [Agent](../core-data-structures/core.md) · [InboxItem](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:333`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:339`](../../packages/core/agent/src/types.ts)
### `agent/inbox/enqueue` — emit
@@ -161,7 +161,7 @@ An item entered the queued or steering inbox. `placement` is the acceptance-time
Types: [Agent](../core-data-structures/core.md) · [InboxItem](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:302`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:308`](../../packages/core/agent/src/types.ts)
### `agent/inbox/update` — emit
@@ -181,7 +181,7 @@ A still-pending queued item changed content. The item id, placement, and positio
Types: [Agent](../core-data-structures/core.md) · [InboxItem](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:311`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:317`](../../packages/core/agent/src/types.ts)
### `agent/prompt-submit` — waterfall
@@ -204,7 +204,7 @@ Allow, rewrite, or block one claimed prompt before it becomes a user message or
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)
Source: [`packages/core/agent/src/types.ts:370`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:376`](../../packages/core/agent/src/types.ts)
### `agent/request` — waterfall
@@ -228,7 +228,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:396`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:402`](../../packages/core/agent/src/types.ts)
### `agent/request-error` — waterfall
@@ -258,7 +258,7 @@ Handle a model-request failure after its failed step has closed but before the f
Types: [Agent](../core-data-structures/core.md) · [LlmFailure](../core-data-structures/llm-streaming.md) · [RequestError](../core-data-structures/core.md) · [RequestErrorAction](../core-data-structures/core.md) · [ResolvedRetryPolicy](../core-data-structures/llm-streaming.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:415`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:421`](../../packages/core/agent/src/types.ts)
### `agent/session-start` — emit
@@ -280,7 +280,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:356`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:362`](../../packages/core/agent/src/types.ts)
### `agent/settled` — emit
@@ -305,7 +305,7 @@ One drain chain reached its terminal turn: that turn's `turn/end` is already com
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:444`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:450`](../../packages/core/agent/src/types.ts)
### `agent/status` — emit
@@ -325,7 +325,7 @@ Agent status changed (`idle` ⇄ `running`). `send()` does not enter `running` s
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:292`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:298`](../../packages/core/agent/src/types.ts)
### `agent/step` — serial
@@ -349,7 +349,7 @@ Awaited serial checkpoint before EVERY request of a turn is built (the first as
Types: [Agent](../core-data-structures/core.md) · [Scoped](../core-data-structures/scope.md)
Source: [`packages/core/agent/src/types.ts:383`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:389`](../../packages/core/agent/src/types.ts)
### `agent/turn-stopping` — serial
@@ -375,7 +375,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:430`](../../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:436`](../../packages/core/agent/src/types.ts)
## `agent-loop/*`

View File

@@ -1970,7 +1970,7 @@ startContinuable(spec: ContinuableStartSpec): ContinuableStart
* Deliver one message to a known continuable child: steer its running
* activation, or cold-resume the durable session into a fresh Task-backed
* activation. The two routes are reported distinctly so timing-dependent
* routing is observable. A throw means the message was NOT delivered — in
* routing is observable. Rejection means the message was NOT delivered — in
* particular, losing a race with Task settlement does not fall through to
* cold resume within the same call; a later retry after Task terminal may
* start the next activation. The started Task owns descriptor lookup and
@@ -1984,7 +1984,7 @@ startContinuable(spec: ContinuableStartSpec): ContinuableStart
* @param source - caller-supplied attribution retained across either route.
* @returns whether the message `steered` the existing Task or `started` a new one.
*/
sendMessage(parent: Agent, childId: SessionId, message: ContentBlock[], source: MessageSource): SendMessageResult
async sendMessage( parent: Agent, childId: SessionId, message: ContentBlock[], source: MessageSource, ): Promise<SendMessageResult>
```
Types: [Agent](../core-data-structures/core.md) · [ContentBlock](../core-data-structures/core.md) · [ContinuableStart](../core-data-structures/subagent.md) · [ContinuableStartSpec](../core-data-structures/subagent.md) · [MessageSource](../core-data-structures/core.md) · [SendMessageResult](../core-data-structures/subagent.md) · [SessionId](../core-data-structures/core.md)

View File

@@ -560,6 +560,8 @@ interface CancelOptions {
}
```
`SteeringReceipt.outcome` always resolves. `admitted` identifies the turn and step whose immutable request history contains that exact message; `rejected` means lifecycle or terminal policy discarded it first. Synchronous input validation still throws from `steer()`.
```ts type-equiv
/** Stable runtime cause accepted by {@link Agent.cancel}. */
type AgentCancelCause =
@@ -661,26 +663,18 @@ interface Agent {
followup(message: UserMessage): void
/**
* Submit steering during prompt admission or an open turn — the
* `next-step`/wakeup preset of {@link send}. It stages for the next steering
* checkpoint before a request or stop decision. If the activity fails before
* that boundary, the remainder stays staged without waking the agent; retry
* or a later prompt takes it. Outside that window steering falls back to a
* woken follow-up turn, while cancellation or disposal may discard pending
* steering.
* Submit steering with a message-owned admission receipt — the
* `next-step`/wakeup preset of {@link send}. During prompt admission or an
* open turn, the message waits in the steering FIFO until a committed step
* snapshots it; outside that window it enters the ordinary queued FIFO. The
* receipt resolves `admitted` only after the message joins that step's
* immutable request history, or `rejected` when terminal policy,
* cancellation, or disposal discards it first. A non-terminal turn close may
* leave it staged for a later admitted prompt without settling the receipt.
* @param message - identified steering content and its producer provenance.
* @returns the receipt for this exact message's eventual admission outcome.
*/
steer(message: UserMessage): void
/**
* Atomically submit steering only while the current step still owns its final
* drain. Returns `false` without accepting the message during admission,
* between steps, or after the final per-step drain has begun. Cancellation or
* disposal may still discard previously accepted steering.
* @param message - identified steering content and its producer provenance.
* @returns whether the message entered the current step.
*/
trySteer?(message: UserMessage): boolean
steer(message: UserMessage): SteeringReceipt
/**
* Append model-facing context without running the model — the

View File

@@ -10,7 +10,7 @@ Sources: [`packages/subagent/subagent/src/types.ts`](../../packages/subagent/sub
## Two kinds of capability, discovered two ways
A provider advertises its **start-time** features on a static descriptor the service checks BEFORE a run exists; a request that needs one the provider lacks is rejected loud (`SubagentError('UNSUPPORTED_CAPABILITY')`), never accepted-then-ignored. **Runtime** features are instead optional methods whose presence IS the capability, with TS narrowing as the discovery mechanism: strict live steering is [`SubagentRun.steer`](#a-live-run-subagentrun) and persisted cold resume is [`SubagentProvider.resume`](#the-provider-seam-subagentprovider).
A provider advertises its **start-time** features on a static descriptor the service checks BEFORE a run exists; a request that needs one the provider lacks is rejected loud (`SubagentError('UNSUPPORTED_CAPABILITY')`), never accepted-then-ignored. **Runtime** features are instead optional methods whose presence IS the capability, with TS narrowing as the discovery mechanism: confirmed live steering is [`SubagentRun.steer`](#a-live-run-subagentrun) and persisted cold resume is [`SubagentProvider.resume`](#the-provider-seam-subagentprovider).
```ts type-equiv
/**
@@ -18,7 +18,7 @@ A provider advertises its **start-time** features on a static descriptor the ser
* {@link SubagentProvider.start}: a request that needs a capability the chosen provider lacks
* is rejected with a typed error rather than accepted-then-ignored (the "fail loud, no silent
* degradation" rule). These static flags cover features needed before a run exists; runtime
* capabilities are optional methods whose presence is the capability — strict live steering
* capabilities are optional methods whose presence is the capability — confirmed live steering
* is {@link SubagentRun.steer} and persisted cold resume is {@link SubagentProvider.resume}. Each
* flag corresponds one-to-one to a {@link SubagentStartRequest} option: `depthLimit` to
* `maxDepth`; the other names match.
@@ -214,7 +214,7 @@ interface SubagentStopReasonMap {
## A live run: `SubagentRun`
`SubagentRun` is the consumer-owned handle for a ready child — one disposable activation, never a durable child handle. Consumers await `result` and always dispose the run to reach quiescence. Child failures resolve with a non-completed stop reason; only unrepresentable infrastructure faults reject. A completed continuable result additionally means the provider confirmed the activation's final state durable; a failed required checkpoint rejects. The optional strict `steer` method advertises live delivery by presence. Cold resume is a provider-level operation: `SubagentProvider.resume` reconstructs a fresh run from the child's persisted session because the process-local run ceases to exist after disposal or process restart.
`SubagentRun` is the consumer-owned handle for a ready child — one disposable activation, never a durable child handle. Consumers await `result` and always dispose the run to reach quiescence. Child failures resolve with a non-completed stop reason; only unrepresentable infrastructure faults reject. A completed continuable result additionally means the provider confirmed the activation's final state durable; a failed required checkpoint rejects. The optional confirmed `steer` method advertises live delivery by presence and fulfills only after a request snapshot admits the message. Cold resume is a provider-level operation: `SubagentProvider.resume` reconstructs a fresh run from the child's persisted session because the process-local run ceases to exist after disposal or process restart.
```ts type-equiv
/**
@@ -251,19 +251,16 @@ interface SubagentRun {
*/
dispose(): Promise<void>
/**
* OPTIONAL (strict live-steering capability): deliver additional content to
* the actively running child turn. STRICT means delivery joins the observed
* turn or fails — the implementation must synchronously verify, with no
* asynchronous boundary before delivery, that the child is running and its
* turn can still record the message, and must not fall back to a queue path
* that could start a new, untracked turn or silently drop the message after
* this run has settled. Throws when delivery cannot join the turn. A run
* represents one disposable activation, so it has no cold-resume operation;
* resuming a settled child goes through {@link SubagentProvider.resume}.
* `source` is retained on the child's logged steering message without
* changing its user role in model history.
* OPTIONAL (confirmed live-steering capability): submit additional content
* to the active child and fulfill only after a committed request snapshot
* admits it. Rejects when terminal policy, cancellation, disposal, or a lost
* settlement race prevents admission; it never falls through to a queued
* untracked turn or cold resume. A run represents one disposable activation,
* so resuming a settled child goes through {@link SubagentProvider.resume}.
* `source` is retained on the admitted steering message without changing its
* user role in model history.
*/
steer?(content: ContentBlock[], source: MessageSource): void
steer?(content: ContentBlock[], source: MessageSource): Promise<void>
}
```

View File

@@ -8,22 +8,22 @@ 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/cancel-requested` | `emit` | [`packages/core/agent/src/types.ts:343`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal-session`](../packages/goal/goal-session) |
| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:274`](../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:283`](../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:457`](../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/dequeue` | `emit` | [`packages/core/agent/src/types.ts:321`](../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:333`](../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:302`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`agent`](../packages/core/agent), `apiproxy`, [`goal-session`](../packages/goal/goal-session) |
| `agent/inbox/update` | `emit` | [`packages/core/agent/src/types.ts:311`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `apiproxy` |
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:370`](../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:396`](../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:415`](../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:356`](../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:444`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`compact-basic`](../packages/compact/compact-basic) |
| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:292`](../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:383`](../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), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/types.ts:430`](../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/cancel-requested` | `emit` | [`packages/core/agent/src/types.ts:349`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`goal-session`](../packages/goal/goal-session) |
| `agent/created` | `emit` | [`packages/core/agent/src/types.ts:280`](../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:289`](../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:463`](../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/dequeue` | `emit` | [`packages/core/agent/src/types.ts:327`](../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:339`](../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:308`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`agent`](../packages/core/agent), `apiproxy`, [`goal-session`](../packages/goal/goal-session) |
| `agent/inbox/update` | `emit` | [`packages/core/agent/src/types.ts:317`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | `apiproxy` |
| `agent/prompt-submit` | `waterfall` | [`packages/core/agent/src/types.ts:376`](../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:402`](../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:421`](../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:362`](../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:450`](../packages/core/agent/src/types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emitAgentEvent`) | [`compact-basic`](../packages/compact/compact-basic) |
| `agent/status` | `emit` | [`packages/core/agent/src/types.ts:298`](../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:389`](../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), [`subagent-inprocess`](../packages/subagent/subagent-inprocess), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill), [`workspace-context`](../packages/context/workspace-context) |
| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/types.ts:436`](../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) |