Files
deepseek-harness/docs/core-data-structures/core.zh.md
T
_Kerman 338da9f2e0 refactor(agent): fold agentInterruptReasonOf into loop-private slot invariants
The public classifier existed to defend an exported reader against
arbitrary signals, but its only production caller is the loop reading
its own machine-private turn signal, where cancel() is the sole aborter
and always writes one frozen canonical cause. Delete the export and its
15-line structural validation: settle() states the slot invariant with
one cast, the boolean call sites ask signal.aborted directly, and the
retry veto drops entirely because a requested window already implies a
live signal (cancel() retires the window before aborting).

The abort(reason) channel and first-wins semantics are unchanged; only
the reader's publicness is gone, and with it the paranoia it required.
2026-07-26 20:51:32 +08:00

31 KiB
Raw Blame History

核心数据结构

English | 中文

本目录编目 DeepSeek Harness 的数据结构:每个核心类型代表什么、它的字面形状,以及完整细节在哪里。它与 architecture.md 互补——后者描述行为(服务映射、会话/轮次/步骤生命周期、事件分类体系);本页描述行为所操作的词汇

什么算"核心"

harness 是一个微内核:一个极小的核心加上众多插件。大多数类型属于某一个插件或某一项能力。但有少数类型构成主干——agent loop(智能体循环)及其事件在每一个轮次中使用的语言,无论加载了哪些可选插件。这些就是"核心"。

精确地说,一个数据结构是核心的,当且仅当满足以下条件之一:

  1. 它流经 agent loop 主干——循环在每个轮次中持有、派生、流式输出或记录它(MessageStreamChunkSessionEventAgent 句柄本身),与当前加载了哪些插件无关;或者
  2. 它是插件作者面向某条流水线编写的代表性类型——ToolDefinition(每个工具是什么)。

其他一切都记录在子页面上,而非本页。划线的规则是:你编写、持有或接收的类型是核心;为它提供类型推导、渲染或持久化的机制是子页面细节。因此 ToolDefinition 是核心,但为它提供类型推导的 ValueSchemaSpec/ParameterSchemaSpec 机制、为它提供渲染意图的 ToolCallView/ToolResultView 词汇,以及存储事件日志的 SessionPersistence seam 都不是——它们分别在下列子页面中。

