docs: replace vague provenance prose with recorded facts

This commit is contained in:
Turtle
2026-08-09 15:35:02 +08:00
parent 8c124f84b6
commit 9704749b01
380 changed files with 946 additions and 874 deletions

View File

@@ -42,7 +42,7 @@ Placement: bugs → postmortems; rationale → Agent Notes; procedures → cookb
- **The owning [subsystems page](subsystems/README.md) updates in the same change** that reshapes a documented type. `verify-type-equiv` catches drifted pastes, not never-documented new types; a type is documented on its declaring package group's page ([page scoping](../.agents/notes/implemented/process/2026-08-03-package-anchored-subsystem-pages.md)).
- **Bilingual pairs update together**: editing either side obligates the counterpart and a re-record in the same change ([i18n contract](i18n/README.md)).
- **Comments and JSDoc state complete contracts, not reasoning transcripts.** Preserve behavior, timing, modality, exceptions, consequences, and non-obvious orientation; delete narration, test walkthroughs, review analysis, and code restatement. Keep the local contract and link its rationale. Use [dsh-prose-standard](../.agents/skills/dsh-prose-standard/SKILL.md) for details.
- Your audience is professional programmers. Prefer concise and straight-forward English over metaphor. Do not overuse words like "gate", "vocabulary", "surface", "seams".
- Write for professional programmers. Use concise, direct English without metaphor. Name the actor, source, event, field, or process; use words you would say to a colleague ([decision](../.agents/notes/implemented/process/2026-08-09-concrete-prose-names-actors-and-recorded-facts.md)). Do not overuse "gate", "vocabulary", "surface", or "seam".
## Wordcount Budgets

View File

@@ -71,7 +71,7 @@ sequenceDiagram
Driver-->>SDK: <code>agent/status</code> idle
```
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.
The `assistant/message` event records every successful provider call, including content-less and `max-tokens` finishes. Empty content stays out of derived history, while the durable event keeps usage and `sourceEventSeqs` listing the exact `assistant/chunk` events, including an explicit empty list.
`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.

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: ed33fb215c3b4fd830bef219da74f4ee98dec81d
architecture.zh.md: 19d2500142449c63d725f89a651b22205af6b301
architecture.md: 5e009d8345112bb0adcb2f55b4e49bbbf3bf8d00
architecture.zh.md: c99336a03cd32fc4fb44b06c40ddd08a36b6a378

View File

@@ -92,7 +92,7 @@ forever:
'step/start'
append the returned batch as separate 'user/message' events
assemble ordered prompt and tool schemas -> snapshot derived messages
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)
agent/request (config only) -> resolve adapter defaults and mark defaulted fields + context capacity under turn signal -> log request/header (+ request/context on route change) -> llm/stream (frozen, registration-bound)
'assistant/chunk'
'assistant/message'
schedule tool calls by ctx.tools.executionMode:
@@ -144,13 +144,13 @@ The session log is authoritative. `deriveMessages()` projects model history; raw
Durability is a plugin concern. Backends copy synchronous `session/event` notifications into fixed-window durable batches; `session/flush` bypasses the wait before requests and top-level tool dispatch, and after `turn/end` before another turn or idle. `SessionPersistence` stores events and header metadata; JSONL defaults to checksummed Zstandard and SQLite shares the contract ([checkpoint decision](../.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md), [batching decision](../.agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md)).
Between turns, owners append log-only events through `Session`, flushing only for durability. `session/title` relies on bounded background persistence and lifecycle drains; manual compaction flushes its bracket before the operation completes. Title work never delays responses; latest wins with provenance. Title records are inherited fork boundaries ([decision](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md)).
Between turns, owners append log-only events through `Session`, flushing only for durability. `session/title` relies on bounded background persistence and lifecycle drains; manual compaction flushes its bracket before the operation completes. Title work never delays responses; the latest title event wins, and it records the source message seqs and whether the user, fallback, or provider supplied it. Title records are inherited fork boundaries ([decision](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md)).
### Model Content
Messages use typed blocks from merge-extensible `ContentBlockMap`; the pattern also types `MessageSource`, `FinishReason`, `TurnTrigger`, and `TurnEndReason`. New blocks coordinate adapters, UI, compaction, token metering, and persistence; replay measurements live in [token-meter.md](subsystems/token-meter.md).
Streaming uses raw chunks and `BlockAssembler`. Each `LlmAdapter.stream()` is one provider attempt; adapters report normalized failure facts, and a handling `agent/request-error` plugin returns a retry action. The loop logs chunks, successful provenance, and replay state. Remote adapters use per-read idle watchdogs. Replay crosses routes only through a shared adapter instance ([contract](subsystems/llm-streaming.md)).
Streaming uses raw chunks and `BlockAssembler`. Each `LlmAdapter.stream()` is one provider attempt; adapters report normalized failure facts, and a handling `agent/request-error` plugin returns a retry action. The loop logs chunks, the successful provider/model route, and replay state. Remote adapters use per-read idle watchdogs. Replay crosses routes only through a shared adapter instance ([contract](subsystems/llm-streaming.md)).
## Extension And Composition

View File

@@ -92,7 +92,7 @@ forever:
'step/start'
append the returned batch as separate 'user/message' events
assemble ordered prompt and tool schemas -> snapshot derived messages
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)
agent/request (config only) -> resolve adapter defaults and mark defaulted fields + context capacity under turn signal -> log request/header (+ request/context on route change) -> llm/stream (frozen, registration-bound)
'assistant/chunk'
'assistant/message'
schedule tool calls by ctx.tools.executionMode:
@@ -144,13 +144,13 @@ idle inject:
持久性由插件负责。后端会将同步的 `session/event` 通知复制到固定窗口的持久化批次中;`session/flush` 会绕过等待,在请求与顶层工具分发之前执行,并在 `turn/end` 之后、另一个轮次或空闲状态之前执行。`SessionPersistence` 存储事件和 header 元数据JSONL 默认采用带校验和的 ZstandardSQLite 遵循同一约定([检查点决策](../.agents/notes/implemented/bug-fix/2026-07-21-semantic-session-checkpoints.md)、[批处理决策](../.agents/notes/implemented/architecture/2026-08-08-bounded-session-persistence-write-batching.md))。
在轮次之间,事件所有方通过 `Session` 追加纯日志事件,仅为持久性而刷写。`session/title` 依赖有界后台持久化与生命周期排空;手动压缩会在操作完成前 flush 其标记对。标题工作绝不延迟响应;最新标题按后写覆盖并携带来源信息。标题记录是可继承的 fork 边界([决策](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md))。
在轮次之间,事件所有方通过 `Session` 追加纯日志事件,仅为持久性而刷写。`session/title` 依赖有界后台持久化与生命周期排空;手动压缩会在操作完成前 flush 其标记对。标题工作绝不延迟响应;最新标题事件生效,并记录来源消息 seq以及标题由用户、后备逻辑还是提供方提供。标题记录是可继承的 fork 边界([决策](../.agents/notes/implemented/feature/2026-07-21-log-backed-session-titles.md))。
### 模型内容
消息使用从可合并扩展的 `ContentBlockMap` 派生的类型化块;同一模式也为 `MessageSource``FinishReason``TurnTrigger``TurnEndReason` 定义类型。新增块会协调适配器、UI、压缩、token 计量和持久化;回放计量见 [token-meter.md](subsystems/token-meter.md)。
流式输出使用原始分片和 `BlockAssembler`。每次 `LlmAdapter.stream()` 调用代表一次提供方尝试;适配器报告标准化的故障事实,负责处理的 `agent/request-error` 插件会返回重试动作。循环会记录分片、成功结果的来源信息和回放状态。远程适配器使用逐次读取空闲看门狗。回放仅通过共用的适配器实例跨路由传递([约定](subsystems/llm-streaming.md))。
流式输出使用原始分片和 `BlockAssembler`。每次 `LlmAdapter.stream()` 调用代表一次提供方尝试;适配器报告标准化的故障事实,负责处理的 `agent/request-error` 插件会返回重试动作。循环会记录分片、成功使用的提供方/模型路由和回放状态。远程适配器使用逐次读取空闲看门狗。回放仅通过共用的适配器实例跨路由传递([约定](subsystems/llm-streaming.md))。
## 扩展与组合

View File