子页面 负责内容
llm-streaming.md StreamChunk 协议格式(wire format+ 适配器契约(adapter contract)、BlockAssemblerLlmAdapter seam
token-meter.md 不可变的标量与位置回放度量,附带已消费日志修订号
scope.md 作用域注册标识、dispatch 载体,以及拥有的 Scope 上下文
goal.md 持久 goal 标识、生命周期快照、激活、变更记录与 Round 归属
commands.md 人类命令 seam:定义、适配器发现、直接调用、结果与解析视图
session.md 完整的 SessionEventMap 变体目录、TurnTrigger/TurnEndReasonderiveMessages()、轮次封闭不变式
persistence.md 持久性 seamSessionPersistence、JSONL + SQLite 后端、session/flush、崩溃恢复、SessionHeader
session-query.md 逻辑记录、有界精确事件读取、关系追踪、语义筛选器/文档与全文检索结果页
session-title.md 持久标题快照、来源 provenance 与异步提供方契约
system-prompt.md 逐次组装的上下文、工具提供方结果、提示词段落与协作式组装
tools.md ToolDefinition 完整字段、schema DSL、ToolExecution/ToolResult、工具展示 UI 类型,以及受保护的执行流水线
user-interaction.md UI 支持的人工问答 seamAskUserQuestionRequest、answer/options 词汇、提供方 API、错误分类体系
approval.md 一次性用户审批 seamApprovalRequestApprovalOutcome、逐会话策略、审计与 answerer 契约
bash.md bash 执行器 seamBashExecRequest/SpecBashRunResult、后台 BashProcess 句柄
pty.md 持久化终端 ID、后端/会话契约、发送就绪状态、有界读取与 owner 可见快照
sandbox.md 每会话策略解析与进程约束 seam:文件效果模式、执行/提供方策略、ConfinedArgv、强制执行与故障关闭错误
code-runtime.md 代码执行 seamCodeRunRequest/Result、绑定命名空间、捕获日志、CodeRunFailure 分类体系
filesystem.md 文件系统 seamFsTarget、读/写/编辑结果、观测到的文件状态、FsErrorCode
lsp.md LSP 导航 seamLspQueryRequest/ResultLspProvider/Service、四种操作、LspError
skills.md skill(技能)服务:发现优先级、SkillSummary/SkillDefinition、会话前缀目录、面向模型的 skill 加载
compaction.md 压缩(compactionseamcompact/* 会话事件、CompactionResultCompactService 接口
subagent.md subagent seam:命名提供方注册表、SubagentStartRequest/Result/Run、启动时与运行时能力拆分
web.md Web 访问 seamWebSearchRequest/ResultWebFetchRequest/ResultWebFetchBody、提供方可用性、WebError
spill.md spill 存储 seamSaveTextSpillSpillOwner/SpillSourceSpillRef、品牌类型 SpillLocator
workflow.md 工作流 seamWorkflowStartRequestWorkflowMetaWorkflowRun/Resultworkflow/* 事件载荷、WorkflowError 致命性

这些页面上的类型声明及其 JSDoc 与源码等价,并由 pnpm run verify-type-equiv 检查漂移(见 development.md)。普通块保留完整声明;public-api 块保留去除实现体的公开 class 声明。Cordis 服务使用生成的服务目录

…Map → derived-union 模式

harness 中几乎所有可扩展的和类型都遵循同一形状:一个以判别标签为键的接口(…Map),联合类型由 keyof 派生。插件通过声明合并添加变体——无需修改拥有该类型的包(package)。

// The pattern, schematically:
interface ThingMap {
  'a': { kind: 'a'; /* … */ }
  'b': { kind: 'b'; /* … */ }
}
type ThingKind = keyof ThingMap          // 'a' | 'b'
type Thing = ThingMap[keyof ThingMap]    // the discriminated union

// A plugin extends it without touching the source package:
declare module '@deepseek-ai/dsh-llm' {
  interface ThingMap {
    'c': { kind: 'c'; /* … */ }
  }
}

六个规范 map 使用此模式;插件作者扩展它们:

Map 派生 目录
ContentBlockMap dsh-llm ContentBlock 下文
MessageSourceMap dsh-llm MessageSource 下文
FinishReasonMap dsh-llm FinishReason 下文
TurnTriggerMap dsh-session TurnTrigger session.md
TurnEndReasonMap dsh-session TurnEndReason session.md
SessionEventMap dsh-session SessionEvent session.md

消费方最常 switch 的两个大型判别联合类型是:StreamChunk(流式协议)和 SessionEvent(日志条目)。按仓库约定,对标签做 switch——不要链式 if——这样每个分支都能窄化类型,拼错的标签会编译失败。

品牌化 ID

跨越包边界的 ID 都经过品牌化——结构上是字符串,但在类型层面不可互换(不能把 SessionId 传给需要 CallId 的位置)。每种类型通过各自的工厂构造;比较、日志记录和 JSON 行为与普通字符串相同。

Branded<B> 原语位于独立的纯类型包 dsh-brand 中(没有运行时代码,也不依赖 Harness 包),因此任何包都能品牌化其拥有的 id,而无需依赖无关的能力包。

源码:packages/util/brand/src/index.ts

/** A string carrying a compile-time-only brand `B`. */
type Branded<B extends string> = string & { readonly [BRAND]: B }

两个核心 ID 是 CallId(关联工具调用及其结果;dsh-llm)和 SessionId(活跃 agent 与持久会话共享的标识;dsh-session)。能力包也会品牌化各自的 id,例如 tasks.md 中的 TaskId

内容块与消息

一段对话由 Message 组成;一条消息是一个类型化内容块的数组。块的联合类型从 ContentBlockMap 派生。

源码:packages/llm/llm/src/types.ts

/**
 * Merge-extensible content blocks keyed by `type`. New core blocks must land
 * with adapter, UI, and compaction support.
 */