@@ -1197,7 +1197,7 @@ export interface Config {
* non-empty override asserts full enforcement and skips built-in selection and
* probing. A runner that starts but refuses its profile must be identifiable by
* {@link runnerFailureSignatures}. Consumers classify spawn rejection; only
* attributable `ENOENT` or `EACCES` with runner argv[0] provenance becomes an
* attributable `ENOENT` or `EACCES` whose error path equals runner argv[0] becomes an
* infrastructure failure.
*/
runnerCommand?: string[]

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/maintaining-dsh-code-review.md
maintaining-dsh-code-review.md: 2b5d0d926ae922f2650daac33cf35991cb71c5e5
maintaining-dsh-code-review.zh.md: 4466565b294ede89fa34da9e76b88f4ac39748eb
maintaining-dsh-code-review.md: 8aa8a5792bff2c49041f2f8b5e8146e7cff14e0e
maintaining-dsh-code-review.zh.md: b26c98e8679eece78e72ee684580c1ed1181aa7c

View File

@@ -10,7 +10,7 @@ Run the private tool daily with a two-UTC-day overlap; until the proposed schedu
1. It selects PRs merged in the chosen window (default two UTC days for the daily cadence, seven for weekly) whose merge commit is reachable from `origin/master`. PRs whose merge commit is not reachable (stacked branches whose parent was squashed) or that exceed a 250-commit acquisition cap are logged to `skipped-pulls.json` and skipped rather than aborting the run.
2. It collects pre-merge human review feedback with commit anchors (inline comments and review submissions), then compares feedback-time and final landed PR patches. It does not acquire PR conversation comments because current GitHub state cannot give them a force-push-safe feedback-time baseline, and it excludes target-branch-only changes from adoption evidence.
3. Two independently configured reviewer adapters classify provenance and adoption, then classify agreed-adopted items against the current skill.
3. Two independently configured reviewer adapters classify who wrote each item and whether the change adopted it, then classify agreed-adopted items against the current skill.
4. The primary adapter drafts a complete revised `SKILL.md`; both adapters review the same diff; blocking findings loop until both approve.
5. `pnpm run doc-sync` and `pnpm run lint` run against the candidate before the tool declares success.
@@ -38,7 +38,7 @@ When a run produces a candidate, a macOS notification arrives with a `dsh-code-r
rm ~/dsh-code-review-outputs/2026-07-16T02-00-00Z.{diff,SKILL.md,manifest.json}
```
- **Batch.** Keep the candidate aside if the update is small and could combine with a future one. The source-skill check still applies; rerun the analysis or manually rebase and re-review the diff if `master` changes first.
- **Promote.** From a clean `master` checkout of the repo, run the promote helper. It refreshes `master`, verifies that the current skill matches the recorded source blob, applies the saved diff, and opens a draft PR whose body carries the manifest's provenance summary. It stops on skill drift rather than overwriting newer guidance; the operator still reviews the PR on GitHub and either merges it or closes it.
- **Promote.** From a clean `master` checkout of the repo, run the promote helper. It refreshes `master`, verifies that the current skill matches the recorded source blob, applies the saved diff, and opens a draft PR whose body lists the source feedback URLs or IDs, landed commit range, originating run, checks, and operator edits. It stops on skill drift rather than overwriting newer guidance; the operator still reviews the PR on GitHub and either merges it or closes it.
```sh
cd ~/path/to/deepseek-harness # clean master

View File

@@ -10,7 +10,7 @@
1. 选择指定窗口内合并、且合并 commit 可从 `origin/master` 到达的 PR每天运行默认选择 2 个 UTC 日,每周运行选择 7 日)。合并 commit 无法到达的 PR例如父分支被 squash 的堆叠分支),或超出 250 个 commit 获取上限的 PR会记录到 `skipped-pulls.json` 并跳过,不会中止本次运行。
2. 收集合并前带 commit 锚点的人工评审反馈(行内评论和评审提交),然后比较反馈时与最终落地的 PR patch。它不获取 PR 会话评论,因为 GitHub 当前状态无法为这些评论提供可抵抗 force-push 的反馈时基线;它也不会把只存在于目标分支的变更作为采纳证据。
3. 两个独立配置的评审适配器先对来源和采纳情况分类,再根据当前 skill 对双方一致认定已采纳的条目分类。
3. 两个独立配置的评审适配器先对每个条目的作者以及更改是否采纳了它进行分类,再根据当前 skill 对双方一致认定已采纳的条目分类。
4. 主适配器起草完整修订版 `SKILL.md`;两个适配器评审同一份 diff只要仍有阻塞性问题循环就会继续直到双方批准。
5. 工具声明成功前,会针对候选版本运行 `pnpm run doc-sync``pnpm run lint`
@@ -38,7 +38,7 @@
rm ~/dsh-code-review-outputs/2026-07-16T02-00-00Z.{diff,SKILL.md,manifest.json}
```
- **留待成批处理。** 如果更新很小,可以把候选版本留待与后续版本合并。源 skill 检查仍然适用;如果 `master` 先发生变化,请重新运行分析,或手动 rebase 并重新评审 diff。
- **提升。** 在仓库的干净 `master` checkout 中运行提升辅助工具。它会刷新 `master`、验证当前 skill 与记录的源 blob 一致、应用保存的 diff并创建一份 draft PR其正文包含 manifest 的来源摘要。如果 skill 已发生漂移,它会停止而不是覆盖更新后的指导;操作员仍需在 GitHub 上评审 PR并选择合并或关闭。
- **提升。** 在仓库的干净 `master` checkout 中运行提升辅助工具。它会刷新 `master`、验证当前 skill 与记录的源 blob 一致、应用保存的 diff并创建一份 draft PR其正文列出原始反馈的 URL 或 ID、已落地的 commit 范围、发起这次更改的运行、检查以及操作员编辑。如果 skill 已发生漂移,它会停止而不是覆盖更新后的指导;操作员仍需在 GitHub 上评审 PR并选择合并或关闭。
```sh
cd ~/path/to/deepseek-harness # clean master

View File

@@ -64,11 +64,11 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
data: SessionEventMap[K]
} & (K extends SurfaceEventType ? {
/**
* Seq numbers of events that are provenance sources of this event
* Seq numbers of earlier events that this event cites as sources
* (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.
* provider stream; omission means the source stream was not recorded.
*/
sourceEventSeqs?: number[]
/** How this event entered the surface; absent for non-surface events. */
@@ -286,7 +286,7 @@ Source: [`packages/compact/compact/src/types.ts:19`](../packages/compact/compact
```ts persistence-catalog
/**
* Provenance record of a completed summarization — log-only, no surfaceOp.
* Completed summary, its inputs, and its model call facts — log-only, no surfaceOp.
* The summary content is in `data.summary`; the actual surface replacement
* is performed by the immediately following `user/message` event that
* shadows the compacted range. That adjacency is contractual — the
@@ -365,7 +365,7 @@ Source: [`packages/goal/goal/src/domain.ts:66`](../packages/goal/goal/src/domain
```ts persistence-catalog
/**
* A hook command was invoked at a hook point — log-only provenance (like
* A hook command was invoked at a hook point — a log-only record (like
* `compact/*`; NOT a {@link SurfaceEventType}, carries no `surfaceOp`).
* `dialect` is the bridge that ran it (`claude`/`codex`), `point`
* the hook point (`PreToolUse`, `Stop`, …), `matcher` the matcher-group

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/subsystems/README.md
README.md: 1c3d14aeb4fd11cbc45eaadd83f0b84de0dfda52
README.zh.md: 04e07cc9c8b8b16c69bb09344e8533729525fe5c
README.md: 94e935998e39d4209476a8c62eb99fed0e0ce317
README.zh.md: 0f4a4c65797cf75acc6f937e8e8beb5fcd641652

View File

@@ -18,7 +18,7 @@ One page per subsystem of the DeepSeek Harness: what it is, the data structures
| [settings.md](settings.md) | the user-settings seam: `SettingsNamespace` registration, layered resolution (defaults → composition `base` → user document), owner scopes, hot commits |
| [credentials.md](credentials.md) | the credential seam: `CredentialRef` references (never values) in configuration, per-operation resolution, UI-safe `CredentialInfo`, provider source layers |
| [session-query.md](session-query.md) | logical records, bounded exact-event reads, relationship traces, semantic filters/documents, and full-text result pages |
| [session-title.md](session-title.md) | durable title snapshots, source provenance, and the asynchronous provider contract |
| [session-title.md](session-title.md) | durable title snapshots, cited source-message seqs, and the asynchronous provider contract |
| [session-reference.md](session-reference.md) | structured cross-session references: `SessionReferenceInput`/`Candidate`, prepared message contexts, the stable error taxonomy |
| [system-prompt.md](system-prompt.md) | per-assembly context, tool-provider results, prompt sections, and cooperative assembly |
| [tools.md](tools.md) | `ToolDefinition` full fields, the schema DSL, `ToolExecution`/`ToolResult`, tool-presentation UI types, and the guarded execution pipeline |

View File

@@ -18,7 +18,7 @@
| [settings.md](settings.md) | 用户设置 seam`SettingsNamespace` 注册、分层解析(默认值 → 组合 `base` → 用户文档、owner scope、热提交 |
| [credentials.md](credentials.md) | 凭据 seam配置中的 `CredentialRef` 引用(绝不含值)、按操作解析、对 UI 安全的 `CredentialInfo`、provider 来源层 |
| [session-query.md](session-query.md) | 逻辑记录、有界精确事件读取、关系追踪、语义筛选器/文档与全文检索结果页 |
| [session-title.md](session-title.md) | 持久标题快照、来源 provenance 与异步提供方约定 |
| [session-title.md](session-title.md) | 持久标题快照、被引用的来源消息 seq 与异步提供方约定 |
| [session-reference.md](session-reference.md) | 结构化跨会话引用:`SessionReferenceInput`/`Candidate`、prepared 消息上下文、稳定错误分类 |
| [system-prompt.md](system-prompt.md) | 逐次组装的上下文、工具提供方结果、提示词段落与协作式组装 |
| [tools.md](tools.md) | `ToolDefinition` 完整字段、schema DSL、`ToolExecution`/`ToolResult`、工具展示 UI 类型,以及受保护的执行流水线 |

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/subsystems/compaction.md
compaction.md: 5aef839192ea6f67bb409622187fe65fed795e73
compaction.zh.md: 20b4b2381f6e638e1d1b4db13ba98d53379b38db
compaction.md: 058fdba7969ac1f5b337b7ff008b0e62cf9ed035
compaction.zh.md: b4108ed5de55e027d633a180db968c7cd7ffee59

View File

@@ -8,15 +8,15 @@ Source: [`packages/compact/compact/src/types.ts`](../../packages/compact/compact
## The `compact/*` session events
Compaction extends [`SessionEventMap`](session.md) with three event types via declaration merging. All three are **log-only** — they record the compaction lock and its provenance, and never join the surface. `SurfaceEventType` is deliberately NOT extended (only message-producing events reach the model), so the summary itself rides on a separate `user/message` with `surfaceOp: { op: 'replace', start, end }` — the only surface mutation performed by summary compaction. See the Agent Note for why reusing `user/message` is honest rather than a workaround.
Compaction extends [`SessionEventMap`](session.md) with three event types via declaration merging. All three are **log-only** — they record the lock, summary, selected range, shadowed event seqs, token count, and model call without joining the surface. `SurfaceEventType` is deliberately NOT extended (only message-producing events reach the model), so the summary itself rides on a separate `user/message` with `surfaceOp: { op: 'replace', start, end }` — the only surface mutation performed by summary compaction. See the Agent Note for why reusing `user/message` is honest rather than a workaround.
| Event | Payload | Role |
|---|---|---|
| `compact/start` | `{ turn }` | acquires the log-recorded lock; a number identifies the open automatic turn, while `null` identifies a standalone manual attempt |
| `compact/summary` | `{ summary, rawOutput?, llmStreamCall?, shadowedRange, shadowedSeqs, shadowedTokenCount, provider, model, maxTokens?, usage? }` | provenance: the safe summary projection, optional complete provider output and usage, an `llmStreamCall: true` marker when producing the result consumed exactly one call through this context's `ctx.llm.stream()` (which requires complete `rawOutput`), the shadowed surface-boundary pair (`start`/`end` seqs — a position span, not a numeric interval), the shadowed seqs in surface order, the estimated token count, and the summarize call's envelope (`provider`, `model`, plus its generation cap when one applied) — logged so the one-shot request is reconstructable from log + code (the reconstructability Agent Note); unmarked `rawOutput` does not identify the call path |
| `compact/summary` | `{ summary, rawOutput?, llmStreamCall?, shadowedRange, shadowedSeqs, shadowedTokenCount, provider, model, maxTokens?, usage? }` | the safe summary projection, optional complete provider output and usage, an `llmStreamCall: true` marker when producing the result consumed exactly one call through this context's `ctx.llm.stream()` (which requires complete `rawOutput`), the shadowed surface-boundary pair (`start`/`end` seqs — a position span, not a numeric interval), the shadowed seqs in surface order, the estimated token count, and the summarize call's envelope (`provider`, `model`, plus its generation cap when one applied) — logged so the one-shot request is reconstructable from log + code (the reconstructability Agent Note); unmarked `rawOutput` does not identify the call path |
| `compact/end` | `{ turn, error? }` | releases the lock with the same numeric-or-null owner (`error` records an unsuccessful attempt) |
The lock brackets the **whole** operation: `compact/start` is appended first, then summarization, the `compact/summary` provenance record, and the `user/message` replacement all land, and only then `compact/end`. Releasing the lock last turns a crash mid-operation into a detectable orphaned lock (a `compact/start` with no matching `compact/end`) rather than a `compact/end` that falsely claims compaction finished.
The lock brackets the **whole** operation: `compact/start` is appended first, then summarization, the `compact/summary` record, and the `user/message` replacement all land, and only then `compact/end`. Releasing the lock last turns a crash mid-operation into a detectable orphaned lock (a `compact/start` with no matching `compact/end`) rather than a `compact/end` that falsely claims compaction finished.
The markers are lock time points, not an exclusive container. An unrelated idle injection can appear between a standalone manual start and end while summarization is pending. The manual path revalidates only its selected positional span, so that injected context survives after the replacement checkpoint. A live unmatched start blocks every entry point; an unmatched start before a newer `session/end-seed` is stale evidence from a prior lifecycle and is ignored.
@@ -88,7 +88,7 @@ The seam exports `toolPairingBalancedBefore(session, seq)` and `toolPairingBalan
The optional tool-result pruning service reports each durable content replacement and the aggregate Unicode-code-point reduction. Its public result types live in [`compact-tool-result-prune/src/types.ts`](../../packages/compact/compact-tool-result-prune/src/types.ts).
```ts type-equiv
/** Provenance and size accounting for one landed surface replacement. */
/** Cited source event and size accounting for one landed surface replacement. */
interface PrunedEntry {
/** Full-fidelity tool-result event shadowed by the replacement. */
readonly originalSeq: number
@@ -212,7 +212,7 @@ pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null
/**
* Prune every over-budget tool result from one stable current-surface snapshot.
* Each replacement preserves the complete event data except for `content`,
* points at the shadowed node for durable provenance and replay, and is
* cites the shadowed node so replay can recover the replacement input, and is
* immediately preceded by a `compact/prune` shadow-price event pricing the
* shadowed node through the injected token meter, so pure consumers can
* subtract it without per-node state.

View File

@@ -8,15 +8,15 @@
## `compact/*` 会话事件
压缩通过声明合并为 [`SessionEventMap`](session.md) 扩展三种事件类型。三者都**仅写入日志**——记录压缩锁及其 provenance,绝不进入 surface。这里有意不扩展 `SurfaceEventType`(只有产生消息的事件才到达模型),因此摘要本身承载在另一条带有 `surfaceOp: { op: 'replace', start, end }``user/message` 上——这是摘要压缩执行的唯一 surface 变更。关于复用 `user/message` 为何是如实建模而非权宜之计,见对应 Agent Note。
压缩通过声明合并为 [`SessionEventMap`](session.md) 扩展三种事件类型。三者都**仅写入日志**——它们记录锁、摘要、选中范围、被遮蔽事件 seq、token 数以及模型调用,绝不进入 surface。这里有意不扩展 `SurfaceEventType`(只有产生消息的事件才到达模型),因此摘要本身承载在另一条带有 `surfaceOp: { op: 'replace', start, end }``user/message` 上——这是摘要压缩执行的唯一 surface 变更。关于复用 `user/message` 为何是如实建模而非权宜之计,见对应 Agent Note。
| 事件 | 载荷 | 作用 |
|---|---|---|
| `compact/start` | `{ turn }` | 获取日志记录的锁;数字标识打开的自动轮次,`null` 标识独立手动尝试 |
| `compact/summary` | `{ summary, rawOutput?, llmStreamCall?, shadowedRange, shadowedSeqs, shadowedTokenCount, provider, model, maxTokens?, usage? }` | provenance安全摘要投影、可选的完整 provider 输出与 usage、生成结果时恰好通过此上下文的 `ctx.llm.stream()` 发起一次调用所带的 `llmStreamCall: true` 标记(此时必须提供完整的 `rawOutput`)、被遮蔽的 surface 边界对(`start`/`end` seq——位置跨度而非数值区间、按 surface 顺序排列的被遮蔽 seq、估算 token 数,以及摘要调用的 envelope`provider``model`,若有生成上限则还包括该上限)——写入日志后,该一次性请求可由日志 + 代码重建(见可重建性 Agent Note未带标记的 `rawOutput` 并不能判定调用路径 |
| `compact/summary` | `{ summary, rawOutput?, llmStreamCall?, shadowedRange, shadowedSeqs, shadowedTokenCount, provider, model, maxTokens?, usage? }` | 安全摘要投影、可选的完整 provider 输出与 usage、生成结果时恰好通过此上下文的 `ctx.llm.stream()` 发起一次调用所带的 `llmStreamCall: true` 标记(此时必须提供完整的 `rawOutput`)、被遮蔽的 surface 边界对(`start`/`end` seq——位置跨度而非数值区间、按 surface 顺序排列的被遮蔽 seq、估算 token 数,以及摘要调用的 envelope`provider``model`,若有生成上限则还包括该上限)——写入日志后,该一次性请求可由日志 + 代码重建(见可重建性 Agent Note未带标记的 `rawOutput` 并不能判定调用路径 |
| `compact/end` | `{ turn, error? }` | 使用相同的数字或 `null` 归属值释放锁(`error` 记录失败尝试) |
锁括住**整个**操作:先追加 `compact/start`,然后执行摘要生成、写入 `compact/summary` 来源记录与 `user/message` 替换,最后才追加 `compact/end`。最后释放锁意味着操作中途崩溃会表现为可检测的遗留锁(有 `compact/start` 而无匹配的 `compact/end`),而非一个虚假声称压缩已完成的 `compact/end`
锁括住**整个**操作:先追加 `compact/start`,然后执行摘要生成、写入 `compact/summary` 记录与 `user/message` 替换,最后才追加 `compact/end`。最后释放锁意味着操作中途崩溃会表现为可检测的遗留锁(有 `compact/start` 而无匹配的 `compact/end`),而非一个虚假声称压缩已完成的 `compact/end`
这些标记表示锁的时间点,而不是排他的容器。摘要等待期间,不相关的空闲注入可以出现在独立的手动 start 与 end 之间。手动路径只重新验证所选位置 span因此替换检查点之后仍保留该注入上下文。活动的未匹配 start 会阻塞所有入口点;较新 `session/end-seed` 之前的未匹配 start 是先前生命周期留下的陈旧证据,会被忽略。
@@ -88,7 +88,7 @@ type ManualCompactionErrorCode =
可选的工具结果剪枝服务会报告每次持久内容替换以及 Unicode code point 的总减少量。其公开结果类型位于 [`compact-tool-result-prune/src/types.ts`](../../packages/compact/compact-tool-result-prune/src/types.ts)。
```ts type-equiv
/** Provenance and size accounting for one landed surface replacement. */
/** Cited source event and size accounting for one landed surface replacement. */
interface PrunedEntry {
/** Full-fidelity tool-result event shadowed by the replacement. */
readonly originalSeq: number
@@ -212,7 +212,7 @@ pruneContent(blocks: readonly ContentBlock[]): ContentBlock[] | null
/**
* Prune every over-budget tool result from one stable current-surface snapshot.
* Each replacement preserves the complete event data except for `content`,
* points at the shadowed node for durable provenance and replay, and is
* cites the shadowed node so replay can recover the replacement input, and is
* immediately preceded by a `compact/prune` shadow-price event pricing the
* shadowed node through the injected token meter, so pure consumers can
* subtract it without per-node state.

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/subsystems/core.md
core.md: b09e5b4f0a076a7f95a751a6204836a81a4c31e9
core.zh.md: 01ac656fb657296fb26217108b8a4a15a5a88280
core.md: b6ed77c1f06b903924db31547d57e07ea37129be
core.zh.md: e0cf8b39ba2430e250c9d0e51eebf902537cf198

View File

@@ -107,7 +107,7 @@ interface Agent {
* cancel leaves it parked. A wake submitted while already idle always opens
* its turn boundary, even when its message is cleared before the driver
* claims ([cancel-convergence wake latch](../../../../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md)).
* @param message - identified content and its producer provenance.
* @param message - identified content and the source that supplied it.
* @param target - the preferred next-turn or next-step inbox boundary.
* @param wakeup - whether delivery may wake the driver.
*/
@@ -116,7 +116,7 @@ interface Agent {
/**
* Queue an ordinary follow-up turn and wake the driver. The item becomes the
* sole ordinary message of its own turn.
* @param message - identified prompt content and its producer provenance.
* @param message - identified prompt content and the source that supplied it.
*/
followup(message: UserMessage): void
@@ -125,7 +125,7 @@ interface Agent {
* a running driver consumes it at its next step boundary.
* A rejected step leaves steering parked in the inbox until the next
* wake; cancellation or disposal may discard pending steering.
* @param message - identified steering content and its producer provenance.
* @param message - identified steering content and the source that supplied it.
*/
steer(message: UserMessage): void
@@ -135,7 +135,7 @@ interface Agent {
* 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.
* @param message - identified injected context and the source that supplied it.
*/
inject(message: UserMessage): void
}
@@ -200,7 +200,7 @@ type AgentCancelCause =
| { readonly kind: 'disposed' }
```
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.
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; recording who requested cancellation would require a separate durable event rather than overloading the terminal result.
The [event taxonomy](../architecture.md#event) owns the `agent/*` lifecycle, checkpoint, and waterfall contracts. Turn and step boundaries are durable session events rather than agent emits.
@@ -210,7 +210,7 @@ The process-local initiator carried by `ctx.agents` is the exact `Agent` above,
## Interception decisions
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.
Pre-step decisions use the same identified `UserMessage` shape as durable user-role input. The entered batch is authoritative and preserves every message's id and source. Hook bridges map their native decision fields onto this typed result.
Source: [`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts)
@@ -243,7 +243,7 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
## Sessions
A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* from the log (`deriveMessages()`), not stored separately. Every entry carries a monotonic `seq`, a `time`, and a `type`-discriminated `data` payload; surface variants additionally carry `sourceEventSeqs` provenance and a `surfaceOp`.
A `Session` is an **append-only log** of typed `SessionEvent`s — the single source of truth. The LLM message history is *derived* from the log (`deriveMessages()`), not stored separately. Every entry carries a monotonic `seq`, a `time`, and a `type`-discriminated `data` payload; surface variants may also list cited earlier events in `sourceEventSeqs` and carry a `surfaceOp`.
The `SessionEvent` envelope's exact conditional shape, the twelve event variants (`turn/start`, `turn/end`, `step/start`, `step/end`, `user/message`, `assistant/chunk`, `assistant/message`, `tool/call`, `tool/result`, `steering/message`, `todo/write`, `request/header`), the `deriveMessages()` projection rules, the `TurnTrigger`/`TurnEndReason` reasons, and the execution-enclosure and standalone-event rules are on **[session.md](session.md)**. How the log is made durable — the `SessionPersistence` seam, JSONL/SQLite backends, the `session/flush` checkpoint, crash recovery, and `SessionHeader` — is on **[persistence.md](persistence.md)**.

View File

@@ -111,7 +111,7 @@ interface Agent {
* cancel leaves it parked. A wake submitted while already idle always opens
* its turn boundary, even when its message is cleared before the driver
* claims ([cancel-convergence wake latch](../../../../.agents/notes/implemented/bug-fix/2026-08-07-cancel-convergence-wake-latch.md)).
* @param message - identified content and its producer provenance.
* @param message - identified content and the source that supplied it.
* @param target - the preferred next-turn or next-step inbox boundary.
* @param wakeup - whether delivery may wake the driver.
*/
@@ -120,7 +120,7 @@ interface Agent {
/**
* Queue an ordinary follow-up turn and wake the driver. The item becomes the
* sole ordinary message of its own turn.
* @param message - identified prompt content and its producer provenance.
* @param message - identified prompt content and the source that supplied it.
*/
followup(message: UserMessage): void
@@ -129,7 +129,7 @@ interface Agent {
* a running driver consumes it at its next step boundary.
* A rejected step leaves steering parked in the inbox until the next
* wake; cancellation or disposal may discard pending steering.
* @param message - identified steering content and its producer provenance.
* @param message - identified steering content and the source that supplied it.
*/
steer(message: UserMessage): void
@@ -139,7 +139,7 @@ interface Agent {
* 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.
* @param message - identified injected context and the source that supplied it.
*/
inject(message: UserMessage): void
}
@@ -204,7 +204,7 @@ type AgentCancelCause =
| { readonly kind: 'disposed' }
```
cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有者会将它复制到仅运行时的 `AbortSignal.reason`signal 不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求 provenance,应使用单独的持久事件,而不是让终态结果承担额外含义。
cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有者会将它复制到仅运行时的 `AbortSignal.reason`signal 不授予协作监听器任何分类权限。持久 `turn/end` 保留粗粒度 `{ kind: 'aborted' }` 结果;若需记录请求了取消,应使用单独的持久事件,而不是让终态结果承担额外含义。
[事件分类](../architecture.md#event)拥有 `agent/*` 生命周期、检查点与 waterfall瀑布式事件约定。轮次和步骤边界是持久会话事件而不是 agent emit。
@@ -216,7 +216,7 @@ cause 是由 TypeScript 强制约束的同进程输入。活跃的取消持有
## 拦截决策
pre-step 决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。进入步骤的批次具有权威性,并保留每条消息的标识与 provenance。钩子桥接层把其原生决策字段映射到这一类型化结果上。
pre-step 决策使用与持久 user-role 输入相同、带标识的 `UserMessage` 形状。进入步骤的批次具有权威性,并保留每条消息的 id 和 source。钩子桥接层把其原生决策字段映射到这一类型化结果上。
源码:[`packages/core/agent/src/types.ts`](../../packages/core/agent/src/types.ts)
@@ -249,7 +249,7 @@ type SessionStartSource = 'startup' | 'resume' | 'clear' | 'compact'
## 会话
`Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM大语言模型消息历史从日志*派生*`deriveMessages()`),而非单独存储。每个条目携带单调的 `seq`、`time` 与按 `type` 判别的 `data` payloadsurface 变体还额外携带 `sourceEventSeqs` provenance 与 `surfaceOp`。
`Session` 是一份类型化 `SessionEvent` 的**仅追加日志**——唯一的真源。LLM大语言模型消息历史从日志*派生*`deriveMessages()`),而非单独存储。每个条目携带单调的 `seq`、`time` 与按 `type` 判别的 `data` payloadsurface 变体还可以在 `sourceEventSeqs` 中列出被引用的较早事件,并携带 `surfaceOp`。
`SessionEvent` 信封的确切条件形状、十二种事件变体(`turn/start`、`turn/end`、`step/start`、`step/end`、`user/message`、`assistant/chunk`、`assistant/message`、`tool/call`、`tool/result`、`steering/message`、`todo/write`、`request/header`)、`deriveMessages()` 投影规则、`TurnTrigger`/`TurnEndReason` 原因以及执行封闭和独立事件规则都在 **[session.md](session.md)** 中。日志如何持久化——`SessionPersistence` seam、JSONL/SQLite 后端、`session/flush` 检查点、崩溃恢复与 `SessionHeader`——则在 **[persistence.md](persistence.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/subsystems/llm-streaming.md
llm-streaming.md: 1705c45697df98b51ba9f69c8c91b3a8a18dea76
llm-streaming.zh.md: b47ae516e39cb6657ce5b6ada69d524d5f5ea174
llm-streaming.md: 37f2fcc51a8b899414927615a7c7750254fa9445
llm-streaming.zh.md: f9dab459be1006c6ea894498b9a28b49e9ec7738

View File

@@ -31,10 +31,10 @@ The block interfaces (full fields in source): `TextBlock` (`text`), `ReasoningBl
Source: [`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts)
A `Message` is one identified, immutable role/source/content value. Model-produced assistant messages carry provider/model ownership and optional adapter-private replay metadata in their source:
A `Message` is one identified, immutable role/source/content value. Model-produced assistant messages name the provider and model that produced them and carry optional adapter-private replay data in their source:
```ts type-equiv
/** Provider ownership and adapter-private replay data for an assistant message. */
/** Provider/model identity and adapter-private replay data for an assistant message. */
interface AssistantProvenance {
/** Provider route that produced the message. */
provider: string
@@ -58,7 +58,7 @@ interface Message {
readonly role: 'system' | 'user' | 'assistant'
/** Exact model-facing blocks. */
readonly content: ContentBlock[]
/** Required producer provenance. */
/** Required source fields supplied by the producer. */
readonly source: MessageSource
}
```
@@ -78,12 +78,12 @@ interface MessageSourceMap {
}
```
Provenance and shape are two independent axes. `kind` answers *who produced this*; the optional `form` a producer mixes in answers *what shape of information it is*, so several producers may share one presentation and one producer may emit more than one shape over a session. The vocabulary is semantic and grows one value at a time; an absent or unrecognized value is the documented default, presented as opaque content:
Producer identity and content shape are independent. `kind` answers *who produced this*; the optional `form` a producer mixes in answers *what shape of information it is*, so several producers may share one presentation and one producer may emit more than one shape over a session. The vocabulary is semantic and grows one value at a time; an absent or unrecognized value is the documented default, presented as opaque content:
```ts type-equiv
/**
* What SHAPE of information a producer-supplied context carries, declared by
* the producer beside its provenance.
* the producer beside the source fields it supplied.
*
* `MessageSource.kind` answers *who produced this*; `form` answers *what kind
* of thing it is*, and the two axes are deliberately independent — several
@@ -210,7 +210,7 @@ Every adapter MUST obey these, and every consumer may rely on them:
- **Context overflow has one canonical code.** Both DeepSeek adapters classify explicit provider detail through `isContextWindowExceededError()` and surface `CONTEXT_WINDOW_EXCEEDED`, whether the failure arrives as a thrown HTTP `LlmError` or an in-band finish error. Consumers route on the code, never provider text.
- **An empty completion is a retryable error, not a silent success.** Both adapters map a terminal `stop` finish that carried no content blocks to `finish {kind:'error'}` with the canonical `EMPTY_RESPONSE` code, and `dsh-llm-retry` retries it by default; see [empty model responses are retryable](../../.agents/notes/implemented/bug-fix/2026-07-24-empty-model-response-is-retryable.md).
- **Every provider HTTP request carries the app-attribution header.** Adapters send `attributionHeaders()` (below) - the `User-Agent` baseline - and prove it with a wire-level test (mock server asserting the received header, or the library's header hook for a library-backed adapter).
- **Replay state is adapter-owned.** A successful `finish` may carry lossless-JSON state needed to reconstruct a native provider response. The loop stores it with the assembled assistant message. On a later request, `LlmService` passes the state only when the historical provider and target provider are currently registered to the exact same adapter instance. That adapter validates the state and owns any cross-model or cross-provider conversion; other adapters receive the provider-neutral content and provenance without the private state.
- **Replay state is adapter-owned.** A successful `finish` may carry lossless-JSON state needed to reconstruct a native provider response. The loop stores it with the assembled assistant message. On a later request, `LlmService` passes the state only when the historical provider and target provider are currently registered to the exact same adapter instance. That adapter validates the state and owns any cross-model or cross-provider conversion; other adapters receive the provider-neutral content plus provider/model fields without the private state.
This contract is pinned down by two deliberately independent implementations: `dsh-llm-deepseek` (direct fetch, SSE framing via `eventsource-parser`) and `dsh-llm-pi-ai` (a generic multi-provider adapter through `@earendil-works/pi-ai`). The library-backed adapter exercises the finish-chunk error path, while transport-boundary tests prove each idle watchdog stops its actual request.
@@ -264,7 +264,7 @@ interface TokenUsage {
## `BlockAssembler`
`BlockAssembler` ([`packages/llm/llm/src/assembler.ts`](../../packages/llm/llm/src/assembler.ts)) is the single shared implementation that folds a `StreamChunk` stream back into `ContentBlock`s, usage, finish reason, and replay state. The loop logs the raw chunks while feeding the same chunks through an assembler, then stores the assembled assistant content with its provider/model provenance. A consumer that needs the assembled result without re-implementing the fold uses this.
`BlockAssembler` ([`packages/llm/llm/src/assembler.ts`](../../packages/llm/llm/src/assembler.ts)) is the single shared implementation that folds a `StreamChunk` stream back into `ContentBlock`s, usage, finish reason, and replay state. The loop logs the raw chunks while feeding the same chunks through an assembler, then stores the assembled assistant content with the provider and model that produced it. A consumer that needs the assembled result without re-implementing the fold uses this.
```ts public-api
/**
@@ -585,9 +585,9 @@ interface LlmDiscoveredModel {
### The request envelope: `LlmCallConfig` and the logged header
The loop builds each request from logged state. `EpochHeader` records call config, adapter-default provenance, rendered prompt, and authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md).
The loop builds each request from logged state. `EpochHeader` records call config, marks the fields supplied by adapter defaults, and records the rendered prompt and authoritative returned tool order (configured by `toolOrder`, or lexicographic when unset) through full `request/header` snapshots. Together with derived history, this makes the request reconstructable from the session log. See [session.md](session.md#the-request-header-event-requestheader) and the [reconstructability Agent Note](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md).
`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus provenance under the turn signal. The prepared call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests.
`agent/request` receives a frozen call-config seed and may return a replacement to switch provider, model, reasoning effort, or sampling. Before the waterfall, the loop removes values marked as adapter defaults so exact-model preparation materializes the selected route's current values; unmarked explicit settings remain in the proposal. After the waterfall, preparation rejects unsupported explicit effort ids without clamping and logs the effective config plus the fields supplied by adapter defaults under the turn signal. The prepared call keeps one adapter registration through dispatch. Requests reaching `llm/stream` are deep-frozen, so mutation throws, and carry a process-local loop identity so observers do not confuse separately logged frozen auxiliary calls with conversation requests.
On the wire, a loop-built request reads the `system` slot (the rendered prompt assembly) followed by the derived history — the boundary snapshot, whose tail is the newest `user/message` on a turn's first step and the previous step's tool results on later steps. The dev invariant recomputes exactly this equation against every loop-built request.

View File

@@ -31,10 +31,10 @@ interface ContentBlockMap {
源码:[`packages/llm/llm/src/message.ts`](../../packages/llm/llm/src/message.ts)
`Message` 是一个带标识且不可变的角色/来源/内容值。模型生的 assistant 消息会在来源中携带提供方模型所有权与可选的适配器私有回放数据:
`Message` 是一个带标识且不可变的角色/来源/内容值。模型生的 assistant 消息会在来源中记录生成它的提供方模型,以及可选的适配器私有回放数据:
```ts type-equiv
/** Provider ownership and adapter-private replay data for an assistant message. */
/** Provider/model identity and adapter-private replay data for an assistant message. */
interface AssistantProvenance {
/** Provider route that produced the message. */
provider: string
@@ -58,7 +58,7 @@ interface Message {
readonly role: 'system' | 'user' | 'assistant'
/** Exact model-facing blocks. */
readonly content: ContentBlock[]
/** Required producer provenance. */
/** Required source fields supplied by the producer. */
readonly source: MessageSource
}
```
@@ -78,12 +78,12 @@ interface MessageSourceMap {
}
```
溯源与形态相互独立的两根轴。`kind` 回答「由谁产生」;生产方可选混入的 `form` 回答「这是何种形态的信息」,因此多个生产方可以共用一种呈现,一个生产方在一次会话中也可以发出多种形态。该词汇表是语义的,逐个取值增长;未声明或无法识别的取值是有文档的默认,按不透明内容呈现:
生产方标识与内容形态相互独立。`kind` 回答「由谁产生」;生产方可选混入的 `form` 回答「这是何种形态的信息」,因此多个生产方可以共用一种呈现,一个生产方在一次会话中也可以发出多种形态。该词汇表是语义的,逐个取值增长;未声明或无法识别的取值是有文档的默认,按不透明内容呈现:
```ts type-equiv
/**
* What SHAPE of information a producer-supplied context carries, declared by
* the producer beside its provenance.
* the producer beside the source fields it supplied.
*
* `MessageSource.kind` answers *who produced this*; `form` answers *what kind
* of thing it is*, and the two axes are deliberately independent — several
@@ -214,7 +214,7 @@ interface LlmFailure {
- **上下文溢出只有一个规范 code。** 两个 DeepSeek 适配器都通过 `isContextWindowExceededError()` 对提供方的显式细节分类并暴露 `CONTEXT_WINDOW_EXCEEDED`,无论失败以抛出的 HTTP `LlmError` 还是带内 finish error 到达。消费方按 code 路由,绝不依赖提供方文本。
- **空 completion 是可重试错误,而不是静默的成功结果。** 两个适配器都把没有携带任何内容块的终止性 `stop` 结束映射为携带规范 `EMPTY_RESPONSE` code 的 `finish {kind:'error'}``dsh-llm-retry` 默认会重试它;详见[空模型响应可重试](../../.agents/notes/implemented/bug-fix/2026-07-24-empty-model-response-is-retryable.md)。
- **每个提供方 HTTP 请求都携带应用归属头。** 适配器发送 `attributionHeaders()`(见下文)作为 `User-Agent` 基线并通过协议级测试加以证明mock 服务器断言收到的 header或对基于库的适配器使用库的 header 钩子)。
- **回放状态归适配器所有。** 成功的 `finish` 可以携带重建提供方原生响应所需的无损 JSON 状态。循环会将其与组装后的 assistant 消息一起存储。后续请求中,仅当历史提供方与目标提供方当前注册到完全相同的适配器实例时,`LlmService` 才会传递该状态。该适配器负责校验状态并拥有所有跨模型或跨提供方转换;其他适配器只会收到提供方无关的内容与 provenance,不会收到私有状态。
- **回放状态归适配器所有。** 成功的 `finish` 可以携带重建提供方原生响应所需的无损 JSON 状态。循环会将其与组装后的 assistant 消息一起存储。后续请求中,仅当历史提供方与目标提供方当前注册到完全相同的适配器实例时,`LlmService` 才会传递该状态。该适配器负责校验状态并拥有所有跨模型或跨提供方转换;其他适配器只会收到提供方无关的内容以及提供方/模型字段,不会收到私有状态。
该约定由两个有意保持独立的实现锁定:`dsh-llm-deepseek`(直接 fetchSSEServer-Sent Events分帧经由 `eventsource-parser`)和 `dsh-llm-pi-ai`(通过 `@earendil-works/pi-ai` 实现的通用多提供方适配器)。基于库的适配器覆盖 finish 分片错误路径,而传输边界测试证明每个空闲 watchdog 都会停止其实际请求。
@@ -272,7 +272,7 @@ interface TokenUsage {
## `BlockAssembler`
`BlockAssembler`[`packages/llm/llm/src/assembler.ts`](../../packages/llm/llm/src/assembler.ts))是唯一的共享实现,负责把 `StreamChunk` 流折叠回 `ContentBlock`、usage、结束原因与回放状态。循环在记录原始分片的同时把同一批分片送入 assembler再将组装后的 assistant 内容连同提供方/模型 provenance 一起存储。需要组装结果、又不想重新实现 fold 的消费方使用它。
`BlockAssembler`[`packages/llm/llm/src/assembler.ts`](../../packages/llm/llm/src/assembler.ts))是唯一的共享实现,负责把 `StreamChunk` 流折叠回 `ContentBlock`、usage、结束原因与回放状态。循环在记录原始分片的同时把同一批分片送入 assembler再将组装后的 assistant 内容连同生成它的提供方模型一起存储。需要组装结果、又不想重新实现 fold 的消费方使用它。
```ts public-api
/**
@@ -593,9 +593,9 @@ interface LlmDiscoveredModel {
### 请求信封:`LlmCallConfig` 与记录的 header
循环从已记录状态构建每个请求。`EpochHeader` 通过完整的 `request/header` 快照记录调用配置、适配器默认值来源、渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Noteagent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。
循环从已记录状态构建每个请求。`EpochHeader` 记录调用配置,标记由适配器默认值提供的字段,并通过完整的 `request/header` 快照记录渲染后的提示词以及权威返回工具顺序(由 `toolOrder` 配置;未配置时按字典序)。结合派生历史,请求便可由会话日志重建。见 [session.md](session.md#the-request-header-event-requestheader) 与[可重建性 Agent Noteagent 决策记录)](../../.agents/notes/implemented/architecture/2026-07-05-reconstructable-requests.md)。
`agent/request` 接收冻结的调用配置种子并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall 开始前循环会移除标记为适配器默认值的值使确切模型准备过程填入所选路由的当前值未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID不自动调整并记录生效配置及其来源。准备完成的调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。
`agent/request` 接收冻结的调用配置种子并可返回替代值以切换提供方、模型、推理强度或采样参数。waterfall 开始前循环会移除标记为适配器默认值的值使确切模型准备过程填入所选路由的当前值未带标记的显式设置仍保留在提议中。waterfall 结束后,准备过程会在轮次信号控制下拒绝显式指定但不受支持的推理强度 ID不自动调整并记录生效配置以及由适配器默认值提供的字段。准备完成的调用直至分派完成始终持有同一项适配器注册。到达 `llm/stream` 的请求会被深度冻结,因此变更会抛异常;请求还携带进程本地循环标识,使观察者不会把单独记录的冻结辅助调用误认成对话请求。
在协议格式上,循环构建的请求先读取 `system` 槽位(渲染后的提示词组装),再读取派生历史——边界快照,其尾部在轮次首步是最新的 `user/message`,在后续步骤是上一步的工具结果。开发不变式针对每个循环构建的请求精确重算此等式。

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/subsystems/session-query.md
session-query.md: 54ae1eaf34a17d4979a621e4fc310780b425364e
session-query.zh.md: b770514757c30aa5d87b25e5d280b1ce6710cd9c
session-query.md: 9e907b2ebf12c01620921c7955f25d2fdd6ce4fb
session-query.zh.md: 4452330ebd5e487387c0ad15bea45b76d9c643be

View File

@@ -292,10 +292,10 @@ interface SessionEventWindow {
## Event relationships
Event traces distinguish positional surface replacement from logged provenance. Every seq list contains direct links except `replacementChain`, which follows immediate replacers from the target to the final positional replacement.
Event traces distinguish positional surface replacement from events cited as sources. Every seq list contains direct links except `replacementChain`, which follows immediate replacers from the target to the final positional replacement.
```ts type-equiv
/** Request for direct surface and provenance relationships around one event. */
/** Request for direct surface replacements and relationships to cited source events around one event. */
interface SessionEventTraceRequest {
/** Session that owns the target event. */
sessionId: SessionId
@@ -305,7 +305,7 @@ interface SessionEventTraceRequest {
```
```ts type-equiv
/** Direct surface and provenance relationships for one event. */
/** Direct surface replacements and relationships to cited source events for one event. */
interface SessionEventTrace {
/** Lightweight target record. */
target: SessionEventRecord
@@ -315,9 +315,9 @@ interface SessionEventTrace {
replacementChain: number[]
/** Surface nodes directly removed when the target itself performed a replacement. */
replacedEventSeqs: number[]
/** Direct logged provenance sources in their recorded order. */
/** Earlier events cited directly as sources, in their recorded order. */
sourceEventSeqs: number[]
/** Later events that directly name the target as a provenance source, in log order. */
/** Later events that directly cite the target as a source, in log order. */
derivedEventSeqs: number[]
}
```
@@ -471,11 +471,11 @@ async readSurface(sessionId: SessionId): Promise<SessionSurfaceSnapshot>
async traceSession(sessionId: SessionId, signal?: AbortSignal): Promise<SessionLineageTrace>
/**
* Trace one event's direct positional and provenance relationships.
* Trace one event's direct positional replacements and cited source events.
* @param request - target session id and event seq.
* @param signal - optional cancellation for persisted source resolution.
* @returns source header, direct links, and the target's positional replacement chain.
* @throws when source resolution fails, the target is absent, or surface/provenance validation fails.
* @throws when source resolution fails, the target is absent, or surface/source-event validation fails.
*/
async traceEvent(request: SessionEventTraceRequest, signal?: AbortSignal): Promise<SessionEventTraceObservation>

View File

@@ -292,10 +292,10 @@ interface SessionEventWindow {
## 事件关系
事件追踪会区分位置替换与日志中记录的来源关系。除 `replacementChain` 外,每个 seq 列表都只包含直接链接;该链从目标沿直接 replacer 追踪到最终的位置替换。
事件追踪会区分位置替换与被引用为来源的事件。除 `replacementChain` 外,每个 seq 列表都只包含直接链接;该链从目标沿直接 replacer 追踪到最终的位置替换。
```ts type-equiv
/** Request for direct surface and provenance relationships around one event. */
/** Request for direct surface replacements and relationships to cited source events around one event. */
interface SessionEventTraceRequest {
/** Session that owns the target event. */
sessionId: SessionId
@@ -305,7 +305,7 @@ interface SessionEventTraceRequest {
```
```ts type-equiv
/** Direct surface and provenance relationships for one event. */
/** Direct surface replacements and relationships to cited source events for one event. */
interface SessionEventTrace {
/** Lightweight target record. */
target: SessionEventRecord
@@ -315,9 +315,9 @@ interface SessionEventTrace {
replacementChain: number[]
/** Surface nodes directly removed when the target itself performed a replacement. */
replacedEventSeqs: number[]
/** Direct logged provenance sources in their recorded order. */
/** Earlier events cited directly as sources, in their recorded order. */
sourceEventSeqs: number[]
/** Later events that directly name the target as a provenance source, in log order. */
/** Later events that directly cite the target as a source, in log order. */
derivedEventSeqs: number[]
}
```
@@ -471,11 +471,11 @@ async readSurface(sessionId: SessionId): Promise<SessionSurfaceSnapshot>
async traceSession(sessionId: SessionId, signal?: AbortSignal): Promise<SessionLineageTrace>
/**
* Trace one event's direct positional and provenance relationships.
* Trace one event's direct positional replacements and cited source events.
* @param request - target session id and event seq.
* @param signal - optional cancellation for persisted source resolution.
* @returns source header, direct links, and the target's positional replacement chain.
* @throws when source resolution fails, the target is absent, or surface/provenance validation fails.
* @throws when source resolution fails, the target is absent, or surface/source-event validation fails.
*/
async traceEvent(request: SessionEventTraceRequest, signal?: AbortSignal): Promise<SessionEventTraceObservation>

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/subsystems/session-title.md
session-title.md: 3a2df284f2af14def284e502f4e37daaf4d7a262
session-title.zh.md: 3a74baa9005459b35b658df95bdd7b57256b3e2e
session-title.md: 339177b03a03b7d82375f7a293934cb164e9f266
session-title.zh.md: b1353025a4f621e9a3117c0406ad304c35828a49

View File

@@ -8,7 +8,7 @@ Sources: [`packages/session/session-title/src/index.ts`](../../packages/session/
## Durable title state
`SessionTitleProviderId` is recorded for provider-produced revisions. `SessionTitleEventData` carries exact human-message provenance, while `SessionTitleSnapshot` adds the durable event envelope facts selected by `foldSessionTitle()`.
`SessionTitleProviderId` is recorded for provider-produced revisions. `SessionTitleEventData` lists the exact human-message seqs used for the title, while `SessionTitleSnapshot` adds the durable event envelope facts selected by `foldSessionTitle()`.
```ts type-equiv
/** Identifies one session-title provider registration. */
@@ -47,7 +47,7 @@ interface SessionTitleEventData {
readonly title: string
/** Exact human `user/message` seqs used to derive this title; empty for an explicit user rename. */
readonly messageSeqs: number[]
/** Built-in fallback, registered-provider, or explicit-user provenance. */
/** Whether the built-in fallback, a registered provider, or the user supplied the title. */
readonly source: SessionTitleSource
}
```
@@ -86,7 +86,7 @@ interface SessionTitleLlmRequestEventData {
## Provider input and output
The service snapshots eligible messages through one revision. A provider returns only seqs from that request; service-owned acceptance verifies ordering, normalizes the title, enforces the byte limit, and appends provenance.
The service snapshots eligible messages through one revision. A provider returns only seqs from that request; service-owned acceptance verifies ordering, normalizes the title, enforces the byte limit, and appends the title with its source-message seqs and source kind.
```ts type-equiv
/** One eligible human text message exposed to title providers. */
@@ -132,14 +132,14 @@ interface SessionTitleProviderResult {
```ts type-equiv
/** One optional asynchronous title implementation registered with the service. */
interface SessionTitleProvider {
/** Stable provider identity recorded in title provenance. */
/** Stable id of the provider recorded with the title. */
readonly id: SessionTitleProviderId
/** When new human prompts start automatic generation. */
readonly automatic: SessionTitleAutomaticMode
/**
* Produce one title revision.
* @param request - message snapshot, current route, session, and cancellation.
* @returns proposed title plus exact input seqs and optional model provenance.
* @returns proposed title plus exact input seqs and the optional provider/model route used to generate it.
*/
generate(request: SessionTitleProviderRequest): Promise<SessionTitleProviderResult>
}

View File

@@ -8,7 +8,7 @@
## 持久标题状态
提供方生成修订时会记录 `SessionTitleProviderId``SessionTitleEventData` 携带精确人类消息来源信息`SessionTitleSnapshot` 则加入 `foldSessionTitle()` 选出的持久事件封装信息。
提供方生成修订时会记录 `SessionTitleProviderId``SessionTitleEventData` 列出生成标题时使用的精确人类消息 seq`SessionTitleSnapshot` 则加入 `foldSessionTitle()` 选出的持久事件封装信息。
```ts type-equiv
/** Identifies one session-title provider registration. */
@@ -47,7 +47,7 @@ interface SessionTitleEventData {
readonly title: string
/** Exact human `user/message` seqs used to derive this title; empty for an explicit user rename. */
readonly messageSeqs: number[]
/** Built-in fallback, registered-provider, or explicit-user provenance. */
/** Whether the built-in fallback, a registered provider, or the user supplied the title. */
readonly source: SessionTitleSource
}
```
@@ -86,7 +86,7 @@ interface SessionTitleLlmRequestEventData {
## 提供方输入与输出
服务会对截至某一修订的合格消息创建快照。提供方返回的 seq 仅可来自该请求;由服务负责的接纳流程会验证顺序、规范化标题、强制执行字节上限并追加来源信息
服务会对截至某一修订的合格消息创建快照。提供方返回的 seq 仅可来自该请求;由服务负责的接纳流程会验证顺序、规范化标题、强制执行字节上限并追加标题及其来源消息 seq 和来源类型
```ts type-equiv
/** One eligible human text message exposed to title providers. */
@@ -132,14 +132,14 @@ interface SessionTitleProviderResult {
```ts type-equiv
/** One optional asynchronous title implementation registered with the service. */
interface SessionTitleProvider {
/** Stable provider identity recorded in title provenance. */
/** Stable id of the provider recorded with the title. */
readonly id: SessionTitleProviderId
/** When new human prompts start automatic generation. */
readonly automatic: SessionTitleAutomaticMode
/**
* Produce one title revision.
* @param request - message snapshot, current route, session, and cancellation.
* @returns proposed title plus exact input seqs and optional model provenance.
* @returns proposed title plus exact input seqs and the optional provider/model route used to generate it.
*/
generate(request: SessionTitleProviderRequest): Promise<SessionTitleProviderResult>
}

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/subsystems/session.md
session.md: 7735e90807f6fce1b3645096437b3e86189dc8ec
session.zh.md: 63eb460f7ef300decf3e32c0ace16b354606c271
session.md: 6fb39e946cf5acdc407aab99888d40960d617248
session.zh.md: 30edd630b4c945d3b696b5ac346b4bcebfb3df16

View File

@@ -8,7 +8,7 @@ Source: [`packages/core/session/src/types.ts`](../../packages/core/session/src/t
## `SessionEventMap` — the event vocabulary
The append-only event types. Merge-extensible: a plugin declares extra event types via declaration merging — e.g. the [compaction seam](compaction.md) adds `compact/start` / `compact/summary` / `compact/end`, and `@deepseek-ai/dsh-hook-protocol` adds log-only `hook/invoked` / `hook/result` provenance for a hook bridge. Like `compact/*`, these are NOT `SurfaceEventType`s (no `surfaceOp`). The generated [persistence log event catalog](../persistence-catalog.md) enumerates every member — core and merged — with its payload, surface badge, and declaration site.
The append-only event types. Merge-extensible: a plugin declares extra event types via declaration merging — e.g. the [compaction seam](compaction.md) adds `compact/start` / `compact/summary` / `compact/end`, and `@deepseek-ai/dsh-hook-protocol` adds log-only `hook/invoked` / `hook/result` records for a hook bridge. Like `compact/*`, these are NOT `SurfaceEventType`s (no `surfaceOp`). The generated [persistence log event catalog](../persistence-catalog.md) enumerates every member — core and merged — with its payload, surface badge, and declaration site.
```ts type-equiv
/** A user-role specialization of the one shared message representation. */
@@ -151,7 +151,7 @@ interface TodoItem {
### The request header event: `request/header`
The request envelope — the `EpochHeader` (call config + adapter-default provenance + rendered system prompt + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a later changed request records another full snapshot with reason `'change'`. `foldRequestHeader(events)` reconstructs the header by selecting the latest snapshot. The event is not a `SurfaceEventType`: it produces no LLM message.
The request envelope — the `EpochHeader` (call config + markers for adapter-supplied defaults + rendered system prompt + assembled tool schemas) — is logged session state, so every conversation request is a pure function of the log (the reconstructability Agent Note). A full `request/header` snapshot with reason `'initial'` or `'resume'` records each loop-instance boundary; a later changed request records another full snapshot with reason `'change'`. `foldRequestHeader(events)` reconstructs the header by selecting the latest snapshot. The event is not a `SurfaceEventType`: it produces no LLM message.
```ts type-equiv
/**
@@ -217,11 +217,11 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
data: SessionEventMap[K]
} & (K extends SurfaceEventType ? {
/**
* Seq numbers of events that are provenance sources of this event
* Seq numbers of earlier events that this event cites as sources
* (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.
* provider stream; omission means the source stream was not recorded.
*/
sourceEventSeqs?: number[]
/** How this event entered the surface; absent for non-surface events. */
@@ -232,7 +232,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
`SessionEventType = keyof SessionEventMap`. Because `SessionEventMap` is merge-extensible, switches over `SessionEvent` must NOT use `assertNever` — a plugin-added variant is a valid unknown value; handle the known cases and fall through `default`.
For `assistant/message`, a present `sourceEventSeqs: []` is a complete known-empty provider stream, while an absent field means legacy or otherwise unrecorded provenance. The loop writes the field for every successful model call; every other surface event requires a non-empty list when the field is present.
For `assistant/message`, a present `sourceEventSeqs: []` is a complete known-empty provider stream, while an absent field means the source stream was not recorded in a legacy or foreign event. The loop writes the field for every successful model call; every other surface event requires a non-empty list when the field is present.
## Surface types
@@ -278,15 +278,15 @@ type SurfaceOp =
```ts type-equiv
/**
* Surface placement and provenance for {@link Session.append}. Required on
* Surface placement and cited source-event seqs for {@link Session.append}. Required on
* message-producing events and forbidden on log-only events.
*/
interface SurfaceIntent {
surfaceOp: SurfaceOp
/**
* Complete known provenance source set. `assistant/message` may use a
* Complete set of known source-event seqs. `assistant/message` may use a
* present empty array for a known empty provider stream; omission means its
* provenance was not recorded. Other surface events require a non-empty set
* source stream was not recorded. Other surface events require a non-empty set
* when this field is present.
*/
sourceEventSeqs?: number[]
@@ -295,7 +295,7 @@ interface SurfaceIntent {
Required for `SurfaceEventType` events — every message-producing event must declare how it joins the surface, the sole source of derived model history. A human-facing transcript is the other projection and reads the log's append-origin events instead, because the surface deliberately shadows the ranges a replacement summarizes (`isAppendSurfaceEvent` in [dsh-session](../../packages/core/session/README.md)). Non-surface types reject it at compile time.
The same provenance distinction applies here: only `assistant/message` may carry a present empty `sourceEventSeqs`; omission does not assert that its source stream was empty.
Only `assistant/message` may carry a present empty `sourceEventSeqs`; omission means the source stream was not recorded and does not assert that the stream was empty.
### `SessionSurface` — the live readonly surface projection
@@ -430,8 +430,8 @@ declare class Session {
* @param type - The event type (key of {@link SessionEventMap}).
* @param data - The event payload; must be JSON-serializable.
* @param opts - Surface metadata: `surfaceOp` controls how the event enters
* the ordered surface; `sourceEventSeqs` records provenance (the seq
* numbers of events this one derives from). REQUIRED for
* the ordered surface; `sourceEventSeqs` lists the seq numbers of earlier
* events this one derives from. REQUIRED for
* {@link SurfaceEventType} events (every message-producing event must
* declare how it joins the surface, the sole source of derived model
* history) and
@@ -445,7 +445,7 @@ declare class Session {
* circular reference, sparse array, or an exotic object such as
* Map/Set/Date/class instance), or when the candidate violates the
* canonical surface contract (marker shape and eligibility, unique
* earlier provenance, positional replacement validity, and complete
* earlier source-event references, positional replacement validity, and complete
* shadowed-node coverage). One recursive pass reads, validates, and
* copies each nested value once, so a stateful getter cannot supply one value
* to validation and another to storage. The event log is the durable source
@@ -508,11 +508,11 @@ declare class Session {
`Session.deriveMessages()` projects the event log into the `Message[]` the model sees — cached (each surface node projected once, when first seen; a surface rewrite rebuilds) and frozen (a fresh array per call over shared, deep-frozen messages, so mutating logged history through a projection is unrepresentable). `deriveEventMessage(event)` is the per-node pure function the fold applies — public so external reconstructors and the dev invariant project a log prefix with exactly the same rules and cannot disagree with the cache. The projection rules:
- `user/message` → a user message carrying exact `content`; an optional envelope remains log-only display metadata.
- `assistant/message` → an assistant message with the event's provider/model provenance and optional adapter-private replay state. Raw `assistant/chunk` events are replay/UI data and are **skipped** in derivation (the assembled message is authoritative). An **empty-content** `assistant/message` is also skipped — a max-tokens step cut off with no content still records an `assistant/message` to host its usage/provenance, but a content-less assistant turn must not enter the provider transcript.
- `assistant/message` → an assistant message with the provider and model that produced it plus optional adapter-private replay state. Raw `assistant/chunk` events are replay/UI data and are **skipped** in derivation (the assembled message is authoritative). An **empty-content** `assistant/message` is also skipped — a max-tokens step cut off with no content still records an `assistant/message` to hold its usage, provider, and model, but a content-less assistant turn must not enter the provider transcript.
- `tool/result` → a user message carrying a `tool-result` block.
- `user/message` (injected context, i.e. non-`user` source) → a user-role message carrying its `content` verbatim at its chronological position; provenance and domain data live in its typed source.
- `user/message` (injected context, i.e. non-`user` source) → a user-role message carrying its `content` verbatim at its chronological position; its typed source names the producer and carries any producer-specific data.
Everything else (`turn/*`, `step/*`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting reads per-step `assistant/chunk { type: 'usage' }` records and treats `assistant/message.usage` as the committed-step fallback when no usage chunk exists; failed model-request attempts have no assistant message, so their usage chunk is the durable accounting record. Because this unreleased format intentionally has no compatibility promise, seed/load validation rejects request headers without provider+model and assistant messages without provider/model provenance instead of guessing a route for historical data.
Everything else (`turn/*`, `step/*`, plugin-owned `llm/retry`) is structural and does not project into a message. Token accounting reads per-step `assistant/chunk { type: 'usage' }` records and treats `assistant/message.usage` as the committed-step fallback when no usage chunk exists; failed model-request attempts have no assistant message, so their usage chunk is the durable accounting record. Because this unreleased format intentionally has no compatibility promise, seed/load validation rejects request headers and assistant messages that omit provider/model instead of guessing a route for historical data.
## Live-session fork API
@@ -577,9 +577,9 @@ Activity ordering excludes the boundary through `lastActivityTime(events)`: pick
## Plugin-contributed log-only events
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).
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 generated [persistence log event catalog](../persistence-catalog.md) enumerates every core and plugin-contributed event with its payload, surface badge, cited source-event seqs, and declaration site; 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`. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` gets no `hook/*` record because it runs before turn 1; its context remains pending in the inbox until a waking delivery opens a turn (see [the hook-bridges Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md)).
The hook bridges' `hook/invoked` / `hook/result` pairs (from `@deepseek-ai/dsh-hook-protocol`) correlate by `handlerId`. `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, and `Stop` fire inside the loop's open turn, so their `hook/*` records are turn-enclosed by construction. `SessionStart` gets no `hook/*` record because it runs before turn 1; its context remains pending in the inbox until a waking delivery opens a turn (see [the hook-bridges Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md)).
## Durability contract

View File

@@ -8,7 +8,7 @@
## `SessionEventMap`:事件词汇
仅追加的事件类型。可通过声明合并扩展:插件通过 declaration merging 声明额外的事件类型。例如[压缩compaction seam](compaction.md) 添加了 `compact/start` / `compact/summary` / `compact/end``@deepseek-ai/dsh-hook-protocol` 添加了仅记录日志的 `hook/invoked` / `hook/result` 溯源事件,用于钩子桥接。与 `compact/*` 一样,这些都不是 `SurfaceEventType`(没有 `surfaceOp`)。生成的[持久化日志事件目录](../persistence-catalog.md)列举了所有成员(核心与合并扩展的),包含其 payload、surface 标记与声明位置。
仅追加的事件类型。可通过声明合并扩展:插件通过 declaration merging 声明额外的事件类型。例如[压缩compaction seam](compaction.md) 添加了 `compact/start` / `compact/summary` / `compact/end``@deepseek-ai/dsh-hook-protocol` 为钩子桥接添加了仅记录日志的 `hook/invoked` / `hook/result` 记录。与 `compact/*` 一样,这些都不是 `SurfaceEventType`(没有 `surfaceOp`)。生成的[持久化日志事件目录](../persistence-catalog.md)列举了所有成员(核心与合并扩展的),包含其 payload、surface 标记与声明位置。
```ts type-equiv
/** A user-role specialization of the one shared message representation. */
@@ -153,7 +153,7 @@ interface TodoItem {
### 请求头事件:`request/header`
请求信封(即 `EpochHeader`:调用配置 + 适配器默认值来源 + 渲染后的系统提示词 + 已组装的工具 schema会作为会话状态写入日志因此每个对话请求都是日志的纯函数见可重建性 Agent Note。带有 reason `'initial'` 或 `'resume'` 的完整 `request/header` 快照记录每个 agent loop 实例的边界;之后请求发生变化时,系统会以 reason `'change'` 记录另一份完整快照。`foldRequestHeader(events)` 通过选择最新快照重建请求头。该事件不是 `SurfaceEventType`,不产生 LLM 消息。
请求信封(即 `EpochHeader`:调用配置 + 适配器所提供默认值的标记 + 渲染后的系统提示词 + 已组装的工具 schema会作为会话状态写入日志因此每个对话请求都是日志的纯函数见可重建性 Agent Note。带有 reason `'initial'` 或 `'resume'` 的完整 `request/header` 快照记录每个 agent loop 实例的边界;之后请求发生变化时,系统会以 reason `'change'` 记录另一份完整快照。`foldRequestHeader(events)` 通过选择最新快照重建请求头。该事件不是 `SurfaceEventType`,不产生 LLM 消息。
```ts type-equiv
/**
@@ -219,11 +219,11 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
data: SessionEventMap[K]
} & (K extends SurfaceEventType ? {
/**
* Seq numbers of events that are provenance sources of this event
* Seq numbers of earlier events that this event cites as sources
* (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.
* provider stream; omission means the source stream was not recorded.
*/
sourceEventSeqs?: number[]
/** How this event entered the surface; absent for non-surface events. */
@@ -234,7 +234,7 @@ type SessionEvent<T extends SessionEventType = SessionEventType> = {
`SessionEventType = keyof SessionEventMap`。由于 `SessionEventMap` 可通过合并扩展,对 `SessionEvent` 的 switch 语句禁止使用 `assertNever`:插件添加的变体是合法的未知值;处理已知 case 后在 `default` 中放行。
对于 `assistant/message`,存在的 `sourceEventSeqs: []` 表示提供方流已知且完整地为空;字段缺失则表示旧格式或其他未记录溯源信息的情况。agent loop 会为每次成功的模型调用写入该字段;其他 surface 事件只要包含该字段,其列表就必须非空。
对于 `assistant/message`,存在的 `sourceEventSeqs: []` 表示提供方流已知且完整地为空;字段缺失则表示旧格式或外部事件没有记录源流。agent loop 会为每次成功的模型调用写入该字段;其他 surface 事件只要包含该字段,其列表就必须非空。
## Surface 类型
@@ -280,15 +280,15 @@ type SurfaceOp =
```ts type-equiv
/**
* Surface placement and provenance for {@link Session.append}. Required on
* Surface placement and cited source-event seqs for {@link Session.append}. Required on
* message-producing events and forbidden on log-only events.
*/
interface SurfaceIntent {
surfaceOp: SurfaceOp
/**
* Complete known provenance source set. `assistant/message` may use a
* Complete set of known source-event seqs. `assistant/message` may use a
* present empty array for a known empty provider stream; omission means its
* provenance was not recorded. Other surface events require a non-empty set
* source stream was not recorded. Other surface events require a non-empty set
* when this field is present.
*/
sourceEventSeqs?: number[]
@@ -297,7 +297,7 @@ interface SurfaceIntent {
对 `SurfaceEventType` 事件必填:每个产生消息的事件都必须声明它如何加入 surface派生模型历史的唯一来源。面向人类的记录transcript是另一个投影读取的是日志中追加来源的事件因为 surface 会有意遮蔽替换所概括的范围(见 [dsh-session](../../packages/core/session/README.md) 的 `isAppendSurfaceEvent`)。非 surface 类型在编译期拒绝此参数。
此处适用相同的溯源区分:只有 `assistant/message` 可以携带存在但为空的 `sourceEventSeqs`;省略该字段并不表示源流为空。
只有 `assistant/message` 可以携带存在但为空的 `sourceEventSeqs`;省略该字段表示源流没有记录,而不是表示源流为空。
### `SessionSurface`:实时只读 surface 投影
@@ -432,8 +432,8 @@ declare class Session {
* @param type - The event type (key of {@link SessionEventMap}).
* @param data - The event payload; must be JSON-serializable.
* @param opts - Surface metadata: `surfaceOp` controls how the event enters
* the ordered surface; `sourceEventSeqs` records provenance (the seq
* numbers of events this one derives from). REQUIRED for
* the ordered surface; `sourceEventSeqs` lists the seq numbers of earlier
* events this one derives from. REQUIRED for
* {@link SurfaceEventType} events (every message-producing event must
* declare how it joins the surface, the sole source of derived model
* history) and
@@ -447,7 +447,7 @@ declare class Session {
* circular reference, sparse array, or an exotic object such as
* Map/Set/Date/class instance), or when the candidate violates the
* canonical surface contract (marker shape and eligibility, unique
* earlier provenance, positional replacement validity, and complete
* earlier source-event references, positional replacement validity, and complete
* shadowed-node coverage). One recursive pass reads, validates, and
* copies each nested value once, so a stateful getter cannot supply one value
* to validation and another to storage. The event log is the durable source
@@ -510,11 +510,11 @@ declare class Session {
`Session.deriveMessages()` 将事件日志投影为模型看到的 `Message[]`。它是缓存的(每个 surface 节点在首次出现时投影一次surface 重写触发重建)且冻结的(每次调用返回一个新数组,引用共享的深冻结消息,因此通过投影修改已记录的历史在类型上不可表达)。`deriveEventMessage(event)` 是折叠所应用的逐节点纯函数,公开暴露以便外部重建器和开发不变式检查能以完全相同的规则投影日志前缀,不会与缓存产生分歧。投影规则:
- `user/message` → 一条携带确切 `content` 的 user 消息;可选 envelope 仅作为日志中的展示元数据保留。
- `assistant/message` → 一条 assistant 消息,包含事件的提供方/模型溯源信息和可选的适配器私有回放状态。原始 `assistant/chunk` 事件属于回放/UI 数据,在派生时会被**跳过**(组装后的消息才是权威)。**内容为空的** `assistant/message` 也会跳过:因 max-tokens 而截断且无内容的步骤仍会记录一条 `assistant/message` 以承载用量和溯源信息,但无内容的 assistant 轮次不得进入提供方 transcript文本记录
- `assistant/message` → 一条 assistant 消息,包含生成它的提供方模型,以及可选的适配器私有回放状态。原始 `assistant/chunk` 事件属于回放/UI 数据,在派生时会被**跳过**(组装后的消息才是权威)。**内容为空的** `assistant/message` 也会跳过:因 max-tokens 而截断且无内容的步骤仍会记录一条 `assistant/message` 来保存用量、提供方和模型,但无内容的 assistant 轮次不得进入提供方 transcript文本记录
- `tool/result` → 一条携带 `tool-result` 块的 user 消息。
- `user/message`(注入上下文,即非 `user` 来源)→ 按时间顺序在相应位置生成一条 user-role 消息,并原样承载其 `content`溯源信息与领域数据都在其类型化 source
- `user/message`(注入上下文,即非 `user` 来源)→ 按时间顺序在相应位置生成一条 user-role 消息,并原样承载其 `content`;其类型化 source 标明生产方,并携带所有生产方专用数据
其余所有事件(`turn/*`、`step/*`、插件所有的 `llm/retry`均为结构信息不会投影为消息。token 记账读取每个步骤的 `assistant/chunk { type: 'usage' }` 记录;如果没有用量分片,则将 `assistant/message.usage` 作为已提交步骤的后备。失败的模型请求尝试没有 assistant 消息因此其用量分片是持久化的记账记录。由于这一尚未发布的格式有意不提供兼容性承诺seed/load 校验会拒绝缺少提供方模型的请求头,以及缺少提供方/模型溯源信息的 assistant 消息,而不会猜测历史数据应走的提供方路由。
其余所有事件(`turn/*`、`step/*`、插件所有的 `llm/retry`均为结构信息不会投影为消息。token 记账读取每个步骤的 `assistant/chunk { type: 'usage' }` 记录;如果没有用量分片,则将 `assistant/message.usage` 作为已提交步骤的后备。失败的模型请求尝试没有 assistant 消息因此其用量分片是持久化的记账记录。由于这一尚未发布的格式有意不提供兼容性承诺seed/load 校验会拒绝没有提供方模型的请求头 assistant 消息,而不会猜测历史数据应走的提供方路由。
## 活跃会话 fork API
@@ -581,9 +581,9 @@ interface TurnEndReasonMap {
## 插件贡献的仅日志事件
插件可以通过 declaration merging 添加额外的 `SessionEventMap` 类型。这些是**仅日志**事件:不是 `SurfaceEventType`(不携带 `surfaceOp`,不参与派生历史)。事件所有方决定它们属于一个开放的执行轮次,还是可以独立位于轮次之间,并在自己的不变量配套插件中强制所需关系。完整的逐事件枚举(核心与插件贡献的,含 payload 与溯源信息)见生成的[持久化日志事件目录](../persistence-catalog.md);压缩 seam 的 `compact/*` 语义在 [compaction.md](compaction.md) 中讨论。
插件可以通过 declaration merging 添加额外的 `SessionEventMap` 类型。这些是**仅日志**事件:不是 `SurfaceEventType`(不携带 `surfaceOp`,不参与派生历史)。事件所有方决定它们属于一个开放的执行轮次,还是可以独立位于轮次之间,并在自己的不变量配套插件中强制所需关系。生成的[持久化日志事件目录](../persistence-catalog.md)会列出每个核心或插件贡献的事件,以及其 payload、surface 标记、被引用的来源事件 seq 和声明位置;压缩 seam 的 `compact/*` 语义在 [compaction.md](compaction.md) 中讨论。
钩子桥接层的 `hook/invoked` / `hook/result` 溯源对(来自 `@deepseek-ai/dsh-hook-protocol`)通过 `handlerId` 关联。`UserPromptSubmit`、`PreToolUse`、`PostToolUse` 与 `Stop` 在 loop 已打开的轮次内触发,因此其 `hook/*` 记录天然位于轮次之内。`SessionStart` 不生成 `hook/*` 记录,因为它在轮次 1 之前运行;其上下文会在 inbox 中保持待处理,直到唤醒交付打开一个轮次(见[钩子桥接 Agent Note](../../.agents/notes/implemented/feature/2026-06-30-hook-bridges.md))。
钩子桥接层的 `hook/invoked` / `hook/result` 对(来自 `@deepseek-ai/dsh-hook-protocol`)通过 `handlerId` 关联。`UserPromptSubmit`、`PreToolUse`、`PostToolUse` 与 `Stop` 在 loop 已打开的轮次内触发,因此其 `hook/*` 记录天然位于轮次之内。`SessionStart` 不生成 `hook/*` 记录,因为它在轮次 1 之前运行;其上下文会在 inbox 中保持待处理,直到唤醒交付打开一个轮次(见[钩子桥接 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/subsystems/spill.md
spill.md: 41a3fdaf7d9b15fbc6d479724f77f579b8a98738
spill.zh.md: 02a6fc4e88dfcfdb47f7639d988fc09c6bc42c69
spill.md: a0a476761ab0633227d7c87033daee79f649bf50
spill.zh.md: 65d7e4cb89a93e2e709af0a620f523aad2315ed2

View File

@@ -8,7 +8,7 @@ Source: [`packages/spill/spill/src/types.ts`](../../packages/spill/spill/src/typ
## The save request
`saveText` is the whole seam: persist `content` verbatim, return an opaque locator, a backend-supplied retrieval hint, and the exact byte count. The request carries the save-time storage namespace (`owner`), WHERE it came from (`source`, descriptive provenance for naming and inspection — not access control), and a `suggestedName` the backend may use as a naming hint (it is not a path).
`saveText` is the whole seam: persist `content` verbatim, return an opaque locator, a backend-supplied retrieval hint, and the exact byte count. The request carries the save-time storage namespace (`owner`), the tool and call that produced it (`source`, used for naming and inspection — not access control), and a `suggestedName` the backend may use as a naming hint (it is not a path).
```ts type-equiv
/** One request to persist text to a spill artifact. */
@@ -42,7 +42,7 @@ interface SpillOwner {
```ts type-equiv
/**
* Provenance of one spilled artifact — recorded by the backend for a readable
* Tool and call that produced one spilled artifact — recorded by the backend for a readable
* filename and inspection. Not interpreted for access control; purely
* descriptive.
*/
@@ -107,7 +107,7 @@ Semantics every implementation must honor:
```ts cordis-catalog
/**
* Persist `input.content` to a session-scoped spill artifact.
* @param input - the owner, provenance, suggested name, and full text to save.
* @param input - the owner, caller-supplied source fields, suggested name, and full text to save.
* @returns the saved artifact's {@link SpillRef}; rejects on a storage failure.
*/
abstract saveText(input: SaveTextSpill): Promise<SpillRef>

View File

@@ -8,7 +8,7 @@
## 保存请求
`saveText` 是整个 seam原样持久保存 `content`,并返回不透明的定位符、后端提供的检索提示和准确字节数。请求携带保存时的存储命名空间(`owner`)、内容来源`source`,用于命名和检查的描述性来源信息,而非访问控制)以及后端可用作命名提示的 `suggestedName`(它不是路径)。
`saveText` 是整个 seam原样持久保存 `content`,并返回不透明的定位符、后端提供的检索提示和准确字节数。请求携带保存时的存储命名空间(`owner`)、生成内容的工具和调用`source`,用于命名和检查,而非访问控制)以及后端可用作命名提示的 `suggestedName`(它不是路径)。
```ts type-equiv
/** One request to persist text to a spill artifact. */
@@ -42,7 +42,7 @@ interface SpillOwner {
```ts type-equiv
/**
* Provenance of one spilled artifact — recorded by the backend for a readable
* Tool and call that produced one spilled artifact — recorded by the backend for a readable
* filename and inspection. Not interpreted for access control; purely
* descriptive.
*/
@@ -107,7 +107,7 @@ Semantics every implementation must honor:
```ts cordis-catalog
/**
* Persist `input.content` to a session-scoped spill artifact.
* @param input - the owner, provenance, suggested name, and full text to save.
* @param input - the owner, caller-supplied source fields, suggested name, and full text to save.
* @returns the saved artifact's {@link SpillRef}; rejects on a storage failure.
*/
abstract saveText(input: SaveTextSpill): Promise<SpillRef>

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/subsystems/subagent.md
subagent.md: 3bf3ac7c48bfa08be86314bb43263d4ee6198654
subagent.zh.md: 37b274d7f3ffa5b5aadea773d29f7e6b4e5a833b
subagent.md: cbb7c37f1ff3bce673f78a29ff8993659078df73
subagent.zh.md: 308770141aa44fadeabed5f080d0df47c3802f15

View File

@@ -137,7 +137,7 @@ persisted Session
The Agent inbox is the only queue. Every continuation message becomes one `Agent.followup()` FIFO turn, so accepted messages have one observable order and a follow-up cannot redirect a turn already underway. Successful delivery returns the accepted `MessageId`; the existing `agent/inbox/enqueue`, `agent/inbox/dequeue`, and `agent/inbox/discard` events remain the message-lifecycle observations, and the continuation layer defines no subagent-specific delivery route.
Follow-up authority comes from an exact live Agent tool context. The authenticated Agent must be the durable child's direct parent recorded in `SessionHeader.parentSession`. `MessageSource` and `senderSessionId` are durable provenance after admission and grant no authority; the optional model-facing tool uses `CoordinatorMessageSource`.
Follow-up authority comes from an exact live Agent tool context. The authenticated Agent must be the durable child's direct parent recorded in `SessionHeader.parentSession`. `MessageSource` and `senderSessionId` record who supplied an admitted message but grant no authority; the optional model-facing tool uses `CoordinatorMessageSource`.
For both operations the caller signal owns lookup, materialization, and admission only until inbox acceptance. Afterwards the manager owns the Activation independently: later caller cancellation neither cancels the accepted turn nor disposes the child, and the seam exposes no steering operation.
@@ -425,7 +425,7 @@ interface SubagentProvider {
}
```
Provider `start()` fulfills with a published run. The service mints a unique `runId`, snapshots `local` from the provider's exact `localAgent`, observes the result, emits `subagent/start`, and returns the same run; a `start()` rejection implies cleanup of unpublished resources and emits no lifecycle pair, while a post-publication result rejection closes the emitted pair. Each continuable Activation emits the same observe-only pair for its residency epoch, so a cold resume is a new epoch with its own `runId`. The paired `subagent/end` carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions. Their `provider` field is provenance for the run or Activation epoch, not a claim that the provider remains registered when the edge is emitted.
Provider `start()` fulfills with a published run. The service mints a unique `runId`, snapshots `local` from the provider's exact `localAgent`, observes the result, emits `subagent/start`, and returns the same run; a `start()` rejection implies cleanup of unpublished resources and emits no lifecycle pair, while a post-publication result rejection closes the emitted pair. Each continuable Activation emits the same observe-only pair for its residency epoch, so a cold resume is a new epoch with its own `runId`. The paired `subagent/end` carries the same identity and the final output or infrastructure failure. Both events are observe-only and contain listener exceptions. Their `provider` field names the provider that started the run or Activation epoch; it does not claim that the provider remains registered when the edge is emitted.
## In-process backends: depth and seed
@@ -469,7 +469,7 @@ async startContinuable(spec: ContinuableStartSpec): Promise<ContinuableStart>
* @param parent - the exact live direct parent authorizing this delivery.
* @param childId - durable child session id.
* @param content - user-role content to deliver.
* @param options - durable provenance and caller cancellation, which stops the
* @param options - the message source fields and caller cancellation, which stops the
* operation only before inbox acceptance.
* @returns the accepted message's inbox id.
* @throws when continuation services are unavailable, parent authority is

View File

@@ -137,7 +137,7 @@ persisted Session
Agent 收件箱是唯一的队列。每条继续执行消息都会成为一个 `Agent.followup()` FIFO 轮次,因此已接受的消息共享同一个可观测顺序,且后续消息无法改变已在进行中的轮次。投递成功会返回被接受的 `MessageId`;既有的 `agent/inbox/enqueue`、`agent/inbox/dequeue` 与 `agent/inbox/discard` 事件仍是消息生命周期的观测点,继续执行层不定义任何 subagent 专属的投递路由。
后续操作的权限来自确切的在线 Agent 工具上下文。已认证的 Agent 必须是持久化子 agent 在 `SessionHeader.parentSession` 中记录的直接父级。`MessageSource` 与 `senderSessionId` 在准入之后是持久的来源凭据,不授予任何权限;可选的面向模型工具使用 `CoordinatorMessageSource`。
后续操作的权限来自确切的在线 Agent 工具上下文。已认证的 Agent 必须是持久化子 agent 在 `SessionHeader.parentSession` 中记录的直接父级。`MessageSource` 与 `senderSessionId` 记录谁提供了已准入的消息,但不授予任何权限;可选的面向模型工具使用 `CoordinatorMessageSource`。
对于这两种操作,调用方 signal 仅在收件箱接受之前掌管查找、物化与准入。此后管理器独立掌管该 Activation之后的调用方取消既不会取消已接受的轮次也不会 dispose 子 agent并且该 seam 不对外暴露任何 steering中途引导操作。
@@ -427,7 +427,7 @@ interface SubagentProvider {
}
```
提供方的 `start()` 会以已发布的 run fulfill。服务铸造唯一的 `runId`,从提供方确切的 `localAgent` 快照 `local`观察结果emit `subagent/start`,并返回同一个 run`start()` rejection 意味着未发布资源已清理,且不会 emit 生命周期事件对,而发布后的结果 rejection 会结束已经 emit 的事件对。每个可继续 Activation 都会为其驻留纪元 emit 相同的仅观察事件对,因此一次冷恢复就是一段拥有自己 `runId` 的新纪元。配对的 `subagent/end` 携带相同标识与最终输出或基础设施失败。两个事件都仅用于观察,且会隔离各自的 listener 异常。其中的 `provider` 字段 run 或 Activation 时段的来源信息,并不声明该 edge 发出时提供方仍处于注册状态。
提供方的 `start()` 会以已发布的 run fulfill。服务铸造唯一的 `runId`,从提供方确切的 `localAgent` 快照 `local`观察结果emit `subagent/start`,并返回同一个 run`start()` rejection 意味着未发布资源已清理,且不会 emit 生命周期事件对,而发布后的结果 rejection 会结束已经 emit 的事件对。每个可继续 Activation 都会为其驻留纪元 emit 相同的仅观察事件对,因此一次冷恢复就是一段拥有自己 `runId` 的新纪元。配对的 `subagent/end` 携带相同标识与最终输出或基础设施失败。两个事件都仅用于观察,且会隔离各自的 listener 异常。其中的 `provider` 字段标明了启动 run 或 Activation 时段的提供方,并不声明该 edge 发出时提供方仍处于注册状态。
## 进程内后端:深度与种子
@@ -471,7 +471,7 @@ async startContinuable(spec: ContinuableStartSpec): Promise<ContinuableStart>
* @param parent - the exact live direct parent authorizing this delivery.
* @param childId - durable child session id.
* @param content - user-role content to deliver.
* @param options - durable provenance and caller cancellation, which stops the
* @param options - the message source fields and caller cancellation, which stops the
* operation only before inbox acceptance.
* @returns the accepted message's inbox id.
* @throws when continuation services are unavailable, parent authority is

View File

@@ -1021,7 +1021,7 @@ Source: [`packages/session-query/tool-session-query/src/index.ts`](../packages/s
### `session_event_trace`
Read every direct replacement and provenance relationship for one event in an authorized session.
Read every direct replacement and relationship to a cited source event for one event in an authorized session.
```json
{