interface ContentBlockMap {
  'text': TextBlock
  'reasoning': ReasoningBlock
  'tool-call': ToolCallBlock
  'tool-result': ToolResultBlock
}

各块接口(完整字段见源码):TextBlocktext)、ReasoningBlockthinking,区别于可见文本)、ToolCallBlockid: CallIdname、原始 JSON arguments)、ToolResultBlocktoolCallId、嵌套 content: ContentBlock[]isError?)。ContentBlock = ContentBlockMap[ContentBlockType]。核心集仅限于每条交付路径都尊重的块——多模态内容(图像、音频等)没有核心块类型;需要的功能通过可合并扩展的 map 添加,同时提供适配器/UI/压缩支持。

Message 由角色和块组成。由循环派生的 assistant 消息携带其持久提供方/模型标识,以及可选的适配器私有回放元数据:

/** Provider ownership and adapter-private replay data for an assistant message. */
interface AssistantProvenance {
  /** Provider route that produced the message. */
  provider: string
  /** Provider model id that produced the message. */
  model: string
  /**
   * Lossless-JSON adapter state needed to replay the provider response.
   * `LlmService` exposes it to a target adapter only when that adapter instance
   * currently owns both this historical provider and the target provider.
   */
  replayState?: unknown
}
/**
 * A single message in a conversation history. Loop-derived assistant messages
 * always carry provenance; callers may omit it on hand-built foreign history.
 */
interface Message {
  role: 'system' | 'user' | 'assistant'
  content: ContentBlock[]
  /** Present only on assistant messages produced by a routed adapter. */
  provenance?: AssistantProvenance
}

消息来源本身也是一个可合并扩展的和类型:

/**
 * Where a message (or injected content) came from.
 * Merge-extensible sum type — plugins add their own `kind`s.
 */
interface MessageSourceMap {
  user: { kind: 'user' }
  plugin: { kind: 'plugin'; plugin: string }
}

流式输出

适配器发出原始分片协议;循环记录分片(回放保真度),同时将同一批分片送入 BlockAssembler 以重建块和消息。StreamChunk 是基于 type 的封闭判别联合——block-starttext-deltareasoning-deltatool-call-deltablock-endusagefinish

完整联合类型、适配器契约(usage-before-finish、原始 JSON 工具参数、两条认可的错误路径)和 BlockAssemblerllm-streaming.md 中。

模型请求

一次模型调用是一个完全组装好的 GenerateOptions。适配器以原始 StreamChunk 流作答;消费方用 BlockAssembler 组装它(见 llm-streaming.md)。

源码:packages/llm/llm/src/types.ts

提供方与模型发现使用小型、提供方无关的描述符。模型目录仅供参考:路由仍以已注册提供方为键,适配器也可以接受未列出的模型 id。

/** Display metadata for one registered provider route. */
interface LlmProviderInfo {
  /** Provider route key used by {@link GenerateOptions.provider}. */
  id: string
  /** Human-readable provider name for selectors and diagnostics. */
  name: string
}
/** One adapter-discovered model; catalog membership is advisory, not request validation. */
interface LlmModelInfo {
  /** Provider route that owns this model entry. */
  provider: string
  /** Model id passed to {@link GenerateOptions.model}. */
  id: string
  /** Human-readable model name for selectors. */
  name: string
  /** Optional user-facing distinction from otherwise similar models. */
  description?: string
}

对正确性敏感的模型容量与参考目录分开查询,并归服务该确切路由的适配器所有。

/** Provider-owned context capacity for one exact provider/model route. */
interface LlmModelContext {
  /** Maximum combined request and response context in tokens. */
  contextWindow: number
}
/** A single model request, fully assembled. */
interface GenerateOptions {
  /** Registered provider route selecting the adapter instance. */
  provider: string
  model: string
  /**
   * Ordered conversation messages, exactly as the provider sees them (after
   * the `system` slot). A loop-built request assembles them as
   * the derived history (dsh-agent-loop); a hand-built one-shot passes any list.
   */
  messages: Message[]
  /** System prompt text (adapters map to the provider's system slot). */
  system?: string
  /** Tool schemas (adapters map to the provider's `tools` field). */
  tools?: ToolSchema[]
  temperature?: number
  maxTokens?: number
  /**
   * Stop sequences: generation halts as soon as the model produces any one of
   * these strings (adapters map to the provider's stop field, e.g. OpenAI
   * `stop`). The stop string itself is not included in the output.
   */
  stop?: string[]
  signal?: AbortSignal
  /**
   * Session identity stamped by the loop for listener routing. Adapters ignore
   * it; replay uses it to keep concurrent parent and child cursors independent.
   */
  sessionId?: Branded<'SessionId'>
  /**
   * Provider-neutral classification for an auxiliary model call. Adapters may
   * map the purpose to model-hidden transport metadata or purpose-specific
   * generation policy. Ordinary conversation requests leave it unset.
   */
  purpose?: 'compaction' | 'session-title'
}

模型响应为何停止由可合并扩展的原因表示。提供方终态失败携带流式契约的 LlmFailure

/**
 * Why a model response stopped.
 * Merge-extensible so adapters can surface provider-specific reasons.
 */
interface FinishReasonMap {
  'stop': { kind: 'stop' }
  'tool-calls': { kind: 'tool-calls' }
  'max-tokens': { kind: 'max-tokens' }
  'aborted': { kind: 'aborted'; failure: LlmFailure }
  'error': { kind: 'error'; failure: LlmFailure }
}

FinishReason = FinishReasonMap[keyof FinishReasonMap]TokenUsage(逐调用计量,含不相交的缓存字段)详见 llm-streaming.md

GenerateOptions.tools 携带 ToolSchema——工具的 JSON Schema 描述,发送给模型。它声明在 dsh-llm(而非 dsh-tools)中,正是因为它是循环每一步组装请求的一部分:

/**
 * JSON-schema description of a tool, as sent to the model.
 *
 * Declared here (not in dsh-tools) because it is part of {@link GenerateOptions};
 * dsh-tools' ToolDefinition and dsh-system-prompt's PromptAssembly both import
 * it from this package.
 */
interface ToolSchema {
  name: string
  description: string
  /** JSON Schema object for the arguments. */
  parameters: Record<string, unknown>
}

面向模型的 ToolSchema 是协议格式;产出它的已注册 ToolDefinitionschema + execute)在 tools.md 中。

请求信封:LlmCallConfig 与记录的 header

循环从已记录状态构建每个请求。EpochHeader 通过完整的 request/header 快照记录调用配置、渲染后的提示词以及权威返回工具顺序(由 toolOrder 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 session.md可重建性 Agent Noteagent 决策记录)

agent/request 接收冻结的调用配置种子,并可返回替代值以切换提供方、模型或采样参数。到达 llm/stream 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。

在协议格式上,循环构建的请求先读取 system 槽位(渲染后的提示词组装),再读取派生历史——边界快照,其尾部在轮次首步是最新的 user/message,在后续步骤是上一步的工具结果。开发不变式针对每个循环构建的请求精确重算此等式。

FIXME(call-config-shape):重新审视此类型的精确定义——出于缓存目的,哪些字段确实属于 epoch 层级(model 肯定属于;采样标量目前出于谨慎放在这里),以及适配器需要时,提供方特有的额外项(推理选项、额外 body 参数)应归属何处。

/**
 * Provider + model + sampling scalars of one conversation's requests. Every field maps
 * 1:1 onto the same-named `GenerateOptions` field; the loop builds requests
 * from the logged header rather than accepting these per call.
 */
interface LlmCallConfig {
  provider: string
  model: string
  temperature?: number
  maxTokens?: number
  stop?: string[]
}

会话

Session 是一份类型化 SessionEvent仅追加日志——唯一的真源。LLM(大语言模型)消息历史从日志派生deriveMessages()),而非单独存储。事件词汇从 SessionEventMap 派生:

源码:packages/core/session/src/types.ts

/**
 * One immutable entry in the session log.
 *
 * A proper discriminated union over `type` (not independent `type`/`data`
 * unions), so `switch (event.type)` narrows `event.data` without casts.
 *
 * The {@link sourceEventSeqs} and {@link surfaceOp} fields are conditional:
 * they only exist on {@link SurfaceEventType} variants (`user/message`,
 * `assistant/message`, `tool/result`, `steering/message`).
 * Non-surface events (boundary markers, chunks, usage, errors) never carry
 * surface metadata — the compiler enforces this at `Session.append()`
 * call sites.
 */
type SessionEvent<T extends SessionEventType = SessionEventType> = {
  [K in SessionEventType]: {
    type: K
    /** Monotonic sequence number within the session. */
    seq: number
    /** Unix epoch milliseconds. */
    time: number
    data: SessionEventMap[K]
  } & (K extends SurfaceEventType ? {
    /**
     * Seq numbers of events that are provenance sources of this event
     * (e.g. the `assistant/chunk` seqs that built an `assistant/message`,
     * or the surface nodes shadowed by a compaction replace node). An
     * `assistant/message` may carry a present empty array for a known empty
     * provider stream; omission means unrecorded provenance.
     */
    sourceEventSeqs?: number[]
    /** How this event entered the surface; absent for non-surface events. */
    surfaceOp?: SurfaceOp
  } : object)
}[T]

十二种事件变体(turn/startturn/endstep/startstep/enduser/messageassistant/chunkassistant/messagetool/calltool/resultsteering/messagetodo/writerequest/header)、deriveMessages() 投影规则、TurnTrigger/TurnEndReason 原因以及轮次封闭不变量都在 session.md 中。日志如何持久化——SessionPersistence seam、JSONL/SQLite 后端、session/flush 检查点、崩溃恢复与 SessionHeader——则在 persistence.md 中。

Agent 句柄

Agent 是每个插件(UI、钩子、orchestrator)面向编程的 surface。具体实现为 dsh-agent-loop 包内部细节;循环外没有任何组件依赖它。

源码:packages/core/agent/src/types.ts

/**
 * Which inbox queue a {@link Agent.send} item joins:
 * - `next-turn` — the item becomes its own turn, claimed at a turn boundary.
 * - `next-step` — the item joins the active turn between steps as steering,
 *   or, when no turn is active, is promoted per its `wakeup` flag.
 */
type SendTarget = 'next-turn' | 'next-step'
/**
 * Options for the unified {@link Agent.send} primitive over the
 * (`target` × `wakeup`) matrix. Named presets: {@link Agent.followup}
 * (`next-turn`/wakeup), {@link Agent.steer} (`next-step`/wakeup), and
 * {@link Agent.inject} (`next-step`/no-wakeup).
 *
 * The object is complete so routing policy is explicit.
 */
interface SendOptions {
  /** Queue the item joins. */
  target: SendTarget
  /**
   * Whether this item makes the model run: wake a parked driver (`next-turn`)
   * or force a continuation step (`next-step` while running). A `false`
   * `next-turn` item queues without waking; a `false`
   * `next-step` item attaches durable context without forcing another step
   * (the injection preset).
   */
  wakeup: boolean
}

固定预设的别名方法自带 targetwakeup;其 UserMessageData 输入同时携带内容与 provenance。

send 返回被接收消息的不透明 AgentMessageId,该 id 在这条消息的各个 agent/inbox/* 事件中保持稳定:

/**
 * Opaque id assigned to one accepted {@link Agent.send} message; returned by
 * `send` and carried on its `agent/inbox/*` events for correlation.
 */
type AgentMessageId = Branded<'AgentMessageId'>

agent/inbox/* 实时事件承载一条已接收的消息;注入绕过两个 FIFO,从不出现在这些事件中:

/**
 * One accepted {@link Agent.send} message, carried by the `agent/inbox/*` live
 * events. `id` is the value `send` returned to the caller, stable across this
 * message's enqueue, dequeue, and discard events. Its content and source are
 * the exact input values accepted by the agent.
 */
interface AgentMessage extends UserMessageData {
  /** The id `send` returned for this message. */
  id: AgentMessageId
}
/** Options for {@link Agent.cancel}. */
interface CancelOptions {
  /**
   * Preserve queued and steering inbox items instead of discarding them. The
   * active turn is still aborted, but un-started and pending work survives for a
   * later turn and no `agent/inbox/discard` fires.
   */
  keepInbox?: boolean
}
/** Stable runtime cause accepted by {@link Agent.cancel}. */
type AgentCancelCause =
  | { readonly kind: 'user' }
  | { readonly kind: 'parent' }

Agent 是覆盖公开活跃 agent 契约的接口。具体驱动器拥有 followup/steer/inject 别名方法,并将它们经由 send 的(target × wakeup)矩阵路由。

/** Public live-agent handle with aliases over the unified delivery primitive. */
interface Agent {
  /** The single identity shared with {@link session}. */
  readonly id: SessionId
  /** The provider route and model this agent's requests use. */
  readonly options: AgentOptions
  /** The live session this agent drives; its log is the durable source of truth. */
  readonly session: Session
  /** The current lifecycle state, mirrored on every `agent/status` transition. */
  readonly status: AgentStatus
  /** Agent-scoped context; its contributions are agent-local, unwind on disposal, and reject registration afterward. */
  readonly ctx: Context

  /**
   * The unified delivery primitive over the (`target` × `wakeup`) matrix.
   * It routes the caller's typed content and source as follows:
   *
   * - `next-turn` queues an item that becomes the sole ordinary message of its
   *   own FIFO-ordered turn; `wakeup:true` wakes a
   *   parked driver, while `wakeup:false` queues without waking.
   * - `next-step` with `wakeup:true` submits steering into the active turn
   *   (idle falls back to a woken `next-turn`).
   * - `next-step` with `wakeup:false` injects durable model-facing context
   *   without running the model: an open turn stages it for the next safe log
   *   position, while an idle injection appends it immediately without opening
   *   a turn.
   * @param input - model-facing content and its producer provenance.
   * @param options - target queue and wakeup decision.
   * @returns the accepted message's {@link AgentMessageId}, stable across its `agent/inbox/*` events.
   */
  send(input: UserMessageData, options: SendOptions): AgentMessageId

  /**
   * Clear queued and steering work — unless `keepInbox` — and abort the active
   * turn. An effective call first emits `agent/cancel-requested` with the
   * resolved typed cause. The first cause wins for the active turn, and
   * `whenIdle()` resolves after cancellation reaches quiescence. Idle
   * cancellation is a no-op and does not arm later work.
   * @param cause - the stable caller intent carried by the current turn signal.
   * @param options - cancellation options; `keepInbox` preserves pending work.
   */
  cancel(cause: AgentCancelCause, options?: CancelOptions): void

  /** Resolve at idle quiescence; disposal waits for driver exit rather than only the status transition. */
  whenIdle(): Promise<void>

  /**
   * Queue an ordinary follow-up turn and wake the driver — the
   * `next-turn`/wakeup preset of {@link send}. The item becomes the sole
   * ordinary message of its own turn.
   * @param input - prompt content and its producer provenance.
   * @returns the accepted message's {@link AgentMessageId}.
   */
  followup(input: UserMessageData): AgentMessageId

  /**
   * Submit steering into the running turn — the `next-step`/wakeup preset of
   * {@link send}. An open turn records it at the next steering checkpoint before
   * a request or stop decision. If the turn fails before that boundary, the
   * remainder stays staged without waking the agent; retry or a later prompt
   * takes it. Idle steering falls back to a woken follow-up turn, while
   * cancellation or disposal may discard pending steering.
   * @param input - steering content and its producer provenance.
   * @returns the accepted message's {@link AgentMessageId}.
   */
  steer(input: UserMessageData): AgentMessageId

  /**
   * Append model-facing context without running the model — the
   * `next-step`/no-wakeup preset of {@link send}. An open-turn injection stages
   * at the next safe log position; an idle injection appends immediately
   * without opening a turn.
   * @param input - injected context and its producer provenance.
   * @returns the accepted message's {@link AgentMessageId}.
   */
  inject(input: UserMessageData): AgentMessageId

  /**
   * Re-open a turn on the current session log without a new prompt — the
   * explicit resummon verb. During `agent/request-error`, this schedules one
   * retry turn after the failed turn closes; while idle, it starts one
   * immediately. Repeated calls before the scheduled retry coalesce.
   * @throws while other agent work is running.
   */
  retry(): void
}

AgentStatus'idle' | 'running'SessionId 是品牌类型。dispose(资源释放)会把 agent 从注册表移除并发出 agent/disposed;它不是一个终态 status 值。running 描述整个驱动器的排空区间,可能跨越连续的排队轮次;它不能证明某个轮次仍然打开。AgentOptions 可合并扩展:core 声明 provider?model?(在 agent/request 后,分发要求两者都存在)。Persona 归 dsh-system-prompt 所有:agent 作用域的 deployment:persona 可以遮蔽全局默认值。

cause 是由 TypeScript 强制约束的同进程输入。活跃的 TurnCancellation 持有者会把其判别字段复制到仅运行时的 AbortSignal.reason,并在发布 turn/end 前退役;冻结后的 AbortSignal.reason 仍可读取。只有 loop 会在结算时从自己机器私有的 signal 上读回 causeuserparent 或仅用于生命周期的 disposed)——不存在公开的读取器,signal 也不授予协作监听器任何分类权限。持久 turn/end 保留粗粒度 { kind: 'aborted' } 结果;若需记录请求 provenance,应使用单独的持久事件,而不是让终态结果承担额外含义。

事件分类拥有 agent/* 生命周期、检查点与 waterfall(瀑布式事件)契约。轮次和步骤边界是持久会话事件,而不是 agent emit。

发起 Agent

ctx.agents 携带的进程本地 initiator 就是上面的确切 Agent,不是单独的 frame 或复制的标识。环境中存在该值既不能证明存活,也不代表授权;其生命周期与边界规则由 initiator 作用域决策规定。

拦截决策

提示词决策与工具后决策使用与持久 user-role 输入相同的 UserMessageData content/source 形状。每个 additionalContexts 条目都会成为一条独立的 user/message,保留各自的 provenance。钩子桥接层把其原生决策字段映射到这些类型化结果上。

源码:packages/core/agent/src/types.ts

agent/prompt-submit 在轮次打开前返回 PromptDecision。allow 可以改写已领取的提示词或附加 additionalContexts;block 拒绝准入且不产生任何轮次事件:

/**
 * Prompt interception result. `allow.content` replaces the prompt, while
 * `additionalContexts` appends model-facing context before the turn starts.
 * An `allow` returned by a listener is authoritative: a listener wrapping
 * `next()` preserves both fields unless it intentionally replaces them.
 */
type PromptDecision =
  | { kind: 'allow'; content?: ContentBlock[]; additionalContexts?: UserMessageData[] }
  | { kind: 'block'; reason: string }

agent/request-error 在失败的模型步骤关闭之后、其轮次关闭之前运行。listener 可以在失败轮次的 signal 仍然存活时修复持久状态或 await 策略工作。处理该错误的 listener 调用 agent.retry() 并直接返回而不调用 next();重复调用会合并成一个重试轮次。

/** Model-request failure with an optional machine-routable provider code. */
type RequestError = Error & { code?: string }

agent/step 是请求推导前唯一的串行边界。agent/stopping 在轮次没有工具或 steering(中途引导)后续时运行,先于最后一次 steering 排空。

agent/session-start 携带 SessionStartSource(会话生命周期为何开始;桥接层据此匹配其 SessionStart):

/** Why a session lifecycle began; seeded creates are `startup`, while persisted loads are `resume`. */
type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'

ToolDefinition

唯一属于核心的流水线编写类型:每个已注册工具是什么——一个面向模型的 ToolSchema 加上一个 execute 函数,以及可选的最终内容回调与 UI 回调。工具作者很少手动构造它(defineTool DSL 会用类型化参数构建),但它是注册表持有、循环分发所经过的契约。

其完整字段、defineTool/ValueSchemaSpec/ParameterSchemaSpec 类型化 schema DSL、ToolExecution/ToolExecutionResult waterfall 形状,以及工具展示 UI 词汇在 tools.md 中。