From f5774a2a269322a73fad447f19b361227c28e322 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sat, 4 Jul 2026 05:41:18 -0700 Subject: [PATCH 01/38] =?UTF-8?q?feat(i18n):=20new=20documents=20merge=20b?= =?UTF-8?q?ilingual=20=E2=80=94=20the=20requiredSince=20date=20frontier?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New docs must not enlarge the translation backlog: any date-named document (yyyy-mm-dd-*.md, i.e. an RFC) dated on/after the manifest's requiredSince cutoff must merge with its complete pair, independent of the required back-catalog list. Cutoff ships at 2026-07-05, so the existing 2026-07-04 RFC wave is grandfathered into the batch backlog and everything after is bilingual from birth. The date comes from the filename, deliberately: it is deterministic from tree content alone — no git history (shallow CI checkouts hold), no PR base ref — matching the gate's pure-content design. A proposal dated before the cutoff that merges later escapes the rule; that is the graceful grandfathering of in-flight work, not a hole. --list marks such files (required by date); contract and RFC updated in both languages and the pairs re-recorded. --- docs/i18n/README.i18n.yaml | 4 +-- docs/i18n/README.md | 2 +- docs/i18n/README.zh.md | 2 +- ...-bilingual-docs-and-pairing-gate.i18n.yaml | 4 +-- ...6-07-02-bilingual-docs-and-pairing-gate.md | 2 +- ...7-02-bilingual-docs-and-pairing-gate.zh.md | 2 +- scripts/translation-pairing.manifest.json | 1 + scripts/verify-translation-pairing.ts | 28 +++++++++++++++++-- 8 files changed, 34 insertions(+), 11 deletions(-) diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index 7a2407ed36..8fc169bdaa 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.i18n.yaml @@ -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 -README.md: 6e2bbd27c3288037bafeb6cc71b801d56b956ab4 -README.zh.md: 04c99ae336cf1e96cbc185f0ccbd063ef8977944 +README.md: ba3431c72d9f2a86220df4ed682620514401291f +README.zh.md: 02957c97937525a1da27242620a8327b863de040 diff --git a/docs/i18n/README.md b/docs/i18n/README.md index 6e2bbd27c3..ba3431c72d 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -43,7 +43,7 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co - `docs/AGENTS.md` — agent instructions, maintained in English only like the root `AGENTS.md`. - `docs/i18n/terminology.md` — the terminology table is itself bilingual by construction. -**Rollout**: the `required` list in the manifest is the enforcement frontier, not the goal. The goal is full bilingual coverage of the scope. Pairs land in reviewable batches (core entry docs, cookbook, RFCs, postmortems, …); each merged batch adds its files to `required`, so the gate ratchets forward and never regresses. Documents not yet in `required` are backlog — visible in `--list` — but any pair that already exists is held to the full contract regardless of the list. Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it. +**Rollout**: new documents don't wait for a batch — a date-named document (`yyyy-mm-dd-*.md`, i.e. an RFC) dated on or after the manifest's `requiredSince` cutoff must merge with its pair, so everything new is bilingual from birth. For the back-catalog, the `required` list in the manifest is the enforcement frontier, not the goal. The goal is full bilingual coverage of the scope. Pairs land in reviewable batches (core entry docs, cookbook, RFCs, postmortems, …); each merged batch adds its files to `required`, so the gate ratchets forward and never regresses. Documents not yet in `required` are backlog — visible in `--list` — but any pair that already exists is held to the full contract regardless of the list. Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it. ## Division of labor diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index 04c99ae336..02957c9793 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -43,7 +43,7 @@ - `docs/AGENTS.md`——agent 指令,与根 `AGENTS.md` 一样只以英文维护。 - `docs/i18n/terminology.md`——术语表本身即是双语构造。 -**推进**:manifest 中的 `required` 列表是强制边界,不是目标。目标是范围内的全量双语覆盖。配对按可评审的批次落地(核心入口文档、cookbook、RFC、postmortem……);每个批次合入后把其文件加进 `required`,门禁只进不退。尚未进入 `required` 的文档是 backlog——在 `--list` 中可见——但任何已存在的配对无论在不在清单里都按完整契约检查。给一篇文档配对是一份承诺:此后对任一侧的每次修改都必须带上另一侧,所以边界的扩张要跟上翻译评审的实际投入节奏,不要抢在前面。 +**推进**:新文档不等批次——文件名带日期的文档(`yyyy-mm-dd-*.md`,即 RFC)日期在 manifest 的 `requiredSince` 当天或之后,就必须连同配对一起合入,新增的一切生来即是双语。对于存量文档,manifest 中的 `required` 列表是强制边界,不是目标。目标是范围内的全量双语覆盖。配对按可评审的批次落地(核心入口文档、cookbook、RFC、postmortem……);每个批次合入后把其文件加进 `required`,门禁只进不退。尚未进入 `required` 的文档是 backlog——在 `--list` 中可见——但任何已存在的配对无论在不在清单里都按完整契约检查。给一篇文档配对是一份承诺:此后对任一侧的每次修改都必须带上另一侧,所以边界的扩张要跟上翻译评审的实际投入节奏,不要抢在前面。 ## 分工 diff --git a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml index bc9a1cd466..1ef023ec50 100644 --- a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml +++ b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml @@ -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 -2026-07-02-bilingual-docs-and-pairing-gate.md: 517a6371eca5d747313c7efdb2756a50257701e4 -2026-07-02-bilingual-docs-and-pairing-gate.zh.md: f8f68bf5d4d7e6795318d9dd435a525f20a4f407 +2026-07-02-bilingual-docs-and-pairing-gate.md: 764ad5a9345c2a138b56e9cedb54848a5f7d6054 +2026-07-02-bilingual-docs-and-pairing-gate.zh.md: f039fbe3fa44cb379905bbea9a4692af19d71230 diff --git a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md index 517a6371ec..764ad5a934 100644 --- a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md +++ b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md @@ -32,5 +32,5 @@ Paired sibling files with locale suffixes are the dominant Chinese big-tech conv - Every pair adds a third file to the tree. The record is machine-written (`--write`), so the cost is directory noise, not maintenance effort; in exchange, "who confirmed these consistent, and when" is answerable from git blame on the yaml. - When the two sides disagree, no mechanical rule picks a winner — the PR review does. That is the price of equal authority, accepted deliberately: the alternative (a canonical language) forbids Chinese-first authoring. - Generated docs (`cordis-catalog/`, `tool-catalog/`, `module-graph.md`) are excluded for now; the planned follow-up is to teach their generators to emit Chinese alongside English, at which point they leave the exclusion list. -- Rollout is incremental by design: documents outside `required` are visible backlog (`--list`), not red CI, so pairs land in reviewable batches without a big-bang PR. +- Rollout is incremental by design: documents outside `required` are visible backlog (`--list`), not red CI, so pairs land in reviewable batches without a big-bang PR. New documents are the exception — a date-named document dated on/after the manifest's `requiredSince` cutoff merges bilingual or not at all, so the backlog only ever shrinks. - The recorded hashes double as the update tool (`git cat-file -p ` recovers either side's last-confirmed text for a minimal diff-based update), so re-translation of whole files is never forced by the mechanism. diff --git a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md index f8f68bf5d4..f039fbe3fa 100644 --- a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md +++ b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md @@ -32,5 +32,5 @@ - 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对一致」可以从 yaml 的 git blame 直接回答。 - 两侧说法冲突时,没有机械规则裁决谁赢——由 PR 评审裁决。这是同权的代价,是有意接受的:另一个选项(正典语言)禁止中文先行撰写。 - 生成文档(`cordis-catalog/`、`tool-catalog/`、`module-graph.md`)暂被排除;计划中的后续工作是让它们的生成器在输出英文的同时输出中文,届时移出排除清单。 -- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(`--list`),不是红的 CI,因此配对按可评审的批次落地,无需一个巨型 PR。 +- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(`--list`),不是红的 CI,因此配对按可评审的批次落地,无需一个巨型 PR。新文档是例外——文件名日期在 manifest `requiredSince` 当天或之后的文档,要么连同配对一起合入,要么不合入,因此 backlog 只会缩小。 - 记录的 hash 兼作更新工具(`git cat-file -p ` 能还原任一侧上次确认的文本,用于基于 diff 的最小更新),所以这套机制从不强迫整篇重译。 diff --git a/scripts/translation-pairing.manifest.json b/scripts/translation-pairing.manifest.json index 8c2708d48d..92e6eef950 100644 --- a/scripts/translation-pairing.manifest.json +++ b/scripts/translation-pairing.manifest.json @@ -1,4 +1,5 @@ { + "requiredSince": "2026-07-05", "required": [ "README.md", "docs/development.md", diff --git a/scripts/verify-translation-pairing.ts b/scripts/verify-translation-pairing.ts index eea30e4b22..3e9043b39d 100644 --- a/scripts/verify-translation-pairing.ts +++ b/scripts/verify-translation-pairing.ts @@ -21,7 +21,10 @@ * depths in order, fenced code blocks VERBATIM (info string + content), * table column counts, list kinds, and every link target except the * switcher itself. - * 3. `excluded` files (generated docs, agent instructions, the bilingual + * 3. Date-named documents (`yyyy-mm-dd-*.md`, i.e. RFCs) dated on/after the + * manifest's `requiredSince` merge bilingual — the frontier for NEW + * documents, independent of the `required` back-catalog list. + * 4. `excluded` files (generated docs, agent instructions, the bilingual * terminology table) have no `.zh.md` and no `.i18n.yaml` at all. * * What it deliberately does NOT check is translation quality or which side @@ -63,6 +66,8 @@ const SCOPE_PATTERNS = ['README.md', 'README.zh.md', 'README.i18n.yaml', 'docs/* interface Manifest { required: string[] excluded: string[] + /** Date-named documents (yyyy-mm-dd-*.md, i.e. RFCs) dated on/after this day must merge bilingual. */ + requiredSince: string } const manifest = JSON.parse(readFileSync(join(root, 'scripts/translation-pairing.manifest.json'), 'utf8')) as Manifest @@ -250,7 +255,22 @@ for (const req of manifest.required) { } } -// 2. Every pair that exists at all is complete and consistent. Anchor on the +// 2. Date-named documents (RFCs) dated on/after the requiredSince cutoff merge +// bilingual: a new RFC lands with its pair or not at all. Deterministic from +// the filename alone — no git history, so it holds on shallow CI checkouts. +const DATED = /(\d{4}-\d{2}-\d{2})-[^/]*\.md$/ +for (const source of sources) { + if (isExcluded(source)) continue + const dated = DATED.exec(source) + if (!dated?.[1] || dated[1] < manifest.requiredSince) continue + const { zh } = pairPaths(source) + if (!existsSync(join(root, zh))) { + errors.push(`${source}: dated ${dated[1]} — documents dated on/after ${manifest.requiredSince} merge bilingual (docs/i18n/README.md); add the counterpart and record the pair`) + state.set(source, 'missing') + } +} + +// 3. Every pair that exists at all is complete and consistent. Anchor on the // union of .zh.md files and .i18n.yaml records so a half-deleted pair is // caught from either remnant. const pairAnchors = new Set() @@ -317,7 +337,9 @@ if (listMode) { const rows = [...state.entries()].sort((a, b) => order[a[1]] - order[b[1]] || a[0].localeCompare(b[0])) for (const [file, status] of rows) { const required = manifest.required.includes(file) - console.log(`${status.padEnd(11)} ${file}${status === 'missing' ? (required ? ' (required)' : ' (backlog)') : ''}`) + const date = DATED.exec(file)?.[1] + const tag = required ? ' (required)' : date && date >= manifest.requiredSince ? ' (required by date)' : ' (backlog)' + console.log(`${status.padEnd(11)} ${file}${status === 'missing' ? tag : ''}`) } const counts = { 'ok': 0, 'out-of-sync': 0, 'missing': 0 } for (const status of state.values()) counts[status]++ From 0bccb483a5b85ff668f5536fdbc9505b8a0b5bff Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sat, 4 Jul 2026 05:54:22 -0700 Subject: [PATCH 02/38] docs(i18n): state the requiredSince grandfather semantics explicitly Files dated before the cutoff are the grandfathered backlog by definition (including cutoff-eve creations); backdating is a review-visible RFC-convention violation, not a loophole. --- docs/i18n/README.i18n.yaml | 4 ++-- docs/i18n/README.md | 2 +- docs/i18n/README.zh.md | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index 8fc169bdaa..cb4e049728 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.i18n.yaml @@ -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 -README.md: ba3431c72d9f2a86220df4ed682620514401291f -README.zh.md: 02957c97937525a1da27242620a8327b863de040 +README.md: bbf292893fc1ca634f3437a8ea0bae855c5712cb +README.zh.md: 29f7408d1e0511ebf5a708507e0ea93aeb7e9ff3 diff --git a/docs/i18n/README.md b/docs/i18n/README.md index ba3431c72d..bbf292893f 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -43,7 +43,7 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co - `docs/AGENTS.md` — agent instructions, maintained in English only like the root `AGENTS.md`. - `docs/i18n/terminology.md` — the terminology table is itself bilingual by construction. -**Rollout**: new documents don't wait for a batch — a date-named document (`yyyy-mm-dd-*.md`, i.e. an RFC) dated on or after the manifest's `requiredSince` cutoff must merge with its pair, so everything new is bilingual from birth. For the back-catalog, the `required` list in the manifest is the enforcement frontier, not the goal. The goal is full bilingual coverage of the scope. Pairs land in reviewable batches (core entry docs, cookbook, RFCs, postmortems, …); each merged batch adds its files to `required`, so the gate ratchets forward and never regresses. Documents not yet in `required` are backlog — visible in `--list` — but any pair that already exists is held to the full contract regardless of the list. Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it. +**Rollout**: new documents don't wait for a batch — a date-named document (`yyyy-mm-dd-*.md`, i.e. an RFC) dated on or after the manifest's `requiredSince` cutoff must merge with its pair, so everything new is bilingual from birth. Files dated before the cutoff are the grandfathered backlog by definition — including files created on the cutoff's eve — and a document's filename date is its first-proposed date per the RFC convention, so backdating past the cutoff is a review-visible violation, not a loophole. For the back-catalog, the `required` list in the manifest is the enforcement frontier, not the goal. The goal is full bilingual coverage of the scope. Pairs land in reviewable batches (core entry docs, cookbook, RFCs, postmortems, …); each merged batch adds its files to `required`, so the gate ratchets forward and never regresses. Documents not yet in `required` are backlog — visible in `--list` — but any pair that already exists is held to the full contract regardless of the list. Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it. ## Division of labor diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index 02957c9793..29f7408d1e 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -43,7 +43,7 @@ - `docs/AGENTS.md`——agent 指令,与根 `AGENTS.md` 一样只以英文维护。 - `docs/i18n/terminology.md`——术语表本身即是双语构造。 -**推进**:新文档不等批次——文件名带日期的文档(`yyyy-mm-dd-*.md`,即 RFC)日期在 manifest 的 `requiredSince` 当天或之后,就必须连同配对一起合入,新增的一切生来即是双语。对于存量文档,manifest 中的 `required` 列表是强制边界,不是目标。目标是范围内的全量双语覆盖。配对按可评审的批次落地(核心入口文档、cookbook、RFC、postmortem……);每个批次合入后把其文件加进 `required`,门禁只进不退。尚未进入 `required` 的文档是 backlog——在 `--list` 中可见——但任何已存在的配对无论在不在清单里都按完整契约检查。给一篇文档配对是一份承诺:此后对任一侧的每次修改都必须带上另一侧,所以边界的扩张要跟上翻译评审的实际投入节奏,不要抢在前面。 +**推进**:新文档不等批次——文件名带日期的文档(`yyyy-mm-dd-*.md`,即 RFC)日期在 manifest 的 `requiredSince` 当天或之后,就必须连同配对一起合入,新增的一切生来即是双语。日期早于 cutoff 的文件按定义属于被豁免的存量——包括 cutoff 前夜创建的文件——而文件名日期按 RFC 惯例即首次提出日期,倒填日期绕过 cutoff 是评审可见的违规,不是漏洞。对于存量文档,manifest 中的 `required` 列表是强制边界,不是目标。目标是范围内的全量双语覆盖。配对按可评审的批次落地(核心入口文档、cookbook、RFC、postmortem……);每个批次合入后把其文件加进 `required`,门禁只进不退。尚未进入 `required` 的文档是 backlog——在 `--list` 中可见——但任何已存在的配对无论在不在清单里都按完整契约检查。给一篇文档配对是一份承诺:此后对任一侧的每次修改都必须带上另一侧,所以边界的扩张要跟上翻译评审的实际投入节奏,不要抢在前面。 ## 分工 From abb145d99f74ac3635f1c00c89b02eb57149d42b Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:03:14 -0700 Subject: [PATCH 03/38] =?UTF-8?q?docs(i18n):=20translation=20capability=20?= =?UTF-8?q?=E2=80=94=20style=20samples,=20voice=20rules,=20two-pass=20skil?= =?UTF-8?q?l?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Human-annotated gold pairs land as docs/i18n/style-samples.md (bilingual by construction, excluded from pairing) and outrank prose tone rules. translation-rules gains a Voice section; shape stays gate-owned so the writer optimizes for natural Chinese. The skill's translate step becomes two passes: native-author writing, then clause-level fidelity check. Terminology rulings: hash -> 哈希 (blob 哈希/提交哈希), HMR and fiber confirmed as tabled. --- .agents/skills/dsh-translate-docs/SKILL.md | 4 +- docs/i18n/README.i18n.yaml | 4 +- docs/i18n/README.md | 2 +- docs/i18n/README.zh.md | 2 +- docs/i18n/style-samples.md | 61 ++++++++++++++++++++++ docs/i18n/terminology.md | 4 +- docs/i18n/translation-rules.i18n.yaml | 4 +- docs/i18n/translation-rules.md | 11 +++- docs/i18n/translation-rules.zh.md | 11 +++- scripts/translation-pairing.manifest.json | 3 +- 10 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 docs/i18n/style-samples.md diff --git a/.agents/skills/dsh-translate-docs/SKILL.md b/.agents/skills/dsh-translate-docs/SKILL.md index cde9f00b0e..8a2b231e8f 100644 --- a/.agents/skills/dsh-translate-docs/SKILL.md +++ b/.agents/skills/dsh-translate-docs/SKILL.md @@ -37,7 +37,9 @@ Do not process every file the same way: ## Translate -- Work through the document applying [translation-rules.md](../../../docs/i18n/translation-rules.md). Internally: first render faithfully, then re-read the counterpart alone for awkward or ambiguous phrasing, then polish — but write ONLY the final text to the file, never drafts or notes. +- **Pass 1 — write, don't transpose.** You are a native technical author of the target language. Read a semantic unit of the source (a paragraph or a tight group), close it, and state its content the way [docs/i18n/style-samples.md](../../../docs/i18n/style-samples.md) does — match the nearest genre sample's register. Shape is the gate's job, not yours: never trade natural phrasing for sentence-by-sentence correspondence. +- **Pass 2 — verify against the source, clause by clause.** Fidelity is checked here, not written in: confirm nothing was added or dropped, every term follows the table, and each code span survived verbatim. Fix by rewriting the sentence natively, not by patching words into it. +- Write ONLY the final text to the file, never drafts or notes. - Every term in [terminology.md](../../../docs/i18n/terminology.md) renders exactly as specified, in both directions, including first-occurrence annotations. A term the table misses: translate only with a citable precedent from a major Chinese OSS/vendor doc; otherwise keep the English and add it to the PR's 「待定术语」 list with your suggested rendering. Never invent a rendering inline — that decision belongs to a human and then to the table. - Code blocks are byte-identical across the pair, comments included. Relative links keep their `.md` targets; only the switcher line links `.zh.md`. diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index cb4e049728..84cc3e7977 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.i18n.yaml @@ -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 -README.md: bbf292893fc1ca634f3437a8ea0bae855c5712cb -README.zh.md: 29f7408d1e0511ebf5a708507e0ea93aeb7e9ff3 +README.md: f28676b7d555aca30d8b5b21eba9fb4222949763 +README.zh.md: 5353a8ba04288cf72defa30b5896731b3d419b69 diff --git a/docs/i18n/README.md b/docs/i18n/README.md index bbf292893f..f28676b7d5 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -41,7 +41,7 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co - `docs/cordis-catalog/`, `docs/tool-catalog/`, `docs/module-graph.md` — generated files; their generators emit English only today, so a hand-written translation would go stale on every regeneration. The planned follow-up is to teach the generators to emit Chinese alongside English, at which point these leave the exclusion list. - `docs/AGENTS.md` — agent instructions, maintained in English only like the root `AGENTS.md`. -- `docs/i18n/terminology.md` — the terminology table is itself bilingual by construction. +- `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction. **Rollout**: new documents don't wait for a batch — a date-named document (`yyyy-mm-dd-*.md`, i.e. an RFC) dated on or after the manifest's `requiredSince` cutoff must merge with its pair, so everything new is bilingual from birth. Files dated before the cutoff are the grandfathered backlog by definition — including files created on the cutoff's eve — and a document's filename date is its first-proposed date per the RFC convention, so backdating past the cutoff is a review-visible violation, not a loophole. For the back-catalog, the `required` list in the manifest is the enforcement frontier, not the goal. The goal is full bilingual coverage of the scope. Pairs land in reviewable batches (core entry docs, cookbook, RFCs, postmortems, …); each merged batch adds its files to `required`, so the gate ratchets forward and never regresses. Documents not yet in `required` are backlog — visible in `--list` — but any pair that already exists is held to the full contract regardless of the list. Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it. diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index 29f7408d1e..5353a8ba04 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -41,7 +41,7 @@ - `docs/cordis-catalog/`、`docs/tool-catalog/`、`docs/module-graph.md`——生成文件;生成器目前只输出英文,手写译文在每次重新生成时必然陈旧。计划中的后续工作是让生成器同时输出中文,届时这些文件移出排除清单。 - `docs/AGENTS.md`——agent 指令,与根 `AGENTS.md` 一样只以英文维护。 -- `docs/i18n/terminology.md`——术语表本身即是双语构造。 +- `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md)——二者本身即为中英对照文档。 **推进**:新文档不等批次——文件名带日期的文档(`yyyy-mm-dd-*.md`,即 RFC)日期在 manifest 的 `requiredSince` 当天或之后,就必须连同配对一起合入,新增的一切生来即是双语。日期早于 cutoff 的文件按定义属于被豁免的存量——包括 cutoff 前夜创建的文件——而文件名日期按 RFC 惯例即首次提出日期,倒填日期绕过 cutoff 是评审可见的违规,不是漏洞。对于存量文档,manifest 中的 `required` 列表是强制边界,不是目标。目标是范围内的全量双语覆盖。配对按可评审的批次落地(核心入口文档、cookbook、RFC、postmortem……);每个批次合入后把其文件加进 `required`,门禁只进不退。尚未进入 `required` 的文档是 backlog——在 `--list` 中可见——但任何已存在的配对无论在不在清单里都按完整契约检查。给一篇文档配对是一份承诺:此后对任一侧的每次修改都必须带上另一侧,所以边界的扩张要跟上翻译评审的实际投入节奏,不要抢在前面。 diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md new file mode 100644 index 0000000000..d5d920c0d8 --- /dev/null +++ b/docs/i18n/style-samples.md @@ -0,0 +1,61 @@ +# 翻译语体样例(style samples) + +本文件是翻译语体的校准锚点:每组样例是一段英文原文与一段人工定稿的中文译文,覆盖本仓库文档的主要文体。**译文的语体以这些样例为准**——它们的效力高于任何对语气的文字描述。翻译或评审时对照最接近的文体样例;样例与规则冲突时,样例胜出。本文件中英对照、自成双语,不参与配对(见 [README.md](README.md) 排除清单)。 + +维护方式:人工评审校准出新的金标段落后追加到对应文体;样例只增不改,改动需评审人签字(PR 评审即签字)。 + +## ① 架构叙述 + +> This document describes the architecture of the DeepSeek Harness — the foundation of **DeepSeek Code**. The governing principle, from the microkernel design discussion: **everything is a plugin**. The core is deliberately tiny — a handful of abstract services plus one concrete loop plugin (`dsh-agent-loop`) — and every product feature is a plugin against the extension surface described here, without modifying the loop. + +本文介绍 DeepSeek Harness 整体架构,它是 **DeepSeek Code** 的底层基座。微内核设计讨论中确立了核心设计准则:**一切皆插件**。内核刻意做得极精简,仅包含少量抽象服务,外加一个实体循环插件 `dsh-agent-loop`。所有产品功能均基于本文定义的扩展接口开发为独立插件,无需改动主循环逻辑。 + +> Dependency rule: extension plugins depend on interfaces, never on `dsh-agent-loop` (the loop is swappable); the sanctioned exception is the composition bundle `dsh-agent-core`, whose job is assembling the concrete spine. + +依赖约束规范:各类扩展插件仅依赖抽象接口,严禁直接依赖 `dsh-agent-loop`(该主循环支持替换实现);唯一允许的特例是组合包 `dsh-agent-core`,它的职责是组装整套实体主干。 + +## ② 防御模式规则 + +> Hard-won bug-class rules: each pattern below is a class of defect that actually shipped or nearly shipped here, stated as the rule that prevents its recurrence. Read this before writing lifecycle, concurrency, subprocess, or teardown code. + +这些都是踩坑总结得出的缺陷分类规范:下文每种范式都对应一类曾上线、或险些流入线上的问题,每条规范旨在杜绝同类问题复现。编写生命周期、并发、子进程、资源销毁相关代码前,请务必阅读本文档。 + +## ③ 测试政策清单 + +> **Coverage gate** (`pnpm run test:coverage`): the gating run, per-file 100% on `packages/*/*/src`. An uncovered line is often dead code the gate is correctly flagging for deletion, not a missing test to bolt on. Line coverage is necessary, never sufficient — it proves lines ran, not that the feature works as shipped. + +覆盖率门禁(`pnpm run test:coverage`):作为合入门禁校验,要求 `packages/*/*/src` 目录下每个文件行覆盖率达到 100%。未覆盖代码行大多是无用死代码,门禁标记这类代码是提示删除,而非单纯补充测试。行覆盖率是必要条件,但远不充分:它仅能证明代码被执行过,无法保证功能符合线上预期。 + +## ④ 机制描述 + +> Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. The recorded hash also recovers the exact last-confirmed text of either side (`git cat-file -p `), so an out-of-sync pair is updated by diffing the edited side against its last-confirmed state and patching the counterpart minimally — never by re-translating whole files. + +系统采用文件 blob 哈希而非提交哈希记录状态。同一 PR 内修改文件时,可通过 `git hash-object foo.md` 直接算出对应哈希,仅对比文件内容即可判断双语文档是否同步。通过记录的哈希值,可使用 `git cat-file -p ` 还原上次确认对齐时两侧的原文。当双语文档不一致时,只需对比修改版本与上次确认版本的差异,最小幅度同步修改另一侧译文,无需全文重新翻译。 + +## ⑤ 政策声明 + +> The gate's limit, stated plainly: a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound. It checks hashes and shape; it cannot judge whether the two sides actually say the same thing — that is the reviewer's half of the contract. A re-recorded pair with a sloppy counterpart passes the gate; it must not pass review. + +明确门禁校验边界:门禁校验通过,仅代表两份文档哈希与结构完全匹配,不代表译文内容准确无误。门禁仅校验哈希与结构,无法判断双语表意是否统一——译文质量把关是评审人的责任。即便译文粗糙、表意偏差,只要哈希匹配,门禁就会放行,但这类 PR 绝不能通过人工评审。 + +## ⑥ RFC 论证 + +> Comparing git timestamps of the pair (no record) — rejected: formatting-only edits would false-positive, and a counterpart committed after an unrelated edit would false-negative; content identity is the only signal that means what the gate claims. + +对比双语文件的 git 时间戳(无哈希记录方案)——不予采纳:仅调整格式的改动会触发误报,无关修改后再提交译文又会造成漏检。只有文件内容完全一致,才能作为门禁可信的校验依据。 + +## ⑦ 推进策略(长段拆分示范) + +> **Rollout**: new documents don't wait for a batch — a date-named document dated on or after the manifest's `requiredSince` cutoff must merge with its pair, so everything new is bilingual from birth. For the back-catalog, the `required` list in the manifest is the enforcement frontier, not the goal. […] Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it. + +**推进**:新增文档不再走批量分批翻译流程。以日期命名的文档,若其标注日期等于或晚于 manifest 里 `requiredSince` 分界时间,提交合入时必须配套对应的双语译文文件——所有新文档从创建起就要求中英双语齐备。针对存量旧文档:manifest 内的强制翻译列表只是当下执行红线,并非最终目标。(……)文档完成双语配对等同于一份长期约束承诺:后续只要修改任一版本,就必须同步更新对应另一语种文件。因此强制翻译范围的推进节奏,要匹配翻译评审实际可投入人力,切勿超前铺开。 + +## 从样例提炼的要点 + +- 语体是规范制度文:完整主谓、确定语气;不口语化,也不学术腔。 +- 给句子补显式执行主体:英文的被动句和抽象主语,中文写成「系统/门禁/工具/评审人」做主语。 +- 用中文工程惯用语替换直译:false positive/negative→误报/漏检、enforcement frontier→执行红线、ratchet→只向前收紧不倒退放宽、reviewable act→评审凭证。 +- 隐喻本地化而非移植:bilingual from birth→从创建起就要求双语齐备;grandfathered→历史存量遗留。 +- 类别名词说中文并在首现括注英文:实操手册(cookbook)、事故复盘(postmortem);指目录或路径时保留代码体英文。 +- 长段按语义单元拆段,一段一件事;名词短语展开为动词句。 +- 母语重写不等于删减:原文每个语义成分都要落地。 diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md index f95a8e3a8f..94b182ae4f 100644 --- a/docs/i18n/terminology.md +++ b/docs/i18n/terminology.md @@ -25,7 +25,9 @@ | agent | agent | 首次出现可写:agent(智能体) | | agent loop | agent loop | | | backlog | backlog | 双语翻译语境指待翻清单 | -| blob hash | blob hash | git 对象哈希;`git hash-object` 的结果 | +| blob hash | blob 哈希 | git 对象哈希;`git hash-object` 的结果 | +| commit hash | 提交哈希 | | +| hash | 哈希 | 代码与命令中保留英文(如 `git hash-object`、`` 占位符) | | doc-sync | doc-sync | 仓库门禁名,保留英文 | | e2e | e2e | | | fiber | fiber | 首次出现可写:fiber(插件运行时) | diff --git a/docs/i18n/translation-rules.i18n.yaml b/docs/i18n/translation-rules.i18n.yaml index 579bd51511..f1aa737d08 100644 --- a/docs/i18n/translation-rules.i18n.yaml +++ b/docs/i18n/translation-rules.i18n.yaml @@ -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 -translation-rules.md: 4e190f58469f7d402dfa5600f17cf1621484f138 -translation-rules.zh.md: 89a1cddd23126f24354ce1f8d9af4e7bd403454d +translation-rules.md: 3e99aa5432ccd904f702238e9b802a9ed9bf6832 +translation-rules.zh.md: 78778907f959d62dad2c9b4c02baf1f664791a43 diff --git a/docs/i18n/translation-rules.md b/docs/i18n/translation-rules.md index 4e190f5846..3e99aa5432 100644 --- a/docs/i18n/translation-rules.md +++ b/docs/i18n/translation-rules.md @@ -10,9 +10,18 @@ How to translate between the two sides of a documentation pair in this repo. Bot - The counterpart SHOULD read as natural technical writing in its own language, not word-by-word gloss. Translate meaning, restructure sentences where the target grammar wants it, and keep the author's register — terse stays terse. - Do not translate the untranslatable: if a sentence resists natural rendering because it leans on an idiom of the source language, translate the idea, not the idiom. +## Voice + +- The register is calibrated by [style-samples.md](style-samples.md) — human-approved gold pairs, one per document genre. The counterpart MUST match the register of the nearest sample; where a sample and a prose rule here disagree, the sample wins. The target is institutional technical Chinese: complete sentences, declarative, neither chatty nor academic. +- Write as a native technical author restating the content, not as a translator transposing sentences. Then verify against the source clause by clause: nothing added, nothing dropped — fluency never justifies losing a clause. +- Give sentences an explicit agent: where the English uses a passive or an abstract subject, name the actor (系统、门禁、评审人). +- Prefer established Chinese engineering idiom over calques (误报/漏检 for false positive/negative, 执行红线 for enforcement frontier); localize metaphors instead of transplanting them, and unpack English noun chains into verb clauses. +- Split long paragraphs by semantic unit — one idea per paragraph. Paragraph boundaries MAY differ from the source; the structural signature does not count paragraphs. +- Category nouns render in Chinese with a first-mention English annotation (实操手册(cookbook)); literal directory or file references stay code-formatted English. + ## Structure preservation -The paired files MUST match one to one in: +Shape is enforced by the pairing gate, so the writer never trades fluency against it — write naturally inside the frame. The paired files MUST match one to one in: - heading hierarchy (same levels, same order — heading TEXT is translated), - list shape and numbering, diff --git a/docs/i18n/translation-rules.zh.md b/docs/i18n/translation-rules.zh.md index 89a1cddd23..78778907f9 100644 --- a/docs/i18n/translation-rules.zh.md +++ b/docs/i18n/translation-rules.zh.md @@ -10,9 +10,18 @@ - 另一侧应当读起来是其语言自然的技术文字,而不是逐词对照。翻译语义,在目标语言语法需要处重组句子,并保持原作者的语域——简练的保持简练。 - 不要翻译不可译的东西:一句话如果依赖源语言的习语而无法自然转换,就翻译它的意思,而不是习语本身。 +## 行文 + +- 语体以 [style-samples.md](style-samples.md) 为校准锚点——人工定稿的金标样例按文体各一组,译文必须对齐最接近的样例语体;样例与本文条款冲突时,以样例为准。目标语体是规范的技术制度文:完整主谓、确定语气,不口语化也不学术腔。 +- 以母语技术作者的身份重述内容,而不是逐句转写的译者。写完后逐句对照原文核验:不添加、不遗漏——流畅永远不是丢掉语义成分的理由。 +- 给句子补显式执行主体:英文的被动句和抽象主语,中文写成「系统、门禁、评审人」等实际执行者做主语。 +- 优先使用中文工程惯用语而非直译(false positive/negative→误报/漏检、enforcement frontier→执行红线);隐喻做本地化替换而不是移植,英文名词链展开为动词句。 +- 长段按语义单元拆分,一段一件事。段落边界可以与原文不同;结构签名不比对段落数。 +- 类别名词说中文并在首现括注英文(实操手册(cookbook));指目录或文件本身时保留代码体英文。 + ## 结构保持 -配对的两个文件必须在以下方面一一对应: +形状由配对门禁强制,译者不需要用流畅度去换结构——在框架内自然行文即可。配对的两个文件必须在以下方面一一对应: - 标题层级(相同级别、相同顺序——标题的**文字**要翻译), - 列表形态与编号, diff --git a/scripts/translation-pairing.manifest.json b/scripts/translation-pairing.manifest.json index 92e6eef950..6f05431ced 100644 --- a/scripts/translation-pairing.manifest.json +++ b/scripts/translation-pairing.manifest.json @@ -12,6 +12,7 @@ "docs/module-graph.md", "docs/cordis-catalog/", "docs/tool-catalog/", - "docs/i18n/terminology.md" + "docs/i18n/terminology.md", + "docs/i18n/style-samples.md" ] } From 4f852742371fda1eb93abd4477cbd2affda3e2fc Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:17:14 -0700 Subject: [PATCH 04/38] docs(i18n): absorb the with-key-testing gold sample; table rules mock and real-API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gold sample recorded terminology-corrected per the table-wins rule (agent stays English, cancellation renders 取消), and that rule itself lands in the samples' notes. New table rows: mock -> 模拟; API row notes real-API as 真实接口 when attributive. --- docs/i18n/style-samples.md | 9 +++++++++ docs/i18n/terminology.md | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index d5d920c0d8..4e20e960b6 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -26,6 +26,14 @@ 覆盖率门禁(`pnpm run test:coverage`):作为合入门禁校验,要求 `packages/*/*/src` 目录下每个文件行覆盖率达到 100%。未覆盖代码行大多是无用死代码,门禁标记这类代码是提示删除,而非单纯补充测试。行覆盖率是必要条件,但远不充分:它仅能证明代码被执行过,无法保证功能符合线上预期。 +> We are DeepSeek — do not ration real-API tests. A no-key test proves the plumbing; only a with-key run proves the agent works against a real model. Write many: real prompts that write files, multi-turn conversations, tool use, cancellation mid-stream. Cheapest and highest-value are **smoke tests** that boot the real example, send one real prompt, and check the world — they catch the "green unit tests, broken product" class that mocks structurally cannot. The self-skip exists only so secretless CI and keyless contributors aren't blocked; it is not a cost signal. + +我们是 DeepSeek:真实接口相关测试不得刻意缩减用例数量。无密钥测试仅能验证底层通路;只有携带有效密钥执行的用例,才能确认 agent 可正常对接真实模型。请大量编写此类测试:包含文件写入类真实提示词、多轮对话、工具调用、流式中途取消等场景。 + +成本最低、收益最高的是**冒烟测试**:拉起完整真实示例,发送一条真实提示并校验整体运行状态。这类用例能捕获一类问题——单元测试全部绿灯,但产品实际运行故障,单靠模拟接口完全无法发现这类缺陷。 + +自带自动跳过逻辑,仅用于保障无密钥 CI 环境、无权限贡献者不会被流程拦截,不代表可以以此为由削减真实接口测试投入。 + ## ④ 机制描述 > Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. The recorded hash also recovers the exact last-confirmed text of either side (`git cat-file -p `), so an out-of-sync pair is updated by diffing the edited side against its last-confirmed state and patching the counterpart minimally — never by re-translating whole files. @@ -59,3 +67,4 @@ - 类别名词说中文并在首现括注英文:实操手册(cookbook)、事故复盘(postmortem);指目录或路径时保留代码体英文。 - 长段按语义单元拆段,一段一件事;名词短语展开为动词句。 - 母语重写不等于删减:原文每个语义成分都要落地。 +- 样例与 [terminology.md](terminology.md) 冲突时,以术语表为准:收录样例前按表修正术语(例如 agent 保留英文、cancellation 译「取消」)。 diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md index 94b182ae4f..5ce1efe7b2 100644 --- a/docs/i18n/terminology.md +++ b/docs/i18n/terminology.md @@ -6,7 +6,7 @@ |---|---|---| | ACP | ACP | 首次出现可写:ACP(Agent Client Protocol) | | AI | AI | 首次出现可写:人工智能(AI) | -| API | API | | +| API | API | real-API 作定语时可译「真实接口」(如 real-API tests → 真实接口测试) | | CI | CI | | | CLI | CLI | 首次出现可写:命令行界面(CLI) | | Cordis | Cordis | 保留英文 | @@ -92,6 +92,7 @@ | language switcher | 语言切换行 | i18n 机制词:双语配对文件顶部的互链行 | | memory | memory / 记忆 / 内存 | 按上下文区分:agent memory 译为“记忆”;resource/memory usage 译为“内存” | | message | 消息 | | +| mock | 模拟 | 如「模拟接口」「模拟模型」;指测试替身 | | mod | 模组 | 区别于 module(模块);plugin 译作「插件」 | | model provider | 模型提供方 | | | module | 模块 | | From 74699d25ba2079abb497ae104622952f9489f026 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:18:08 -0700 Subject: [PATCH 05/38] docs(i18n): mock stays English per ruling; sample corrected to match --- docs/i18n/style-samples.md | 4 ++-- docs/i18n/terminology.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index 4e20e960b6..cb043a19d9 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -30,7 +30,7 @@ 我们是 DeepSeek:真实接口相关测试不得刻意缩减用例数量。无密钥测试仅能验证底层通路;只有携带有效密钥执行的用例,才能确认 agent 可正常对接真实模型。请大量编写此类测试:包含文件写入类真实提示词、多轮对话、工具调用、流式中途取消等场景。 -成本最低、收益最高的是**冒烟测试**:拉起完整真实示例,发送一条真实提示并校验整体运行状态。这类用例能捕获一类问题——单元测试全部绿灯,但产品实际运行故障,单靠模拟接口完全无法发现这类缺陷。 +成本最低、收益最高的是**冒烟测试**:拉起完整真实示例,发送一条真实提示并校验整体运行状态。这类用例能捕获一类问题——单元测试全部绿灯,但产品实际运行故障,单靠 mock 完全无法发现这类缺陷。 自带自动跳过逻辑,仅用于保障无密钥 CI 环境、无权限贡献者不会被流程拦截,不代表可以以此为由削减真实接口测试投入。 @@ -67,4 +67,4 @@ - 类别名词说中文并在首现括注英文:实操手册(cookbook)、事故复盘(postmortem);指目录或路径时保留代码体英文。 - 长段按语义单元拆段,一段一件事;名词短语展开为动词句。 - 母语重写不等于删减:原文每个语义成分都要落地。 -- 样例与 [terminology.md](terminology.md) 冲突时,以术语表为准:收录样例前按表修正术语(例如 agent 保留英文、cancellation 译「取消」)。 +- 样例与 [terminology.md](terminology.md) 冲突时,以术语表为准:收录样例前按表修正术语(例如 agent、mock 保留英文,cancellation 译「取消」)。 diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md index 5ce1efe7b2..36b0244343 100644 --- a/docs/i18n/terminology.md +++ b/docs/i18n/terminology.md @@ -92,7 +92,7 @@ | language switcher | 语言切换行 | i18n 机制词:双语配对文件顶部的互链行 | | memory | memory / 记忆 / 内存 | 按上下文区分:agent memory 译为“记忆”;resource/memory usage 译为“内存” | | message | 消息 | | -| mock | 模拟 | 如「模拟接口」「模拟模型」;指测试替身 | +| mock | mock | 保留英文;指测试替身 | | mod | 模组 | 区别于 module(模块);plugin 译作「插件」 | | model provider | 模型提供方 | | | module | 模块 | | From daaddcaaf3c5ec355b94725d701ae00dceecfc8c Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:24:56 -0700 Subject: [PATCH 06/38] docs(i18n): absorb round-4 gold samples, terminology-corrected MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Architecture pointer paragraph, two defensive patterns, and the prefer-real-over-mock rule join the samples. Recorded per the table-wins rule: agent/mock/LLM stay English, registry renders 注册表, and code-spanned identifiers (event names, status values, package names) survive verbatim — that boundary is now a stated sample note. orphan row notes the process-context rendering 孤儿进程. --- docs/i18n/style-samples.md | 19 ++++++++++++++++++- docs/i18n/terminology.md | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index cb043a19d9..d75fa6e54b 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -14,12 +14,24 @@ 依赖约束规范:各类扩展插件仅依赖抽象接口,严禁直接依赖 `dsh-agent-loop`(该主循环支持替换实现);唯一允许的特例是组合包 `dsh-agent-core`,它的职责是组装整套实体主干。 +> This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the [generated catalog](cordis-catalog/events-and-services.md), per-package contracts in the package READMEs ([map](../packages/README.md)). + +本文档描述整体行为逻辑;类型定义存放于 [core-data-structures/](core-data-structures/core.md);各类事件、服务的详细参考见[生成目录](cordis-catalog/events-and-services.md);各 package 对外约束协议写在对应包的 README([索引](../packages/README.md))。 + ## ② 防御模式规则 > Hard-won bug-class rules: each pattern below is a class of defect that actually shipped or nearly shipped here, stated as the rule that prevents its recurrence. Read this before writing lifecycle, concurrency, subprocess, or teardown code. 这些都是踩坑总结得出的缺陷分类规范:下文每种范式都对应一类曾上线、或险些流入线上的问题,每条规范旨在杜绝同类问题复现。编写生命周期、并发、子进程、资源销毁相关代码前,请务必阅读本文档。 +> **Dispose must reach quiescence, not just request it** — A teardown that issues kills/aborts but returns before the work stops leaves orphans. Make cleanup async and await the children's exit (kill → await `done`), and close listener/notification registries BEFORE killing so late completions stay silent. Tests prove disposal waited (pid gone right after `await fiber.dispose()`), not merely that the process eventually dies. + +**销毁操作必须等待所有任务完全停稳,不能仅下发终止指令就返回**——若销毁逻辑仅发送终止、中断信号,但不等任务停止就直接退出,会产生孤儿进程。清理逻辑需设为异步,等待所有子任务彻底退出(先下发终止信号,再等待执行完成);在执行终止操作前先关闭监听器与通知注册表,让延迟到达的完成事件不再触发任何通知。测试要验证销毁流程确实完成等待:执行完 `await fiber.dispose()` 后进程 PID 立即消失,不能仅校验进程最终会自行消亡。 + +> **Async state is not synchronous state** — `agent.send()` does not flip status before returning; a background task's completion races turn boundaries; `reader.close()` fires for both EOF and disposal. Never gate control flow on a status you only just requested — drive lifecycle off the events/promises that actually fire (`agent/status`, `task.done`), and observe the transition (saw `running` THEN `idle`) rather than counting actions you assume map 1:1 to turns. + +**异步状态不等同于同步瞬时状态**——调用 `agent.send()` 不会在返回前同步更新状态;后台任务完成时机与轮次边界存在竞态;调用 `reader.close()` 既可能是读到文件末尾,也可能是资源销毁触发。切勿仅凭刚查询到的状态来阻断流程;生命周期逻辑应基于真实触发的事件与 promise 驱动(`agent/status`、`task.done`),观测完整状态切换(先 `running`、再 `idle`),而非主观认定操作和执行轮次一一对应(主循环会批量处理排队消息)。 + ## ③ 测试政策清单 > **Coverage gate** (`pnpm run test:coverage`): the gating run, per-file 100% on `packages/*/*/src`. An uncovered line is often dead code the gate is correctly flagging for deletion, not a missing test to bolt on. Line coverage is necessary, never sufficient — it proves lines ran, not that the feature works as shipped. @@ -34,6 +46,10 @@ 自带自动跳过逻辑,仅用于保障无密钥 CI 环境、无权限贡献者不会被流程拦截,不代表可以以此为由削减真实接口测试投入。 +> **Prefer the real implementation over a mock** — Mock only the genuinely expensive or non-deterministic boundary (the LLM adapter, the network, the clock); keep everything downstream real. A hand-rolled stand-in proves the bridge moves bytes, not that the shipping tool behaves as asserted — the two drift while the test stays green. + +**优先使用真实实现,而非 mock 替身**——仅对开销极大、结果不确定的边界模块做 mock(LLM 适配器、网络、时钟),其余下游组件全部使用真实实现。手写的 mock 替身只能验证数据通路能传输字节,无法保证线上工具符合预期逻辑;长期下来业务逻辑与 mock 实现会出现偏差,但测试仍会显示通过。 + ## ④ 机制描述 > Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. The recorded hash also recovers the exact last-confirmed text of either side (`git cat-file -p `), so an out-of-sync pair is updated by diffing the edited side against its last-confirmed state and patching the counterpart minimally — never by re-translating whole files. @@ -67,4 +83,5 @@ - 类别名词说中文并在首现括注英文:实操手册(cookbook)、事故复盘(postmortem);指目录或路径时保留代码体英文。 - 长段按语义单元拆段,一段一件事;名词短语展开为动词句。 - 母语重写不等于删减:原文每个语义成分都要落地。 -- 样例与 [terminology.md](terminology.md) 冲突时,以术语表为准:收录样例前按表修正术语(例如 agent、mock 保留英文,cancellation 译「取消」)。 +- 样例与 [terminology.md](terminology.md) 冲突时,以术语表为准:收录样例前按表修正术语(例如 agent、mock、LLM 保留英文,cancellation 译「取消」)。 +- 代码体标识符(事件名 `agent/status`、状态值 `running`、包名 `dsh-bash-local` 等)在译文中保留 code span 原文,不得口语化改写——这是行文规则的硬边界,Pass 2 逐句核验的重点。 diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md index 36b0244343..7d3360a689 100644 --- a/docs/i18n/terminology.md +++ b/docs/i18n/terminology.md @@ -96,7 +96,7 @@ | mod | 模组 | 区别于 module(模块);plugin 译作「插件」 | | model provider | 模型提供方 | | | module | 模块 | | -| orphan | 孤立 | git 官方中文同译(如「孤立分支」);指英文源已不存在的 `.zh.md`;不要译作:孤儿 | +| orphan | 孤立 | git 官方中文同译(如「孤立分支」);指英文源已不存在的 `.zh.md`;不要译作:孤儿。进程语境按 OS 惯用语译「孤儿进程」 | | pairing | 配对 | | | permission | 权限 | | | persistence | 持久化 | | From 60cfec6073954b7eeff6f10f03d89b980b84dafa Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:33:24 -0700 Subject: [PATCH 07/38] docs(i18n): retarget sample-excerpt links to resolve from docs/i18n MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Relative links copied into an excerpt resolve from the excerpt's home, not the source doc's — verify-md-links caught the moved-directory break. --- docs/i18n/style-samples.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index d75fa6e54b..b72d810afc 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -14,9 +14,9 @@ 依赖约束规范:各类扩展插件仅依赖抽象接口,严禁直接依赖 `dsh-agent-loop`(该主循环支持替换实现);唯一允许的特例是组合包 `dsh-agent-core`,它的职责是组装整套实体主干。 -> This document covers **behavior**; type shapes live in [core-data-structures/](core-data-structures/core.md), the per-event/service reference in the [generated catalog](cordis-catalog/events-and-services.md), per-package contracts in the package READMEs ([map](../packages/README.md)). +> This document covers **behavior**; type shapes live in [core-data-structures/](../core-data-structures/core.md), the per-event/service reference in the [generated catalog](../cordis-catalog/events-and-services.md), per-package contracts in the package READMEs ([map](../../packages/README.md)). -本文档描述整体行为逻辑;类型定义存放于 [core-data-structures/](core-data-structures/core.md);各类事件、服务的详细参考见[生成目录](cordis-catalog/events-and-services.md);各 package 对外约束协议写在对应包的 README([索引](../packages/README.md))。 +本文档描述整体行为逻辑;类型定义存放于 [core-data-structures/](../core-data-structures/core.md);各类事件、服务的详细参考见[生成目录](../cordis-catalog/events-and-services.md);各 package 对外约束协议写在对应包的 README([索引](../../packages/README.md))。 ## ② 防御模式规则 From b43a9e9ab1c4fae76a5a1ccde5b71607a1de4215 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:41:04 -0700 Subject: [PATCH 08/38] docs(i18n): package stays English per ruling --- docs/i18n/terminology.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md index 7d3360a689..0166b87e4c 100644 --- a/docs/i18n/terminology.md +++ b/docs/i18n/terminology.md @@ -36,6 +36,7 @@ | harness | harness | 保留英文 | | manifest | manifest | 描述模块或工具元数据的文件 | | monorepo | monorepo | | +| package | package | 保留英文;指 npm 包(`@deepseek-ai/dsh-*`) | | schema DSL | schema DSL | | | schema | schema | 保留英文 | | seam | seam | 首次出现可写:seam(扩展点) | From ecb8aa5b8e319bd0f661a045d6625ba8fb79fcd2 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Fri, 10 Jul 2026 01:51:50 +0800 Subject: [PATCH 09/38] Add a gated Known Limitations and Deferred Work section to every package README Every packages/*/* README now carries a canonical '## Known Limitations and Deferred Work' section: condensed, evidence-backed bullets for consumer-visible gaps (unimplemented features, platform caveats, MVP cuts) and consciously postponed work (TODO/FIXME/XXX markers, RFC deferrals still open). The ten pre-existing ad-hoc variants ('What is NOT here (TODO)', 'Deferred', 'Limitations (MVP)', 'Known limitations (tracked TODOs)', ...) are normalized into the canonical heading. A new doc-sync gate, scripts/verify-readme-limitations.ts, enforces the shape: exactly one limitations-like heading per package README, byte-equal to the canonical h2, with at least one bullet; near-miss headings fail so variants cannot creep back. Packages with genuinely nothing to declare (dsh-brand, dsh-timeout, dsh-subagent-mock, dsh-app-boot) are whitelisted in the script and must NOT carry the section; whitelist entries are validated against the scanned package set so a rename fails loud. Wired into the doc-sync chain (package.json) and the run-gates doc-sync leaf set; the standing rule lands in packages/AGENTS.md and the adding-a-package cookbook; decision record in docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md (RFC index regenerated). Also fixes two stale '(deferred)' markers claiming dsh-compact-basic is unimplemented (the dsh-compact seam README's package table and the seam's module doc comment). --- docs/cookbook/adding-a-package.md | 4 +- docs/rfc/INDEX.md | 1 + ...026-07-10-readme-known-limitations-gate.md | 28 ++++ package.json | 3 +- packages/AGENTS.md | 3 +- packages/bash/bash-local/README.md | 10 +- packages/bash/bash/README.md | 6 + packages/bash/tool-bash/README.md | 5 + .../code-runtime-worker/README.md | 8 ++ packages/code-runtime/code-runtime/README.md | 6 + packages/compact/compact-basic/README.md | 8 ++ packages/compact/compact/README.md | 9 +- packages/compact/compact/src/index.ts | 2 +- packages/cordis/tool-cordis/README.md | 6 + packages/core/agent-core/README.md | 5 + packages/core/agent-loop/README.md | 10 +- packages/core/agent/README.md | 8 +- packages/core/session/README.md | 5 +- packages/core/system-prompt/README.md | 14 +- packages/core/tools/README.md | 11 +- packages/fs/fs-local/README.md | 13 +- packages/fs/fs-policy/README.md | 7 + packages/fs/fs/README.md | 8 ++ packages/fs/tool-fs/README.md | 6 + packages/guard/repeat-tool-guard/README.md | 9 ++ packages/hooks/hook-protocol/README.md | 5 +- packages/hooks/hooks-claude/README.md | 5 +- packages/hooks/hooks-codex/README.md | 10 +- packages/llm/llm-deepseek/README.md | 10 +- packages/llm/llm-pi-ai/README.md | 12 +- packages/llm/llm/README.md | 9 ++ .../session-persistence-jsonl/README.md | 7 +- .../session-persistence-sqlite/README.md | 9 +- .../session-persistence/README.md | 6 + packages/subagent/subagent-acp/README.md | 8 ++ packages/subagent/subagent-fork/README.md | 6 + .../subagent/subagent-inprocess/README.md | 6 + packages/subagent/subagent-spawn/README.md | 5 + packages/subagent/subagent/README.md | 5 +- packages/subagent/tool-subagent/README.md | 6 + packages/support/acp-snapshot/README.md | 5 + packages/support/invariants/README.md | 5 + packages/support/llm-replay/README.md | 5 + packages/timeout/timeout-policy/README.md | 5 + packages/todo/tool-todo/README.md | 6 + packages/ui/acp-agent/README.md | 4 + packages/ui/acp/README.md | 5 +- packages/ui/stdio-agent/README.md | 5 + packages/ui/tool-ask-user/README.md | 5 + packages/ui/user-interaction/README.md | 5 + packages/web/tool-web/README.md | 6 + packages/web/web-fetch-local/README.md | 6 +- packages/web/web-search-deepseek/README.md | 7 + packages/web/web-search-exa/README.md | 6 + packages/web/web-search-perplexity/README.md | 7 + packages/web/web/README.md | 7 + scripts/run-gates.ts | 1 + scripts/verify-readme-limitations.ts | 134 ++++++++++++++++++ 58 files changed, 483 insertions(+), 45 deletions(-) create mode 100644 docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md create mode 100644 scripts/verify-readme-limitations.ts diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 1ab6931696..aad9821d04 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -13,7 +13,9 @@ packages/// # you use Config, + ../..// for each dsh dep) src/index.ts # service default export or plugin (name/inject/apply/Config) tests/.spec.ts - README.md # service API, events, extension points, design notes + README.md # service API, events, extension points, design notes, + # + the gated "Known Limitations and Deferred Work" section + # (or a whitelist entry in scripts/verify-readme-limitations.ts) ``` Choose an existing group when one matches the package's role (`core`, `llm`, `bash`, `compact`, `subagent`, `todo`, `session-persistence`, `ui`, `util`, or `support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it. diff --git a/docs/rfc/INDEX.md b/docs/rfc/INDEX.md index 654b68ffb5..9b13bfe557 100644 --- a/docs/rfc/INDEX.md +++ b/docs/rfc/INDEX.md @@ -161,6 +161,7 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand; | [Raise the Node LTS engine floor to 22.19](implemented/process/2026-07-06-node-engine-floor.md) | 2026-07-06 | | [Parallel GitHub CI gates](implemented/process/2026-07-06-parallel-github-ci-gates.md) | 2026-07-06 | | [Parallel pre-push gates](implemented/process/2026-07-06-parallel-pre-push-gates.md) | 2026-07-06 | +| [A gated Known-Limitations section in every package README](implemented/process/2026-07-10-readme-known-limitations-gate.md) | 2026-07-10 | ### Testing diff --git a/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md b/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md new file mode 100644 index 0000000000..cc25c0efe7 --- /dev/null +++ b/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md @@ -0,0 +1,28 @@ +# RFC: A gated Known-Limitations section in every package README + +Status: implemented + +## Problem + +The [documentation standard](../../../AGENTS.md) assigns limitations to the package-README tier ("the per-package contract: config, semantics, limitations, extension points"), but nothing enforced that the section exists or shares a shape. Ten READMEs carried the content under five ad-hoc headings — "What is NOT here (TODO)", "What is NOT here", "Deferred (faithful-but-degraded)", "Limitations (MVP, documented deliberately)", "Known limitations (tracked TODOs)" — and the other forty-odd carried nothing, so a reader could not distinguish "this package has no known limitations" from "nobody wrote them down", and no grep could enumerate the repo's known gaps. + +## Decision + +Every `packages///README.md` carries a canonical `## Known Limitations and Deferred Work` section: a condensed bullet list of consumer-visible gaps (unimplemented features, platform caveats, deliberate MVP cuts) and consciously postponed work (TODO markers, RFC deferrals still open). A `doc-sync` gate, `verify-readme-limitations` ([scripts/verify-readme-limitations.ts](../../../../scripts/verify-readme-limitations.ts)), enforces the shape per README: exactly one limitations-like heading, byte-equal to the canonical h2, with at least one top-level bullet. Near-miss headings at any level ("Limitations", "Deferred", "What is NOT here", …) fail the gate, so variant sections cannot creep back beside — or instead of — the canonical one. + +A package with genuinely nothing to declare is whitelisted (`NO_LIMITATIONS` in the script) and must NOT carry the section. The inverted check keeps the whitelist honest in both directions: an empty or boilerplate section cannot satisfy the gate, and giving a whitelisted package real limitations forces the whitelist edit in the same change. Whitelist entries are validated against the scanned package set, so a package rename or removal fails loud instead of silently un-gating a README. + +The gate checks presence, shape, and the whitelist; the bullets' truthfulness and specificity are governed by review under the documentation standard, like the rest of the README tier. The standing rule lives in [packages/AGENTS.md](../../../../packages/AGENTS.md). + +## Alternatives considered + +- **Free-form headings, gate only that "something limitations-like" exists** — keeps the five variants, stays un-greppable, and needs the same near-miss heuristics anyway without buying uniformity. +- **Require the section in ALL READMEs, allowing an empty body or "None."** — boilerplate "None" rots silently as a package gains real limitations; the whitelist inversion turns "nothing declared" into an explicit, lintable claim that review can challenge. +- **A word-count ceiling on the section** — limitation lists are legitimately variable in length; package READMEs are deliberately unbudgeted (per the [budget policy](../../../AGENTS.md)) and review governs their prose. + +## Consequences + +- A new package cannot ship without either declaring its gaps or explicitly claiming it has none; a missing, drifted, or empty section fails `doc-sync` locally (pre-push) and in CI (`readme-limitations` in the run-gates doc-sync leaf set). +- The pre-existing variant sections are normalized to the canonical heading, and every package README now answers the limitations question one way or the other. +- One more fast tsx script in the `doc-sync` chain; no new dependency (plain `node:fs` glob + line scan). +- The canonical heading is enforced verbatim, so renaming it later is a mechanical one-script-plus-all-READMEs change guarded by the same gate. diff --git a/package.json b/package.json index ed0b603feb..2b697070c1 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "verify-type-equiv": "tsx scripts/verify-type-equiv.ts", "verify-translation-pairing": "tsx scripts/verify-translation-pairing.ts", "verify-doc-budgets": "tsx scripts/verify-doc-budgets.ts", + "verify-readme-limitations": "tsx scripts/verify-readme-limitations.ts", "verify-node-next-types": "tsx scripts/verify-node-next-types.ts", "gen-cordis-catalog": "tsx scripts/gen-cordis-catalog.ts", "gen-rfc-index": "tsx scripts/gen-rfc-index.ts", @@ -63,7 +64,7 @@ "verify-scoped-dispatch": "tsx scripts/verify-scoped-dispatch.ts", "verify-module-graph": "tsx scripts/gen-module-graph.ts --check", "constraints": "tsx scripts/check-workspace-constraints.ts", - "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-cordis-api && pnpm run verify-export-jsdoc && pnpm run verify-tool-catalog && pnpm run verify-config-catalog && pnpm run verify-persistence-catalog && pnpm run verify-doc-graphs && pnpm run verify-scoped-dispatch && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-mermaid && pnpm run verify-rfc-classification && pnpm run verify-rfc-format && pnpm run verify-type-equiv && pnpm run verify-translation-pairing && pnpm run verify-doc-budgets", + "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-cordis-api && pnpm run verify-export-jsdoc && pnpm run verify-tool-catalog && pnpm run verify-config-catalog && pnpm run verify-persistence-catalog && pnpm run verify-doc-graphs && pnpm run verify-scoped-dispatch && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-mermaid && pnpm run verify-rfc-classification && pnpm run verify-rfc-format && pnpm run verify-type-equiv && pnpm run verify-translation-pairing && pnpm run verify-doc-budgets && pnpm run verify-readme-limitations", "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types", "demo:echo": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/echo-agent/cordis.yml", "demo:repl": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/coding-agent/cordis.yml", diff --git a/packages/AGENTS.md b/packages/AGENTS.md index ac26b926f7..6a39b65b1f 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -12,5 +12,6 @@ Naming notes: - `src/types.ts` contains only types — no runtime code. - Tests live at package level under `tests/`, not `src/__tests__/`. - A package's README and JSDoc are part of the change: altered behavior (config keys, defaults, error codes, wire fields) updates them in the same commit. `doc-sync` gates what it can; prose accuracy stays on the author ([the documentation standard](../docs/AGENTS.md)). +- Every package README carries a `## Known Limitations and Deferred Work` section — condensed bullets for consumer-visible gaps and consciously postponed work; `verify-readme-limitations` (in `doc-sync`) gates it. A package with genuinely nothing to declare is instead whitelisted in [scripts/verify-readme-limitations.ts](../scripts/verify-readme-limitations.ts) and must not carry the section ([rationale](../docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md)). -Read the per-package README.md for package-specific details: service API, events, extension points, TODOs. +Read the per-package README.md for package-specific details: service API, events, extension points, known limitations. diff --git a/packages/bash/bash-local/README.md b/packages/bash/bash-local/README.md index 1ac8a7d06b..0a2c2ec5ec 100644 --- a/packages/bash/bash-local/README.md +++ b/packages/bash/bash-local/README.md @@ -25,6 +25,12 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi; - **Model-friendly env + credential scrub** — `process.env` minus credential-shaped vars (`*KEY*`/`*SECRET*`/`*TOKEN*`), then `NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` (Codex's hardcoded set) so pagers and ANSI color don't garble results. This scrub is the security control that keeps the harness's *ambient* credentials out of a spawned command. A spec's `env` is merged LAST (after the scrub), so a caller's explicit entry — a value it already holds — wins even on a credential-shaped name. The spec's `stdin`, when supplied, is written to the child and closed; with none supplied, fd 0 is `/dev/null` — the exact pre-seam default, so a command that probes stdin's file type is unaffected. Both `env`/`stdin` are set by in-process plugins (the hooks bridges); the model-facing tool doesn't expose them. See [the bash-stdin-env RFC](../../../docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md). - **Background tasks** — `start()` returns immediately, no timeout applies (Claude Code detaches timeouts when backgrounding), `readOutput()` is incremental with whole-stream byte offsets, and disposal kills everything. The spec's opaque `owner` token is stored on the tracked task and returned by `ownerOf(id)` — the executor never interprets it (the consumer's access policy does), and because it lives with the task here it survives a `tool-bash` HMR reload. -## Sandboxing +## Known Limitations and Deferred Work -Execution policy does NOT belong in this package: this executor always runs commands unconfined. Confinement is [`dsh-bash-sandbox`](../bash-sandbox/README.md), which extends this executor verbatim and confines commands under the `ctx.sandbox` seam's bwrap/Landlock/Seatbelt backends ([sandbox RFC](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md)); per-call allow/deny/ask policy belongs on the `tools/pre-execute` gate. +- **Unconfined by itself** — this executor always runs commands with the harness process's authority; deployments needing confinement compose [`dsh-bash-sandbox`](../bash-sandbox/README.md), while per-call allow/deny/ask policy belongs on `tools/pre-execute`. +- **POSIX-only** — the `bash` binary, detached process groups, group kills, and SIGTERM→SIGKILL escalation are hardcoded; Windows is unsupported. +- **The credential scrub is a name heuristic** — `*KEY*`/`*SECRET*`/`*TOKEN*` only; differently-named secrets (e.g. `*PASSWORD*`) pass through, and a whitelist for over-scrubbed vars is noted future work. +- **Spill files are never deleted** — full-output recovery files (and the private per-process spill dir) accumulate under the OS tmpdir until something external cleans them. +- **Finished background tasks are never evicted** — they stay in the task map, retaining their in-memory output tails, until executor disposal. + +The raw process handling lives in `src/run.ts`; `src/index.ts` is the service wiring. diff --git a/packages/bash/bash/README.md b/packages/bash/bash/README.md index ac7e00c3a3..55bf9d0703 100644 --- a/packages/bash/bash/README.md +++ b/packages/bash/bash/README.md @@ -35,3 +35,9 @@ Implementations subclass `BashExecutor`, implement the abstract methods, and cal The seam also owns the per-session mode override vocabulary (the sandbox RFC § Per-session mode switching): the log-only `'bash/sandbox-mode'` session event, the pure fold `effectiveSandboxMode(events)` (last event wins; `undefined` means "apply the executor default"), and THE write path `setSandboxMode(session, mode)` — the session log is the store, so an override survives restart by replay and two sessions can never see each other's mode. Writers must respect turn-enclosure: the ACP bridge anchors an idle switch at the next turn rather than appending between turns. The task id (`BashTaskId`) and the `owner` token (`OwnerToken`) are [branded](../../util/brand) — `OwnerToken` is a DISTINCT brand from `SessionId` (the seam never imports `dsh-session`; the `dsh-tool-bash` consumer is the single boundary that casts its `SessionId` into one). `run()` returns `BashRunResult` (exitCode, signal, timedOut, aborted, timeoutMs, stdout/stderr as `CollectedOutput`) and `start()`/`readOutput()` use `BashTask`/`BashTaskRead` for the background side. A sandboxing executor additionally stamps `sandbox` result facts on results and settled tasks (`BashSandboxInfo`: the mode it executed under, the conservative `denied` classification, and — for confined modes — the backend's `enforcement` completeness); the mode/enforcement vocabulary is owned by the [`dsh-sandbox`](../../sandbox/sandbox/) seam, and the facts are documented in [core-data-structures/bash.md](../../../docs/core-data-structures/bash.md). See `src/types.ts` for the full contracts. `stdin` and `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload on stdin and its `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` env. The model-facing `dsh-tool-bash` tool does not expose them as parameters — a model already has equivalent power through shell syntax (`FOO=bar cmd`, a heredoc), so they would be redundant tool params. This is not a security boundary: the implementation's credential scrub (not these fields) is what keeps the harness's ambient secrets out of a spawned command. They are plain optionals on the resolved spec (unlike `owner`'s required-but-nullable): a missing one means "none", the safe default. See [the bash-stdin-env RFC](../../../docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md). + +## Known Limitations and Deferred Work + +- **No interactive-input vocabulary** — `stdin` is written once at spawn and closed; the seam has no channel to feed a running task and no PTY session concept. +- **Two overlapping surfaces flagged for pruning** — `BashTask.done` duplicates `onTaskDone` (shipped consumers use only the latter), and `get()`/`list()` have test-harness consumers only; both are marked in [the long-running-runtime RFC](../../../docs/rfc/proposed/architecture/2026-06-20-generic-long-running-tool-runtime.md). +- **Foreground timeouts are always executor-owned** — a caller-owned-deadline mode on the seam is explicitly deferred by [the tool-call timeout-policy RFC](../../../docs/rfc/implemented/architecture/2026-07-07-tool-call-timeout-policy.md). diff --git a/packages/bash/tool-bash/README.md b/packages/bash/tool-bash/README.md index 800de0132c..6251b659fb 100644 --- a/packages/bash/tool-bash/README.md +++ b/packages/bash/tool-bash/README.md @@ -57,3 +57,8 @@ On top of a denial sits the escalation gate ([the sandbox RFC § Escalation](../ ## Per-session mode switching Under a sandboxing executor this plugin makes the session's standing mode override ([the sandbox RFC § Per-session mode switching](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md); the `bash/sandbox-mode` fold owned by [`dsh-bash`](../bash/README.md)) real at EXECUTION: every call is stamped `escalation grant > session override > undefined` onto `BashExecRequest.sandboxMode`; without either, the executor's `resolve()` applies its configured default. Nothing is stamped under a non-sandboxing executor (nothing would honor it) or for an agent-less caller (no session to fold). The prompt deliberately does NOT state the mode and a switch is not narrated: a standing declaration teaches the model to refuse preemptively, while the denial marker already names the mode the command ran under exactly when the boundary is hit — behavior, not belief, carries the state. + +## Known Limitations and Deferred Work + +- **Replay exit pills parse from result text** — output whose final line happens to be exactly `[exit code: N]` / `[killed by signal: …]` shows a wrong pill on session replay; a display-only known residual. +- **The bash tools opt out of `timeout-policy` budgets** — `bash` keeps the executor-owned `BASH_TIMEOUT` path and `bash_output`/`bash_kill` declare no budget, per [the tool-call timeout-policy RFC](../../../docs/rfc/implemented/architecture/2026-07-07-tool-call-timeout-policy.md). diff --git a/packages/code-runtime/code-runtime-worker/README.md b/packages/code-runtime/code-runtime-worker/README.md index f691904e88..a483b00839 100644 --- a/packages/code-runtime/code-runtime-worker/README.md +++ b/packages/code-runtime/code-runtime-worker/README.md @@ -30,3 +30,11 @@ Every field is validated (positive numbers) and defaulted; there are no other tu ## The worker entry, unbuilt and built `worker.ts` is deliberately erasable-only TypeScript with type-only cross-package imports: unbuilt (vitest/tsx), the host spawns `src/worker.ts` directly and Node's native type stripping loads it; built, the entry ships as the sibling bundle `lib/worker.js` (its own tsdown entry). The built path is pinned by `tests/built-lib.e2e.ts`, the real-load-path guard from [docs/testing.md](../../../docs/testing.md). + +## Known Limitations and Deferred Work + +- **OS processes a program spawns survive termination** — `worker.terminate()` ends the thread only, weaker than bash-local's process-group kill; orphan cleanup is a deployment concern until a container backend exists. +- **Type-strip rides Node's experimental `stripTypeScriptTypes` API** — the relied-on behavior is pinned by unit tests, with amaro/sucrase as named drop-in replacements if it shifts. +- **`computeMs` expiry can overshoot by up to one poll interval** — busy time is sampled every 25 ms (an internal constant, deliberately not config). +- **Programs get a five-method `console` shim** (`log`/`info`/`warn`/`error`/`debug`) — deliberately not Node's full console surface. +- **A non-cloneable or oversize completion value does not cross as a value** — it arrives as a bounded, truncation-marked `util.inspect` rendering in `value`'s place. diff --git a/packages/code-runtime/code-runtime/README.md b/packages/code-runtime/code-runtime/README.md index 20c9274b9c..ac903ac6d4 100644 --- a/packages/code-runtime/code-runtime/README.md +++ b/packages/code-runtime/code-runtime/README.md @@ -17,3 +17,9 @@ Semantics every implementation must honor (contract details in the class JSDoc): ## Vocabulary `CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on — defaulting (time budgets, output caps) is the implementation's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions`), each exposed to the program as one global object of async callables. `CodeRunResult` reports the completion `value?`, the ordered `logs` (`CodeLogEntry`: `console`/`stdout`/`stderr` source, console `level`, capped text), and the `error?` (`CodeRunFailure`: `kind` + model-feedable `message`). See `src/types.ts` for the full contracts. + +## Known Limitations and Deferred Work + +- **`run()` is one-shot** — `logs` arrive only on the resolved `CodeRunResult`; the seam exposes no streaming-log or progress surface for a live program's output. +- **A persistent REPL-style kernel is recorded future work** — the no-state-between-runs contract stands until a persistent-kernel backend brings its own logging story ([Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md)). +- **Only the worker-thread backend ships** — `'process'`/`'container'` are declared well-known `isolation` values with no implementation; a hard security boundary awaits a container backend. diff --git a/packages/compact/compact-basic/README.md b/packages/compact/compact-basic/README.md index a06e74b818..f68b52c8b5 100644 --- a/packages/compact/compact-basic/README.md +++ b/packages/compact/compact-basic/README.md @@ -56,3 +56,11 @@ export function apply(ctx: Context): void { ``` Loading the plugin registers `ctx.compact`. With `auto: true` (the default) it compacts automatically under token pressure; a consumer (a future `/compact` tool) can also call `ctx.compact.compactIfNeeded(...)` or `ctx.compact.compactRegion(...)` directly. + +## Known Limitations and Deferred Work + +- **Token estimation is the chars/`charsPerToken` heuristic** — a marked TODO schedules replacing it with an exact count (a real tokenizer, or provider `usage` fed back) so thresholds track the model's actual budget. +- **`estimatePressure()` does not count the request's `tools` field** — pressure is underestimated by the size of the serialized tool schemas the request also carries. +- **`compactRegion` requires an open turn** — a manual call on a fully-closed session throws ("no open turn") rather than compacting. +- **Summarization failure fails closed with full, over-budget history** — including truncation at the summarization `maxTokens`, which hidden reasoning tokens can consume; the auto path logs a warning and proceeds. +- **The summarization call has no transcript-snapshot coverage** — `dsh-llm-replay` derives calls from `assistant/chunk` events, so this chunk-less direct `ctx.llm.stream()` call cannot replay (named deferred replay infrastructure in [the seam RFC](../../../docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md)). diff --git a/packages/compact/compact/README.md b/packages/compact/compact/README.md index e98f00899f..3e47f9ca85 100644 --- a/packages/compact/compact/README.md +++ b/packages/compact/compact/README.md @@ -7,7 +7,7 @@ This package is the interface tier of the compaction capability, split so each c | Package | Role | |---|---| | `@deepseek-ai/dsh-compact` (this) | the interface: abstract service + `compact/*` events + `CompactionResult` + the shared transcript renderer (`renderTranscript`/`renderContentBlocks`) | -| `@deepseek-ai/dsh-compact-basic` (deferred) | a backend: chars-per-token estimation (`charsPerToken`, default 4) + token-budget retention + `llm.stream()` summarization | +| `@deepseek-ai/dsh-compact-basic` | a backend: chars-per-token estimation (`charsPerToken`, default 4) + token-budget retention + `llm.stream()` summarization | | `@deepseek-ai/dsh-tool-compact` (deferred) | the model-facing `/compact` tool over `ctx.compact` | Unlike the bash seam, this interface depends on `@deepseek-ai/dsh-session` and `@deepseek-ai/dsh-llm` — the contract's verbs are defined over a `Session` and its output is the `ContentBlock` vocabulary, so they cannot be expressed without naming those packages. That deviation from the "interface depends only on cordis" guidance is intentional and recorded in the [compaction capability-seam RFC](../../../docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md). @@ -48,3 +48,10 @@ The `compact/*` events extend `SessionEventMap` (merge-extensible) via declarati ## Implementing a backend Subclass `CompactService`, implement `compactIfNeeded` and `compactRegion`, and load the subclass as a plugin — it registers as `ctx.compact`. A tokenizer-, template-, or model-backed implementation can live as a sibling package without changing callers. + +## Known Limitations and Deferred Work + +- **No model-facing consumer tier yet** — `@deepseek-ai/dsh-tool-compact` (the `/compact` tool) is deferred; compaction is reachable only via direct `ctx.compact` calls or a backend's auto listener. +- **Single-unit overflow is out of contract** — one retained unit (a closed step or a large pasted `user/message`) alone exceeding the budget cannot be compacted; the call may go out over-budget. +- **A session prefix that alone approaches the window is a configuration error no backend fixes** — compaction shrinks derived history, never the prefix. +- **Request context injected by downstream `agent/request` listeners sits outside pressure accounting** — `compactIfNeeded` counts prefix, derived history, and system prompt only. diff --git a/packages/compact/compact/src/index.ts b/packages/compact/compact/src/index.ts index 7d1138314c..49330f7647 100644 --- a/packages/compact/compact/src/index.ts +++ b/packages/compact/compact/src/index.ts @@ -11,7 +11,7 @@ * touching consumers. * * The split follows the capability-seams RFC — interface (this) / - * implementation (deferred) / consumer (a `/compact` tool, deferred) — modeled + * implementation (`dsh-compact-basic`) / consumer (a `/compact` tool, deferred) — modeled * on the bash trio. Unlike `dsh-bash`, this interface necessarily * depends on `dsh-session` and `dsh-llm`: the contract's verbs are defined over * a `Session` and its output is the `ContentBlock` vocabulary. That deviation diff --git a/packages/cordis/tool-cordis/README.md b/packages/cordis/tool-cordis/README.md index c4f3cf11b9..78ec4ee94b 100644 --- a/packages/cordis/tool-cordis/README.md +++ b/packages/cordis/tool-cordis/README.md @@ -31,3 +31,9 @@ All three tools render `generic` cards (`read` / `execute` / `delete`); `cordis_ ## Export shape Namespace plugin: named exports `name` / `inject` / `Config` / `apply`, no default export ([docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). + +## Known Limitations and Deferred Work + +- **The sandbox is containment for honest code, not a security boundary** — host-realm helpers on the sandbox global are reachable, so mount code can reach Node; load this plugin as deliberately as you would grant a bash tool (see § Trust stance). +- **The `ctx` façade exposes no `effect()`** — mount code cannot register a bespoke disposer; `on`/`provide`/`tools.register` cover every mount seen so far, and a guarded `effect` waits on a real need (`FIXME(sandbox-effect)`). +- **`vmTimeoutMs` bounds only synchronous evaluation** — an async mount body escapes it; there is no async budget on mount code. diff --git a/packages/core/agent-core/README.md b/packages/core/agent-core/README.md index 33cd0faddd..28e75a9d80 100644 --- a/packages/core/agent-core/README.md +++ b/packages/core/agent-core/README.md @@ -48,3 +48,8 @@ The bundle FORWARDS each field to the child that owns it: `agents` to `agent-loo ## Why a code bundle, not a shared YAML include A YAML include can dedupe the config, but it cannot OWN a `bin`, and it can only *describe* the front-door coupling in a comment and trust each leaf to obey. Moving the spine into a package, and the front-door cluster into the app packages, means the default leaf for an ACP server has no logger entry to copy wrong — "the ACP app never logs to stdout" stops being a prose warning a leaf must remember and becomes the app package's default shape (a leaf can still add a sibling logger, so the rule stays documented — but it has nothing to get wrong by default). Services register in the root store keyed by their isolate symbol, so a child loaded here is visible to the bundle's siblings (the leaf's adapter and executor) exactly as a nested `plugin-include` subtree's services were — cordis gates every read on `inject`, never on load order. + +## Known Limitations and Deferred Work + +- **The spine set is fixed in code** — `apply()` mounts every child unconditionally (including `tool-bash`); no config excludes or replaces one, so swapping the loop or dropping a spine member means composing a different bundle. +- **`dsh-invariants` mounts unconditionally** — a dev/test plugin with default `freeze: true` and no reachable toggle in this bundle's `Config`, so every deployment currently pays the assertion/freeze cost. diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 217ba0a643..1004083829 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -93,7 +93,7 @@ Error containment: a throwing plugin ends the **turn**, never the loop. A malfor Cancellation: `agent.cancel()` is the single public stop primitive — it clears the queued + steering FIFOs, aborts the in-flight step, and drives a turn-scoped marker the driver checks at every point a turn could start or continue (right after the idle wait, after the `running` flip, before each step, and at the continuation gate) so a turn about to start is dropped. A cancelled turn ends `aborted`; a queued-but-not-started prompt never runs and cannot be batched into the cancelled turn. The marker is reset once per loop iteration, so a cancel governs exactly one turn and never leaks onto a later prompt. (The loop still aborts its own per-step `AbortController` directly on disposal and from `cancel()`; that controller is loop-internal, not a public verb.) -### What is NOT here +### What belongs to plugins Everything that goes beyond "call the model, run the tools, repeat" belongs to plugins listening on the event taxonomy: - Hooks and policy: the relevant `agent/*` checkpoints plus the guarded `tools/pre-execute` → `tools/execute` → `tools/post-execute` → `tools/result` pipeline; exact signatures and modes live in the [generated event catalog](../../../docs/cordis-catalog/events.md) @@ -102,3 +102,11 @@ Everything that goes beyond "call the model, run the tools, repeat" belongs to p - Sub-agents: implemented outside the loop as `ctx.subagents` providers; in-process providers use `ctx.agents.create()` and owned `AgentHandle` teardown, while child streaming/progress and background/poll collection remain deferred. - Persistence: `session/event` + `session/flush` - UI: `session/event` (assistant token stream, boundaries, tool activity) + `agent/*` control events (`agent/status`, `agent/created`/`agent/disposed`) + +## Known Limitations and Deferred Work + +- **Tool calls within a step execute sequentially** — parallel execution waits on concurrency-safety metadata in the tool contract (see `dsh-tools`). +- **No resume-or-create policy on the config path** — config-driven `create()` starts a fresh `${id}-session-` every run (`TODO(demo)`), and a config `resumeSessionId` whose resume fails logs a warning and creates no agent. +- **Config agents carry no session `cwd`** — `Config` has no cwd field, so a persona referencing `{{cwd}}` fails prompt assembly for config-created agents. +- **No built-in turn budget** — the default continuation is `continue` whenever a step had tool calls or steering; bounding a runaway turn requires an `agent/turn-continuation` force-stop plugin. +- **`runLoop`/`Inbox`/`InboxMessage` stay exported with no outside consumer** — [removal is proposed](../../../docs/rfc/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md). diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index 71e2be279d..c31eb3680c 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -52,6 +52,12 @@ The handle every plugin programs against: - Event listeners: all `agent/*` events are declared here — no dependency on the loop package needed. - Subagent delegation: implemented by `@deepseek-ai/dsh-subagent`, not by a method on `Agent`; providers create or drive ordinary `Agent` handles through the factory seam, so spawn/fork/ACP transports stay outside the core agent interface. -### What is NOT here (TODO) +## Known Limitations and Deferred Work - **Inter-agent channels beyond delegation** — shared state, streaming child output, and background/poll semantics remain outside the current synchronous `ctx.subagents` seam. +- **`agent/session-start` cannot gate startup** — a synchronous, veto-less emit, so an async listener's injection is best-effort before turn 1; startup gating is a deferred loop-level change. +- **No public step-only abort** — `cancel()` clears ALL pending work (queued + steering + in-flight); an abort that preserves queued prompts returns only with a named consumer ([stop-surface RFC](../../../docs/rfc/implemented/simplification/2026-06-20-public-agent-stop-surface.md)). +- **`AgentRegistry` enforces unique agent ids, not session ids** — two live agents sharing a session id mis-route bash owner-token notices; [id unification stays proposed](../../../docs/rfc/proposed/simplification/2026-06-20-unify-agent-and-session-id.md). +- **`HookContext` carries exactly one `MessageSource`** — contributions from several plugins merged onto one tool call collapse under one source; mixed provenance is unrepresentable. +- **`SessionStartSource` reserves `'clear'`/`'compact'` with no emitter yet** — only `'startup'`/`'resume'` occur until the driving subsystems land (`TODO(compaction)`). +- **`agent/pre-step`'s `fullSystemPrompt`/`sessionPrefix` parameters are a flagged smell** — compaction is their only consumer; a lazy prompt provider or a compaction-specific pressure seam is the marked revisit. diff --git a/packages/core/session/README.md b/packages/core/session/README.md index 094c3073df..fe15d4ab97 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -73,6 +73,9 @@ Every `SessionEvent` carries two optional top-level fields (structural metadata) - Replay/fork: `ctx.sessions.create(id, { seed })` seeds a new session with an existing event log. The surface rebuilds deterministically from `surfaceOp` markers in the seeded events. The seed is validated to the SAME always-on invariants `append` enforces — contiguous seqs, JSON-serializable data, and required `surfaceOp` markers on surface-eligible events — so marker-less message events are rejected at construction rather than silently vanishing from `deriveMessages()`. Broader turn-enclosure checks stay in `dsh-invariants` and persistence repair. Ordinary live-session forks use `ctx.sessions.fork(source, boundary?, childSessionId?)`, where `boundary` is the inclusive source event seq to fork through. - Compaction: the `dsh-compact-basic` plugin appends a `user/message` with `surfaceOp: { op: 'replace', start, end }` to shadow old surface nodes behind a summary checkpoint. -### What is NOT here (TODO) +## Known Limitations and Deferred Work - **Session branching/tree** (pi-style entry tree) — deferred unless needed beyond boundary-based `fork()`. +- **`fork()` cuts only at closed-turn boundaries of live sessions** — the boundary must be a `turn/end` event and the source must be in the store; forking a persisted-but-unloaded session is excluded from the [fork API](../../../docs/rfc/implemented/feature/2026-06-30-session-store-fork-api.md). +- **`SESSION_FORMAT_VERSION` stays pinned at `0`** — pre-release, no compatibility implied: a backend rejects any other version, and no migration path exists until the first release ([policy](../../../AGENTS.md)). +- **`TurnEndReasonMap` omits the ACP-named `refusal` / `max_turn_requests` variants** — producer-gated: they land when an adapter or the loop first emits them. diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index d712cd4b5b..f55df539d3 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -41,9 +41,13 @@ Merge-extensible: plugins can declare extra fields on `PromptAssembly` and `Asse - The `system-prompt/assemble` waterfall: mutate or replace the assembly per caller (dynamic tool filtering, extra variables). - `systemPrompt.protect()`: reserve canonical section/tool contributions for invariants that ordinary waterfall listeners must not be able to remove or replace. -### What is NOT here - -- Any deployment-authored prompt text outside config — the persona is this plugin's `persona` config, and every other section comes from the plugin that owns the fact. (The `harness:identity` line is deliberately a code literal: a harness fact, not a deployment choice; the `system-prompt/assemble` waterfall is the escape valve for a deployment that must drop it.) -- Prompt compaction (belongs on the `agent/pre-step` seam in `dsh-agent`). - Design rationale: [the prompt-variables RFC](../../../docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md). + +## Known Limitations and Deferred Work + +- **No deployment-authored prompt text outside config** — the persona is this plugin's `persona` config, and every other section comes from the plugin that owns the fact. (The `harness:identity` line is deliberately a code literal: a harness fact, not a deployment choice; the `system-prompt/assemble` waterfall is the escape valve for a deployment that must drop it.) +- **No prompt compaction here** — it belongs on the `agent/pre-step` seam in `dsh-agent` (implemented by `dsh-compact-basic`). +- **No escape syntax for literal `{{…}}` braces** — every complete group is interpolated against registered variables; an escape is deferred until a real prompt needs one. +- **`persona` is context-global** — one deployment fragment shared by every agent, subagents included; per-agent personas need a `system-prompt/assemble` listener. +- **`toolOrder` misconfiguration surfaces at prompt assembly (the first turn), not at boot** — only shape violations throw at config load. +- **Sections sharing an `order` value tie-break by registration order** — a plugin-load artifact; determinism relies on the distinct-order band convention, unlike the canonicalized tool order. diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index 2a678408e0..b46a109679 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -140,7 +140,12 @@ Under `mode: code` (or `both`) the registry turns the tool surface into a progra The wire collapse is the registry's own contribution (`systemPrompt.tools()` is mode-aware), so the logged `request/header` records it for free. With no deliberate schema-adding assembly listener, `code` assembles exactly `[run_code]`, pinned by tests and the snapshot goldens; protection guarantees that `run_code` and `tools:sdk` remain present, not that unrelated listener additions are erased. Try it: `pnpm run demo:code-mode` ([the coding-agent example's Code Mode overlay](../../../examples/coding-agent/README.md#code-mode)); `pnpm run demo:code-mode acp` serves the same mode over ACP instead of the REPL. -### What is NOT here (TODO) +## Known Limitations and Deferred Work -- **Concurrency metadata** — tool definitions do not declare whether executions are safe to overlap. -- **Parallel execution** — the loop and Code Mode bridge execute tool calls sequentially until that metadata exists. +- **Native tool calls execute sequentially** — `ToolDefinition` carries no concurrency-safety metadata; adding it (and parallel execution in the loop) waits on the deferred tool-shapes review (`TODO(review)`). +- **`tools/pre-execute` deliberately cannot rewrite `exec.arguments`** — logged and rendered args would desync from what ran; the rewrite design is [a proposed RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md). +- **`defineTool`'s schema DSL is a deliberate subset** — string/number/boolean/object/array with string-only `enum`; `validateArgs` tolerates extra keys and never applies `default` (`XXX(unused-default)` flags removing that field); raw-registered JSON-Schema tools validate their own input. +- **`timeoutMs` on a definition is declarative only** — the registry never enforces deadlines; enforcement requires the `@deepseek-ai/dsh-timeout-policy` wrapper. +- **Code Mode is TypeScript-only and registry-wide** — `mode: code`/`both` rejects prompt assembly unless `ctx.codeRuntime.language === 'typescript'`, and per-tool visibility tiers (one tool native, another code-only) are knowingly deferred. +- **Code Mode bindings return text only** — non-text content blocks in a sub-call result collapse to `[ content]` placeholders. +- **`run_code` state is fresh per run** — a persistent REPL-style kernel is rejected for the MVP (cross-call state would be invisible to the log); see [the Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md). diff --git a/packages/fs/fs-local/README.md b/packages/fs/fs-local/README.md index 26524fad24..21a057b101 100644 --- a/packages/fs/fs-local/README.md +++ b/packages/fs/fs-local/README.md @@ -19,8 +19,13 @@ await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) - **`writeText`** — atomic: writes to a temp file opened exclusively (`wx`, `0o600`) inside a randomly-named private staging dir (`0o700`) next to the target, fsyncs, then renames over the target. An existing file's mode is preserved, while new files default to `0o600`. The `expected` guard is OPTIONAL: omitting it unconditionally creates-or-overwrites; `createIfAbsent` creates a missing target and rejects an existing one (`FS_NOT_OBSERVED`); `replaceIfVersion` replaces only at the observed version (a missing target or mismatch is `FS_STALE_VERSION`). - **`editText`** — atomic literal read-modify-write over the same primitive, serialized per target by a mutation lock. The `expected` guard is OPTIONAL: when supplied it verifies the version BEFORE literal matching (a stale edit reports `FS_STALE_VERSION`, never `FS_EDIT_NOT_FOUND`/`FS_AMBIGUOUS_EDIT` against newer content); omitting it edits the current content unconditionally. A missing target reports `FS_STALE_VERSION` either way. LF-normalizes for matching, restores the file's dominant CRLF/LF style, and rejects empty `oldString` / zero matches (`FS_EDIT_NOT_FOUND`) or ambiguous multi-matches without `replace_all` (`FS_AMBIGUOUS_EDIT`). -## `cwd` is not a sandbox - -`config.cwd` is a resolution default, not a containment boundary — absolute paths and `..` escape it. Enforce containment with a stricter `ctx.fs` backend or a permission plugin on the `tools/execute` waterfall. See [the filesystem capability-seam RFC's Consequences section](../../../docs/rfc/implemented/architecture/2026-06-17-filesystem-capability-seam.md#consequences). - The raw I/O lives in `src/fsio.ts` (Cordis-free, independently unit-tested); `src/index.ts` is the thin service wiring. + +## Known Limitations and Deferred Work + +- **`config.cwd` is not a sandbox** — it is a resolution default, not containment: absolute paths and `..` escape it. Enforce containment with a stricter `ctx.fs` backend or a permission plugin on the `tools/execute` waterfall ([capability-seam RFC](../../../docs/rfc/implemented/architecture/2026-06-17-filesystem-capability-seam.md#consequences)). +- **An overwrite reads the whole prior file into memory** — solely as the UI diff basis; bounding that pre-read above a size threshold is deferred (`TODO(overwrite-diff-bound)`). +- **Version tokens are `mtimeMs:size`** — an external change that preserves both within the filesystem's timestamp granularity defeats the stale guard. +- **`editText` holds the whole file (plus the edited copy) in memory** — streaming exists only on the read path. +- **Binary detection is asymmetric** — reads NUL-sample only the first 8192 bytes while edits scan the whole buffer, so a file with a late NUL reads fine but rejects edits. +- **The per-target mutation lock is in-process only** — a writer in another process is caught only by the optional version guard, never serialized. diff --git a/packages/fs/fs-policy/README.md b/packages/fs/fs-policy/README.md index ad912bfc95..f3741d6ed9 100644 --- a/packages/fs/fs-policy/README.md +++ b/packages/fs/fs-policy/README.md @@ -46,3 +46,10 @@ The `fs/write-intent`/`fs/edit-intent` slots hold exactly one decider — this p ## No method coupling Because the plugin influences the world only through events, removing it does not break `@deepseek-ai/dsh-tool-fs` at a service-injection boundary: the tool falls through to the bare `ctx.fs` provider (unconditional write/edit, no observed-state). Loading it back layers the policy on. That graceful add/remove is the whole point of the event gate over a mandatory method service. + +## Known Limitations and Deferred Work + +- **Observed state does not survive a session resume** — persistence of the `WeakMap` record is deferred, so a resumed session must re-read files before guarded writes/edits. +- **Actors without an agent session can never satisfy the policy** — their edits throw `FS_NOT_OBSERVED` and their writes always resolve `createIfAbsent`, so a non-agent caller cannot overwrite an existing file through the gate. +- **Direct `ctx.fs` reads emit no `fs/observed`** — a file read outside the `read` tool stays unobserved, and a later guarded edit rejects with `FS_NOT_OBSERVED` until the tool reads it. +- **Authorization is version freshness, not view completeness** — any windowed read authorizes a full-file overwrite of an unchanged file, deliberately weaker than a full-view rule ([seam-split RFC](../../../docs/rfc/implemented/simplification/2026-06-26-fsspec-style-fs-seam.md)). diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index ac2866802b..9f5ce3d950 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -42,3 +42,11 @@ This package declares three events (see the generated [events catalog](../../../ ## Vocabulary `FsTargetKey` / `FsVersion` are branded opaque ids ([the branded-ids RFC](../../../docs/rfc/implemented/architecture/2026-06-20-branded-ids.md)) — consumers must not parse `targetKey` or interpret `version`; only `displayPath` is for model/UI output. `FsWriteIntent` is the explicit GUARDED write intent (`createIfAbsent` creates a missing target and rejects an existing one with `FS_NOT_OBSERVED`; `replaceIfVersion` replaces only at the observed version, else `FS_STALE_VERSION`); omitting it from `writeText` is the third, unconditional state. Failures throw `FsError` (extends `HarnessError`, [the structured error taxonomy RFC](../../../docs/rfc/implemented/architecture/2026-06-11-structured-error-taxonomy.md)) carrying a stable `FsErrorCode` (`FS_NOT_FOUND`, `FS_NOT_DIRECTORY`, `FS_NOT_TEXT`, `FS_NOT_REGULAR_FILE`, `FS_PERMISSION_DENIED`, `FS_IO_ERROR`, `FS_STALE_VERSION`, `FS_NOT_OBSERVED`, `FS_AMBIGUOUS_EDIT`, `FS_EDIT_NOT_FOUND`, `FS_ABORTED`); the tool registry surfaces `{ name, code }` on `isError` results. See `src/types.ts` for the full contracts. + +## Known Limitations and Deferred Work + +- **Text-only by contract** — backends reject binary/non-UTF-8 content with `FS_NOT_TEXT`; binary-safe operations are a deliberate deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md). +- **Seven primitives only** — no delete, rename/move, copy, or watch; `listDir` is single-level, with recursion, globbing, pagination, and search out of scope per [the directory-listing RFC](../../../docs/rfc/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.md). +- **`listDir` has no in-repo consumer yet** — skill discovery and a model-facing `ls` tool are the named follow-ups; sessions list directories via `bash` meanwhile. +- **No IO deadline** — the seam arms no timeout; cancellation is a best-effort optional `AbortSignal` per primitive (the deliberate [fs-family stance](../README.md)). +- **Resolve-then-operate costs a remote backend two round-trips per tool call** — folding or caching resolution is left to such a backend. diff --git a/packages/fs/tool-fs/README.md b/packages/fs/tool-fs/README.md index fedd1ff7a2..88a0e9663a 100644 --- a/packages/fs/tool-fs/README.md +++ b/packages/fs/tool-fs/README.md @@ -47,3 +47,9 @@ The tool passes `exec` (the tool-execution context) as the opaque `actor` on eve `fs/observed` fires AFTER the read/write/edit already succeeded, via a plain `ctx.emit`. A listener is contractually a synchronous, side-effect-only recorder (`@deepseek-ai/dsh-fs-policy`'s is a `WeakMap.set`); the tool does not guard the emit, so a listener that throws would surface as the tool's `isError` result — async or fallible observation does not belong on this event. The read rendering (line windowing + output formatting) lives in `src/read-render.ts` (Cordis-free, independently unit-tested); `src/read.ts`/`write.ts`/`edit.ts` are the tool executors and `src/index.ts` composes them. + +## Known Limitations and Deferred Work + +- **No directory-listing, glob, grep, or search tools ship** — a deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md) (and `ctx.fs.listDir` has no tool consumer yet); models fall back to `bash`. +- **`read` handles UTF-8 text files only** — binary-safe reads and PDF/image/multimodal content are deferred; a directory target is `FS_NOT_REGULAR_FILE`. +- **No timeout surface** — `read`/`write`/`edit` take no timeout argument and declare no `timeout-policy` budget; cancellation rides `exec.signal` only (the deliberate [fs-family stance](../README.md)). diff --git a/packages/guard/repeat-tool-guard/README.md b/packages/guard/repeat-tool-guard/README.md index dc385bc033..566c2bc576 100644 --- a/packages/guard/repeat-tool-guard/README.md +++ b/packages/guard/repeat-tool-guard/README.md @@ -35,3 +35,12 @@ Reminders ride the post-execute decision's `additionalContext` (source `{kind: ' ## Testing Unit suites drive a real agent loop against a mock adapter (no network) and cover the chain semantics above to per-file 100%. The snapshot tier owns the transcript surface: a scripted-replay scenario repeats a call five times and pins both reminder tiers (gentle at 3, detailed at 5) as `context/message`s in the ACP transcript. + +## Known Limitations and Deferred Work + +- **Exact-match detection only** — canonicalization is a deep key-sort, so near-identical variants (a tweaked path, extra whitespace inside a value) evade the chain; fuzzy matching is rejected pending evidence of need. +- **Compaction does not reset chains** — a chain spanning a compaction checkpoint keeps counting. +- **Advisory only** — escalating to `block` at a high threshold is not implemented, though `PostToolDecision` already supports blocking. +- **No subagent chain-sharing** — chains stay isolated per agent; a parent and its subagent repeating the same call never combine. +- **Legitimate idempotent polling still draws nudges** past the thresholds — the pressure valves are `thresholds`/`exclude` config. +- **Past the highest threshold a chain goes silent** — reminders fire only at exact configured counts, never beyond them. diff --git a/packages/hooks/hook-protocol/README.md b/packages/hooks/hook-protocol/README.md index 8296821503..457351d8bb 100644 --- a/packages/hooks/hook-protocol/README.md +++ b/packages/hooks/hook-protocol/README.md @@ -29,6 +29,7 @@ Declaration-merged into `SessionEventMap` (log-only, like `compact/*` — NOT a Like every event they must sit inside an open turn. The mid-turn points (`PreToolUse`/`PostToolUse`/`UserPromptSubmit`/`Stop`) fire inside the loop's open turn by construction; `SessionStart` gets no `hook/*` record (its injected `context/message` is the durable evidence) — see the hooks RFC. -## Input rewrite is parsed but not honored +## Known Limitations and Deferred Work -`HookOutput.updatedInput` carries a hook's requested tool-input rewrite (CC `updatedInput`), but the harness does not honor it yet — input rewrite is a deferred consistency-design problem ([the pre-tool-input-rewrite RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md)). A bridge logs + warns when a hook sets it. See `src/types.ts` for the full contracts. +- **`HookOutput.updatedInput` is parsed but not honored** — input rewrite is a deferred consistency-design problem ([the pre-tool-input-rewrite RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md)); a bridge logs + warns when a hook sets it. See `src/types.ts` for the full contracts. +- **An invalid matcher regex matches nothing, silently** — `matchesMatcher` never throws; surfacing the error needs a diagnostic-returning variant or parse-time validation (`TODO(matcher-diagnostics)`). diff --git a/packages/hooks/hooks-claude/README.md b/packages/hooks/hooks-claude/README.md index f97cdb5cfc..7a738849f4 100644 --- a/packages/hooks/hooks-claude/README.md +++ b/packages/hooks/hooks-claude/README.md @@ -50,8 +50,11 @@ The matcher subject is the tool name (`PreToolUse`/`PostToolUse`), the session s Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-claude' }` source. `agent.inject()` defaults a missing source to `{ kind: 'user' }`, which would mislabel plugin context as a user prompt — so the bridge always names itself. -## Deferred (faithful-but-degraded) +## Known Limitations and Deferred Work - **`updatedInput` (tool-input rewrite)** is logged + warned, **not honored** — input rewrite is a deferred consistency-design problem ([the pre-tool-input-rewrite RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md)). - **`systemMessage`** (a hook's user-facing warning) is logged + warned, **not surfaced** — there is no user-message channel on these seams yet (only model-facing `additionalContext`). The shared merge collects it; the bridge does not yet render it. - **Stop loop-guard.** CC breaks an infinite force-continue with `stop_hook_active` (true once a Stop hook has fired this run) plus a max-consecutive cap; both are deferred (`TODO(stop-loop-guard)`). Today `stop_hook_active` is always `false`, so a Stop hook that unconditionally blocks would force-continue every step — a hook author must self-limit until the guard lands. +- **`{"continue": false}` is recorded, not enforced** — the merge computes the `stop`/`stopReason` outcome and the `hook/result` event records decision `stop`, but the run is not halted (`TODO(hook-continue-false)`). +- **`SessionStart` cannot gate the first turn** — `agent/session-start` is a synchronous emit, so a hook's injected context lands best-effort before turn 1 (`TODO(session-start-gating)`). +- **Hook config is process-level** — one `configPath` parsed at load for the whole process; per-session discovery of a project-local config is deferred (`TODO(per-session-hook-config)`). diff --git a/packages/hooks/hooks-codex/README.md b/packages/hooks/hooks-codex/README.md index b3fbc39928..aa5975eeb7 100644 --- a/packages/hooks/hooks-codex/README.md +++ b/packages/hooks/hooks-codex/README.md @@ -54,8 +54,10 @@ A tool call's payload carries the real `tool_name` (the same value the matcher t Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-codex' }` source (`agent.inject()` would otherwise default it to `{ kind: 'user' }`). -## Deferred +## Known Limitations and Deferred Work -**Stop loop-guard** (`TODO(stop-loop-guard)`): as in CC, a Stop hook that unconditionally blocks would force-continue every step (`stop_hook_active` is always `false` here); the loop-guard is deferred. A hook author must self-limit until it lands. - -**`systemMessage`**: a hook's user-facing warning is logged + warned, not surfaced — there is no user-message channel on these seams yet (only model-facing `additionalContext`). +- **Stop loop-guard** (`TODO(stop-loop-guard)`) — as in CC, a Stop hook that unconditionally blocks would force-continue every step (`stop_hook_active` is always `false` here); the loop-guard is deferred, and a hook author must self-limit until it lands. +- **`systemMessage`** — a hook's user-facing warning is logged + warned, not surfaced; there is no user-message channel on these seams yet (only model-facing `additionalContext`). +- **`{"continue": false}` is recorded, not enforced** — the `hook/result` event records decision `stop`, but the run is not halted (`TODO(hook-continue-false)`). +- **`SessionStart` cannot gate the first turn** — `agent/session-start` is a synchronous emit with a detached continuation, so a hook's injected context lands best-effort before turn 1 (`TODO(session-start-gating)`). +- **Hook config is process-level** — one `configPath` parsed at load for the whole process; per-session discovery of a project-local config is deferred (`TODO(per-session-hook-config)`). diff --git a/packages/llm/llm-deepseek/README.md b/packages/llm/llm-deepseek/README.md index 15fad9f881..63711926f7 100644 --- a/packages/llm/llm-deepseek/README.md +++ b/packages/llm/llm-deepseek/README.md @@ -34,10 +34,6 @@ Every request carries the shared attribution header from dsh-llm's `attributionH - **Reasoning passback rule**: on assistant turns that carried tool calls, `reasoning_content` is serialized back in history (required by the API in thinking mode); on tool-call-free turns it is dropped (ignored anyway — saves tokens). - Cache accounting: `cacheReadTokens` ← `prompt_cache_hit_tokens` / `prompt_tokens_details.cached_tokens`; DeepSeek reports no cache-write metric. -## Limitations (MVP, documented deliberately) - -- `tool_choice` is not mapped (not part of the core vocabulary). - ## Errors Non-2xx responses throw `LlmError` with stable codes: `AUTH` (401/403), `RATE_LIMIT` (429), `INVALID_REQUEST` (400), `SERVER` (5xx), `HTTP_` otherwise. Protocol violations throw `STREAM_CLOSED` (no `[DONE]`) or `MALFORMED_RESPONSE` (bad JSON payload). Unknown wire `finish_reason`s (e.g. `content_filter`, `insufficient_system_resource`) become `finish {kind: 'error', code: }` chunks. @@ -45,3 +41,9 @@ Non-2xx responses throw `LlmError` with stable codes: `AUTH` (401/403), `RATE_LI ## Testing Unit suites run against a local `node:http` mock SSE server (no network). Real-API coverage lives in `tests/adapter.e2e.ts` (`pnpm run test:e2e`, key-gated): V4 Flash + V4 Pro across thinking enabled/disabled and both official effort levels, including the thinking+tools round trip with reasoning passback. + +## Known Limitations and Deferred Work + +- **`tool_choice` is not mapped** — not part of the core vocabulary (MVP cut, shared with the pi-ai twin). +- **Requests use raw `fetch`, not `@cordisjs/plugin-http`** — no shared proxy/interception configuration; adoption is deferred until a second adapter wants it (`TODO(http)`). +- **Serialization flattens user and tool-result content to text blocks** — plugin-added block types are skipped, and empty tool output crosses the wire as the literal `(no output)`. diff --git a/packages/llm/llm-pi-ai/README.md b/packages/llm/llm-pi-ai/README.md index f74ccd2246..4af00a48ae 100644 --- a/packages/llm/llm-pi-ai/README.md +++ b/packages/llm/llm-pi-ai/README.md @@ -33,10 +33,14 @@ Every request carries the shared attribution header from dsh-llm's `attributionH pi-ai declares the openai/anthropic/google/mistral/AWS SDKs as install-time dependencies. They are lazy-loaded — only the openai SDK actually loads for this adapter — but they do land in `node_modules`. Accepted for a package whose purpose is design verification. -## Limitations - -Same MVP contract as llm-deepseek: `tool_choice` is not mapped. - ## Testing Unit suites run against a local `node:http` mock SSE server (pi-ai's openai SDK happily talks to any base URL). Real-API coverage in `tests/adapter.e2e.ts` (`pnpm run test:e2e`, key-gated): V4 Flash + V4 Pro across all exposed reasoning levels (off/high/xhigh), the thinking+tools round trip, and a cross-adapter structural-equivalence check against llm-deepseek. + +## Known Limitations and Deferred Work + +- **`tool_choice` is not mapped** — same MVP contract as llm-deepseek. +- **In-history `system`-role messages fold into `user`-role wire messages** — pi-ai exposes a single `systemPrompt` slot, diverging from the hand-rolled twin's `role: 'system'` passthrough. +- **`LlmError.status` is never set** — pi-ai reports failures as in-stream events with no HTTP status, so error codes are regex-classified from the error text. +- **`buildModel` hardcodes descriptor metadata** — `contextWindow: 128000`, `maxTokens: 64000`, zero cost, identically for every registered model name; not configurable. +- **pi-ai's built-in retries are disabled (`maxRetries: 0`)** — failures surface immediately; retry policy belongs to `llm/stream` listeners. diff --git a/packages/llm/llm/README.md b/packages/llm/llm/README.md index 42694ba5ea..20db4064f6 100644 --- a/packages/llm/llm/README.md +++ b/packages/llm/llm/README.md @@ -47,3 +47,12 @@ Every product adapter must identify the application on every provider HTTP reque ### Real adapters Two adapters implement `LlmAdapter` against this vocabulary, deliberately built on different internals to keep the contract honest (see [the twin LLM adapters](../../../docs/rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md)): [`@deepseek-ai/dsh-llm-deepseek`](../llm-deepseek) (hand-rolled fetch/SSE) and [`@deepseek-ai/dsh-llm-pi-ai`](../llm-pi-ai) (via `@earendil-works/pi-ai`). The pair pinned down the `StreamChunk` conventions now documented in `types.ts` (usage before finish, raw-string tool arguments, the two sanctioned error paths). + +## Known Limitations and Deferred Work + +- **No retry/caching/rate-limit layer ships** — `llm/stream` is the intended wrap seam and has no production listener, so provider 429/5xx failures surface immediately. +- **`GenerateOptions` sampling is `temperature`/`maxTokens`/`stop` only** — no `tool_choice`, `top_p`, or penalty fields; the vocabulary grows when a producer lands ([dropped inert knobs](../../../docs/rfc/implemented/simplification/2026-07-04-drop-inert-request-knobs.md)). +- **Producer-gated variants stay out until produced** — `prefill`, per-tool `strict`, block `cache` hints, and the `agent` message-source variant were pruned as producerless ([RFC](../../../docs/rfc/implemented/simplification/2026-07-04-prune-producerless-vocabulary-variants.md)). +- **`BlockAssembler` handles core block kinds only** — a plugin-added block type whose stream is never closed by `block-end` makes `blocks()` throw. +- **`APP_IDENTITY.url` names a repository that does not exist yet** — `FIXME`: creating the public `deepseek-ai/deepseek-harness-sdk` repo gates the first release. +- **`GenerateOptions.sessionId` is a locally-declared brand** — importing dsh-session's `SessionId` would cycle; a future ids-owning package would dissolve the workaround. diff --git a/packages/session-persistence/session-persistence-jsonl/README.md b/packages/session-persistence/session-persistence-jsonl/README.md index 9a76381614..9c81c3be5e 100644 --- a/packages/session-persistence/session-persistence-jsonl/README.md +++ b/packages/session-persistence/session-persistence-jsonl/README.md @@ -25,8 +25,13 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence - **Append-only.** Committed events (at or below a flushed `turn/end`) are never rewritten. Subsequent appends are line appends at EOF + `fsync`. - **Crash recovery — close, don't truncate.** A crash can leave a log whose final turn never closed (real events after the last `turn/end`). `load` PRESERVES those events (a turn can be huge — they are real work) and closes the orphaned turn by durably appending synthetic boundary events: an error `tool/result` for every `tool-call` the crash left unanswered (the loop logs the assistant message before running the tools, so a mid-tool crash leaves dangling calls — and `deriveMessages()` would replay an assistant tool-call with no result, which providers reject), then a `step/end` if a step was open, then `turn/end {kind:'interrupted'}`, returning a balanced log. Only a never-fully-written **torn tail fragment** (a final line with no newline / unparseable) is `ftruncate`d away before the closers are written. See [session persistence](../../../docs/rfc/implemented/architecture/2026-06-14-session-persistence.md). - **Contiguous-seq.** `load` rejects a mid-log parse error or `seq` gap (unloadable); `append` rejects a batch whose first `seq` does not continue the stored log, and rejects non-JSON-serializable `event.data` naming the offending event type. -- **Format version.** Only the current `SESSION_FORMAT_VERSION` (v0) is supported; `load` rejects any other version. While the harness is unreleased the on-disk format is pre-release/unstable: a breaking format change is absorbed at v0 (no bump until the first tagged release) and non-current logs are rejected — there is no migration (no persisted user data to preserve). ## Write path The plugin generalizes the example `session-jsonl.ts`: it subscribes to `session/created` (capture the header; persist a fork's seed once), `session/event` (snapshot each event when buffering — the live `session.events` object is mutable), and `session/flush`/dispose (drain the write-behind buffer through `append`). A per-session write cursor means a resumed session never re-appends already-stored events. Existing live sessions are seeded on plugin apply (HMR does not replay `session/created`). All backend operations for one session are serialized, and disposal awaits quiescence (every init + final drain) before returning, so no write lands after teardown. + +## Known Limitations and Deferred Work + +- **Only the current `SESSION_FORMAT_VERSION` (v0) loads** — the on-disk format is pre-release/unstable: a breaking format change is absorbed at v0 and non-current logs are rejected; there is no migration. +- **Nothing deletes session files** — logs accumulate under `root` until removed externally (the seam has no deletion surface). +- **Single-process assumption** — per-session serialization and the write cursor live in this process; two processes appending to the same `root` are not coordinated. diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index c14d3a70ea..9b445a335a 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -2,8 +2,6 @@ A SQLite durable session-persistence backend — a second `SessionPersistence` implementation ([session persistence](../../../docs/rfc/implemented/architecture/2026-06-14-session-persistence.md)), built to validate that the abstract seam and the shared `runPersistenceContract` suite are genuinely backend-agnostic. It satisfies the SAME contract as `dsh-session-persistence-jsonl` (append-only, contiguous-seq, lazy materialization, interrupted-turn close on load), expressed over `node:sqlite` rows instead of file bytes. -> **TODO:** this backend talks to `node:sqlite` directly. If a cordis database service (`cordis/db` / a `@cordisjs` SQL driver plugin) is adopted, route through that instead of holding a raw `DatabaseSync` here — the contract surface (`SessionPersistence`) would not change, only the storage driver. - ## Storage model Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, type, time, data, source_event_seqs, surface_op)` — `data` is the event payload as JSON text, so the row shape is the event verbatim (including `assistant/chunk`, keeping `seq` contiguous). The two `TEXT` columns `source_event_seqs` and `surface_op` are nullable; they store the event's optional surface-metadata fields (see [session surface](../../../docs/rfc/implemented/architecture/2026-06-18-session-surface.md)). Out-of-log metadata (`SessionHeader`) lives in a `sessions` row. A `sessions` row is written only by the first `append` — its existence is the lazy-materialization signal (`list` reports exactly the sessions that have a row), so no separate column is needed. @@ -28,3 +26,10 @@ interface Config { ## Write path Like the JSONL backend, the plugin also installs the `session/event` → buffer → `session/flush` drain: it snapshots each event when buffered (the live `session.events` object is mutable), persists a fork's seed once on `session/created`, keeps a per-session write cursor so a resumed session never re-appends stored events, and seeds existing live sessions on apply (HMR does not replay `session/created`). Dispose awaits every in-flight init + final drain and then closes the database, so no write lands after teardown. + +## Known Limitations and Deferred Work + +- **Raw `node:sqlite`, pending a cordis database service** — the backend holds a `DatabaseSync` directly; if a `cordis/db` / `@cordisjs` SQL driver is adopted, the storage driver routes through it (the `SessionPersistence` contract would not change) — a marked TODO. +- **`DatabaseSync` is synchronous** — every append transaction blocks the event loop for its duration; acceptable for local stores, a throughput ceiling for busy multi-session servers. +- **Only the current `SCHEMA_VERSION` opens** — a database written by any other build is rejected rather than migrated (unreleased software; no persisted user data to preserve). +- **Nothing deletes stored sessions** — rows accumulate until removed externally (the seam has no deletion surface; `ON DELETE CASCADE` is wired for such out-of-band cleanup). diff --git a/packages/session-persistence/session-persistence/README.md b/packages/session-persistence/session-persistence/README.md index 8bd3fed568..9a96515082 100644 --- a/packages/session-persistence/session-persistence/README.md +++ b/packages/session-persistence/session-persistence/README.md @@ -49,3 +49,9 @@ Three backends run these suites: an in-memory reference (in `tests/`), `dsh-sess ## Metadata types Re-exported from `dsh-session`: `SessionHeader` (immutable session metadata: `version`, `id`, `createdAt`, `cwd?`, `parentSession?`, `seedLength?`). + +## Known Limitations and Deferred Work + +- **No deletion or retention surface** — the seam is `create`/`append`/`load`/`list` only; pruning stored sessions is out-of-band backend maintenance. +- **`list()` is unpaginated and unfiltered** — it returns every stored session's header; fine for local stores, unindexed at scale. +- **Repair-time synthetic closers are the only crash story** — a backend must synthesize `tool/result`/`step/end`/`turn/end` closers on load; there is no partial-turn resume that continues an interrupted turn instead of closing it. diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index cb51986915..db2b59d412 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -69,3 +69,11 @@ The child env is built by [`buildChildEnv` from `@deepseek-ai/dsh-subagent-subpr ## Plugin export shape Named `name` / `inject` / `Config` / `apply`, with **no default export**: the cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray default would collapse the module to the bare function and drop the `inject` namespace (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). + +## Known Limitations and Deferred Work + +- **A fresh process per run** — persistent-process pooling is a future optimization ([the seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). +- **No start-time capability enforcement** — an out-of-process child cannot honor the parent's `outputSchema`/`depthLimit`/`toolFilter`, so the provider advertises none and `request.parent` is ignored. +- **Only `agent_message_chunk` text is collected** — the child's tool-call activity, thought chunks, and plan updates are not surfaced to the parent. +- **Permission prompts are auto-answered** (`permission: allow | reject`) — no human is surfaced a child's `session/request_permission` in this cut. +- **No snapshot-tier replay coverage** (`TODO(acp-subagent-replay)`) — an ACP child is its own process with its own replay shape, deferred. diff --git a/packages/subagent/subagent-fork/README.md b/packages/subagent/subagent-fork/README.md index 1434601c8e..a6e2785795 100644 --- a/packages/subagent/subagent-fork/README.md +++ b/packages/subagent/subagent-fork/README.md @@ -21,3 +21,9 @@ The seam this rides on: `CreateAgentOptions.seed` (added on `dsh-agent`, threade | `providerName` | Registry name on `ctx.subagents` (default `fork`). | See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, model inheritance, and depth tracking — all shared. + +## Known Limitations and Deferred Work + +- **Tool-scoping (`toolFilter`) is not supported** — declared `false`, so the service rejects a request needing it before `start` runs. +- **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. +- **The seed is a one-time snapshot** — the child sees the parent's completed turns as of the fork and nothing the parent logs afterwards; there is no live context sharing. diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md index caa7b9b161..6084fe569c 100644 --- a/packages/subagent/subagent-inprocess/README.md +++ b/packages/subagent/subagent-inprocess/README.md @@ -37,3 +37,9 @@ Delegation depth rides on a merge-extensible `AgentOptions.subagentDepth` field ### `SubagentDepthError` Thrown by `startInProcessRun` when a spawn would exceed the request's `maxDepth` cap; carries `attemptedDepth` and `maxDepth`. + +## Known Limitations and Deferred Work + +- **The structured-output runtime is context-global** — the tool registry and prompt assembly are context-wide while schemas differ per concurrent child, hence the final-assembly enforcement dance; per-agent/per-session scoping would dissolve it (the module-doc `FIXME`). +- **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. +- **`toolFilter` is unimplemented in this driver** — both in-process backends declare it `false`; scoping a child's tool set is deferred. diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index e411d44ab0..0a3a17f087 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -17,3 +17,8 @@ The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../ | Key | Meaning | |---|---| | `providerName` | Registry name on `ctx.subagents` (default `spawn`). | + +## Known Limitations and Deferred Work + +- **Tool-scoping (`toolFilter`) is not supported** — the capability is declared `false`, so the service rejects a request needing it before `start` runs; scoping a child's tool set is deferred. +- **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index e214ba58c6..2d11d874f7 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -36,8 +36,9 @@ Beside `capabilities` sits one DESCRIPTIVE fact, not validated by the service: ` The service also announces provider lifecycle: `subagent/provider-added` (the frozen registry snapshot) fires after a registration and `subagent/provider-removed` (the accepted name) after an unregistration, so a consumer deriving state from a named provider (the model-facing tool wording) mirrors registry membership instead of assuming load order — the cordis Loader starts sibling plugins concurrently, so "listed earlier" does not mean "registered earlier". Run lifecycle is gated by provider readiness: `subagent/start` (payload `SubagentRunInfo`) fires only after `run.started` fulfills, and `subagent/end` (payload `SubagentRunEndInfo`) fires only for that announced run; readiness rejection emits neither. For spawn/fork, the start listener can resolve the published child via `ctx.agents.get(info.id)`; a remote provider need not have a local registry entry. Both events are **observe-only** plain emits. The service observes `result` immediately even while readiness is pending, clones its output before the caller can mutate it, and buffers that end payload until start has fired; a rejecting result cannot become an unhandled detached promise, start always precedes end, and a listener cannot corrupt the caller's result. `subagent/end` carries the cloned output as `lastAssistantMessage` on the settle path and omits it on infrastructure rejection. Any run-affecting decision is out of scope for this observe-only surface. -## Scope (first cut) +## Known Limitations and Deferred Work -The consumer collects **synchronously**: it starts a run and awaits `result`. Steering (`sendMessage`) is part of the contract but intentionally unused. Background / poll / spill semantics are deferred to a future redesign unifying long-running-tool handling across subagents and bash. See the RFC: [docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md). +- **The consumer collects synchronously** — it starts a run and awaits `result`; steering (`sendMessage`) is part of the contract but intentionally unused, and background / poll / spill semantics are deferred to a future redesign unifying long-running-tool handling across subagents and bash ([the seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). +- **The lifecycle events are observe-only** — a run-affecting `subagent/end` (an awaited continuation/decision surface) is deferred until a consumer needs one (`FIXME(subagent-continuation)`). See `src/types.ts` for the full contracts. diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index 715fb0b9db..18ebea6ada 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -24,3 +24,9 @@ The tool description and the `prompt` parameter description are DERIVED from the `execute` starts a run on the configured provider and **awaits `run.result` inside a `try/finally` that always `dispose()`s the run** — the owned child agent/session is torn down on every path (success, error, abort), never leaked. The tool's abort signal (`exec.signal`) is bridged to `run.cancel()`. A non-`completed` stop reason (aborted/error/max-tokens/refusal) maps to an `isError` tool result rather than returning partial output as success. Background / poll collection is deferred (see the [RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)); this cut blocks the parent turn until the child finishes. + +## Known Limitations and Deferred Work + +- **Delegation blocks the parent turn** — synchronous collect only; background start + poll collection is deferred to the long-running-runtime redesign. +- **Duplicate `toolName` across waiting loads is detected late** (`TODO(subagent-dup-toolname)`) — two loads waiting on providers collide only when a provider arrives, and the throw rolls back the provider's fiber rather than the misconfigured tool's; config-time detection needs a cross-fiber registry of intended names. +- **No per-child persona or prompt shaping** — `agentOptions` carries only `model`; the deployment persona is context-wide, and per-child system-prompt variation has no seam yet. diff --git a/packages/support/acp-snapshot/README.md b/packages/support/acp-snapshot/README.md index 002dbf8a19..89f4ed0f8a 100644 --- a/packages/support/acp-snapshot/README.md +++ b/packages/support/acp-snapshot/README.md @@ -40,3 +40,8 @@ A scenario booting a differently-composed tree sets its own `configPath` (an ove The example also ships a `cordis.snapshot.yml` replay overlay next to its `cordis.yml` (the bin swaps them under `DSH_SNAPSHOT=replay` — [single-source replay config RFC](../../../docs/rfc/implemented/testing/2026-07-04-single-source-acp-replay-config.md)); replay fixtures are served by [`dsh-llm-replay`](../llm-replay/README.md), which this package points at via the `DSH_SNAPSHOT_*` env vars it sets on the child. `pnpm run test:snapshot:record` calls the live LLM and rewrites the recorded scenarios' model fixtures; `pnpm run test:snapshot:refresh` stays keyless, runs the replay overlay, and rewrites stdout plus comparable session-log goldens from the committed model scripts. Fixture roles, record/replay/refresh semantics, and scenario-table fields are documented on `Scenario` and in the [snapshot RFC](../../../docs/rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md). Constraints: `suite.ts` imports vitest, so the package is importable only inside a vitest run (the harness and normalizers have no such dependency but ship from the same entry). ACP-specific by design — the harness speaks the SDK's `ClientSideConnection`. Permission round-trips are scriptable: `InputScript.permissionAnswers` is a FIFO queue of option-kind selections (`allow_once`, `reject_once`, …) the client maps to the agent-issued `optionId` at answer time; an absent or exhausted queue answers `cancelled`, and a kind the request never offered rejects the run (the agent is answered `cancelled`, so a tolerant agent cannot absorb the scenario bug). Session config options are scriptable too: the `setConfigOption` step switches a knob over `session/set_config_option`, and `setConfigOptionExpectError` asserts the bridge rejects an unknown id or out-of-vocabulary value (the error frame stays in the transcript). + +## Known Limitations and Deferred Work + +- **Session harvest is JSONL-only** — `runScenario` collects persisted `.jsonl` logs, so an example composed over the SQLite persistence backend has no snapshot path. +- **The subprocess boots the unbuilt tsx/Loader path only** — the built-bin artifact is guarded by the separate `built-bin` e2e smokes, never by this tier. diff --git a/packages/support/invariants/README.md b/packages/support/invariants/README.md index 94c2682f9d..acc0fa2a79 100644 --- a/packages/support/invariants/README.md +++ b/packages/support/invariants/README.md @@ -53,3 +53,8 @@ A `DeepReadonly` is high type-noise across every log consumer, and ## Seeded sessions A seeded/forked session arrives with events already in its log (the `Session` constructor copies the seed without emitting `session/event`). On `session/created` the plugin replays the existing log through the checker and freezes those entries, so seeded history is held to the same contract. + +## Known Limitations and Deferred Work + +- **The request-reconstructability assertion covers loop-built requests only** — hand-built one-shots (e.g. compaction's summarize call) carry no live `sessionId` marker and are skipped. +- **Merge-extended event families get no family-specific assertions** — `compact/*` lock pairing and `hook/*` invoked/result pairing are not checked here; only the core turn/step/chunk/tool-result contract is. diff --git a/packages/support/llm-replay/README.md b/packages/support/llm-replay/README.md index 78b411d54c..91a5a89dd8 100644 --- a/packages/support/llm-replay/README.md +++ b/packages/support/llm-replay/README.md @@ -43,3 +43,8 @@ Replay keys every call by its calling session id (`GenerateOptions.sessionId`, s ## Plugin export shape Named `name` / `inject` / `Config` / `apply`, with **no default export**: the cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray default would collapse the module to the bare function and drop the `inject` namespace (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). + +## Known Limitations and Deferred Work + +- **First-call-order script binding assumes sequential delegation** — a cut that runs sibling subagents concurrently (or a compaction summarize call landing mid-run) would bind live sessions to recorded scripts non-deterministically; a stronger keying is deferred until such a scenario exists (`XXX(concurrent-subagents)`). +- **Only chunk-producing calls are derivable** — a pure pre-chunk throw or a cancel/hang scenario needs the `replay.override.json` sidecar; the override replaces the PRIMARY session's script only. diff --git a/packages/timeout/timeout-policy/README.md b/packages/timeout/timeout-policy/README.md index e637a658bf..8400af5776 100644 --- a/packages/timeout/timeout-policy/README.md +++ b/packages/timeout/timeout-policy/README.md @@ -32,3 +32,8 @@ The derived signal only **notifies**; termination stays with the tool and the ca ### Composing with other `tools/execute` wrappers Multiple `tools/execute` listeners compose by cordis registration order. Combined with a future retry/sandbox/metrics wrapper, registration order chooses the semantics — "timeout covers the whole retry operation" (timeout registered outer) versus "timeout covers each attempt" (timeout registered inner). + +## Known Limitations and Deferred Work + +- **Cooperative, never a hard kill** — the deadline only notifies via `exec.signal`; a tool that ignores the signal does not stop on timeout (see § Cooperative, not a hard kill). +- **No blanket budget** — only tools that declare `timeoutMs` on their `ToolDefinition` get a deadline; there is no registry-wide default for undeclared tools (the shipped `bash`/`read`/`write`/`edit` deliberately declare none). diff --git a/packages/todo/tool-todo/README.md b/packages/todo/tool-todo/README.md index b27cc65227..2766fd0eb1 100644 --- a/packages/todo/tool-todo/README.md +++ b/packages/todo/tool-todo/README.md @@ -23,3 +23,9 @@ The tool writes only the session event; it does not render. UIs subscribe to `se ## Export shape A function/namespace plugin: it exports `name` / `inject` / `apply` and NO default. A stray `export default` would collapse the module via the Loader's `unwrapExports` and drop `inject` (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). + +## Known Limitations and Deferred Work + +- **Single-owner scope only** — the list belongs to the one calling agent session; subagent/shared/swarm scopes are a deliberate cut (see § Single owner), and a non-agent caller is rejected. +- **The item shape is deliberately minimal** — `content` plus three-state `status`; no id, priority, or active-form fields, and the ACP bridge synthesizes the `priority` ACP requires. +- **Whole-list replacement is the only operation** — no partial updates, no read-back tool; the model must resend the entire list each call. diff --git a/packages/ui/acp-agent/README.md b/packages/ui/acp-agent/README.md index d81c9cc091..ed83bfaf4d 100644 --- a/packages/ui/acp-agent/README.md +++ b/packages/ui/acp-agent/README.md @@ -42,3 +42,7 @@ The leaf supplies the swappable backends: an LLM adapter (`llm-deepseek` for the Run it under `node --expose-internals`: the cordis Loader resolves the config's bare plugin specifiers through its internal module loader, active only under that flag. (`demo:acp` runs under tsx, whose tsconfig `paths` map resolves them instead.) All diagnostics go to **stderr** — stdout is the protocol. + +## Known Limitations and Deferred Work + +- **The front-door cluster is fixed in code** — only `model`/`persona`/`toolOrder`/`persistenceRoot` are config; swapping the baked JSONL persistence for another backend, or adding the deliberately-omitted `tool-ask-user`, means a leaf-level sibling entry or a differently-composed app package. diff --git a/packages/ui/acp/README.md b/packages/ui/acp/README.md index 2f1f35adc4..465f0a42e6 100644 --- a/packages/ui/acp/README.md +++ b/packages/ui/acp/README.md @@ -81,9 +81,12 @@ The bridge registers an `approval/request` waterfall listener — the ACP answer Teardown reaches quiescence: for EVERY live session settle any pending prompt as `cancelled`, then run that session's [`AgentHandle`](../../core/agent/README.md) `dispose()` — which stops the loop (sets `disposed` + aborts the in-flight step), `await`s the loop's exit (the final `turn/end` + `session/flush` are captured while the session is still attached), unregisters the agent, and removes its session from the store. A turn cut off mid-flight by teardown ends with reason `disposed` (not `aborted` — `dispose()` uses the disposed path, not `session/cancel`'s queue-aware `cancel()`). The per-session disposes run in parallel. The same teardown runs on a **client disconnect** (`conn.closed` resolves when the editor quits / the transport EOFs), so a vanished client never leaves an orphaned running — or idled-but-still-registered — agent whose `session/update` writes are silently swallowed. The two paths are idempotent and memoized (the first clears the `sessions` map; a second caller awaits the same teardown promise). -## Known limitations (tracked TODOs) +## Known Limitations and Deferred Work - **`additionalDirectories`** — rejected. A session operates in its single `cwd` (see Per-session cwd); widening the tool/filesystem scope to extra roots is a separate sandbox concern, not yet implemented. +- **Prompt content is `text` + `resource_link` only** — image, audio, and embedded-resource blocks are rejected, as is a non-empty `mcpServers` list at `session/new`. +- **One configured `model` for every created session** — per-session model selection has no config or protocol surface here yet. +- **Terminal cards render completed output** — live incremental streaming and command classification are named follow-ups of [the terminal-rendering RFC](../../../docs/rfc/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md). ## stdout is the protocol diff --git a/packages/ui/stdio-agent/README.md b/packages/ui/stdio-agent/README.md index 27f0d20be7..496aea08fa 100644 --- a/packages/ui/stdio-agent/README.md +++ b/packages/ui/stdio-agent/README.md @@ -63,3 +63,8 @@ Fresh stdio sessions use the process launch directory as `session.header.cwd`, s ``` Swap `llm-deepseek` for a `mock-llm` leaf plugin and you have the echo demo — "swap the backend, keep the app". + +## Known Limitations and Deferred Work + +- **One pre-created `main` agent drives the readline UI** — there is no multi-session or concurrent-agent surface in this app; a run is one conversation. +- **The front-door cluster is fixed in code** — the JSONL persistence backend and the ask-user tooling are baked; a different composition is a leaf-level sibling entry or another app package. diff --git a/packages/ui/tool-ask-user/README.md b/packages/ui/tool-ask-user/README.md index 10d4a082ba..b42192f91a 100644 --- a/packages/ui/tool-ask-user/README.md +++ b/packages/ui/tool-ask-user/README.md @@ -18,3 +18,8 @@ The tool calls `ctx.userInteraction.ask()` and returns JSON text shaped as `{ "a ## Role This is the consumer package for the user-interaction seam. It does not render UI and does not know how input is collected; it only translates model arguments into `AskUserQuestionRequest` and returns the human answer to the agent loop. + +## Known Limitations and Deferred Work + +- **A pending question blocks the tool call until the human answers** — the tool declares no `timeout-policy` budget; cancellation rides the turn's `exec.signal` only. +- **Answers return as JSON text** — the seam's structured `AskUserQuestionAnswer` is serialized into the tool result rather than carried as typed content blocks. diff --git a/packages/ui/user-interaction/README.md b/packages/ui/user-interaction/README.md index 6377c2b7ef..b3113b032c 100644 --- a/packages/ui/user-interaction/README.md +++ b/packages/ui/user-interaction/README.md @@ -22,3 +22,8 @@ When an answer includes `custom`, `selected` is empty; custom text is an overrid ## Role This is the interface package. Model-facing consumers such as `@deepseek-ai/dsh-tool-ask-user` depend on this seam; UI front doors such as the `stdio-agent` readline module and the `acp` bridge provide the provider. The loop stays unchanged: a tool call simply awaits a promise, and the tool result resumes the normal agent loop. + +## Known Limitations and Deferred Work + +- **One provider per context** — there is no routing or fan-out to multiple UIs; a second registration throws `DUPLICATE_PROVIDER`, and with none registered `ask()` throws `NO_PROVIDER` rather than degrading. +- **The vocabulary is the question-form shape only** — selectable options plus optional custom text; richer interaction shapes (file pickers, diff-preview confirmations) have no seam vocabulary yet. diff --git a/packages/web/tool-web/README.md b/packages/web/tool-web/README.md index ab1326a21e..9b0ea33d86 100644 --- a/packages/web/tool-web/README.md +++ b/packages/web/tool-web/README.md @@ -33,3 +33,9 @@ Each tool is registered independently; a product that wants only one disables th Tool registration follows product **enablement**, not backend availability. A tool stays visible even when its selected provider is missing, misconfigured, ambiguous, or temporarily unavailable; the seam resolves the provider at execution time and execution fails with a structured `WebError` (e.g. `WEB_PROVIDER_UNAVAILABLE`, `WEB_PROVIDER_AMBIGUOUS`), which `ToolRegistry.execute()` turns into an error tool result the model can read and hooks/UI can route on. This keeps the model schema stable without making plugin load order, credential state, or HMR timing part of the model-facing contract. To remove a web tool entirely, disable it here in config. The tool never calls a provider's `status()` and never enumerates providers — its only execution path is `ctx.web.search()` / `ctx.web.fetch()`, and provider unavailability reaches it as the structured `WebError` codes selection throws at execution time. Provider selection stays entirely inside the seam, with one owner. + +## Known Limitations and Deferred Work + +- **`htmlToMarkdown` is a minimal regex converter, not an HTML parser** — it strips script/style/noscript, keeps headings/bullets/links, and decodes about a dozen named entities; tables, images, and nested formatting are lost. +- **The model-facing surface is minimal by design, with promotions deferred** — `max_results` stays a config bound (not a model argument), and `web_fetch` takes only `url` (no `format`/`prompt`/LLM-summarization mode); both are named later steps in [the seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md). +- **No permission gating** — both tools execute without any permission prompt or policy; integration awaits the deferred permission system, with the owner (a `tools/execute` plugin, provider config, or both) undecided. diff --git a/packages/web/web-fetch-local/README.md b/packages/web/web-fetch-local/README.md index 9c2ef0030f..bada10d7c3 100644 --- a/packages/web/web-fetch-local/README.md +++ b/packages/web/web-fetch-local/README.md @@ -33,6 +33,8 @@ The provider's `timeoutMs`/`maxTimeoutMs` is a **resource backstop** for direct The numeric limits are validated at plugin construction: every cap except `maxRedirects` must be a positive finite number, and `maxRedirects` must be a non-negative integer. An invalid value throws rather than silently constructing a provider with nonsensical limits. -## Security note +## Known Limitations and Deferred Work -SSRF / private-network protection (blocking private, loopback, link-local, multicast, and otherwise non-public destinations, with DNS-resolve-then-validate and per-hop re-validation) is **deferred** — see the [web capability seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md). Until it lands, this provider is an SSRF primitive and **must not be enabled** in a deployment that can reach sensitive internal network targets. +- **SSRF / private-network protection is deferred** — no blocking of private, loopback, link-local, multicast, or otherwise non-public destinations, no DNS-resolve-then-validate, no per-hop re-validation (see [the web capability seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). Until it lands, this provider is an SSRF primitive and **must not be enabled** in a deployment that can reach sensitive internal network targets. +- **Only textual content decodes** — html/xhtml and `text/*`-plus-JSON/XML families; a missing `Content-Type` or any binary type throws `WEB_UNSUPPORTED_CONTENT_TYPE`, and text-extractable PDF decoding is named deferred work. +- **Charset comes only from the `Content-Type` header** (UTF-8 default) — an HTML `` declaration is ignored, and a declared-but-unrecognized charset label throws rather than falling back. diff --git a/packages/web/web-search-deepseek/README.md b/packages/web/web-search-deepseek/README.md index 41b000d26a..95e5934806 100644 --- a/packages/web/web-search-deepseek/README.md +++ b/packages/web/web-search-deepseek/README.md @@ -34,3 +34,10 @@ It reuses `$DEEPSEEK_API_KEY` (no new secret) but **not** `$DEEPSEEK_BASE_URL`: ## Mapping DeepSeek returns no provider-generated answer surface this provider trusts as `content`, so `content` is omitted. `sources[]` is built from the `web_search_result` items inside `web_search_tool_result` blocks: `url` ← `url`, `title` ← `title`, `publishedAt` ← `page_age`. The per-source `snippet` lives separately in a `text` block's `citations[]` (a `cited_text` keyed by `url`), so the provider joins the two — a result with no citation excerpt simply has no `snippet`. Results are deduped by `url` (a `maxUses > 1` request can surface the same URL across searches). DeepSeek's `web_search` has no result-count knob (only `maxUses`), so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`). Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. + +## Known Limitations and Deferred Work + +- **One search costs a full Messages model turn** — latency plus generated tokens, with up to `maxUses` server-side searches; DeepSeek exposes no dedicated retrieval endpoint. +- **Over-returned sources still cost tokens** — with no result-count knob on the wire, `maxResults` is enforced only post-hoc by seam truncation. +- **Uncited results carry no `snippet`** — a source gains one only when a `text` block citation (`cited_text`) matches its URL. +- **Abort classification is error-shape-based** — only a `DOMException` named `AbortError` maps to `WEB_ABORTED`; an abort carrying a custom reason (e.g. `dsh-timeout`'s `TimeoutReason`) surfaces as `WEB_PROVIDER_ERROR`. diff --git a/packages/web/web-search-exa/README.md b/packages/web/web-search-exa/README.md index 0bc58d6559..660de43c60 100644 --- a/packages/web/web-search-exa/README.md +++ b/packages/web/web-search-exa/README.md @@ -24,3 +24,9 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i ## Mapping Exa returns a flat `results[]` and no generated answer, so `content` is omitted. Each result maps to a `WebSearchSource`: `url` ← `url`, `title` ← `title`, `snippet` ← the first non-empty `highlights[]` entry (a result with no highlight has no portable snippet and is dropped), `publishedAt` ← `publishedDate`. A request's `maxResults` wins over the configured `numResults` default and is sent as Exa's `numResults` for a cost/latency optimization; the final bound is enforced by the seam. Provider failures (HTTP errors, network failure, unparseable or wrong-shape bodies) surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. + +## Known Limitations and Deferred Work + +- **A result with no non-blank highlight is dropped entirely** — no portable snippet to map, so fewer sources than the requested count can return. +- **Only `searchType`/`numResults`/`highlightsPerResult` are exposed** — Exa's other controls (livecrawl, category, domain/date filters, full-text contents) wait on provider-neutral seam fields ([seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). +- **Abort classification is error-shape-based** — only a `DOMException` named `AbortError` maps to `WEB_ABORTED`; an abort carrying a custom reason (e.g. `dsh-timeout`'s `TimeoutReason`) surfaces as `WEB_PROVIDER_ERROR`. diff --git a/packages/web/web-search-perplexity/README.md b/packages/web/web-search-perplexity/README.md index f944413c96..ad64518f2d 100644 --- a/packages/web/web-search-perplexity/README.md +++ b/packages/web/web-search-perplexity/README.md @@ -24,3 +24,10 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i ## Mapping `content` ← `choices[0].message.content` (the generated answer). `sources[]` prefers the structured `search_results[]` (`url`, `title`, `snippet`, `publishedAt` ← `date`), falling back to the URL-only `citations[]` array only when `search_results` is absent — those sources carry just a `url`, which is why `title`/`snippet`/`publishedAt` are optional on the seam. Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. Perplexity has no result-count control, so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`). + +## Known Limitations and Deferred Work + +- **Citation-fallback sources are URL-only** — when Perplexity omits structured `search_results[]`, sources carry no `title`/`snippet`/`publishedAt`, so the tool renders bare hostname labels. +- **Over-returned sources still cost tokens and latency** — with no result-count control on the wire, `maxResults` is enforced only post-hoc by seam truncation. +- **Only `model`/`maxTokens`/`searchRecency` are exposed** — Perplexity's other search controls (domain filters, `web_search_options` context size, images) wait on provider-neutral seam fields ([seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). +- **Abort classification is error-shape-based** — only a `DOMException` named `AbortError` maps to `WEB_ABORTED`; an abort carrying a custom reason (e.g. `dsh-timeout`'s `TimeoutReason`) surfaces as `WEB_PROVIDER_ERROR`. diff --git a/packages/web/web/README.md b/packages/web/web/README.md index fe5f1e650e..2f5d6508f1 100644 --- a/packages/web/web/README.md +++ b/packages/web/web/README.md @@ -42,3 +42,10 @@ The failure branches throw `WebError`, whose structured code (plus message detai ## Vocabulary `WebSearchRequest` (`query`, `maxResults?`) → `WebSearchResult` (`providerId`, `query`, `content?`, `sources[]`, `truncated`); each `WebSearchSource` has a required `url` and optional `title`/`snippet`/`publishedAt` (Perplexity citations may be URL-only). `WebFetchRequest` (`url`, `timeoutMs?`) → `WebFetchResult` (`providerId`, final `url`, `statusCode`, `body`, `truncated`); `WebFetchBody` is a CLOSED discriminated union (`html` | `text`) owned here — consumers `switch` to exhaustiveness so a new kind breaks their compilation until handled. See `src/types.ts` for the full contracts and the `WebError` code taxonomy. + +## Known Limitations and Deferred Work + +- **No observation surface** — no provider-change event and no capability-status query; availability is observed only by executing `search()`/`fetch()` and routing the thrown `WebError` codes, and the no-provider failure is the generic `WEB_PROVIDER_UNAVAILABLE` with no per-provider reason enumeration ([RFC](../../../docs/rfc/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md)). +- **`WebSearchRequest` carries only `query` + `maxResults`** — provider-neutral controls (recency, domain filters, regional hints, search depth) are deferred until Exa and Perplexity can both honor them honestly ([seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). +- **`WebFetchBody` has no `pdf` arm** — text-extractable PDF support is named deferred work; the closed union makes adding it a compile-enforced change across the three web packages. +- **Provider-backed page extraction is out of scope of `fetch()`** — a Firecrawl/Tavily-style `web_extract` capability is deferred rather than widening the fetch seam. diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 2f98c74eea..da238cf7b1 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -273,6 +273,7 @@ function docSyncLeafGates(): Gate[] { pnpmScript('type-equivalence', 'verify-type-equiv', { label: 'type equivalence' }), pnpmScript('translation-pairing', 'verify-translation-pairing', { label: 'translation pairing' }), pnpmScript('doc-budgets', 'verify-doc-budgets', { label: 'doc budgets' }), + pnpmScript('readme-limitations', 'verify-readme-limitations', { label: 'readme limitations' }), ] } diff --git a/scripts/verify-readme-limitations.ts b/scripts/verify-readme-limitations.ts new file mode 100644 index 0000000000..5a215a53fd --- /dev/null +++ b/scripts/verify-readme-limitations.ts @@ -0,0 +1,134 @@ +/** + * Doc-sync gate: every package README carries the standard + * `## Known Limitations and Deferred Work` section — the per-package home for + * consumer-visible gaps and consciously postponed work that the + * [documentation standard](../docs/AGENTS.md) assigns to the package-README + * tier. One canonical heading instead of per-package variants ("Limitations", + * "What is NOT here", …) keeps the section greppable across the repo and makes + * its absence a gate failure rather than an oversight. + * + * A package with genuinely nothing to declare is listed in NO_LIMITATIONS + * below and must NOT carry the section — an empty section invites boilerplate, + * and a whitelisted package that gains real limitations leaves the whitelist + * in the same change. Whitelist entries are validated against the scanned + * package set, so a rename or removal fails loud instead of silently + * un-gating a README. + * + * Checks, per packages///README.md (fenced code excluded): + * 1. Non-whitelisted: exactly one limitations-like heading, byte-equal to the + * canonical h2, with at least one top-level `- ` bullet before the next + * heading. + * 2. Whitelisted: no limitations-like heading at all. + * 3. Every whitelist entry names a scanned package. + * + * "Limitations-like" also matches near-miss headings at any level ("known + * limitations", "deferred work", "what is not here", a heading starting with + * "limitations"/"deferred") so a drifted heading cannot impersonate the + * canonical section and a second competing section cannot coexist with it. + * + * Checker, not fixer: it reports and never rewrites. + * Run: `tsx scripts/verify-readme-limitations.ts`. + */ + +import { globSync, readFileSync } from 'node:fs' +import { resolve } from 'node:path' + +const root = resolve(import.meta.dirname, '..') + +/** The one canonical section heading, required verbatim as an h2. */ +const CANONICAL = '## Known Limitations and Deferred Work' + +/** + * Packages with genuinely no known limitations or deferred work (keyed by + * package directory relative to the repo root). Their READMEs must NOT carry + * the section; adding one moves the package off this list in the same change. + */ +const NO_LIMITATIONS: readonly string[] = [ + 'packages/support/subagent-mock', + 'packages/ui/app-boot', + 'packages/util/brand', + 'packages/util/timeout', +] + +/** A heading that reads as a limitations section — canonical or drifted. */ +function isLimitationsLike(headingText: string): boolean { + return ( + /known limitation/i.test(headingText) + || /deferred work/i.test(headingText) + || /what is not here/i.test(headingText) + || /^limitations?\b/i.test(headingText) + || /^deferred\b/i.test(headingText) + ) +} + +interface Line { + index: number + raw: string +} + +/** Split a README into prose lines (fenced code dropped), keeping 1-based line numbers. */ +function proseLines(text: string): Line[] { + let inFence = false + const kept: Line[] = [] + text.split('\n').forEach((raw, i) => { + if (raw.startsWith('```')) { + inFence = !inFence + return + } + if (!inFence) kept.push({ index: i + 1, raw }) + }) + return kept +} + +const readmes = globSync('packages/*/*/README.md', { cwd: root }).sort() +const scannedPackages = new Set(readmes.map(path => path.slice(0, -'/README.md'.length))) +const failures: string[] = [] + +for (const entry of NO_LIMITATIONS) { + if (!scannedPackages.has(entry)) { + failures.push(`whitelist entry ${entry} does not name a scanned package — renamed or removed? update NO_LIMITATIONS in scripts/verify-readme-limitations.ts in the same change`) + } +} + +for (const readme of readmes) { + const pkg = readme.slice(0, -'/README.md'.length) + const lines = proseLines(readFileSync(resolve(root, readme), 'utf8')) + const headings = lines.filter(line => /^#{1,6} /.test(line.raw)) + const limitations = headings.filter(line => isLimitationsLike(line.raw.replace(/^#{1,6}\s+/, ''))) + + if (NO_LIMITATIONS.includes(pkg)) { + for (const heading of limitations) { + failures.push(`${readme}:${heading.index}: whitelisted as having no known limitations, but carries ${JSON.stringify(heading.raw)} — drop the section or remove the package from NO_LIMITATIONS`) + } + continue + } + + const heading = limitations.at(0) + if (heading === undefined) { + failures.push(`${readme}: missing the \`${CANONICAL}\` section (a package with genuinely nothing to declare joins NO_LIMITATIONS in scripts/verify-readme-limitations.ts instead)`) + continue + } + if (limitations.length > 1) { + failures.push(`${readme}: ${limitations.length} limitations-like headings (lines ${limitations.map(line => line.index).join(', ')}) — keep exactly one \`${CANONICAL}\` section`) + continue + } + if (heading.raw.trimEnd() !== CANONICAL) { + failures.push(`${readme}:${heading.index}: non-canonical heading ${JSON.stringify(heading.raw)} — use \`${CANONICAL}\``) + continue + } + const headingAt = lines.indexOf(heading) + const body = lines.slice(headingAt + 1) + const end = body.findIndex(line => /^#{1,6} /.test(line.raw)) + const section = end === -1 ? body : body.slice(0, end) + if (!section.some(line => /^- /.test(line.raw))) { + failures.push(`${readme}:${heading.index}: the \`${CANONICAL}\` section has no top-level \`- \` bullet — state the limitations, or whitelist the package if there are genuinely none`) + } +} + +if (failures.length > 0) { + console.error('verify-readme-limitations: violations found:') + for (const failure of failures) console.error(` ${failure}`) + process.exit(1) +} + +console.log(`verify-readme-limitations: ${readmes.length} package READMEs checked (${NO_LIMITATIONS.length} whitelisted), all conform.`) From 41a7c70cb1f1a69b3a7e5b805abd08d19700f569 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:08:35 +0800 Subject: [PATCH 10/38] docs: audit package limitations on current stack --- ...026-07-10-readme-known-limitations-gate.md | 2 +- packages/bash/bash-local/README.md | 2 + packages/bash/bash-sandbox/README.md | 7 +++ packages/bash/tool-bash/README.md | 4 +- packages/core/agent-core/README.md | 2 +- packages/core/agent-loop/README.md | 4 +- packages/core/agent/README.md | 3 +- packages/core/scope/README.md | 7 +++ packages/core/system-prompt/README.md | 5 +- packages/core/tools/README.md | 2 +- packages/fs/fs/README.md | 1 - packages/fs/tool-fs/README.md | 2 +- packages/sandbox/sandbox-local/README.md | 8 +++ packages/sandbox/sandbox/README.md | 7 +++ .../session-persistence/README.md | 2 +- packages/skill/skill-local/README.md | 9 ++- packages/skill/skill/README.md | 7 +++ packages/skill/tool-skill/README.md | 7 +++ packages/subagent/subagent-acp/README.md | 2 +- packages/subagent/subagent-fork/README.md | 1 - .../subagent/subagent-inprocess/README.md | 3 +- packages/subagent/subagent-spawn/README.md | 2 +- .../subagent/subagent-subprocess/README.md | 7 +++ packages/subagent/tool-subagent/README.md | 2 +- packages/support/subagent-mock/README.md | 8 ++- packages/ui/acp-agent/README.md | 7 ++- packages/ui/acp/README.md | 5 +- packages/ui/app-boot/README.md | 6 ++ packages/ui/stdio-agent/README.md | 5 +- packages/ui/user-approval/README.md | 7 +++ packages/util/timeout/README.md | 6 ++ packages/web/tool-web/README.md | 2 +- packages/workflow/tool-workflow/README.md | 10 ++- .../workflow/workflow-workerthread/README.md | 10 ++- packages/workflow/workflow/README.md | 10 ++- scripts/verify-readme-limitations.ts | 62 ++++++++++++------- 36 files changed, 180 insertions(+), 56 deletions(-) diff --git a/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md b/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md index cc25c0efe7..d4814ce283 100644 --- a/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md +++ b/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md @@ -8,7 +8,7 @@ The [documentation standard](../../../AGENTS.md) assigns limitations to the pack ## Decision -Every `packages///README.md` carries a canonical `## Known Limitations and Deferred Work` section: a condensed bullet list of consumer-visible gaps (unimplemented features, platform caveats, deliberate MVP cuts) and consciously postponed work (TODO markers, RFC deferrals still open). A `doc-sync` gate, `verify-readme-limitations` ([scripts/verify-readme-limitations.ts](../../../../scripts/verify-readme-limitations.ts)), enforces the shape per README: exactly one limitations-like heading, byte-equal to the canonical h2, with at least one top-level bullet. Near-miss headings at any level ("Limitations", "Deferred", "What is NOT here", …) fail the gate, so variant sections cannot creep back beside — or instead of — the canonical one. +Every package manifest under `packages///package.json` has a sibling README carrying a canonical `## Known Limitations and Deferred Work` section: a condensed bullet list of consumer-visible gaps (unimplemented features, platform caveats, deliberate MVP cuts) and consciously postponed work (TODO markers, RFC deferrals still open). A `doc-sync` gate, `verify-readme-limitations` ([scripts/verify-readme-limitations.ts](../../../../scripts/verify-readme-limitations.ts)), derives the package set from those manifests, rejects a missing README, and enforces the shape per README: exactly one limitations-like heading, byte-equal to the canonical h2, with at least one top-level bullet. Near-miss headings at any level ("Limitations", "Deferred", "What is NOT here", "Non-goals", …) fail the gate, so variant sections cannot creep back beside — or instead of — the canonical one. A package with genuinely nothing to declare is whitelisted (`NO_LIMITATIONS` in the script) and must NOT carry the section. The inverted check keeps the whitelist honest in both directions: an empty or boilerplate section cannot satisfy the gate, and giving a whitelisted package real limitations forces the whitelist edit in the same change. Whitelist entries are validated against the scanned package set, so a package rename or removal fails loud instead of silently un-gating a README. diff --git a/packages/bash/bash-local/README.md b/packages/bash/bash-local/README.md index 0a2c2ec5ec..ea4cc7dc74 100644 --- a/packages/bash/bash-local/README.md +++ b/packages/bash/bash-local/README.md @@ -28,9 +28,11 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi; ## Known Limitations and Deferred Work - **Unconfined by itself** — this executor always runs commands with the harness process's authority; deployments needing confinement compose [`dsh-bash-sandbox`](../bash-sandbox/README.md), while per-call allow/deny/ask policy belongs on `tools/pre-execute`. +- **No persistent shell or PTY** — every call starts a fresh non-login `bash -c`; cwd-only persistence and interactive terminal sessions remain deferred until a real workflow requires them. - **POSIX-only** — the `bash` binary, detached process groups, group kills, and SIGTERM→SIGKILL escalation are hardcoded; Windows is unsupported. - **The credential scrub is a name heuristic** — `*KEY*`/`*SECRET*`/`*TOKEN*` only; differently-named secrets (e.g. `*PASSWORD*`) pass through, and a whitelist for over-scrubbed vars is noted future work. - **Spill files are never deleted** — full-output recovery files (and the private per-process spill dir) accumulate under the OS tmpdir until something external cleans them. - **Finished background tasks are never evicted** — they stay in the task map, retaining their in-memory output tails, until executor disposal. +- **`OutputCollector.snapshot()` / `totalBytes` are test-shaped residuals** — the live poll path uses `readFrom()` and a marked cleanup can inline the final snapshot and remove the unused public getter. The raw process handling lives in `src/run.ts`; `src/index.ts` is the service wiring. diff --git a/packages/bash/bash-sandbox/README.md b/packages/bash/bash-sandbox/README.md index 2a5ae63b8e..9fdd223be5 100644 --- a/packages/bash/bash-sandbox/README.md +++ b/packages/bash/bash-sandbox/README.md @@ -31,3 +31,10 @@ Deny-only at the seam: a denial is a reported fact, and this executor never nego ``` The keyless consumer-integration proofs are `tests/bwrap.e2e.ts`, `tests/landlock.e2e.ts`, and `tests/seatbelt.e2e.ts` (the real provider + real runner driven through `ctx.bash`, world-verified, each self-skipping where its runner is absent); see [`examples/sandbox-acp-agent`](../../../examples/sandbox-acp-agent/) for the runnable demo. + +## Known Limitations and Deferred Work + +- **Confinement covers file effects only** — network access and process visibility are unchanged, so the modes are not a general-purpose security sandbox. +- **Denials are inferred from failed-command stderr** — backend signatures make the inference portable, but a matching application error can be classified as a denial and a denial omitted from the retained tail can be missed. +- **A background runner failure has no immediate error channel** — it is recorded on the settled task and surfaces when the caller polls with `bash_output`. +- **`danger-full-access` deliberately bypasses `ctx.sandbox`** — it is an explicit unconfined mode, not a wider sandbox profile. diff --git a/packages/bash/tool-bash/README.md b/packages/bash/tool-bash/README.md index 6251b659fb..f3168e00f0 100644 --- a/packages/bash/tool-bash/README.md +++ b/packages/bash/tool-bash/README.md @@ -38,7 +38,7 @@ The owning agent's session token (`session.header.id`) is stamped onto the task ## UI presentation -These tools own how their calls render in a UI (an editor's tool-call card) via the `dsh-tools` `presentCall`/`presentResult` seam, each returning a `card`-tagged render intent — a UI never special-cases tool names. A FOREGROUND `bash` run declares a **terminal card**: `presentCall` returns `{ card: 'terminal', title, description?, cwd? }` — the **title** is the exact `command` ("ls -la src"), the model-written `description` rides along (rendered ABOVE the card), and `cwd` comes from the model `workdir` when given (absolute as-is, relative for the UI bridge to resolve against the session cwd; else left for the bridge to fill from the session cwd) — and `presentResult` returns `{ card: 'terminal', title?, output?, exitCode?, signal? }` carrying the raw output plus the parsed `exitCode`/`signal`, so a capable client (Zed) renders a terminal card with an exit-status pill. The result carries the raw `output`; the bridge DERIVES the ` ```console ` fenced fallback for a no-terminal-capability UI (the tool no longer encodes the fences itself), so the model-facing result text stays unfenced. A `run_in_background` call is NOT a terminal (it returns a task id immediately and never streams a terminal — poll with `bash_output`) and instead returns a **generic card** (`{ card: 'generic', title, kind: 'execute', rawInput: command, content: [description] }`); an `isError` result (spawn failure / abort) likewise returns a `generic` result view with no exit pill (there is no real process exit). `bash_output`/`bash_kill` return a `generic` card with a task-scoped title ("Read output from background task bash-3" / "Kill background task bash-3") and the task id as rawInput. These methods are pure/display-only (they also run on `session/load` replay), and a malformed/older logged arg shape falls back to a generic presentation rather than throwing. See `packages/core/tools` ("Tool-owned UI presentation") and `packages/ui/acp` ("Terminal card" / "Tool-call presentation"). +These tools own how their calls render in a UI (an editor's tool-call card) via the `dsh-tools` `presentCall`/`presentResult` seam, each returning a `card`-tagged render intent — a UI never special-cases tool names. A FOREGROUND `bash` run declares a **terminal card**: `presentCall` returns `{ card: 'terminal', title, description?, cwd? }` — the **title** is the exact `command` ("ls -la src"), the model-written `description` rides along (rendered ABOVE the card), and `cwd` comes from the model `workdir` when given (absolute as-is, relative for the UI bridge to resolve against the session cwd; else left for the bridge to fill from the session cwd) — and `presentResult` returns `{ card: 'terminal', title?, output?, exitCode?, signal? }` carrying the raw output plus the parsed `exitCode`/`signal`, so a capable client (Zed) renders a terminal card with an exit-status pill. The result carries the raw `output`; the bridge DERIVES the ` ```console ` fenced fallback for a no-terminal-capability UI while the tool keeps model-facing result text unfenced. A `run_in_background` call is NOT a terminal (it returns a task id immediately and never streams a terminal — poll with `bash_output`) and instead returns a **generic card** (`{ card: 'generic', title, kind: 'execute', rawInput: command, content: [description] }`); an `isError` result (spawn failure / abort) likewise returns a `generic` result view with no exit pill (there is no real process exit). `bash_output`/`bash_kill` return a `generic` card with a task-scoped title ("Read output from background task bash-3" / "Kill background task bash-3") and the task id as rawInput. These methods are pure/display-only (they also run on `session/load` replay), and a malformed/older logged arg shape falls back to a generic presentation rather than throwing. See `packages/core/tools` ("Tool-owned UI presentation") and `packages/ui/acp` ("Terminal card" / "Tool-call presentation"). ## Background completion notices @@ -62,3 +62,5 @@ Under a sandboxing executor this plugin makes the session's standing mode overri - **Replay exit pills parse from result text** — output whose final line happens to be exactly `[exit code: N]` / `[killed by signal: …]` shows a wrong pill on session replay; a display-only known residual. - **The bash tools opt out of `timeout-policy` budgets** — `bash` keeps the executor-owned `BASH_TIMEOUT` path and `bash_output`/`bash_kill` declare no budget, per [the tool-call timeout-policy RFC](../../../docs/rfc/implemented/architecture/2026-07-07-tool-call-timeout-policy.md). +- **Completion notices do not wake an idle agent** — they become durable context for the next request; a caller needing progress now must poll `bash_output` or send another message. +- **Tasks started outside an agent have no ownership fence** — their predictable ids are readable and killable by any caller; only agent-started tasks carry a session owner token. diff --git a/packages/core/agent-core/README.md b/packages/core/agent-core/README.md index 28e75a9d80..af660bf2b3 100644 --- a/packages/core/agent-core/README.md +++ b/packages/core/agent-core/README.md @@ -2,7 +2,7 @@ The **default executor-less, UI-less agent spine** as ONE Cordis bundle plugin. It loads the fixed set of services every harness agent needs, including the local skill provider, and forwards the loop's `agents` list as its own config — so an app package composes a working agent by adding only a front door and the swappable backends. -This is the package to read to see **the whole plugin tree at once** — the teaching role the inlined `echo-agent` `cordis.yml` used to play before the spine moved behind this bundle. +This is the package to read to see **the whole shared plugin tree at once**: the teaching overview of the spine behind every app package. ## The tree it loads diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 1004083829..31bf9bf008 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -36,7 +36,7 @@ interface Config { } ``` -Agents listed in config are auto-created at startup. `cwd` applies only to fresh config-created sessions; `resumeSessionId` keeps the persisted session header. There is no per-agent persona: the deployment persona is `dsh-system-prompt`'s own `persona` config, shared by every agent in the context. The plugin registers the built-in `model`/`cwd` prompt variables on `ctx.systemPrompt`, resolved per step from the `assemble({ agent })` context — runtime facts of the agents THIS loop drives, unlike the `harness:identity`/`deployment:persona` sections, which live on `dsh-system-prompt` so they survive a swapped loop plugin. +Agents listed in config are auto-created at startup. `cwd` applies only to fresh config-created sessions; `resumeSessionId` keeps the persisted session header. Config agents have no per-agent persona field: they use `dsh-system-prompt`'s deployment default, while programmatic factory callers can register an agent-scoped `deployment:persona` shadow in `setup`. The plugin registers the built-in `model`/`cwd` prompt variables on `ctx.systemPrompt`, resolved per step from the `assemble({ agent })` context — runtime facts of the agents THIS loop drives, unlike the `harness:identity`/default `deployment:persona` sections, which live on `dsh-system-prompt` so they survive a swapped loop plugin. ### Exported concrete class @@ -107,6 +107,6 @@ Everything that goes beyond "call the model, run the tools, repeat" belongs to p - **Tool calls within a step execute sequentially** — parallel execution waits on concurrency-safety metadata in the tool contract (see `dsh-tools`). - **No resume-or-create policy on the config path** — config-driven `create()` starts a fresh `${id}-session-` every run (`TODO(demo)`), and a config `resumeSessionId` whose resume fails logs a warning and creates no agent. -- **Config agents carry no session `cwd`** — `Config` has no cwd field, so a persona referencing `{{cwd}}` fails prompt assembly for config-created agents. +- **Config agents have no per-agent persona field or setup hook** — they use the deployment persona; scoped persona/tool composition is available only through the programmatic `ctx.agents.create()` / `resume()` factory options. - **No built-in turn budget** — the default continuation is `continue` whenever a step had tool calls or steering; bounding a runaway turn requires an `agent/turn-continuation` force-stop plugin. - **`runLoop`/`Inbox`/`InboxMessage` stay exported with no outside consumer** — [removal is proposed](../../../docs/rfc/proposed/simplification/2026-07-04-prune-dead-core-spine-surface.md). diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index c31eb3680c..c75ce874fb 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -55,9 +55,8 @@ The handle every plugin programs against: ## Known Limitations and Deferred Work - **Inter-agent channels beyond delegation** — shared state, streaming child output, and background/poll semantics remain outside the current synchronous `ctx.subagents` seam. -- **`agent/session-start` cannot gate startup** — a synchronous, veto-less emit, so an async listener's injection is best-effort before turn 1; startup gating is a deferred loop-level change. +- **`agent/session-start` cannot gate startup** — it remains a synchronous, veto-less notification; async composition that must finish before publication belongs in the factory's `setup(agentCtx)` transaction instead. - **No public step-only abort** — `cancel()` clears ALL pending work (queued + steering + in-flight); an abort that preserves queued prompts returns only with a named consumer ([stop-surface RFC](../../../docs/rfc/implemented/simplification/2026-06-20-public-agent-stop-surface.md)). -- **`AgentRegistry` enforces unique agent ids, not session ids** — two live agents sharing a session id mis-route bash owner-token notices; [id unification stays proposed](../../../docs/rfc/proposed/simplification/2026-06-20-unify-agent-and-session-id.md). - **`HookContext` carries exactly one `MessageSource`** — contributions from several plugins merged onto one tool call collapse under one source; mixed provenance is unrepresentable. - **`SessionStartSource` reserves `'clear'`/`'compact'` with no emitter yet** — only `'startup'`/`'resume'` occur until the driving subsystems land (`TODO(compaction)`). - **`agent/pre-step`'s `fullSystemPrompt`/`sessionPrefix` parameters are a flagged smell** — compaction is their only consumer; a lazy prompt provider or a compaction-specific pressure seam is the marked revisit. diff --git a/packages/core/scope/README.md b/packages/core/scope/README.md index e57f475816..f76254a406 100644 --- a/packages/core/scope/README.md +++ b/packages/core/scope/README.md @@ -19,3 +19,10 @@ Scoped-context registration primitive. `createScope(ctx, key)` mints a Cordis co Ownership and visibility derive from ONE fact — which context a registration went through. An explicit `{ scope }` registration parameter could express "visible to X, disposed with Y", which is almost always a bug; the scoped context makes it unrepresentable. Rationale and alternatives: [the agent-scope RFC](../../../docs/rfc/implemented/architecture/2026-07-08-agent-scope-contexts.md). Handing out a scoped context hands out the minting plugin's service-resolution capability (resolution walks the minting fiber's dependency chain, not the holder's) — mint scopes from a plugin whose `inject` surface is what scope holders should reach. + +## Known Limitations and Deferred Work + +- **Only scope-aware surfaces isolate state** — registries must file by `scopeOf()` and events must dispatch through `scopeTarget()`; an arbitrary Cordis service remains context-global merely because it is called through a scoped context. +- **A context carries one nearest scope key** — nested scopes shadow their parent's tag rather than forming hierarchical or multi-membership policy sets. +- **Dispatch carriers preserve behavior, not identity** — listener `this` can call the subject's methods, but `this !== subject` and a method read returns a newly bound function. +- **Service reachability comes from the scope minter** — handing out `Scope.ctx` also hands out the minting plugin's injected service surface, so a broader minter cannot later be narrowed by the holder. diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index f55df539d3..a3d4c0bc24 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -6,7 +6,7 @@ System prompt assembly registry. Plugins contribute ordered text sections, tool- | Key | Default | Meaning | |---|---|---| -| `persona` | `''` | The deployment persona: the ONE deployment-authored prompt fragment, rendered as the order-0 `deployment:persona` section and shared by every agent in the context (subagents included). A template — complete `{{…}}` groups are interpreted strictly against the registered variables (the shipped loop registers `{{model}}`/`{{cwd}}`), with no escape syntax for literal braces yet. Empty ⇒ the section is dropped at render. | +| `persona` | `''` | The global deployment-persona default: the ONE config-authored prompt fragment, rendered as the order-0 `deployment:persona` section unless an agent-scoped contribution shadows it. A template — complete `{{…}}` groups are interpreted strictly against the registered variables (the shipped loop registers `{{model}}`/`{{cwd}}`), with no escape syntax for literal braces yet. Empty ⇒ the section is dropped at render. | | `toolOrder` | — | Explicit model-facing tool order, as a list of `ToolSchema.name`s with one `''` rest entry (`TOOL_ORDER_REST`): listed tools take their listed position, unlisted tools land at the rest entry in lexicographic name order. Absent ⇒ plain lexicographic name order. Applied to the collected tools BEFORE the `system-prompt/assemble` waterfall — like the sections' `order` sort, it canonicalizes what the registry contributed (registration order is a plugin-load artifact), and a waterfall listener that mutates the list owns the determinism of what it emits. Misconfiguration fails loud: a list without exactly one rest entry, or with duplicates, throws at load; a listed name with no registered tool rejects every `assemble()`; a tool provider returning the reserved rest-entry name also rejects. Under the shipped loop the turn fails before any model request. Why a central list and not per-plugin weights: [Explicit model-facing tool order](../../../docs/rfc/implemented/feature/2026-07-06-explicit-tool-order.md). | ## Service: `SystemPrompt` (ctx key: `systemPrompt`) @@ -45,9 +45,8 @@ Design rationale: [the prompt-variables RFC](../../../docs/rfc/implemented/archi ## Known Limitations and Deferred Work -- **No deployment-authored prompt text outside config** — the persona is this plugin's `persona` config, and every other section comes from the plugin that owns the fact. (The `harness:identity` line is deliberately a code literal: a harness fact, not a deployment choice; the `system-prompt/assemble` waterfall is the escape valve for a deployment that must drop it.) +- **Deployment-authored prompt text is config/composition only** — this plugin owns the global persona default, creator plugins may register agent-scoped shadows, and other sections come from the plugin that owns the fact; there is no end-user prompt-editing API. - **No prompt compaction here** — it belongs on the `agent/pre-step` seam in `dsh-agent` (implemented by `dsh-compact-basic`). - **No escape syntax for literal `{{…}}` braces** — every complete group is interpolated against registered variables; an escape is deferred until a real prompt needs one. -- **`persona` is context-global** — one deployment fragment shared by every agent, subagents included; per-agent personas need a `system-prompt/assemble` listener. - **`toolOrder` misconfiguration surfaces at prompt assembly (the first turn), not at boot** — only shape violations throw at config load. - **Sections sharing an `order` value tie-break by registration order** — a plugin-load artifact; determinism relies on the distinct-order band convention, unlike the canonicalized tool order. diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index b46a109679..63a094ce97 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -146,6 +146,6 @@ The wire collapse is the registry's own contribution (`systemPrompt.tools()` is - **`tools/pre-execute` deliberately cannot rewrite `exec.arguments`** — logged and rendered args would desync from what ran; the rewrite design is [a proposed RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md). - **`defineTool`'s schema DSL is a deliberate subset** — string/number/boolean/object/array with string-only `enum`; `validateArgs` tolerates extra keys and never applies `default` (`XXX(unused-default)` flags removing that field); raw-registered JSON-Schema tools validate their own input. - **`timeoutMs` on a definition is declarative only** — the registry never enforces deadlines; enforcement requires the `@deepseek-ai/dsh-timeout-policy` wrapper. -- **Code Mode is TypeScript-only and registry-wide** — `mode: code`/`both` rejects prompt assembly unless `ctx.codeRuntime.language === 'typescript'`, and per-tool visibility tiers (one tool native, another code-only) are knowingly deferred. +- **Code Mode is TypeScript-only and the presentation mode is service-wide** — `mode: code`/`both` rejects prompt assembly unless `ctx.codeRuntime.language === 'typescript'`; scoped restrictions/shadows still choose each agent's visible bindings, but one tool cannot be native-only while another is code-only. - **Code Mode bindings return text only** — non-text content blocks in a sub-call result collapse to `[ content]` placeholders. - **`run_code` state is fresh per run** — a persistent REPL-style kernel is rejected for the MVP (cross-call state would be invisible to the log); see [the Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md). diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index 9f5ce3d950..044ed53364 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -47,6 +47,5 @@ This package declares three events (see the generated [events catalog](../../../ - **Text-only by contract** — backends reject binary/non-UTF-8 content with `FS_NOT_TEXT`; binary-safe operations are a deliberate deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md). - **Seven primitives only** — no delete, rename/move, copy, or watch; `listDir` is single-level, with recursion, globbing, pagination, and search out of scope per [the directory-listing RFC](../../../docs/rfc/implemented/architecture/2026-07-03-filesystem-directory-listing-seam.md). -- **`listDir` has no in-repo consumer yet** — skill discovery and a model-facing `ls` tool are the named follow-ups; sessions list directories via `bash` meanwhile. - **No IO deadline** — the seam arms no timeout; cancellation is a best-effort optional `AbortSignal` per primitive (the deliberate [fs-family stance](../README.md)). - **Resolve-then-operate costs a remote backend two round-trips per tool call** — folding or caching resolution is left to such a backend. diff --git a/packages/fs/tool-fs/README.md b/packages/fs/tool-fs/README.md index 88a0e9663a..f3988d6ae7 100644 --- a/packages/fs/tool-fs/README.md +++ b/packages/fs/tool-fs/README.md @@ -50,6 +50,6 @@ The read rendering (line windowing + output formatting) lives in `src/read-rende ## Known Limitations and Deferred Work -- **No directory-listing, glob, grep, or search tools ship** — a deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md) (and `ctx.fs.listDir` has no tool consumer yet); models fall back to `bash`. +- **No directory-listing, glob, grep, or search tools ship** — a deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md); `ctx.fs.listDir` serves provider code such as skill discovery but still has no model-facing consumer, so models fall back to `bash`. - **`read` handles UTF-8 text files only** — binary-safe reads and PDF/image/multimodal content are deferred; a directory target is `FS_NOT_REGULAR_FILE`. - **No timeout surface** — `read`/`write`/`edit` take no timeout argument and declare no `timeout-policy` budget; cancellation rides `exec.signal` only (the deliberate [fs-family stance](../README.md)). diff --git a/packages/sandbox/sandbox-local/README.md b/packages/sandbox/sandbox-local/README.md index abf7511438..0d6f70651c 100644 --- a/packages/sandbox/sandbox-local/README.md +++ b/packages/sandbox/sandbox-local/README.md @@ -16,3 +16,11 @@ Every rung has its keyless world-proof (`tests/bwrap.e2e.ts`, `tests/landlock.e2 ``` Consumers: [`@deepseek-ai/dsh-bash-sandbox`](../../bash/bash-sandbox/); see [`examples/sandbox-acp-agent`](../../../examples/sandbox-acp-agent/) for the runnable composition. + +## Known Limitations and Deferred Work + +- **Windows has no runner** — `win32` fails closed with `SANDBOX_UNAVAILABLE`; an AppContainer-family backend is deferred. +- **Landlock may be partial** — older supported kernel ABIs confine only the access classes they expose, reported as `enforcement: 'partial'` rather than overstated as full. +- **Seatbelt depends on deprecated `sandbox-exec`** — macOS still ships it, but this provider cannot replace or probe that private policy engine if Apple removes it. +- **Runner selection is cached for the provider lifetime** — installing, removing, or repairing a runner requires reloading the plugin before selection changes. +- **`runnerCommand` is an operator assertion** — a configured custom runner skips functional probes and is assumed to implement the bwrap-shaped profile honestly. diff --git a/packages/sandbox/sandbox/README.md b/packages/sandbox/sandbox/README.md index ef381625b2..c552b7846e 100644 --- a/packages/sandbox/sandbox/README.md +++ b/packages/sandbox/sandbox/README.md @@ -9,3 +9,10 @@ Policy rides the call, not the provider: two consumers may confine under differe **Same-world confinement only.** A backend shares the host's filesystem and kernel (`bwrap`, Landlock, Seatbelt); `workspaceRoot` names a real host path. Containers, microVMs, and remote executors are NOT backends of this seam — they replace whole capability implementations (`ctx.bash`, `ctx.fs`) as environment-coherent groups. The boundary and its rationale: [the sandbox RFC](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md). Implementations: [`@deepseek-ai/dsh-sandbox-local`](../sandbox-local/) (Linux: `bwrap`, else the per-platform Landlock launcher; macOS: `sandbox-exec`/Seatbelt). Consumers: [`@deepseek-ai/dsh-bash-sandbox`](../../bash/bash-sandbox/) (wraps `['bash', '-c', command]`). + +## Known Limitations and Deferred Work + +- **File effects are the whole policy vocabulary** — the seam expresses no network, process, syscall, device, or credential restrictions. +- **Same-world confinement only** — containers, microVMs, and remote execution require replacing capability implementations rather than adding a provider here. +- **Denial reporting is a stderr dialect** — the seam returns backend signatures instead of a typed runtime denial channel, so consumers that need classification must infer it from the child process's output. +- **One provider per context** — composing different sandbox mechanisms simultaneously requires a provider-level ladder or separate Cordis contexts; callers choose policy per call, not backend identity. diff --git a/packages/session-persistence/session-persistence/README.md b/packages/session-persistence/session-persistence/README.md index 9a96515082..92a432d44b 100644 --- a/packages/session-persistence/session-persistence/README.md +++ b/packages/session-persistence/session-persistence/README.md @@ -24,7 +24,7 @@ The persisted unit IS the existing `SessionEvent` (event-sourced model — the l The two first-party backends were byte-identical (or same-algorithm) for ALL of their write-path orchestration — the in-memory bookkeeping (per-id state, write-behind buffers, per-id serialization chains, per-session init promises), the `session/event` → buffer → `session/flush` drain, lazy materialization, crash-tail repair on load, the four `session/created` adoption cases (new / HMR-adopt / collision / ownerless-claim), and dispose-time quiescence. Only the STORAGE primitives differed (write bytes vs. INSERT rows). -`PersistenceCoordinator` owns that orchestration once. A first-party backend composes one (`new PersistenceCoordinator(ctx, this)`), implements the small `PersistenceBackend` hook interface, and delegates its four public service methods to the coordinator. This keeps the duplicated, correctness-heavy orchestration in a single place (it used to receive the same fixes twice). +`PersistenceCoordinator` owns that orchestration once. A first-party backend composes one (`new PersistenceCoordinator(ctx, this)`), implements the small `PersistenceBackend` hook interface, and delegates its four public service methods to the coordinator. The correctness-heavy orchestration therefore has one implementation and one place for fixes. The `PersistenceBackend` hooks (the only seam between the coordinator and storage): diff --git a/packages/skill/skill-local/README.md b/packages/skill/skill-local/README.md index c885416ff5..77814bdbae 100644 --- a/packages/skill/skill-local/README.md +++ b/packages/skill/skill-local/README.md @@ -28,10 +28,17 @@ Default roots are resolved in this provider's rank order: | 400 | `user-dsh` | `/skills` | | 500 | `user-agents` | `/skills` | -The project root is the nearest ancestor containing `.git`; without one, the current cwd is used. The user DSH root skips its `.system` child so system-owned directories are not accidentally treated as normal user skills. DeepSeek Harness no longer ships built-in system skills from this provider; additional built-ins can be supplied later by another provider. +The project root is the nearest ancestor containing `.git`; without one, the current cwd is used. The user DSH root skips its `.system` child so system-owned directories are not accidentally treated as normal user skills. This provider ships no built-in system skills; another provider can supply embedded or remote built-ins. When `ctx.fs` is available, discovery lists roots through `ctx.fs.listDir`, reads skill files through `ctx.fs.readText`, and probes `.git` through the filesystem service. Full skill loads forward the lookup abort signal to filesystem metadata and content reads. Without a filesystem service, the provider falls back to abortable Node filesystem I/O so minimal local contexts can still load skills. Missing, unreadable, or malformed skill files warn and skip instead of failing the whole request. ## Skill Format Skills can be single-level directory bundles (`/SKILL.md`) or flat Markdown files (`.md`). Nested `**/SKILL.md` discovery is intentionally not part of v1. Frontmatter is parsed as YAML with the `yaml` package; it requires `name` and `description`, while `whenToUse`, `disableModelInvocation`, and `metadata` are optional. Names must be kebab-case. + +## Known Limitations and Deferred Work + +- **Discovery is one level deep** — only `//SKILL.md` and `/.md` are recognized; nested skill trees and package manifests are ignored. +- **Project scope is the nearest `.git` ancestor** — workspaces without that marker fall back to the supplied cwd, with no alternate project-root marker or monorepo subproject selection. +- **Unreadable or malformed entries disappear with a warning** — the model catalog receives no per-skill diagnostic and cannot distinguish an absent skill from a skipped one. +- **No filesystem watching** — edits rely on the registry cache being evicted or invalidated by provider reload before a previously collected cwd is rediscovered. diff --git a/packages/skill/skill/README.md b/packages/skill/skill/README.md index 33d16e1b69..eeddc9dd11 100644 --- a/packages/skill/skill/README.md +++ b/packages/skill/skill/README.md @@ -32,3 +32,10 @@ The registry validates candidate names, descriptions, ranks, and provider owners ## Consumer boundary The registry does not render model guidance or register model-facing tools. [`@deepseek-ai/dsh-tool-skill`](../tool-skill) consumes `ctx.skills` to provide the session-prefix catalog and `skill` tool, so providers remain independent of the model surface. + +## Known Limitations and Deferred Work + +- **Completed catalogs have no TTL or watcher invalidation** — a provider's underlying files or remote data can change without a registration revision, so a cached cwd stays stale until eviction or provider/runtime reload. +- **Providers are queried sequentially** — one slow cooperative provider delays every provider registered after it; cancellation stops the caller's wait but cannot terminate work an uncooperative provider keeps running. +- **A provider-list failure removes that whole source for the request** — the registry logs and skips it, with no model-visible diagnostic or partial-catalog recovery contract. +- **Duplicate resolution is first-wins** — later lower-priority candidates are logged and hidden; there is no API to inspect all shadowed definitions. diff --git a/packages/skill/tool-skill/README.md b/packages/skill/tool-skill/README.md index 7e72f89000..ce65c9ce3a 100644 --- a/packages/skill/tool-skill/README.md +++ b/packages/skill/tool-skill/README.md @@ -19,3 +19,10 @@ The plugin contributes one user-role `` catalog through `agent/ Execution uses the calling agent's `session.header.cwd` so workspace-sensitive providers can resolve the right winning skill. A successful call returns one text tool result with ``, containing `` followed by ``. Resource guidance resolves paths or URLs explicitly referenced by the loaded instructions against `resourceBase`; referenced scripts, references, and assets load only when needed, and the tool does not enumerate a skill directory. Local filesystem skills provide a base directory, while remote or embedded providers can provide a URL or opaque provider-managed guidance. A name that cannot be resolved reports that the skill is unknown or no longer available; invalid names and skills marked `disableModelInvocation: true` retain distinct `isError` results. The tool does not call `agent.inject()` in v1. Its result is already recorded as the tool result and becomes available to the next model step without duplicating the content as synthetic context. + +## Known Limitations and Deferred Work + +- **The catalog omits `whenToUse`, source, and provider metadata** — routing is based only on name and a capped description; `whenToUse` remains provider metadata and is not rendered by the loaded wrapper either. +- **Loaded instruction bodies have no size cap** — a provider can return a skill large enough to consume substantial next-step context; only catalog descriptions are truncated. +- **Resources are guidance, not attachments** — the tool reports a base directory/URL/opaque hint but neither enumerates nor fetches referenced files for the model. +- **Loading is one-shot text** — there is no partial, streaming, or cached-content handle when a remote provider is slow or a skill body is large. diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index db2b59d412..ca331796c8 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -73,7 +73,7 @@ Named `name` / `inject` / `Config` / `apply`, with **no default export**: the co ## Known Limitations and Deferred Work - **A fresh process per run** — persistent-process pooling is a future optimization ([the seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). -- **No start-time capability enforcement** — an out-of-process child cannot honor the parent's `outputSchema`/`depthLimit`/`toolFilter`, so the provider advertises none and `request.parent` is ignored. +- **No start-time capability enforcement** — an out-of-process child cannot honor the parent's `outputSchema`/`depthLimit`/`toolFilter`/`persona`, so the provider advertises none and `request.parent` is ignored. - **Only `agent_message_chunk` text is collected** — the child's tool-call activity, thought chunks, and plan updates are not surfaced to the parent. - **Permission prompts are auto-answered** (`permission: allow | reject`) — no human is surfaced a child's `session/request_permission` in this cut. - **No snapshot-tier replay coverage** (`TODO(acp-subagent-replay)`) — an ACP child is its own process with its own replay shape, deferred. diff --git a/packages/subagent/subagent-fork/README.md b/packages/subagent/subagent-fork/README.md index a6e2785795..dcad34e80e 100644 --- a/packages/subagent/subagent-fork/README.md +++ b/packages/subagent/subagent-fork/README.md @@ -24,6 +24,5 @@ See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, m ## Known Limitations and Deferred Work -- **Tool-scoping (`toolFilter`) is not supported** — declared `false`, so the service rejects a request needing it before `start` runs. - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. - **The seed is a one-time snapshot** — the child sees the parent's completed turns as of the fork and nothing the parent logs afterwards; there is no live context sharing. diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md index 6084fe569c..5f952a6c8b 100644 --- a/packages/subagent/subagent-inprocess/README.md +++ b/packages/subagent/subagent-inprocess/README.md @@ -40,6 +40,5 @@ Thrown by `startInProcessRun` when a spawn would exceed the request's `maxDepth` ## Known Limitations and Deferred Work -- **The structured-output runtime is context-global** — the tool registry and prompt assembly are context-wide while schemas differ per concurrent child, hence the final-assembly enforcement dance; per-agent/per-session scoping would dissolve it (the module-doc `FIXME`). - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. -- **`toolFilter` is unimplemented in this driver** — both in-process backends declare it `false`; scoping a child's tool set is deferred. +- **Structured capture accepts the `defineTool` schema subset only** — unsupported JSON Schema constructs fail before the child is created; a provider needing a broader schema vocabulary requires a different runtime. diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index 0a3a17f087..4a86a003fd 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -20,5 +20,5 @@ The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../ ## Known Limitations and Deferred Work -- **Tool-scoping (`toolFilter`) is not supported** — the capability is declared `false`, so the service rejects a request needing it before `start` runs; scoping a child's tool set is deferred. - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. +- **Fresh means no parent transcript** — the child inherits cwd, lineage, model, and explicitly configured persona/tool restrictions, but none of the parent's conversation; use the fork provider when completed-turn context is required. diff --git a/packages/subagent/subagent-subprocess/README.md b/packages/subagent/subagent-subprocess/README.md index ccc68bf31b..f8ddce64c4 100644 --- a/packages/subagent/subagent-subprocess/README.md +++ b/packages/subagent/subagent-subprocess/README.md @@ -38,3 +38,10 @@ A per-run isolated config directory for an external CLI child (the target of `CL ## Testing `tests/subagent-subprocess.spec.ts`: the env scrub and config-dir helpers run against the real process env and real filesystem (the rm-failure path injects its rejection at the fs boundary — a real recursive-rm failure is not portably provokable, and root ignores permission bits); the exit waits and the dispose ladder run against a scriptable fake child, driving each escalation tier deterministically. The [ACP backend suite](../subagent-acp/README.md) exercises the same ladder against real subprocesses (EOF-cooperative, EOF-ignoring, and SIGTERM-trapping children) end to end. + +## Known Limitations and Deferred Work + +- **The credential scrub is name-based** — only variables matching `KEY` / `SECRET` / `TOKEN` are removed; differently named secrets such as `PASSWORD` pass through unless the backend supplies a stricter environment. +- **Signals target the direct child only** — teardown relies on a cooperative CLI to reap its descendants before exit; a re-parented or independently detached grandchild can outlive the ladder. +- **Fresh config-dir cleanup is best-effort** — an `rm` failure leaves private state under the OS temp root rather than failing disposal. +- **Pinned config directories are wholly operator-owned** — the helper neither creates, validates, locks, nor removes them, so concurrent runs may share and race on that state. diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index 18ebea6ada..e7ff177c90 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -29,4 +29,4 @@ Background / poll collection is deferred (see the [RFC](../../../docs/rfc/implem - **Delegation blocks the parent turn** — synchronous collect only; background start + poll collection is deferred to the long-running-runtime redesign. - **Duplicate `toolName` across waiting loads is detected late** (`TODO(subagent-dup-toolname)`) — two loads waiting on providers collide only when a provider arrives, and the throw rolls back the provider's fiber rather than the misconfigured tool's; config-time detection needs a cross-fiber registry of intended names. -- **No per-child persona or prompt shaping** — `agentOptions` carries only `model`; the deployment persona is context-wide, and per-child system-prompt variation has no seam yet. +- **Child policy is fixed per tool registration** — `model`, persona, tool filter, and depth cap come from this plugin load's config, not model-call arguments; exposing another policy requires another distinctly named tool. diff --git a/packages/support/subagent-mock/README.md b/packages/support/subagent-mock/README.md index af169ed4c2..89b69c9ccb 100644 --- a/packages/support/subagent-mock/README.md +++ b/packages/support/subagent-mock/README.md @@ -13,8 +13,14 @@ Load it as a plugin (functional shape: `name`/`inject`/`Config`/`apply`, no defa | `name` | `mock` | Registry name to register the provider under. | | `reply` | `mock subagent reply` | The scripted child's final answer text. | | `stopReason` | `completed` | The stop reason `result` settles with. | -| `capabilities` | all `true` | Which start-time capabilities (`outputSchema`/`depthLimit`/`toolFilter`) the provider advertises. | +| `capabilities` | all `true` | Which start-time capabilities (`outputSchema`/`depthLimit`/`toolFilter`/`persona`) the provider advertises. | | `inheritsParentContext` | `false` | The context contract to declare; `true` exercises the fork-shaped tool wording in consumer tests. | | `structured` | `{ reply }` | Structured value surfaced when a request carries an `outputSchema` and the capability is on. | A `cancel()` issued before `result` settles flips the stop reason to `aborted`, so the cancellation path is observable. + +## Known Limitations and Deferred Work + +- **Scripted provider only** — it does not run a model, create a child agent, or exercise real prompt/tool-loop behavior. +- **One immediate synthetic outcome per run** — it models no multi-turn, streaming, readiness delay, or subprocess transport behavior. +- **`dispose()` is a no-op** — lifecycle tests using it prove consumer control flow, not resource teardown or quiescence of a real backend. diff --git a/packages/ui/acp-agent/README.md b/packages/ui/acp-agent/README.md index ed83bfaf4d..2e38e5a8cc 100644 --- a/packages/ui/acp-agent/README.md +++ b/packages/ui/acp-agent/README.md @@ -15,6 +15,7 @@ stdout is the ACP JSON-RPC channel, so the cluster is defined as much by what it | `@deepseek-ai/dsh-session-persistence-jsonl` | durable JSONL session log (the bridge advertises `loadSession`) | | `@deepseek-ai/dsh-acp` | the bridge that owns stdout for JSON-RPC and provides ACP-backed user answers when a leaf explicitly exposes a user-question tool | | ~~`@deepseek-ai/dsh-tool-ask-user`~~ | **omitted by default** — ACP elicitation support is still client-dependent, so leaves must opt in deliberately | +| ~~`@deepseek-ai/dsh-user-approval`~~ | **omitted by default** — permission policy is deployment-specific; sandbox/approval leaves opt in and the ACP bridge then supplies the answerer | | ~~console logger~~ | **omitted** — it writes to stdout and would corrupt the protocol frames ([the stdout-purity footgun](../acp/README.md)) | | ~~`hmr`~~ | **omitted** — the editor owns the subprocess | @@ -27,6 +28,8 @@ Because the package wires no logger entry, an ACP leaf has **nothing to get wron | `model` | (required) | the per-session agent template the bridge creates agents from | | `persona` | — | the deployment persona template (may reference `{{model}}`/`{{cwd}}`), routed to `dsh-system-prompt` | | `toolOrder` | — | explicit model-facing tool order (a name list with one `''` rest entry; absent — lexicographic; an unregistered name fails each turn at prompt assembly), routed to `dsh-system-prompt` | +| `tools` | `{ mode: 'native' }` | tool-registry presentation config (`native` / `code` / `both`), routed through `dsh-agent-core` | +| `skills` | owner defaults | registry-cache, local-provider, and model-facing skill-tool config, routed through `dsh-agent-core` | | `persistenceRoot` | `./.sessions` | the JSONL backend's root directory | The leaf supplies the swappable backends: an LLM adapter (`llm-deepseek` for the real model, `llm-replay` for keyless snapshot replay) and a bash executor (`bash-local`). @@ -45,4 +48,6 @@ All diagnostics go to **stderr** — stdout is the protocol. ## Known Limitations and Deferred Work -- **The front-door cluster is fixed in code** — only `model`/`persona`/`toolOrder`/`persistenceRoot` are config; swapping the baked JSONL persistence for another backend, or adding the deliberately-omitted `tool-ask-user`, means a leaf-level sibling entry or a differently-composed app package. +- **JSONL persistence is baked in** — config chooses its root but cannot select a different backend; that requires a sibling entry or differently composed app package. +- **User-question and approval mechanisms are omitted by default** — the bridge can answer both when their services/tools are composed, but this front door does not enable those deployment policies itself. +- **A leaf can still corrupt stdout** — the app mounts no console logger, but it cannot prevent a sibling leaf entry from writing non-protocol bytes to the ACP channel. diff --git a/packages/ui/acp/README.md b/packages/ui/acp/README.md index 465f0a42e6..c657d6c11f 100644 --- a/packages/ui/acp/README.md +++ b/packages/ui/acp/README.md @@ -46,7 +46,7 @@ Background-task isolation rides on `dsh-tool-bash`: bash task ids are global and ## Per-session cwd -Each session runs in its own workspace, recorded as the session's `SessionHeader.cwd`. On `session/new` the (absolute) request `cwd` becomes that header cwd; on `session/load` the resumed session keeps its PERSISTED header cwd and the request `cwd` must be absolute and equal to it, so the editor and bash executor agree on the workspace before an agent is constructed. A load whose persisted session has no absolute cwd is REJECTED up front via a metadata-only `list()` check, BEFORE resume constructs an agent (else bash would silently fall back to the server's launch dir, and a post-resume reject would leak the registered agent). `dsh-tool-bash` then defaults the bash workdir to the calling agent's `session.header.cwd` (an explicit model `workdir` still wins; a relative one resolves against the session cwd; with no session cwd the executor falls back to its own config / `process.cwd()`). So the server no longer has to be launched in the workspace — an editor can open any project folder, and N sessions over one connection can each target a different directory. (`additionalDirectories` is still rejected: widening the tool/filesystem scope beyond the single cwd is a separate sandbox concern.) +Each session runs in its own workspace, recorded as the session's `SessionHeader.cwd`. On `session/new` the (absolute) request `cwd` becomes that header cwd; on `session/load` the resumed session keeps its PERSISTED header cwd and the request `cwd` must be absolute and equal to it, so the editor and bash executor agree on the workspace before an agent is constructed. A load whose persisted session has no absolute cwd is REJECTED up front via a metadata-only `list()` check, BEFORE resume constructs an agent (else bash would silently fall back to the server's launch dir, and a post-resume reject would leak the registered agent). `dsh-tool-bash` then defaults the bash workdir to the calling agent's `session.header.cwd` (an explicit model `workdir` still wins; a relative one resolves against the session cwd; with no session cwd the executor falls back to its own config / `process.cwd()`). The server may be launched outside every workspace: an editor can open any project folder, and N sessions over one connection can each target a different directory. (`additionalDirectories` is still rejected: widening the tool/filesystem scope beyond the single cwd is a separate sandbox concern.) ## Tool-call presentation @@ -67,7 +67,7 @@ A tool whose call IS a shell command (`bash`) can render as a real **terminal ca - `tool_call`: `content:[…, {type:'terminal', terminalId}]` + `_meta.terminal_info.{terminal_id, cwd}` — the terminal id is the harness `callId`; the cwd is the card's explicit absolute `cwd`, else a relative `cwd` resolved against the session cwd, else the session's workspace cwd (the bridge fills the default, since the pure tool presenter can't see it). The card's `description` renders as a content block BEFORE the terminal block, so the description sits above the card. - `tool_call_update`: `_meta.terminal_output.{terminal_id, data}` (the terminal card's `output`) plus `_meta.terminal_exit.{terminal_id, exit_code | signal}` when the card reported a structured `exitCode`/`signal`. In terminal mode the update's `content` is OMITTED — an ACP `tool_call_update.content` REPLACES the call's content, so sending the fenced text block would clobber the terminal content block from the call. -When the client does NOT advertise the capability, none of the `_meta`/terminal content is emitted: the `tool_call` shows the `description` content block and the `tool_call_update` carries a ` ```console ` text block the bridge DERIVES by fencing the terminal result's `output` (the tool no longer double-encodes the fences) — so a non-Zed client is never worse off. The `_meta` object is ACP's spec-blessed extensibility point; the specific `terminal_info`/`terminal_output`/`terminal_exit` keys are a Zed convention, not the ACP `terminal/create` sub-protocol (which would make the editor execute the command, bypassing `dsh-bash`'s sandbox/env-scrub/ownership/cwd). Live incremental streaming and command classification are follow-ups. See [the terminal-rendering RFC](../../../docs/rfc/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md) and [the render-intent-union RFC](../../../docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md). +When the client does NOT advertise the capability, none of the `_meta`/terminal content is emitted: the `tool_call` shows the `description` content block and the `tool_call_update` carries a ` ```console ` text block the bridge DERIVES by fencing the terminal result's unfenced `output` — so a non-Zed client is never worse off. The `_meta` object is ACP's spec-blessed extensibility point; the specific `terminal_info`/`terminal_output`/`terminal_exit` keys are a Zed convention, not the ACP `terminal/create` sub-protocol (which would make the editor execute the command, bypassing `dsh-bash`'s sandbox/env-scrub/ownership/cwd). Live incremental streaming and command classification are follow-ups. See [the terminal-rendering RFC](../../../docs/rfc/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md) and [the render-intent-union RFC](../../../docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md). ## Settle-exactly-once @@ -87,6 +87,7 @@ Teardown reaches quiescence: for EVERY live session settle any pending prompt as - **Prompt content is `text` + `resource_link` only** — image, audio, and embedded-resource blocks are rejected, as is a non-empty `mcpServers` list at `session/new`. - **One configured `model` for every created session** — per-session model selection has no config or protocol surface here yet. - **Terminal cards render completed output** — live incremental streaming and command classification are named follow-ups of [the terminal-rendering RFC](../../../docs/rfc/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md). +- **Permission answers are one-shot only** — the bridge offers `allow_once` / `reject_once`; durable `allow_always` grants and their storage/revocation policy remain deferred to the approval seam. ## stdout is the protocol diff --git a/packages/ui/app-boot/README.md b/packages/ui/app-boot/README.md index 92fbc333be..c31ff751e4 100644 --- a/packages/ui/app-boot/README.md +++ b/packages/ui/app-boot/README.md @@ -13,3 +13,9 @@ Shared boot glue for the app bins ([`dsh-stdio-agent`](../stdio-agent/README.md) Two failure classes the guards handle: `loader.await()` swallows init rejections (`Promise.allSettled`) — Node still exits non-zero on the resulting unhandled rejection, and `installFailLoud` replaces the noisy dump with one labelled line and a guaranteed `exit(1)`; a failed plugin IMPORT is only logged by the Loader (the process would otherwise exit 0 on a usable config typo), leaving a fiber-less entry that `assertEntriesLoaded` turns into a `boot()` rejection. Bare plugin specifiers in a config (`@deepseek-ai/dsh-*`) resolve through the cordis Loader's internal module loader, active only under `node --expose-internals`; the bins' subprocess smokes exercise that path, while this package's unit suite drives `boot()` in-process against configs with relative specifiers. + +## Known Limitations and Deferred Work + +- **Bare package specifiers depend on Loader internals** — production bins need `node --expose-internals`; an in-process caller without it must use resolvable relative/file specifiers or tsx path mapping. +- **Snapshot replay swapping is basename-specific** — only a config ending in `cordis.yml` or `cordis.yaml` maps to the sibling `cordis.snapshot.yml`; custom config names require caller-managed selection. +- **Environment loading is cwd-scoped and optional** — the helper loads one `.env` file and warns on failure; it does not search parents, merge profiles, or validate required variables. diff --git a/packages/ui/stdio-agent/README.md b/packages/ui/stdio-agent/README.md index 496aea08fa..62f24ecd14 100644 --- a/packages/ui/stdio-agent/README.md +++ b/packages/ui/stdio-agent/README.md @@ -26,8 +26,10 @@ The leaf `cordis.yml` supplies only the **swappable backends** — an LLM adapte | Key | Default | Routed to | |---|---|---| | `model` | (required) | the pre-created `main` agent's model | -| `persona` | — | the deployment persona template (may reference `{{model}}`), routed to `dsh-system-prompt` | +| `persona` | — | the deployment persona template (may reference `{{model}}`/`{{cwd}}`), routed to `dsh-system-prompt` | | `toolOrder` | — | explicit model-facing tool order (a name list with one `''` rest entry; absent — lexicographic; an unregistered name fails each turn at prompt assembly), routed to `dsh-system-prompt` | +| `tools` | `{ mode: 'native' }` | tool-registry presentation config (`native` / `code` / `both`), routed through `dsh-agent-core` | +| `skills` | owner defaults | registry-cache, local-provider, and model-facing skill-tool config, routed through `dsh-agent-core` | | `persistenceRoot` | `./.sessions` | the JSONL backend's root directory | | `welcome` | `ready.` | the stdin-chat banner | | `resumeSessionId` | — | resume a persisted session id instead of starting fresh (sourced from an env var in the leaf) | @@ -68,3 +70,4 @@ Swap `llm-deepseek` for a `mock-llm` leaf plugin and you have the echo demo — - **One pre-created `main` agent drives the readline UI** — there is no multi-session or concurrent-agent surface in this app; a run is one conversation. - **The front-door cluster is fixed in code** — the JSONL persistence backend and the ask-user tooling are baked; a different composition is a leaf-level sibling entry or another app package. +- **The question tool is not an approval answerer** — this app mounts `user-interaction` and `ask_user_question`, but not `ctx.approval`; a `tools/pre-execute` `ask` therefore fails closed unless the leaf composes an approval service and terminal answerer. diff --git a/packages/ui/user-approval/README.md b/packages/ui/user-approval/README.md index 821e98638f..2027d9c1f9 100644 --- a/packages/ui/user-approval/README.md +++ b/packages/ui/user-approval/README.md @@ -11,3 +11,10 @@ The seam also owns the per-session POLICY tier ([the sandbox RFC § Per-session One seam serves both ask paths of [the sandbox RFC](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md): the `tools/pre-execute` `ask` decision (routed by [`@deepseek-ai/dsh-tools`](../../core/tools/) when this service is mounted; degrading to deny when it is not), and the sandbox post-denial escalated retry (the bash tool's `sandbox_permissions` gate in [`@deepseek-ai/dsh-tool-bash`](../../bash/tool-bash/) — [the sandbox RFC § Escalation](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md)). The full design: [the approval-seam RFC](../../../docs/rfc/implemented/feature/2026-07-06-approval-seam.md). Answerers today: the ACP bridge ([`@deepseek-ai/dsh-acp`](../../ui/acp/)) forwards to the editor's `session/request_permission` prompt for agents it owns. The audit events are log-only session records — the model only ever sees the tool result the asker derives from the outcome. + +## Known Limitations and Deferred Work + +- **Requests are valid only inside an open turn** — an idle or between-turn caller throws before auditing; a durable out-of-turn approval workflow is deferred. +- **Only one-shot grants exist** — the outcome vocabulary has `allowed-once` but no `allow-always`, remembered rule, revocation, or grant store; session policy is only `ask` / `never`. +- **The request carries no tool arguments** — a UI must correlate `callId` with an already rendered tool call, and a call-less request cannot be presented by the shipped ACP answerer. +- **No built-in answerer** — headless or incompletely composed deployments resolve `unavailable` and fail closed; the service itself never prompts a human. diff --git a/packages/util/timeout/README.md b/packages/util/timeout/README.md index db2b06ba53..2f1fb41ef8 100644 --- a/packages/util/timeout/README.md +++ b/packages/util/timeout/README.md @@ -40,3 +40,9 @@ Pass your own `code` to `timeoutOf` so classification composes under nesting: wh ## What does NOT get a timeout Local file `read`/`write`/`edit` take no `timeoutMs`: a syscall is best-effort-abortable at most, a timeout could not force `fsync`/`rename` to stop, and adding one would be an implicit default that violates explicit-over-implicit. See [`fs/`](../../fs/README.md). + +## Known Limitations and Deferred Work + +- **Notification only** — a deadline cannot stop work that ignores its signal; every capability still needs its own socket/process/task termination path. +- **`timeoutMs <= 0` is internal vocabulary** — it disables the local timer only after an owning backend has resolved policy, never as a public model/plugin knob. +- **The first abort reason wins classification** — when an upstream cancellation beats the local timer, this layer cannot later report that its own timeout would also have elapsed. diff --git a/packages/web/tool-web/README.md b/packages/web/tool-web/README.md index 9b0ea33d86..81c19e5e8b 100644 --- a/packages/web/tool-web/README.md +++ b/packages/web/tool-web/README.md @@ -38,4 +38,4 @@ The tool never calls a provider's `status()` and never enumerates providers — - **`htmlToMarkdown` is a minimal regex converter, not an HTML parser** — it strips script/style/noscript, keeps headings/bullets/links, and decodes about a dozen named entities; tables, images, and nested formatting are lost. - **The model-facing surface is minimal by design, with promotions deferred** — `max_results` stays a config bound (not a model argument), and `web_fetch` takes only `url` (no `format`/`prompt`/LLM-summarization mode); both are named later steps in [the seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md). -- **No permission gating** — both tools execute without any permission prompt or policy; integration awaits the deferred permission system, with the owner (a `tools/execute` plugin, provider config, or both) undecided. +- **No web-specific permission policy** — both tools execute without requesting `ctx.approval`; a deployment that needs confirmation must add a `tools/pre-execute` policy, and the package does not define persistent URL/domain grants. diff --git a/packages/workflow/tool-workflow/README.md b/packages/workflow/tool-workflow/README.md index 44160d8fc1..100d5418b8 100644 --- a/packages/workflow/tool-workflow/README.md +++ b/packages/workflow/tool-workflow/README.md @@ -4,7 +4,7 @@ The model-facing **`workflow` tool**: run a JavaScript orchestration script that ## What the model sees -Two parameters: `script` (required — the full `export const meta = {...}` + body text; the tool DESCRIPTION carries the complete authoring contract: hooks, semantics, the supported schema subset) and `args` (optional JSON object exposed to the script as the `args` global; a bare list is wrapped as a field, a deliberate deviation from Claude Code's any-JSON `args` so the wire schema stays honest). The plugin also contributes a `tool:` system-prompt section carrying the usage policy — use the tool only on an explicit user ask for a workflow / large orchestration; prefer plain subagent calls for one or two delegations — per the convention that tool guidance ships with the tool plugin, never in the deployment persona. +Three parameters: `script` (required plain-JavaScript body with no `export const meta` statement), `meta` (required JSON identity with `name`/`description` and optional `whenToUse`/`phases`), and `args` (optional JSON object exposed to the script as the `args` global; wrap a bare list as a field so the wire schema stays honest). The tool description carries the complete authoring contract: hooks, semantics, and the supported schema subset. The plugin also contributes a `tool:` system-prompt section carrying the usage policy — use the tool only on an explicit user ask for a workflow / large orchestration; prefer plain subagent calls for one or two delegations — per the convention that tool guidance ships with the tool plugin, never in the deployment persona. ## Lifecycle @@ -12,7 +12,7 @@ Collection is SYNCHRONOUS this cut (like [`dsh-tool-subagent`](../../subagent/to ## Render intent -Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md)): a `generic` card titled `workflow: `, the name sniffed TEXTUALLY from `args.script` (presentation must be a pure function of args, so it cannot ask the engine to parse); the script text rides as `rawInput`. The result keeps the generic card. +Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/architecture/2026-07-02-tool-render-intent-union.md)): a `generic` card titled `workflow: `, read directly from the call's `meta` parameter (presentation is a pure function of args); the script text rides as `rawInput`. The result keeps the generic card. ## Config @@ -20,3 +20,9 @@ Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/arch |---|---|---| | `toolName` | `workflow` | The model-facing tool name to register. | | `maxResultChars` | `50000` | Rendered-result ceiling; longer JSON is truncated with a notice. | + +## Known Limitations and Deferred Work + +- **The parent turn blocks until the whole workflow settles** — there is no background start/poll surface, and cancellation discards partial output as an error. +- **`args` must be an object and the result is bounded text** — callers wrap top-level arrays/scalars in a field, and JSON beyond `maxResultChars` is truncated rather than stored behind a retrieval handle. +- **Workflow policy is fixed per tool registration** — provider selection, caps, and tool name are deployment config, not model-call arguments. diff --git a/packages/workflow/workflow-workerthread/README.md b/packages/workflow/workflow-workerthread/README.md index 86d386350a..076db8423d 100644 --- a/packages/workflow/workflow-workerthread/README.md +++ b/packages/workflow/workflow-workerthread/README.md @@ -39,7 +39,7 @@ Per-run limits: a concurrency semaphore (`maxConcurrentAgents`), a total-`agent( A worker that dies unexpectedly (an OOM, a script reaching `process.exit` through the documented vm escape) settles the run `stopReason: 'error'` with the exit diagnostics — or `'cancelled'` when a cancel was in flight — and the host-side child registry is what winds every surviving child down. `dispose()` = cancel + immediate host-driven disposal of every registered child (a wedged worker can relay no dispose RPC, so child teardown overlaps the grace instead of starting after it; the worker's own dispose RPCs join the same per-child disposal) + bounded wait (result, then child-registry quiescence, capped by the grace) + unconditional `worker.terminate()`: the thread never outlives its run. Once a run settles, stray children a script fired without awaiting are cancelled too, and `dispose()` waits for their disposal (bounded by the grace) before returning. `agent-start`/`agent-end` pairing is host-guaranteed the same way: forwarded starts live in a ledger, worker-reported ends pair them on the graceful paths, and the termination paths (grace force-settle, worker death) synthesize the missing ends (outcome `cancelled`) before the run settles — a start still in flight across the force-settle can surface after `workflow/end`, immediately paired the same way. -**Engine-specific limitations**: worker startup is paid per run; on a termination path `agentsStarted` reports the HOST-observed count (accepted `child-start`s — calls still queued worker-side for a concurrency slot are unknowable then); and a returned promise or thenable resolves per JavaScript semantics BEFORE materialization — that is what makes an un-awaited `return agent('x')` work — with the value-boundary guard applying to the resolution. +A returned promise or thenable resolves per JavaScript semantics BEFORE materialization — that is what makes an un-awaited `return agent('x')` work — with the value-boundary guard applying to the resolution. ## Config @@ -51,3 +51,11 @@ A worker that dies unexpectedly (an OOM, a script reaching `process.exit` throug | `maxItemsPerCall` | `4096` | Items accepted by one `parallel()`/`pipeline()` call. | | `syncTimeoutMs` | `5000` | vm timeout for the script's initial synchronous slice (in the worker). | | `disposeGraceMs` | `5000` | How long a cancelled run may stay unsettled before force-settle + terminate; also bounds `dispose()`. | + +## Known Limitations and Deferred Work + +- **The worker/vm is not a security boundary** — model-written code can escape `node:vm` and reach the worker's process authority; a hostile-code deployment needs a separate-process or container engine. +- **One worker thread is paid per run** — there is no pool, warm runtime, or cross-run script cache. +- **No ambient timers, filesystem, or network are injected, but escaped code can still reach Node** — the missing globals are portability API, not containment. +- **Termination can only report host-observed starts** — `agentsStarted` excludes worker-side calls still queued behind concurrency when a forced termination makes them unknowable. +- **Cross-realm errors fail `instanceof Error` inside scripts** — workflow authors must branch on stable fields such as `name` and `code`. diff --git a/packages/workflow/workflow/README.md b/packages/workflow/workflow/README.md index 5332243a85..4c90239792 100644 --- a/packages/workflow/workflow/README.md +++ b/packages/workflow/workflow/README.md @@ -24,6 +24,12 @@ All observe-only emits carrying DATA SNAPSHOTS (`WorkflowRunInfo` = id + meta) - `workflow/phase`(info, title) / `workflow/log`(info, message) — script narration. - `workflow/agent-start`(info, agent) / `workflow/agent-end`(info, agent + outcome) — ready-child lifecycle correlated by `seq`; the [generated event contract](../../../docs/cordis-catalog/events.md#workflowagent-start--emit) defines publication and pairing. -## Non-goals (this cut) +## Known Limitations and Deferred Work -Background collection, journaling/resume, saved workflows, nested `workflow()`, token budgets — see the [RFC's deferred section](../../../docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md). +- **Foreground collection only** — the caller owns one live run and awaits it; background start/poll, spill handles, and detached collection are deferred. +- **No journaling or resume** — scripts, child progress, and intermediate values are not checkpointed, so a process restart cannot continue a run. +- **No saved or nested workflows** — the seam starts caller-supplied scripts only, and a workflow script receives no `workflow()` hook for recursive orchestration. +- **No token-budget vocabulary** — engines cap concurrency/items/agents, but neither the request nor result accounts for model tokens across children. +- **Runs are holder-owned, not service-tracked** — unloading the engine does not discover independent live handles; every consumer must dispose the run it started. + +See the [dynamic-workflows RFC](../../../docs/rfc/implemented/feature/2026-07-05-dynamic-workflows.md) for the deferred workflow surface. diff --git a/scripts/verify-readme-limitations.ts b/scripts/verify-readme-limitations.ts index 5a215a53fd..2d10dfe566 100644 --- a/scripts/verify-readme-limitations.ts +++ b/scripts/verify-readme-limitations.ts @@ -14,7 +14,9 @@ * package set, so a rename or removal fails loud instead of silently * un-gating a README. * - * Checks, per packages///README.md (fenced code excluded): + * The package set comes from `packages///package.json`, so a manifest with no + * sibling README fails instead of escaping a README-only glob. Checks, per + * package README (fenced code excluded): * 1. Non-whitelisted: exactly one limitations-like heading, byte-equal to the * canonical h2, with at least one top-level `- ` bullet before the next * heading. @@ -30,7 +32,7 @@ * Run: `tsx scripts/verify-readme-limitations.ts`. */ -import { globSync, readFileSync } from 'node:fs' +import { existsSync, globSync, readFileSync } from 'node:fs' import { resolve } from 'node:path' const root = resolve(import.meta.dirname, '..') @@ -43,21 +45,18 @@ const CANONICAL = '## Known Limitations and Deferred Work' * package directory relative to the repo root). Their READMEs must NOT carry * the section; adding one moves the package off this list in the same change. */ -const NO_LIMITATIONS: readonly string[] = [ - 'packages/support/subagent-mock', - 'packages/ui/app-boot', - 'packages/util/brand', - 'packages/util/timeout', -] +const NO_LIMITATIONS: Readonly> = { + 'packages/util/brand': 'Type-only nominal-branding primitive with no runtime behavior or deferred work.', +} /** A heading that reads as a limitations section — canonical or drifted. */ function isLimitationsLike(headingText: string): boolean { return ( - /known limitation/i.test(headingText) + /\blimitations?\b/i.test(headingText) || /deferred work/i.test(headingText) || /what is not here/i.test(headingText) - || /^limitations?\b/i.test(headingText) || /^deferred\b/i.test(headingText) + || /^non-goals?\b/i.test(headingText) ) } @@ -66,37 +65,52 @@ interface Line { raw: string } +const ATX_HEADING = /^ {0,3}#{1,6}[ \t]+/ + /** Split a README into prose lines (fenced code dropped), keeping 1-based line numbers. */ function proseLines(text: string): Line[] { - let inFence = false + let fence: { marker: '`' | '~'; length: number } | undefined const kept: Line[] = [] text.split('\n').forEach((raw, i) => { - if (raw.startsWith('```')) { - inFence = !inFence + const token = /^ {0,3}(`{3,}|~{3,})/.exec(raw)?.[1] + if (token !== undefined) { + const marker = token[0] as '`' | '~' + if (fence === undefined) { + fence = { marker, length: token.length } + } else if (marker === fence.marker && token.length >= fence.length) { + fence = undefined + } return } - if (!inFence) kept.push({ index: i + 1, raw }) + if (fence === undefined) kept.push({ index: i + 1, raw }) }) return kept } -const readmes = globSync('packages/*/*/README.md', { cwd: root }).sort() -const scannedPackages = new Set(readmes.map(path => path.slice(0, -'/README.md'.length))) +const packageJsons = globSync('packages/*/*/package.json', { cwd: root }).sort() +const scannedPackages = new Set(packageJsons.map(path => path.slice(0, -'/package.json'.length))) const failures: string[] = [] -for (const entry of NO_LIMITATIONS) { +for (const [entry, reason] of Object.entries(NO_LIMITATIONS)) { if (!scannedPackages.has(entry)) { failures.push(`whitelist entry ${entry} does not name a scanned package — renamed or removed? update NO_LIMITATIONS in scripts/verify-readme-limitations.ts in the same change`) } + if (reason.trim().length === 0) { + failures.push(`whitelist entry ${entry} has no justification — state why a limitations section would be empty boilerplate`) + } } -for (const readme of readmes) { - const pkg = readme.slice(0, -'/README.md'.length) +for (const pkg of scannedPackages) { + const readme = `${pkg}/README.md` + if (!existsSync(resolve(root, readme))) { + failures.push(`${readme}: package manifest has no sibling README with the \`${CANONICAL}\` section`) + continue + } const lines = proseLines(readFileSync(resolve(root, readme), 'utf8')) - const headings = lines.filter(line => /^#{1,6} /.test(line.raw)) - const limitations = headings.filter(line => isLimitationsLike(line.raw.replace(/^#{1,6}\s+/, ''))) + const headings = lines.filter(line => ATX_HEADING.test(line.raw)) + const limitations = headings.filter(line => isLimitationsLike(line.raw.replace(ATX_HEADING, ''))) - if (NO_LIMITATIONS.includes(pkg)) { + if (Object.hasOwn(NO_LIMITATIONS, pkg)) { for (const heading of limitations) { failures.push(`${readme}:${heading.index}: whitelisted as having no known limitations, but carries ${JSON.stringify(heading.raw)} — drop the section or remove the package from NO_LIMITATIONS`) } @@ -118,7 +132,7 @@ for (const readme of readmes) { } const headingAt = lines.indexOf(heading) const body = lines.slice(headingAt + 1) - const end = body.findIndex(line => /^#{1,6} /.test(line.raw)) + const end = body.findIndex(line => ATX_HEADING.test(line.raw)) const section = end === -1 ? body : body.slice(0, end) if (!section.some(line => /^- /.test(line.raw))) { failures.push(`${readme}:${heading.index}: the \`${CANONICAL}\` section has no top-level \`- \` bullet — state the limitations, or whitelist the package if there are genuinely none`) @@ -131,4 +145,4 @@ if (failures.length > 0) { process.exit(1) } -console.log(`verify-readme-limitations: ${readmes.length} package READMEs checked (${NO_LIMITATIONS.length} whitelisted), all conform.`) +console.log(`verify-readme-limitations: ${scannedPackages.size} package READMEs checked (${Object.keys(NO_LIMITATIONS).length} whitelisted), all conform.`) From 027970043c30048048433fea95e7e79a55bd7a4b Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:12:36 +0800 Subject: [PATCH 11/38] docs: document package model experience --- docs/AGENTS.md | 11 ++- docs/cookbook/adding-a-package.md | 5 +- docs/rfc/INDEX.md | 1 + ...07-12-package-model-experience-contract.md | 27 +++++++ package.json | 3 +- packages/AGENTS.md | 1 + packages/README.md | 2 +- packages/bash/bash-local/README.md | 6 ++ packages/bash/bash-sandbox/README.md | 7 ++ packages/bash/bash/README.md | 6 ++ packages/bash/tool-bash/README.md | 8 ++ .../code-runtime-worker/README.md | 6 ++ packages/code-runtime/code-runtime/README.md | 6 ++ packages/compact/compact-basic/README.md | 7 ++ packages/compact/compact/README.md | 6 ++ packages/cordis/tool-cordis/README.md | 8 ++ packages/core/agent-core/README.md | 7 ++ packages/core/agent-loop/README.md | 7 ++ packages/core/agent/README.md | 7 ++ packages/core/scope/README.md | 6 ++ packages/core/session/README.md | 7 ++ packages/core/system-prompt/README.md | 7 ++ packages/core/tools/README.md | 7 ++ packages/fs/fs-local/README.md | 6 ++ packages/fs/fs-policy/README.md | 6 ++ packages/fs/fs/README.md | 6 ++ packages/fs/tool-fs/README.md | 8 ++ packages/guard/repeat-tool-guard/README.md | 6 ++ packages/hooks/hook-protocol/README.md | 6 ++ packages/hooks/hooks-claude/README.md | 7 ++ packages/hooks/hooks-codex/README.md | 7 ++ packages/llm/llm-deepseek/README.md | 7 ++ packages/llm/llm-pi-ai/README.md | 7 ++ packages/llm/llm/README.md | 7 ++ packages/sandbox/sandbox-local/README.md | 6 ++ packages/sandbox/sandbox/README.md | 6 ++ .../session-persistence-jsonl/README.md | 6 ++ .../session-persistence-sqlite/README.md | 6 ++ .../session-persistence/README.md | 6 ++ packages/skill/skill-local/README.md | 6 ++ packages/skill/skill/README.md | 6 ++ packages/skill/tool-skill/README.md | 7 ++ packages/subagent/subagent-acp/README.md | 7 ++ packages/subagent/subagent-fork/README.md | 7 ++ .../subagent/subagent-inprocess/README.md | 7 ++ packages/subagent/subagent-spawn/README.md | 7 ++ .../subagent/subagent-subprocess/README.md | 6 ++ packages/subagent/subagent/README.md | 6 ++ packages/subagent/tool-subagent/README.md | 7 ++ packages/support/acp-snapshot/README.md | 6 ++ packages/support/invariants/README.md | 6 ++ packages/support/llm-replay/README.md | 6 ++ packages/support/subagent-mock/README.md | 6 ++ packages/timeout/timeout-policy/README.md | 6 ++ packages/todo/tool-todo/README.md | 7 ++ packages/ui/acp-agent/README.md | 6 ++ packages/ui/acp/README.md | 8 ++ packages/ui/app-boot/README.md | 6 ++ packages/ui/stdio-agent/README.md | 6 ++ packages/ui/tool-ask-user/README.md | 7 ++ packages/ui/user-approval/README.md | 7 ++ packages/ui/user-interaction/README.md | 6 ++ packages/util/brand/README.md | 6 ++ packages/util/timeout/README.md | 6 ++ packages/web/tool-web/README.md | 8 ++ packages/web/web-fetch-local/README.md | 6 ++ packages/web/web-search-deepseek/README.md | 7 ++ packages/web/web-search-exa/README.md | 6 ++ packages/web/web-search-perplexity/README.md | 7 ++ packages/web/web/README.md | 6 ++ packages/workflow/tool-workflow/README.md | 9 ++- .../workflow/workflow-workerthread/README.md | 7 ++ packages/workflow/workflow/README.md | 6 ++ scripts/run-gates.ts | 1 + .../verify-package-readme-model-experience.ts | 81 +++++++++++++++++++ 75 files changed, 561 insertions(+), 5 deletions(-) create mode 100644 docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md create mode 100644 scripts/verify-package-readme-model-experience.ts diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 6824c71cc6..239af47e48 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -15,7 +15,7 @@ Every fact has exactly one home — the tier whose job it is — and every other | [rfc/](rfc/README.md) | Decision records: the why and the what-was-given-up; `implemented/` RFCs describe shipped reality in present tense | Migration plans, test checklists, and spec-speak ("should…") once the decision has shipped | | [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — | | [cookbook/](cookbook/adding-a-package.md) | Step-by-step how-tos with numbered verify steps | Design rationale (→ the RFC each guide links) | -| Package README | The per-package contract: config, semantics, limitations, extension points | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns | +| Package README | The per-package contract: config, semantics, limitations, extension points, and [Model Experience](#package-model-experience) | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns | | [development.md](development.md) | First-stop contributor onboarding: local setup, daily workflow, and CI shape at summary level; a bilingual pair under the [i18n contract](i18n/README.md) | Runtime/version rationale (→ RFCs), gate-by-gate enumerations that drift from `package.json` scripts | | Generated catalogs: [cordis events](cordis-catalog/events.md), [cordis services](cordis-catalog/services.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind | | Skills (`.agents/skills/`) | Workflows: how to carry out a recurring task against the contracts | The contracts themselves (→ docs) | @@ -33,6 +33,15 @@ Placement test: a story about a bug → postmortem. Why we chose X → RFC. How - **Bilingual pairs update together**: editing either side obligates the counterpart and a re-record in the same change ([i18n contract](i18n/README.md)). - Your audience is professional programmers. Prefer concise and straight-forward English over metaphor. Do not overuse words like "gate", "vocabulary", "surface", "seams". +## Package Model Experience + +Every package README carries `## Model Experience` and this table: + +| Context surface | What the model sees | Token effect | +|---|---|---| + +Rows name the request surface and condition, including agent scope, then classify tokens as fixed per request, conditional per call, retained, replaced, capped, or zero-direct. Distinguish the conversation model from auxiliary calls. A zero-direct package names its indirect path. `verify-package-readme-model-experience` enforces shape; review owns accuracy. + ## Wordcount Budgets Every PR has a lesson it wants to append, and without pressure nothing leaves. [scripts/doc-budgets.manifest.json](../scripts/doc-budgets.manifest.json) stores the allowed word-count ceiling for each budgeted standing doc; `pnpm run verify-doc-budgets` fails when a doc exceeds its ceiling or a budgeted file is missing. diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 1ab6931696..5a17e572d2 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -13,9 +13,11 @@ packages/// # you use Config, + ../..// for each dsh dep) src/index.ts # service default export or plugin (name/inject/apply/Config) tests/.spec.ts - README.md # service API, events, extension points, design notes + README.md # contract plus the required Model Experience table ``` +Fill the canonical [Model Experience table](../AGENTS.md#package-model-experience) from the implementation: name every direct request contribution and token-growth condition, or state zero direct tokens and the exact indirect path. Every package participates, including type-only libraries and backend seams. + Choose an existing group when one matches the package's role (`core`, `llm`, `bash`, `compact`, `subagent`, `todo`, `session-persistence`, `ui`, `util`, or `support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it. package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list is precise: `lib/index.js`, `lib/types/**/*.d.ts`, `lib/types/**/*.d.ts.map`, and `src`; do not publish `lib/types` JS or JS-map intermediates or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`. @@ -41,6 +43,7 @@ For a swappable capability, split interface / implementation / consumer into sep ```sh pnpm install # registers the workspace +pnpm run verify-package-readme-model-experience pnpm run constraints && pnpm run typecheck && pnpm run lint pnpm run test:coverage # 100% per-file over src (types.ts exempt) pnpm run build && pnpm run hygiene diff --git a/docs/rfc/INDEX.md b/docs/rfc/INDEX.md index 654b68ffb5..6f684de03e 100644 --- a/docs/rfc/INDEX.md +++ b/docs/rfc/INDEX.md @@ -161,6 +161,7 @@ Generated by `pnpm run gen-rfc-index` from the RFC tree — never edit by hand; | [Raise the Node LTS engine floor to 22.19](implemented/process/2026-07-06-node-engine-floor.md) | 2026-07-06 | | [Parallel GitHub CI gates](implemented/process/2026-07-06-parallel-github-ci-gates.md) | 2026-07-06 | | [Parallel pre-push gates](implemented/process/2026-07-06-parallel-pre-push-gates.md) | 2026-07-06 | +| [Package Model Experience contract](implemented/process/2026-07-12-package-model-experience-contract.md) | 2026-07-12 | ### Testing diff --git a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md new file mode 100644 index 0000000000..755bb5a80b --- /dev/null +++ b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md @@ -0,0 +1,27 @@ +# RFC: Package Model Experience contract + +Status: implemented + +## Problem + +A package README can explain APIs and runtime mechanics without answering the question that dominates an agent harness's behavior and cost: what from this package reaches a model request, under which conditions, and how long those tokens remain. The omission is especially hard to audit in a plugin architecture. A consumer may turn a backend result into a tool message, a policy plugin may replace success with an error, compaction may remove old history, and an agent-scoped registration may change one agent's prompt or schemas while leaving every other agent unchanged. Reading only the nominally model-facing packages therefore misses real context effects, while reading source across every dependency is too expensive for routine review. + +## Decision + +Every workspace package README carries the canonical [Model Experience table](../../../AGENTS.md#package-model-experience). Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. + +Every package participates. A service seam, storage backend, test helper, or type-only library that contributes no prompt text, tool schema, message, or auxiliary request records zero direct tokens and names the consumer or control path through which it can still change model-visible material. This explicit negative contract prevents readers from having to infer whether the section was forgotten. + +`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README, one exact `## Model Experience` heading, the canonical three-column table header, and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns shape and completeness; implementation review owns the truth of the prose. + +## Alternatives considered + +- **Document only packages that register prompts or tools** — rejected because backends, policy plugins, adapters, persistence, scoping, and compaction change the content or lifetime of tokens without owning a model-facing schema. +- **Generate one central context-cost catalog from source** — rejected because an AST can find registrations but cannot infer semantic conditions such as history retention, output truncation, parent-versus-child visibility, or an auxiliary model boundary. The package README is the implementation-local contract; a central copy would add another drift surface. +- **Require numeric token counts** — rejected because exact counts depend on the selected model tokenizer, adapter serialization, configuration, and runtime data. The stable contract is the growth shape: fixed per request, conditional per call, retained, replaced, capped, or zero-direct. +- **Allow zero-impact packages to omit the section** — rejected because absence is ambiguous between an audited zero and forgotten documentation. One explicit row is cheap and mechanically distinguishable. +- **Convention without a gate** — rejected because a repo-wide contract must also cover every future package; review memory cannot reliably detect an omitted README section. + +## Consequences + +A reviewer can start at any package and see its contribution to the conversation model, child models, and auxiliary calls without reconstructing the full plugin graph. Token-budget work can distinguish repeated request overhead from data-dependent history, and agent-scoped changes have an explicit documentation checkpoint. Package authors pay for one small table and must update it whenever model-visible behavior changes. The table deliberately does not promise provider-exact token counts; measurements remain model- and workload-specific, while the documented growth and visibility contract stays stable. diff --git a/package.json b/package.json index ed0b603feb..f4428f0df0 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "verify-md-links": "tsx scripts/verify-md-links.ts", "verify-doc-refs": "tsx scripts/verify-doc-refs.ts", "verify-package-paths": "tsx scripts/verify-package-paths.ts", + "verify-package-readme-model-experience": "tsx scripts/verify-package-readme-model-experience.ts", "verify-mermaid": "tsx scripts/verify-mermaid.ts", "verify-rfc-classification": "tsx scripts/verify-rfc-classification.ts", "verify-rfc-format": "tsx scripts/verify-rfc-format.ts", @@ -63,7 +64,7 @@ "verify-scoped-dispatch": "tsx scripts/verify-scoped-dispatch.ts", "verify-module-graph": "tsx scripts/gen-module-graph.ts --check", "constraints": "tsx scripts/check-workspace-constraints.ts", - "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-cordis-api && pnpm run verify-export-jsdoc && pnpm run verify-tool-catalog && pnpm run verify-config-catalog && pnpm run verify-persistence-catalog && pnpm run verify-doc-graphs && pnpm run verify-scoped-dispatch && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-mermaid && pnpm run verify-rfc-classification && pnpm run verify-rfc-format && pnpm run verify-type-equiv && pnpm run verify-translation-pairing && pnpm run verify-doc-budgets", + "doc-sync": "pnpm run doc-typecheck && pnpm run verify-cordis-catalog && pnpm run verify-cordis-api && pnpm run verify-export-jsdoc && pnpm run verify-tool-catalog && pnpm run verify-config-catalog && pnpm run verify-persistence-catalog && pnpm run verify-doc-graphs && pnpm run verify-scoped-dispatch && pnpm run verify-md-wrap && pnpm run verify-md-links && pnpm run verify-doc-refs && pnpm run verify-package-paths && pnpm run verify-package-readme-model-experience && pnpm run verify-mermaid && pnpm run verify-rfc-classification && pnpm run verify-rfc-format && pnpm run verify-type-equiv && pnpm run verify-translation-pairing && pnpm run verify-doc-budgets", "hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types", "demo:echo": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/echo-agent/cordis.yml", "demo:repl": "node --expose-internals --import tsx packages/ui/stdio-agent/src/bin.ts examples/coding-agent/cordis.yml", diff --git a/packages/AGENTS.md b/packages/AGENTS.md index ac26b926f7..2ae11e83ae 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -12,5 +12,6 @@ Naming notes: - `src/types.ts` contains only types — no runtime code. - Tests live at package level under `tests/`, not `src/__tests__/`. - A package's README and JSDoc are part of the change: altered behavior (config keys, defaults, error codes, wire fields) updates them in the same commit. `doc-sync` gates what it can; prose accuracy stays on the author ([the documentation standard](../docs/AGENTS.md)). +- Every package README carries the [Model Experience table](../docs/AGENTS.md#package-model-experience); update it when code changes a prompt section, session prefix, tool schema/result, history rewrite, auxiliary model call, or indirect visibility policy. Read the per-package README.md for package-specific details: service API, events, extension points, TODOs. diff --git a/packages/README.md b/packages/README.md index 35c4652a1f..651ba370e6 100644 --- a/packages/README.md +++ b/packages/README.md @@ -37,4 +37,4 @@ The inter-package dependency graph is generated: [docs/module-graph.md](../docs/ The rule it must obey: **extension plugins depend on interfaces, never on the concrete loop.** `dsh-agent-loop` is swappable — UI/hook/tool plugins keep working against the `dsh-agent` vocabulary if the loop is replaced. The sanctioned exception is a **composition/bundle** package like `dsh-agent-core`, whose whole job is to assemble the concrete spine: it depends on `dsh-agent-loop` (and the other concrete spine plugins) on purpose. The rule constrains plugins that EXTEND the system, not the bundle that COMPOSES it. A swappable capability splits into interface / implementation / consumer packages (the bash trio is the template — see [capability seams](../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)). -Each package has its own `README.md` with purpose, service API, events, extension points, and deliberate non-goals (TODOs). +Each package has its own `README.md` with purpose, service API, events, extension points, deliberate non-goals, and the standard [Model Experience table](../docs/AGENTS.md#package-model-experience). diff --git a/packages/bash/bash-local/README.md b/packages/bash/bash-local/README.md index 1ac8a7d06b..8643fd2652 100644 --- a/packages/bash/bash-local/README.md +++ b/packages/bash/bash-local/README.md @@ -2,6 +2,12 @@ Local-subprocess implementation of the `@deepseek-ai/dsh-bash` executor seam: `LocalBashExecutor` spawns `bash -c ` per call in its own process group, collects bounded output with full-stream spill files, and escalates kills SIGTERM→SIGKILL across the whole group. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the retained stdout and stderr tail, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. | + ## Config ```yaml diff --git a/packages/bash/bash-sandbox/README.md b/packages/bash/bash-sandbox/README.md index 2a5ae63b8e..fc83753774 100644 --- a/packages/bash/bash-sandbox/README.md +++ b/packages/bash/bash-sandbox/README.md @@ -31,3 +31,10 @@ Deny-only at the seam: a denial is a reported fact, and this executor never nego ``` The keyless consumer-integration proofs are `tests/bwrap.e2e.ts`, `tests/landlock.e2e.ts`, and `tests/seatbelt.e2e.ts` (the real provider + real runner driven through `ctx.bash`, world-verified, each self-skipping where its runner is absent); see [`examples/sandbox-acp-agent`](../../../examples/sandbox-acp-agent/) for the runnable demo. + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt, indirectly | By advertising a confining `sandboxMode`, this backend makes `dsh-tool-bash` state the calling session's effective mode and expose escalation fields. The backend itself adds no prose. | Small fixed per-request cost through the consumer, plus a retained notice when the session mode changes. | +| Bash tool result, indirectly | The model sees ordinary bounded command output plus denial markers, the mode used, and sandbox-unavailable failures shaped by `dsh-tool-bash`; runner details stay internal. | Zero additional tokens on an unremarkable allowed run beyond ordinary output. Denial or failure adds a small conditional marker or error retained until compaction. | diff --git a/packages/bash/bash/README.md b/packages/bash/bash/README.md index ac7e00c3a3..34accb1c69 100644 --- a/packages/bash/bash/README.md +++ b/packages/bash/bash/README.md @@ -13,6 +13,12 @@ This package is the interface quarter of the bash capability, split so each conc The split mirrors the LLM seam (`LlmService`/`LlmAdapter`) and the agent-tool survey: pi hides execution behind a `BashOperations` interface (local shell / SSH / VM backends), Codex behind an exec-server protocol. `dsh-bash-sandbox` is exactly that swap in action — a sandboxing executor behind the same interface, tool schemas untouched; a containerized or remote executor slots in the same way. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This interface registers no prompt, tool schema, or message. `dsh-tool-bash` turns an implementation's stdout, stderr, task state, and sandbox facts into model-visible tool results and guidance. | Zero direct tokens. Result size and sandbox state affect input tokens only when a consumer renders them. | + ## Service API (`ctx.bash`) | Member | Semantics | diff --git a/packages/bash/tool-bash/README.md b/packages/bash/tool-bash/README.md index 800de0132c..67007bc5cc 100644 --- a/packages/bash/tool-bash/README.md +++ b/packages/bash/tool-bash/README.md @@ -6,6 +6,14 @@ Requires a loaded executor implementation (e.g. `@deepseek-ai/dsh-bash-local`); The plugin also contributes the `tool:bash` prompt section (order 105) — the cross-call habit the per-tool descriptions cannot carry: check the `[exit code: N]` marker on every result and investigate failures before moving on. Under a sandboxing executor it additionally contributes the per-agent `env:bash-sandbox` section (order 110) stating each session's EFFECTIVE mode, and the pre-step narrator — see [Per-session mode](#per-session-mode-switching-and-visibility). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Every request for an agent that can see these tools carries the short `tool:bash` exit-code instruction. With a sandboxing executor it also carries that session's effective sandbox mode, plus a logged context notice after a mode change. | Small fixed input cost per request; a mode-change notice is conditional and then remains in conversation history. | +| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. | +| Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. | + ## Tools ### `bash` diff --git a/packages/code-runtime/code-runtime-worker/README.md b/packages/code-runtime/code-runtime-worker/README.md index f691904e88..a2ebd2f496 100644 --- a/packages/code-runtime/code-runtime-worker/README.md +++ b/packages/code-runtime/code-runtime-worker/README.md @@ -2,6 +2,12 @@ Worker-thread implementation of the [`@deepseek-ai/dsh-code-runtime`](../code-runtime/README.md) seam: `WorkerCodeRuntime` runs each program in ONE fresh Node `worker_threads.Worker` — TypeScript in, type-stripped host-side, bindings bridged over the message port, `{ value, logs, error? }` out. **Containment, not a security boundary**: trust posture is bash-equivalent by design (the [Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md) § Trust posture), with containment bash does not have — separate isolate, empty environment, heap cap, hard termination. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| `run_code` result, indirectly | The conversation model sees only what the model-written program prints or returns, or a shaped failure; binding-call traffic and worker internals stay outside its context. This backend contributes no schema or prompt itself. | Zero tokens until Code Mode executes a program. `maxLogBytes` and `maxValueBytes` cap the model-visible result, which then remains in tool history until compaction. | + ## Config ```yaml diff --git a/packages/code-runtime/code-runtime/README.md b/packages/code-runtime/code-runtime/README.md index 20c9274b9c..4c66881683 100644 --- a/packages/code-runtime/code-runtime/README.md +++ b/packages/code-runtime/code-runtime/README.md @@ -4,6 +4,12 @@ The **code-execution seam**: an abstract `CodeRuntime` service (`ctx.codeRuntime This package is the interface third of the capability (the bash trio is the template — see [capability seams](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)): implementations subclass `CodeRuntime` and register the service; the consumer is the tool registry's Code Mode, which generates the model-facing SDK and bridges tool dispatch — both specified in the [Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md), whose first implementation is a Node worker-thread backend. The runtime knows nothing about tools or sessions: it is handed named async functions and a program string, and everything tool-shaped stays with the consumer. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The seam receives a program and host bindings but registers no prompt, schema, or message. Code Mode in `dsh-tools` exposes the SDK and `run_code`, then converts `CodeRunResult` into the outer tool result. | Zero direct tokens. Program logs, values, and failures affect the conversation only through the Code Mode consumer. | + ## Service API (`ctx.codeRuntime`) | Member | Semantics | diff --git a/packages/compact/compact-basic/README.md b/packages/compact/compact-basic/README.md index a06e74b818..3b639802b6 100644 --- a/packages/compact/compact-basic/README.md +++ b/packages/compact/compact-basic/README.md @@ -4,6 +4,13 @@ The **basic compaction backend**: a `BasicCompactService` implementing the `@dee This is the implementation tier of the compaction capability — see the [interface package](../compact/README.md) for the seam and the [capability-seam RFC](../../../docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md) for the design. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conversation history | Before a step whose estimated system prompt, session prefix, and history exceed the threshold, the conversation model receives one framed summary checkpoint in place of the older balanced surface range, followed by the retained recent units. | The replacement reduces future input history rather than appending a second copy. The summary remains until a later compaction replaces it; one oversized indivisible unit can still exceed the budget. | +| Auxiliary summarizer request | The summarization model sees a fixed checkpoint-writing system instruction and a flattened transcript of the selected range. The conversation model never sees this private request or its reasoning; only returned text is stored. | This is a separate model call with data-dependent input and `maxTokens`-capped output. Convergence retries can pay this cost more than once. | + ## What it owns The abstract contract states only WHAT compaction does; this backend owns every HOW decision: diff --git a/packages/compact/compact/README.md b/packages/compact/compact/README.md index e98f00899f..5842187e02 100644 --- a/packages/compact/compact/README.md +++ b/packages/compact/compact/README.md @@ -12,6 +12,12 @@ This package is the interface tier of the compaction capability, split so each c Unlike the bash seam, this interface depends on `@deepseek-ai/dsh-session` and `@deepseek-ai/dsh-llm` — the contract's verbs are defined over a `Session` and its output is the `ContentBlock` vocabulary, so they cannot be expressed without naming those packages. That deviation from the "interface depends only on cordis" guidance is intentional and recorded in the [compaction capability-seam RFC](../../../docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conversation history, when a backend is invoked | A successful implementation replaces an older surface range with one user-role summary checkpoint; the raw events stay logged but stop appearing in derived model messages. The seam itself performs no rewrite. | Zero direct tokens from this interface. A backend trades many retained history tokens for one summary and leaves the recent tail unchanged. | + ## Service API (`ctx.compact`) Both methods are **abstract** — the backend owns the entire strategy (token estimation, retention policy, event sequencing, summarization). diff --git a/packages/cordis/tool-cordis/README.md b/packages/cordis/tool-cordis/README.md index c4f3cf11b9..45fefda540 100644 --- a/packages/cordis/tool-cordis/README.md +++ b/packages/cordis/tool-cordis/README.md @@ -2,6 +2,14 @@ The self-referential cordis toolset: three model-facing tools over the live runtime the agent runs inside. Design home — sandbox semantics, mount lifecycle, cross-mount composition, the generated API catalog, standing decisions: [the toolset RFC](../../../docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schemas | The conversation model sees `cordis_inspect`, `cordis_mount`, and `cordis_unmount` whenever this plugin is visible. | Fixed schema cost on every request in that tool view. | +| Tool-call history and results | Inspect returns selected live services, plugins, tools, or generated API and event references; mount and unmount return lifecycle facts or structured errors. The submitted mount program remains in the assistant tool-call history. | Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small. | +| Later requests after a mount | A mounted plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; unmount removes those contributions after quiescence. | Indirect token impact equals the mounted plugin's contributions and lasts only for the mount lifetime. | + ## What it does - `cordis_inspect` — read-only report over the runtime: services, the loaded-plugin list, registered tools, the dynamic-mount table, and the catalog-backed `api` / `events` references. diff --git a/packages/core/agent-core/README.md b/packages/core/agent-core/README.md index 33cd0faddd..1cc957e82b 100644 --- a/packages/core/agent-core/README.md +++ b/packages/core/agent-core/README.md @@ -4,6 +4,13 @@ The **default executor-less, UI-less agent spine** as ONE Cordis bundle plugin. This is the package to read to see **the whole plugin tree at once** — the teaching role the inlined `echo-agent` `cordis.yml` used to play before the spine moved behind this bundle. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed system prompt and session prefix | Through its children, the bundle supplies the harness identity, configured persona, and the local skill catalog when skills exist. | The bundle adds no wrapper prose; input cost is exactly the sum of the child contributions, repeated on each request according to their lifecycles. | +| Composed tool surface | The `skill` schema is present, and the three bash schemas appear when a bash executor activates `dsh-tool-bash`; `tools` config can select normal, Code Mode, or both. | Fixed per-request schema or SDK cost for the visible composition. Tool results add data-dependent retained history. | + ## The tree it loads `apply(ctx, config)` mounts each of these as a child of the bundle fiber: diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 217ba0a643..df44a6dd05 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -4,6 +4,13 @@ THE concrete agent plugin: `ReactLoopAgent` and the loop driver. Implements the This is the only package in the harness that contains concrete loop logic. Everything else is an abstract service or a plugin against extension seams — new behavior goes into plugins, not here. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Complete conversation request | For each step, the loop sends the rendered per-agent system prompt, visible tool schemas, the frozen session prefix, and the session's derived messages. It supplies `model` and `cwd` variable values but no additional fixed prose. | System text, schemas, and prefix are paid again on every step. Per-agent scoping can substitute or remove individual contributions. | +| Retained message history | Accepted user messages, assistant messages, tool calls and results, injected context, and steering are logged and sent on later steps. Raw stream chunks, lifecycle boundaries, and other log-only events are excluded. | Input grows with every surface message until a compaction replacement shadows older nodes; a multi-step tool turn resends the accumulated prefix and history each step. | + ## Service: `AgentLoop` (ctx key: `agentLoop`) ### Public API diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index 71e2be279d..e66f4dae6d 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -2,6 +2,13 @@ Agent interface, registry, and `agent/*` event vocabulary. Every plugin (UI, hooks, orchestrators) programs against the `Agent` handle defined here — it has zero loop dependency, so the loop is swappable. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| User, steering, and injected messages | `send`, `steer`, and `inject` feed the owning session. `agent/prompt-submit`, `agent/session-prefix`, and other declared events let plugins block a prompt or add request material; this interface contributes no fixed prose itself. | Accepted content becomes retained history or a repeated session prefix; blocked content contributes no request tokens. Size is caller- and plugin-dependent. | +| Agent-scoped request composition | Registrations through `agent.ctx` can shadow prompt sections or tools and can install agent-only interceptors during unpublished setup. | The package adds zero tokens itself; scoped contributions affect only that agent and disappear on disposal. | + ## Service: `AgentRegistry` (ctx key: `agents`) Tracks live agents so UI, hook, and orchestrator plugins can find them without importing the concrete loop package. diff --git a/packages/core/scope/README.md b/packages/core/scope/README.md index e57f475816..887689d375 100644 --- a/packages/core/scope/README.md +++ b/packages/core/scope/README.md @@ -2,6 +2,12 @@ Scoped-context registration primitive. `createScope(ctx, key)` mints a Cordis context that TAGS everything registered through it with an opaque `ScopeKey` and OWNS those registrations' lifetime (one backing fiber drives both facts); `scopeOf(ctx)` reads the tag; `scopeTarget(base, key)` builds the dispatch carrier that makes an event scope-filtered — listeners registered through a scoped context fire only for their key's subject, while plain plugin listeners keep firing for every subject. The agent loop is the one scope minter today (one scope per live agent, key = the `Agent` object — the `Agent.ctx` contract in `dsh-agent`), but the mechanism is key-agnostic so packages below the agent layer (`dsh-session`, `dsh-system-prompt`) depend on it without a dependency cycle. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Per-agent visibility control | This package emits no text or schema. It decides whether agent-scoped prompt sections, variables, tools, restrictions, and listeners apply to one agent, can shadow same-named global contributions, and removes them with that agent. | Zero direct tokens. It can add, replace, or remove whole contributions for one agent without changing another agent's request. | + ## Public API - `createScope(ctx: Context, key: ScopeKey): Scope` Mint a scope under `ctx`'s fiber. Usable synchronously (effect collection is uid-gated; service resolution falls through to the minting plugin's dependency surface). Throws on a primitive key, or when `ctx`'s fiber is disposing (`INACTIVE_EFFECT`). diff --git a/packages/core/session/README.md b/packages/core/session/README.md index 094c3073df..7307689e7b 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -2,6 +2,13 @@ Event-sourced session log and in-memory store. A `Session` is the append-only source of truth for an agent's whole interaction history — the LLM message history is *derived* from it. A **surface** layer (a linked list of message-producing events) is maintained on top of the raw log for efficient derivation and compaction. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Derived message history | The model receives projections of `user/message`, `assistant/message`, `tool/result`, `context/message`, and `steering/message` surface nodes. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message. | Appended surface nodes are resent on later steps. A `replace` surface operation removes the shadowed nodes from future inputs without deleting their raw log records. | +| Logged request header | The session reconstructs the system prompt, tool schemas, call config, and session prefix that the loop actually sent. Header events do not add a second copy to message history; the prefix is prepended outside `deriveMessages()`. | Zero duplicate tokens from logging. The reconstructed prefix, system text, and schemas still incur their normal per-request cost. | + ## Service: `SessionStore` (ctx key: `sessions`) Creates and holds event-sourced `Session` instances. Persistence is intentionally not implemented here — plugins subscribe to `session/event` and flush on `session/flush`. diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index d712cd4b5b..648df82b74 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -2,6 +2,13 @@ System prompt assembly registry. Plugins contribute ordered text sections, tool-schema providers, named prompt variables, and authoritative named protections; the agent loop calls `assemble(context)` once per step, and `renderPrompt(assembly)` is the full system prompt the model sees. The plugin registers the harness-owned openers itself — the static `harness:identity` section and the deployment's `deployment:persona` section — so they exist for every agent regardless of which loop plugin drives it. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Every assembly starts with `You are an AI agent powered by the DeepSeek Harness SDK.`, then the configured persona and ordered plugin sections after strict variable interpolation. Empty sections disappear; scoped sections and variables can shadow globals for one agent. | Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content. | +| Tool schemas | The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent; reordering changes cache shape but not semantic content. | + ## Config | Key | Default | Meaning | diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index 2a678408e0..bf8e92b3e6 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -2,6 +2,13 @@ Tool registry and execution pipeline. Tool plugins register their schemas and executors; the agent loop executes each call through `tools/pre-execute` (the extensible allow/deny gate) → monotonic registered guards → `tools/execute` (an around-dispatch wrapper for timeout/retry/metrics plugins) → `tools/post-execute` (inspect/replace the result, attach context) → the observe-only `tools/result` notification. The registry also owns HOW its tools are presented to the model — its `mode` config selects native function calling, [Code Mode](#code-mode), or both. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead protects one `run_code` wire schema and adds a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's set. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | +| Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | + ## Service: `ToolRegistry` (ctx key: `tools`) ### Config diff --git a/packages/fs/fs-local/README.md b/packages/fs/fs-local/README.md index 26524fad24..4cd2d6cb7e 100644 --- a/packages/fs/fs-local/README.md +++ b/packages/fs/fs-local/README.md @@ -10,6 +10,12 @@ await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) // freshness policy gate and @deepseek-ai/dsh-tool-fs to expose read/write/edit. ``` +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Filesystem tool results, indirectly | Through `dsh-tool-fs`, the model sees line-windowed UTF-8 file content, mutation acknowledgements, or structured filesystem errors. Real paths, versions, atomic-write mechanics, and directory metadata remain internal unless a consumer renders them. | Zero direct tokens. Read tokens are bounded by the tool's line, line-length, and byte caps; mutation results are small and remain in history until compaction. | + ## Behavior - **`resolve(path, opts?)`** — a relative `path` resolves against `opts.cwd` when the caller supplies one (the model-facing tools pass the calling agent's session cwd — see [the per-session cwd RFC](../../../docs/rfc/implemented/architecture/2026-07-02-fs-per-session-cwd.md)), else `config.cwd` (default `process.cwd()`); an absolute `path` ignores both. The `targetKey` is the file's `realpath`, so two input paths reaching the same file through symlinks share one identity, and writes/edits land on the link target (preserving the link). A not-yet-existing path uses the realpathed parent directory plus basename when the parent exists; only an unresolvable parent falls back to the absolute path. `displayPath` is the absolute (un-resolved) path. diff --git a/packages/fs/fs-policy/README.md b/packages/fs/fs-policy/README.md index ad912bfc95..01cc8a05b0 100644 --- a/packages/fs/fs-policy/README.md +++ b/packages/fs/fs-policy/README.md @@ -16,6 +16,12 @@ declare const ctx: Context await ctx.plugin(FsPolicy) ``` +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Filesystem tool outcome | This plugin adds no prompt or schema. It can turn an unobserved or stale write or edit into a structured `FS_NOT_OBSERVED` or `FS_STALE_VERSION` error result instead of a success; observation state itself is never shown. | Zero tokens on allowed operations beyond the ordinary tool result. A denial adds a small retained error result and avoids any success payload. | + ## The four-layer split | Layer | Package | Role | diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index ac2866802b..e492af2e3e 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -13,6 +13,12 @@ This package is the provider-seam layer of the four-layer filesystem stack, spli A future sandboxed, virtual, or remote backend implements this interface and the policy/tool layers don't change. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The provider seam registers no prompt or tool. `dsh-tool-fs` converts provider text and structured `FsError` values into model-visible read, write, and edit results; policy listeners can change which outcome it receives. | Zero direct tokens. File content and errors enter context only through a consumer, whose window and byte caps determine result size. | + ## Service API (`ctx.fs`) A backend subclasses `FileSystem` and implements seven primitives. diff --git a/packages/fs/tool-fs/README.md b/packages/fs/tool-fs/README.md index fedd1ff7a2..732d9d5ddc 100644 --- a/packages/fs/tool-fs/README.md +++ b/packages/fs/tool-fs/README.md @@ -11,6 +11,14 @@ await ctx.plugin(ToolFs) // this package — re `@deepseek-ai/dsh-fs-policy` is **optional**: omit it and the tools run against the bare provider (unconditional write/overwrite/edit, no observed-state). A deployment that loads these tools is expected to also load it, so the behavior is read-before-write/edit. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Visible agents receive three short sections explaining line-windowed reads, whole-file writes, literal edits, and the default read-before-mutate habit. | Fixed guidance cost per request while the tools are visible. | +| Tool schemas | The model sees `read`, `write`, and `edit` with their snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. | +| Tool-call history and results | Read returns numbered UTF-8 lines and a pagination or cap footer; write and edit return concise success text or structured errors. The model-emitted write or edit content also remains in the assistant tool-call arguments. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`. Call arguments and results are resent until compaction, so large write payloads can dominate history even though the success result is small. | + ## Config All keys are optional; the defaults are the shipped read caps. diff --git a/packages/guard/repeat-tool-guard/README.md b/packages/guard/repeat-tool-guard/README.md index dc385bc033..11975af2bb 100644 --- a/packages/guard/repeat-tool-guard/README.md +++ b/packages/guard/repeat-tool-guard/README.md @@ -2,6 +2,12 @@ An advisory loop-breaker, not a model-facing tool: it never appears in the tool list, never vetoes or rewrites a call, and adds exactly one behavior — it watches each agent's stream of tool calls, counts runs of consecutive calls to the same tool with identical canonicalized arguments, and at configured run lengths injects an escalating advisory reminder telling the model to stop repeating itself, re-read the last result, and either change approach or conclude. The decision (retry differently, gather more evidence, or finish) stays entirely with the model: a legitimately repeated call is delayed by nothing and blocked by nothing. Decision record: [the repeat-tool-guard RFC](../../../docs/rfc/implemented/feature/2026-07-08-repeat-tool-guard.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conditional context message | At configured consecutive-repeat thresholds, that agent receives a source-attributed synthetic user reminder after the tool results, asking it to inspect the prior result and change approach or finish. No tool schema or normal-call text is added. | Zero tokens before a threshold. Each reminder is retained history; the detailed form caps the quoted canonical arguments at `argumentsPreviewChars`, while agents keep independent counters. | + ## Config ```yaml diff --git a/packages/hooks/hook-protocol/README.md b/packages/hooks/hook-protocol/README.md index 8296821503..2ac7067d14 100644 --- a/packages/hooks/hook-protocol/README.md +++ b/packages/hooks/hook-protocol/README.md @@ -4,6 +4,12 @@ The **shared core** of the Claude Code / Codex hook wire protocol. NOT a cordis Why a shared lib at all: Codex deliberately reimplements a *subset* of the Claude Code hook protocol — the same `hooks.json` matcher-group shape, the same exit-code/stdout output contract, the same command-hook execution model. The genuinely-shared parts live here; each bridge owns only what differs. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This library registers nothing. Its `hook/invoked` and `hook/result` events are log-only and do not enter derived messages; bridge packages decide whether parsed `additionalContext`, blocks, or continuation feedback reach the model. | Zero direct tokens. Persisted hook audit records add no context tokens. | + ## What's shared (here) vs. per-dialect (the bridges) | Concern | Here (`dsh-hook-protocol`) | The bridge (`dsh-hooks-claude` / `-codex`) | diff --git a/packages/hooks/hooks-claude/README.md b/packages/hooks/hooks-claude/README.md index f97cdb5cfc..eace3116cd 100644 --- a/packages/hooks/hooks-claude/README.md +++ b/packages/hooks/hooks-claude/README.md @@ -4,6 +4,13 @@ A cordis plugin that runs a user's existing **Claude Code** hook config (a `hook A native cordis plugin could do everything this bridge does — more powerfully, with typed returns and no serialization boundary. **The bridge exists only to run UNMODIFIED external CC hooks faithfully**; anything bespoke should be a native plugin on the same seams (see [the interception-seams RFC](../../../docs/rfc/implemented/feature/2026-06-30-interception-seams.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Hook-provided context | `SessionStart`, accepted prompt, post-tool, and live in-process subagent-start hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. Remote-child injection has no local target. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent in later conversation requests until compaction. | +| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny or ask before a tool, block a post-tool result with feedback, or force another model step. `systemMessage` and `updatedInput` are logged or warned but are not model-visible in this implementation. | Blocking a prompt removes that prompt's request tokens; denial or feedback adds a retained error or context result; forced continuation pays another full request. | + ## Config ```ts diff --git a/packages/hooks/hooks-codex/README.md b/packages/hooks/hooks-codex/README.md index b3fbc39928..6ac33a46fb 100644 --- a/packages/hooks/hooks-codex/README.md +++ b/packages/hooks/hooks-codex/README.md @@ -12,6 +12,13 @@ Codex's hook protocol is a deliberate **subset** of Claude Code's (same `hooks.j A native cordis plugin could do everything this bridge does, more powerfully; the bridge exists only to run UNMODIFIED external Codex hooks faithfully (see [the interception-seams RFC](../../../docs/rfc/implemented/feature/2026-06-30-interception-seams.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Hook-provided context | `SessionStart`, accepted prompt, and post-tool hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent until compaction. | +| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny a tool, block a post-tool result with feedback, or force another model step. Codex `systemMessage` is not surfaced. | Blocking a prompt removes its request tokens; denial or feedback adds retained result text; forced continuation pays another full request. | + ## Config ```ts diff --git a/packages/llm/llm-deepseek/README.md b/packages/llm/llm-deepseek/README.md index 15fad9f881..00eecc7e89 100644 --- a/packages/llm/llm-deepseek/README.md +++ b/packages/llm/llm-deepseek/README.md @@ -4,6 +4,13 @@ DeepSeek chat-completions adapter for the harness LLM seam: hand-rolled `fetch` A second, independent implementation of the same seam exists in `@deepseek-ai/dsh-llm-pi-ai` (library-backed). Same Config shape — pick one per context (registering both for the same model names throws by design). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| DeepSeek request | The selected DeepSeek model receives the harness system prompt, message history, tool schemas, stop sequences, and call config without adapter-authored prompt prose. On a prior assistant turn with tool calls, its reasoning content is passed back as required; reasoning from tool-call-free turns is omitted. | Provider tokenization governs exact input. Conditional reasoning passback increases tool-round-trip context, while dropping other reasoning avoids paying those tokens again; cache-read usage is reported when available. | +| DeepSeek response | Reasoning, text, and raw-string tool arguments are translated into harness chunks for the loop to log and assemble. | Generated tokens follow provider thinking and effort settings plus the request's `maxTokens`; only loop-retained blocks affect later input. | + ## Config ```yaml diff --git a/packages/llm/llm-pi-ai/README.md b/packages/llm/llm-pi-ai/README.md index f74ccd2246..ee4a4b32b4 100644 --- a/packages/llm/llm-pi-ai/README.md +++ b/packages/llm/llm-pi-ai/README.md @@ -2,6 +2,13 @@ DeepSeek adapter for the harness LLM seam backed by [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai) (the LLM library behind the pi agent). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| DeepSeek request through pi-ai | The selected model receives the same logical system prompt, history, tools, stop sequences, and raw replayed tool arguments as the hand-written adapter. This package adds no prompt prose and removes pi-ai's own per-tool `strict` default to preserve that contract. | Provider tokenization governs exact input. Reasoning level changes generated and passback content; pi-ai reports reasoning inside output usage rather than as a separate count. | +| DeepSeek response | pi-ai events become harness reasoning, text, tool-call, usage, and finish chunks; parsed tool arguments are restored to raw JSON strings at the harness boundary. | Generated content affects later inputs only after the loop records it; adapter conversion adds no model-visible text. | + ## Why a second adapter exists `@deepseek-ai/dsh-llm-deepseek` already talks to the same endpoint. This package is its **design-verification twin**: same models, same wire protocol, completely different internals — a unified LLM library with its own event vocabulary versus hand-rolled fetch/SSE. Anything the harness `StreamChunk` protocol cannot express for BOTH implementations is a core-vocabulary bug. The differences it exercised on purpose: diff --git a/packages/llm/llm/README.md b/packages/llm/llm/README.md index 42694ba5ea..cb6fd18b9f 100644 --- a/packages/llm/llm/README.md +++ b/packages/llm/llm/README.md @@ -2,6 +2,13 @@ Provider-neutral LLM vocabulary and abstract service. This package defines the canonical language spoken by the agent loop, session logs, and every plugin. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Provider request transport | This service adds no system text, schema, or message. It routes the already-assembled frozen `GenerateOptions` to one adapter, while `llm/stream` listeners may cache, retry, or replace the stream without mutating that request. | Zero direct context tokens. The selected adapter and provider tokenizer determine billing, cache accounting, and serialization overhead for the existing content. | +| Streamed model output | Text, reasoning, and tool-call chunks are exposed to the loop, which decides what becomes retained assistant history. | Output usage is provider-reported; later input cost arises only after the loop records assembled content. | + ## Service: `LlmService` (ctx key: `llm`) An adapter registry plus a single streaming call surface, interceptable via a waterfall event. diff --git a/packages/sandbox/sandbox-local/README.md b/packages/sandbox/sandbox-local/README.md index abf7511438..0e47fe5fe6 100644 --- a/packages/sandbox/sandbox-local/README.md +++ b/packages/sandbox/sandbox-local/README.md @@ -16,3 +16,9 @@ Every rung has its keyless world-proof (`tests/bwrap.e2e.ts`, `tests/landlock.e2 ``` Consumers: [`@deepseek-ai/dsh-bash-sandbox`](../../bash/bash-sandbox/); see [`examples/sandbox-acp-agent`](../../../examples/sandbox-acp-agent/) for the runnable composition. + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Sandbox result facts, indirectly | This provider adds no prompt or tool. It supplies the selected enforcement and denial dialect to `dsh-bash-sandbox`, which can become a denial marker or sandbox-unavailable error in the bash result. Runner selection and profiles are not shown. | Zero direct tokens; only a conditional small fact or error reaches context through the bash consumer. | diff --git a/packages/sandbox/sandbox/README.md b/packages/sandbox/sandbox/README.md index ef381625b2..d09b861046 100644 --- a/packages/sandbox/sandbox/README.md +++ b/packages/sandbox/sandbox/README.md @@ -9,3 +9,9 @@ Policy rides the call, not the provider: two consumers may confine under differe **Same-world confinement only.** A backend shares the host's filesystem and kernel (`bwrap`, Landlock, Seatbelt); `workspaceRoot` names a real host path. Containers, microVMs, and remote executors are NOT backends of this seam — they replace whole capability implementations (`ctx.bash`, `ctx.fs`) as environment-coherent groups. The boundary and its rationale: [the sandbox RFC](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md). Implementations: [`@deepseek-ai/dsh-sandbox-local`](../sandbox-local/) (Linux: `bwrap`, else the per-platform Landlock launcher; macOS: `sandbox-exec`/Seatbelt). Consumers: [`@deepseek-ai/dsh-bash-sandbox`](../../bash/bash-sandbox/) (wraps `['bash', '-c', command]`). + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This seam registers no prompt, schema, or message. A consumer may expose configured mode, enforcement, denial, or `SANDBOX_UNAVAILABLE` facts in its own guidance or result. | Zero direct tokens. Confinement changes model-visible text only through the consuming capability. | diff --git a/packages/session-persistence/session-persistence-jsonl/README.md b/packages/session-persistence/session-persistence-jsonl/README.md index 9a76381614..714a036a9d 100644 --- a/packages/session-persistence/session-persistence-jsonl/README.md +++ b/packages/session-persistence/session-persistence-jsonl/README.md @@ -2,6 +2,12 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence` (the `dsh-session-persistence` seam). One append-only `.jsonl` event log per session. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | JSONL storage contributes no live prompt or schema. Loading restores stored surface history and preserves prior request headers for reconstruction; the new loop composes its current envelope. An interrupted tail is balanced with error tool results. Raw `assistant/chunk` records do not duplicate messages. | Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus small repair results only after an interrupted tool turn. | + ## On-disk layout ``` diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index c14d3a70ea..cc6add3b56 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -4,6 +4,12 @@ A SQLite durable session-persistence backend — a second `SessionPersistence` i > **TODO:** this backend talks to `node:sqlite` directly. If a cordis database service (`cordis/db` / a `@cordisjs` SQL driver plugin) is adopted, route through that instead of holding a raw `DatabaseSync` here — the contract surface (`SessionPersistence`) would not change, only the storage driver. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | SQLite storage contributes no live prompt or schema. Loading restores the same surface history as JSONL and preserves prior headers for reconstruction; the new loop composes its current envelope. Interrupted rows are balanced with error tool results. Row metadata and raw chunks are not messages. | Zero live-request tokens. Resume restores retained history and pays the current envelope, with small repair-result tokens only for an interrupted tool turn. | + ## Storage model Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, type, time, data, source_event_seqs, surface_op)` — `data` is the event payload as JSON text, so the row shape is the event verbatim (including `assistant/chunk`, keeping `seq` contiguous). The two `TEXT` columns `source_event_seqs` and `surface_op` are nullable; they store the event's optional surface-metadata fields (see [session surface](../../../docs/rfc/implemented/architecture/2026-06-18-session-surface.md)). Out-of-log metadata (`SessionHeader`) lives in a `sessions` row. A `sessions` row is written only by the first `append` — its existence is the lazy-materialization signal (`list` reports exactly the sessions that have a row), so no separate column is needed. diff --git a/packages/session-persistence/session-persistence/README.md b/packages/session-persistence/session-persistence/README.md index 8bd3fed568..87ab386ba6 100644 --- a/packages/session-persistence/session-persistence/README.md +++ b/packages/session-persistence/session-persistence/README.md @@ -4,6 +4,12 @@ The abstract durable session-persistence seam (`ctx.sessionPersistence`). Define The persisted unit IS the existing `SessionEvent` (event-sourced model — the log is the single source of truth), so there is no parallel "persisted message" type. Metadata that is NOT replayable conversation state (format version, cwd, lineage, seed boundary) travels separately as `SessionHeader`, owned by `dsh-session` and re-exported here. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | This seam adds no prompt or schema. Resume restores stored surface events as message history; stored request headers reconstruct earlier calls, while the new loop composes the current system prompt, tools, and session prefix for its next request. Crash repair inserts an error result for each unanswered tool call. | Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; crash repair can add small error-result tokens that keep the provider transcript valid. | + ## Service API (`ctx.sessionPersistence`) | Method | Contract | diff --git a/packages/skill/skill-local/README.md b/packages/skill/skill-local/README.md index c885416ff5..b48b444d2d 100644 --- a/packages/skill/skill-local/README.md +++ b/packages/skill/skill-local/README.md @@ -4,6 +4,12 @@ Local filesystem provider for the `ctx.skills` registry. This package implements one skill source. It scans local project, custom, and user skill roots, parses `SKILL.md` or flat Markdown skill files, and registers the provider on `ctx.skills`. The registry remains in `@deepseek-ai/dsh-skill`; the session-prefix catalog and model-facing loader tool remain in `@deepseek-ai/dsh-tool-skill`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Skill catalog and loaded body, indirectly | When `dsh-tool-skill` is visible, discovered model-invocable skill names and descriptions enter its session-prefix catalog; a `skill` call returns the selected instructions and resource-base guidance. Paths, provider ranks, and disabled skills stay out of the catalog. | Zero direct tokens from this provider. Catalog cost scales with discovered entries under the consumer's per-description cap; a full body is added only after selection and remains in tool history. | + ## Plugin Requires `ctx.skills` (`inject: ['skills']`). diff --git a/packages/skill/skill/README.md b/packages/skill/skill/README.md index 33d16e1b69..f51f525ee7 100644 --- a/packages/skill/skill/README.md +++ b/packages/skill/skill/README.md @@ -4,6 +4,12 @@ Pure agent skill provider registry. This package owns the `ctx.skills` interface. It does not know whether skills come from local files, embedded plugin data, HTTP, or another backend; providers register those sources with `ctx.skills.registerProvider(...)`. The shipped local implementation is [`@deepseek-ai/dsh-skill-local`](../skill-local). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The registry renders nothing and registers no tool. `dsh-tool-skill` turns `list()` summaries into a session prefix and a selected `get()` body into a tool result; provider failures can remove entries from that request's catalog. | Zero direct tokens. Catalog size, descriptions, and loaded body length affect context only through the consumer. | + ## Service: `SkillService` (ctx key: `skills`) ### Public API diff --git a/packages/skill/tool-skill/README.md b/packages/skill/tool-skill/README.md index 7e72f89000..333a0d00bc 100644 --- a/packages/skill/tool-skill/README.md +++ b/packages/skill/tool-skill/README.md @@ -4,6 +4,13 @@ The model-facing skill catalog and `skill` tool. Requires `ctx.tools` and `ctx.skills` (`inject: ['tools', 'skills']`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Session prefix | If model-invocable skills exist and this exact `skill` tool is visible, the agent receives one user-role `` listing sorted names and capped descriptions. The composed catalog is frozen for the loop instance and prepended to every request, outside ordinary history. | Repeated input cost scales with skill count and `catalogDescriptionMaxLength`; no catalog tokens are sent when the list is empty or the tool is hidden or shadowed. | +| Tool schema and result | The model sees the fixed `skill(name)` schema. A successful call returns the selected full instructions plus resource-resolution guidance; no duplicate `agent.inject()` copy is made. | Fixed schema cost per request. Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction. | + ## Session-prefix catalog The plugin contributes one user-role `` catalog through `agent/session-prefix`. It resolves skills for the calling session's cwd, forwards the prefix abort signal to discovery, and lists only sorted `name` and `description` entries; skill bodies, paths, sources, providers, and `whenToUse` hints remain outside the catalog. The catalog is omitted when no model-invocable skills are available, and also when that agent's tool view restricts away the shipped `skill` tool or resolves a same-name scoped shadow instead. This exact-definition check keeps prompt guidance, the model-visible schema, and executable dispatch aligned. diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index cb51986915..0c07fa2953 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -4,6 +4,13 @@ The out-of-process **ACP subagent backend**: runs each child agent in a spawned It is the direction-inverted twin of the server-side bridge in [`@deepseek-ai/dsh-acp`](../../ui/acp): that package is the ACP *agent* (it answers `initialize`/`newSession`/`prompt`); this one is the ACP *client* (it *calls* them and implements the `sessionUpdate`/`requestPermission` callbacks). Point the configured command at the `acp-agent` example to "talk to our own process". +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The remote child receives the standalone task through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation and cannot enforce the parent's scoped persona or tool filter. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | +| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or a stop-reason error, not intermediate messages or tool traffic. | Parent input grows only by the final result, which is data-dependent and retained until compaction. This provider adds no parent schema itself. | + ## What it does `start(request)` spawns the configured command, wraps its stdio in an ACP `ClientSideConnection`, and drives one session: `initialize` → `newSession` → `prompt`. `run.started` resolves after `newSession` publishes the remote session and rejects when initialization fails or cancellation wins first; the service emits no start/end pair for a child that never became live. The child's streamed `agent_message_chunk` text becomes the `SubagentResult.output`; the prompt's terminal `StopReason` maps to the stop reason. `dispose()` kills the subprocess and awaits its exit. diff --git a/packages/subagent/subagent-fork/README.md b/packages/subagent/subagent-fork/README.md index 1434601c8e..174508b324 100644 --- a/packages/subagent/subagent-fork/README.md +++ b/packages/subagent/subagent-fork/README.md @@ -2,6 +2,13 @@ The in-process **fork** subagent backend: a [`SubagentProvider`](../subagent/README.md) that runs each child as a child [`Agent`](../../core/agent) **seeded with a prefix of the parent's session log** — so the child inherits the parent's conversation context instead of starting fresh. Shares the run driver (`startInProcessRun`) with [`dsh-subagent-spawn`](../subagent-spawn/README.md); the only difference is the seed. The shared `run.started` boundary resolves only after the seeded child is published, so `subagent/start` observers see a live registry entry. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent history | The child receives the parent's balanced completed-turn surface prefix, then the new task, along with its own scoped persona, tool filter, and optional structured-output contract. The parent's current in-flight turn is excluded. | Forking duplicates the retained completed history into a separate child's requests; the child then accumulates its own tokens independently. A first-turn fork has no inherited history. | +| Parent tool result, indirectly | The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work. | Parent input grows by one data-dependent final result retained until compaction. | + ## The seed boundary (the crux) At the moment a subagent tool's `execute` runs, the parent's CURRENT turn is open and unbalanced: the log holds the `assistant/message` carrying this spawn's tool-call and the dangling `tool/call` with no `tool/result` yet. Seeding that raw prefix would give the child an open turn that the session constructor and the dev-mode [invariants](../../support/invariants) replay **reject**. diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md index caa7b9b161..55dc526915 100644 --- a/packages/subagent/subagent-inprocess/README.md +++ b/packages/subagent/subagent-inprocess/README.md @@ -2,6 +2,13 @@ The shared **in-process subagent run driver**. A library with no provider or import-time registration that the in-process backends — [spawn](../subagent-spawn/README.md) (a fresh child) and [fork](../subagent-fork/README.md) (a child seeded with a prefix of the parent's log) — both build on. Each accepted run installs one provider-owned cleanup effect. The backends are thin shells that differ ONLY in the session seed they pass; everything downstream lives here, so neither backend depends on the other. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The shared driver sends the task as the child's user message and composes per-child scoped persona and tool restrictions. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. Structured output adds fixed instruction and capability tokens only to that child for that run. | +| Parent result, indirectly | The driver extracts only the child's own last assistant output or captured structured value; seeded parent messages and intermediate child work do not become the result. | The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session. | + ## What it exports ### `startInProcessRun(ctx, request, options): SubagentRun` diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index e411d44ab0..3c960af2aa 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -4,6 +4,13 @@ The in-process **spawn** subagent backend: a [`SubagentProvider`](../subagent/RE The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../subagent-inprocess/README.md) driver (`startInProcessRun`); this backend just passes **no seed** (a fresh child). The [fork](../subagent-fork/README.md) backend is an independent peer over the same driver — neither knows about the other. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the globally composed prompt and tools after its scoped persona and tool filter. It receives zero parent conversation messages. | The child pays for a new independent context and history; no parent-history tokens are duplicated. | +| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error. | Parent input grows by one data-dependent result retained until compaction. | + ## What it does `start(request)` delegates to `startInProcessRun(ctx, request, {})` with no seed: a fresh child agent with the parent's `cwd`/`parentSession` lineage and (by default) the parent's model. The driver creates one run-owner fiber under `parent.ctx`; parent teardown, this provider's teardown, and manual disposal all converge there before child publication. Its `run.started` boundary resolves only after the fresh child is published, so `subagent/start` observers see a live registry entry. See the [driver README](../subagent-inprocess/README.md) for the full lifecycle (depth check, one-shot drive, result read, dispose). diff --git a/packages/subagent/subagent-subprocess/README.md b/packages/subagent/subagent-subprocess/README.md index ccc68bf31b..a24fcc813b 100644 --- a/packages/subagent/subagent-subprocess/README.md +++ b/packages/subagent/subagent-subprocess/README.md @@ -4,6 +4,12 @@ Shared machinery for **out-of-process subagent backends** — providers that spa Every tunable is a **parameter**: the dispose ladder takes its grace periods per call, the config-dir helper takes an optional pinned path. Defaults live in each consuming plugin's Config (defaulted, validated fields changeable from `cordis.yml`), never in this library. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This process utility registers no provider, prompt, tool, or message. A consuming backend's child application decides the child's model context; environment scrubbing and isolated config directories prevent ambient credentials and user state from silently changing that composition. | Zero direct tokens. It can indirectly stabilize child context, but it adds no text to parent or child requests. | + ## What it exports ### `SENSITIVE_ENV_PATTERN` / `buildChildEnv(extra)` diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index e214ba58c6..26bb8653ed 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -14,6 +14,12 @@ This package is the interface third of the capability seam, split so each concer Unlike the bash seam (one executor per context, second load throws), **multiple providers coexist** here. Each registers under a unique name and a caller picks one by name — the shape mirrors the LLM adapter registry (`LlmService.registerAdapter`), not the single-service bash executor. This is the requirement that rules out the bash shape: an agent may want an in-process child for a cheap subtask and an out-of-process ACP child for an isolated one, in the same runtime. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The provider registry registers no prompt or tool. Provider lifecycle makes a bound `dsh-tool-subagent` schema appear or disappear, and `inheritsParentContext` selects truthful fresh-versus-fork wording. Run events are observe-only. | Zero direct tokens. Child prompts and final results enter model contexts only through a provider and consumer. | + ## Service API (`ctx.subagents`) | Member | Semantics | diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index 715fb0b9db..cb5f9c2a89 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -2,6 +2,13 @@ The model-facing `subagent` tool: delegate a self-contained task to a child agent and return its final output. Pure schema + lifecycle shaping over the [`ctx.subagents`](../subagent/README.md) provider registry — an in-process, ACP, or future A2A backend swaps in without changing what the model sees. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | +| Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. The result contains only the child's final text or a stop-reason error, never intermediate child steps. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | + ## Provider selection is config, not model-facing This plugin binds to **exactly one** provider (`Config.provider`). The model sees only `{ description, prompt }` — there is no provider/type parameter in the schema. To expose more than one transport, load the plugin more than once, each bound to a different provider **and a distinct `toolName`** (the tool registry rejects a duplicate name, so a second load that kept the default `subagent` name would throw). Keeping selection in config (not the schema) is the deliberate split: the *service* holds a multi-provider registry; the *tool* picks one. diff --git a/packages/support/acp-snapshot/README.md b/packages/support/acp-snapshot/README.md index 501b98b793..e2dfa564bb 100644 --- a/packages/support/acp-snapshot/README.md +++ b/packages/support/acp-snapshot/README.md @@ -40,3 +40,9 @@ A scenario booting a differently-composed tree sets its own `configPath` (an ove The example also ships a `cordis.snapshot.yml` replay overlay next to its `cordis.yml` (the bin swaps them under `DSH_SNAPSHOT=replay` — [single-source replay config RFC](../../../docs/rfc/implemented/testing/2026-07-04-single-source-acp-replay-config.md)); replay fixtures are served by [`dsh-llm-replay`](../llm-replay/README.md), which this package points at via the `DSH_SNAPSHOT_*` env vars it sets on the child. `pnpm run test:snapshot:record` calls the live LLM and rewrites the recorded scenarios' model fixtures; `pnpm run test:snapshot:refresh` stays keyless, runs the replay overlay, and rewrites stdout, comparable session-log goldens, and each pin's Markdown prompt snapshot from the committed model scripts. Fixture roles, record/replay/refresh semantics, and scenario-table fields are documented on `Scenario` and in the [snapshot RFC](../../../docs/rfc/implemented/testing/2026-06-19-acp-snapshot-tests.md). Constraints: `suite.ts` imports vitest, so the package is importable only inside a vitest run (the harness and normalizers have no such dependency but ship from the same entry). ACP-specific by design — the harness speaks the SDK's `ClientSideConnection`. Permission round-trips are scriptable: `InputScript.permissionAnswers` is a FIFO queue of option-kind selections (`allow_once`, `reject_once`, …) the client maps to the agent-issued `optionId` at answer time; an absent or exhausted queue answers `cancelled`, and a kind the request never offered rejects the run (the agent is answered `cancelled`, so a tolerant agent cannot absorb the scenario bug). Session config options are scriptable too: the `setConfigOption` step switches a knob over `session/set_config_option`, and `setConfigOptionExpectError` asserts the bridge rejects an unknown id or out-of-vocabulary value (the error frame stays in the transcript). + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None in production | The test harness records, normalizes, scrubs, and compares request headers and ACP transcripts but does not alter the agent's assembled context. Replay scenarios obtain assistant chunks from `dsh-llm-replay`; record mode uses the real composition. | Zero production tokens. Replay spends no provider tokens, while record mode pays the composition's ordinary model cost; golden scrubbing changes files only, never the live request. | diff --git a/packages/support/invariants/README.md b/packages/support/invariants/README.md index 94c2682f9d..1bef6216f5 100644 --- a/packages/support/invariants/README.md +++ b/packages/support/invariants/README.md @@ -4,6 +4,12 @@ Dev-mode event-contract invariants and session-log freeze. A pure-listener plugi **Off in production.** Enable it in tests and the demos, where a contract violation should fail loudly. It costs nothing when not registered, and doubles as executable documentation of the event taxonomy — the assertions *are* the contract. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None | The plugin observes and validates session events, agent states, and frozen model requests; it does not rewrite a prompt, schema, message, or stream. An invariant failure aborts the faulty turn instead of adding guidance. | Zero model tokens when checks pass; a failure prevents or ends a request rather than contributing context. | + ## Plugin A functional plugin — register the module namespace (this is what loading by name in `cordis.yml` does): diff --git a/packages/support/llm-replay/README.md b/packages/support/llm-replay/README.md index 78b411d54c..84a6f71dfb 100644 --- a/packages/support/llm-replay/README.md +++ b/packages/support/llm-replay/README.md @@ -4,6 +4,12 @@ A replay LLM plugin for keyless snapshot tests. It installs a single `llm/stream Its consumer is the ACP snapshot harness in `examples/acp-agent`, which loads this plugin (via `cordis.snapshot.yml`) in place of a real LLM adapter. The package exists so its derive/parse/replay logic falls under the per-file 100% coverage gate on `packages/*/src` (the same logic, while it lived under `examples/`, was outside the gate). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Keyless test stream | The real loop still assembles its normal system prompt, tools, prefix, and history, but no provider model receives them. Recorded assistant chunks are replayed as the response and then enter later history exactly like live output. | Zero billed or tokenizer-evaluated model tokens. Fixture output creates deterministic retained test context for later replay steps. | + ## How the fixture works The fixture IS the persisted session log (`/session.jsonl`). Its `assistant/chunk` events carry every `StreamChunk`, so grouping them by `(turn, step)` reconstructs each `stream()` call's chunk sequence (one model call per loop step). Recording is therefore "run the real agent once and harvest the `.jsonl`", done by the snapshot harness — this plugin does not record. A fixture may carry its `request/header` content tokenized to `{{system}}`/`{{tools}}` (the harness pins that content in one scenario and scrubs the rest); replay is indifferent — derivation reads only `assistant/chunk` events and the line-0 session header. diff --git a/packages/support/subagent-mock/README.md b/packages/support/subagent-mock/README.md index af169ed4c2..74cfbf6bc8 100644 --- a/packages/support/subagent-mock/README.md +++ b/packages/support/subagent-mock/README.md @@ -4,6 +4,12 @@ A scripted `SubagentProvider` for testing the [subagent seam](../../subagent/sub It lets a test drive `ctx.subagents` and the model-facing `dsh-tool-subagent` through the **real cordis Loader / export path**, exercising provider registration, start-time capability validation, the run lifecycle (`result` / `cancel` / `dispose`), and the structured-output branch — all deterministically and keylessly. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Deterministic test result | No child model runs. When exercised through `dsh-tool-subagent`, the parent sees the mock provider's configured reply or stop-reason error, and structured tests receive the configured object. | Zero child-model tokens. Only the scripted final result is added to the parent test history. | + ## Usage Load it as a plugin (functional shape: `name`/`inject`/`Config`/`apply`, no default). Config (all optional): diff --git a/packages/timeout/timeout-policy/README.md b/packages/timeout/timeout-policy/README.md index e637a658bf..84f7d07f04 100644 --- a/packages/timeout/timeout-policy/README.md +++ b/packages/timeout/timeout-policy/README.md @@ -2,6 +2,12 @@ Tool-call timeout enforcer: a single `tools/execute` around-dispatch listener that arms a per-call cooperative deadline on `exec.signal` for a tool declaring `timeoutMs` on its `ToolDefinition` and returns a structured `TOOL_TIMEOUT` result when that deadline wins. The budget is read from the tool's own declaration (`ToolDefinition.timeoutMs`, set by the owning tool plugin), so this plugin is **zero-config**. It is the reference `tools/execute` wrapper and the enforcement home for model-facing tool-call budgets (the timeout-library RFC's foreseen middleware). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conditional tool result | This plugin adds no prompt or schema. If a declared deadline wins, it replaces the provider's outcome with `Error: tool call timed out after ms` plus structured `TOOL_TIMEOUT`; otherwise the original result passes through unchanged. | Zero tokens on non-timeout calls. A timeout adds one small retained error result and can prevent a larger late provider result from entering context. | + ## Plugin (namespace: `timeout-policy`) A function/namespace plugin (`name` / `inject` / `apply`), not a service. It registers no tool and takes no config — it consumes `ctx.tools`'s `tools/execute` waterfall (which the `dsh-tools` registry always provides) and reads each dispatched tool's declared `timeoutMs` from the registry (`ctx.tools.get(exec.name)`). diff --git a/packages/todo/tool-todo/README.md b/packages/todo/tool-todo/README.md index b27cc65227..bdab52d492 100644 --- a/packages/todo/tool-todo/README.md +++ b/packages/todo/tool-todo/README.md @@ -2,6 +2,13 @@ The model-facing `todo_write` tool: the agent's whole task list, replaced wholesale on each call. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | The model sees `todo_write` with the complete-list array and the three status values. | Fixed schema cost on every request where the tool is visible. | +| Tool-call history and result | Each assistant tool call retains the entire replacement list in its arguments. The tool result reports only pending, in-progress, and completed counts; the full `todo/write` session event is UI and replay state, not a second model message. | Token growth scales with every full list the model submits, and those call arguments remain until compaction. The result itself is small and fixed-shape. | + ## What it does Registers one tool, `todo_write(todos: [{ content, status }])`, on `ctx.tools`. The model sends the ENTIRE list every call — there are no partial updates or per-item edits. Each call appends a `todo/write` event (the full list snapshot) to the calling agent's session log via `agent.session.append('todo/write', { todos })`; the current list is the most recent such event (last-write-wins on replay). diff --git a/packages/ui/acp-agent/README.md b/packages/ui/acp-agent/README.md index d81c9cc091..f7bba0c5d8 100644 --- a/packages/ui/acp-agent/README.md +++ b/packages/ui/acp-agent/README.md @@ -4,6 +4,12 @@ The **ACP server app**: a Cordis app plugin that composes the default agent spin It is the structured counterpart to [`@deepseek-ai/dsh-stdio-agent`](../stdio-agent/README.md): both consume the same spine, but this one bakes in the OPPOSITE front-door cluster. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed ACP agent request | Through `dsh-agent-core`, an ACP-created agent receives the harness identity, configured persona, skill catalog, visible tools, and its own ACP prompt history. This app adds no extra prompt prose and omits `ask_user_question` unless a leaf opts in. | Per-request cost is the sum of the composed child packages. ACP framing, JSON-RPC, persistence, and UI rendering add zero model tokens. | + ## What it bakes in — and what it deliberately omits stdout is the ACP JSON-RPC channel, so the cluster is defined as much by what it LEAVES OUT as what it includes: diff --git a/packages/ui/acp/README.md b/packages/ui/acp/README.md index 2f1f35adc4..1721caf5e5 100644 --- a/packages/ui/acp/README.md +++ b/packages/ui/acp/README.md @@ -4,6 +4,14 @@ The **Agent Client Protocol (ACP)** bridge: exposes DeepSeek Harness SDK agents It is a **client-driver / UI plugin**, the structured analogue of the readline `stdio-chat` plugin — NOT a loop change and NOT a [capability seam](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md). It consumes the existing `agent/*` event taxonomy, the `dsh-agent` create/resume factory, and `dsh-session-persistence`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| User messages | Each ACP `session/prompt` becomes an agent user message: text passes through and a `resource_link` is rendered as text. Unsupported image, audio, and embedded-resource blocks are rejected rather than silently omitted. | Prompt tokens are data-dependent and remain in that session's history until compaction. Concurrent ACP sessions keep separate contexts. | +| Human answers and permissions | When optional consumers are loaded, ACP form answers become `ask_user_question` tool results and permission decisions control whether a tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only. | Answer and denial text enters context only through the owning tool result; presentation metadata adds zero model tokens. | +| Loaded sessions | `session/load` resumes the persisted log, after which the loop sends its reconstructed history and request header. Replaying that log to the editor is not an extra model message. | Restored context has the persistence and session packages' normal retained cost; ACP replay to the client adds none. | + ## Service / plugin `apply(ctx, config)` — wires an `AgentSideConnection` (from `@agentclientprotocol/sdk`) to `process.stdin`/`process.stdout` and implements the ACP `Agent` method surface. diff --git a/packages/ui/app-boot/README.md b/packages/ui/app-boot/README.md index 92fbc333be..7630d73208 100644 --- a/packages/ui/app-boot/README.md +++ b/packages/ui/app-boot/README.md @@ -13,3 +13,9 @@ Shared boot glue for the app bins ([`dsh-stdio-agent`](../stdio-agent/README.md) Two failure classes the guards handle: `loader.await()` swallows init rejections (`Promise.allSettled`) — Node still exits non-zero on the resulting unhandled rejection, and `installFailLoud` replaces the noisy dump with one labelled line and a guaranteed `exit(1)`; a failed plugin IMPORT is only logged by the Loader (the process would otherwise exit 0 on a usable config typo), leaving a fiber-less entry that `assertEntriesLoaded` turns into a `boot()` rejection. Bare plugin specifiers in a config (`@deepseek-ai/dsh-*`) resolve through the cordis Loader's internal module loader, active only under `node --expose-internals`; the bins' subprocess smokes exercise that path, while this package's unit suite drives `boot()` in-process against configs with relative specifiers. + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | Boot and environment helpers load the configured plugin tree but register no prompt, schema, or message of their own. `.env`, loader diagnostics, and config-path selection are process concerns, not model context. | Zero direct tokens. The selected configuration indirectly determines which other packages contribute context. | diff --git a/packages/ui/stdio-agent/README.md b/packages/ui/stdio-agent/README.md index 27f0d20be7..5e8ef1bab7 100644 --- a/packages/ui/stdio-agent/README.md +++ b/packages/ui/stdio-agent/README.md @@ -4,6 +4,12 @@ The **terminal stdio chat app**: a Cordis app plugin that composes the default a It is the readline counterpart to [`@deepseek-ai/dsh-acp-agent`](../acp-agent/README.md): both consume the same spine, but each bakes in the OPPOSITE front-door cluster. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed terminal agent request | Through `dsh-agent-core`, the `main` agent receives the harness identity, configured persona, skill catalog, and visible tools; this app also composes the `ask_user_question` schema. Each readline submission becomes a user message. | Child prompt and schema costs repeat per request; user input and tool history grow until compaction. The welcome banner, logger output, and rendered transcript are terminal-only and add zero model tokens. | + ## What it bakes in A terminal chat always wants the same cluster, so the package owns it rather than trusting each leaf to re-wire it: diff --git a/packages/ui/tool-ask-user/README.md b/packages/ui/tool-ask-user/README.md index 10d4a082ba..9aefb6aef4 100644 --- a/packages/ui/tool-ask-user/README.md +++ b/packages/ui/tool-ask-user/README.md @@ -2,6 +2,13 @@ Model-facing `ask_user_question` tool over `ctx.userInteraction`. It lets the model ask the human a concise question when it needs confirmation, a choice, or missing information before continuing. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | The model sees `ask_user_question` with question ids, prompts, headings, options, and multi-select flags. | Fixed schema cost on every request where the tool is visible. | +| Tool-call history and result | The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees JSON containing selected labels and optional custom text. UI interaction while the call is pending is not model context. | Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human. | + ## Tool `ask_user_question` accepts: diff --git a/packages/ui/user-approval/README.md b/packages/ui/user-approval/README.md index 821e98638f..b4358ccbfa 100644 --- a/packages/ui/user-approval/README.md +++ b/packages/ui/user-approval/README.md @@ -11,3 +11,10 @@ The seam also owns the per-session POLICY tier ([the sandbox RFC § Per-session One seam serves both ask paths of [the sandbox RFC](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md): the `tools/pre-execute` `ask` decision (routed by [`@deepseek-ai/dsh-tools`](../../core/tools/) when this service is mounted; degrading to deny when it is not), and the sandbox post-denial escalated retry (the bash tool's `sandbox_permissions` gate in [`@deepseek-ai/dsh-tool-bash`](../../bash/tool-bash/) — [the sandbox RFC § Escalation](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md)). The full design: [the approval-seam RFC](../../../docs/rfc/implemented/feature/2026-07-06-approval-seam.md). Answerers today: the ACP bridge ([`@deepseek-ai/dsh-acp`](../../ui/acp/)) forwards to the editor's `session/request_permission` prompt for agents it owns. The audit events are log-only session records — the model only ever sees the tool result the asker derives from the outcome. + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt and policy notice | Each agent request carries a source-owned approval-policy marker. Under `never`, it also states that approval-requiring actions are rejected and sandbox escalation must not be requested. A policy change injects at most one attributed notice before the next step. | Small fixed per-request cost, larger under `never`; a change notice is conditional and retained in history. | +| Tool outcome | `approval/asked` and `approval/decided` are log-only. The model sees only the asking consumer's eventual allowed, rejected, cancelled, or unavailable tool outcome; the human permission UI is not context. | Zero duplicate audit tokens. A rejection may replace a normal tool result with a small retained error, while an allowance leaves the consumer's ordinary result. | diff --git a/packages/ui/user-interaction/README.md b/packages/ui/user-interaction/README.md index 6377c2b7ef..e3b4748e80 100644 --- a/packages/ui/user-interaction/README.md +++ b/packages/ui/user-interaction/README.md @@ -2,6 +2,12 @@ Abstract user-interaction seam. It owns `ctx.userInteraction`, the service a model-facing tool or permission plugin uses when it needs to pause work and ask the human for a decision. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This UI-neutral seam registers no prompt or tool. A consumer such as `dsh-tool-ask-user` turns a model call into an `ask()` request and converts the provider's human answer into a model-visible tool result. | Zero direct tokens. Question and answer size affect context only through the consumer. | + ## Service: `UserInteractionService` (ctx key: `userInteraction`) ### Public API diff --git a/packages/util/brand/README.md b/packages/util/brand/README.md index 8f7943def7..22c9c47bb3 100644 --- a/packages/util/brand/README.md +++ b/packages/util/brand/README.md @@ -2,6 +2,12 @@ The `Branded` nominal-typing primitive — a tiny, **type-only** package (no runtime code, no harness-package dependency) shared by every package that owns a cross-boundary id. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None | `Branded` is erased at compile time and registers no runtime plugin, prompt, schema, event, or message. Branded ids serialize exactly as their underlying strings when another package logs or renders them. | Zero direct or indirect token overhead beyond the string another package already chose to expose. | + ## What `Branded` is A brand makes structurally-identical strings non-interchangeable at the type level: an `AgentId` cannot be passed where a `CallId` is expected, even though both are plain `string`s at runtime. diff --git a/packages/util/timeout/README.md b/packages/util/timeout/README.md index db2b06ba53..e5418cd7e2 100644 --- a/packages/util/timeout/README.md +++ b/packages/util/timeout/README.md @@ -6,6 +6,12 @@ It owns **no termination**. The signal it hands out only *notifies*; actually st It is a **library, not a service or plugin**: no `ctx`, registers nothing, holds no state, emits no events. A "timeout service" would have to understand how to stop every capability's work — exactly the knowledge a microkernel keeps out of shared layers. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This library only creates and classifies abort signals. It registers no prompt, schema, or message; consumers decide whether a timeout becomes a marker, a structured error, or no model-visible change. | Zero direct tokens. It can indirectly cap or replace a consumer's result when that consumer renders a timeout. | + ## Surface ```ts diff --git a/packages/web/tool-web/README.md b/packages/web/tool-web/README.md index ab1326a21e..6692fa5791 100644 --- a/packages/web/tool-web/README.md +++ b/packages/web/tool-web/README.md @@ -4,6 +4,14 @@ The model-facing web tool suite — `web_search` and `web_fetch` — over the [w Each tool is registered independently; a product that wants only one disables the other via config (`{ search: false }` / `{ fetch: false }`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Each enabled tool adds one short section: search guidance says to discover current sources and follow with fetch; fetch guidance says to retrieve a specific HTTP(S) URL and cite it. | Fixed guidance cost per request for each enabled tool. | +| Tool schemas | According to config, the model sees `web_search(query)`, `web_fetch(url)`, or both. Result-count and timeout budgets are deployment settings, not model arguments. | Fixed schema cost per request; disabling a tool removes its schema and guidance. | +| Tool-call history and results | Search returns an optional answer and bounded source entries; fetch returns status plus decoded text or markdown-shaped HTML, or a structured error. Queries and URLs remain in call history. | Data-dependent results are resent until compaction. Search sources are capped by `searchMaxResults`; fetch providers cap body size, and timeout policy can replace a late result with a short error. | + ## Tools | Tool | Args | Behavior | diff --git a/packages/web/web-fetch-local/README.md b/packages/web/web-fetch-local/README.md index 9c2ef0030f..a300f80c56 100644 --- a/packages/web/web-fetch-local/README.md +++ b/packages/web/web-fetch-local/README.md @@ -4,6 +4,12 @@ An anonymous public HTTP(S) `WebFetchProvider` for the harness [web capability s This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. It is a function/namespace plugin (`inject: ['web']`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Web fetch result, indirectly | Through `dsh-tool-web`, the conversation model sees the final URL, HTTP status, and decoded text or markdown-shaped HTML, or a structured retrieval error. Redirects, headers, and transport mechanics are not added to context unless reflected in an error. | Zero direct tokens. `maxBodyChars` bounds decoded result length before the tool records it; the retained result is resent until compaction. | + ## Responsibility split The provider owns **safe resource retrieval**: URL validation, HTTP transport, redirect policy, a resource-backstop timeout, abort propagation, byte caps, charset decoding, content-type classification, and binary rejection. `@deepseek-ai/dsh-tool-web` owns **presentation** (HTML→markdown, truncation formatting). A non-2xx HTTP response is a *result* (status code + decoded body), not an error; `WebError` is reserved for failures to safely retrieve or represent the resource. diff --git a/packages/web/web-search-deepseek/README.md b/packages/web/web-search-deepseek/README.md index 41b000d26a..eed8f993dd 100644 --- a/packages/web/web-search-deepseek/README.md +++ b/packages/web/web-search-deepseek/README.md @@ -4,6 +4,13 @@ A [DeepSeek](https://deepseek.com)-backed `WebSearchProvider` for the harness [w This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`). The Anthropic wire shape is a provider-private detail — it does **not** make this provider depend on `ctx.llm`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Auxiliary DeepSeek search request | A separate DeepSeek model receives the search query and native `web_search` server-tool definition. This request is not part of the conversation model's context. | Separate provider input and output tokens are incurred for each search; `maxTokens` caps generated output and `maxUses` caps native search uses. | +| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees deduplicated URLs, titles, dates, and citation snippets from structured search blocks; provider prose is not trusted as an answer. | Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the seam enforces the requested source bound. | + ## How it differs from a dedicated search endpoint Exa and Perplexity expose dedicated search endpoints; DeepSeek does not. Instead this provider issues a **full Messages model call** carrying the `web_search` server tool, so one search costs a complete model turn in latency and tokens — heavier than a pure retrieval endpoint. DeepSeek runs the search server-side and returns **structured** `web_search_tool_result` blocks; the provider parses those blocks and **never scrapes URLs out of model prose**. diff --git a/packages/web/web-search-exa/README.md b/packages/web/web-search-exa/README.md index 0bc58d6559..11b7cd7473 100644 --- a/packages/web/web-search-exa/README.md +++ b/packages/web/web-search-exa/README.md @@ -4,6 +4,12 @@ An [Exa](https://exa.ai)-backed `WebSearchProvider` for the harness [web capabil This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the `ctx.web` key and it does not register a model-facing tool (that is `@deepseek-ai/dsh-tool-web`). Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`) that registers its backend, not a default-export service. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Web search result, indirectly | Through `dsh-tool-web`, the conversation model sees Exa result URLs, titles, first highlight snippets, and publication dates. No generated answer or provider-private response fields enter the tool result. | Zero direct harness-model tokens. Result size scales with the bounded source list and snippets; the seam enforces `maxResults`, and retained results remain until compaction. | + ## Config | Key | Default | Meaning | diff --git a/packages/web/web-search-perplexity/README.md b/packages/web/web-search-perplexity/README.md index f944413c96..ffc475e539 100644 --- a/packages/web/web-search-perplexity/README.md +++ b/packages/web/web-search-perplexity/README.md @@ -4,6 +4,13 @@ A [Perplexity](https://perplexity.ai)-backed `WebSearchProvider` for the harness This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`). The OpenAI-compatible wire shape is a provider-private detail — it does **not** make this provider depend on `ctx.llm`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Auxiliary Perplexity request | A separate Perplexity model receives the search query through its chat-completions endpoint. This request is not part of the conversation model's context. | Separate provider tokens are incurred per search; `maxTokens` caps the generated answer. | +| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees the generated answer plus structured result metadata or URL-only citations. | Zero direct conversation tokens from registration. Answer and source tokens are data-dependent, source count is seam-bounded, and the retained result is resent until compaction. | + ## Config | Key | Default | Meaning | diff --git a/packages/web/web/README.md b/packages/web/web/README.md index fe5f1e650e..97ca9d97bb 100644 --- a/packages/web/web/README.md +++ b/packages/web/web/README.md @@ -14,6 +14,12 @@ This package is the interface third of the web capability. Unlike bash/fs it spa Search and fetch share no request schema and no business logic, but they are deliberately one seam: `ctx.web` is a single web-access middle layer with one provider-selection policy owner, one abort/error vocabulary, and one product-facing "how this harness reaches the web" config surface. The cost is the parallel `Search`/`Fetch` method pairs; that parallelism is intentional, not a missed extraction. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The seam registers providers, not tools or prompt text. `dsh-tool-web` renders normalized search answers, sources, fetched bodies, and structured `WebError` values. Provider selection details stay internal except for an execution error. | Zero direct tokens. The seam indirectly bounds search result tokens by truncating sources to `maxResults`; all rendered size comes through a consumer. | + ## Service API (`ctx.web`) | Member | Semantics | diff --git a/packages/workflow/tool-workflow/README.md b/packages/workflow/tool-workflow/README.md index 44160d8fc1..abf2030706 100644 --- a/packages/workflow/tool-workflow/README.md +++ b/packages/workflow/tool-workflow/README.md @@ -2,9 +2,16 @@ The model-facing **`workflow` tool**: run a JavaScript orchestration script that fans out subagents, and return the script's final value. Pure schema + lifecycle shaping over [`ctx.workflows`](../workflow/README.md) — script parsing, execution, caps, and cancellation live behind the seam, so a hardened engine swaps in without touching what the model sees. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt and tool schema | The parent model receives a short use-only-for-large-orchestration section plus the `workflow` schema. The schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial but fixed per-request guidance and schema cost while visible. | +| Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. The result contains the workflow name, child count, and final JSON value or a shaped error; intermediate child messages are omitted. | Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context. | + ## What the model sees -Two parameters: `script` (required — the full `export const meta = {...}` + body text; the tool DESCRIPTION carries the complete authoring contract: hooks, semantics, the supported schema subset) and `args` (optional JSON object exposed to the script as the `args` global; a bare list is wrapped as a field, a deliberate deviation from Claude Code's any-JSON `args` so the wire schema stays honest). The plugin also contributes a `tool:` system-prompt section carrying the usage policy — use the tool only on an explicit user ask for a workflow / large orchestration; prefer plain subagent calls for one or two delegations — per the convention that tool guidance ships with the tool plugin, never in the deployment persona. +Three parameters: `script` (required JavaScript body with top-level await and return, but no `export const meta` statement; the tool description carries the complete hooks and semantics contract), `meta` (required plain-JSON identity with name, description, and optional usage and phase guidance), and `args` (optional JSON object exposed as the `args` global; wrap a bare list in a field). The plugin also contributes a `tool:` system-prompt section carrying the usage policy — use the tool only on an explicit user ask for a workflow or large orchestration; prefer plain subagent calls for one or two delegations — per the convention that tool guidance ships with the tool plugin, never in the deployment persona. ## Lifecycle diff --git a/packages/workflow/workflow-workerthread/README.md b/packages/workflow/workflow-workerthread/README.md index 86d386350a..c6c253034b 100644 --- a/packages/workflow/workflow-workerthread/README.md +++ b/packages/workflow/workflow-workerthread/README.md @@ -2,6 +2,13 @@ The [`WorkflowService`](../workflow/README.md) implementation, on **`node:worker_threads`**: each run gets its OWN worker thread (one run = one worker, no pooling — a run is heavyweight, so the ~tens-of-ms thread spin-up is noise), the script executes in a vm context INSIDE that worker with the workflow hooks injected, and every `agent()` call bridges back over the message port to [`ctx.subagents`](../../subagent/README.md) on the host. Child agents are I/O-bound LLM loops and stay on the host event loop; the thread isolates the SCRIPT, the only part that can spin synchronously. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent requests | Every script `agent()` call sends its prompt and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events. | Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly. | +| Parent tool result, indirectly | The parent sees only the script's materialized final JSON value, child count, or error through `dsh-tool-workflow`. Intermediate child outputs are available to the script but not the parent model. | Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction. | + ## Trust premise: what the thread buys (and what it does not) Workflow scripts are **model-written** — the same trust level as the model's existing bash access — so this engine defends against **buggy** scripts, never hostile ones. A worker thread is NOT a security boundary: the vm context inside it is escapable by construction (`node:vm` shares object machinery with its surrounding realm, so a script can reach the `Function` constructor via `globalThis.constructor.constructor` and from it `process` and every Node builtin), and an escapee holds the same process privileges as the host — Node's permission model is process-wide. The absent globals are API surface that keeps honest scripts portable, not walls. What the thread concretely buys: diff --git a/packages/workflow/workflow/README.md b/packages/workflow/workflow/README.md index 5332243a85..361001f0b0 100644 --- a/packages/workflow/workflow/README.md +++ b/packages/workflow/workflow/README.md @@ -2,6 +2,12 @@ The **workflow seam** (`ctx.workflows`): an abstract service defining WHAT a workflow engine does — execute a model-written orchestration script that fans out subagents — without saying HOW. The bash-shaped third of the [workflow family](../README.md): implementations subclass `WorkflowService` and register as the `workflows` service (one per context); [`dsh-workflow-workerthread`](../workflow-workerthread/README.md) (one worker thread per run) is the implementation, and [`dsh-tool-workflow`](../tool-workflow/README.md) is the model-facing consumer. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The service seam and `workflow/*` observer events register no prompt, schema, or message. `dsh-tool-workflow` renders the parent-facing contract and final value; an engine decides which child prompts run. | Zero direct tokens. Parent result and child contexts affect tokens only through the consumer and implementation. | + ## Service: `WorkflowService` (abstract) `start(request: WorkflowStartRequest): WorkflowRun` — parse and execute a script. Throws synchronously (`SCRIPT_PARSE`/`META_INVALID`) for a script that cannot begin; once a run is returned, its `result` NEVER rejects — every failure resolves with `stopReason: 'error'` (or `'cancelled'`) — and once the run is cancelled, `result` settles within the implementation's bounded grace even if the script itself never settles (a consumer awaiting `result` must never be wedged past a cancellation). `dispose()` must reach quiescence within a bounded grace (cancel → wait for the script to settle and its children to finish disposing → abandon), never hanging its caller. Runs are HOLDER-owned: the engine does not track its live runs, so disposing the engine's fiber mid-run leaves each run to its holder's teardown. diff --git a/scripts/run-gates.ts b/scripts/run-gates.ts index 2f98c74eea..8f87ab66f7 100644 --- a/scripts/run-gates.ts +++ b/scripts/run-gates.ts @@ -267,6 +267,7 @@ function docSyncLeafGates(): Gate[] { pnpmScript('markdown-links', 'verify-md-links', { label: 'markdown links' }), pnpmScript('doc-refs', 'verify-doc-refs', { label: 'doc refs' }), pnpmScript('package-paths', 'verify-package-paths', { label: 'package paths' }), + pnpmScript('package-readme-model-experience', 'verify-package-readme-model-experience', { label: 'package README model experience' }), pnpmScript('mermaid', 'verify-mermaid'), pnpmScript('rfc-classification', 'verify-rfc-classification', { label: 'rfc classification' }), pnpmScript('rfc-format', 'verify-rfc-format', { label: 'rfc format' }), diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts new file mode 100644 index 0000000000..84ba22ea88 --- /dev/null +++ b/scripts/verify-package-readme-model-experience.ts @@ -0,0 +1,81 @@ +/** + * Doc-sync gate: require every workspace package README to explain its exact + * model-visible context surface and token behavior in the canonical table. + * + * Run: `tsx scripts/verify-package-readme-model-experience.ts`. + */ + +import { existsSync, globSync, readFileSync } from 'node:fs' +import { relative, resolve } from 'node:path' + +const root = resolve(import.meta.dirname, '..') +const HEADING = '## Model Experience' +const HEADING_PATTERN = /^## Model Experience$/gm +const TABLE_HEADER = '| Context surface | What the model sees | Token effect |' +const TABLE_DIVIDER = '|---|---|---|' + +interface Failure { + path: string + message: string +} + +const failures: Failure[] = [] +const packageJsons = globSync('packages/*/*/package.json', { cwd: root }).sort() + +for (const packageJson of packageJsons) { + const readme = packageJson.replace(/package\.json$/, 'README.md') + const abs = resolve(root, readme) + if (!existsSync(abs)) { + failures.push({ path: readme, message: `missing package README; add one with ${HEADING}` }) + continue + } + + const source = readFileSync(abs, 'utf8') + const matches = [...source.matchAll(HEADING_PATTERN)] + if (matches.length !== 1) { + failures.push({ + path: readme, + message: matches.length === 0 ? `missing ${HEADING}` : `contains ${matches.length} copies of ${HEADING}`, + }) + continue + } + + const match = matches[0] + if (match?.index === undefined) { + failures.push({ path: readme, message: `could not locate ${HEADING}` }) + continue + } + const headingIndex = match.index + const bodyStart = headingIndex + HEADING.length + const nextHeadingOffset = source.slice(bodyStart).search(/^## /m) + const section = source.slice(bodyStart, nextHeadingOffset < 0 ? undefined : bodyStart + nextHeadingOffset) + const lines = section.split('\n') + const headerIndex = lines.indexOf(TABLE_HEADER) + if (headerIndex < 0 || lines[headerIndex + 1] !== TABLE_DIVIDER) { + failures.push({ path: readme, message: `must contain the exact table header ${TABLE_HEADER}` }) + continue + } + + const rows = lines.slice(headerIndex + 2).filter(line => line.startsWith('|')) + if (rows.length === 0) { + failures.push({ path: readme, message: 'Model Experience table must contain at least one data row' }) + continue + } + for (const row of rows) { + const cells = row.split('|').slice(1, -1).map(cell => cell.trim()) + if (cells.length !== 3 || cells.some(cell => cell.length === 0)) { + failures.push({ path: readme, message: `invalid three-column Model Experience row: ${row}` }) + } + } +} + +if (failures.length === 0) { + console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) carry the canonical ${HEADING} table.`) + process.exit(0) +} + +console.error('verify-package-readme-model-experience failed:') +for (const failure of failures) { + console.error(` ${relative(root, resolve(root, failure.path))}: ${failure.message}`) +} +process.exit(1) From b5cd511f35c52a8c1c4c2530b446b1a6b0574cab Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:55:26 +0800 Subject: [PATCH 12/38] docs: address model experience review --- docs/AGENTS.md | 4 +-- docs/cookbook/adding-a-package.md | 24 +++++++++++--- ...07-12-package-model-experience-contract.md | 4 +-- packages/bash/bash-local/README.md | 12 +++---- packages/bash/bash/README.md | 12 +++---- packages/bash/tool-bash/README.md | 16 +++++----- .../code-runtime-worker/README.md | 12 +++---- packages/code-runtime/code-runtime/README.md | 12 +++---- packages/compact/compact-basic/README.md | 14 ++++---- packages/compact/compact/README.md | 12 +++---- packages/cordis/tool-cordis/README.md | 16 +++++----- packages/core/agent-core/README.md | 14 ++++---- packages/core/agent-loop/README.md | 14 ++++---- packages/core/agent/README.md | 14 ++++---- packages/core/scope/README.md | 12 +++---- packages/core/session/README.md | 14 ++++---- packages/core/system-prompt/README.md | 14 ++++---- packages/core/tools/README.md | 14 ++++---- packages/fs/fs-local/README.md | 12 +++---- packages/fs/fs-policy/README.md | 12 +++---- packages/fs/fs/README.md | 12 +++---- packages/fs/tool-fs/README.md | 16 +++++----- packages/guard/repeat-tool-guard/README.md | 12 +++---- packages/hooks/hook-protocol/README.md | 12 +++---- packages/hooks/hooks-claude/README.md | 14 ++++---- packages/hooks/hooks-codex/README.md | 14 ++++---- packages/llm/llm-deepseek/README.md | 14 ++++---- packages/llm/llm-pi-ai/README.md | 14 ++++---- packages/llm/llm/README.md | 14 ++++---- .../session-persistence-jsonl/README.md | 12 +++---- .../session-persistence-sqlite/README.md | 12 +++---- .../session-persistence/README.md | 12 +++---- packages/skill/skill-local/README.md | 12 +++---- packages/skill/skill/README.md | 12 +++---- packages/skill/tool-skill/README.md | 14 ++++---- packages/subagent/subagent-acp/README.md | 14 ++++---- packages/subagent/subagent-fork/README.md | 14 ++++---- .../subagent/subagent-inprocess/README.md | 14 ++++---- packages/subagent/subagent-spawn/README.md | 14 ++++---- .../subagent/subagent-subprocess/README.md | 12 +++---- packages/subagent/subagent/README.md | 12 +++---- packages/subagent/tool-subagent/README.md | 14 ++++---- packages/support/invariants/README.md | 12 +++---- packages/support/llm-replay/README.md | 12 +++---- packages/support/subagent-mock/README.md | 12 +++---- packages/timeout/timeout-policy/README.md | 12 +++---- packages/todo/tool-todo/README.md | 14 ++++---- packages/ui/acp-agent/README.md | 12 +++---- packages/ui/acp/README.md | 32 +++++++++---------- packages/ui/stdio-agent/README.md | 12 +++---- packages/ui/tool-ask-user/README.md | 14 ++++---- packages/ui/user-interaction/README.md | 12 +++---- packages/util/brand/README.md | 12 +++---- packages/util/timeout/README.md | 12 +++---- packages/web/tool-web/README.md | 16 +++++----- packages/web/web-fetch-local/README.md | 12 +++---- packages/web/web-search-deepseek/README.md | 14 ++++---- packages/web/web-search-exa/README.md | 12 +++---- packages/web/web-search-perplexity/README.md | 14 ++++---- packages/web/web/README.md | 12 +++---- packages/workflow/tool-workflow/README.md | 14 ++++---- .../workflow/workflow-workerthread/README.md | 14 ++++---- packages/workflow/workflow/README.md | 12 +++---- .../verify-package-readme-model-experience.ts | 17 ++++++++++ 64 files changed, 443 insertions(+), 410 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 239af47e48..ad592333d5 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -35,12 +35,12 @@ Placement test: a story about a bug → postmortem. Why we chose X → RFC. How ## Package Model Experience -Every package README carries `## Model Experience` and this table: +Every package README ends with this table followed by `## Known Limitations and Deferred Work`; [allowlisted packages](../scripts/verify-readme-limitations.ts) end after the table: | Context surface | What the model sees | Token effect | |---|---|---| -Rows name the request surface and condition, including agent scope, then classify tokens as fixed per request, conditional per call, retained, replaced, capped, or zero-direct. Distinguish the conversation model from auxiliary calls. A zero-direct package names its indirect path. `verify-package-readme-model-experience` enforces shape; review owns accuracy. +Rows name request surface, condition, and agent scope, then classify tokens as fixed per request, conditional per call, retained, replaced, capped, or zero-direct. Separate conversation and auxiliary calls; zero-direct rows name the indirect path. `verify-package-readme-model-experience` enforces shape and order; review owns accuracy. ## Wordcount Budgets diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 9fd4ed005c..0ccee2b217 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -19,8 +19,6 @@ packages/// # (or a whitelist entry in scripts/verify-readme-limitations.ts) ``` -Fill the canonical [Model Experience table](../AGENTS.md#package-model-experience) from the implementation: name every direct request contribution and token-growth condition, or state zero direct tokens and the exact indirect path. Every package participates, including type-only libraries and backend seams. - Choose an existing group when one matches the package's role (`core`, `llm`, `bash`, `compact`, `subagent`, `todo`, `session-persistence`, `ui`, `util`, or `support`). A new group is allowed, but it is a pure container: no `package.json`, no source files, and packages still sit exactly one level below it. package.json invariants (enforced by `pnpm run constraints` / `scripts/check-workspace-constraints.ts`): `private: true`, `version: 0.0.1`, `type: module`, `main: "lib/index.js"`, `types: "lib/types/index.d.ts"`, `exports["."].types: "./lib/types/index.d.ts"`, `exports["."].default: "./lib/index.js"`, `cordis` in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. `schemastery` goes in `dependencies` (it is a runtime validator), matching agent-loop. The `files` list is precise: `lib/index.js`, `lib/types/**/*.d.ts`, `lib/types/**/*.d.ts.map`, and `src`; do not publish `lib/types` JS or JS-map intermediates or stale root declaration files. CLI app packages with a package `bin` include `lib/bin.js` immediately after `lib/index.js` in `files`. @@ -42,11 +40,29 @@ Covered automatically by globs or package-manifest discovery — no edits needed For a swappable capability, split interface / implementation / consumer into separate packages (see docs/architecture.md § "Capability seams" — the bash trio is the template). A single-purpose plugin stays one package. -## 4. Verify +## 4. Write the package README + +Keep package-specific service API, config, events, extension points, and design notes first. End a package README with this canonical sequence: + +```markdown +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Request surface and condition | Exact context visible to the model | Fixed, conditional, retained, replaced, capped, or zero-direct token effect | + +## Known Limitations and Deferred Work + +- **Consumer-visible gap** — exact boundary or deliberately deferred work. +``` + +Fill [Model Experience](../AGENTS.md#package-model-experience) from the implementation: name every direct request contribution and token-growth condition, or state zero direct tokens and its indirect path. Every package participates, including type-only libraries and backend seams. A package with genuinely no limitations joins the justified allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. + +## 5. Verify ```sh pnpm install # registers the workspace -pnpm run verify-package-readme-model-experience +pnpm run doc-sync pnpm run constraints && pnpm run typecheck && pnpm run lint pnpm run test:coverage # 100% per-file over src (types.ts exempt) pnpm run build && pnpm run hygiene diff --git a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md index 755bb5a80b..a083e69cb8 100644 --- a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md +++ b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md @@ -8,11 +8,11 @@ A package README can explain APIs and runtime mechanics without answering the qu ## Decision -Every workspace package README carries the canonical [Model Experience table](../../../AGENTS.md#package-model-experience). Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. +Every workspace package README ends with the canonical [Model Experience table](../../../AGENTS.md#package-model-experience), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Every package participates. A service seam, storage backend, test helper, or type-only library that contributes no prompt text, tool schema, message, or auxiliary request records zero direct tokens and names the consumer or control path through which it can still change model-visible material. This explicit negative contract prevents readers from having to infer whether the section was forgotten. -`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README, one exact `## Model Experience` heading, the canonical three-column table header, and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns shape and completeness; implementation review owns the truth of the prose. +`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README, the canonical final-section order, one exact `## Model Experience` heading and three-column table header, and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns shape and completeness; implementation review owns the truth of the prose. ## Alternatives considered diff --git a/packages/bash/bash-local/README.md b/packages/bash/bash-local/README.md index 1bc3f2baab..42a2a0618d 100644 --- a/packages/bash/bash-local/README.md +++ b/packages/bash/bash-local/README.md @@ -2,12 +2,6 @@ Local-subprocess implementation of the `@deepseek-ai/dsh-bash` executor seam: `LocalBashExecutor` spawns `bash -c ` per call in its own process group, collects bounded output with full-stream spill files, and escalates kills SIGTERM→SIGKILL across the whole group. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the retained stdout and stderr tail, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. | - ## Config ```yaml @@ -31,6 +25,12 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi; - **Model-friendly env + credential scrub** — `process.env` minus credential-shaped vars (`*KEY*`/`*SECRET*`/`*TOKEN*`), then `NO_COLOR=1 TERM=dumb PAGER=cat GIT_PAGER=cat` (Codex's hardcoded set) so pagers and ANSI color don't garble results. This scrub is the security control that keeps the harness's *ambient* credentials out of a spawned command. A spec's `env` is merged LAST (after the scrub), so a caller's explicit entry — a value it already holds — wins even on a credential-shaped name. The spec's `stdin`, when supplied, is written to the child and closed; with none supplied, fd 0 is `/dev/null` — the exact pre-seam default, so a command that probes stdin's file type is unaffected. Both `env`/`stdin` are set by in-process plugins (the hooks bridges); the model-facing tool doesn't expose them. See [the bash-stdin-env RFC](../../../docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md). - **Background tasks** — `start()` returns immediately, no timeout applies (Claude Code detaches timeouts when backgrounding), `readOutput()` is incremental with whole-stream byte offsets, and disposal kills everything. The spec's opaque `owner` token is stored on the tracked task and returned by `ownerOf(id)` — the executor never interprets it (the consumer's access policy does), and because it lives with the task here it survives a `tool-bash` HMR reload. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the retained stdout and stderr tail, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. | + ## Known Limitations and Deferred Work - **Unconfined by itself** — this executor always runs commands with the harness process's authority; deployments needing confinement compose [`dsh-bash-sandbox`](../bash-sandbox/README.md), while per-call allow/deny/ask policy belongs on `tools/pre-execute`. diff --git a/packages/bash/bash/README.md b/packages/bash/bash/README.md index 3ce0845e73..36f6686522 100644 --- a/packages/bash/bash/README.md +++ b/packages/bash/bash/README.md @@ -13,12 +13,6 @@ This package is the interface quarter of the bash capability, split so each conc The split mirrors the LLM seam (`LlmService`/`LlmAdapter`) and the agent-tool survey: pi hides execution behind a `BashOperations` interface (local shell / SSH / VM backends), Codex behind an exec-server protocol. `dsh-bash-sandbox` is exactly that swap in action — a sandboxing executor behind the same interface, tool schemas untouched; a containerized or remote executor slots in the same way. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This interface registers no prompt, tool schema, or message. `dsh-tool-bash` turns an implementation's stdout, stderr, task state, and sandbox facts into model-visible tool results and guidance. | Zero direct tokens. Result size and sandbox state affect input tokens only when a consumer renders them. | - ## Service API (`ctx.bash`) | Member | Semantics | @@ -42,6 +36,12 @@ The seam also owns the per-session mode override vocabulary (the sandbox RFC § `stdin` and `env` are set by in-process plugins (the hooks bridges, native plugins) to feed a hook command its JSON payload on stdin and its `CLAUDE_PROJECT_DIR`/`CLAUDE_PLUGIN_ROOT` env. The model-facing `dsh-tool-bash` tool does not expose them as parameters — a model already has equivalent power through shell syntax (`FOO=bar cmd`, a heredoc), so they would be redundant tool params. This is not a security boundary: the implementation's credential scrub (not these fields) is what keeps the harness's ambient secrets out of a spawned command. They are plain optionals on the resolved spec (unlike `owner`'s required-but-nullable): a missing one means "none", the safe default. See [the bash-stdin-env RFC](../../../docs/rfc/implemented/architecture/2026-06-30-bash-stdin-env-trusted-plugin-surface.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This interface registers no prompt, tool schema, or message. `dsh-tool-bash` turns an implementation's stdout, stderr, task state, and sandbox facts into model-visible tool results and guidance. | Zero direct tokens. Result size and sandbox state affect input tokens only when a consumer renders them. | + ## Known Limitations and Deferred Work - **No interactive-input vocabulary** — `stdin` is written once at spawn and closed; the seam has no channel to feed a running task and no PTY session concept. diff --git a/packages/bash/tool-bash/README.md b/packages/bash/tool-bash/README.md index 66fc7968a6..da1ab41c14 100644 --- a/packages/bash/tool-bash/README.md +++ b/packages/bash/tool-bash/README.md @@ -6,14 +6,6 @@ Requires a loaded executor implementation (e.g. `@deepseek-ai/dsh-bash-local`); The plugin also contributes the `tool:bash` prompt section (order 105) — the cross-call habit the per-tool descriptions cannot carry: check the `[exit code: N]` marker on every result and investigate failures before moving on. Under a sandboxing executor it additionally contributes the per-agent `env:bash-sandbox` section (order 110) stating each session's EFFECTIVE mode, and the pre-step narrator — see [Per-session mode](#per-session-mode-switching-and-visibility). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt | Every request for an agent that can see these tools carries the short `tool:bash` exit-code instruction. With a sandboxing executor it also carries that session's effective sandbox mode, plus a logged context notice after a mode change. | Small fixed input cost per request; a mode-change notice is conditional and then remains in conversation history. | -| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. | -| Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. | - ## Tools ### `bash` @@ -66,6 +58,14 @@ On top of a denial sits the escalation gate ([the sandbox RFC § Escalation](../ Under a sandboxing executor this plugin makes the session's standing mode override ([the sandbox RFC § Per-session mode switching](../../../docs/rfc/implemented/feature/2026-07-06-sandbox.md); the `bash/sandbox-mode` fold owned by [`dsh-bash`](../bash/README.md)) real at EXECUTION: every call is stamped `escalation grant > session override > undefined` onto `BashExecRequest.sandboxMode`; without either, the executor's `resolve()` applies its configured default. Nothing is stamped under a non-sandboxing executor (nothing would honor it) or for an agent-less caller (no session to fold). The prompt deliberately does NOT state the mode and a switch is not narrated: a standing declaration teaches the model to refuse preemptively, while the denial marker already names the mode the command ran under exactly when the boundary is hit — behavior, not belief, carries the state. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Every request for an agent that can see these tools carries the short `tool:bash` exit-code instruction. With a sandboxing executor it also carries that session's effective sandbox mode, plus a logged context notice after a mode change. | Small fixed input cost per request; a mode-change notice is conditional and then remains in conversation history. | +| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. | +| Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. | + ## Known Limitations and Deferred Work - **Replay exit pills parse from result text** — output whose final line happens to be exactly `[exit code: N]` / `[killed by signal: …]` shows a wrong pill on session replay; a display-only known residual. diff --git a/packages/code-runtime/code-runtime-worker/README.md b/packages/code-runtime/code-runtime-worker/README.md index fe4b7d6c04..303b9abb90 100644 --- a/packages/code-runtime/code-runtime-worker/README.md +++ b/packages/code-runtime/code-runtime-worker/README.md @@ -2,12 +2,6 @@ Worker-thread implementation of the [`@deepseek-ai/dsh-code-runtime`](../code-runtime/README.md) seam: `WorkerCodeRuntime` runs each program in ONE fresh Node `worker_threads.Worker` — TypeScript in, type-stripped host-side, bindings bridged over the message port, `{ value, logs, error? }` out. **Containment, not a security boundary**: trust posture is bash-equivalent by design (the [Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md) § Trust posture), with containment bash does not have — separate isolate, empty environment, heap cap, hard termination. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| `run_code` result, indirectly | The conversation model sees only what the model-written program prints or returns, or a shaped failure; binding-call traffic and worker internals stay outside its context. This backend contributes no schema or prompt itself. | Zero tokens until Code Mode executes a program. `maxLogBytes` and `maxValueBytes` cap the model-visible result, which then remains in tool history until compaction. | - ## Config ```yaml @@ -37,6 +31,12 @@ Every field is validated (positive numbers) and defaulted; there are no other tu `worker.ts` is deliberately erasable-only TypeScript with type-only cross-package imports: unbuilt (vitest/tsx), the host spawns `src/worker.ts` directly and Node's native type stripping loads it; built, the entry ships as the sibling bundle `lib/worker.js` (its own tsdown entry). The built path is pinned by `tests/built-lib.e2e.ts`, the real-load-path guard from [docs/testing.md](../../../docs/testing.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| `run_code` result, indirectly | The conversation model sees only what the model-written program prints or returns, or a shaped failure; binding-call traffic and worker internals stay outside its context. This backend contributes no schema or prompt itself. | Zero tokens until Code Mode executes a program. `maxLogBytes` and `maxValueBytes` cap the model-visible result, which then remains in tool history until compaction. | + ## Known Limitations and Deferred Work - **OS processes a program spawns survive termination** — `worker.terminate()` ends the thread only, weaker than bash-local's process-group kill; orphan cleanup is a deployment concern until a container backend exists. diff --git a/packages/code-runtime/code-runtime/README.md b/packages/code-runtime/code-runtime/README.md index e6e9a17267..9c256a31dc 100644 --- a/packages/code-runtime/code-runtime/README.md +++ b/packages/code-runtime/code-runtime/README.md @@ -4,12 +4,6 @@ The **code-execution seam**: an abstract `CodeRuntime` service (`ctx.codeRuntime This package is the interface third of the capability (the bash trio is the template — see [capability seams](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)): implementations subclass `CodeRuntime` and register the service; the consumer is the tool registry's Code Mode, which generates the model-facing SDK and bridges tool dispatch — both specified in the [Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md), whose first implementation is a Node worker-thread backend. The runtime knows nothing about tools or sessions: it is handed named async functions and a program string, and everything tool-shaped stays with the consumer. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The seam receives a program and host bindings but registers no prompt, schema, or message. Code Mode in `dsh-tools` exposes the SDK and `run_code`, then converts `CodeRunResult` into the outer tool result. | Zero direct tokens. Program logs, values, and failures affect the conversation only through the Code Mode consumer. | - ## Service API (`ctx.codeRuntime`) | Member | Semantics | @@ -24,6 +18,12 @@ Semantics every implementation must honor (contract details in the class JSDoc): `CodeRunRequest` (`program`, `bindings`, `signal?`) carries everything the runtime acts on — defaulting (time budgets, output caps) is the implementation's validated config, never a hidden `??` inside `run()`. `bindings` is a list of `CodeBindingNamespace`s (`global` + `functions`), each exposed to the program as one global object of async callables. `CodeRunResult` reports the completion `value?`, the ordered `logs` (`CodeLogEntry`: `console`/`stdout`/`stderr` source, console `level`, capped text), and the `error?` (`CodeRunFailure`: `kind` + model-feedable `message`). See `src/types.ts` for the full contracts. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The seam receives a program and host bindings but registers no prompt, schema, or message. Code Mode in `dsh-tools` exposes the SDK and `run_code`, then converts `CodeRunResult` into the outer tool result. | Zero direct tokens. Program logs, values, and failures affect the conversation only through the Code Mode consumer. | + ## Known Limitations and Deferred Work - **`run()` is one-shot** — `logs` arrive only on the resolved `CodeRunResult`; the seam exposes no streaming-log or progress surface for a live program's output. diff --git a/packages/compact/compact-basic/README.md b/packages/compact/compact-basic/README.md index d47db04d99..395b3cccd8 100644 --- a/packages/compact/compact-basic/README.md +++ b/packages/compact/compact-basic/README.md @@ -4,13 +4,6 @@ The **basic compaction backend**: a `BasicCompactService` implementing the `@dee This is the implementation tier of the compaction capability — see the [interface package](../compact/README.md) for the seam and the [capability-seam RFC](../../../docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md) for the design. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Conversation history | Before a step whose estimated system prompt, session prefix, and history exceed the threshold, the conversation model receives one framed summary checkpoint in place of the older balanced surface range, followed by the retained recent units. | The replacement reduces future input history rather than appending a second copy. The summary remains until a later compaction replaces it; one oversized indivisible unit can still exceed the budget. | -| Auxiliary summarizer request | The summarization model sees a fixed checkpoint-writing system instruction and a flattened transcript of the selected range. The conversation model never sees this private request or its reasoning; only returned text is stored. | This is a separate model call with data-dependent input and `maxTokens`-capped output. Convergence retries can pay this cost more than once. | - ## What it owns The abstract contract states only WHAT compaction does; this backend owns every HOW decision: @@ -64,6 +57,13 @@ export function apply(ctx: Context): void { Loading the plugin registers `ctx.compact`. With `auto: true` (the default) it compacts automatically under token pressure; a consumer (a future `/compact` tool) can also call `ctx.compact.compactIfNeeded(...)` or `ctx.compact.compactRegion(...)` directly. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conversation history | Before a step whose estimated system prompt, session prefix, and history exceed the threshold, the conversation model receives one framed summary checkpoint in place of the older balanced surface range, followed by the retained recent units. | The replacement reduces future input history rather than appending a second copy. The summary remains until a later compaction replaces it; one oversized indivisible unit can still exceed the budget. | +| Auxiliary summarizer request | The summarization model sees a fixed checkpoint-writing system instruction and a flattened transcript of the selected range. The conversation model never sees this private request or its reasoning; only returned text is stored. | This is a separate model call with data-dependent input and `maxTokens`-capped output. Convergence retries can pay this cost more than once. | + ## Known Limitations and Deferred Work - **Token estimation is the chars/`charsPerToken` heuristic** — a marked TODO schedules replacing it with an exact count (a real tokenizer, or provider `usage` fed back) so thresholds track the model's actual budget. diff --git a/packages/compact/compact/README.md b/packages/compact/compact/README.md index 098b8dc1fd..73d7c22375 100644 --- a/packages/compact/compact/README.md +++ b/packages/compact/compact/README.md @@ -12,12 +12,6 @@ This package is the interface tier of the compaction capability, split so each c Unlike the bash seam, this interface depends on `@deepseek-ai/dsh-session` and `@deepseek-ai/dsh-llm` — the contract's verbs are defined over a `Session` and its output is the `ContentBlock` vocabulary, so they cannot be expressed without naming those packages. That deviation from the "interface depends only on cordis" guidance is intentional and recorded in the [compaction capability-seam RFC](../../../docs/rfc/implemented/feature/2026-06-18-compaction-capability-seam.md). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Conversation history, when a backend is invoked | A successful implementation replaces an older surface range with one user-role summary checkpoint; the raw events stay logged but stop appearing in derived model messages. The seam itself performs no rewrite. | Zero direct tokens from this interface. A backend trades many retained history tokens for one summary and leaves the recent tail unchanged. | - ## Service API (`ctx.compact`) Both methods are **abstract** — the backend owns the entire strategy (token estimation, retention policy, event sequencing, summarization). @@ -55,6 +49,12 @@ The `compact/*` events extend `SessionEventMap` (merge-extensible) via declarati Subclass `CompactService`, implement `compactIfNeeded` and `compactRegion`, and load the subclass as a plugin — it registers as `ctx.compact`. A tokenizer-, template-, or model-backed implementation can live as a sibling package without changing callers. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conversation history, when a backend is invoked | A successful implementation replaces an older surface range with one user-role summary checkpoint; the raw events stay logged but stop appearing in derived model messages. The seam itself performs no rewrite. | Zero direct tokens from this interface. A backend trades many retained history tokens for one summary and leaves the recent tail unchanged. | + ## Known Limitations and Deferred Work - **No model-facing consumer tier yet** — `@deepseek-ai/dsh-tool-compact` (the `/compact` tool) is deferred; compaction is reachable only via direct `ctx.compact` calls or a backend's auto listener. diff --git a/packages/cordis/tool-cordis/README.md b/packages/cordis/tool-cordis/README.md index 8dbb70db37..1fb20a1906 100644 --- a/packages/cordis/tool-cordis/README.md +++ b/packages/cordis/tool-cordis/README.md @@ -2,14 +2,6 @@ The self-referential cordis toolset: three model-facing tools over the live runtime the agent runs inside. Design home — sandbox semantics, mount lifecycle, cross-mount composition, the generated API catalog, standing decisions: [the toolset RFC](../../../docs/rfc/implemented/feature/2026-07-08-self-referential-cordis-toolset.md). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schemas | The conversation model sees `cordis_inspect`, `cordis_mount`, and `cordis_unmount` whenever this plugin is visible. | Fixed schema cost on every request in that tool view. | -| Tool-call history and results | Inspect returns selected live services, plugins, tools, or generated API and event references; mount and unmount return lifecycle facts or structured errors. The submitted mount program remains in the assistant tool-call history. | Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small. | -| Later requests after a mount | A mounted plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; unmount removes those contributions after quiescence. | Indirect token impact equals the mounted plugin's contributions and lasts only for the mount lifetime. | - ## What it does - `cordis_inspect` — read-only report over the runtime: services, the loaded-plugin list, registered tools, the dynamic-mount table, and the catalog-backed `api` / `events` references. @@ -40,6 +32,14 @@ All three tools render `generic` cards (`read` / `execute` / `delete`); `cordis_ Namespace plugin: named exports `name` / `inject` / `Config` / `apply`, no default export ([docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schemas | The conversation model sees `cordis_inspect`, `cordis_mount`, and `cordis_unmount` whenever this plugin is visible. | Fixed schema cost on every request in that tool view. | +| Tool-call history and results | Inspect returns selected live services, plugins, tools, or generated API and event references; mount and unmount return lifecycle facts or structured errors. The submitted mount program remains in the assistant tool-call history. | Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small. | +| Later requests after a mount | A mounted plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; unmount removes those contributions after quiescence. | Indirect token impact equals the mounted plugin's contributions and lasts only for the mount lifetime. | + ## Known Limitations and Deferred Work - **The sandbox is containment for honest code, not a security boundary** — host-realm helpers on the sandbox global are reachable, so mount code can reach Node; load this plugin as deliberately as you would grant a bash tool (see § Trust stance). diff --git a/packages/core/agent-core/README.md b/packages/core/agent-core/README.md index bb75fdbfca..c7cadb616c 100644 --- a/packages/core/agent-core/README.md +++ b/packages/core/agent-core/README.md @@ -4,13 +4,6 @@ The **default executor-less, UI-less agent spine** as ONE Cordis bundle plugin. This is the package to read to see **the whole shared plugin tree at once**: the teaching overview of the spine behind every app package. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Composed system prompt and session prefix | Through its children, the bundle supplies the harness identity, configured persona, and the local skill catalog when skills exist. | The bundle adds no wrapper prose; input cost is exactly the sum of the child contributions, repeated on each request according to their lifecycles. | -| Composed tool surface | The `skill` schema is present, and the three bash schemas appear when a bash executor activates `dsh-tool-bash`; `tools` config can select normal, Code Mode, or both. | Fixed per-request schema or SDK cost for the visible composition. Tool results add data-dependent retained history. | - ## The tree it loads `apply(ctx, config)` mounts each of these as a child of the bundle fiber: @@ -56,6 +49,13 @@ The bundle FORWARDS each field to the child that owns it: `agents` to `agent-loo A YAML include can dedupe the config, but it cannot OWN a `bin`, and it can only *describe* the front-door coupling in a comment and trust each leaf to obey. Moving the spine into a package, and the front-door cluster into the app packages, means the default leaf for an ACP server has no logger entry to copy wrong — "the ACP app never logs to stdout" stops being a prose warning a leaf must remember and becomes the app package's default shape (a leaf can still add a sibling logger, so the rule stays documented — but it has nothing to get wrong by default). Services register in the root store keyed by their isolate symbol, so a child loaded here is visible to the bundle's siblings (the leaf's adapter and executor) exactly as a nested `plugin-include` subtree's services were — cordis gates every read on `inject`, never on load order. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed system prompt and session prefix | Through its children, the bundle supplies the harness identity, configured persona, and the local skill catalog when skills exist. | The bundle adds no wrapper prose; input cost is exactly the sum of the child contributions, repeated on each request according to their lifecycles. | +| Composed tool surface | The `skill` schema is present, and the three bash schemas appear when a bash executor activates `dsh-tool-bash`; `tools` config can select normal, Code Mode, or both. | Fixed per-request schema or SDK cost for the visible composition. Tool results add data-dependent retained history. | + ## Known Limitations and Deferred Work - **The spine set is fixed in code** — `apply()` mounts every child unconditionally (including `tool-bash`); no config excludes or replaces one, so swapping the loop or dropping a spine member means composing a different bundle. diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index cde258a9a1..6a233a03b8 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -4,13 +4,6 @@ THE concrete agent plugin: `ReactLoopAgent` and the loop driver. Implements the This is the only package in the harness that contains concrete loop logic. Everything else is an abstract service or a plugin against extension seams — new behavior goes into plugins, not here. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Complete conversation request | For each step, the loop sends the rendered per-agent system prompt, visible tool schemas, the frozen session prefix, and the session's derived messages. It supplies `model` and `cwd` variable values but no additional fixed prose. | System text, schemas, and prefix are paid again on every step. Per-agent scoping can substitute or remove individual contributions. | -| Retained message history | Accepted user messages, assistant messages, tool calls and results, injected context, and steering are logged and sent on later steps. Raw stream chunks, lifecycle boundaries, and other log-only events are excluded. | Input grows with every surface message until a compaction replacement shadows older nodes; a multi-step tool turn resends the accumulated prefix and history each step. | - ## Service: `AgentLoop` (ctx key: `agentLoop`) ### Public API @@ -110,6 +103,13 @@ Everything that goes beyond "call the model, run the tools, repeat" belongs to p - Persistence: `session/event` + `session/flush` - UI: `session/event` (assistant token stream, boundaries, tool activity) + `agent/*` control events (`agent/status`, `agent/created`/`agent/disposed`) +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Complete conversation request | For each step, the loop sends the rendered per-agent system prompt, visible tool schemas, the frozen session prefix, and the session's derived messages. It supplies `model` and `cwd` variable values but no additional fixed prose. | System text, schemas, and prefix are paid again on every step. Per-agent scoping can substitute or remove individual contributions. | +| Retained message history | Accepted user messages, assistant messages, tool calls and results, injected context, and steering are logged and sent on later steps. Raw stream chunks, lifecycle boundaries, and other log-only events are excluded. | Input grows with every surface message until a compaction replacement shadows older nodes; a multi-step tool turn resends the accumulated prefix and history each step. | + ## Known Limitations and Deferred Work - **Tool calls within a step execute sequentially** — parallel execution waits on concurrency-safety metadata in the tool contract (see `dsh-tools`). diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index 1c7bf63b99..f516618c70 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -2,13 +2,6 @@ Agent interface, registry, and `agent/*` event vocabulary. Every plugin (UI, hooks, orchestrators) programs against the `Agent` handle defined here — it has zero loop dependency, so the loop is swappable. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| User, steering, and injected messages | `send`, `steer`, and `inject` feed the owning session. `agent/prompt-submit`, `agent/session-prefix`, and other declared events let plugins block a prompt or add request material; this interface contributes no fixed prose itself. | Accepted content becomes retained history or a repeated session prefix; blocked content contributes no request tokens. Size is caller- and plugin-dependent. | -| Agent-scoped request composition | Registrations through `agent.ctx` can shadow prompt sections or tools and can install agent-only interceptors during unpublished setup. | The package adds zero tokens itself; scoped contributions affect only that agent and disappear on disposal. | - ## Service: `AgentRegistry` (ctx key: `agents`) Tracks live agents so UI, hook, and orchestrator plugins can find them without importing the concrete loop package. @@ -59,6 +52,13 @@ The handle every plugin programs against: - Event listeners: all `agent/*` events are declared here — no dependency on the loop package needed. - Subagent delegation: implemented by `@deepseek-ai/dsh-subagent`, not by a method on `Agent`; providers create or drive ordinary `Agent` handles through the factory seam, so spawn/fork/ACP transports stay outside the core agent interface. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| User, steering, and injected messages | `send`, `steer`, and `inject` feed the owning session. `agent/prompt-submit`, `agent/session-prefix`, and other declared events let plugins block a prompt or add request material; this interface contributes no fixed prose itself. | Accepted content becomes retained history or a repeated session prefix; blocked content contributes no request tokens. Size is caller- and plugin-dependent. | +| Agent-scoped request composition | Registrations through `agent.ctx` can shadow prompt sections or tools and can install agent-only interceptors during unpublished setup. | The package adds zero tokens itself; scoped contributions affect only that agent and disappear on disposal. | + ## Known Limitations and Deferred Work - **Inter-agent channels beyond delegation** — shared state, streaming child output, and background/poll semantics remain outside the current synchronous `ctx.subagents` seam. diff --git a/packages/core/scope/README.md b/packages/core/scope/README.md index e8ce1b41f1..a939a4c2af 100644 --- a/packages/core/scope/README.md +++ b/packages/core/scope/README.md @@ -2,12 +2,6 @@ Scoped-context registration primitive. `createScope(ctx, key)` mints a Cordis context that TAGS everything registered through it with an opaque `ScopeKey` and OWNS those registrations' lifetime (one backing fiber drives both facts); `scopeOf(ctx)` reads the tag; `scopeTarget(base, key)` builds the dispatch carrier that makes an event scope-filtered — listeners registered through a scoped context fire only for their key's subject, while plain plugin listeners keep firing for every subject. The agent loop is the one scope minter today (one scope per live agent, key = the `Agent` object — the `Agent.ctx` contract in `dsh-agent`), but the mechanism is key-agnostic so packages below the agent layer (`dsh-session`, `dsh-system-prompt`) depend on it without a dependency cycle. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Per-agent visibility control | This package emits no text or schema. It decides whether agent-scoped prompt sections, variables, tools, restrictions, and listeners apply to one agent, can shadow same-named global contributions, and removes them with that agent. | Zero direct tokens. It can add, replace, or remove whole contributions for one agent without changing another agent's request. | - ## Public API - `createScope(ctx: Context, key: ScopeKey): Scope` Mint a scope under `ctx`'s fiber. Usable synchronously (effect collection is uid-gated; service resolution falls through to the minting plugin's dependency surface). Throws on a primitive key, or when `ctx`'s fiber is disposing (`INACTIVE_EFFECT`). @@ -26,6 +20,12 @@ Ownership and visibility derive from ONE fact — which context a registration w Handing out a scoped context hands out the minting plugin's service-resolution capability (resolution walks the minting fiber's dependency chain, not the holder's) — mint scopes from a plugin whose `inject` surface is what scope holders should reach. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Per-agent visibility control | This package emits no text or schema. It decides whether agent-scoped prompt sections, variables, tools, restrictions, and listeners apply to one agent, can shadow same-named global contributions, and removes them with that agent. | Zero direct tokens. It can add, replace, or remove whole contributions for one agent without changing another agent's request. | + ## Known Limitations and Deferred Work - **Only scope-aware surfaces isolate state** — registries must file by `scopeOf()` and events must dispatch through `scopeTarget()`; an arbitrary Cordis service remains context-global merely because it is called through a scoped context. diff --git a/packages/core/session/README.md b/packages/core/session/README.md index f42b31f1cd..13131f0aec 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -2,13 +2,6 @@ Event-sourced session log and in-memory store. A `Session` is the append-only source of truth for an agent's whole interaction history — the LLM message history is *derived* from it. A **surface** layer (a linked list of message-producing events) is maintained on top of the raw log for efficient derivation and compaction. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Derived message history | The model receives projections of `user/message`, `assistant/message`, `tool/result`, `context/message`, and `steering/message` surface nodes. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message. | Appended surface nodes are resent on later steps. A `replace` surface operation removes the shadowed nodes from future inputs without deleting their raw log records. | -| Logged request header | The session reconstructs the system prompt, tool schemas, call config, and session prefix that the loop actually sent. Header events do not add a second copy to message history; the prefix is prepended outside `deriveMessages()`. | Zero duplicate tokens from logging. The reconstructed prefix, system text, and schemas still incur their normal per-request cost. | - ## Service: `SessionStore` (ctx key: `sessions`) Creates and holds event-sourced `Session` instances. Persistence is intentionally not implemented here — plugins subscribe to `session/event` and flush on `session/flush`. @@ -80,6 +73,13 @@ Every `SessionEvent` carries two optional top-level fields (structural metadata) - Replay/fork: `ctx.sessions.create(id, { seed })` seeds a new session with an existing event log. The surface rebuilds deterministically from `surfaceOp` markers in the seeded events. The seed is validated to the SAME always-on invariants `append` enforces — contiguous seqs, JSON-serializable data, and required `surfaceOp` markers on surface-eligible events — so marker-less message events are rejected at construction rather than silently vanishing from `deriveMessages()`. Broader turn-enclosure checks stay in `dsh-invariants` and persistence repair. Ordinary live-session forks use `ctx.sessions.fork(source, boundary?, childSessionId?)`, where `boundary` is the inclusive source event seq to fork through. - Compaction: the `dsh-compact-basic` plugin appends a `user/message` with `surfaceOp: { op: 'replace', start, end }` to shadow old surface nodes behind a summary checkpoint. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Derived message history | The model receives projections of `user/message`, `assistant/message`, `tool/result`, `context/message`, and `steering/message` surface nodes. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message. | Appended surface nodes are resent on later steps. A `replace` surface operation removes the shadowed nodes from future inputs without deleting their raw log records. | +| Logged request header | The session reconstructs the system prompt, tool schemas, call config, and session prefix that the loop actually sent. Header events do not add a second copy to message history; the prefix is prepended outside `deriveMessages()`. | Zero duplicate tokens from logging. The reconstructed prefix, system text, and schemas still incur their normal per-request cost. | + ## Known Limitations and Deferred Work - **Session branching/tree** (pi-style entry tree) — deferred unless needed beyond boundary-based `fork()`. diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index 91002cd1c8..5708083d99 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -2,13 +2,6 @@ System prompt assembly registry. Plugins contribute ordered text sections, tool-schema providers, named prompt variables, and authoritative named protections; the agent loop calls `assemble(context)` once per step, and `renderPrompt(assembly)` is the full system prompt the model sees. The plugin registers the harness-owned openers itself — the static `harness:identity` section and the deployment's `deployment:persona` section — so they exist for every agent regardless of which loop plugin drives it. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt | Every assembly starts with `You are an AI agent powered by the DeepSeek Harness SDK.`, then the configured persona and ordered plugin sections after strict variable interpolation. Empty sections disappear; scoped sections and variables can shadow globals for one agent. | Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content. | -| Tool schemas | The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent; reordering changes cache shape but not semantic content. | - ## Config | Key | Default | Meaning | @@ -50,6 +43,13 @@ Merge-extensible: plugins can declare extra fields on `PromptAssembly` and `Asse Design rationale: [the prompt-variables RFC](../../../docs/rfc/implemented/architecture/2026-07-05-prompt-variables-and-tool-guidance-ownership.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Every assembly starts with `You are an AI agent powered by the DeepSeek Harness SDK.`, then the configured persona and ordered plugin sections after strict variable interpolation. Empty sections disappear; scoped sections and variables can shadow globals for one agent. | Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content. | +| Tool schemas | The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent; reordering changes cache shape but not semantic content. | + ## Known Limitations and Deferred Work - **Deployment-authored prompt text is config/composition only** — this plugin owns the global persona default, creator plugins may register agent-scoped shadows, and other sections come from the plugin that owns the fact; there is no end-user prompt-editing API. diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index 6f86fe501f..c65948464b 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -2,13 +2,6 @@ Tool registry and execution pipeline. Tool plugins register their schemas and executors; the agent loop executes each call through `tools/pre-execute` (the extensible allow/deny gate) → monotonic registered guards → `tools/execute` (an around-dispatch wrapper for timeout/retry/metrics plugins) → `tools/post-execute` (inspect/replace the result, attach context) → the observe-only `tools/result` notification. The registry also owns HOW its tools are presented to the model — its `mode` config selects native function calling, [Code Mode](#code-mode), or both. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead protects one `run_code` wire schema and adds a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's set. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | -| Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | - ## Service: `ToolRegistry` (ctx key: `tools`) ### Config @@ -147,6 +140,13 @@ Under `mode: code` (or `both`) the registry turns the tool surface into a progra The wire collapse is the registry's own contribution (`systemPrompt.tools()` is mode-aware), so the logged `request/header` records it for free. With no deliberate schema-adding assembly listener, `code` assembles exactly `[run_code]`, pinned by tests and the snapshot goldens; protection guarantees that `run_code` and `tools:sdk` remain present, not that unrelated listener additions are erased. Try it: `pnpm run demo:code-mode` ([the coding-agent example's Code Mode overlay](../../../examples/coding-agent/README.md#code-mode)); `pnpm run demo:code-mode acp` serves the same mode over ACP instead of the REPL. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead protects one `run_code` wire schema and adds a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's set. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | +| Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | + ## Known Limitations and Deferred Work - **Native tool calls execute sequentially** — `ToolDefinition` carries no concurrency-safety metadata; adding it (and parallel execution in the loop) waits on the deferred tool-shapes review (`TODO(review)`). diff --git a/packages/fs/fs-local/README.md b/packages/fs/fs-local/README.md index 135791e9c4..b45cbb13dd 100644 --- a/packages/fs/fs-local/README.md +++ b/packages/fs/fs-local/README.md @@ -10,12 +10,6 @@ await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) // freshness policy gate and @deepseek-ai/dsh-tool-fs to expose read/write/edit. ``` -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Filesystem tool results, indirectly | Through `dsh-tool-fs`, the model sees line-windowed UTF-8 file content, mutation acknowledgements, or structured filesystem errors. Real paths, versions, atomic-write mechanics, and directory metadata remain internal unless a consumer renders them. | Zero direct tokens. Read tokens are bounded by the tool's line, line-length, and byte caps; mutation results are small and remain in history until compaction. | - ## Behavior - **`resolve(path, opts?)`** — a relative `path` resolves against `opts.cwd` when the caller supplies one (the model-facing tools pass the calling agent's session cwd — see [the per-session cwd RFC](../../../docs/rfc/implemented/architecture/2026-07-02-fs-per-session-cwd.md)), else `config.cwd` (default `process.cwd()`); an absolute `path` ignores both. The `targetKey` is the file's `realpath`, so two input paths reaching the same file through symlinks share one identity, and writes/edits land on the link target (preserving the link). A not-yet-existing path uses the realpathed parent directory plus basename when the parent exists; only an unresolvable parent falls back to the absolute path. `displayPath` is the absolute (un-resolved) path. @@ -27,6 +21,12 @@ await ctx.plugin(LocalFileSystem, { cwd: process.cwd() }) The raw I/O lives in `src/fsio.ts` (Cordis-free, independently unit-tested); `src/index.ts` is the thin service wiring. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Filesystem tool results, indirectly | Through `dsh-tool-fs`, the model sees line-windowed UTF-8 file content, mutation acknowledgements, or structured filesystem errors. Real paths, versions, atomic-write mechanics, and directory metadata remain internal unless a consumer renders them. | Zero direct tokens. Read tokens are bounded by the tool's line, line-length, and byte caps; mutation results are small and remain in history until compaction. | + ## Known Limitations and Deferred Work - **`config.cwd` is not a sandbox** — it is a resolution default, not containment: absolute paths and `..` escape it. Enforce containment with a stricter `ctx.fs` backend or a permission plugin on the `tools/execute` waterfall ([capability-seam RFC](../../../docs/rfc/implemented/architecture/2026-06-17-filesystem-capability-seam.md#consequences)). diff --git a/packages/fs/fs-policy/README.md b/packages/fs/fs-policy/README.md index a81f6b1f05..df454a74bd 100644 --- a/packages/fs/fs-policy/README.md +++ b/packages/fs/fs-policy/README.md @@ -16,12 +16,6 @@ declare const ctx: Context await ctx.plugin(FsPolicy) ``` -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Filesystem tool outcome | This plugin adds no prompt or schema. It can turn an unobserved or stale write or edit into a structured `FS_NOT_OBSERVED` or `FS_STALE_VERSION` error result instead of a success; observation state itself is never shown. | Zero tokens on allowed operations beyond the ordinary tool result. A denial adds a small retained error result and avoids any success payload. | - ## The four-layer split | Layer | Package | Role | @@ -53,6 +47,12 @@ The `fs/write-intent`/`fs/edit-intent` slots hold exactly one decider — this p Because the plugin influences the world only through events, removing it does not break `@deepseek-ai/dsh-tool-fs` at a service-injection boundary: the tool falls through to the bare `ctx.fs` provider (unconditional write/edit, no observed-state). Loading it back layers the policy on. That graceful add/remove is the whole point of the event gate over a mandatory method service. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Filesystem tool outcome | This plugin adds no prompt or schema. It can turn an unobserved or stale write or edit into a structured `FS_NOT_OBSERVED` or `FS_STALE_VERSION` error result instead of a success; observation state itself is never shown. | Zero tokens on allowed operations beyond the ordinary tool result. A denial adds a small retained error result and avoids any success payload. | + ## Known Limitations and Deferred Work - **Observed state does not survive a session resume** — persistence of the `WeakMap` record is deferred, so a resumed session must re-read files before guarded writes/edits. diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index 0376bf1c0f..851a509cfd 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -13,12 +13,6 @@ This package is the provider-seam layer of the four-layer filesystem stack, spli A future sandboxed, virtual, or remote backend implements this interface and the policy/tool layers don't change. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The provider seam registers no prompt or tool. `dsh-tool-fs` converts provider text and structured `FsError` values into model-visible read, write, and edit results; policy listeners can change which outcome it receives. | Zero direct tokens. File content and errors enter context only through a consumer, whose window and byte caps determine result size. | - ## Service API (`ctx.fs`) A backend subclasses `FileSystem` and implements seven primitives. @@ -49,6 +43,12 @@ This package declares three events (see the generated [events catalog](../../../ `FsTargetKey` / `FsVersion` are branded opaque ids ([the branded-ids RFC](../../../docs/rfc/implemented/architecture/2026-06-20-branded-ids.md)) — consumers must not parse `targetKey` or interpret `version`; only `displayPath` is for model/UI output. `FsWriteIntent` is the explicit GUARDED write intent (`createIfAbsent` creates a missing target and rejects an existing one with `FS_NOT_OBSERVED`; `replaceIfVersion` replaces only at the observed version, else `FS_STALE_VERSION`); omitting it from `writeText` is the third, unconditional state. Failures throw `FsError` (extends `HarnessError`, [the structured error taxonomy RFC](../../../docs/rfc/implemented/architecture/2026-06-11-structured-error-taxonomy.md)) carrying a stable `FsErrorCode` (`FS_NOT_FOUND`, `FS_NOT_DIRECTORY`, `FS_NOT_TEXT`, `FS_NOT_REGULAR_FILE`, `FS_PERMISSION_DENIED`, `FS_IO_ERROR`, `FS_STALE_VERSION`, `FS_NOT_OBSERVED`, `FS_AMBIGUOUS_EDIT`, `FS_EDIT_NOT_FOUND`, `FS_ABORTED`); the tool registry surfaces `{ name, code }` on `isError` results. See `src/types.ts` for the full contracts. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The provider seam registers no prompt or tool. `dsh-tool-fs` converts provider text and structured `FsError` values into model-visible read, write, and edit results; policy listeners can change which outcome it receives. | Zero direct tokens. File content and errors enter context only through a consumer, whose window and byte caps determine result size. | + ## Known Limitations and Deferred Work - **Text-only by contract** — backends reject binary/non-UTF-8 content with `FS_NOT_TEXT`; binary-safe operations are a deliberate deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md). diff --git a/packages/fs/tool-fs/README.md b/packages/fs/tool-fs/README.md index 8bcdba2ffb..c955956f55 100644 --- a/packages/fs/tool-fs/README.md +++ b/packages/fs/tool-fs/README.md @@ -11,14 +11,6 @@ await ctx.plugin(ToolFs) // this package — re `@deepseek-ai/dsh-fs-policy` is **optional**: omit it and the tools run against the bare provider (unconditional write/overwrite/edit, no observed-state). A deployment that loads these tools is expected to also load it, so the behavior is read-before-write/edit. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt | Visible agents receive three short sections explaining line-windowed reads, whole-file writes, literal edits, and the default read-before-mutate habit. | Fixed guidance cost per request while the tools are visible. | -| Tool schemas | The model sees `read`, `write`, and `edit` with their snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. | -| Tool-call history and results | Read returns numbered UTF-8 lines and a pagination or cap footer; write and edit return concise success text or structured errors. The model-emitted write or edit content also remains in the assistant tool-call arguments. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`. Call arguments and results are resent until compaction, so large write payloads can dominate history even though the success result is small. | - ## Config All keys are optional; the defaults are the shipped read caps. @@ -56,6 +48,14 @@ The tool passes `exec` (the tool-execution context) as the opaque `actor` on eve The read rendering (line windowing + output formatting) lives in `src/read-render.ts` (Cordis-free, independently unit-tested); `src/read.ts`/`write.ts`/`edit.ts` are the tool executors and `src/index.ts` composes them. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Visible agents receive three short sections explaining line-windowed reads, whole-file writes, literal edits, and the default read-before-mutate habit. | Fixed guidance cost per request while the tools are visible. | +| Tool schemas | The model sees `read`, `write`, and `edit` with their snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. | +| Tool-call history and results | Read returns numbered UTF-8 lines and a pagination or cap footer; write and edit return concise success text or structured errors. The model-emitted write or edit content also remains in the assistant tool-call arguments. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`. Call arguments and results are resent until compaction, so large write payloads can dominate history even though the success result is small. | + ## Known Limitations and Deferred Work - **No directory-listing, glob, grep, or search tools ship** — a deferral of [the tool-schemas RFC](../../../docs/rfc/implemented/feature/2026-06-17-filesystem-tool-schemas.md); `ctx.fs.listDir` serves provider code such as skill discovery but still has no model-facing consumer, so models fall back to `bash`. diff --git a/packages/guard/repeat-tool-guard/README.md b/packages/guard/repeat-tool-guard/README.md index 9bb3bc0e02..8600b3d85e 100644 --- a/packages/guard/repeat-tool-guard/README.md +++ b/packages/guard/repeat-tool-guard/README.md @@ -2,12 +2,6 @@ An advisory loop-breaker, not a model-facing tool: it never appears in the tool list, never vetoes or rewrites a call, and adds exactly one behavior — it watches each agent's stream of tool calls, counts runs of consecutive calls to the same tool with identical canonicalized arguments, and at configured run lengths injects an escalating advisory reminder telling the model to stop repeating itself, re-read the last result, and either change approach or conclude. The decision (retry differently, gather more evidence, or finish) stays entirely with the model: a legitimately repeated call is delayed by nothing and blocked by nothing. Decision record: [the repeat-tool-guard RFC](../../../docs/rfc/implemented/feature/2026-07-08-repeat-tool-guard.md). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Conditional context message | At configured consecutive-repeat thresholds, that agent receives a source-attributed synthetic user reminder after the tool results, asking it to inspect the prior result and change approach or finish. No tool schema or normal-call text is added. | Zero tokens before a threshold. Each reminder is retained history; the detailed form caps the quoted canonical arguments at `argumentsPreviewChars`, while agents keep independent counters. | - ## Config ```yaml @@ -42,6 +36,12 @@ Reminders ride the post-execute decision's `additionalContext` (source `{kind: ' Unit suites drive a real agent loop against a mock adapter (no network) and cover the chain semantics above to per-file 100%. The snapshot tier owns the transcript surface: a scripted-replay scenario repeats a call five times and pins both reminder tiers (gentle at 3, detailed at 5) as `context/message`s in the ACP transcript. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conditional context message | At configured consecutive-repeat thresholds, that agent receives a source-attributed synthetic user reminder after the tool results, asking it to inspect the prior result and change approach or finish. No tool schema or normal-call text is added. | Zero tokens before a threshold. Each reminder is retained history; the detailed form caps the quoted canonical arguments at `argumentsPreviewChars`, while agents keep independent counters. | + ## Known Limitations and Deferred Work - **Exact-match detection only** — canonicalization is a deep key-sort, so near-identical variants (a tweaked path, extra whitespace inside a value) evade the chain; fuzzy matching is rejected pending evidence of need. diff --git a/packages/hooks/hook-protocol/README.md b/packages/hooks/hook-protocol/README.md index f39dfe6ccf..754d6ee6f1 100644 --- a/packages/hooks/hook-protocol/README.md +++ b/packages/hooks/hook-protocol/README.md @@ -4,12 +4,6 @@ The **shared core** of the Claude Code / Codex hook wire protocol. NOT a cordis Why a shared lib at all: Codex deliberately reimplements a *subset* of the Claude Code hook protocol — the same `hooks.json` matcher-group shape, the same exit-code/stdout output contract, the same command-hook execution model. The genuinely-shared parts live here; each bridge owns only what differs. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This library registers nothing. Its `hook/invoked` and `hook/result` events are log-only and do not enter derived messages; bridge packages decide whether parsed `additionalContext`, blocks, or continuation feedback reach the model. | Zero direct tokens. Persisted hook audit records add no context tokens. | - ## What's shared (here) vs. per-dialect (the bridges) | Concern | Here (`dsh-hook-protocol`) | The bridge (`dsh-hooks-claude` / `-codex`) | @@ -35,6 +29,12 @@ Declaration-merged into `SessionEventMap` (log-only, like `compact/*` — NOT a Like every event they must sit inside an open turn. The mid-turn points (`PreToolUse`/`PostToolUse`/`UserPromptSubmit`/`Stop`) fire inside the loop's open turn by construction; `SessionStart` gets no `hook/*` record (its injected `context/message` is the durable evidence) — see the hooks RFC. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This library registers nothing. Its `hook/invoked` and `hook/result` events are log-only and do not enter derived messages; bridge packages decide whether parsed `additionalContext`, blocks, or continuation feedback reach the model. | Zero direct tokens. Persisted hook audit records add no context tokens. | + ## Known Limitations and Deferred Work - **`HookOutput.updatedInput` is parsed but not honored** — input rewrite is a deferred consistency-design problem ([the pre-tool-input-rewrite RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md)); a bridge logs + warns when a hook sets it. See `src/types.ts` for the full contracts. diff --git a/packages/hooks/hooks-claude/README.md b/packages/hooks/hooks-claude/README.md index 858d4c70ca..f55790e9e7 100644 --- a/packages/hooks/hooks-claude/README.md +++ b/packages/hooks/hooks-claude/README.md @@ -4,13 +4,6 @@ A cordis plugin that runs a user's existing **Claude Code** hook config (a `hook A native cordis plugin could do everything this bridge does — more powerfully, with typed returns and no serialization boundary. **The bridge exists only to run UNMODIFIED external CC hooks faithfully**; anything bespoke should be a native plugin on the same seams (see [the interception-seams RFC](../../../docs/rfc/implemented/feature/2026-06-30-interception-seams.md)). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Hook-provided context | `SessionStart`, accepted prompt, post-tool, and live in-process subagent-start hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. Remote-child injection has no local target. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent in later conversation requests until compaction. | -| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny or ask before a tool, block a post-tool result with feedback, or force another model step. `systemMessage` and `updatedInput` are logged or warned but are not model-visible in this implementation. | Blocking a prompt removes that prompt's request tokens; denial or feedback adds a retained error or context result; forced continuation pays another full request. | - ## Config ```ts @@ -57,6 +50,13 @@ The matcher subject is the tool name (`PreToolUse`/`PostToolUse`), the session s Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-claude' }` source. `agent.inject()` defaults a missing source to `{ kind: 'user' }`, which would mislabel plugin context as a user prompt — so the bridge always names itself. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Hook-provided context | `SessionStart`, accepted prompt, post-tool, and live in-process subagent-start hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. Remote-child injection has no local target. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent in later conversation requests until compaction. | +| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny or ask before a tool, block a post-tool result with feedback, or force another model step. `systemMessage` and `updatedInput` are logged or warned but are not model-visible in this implementation. | Blocking a prompt removes that prompt's request tokens; denial or feedback adds a retained error or context result; forced continuation pays another full request. | + ## Known Limitations and Deferred Work - **`updatedInput` (tool-input rewrite)** is logged + warned, **not honored** — input rewrite is a deferred consistency-design problem ([the pre-tool-input-rewrite RFC](../../../docs/rfc/proposed/feature/2026-06-30-pre-tool-input-rewrite.md)). diff --git a/packages/hooks/hooks-codex/README.md b/packages/hooks/hooks-codex/README.md index f4ad398aa8..b5ed2ec35a 100644 --- a/packages/hooks/hooks-codex/README.md +++ b/packages/hooks/hooks-codex/README.md @@ -12,13 +12,6 @@ Codex's hook protocol is a deliberate **subset** of Claude Code's (same `hooks.j A native cordis plugin could do everything this bridge does, more powerfully; the bridge exists only to run UNMODIFIED external Codex hooks faithfully (see [the interception-seams RFC](../../../docs/rfc/implemented/feature/2026-06-30-interception-seams.md)). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Hook-provided context | `SessionStart`, accepted prompt, and post-tool hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent until compaction. | -| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny a tool, block a post-tool result with feedback, or force another model step. Codex `systemMessage` is not surfaced. | Blocking a prompt removes its request tokens; denial or feedback adds retained result text; forced continuation pays another full request. | - ## Config ```ts @@ -61,6 +54,13 @@ A tool call's payload carries the real `tool_name` (the same value the matcher t Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-codex' }` source (`agent.inject()` would otherwise default it to `{ kind: 'user' }`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Hook-provided context | `SessionStart`, accepted prompt, and post-tool hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent until compaction. | +| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny a tool, block a post-tool result with feedback, or force another model step. Codex `systemMessage` is not surfaced. | Blocking a prompt removes its request tokens; denial or feedback adds retained result text; forced continuation pays another full request. | + ## Known Limitations and Deferred Work - **Stop loop-guard** (`TODO(stop-loop-guard)`) — as in CC, a Stop hook that unconditionally blocks would force-continue every step (`stop_hook_active` is always `false` here); the loop-guard is deferred, and a hook author must self-limit until it lands. diff --git a/packages/llm/llm-deepseek/README.md b/packages/llm/llm-deepseek/README.md index 11f7396d77..f491605b39 100644 --- a/packages/llm/llm-deepseek/README.md +++ b/packages/llm/llm-deepseek/README.md @@ -4,13 +4,6 @@ DeepSeek chat-completions adapter for the harness LLM seam: hand-rolled `fetch` A second, independent implementation of the same seam exists in `@deepseek-ai/dsh-llm-pi-ai` (library-backed). Same Config shape — pick one per context (registering both for the same model names throws by design). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| DeepSeek request | The selected DeepSeek model receives the harness system prompt, message history, tool schemas, stop sequences, and call config without adapter-authored prompt prose. On a prior assistant turn with tool calls, its reasoning content is passed back as required; reasoning from tool-call-free turns is omitted. | Provider tokenization governs exact input. Conditional reasoning passback increases tool-round-trip context, while dropping other reasoning avoids paying those tokens again; cache-read usage is reported when available. | -| DeepSeek response | Reasoning, text, and raw-string tool arguments are translated into harness chunks for the loop to log and assemble. | Generated tokens follow provider thinking and effort settings plus the request's `maxTokens`; only loop-retained blocks affect later input. | - ## Config ```yaml @@ -49,6 +42,13 @@ Non-2xx responses throw `LlmError` with stable codes: `AUTH` (401/403), `RATE_LI Unit suites run against a local `node:http` mock SSE server (no network). Real-API coverage lives in `tests/adapter.e2e.ts` (`pnpm run test:e2e`, key-gated): V4 Flash + V4 Pro across thinking enabled/disabled and both official effort levels, including the thinking+tools round trip with reasoning passback. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| DeepSeek request | The selected DeepSeek model receives the harness system prompt, message history, tool schemas, stop sequences, and call config without adapter-authored prompt prose. On a prior assistant turn with tool calls, its reasoning content is passed back as required; reasoning from tool-call-free turns is omitted. | Provider tokenization governs exact input. Conditional reasoning passback increases tool-round-trip context, while dropping other reasoning avoids paying those tokens again; cache-read usage is reported when available. | +| DeepSeek response | Reasoning, text, and raw-string tool arguments are translated into harness chunks for the loop to log and assemble. | Generated tokens follow provider thinking and effort settings plus the request's `maxTokens`; only loop-retained blocks affect later input. | + ## Known Limitations and Deferred Work - **`tool_choice` is not mapped** — not part of the core vocabulary (MVP cut, shared with the pi-ai twin). diff --git a/packages/llm/llm-pi-ai/README.md b/packages/llm/llm-pi-ai/README.md index a491f847fe..7a301f8504 100644 --- a/packages/llm/llm-pi-ai/README.md +++ b/packages/llm/llm-pi-ai/README.md @@ -2,13 +2,6 @@ DeepSeek adapter for the harness LLM seam backed by [`@earendil-works/pi-ai`](https://www.npmjs.com/package/@earendil-works/pi-ai) (the LLM library behind the pi agent). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| DeepSeek request through pi-ai | The selected model receives the same logical system prompt, history, tools, stop sequences, and raw replayed tool arguments as the hand-written adapter. This package adds no prompt prose and removes pi-ai's own per-tool `strict` default to preserve that contract. | Provider tokenization governs exact input. Reasoning level changes generated and passback content; pi-ai reports reasoning inside output usage rather than as a separate count. | -| DeepSeek response | pi-ai events become harness reasoning, text, tool-call, usage, and finish chunks; parsed tool arguments are restored to raw JSON strings at the harness boundary. | Generated content affects later inputs only after the loop records it; adapter conversion adds no model-visible text. | - ## Why a second adapter exists `@deepseek-ai/dsh-llm-deepseek` already talks to the same endpoint. This package is its **design-verification twin**: same models, same wire protocol, completely different internals — a unified LLM library with its own event vocabulary versus hand-rolled fetch/SSE. Anything the harness `StreamChunk` protocol cannot express for BOTH implementations is a core-vocabulary bug. The differences it exercised on purpose: @@ -44,6 +37,13 @@ pi-ai declares the openai/anthropic/google/mistral/AWS SDKs as install-time depe Unit suites run against a local `node:http` mock SSE server (pi-ai's openai SDK happily talks to any base URL). Real-API coverage in `tests/adapter.e2e.ts` (`pnpm run test:e2e`, key-gated): V4 Flash + V4 Pro across all exposed reasoning levels (off/high/xhigh), the thinking+tools round trip, and a cross-adapter structural-equivalence check against llm-deepseek. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| DeepSeek request through pi-ai | The selected model receives the same logical system prompt, history, tools, stop sequences, and raw replayed tool arguments as the hand-written adapter. This package adds no prompt prose and removes pi-ai's own per-tool `strict` default to preserve that contract. | Provider tokenization governs exact input. Reasoning level changes generated and passback content; pi-ai reports reasoning inside output usage rather than as a separate count. | +| DeepSeek response | pi-ai events become harness reasoning, text, tool-call, usage, and finish chunks; parsed tool arguments are restored to raw JSON strings at the harness boundary. | Generated content affects later inputs only after the loop records it; adapter conversion adds no model-visible text. | + ## Known Limitations and Deferred Work - **`tool_choice` is not mapped** — same MVP contract as llm-deepseek. diff --git a/packages/llm/llm/README.md b/packages/llm/llm/README.md index cc1107fe54..8acff4f0da 100644 --- a/packages/llm/llm/README.md +++ b/packages/llm/llm/README.md @@ -2,13 +2,6 @@ Provider-neutral LLM vocabulary and abstract service. This package defines the canonical language spoken by the agent loop, session logs, and every plugin. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Provider request transport | This service adds no system text, schema, or message. It routes the already-assembled frozen `GenerateOptions` to one adapter, while `llm/stream` listeners may cache, retry, or replace the stream without mutating that request. | Zero direct context tokens. The selected adapter and provider tokenizer determine billing, cache accounting, and serialization overhead for the existing content. | -| Streamed model output | Text, reasoning, and tool-call chunks are exposed to the loop, which decides what becomes retained assistant history. | Output usage is provider-reported; later input cost arises only after the loop records assembled content. | - ## Service: `LlmService` (ctx key: `llm`) An adapter registry plus a single streaming call surface, interceptable via a waterfall event. @@ -55,6 +48,13 @@ Every product adapter must identify the application on every provider HTTP reque Two adapters implement `LlmAdapter` against this vocabulary, deliberately built on different internals to keep the contract honest (see [the twin LLM adapters](../../../docs/rfc/implemented/architecture/2026-06-13-twin-llm-adapters.md)): [`@deepseek-ai/dsh-llm-deepseek`](../llm-deepseek) (hand-rolled fetch/SSE) and [`@deepseek-ai/dsh-llm-pi-ai`](../llm-pi-ai) (via `@earendil-works/pi-ai`). The pair pinned down the `StreamChunk` conventions now documented in `types.ts` (usage before finish, raw-string tool arguments, the two sanctioned error paths). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Provider request transport | This service adds no system text, schema, or message. It routes the already-assembled frozen `GenerateOptions` to one adapter, while `llm/stream` listeners may cache, retry, or replace the stream without mutating that request. | Zero direct context tokens. The selected adapter and provider tokenizer determine billing, cache accounting, and serialization overhead for the existing content. | +| Streamed model output | Text, reasoning, and tool-call chunks are exposed to the loop, which decides what becomes retained assistant history. | Output usage is provider-reported; later input cost arises only after the loop records assembled content. | + ## Known Limitations and Deferred Work - **No retry/caching/rate-limit layer ships** — `llm/stream` is the intended wrap seam and has no production listener, so provider 429/5xx failures surface immediately. diff --git a/packages/session-persistence/session-persistence-jsonl/README.md b/packages/session-persistence/session-persistence-jsonl/README.md index 410879728a..4e150f85a0 100644 --- a/packages/session-persistence/session-persistence-jsonl/README.md +++ b/packages/session-persistence/session-persistence-jsonl/README.md @@ -2,12 +2,6 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence` (the `dsh-session-persistence` seam). One append-only `.jsonl` event log per session. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Resumed conversation history | JSONL storage contributes no live prompt or schema. Loading restores stored surface history and preserves prior request headers for reconstruction; the new loop composes its current envelope. An interrupted tail is balanced with error tool results. Raw `assistant/chunk` records do not duplicate messages. | Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus small repair results only after an interrupted tool turn. | - ## On-disk layout ``` @@ -36,6 +30,12 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence The plugin generalizes the example `session-jsonl.ts`: it subscribes to `session/created` (capture the header; persist a fork's seed once), `session/event` (snapshot each event when buffering — the live `session.events` object is mutable), and `session/flush`/dispose (drain the write-behind buffer through `append`). A per-session write cursor means a resumed session never re-appends already-stored events. Existing live sessions are seeded on plugin apply (HMR does not replay `session/created`). All backend operations for one session are serialized, and disposal awaits quiescence (every init + final drain) before returning, so no write lands after teardown. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | JSONL storage contributes no live prompt or schema. Loading restores stored surface history and preserves prior request headers for reconstruction; the new loop composes its current envelope. An interrupted tail is balanced with error tool results. Raw `assistant/chunk` records do not duplicate messages. | Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus small repair results only after an interrupted tool turn. | + ## Known Limitations and Deferred Work - **Only the current `SESSION_FORMAT_VERSION` (v0) loads** — the on-disk format is pre-release/unstable: a breaking format change is absorbed at v0 and non-current logs are rejected; there is no migration. diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index ab5755bb52..558a24af6c 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -4,12 +4,6 @@ A SQLite durable session-persistence backend — a second `SessionPersistence` i > **TODO:** this backend talks to `node:sqlite` directly. If a cordis database service (`cordis/db` / a `@cordisjs` SQL driver plugin) is adopted, route through that instead of holding a raw `DatabaseSync` here — the contract surface (`SessionPersistence`) would not change, only the storage driver. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Resumed conversation history | SQLite storage contributes no live prompt or schema. Loading restores the same surface history as JSONL and preserves prior headers for reconstruction; the new loop composes its current envelope. Interrupted rows are balanced with error tool results. Row metadata and raw chunks are not messages. | Zero live-request tokens. Resume restores retained history and pays the current envelope, with small repair-result tokens only for an interrupted tool turn. | - ## Storage model Each `SessionEvent` maps 1:1 onto a row in an `events` table `(session_id, seq, type, time, data, source_event_seqs, surface_op)` — `data` is the event payload as JSON text, so the row shape is the event verbatim (including `assistant/chunk`, keeping `seq` contiguous). The two `TEXT` columns `source_event_seqs` and `surface_op` are nullable; they store the event's optional surface-metadata fields (see [session surface](../../../docs/rfc/implemented/architecture/2026-06-18-session-surface.md)). Out-of-log metadata (`SessionHeader`) lives in a `sessions` row. A `sessions` row is written only by the first `append` — its existence is the lazy-materialization signal (`list` reports exactly the sessions that have a row), so no separate column is needed. @@ -35,6 +29,12 @@ interface Config { Like the JSONL backend, the plugin also installs the `session/event` → buffer → `session/flush` drain: it snapshots each event when buffered (the live `session.events` object is mutable), persists a fork's seed once on `session/created`, keeps a per-session write cursor so a resumed session never re-appends stored events, and seeds existing live sessions on apply (HMR does not replay `session/created`). Dispose awaits every in-flight init + final drain and then closes the database, so no write lands after teardown. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | SQLite storage contributes no live prompt or schema. Loading restores the same surface history as JSONL and preserves prior headers for reconstruction; the new loop composes its current envelope. Interrupted rows are balanced with error tool results. Row metadata and raw chunks are not messages. | Zero live-request tokens. Resume restores retained history and pays the current envelope, with small repair-result tokens only for an interrupted tool turn. | + ## Known Limitations and Deferred Work - **Raw `node:sqlite`, pending a cordis database service** — the backend holds a `DatabaseSync` directly; if a `cordis/db` / `@cordisjs` SQL driver is adopted, the storage driver routes through it (the `SessionPersistence` contract would not change) — a marked TODO. diff --git a/packages/session-persistence/session-persistence/README.md b/packages/session-persistence/session-persistence/README.md index eabd98cecf..c8e2cbe35b 100644 --- a/packages/session-persistence/session-persistence/README.md +++ b/packages/session-persistence/session-persistence/README.md @@ -4,12 +4,6 @@ The abstract durable session-persistence seam (`ctx.sessionPersistence`). Define The persisted unit IS the existing `SessionEvent` (event-sourced model — the log is the single source of truth), so there is no parallel "persisted message" type. Metadata that is NOT replayable conversation state (format version, cwd, lineage, seed boundary) travels separately as `SessionHeader`, owned by `dsh-session` and re-exported here. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Resumed conversation history | This seam adds no prompt or schema. Resume restores stored surface events as message history; stored request headers reconstruct earlier calls, while the new loop composes the current system prompt, tools, and session prefix for its next request. Crash repair inserts an error result for each unanswered tool call. | Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; crash repair can add small error-result tokens that keep the provider transcript valid. | - ## Service API (`ctx.sessionPersistence`) | Method | Contract | @@ -56,6 +50,12 @@ Three backends run these suites: an in-memory reference (in `tests/`), `dsh-sess Re-exported from `dsh-session`: `SessionHeader` (immutable session metadata: `version`, `id`, `createdAt`, `cwd?`, `parentSession?`, `seedLength?`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Resumed conversation history | This seam adds no prompt or schema. Resume restores stored surface events as message history; stored request headers reconstruct earlier calls, while the new loop composes the current system prompt, tools, and session prefix for its next request. Crash repair inserts an error result for each unanswered tool call. | Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; crash repair can add small error-result tokens that keep the provider transcript valid. | + ## Known Limitations and Deferred Work - **No deletion or retention surface** — the seam is `create`/`append`/`load`/`list` only; pruning stored sessions is out-of-band backend maintenance. diff --git a/packages/skill/skill-local/README.md b/packages/skill/skill-local/README.md index 44b8f15e33..7833f55e09 100644 --- a/packages/skill/skill-local/README.md +++ b/packages/skill/skill-local/README.md @@ -4,12 +4,6 @@ Local filesystem provider for the `ctx.skills` registry. This package implements one skill source. It scans local project, custom, and user skill roots, parses `SKILL.md` or flat Markdown skill files, and registers the provider on `ctx.skills`. The registry remains in `@deepseek-ai/dsh-skill`; the session-prefix catalog and model-facing loader tool remain in `@deepseek-ai/dsh-tool-skill`. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Skill catalog and loaded body, indirectly | When `dsh-tool-skill` is visible, discovered model-invocable skill names and descriptions enter its session-prefix catalog; a `skill` call returns the selected instructions and resource-base guidance. Paths, provider ranks, and disabled skills stay out of the catalog. | Zero direct tokens from this provider. Catalog cost scales with discovered entries under the consumer's per-description cap; a full body is added only after selection and remains in tool history. | - ## Plugin Requires `ctx.skills` (`inject: ['skills']`). @@ -42,6 +36,12 @@ When `ctx.fs` is available, discovery lists roots through `ctx.fs.listDir`, read Skills can be single-level directory bundles (`/SKILL.md`) or flat Markdown files (`.md`). Nested `**/SKILL.md` discovery is intentionally not part of v1. Frontmatter is parsed as YAML with the `yaml` package; it requires `name` and `description`, while `whenToUse`, `disableModelInvocation`, and `metadata` are optional. Names must be kebab-case. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Skill catalog and loaded body, indirectly | When `dsh-tool-skill` is visible, discovered model-invocable skill names and descriptions enter its session-prefix catalog; a `skill` call returns the selected instructions and resource-base guidance. Paths, provider ranks, and disabled skills stay out of the catalog. | Zero direct tokens from this provider. Catalog cost scales with discovered entries under the consumer's per-description cap; a full body is added only after selection and remains in tool history. | + ## Known Limitations and Deferred Work - **Discovery is one level deep** — only `//SKILL.md` and `/.md` are recognized; nested skill trees and package manifests are ignored. diff --git a/packages/skill/skill/README.md b/packages/skill/skill/README.md index 044cff7eeb..595b0d92f1 100644 --- a/packages/skill/skill/README.md +++ b/packages/skill/skill/README.md @@ -4,12 +4,6 @@ Pure agent skill provider registry. This package owns the `ctx.skills` interface. It does not know whether skills come from local files, embedded plugin data, HTTP, or another backend; providers register those sources with `ctx.skills.registerProvider(...)`. The shipped local implementation is [`@deepseek-ai/dsh-skill-local`](../skill-local). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The registry renders nothing and registers no tool. `dsh-tool-skill` turns `list()` summaries into a session prefix and a selected `get()` body into a tool result; provider failures can remove entries from that request's catalog. | Zero direct tokens. Catalog size, descriptions, and loaded body length affect context only through the consumer. | - ## Service: `SkillService` (ctx key: `skills`) ### Public API @@ -39,6 +33,12 @@ The registry validates candidate names, descriptions, ranks, and provider owners The registry does not render model guidance or register model-facing tools. [`@deepseek-ai/dsh-tool-skill`](../tool-skill) consumes `ctx.skills` to provide the session-prefix catalog and `skill` tool, so providers remain independent of the model surface. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The registry renders nothing and registers no tool. `dsh-tool-skill` turns `list()` summaries into a session prefix and a selected `get()` body into a tool result; provider failures can remove entries from that request's catalog. | Zero direct tokens. Catalog size, descriptions, and loaded body length affect context only through the consumer. | + ## Known Limitations and Deferred Work - **Completed catalogs have no TTL or watcher invalidation** — a provider's underlying files or remote data can change without a registration revision, so a cached cwd stays stale until eviction or provider/runtime reload. diff --git a/packages/skill/tool-skill/README.md b/packages/skill/tool-skill/README.md index 681855e6ef..a9ad9e0053 100644 --- a/packages/skill/tool-skill/README.md +++ b/packages/skill/tool-skill/README.md @@ -4,13 +4,6 @@ The model-facing skill catalog and `skill` tool. Requires `ctx.tools` and `ctx.skills` (`inject: ['tools', 'skills']`). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Session prefix | If model-invocable skills exist and this exact `skill` tool is visible, the agent receives one user-role `` listing sorted names and capped descriptions. The composed catalog is frozen for the loop instance and prepended to every request, outside ordinary history. | Repeated input cost scales with skill count and `catalogDescriptionMaxLength`; no catalog tokens are sent when the list is empty or the tool is hidden or shadowed. | -| Tool schema and result | The model sees the fixed `skill(name)` schema. A successful call returns the selected full instructions plus resource-resolution guidance; no duplicate `agent.inject()` copy is made. | Fixed schema cost per request. Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction. | - ## Session-prefix catalog The plugin contributes one user-role `` catalog through `agent/session-prefix`. It resolves skills for the calling session's cwd, forwards the prefix abort signal to discovery, and lists only sorted `name` and `description` entries; skill bodies, paths, sources, providers, and `whenToUse` hints remain outside the catalog. The catalog is omitted when no model-invocable skills are available, and also when that agent's tool view restricts away the shipped `skill` tool or resolves a same-name scoped shadow instead. This exact-definition check keeps prompt guidance, the model-visible schema, and executable dispatch aligned. @@ -27,6 +20,13 @@ Execution uses the calling agent's `session.header.cwd` so workspace-sensitive p The tool does not call `agent.inject()` in v1. Its result is already recorded as the tool result and becomes available to the next model step without duplicating the content as synthetic context. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Session prefix | If model-invocable skills exist and this exact `skill` tool is visible, the agent receives one user-role `` listing sorted names and capped descriptions. The composed catalog is frozen for the loop instance and prepended to every request, outside ordinary history. | Repeated input cost scales with skill count and `catalogDescriptionMaxLength`; no catalog tokens are sent when the list is empty or the tool is hidden or shadowed. | +| Tool schema and result | The model sees the fixed `skill(name)` schema. A successful call returns the selected full instructions plus resource-resolution guidance; no duplicate `agent.inject()` copy is made. | Fixed schema cost per request. Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction. | + ## Known Limitations and Deferred Work - **The catalog omits `whenToUse`, source, and provider metadata** — routing is based only on name and a capped description; `whenToUse` remains provider metadata and is not rendered by the loaded wrapper either. diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index 6ff15ca96c..e12586c5a7 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -4,13 +4,6 @@ The out-of-process **ACP subagent backend**: runs each child agent in a spawned It is the direction-inverted twin of the server-side bridge in [`@deepseek-ai/dsh-acp`](../../ui/acp): that package is the ACP *agent* (it answers `initialize`/`newSession`/`prompt`); this one is the ACP *client* (it *calls* them and implements the `sessionUpdate`/`requestPermission` callbacks). Point the configured command at the `acp-agent` example to "talk to our own process". -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent request | The remote child receives the standalone task through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation and cannot enforce the parent's scoped persona or tool filter. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | -| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or a stop-reason error, not intermediate messages or tool traffic. | Parent input grows only by the final result, which is data-dependent and retained until compaction. This provider adds no parent schema itself. | - ## What it does `start(request)` spawns the configured command, wraps its stdio in an ACP `ClientSideConnection`, and drives one session: `initialize` → `newSession` → `prompt`. `run.started` resolves after `newSession` publishes the remote session and rejects when initialization fails or cancellation wins first; the service emits no start/end pair for a child that never became live. The child's streamed `agent_message_chunk` text becomes the `SubagentResult.output`; the prompt's terminal `StopReason` maps to the stop reason. `dispose()` kills the subprocess and awaits its exit. @@ -77,6 +70,13 @@ The child env is built by [`buildChildEnv` from `@deepseek-ai/dsh-subagent-subpr Named `name` / `inject` / `Config` / `apply`, with **no default export**: the cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray default would collapse the module to the bare function and drop the `inject` namespace (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The remote child receives the standalone task through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation and cannot enforce the parent's scoped persona or tool filter. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | +| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or a stop-reason error, not intermediate messages or tool traffic. | Parent input grows only by the final result, which is data-dependent and retained until compaction. This provider adds no parent schema itself. | + ## Known Limitations and Deferred Work - **A fresh process per run** — persistent-process pooling is a future optimization ([the seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). diff --git a/packages/subagent/subagent-fork/README.md b/packages/subagent/subagent-fork/README.md index e3882dbb1a..c747988d78 100644 --- a/packages/subagent/subagent-fork/README.md +++ b/packages/subagent/subagent-fork/README.md @@ -2,13 +2,6 @@ The in-process **fork** subagent backend: a [`SubagentProvider`](../subagent/README.md) that runs each child as a child [`Agent`](../../core/agent) **seeded with a prefix of the parent's session log** — so the child inherits the parent's conversation context instead of starting fresh. Shares the run driver (`startInProcessRun`) with [`dsh-subagent-spawn`](../subagent-spawn/README.md); the only difference is the seed. The shared `run.started` boundary resolves only after the seeded child is published, so `subagent/start` observers see a live registry entry. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent history | The child receives the parent's balanced completed-turn surface prefix, then the new task, along with its own scoped persona, tool filter, and optional structured-output contract. The parent's current in-flight turn is excluded. | Forking duplicates the retained completed history into a separate child's requests; the child then accumulates its own tokens independently. A first-turn fork has no inherited history. | -| Parent tool result, indirectly | The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work. | Parent input grows by one data-dependent final result retained until compaction. | - ## The seed boundary (the crux) At the moment a subagent tool's `execute` runs, the parent's CURRENT turn is open and unbalanced: the log holds the `assistant/message` carrying this spawn's tool-call and the dangling `tool/call` with no `tool/result` yet. Seeding that raw prefix would give the child an open turn that the session constructor and the dev-mode [invariants](../../support/invariants) replay **reject**. @@ -29,6 +22,13 @@ The seam this rides on: `CreateAgentOptions.seed` (added on `dsh-agent`, threade See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, model inheritance, and depth tracking — all shared. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent history | The child receives the parent's balanced completed-turn surface prefix, then the new task, along with its own scoped persona, tool filter, and optional structured-output contract. The parent's current in-flight turn is excluded. | Forking duplicates the retained completed history into a separate child's requests; the child then accumulates its own tokens independently. A first-turn fork has no inherited history. | +| Parent tool result, indirectly | The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work. | Parent input grows by one data-dependent final result retained until compaction. | + ## Known Limitations and Deferred Work - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md index 4f8175cf2b..48bc34f22a 100644 --- a/packages/subagent/subagent-inprocess/README.md +++ b/packages/subagent/subagent-inprocess/README.md @@ -2,13 +2,6 @@ The shared **in-process subagent run driver**. A library with no provider or import-time registration that the in-process backends — [spawn](../subagent-spawn/README.md) (a fresh child) and [fork](../subagent-fork/README.md) (a child seeded with a prefix of the parent's log) — both build on. Each accepted run installs one provider-owned cleanup effect. The backends are thin shells that differ ONLY in the session seed they pass; everything downstream lives here, so neither backend depends on the other. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent request | The shared driver sends the task as the child's user message and composes per-child scoped persona and tool restrictions. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. Structured output adds fixed instruction and capability tokens only to that child for that run. | -| Parent result, indirectly | The driver extracts only the child's own last assistant output or captured structured value; seeded parent messages and intermediate child work do not become the result. | The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session. | - ## What it exports ### `startInProcessRun(ctx, request, options): SubagentRun` @@ -45,6 +38,13 @@ Delegation depth rides on a merge-extensible `AgentOptions.subagentDepth` field Thrown by `startInProcessRun` when a spawn would exceed the request's `maxDepth` cap; carries `attemptedDepth` and `maxDepth`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The shared driver sends the task as the child's user message and composes per-child scoped persona and tool restrictions. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. Structured output adds fixed instruction and capability tokens only to that child for that run. | +| Parent result, indirectly | The driver extracts only the child's own last assistant output or captured structured value; seeded parent messages and intermediate child work do not become the result. | The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session. | + ## Known Limitations and Deferred Work - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index db47d036e7..a67c611e47 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -4,13 +4,6 @@ The in-process **spawn** subagent backend: a [`SubagentProvider`](../subagent/RE The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../subagent-inprocess/README.md) driver (`startInProcessRun`); this backend just passes **no seed** (a fresh child). The [fork](../subagent-fork/README.md) backend is an independent peer over the same driver — neither knows about the other. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the globally composed prompt and tools after its scoped persona and tool filter. It receives zero parent conversation messages. | The child pays for a new independent context and history; no parent-history tokens are duplicated. | -| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error. | Parent input grows by one data-dependent result retained until compaction. | - ## What it does `start(request)` delegates to `startInProcessRun(ctx, request, {})` with no seed: a fresh child agent with the parent's `cwd`/`parentSession` lineage and (by default) the parent's model. The driver creates one run-owner fiber under `parent.ctx`; parent teardown, this provider's teardown, and manual disposal all converge there before child publication. Its `run.started` boundary resolves only after the fresh child is published, so `subagent/start` observers see a live registry entry. See the [driver README](../subagent-inprocess/README.md) for the full lifecycle (depth check, one-shot drive, result read, dispose). @@ -25,6 +18,13 @@ The run mechanics live in the shared [`@deepseek-ai/dsh-subagent-inprocess`](../ |---|---| | `providerName` | Registry name on `ctx.subagents` (default `spawn`). | +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the globally composed prompt and tools after its scoped persona and tool filter. It receives zero parent conversation messages. | The child pays for a new independent context and history; no parent-history tokens are duplicated. | +| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error. | Parent input grows by one data-dependent result retained until compaction. | + ## Known Limitations and Deferred Work - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs; the consumer collects synchronously. diff --git a/packages/subagent/subagent-subprocess/README.md b/packages/subagent/subagent-subprocess/README.md index 765ea4af92..b09df7b2aa 100644 --- a/packages/subagent/subagent-subprocess/README.md +++ b/packages/subagent/subagent-subprocess/README.md @@ -4,12 +4,6 @@ Shared machinery for **out-of-process subagent backends** — providers that spa Every tunable is a **parameter**: the dispose ladder takes its grace periods per call, the config-dir helper takes an optional pinned path. Defaults live in each consuming plugin's Config (defaulted, validated fields changeable from `cordis.yml`), never in this library. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This process utility registers no provider, prompt, tool, or message. A consuming backend's child application decides the child's model context; environment scrubbing and isolated config directories prevent ambient credentials and user state from silently changing that composition. | Zero direct tokens. It can indirectly stabilize child context, but it adds no text to parent or child requests. | - ## What it exports ### `SENSITIVE_ENV_PATTERN` / `buildChildEnv(extra)` @@ -45,6 +39,12 @@ A per-run isolated config directory for an external CLI child (the target of `CL `tests/subagent-subprocess.spec.ts`: the env scrub and config-dir helpers run against the real process env and real filesystem (the rm-failure path injects its rejection at the fs boundary — a real recursive-rm failure is not portably provokable, and root ignores permission bits); the exit waits and the dispose ladder run against a scriptable fake child, driving each escalation tier deterministically. The [ACP backend suite](../subagent-acp/README.md) exercises the same ladder against real subprocesses (EOF-cooperative, EOF-ignoring, and SIGTERM-trapping children) end to end. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This process utility registers no provider, prompt, tool, or message. A consuming backend's child application decides the child's model context; environment scrubbing and isolated config directories prevent ambient credentials and user state from silently changing that composition. | Zero direct tokens. It can indirectly stabilize child context, but it adds no text to parent or child requests. | + ## Known Limitations and Deferred Work - **The credential scrub is name-based** — only variables matching `KEY` / `SECRET` / `TOKEN` are removed; differently named secrets such as `PASSWORD` pass through unless the backend supplies a stricter environment. diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index 41aaf6afd6..73c4a727c6 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -14,12 +14,6 @@ This package is the interface third of the capability seam, split so each concer Unlike the bash seam (one executor per context, second load throws), **multiple providers coexist** here. Each registers under a unique name and a caller picks one by name — the shape mirrors the LLM adapter registry (`LlmService.registerAdapter`), not the single-service bash executor. This is the requirement that rules out the bash shape: an agent may want an in-process child for a cheap subtask and an out-of-process ACP child for an isolated one, in the same runtime. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The provider registry registers no prompt or tool. Provider lifecycle makes a bound `dsh-tool-subagent` schema appear or disappear, and `inheritsParentContext` selects truthful fresh-versus-fork wording. Run events are observe-only. | Zero direct tokens. Child prompts and final results enter model contexts only through a provider and consumer. | - ## Service API (`ctx.subagents`) | Member | Semantics | @@ -42,6 +36,12 @@ Beside `capabilities` sits one DESCRIPTIVE fact, not validated by the service: ` The service also announces provider lifecycle: `subagent/provider-added` (the frozen registry snapshot) fires after a registration and `subagent/provider-removed` (the accepted name) after an unregistration, so a consumer deriving state from a named provider (the model-facing tool wording) mirrors registry membership instead of assuming load order — the cordis Loader starts sibling plugins concurrently, so "listed earlier" does not mean "registered earlier". Run lifecycle is gated by provider readiness: `subagent/start` (payload `SubagentRunInfo`) fires only after `run.started` fulfills, and `subagent/end` (payload `SubagentRunEndInfo`) fires only for that announced run; readiness rejection emits neither. For spawn/fork, the start listener can resolve the published child via `ctx.agents.get(info.id)`; a remote provider need not have a local registry entry. Both events are **observe-only** plain emits. The service observes `result` immediately even while readiness is pending, clones its output before the caller can mutate it, and buffers that end payload until start has fired; a rejecting result cannot become an unhandled detached promise, start always precedes end, and a listener cannot corrupt the caller's result. `subagent/end` carries the cloned output as `lastAssistantMessage` on the settle path and omits it on infrastructure rejection. Any run-affecting decision is out of scope for this observe-only surface. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The provider registry registers no prompt or tool. Provider lifecycle makes a bound `dsh-tool-subagent` schema appear or disappear, and `inheritsParentContext` selects truthful fresh-versus-fork wording. Run events are observe-only. | Zero direct tokens. Child prompts and final results enter model contexts only through a provider and consumer. | + ## Known Limitations and Deferred Work - **The consumer collects synchronously** — it starts a run and awaits `result`; steering (`sendMessage`) is part of the contract but intentionally unused, and background / poll / spill semantics are deferred to a future redesign unifying long-running-tool handling across subagents and bash ([the seam RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)). diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index 06a8c970c8..c9befb38ae 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -2,13 +2,6 @@ The model-facing `subagent` tool: delegate a self-contained task to a child agent and return its final output. Pure schema + lifecycle shaping over the [`ctx.subagents`](../subagent/README.md) provider registry — an in-process, ACP, or future A2A backend swaps in without changing what the model sees. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | -| Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. The result contains only the child's final text or a stop-reason error, never intermediate child steps. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | - ## Provider selection is config, not model-facing This plugin binds to **exactly one** provider (`Config.provider`). The model sees only `{ description, prompt }` — there is no provider/type parameter in the schema. To expose more than one transport, load the plugin more than once, each bound to a different provider **and a distinct `toolName`** (the tool registry rejects a duplicate name, so a second load that kept the default `subagent` name would throw). Keeping selection in config (not the schema) is the deliberate split: the *service* holds a multi-provider registry; the *tool* picks one. @@ -32,6 +25,13 @@ The tool description and the `prompt` parameter description are DERIVED from the Background / poll collection is deferred (see the [RFC](../../../docs/rfc/implemented/feature/2026-06-21-subagent-capability-seam.md)); this cut blocks the parent turn until the child finishes. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | +| Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. The result contains only the child's final text or a stop-reason error, never intermediate child steps. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | + ## Known Limitations and Deferred Work - **Delegation blocks the parent turn** — synchronous collect only; background start + poll collection is deferred to the long-running-runtime redesign. diff --git a/packages/support/invariants/README.md b/packages/support/invariants/README.md index e7d8e37b0f..5d44b42cac 100644 --- a/packages/support/invariants/README.md +++ b/packages/support/invariants/README.md @@ -4,12 +4,6 @@ Dev-mode event-contract invariants and session-log freeze. A pure-listener plugi **Off in production.** Enable it in tests and the demos, where a contract violation should fail loudly. It costs nothing when not registered, and doubles as executable documentation of the event taxonomy — the assertions *are* the contract. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None | The plugin observes and validates session events, agent states, and frozen model requests; it does not rewrite a prompt, schema, message, or stream. An invariant failure aborts the faulty turn instead of adding guidance. | Zero model tokens when checks pass; a failure prevents or ends a request rather than contributing context. | - ## Plugin A functional plugin — register the module namespace (this is what loading by name in `cordis.yml` does): @@ -60,6 +54,12 @@ A `DeepReadonly` is high type-noise across every log consumer, and A seeded/forked session arrives with events already in its log (the `Session` constructor copies the seed without emitting `session/event`). On `session/created` the plugin replays the existing log through the checker and freezes those entries, so seeded history is held to the same contract. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None | The plugin observes and validates session events, agent states, and frozen model requests; it does not rewrite a prompt, schema, message, or stream. An invariant failure aborts the faulty turn instead of adding guidance. | Zero model tokens when checks pass; a failure prevents or ends a request rather than contributing context. | + ## Known Limitations and Deferred Work - **The request-reconstructability assertion covers loop-built requests only** — hand-built one-shots (e.g. compaction's summarize call) carry no live `sessionId` marker and are skipped. diff --git a/packages/support/llm-replay/README.md b/packages/support/llm-replay/README.md index 39794ca8f9..57badc9452 100644 --- a/packages/support/llm-replay/README.md +++ b/packages/support/llm-replay/README.md @@ -4,12 +4,6 @@ A replay LLM plugin for keyless snapshot tests. It installs a single `llm/stream Its consumer is the ACP snapshot harness in `examples/acp-agent`, which loads this plugin (via `cordis.snapshot.yml`) in place of a real LLM adapter. The package exists so its derive/parse/replay logic falls under the per-file 100% coverage gate on `packages/*/src` (the same logic, while it lived under `examples/`, was outside the gate). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Keyless test stream | The real loop still assembles its normal system prompt, tools, prefix, and history, but no provider model receives them. Recorded assistant chunks are replayed as the response and then enter later history exactly like live output. | Zero billed or tokenizer-evaluated model tokens. Fixture output creates deterministic retained test context for later replay steps. | - ## How the fixture works The fixture IS the persisted session log (`/session.jsonl`). Its `assistant/chunk` events carry every `StreamChunk`, so grouping them by `(turn, step)` reconstructs each `stream()` call's chunk sequence (one model call per loop step). Recording is therefore "run the real agent once and harvest the `.jsonl`", done by the snapshot harness — this plugin does not record. A fixture may carry its `request/header` content tokenized to `{{system}}`/`{{tools}}` (the harness pins that content in one scenario and scrubs the rest); replay is indifferent — derivation reads only `assistant/chunk` events and the line-0 session header. @@ -50,6 +44,12 @@ Replay keys every call by its calling session id (`GenerateOptions.sessionId`, s Named `name` / `inject` / `Config` / `apply`, with **no default export**: the cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray default would collapse the module to the bare function and drop the `inject` namespace (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Keyless test stream | The real loop still assembles its normal system prompt, tools, prefix, and history, but no provider model receives them. Recorded assistant chunks are replayed as the response and then enter later history exactly like live output. | Zero billed or tokenizer-evaluated model tokens. Fixture output creates deterministic retained test context for later replay steps. | + ## Known Limitations and Deferred Work - **First-call-order script binding assumes sequential delegation** — a cut that runs sibling subagents concurrently (or a compaction summarize call landing mid-run) would bind live sessions to recorded scripts non-deterministically; a stronger keying is deferred until such a scenario exists (`XXX(concurrent-subagents)`). diff --git a/packages/support/subagent-mock/README.md b/packages/support/subagent-mock/README.md index b99eee9aea..b263b3fe7b 100644 --- a/packages/support/subagent-mock/README.md +++ b/packages/support/subagent-mock/README.md @@ -4,12 +4,6 @@ A scripted `SubagentProvider` for testing the [subagent seam](../../subagent/sub It lets a test drive `ctx.subagents` and the model-facing `dsh-tool-subagent` through the **real cordis Loader / export path**, exercising provider registration, start-time capability validation, the run lifecycle (`result` / `cancel` / `dispose`), and the structured-output branch — all deterministically and keylessly. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Deterministic test result | No child model runs. When exercised through `dsh-tool-subagent`, the parent sees the mock provider's configured reply or stop-reason error, and structured tests receive the configured object. | Zero child-model tokens. Only the scripted final result is added to the parent test history. | - ## Usage Load it as a plugin (functional shape: `name`/`inject`/`Config`/`apply`, no default). Config (all optional): @@ -25,6 +19,12 @@ Load it as a plugin (functional shape: `name`/`inject`/`Config`/`apply`, no defa A `cancel()` issued before `result` settles flips the stop reason to `aborted`, so the cancellation path is observable. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Deterministic test result | No child model runs. When exercised through `dsh-tool-subagent`, the parent sees the mock provider's configured reply or stop-reason error, and structured tests receive the configured object. | Zero child-model tokens. Only the scripted final result is added to the parent test history. | + ## Known Limitations and Deferred Work - **Scripted provider only** — it does not run a model, create a child agent, or exercise real prompt/tool-loop behavior. diff --git a/packages/timeout/timeout-policy/README.md b/packages/timeout/timeout-policy/README.md index 27c7ab94ad..76f153320f 100644 --- a/packages/timeout/timeout-policy/README.md +++ b/packages/timeout/timeout-policy/README.md @@ -2,12 +2,6 @@ Tool-call timeout enforcer: a single `tools/execute` around-dispatch listener that arms a per-call cooperative deadline on `exec.signal` for a tool declaring `timeoutMs` on its `ToolDefinition` and returns a structured `TOOL_TIMEOUT` result when that deadline wins. The budget is read from the tool's own declaration (`ToolDefinition.timeoutMs`, set by the owning tool plugin), so this plugin is **zero-config**. It is the reference `tools/execute` wrapper and the enforcement home for model-facing tool-call budgets (the timeout-library RFC's foreseen middleware). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Conditional tool result | This plugin adds no prompt or schema. If a declared deadline wins, it replaces the provider's outcome with `Error: tool call timed out after ms` plus structured `TOOL_TIMEOUT`; otherwise the original result passes through unchanged. | Zero tokens on non-timeout calls. A timeout adds one small retained error result and can prevent a larger late provider result from entering context. | - ## Plugin (namespace: `timeout-policy`) A function/namespace plugin (`name` / `inject` / `apply`), not a service. It registers no tool and takes no config — it consumes `ctx.tools`'s `tools/execute` waterfall (which the `dsh-tools` registry always provides) and reads each dispatched tool's declared `timeoutMs` from the registry (`ctx.tools.get(exec.name)`). @@ -39,6 +33,12 @@ The derived signal only **notifies**; termination stays with the tool and the ca Multiple `tools/execute` listeners compose by cordis registration order. Combined with a future retry/sandbox/metrics wrapper, registration order chooses the semantics — "timeout covers the whole retry operation" (timeout registered outer) versus "timeout covers each attempt" (timeout registered inner). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Conditional tool result | This plugin adds no prompt or schema. If a declared deadline wins, it replaces the provider's outcome with `Error: tool call timed out after ms` plus structured `TOOL_TIMEOUT`; otherwise the original result passes through unchanged. | Zero tokens on non-timeout calls. A timeout adds one small retained error result and can prevent a larger late provider result from entering context. | + ## Known Limitations and Deferred Work - **Cooperative, never a hard kill** — the deadline only notifies via `exec.signal`; a tool that ignores the signal does not stop on timeout (see § Cooperative, not a hard kill). diff --git a/packages/todo/tool-todo/README.md b/packages/todo/tool-todo/README.md index 2ba027120d..32bb39c860 100644 --- a/packages/todo/tool-todo/README.md +++ b/packages/todo/tool-todo/README.md @@ -2,13 +2,6 @@ The model-facing `todo_write` tool: the agent's whole task list, replaced wholesale on each call. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schema | The model sees `todo_write` with the complete-list array and the three status values. | Fixed schema cost on every request where the tool is visible. | -| Tool-call history and result | Each assistant tool call retains the entire replacement list in its arguments. The tool result reports only pending, in-progress, and completed counts; the full `todo/write` session event is UI and replay state, not a second model message. | Token growth scales with every full list the model submits, and those call arguments remain until compaction. The result itself is small and fixed-shape. | - ## What it does Registers one tool, `todo_write(todos: [{ content, status }])`, on `ctx.tools`. The model sends the ENTIRE list every call — there are no partial updates or per-item edits. Each call appends a `todo/write` event (the full list snapshot) to the calling agent's session log via `agent.session.append('todo/write', { todos })`; the current list is the most recent such event (last-write-wins on replay). @@ -31,6 +24,13 @@ The tool writes only the session event; it does not render. UIs subscribe to `se A function/namespace plugin: it exports `name` / `inject` / `apply` and NO default. A stray `export default` would collapse the module via the Loader's `unwrapExports` and drop `inject` (see [docs/postmortem/0001](../../../docs/postmortem/0001-acp-default-export-drops-inject.md)). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | The model sees `todo_write` with the complete-list array and the three status values. | Fixed schema cost on every request where the tool is visible. | +| Tool-call history and result | Each assistant tool call retains the entire replacement list in its arguments. The tool result reports only pending, in-progress, and completed counts; the full `todo/write` session event is UI and replay state, not a second model message. | Token growth scales with every full list the model submits, and those call arguments remain until compaction. The result itself is small and fixed-shape. | + ## Known Limitations and Deferred Work - **Single-owner scope only** — the list belongs to the one calling agent session; subagent/shared/swarm scopes are a deliberate cut (see § Single owner), and a non-agent caller is rejected. diff --git a/packages/ui/acp-agent/README.md b/packages/ui/acp-agent/README.md index a1c33de460..00cfc9dccf 100644 --- a/packages/ui/acp-agent/README.md +++ b/packages/ui/acp-agent/README.md @@ -4,12 +4,6 @@ The **ACP server app**: a Cordis app plugin that composes the default agent spin It is the structured counterpart to [`@deepseek-ai/dsh-stdio-agent`](../stdio-agent/README.md): both consume the same spine, but this one bakes in the OPPOSITE front-door cluster. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Composed ACP agent request | Through `dsh-agent-core`, an ACP-created agent receives the harness identity, configured persona, skill catalog, visible tools, and its own ACP prompt history. This app adds no extra prompt prose and omits `ask_user_question` unless a leaf opts in. | Per-request cost is the sum of the composed child packages. ACP framing, JSON-RPC, persistence, and UI rendering add zero model tokens. | - ## What it bakes in — and what it deliberately omits stdout is the ACP JSON-RPC channel, so the cluster is defined as much by what it LEAVES OUT as what it includes: @@ -52,6 +46,12 @@ Run it under `node --expose-internals`: the cordis Loader resolves the config's All diagnostics go to **stderr** — stdout is the protocol. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed ACP agent request | Through `dsh-agent-core`, an ACP-created agent receives the harness identity, configured persona, skill catalog, visible tools, and its own ACP prompt history. This app adds no extra prompt prose and omits `ask_user_question` unless a leaf opts in. | Per-request cost is the sum of the composed child packages. ACP framing, JSON-RPC, persistence, and UI rendering add zero model tokens. | + ## Known Limitations and Deferred Work - **JSONL persistence is baked in** — config chooses its root but cannot select a different backend; that requires a sibling entry or differently composed app package. diff --git a/packages/ui/acp/README.md b/packages/ui/acp/README.md index 80769ef793..60e10d1733 100644 --- a/packages/ui/acp/README.md +++ b/packages/ui/acp/README.md @@ -4,14 +4,6 @@ The **Agent Client Protocol (ACP)** bridge: exposes DeepSeek Harness SDK agents It is a **client-driver / UI plugin**, the structured analogue of the readline `stdio-chat` plugin — NOT a loop change and NOT a [capability seam](../../../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md). It consumes the existing `agent/*` event taxonomy, the `dsh-agent` create/resume factory, and `dsh-session-persistence`. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| User messages | Each ACP `session/prompt` becomes an agent user message: text passes through and a `resource_link` is rendered as text. Unsupported image, audio, and embedded-resource blocks are rejected rather than silently omitted. | Prompt tokens are data-dependent and remain in that session's history until compaction. Concurrent ACP sessions keep separate contexts. | -| Human answers and permissions | When optional consumers are loaded, ACP form answers become `ask_user_question` tool results and permission decisions control whether a tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only. | Answer and denial text enters context only through the owning tool result; presentation metadata adds zero model tokens. | -| Loaded sessions | `session/load` resumes the persisted log, after which the loop sends its reconstructed history and request header. Replaying that log to the editor is not an extra model message. | Restored context has the persistence and session packages' normal retained cost; ACP replay to the client adds none. | - ## Service / plugin `apply(ctx, config)` — wires an `AgentSideConnection` (from `@agentclientprotocol/sdk`) to `process.stdin`/`process.stdout` and implements the ACP `Agent` method surface. @@ -89,14 +81,6 @@ The bridge registers an `approval/request` waterfall listener — the ACP answer Teardown reaches quiescence: for EVERY live session settle any pending prompt as `cancelled`, then run that session's [`AgentHandle`](../../core/agent/README.md) `dispose()` — which stops the loop (sets `disposed` + aborts the in-flight step), `await`s the loop's exit (the final `turn/end` + `session/flush` are captured while the session is still attached), unregisters the agent, and removes its session from the store. A turn cut off mid-flight by teardown ends with reason `disposed` (not `aborted` — `dispose()` uses the disposed path, not `session/cancel`'s queue-aware `cancel()`). The per-session disposes run in parallel. The same teardown runs on a **client disconnect** (`conn.closed` resolves when the editor quits / the transport EOFs), so a vanished client never leaves an orphaned running — or idled-but-still-registered — agent whose `session/update` writes are silently swallowed. The two paths are idempotent and memoized (the first clears the `sessions` map; a second caller awaits the same teardown promise). -## Known Limitations and Deferred Work - -- **`additionalDirectories`** — rejected. A session operates in its single `cwd` (see Per-session cwd); widening the tool/filesystem scope to extra roots is a separate sandbox concern, not yet implemented. -- **Prompt content is `text` + `resource_link` only** — image, audio, and embedded-resource blocks are rejected, as is a non-empty `mcpServers` list at `session/new`. -- **One configured `model` for every created session** — per-session model selection has no config or protocol surface here yet. -- **Terminal cards render completed output** — live incremental streaming and command classification are named follow-ups of [the terminal-rendering RFC](../../../docs/rfc/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md). -- **Permission answers are one-shot only** — the bridge offers `allow_once` / `reject_once`; durable `allow_always` grants and their storage/revocation policy remain deferred to the approval seam. - ## stdout is the protocol The JSON-RPC frames go on stdout, so this plugin MUST run in an example that loads **no stdout logger** (the console logger writes to stdout and would corrupt the frames). The guarantee is config-only — see `examples/acp-agent` (no console logger) and [ACP support risks](../../../docs/rfc/implemented/feature/2026-06-14-acp-agent-client-protocol.md#risks). A stderr exporter is fine for logging. @@ -115,3 +99,19 @@ The JSON-RPC frames go on stdout, so this plugin MUST run in an example that loa } } ``` + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| User messages | Each ACP `session/prompt` becomes an agent user message: text passes through and a `resource_link` is rendered as text. Unsupported image, audio, and embedded-resource blocks are rejected rather than silently omitted. | Prompt tokens are data-dependent and remain in that session's history until compaction. Concurrent ACP sessions keep separate contexts. | +| Human answers and permissions | When optional consumers are loaded, ACP form answers become `ask_user_question` tool results and permission decisions control whether a tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only. | Answer and denial text enters context only through the owning tool result; presentation metadata adds zero model tokens. | +| Loaded sessions | `session/load` resumes the persisted log, after which the loop sends its reconstructed history and request header. Replaying that log to the editor is not an extra model message. | Restored context has the persistence and session packages' normal retained cost; ACP replay to the client adds none. | + +## Known Limitations and Deferred Work + +- **`additionalDirectories`** — rejected. A session operates in its single `cwd` (see Per-session cwd); widening the tool/filesystem scope to extra roots is a separate sandbox concern, not yet implemented. +- **Prompt content is `text` + `resource_link` only** — image, audio, and embedded-resource blocks are rejected, as is a non-empty `mcpServers` list at `session/new`. +- **One configured `model` for every created session** — per-session model selection has no config or protocol surface here yet. +- **Terminal cards render completed output** — live incremental streaming and command classification are named follow-ups of [the terminal-rendering RFC](../../../docs/rfc/implemented/feature/2026-06-18-acp-terminal-and-tool-rendering.md). +- **Permission answers are one-shot only** — the bridge offers `allow_once` / `reject_once`; durable `allow_always` grants and their storage/revocation policy remain deferred to the approval seam. diff --git a/packages/ui/stdio-agent/README.md b/packages/ui/stdio-agent/README.md index 9f57f5d4cc..38254f5f5d 100644 --- a/packages/ui/stdio-agent/README.md +++ b/packages/ui/stdio-agent/README.md @@ -4,12 +4,6 @@ The **terminal stdio chat app**: a Cordis app plugin that composes the default a It is the readline counterpart to [`@deepseek-ai/dsh-acp-agent`](../acp-agent/README.md): both consume the same spine, but each bakes in the OPPOSITE front-door cluster. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Composed terminal agent request | Through `dsh-agent-core`, the `main` agent receives the harness identity, configured persona, skill catalog, and visible tools; this app also composes the `ask_user_question` schema. Each readline submission becomes a user message. | Child prompt and schema costs repeat per request; user input and tool history grow until compaction. The welcome banner, logger output, and rendered transcript are terminal-only and add zero model tokens. | - ## What it bakes in A terminal chat always wants the same cluster, so the package owns it rather than trusting each leaf to re-wire it: @@ -72,6 +66,12 @@ Fresh stdio sessions use the process launch directory as `session.header.cwd`, s Swap `llm-deepseek` for a `mock-llm` leaf plugin and you have the echo demo — "swap the backend, keep the app". +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Composed terminal agent request | Through `dsh-agent-core`, the `main` agent receives the harness identity, configured persona, skill catalog, and visible tools; this app also composes the `ask_user_question` schema. Each readline submission becomes a user message. | Child prompt and schema costs repeat per request; user input and tool history grow until compaction. The welcome banner, logger output, and rendered transcript are terminal-only and add zero model tokens. | + ## Known Limitations and Deferred Work - **One pre-created `main` agent drives the readline UI** — there is no multi-session or concurrent-agent surface in this app; a run is one conversation. diff --git a/packages/ui/tool-ask-user/README.md b/packages/ui/tool-ask-user/README.md index 851b9f5864..c41e43d920 100644 --- a/packages/ui/tool-ask-user/README.md +++ b/packages/ui/tool-ask-user/README.md @@ -2,13 +2,6 @@ Model-facing `ask_user_question` tool over `ctx.userInteraction`. It lets the model ask the human a concise question when it needs confirmation, a choice, or missing information before continuing. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Tool schema | The model sees `ask_user_question` with question ids, prompts, headings, options, and multi-select flags. | Fixed schema cost on every request where the tool is visible. | -| Tool-call history and result | The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees JSON containing selected labels and optional custom text. UI interaction while the call is pending is not model context. | Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human. | - ## Tool `ask_user_question` accepts: @@ -26,6 +19,13 @@ The tool calls `ctx.userInteraction.ask()` and returns JSON text shaped as `{ "a This is the consumer package for the user-interaction seam. It does not render UI and does not know how input is collected; it only translates model arguments into `AskUserQuestionRequest` and returns the human answer to the agent loop. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Tool schema | The model sees `ask_user_question` with question ids, prompts, headings, options, and multi-select flags. | Fixed schema cost on every request where the tool is visible. | +| Tool-call history and result | The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees JSON containing selected labels and optional custom text. UI interaction while the call is pending is not model context. | Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human. | + ## Known Limitations and Deferred Work - **A pending question blocks the tool call until the human answers** — the tool declares no `timeout-policy` budget; cancellation rides the turn's `exec.signal` only. diff --git a/packages/ui/user-interaction/README.md b/packages/ui/user-interaction/README.md index 4261cef246..378e7a626f 100644 --- a/packages/ui/user-interaction/README.md +++ b/packages/ui/user-interaction/README.md @@ -2,12 +2,6 @@ Abstract user-interaction seam. It owns `ctx.userInteraction`, the service a model-facing tool or permission plugin uses when it needs to pause work and ask the human for a decision. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This UI-neutral seam registers no prompt or tool. A consumer such as `dsh-tool-ask-user` turns a model call into an `ask()` request and converts the provider's human answer into a model-visible tool result. | Zero direct tokens. Question and answer size affect context only through the consumer. | - ## Service: `UserInteractionService` (ctx key: `userInteraction`) ### Public API @@ -29,6 +23,12 @@ When an answer includes `custom`, `selected` is empty; custom text is an overrid This is the interface package. Model-facing consumers such as `@deepseek-ai/dsh-tool-ask-user` depend on this seam; UI front doors such as the `stdio-agent` readline module and the `acp` bridge provide the provider. The loop stays unchanged: a tool call simply awaits a promise, and the tool result resumes the normal agent loop. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This UI-neutral seam registers no prompt or tool. A consumer such as `dsh-tool-ask-user` turns a model call into an `ask()` request and converts the provider's human answer into a model-visible tool result. | Zero direct tokens. Question and answer size affect context only through the consumer. | + ## Known Limitations and Deferred Work - **One provider per context** — there is no routing or fan-out to multiple UIs; a second registration throws `DUPLICATE_PROVIDER`, and with none registered `ask()` throws `NO_PROVIDER` rather than degrading. diff --git a/packages/util/brand/README.md b/packages/util/brand/README.md index 22c9c47bb3..5c7fa07272 100644 --- a/packages/util/brand/README.md +++ b/packages/util/brand/README.md @@ -2,12 +2,6 @@ The `Branded` nominal-typing primitive — a tiny, **type-only** package (no runtime code, no harness-package dependency) shared by every package that owns a cross-boundary id. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None | `Branded` is erased at compile time and registers no runtime plugin, prompt, schema, event, or message. Branded ids serialize exactly as their underlying strings when another package logs or renders them. | Zero direct or indirect token overhead beyond the string another package already chose to expose. | - ## What `Branded` is A brand makes structurally-identical strings non-interchangeable at the type level: an `AgentId` cannot be passed where a `CallId` is expected, even though both are plain `string`s at runtime. @@ -30,3 +24,9 @@ Construction goes through the per-id factory in the OWNING package (a plain cast A package brands the ids it OWNS — `CallId` in `dsh-llm` (tool-call correlation), `SessionId` in `dsh-session`, `AgentId` in `dsh-agent`, `BashTaskId`/`OwnerToken` in `dsh-bash`. Branding is for ids that cross package boundaries and could plausibly be confused; **not every string needs a brand.** This package owns ONLY the primitive — no concrete id, no runtime code beyond the (erased) type. Keeping the primitive dependency-free is the point: a capability package can brand its ids without depending on an unrelated package. `dsh-bash`, for example, brands `BashTaskId`/`OwnerToken` by depending on `dsh-brand` alone — it never pulls in `dsh-llm` (or `dsh-session`) just to reach `Branded`. + +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None | `Branded` is erased at compile time and registers no runtime plugin, prompt, schema, event, or message. Branded ids serialize exactly as their underlying strings when another package logs or renders them. | Zero direct or indirect token overhead beyond the string another package already chose to expose. | diff --git a/packages/util/timeout/README.md b/packages/util/timeout/README.md index 4ae6aefbe9..86c76ca5b4 100644 --- a/packages/util/timeout/README.md +++ b/packages/util/timeout/README.md @@ -6,12 +6,6 @@ It owns **no termination**. The signal it hands out only *notifies*; actually st It is a **library, not a service or plugin**: no `ctx`, registers nothing, holds no state, emits no events. A "timeout service" would have to understand how to stop every capability's work — exactly the knowledge a microkernel keeps out of shared layers. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This library only creates and classifies abort signals. It registers no prompt, schema, or message; consumers decide whether a timeout becomes a marker, a structured error, or no model-visible change. | Zero direct tokens. It can indirectly cap or replace a consumer's result when that consumer renders a timeout. | - ## Surface ```ts @@ -47,6 +41,12 @@ Pass your own `code` to `timeoutOf` so classification composes under nesting: wh Local file `read`/`write`/`edit` take no `timeoutMs`: a syscall is best-effort-abortable at most, a timeout could not force `fsync`/`rename` to stop, and adding one would be an implicit default that violates explicit-over-implicit. See [`fs/`](../../fs/README.md). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | This library only creates and classifies abort signals. It registers no prompt, schema, or message; consumers decide whether a timeout becomes a marker, a structured error, or no model-visible change. | Zero direct tokens. It can indirectly cap or replace a consumer's result when that consumer renders a timeout. | + ## Known Limitations and Deferred Work - **Notification only** — a deadline cannot stop work that ignores its signal; every capability still needs its own socket/process/task termination path. diff --git a/packages/web/tool-web/README.md b/packages/web/tool-web/README.md index 4d0a53e95a..9b821aaf82 100644 --- a/packages/web/tool-web/README.md +++ b/packages/web/tool-web/README.md @@ -4,14 +4,6 @@ The model-facing web tool suite — `web_search` and `web_fetch` — over the [w Each tool is registered independently; a product that wants only one disables the other via config (`{ search: false }` / `{ fetch: false }`). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt | Each enabled tool adds one short section: search guidance says to discover current sources and follow with fetch; fetch guidance says to retrieve a specific HTTP(S) URL and cite it. | Fixed guidance cost per request for each enabled tool. | -| Tool schemas | According to config, the model sees `web_search(query)`, `web_fetch(url)`, or both. Result-count and timeout budgets are deployment settings, not model arguments. | Fixed schema cost per request; disabling a tool removes its schema and guidance. | -| Tool-call history and results | Search returns an optional answer and bounded source entries; fetch returns status plus decoded text or markdown-shaped HTML, or a structured error. Queries and URLs remain in call history. | Data-dependent results are resent until compaction. Search sources are capped by `searchMaxResults`; fetch providers cap body size, and timeout policy can replace a late result with a short error. | - ## Tools | Tool | Args | Behavior | @@ -42,6 +34,14 @@ Tool registration follows product **enablement**, not backend availability. A to The tool never calls a provider's `status()` and never enumerates providers — its only execution path is `ctx.web.search()` / `ctx.web.fetch()`, and provider unavailability reaches it as the structured `WebError` codes selection throws at execution time. Provider selection stays entirely inside the seam, with one owner. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt | Each enabled tool adds one short section: search guidance says to discover current sources and follow with fetch; fetch guidance says to retrieve a specific HTTP(S) URL and cite it. | Fixed guidance cost per request for each enabled tool. | +| Tool schemas | According to config, the model sees `web_search(query)`, `web_fetch(url)`, or both. Result-count and timeout budgets are deployment settings, not model arguments. | Fixed schema cost per request; disabling a tool removes its schema and guidance. | +| Tool-call history and results | Search returns an optional answer and bounded source entries; fetch returns status plus decoded text or markdown-shaped HTML, or a structured error. Queries and URLs remain in call history. | Data-dependent results are resent until compaction. Search sources are capped by `searchMaxResults`; fetch providers cap body size, and timeout policy can replace a late result with a short error. | + ## Known Limitations and Deferred Work - **`htmlToMarkdown` is a minimal regex converter, not an HTML parser** — it strips script/style/noscript, keeps headings/bullets/links, and decodes about a dozen named entities; tables, images, and nested formatting are lost. diff --git a/packages/web/web-fetch-local/README.md b/packages/web/web-fetch-local/README.md index ea0141d69a..7602fd6548 100644 --- a/packages/web/web-fetch-local/README.md +++ b/packages/web/web-fetch-local/README.md @@ -4,12 +4,6 @@ An anonymous public HTTP(S) `WebFetchProvider` for the harness [web capability s This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. It is a function/namespace plugin (`inject: ['web']`). -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Web fetch result, indirectly | Through `dsh-tool-web`, the conversation model sees the final URL, HTTP status, and decoded text or markdown-shaped HTML, or a structured retrieval error. Redirects, headers, and transport mechanics are not added to context unless reflected in an error. | Zero direct tokens. `maxBodyChars` bounds decoded result length before the tool records it; the retained result is resent until compaction. | - ## Responsibility split The provider owns **safe resource retrieval**: URL validation, HTTP transport, redirect policy, a resource-backstop timeout, abort propagation, byte caps, charset decoding, content-type classification, and binary rejection. `@deepseek-ai/dsh-tool-web` owns **presentation** (HTML→markdown, truncation formatting). A non-2xx HTTP response is a *result* (status code + decoded body), not an error; `WebError` is reserved for failures to safely retrieve or represent the resource. @@ -39,6 +33,12 @@ The provider's `timeoutMs`/`maxTimeoutMs` is a **resource backstop** for direct The numeric limits are validated at plugin construction: every cap except `maxRedirects` must be a positive finite number, and `maxRedirects` must be a non-negative integer. An invalid value throws rather than silently constructing a provider with nonsensical limits. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Web fetch result, indirectly | Through `dsh-tool-web`, the conversation model sees the final URL, HTTP status, and decoded text or markdown-shaped HTML, or a structured retrieval error. Redirects, headers, and transport mechanics are not added to context unless reflected in an error. | Zero direct tokens. `maxBodyChars` bounds decoded result length before the tool records it; the retained result is resent until compaction. | + ## Known Limitations and Deferred Work - **SSRF / private-network protection is deferred** — no blocking of private, loopback, link-local, multicast, or otherwise non-public destinations, no DNS-resolve-then-validate, no per-hop re-validation (see [the web capability seam RFC](../../../docs/rfc/implemented/architecture/2026-06-24-web-capability-seam.md)). Until it lands, this provider is an SSRF primitive and **must not be enabled** in a deployment that can reach sensitive internal network targets. diff --git a/packages/web/web-search-deepseek/README.md b/packages/web/web-search-deepseek/README.md index ae83c4eeb4..cc0c95eb75 100644 --- a/packages/web/web-search-deepseek/README.md +++ b/packages/web/web-search-deepseek/README.md @@ -4,13 +4,6 @@ A [DeepSeek](https://deepseek.com)-backed `WebSearchProvider` for the harness [w This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`). The Anthropic wire shape is a provider-private detail — it does **not** make this provider depend on `ctx.llm`. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Auxiliary DeepSeek search request | A separate DeepSeek model receives the search query and native `web_search` server-tool definition. This request is not part of the conversation model's context. | Separate provider input and output tokens are incurred for each search; `maxTokens` caps generated output and `maxUses` caps native search uses. | -| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees deduplicated URLs, titles, dates, and citation snippets from structured search blocks; provider prose is not trusted as an answer. | Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the seam enforces the requested source bound. | - ## How it differs from a dedicated search endpoint Exa and Perplexity expose dedicated search endpoints; DeepSeek does not. Instead this provider issues a **full Messages model call** carrying the `web_search` server tool, so one search costs a complete model turn in latency and tokens — heavier than a pure retrieval endpoint. DeepSeek runs the search server-side and returns **structured** `web_search_tool_result` blocks; the provider parses those blocks and **never scrapes URLs out of model prose**. @@ -42,6 +35,13 @@ It reuses `$DEEPSEEK_API_KEY` (no new secret) but **not** `$DEEPSEEK_BASE_URL`: DeepSeek returns no provider-generated answer surface this provider trusts as `content`, so `content` is omitted. `sources[]` is built from the `web_search_result` items inside `web_search_tool_result` blocks: `url` ← `url`, `title` ← `title`, `publishedAt` ← `page_age`. The per-source `snippet` lives separately in a `text` block's `citations[]` (a `cited_text` keyed by `url`), so the provider joins the two — a result with no citation excerpt simply has no `snippet`. Results are deduped by `url` (a `maxUses > 1` request can surface the same URL across searches). DeepSeek's `web_search` has no result-count knob (only `maxUses`), so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`). Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Auxiliary DeepSeek search request | A separate DeepSeek model receives the search query and native `web_search` server-tool definition. This request is not part of the conversation model's context. | Separate provider input and output tokens are incurred for each search; `maxTokens` caps generated output and `maxUses` caps native search uses. | +| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees deduplicated URLs, titles, dates, and citation snippets from structured search blocks; provider prose is not trusted as an answer. | Zero direct conversation tokens from registration. Result tokens scale with returned sources and snippets, then the seam enforces the requested source bound. | + ## Known Limitations and Deferred Work - **One search costs a full Messages model turn** — latency plus generated tokens, with up to `maxUses` server-side searches; DeepSeek exposes no dedicated retrieval endpoint. diff --git a/packages/web/web-search-exa/README.md b/packages/web/web-search-exa/README.md index e484acf936..388eb7c370 100644 --- a/packages/web/web-search-exa/README.md +++ b/packages/web/web-search-exa/README.md @@ -4,12 +4,6 @@ An [Exa](https://exa.ai)-backed `WebSearchProvider` for the harness [web capabil This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the `ctx.web` key and it does not register a model-facing tool (that is `@deepseek-ai/dsh-tool-web`). Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`) that registers its backend, not a default-export service. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Web search result, indirectly | Through `dsh-tool-web`, the conversation model sees Exa result URLs, titles, first highlight snippets, and publication dates. No generated answer or provider-private response fields enter the tool result. | Zero direct harness-model tokens. Result size scales with the bounded source list and snippets; the seam enforces `maxResults`, and retained results remain until compaction. | - ## Config | Key | Default | Meaning | @@ -31,6 +25,12 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i Exa returns a flat `results[]` and no generated answer, so `content` is omitted. Each result maps to a `WebSearchSource`: `url` ← `url`, `title` ← `title`, `snippet` ← the first non-empty `highlights[]` entry (a result with no highlight has no portable snippet and is dropped), `publishedAt` ← `publishedDate`. A request's `maxResults` wins over the configured `numResults` default and is sent as Exa's `numResults` for a cost/latency optimization; the final bound is enforced by the seam. Provider failures (HTTP errors, network failure, unparseable or wrong-shape bodies) surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Web search result, indirectly | Through `dsh-tool-web`, the conversation model sees Exa result URLs, titles, first highlight snippets, and publication dates. No generated answer or provider-private response fields enter the tool result. | Zero direct harness-model tokens. Result size scales with the bounded source list and snippets; the seam enforces `maxResults`, and retained results remain until compaction. | + ## Known Limitations and Deferred Work - **A result with no non-blank highlight is dropped entirely** — no portable snippet to map, so fewer sources than the requested count can return. diff --git a/packages/web/web-search-perplexity/README.md b/packages/web/web-search-perplexity/README.md index 2c331ce81c..faa33d432f 100644 --- a/packages/web/web-search-perplexity/README.md +++ b/packages/web/web-search-perplexity/README.md @@ -4,13 +4,6 @@ A [Perplexity](https://perplexity.ai)-backed `WebSearchProvider` for the harness This is an **implementation** package: it registers a provider into `ctx.web`, it does not own the key and it does not register a model-facing tool. Like `@deepseek-ai/dsh-llm-deepseek`, it is a function/namespace plugin (`inject: ['web']`). The OpenAI-compatible wire shape is a provider-private detail — it does **not** make this provider depend on `ctx.llm`. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Auxiliary Perplexity request | A separate Perplexity model receives the search query through its chat-completions endpoint. This request is not part of the conversation model's context. | Separate provider tokens are incurred per search; `maxTokens` caps the generated answer. | -| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees the generated answer plus structured result metadata or URL-only citations. | Zero direct conversation tokens from registration. Answer and source tokens are data-dependent, source count is seam-bounded, and the retained result is resent until compaction. | - ## Config | Key | Default | Meaning | @@ -32,6 +25,13 @@ This is an **implementation** package: it registers a provider into `ctx.web`, i `content` ← `choices[0].message.content` (the generated answer). `sources[]` prefers the structured `search_results[]` (`url`, `title`, `snippet`, `publishedAt` ← `date`), falling back to the URL-only `citations[]` array only when `search_results` is absent — those sources carry just a `url`, which is why `title`/`snippet`/`publishedAt` are optional on the seam. Provider failures surface as `WebError` `WEB_PROVIDER_ERROR`; an aborted request surfaces as `WEB_ABORTED`. Perplexity has no result-count control, so `maxResults` is enforced by the seam (truncating `sources[]` and setting `truncated`). +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Auxiliary Perplexity request | A separate Perplexity model receives the search query through its chat-completions endpoint. This request is not part of the conversation model's context. | Separate provider tokens are incurred per search; `maxTokens` caps the generated answer. | +| Conversation tool result, indirectly | Through `dsh-tool-web`, the conversation model sees the generated answer plus structured result metadata or URL-only citations. | Zero direct conversation tokens from registration. Answer and source tokens are data-dependent, source count is seam-bounded, and the retained result is resent until compaction. | + ## Known Limitations and Deferred Work - **Citation-fallback sources are URL-only** — when Perplexity omits structured `search_results[]`, sources carry no `title`/`snippet`/`publishedAt`, so the tool renders bare hostname labels. diff --git a/packages/web/web/README.md b/packages/web/web/README.md index f63e24c2a4..3582ca9d1d 100644 --- a/packages/web/web/README.md +++ b/packages/web/web/README.md @@ -14,12 +14,6 @@ This package is the interface third of the web capability. Unlike bash/fs it spa Search and fetch share no request schema and no business logic, but they are deliberately one seam: `ctx.web` is a single web-access middle layer with one provider-selection policy owner, one abort/error vocabulary, and one product-facing "how this harness reaches the web" config surface. The cost is the parallel `Search`/`Fetch` method pairs; that parallelism is intentional, not a missed extraction. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The seam registers providers, not tools or prompt text. `dsh-tool-web` renders normalized search answers, sources, fetched bodies, and structured `WebError` values. Provider selection details stay internal except for an execution error. | Zero direct tokens. The seam indirectly bounds search result tokens by truncating sources to `maxResults`; all rendered size comes through a consumer. | - ## Service API (`ctx.web`) | Member | Semantics | @@ -49,6 +43,12 @@ The failure branches throw `WebError`, whose structured code (plus message detai `WebSearchRequest` (`query`, `maxResults?`) → `WebSearchResult` (`providerId`, `query`, `content?`, `sources[]`, `truncated`); each `WebSearchSource` has a required `url` and optional `title`/`snippet`/`publishedAt` (Perplexity citations may be URL-only). `WebFetchRequest` (`url`, `timeoutMs?`) → `WebFetchResult` (`providerId`, final `url`, `statusCode`, `body`, `truncated`); `WebFetchBody` is a CLOSED discriminated union (`html` | `text`) owned here — consumers `switch` to exhaustiveness so a new kind breaks their compilation until handled. See `src/types.ts` for the full contracts and the `WebError` code taxonomy. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The seam registers providers, not tools or prompt text. `dsh-tool-web` renders normalized search answers, sources, fetched bodies, and structured `WebError` values. Provider selection details stay internal except for an execution error. | Zero direct tokens. The seam indirectly bounds search result tokens by truncating sources to `maxResults`; all rendered size comes through a consumer. | + ## Known Limitations and Deferred Work - **No observation surface** — no provider-change event and no capability-status query; availability is observed only by executing `search()`/`fetch()` and routing the thrown `WebError` codes, and the no-provider failure is the generic `WEB_PROVIDER_UNAVAILABLE` with no per-provider reason enumeration ([RFC](../../../docs/rfc/implemented/simplification/2026-07-04-drop-unconsumed-web-observation-surface.md)). diff --git a/packages/workflow/tool-workflow/README.md b/packages/workflow/tool-workflow/README.md index cdeb867a6e..83b46a6852 100644 --- a/packages/workflow/tool-workflow/README.md +++ b/packages/workflow/tool-workflow/README.md @@ -2,13 +2,6 @@ The model-facing **`workflow` tool**: run a JavaScript orchestration script that fans out subagents, and return the script's final value. Pure schema + lifecycle shaping over [`ctx.workflows`](../workflow/README.md) — script parsing, execution, caps, and cancellation live behind the seam, so a hardened engine swaps in without touching what the model sees. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| System prompt and tool schema | The parent model receives a short use-only-for-large-orchestration section plus the `workflow` schema. The schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial but fixed per-request guidance and schema cost while visible. | -| Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. The result contains the workflow name, child count, and final JSON value or a shaped error; intermediate child messages are omitted. | Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context. | - ## What the model sees Three parameters: `script` (required plain-JavaScript body with no `export const meta` statement), `meta` (required JSON identity with `name`/`description` and optional `whenToUse`/`phases`), and `args` (optional JSON object exposed to the script as the `args` global; wrap a bare list as a field so the wire schema stays honest). The tool description carries the complete authoring contract: hooks, semantics, and the supported schema subset. The plugin also contributes a `tool:` system-prompt section carrying the usage policy — use the tool only on an explicit user ask for a workflow / large orchestration; prefer plain subagent calls for one or two delegations — per the convention that tool guidance ships with the tool plugin, never in the deployment persona. @@ -28,6 +21,13 @@ Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/arch | `toolName` | `workflow` | The model-facing tool name to register. | | `maxResultChars` | `50000` | Rendered-result ceiling; longer JSON is truncated with a notice. | +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| System prompt and tool schema | The parent model receives a short use-only-for-large-orchestration section plus the `workflow` schema. The schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial but fixed per-request guidance and schema cost while visible. | +| Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. The result contains the workflow name, child count, and final JSON value or a shaped error; intermediate child messages are omitted. | Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context. | + ## Known Limitations and Deferred Work - **The parent turn blocks until the whole workflow settles** — there is no background start/poll surface, and cancellation discards partial output as an error. diff --git a/packages/workflow/workflow-workerthread/README.md b/packages/workflow/workflow-workerthread/README.md index 9944012a0b..5cdd267451 100644 --- a/packages/workflow/workflow-workerthread/README.md +++ b/packages/workflow/workflow-workerthread/README.md @@ -2,13 +2,6 @@ The [`WorkflowService`](../workflow/README.md) implementation, on **`node:worker_threads`**: each run gets its OWN worker thread (one run = one worker, no pooling — a run is heavyweight, so the ~tens-of-ms thread spin-up is noise), the script executes in a vm context INSIDE that worker with the workflow hooks injected, and every `agent()` call bridges back over the message port to [`ctx.subagents`](../../subagent/README.md) on the host. Child agents are I/O-bound LLM loops and stay on the host event loop; the thread isolates the SCRIPT, the only part that can spin synchronously. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| Child-agent requests | Every script `agent()` call sends its prompt and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events. | Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly. | -| Parent tool result, indirectly | The parent sees only the script's materialized final JSON value, child count, or error through `dsh-tool-workflow`. Intermediate child outputs are available to the script but not the parent model. | Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction. | - ## Trust premise: what the thread buys (and what it does not) Workflow scripts are **model-written** — the same trust level as the model's existing bash access — so this engine defends against **buggy** scripts, never hostile ones. A worker thread is NOT a security boundary: the vm context inside it is escapable by construction (`node:vm` shares object machinery with its surrounding realm, so a script can reach the `Function` constructor via `globalThis.constructor.constructor` and from it `process` and every Node builtin), and an escapee holds the same process privileges as the host — Node's permission model is process-wide. The absent globals are API surface that keeps honest scripts portable, not walls. What the thread concretely buys: @@ -59,6 +52,13 @@ A returned promise or thenable resolves per JavaScript semantics BEFORE material | `syncTimeoutMs` | `5000` | vm timeout for the script's initial synchronous slice (in the worker). | | `disposeGraceMs` | `5000` | How long a cancelled run may stay unsettled before force-settle + terminate; also bounds `dispose()`. | +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| Child-agent requests | Every script `agent()` call sends its prompt and optional model or structured-output schema to a subagent provider. Each child sees that provider's own context; phase and log narration stays on observer events. | Potentially many independent child contexts are paid, bounded by `maxConcurrentAgents`, `maxTotalAgents`, and `maxItemsPerCall`; they never join the parent history directly. | +| Parent tool result, indirectly | The parent sees only the script's materialized final JSON value, child count, or error through `dsh-tool-workflow`. Intermediate child outputs are available to the script but not the parent model. | Zero direct parent tokens from this engine. Final result size is capped by the tool consumer and retained until compaction. | + ## Known Limitations and Deferred Work - **The worker/vm is not a security boundary** — model-written code can escape `node:vm` and reach the worker's process authority; a hostile-code deployment needs a separate-process or container engine. diff --git a/packages/workflow/workflow/README.md b/packages/workflow/workflow/README.md index 7719fa47e4..2c4d3dec89 100644 --- a/packages/workflow/workflow/README.md +++ b/packages/workflow/workflow/README.md @@ -2,12 +2,6 @@ The **workflow seam** (`ctx.workflows`): an abstract service defining WHAT a workflow engine does — execute a model-written orchestration script that fans out subagents — without saying HOW. The bash-shaped third of the [workflow family](../README.md): implementations subclass `WorkflowService` and register as the `workflows` service (one per context); [`dsh-workflow-workerthread`](../workflow-workerthread/README.md) (one worker thread per run) is the implementation, and [`dsh-tool-workflow`](../tool-workflow/README.md) is the model-facing consumer. -## Model Experience - -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The service seam and `workflow/*` observer events register no prompt, schema, or message. `dsh-tool-workflow` renders the parent-facing contract and final value; an engine decides which child prompts run. | Zero direct tokens. Parent result and child contexts affect tokens only through the consumer and implementation. | - ## Service: `WorkflowService` (abstract) `start(request: WorkflowStartRequest): WorkflowRun` — parse and execute a script. Throws synchronously (`SCRIPT_PARSE`/`META_INVALID`) for a script that cannot begin; once a run is returned, its `result` NEVER rejects — every failure resolves with `stopReason: 'error'` (or `'cancelled'`) — and once the run is cancelled, `result` settles within the implementation's bounded grace even if the script itself never settles (a consumer awaiting `result` must never be wedged past a cancellation). `dispose()` must reach quiescence within a bounded grace (cancel → wait for the script to settle and its children to finish disposing → abandon), never hanging its caller. Runs are HOLDER-owned: the engine does not track its live runs, so disposing the engine's fiber mid-run leaves each run to its holder's teardown. @@ -30,6 +24,12 @@ All observe-only emits carrying DATA SNAPSHOTS (`WorkflowRunInfo` = id + meta) - `workflow/phase`(info, title) / `workflow/log`(info, message) — script narration. - `workflow/agent-start`(info, agent) / `workflow/agent-end`(info, agent + outcome) — ready-child lifecycle correlated by `seq`; the [generated event contract](../../../docs/cordis-catalog/events.md#workflowagent-start--emit) defines publication and pairing. +## Model Experience + +| Context surface | What the model sees | Token effect | +|---|---|---| +| None directly | The service seam and `workflow/*` observer events register no prompt, schema, or message. `dsh-tool-workflow` renders the parent-facing contract and final value; an engine decides which child prompts run. | Zero direct tokens. Parent result and child contexts affect tokens only through the consumer and implementation. | + ## Known Limitations and Deferred Work - **Foreground collection only** — the caller owns one live run and awaits it; background start/poll, spill handles, and detached collection are deferred. diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 84ba22ea88..3144c0e560 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -11,6 +11,7 @@ import { relative, resolve } from 'node:path' const root = resolve(import.meta.dirname, '..') const HEADING = '## Model Experience' const HEADING_PATTERN = /^## Model Experience$/gm +const LIMITATIONS_HEADING = '## Known Limitations and Deferred Work' const TABLE_HEADER = '| Context surface | What the model sees | Token effect |' const TABLE_DIVIDER = '|---|---|---|' @@ -46,6 +47,22 @@ for (const packageJson of packageJsons) { continue } const headingIndex = match.index + const h2Headings = [...source.matchAll(/^## .+$/gm)] + const modelH2Index = h2Headings.findIndex(heading => heading.index === headingIndex) + const limitationsH2Index = h2Headings.findIndex(heading => heading[0] === LIMITATIONS_HEADING) + if (limitationsH2Index >= 0) { + if (modelH2Index !== h2Headings.length - 2 || limitationsH2Index !== h2Headings.length - 1) { + failures.push({ + path: readme, + message: `${HEADING} and ${LIMITATIONS_HEADING} must be the final two H2 sections, in that order`, + }) + continue + } + } else if (modelH2Index !== h2Headings.length - 1) { + failures.push({ path: readme, message: `${HEADING} must be the final H2 when ${LIMITATIONS_HEADING} is absent` }) + continue + } + const bodyStart = headingIndex + HEADING.length const nextHeadingOffset = source.slice(bodyStart).search(/^## /m) const section = source.slice(bodyStart, nextHeadingOffset < 0 ? undefined : bodyStart + nextHeadingOffset) From 8d5dce182406e8754a9b99159f42e2ac7400d043 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:49:42 +0800 Subject: [PATCH 13/38] docs: condense package guidance after scope merge --- packages/AGENTS.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/AGENTS.md b/packages/AGENTS.md index 1fe9e1da84..d94e41d497 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md — Harness Packages -This directory contains all `@deepseek-ai/dsh-*` harness packages. Repo-wide conventions (effects, declaration merging, waterfall semantics, ESM, testing policy) are in the root [AGENTS.md](../AGENTS.md) § Conventions; the points below are packages-specific. +These package-specific rules supplement the repo-wide [conventions](../AGENTS.md#conventions). - **Plugin export shape — namespace OR default, never both.** A *service* package exports the service class as `export default` (the Loader instantiates it). A *function/namespace* plugin exports `name` / `inject` / `Config` / `apply` as separate named exports and **must NOT add `export default`** — the cordis Loader's `unwrapExports` does `exports.default ?? exports`, so a stray default export collapses the module to the bare `apply` function and silently discards the `inject`/`name`/`Config` namespace, leaving the plugin with no injected services (it then throws `cannot get property … without inject` at load). See [docs/postmortem/0001](../docs/postmortem/0001-acp-default-export-drops-inject.md). - **Read an optional (non-injected) service via `ctx.get(name)`, not `ctx.`.** For a service a plugin reads opportunistically but deliberately leaves out of `static inject` (e.g. `AgentLoop` reading `sessionPersistence`), the `ctx.` property proxy resolves by an ancestor-only fiber walk that throws when the call arrives through a foreign traceable shadow (the service lives on a sibling fiber). `ctx.get(name)` is the topology-independent global-store lookup, strict by default (an inactive/absent backend reads as `undefined` — prefer it over the `ctx.get(name, false)` overload, which also skips the active-state check). Services that ARE in `static inject` resolve fine via `ctx.`. See [docs/postmortem/0001](../docs/postmortem/0001-acp-default-export-drops-inject.md). @@ -14,6 +14,4 @@ Naming notes: - `src/types.ts` contains only types — no runtime code. - Tests live at package level under `tests/`, not `src/__tests__/`. - A package's README and JSDoc are part of the change: altered behavior (config keys, defaults, error codes, wire fields) updates them in the same commit. `doc-sync` gates what it can; prose accuracy stays on the author ([the documentation standard](../docs/AGENTS.md)). -- Every package README carries a `## Known Limitations and Deferred Work` section — condensed bullets for consumer-visible gaps and consciously postponed work; `verify-readme-limitations` (in `doc-sync`) gates it. A package with genuinely nothing to declare is instead whitelisted in [scripts/verify-readme-limitations.ts](../scripts/verify-readme-limitations.ts) and must not carry the section ([rationale](../docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md)). - -Read the per-package README.md for package-specific details: service API, events, extension points, known limitations. +- Package READMEs carry `## Known Limitations and Deferred Work` or a justified [allowlist entry](../scripts/verify-readme-limitations.ts) ([rationale](../docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md)). From 6ebc7313a02cfdae744e1b9d44c31fdb3757c700 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:55:01 +0800 Subject: [PATCH 14/38] docs(agent-core): correct invariants limitation --- packages/core/agent-core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/agent-core/README.md b/packages/core/agent-core/README.md index af660bf2b3..8acbc72c16 100644 --- a/packages/core/agent-core/README.md +++ b/packages/core/agent-core/README.md @@ -52,4 +52,4 @@ A YAML include can dedupe the config, but it cannot OWN a `bin`, and it can only ## Known Limitations and Deferred Work - **The spine set is fixed in code** — `apply()` mounts every child unconditionally (including `tool-bash`); no config excludes or replaces one, so swapping the loop or dropping a spine member means composing a different bundle. -- **`dsh-invariants` mounts unconditionally** — a dev/test plugin with default `freeze: true` and no reachable toggle in this bundle's `Config`, so every deployment currently pays the assertion/freeze cost. +- **`dsh-invariants` mounts unconditionally** — this bundle has no toggle, so every composition using it pays the dev-mode relational assertions; Session's always-on validation and freezing are separate. From 23850b4ad9e90dac72e358578587684ac93be2c1 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:59:05 +0800 Subject: [PATCH 15/38] docs: align model experience with scoped runtime --- docs/AGENTS.md | 10 +++++----- packages/core/agent-loop/README.md | 2 +- packages/core/scope/README.md | 2 +- packages/core/system-prompt/README.md | 2 +- packages/core/tools/README.md | 4 ++-- packages/subagent/subagent-acp/README.md | 2 +- packages/subagent/subagent-fork/README.md | 2 +- packages/subagent/subagent-inprocess/README.md | 2 +- packages/subagent/subagent-spawn/README.md | 2 +- packages/subagent/tool-subagent/README.md | 2 +- 10 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index ad592333d5..90b5b7354f 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -1,10 +1,10 @@ # AGENTS.md — The documentation standard -This file is the contract for every Markdown files in the repo: each tier's job, the writing rules, and the word budgets that `verify-doc-budgets` enforces. The audit/apply workflow is the [dsh-doc-standards](../.agents/skills/dsh-doc-standards/SKILL.md) skill; the decision record is [the doc-tiers-and-budgets RFC](rfc/implemented/process/2026-07-04-doc-tiers-and-budgets.md). +This is the repo's Markdown placement, writing, and budget contract. Use [dsh-doc-standards](../.agents/skills/dsh-doc-standards/SKILL.md) to apply it; the [doc-tiers-and-budgets RFC](rfc/implemented/process/2026-07-04-doc-tiers-and-budgets.md) records the rationale. ## The tier taxonomy: one home per fact -Every fact has exactly one home — the tier whose job it is — and every other place that needs it links there instead of restating it. A rule restated in two files drifts word-by-word until the copies disagree; a link cannot drift, and `verify-md-links` keeps it resolving. +Each fact has one owning tier; other tiers link to it. Restated rules drift, while `verify-md-links` keeps links resolving. | Tier | Job | Does NOT belong there | |---|---|---| @@ -20,7 +20,7 @@ Every fact has exactly one home — the tier whose job it is — and every other | Generated catalogs: [cordis events](cordis-catalog/events.md), [cordis services](cordis-catalog/services.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind | | Skills (`.agents/skills/`) | Workflows: how to carry out a recurring task against the contracts | The contracts themselves (→ docs) | -Placement test: a story about a bug → postmortem. Why we chose X → RFC. How to do task Y → cookbook. What type Z looks like → core-data-structures. What package P promises → its README. A rule every agent must always obey → root AGENTS.md, one line, linking the home that holds the why. +Placement: bugs → postmortems; rationale → RFCs; procedures → cookbooks; type shapes → core data; package promises → READMEs; standing orders → root `AGENTS.md` with a link to their rationale. ## Writing rules @@ -35,12 +35,12 @@ Placement test: a story about a bug → postmortem. Why we chose X → RFC. How ## Package Model Experience -Every package README ends with this table followed by `## Known Limitations and Deferred Work`; [allowlisted packages](../scripts/verify-readme-limitations.ts) end after the table: +Every package README ends with this table immediately before `## Known Limitations and Deferred Work`; [allowlisted packages](../scripts/verify-readme-limitations.ts) end after it: | Context surface | What the model sees | Token effect | |---|---|---| -Rows name request surface, condition, and agent scope, then classify tokens as fixed per request, conditional per call, retained, replaced, capped, or zero-direct. Separate conversation and auxiliary calls; zero-direct rows name the indirect path. `verify-package-readme-model-experience` enforces shape and order; review owns accuracy. +Rows state what reaches which model and classify token cost or lifetime; zero-direct rows name the indirect path. `verify-package-readme-model-experience` gates shape and order, while review owns accuracy ([rationale](rfc/implemented/process/2026-07-12-package-model-experience-contract.md)). ## Wordcount Budgets diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 981c13e656..b5d2a5f441 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -111,7 +111,7 @@ Everything that goes beyond "call the model, run the tools, repeat" belongs to p | Context surface | What the model sees | Token effect | |---|---|---| -| Complete conversation request | For each step, the loop sends the rendered per-agent system prompt, visible tool schemas, the frozen session prefix, and the session's derived messages. It supplies `model` and `cwd` variable values but no additional fixed prose. | System text, schemas, and prefix are paid again on every step. Per-agent scoping can substitute or remove individual contributions. | +| Complete conversation request | For each step, the loop sends the rendered per-agent system prompt, visible tool schemas, the frozen session prefix, and the session's derived messages. It supplies `model` and `cwd` variable values but no additional fixed prose. | System text, schemas, and prefix are paid again on every step. Per-agent scoping can substitute or remove ordinary contributions; owner-final protocol contributions retain their canonical state. | | Retained message history | Accepted user messages, assistant messages, tool calls and results, injected context, and steering are logged and sent on later steps. Raw stream chunks, lifecycle boundaries, and other log-only events are excluded. | Input grows with every surface message until a compaction replacement shadows older nodes; a multi-step tool turn resends the accumulated prefix and history each step. | ## Known Limitations and Deferred Work diff --git a/packages/core/scope/README.md b/packages/core/scope/README.md index bb8189e037..64e2df91ea 100644 --- a/packages/core/scope/README.md +++ b/packages/core/scope/README.md @@ -23,7 +23,7 @@ Handing out a scoped context hands out the minting plugin's service-resolution s | Context surface | What the model sees | Token effect | |---|---|---| -| Per-agent visibility control | This package emits no text or schema. It decides whether agent-scoped prompt sections, variables, tools, restrictions, and listeners apply to one agent, can shadow same-named global contributions, and removes them with that agent. | Zero direct tokens. It can add, replace, or remove whole contributions for one agent without changing another agent's request. | +| Per-agent visibility control | This package emits no text or schema. It routes scoped prompt sections, variables, tools, restrictions, and listeners to one agent: scoped registrations can shadow same-named globals, while restrictions filter global tools before scope-local tools are merged. All disappear with that agent. This is request composition, not authority confinement. | Zero direct tokens. It can add, replace, or remove whole contributions for one agent without changing another agent's request. | ## Known Limitations and Deferred Work diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index d7f5abcb0a..e17c72ba20 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -45,7 +45,7 @@ Design rationale: [the prompt-variables RFC](../../../docs/rfc/implemented/archi | Context surface | What the model sees | Token effect | |---|---|---| -| System prompt | Every assembly starts with `You are an AI agent powered by the DeepSeek Harness SDK.`, then the configured persona and ordered plugin sections after strict variable interpolation. Empty sections disappear; scoped sections and variables can shadow globals for one agent. | Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content. | +| System prompt | Every assembly starts with `You are an AI agent powered by the DeepSeek Harness SDK.`, then the configured persona and ordered plugin sections after strict variable interpolation. Empty sections disappear; ordinary scoped sections and variables can shadow globals for one agent, while owner-final contributions return to their canonical presence and definition after assembly interception. | Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content. | | Tool schemas | The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent; reordering changes cache shape but not semantic content. | ## Known Limitations and Deferred Work diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index 5a6f3da04c..04949af9a6 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -132,7 +132,7 @@ const bash = defineTool({ Under `mode: code` (or `both`) the registry turns the tool surface into a programming API, per the [Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md): the model writes a TypeScript program (the body of an async function) and passes it to the reserved wire transport `run_code`; the program runs in `ctx.codeRuntime` (the [code-execution seam](../../code-runtime/README.md) — the shipped backend is a worker thread) with one async binding per visible end-capability tool (`await tools.bash({...})`), and ONLY what it prints or returns re-enters the model's context. Scope restrictions change those SDK bindings but cannot remove or replace the transport itself. -- **The SDK section** (`tools:sdk`, order 150): a lazy prompt section regenerating, at each assembly, a `declare const tools: {...}` TypeScript declaration of the calling scope's visible end capabilities (exotic names via quoted keys), plus fixed usage instructions. The registry protects this section and the `run_code` wire schema after the assembly waterfall, so Code Mode cannot silently lose either half of its transport. Deterministic — lexicographic tool order, byte-identical text for an unchanged tool set (prefix-cache-friendly). The codegen (`jsonSchemaToTs`, exported) is total: constructs outside the `defineTool` subset degrade to `unknown`, never throw. +- **The SDK section** (`tools:sdk`, order 150): a lazy prompt section regenerating, at each assembly, a `declare const tools: {...}` TypeScript declaration of the calling scope's visible end capabilities (exotic names via quoted keys), plus fixed usage instructions. The registry marks this section and the `run_code` wire schema owner-final, so Code Mode cannot silently lose either half of its transport during assembly. Deterministic — lexicographic tool order, byte-identical text for an unchanged tool set (prefix-cache-friendly). The codegen (`jsonSchemaToTs`, exported) is total: constructs outside the `defineTool` subset degrade to `unknown`, never throw. - **The dispatch bridge** (`run_code`'s execute): every binding call is JSON-normalized before dispatch (a value that does not survive — `BigInt`, circulars — rejects that one call, so the dispatched form and logged form are the same JSON value by construction), serialized through a per-run queue (even `Promise.all` executes underlying calls one at a time in submission order), given the outer execution's opaque token as `parent`, and run through the complete pre-execute → guards → execute → post-execute → result pipeline. A denial reaches the program as a binding rejection, and each sub-call is logged as a `tool/code-dispatch` session event with deterministic id `:code:`; `deriveMessages()` does not surface that event. Token correlation lets commit-style observers defer an inner success until the final `run_code` result without exposing the live outer execution; ordinary tool side effects are not rolled back. A sub-call's `additionalContext` is deliberately dropped because inserting it inside a running parent call would break tool-call/result adjacency. - **Settlement discipline**: the bridge owns a run-scoped abort that follows the outer signal in and fires when the run settles for any reason, so a budget expiry aborts an in-flight sub-tool instead of orphaning it; the bridge then drains its queue BEFORE returning, so every `tool/code-dispatch` lands inside the open turn. A failed run throws `CodeRunFailedError` (`code: 'CODE_RUN_FAILED'`, message = the failure kind + captured logs), which the pipeline converts to a structured `isError` the model self-corrects from. @@ -142,7 +142,7 @@ The wire collapse is the registry's own contribution (`systemPrompt.tools()` is | Context surface | What the model sees | Token effect | |---|---|---| -| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead protects one `run_code` wire schema and adds a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's set. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | +| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead contributes one owner-final `run_code` wire schema and a generated owner-final TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's end-tool set but cannot remove or replace the transport. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | | Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index 5f5fbf8d2f..06c8180f88 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -61,7 +61,7 @@ Keyless tests drive a scripted ACP subprocess over real stdio. The with-key e2e | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent request | The remote child receives the standalone task through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation and cannot enforce the parent's scoped persona or tool filter. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | +| Child-agent request | The remote child receives the standalone task through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation. This provider advertises no optional start-time capabilities, so the local service rejects requests for persona, tool filtering, depth enforcement, or structured output instead of silently omitting them. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | | Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or a stop-reason error, not intermediate messages or tool traffic. | Parent input grows only by the final result, which is data-dependent and retained until compaction. This provider adds no parent schema itself. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-fork/README.md b/packages/subagent/subagent-fork/README.md index b0d925689c..930c50836b 100644 --- a/packages/subagent/subagent-fork/README.md +++ b/packages/subagent/subagent-fork/README.md @@ -27,7 +27,7 @@ See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, m | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent history | The child receives the parent's balanced completed-turn surface prefix, then the new task, along with its own scoped persona, tool filter, and optional structured-output contract. The parent's current in-flight turn is excluded. | Forking duplicates the retained completed history into a separate child's requests; the child then accumulates its own tokens independently. A first-turn fork has no inherited history. | +| Child-agent history | The child receives the parent's balanced completed-turn surface prefix, then the new task. Configured persona and tool restrictions compose only in the child's fresh scope; the parent's tool view and authority are not inherited. An optional structured-output request adds its child-only contract. The parent's current in-flight turn is excluded. | Forking duplicates the retained completed history into a separate child's requests; the child then accumulates its own tokens independently. A first-turn fork has no inherited history. | | Parent tool result, indirectly | The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work. | Parent input grows by one data-dependent final result retained until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md index 7aa8dcab19..9d8eea23e4 100644 --- a/packages/subagent/subagent-inprocess/README.md +++ b/packages/subagent/subagent-inprocess/README.md @@ -44,7 +44,7 @@ A clean turn that never commits the required structured value reports `error`; t | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent request | The shared driver sends the task as the child's user message and composes per-child scoped persona and tool restrictions. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. Structured output adds fixed instruction and capability tokens only to that child for that run. | +| Child-agent request | The shared driver sends the task as the child's user message and, when requested, composes persona and global-tool restrictions in the unpublished child's fresh scope; parent restrictions are not inherited. Structured runs add an owner-final scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. Optional persona, filtering, and structured-output changes affect only that child; structured output adds fixed instruction and capability tokens for the run. | | Parent result, indirectly | The driver extracts only the child's own last assistant output or captured structured value; seeded parent messages and intermediate child work do not become the result. | The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index 27e7f0a270..8ad605d68b 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -22,7 +22,7 @@ Spawn advertises `{ outputSchema: true, depthLimit: true, toolFilter: true, pers | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the globally composed prompt and tools after its scoped persona and tool filter. It receives zero parent conversation messages. | The child pays for a new independent context and history; no parent-history tokens are duplicated. | +| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the globally composed prompt and tools after any configured child-scoped persona shadow and global-tool restriction. It receives zero parent conversation messages; the filter is visibility/composition, not an authority grant inherited from the parent. | The child pays for a new independent context and history; no parent-history tokens are duplicated. Persona or filtering changes only this child's repeated prompt/schema cost. | | Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error. | Parent input grows by one data-dependent result retained until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index 58bea7cd2a..94b4df9f99 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -31,7 +31,7 @@ A non-`completed` stop reason becomes an `isError` tool result; partial child ou | Context surface | What the model sees | Token effect | |---|---|---| -| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | +| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. The filter changes the child's visible global tools, not an inherited authority ceiling. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | | Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. The result contains only the child's final text or a stop-reason error, never intermediate child steps. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | ## Known Limitations and Deferred Work From 1a4ef8c8d957622f86c293309353ec8ba00f873c Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 12 Jul 2026 20:34:05 -0700 Subject: [PATCH 16/38] =?UTF-8?q?docs(i18n):=20restructure=20terminology?= =?UTF-8?q?=20table=20=E2=80=94=20=E9=A6=96=E6=AC=A1=E5=87=BA=E7=8E=B0/?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E8=AF=91=E4=BD=9C=20as=20real=20columns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自由文本备注里的「首次出现可写:」「不要译作:」提为独立列,机器可 直接按列执行;表头补各列语义。按评审意见收录 counterpart、sidecar record(禁「旁挂记录」)、source of truth 裁定行,agent 组合词与 seam 括注规则细化。 --- docs/i18n/terminology.md | 282 ++++++++++++++++++++------------------- 1 file changed, 145 insertions(+), 137 deletions(-) diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md index 0166b87e4c..408048a66f 100644 --- a/docs/i18n/terminology.md +++ b/docs/i18n/terminology.md @@ -1,140 +1,148 @@ # Terminology -本表约定本仓库的中英术语统一译法。 +本表约定本仓库的中英术语统一译法。各列语义: -| English | 中文 | 备注 | -|---|---|---| -| ACP | ACP | 首次出现可写:ACP(Agent Client Protocol) | -| AI | AI | 首次出现可写:人工智能(AI) | -| API | API | real-API 作定语时可译「真实接口」(如 real-API tests → 真实接口测试) | -| CI | CI | | -| CLI | CLI | 首次出现可写:命令行界面(CLI) | -| Cordis | Cordis | 保留英文 | -| Function Calling | Function Calling | 首次出现可写:Function Calling(函数调用) | -| HMR | HMR | 首次出现可写:热模块替换(HMR) | -| JSON Schema | JSON Schema | | -| JSONL | JSONL | | -| lint | lint | | -| loader | loader | | -| LLM | LLM | 首次出现可写:大语言模型(LLM) | -| MCP | MCP | | -| PR | PR | 首次出现可写:PR(pull request) | -| RAG | RAG | 首次出现可写:检索增强生成(RAG) | -| SDK | SDK | | -| SSE | SSE | 首次出现可写:SSE(Server-Sent Events) | -| agent | agent | 首次出现可写:agent(智能体) | -| agent loop | agent loop | | -| backlog | backlog | 双语翻译语境指待翻清单 | -| blob hash | blob 哈希 | git 对象哈希;`git hash-object` 的结果 | -| commit hash | 提交哈希 | | -| hash | 哈希 | 代码与命令中保留英文(如 `git hash-object`、`` 占位符) | -| doc-sync | doc-sync | 仓库门禁名,保留英文 | -| e2e | e2e | | -| fiber | fiber | 首次出现可写:fiber(插件运行时) | -| fixture | fixture | 指测试前置数据或环境 | -| fork | fork | 保留英文 | -| harness | harness | 保留英文 | -| manifest | manifest | 描述模块或工具元数据的文件 | -| monorepo | monorepo | | -| package | package | 保留英文;指 npm 包(`@deepseek-ai/dsh-*`) | -| schema DSL | schema DSL | | -| schema | schema | 保留英文 | -| seam | seam | 首次出现可写:seam(扩展点) | -| skill | skill | 首次出现可写:skill(技能) | -| spawn | spawn | 保留英文 | -| steering | steering | 首次出现可写:steering(中途引导) | -| subagent | subagent | 首次出现可写:subagent(子 agent) | -| transcript | transcript | 首次出现可写:transcript(文本记录);指会话渲染给用户或编辑器的完整文本,区别于事件日志(event log) | -| waterfall | waterfall | 首次出现可写:waterfall(瀑布式事件) | -| worktree | worktree | git 工作区概念,保留英文 | -| wire format | 协议格式 | 首次出现可写:协议格式(wire format) | -| adapter contract | 适配器契约 | 首次出现可写:适配器契约(adapter contract) | -| adapter | 适配器 | | -| append-only | 仅追加 | | -| artifact | 产物 | | -| block | 块 | | -| background task | 后台任务 | | -| backend | 后端 | | -| capability | 能力 | | -| cancel | 取消 | | -| checkpoint | 检查点 | | -| chunk | 分片 | | -| compaction | compaction | 首次出现可写:compaction(上下文压缩);正文优先保留英文 | -| consumer | 消费方 | | -| content block | 内容块 | | -| config | 配置 | | -| context | 上下文 | | -| context compaction | 上下文压缩 | 首次出现可写:上下文压缩(context compaction) | -| contract | 契约 | 如:配对契约(pairing contract);另见 adapter contract | -| coverage | 覆盖率 | | -| crash recovery | 崩溃恢复 | | -| dispose | dispose | 首次出现可写:dispose(释放资源);正文优先保留英文 | -| durability | 持久性 | | -| enforcement frontier | 强制边界 | i18n 机制词:manifest `required` 清单所划的门禁生效范围 | -| event log | 事件日志 | | -| event | 事件 | | -| event stream | 事件流 | | -| event-sourced | 事件溯源 | DDD 社区通行译法 | -| executor | 执行器 | | -| extension | 扩展 | | -| fail-fast | 快速失败 | | -| fenced code block | 围栏代码块 | MDN 中文同译 | -| finish reason | 结束原因 | | -| fingerprint | 指纹 | i18n 机制词:`.zh.md` 首行记录英文源 blob hash 的 `i18n-source` 注释 | -| foreground run | 前台运行 | | -| freshness | 新鲜度 | MDN HTTP 缓存中文同译(freshness lifetime → 新鲜度生命周期);指译文相对英文源的同步状态 | -| hook | 钩子 | | -| implementation | 实现 | | -| inference | 推理(inference) | 每次提及时保留英文括注,避免与 reasoning 混淆 | -| info string | 信息字符串 | CommonMark 中文同译;代码围栏 ``` 之后的语言标注 | -| injection | 注入 | | -| interface | 接口 | | -| integration | 集成 | | -| language switcher | 语言切换行 | i18n 机制词:双语配对文件顶部的互链行 | -| memory | memory / 记忆 / 内存 | 按上下文区分:agent memory 译为“记忆”;resource/memory usage 译为“内存” | -| message | 消息 | | -| mock | mock | 保留英文;指测试替身 | -| mod | 模组 | 区别于 module(模块);plugin 译作「插件」 | -| model provider | 模型提供方 | | -| module | 模块 | | -| orphan | 孤立 | git 官方中文同译(如「孤立分支」);指英文源已不存在的 `.zh.md`;不要译作:孤儿。进程语境按 OS 惯用语译「孤儿进程」 | -| pairing | 配对 | | -| permission | 权限 | | -| persistence | 持久化 | | -| pipeline | 流水线 | | -| plugin | 插件 | mod 对应“模组” | -| prompt | 提示词 | | -| provider | 提供方 | | -| provider-neutral | 提供方无关 | | -| quality gate | 质量门禁 | | -| registry | 注册表 | | -| reasoning | 推理(reasoning) | 需要和 inference 区分时保留英文括注;`reasoning_content` 译为“思考内容” | -| replay | 回放 | | -| resume | 恢复 | | -| runtime | 运行时 | | -| sandbox | 沙箱 | | -| service | 服务 | | -| session | 会话 | | -| session event | 会话事件 | | -| smoke test | 冒烟测试 | | -| snapshot | 快照 | | -| spine | 主干 | | -| staged | 暂存 | git 官方中文同译 | -| stale | 陈旧 | MDN HTTP 缓存中文同译,与「新鲜(fresh)」成对;门禁输出保留英文 `stale`;expired 才译「过期」 | -| step | 步骤 | | -| stream | 流 | | -| streaming | 流式输出 | | -| structural signature | 结构签名 | i18n 机制词:配对门禁比对的有序结构序列 | -| system prompt | 系统提示词 | | -| taxonomy | 分类体系 | | -| token usage | token 用量 | | -| thinking | thinking | API 字段保留;模型模式译为“思考” | -| tool | 工具 | | -| tool call | 工具调用 | | -| tool result | 工具结果 | | -| tool schema | 工具 schema | | -| toolkit | 工具包 | | -| turn | 轮次 | | -| typecheck | 类型检查 | | -| vocabulary | 词汇 | | -| workflow | 工作流 | | +- **中文** — 正文中的固定译法;写「保留英文」表示不译。 +- **首次出现** — 术语在一篇文档中第一次出现时的完整写法(含括注);之后只写「中文」列的形式。组合词已括注过的成分,单独出现时不再括注。 +- **不要译作** — 禁用译法,逐项列出;出现任何一项即评审阻断。 +- **备注** — 语境区分、裁定出处等自由说明;不承载可执行规则。 + +| English | 中文 | 首次出现 | 不要译作 | 备注 | +|---|---|---|---|---| +| ACP | ACP | ACP(Agent Client Protocol) | | | +| AI | AI | 人工智能(AI) | | | +| API | API | | | real-API 作定语时可译「真实接口」(如 real-API tests → 真实接口测试) | +| CI | CI | | | | +| CLI | CLI | 命令行界面(CLI) | | | +| Cordis | Cordis | | | 保留英文 | +| Function Calling | Function Calling | Function Calling(函数调用) | | | +| HMR | HMR | 热模块替换(HMR) | | | +| JSON Schema | JSON Schema | | | | +| JSONL | JSONL | | | | +| lint | lint | | | | +| loader | loader | | | | +| LLM | LLM | 大语言模型(LLM) | | | +| MCP | MCP | | | | +| PR | PR | PR(pull request) | | | +| RAG | RAG | 检索增强生成(RAG) | | | +| SDK | SDK | | | | +| SSE | SSE | SSE(Server-Sent Events) | | | +| agent | agent | agent(智能体) | | 括注只落在 agent 单独首现处;组合词见下行 | +| agent harness, agent workflow, agent loop, agent skill | 保留英文 | | | agent 组合词整体保留英文、不括注;其中的 workflow/loop/skill 不单独拆译;未列出的 agent 组合词同此处理 | +| backlog | backlog | | | 双语翻译语境指待翻清单 | +| blob hash | blob 哈希 | | | git 对象哈希;`git hash-object` 的结果 | +| commit hash | 提交哈希 | | | | +| hash | 哈希 | | | 代码与命令中保留英文(如 `git hash-object`、`` 占位符) | +| doc-sync | doc-sync | | | 仓库门禁名,保留英文 | +| e2e | e2e | | | | +| fiber | fiber | fiber(插件运行时) | | | +| fixture | fixture | | | 指测试前置数据或环境 | +| fork | fork | | | 保留英文 | +| harness | harness | | | 保留英文 | +| manifest | manifest | | | 描述模块或工具元数据的文件 | +| monorepo | monorepo | | | | +| package | package | | | 保留英文;指 npm 包(`@deepseek-ai/dsh-*`) | +| schema DSL | schema DSL | | | | +| schema | schema | | | 保留英文 | +| seam | seam | seam(扩展点) | | 同句已出现「扩展点」(extension point)时不加括注,直接写 seam,避免一词两指 | +| skill | skill | skill(技能) | | | +| spawn | spawn | | | 保留英文 | +| steering | steering | steering(中途引导) | | | +| subagent | subagent | subagent(子 agent) | | | +| transcript | transcript | transcript(文本记录) | | 指会话渲染给用户或编辑器的完整文本,区别于事件日志(event log) | +| waterfall | waterfall | waterfall(瀑布式事件) | | | +| worktree | worktree | | | git 工作区概念,保留英文 | +| wire format | 协议格式 | 协议格式(wire format) | | | +| adapter contract | 适配器契约 | 适配器契约(adapter contract) | | | +| adapter | 适配器 | | | | +| append-only | 仅追加 | | | | +| artifact | 产物 | | | | +| block | 块 | | | | +| background task | 后台任务 | | | | +| backend | 后端 | | | | +| capability | 能力 | | | | +| cancel | 取消 | | | | +| checkpoint | 检查点 | | | | +| chunk | 分片 | | | | +| compaction | compaction | compaction(上下文压缩) | | 正文优先保留英文 | +| consumer | 消费方 | | | | +| counterpart | 对侧文件 | | 对应物、配对物 | 双语配对语境;泛指“另一侧”时可写「另一侧」 | +| content block | 内容块 | | | | +| config | 配置 | | | | +| context | 上下文 | | | | +| context compaction | 上下文压缩 | 上下文压缩(context compaction) | | | +| contract | 契约 | | | 如:配对契约(pairing contract);另见 adapter contract | +| coverage | 覆盖率 | | | | +| crash recovery | 崩溃恢复 | | | | +| dispose | dispose | dispose(释放资源) | | 正文优先保留英文 | +| durability | 持久性 | | | | +| enforcement frontier | 强制边界 | | | i18n 机制词:manifest `required` 清单所划的门禁生效范围 | +| event log | 事件日志 | | | | +| event | 事件 | | | | +| event stream | 事件流 | | | | +| event-sourced | 事件溯源 | | | DDD 社区通行译法 | +| executor | 执行器 | | | | +| extension | 扩展 | | | | +| fail-fast | 快速失败 | | | | +| fenced code block | 围栏代码块 | | | MDN 中文同译 | +| finish reason | 结束原因 | | | | +| fingerprint | 指纹 | | | i18n 机制词:`.zh.md` 首行记录英文源 blob hash 的 `i18n-source` 注释 | +| foreground run | 前台运行 | | | | +| freshness | 新鲜度 | | | MDN HTTP 缓存中文同译(freshness lifetime → 新鲜度生命周期);指译文相对英文源的同步状态 | +| hook | 钩子 | | | | +| implementation | 实现 | | | | +| inference | 推理(inference) | | | 每次提及时保留英文括注,避免与 reasoning 混淆 | +| info string | 信息字符串 | | | CommonMark 中文同译;代码围栏 ``` 之后的语言标注 | +| injection | 注入 | | | | +| interface | 接口 | | | | +| integration | 集成 | | | | +| language switcher | 语言切换行 | | | i18n 机制词:双语配对文件顶部的互链行 | +| memory | memory / 记忆 / 内存 | | | 按上下文区分:agent memory 译为“记忆”;resource/memory usage 译为“内存” | +| message | 消息 | | | | +| mock | mock | | | 保留英文;指测试替身 | +| mod | 模组 | | | 区别于 module(模块);plugin 译作「插件」 | +| model provider | 模型提供方 | | | | +| module | 模块 | | | | +| orphan | 孤立 | | 孤儿 | git 官方中文同译(如「孤立分支」);指英文源已不存在的 `.zh.md`;进程语境按 OS 惯用语译「孤儿进程」 | +| pairing | 配对 | | | | +| permission | 权限 | | | | +| persistence | 持久化 | | | | +| pipeline | 流水线 | | | | +| plugin | 插件 | | | mod 对应“模组” | +| prompt | 提示词 | | | | +| provider | 提供方 | | | | +| provider-neutral | 提供方无关 | | | | +| quality gate | 质量门禁 | | | | +| registry | 注册表 | | | | +| reasoning | 推理(reasoning) | | | 需要和 inference 区分时保留英文括注;`reasoning_content` 译为“思考内容” | +| replay | 回放 | | | | +| resume | 恢复 | | | | +| runtime | 运行时 | | | | +| sandbox | 沙箱 | | | | +| service | 服务 | | | | +| session | 会话 | | | | +| session event | 会话事件 | | | | +| sidecar record | 伴随记录 | | 旁挂记录 | 指 `.i18n.yaml` 这类随主文件存放的记录文件 | +| smoke test | 冒烟测试 | | | | +| snapshot | 快照 | | | | +| source of truth | 真源 | 真源(source of truth) | | 评审裁定译法;如后续裁定调整,改此表即可 | +| spine | 主干 | | | | +| staged | 暂存 | | | git 官方中文同译 | +| stale | 陈旧 | | | MDN HTTP 缓存中文同译,与「新鲜(fresh)」成对;门禁输出保留英文 `stale`;expired 才译「过期」 | +| step | 步骤 | | | | +| stream | 流 | | | | +| streaming | 流式输出 | | | | +| structural signature | 结构签名 | | | i18n 机制词:配对门禁比对的有序结构序列 | +| system prompt | 系统提示词 | | | | +| taxonomy | 分类体系 | | | | +| token usage | token 用量 | | | | +| thinking | thinking | | | API 字段保留;模型模式译为“思考” | +| tool | 工具 | | | | +| tool call | 工具调用 | | | | +| tool result | 工具结果 | | | | +| tool schema | 工具 schema | | | | +| toolkit | 工具包 | | | | +| turn | 轮次 | | | | +| typecheck | 类型检查 | | | | +| vocabulary | 词汇 | | | | +| workflow | 工作流 | | | | From 8ad6dbc3596fcbeb46da1c6383e7a7663ea2895a Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 12 Jul 2026 20:34:19 -0700 Subject: [PATCH 17/38] docs(i18n): commit the pipeline translation prompt as a repo asset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 自动翻译流水线的 prompt 模板进仓(docs/i18n/translation-prompt.md): 占位符契约(source_lang/target_lang/terminology 三个,旧 %% 分段协议 废弃)+ 逐字模板正文(三段 XML 输出、分类自审、8 组正误例)。列入 配对排除清单;skill 与流水线自此共用同一套规则文件。 --- .agents/skills/dsh-translate-docs/SKILL.md | 1 + docs/i18n/README.i18n.yaml | 4 +- docs/i18n/README.md | 1 + docs/i18n/README.zh.md | 1 + docs/i18n/translation-prompt.md | 178 +++++++++++++++++++++ scripts/translation-pairing.manifest.json | 3 +- 6 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 docs/i18n/translation-prompt.md diff --git a/.agents/skills/dsh-translate-docs/SKILL.md b/.agents/skills/dsh-translate-docs/SKILL.md index 8a2b231e8f..1a5cc03549 100644 --- a/.agents/skills/dsh-translate-docs/SKILL.md +++ b/.agents/skills/dsh-translate-docs/SKILL.md @@ -14,6 +14,7 @@ These are authoritative; read them at the source so this skill never drifts out - **[docs/i18n/README.md](../../../docs/i18n/README.md)** — the pairing contract: the three-file pair (`foo.md`, `foo.zh.md`, `foo.i18n.yaml`), the consistency record's both-side blob hashes, the language-switcher lines, scope/exclusions, and the rollout manifest. - **[docs/i18n/translation-rules.md](../../../docs/i18n/translation-rules.md)** — how to translate: faithfulness, structure preservation, terminology discipline, typography (MUST/SHOULD levels). - **[docs/i18n/terminology.md](../../../docs/i18n/terminology.md)** — the terminology table, binding in both directions. Load it BEFORE translating, not when a term feels uncertain; the terms you don't notice are the ones that drift. +- **[docs/i18n/translation-prompt.md](../../../docs/i18n/translation-prompt.md)** — the automated pipeline's prompt template (placeholder contract + the verbatim prompt body). Agents following THIS skill do not render that template; it exists so the pipeline and this skill share one set of rules — a rule change lands in both or it is a bug. ## Find the work diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index 84cc3e7977..3c954439ce 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.i18n.yaml @@ -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 -README.md: f28676b7d555aca30d8b5b21eba9fb4222949763 -README.zh.md: 5353a8ba04288cf72defa30b5896731b3d419b69 +README.md: 94c8ad9e3a83b490a854dce69b27bd7e13d834b5 +README.zh.md: 4c66da76feff1b1a6c175da62cbd986838b3577e diff --git a/docs/i18n/README.md b/docs/i18n/README.md index f28676b7d5..94c8ad9e3a 100644 --- a/docs/i18n/README.md +++ b/docs/i18n/README.md @@ -42,6 +42,7 @@ The gate's limit, stated plainly: **a green gate means the pair was confirmed co - `docs/cordis-catalog/`, `docs/tool-catalog/`, `docs/module-graph.md` — generated files; their generators emit English only today, so a hand-written translation would go stale on every regeneration. The planned follow-up is to teach the generators to emit Chinese alongside English, at which point these leave the exclusion list. - `docs/AGENTS.md` — agent instructions, maintained in English only like the root `AGENTS.md`. - `docs/i18n/terminology.md` and [style-samples.md](style-samples.md) — both are bilingual by construction. +- [translation-prompt.md](translation-prompt.md) — the automated pipeline's prompt template; its body is machine-consumed verbatim, so a paired translation would change pipeline behavior. **Rollout**: new documents don't wait for a batch — a date-named document (`yyyy-mm-dd-*.md`, i.e. an RFC) dated on or after the manifest's `requiredSince` cutoff must merge with its pair, so everything new is bilingual from birth. Files dated before the cutoff are the grandfathered backlog by definition — including files created on the cutoff's eve — and a document's filename date is its first-proposed date per the RFC convention, so backdating past the cutoff is a review-visible violation, not a loophole. For the back-catalog, the `required` list in the manifest is the enforcement frontier, not the goal. The goal is full bilingual coverage of the scope. Pairs land in reviewable batches (core entry docs, cookbook, RFCs, postmortems, …); each merged batch adds its files to `required`, so the gate ratchets forward and never regresses. Documents not yet in `required` are backlog — visible in `--list` — but any pair that already exists is held to the full contract regardless of the list. Pairing a document is a commitment: every later edit to either side must carry the counterpart along, so grow the frontier at the pace translation review is actually resourced, not ahead of it. diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index 5353a8ba04..4c66da76fe 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -42,6 +42,7 @@ - `docs/cordis-catalog/`、`docs/tool-catalog/`、`docs/module-graph.md`——生成文件;生成器目前只输出英文,手写译文在每次重新生成时必然陈旧。计划中的后续工作是让生成器同时输出中文,届时这些文件移出排除清单。 - `docs/AGENTS.md`——agent 指令,与根 `AGENTS.md` 一样只以英文维护。 - `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md)——二者本身即为中英对照文档。 +- [translation-prompt.md](translation-prompt.md)——自动翻译流水线的 prompt 模板;正文逐字进入模型请求,配对翻译会改变流水线行为。 **推进**:新文档不等批次——文件名带日期的文档(`yyyy-mm-dd-*.md`,即 RFC)日期在 manifest 的 `requiredSince` 当天或之后,就必须连同配对一起合入,新增的一切生来即是双语。日期早于 cutoff 的文件按定义属于被豁免的存量——包括 cutoff 前夜创建的文件——而文件名日期按 RFC 惯例即首次提出日期,倒填日期绕过 cutoff 是评审可见的违规,不是漏洞。对于存量文档,manifest 中的 `required` 列表是强制边界,不是目标。目标是范围内的全量双语覆盖。配对按可评审的批次落地(核心入口文档、cookbook、RFC、postmortem……);每个批次合入后把其文件加进 `required`,门禁只进不退。尚未进入 `required` 的文档是 backlog——在 `--list` 中可见——但任何已存在的配对无论在不在清单里都按完整契约检查。给一篇文档配对是一份承诺:此后对任一侧的每次修改都必须带上另一侧,所以边界的扩张要跟上翻译评审的实际投入节奏,不要抢在前面。 diff --git a/docs/i18n/translation-prompt.md b/docs/i18n/translation-prompt.md new file mode 100644 index 0000000000..db9b78aaee --- /dev/null +++ b/docs/i18n/translation-prompt.md @@ -0,0 +1,178 @@ +# Translation prompt (pipeline asset) + +本文件是自动翻译流水线使用的 prompt 模板,正文(自 `# Translation Prompt` 起)逐字进入模型请求,不参与双语配对(见 [README.md](README.md) 排除清单)。模板与仓库规则的关系:[terminology.md](terminology.md) 在渲染时整表填入 `{{terminology}}`;文体金标见 [style-samples.md](style-samples.md),模板内嵌的 Examples 是其中问题类别的最小抽样,两者冲突时以 style-samples 为准。修改本文件即修改线上翻译行为,按正常 PR 评审。 + +## 占位符契约 + +流水线渲染模板时替换以下占位符,除此之外不做任何文本处理: + +| 占位符 | 填入内容 | 来源 | +|---|---|---| +| `{{source_lang}}` | 源语言名(`English` / `Chinese`) | 由改动侧文件推断:`.zh.md` 被改则为 `Chinese` | +| `{{target_lang}}` | 目标语言名(`Chinese` / `English`) | 与 `{{source_lang}}` 相对 | +| `{{terminology}}` | [terminology.md](terminology.md) 的完整表格(Markdown 原文) | 渲染时读取仓库当前版本,不缓存 | + +历史模板的 `{{to}}`、`{{title_prompt}}`、`{{summary_prompt}}`、`{{terms_prompt}}`、`{{imt_style_guide}}` 占位符与 `%%` 分段协议已废弃:本模板按整文档翻译(非分段),输出协议为下方三段 XML。 + +## 模板正文 + +````text +# Translation Prompt + +You are a senior technical translator specializing in LLM and agent development documentation. Your task is to translate the given source document from {{source_lang}} to {{target_lang}}, producing natural, professional technical prose. + +## Quality Requirements + +### Structure and Format Preservation +- Output a complete translated document that maintains exactly the same structure as the source: heading hierarchy, list shape, table columns, link targets, and code blocks. +- Fenced code blocks must be byte-identical to the source, including comments. Do not translate any content inside code fences. +- Inline code spans (commands, flags, paths, API names, version numbers) must be kept verbatim. Never translate or reformat them. +- Every relative link must point to the same target as in the source. Link text is translated; link targets are not. +- After a closing bold marker `**`, always insert a space before the next character. + +### Tone and Style +- The translation must read as if originally written in the target language by a native speaker. If an expression sounds like a word-for-word rendering from the source language, rephrase it. +- Write in a professional, formal tone appropriate for developer documentation. Never use colloquial or casual expressions. +- Use polite imperative forms where the text instructs the reader to do something. +- Keep the author's register: concise stays concise, detailed stays detailed. + +### Sentence Structure +- Break long sentences with commas or semicolons. Avoid run-on sentences. +- Prefer active voice. Convert passive constructions to active if it reads more naturally. +- Translate meaning, not words. Restructure sentences where the target language grammar requires it. +- Do not invent words or expressions that do not exist in natural technical writing of the target language. + +### Word Choice +- Prefer precise, formal vocabulary over casual or colloquial alternatives. +- When multiple synonyms exist, choose the one most commonly used in professional technical documentation of the target language. +- Avoid slang, internal jargon, or overly literal translations that would not be recognized by the general developer audience. +- Do not use the same word to translate two different source-language terms that carry distinct meanings. +- Avoid repeating the same verb in close proximity; vary word choice for readability. + +### Punctuation + +#### When translating into Chinese +- Use full-width Chinese punctuation in prose: `,。:;?!()「」`. +- Replace em-dashes (——) with colons, periods, commas, or parentheses as appropriate. Only keep em-dashes when they are truly the best choice. +- Use enumeration commas (、) between parallel items, not regular commas. +- List item endings: use semicolons or no punctuation. Do not end list items with commas. +- Put one half-width space between Chinese text and Latin words/numbers. +- For RFC 2119 keywords (MUST, MUST NOT, SHOULD, MAY), render the corresponding Chinese term in italics: *必须*、*禁止*、*应当*、*可以*. + +#### When translating into English +(To be added.) + +## Terminology + +A terminology table is provided below. Follow it strictly: +- Render every listed term exactly as specified. +- First occurrence: write as shown in the "首次出现" column (with parenthetical gloss). Subsequent occurrences: write only the part before the parentheses. +- If a term has already been glossed as part of a compound term, do not gloss it again when it appears alone later. +- NEVER use translations listed in the "不要译作" column. +- For technical terms not in the table: keep them in the source language. Do not invent a translation. This rule applies to terminology only; for general prose, freely restructure and paraphrase for natural expression. + +{{terminology}} + +## Output Format + +Produce your output in three XML sections: + +```xml + +(Complete translation of the source document) + + + +(Self-review notes, one correction per line with category tag, e.g.) +- [Tone] "旁挂记录" → "伴随记录"(生造词) +- [Sentence] 第 3 段补充逗号断句 +- [Punctuation] 两处破折号替换为冒号 +- 无修正 + + + +(Final translation after corrections) + +``` + +## Self-Review Instructions + +After writing ``, re-read it in the target language only, without looking at the source. Check by category: + +**Structure** +- Is the heading hierarchy, list shape, and code block content identical to the source? +- Are link targets preserved and bold markers followed by a space? + +**Tone & Style** +- Does every sentence read as if originally written by a native speaker? +- Is there any colloquial, casual, or overly informal phrasing? + +**Sentence Structure** +- Are there run-on sentences that need breaking? +- Are there stiff passive constructions that should be converted to active voice? + +**Word Choice** +- Are there overly literal translations that sound unnatural? +- Is the same target-language word used to translate two distinct source concepts? +- Is any slang or internal jargon present? + +**Terminology** +- Are first-occurrence glosses correctly applied (not missing, not repeated)? +- Are any "不要译作" forbidden translations present? +- Are unlisted terms correctly kept in the source language? + +**Punctuation** (when target is Chinese) +- Are there em-dashes that should be replaced with colons, periods, or commas? +- Are list items ending with commas instead of semicolons? +- Are RFC 2119 keywords rendered in italics? + +Record corrections in `` with category tags. Then output the corrected version in ``. If no corrections are needed, write "无修正" in `` and copy the translation unchanged into ``. + +## Examples + +Below are representative examples of common problems and their corrections. Follow the "Good" versions. + +### Colloquial verb → Professional verb +- Source: `The repo pins pnpm@11.7.0 in package.json` +- Bad: `仓库在 package.json 中钉住 pnpm@11.7.0` +- Good: `该仓库在 package.json 中固定使用 pnpm@11.7.0` + +### Run-on sentence → Natural phrasing with pause +- Source: `Read docs/architecture.md before changing anything under packages/.` +- Bad: `改动 packages/ 下的任何东西之前先读 docs/architecture.md。` +- Good: `在修改 packages/ 目录下的任何内容之前,请先阅读 docs/architecture.md。` + +### Stiff passive voice → Active and natural +- Source: `a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.` +- Bad: `门禁绿意味着这对文档曾在当前内容上被确认一致,不意味着这次确认本身是对的。` +- Good: `门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。` + +### Invented word → Natural expression +- Source: `A sidecar record of both blob hashes makes consistency checkable` +- Bad: `旁挂记录两侧 blob hash,使一致性可检查` +- Good: `伴随记录保存两侧 blob hash,使一致性可检查` + +### Em-dash → Colon/period +- Source: `FIXME — an issue that should block a new release. A release should not ship with an open FIXME unless reviewers explicitly agree the change can be merged anyway.` +- Bad: `FIXME——应当阻塞新版本发布的问题。除非评审者明确同意可以照常合入,发布不应带着未解决的 FIXME 出门。` +- Good: `FIXME:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 FIXME。` + +### Overly literal → Meaningful rendering +- Source: `awkward phrasing is easier to hear without the source anchoring you` +- Bad: `没有源文锚着,别扭的表述更容易被听出来` +- Good: `不对照原文时,更容易察觉别扭的表达` + +### Terminology — do not translate what should be kept in English +- Source: `typed service seams, and explicit extension points` +- Bad: `类型化的服务 seam(扩展点)与显式扩展点` +- Good: `类型化的服务 seam 与显式扩展点` + +### Slang/jargon → Professional phrasing +- Source: `The committed agent workflow lives in .agents/skills/dsh-translate-docs` +- Bad: `进仓的 agent 工作流见 .agents/skills/dsh-translate-docs` +- Good: `仓库内置的 agent 工作流见 .agents/skills/dsh-translate-docs` + +--- + +Now translate the following document: +```` diff --git a/scripts/translation-pairing.manifest.json b/scripts/translation-pairing.manifest.json index 6f05431ced..77d7936822 100644 --- a/scripts/translation-pairing.manifest.json +++ b/scripts/translation-pairing.manifest.json @@ -13,6 +13,7 @@ "docs/cordis-catalog/", "docs/tool-catalog/", "docs/i18n/terminology.md", - "docs/i18n/style-samples.md" + "docs/i18n/style-samples.md", + "docs/i18n/translation-prompt.md" ] } From c74cc5a6099c79d62a5c0ba7a19b3d136dc3cc15 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 12 Jul 2026 20:41:10 -0700 Subject: [PATCH 18/38] docs(i18n): credit the prompt and few-shot examples to jingtingxiang --- docs/i18n/translation-prompt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/i18n/translation-prompt.md b/docs/i18n/translation-prompt.md index db9b78aaee..573f125878 100644 --- a/docs/i18n/translation-prompt.md +++ b/docs/i18n/translation-prompt.md @@ -1,6 +1,6 @@ # Translation prompt (pipeline asset) -本文件是自动翻译流水线使用的 prompt 模板,正文(自 `# Translation Prompt` 起)逐字进入模型请求,不参与双语配对(见 [README.md](README.md) 排除清单)。模板与仓库规则的关系:[terminology.md](terminology.md) 在渲染时整表填入 `{{terminology}}`;文体金标见 [style-samples.md](style-samples.md),模板内嵌的 Examples 是其中问题类别的最小抽样,两者冲突时以 style-samples 为准。修改本文件即修改线上翻译行为,按正常 PR 评审。 +本文件是自动翻译流水线使用的 prompt 模板,正文(自 `# Translation Prompt` 起)逐字进入模型请求,不参与双语配对(见 [README.md](README.md) 排除清单)。模板正文与其内嵌 few-shot 正误例由 jingtingxiang 基于对存量译文的质量评审撰写。模板与仓库规则的关系:[terminology.md](terminology.md) 在渲染时整表填入 `{{terminology}}`;文体金标见 [style-samples.md](style-samples.md),模板内嵌的 Examples 是其中问题类别的最小抽样,两者冲突时以 style-samples 为准。修改本文件即修改线上翻译行为,按正常 PR 评审。 ## 占位符契约 From ba4e39f10d52507c05c31e852bd9622bb905b784 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:07:45 -0700 Subject: [PATCH 19/38] docs(i18n): adopt jingtingxiang's calibrated terminology table and five gold pairs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 术语表整体采用 jingtingxiang 的重构版(PR #244):按缩写类/英文类/ 双语类分节、通用规则前置、首次出现与不要译作独立列;保留本 stack 的 mock/package/counterpart 与 agent 组合词裁定行。五组金标译文按 其定稿采用(development、i18n README、translation-rules、双语 RFC、 根 README),README.zh 吸收 PR #249 的两处措辞(「智能体辔架」括注 与其自身新表冲突,未采用)。translation-prompt.md 增补 Few-shot 金 标一节:声明这 5 组配对即流水线的整文档级 few-shot,注入方式为多轮 示例对话。style-samples 的 blob hash 译法按新表回改为保留英文。 Supersedes the .zh.md content of #244/#249/#264. --- README.i18n.yaml | 2 +- README.zh.md | 4 +- docs/development.i18n.yaml | 2 +- docs/development.zh.md | 58 ++-- docs/i18n/style-samples.md | 2 +- docs/i18n/terminology.md | 301 +++++++++--------- docs/i18n/translation-prompt.md | 12 + docs/i18n/translation-rules.i18n.yaml | 2 +- docs/i18n/translation-rules.zh.md | 62 ++-- ...-bilingual-docs-and-pairing-gate.i18n.yaml | 2 +- ...7-02-bilingual-docs-and-pairing-gate.zh.md | 42 +-- 11 files changed, 262 insertions(+), 227 deletions(-) diff --git a/README.i18n.yaml b/README.i18n.yaml index 0a981d4323..b765b44217 100644 --- a/README.i18n.yaml +++ b/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write README.md: 7ddf68bab06ecf891856e6d1393ccdefd9eeba38 -README.zh.md: 59a0419164f2dfee6f66903cc93d7b35da1d9063 +README.zh.md: ad568c808d4c8003a84734bc8035fac9a07d27b4 diff --git a/README.zh.md b/README.zh.md index 59a0419164..ad568c808d 100644 --- a/README.zh.md +++ b/README.zh.md @@ -6,7 +6,7 @@ ## 开发 -本 monorepo 基于 [Cordis](https://github.com/cordiverse/cordis) 框架构建(以源码形式收录在 `vendor/` 下),采用微内核风格:一切皆插件。 +本 monorepo 基于 [Cordis](https://github.com/cordiverse/cordis) 框架构建(以源码形式收录在 `vendor/` 下),采用微内核风格:所有功能都以插件形式提供。 ```sh pnpm install @@ -15,6 +15,6 @@ pnpm run demo:repl # REPL agent demo (needs DEEPSEEK_API_KEY) pnpm run demo:acp # ACP server agent demo (needs DEEPSEEK_API_KEY) ``` -面向人类读者:先读[开发指南](docs/development.md)了解本地环境搭建、钩子、环境变量与质量门禁,动手改 package 之前再读[架构设计](docs/architecture.md)。局部上下文见 [packages/](packages/) 与 [vendor/](vendor/)。 +面向开发者:先读[开发指南](docs/development.md),了解本地环境搭建、钩子、环境变量与质量门禁,动手改 package 之前再读[架构设计](docs/architecture.md)。局部上下文见 [packages/](packages/) 与 [vendor/](vendor/)。 面向 agent:遵循 [AGENTS.md](AGENTS.md)。 diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index d8f76162db..ac9ee414b1 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write development.md: 28babca2b59c844690750d767c242c08c37bf702 -development.zh.md: b34c52cebd3f333b6554ca2b5ebd66463e436572 +development.zh.md: 91b63d7511f856f21162f8ab66eae6e25d925059 diff --git a/docs/development.zh.md b/docs/development.zh.md index b34c52cebd..91b63d7511 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -2,14 +2,14 @@ [English](development.md) | 中文 -本指南覆盖参与 DeepSeek Harness 开发所需的本地环境搭建,并帮助你理解本地钩子、日常检查与 CI 门禁。 +本指南覆盖参与 DeepSeek Harness 开发所需的本地环境搭建,并帮助你了解本地钩子、日常检查与 CI 门禁。 ## 前置条件 -- Node.js 24 或更新版本。仓库声明 `node >=24`;CI 在 Node 24 和 26 上跑矩阵。 -- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中钉住 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,先运行 `corepack enable`。 +- Node.js 24 或更新版本。仓库声明 `node >=24`;CI 在 Node 24 和 26 上运行矩阵测试。 +- 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。 - Git。 -- 可选:一个 DeepSeek API key,用于 REPL/ACP agent(智能体)演示和真实 API 的 e2e 测试。 +- 可选:一个 DeepSeek API key,用于 REPL/ACP agent 演示和真实 API 的 e2e 测试。 ## 首次搭建 @@ -19,23 +19,23 @@ pnpm install ``` -安装同时会运行根目录的 `postinstall` 脚本,它通过 `scripts/install-lefthook.mjs` 从仓库 dev 依赖安装 lefthook;该包装脚本使用 lefthook 经过评审的 `--force` 模式,使已存在 `core.hooksPath` 的关联 worktree 不会让正常的 `pnpm run …` 命令失败。 +安装过程同时会运行根目录的 `postinstall` 脚本,该脚本通过 `scripts/install-lefthook.mjs` 从仓库 dev 依赖安装 lefthook。包装脚本使用 lefthook 经过评审的 `--force` 模式,确保已存在 `core.hooksPath` 的关联 worktree 不会导致正常的 `pnpm run …` 命令失败。 -如果因为依赖是从缓存恢复或 `postinstall` 被跳过而缺少钩子,手动安装: +如果依赖是从缓存恢复或 `postinstall` 被跳过而导致缺少钩子,请手动安装: ```sh pnpm exec lefthook install --force ``` -新克隆后先跑一次类型检查: +新克隆后请先运行一次类型检查: ```sh pnpm run typecheck ``` -这次首跑会构建 package/vendor 构建图,并跑根目录 no-emit `tsconfig.json` 图(覆盖 examples、tests 和 scripts)。根图使用同一份源码 `paths` 映射,但依赖 project references,因此 vendor 代码在它自己的 tsconfig 设置下被检查。 +首次类型检查会执行 package/vendor 的构建图,以及根目录下用于示例、测试和脚本的 no-emit `tsconfig.json` 项目图。根图使用同一份源码 `paths` 映射,但依赖 project references,因此 vendor 代码在它自己的 tsconfig 设置下被检查。 -如果准备从新克隆或新 worktree 推送,还要构建一次: +如果准备从新克隆或新 worktree 推送,还需要构建一次: ```sh pnpm run build @@ -45,29 +45,29 @@ pnpm run build ## 环境变量 -真实的 DeepSeek 适配器和需要密钥的 agent 演示从环境变量或仓库根目录一个被 gitignore 的 `.env` 读取凭证: +真实的 DeepSeek 适配器和需要密钥的 agent 演示从环境变量或仓库根目录一个被 gitignore 的 `.env` 文件读取凭证: ```sh DEEPSEEK_API_KEY=sk-... DEEPSEEK_BASE_URL=https://... # optional ``` -`DEEPSEEK_BASE_URL` 可选,默认为公开 API。绝不要提交真实凭证。未设置 `DEEPSEEK_API_KEY` 时,真实 API 的 e2e 套件会自动跳过。 +`DEEPSEEK_BASE_URL` 可选,默认为公开 API。请勿提交真实凭证。未设置 `DEEPSEEK_API_KEY` 时,真实 API 的 e2e 套件会自动跳过。 ## Git 钩子 lefthook 在 `lefthook.yml` 中配置,作为评审前的本地早期检查点: -- `pre-commit` 运行对暂存文件的 ESLint 修复、`pnpm run typecheck` 和 vendor manifest 守卫。 +- `pre-commit` 运行对暂存文件的 ESLint 修复、`pnpm run typecheck` 和 vendor manifest 守卫; - `pre-push` 运行 `pnpm run test`、`pnpm run test:snapshot`、`pnpm run hygiene`、`pnpm run doc-sync` 和 `pnpm run verify-module-graph`。 -vendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。编辑 vendor 代码前先看 `vendor/README.md`。 +vendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。 -这些钩子并不与 CI 完全一致。特别是:`pre-push` 跑不带覆盖率的单元测试,而 CI 跑 `pnpm run test:coverage`;CI 还会跑 echo-agent 和 built-bin 冒烟测试,并在 Node 24 和 26 上跑矩阵。 +这些钩子并不与 CI 完全一致。特别是:`pre-push` 运行不带覆盖率的单元测试,而 CI 运行 `pnpm run test:coverage`;CI 还会运行 echo-agent 和 built-bin 冒烟测试,并在 Node 24 和 26 上运行矩阵。 ## CI 门禁 -GitHub 工作流在每个 pull request 上运行这些门禁: +GitHub 工作流在每个 Pull Request 上运行以下门禁: - `pnpm install --frozen-lockfile` - `pnpm run constraints` @@ -82,7 +82,7 @@ GitHub 工作流在每个 pull request 上运行这些门禁: - 一个 echo-agent 冒烟测试,检查演示的工具调用、工具结果和 JSONL 输出 - built-bin 冒烟测试,用纯 `node` 运行发布产物 `lib/bin.js` 入口 -`pnpm run hygiene` 是 `pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types` 的本地简写;CI 还会把 `pnpm run constraints` 作为更早的快速失败步骤单独跑一次,然后在 `pnpm run build` 之后跑完整的 hygiene 脚本。 +`pnpm run hygiene` 是 `pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types` 的本地简写;CI 还会将 `pnpm run constraints` 作为更早的快速失败步骤单独运行一次,然后在 `pnpm run build` 之后运行完整的 hygiene 脚本。 ## 日常命令 @@ -96,9 +96,13 @@ pnpm run typecheck # build package/vendor outputs, then typecheck examples, pnpm run lint # eslint . pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs -pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events-and-services.md from source -pnpm run verify-cordis-catalog # fail if the cordis events/services catalog is stale +pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events.md + services.md from source +pnpm run verify-cordis-catalog # fail if either cordis catalog is stale +pnpm run gen-doc-graphs # regenerate generated relationship docs from source and curated graph definitions +pnpm run verify-doc-graphs # fail if generated relationship docs are stale +pnpm run gen-rfc-index # regenerate the docs/rfc/README.md index tables from the RFC tree pnpm run verify-md-wrap # fail on hard-wrapped prose paragraphs in docs/README markdown +pnpm run verify-mermaid # fail if a ```mermaid diagram has invalid Mermaid syntax pnpm run verify-type-equiv # fail if a ```ts type-equiv doc block drifts from its source type pnpm run verify-doc-budgets # fail if a budgeted standing doc exceeds its word ceiling pnpm run doc-sync # all Markdown/doc gates; see the doc-sync script in package.json for the full list @@ -109,7 +113,7 @@ pnpm run verify-node-next-types # fail if built declarations are not NodeNext-c pnpm run hygiene # knip, publint, workspace constraints, and NodeNext declaration check ``` -改动 package 的公开行为时,在同一个变更里更新相关 README 或 JSDoc。`pnpm run doc-sync` 能抓住被检查的 TypeScript 片段、cordis 事件/服务目录漂移和硬折行的 markdown 段落,但更广泛的行文/API 同步仍需评审把关。 +修改 package 的公开行为时,请在同一个变更中更新相关 README 或 JSDoc。`pnpm run doc-sync` 能检测到被检查的 TypeScript 片段、生成文档的新鲜度、Markdown 换行/链接漂移、type-equiv、翻译配对、Mermaid 语法和文档预算,但更广泛的行文/API 同步仍需评审把关。 ## 演示 @@ -133,24 +137,24 @@ pnpm run demo:acp ## TODO 标记 -用三种注释标签之一标记代码中的已知问题,按紧急程度排序: +请使用以下三种注释标签之一标记代码中的已知问题,按紧急程度排序: -- `FIXME`——应当阻塞新版本发布的问题。除非评审者明确同意可以照常合入,发布不应带着未解决的 `FIXME` 出门。 -- `TODO`——应当尽快修复的问题,等资源到位就处理。 -- `XXX`——也许某天会修的问题;优先级最低,不作承诺。 +- `FIXME`:应当阻塞新版本发布的问题。除非评审者明确同意该更改可以合并,否则发布版本不应包含未解决的 `FIXME`; +- `TODO`:应当尽快修复的问题,等资源到位即可处理; +- `XXX`:也许某天会修复的问题,优先级最低,不作承诺。 -选择与紧急程度匹配的标签,让扫代码的人一眼分清「发布阻塞」和「有空再说」。 +请选择与紧急程度匹配的标签,让浏览代码的人一眼分清「发布阻塞」和「有空再说」。 ## 逐字记录类型(`ts type-equiv`) -[核心数据结构](core-data-structures/core.md)文档粘贴真实的类型定义,让读者看到确切的形状。为防止粘贴内容在源码变化时漂移,把它围栏成 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号: +[核心数据结构](core-data-structures/core.md)文档粘贴真实的类型定义,让读者看到确切的形状。为防止粘贴内容在源码变化时漂移,请将其围栏为 ` ```ts type-equiv `(而不是 ` ```ts `),并在 `scripts/type-equiv.manifest.json` 中登记它镜像的源文件和符号: ```json { "doc": "docs/core-data-structures/session.md", "symbol": "SessionEvent", "source": "packages/core/session/src/types.ts" } ``` -`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明,并断言文档块与之一致(对空白和注释不敏感,因此文档块可以展示干净的定义,语义由行文承载)。它还强制 1:1 对应:每个 `ts type-equiv` 块恰好有一条 manifest 条目,反之亦然,因此不会有块被静默漏检,也不会有陈旧条目滞留。`doc-typecheck` 跳过 `ts type-equiv` 块(它们不能独立编译),并将其排除在 opt-out 比例之外。当你改动一个被记录的类型,门禁会失败直到你更新粘贴内容;当你增删一个块,在同一个变更里更新 manifest。 +`pnpm run verify-type-equiv`(`doc-sync` 的一环)随后通过 TypeScript 解析器从源码提取该符号的声明,并断言文档块与之一致(对空白和注释不敏感,因此文档块可以展示干净的定义,语义由行文承载)。它还强制 1:1 对应:每个 `ts type-equiv` 块恰好有一条 manifest 条目,反之亦然;因此不会有块被静默漏检,也不会有陈旧条目滞留。`doc-typecheck` 跳过 `ts type-equiv` 块(它们不能独立编译),并将其排除在 opt-out 比例之外。当你改动一个被记录的类型时,门禁会失败直到你更新粘贴内容;当你增删一个块时,请在同一个变更里更新 manifest。 ## 架构上下文 -改动 `packages/` 下的任何东西之前先读 `docs/architecture.md`。这套代码围绕 Cordis 插件、事件溯源的会话、类型化的服务 seam(扩展点)与显式扩展点构建。 +在修改 `packages/` 目录下的任何内容之前,请先阅读 `docs/architecture.md`。这套代码围绕 Cordis 插件、事件溯源的会话、类型化的服务 seam 与显式扩展点构建。 diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index b72d810afc..6376b3964f 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -54,7 +54,7 @@ > Blob hashes, not commit hashes, so the record is computable for files edited in the same PR (`git hash-object foo.md`) and consistency is a pure content comparison. The recorded hash also recovers the exact last-confirmed text of either side (`git cat-file -p `), so an out-of-sync pair is updated by diffing the edited side against its last-confirmed state and patching the counterpart minimally — never by re-translating whole files. -系统采用文件 blob 哈希而非提交哈希记录状态。同一 PR 内修改文件时,可通过 `git hash-object foo.md` 直接算出对应哈希,仅对比文件内容即可判断双语文档是否同步。通过记录的哈希值,可使用 `git cat-file -p ` 还原上次确认对齐时两侧的原文。当双语文档不一致时,只需对比修改版本与上次确认版本的差异,最小幅度同步修改另一侧译文,无需全文重新翻译。 +系统采用文件 blob hash 而非 commit hash 记录状态。同一 PR 内修改文件时,可通过 `git hash-object foo.md` 直接算出对应 blob hash,仅对比文件内容即可判断双语文档是否同步。通过记录的 blob hash,可使用 `git cat-file -p ` 还原上次确认对齐时两侧的原文。当双语文档不一致时,只需对比修改版本与上次确认版本的差异,最小幅度同步修改另一侧译文,无需全文重新翻译。 ## ⑤ 政策声明 diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md index 408048a66f..ae6f24acdb 100644 --- a/docs/i18n/terminology.md +++ b/docs/i18n/terminology.md @@ -1,148 +1,165 @@ # Terminology -本表约定本仓库的中英术语统一译法。各列语义: +本表约定本仓库的中英术语统一译法。 -- **中文** — 正文中的固定译法;写「保留英文」表示不译。 -- **首次出现** — 术语在一篇文档中第一次出现时的完整写法(含括注);之后只写「中文」列的形式。组合词已括注过的成分,单独出现时不再括注。 -- **不要译作** — 禁用译法,逐项列出;出现任何一项即评审阻断。 -- **备注** — 语境区分、裁定出处等自由说明;不承载可执行规则。 +**通用规则:** +- "中文"列为中文译文的正文默认用词。若该列为英文,则中文译文的正文中保留英文不翻译。 +- 首次出现按"首次出现"列书写(带括号注释);后续出现只写括号前的部分(可能为中文,也可能为英文),不出现括号内的注释。 +- "不要译作"列为严格禁止的译法。 +- 如果某术语已经作为另一个术语的组成部分被括注过(如 `agent loop(智能体循环)` 中已包含 `agent` 的括注),则该术语后续单独出现时无需再次括注。 + +## 缩写类(中英文文本中均使用缩写) | English | 中文 | 首次出现 | 不要译作 | 备注 | |---|---|---|---|---| -| ACP | ACP | ACP(Agent Client Protocol) | | | -| AI | AI | 人工智能(AI) | | | -| API | API | | | real-API 作定语时可译「真实接口」(如 real-API tests → 真实接口测试) | -| CI | CI | | | | -| CLI | CLI | 命令行界面(CLI) | | | -| Cordis | Cordis | | | 保留英文 | -| Function Calling | Function Calling | Function Calling(函数调用) | | | -| HMR | HMR | 热模块替换(HMR) | | | -| JSON Schema | JSON Schema | | | | -| JSONL | JSONL | | | | -| lint | lint | | | | -| loader | loader | | | | -| LLM | LLM | 大语言模型(LLM) | | | -| MCP | MCP | | | | -| PR | PR | PR(pull request) | | | -| RAG | RAG | 检索增强生成(RAG) | | | -| SDK | SDK | | | | -| SSE | SSE | SSE(Server-Sent Events) | | | -| agent | agent | agent(智能体) | | 括注只落在 agent 单独首现处;组合词见下行 | -| agent harness, agent workflow, agent loop, agent skill | 保留英文 | | | agent 组合词整体保留英文、不括注;其中的 workflow/loop/skill 不单独拆译;未列出的 agent 组合词同此处理 | -| backlog | backlog | | | 双语翻译语境指待翻清单 | -| blob hash | blob 哈希 | | | git 对象哈希;`git hash-object` 的结果 | -| commit hash | 提交哈希 | | | | -| hash | 哈希 | | | 代码与命令中保留英文(如 `git hash-object`、`` 占位符) | -| doc-sync | doc-sync | | | 仓库门禁名,保留英文 | -| e2e | e2e | | | | -| fiber | fiber | fiber(插件运行时) | | | -| fixture | fixture | | | 指测试前置数据或环境 | -| fork | fork | | | 保留英文 | -| harness | harness | | | 保留英文 | -| manifest | manifest | | | 描述模块或工具元数据的文件 | -| monorepo | monorepo | | | | -| package | package | | | 保留英文;指 npm 包(`@deepseek-ai/dsh-*`) | -| schema DSL | schema DSL | | | | -| schema | schema | | | 保留英文 | -| seam | seam | seam(扩展点) | | 同句已出现「扩展点」(extension point)时不加括注,直接写 seam,避免一词两指 | -| skill | skill | skill(技能) | | | -| spawn | spawn | | | 保留英文 | -| steering | steering | steering(中途引导) | | | -| subagent | subagent | subagent(子 agent) | | | -| transcript | transcript | transcript(文本记录) | | 指会话渲染给用户或编辑器的完整文本,区别于事件日志(event log) | -| waterfall | waterfall | waterfall(瀑布式事件) | | | -| worktree | worktree | | | git 工作区概念,保留英文 | -| wire format | 协议格式 | 协议格式(wire format) | | | -| adapter contract | 适配器契约 | 适配器契约(adapter contract) | | | -| adapter | 适配器 | | | | -| append-only | 仅追加 | | | | -| artifact | 产物 | | | | -| block | 块 | | | | -| background task | 后台任务 | | | | -| backend | 后端 | | | | -| capability | 能力 | | | | -| cancel | 取消 | | | | -| checkpoint | 检查点 | | | | -| chunk | 分片 | | | | -| compaction | compaction | compaction(上下文压缩) | | 正文优先保留英文 | -| consumer | 消费方 | | | | -| counterpart | 对侧文件 | | 对应物、配对物 | 双语配对语境;泛指“另一侧”时可写「另一侧」 | -| content block | 内容块 | | | | -| config | 配置 | | | | -| context | 上下文 | | | | -| context compaction | 上下文压缩 | 上下文压缩(context compaction) | | | -| contract | 契约 | | | 如:配对契约(pairing contract);另见 adapter contract | -| coverage | 覆盖率 | | | | -| crash recovery | 崩溃恢复 | | | | -| dispose | dispose | dispose(释放资源) | | 正文优先保留英文 | -| durability | 持久性 | | | | -| enforcement frontier | 强制边界 | | | i18n 机制词:manifest `required` 清单所划的门禁生效范围 | -| event log | 事件日志 | | | | -| event | 事件 | | | | -| event stream | 事件流 | | | | -| event-sourced | 事件溯源 | | | DDD 社区通行译法 | -| executor | 执行器 | | | | -| extension | 扩展 | | | | -| fail-fast | 快速失败 | | | | -| fenced code block | 围栏代码块 | | | MDN 中文同译 | -| finish reason | 结束原因 | | | | -| fingerprint | 指纹 | | | i18n 机制词:`.zh.md` 首行记录英文源 blob hash 的 `i18n-source` 注释 | -| foreground run | 前台运行 | | | | -| freshness | 新鲜度 | | | MDN HTTP 缓存中文同译(freshness lifetime → 新鲜度生命周期);指译文相对英文源的同步状态 | -| hook | 钩子 | | | | -| implementation | 实现 | | | | -| inference | 推理(inference) | | | 每次提及时保留英文括注,避免与 reasoning 混淆 | -| info string | 信息字符串 | | | CommonMark 中文同译;代码围栏 ``` 之后的语言标注 | -| injection | 注入 | | | | -| interface | 接口 | | | | -| integration | 集成 | | | | -| language switcher | 语言切换行 | | | i18n 机制词:双语配对文件顶部的互链行 | -| memory | memory / 记忆 / 内存 | | | 按上下文区分:agent memory 译为“记忆”;resource/memory usage 译为“内存” | -| message | 消息 | | | | -| mock | mock | | | 保留英文;指测试替身 | -| mod | 模组 | | | 区别于 module(模块);plugin 译作「插件」 | -| model provider | 模型提供方 | | | | -| module | 模块 | | | | -| orphan | 孤立 | | 孤儿 | git 官方中文同译(如「孤立分支」);指英文源已不存在的 `.zh.md`;进程语境按 OS 惯用语译「孤儿进程」 | -| pairing | 配对 | | | | -| permission | 权限 | | | | -| persistence | 持久化 | | | | -| pipeline | 流水线 | | | | -| plugin | 插件 | | | mod 对应“模组” | -| prompt | 提示词 | | | | -| provider | 提供方 | | | | -| provider-neutral | 提供方无关 | | | | -| quality gate | 质量门禁 | | | | -| registry | 注册表 | | | | -| reasoning | 推理(reasoning) | | | 需要和 inference 区分时保留英文括注;`reasoning_content` 译为“思考内容” | -| replay | 回放 | | | | -| resume | 恢复 | | | | -| runtime | 运行时 | | | | -| sandbox | 沙箱 | | | | -| service | 服务 | | | | -| session | 会话 | | | | -| session event | 会话事件 | | | | -| sidecar record | 伴随记录 | | 旁挂记录 | 指 `.i18n.yaml` 这类随主文件存放的记录文件 | -| smoke test | 冒烟测试 | | | | -| snapshot | 快照 | | | | -| source of truth | 真源 | 真源(source of truth) | | 评审裁定译法;如后续裁定调整,改此表即可 | -| spine | 主干 | | | | -| staged | 暂存 | | | git 官方中文同译 | -| stale | 陈旧 | | | MDN HTTP 缓存中文同译,与「新鲜(fresh)」成对;门禁输出保留英文 `stale`;expired 才译「过期」 | -| step | 步骤 | | | | -| stream | 流 | | | | -| streaming | 流式输出 | | | | -| structural signature | 结构签名 | | | i18n 机制词:配对门禁比对的有序结构序列 | -| system prompt | 系统提示词 | | | | -| taxonomy | 分类体系 | | | | -| token usage | token 用量 | | | | -| thinking | thinking | | | API 字段保留;模型模式译为“思考” | -| tool | 工具 | | | | -| tool call | 工具调用 | | | | -| tool result | 工具结果 | | | | -| tool schema | 工具 schema | | | | -| toolkit | 工具包 | | | | -| turn | 轮次 | | | | -| typecheck | 类型检查 | | | | -| vocabulary | 词汇 | | | | -| workflow | 工作流 | | | | +| ACP | ACP | ACP(Agent Client Protocol) | | | +| AI | AI | AI(人工智能) | | | +| API | API | | | | +| CI | CI | | | | +| CLI | CLI | CLI(命令行界面) | | | +| e2e | e2e | | | | +| HMR | HMR | HMR(热模块替换) | | | +| JSON Schema | JSON Schema | | | | +| JSONL | JSONL | | | | +| LLM | LLM | LLM(大语言模型) | | | +| MCP | MCP | | | | +| PR | PR | PR(Pull Request) | | | +| RAG | RAG | RAG(检索增强生成) | | | +| SDK | SDK | | | | +| SSE | SSE | SSE(Server-Sent Events) | | | + +## 英文类(中英文文本中均使用英文) + +| English | 中文 | 首次出现 | 不要译作 | 备注 | +|---|---|---|---|---| +| agent | agent | agent(智能体) | | | +| agent harness | agent harness | | | agent 组合词(agent harness/workflow/loop/skill 等)整体保留英文;未括注过 agent 时首现按 agent 行处理 | +| agent loop | agent loop | agent loop(智能体循环) | | | +| backlog | backlog | backlog(待翻清单) | | 仅在双语翻译语境里括注`待翻清单` | +| blob hash | blob hash | | | `git hash-object` 的结果 | +| Cordis | Cordis | | | | +| dispose | dispose | dispose(资源释放) | | | +| doc-sync | doc-sync | doc-sync(文档同步门禁) | | | +| fiber | fiber | fiber(插件运行时) | | | +| fixture | fixture | fixture(测试前置数据) | | | +| fork | fork | | | | +| Function Calling | Function Calling | Function Calling(函数调用) | | | +| harness | harness | | | | +| harness engineering | harness engineering | | | | +| lint | lint | | | | +| mock | mock | | | 保留英文;指测试替身 | +| loader | loader | | | | +| manifest | manifest | manifest(元数据清单) | | | +| monorepo | monorepo | | | | +| package | package | | | 保留英文;指 npm 包(`@deepseek-ai/dsh-*`) | +| schema | schema | | | | +| schema DSL | schema DSL | | | | +| seam | seam | | | 与 `extension point` 是不同概念;根据具体语境,可译为`服务边界`或`可替换点` | +| skill | skill | skill(技能) | | | +| spawn | spawn | | | | +| steering | steering | steering(中途引导) | | | +| subagent | subagent | | | | +| thinking | thinking | | | API 字段保留英文;描述模型模式时译为`思考` | +| transcript | transcript | transcript(文本记录) | | 指会话渲染给用户或编辑器的完整文本,区别于事件日志 | +| waterfall | waterfall | waterfall(瀑布式事件) | | | +| worktree | worktree | | | git 工作区概念 | + +## 双语类(中英文文本各自使用中英文) + +| English | 中文 | 首次出现 | 不要译作 | 备注 | +|---|---|---|---|---| +| adapter | 适配器 | | | | +| adapter contract | 适配器契约 | 适配器契约(adapter contract) | | | +| append-only | 仅追加 | | | | +| artifact | 产物 | | | | +| backend | 后端 | | | | +| background task | 后台任务 | | | | +| block | 块 | | | | +| cancel | 取消 | | | | +| capability | 能力 | | | | +| checkpoint | 检查点 | | | | +| chunk | 分片 | | | | +| compaction | 压缩 | 压缩(compaction) | | | +| config | 配置 | | | | +| consumer | 消费方 | | | | +| content block | 内容块 | | | | +| context | 上下文 | | | | +| counterpart | 对侧文件 | | 对应物、配对物 | 双语配对语境;泛指"另一侧"时可写「另一侧」 | +| context compaction | 上下文压缩 | 上下文压缩(context compaction) | | | +| contract | 契约 | | | 如:`pairing contract` →`配对契约` | +| coverage | 覆盖率 | | | | +| crash recovery | 崩溃恢复 | | | | +| durability | 持久性 | | | | +| enforcement frontier | 强制边界 | | | i18n 配对机制用语:manifest `required` 清单所划的门禁生效范围 | +| event | 事件 | | | | +| event log | 事件日志 | | | | +| event stream | 事件流 | | | | +| event-sourced | 事件溯源 | | | 沿用 DDD 社区通行译法 | +| executor | 执行器 | | | | +| extension | 扩展 | | | | +| extension point | 扩展点 | | | 注意与 `seam` 区分 | +| fail-fast | 快速失败 | | | | +| fenced code block | 围栏代码块 | | | 沿用 MDN 中文翻译 | +| fingerprint | 指纹 | | | i18n 配对机制用语:`.zh.md` 首行记录英文源 blob hash 的注释 | +| finish reason | 结束原因 | | | | +| foreground run | 前台运行 | | | | +| freshness | 新鲜度 | | | 沿用 MDN 中文翻译;在本项目中指译文相对源文的同步状态 | +| hook | 钩子 | | | | +| implementation | 实现 | | | | +| inference | 推理 | 推理(inference) | | 需要和 `reasoning` 区分时保留英文括注 | +| info string | 信息字符串 | | | 沿用 CommonMark 中文翻译;指代码围栏 ``` 之后的语言标注 | +| injection | 注入 | | | | +| integration | 集成 | | | | +| interface | 接口 | | | | +| language switcher | 语言切换行 | | | i18n 配对机制用语:双语配对文件顶部的互链行 | +| memory | 记忆 / 内存 | | | 与 `agent` 搭配时译为`记忆`(如 `agent memory` →`智能体记忆`);指系统资源时译为`内存` | +| merge | 合并 | | | | +| message | 消息 | | | | +| mod | 模组 | | | | +| model provider | 模型提供方 | | | | +| module | 模块 | | | | +| orphan | 孤立 | | 孤儿 | 指英文源已不存在的 `.zh.md` | +| orphan branch | 孤立分支 | | 孤儿分支 | 沿用 git 官方中文翻译 | +| pairing | 配对 | | | | +| permission | 权限 | | | | +| persistence | 持久化 | | | | +| pipeline | 流水线 | | | | +| plugin | 插件 | | | | +| prompt | 提示词 | | | | +| provider | 提供方 | | | | +| provider-neutral | 提供方无关 | | | | +| quality gate | 质量门禁 | | | | +| reasoning | 推理 | 推理(reasoning) | | 需要和 `inference` 区分时保留英文括注 | +| reasoning_content | 思考内容 | | | | +| registry | 注册表 | | | | +| replay | 回放 | | | | +| resume | 恢复 | | | | +| runtime | 运行时 | | | | +| sandbox | 沙箱 | | | | +| service | 服务 | | | | +| session | 会话 | | | | +| session event | 会话事件 | | | | +| sidecar record | 伴随记录 | | 旁挂记录 | 指与文档同目录的伴随记录文件 | +| smoke test | 冒烟测试 | | | | +| snapshot | 快照 | | | | +| source of truth | 真源 | | | | +| spine | 主干 | | | | +| staged | 暂存 | | | 沿用 git 官方中文翻译 | +| stale | 陈旧 | | 过期 | 与 `fresh`(`新鲜`)成对;门禁输出中保留英文 `stale` 不翻译;`expired` 才译为`过期` | +| step | 步骤 | | | | +| stream | 流 | | | | +| streaming | 流式输出 | | | | +| structural signature | 结构签名 | | | i18n 配对机制用语:门禁比对两侧文件时提取的有序结构序列(标题层级、代码块、列表等) | +| system prompt | 系统提示词 | | | | +| taxonomy | 分类体系 | | | | +| token usage | token 用量 | | | | +| tool | 工具 | | | | +| tool call | 工具调用 | | | | +| tool result | 工具结果 | | | | +| tool schema | 工具 schema | | | | +| toolkit | 工具包 | | | | +| turn | 轮次 | | | | +| typecheck | 类型检查 | | | | +| vocabulary | 词汇 | | | | +| wire format | 协议格式 | 协议格式(wire format) | | | +| workflow | 工作流 | | | | diff --git a/docs/i18n/translation-prompt.md b/docs/i18n/translation-prompt.md index 573f125878..db707730fa 100644 --- a/docs/i18n/translation-prompt.md +++ b/docs/i18n/translation-prompt.md @@ -14,6 +14,18 @@ 历史模板的 `{{to}}`、`{{title_prompt}}`、`{{summary_prompt}}`、`{{terms_prompt}}`、`{{imt_style_guide}}` 占位符与 `%%` 分段协议已废弃:本模板按整文档翻译(非分段),输出协议为下方三段 XML。 +## Few-shot 金标 + +流水线的 few-shot 是**整文档级**的中英对照,不是模板内嵌的句子级正误例(那是最小抽样)。few-shot 集取自以下 5 组人工定稿的配对文档,以仓库当前版本为准、随仓库更新: + +- `README.md` ↔ `README.zh.md` +- `docs/development.md` ↔ `docs/development.zh.md` +- `docs/i18n/README.md` ↔ `docs/i18n/README.zh.md` +- `docs/i18n/translation-rules.md` ↔ `docs/i18n/translation-rules.zh.md` +- `docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.md` ↔ 对应 `.zh.md` + +注入方式:在系统消息(本模板)之后、待译文档之前,每组作为一轮示例对话——user 消息为源文档全文,assistant 消息为定稿译文全文(不带三段 XML 包装;只有真实请求要求三段输出)。上下文紧张时按上列顺序从后往前裁剪组数。这 5 组也是评审校准锚点(见 [style-samples.md](style-samples.md)),改动任何一组即改变流水线行为。 + ## 模板正文 ````text diff --git a/docs/i18n/translation-rules.i18n.yaml b/docs/i18n/translation-rules.i18n.yaml index f1aa737d08..d340ff42b8 100644 --- a/docs/i18n/translation-rules.i18n.yaml +++ b/docs/i18n/translation-rules.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write translation-rules.md: 3e99aa5432ccd904f702238e9b802a9ed9bf6832 -translation-rules.zh.md: 78778907f959d62dad2c9b4c02baf1f664791a43 +translation-rules.zh.md: 0ff2ab43d59c6e0dd07e20e2f1a0a1c28c2f245c diff --git a/docs/i18n/translation-rules.zh.md b/docs/i18n/translation-rules.zh.md index 78778907f9..0ff2ab43d5 100644 --- a/docs/i18n/translation-rules.zh.md +++ b/docs/i18n/translation-rules.zh.md @@ -2,13 +2,13 @@ [English](translation-rules.md) | 中文 -本文规定如何在本仓库文档配对的两侧之间进行翻译。两种语言同权(见 [README.md](README.md)):一次变更用任一语言撰写,那一侧就是这次更新的源——本文的规则约束的是产出或更新另一侧。这些规则对人和 agent(智能体)同等生效;应用它们的进仓 agent 工作流是 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md)。规则级别沿用 RFC 2119 的用法:**必须(MUST)**/**禁止(MUST NOT)**会卡门禁或评审;**应当(SHOULD)**偏离时要说明理由;**可以(MAY)**自行裁量。 +本文规定:如何在本仓库文档配对的中英文两种语言之间进行翻译。两种语言同权(见 [README.md](README.md)):每次变更可以用任一语言撰写,被编辑的一侧即为本次更新的源;本文的规则约束如何产出或更新对侧文件。这些规则对人类和 agent(智能体)同等生效;应用这些规则的仓库内置 agent 工作流是 [.agents/skills/dsh-translate-docs](../../.agents/skills/dsh-translate-docs/SKILL.md)。规则级别沿用 RFC 2119 的用法:**必须(MUST)** / **禁止(MUST NOT)** 会卡门禁或评审;**应当(SHOULD)** 偏离时要说明理由;**可以(MAY)** 自行裁量。 ## 忠实性 -- 另一侧必须说撰写侧所说的话——不添加行为、前置条件、警告、版本声明或示例,也不丢弃任何一项。如果两侧在实质内容上不一致,没有哪种语言默认获胜:改正错的那一侧,并在同一个变更里把另一侧带上。 -- 另一侧应当读起来是其语言自然的技术文字,而不是逐词对照。翻译语义,在目标语言语法需要处重组句子,并保持原作者的语域——简练的保持简练。 -- 不要翻译不可译的东西:一句话如果依赖源语言的习语而无法自然转换,就翻译它的意思,而不是习语本身。 +- 对侧文件*必须*传达与撰写侧相同的内容:不添加行为、前置条件、警告、版本声明或示例,也不漏掉任何一项。如果两侧在实质内容上不一致,没有哪种语言默认获胜;请修正错误的一侧,并在同一个变更里同步更新另一侧。 +- 对侧文件读起来*应当*是其语言自然的技术文字,而非逐词对照的译文。请根据语义翻译,在目标语言语法需要时重组句子,并保持原作者的语域(比如:简练的保持简练)。 +- 不要翻译不可译的内容:如果一句话依赖源语言的习语、无法自然转换,请翻译它的意思,而非习语本身。 ## 行文 @@ -23,47 +23,47 @@ 形状由配对门禁强制,译者不需要用流畅度去换结构——在框架内自然行文即可。配对的两个文件必须在以下方面一一对应: -- 标题层级(相同级别、相同顺序——标题的**文字**要翻译), -- 列表形态与编号, -- 表格(相同的列、相同的行序;表头单元格按术语表翻译), -- 围栏代码块——**逐字节一致,包括注释**;代码属于受验证的范围(` ```ts ` 块要通过 `doc-typecheck` 编译),而被改动的注释是代码块计数门禁看不见的漂移, -- 行内代码(命令、flag、配置键、文件路径、事件名、API 名、版本号)——原样保留,从不翻译或重排, -- 链接与锚点:每个相对链接在两个文件中必须指向相同的目标——按约定是 `.md` 路径而非 `.zh.md` 兄弟文件——这样某对文档先于相邻文件落地时,链接也永不悬空。唯一的 zh 特有链接是语言切换行。链接**文字**翻译;链接目标不翻。 +- 标题层级(相同级别、相同顺序;标题的**文字**要翻译); +- 列表形态与编号; +- 表格(相同的列、相同的行序;表头单元格按术语表翻译); +- 围栏代码块:**逐字节一致,包括注释**。代码属于受验证的范围(` ```ts ` 块要通过 `doc-typecheck` 编译),而被改动的注释是代码块计数门禁看不见的漂移; +- 行内代码(命令、flag、配置键、文件路径、事件名、API 名、版本号):原样保留,从不翻译或重排; +- 链接与锚点:每个相对链接在两个文件中必须指向相同的目标(按约定是 `.md` 路径而非 `.zh.md` 兄弟文件),这样即使某对文档先于相邻文件落地,链接也不会悬空。唯一的 zh 特有链接是语言切换行。链接**文字**翻译;链接目标不翻。 本仓库的 Markdown 约定对 `.zh.md` 文件原样生效:一个段落一个物理行(`verify-md-wrap`)、相对链接必须可解析(`verify-md-links`)、文件末尾恰好一个换行。 ## 术语 -- [terminology.md](terminology.md) 是双向的术语真源。翻译前先加载它;翻译中,表内的每个术语都必须严格按表规定的译法呈现,包括首次出现的括注(如首现写 `agent(智能体)`,之后写 `agent`)与「不要译作」的禁项。中文先行撰写时,英文另一侧同样按表中英文列使用术语。 -- 表中**没有**的技术术语,只有当某个主要中文 OSS 或厂商文档已有成型译法时(K8s/Vue/MDN 中文文档、微软简中风格指南、大厂项目文档)才可以翻译。在 PR 中注明先例出处。 -- **没有**成型先例的术语,译文中必须保留英文,并且必须在 PR 描述的「待定术语」下列出、附上建议译法交评审者定夺。禁止就地发明中文译法——无先例的翻译恰恰制造了术语表要防止的歧义。定下来的术语随后在同一个 PR 或后续 PR 进入 [terminology.md](terminology.md)。 +- [terminology.md](terminology.md) 是双向的术语真源。翻译前请先加载它;翻译过程中,表内的每个术语都*必须*严格按表中规定的译法呈现,包括首次出现的括注(如首现写 `agent(智能体)`,之后写 `agent`)与「不要译作」的禁项。中文先行撰写时,英文对侧同样按表中英文列使用术语。 +- 表中**没有**的技术术语,只有当某个主要中文 OSS 或厂商文档已有成型译法时(K8s/Vue/MDN 中文文档、微软简中风格指南、大厂项目文档)才*可以*翻译。请在 PR 中注明先例出处。 +- **没有**成型先例的术语,译文中*必须*保留英文,并且*必须*在 PR 描述的「待定术语」下列出,附上建议译法交评审者定夺。*禁止*就地发明中文译法,因为无先例的翻译恰恰会制造术语表要防止的歧义。确定下来的术语随后在同一个 PR 或后续 PR 中进入 [terminology.md](terminology.md)。 ## 排版 -本节规则约束中文一侧;英文一侧遵循仓库常规的 Markdown 约定(根 `AGENTS.md`)。下面的中西文混排规则遵循 [MDN 简体中文翻译指南](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md)、[Kubernetes 中文本地化指南](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/)、[Vue.js 中文翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5)与[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)的跨项目共识,其根据是 [W3C clreq](https://www.w3.org/TR/clreq/) 与 GB/T 15834—2011: +本节规则约束中文一侧;英文一侧遵循仓库常规的 Markdown 约定(根 `AGENTS.md`)。以下中西文混排规则遵循 [MDN 简体中文翻译指南](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md)、[Kubernetes 中文本地化指南](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/)、[Vue.js 中文翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5) 与[中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)的跨项目共识,其根据是 [W3C clreq](https://www.w3.org/TR/clreq/) 与 GB/T 15834—2011: -- 必须在中文与拉丁词之间、中文与数字之间各留一个半角空格:`每个 plugin 注册 3 个 tool`。全角标点与任何字符之间不加空格。 -- 中文行文必须使用全角(中文)标点:`,。:;?!()「」`。半角标点保留在代码内、按原样引用的完整英文句子内、以及数字内(`3.5`、`1,024`)。 -- 并列顿开:中文的并列项之间用顿号(、),不用逗号。 -- 禁止使用全角数字或全角拉丁字母——永远不写 `123`,永远写 `123`。 -- 专有名词保持规范大小写:GitHub、TypeScript、DeepSeek——除非引用代码,否则绝不写 `github`/`Github`。 +- *必须*在中文与拉丁词之间、中文与数字之间各留一个半角空格:`每个 plugin 注册 3 个 tool`。全角标点与任何字符之间不加空格。 +- 中文行文*必须*使用全角(中文)标点:`,。:;?!()「」`。半角标点保留在代码内、按原样引用的完整英文句子内、以及数字内(`3.5`、`1,024`)。 +- 顿号:中文的并列项之间使用顿号(、),而非逗号。 +- *禁止*使用全角数字或全角拉丁字母:永远不写 `123`,永远写 `123`。 +- 专有名词保持规范大小写:GitHub、TypeScript、DeepSeek。除非引用代码,否则绝不写 `github`/`Github`。 - 第二人称用「你」,不用「您」(与 Vue、Kubernetes 中文约定及本仓库的直接语气一致)。 -- 强调标记(`**加粗**`、`*斜体*`)落在与另一侧相同的文字段上;中文没有斜体,渲染效果可能看不出差别——不要用引号或其他装饰替代。 +- 强调标记(`**加粗**`、`*斜体*`)落在与对侧相同的文字段上。中文没有斜体,渲染效果可能看不出差别,不要用引号或其他装饰替代。 -## 质量线 +## 质量标准 -- 一对文档的完成标准:一位双语工程师只读其中任一文件,得到与另一文件读者完全相同的信息——相同的事实、相同的告诫、相同的语气——并且没有任何多余的内容。 -- 交付前,对照本文自查一遍,并**只读另一侧**再通读一遍、不看源侧对照;没有源文锚着,别扭的表述更容易被听出来。 -- 机械契约(一致性记录、切换行、结构、折行、链接)由 `pnpm run verify-translation-pairing` 和 `doc-sync` 的其余门禁检查——跑门禁;门禁覆盖的不要手工核对。 +- 一对文档的完成标准:一位双语工程师只读其中任一文件,能获得与另一文件读者完全相同的信息(相同的事实、相同的告诫、相同的语气),并且没有任何多余的内容。 +- 交付前,请对照本文自查一遍,并**单独通读对侧文件**,不与源侧对照;不对照原文时,更容易察觉别扭的表达。 +- 机械契约(一致性记录、切换行、结构、折行、链接)由 `pnpm run verify-translation-pairing` 和 `doc-sync` 的其余门禁检查。请运行门禁;门禁已覆盖的内容无需手工核对。 ## 参考资料 本文各规则引用的权威出处,供想了解底层依据的人和 agent 查阅: -- [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines)——中西文混排空格与标点的社区事实标准。 -- [MDN 简体中文翻译指南](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md)——与本文同形态的进仓翻译规则文件;空格、标点与术语表实践。 -- [Kubernetes 中文本地化指南](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/)——最大的中文本地化团队的术语首现与标点实践。 -- [Vue.js docs-zh-cn 翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5)——逐术语的译/留决策与语气。 -- [zh-style-guide](https://zh-style-guide.readthedocs.io)——社区中文技术文档写作规范,本文借用了它的规则级别分类体系(与 RFC 2119 关键词分级);它聚合了 GB/T 15834/15835、clreq 与各厂商指南。 -- [W3C clreq](https://www.w3.org/TR/clreq/) 与[微软简体中文风格指南](https://learn.microsoft.com/en-us/globalization/reference/microsoft-style-guides)——排版学与厂商本地化的正式基线。 -- GB/T 19682-2005《翻译服务译文质量要求》——国家标准;本文「忠实性」与「术语」两节把它的三项基本要求(忠实原文、术语统一、行文通顺)落成可操作规则。 +- [中文文案排版指北](https://github.com/sparanoid/chinese-copywriting-guidelines):中西文混排空格与标点的社区事实标准。 +- [MDN 简体中文翻译指南](https://github.com/mdn/translated-content/blob/main/docs/zh-cn/translation-guide.md):与本文同形态的仓库内置翻译规则文件;空格、标点与术语表实践。 +- [Kubernetes 中文本地化指南](https://kubernetes.io/zh-cn/docs/contribute/localization_zh/):最大的中文本地化团队的术语首现与标点实践。 +- [Vue.js docs-zh-cn 翻译须知](https://github.com/vuejs-translations/docs-zh-cn/wiki/%E7%BF%BB%E8%AF%91%E9%A1%BB%E7%9F%A5):逐术语的译/留决策与语气。 +- [zh-style-guide](https://zh-style-guide.readthedocs.io):社区中文技术文档写作规范,本文借用了它的规则级别分类体系(与 RFC 2119 关键词分级);它聚合了 GB/T 15834/15835、clreq 与各厂商指南。 +- [W3C clreq](https://www.w3.org/TR/clreq/) 与[微软简体中文风格指南](https://learn.microsoft.com/en-us/globalization/reference/microsoft-style-guides):排版学与厂商本地化的正式基线。 +- GB/T 19682-2005《翻译服务译文质量要求》:国家标准;本文「忠实性」与「术语」两节将其三项基本要求(忠实原文、术语统一、行文通顺)落实为可操作的规则。 diff --git a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml index 1ef023ec50..f409941924 100644 --- a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml +++ b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write 2026-07-02-bilingual-docs-and-pairing-gate.md: 764ad5a9345c2a138b56e9cedb54848a5f7d6054 -2026-07-02-bilingual-docs-and-pairing-gate.zh.md: f039fbe3fa44cb379905bbea9a4692af19d71230 +2026-07-02-bilingual-docs-and-pairing-gate.zh.md: c752d76f12f556ce190bf80c4f3a531c0821be8e diff --git a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md index f039fbe3fa..c752d76f12 100644 --- a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md +++ b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.zh.md @@ -1,36 +1,38 @@ -# 通过配对兄弟文件与配对门禁实现双语文档 +# RFC:通过配对兄弟文件与配对门禁实现双语文档 + +Status: implemented [English](2026-07-02-bilingual-docs-and-pairing-gate.md) | 中文 -## 背景 +## 问题 -本仓库的 README 与 docs 目录树会被公司内外的人和 agent(智能体)以中英两种语言阅读。没有机制、纯靠手工维护第二语言,正是译文腐烂的方式:一侧继续演进,另一侧默默地说谎,而没有门禁会注意到。对这类不变式,本仓库一贯的答案是把它编码成机械检查(见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync 强制](2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。 +本仓库的 README 与 docs 目录树会被公司内外的人和 agent(智能体)以中英两种语言阅读。在没有机制的情况下纯靠手工维护第二语言,正是译文腐烂的根源:一侧持续演进,另一侧默默失实,而没有门禁会注意到。对于这类不变式,本仓库一贯的做法是将其编码为机械检查(见[质量门禁](2026-06-11-quality-gates.md)与 [doc-sync 强制](2026-06-11-doc-sync-enforcement.md)),因此双语政策随附一道门禁一起交付。 ## 决策 -- **配对兄弟文件,两种语言同权。**一对文档是三个兄弟文件:英文 `foo.md`、中文 `foo.zh.md`,加一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典——一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束这对文件的是两侧必须说同样的话,且配对整体合入(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../i18n/terminology.md)。 -- **旁挂记录两侧 blob hash,使一致性可检查。**`foo.i18n.yaml` 保存两侧文件在上一次确认一致状态下各自的完整 git blob hash。此后改了任一侧而没重新确认配对,都能被机械检测出来——纯内容比较、无需查询历史——而且同一个 PR 里改动的文件也能算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)产生一份可评审的 yaml diff:确认一致在 PR 里是一个显式、可见的动作。 -- **`verify-translation-pairing` 加入 `doc-sync`。**门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行:required 的配对存在;任何已存在的配对完整(三个文件齐全)且一致(两个 hash 都匹配、切换行双向互链、结构签名一致);被排除的文件(生成物或本身即双语的)保持不配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `required` 清单是一个棘轮:每个合入的翻译批次把自己的文件加进去,覆盖面只增不减。 -- **翻译是 agent 的工作,由人评审。**进仓的工作流是 [.agents/skills/dsh-translate-docs](../../../../.agents/skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../../.agents/skills/dsh-code-review/SKILL.md) 同一模式:skill 承载工作流,并把真源让给文档。 +- **配对兄弟文件,两种语言同权。** 一对文档由三个兄弟文件组成:英文 `foo.md`、中文 `foo.zh.md`,以及一份一致性记录 `foo.i18n.yaml`。没有哪种语言是正典:一篇文档可以先用中文撰写和评审、之后再译成英文,反之亦可;约束配对的是:两侧必须表达相同的内容,且配对整体合并(两种语言加记录,绝不单独落一侧)。政策见 [docs/i18n/README.md](../../../i18n/README.md);翻译规则见 [docs/i18n/translation-rules.md](../../../i18n/translation-rules.md);术语真源见 [docs/i18n/terminology.md](../../../i18n/terminology.md)。 +- **伴随记录保存两侧 blob hash,使一致性可检查。** `foo.i18n.yaml` 保存两侧文件在上一次确认一致时各自的完整 git blob hash。此后修改了任一侧而未重新确认配对,都能被机械检测出来(纯内容比较,无需查询历史),而且同一个 PR 内改动的文件也能计算出 hash,commit hash 式的记录做不到这一点。重新记录(`verify-translation-pairing --write`)会产生一份可评审的 yaml diff:确认一致在 PR 中是一个显式、可见的动作。 +- **`verify-translation-pairing` 加入 `doc-sync`。** 门禁([scripts/verify-translation-pairing.ts](../../../../scripts/verify-translation-pairing.ts))强制执行以下规则:required 的配对必须存在;任何已存在的配对必须完整(三个文件齐全)且一致(两个 hash 匹配、切换行双向互链、结构签名一致);被排除的文件(生成物或本身即双语的)保持不配对。[scripts/translation-pairing.manifest.json](../../../../scripts/translation-pairing.manifest.json) 中的 `required` 清单只进不退:每个合并的翻译批次将自己的文件加入其中,覆盖面只增不减。 +- **翻译是 agent 的工作,由人评审。** 仓库内置的工作流是 [.agents/skills/dsh-translate-docs](../../../../.agents/skills/dsh-translate-docs/SKILL.md),与 [dsh-code-review](../../../../.agents/skills/dsh-code-review/SKILL.md) 模式相同:skill 承载工作流,并将文档作为真源。 ## 曾考虑的替代方案 -- **英文为正典源、指纹放在译文内**——本 RFC 最初提出的设计:`.zh.md` 文件携带一条 HTML 注释记录英文源的 blob hash,翻译只沿 EN → ZH 单向流动。评审中修订:团队需要中文先行的撰写方式(先写、先审中文 RFC,再译英文),两种语言同权,而单向正典模型无法表达这一点。覆盖**两侧**的旁挂记录取代了文件内的单向指纹;blob hash 的机制原样保留。 -- **语言目录(`docs/en/` + `docs/zh/`,Kubernetes/ECharts 模式)**——否决:本仓库没有把 locale 映射到路由的文档站框架,挪动每个英文文件会搅动所有既有交叉引用,且 `verify-md-links`/`verify-doc-refs` 将需要路径映射逻辑而不是原样工作。 -- **独立翻译仓库(PingCAP `docs`/`docs-cn` 模式)**——否决:适合有独立发布节奏的文档产品,对 monorepo 自己的文档而言过重;还会把译文置于本仓库门禁够不到的地方。 -- **中英混排单文件(一个文件、两种语言)**——否决:每个 diff 都翻倍,破坏一段一行约定的 diff 工效,且局部不一致不可见。 -- **Commit hash 式记录(MDN `l10n.sourceCommit` 模式)**——否决,改用 blob hash:同一个 PR 内的改动还没有 commit hash,MDN 模式无法表达「与本 PR 引入的状态一致」,且校验它需要 git 历史而非文件内容。 -- **比较配对两侧的 git 时间戳(无记录)**——否决:纯格式化的改动会误报,一次无关改动之后提交的另一侧会漏报;只有内容同一性这个信号与门禁的承诺名实相符。 +- **英文为正典源、指纹放在译文内**:本 RFC 最初提出的设计:`.zh.md` 文件携带一条 HTML 注释记录英文源的 blob hash,翻译只沿 EN → ZH 单向流动。评审中修订:团队需要中文先行的撰写方式(先写、先审中文 RFC,再译英文),两种语言同权,而单向正典模型无法表达这一点。覆盖**两侧**的伴随记录取代了文件内的单向指纹;blob hash 的机制本身保持不变。 +- **语言目录(`docs/en/` + `docs/zh/`,Kubernetes/ECharts 模式)**:否决。本仓库没有将 locale 映射到路由的文档站框架;如果移动所有英文文件,所有既有交叉引用都要随之修改;且 `verify-md-links`/`verify-doc-refs` 将需要路径映射逻辑,而非原样工作。 +- **独立翻译仓库(PingCAP `docs`/`docs-cn` 模式)**:否决。适合有独立发布节奏的文档产品,对 monorepo 自身的文档而言过重;还会把译文置于本仓库门禁触及不到的地方。 +- **中英混排单文件(一个文件、两种语言)**:否决。每个 diff 都翻倍,破坏一段一行约定的 diff 易读性,且局部不一致不可见。 +- **Commit hash 式记录(MDN `l10n.sourceCommit` 模式)**:否决,改用 blob hash。同一个 PR 内的改动还没有 commit hash,MDN 模式无法表达「与本 PR 引入的状态一致」,且校验它需要 git 历史而非文件内容。 +- **比较配对两侧的 git 时间戳(无记录)**:否决。纯格式化的改动会误报,一次无关改动之后提交的对侧文件会漏报;只有内容同一性这个信号才与门禁的承诺名实相符。 ## 业界先例 -带语言后缀的配对兄弟文件是中国大厂的主流约定(ant-design 的 `index.zh-CN.md`/`index.en-US.md`;arco-design 的 `README.zh-CN.md` 加顶部切换行;Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`)——但这些仓库都没有在 CI 里**强制**配对或一致性;约定纯靠评审维系。一致性自动化存在于中国之外:MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action(监视上游 commit、为陈旧译文自动开 issue/PR)、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator(CI 中由源 hash 驱动的 LLM 重译)。本设计把两者结合:中文生态的文件布局,加 hash 对门禁,再加一个进仓 agent skill(技能)替代 bot 服务。 +带语言后缀的配对兄弟文件是中国大厂的主流约定(ant-design 的 `index.zh-CN.md`/`index.en-US.md`;arco-design 的 `README.zh-CN.md` 加顶部切换行;Apache ShardingSphere 的 387 对 `.cn.md`/`.en.md`),但这些仓库都没有在 CI 中**强制**配对或一致性检查;约定纯靠评审维系。一致性自动化存在于中国以外:MDN 的 `l10n.sourceCommit` front-matter 指纹、Vue 的 Ryu-Cho action(监视上游 commit,为陈旧译文自动开 issue/PR)、Kubernetes 的本地化漂移脚本、微软 Azure co-op-translator(CI 中由源 hash 驱动的 LLM 重译)。本设计将两者结合:中文生态的文件布局,加上 hash 配对门禁,再加一个仓库内置的 agent skill 替代 bot 服务。 ## 后果 -- 修改已配对文档的任一侧,同一个 PR 就有义务更新另一侧并重新记录配对——门禁把 doc-sync 规则双语化,不变式由 CI(而非评审者的记忆)承载。 -- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对一致」可以从 yaml 的 git blame 直接回答。 -- 两侧说法冲突时,没有机械规则裁决谁赢——由 PR 评审裁决。这是同权的代价,是有意接受的:另一个选项(正典语言)禁止中文先行撰写。 -- 生成文档(`cordis-catalog/`、`tool-catalog/`、`module-graph.md`)暂被排除;计划中的后续工作是让它们的生成器在输出英文的同时输出中文,届时移出排除清单。 -- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(`--list`),不是红的 CI,因此配对按可评审的批次落地,无需一个巨型 PR。新文档是例外——文件名日期在 manifest `requiredSince` 当天或之后的文档,要么连同配对一起合入,要么不合入,因此 backlog 只会缩小。 -- 记录的 hash 兼作更新工具(`git cat-file -p ` 能还原任一侧上次确认的文本,用于基于 diff 的最小更新),所以这套机制从不强迫整篇重译。 +- 修改已配对文档的任一侧,同一个 PR 就有义务更新对侧并重新记录配对。门禁将 doc-sync 规则双语化,不变式由 CI(而非评审者的记忆)承载。 +- 每个配对给目录树多添一个文件。记录由机器写入(`--write`),代价是目录噪音而非维护负担;换来的是「谁在何时确认过这对文档一致」可以从 yaml 的 git blame 直接回答。 +- 两侧说法冲突时,没有机械规则裁决谁赢,由 PR 评审裁决。这是同权的代价,且是有意接受的:另一个选项(正典语言)会禁止中文先行撰写。 +- 生成文档(`cordis-catalog/`、`tool-catalog/`、`module-graph.md`)暂被排除;计划中的后续工作是让生成器在输出英文的同时输出中文,届时将这些文件移出排除清单。 +- 推进天然是渐进的:`required` 之外的文档是可见的 backlog(`--list`),而非红色的 CI;因此配对按可评审的批次落地,无需一个巨型 PR。 +- 记录的 hash 兼作更新工具(`git cat-file -p ` 能还原任一侧上次确认的文本,用于基于 diff 的最小更新),因此这套机制从不强迫整篇重译。 From 1583cb47a088834fd5a698665cde24773575bc4c Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:14:47 -0700 Subject: [PATCH 20/38] docs(i18n): reconcile gold pairs with master's EN-side drift MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 金标译文写于 0710 的 master,英文侧此后有漂移,按最小更新补齐: development.zh 的 Node 引擎下限句、CI 门禁节改写为八任务车道描述、 代码块与英文侧逐字节同步;README.zh 恢复 graph-atlas 链接; style-samples 的目录链接随 events-and-services.md 拆分改指 events.md。 requiredSince 推至 2026-07-14:0705–0713 间已合入的两篇无配对 RFC 成为存量(原分界在其合入时未生效,回溯会红在与本 PR 无关的文件上)。 --- README.i18n.yaml | 2 +- README.zh.md | 2 +- docs/development.i18n.yaml | 2 +- docs/development.zh.md | 22 +++++-------------- docs/i18n/style-samples.md | 4 ++-- ...-bilingual-docs-and-pairing-gate.i18n.yaml | 4 ++-- scripts/translation-pairing.manifest.json | 2 +- 7 files changed, 13 insertions(+), 25 deletions(-) diff --git a/README.i18n.yaml b/README.i18n.yaml index db27519f15..37a95ccdac 100644 --- a/README.i18n.yaml +++ b/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write README.md: 53dd3896eb15800125673e7c44f7de02daca9376 -README.zh.md: 5de4c5b6804648f061647d9e315c08a32b42b39b +README.zh.md: 2119dabf0ae2d2e16e274e44fda7cbbaec146dc9 diff --git a/README.zh.md b/README.zh.md index ad568c808d..2119dabf0a 100644 --- a/README.zh.md +++ b/README.zh.md @@ -15,6 +15,6 @@ pnpm run demo:repl # REPL agent demo (needs DEEPSEEK_API_KEY) pnpm run demo:acp # ACP server agent demo (needs DEEPSEEK_API_KEY) ``` -面向开发者:先读[开发指南](docs/development.md),了解本地环境搭建、钩子、环境变量与质量门禁,动手改 package 之前再读[架构设计](docs/architecture.md)。局部上下文见 [packages/](packages/) 与 [vendor/](vendor/)。 +面向开发者:先读[开发指南](docs/development.md),了解本地环境搭建、钩子、环境变量与质量门禁,动手改 package 之前再读[架构设计](docs/architecture.md)和[文档关系图索引](docs/graph-atlas.md)。局部上下文见 [packages/](packages/) 与 [vendor/](vendor/)。 面向 agent:遵循 [AGENTS.md](AGENTS.md)。 diff --git a/docs/development.i18n.yaml b/docs/development.i18n.yaml index c1e557170d..face2eeeaa 100644 --- a/docs/development.i18n.yaml +++ b/docs/development.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write development.md: bd6f6b561480419abea7a42a44b4078e2c59b1cb -development.zh.md: 54bf19765d2b4dc419e6b71684dbcfcd28230541 +development.zh.md: 61b75b8128ac40b4483debff8aeaeabcfe98752f diff --git a/docs/development.zh.md b/docs/development.zh.md index 91b63d7511..61b75b8128 100644 --- a/docs/development.zh.md +++ b/docs/development.zh.md @@ -6,7 +6,7 @@ ## 前置条件 -- Node.js 24 或更新版本。仓库声明 `node >=24`;CI 在 Node 24 和 26 上运行矩阵测试。 +- Node.js 支持 22.19+ 与 24+。CI 覆盖 22.19、24 和 26;见 [Node 引擎下限 RFC](rfc/implemented/process/2026-07-06-node-engine-floor.md)。 - 启用了 Corepack 的 pnpm。仓库在 `package.json` 中固定使用 `pnpm@11.7.0`;如果 `pnpm --version` 无法通过 Corepack 解析,请先运行 `corepack enable`。 - Git。 - 可选:一个 DeepSeek API key,用于 REPL/ACP agent 演示和真实 API 的 e2e 测试。 @@ -63,26 +63,13 @@ lefthook 在 `lefthook.yml` 中配置,作为评审前的本地早期检查点 vendor manifest 守卫检查 `vendor/*/src` 下的改动是否连同对应的 `vendor/README.md` manifest 更新一起暂存。请在编辑 vendor 代码前先阅读 `vendor/README.md`。 -这些钩子并不与 CI 完全一致。特别是:`pre-push` 运行不带覆盖率的单元测试,而 CI 运行 `pnpm run test:coverage`;CI 还会运行 echo-agent 和 built-bin 冒烟测试,并在 Node 24 和 26 上运行矩阵。 +这些钩子并不与 CI 完全一致。特别是:`pre-push` 运行不带覆盖率的单元测试,而 CI 运行 `pnpm run test:coverage`;CI 还会运行 echo-agent 和 built-bin 冒烟测试,并在 Node 22.19、24 和 26 上执行兼容性矩阵。 ## CI 门禁 -GitHub 工作流在每个 Pull Request 上运行以下门禁: +无密钥 GitHub 工作流共有八个任务:五条 Node 24 车道分别运行静态门禁、lint、覆盖率、快照回放与产物门禁;三个兼容性任务在 Node 22.19、24 和 26 上运行 `pnpm run check:node-compat`。车道调度器从 `package.json` 展开相互独立的门禁:constraints、typecheck、lint、覆盖率、快照回放、`doc-sync` 各成员、module-graph 新鲜度、`knip`,以及 echo-agent 冒烟测试。 -- `pnpm install --frozen-lockfile` -- `pnpm run constraints` -- `pnpm run typecheck` -- `pnpm run lint` -- `pnpm run doc-sync` -- `pnpm run verify-module-graph` -- `pnpm run test:coverage` -- `pnpm run test:snapshot` -- `pnpm run build` -- `pnpm run hygiene` -- 一个 echo-agent 冒烟测试,检查演示的工具调用、工具结果和 JSONL 输出 -- built-bin 冒烟测试,用纯 `node` 运行发布产物 `lib/bin.js` 入口 - -`pnpm run hygiene` 是 `pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-node-next-types` 的本地简写;CI 还会将 `pnpm run constraints` 作为更早的快速失败步骤单独运行一次,然后在 `pnpm run build` 之后运行完整的 hygiene 脚本。 +`pnpm run build` 供给产物车道;`publint`、`verify-node-next-types` 与 built-bin 冒烟测试等待构建产出。独立的真实 API 工作流使用密钥运行 `pnpm run test:e2e`,并设置 `DSH_E2E_MAX_WORKERS=14`。 ## 日常命令 @@ -98,6 +85,7 @@ pnpm run lint:fix # eslint . --fix pnpm run doc-typecheck # compile checked TypeScript snippets in Markdown docs pnpm run gen-cordis-catalog # regenerate docs/cordis-catalog/events.md + services.md from source pnpm run verify-cordis-catalog # fail if either cordis catalog is stale +pnpm run verify-export-jsdoc # fail if a module-level package export lacks complete JSDoc pnpm run gen-doc-graphs # regenerate generated relationship docs from source and curated graph definitions pnpm run verify-doc-graphs # fail if generated relationship docs are stale pnpm run gen-rfc-index # regenerate the docs/rfc/README.md index tables from the RFC tree diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index 6376b3964f..174a5612ff 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -14,9 +14,9 @@ 依赖约束规范:各类扩展插件仅依赖抽象接口,严禁直接依赖 `dsh-agent-loop`(该主循环支持替换实现);唯一允许的特例是组合包 `dsh-agent-core`,它的职责是组装整套实体主干。 -> This document covers **behavior**; type shapes live in [core-data-structures/](../core-data-structures/core.md), the per-event/service reference in the [generated catalog](../cordis-catalog/events-and-services.md), per-package contracts in the package READMEs ([map](../../packages/README.md)). +> This document covers **behavior**; type shapes live in [core-data-structures/](../core-data-structures/core.md), the per-event/service reference in the [generated catalog](../cordis-catalog/events.md), per-package contracts in the package READMEs ([map](../../packages/README.md)). -本文档描述整体行为逻辑;类型定义存放于 [core-data-structures/](../core-data-structures/core.md);各类事件、服务的详细参考见[生成目录](../cordis-catalog/events-and-services.md);各 package 对外约束协议写在对应包的 README([索引](../../packages/README.md))。 +本文档描述整体行为逻辑;类型定义存放于 [core-data-structures/](../core-data-structures/core.md);各类事件、服务的详细参考见[生成目录](../cordis-catalog/events.md);各 package 对外约束协议写在对应包的 README([索引](../../packages/README.md))。 ## ② 防御模式规则 diff --git a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml index ecbd6f196b..f20250c3ba 100644 --- a/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml +++ b/docs/rfc/implemented/process/2026-07-02-bilingual-docs-and-pairing-gate.i18n.yaml @@ -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 -2026-07-02-bilingual-docs-and-pairing-gate.md: 8731fef46b16cfa20d223575c70774cff780a6aa -2026-07-02-bilingual-docs-and-pairing-gate.zh.md: ce2589498ab16cf5ca2f5cdb3f58d031aeb8298f +2026-07-02-bilingual-docs-and-pairing-gate.md: 1e96622e7fb5694ab61772d68744394ef1aeb53a +2026-07-02-bilingual-docs-and-pairing-gate.zh.md: c752d76f12f556ce190bf80c4f3a531c0821be8e diff --git a/scripts/translation-pairing.manifest.json b/scripts/translation-pairing.manifest.json index 891aae375b..f45659ea4b 100644 --- a/scripts/translation-pairing.manifest.json +++ b/scripts/translation-pairing.manifest.json @@ -1,5 +1,5 @@ { - "requiredSince": "2026-07-05", + "requiredSince": "2026-07-14", "required": [ "README.md", "docs/development.md", From 8befd9b88c295381a86c260e2efc11577fce2965 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 12 Jul 2026 22:45:26 -0700 Subject: [PATCH 21/38] =?UTF-8?q?docs(i18n):=20harden=20the=20pipeline=20p?= =?UTF-8?q?rompt=20=E2=80=94=20switcher=20flip,=20code-comment=20hard=20ru?= =?UTF-8?q?le,=20em-dash=20preference,=20three=20new=20few-shots?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/i18n/translation-prompt.md | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/i18n/translation-prompt.md b/docs/i18n/translation-prompt.md index db707730fa..2082d83da5 100644 --- a/docs/i18n/translation-prompt.md +++ b/docs/i18n/translation-prompt.md @@ -37,9 +37,10 @@ You are a senior technical translator specializing in LLM and agent development ### Structure and Format Preservation - Output a complete translated document that maintains exactly the same structure as the source: heading hierarchy, list shape, table columns, link targets, and code blocks. -- Fenced code blocks must be byte-identical to the source, including comments. Do not translate any content inside code fences. +- Fenced code blocks must be byte-identical to the source, including ALL comments inside them. Do NOT translate comments inside code blocks. This is a hard rule with no exceptions. - Inline code spans (commands, flags, paths, API names, version numbers) must be kept verbatim. Never translate or reformat them. - Every relative link must point to the same target as in the source. Link text is translated; link targets are not. +- Language switcher line: when translating into Chinese, write `[English](source-filename.md) | 中文`. When translating into English, write `English | [中文](source-filename.zh.md)`. Do NOT copy the switcher line from the source file unchanged — you must flip the link direction. - After a closing bold marker `**`, always insert a space before the next character. ### Tone and Style @@ -65,7 +66,7 @@ You are a senior technical translator specializing in LLM and agent development #### When translating into Chinese - Use full-width Chinese punctuation in prose: `,。:;?!()「」`. -- Replace em-dashes (——) with colons, periods, commas, or parentheses as appropriate. Only keep em-dashes when they are truly the best choice. +- Strongly prefer replacing all em-dashes (——) with colons, periods, commas, or parentheses. Keep an em-dash only if no other punctuation works at all. - Use enumeration commas (、) between parallel items, not regular commas. - List item endings: use semicolons or no punctuation. Do not end list items with commas. - Put one half-width space between Chinese text and Latin words/numbers. @@ -113,6 +114,8 @@ After writing ``, re-read it in the target language only, without l **Structure** - Is the heading hierarchy, list shape, and code block content identical to the source? +- Are ALL comments inside code blocks left untranslated (byte-identical to source)? +- Is the language switcher line correctly flipped (not copied from source)? - Are link targets preserved and bold markers followed by a space? **Tone & Style** @@ -184,6 +187,21 @@ Below are representative examples of common problems and their corrections. Foll - Bad: `进仓的 agent 工作流见 .agents/skills/dsh-translate-docs` - Good: `仓库内置的 agent 工作流见 .agents/skills/dsh-translate-docs` +### "For humans" — translate the intent, not the word +- Source: `For humans, start with the development guide` +- Bad: `对于人工读者,请先从开发指南开始`("人工读者"生硬) +- Good: `面向开发者:请先阅读开发指南`("开发者"自然,且中文里冒号在此处更自然) + +### Code block comments — NEVER translate +- Source code block contains: `# REPL agent demo (needs DEEPSEEK_API_KEY)` +- Bad: `# REPL agent 演示(需要 DEEPSEEK_API_KEY)` +- Good: `# REPL agent demo (needs DEEPSEEK_API_KEY)` (keep exactly as-is, byte-for-byte) + +### Language switcher — flip direction +- Source file (English) has: `English | [中文](README.zh.md)` +- Bad (copying source unchanged): `English | [中文](README.zh.md)` +- Good (flipped for Chinese file): `[English](README.md) | 中文` + --- Now translate the following document: From 8a53f3d6f9e74652ebeafd17061e6fdcc0252ed0 Mon Sep 17 00:00:00 2001 From: Ziya <199893125+ZiyaZhang@users.noreply.github.com> Date: Sun, 12 Jul 2026 23:10:43 -0700 Subject: [PATCH 22/38] =?UTF-8?q?docs(i18n):=20address=20PR=20#172=20revie?= =?UTF-8?q?w=20=E2=80=94=20terminology=20rulings=20and=20register=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 按 #172 评审意见整改(修复落在本 stack 上层,随 stack 合入): enforcement frontier 裁定为「执行红线」(与金标样例⑦一致), 「强制边界」入不要译作列,消除 translation-rules 与术语表的矛盾; orphan 裁定为「遗留」(崔),孤立/孤儿均入禁项,orphan branch 仍 沿用 git 官方「孤立分支」;README.zh 推进段与 translation-rules.zh 的门禁句按金标语体重写(崔);style-samples ⑥ 的「文件内容完全一 致」改为准确描述 blob hash 与伴随记录的比对机制(bot)。 --- docs/i18n/README.i18n.yaml | 2 +- docs/i18n/README.zh.md | 2 +- docs/i18n/style-samples.md | 2 +- docs/i18n/terminology.md | 4 ++-- docs/i18n/translation-rules.i18n.yaml | 2 +- docs/i18n/translation-rules.zh.md | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/i18n/README.i18n.yaml b/docs/i18n/README.i18n.yaml index 3c954439ce..f058b57082 100644 --- a/docs/i18n/README.i18n.yaml +++ b/docs/i18n/README.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write README.md: 94c8ad9e3a83b490a854dce69b27bd7e13d834b5 -README.zh.md: 4c66da76feff1b1a6c175da62cbd986838b3577e +README.zh.md: 5ed960a4dbf48d3f9b2f681298fd4d7487ca3eaf diff --git a/docs/i18n/README.zh.md b/docs/i18n/README.zh.md index 4c66da76fe..5ed960a4db 100644 --- a/docs/i18n/README.zh.md +++ b/docs/i18n/README.zh.md @@ -44,7 +44,7 @@ - `docs/i18n/terminology.md` 与 [style-samples.md](style-samples.md)——二者本身即为中英对照文档。 - [translation-prompt.md](translation-prompt.md)——自动翻译流水线的 prompt 模板;正文逐字进入模型请求,配对翻译会改变流水线行为。 -**推进**:新文档不等批次——文件名带日期的文档(`yyyy-mm-dd-*.md`,即 RFC)日期在 manifest 的 `requiredSince` 当天或之后,就必须连同配对一起合入,新增的一切生来即是双语。日期早于 cutoff 的文件按定义属于被豁免的存量——包括 cutoff 前夜创建的文件——而文件名日期按 RFC 惯例即首次提出日期,倒填日期绕过 cutoff 是评审可见的违规,不是漏洞。对于存量文档,manifest 中的 `required` 列表是强制边界,不是目标。目标是范围内的全量双语覆盖。配对按可评审的批次落地(核心入口文档、cookbook、RFC、postmortem……);每个批次合入后把其文件加进 `required`,门禁只进不退。尚未进入 `required` 的文档是 backlog——在 `--list` 中可见——但任何已存在的配对无论在不在清单里都按完整契约检查。给一篇文档配对是一份承诺:此后对任一侧的每次修改都必须带上另一侧,所以边界的扩张要跟上翻译评审的实际投入节奏,不要抢在前面。 +**推进**:新增文档不再等待批量翻译。以日期命名的文档(`yyyy-mm-dd-*.md`,即 RFC),只要标注日期等于或晚于 manifest 里的 `requiredSince` 分界日期,合入时就必须配齐双语配对——所有新文档从创建起就要求双语齐备。日期早于分界的文件按定义属于豁免的存量(包括分界前夜创建的文件);文件名日期按 RFC 惯例即首次提出日期,倒填日期绕过分界属于评审可见的违规,构不成漏洞。对于存量文档,manifest 中的 `required` 列表只是当下的执行红线,并非最终目标;目标是范围内的全量双语覆盖。配对按可评审的批次落地(核心入口文档、实操手册(cookbook)、RFC、事故复盘(postmortem)等);每个批次合入后把对应文件加进 `required`,门禁只向前收紧、不倒退放宽。尚未进入 `required` 的文档是待翻清单(backlog),可通过 `--list` 查看;但任何已存在的配对,无论是否在清单内,都按完整契约检查。为一篇文档建立配对等同于一份长期承诺:此后修改任一侧,都必须同步更新另一侧。因此执行红线的推进节奏要匹配翻译评审的实际投入,切勿超前铺开。 ## 分工 diff --git a/docs/i18n/style-samples.md b/docs/i18n/style-samples.md index 174a5612ff..e97cc331a9 100644 --- a/docs/i18n/style-samples.md +++ b/docs/i18n/style-samples.md @@ -66,7 +66,7 @@ > Comparing git timestamps of the pair (no record) — rejected: formatting-only edits would false-positive, and a counterpart committed after an unrelated edit would false-negative; content identity is the only signal that means what the gate claims. -对比双语文件的 git 时间戳(无哈希记录方案)——不予采纳:仅调整格式的改动会触发误报,无关修改后再提交译文又会造成漏检。只有文件内容完全一致,才能作为门禁可信的校验依据。 +对比双语文件的 git 时间戳(无记录方案)——不予采纳:仅调整格式的改动会触发误报,无关修改后再提交译文又会造成漏检。只有基于内容本身的标识(每侧文件的 blob hash 与伴随记录比对),才能承载门禁所声称的语义。 ## ⑦ 推进策略(长段拆分示范) diff --git a/docs/i18n/terminology.md b/docs/i18n/terminology.md index ae6f24acdb..1da19d30a8 100644 --- a/docs/i18n/terminology.md +++ b/docs/i18n/terminology.md @@ -90,7 +90,7 @@ | coverage | 覆盖率 | | | | | crash recovery | 崩溃恢复 | | | | | durability | 持久性 | | | | -| enforcement frontier | 强制边界 | | | i18n 配对机制用语:manifest `required` 清单所划的门禁生效范围 | +| enforcement frontier | 执行红线 | | 强制边界 | i18n 配对机制用语:manifest `required` 清单所划的门禁生效范围;与金标样例(style-samples ⑦)一致 | | event | 事件 | | | | | event log | 事件日志 | | | | | event stream | 事件流 | | | | @@ -118,7 +118,7 @@ | mod | 模组 | | | | | model provider | 模型提供方 | | | | | module | 模块 | | | | -| orphan | 孤立 | | 孤儿 | 指英文源已不存在的 `.zh.md` | +| orphan | 遗留 | | 孤儿、孤立 | 指英文源已不存在的 `.zh.md`(如「遗留译文」);进程语境按 OS 惯用语译「孤儿进程」 | | orphan branch | 孤立分支 | | 孤儿分支 | 沿用 git 官方中文翻译 | | pairing | 配对 | | | | | permission | 权限 | | | | diff --git a/docs/i18n/translation-rules.i18n.yaml b/docs/i18n/translation-rules.i18n.yaml index d340ff42b8..048ce5eef7 100644 --- a/docs/i18n/translation-rules.i18n.yaml +++ b/docs/i18n/translation-rules.i18n.yaml @@ -3,4 +3,4 @@ # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write translation-rules.md: 3e99aa5432ccd904f702238e9b802a9ed9bf6832 -translation-rules.zh.md: 0ff2ab43d59c6e0dd07e20e2f1a0a1c28c2f245c +translation-rules.zh.md: 1d8dcb04f1015af2db46c2e25b44f5cd147b6ecc diff --git a/docs/i18n/translation-rules.zh.md b/docs/i18n/translation-rules.zh.md index 0ff2ab43d5..1d8dcb04f1 100644 --- a/docs/i18n/translation-rules.zh.md +++ b/docs/i18n/translation-rules.zh.md @@ -21,7 +21,7 @@ ## 结构保持 -形状由配对门禁强制,译者不需要用流畅度去换结构——在框架内自然行文即可。配对的两个文件必须在以下方面一一对应: +结构一致性由配对门禁负责校验,译者无需为保持结构而牺牲行文的流畅:在既定框架内自然书写即可。配对的两个文件必须在以下方面一一对应: - 标题层级(相同级别、相同顺序;标题的**文字**要翻译); - 列表形态与编号; From 46ba91bc98ca6eeb529aa343ed905c732deea542 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:51:00 +0800 Subject: [PATCH 23/38] docs: sharpen package limitation audit --- packages/core/agent-core/README.md | 2 +- .../session-persistence-jsonl/README.md | 3 ++- .../session-persistence-jsonl/src/index.ts | 4 ++-- .../session-persistence-sqlite/README.md | 3 ++- packages/support/README.md | 4 ++-- packages/support/invariants/README.md | 6 +++--- packages/support/invariants/package.json | 2 +- packages/support/invariants/src/index.ts | 16 +++++++++------- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/packages/core/agent-core/README.md b/packages/core/agent-core/README.md index 8acbc72c16..ddbc708121 100644 --- a/packages/core/agent-core/README.md +++ b/packages/core/agent-core/README.md @@ -17,7 +17,7 @@ This is the package to read to see **the whole shared plugin tree at once**: the @deepseek-ai/dsh-skill skill provider registry @deepseek-ai/dsh-skill-local local filesystem skill provider @deepseek-ai/dsh-agent agent registry + agent/* event vocabulary -@deepseek-ai/dsh-invariants dev-mode event-contract assertions +@deepseek-ai/dsh-invariants runtime event-contract assertions @deepseek-ai/dsh-tool-bash the model-facing bash/bash_output/bash_kill schemas @deepseek-ai/dsh-tool-skill session-prefix skill catalog + model-facing loader schema @deepseek-ai/dsh-agent-loop THE concrete loop (gets the forwarded `agents`) diff --git a/packages/session-persistence/session-persistence-jsonl/README.md b/packages/session-persistence/session-persistence-jsonl/README.md index 27d965195e..c74095e9bf 100644 --- a/packages/session-persistence/session-persistence-jsonl/README.md +++ b/packages/session-persistence/session-persistence-jsonl/README.md @@ -21,7 +21,7 @@ The JSONL durable session-persistence backend — a concrete `SessionPersistence ## Durability and crash semantics -- **Lazy materialization.** `create(meta)` writes nothing; the `.jsonl` (header + first batch) is written atomically (temp-write + `fsync` + rename) on the first `append`. A created-but-never-appended session leaves nothing on disk and is absent from `list`. +- **Lazy materialization.** `create(meta)` writes nothing; on the first `append`, the backend writes and `fsync`s a temporary file, publishes it without overwrite via a hard link, then `fsync`s the directory. A created-but-never-appended session leaves nothing on disk and is absent from `list`. - **Append-only.** Committed events (at or below a flushed `turn/end`) are never rewritten. Subsequent appends are line appends at EOF + `fsync`. - **Crash recovery — close, don't truncate.** A crash can leave a log whose final turn never closed (real events after the last `turn/end`). `load` PRESERVES those events (a turn can be huge — they are real work) and closes the orphaned turn by durably appending synthetic boundary events: an error `tool/result` for every `tool-call` the crash left unanswered (the loop logs the assistant message before running the tools, so a mid-tool crash leaves dangling calls — and `deriveMessages()` would replay an assistant tool-call with no result, which providers reject), then a `step/end` if a step was open, then `turn/end {kind:'interrupted'}`, returning a balanced log. Only a never-fully-written **torn tail fragment** (a final line with no newline / unparseable) is `ftruncate`d away before the closers are written. See [session persistence](../../../docs/rfc/implemented/architecture/2026-06-14-session-persistence.md). - **Contiguous-seq.** `load` rejects a mid-log parse error or `seq` gap (unloadable); `append` rejects a batch whose first `seq` does not continue the stored log, and rejects non-JSON-serializable `event.data` naming the offending event type. @@ -35,3 +35,4 @@ The plugin generalizes the example `session-jsonl.ts`: it subscribes to `session - **Only the current `SESSION_FORMAT_VERSION` (v0) loads** — the on-disk format is pre-release/unstable: a breaking format change is absorbed at v0 and non-current logs are rejected; there is no migration. - **Nothing deletes session files** — logs accumulate under `root` until removed externally (the seam has no deletion surface). - **Single-process assumption** — per-session serialization and the write cursor live in this process; two processes appending to the same `root` are not coordinated. +- **Initial materialization requires hard-link support** — first append uses `link()` so same-id races fail instead of overwriting a committed log; a filesystem that cannot create hard links cannot host this backend. diff --git a/packages/session-persistence/session-persistence-jsonl/src/index.ts b/packages/session-persistence/session-persistence-jsonl/src/index.ts index a69c979756..141505644f 100644 --- a/packages/session-persistence/session-persistence-jsonl/src/index.ts +++ b/packages/session-persistence/session-persistence-jsonl/src/index.ts @@ -193,7 +193,7 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi // --- materialization / append / repair (file mechanics) --- - /** Atomically write the header line + first batch (temp-write, fsync, rename). */ + /** Atomically write the header line + first batch (temp-write, fsync, collision-safe hard-link publish). */ private async materialize(meta: SessionHeader, events: readonly SessionEvent[]): Promise { const dir = sessionDir(this.root, meta.cwd) await mkdir(this.root, { recursive: true, mode: 0o700 }) @@ -250,7 +250,7 @@ export class SessionPersistenceJsonl extends SessionPersistence implements Persi } } - /** fsync a directory so a just-created/renamed entry inside it is crash-durable. */ + /** fsync a directory so a just-created or published entry inside it is crash-durable. */ private async syncDir(dir: string): Promise { const handle = await open(dir, 'r') try { diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index 403b6e4432..873dc833b6 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -31,5 +31,6 @@ Like the JSONL backend, the plugin also installs the `session/event` → buffer - **Raw `node:sqlite`, pending a cordis database service** — the backend holds a `DatabaseSync` directly; if a `cordis/db` / `@cordisjs` SQL driver is adopted, the storage driver routes through it (the `SessionPersistence` contract would not change) — a marked TODO. - **`DatabaseSync` is synchronous** — every append transaction blocks the event loop for its duration; acceptable for local stores, a throughput ceiling for busy multi-session servers. -- **Only the current `SCHEMA_VERSION` opens** — a database written by any other build is rejected rather than migrated (unreleased software; no persisted user data to preserve). +- **Write contention has no wait or retry policy** — the backend sets no busy timeout and retries no locked-database error, so another connection holding a write transaction makes the operation reject immediately. +- **Only the current `SCHEMA_VERSION` opens** — a database with any other schema version is rejected rather than migrated (unreleased software; no persisted user data to preserve). - **Nothing deletes stored sessions** — rows accumulate until removed externally (the seam has no deletion surface; `ON DELETE CASCADE` is wired for such out-of-band cleanup). diff --git a/packages/support/README.md b/packages/support/README.md index c8883a89ff..b6559a1a79 100644 --- a/packages/support/README.md +++ b/packages/support/README.md @@ -5,8 +5,8 @@ Packages that exist to serve development, testing, and the examples rather than | Package | Role | ctx key | |---|---|---| | `acp-snapshot/` | ACP snapshot suite kit: subprocess scenario harness + golden normalizers + the `defineAcpSnapshotSuite` factory | (library — imported by example `*.snapshot.ts` suites) | -| `invariants/` | Dev-mode event-contract assertions | (listens on `session/*`, `agent/*`) | +| `invariants/` | Runtime event-contract assertions for development diagnostics | (listens on `session/*`, `agent/*`) | | `llm-replay/` | Record/replay adapter: short-circuits `llm/stream` from a recorded session JSONL (keyless snapshot tests) | (listens on `llm/stream`) | | `subagent-mock/` | Scripted `SubagentProvider` for deterministic seam/tool tests | (registers on `ctx.subagents`) | -`invariants` runs only in dev mode (contract checks, not runtime behavior). `llm-replay` backs the demos and the snapshot test tier under the per-file coverage gate. `acp-snapshot` carries the snapshot tier's harness/normalizer/suite machinery so every example's suite is a scenario table over one shared, gate-covered implementation. `subagent-mock` exercises the real `ctx.subagents` load path without a model or child agent. A package graduates OUT of `support/` into a product group only when it gains documented product consumers. +`invariants` is development support but has no environment guard: it runs wherever registered, and the default `dsh-agent-core` bundle mounts it unconditionally. `llm-replay` backs the demos and the snapshot test tier under the per-file coverage gate. `acp-snapshot` carries the snapshot tier's harness/normalizer/suite machinery so every example's suite is a scenario table over one shared, gate-covered implementation. `subagent-mock` exercises the real `ctx.subagents` load path without a model or child agent. A package graduates OUT of `support/` into a product group only when it gains documented product consumers. diff --git a/packages/support/invariants/README.md b/packages/support/invariants/README.md index 2fc56fc2ae..e48eb71a85 100644 --- a/packages/support/invariants/README.md +++ b/packages/support/invariants/README.md @@ -1,8 +1,8 @@ # dsh-invariants -Dev-mode event-contract assertions. This pure-listener plugin checks relationships among session events, agent states, scoped dispatches, and model requests at runtime; it does not own or change product behavior. +Runtime event-contract assertions intended for development diagnostics. This pure-listener plugin checks relationships among session events, agent states, scoped dispatches, and model requests; it does not own or change product behavior. -**Off in production.** Enable it in tests and the demos, where a contract violation should fail loudly. It costs nothing when not registered, and doubles as executable documentation of the event taxonomy — the assertions *are* the contract. +The plugin has no environment guard: it is active wherever it is registered. The default [`dsh-agent-core`](../../core/agent-core/README.md) bundle mounts it unconditionally; a custom composition can omit it when the runtime cost is undesirable. It doubles as executable documentation of the event taxonomy — the assertions *are* the contract. Session itself owns immutable log storage in every composition: it takes one lossless JSON snapshot of each accepted event, deep-freezes that record, and exposes the log through immutable array snapshots. The invariants plugin checks the cross-record and cross-seam rules that storage immutability cannot express. @@ -45,7 +45,7 @@ On any violation it throws `InvariantError` (`code: 'INVARIANT'`). ## Why runtime assertions remain useful -Session enforces the per-record storage boundary at runtime, where a cast cannot bypass it. Pervasive `DeepReadonly` types would add noise across consumers without expressing relationships such as turn/step nesting, subject-correct scoped dispatch, or equality between a request and its log reconstruction. This plugin checks those relationships in development while `dsh-session` keeps history immutable in every composition. See [source-owned session immutability and dev-mode invariants](../../../docs/rfc/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md). +Session enforces the per-record storage boundary at runtime, where a cast cannot bypass it. Pervasive `DeepReadonly` types would add noise across consumers without expressing relationships such as turn/step nesting, subject-correct scoped dispatch, or equality between a request and its log reconstruction. This plugin checks those relationships wherever it is mounted while `dsh-session` keeps history immutable in every composition. See [source-owned session immutability and dev-mode invariants](../../../docs/rfc/implemented/architecture/2026-06-11-dev-invariants-over-deep-readonly.md). ## Seeded sessions diff --git a/packages/support/invariants/package.json b/packages/support/invariants/package.json index 588c658857..3568465d18 100644 --- a/packages/support/invariants/package.json +++ b/packages/support/invariants/package.json @@ -1,6 +1,6 @@ { "name": "@deepseek-ai/dsh-invariants", - "description": "Dev-mode event-contract assertions for the DeepSeek Harness", + "description": "Runtime event-contract assertions for DeepSeek Harness development diagnostics", "version": "0.0.1", "private": true, "type": "module", diff --git a/packages/support/invariants/src/index.ts b/packages/support/invariants/src/index.ts index f5ef6d2b4a..15a57ab08a 100644 --- a/packages/support/invariants/src/index.ts +++ b/packages/support/invariants/src/index.ts @@ -1,13 +1,15 @@ /** - * Dev-mode invariants: a pure-listener plugin that asserts relationships in - * the harness event contract at runtime. + * Runtime invariants: a pure-listener plugin that asserts relationships in + * the harness event contract. It is intended for development diagnostics but + * has no environment guard, so it is active in every composition that mounts + * it (including the default `dsh-agent-core` bundle). * * Everything is a plugin — this is just listeners on `session/created`, * `session/event`, `agent/status`, and the scoped dispatch and request seams. - * It is **off in production**: enable it in tests and demos, where a contract - * violation should be a loud failure rather than a subtle one. It doubles as - * executable documentation of the event taxonomy: the assertions below are - * the contract. + * Custom compositions can omit it when the runtime assertion cost is + * undesirable. When mounted, a contract violation is a loud failure rather + * than a subtle one. It doubles as executable documentation of the event + * taxonomy: the assertions below are the contract. * * Session owns immutable log storage: it snapshots and deep-freezes every * accepted event at the source. This plugin checks relationships that one @@ -344,7 +346,7 @@ function checkTransition(from: AgentStatus | undefined, to: AgentStatus): void { } /** - * Register the dev-mode invariants. Contributions are effect-scoped, so + * Register the runtime invariants. Contributions are effect-scoped, so * disposing the plugin fiber removes all listeners (HMR-safe). On (re-)apply * the trace state is rebuilt by replaying each existing session's log, so a * hot reload mid-turn does not falsely reject the next event. From 7b539a0f8b3d6e04252db5517ab7551aca6512b3 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:00:47 +0800 Subject: [PATCH 24/38] docs: align model experience with runtime visibility --- docs/AGENTS.md | 2 +- docs/cookbook/adding-a-package.md | 2 +- docs/core-data-structures/bash.md | 4 +- docs/persistence-catalog.md | 2 +- ...-subagent-persona-tool-filter-and-depth.md | 2 +- ...026-07-10-readme-known-limitations-gate.md | 6 +- ...07-12-package-model-experience-contract.md | 4 +- packages/bash/bash-sandbox/README.md | 4 +- packages/bash/bash-sandbox/src/index.ts | 6 +- packages/bash/bash/README.md | 2 +- packages/bash/bash/src/session-mode.ts | 22 +++--- packages/bash/tool-bash/README.md | 6 +- packages/bash/tool-bash/src/index.ts | 12 ++-- packages/core/scope/README.md | 2 +- packages/core/system-prompt/README.md | 2 +- packages/core/tools/README.md | 2 +- packages/fs/tool-fs/README.md | 2 +- packages/subagent/subagent-fork/README.md | 2 +- .../subagent/subagent-inprocess/README.md | 2 +- packages/subagent/subagent-spawn/README.md | 2 +- packages/subagent/tool-subagent/README.md | 2 +- packages/web/tool-web/README.md | 4 +- packages/workflow/tool-workflow/README.md | 5 +- .../verify-package-readme-model-experience.ts | 71 +++++++++++++------ 24 files changed, 98 insertions(+), 72 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index 90b5b7354f..a837908d90 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -40,7 +40,7 @@ Every package README ends with this table immediately before `## Known Limitatio | Context surface | What the model sees | Token effect | |---|---|---| -Rows state what reaches which model and classify token cost or lifetime; zero-direct rows name the indirect path. `verify-package-readme-model-experience` gates shape and order, while review owns accuracy ([rationale](rfc/implemented/process/2026-07-12-package-model-experience-contract.md)). +Rows state what reaches which model and classify token cost or lifetime. Treat prompt text and tool schemas separately when their visibility conditions differ; zero-direct rows name the indirect path. `verify-package-readme-model-experience` gates shape and order, while review owns accuracy ([rationale](rfc/implemented/process/2026-07-12-package-model-experience-contract.md)). ## Wordcount Budgets diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 0ccee2b217..d8def5ebfe 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -56,7 +56,7 @@ Keep package-specific service API, config, events, extension points, and design - **Consumer-visible gap** — exact boundary or deliberately deferred work. ``` -Fill [Model Experience](../AGENTS.md#package-model-experience) from the implementation: name every direct request contribution and token-growth condition, or state zero direct tokens and its indirect path. Every package participates, including type-only libraries and backend seams. A package with genuinely no limitations joins the justified allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. +Fill [Model Experience](../AGENTS.md#package-model-experience) from the implementation: name every direct request contribution and token-growth condition, or state zero direct tokens and its indirect path. Do not infer prompt visibility from tool-schema visibility; independently registered guidance can remain after a scoped tool restriction. Every package participates, including type-only libraries and backend seams. A package with genuinely no limitations joins the justified allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. ## 5. Verify diff --git a/docs/core-data-structures/bash.md b/docs/core-data-structures/bash.md index 51f7cb0696..94c5cb9a7f 100644 --- a/docs/core-data-structures/bash.md +++ b/docs/core-data-structures/bash.md @@ -154,7 +154,7 @@ interface CollectedOutput { ## File sandbox: `BashSandboxInfo` -A sandbox-consuming executor (`dsh-bash-sandbox`) exposes its configured fallback through `BashExecutor.sandboxMode`. The tool layer folds each agent session's durable `bash/sandbox-mode` override, stamps the effective mode onto the request, states it in the per-agent prompt, and may replace it for one user-approved strictly wider call. The mode/enforcement vocabulary is owned and cataloged by the [`@deepseek-ai/dsh-sandbox` seam](sandbox.md), whose provider wraps the executor's argv; modes govern FILE effects only, not network or process visibility. +A sandbox-consuming executor (`dsh-bash-sandbox`) exposes its configured fallback through `BashExecutor.sandboxMode`. The tool layer folds each agent session's durable `bash/sandbox-mode` override, stamps the effective mode onto the request, and may replace it for one user-approved strictly wider call. It deliberately neither states the standing mode nor narrates switches; a denial result names the mode that command actually ran under. The mode/enforcement vocabulary is owned and cataloged by the [`@deepseek-ai/dsh-sandbox` seam](sandbox.md), whose provider wraps the executor's argv; modes govern FILE effects only, not network or process visibility. A sandboxed run always reports the facts it executed under on `BashRunResult.sandbox`: `denied` is the executor's conservative classification of a failure as sandbox-caused (a failed exit whose stderr carries a filesystem-permission signature — never a clean exit or a signal kill), read from the collected stderr tail; `enforcement` reports how completely the selected backend governs the mode's file effects (`SandboxEnforcement = 'full' | 'partial'` — `partial` when an older Landlock ABI governs only a subset of the requested accesses; absent under `danger-full-access`, where nothing is confined); `runnerFailed` marks the opposite of a denial — the sandbox RUNNER itself failed and the command never ran (stamped only on settled background tasks; a foreground run surfaces the same condition as the thrown `SANDBOX_UNAVAILABLE` error): @@ -193,7 +193,7 @@ interface BashSandboxInfo { } ``` -One more piece completes the vocabulary: the `SANDBOX_UNAVAILABLE` error code (owned by the [sandbox seam](sandbox.md)) is what the `ctx.sandbox` provider throws — and the executor propagates — when a confined mode has no usable backend. A selected runner refusing its profile reaches the same fail-closed foreground error; a settled background task records `runnerFailed`. The model sees the current effective mode in the prompt, receives denial/runner facts in results, and can request a one-shot strictly wider retry through `sandbox_permissions` plus `justification`; `ctx.approval` must grant that exact call before anything executes. The complete policy and switching design is the [sandbox RFC](../rfc/implemented/feature/2026-07-06-sandbox.md). +One more piece completes the vocabulary: the `SANDBOX_UNAVAILABLE` error code (owned by the [sandbox seam](sandbox.md)) is what the `ctx.sandbox` provider throws — and the executor propagates — when a confined mode has no usable backend. A selected runner refusing its profile reaches the same fail-closed foreground error; a settled background task records `runnerFailed`. The model receives denial/runner facts in results, learns the effective mode only when a denial marker names it, and can request a one-shot strictly wider retry through `sandbox_permissions` plus `justification`; `ctx.approval` must grant that exact call before anything executes. The complete policy and switching design is the [sandbox RFC](../rfc/implemented/feature/2026-07-06-sandbox.md). ## Background tasks: `BashTask` diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 245d25fa5f..47640ba8ee 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -75,7 +75,7 @@ Source: [`packages/core/session/src/types.ts:329`](../packages/core/session/src/ #### `bash/sandbox-mode` — log-only -The session's sandbox mode was switched — log-only (like `approval/*`; NOT a surface event, carries no `surfaceOp`): durable and replayable, never in the model transcript. The LAST such event is the session's override (effectiveSandboxMode); who asked for it is derivable from position (an event after the log's last `request/header*` was a runtime switch by the user; see the tool layer's narrator). +The session's sandbox mode was switched — log-only (like `approval/*`; NOT a surface event, carries no `surfaceOp`): durable and replayable, never in the model transcript. The LAST such event is the session's override (effectiveSandboxMode); execution and ACP config-option reporting fold it without adding prompt text or a context notice. ```ts persistence-catalog 'bash/sandbox-mode': { mode: SandboxMode } diff --git a/docs/rfc/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md b/docs/rfc/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md index 5b6eeb4ccb..3c2e17aebd 100644 --- a/docs/rfc/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md +++ b/docs/rfc/implemented/feature/2026-07-12-subagent-persona-tool-filter-and-depth.md @@ -32,7 +32,7 @@ This uses the normal system-prompt registration mechanism rather than a second p ### Tool filtering is one live global-view rule -The tool filter controls visibility and executable lookup together. An in-process provider installs `ToolRegistry.restrict()` in the child's scope before publication, and the registry's single resolver applies the same result to prompt schemas, lookup, execution, and Code Mode SDK generation. +The tool filter controls capability visibility and executable lookup together. An in-process provider installs `ToolRegistry.restrict()` in the child's scope before publication, and the registry's single resolver applies the same result to wire tool schemas, lookup, execution, and Code Mode SDK generation. Independently registered system-prompt sections are outside `ToolRegistry`, so filtering a tool does not remove that plugin's standalone guidance. Resolution follows these rules: diff --git a/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md b/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md index d4814ce283..e2fbe9cdc6 100644 --- a/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md +++ b/docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md @@ -4,7 +4,7 @@ Status: implemented ## Problem -The [documentation standard](../../../AGENTS.md) assigns limitations to the package-README tier ("the per-package contract: config, semantics, limitations, extension points"), but nothing enforced that the section exists or shares a shape. Ten READMEs carried the content under five ad-hoc headings — "What is NOT here (TODO)", "What is NOT here", "Deferred (faithful-but-degraded)", "Limitations (MVP, documented deliberately)", "Known limitations (tracked TODOs)" — and the other forty-odd carried nothing, so a reader could not distinguish "this package has no known limitations" from "nobody wrote them down", and no grep could enumerate the repo's known gaps. +The [documentation standard](../../../AGENTS.md) assigns limitations to the package-README tier ("the per-package contract: config, semantics, limitations, extension points"). Without a required shared shape, variant headings and omissions make "this package has no known limitations" indistinguishable from "nobody wrote them down", and no single grep can enumerate the repo's known gaps. ## Decision @@ -16,13 +16,13 @@ The gate checks presence, shape, and the whitelist; the bullets' truthfulness an ## Alternatives considered -- **Free-form headings, gate only that "something limitations-like" exists** — keeps the five variants, stays un-greppable, and needs the same near-miss heuristics anyway without buying uniformity. +- **Free-form headings, gate only that "something limitations-like" exists** — preserves variant headings, stays un-greppable, and needs the same near-miss heuristics anyway without buying uniformity. - **Require the section in ALL READMEs, allowing an empty body or "None."** — boilerplate "None" rots silently as a package gains real limitations; the whitelist inversion turns "nothing declared" into an explicit, lintable claim that review can challenge. - **A word-count ceiling on the section** — limitation lists are legitimately variable in length; package READMEs are deliberately unbudgeted (per the [budget policy](../../../AGENTS.md)) and review governs their prose. ## Consequences - A new package cannot ship without either declaring its gaps or explicitly claiming it has none; a missing, drifted, or empty section fails `doc-sync` locally (pre-push) and in CI (`readme-limitations` in the run-gates doc-sync leaf set). -- The pre-existing variant sections are normalized to the canonical heading, and every package README now answers the limitations question one way or the other. +- Every package README answers the limitations question through the canonical heading or an explicit no-limitations allowlist entry. - One more fast tsx script in the `doc-sync` chain; no new dependency (plain `node:fs` glob + line scan). - The canonical heading is enforced verbatim, so renaming it later is a mechanical one-script-plus-all-READMEs change guarded by the same gate. diff --git a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md index a083e69cb8..8fb3e45a83 100644 --- a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md +++ b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md @@ -8,11 +8,11 @@ A package README can explain APIs and runtime mechanics without answering the qu ## Decision -Every workspace package README ends with the canonical [Model Experience table](../../../AGENTS.md#package-model-experience), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. +Every workspace package README ends with the canonical [Model Experience table](../../../AGENTS.md#package-model-experience), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other. Every package participates. A service seam, storage backend, test helper, or type-only library that contributes no prompt text, tool schema, message, or auxiliary request records zero direct tokens and names the consumer or control path through which it can still change model-visible material. This explicit negative contract prevents readers from having to infer whether the section was forgotten. -`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README, the canonical final-section order, one exact `## Model Experience` heading and three-column table header, and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns shape and completeness; implementation review owns the truth of the prose. +`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README, the canonical final-section order, one exact `## Model Experience` heading and three-column table header outside fenced code, and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns structural presence, shape, and order; implementation review owns coverage and the truth of the prose. ## Alternatives considered diff --git a/packages/bash/bash-sandbox/README.md b/packages/bash/bash-sandbox/README.md index f971ff322a..99cb3b2423 100644 --- a/packages/bash/bash-sandbox/README.md +++ b/packages/bash/bash-sandbox/README.md @@ -1,6 +1,6 @@ # @deepseek-ai/dsh-bash-sandbox -Sandbox-consuming implementation of the [`@deepseek-ai/dsh-bash`](../bash/) executor seam. Load it **instead of** `@deepseek-ai/dsh-bash-local`, together with a [`ctx.sandbox`](../../sandbox/sandbox/) provider (e.g. [`@deepseek-ai/dsh-sandbox-local`](../../sandbox/sandbox-local/)) — the model-facing tool layer (`dsh-tool-bash`) is untouched; that swap is exactly what the seams exist for. +Sandbox-consuming implementation of the [`@deepseek-ai/dsh-bash`](../bash/) executor seam. Load it **instead of** `@deepseek-ai/dsh-bash-local`, together with a [`ctx.sandbox`](../../sandbox/sandbox/) provider (e.g. [`@deepseek-ai/dsh-sandbox-local`](../../sandbox/sandbox-local/)) — no alternate tool plugin is needed; `dsh-tool-bash` detects the executor's `sandboxMode` capability and adds the escalation fields. Every command is confined by handing the provider the exact `['bash', '-c', command]` argv this executor is about to spawn and spawning the returned (wrapped) argv instead. WHICH platform runner confines it — and whether one is usable at all (fail closed with a structured `SANDBOX_UNAVAILABLE` error, never a silent unconfined run) — is the provider's concern; this package owns the bash side only. @@ -36,7 +36,7 @@ The keyless consumer-integration proofs are `tests/bwrap.e2e.ts`, `tests/landloc | Context surface | What the model sees | Token effect | |---|---|---| -| System prompt, indirectly | By advertising a confining `sandboxMode`, this backend makes `dsh-tool-bash` state the calling session's effective mode and expose escalation fields. The backend itself adds no prose. | Small fixed per-request cost through the consumer, plus a retained notice when the session mode changes. | +| Bash tool schema, indirectly | By advertising a confining `sandboxMode`, this backend makes `dsh-tool-bash` expose `sandbox_permissions` and `justification`. The backend adds no prompt prose, and the session's effective mode remains unstated. | Small fixed schema increment on requests where `bash` is visible; mode switches add no context tokens. | | Bash tool result, indirectly | The model sees ordinary bounded command output plus denial markers, the mode used, and sandbox-unavailable failures shaped by `dsh-tool-bash`; runner details stay internal. | Zero additional tokens on an unremarkable allowed run beyond ordinary output. Denial or failure adds a small conditional marker or error retained until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/bash/bash-sandbox/src/index.ts b/packages/bash/bash-sandbox/src/index.ts index 090d06b2fe..65d5d8b989 100644 --- a/packages/bash/bash-sandbox/src/index.ts +++ b/packages/bash/bash-sandbox/src/index.ts @@ -143,9 +143,9 @@ function matchesSignature(exitCode: number | null, stderr: string, signatures: r * fallback exposed by {@link sandboxMode}; `dsh-tool-bash` folds a session's * durable `bash/sandbox-mode` override and stamps the effective mode onto each * request, while an approved escalation may stamp a strictly wider mode for - * one call. The tool's per-agent prompt section states that same effective - * mode, and each run's `result.sandbox` reports what actually executed plus - * enforcement completeness. + * one call. The prompt deliberately does not state the mode; each run's + * `result.sandbox` reports what actually executed plus enforcement + * completeness, and the tool layer renders denial or runner-failure facts. */ export class SandboxBashExecutor extends LocalBashExecutor { static inject = ['sandbox'] diff --git a/packages/bash/bash/README.md b/packages/bash/bash/README.md index 36f6686522..97c1b91dff 100644 --- a/packages/bash/bash/README.md +++ b/packages/bash/bash/README.md @@ -11,7 +11,7 @@ This package is the interface quarter of the bash capability, split so each conc | `@deepseek-ai/dsh-bash-sandbox` | an implementation: `dsh-bash-local`'s mechanics with every spawn confined via [`ctx.sandbox`](../../sandbox/sandbox/), denials reported as result facts | | `@deepseek-ai/dsh-tool-bash` | the model-facing tool schemas over `ctx.bash` | -The split mirrors the LLM seam (`LlmService`/`LlmAdapter`) and the agent-tool survey: pi hides execution behind a `BashOperations` interface (local shell / SSH / VM backends), Codex behind an exec-server protocol. `dsh-bash-sandbox` is exactly that swap in action — a sandboxing executor behind the same interface, tool schemas untouched; a containerized or remote executor slots in the same way. +The split mirrors the LLM seam (`LlmService`/`LlmAdapter`) and the agent-tool survey: pi hides execution behind a `BashOperations` interface (local shell / SSH / VM backends), Codex behind an exec-server protocol. `dsh-bash-sandbox` is exactly that swap in action — a sandboxing executor behind the same interface; the consumer detects its `sandboxMode` capability and adds escalation fields without importing the implementation. A containerized or remote executor slots in the same way. ## Service API (`ctx.bash`) diff --git a/packages/bash/bash/src/session-mode.ts b/packages/bash/bash/src/session-mode.ts index 03ad6e3d7c..ff0b9ceba1 100644 --- a/packages/bash/bash/src/session-mode.ts +++ b/packages/bash/bash/src/session-mode.ts @@ -5,12 +5,13 @@ * `effective = fold(events) ?? the executor's configured default`, so an * override survives restart by replay, two sessions can never see each * other's state, and there is no external config store. The event is - * log-only (the `approval/*` precedent): the model learns the mode from the - * prompt section and the boundary notices in `@deepseek-ai/dsh-tool-bash`, - * never from the event itself. EXECUTION honors the fold in the tool layer — - * it stamps the effective mode onto each call's `BashExecRequest.sandboxMode` - * (weakest-precedence: an escalation grant for the call outranks it) — the - * executor itself stays a config-fixed default plus per-call overrides. + * log-only (the `approval/*` precedent): the model receives neither this event + * nor a standing mode statement. `@deepseek-ai/dsh-tool-bash` names the mode + * only when it renders a sandbox denial. EXECUTION honors the fold in the tool + * layer — it stamps the effective mode onto each call's + * `BashExecRequest.sandboxMode` (weakest-precedence: an escalation grant for + * the call outranks it) — the executor itself stays a config-fixed default + * plus per-call overrides. * * @module dsh-bash/session-mode */ @@ -24,9 +25,8 @@ declare module '@deepseek-ai/dsh-session' { * The session's sandbox mode was switched — log-only (like `approval/*`; * NOT a surface event, carries no `surfaceOp`): durable and replayable, * never in the model transcript. The LAST such event is the session's - * override ({@link effectiveSandboxMode}); who asked for it is derivable - * from position (an event after the log's last `request/header*` was a - * runtime switch by the user; see the tool layer's narrator). + * override ({@link effectiveSandboxMode}); execution and ACP config-option + * reporting fold it without adding prompt text or a context notice. */ 'bash/sandbox-mode': { mode: SandboxMode } } @@ -54,8 +54,8 @@ export function effectiveSandboxMode(events: readonly SessionEvent[]): SandboxMo /** * THE write path for a session's sandbox-mode override: appends exactly one * `bash/sandbox-mode` event — the switch IS its event; nothing mutates mode - * state out of band. Takes effect on the session's next bash call and next - * prompt assembly (the consumers fold on every read). + * state out of band. Subsequent execution and ACP config-option reporting fold + * it on read; no prompt assembly consumes it. * @param session - the session the override belongs to. * @param mode - the mode every subsequent bash call in this session runs * under (until the next switch). diff --git a/packages/bash/tool-bash/README.md b/packages/bash/tool-bash/README.md index da1ab41c14..8bab38caa5 100644 --- a/packages/bash/tool-bash/README.md +++ b/packages/bash/tool-bash/README.md @@ -1,10 +1,10 @@ # @deepseek-ai/dsh-tool-bash -The model-facing bash tools — `bash`, `bash_output`, `bash_kill` — registered over the `ctx.bash` executor seam (`@deepseek-ai/dsh-bash`). Pure schema + text shaping; every process concern lives behind the seam, so sandboxed or remote executor implementations swap in without changing what the model sees. +The model-facing bash tools — `bash`, `bash_output`, `bash_kill` — registered over the `ctx.bash` executor seam (`@deepseek-ai/dsh-bash`). This package owns schema and text shaping while process concerns stay behind the seam. Executor facts can change rendered results, and a sandboxing executor activates the escalation fields, without moving those presentation rules into the backend. Requires a loaded executor implementation (e.g. `@deepseek-ai/dsh-bash-local`); the plugin stays pending until `ctx.bash` exists (`inject: ['tools', 'bash', 'systemPrompt']`). -The plugin also contributes the `tool:bash` prompt section (order 105) — the cross-call habit the per-tool descriptions cannot carry: check the `[exit code: N]` marker on every result and investigate failures before moving on. Under a sandboxing executor it additionally contributes the per-agent `env:bash-sandbox` section (order 110) stating each session's EFFECTIVE mode, and the pre-step narrator — see [Per-session mode](#per-session-mode-switching-and-visibility). +The plugin also contributes the `tool:bash` prompt section (order 105) — the cross-call habit the per-tool descriptions cannot carry: check the `[exit code: N]` marker on every result and investigate failures before moving on. A sandboxing executor changes the `bash` schema and result markers but adds no mode statement or switch notice; see [Per-session mode](#per-session-mode-switching). ## Tools @@ -62,7 +62,7 @@ Under a sandboxing executor this plugin makes the session's standing mode overri | Context surface | What the model sees | Token effect | |---|---|---| -| System prompt | Every request for an agent that can see these tools carries the short `tool:bash` exit-code instruction. With a sandboxing executor it also carries that session's effective sandbox mode, plus a logged context notice after a mode change. | Small fixed input cost per request; a mode-change notice is conditional and then remains in conversation history. | +| System prompt | Every request in this plugin's registration scope carries the short `tool:bash` exit-code instruction. A sandboxing executor adds no mode statement or switch notice. Scoped tool restrictions can hide the schemas without removing this independently registered section. | Small fixed input cost per request while the plugin is active, unchanged by sandbox mode or mode switches. | | Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. | | Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. | diff --git a/packages/bash/tool-bash/src/index.ts b/packages/bash/tool-bash/src/index.ts index 31e6512ae0..c22e789c79 100644 --- a/packages/bash/tool-bash/src/index.ts +++ b/packages/bash/tool-bash/src/index.ts @@ -483,12 +483,12 @@ export function apply(ctx: Context): void { /** * The session's standing mode override for an ordinary (non-escalating) * call: the `bash/sandbox-mode` fold of the calling agent's log, stamped - * onto the request so EXECUTION follows the same effective mode the prompt - * section states. Weakest precedence — an escalation grant (freshly - * approved for exactly this call) outranks it, and without either the - * executor's `resolve()` applies its configured default. Undefined for a - * non-sandboxing executor (nothing honors it) and for agent-less callers - * (no session to fold). + * onto the request so execution follows the fold without stating it in the + * prompt. Weakest precedence — an escalation grant (freshly approved for + * exactly this call) outranks it, and without either the executor's + * `resolve()` applies its configured default. Undefined for a non-sandboxing + * executor (nothing honors it) and for agent-less callers (no session to + * fold). */ const sessionOverride = (exec: ToolExecution): SandboxMode | undefined => defaultMode === undefined || exec.agent === undefined ? undefined : effectiveSandboxMode(exec.agent.session.events) diff --git a/packages/core/scope/README.md b/packages/core/scope/README.md index 64e2df91ea..f16d527bb4 100644 --- a/packages/core/scope/README.md +++ b/packages/core/scope/README.md @@ -23,7 +23,7 @@ Handing out a scoped context hands out the minting plugin's service-resolution s | Context surface | What the model sees | Token effect | |---|---|---| -| Per-agent visibility control | This package emits no text or schema. It routes scoped prompt sections, variables, tools, restrictions, and listeners to one agent: scoped registrations can shadow same-named globals, while restrictions filter global tools before scope-local tools are merged. All disappear with that agent. This is request composition, not authority confinement. | Zero direct tokens. It can add, replace, or remove whole contributions for one agent without changing another agent's request. | +| Per-agent visibility control | This package emits no text or schema. It routes scoped prompt sections, variables, tools, restrictions, and listeners to one agent: scoped registrations can shadow same-named globals, while restrictions filter global tool schemas and Code Mode bindings before scope-local tools are merged. Restrictions do not filter independently registered prompt sections. All scoped state disappears with that agent. This is request composition, not authority confinement. | Zero direct tokens. Scoped registrations can add or replace whole contributions for one agent; restrictions remove schema entries or SDK bindings and reduce that agent's repeated envelope cost without changing another agent's request. | ## Known Limitations and Deferred Work diff --git a/packages/core/system-prompt/README.md b/packages/core/system-prompt/README.md index 9e1d8d5032..f07cced7ec 100644 --- a/packages/core/system-prompt/README.md +++ b/packages/core/system-prompt/README.md @@ -45,7 +45,7 @@ Design rationale: [the prompt-variables RFC](../../../docs/rfc/implemented/archi | Context surface | What the model sees | Token effect | |---|---|---| | System prompt | Every assembly starts with `You are an AI agent powered by the DeepSeek Harness SDK.`, then the configured persona and ordered plugin sections after strict variable interpolation. Empty sections disappear; scoped sections and variables can shadow globals for one agent. The final `system-prompt/assemble` waterfall result is authoritative, so an expert listener's changes determine the delivered prompt and tool schemas. | Identity is a fixed per-request cost. Persona and plugin text are repeated per request and scale with their rendered content. | -| Tool schemas | The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent; reordering changes cache shape but not semantic content. | +| Tool schemas | The model receives the collected, per-agent-visible tool names, descriptions, and JSON schemas in configured or lexicographic order after restrictions and assembly interception. Sections and schema providers are separate assembly inputs, so a tool restriction does not remove independently registered guidance. | Schema tokens repeat on every request. Restricting a tool removes its entire schema cost for that agent but not a separate prompt section; reordering changes cache shape but not semantic content. | ## Known Limitations and Deferred Work diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index 252ed65580..ff09be73ee 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -142,7 +142,7 @@ The wire collapse is the registry's own contribution (`systemPrompt.tools()` is | Context surface | What the model sees | Token effect | |---|---|---| -| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead contributes one reserved `run_code` wire schema and a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's end-tool set without filtering the transport. The expert `system-prompt/assemble` waterfall can replace the final assembly and then owns preserving Code Mode protocol coherence. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | +| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead contributes one reserved `run_code` wire schema and a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's end-tool set and registry-owned SDK without filtering the transport or another plugin's independently registered prompt sections. The expert `system-prompt/assemble` waterfall can replace the final assembly and then owns preserving Code Mode protocol coherence. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | | Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | ## Known Limitations and Deferred Work diff --git a/packages/fs/tool-fs/README.md b/packages/fs/tool-fs/README.md index c955956f55..0f0b13ac8f 100644 --- a/packages/fs/tool-fs/README.md +++ b/packages/fs/tool-fs/README.md @@ -52,7 +52,7 @@ The read rendering (line windowing + output formatting) lives in `src/read-rende | Context surface | What the model sees | Token effect | |---|---|---| -| System prompt | Visible agents receive three short sections explaining line-windowed reads, whole-file writes, literal edits, and the default read-before-mutate habit. | Fixed guidance cost per request while the tools are visible. | +| System prompt | Every request in this plugin's registration scope receives three short sections explaining line-windowed reads, whole-file writes, literal edits, and the default read-before-mutate habit. Scoped tool restrictions can hide schemas without removing these independently registered sections. | Fixed guidance cost per request while the plugin is active, even when a restriction hides one or more tools. | | Tool schemas | The model sees `read`, `write`, and `edit` with their snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. | | Tool-call history and results | Read returns numbered UTF-8 lines and a pagination or cap footer; write and edit return concise success text or structured errors. The model-emitted write or edit content also remains in the assistant tool-call arguments. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`. Call arguments and results are resent until compaction, so large write payloads can dominate history even though the success result is small. | diff --git a/packages/subagent/subagent-fork/README.md b/packages/subagent/subagent-fork/README.md index 930c50836b..bafd7792a2 100644 --- a/packages/subagent/subagent-fork/README.md +++ b/packages/subagent/subagent-fork/README.md @@ -27,7 +27,7 @@ See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, m | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent history | The child receives the parent's balanced completed-turn surface prefix, then the new task. Configured persona and tool restrictions compose only in the child's fresh scope; the parent's tool view and authority are not inherited. An optional structured-output request adds its child-only contract. The parent's current in-flight turn is excluded. | Forking duplicates the retained completed history into a separate child's requests; the child then accumulates its own tokens independently. A first-turn fork has no inherited history. | +| Child-agent history and envelope | The child receives the parent's balanced completed-turn surface prefix, then the new task. A configured persona shadows prompt text in the child's fresh scope; a tool restriction filters its global wire schemas, executable lookup, and Code Mode SDK bindings but not standalone guidance. The parent's tool view and authority are not inherited. An optional structured-output request adds its child-only contract. The parent's current in-flight turn is excluded. | Forking duplicates retained completed history into separate child requests; the child then accumulates its own tokens independently. Persona changes repeated prompt cost, filtering changes schema or generated SDK cost, and a first-turn fork has no inherited history. | | Parent tool result, indirectly | The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work. | Parent input grows by one data-dependent final result retained until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md index c877ee3284..0d7c2886b5 100644 --- a/packages/subagent/subagent-inprocess/README.md +++ b/packages/subagent/subagent-inprocess/README.md @@ -44,7 +44,7 @@ A clean turn that never commits the required structured value reports `error`; t | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent request | The shared driver sends the task as the child's user message and, when requested, composes persona and global-tool restrictions in the unpublished child's fresh scope; parent restrictions are not inherited. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. Optional persona, filtering, and structured-output changes affect only that child; structured output adds fixed instruction and capability tokens for the run. | +| Child-agent request | The shared driver sends the task as the child's user message and, when requested, shadows the persona and restricts global tool schemas, lookup, execution, and Code Mode SDK bindings in the unpublished child's fresh scope; parent restrictions are not inherited, and standalone tool-guidance sections remain. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. A persona changes repeated prompt text; filtering changes schema or generated SDK cost but not independently registered guidance. Structured output adds fixed instruction and capability tokens for that child. | | Parent result, indirectly | The driver extracts only the child's own last assistant output or captured structured value; seeded parent messages and intermediate child work do not become the result. | The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index 8ad605d68b..78ecb4c035 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -22,7 +22,7 @@ Spawn advertises `{ outputSchema: true, depthLimit: true, toolFilter: true, pers | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the globally composed prompt and tools after any configured child-scoped persona shadow and global-tool restriction. It receives zero parent conversation messages; the filter is visibility/composition, not an authority grant inherited from the parent. | The child pays for a new independent context and history; no parent-history tokens are duplicated. Persona or filtering changes only this child's repeated prompt/schema cost. | +| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the global prompt with any configured child-scoped persona shadow. A tool filter removes global wire schemas, executable lookup, and Code Mode SDK bindings for that child but leaves independently registered guidance. It receives zero parent conversation messages; the filter is visibility/composition, not an authority grant inherited from the parent. | The child pays for a new independent context and history; no parent-history tokens are duplicated. Persona changes this child's repeated prompt cost, while filtering changes its schema or generated SDK cost. | | Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error. | Parent input grows by one data-dependent result retained until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index 8c0f5460d4..416c24af5a 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -31,7 +31,7 @@ A non-`completed` stop reason becomes an `isError` tool result; partial child ou | Context surface | What the model sees | Token effect | |---|---|---| -| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. The filter changes the child's visible global tools, not an inherited authority ceiling. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | +| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. For a capable in-process provider, the filter changes the child's global wire schemas, lookup, execution, and Code Mode SDK bindings, not standalone guidance or an inherited authority ceiling. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | | Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. The result contains only the child's final text or a stop-reason error, never intermediate child steps. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | ## Known Limitations and Deferred Work diff --git a/packages/web/tool-web/README.md b/packages/web/tool-web/README.md index 9b821aaf82..a8b49180f8 100644 --- a/packages/web/tool-web/README.md +++ b/packages/web/tool-web/README.md @@ -38,8 +38,8 @@ The tool never calls a provider's `status()` and never enumerates providers — | Context surface | What the model sees | Token effect | |---|---|---| -| System prompt | Each enabled tool adds one short section: search guidance says to discover current sources and follow with fetch; fetch guidance says to retrieve a specific HTTP(S) URL and cite it. | Fixed guidance cost per request for each enabled tool. | -| Tool schemas | According to config, the model sees `web_search(query)`, `web_fetch(url)`, or both. Result-count and timeout budgets are deployment settings, not model arguments. | Fixed schema cost per request; disabling a tool removes its schema and guidance. | +| System prompt | Each config-enabled tool adds one short section: search guidance says to discover current sources and follow with fetch; fetch guidance says to retrieve a specific HTTP(S) URL and cite it. A scoped tool restriction does not remove these independently registered sections. | Fixed guidance cost per request for each config-enabled tool, even when a restriction hides its schema. | +| Tool schemas | According to config and scoped restrictions, the model sees `web_search(query)`, `web_fetch(url)`, or both. Result-count and timeout budgets are deployment settings, not model arguments. | Fixed schema cost per request; config disablement removes both schema and guidance, while a scoped restriction removes only the schema. | | Tool-call history and results | Search returns an optional answer and bounded source entries; fetch returns status plus decoded text or markdown-shaped HTML, or a structured error. Queries and URLs remain in call history. | Data-dependent results are resent until compaction. Search sources are capped by `searchMaxResults`; fetch providers cap body size, and timeout policy can replace a late result with a short error. | ## Known Limitations and Deferred Work diff --git a/packages/workflow/tool-workflow/README.md b/packages/workflow/tool-workflow/README.md index 83b46a6852..9fd79b5d52 100644 --- a/packages/workflow/tool-workflow/README.md +++ b/packages/workflow/tool-workflow/README.md @@ -1,6 +1,6 @@ # @deepseek-ai/dsh-tool-workflow -The model-facing **`workflow` tool**: run a JavaScript orchestration script that fans out subagents, and return the script's final value. Pure schema + lifecycle shaping over [`ctx.workflows`](../workflow/README.md) — script parsing, execution, caps, and cancellation live behind the seam, so a hardened engine swaps in without touching what the model sees. +The model-facing **`workflow` tool**: run a JavaScript orchestration script that fans out subagents, and return the script's final value. This package owns schema and lifecycle shaping over [`ctx.workflows`](../workflow/README.md); script parsing, execution, caps, and cancellation live behind the seam, while the consumer retains ownership of the parent-facing schema and result envelope. ## What the model sees @@ -25,7 +25,8 @@ Decided up front (per the [render-intent RFC](../../../docs/rfc/implemented/arch | Context surface | What the model sees | Token effect | |---|---|---| -| System prompt and tool schema | The parent model receives a short use-only-for-large-orchestration section plus the `workflow` schema. The schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial but fixed per-request guidance and schema cost while visible. | +| System prompt | Every parent request in this plugin's registration scope receives a short use-only-for-large-orchestration section. A scoped tool restriction can hide the schema without removing this independently registered guidance. | Small fixed guidance cost per request while the plugin is active. | +| Tool schema | When visible, the `workflow` schema description carries the complete JavaScript hook and metadata contract; the model submits script, metadata, and optional args. | Substantial fixed schema cost on each request where the tool is visible. | | Tool-call history and result | The full model-written script, metadata, and args remain in the assistant tool call. The result contains the workflow name, child count, and final JSON value or a shaped error; intermediate child messages are omitted. | Call tokens can be large and remain until compaction. Result rendering is capped by `maxResultChars`; child-model tokens are separate from the parent's retained context. | ## Known Limitations and Deferred Work diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 3144c0e560..33f383b4f1 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -10,16 +10,41 @@ import { relative, resolve } from 'node:path' const root = resolve(import.meta.dirname, '..') const HEADING = '## Model Experience' -const HEADING_PATTERN = /^## Model Experience$/gm const LIMITATIONS_HEADING = '## Known Limitations and Deferred Work' const TABLE_HEADER = '| Context surface | What the model sees | Token effect |' const TABLE_DIVIDER = '|---|---|---|' +const H2_HEADING = /^## .+$/ interface Failure { path: string message: string } +interface Line { + index: number + raw: string +} + +/** Split Markdown into prose lines, excluding fenced code that may quote the contract. */ +function proseLines(text: string): Line[] { + let fence: { marker: '`' | '~'; length: number } | undefined + const kept: Line[] = [] + text.split('\n').forEach((raw, i) => { + const token = /^ {0,3}(`{3,}|~{3,})/.exec(raw)?.[1] + if (token !== undefined) { + const marker = token[0] as '`' | '~' + if (fence === undefined) { + fence = { marker, length: token.length } + } else if (marker === fence.marker && token.length >= fence.length) { + fence = undefined + } + return + } + if (fence === undefined) kept.push({ index: i + 1, raw }) + }) + return kept +} + const failures: Failure[] = [] const packageJsons = globSync('packages/*/*/package.json', { cwd: root }).sort() @@ -31,25 +56,20 @@ for (const packageJson of packageJsons) { continue } - const source = readFileSync(abs, 'utf8') - const matches = [...source.matchAll(HEADING_PATTERN)] - if (matches.length !== 1) { + const lines = proseLines(readFileSync(abs, 'utf8')) + const h2Headings = lines.filter(line => H2_HEADING.test(line.raw)) + const modelHeadings = h2Headings.filter(line => line.raw === HEADING) + if (modelHeadings.length !== 1) { failures.push({ path: readme, - message: matches.length === 0 ? `missing ${HEADING}` : `contains ${matches.length} copies of ${HEADING}`, + message: modelHeadings.length === 0 ? `missing ${HEADING}` : `contains ${modelHeadings.length} copies of ${HEADING}`, }) continue } - const match = matches[0] - if (match?.index === undefined) { - failures.push({ path: readme, message: `could not locate ${HEADING}` }) - continue - } - const headingIndex = match.index - const h2Headings = [...source.matchAll(/^## .+$/gm)] - const modelH2Index = h2Headings.findIndex(heading => heading.index === headingIndex) - const limitationsH2Index = h2Headings.findIndex(heading => heading[0] === LIMITATIONS_HEADING) + const modelHeading = modelHeadings[0] as Line + const modelH2Index = h2Headings.indexOf(modelHeading) + const limitationsH2Index = h2Headings.findIndex(heading => heading.raw === LIMITATIONS_HEADING) if (limitationsH2Index >= 0) { if (modelH2Index !== h2Headings.length - 2 || limitationsH2Index !== h2Headings.length - 1) { failures.push({ @@ -63,25 +83,30 @@ for (const packageJson of packageJsons) { continue } - const bodyStart = headingIndex + HEADING.length - const nextHeadingOffset = source.slice(bodyStart).search(/^## /m) - const section = source.slice(bodyStart, nextHeadingOffset < 0 ? undefined : bodyStart + nextHeadingOffset) - const lines = section.split('\n') - const headerIndex = lines.indexOf(TABLE_HEADER) - if (headerIndex < 0 || lines[headerIndex + 1] !== TABLE_DIVIDER) { + const body = lines.slice(lines.indexOf(modelHeading) + 1) + const nextH2 = body.findIndex(line => H2_HEADING.test(line.raw)) + const section = nextH2 < 0 ? body : body.slice(0, nextH2) + const headers = section.filter(line => line.raw === TABLE_HEADER) + const header = headers[0] + const headerIndex = header === undefined ? -1 : section.indexOf(header) + if (headers.length !== 1 || headerIndex < 0 || section[headerIndex + 1]?.raw !== TABLE_DIVIDER) { failures.push({ path: readme, message: `must contain the exact table header ${TABLE_HEADER}` }) continue } - const rows = lines.slice(headerIndex + 2).filter(line => line.startsWith('|')) + const rows: Line[] = [] + for (const line of section.slice(headerIndex + 2)) { + if (!line.raw.startsWith('|')) break + rows.push(line) + } if (rows.length === 0) { failures.push({ path: readme, message: 'Model Experience table must contain at least one data row' }) continue } for (const row of rows) { - const cells = row.split('|').slice(1, -1).map(cell => cell.trim()) + const cells = row.raw.split('|').slice(1, -1).map(cell => cell.trim()) if (cells.length !== 3 || cells.some(cell => cell.length === 0)) { - failures.push({ path: readme, message: `invalid three-column Model Experience row: ${row}` }) + failures.push({ path: readme, message: `line ${row.index}: invalid three-column Model Experience row: ${row.raw}` }) } } } From 7195d9eda2305ad08e2f5f02e5116f69ac34ebf9 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:47:46 +0800 Subject: [PATCH 25/38] docs: gate concise model experience summaries --- docs/AGENTS.md | 4 +- docs/cookbook/adding-a-package.md | 6 +- ...07-12-package-model-experience-contract.md | 11 +-- packages/bash/bash/README.md | 4 +- packages/code-runtime/code-runtime/README.md | 4 +- packages/fs/fs/README.md | 4 +- packages/hooks/hook-protocol/README.md | 4 +- packages/sandbox/sandbox/README.md | 4 +- packages/skill/skill/README.md | 4 +- .../subagent/subagent-subprocess/README.md | 4 +- packages/subagent/subagent/README.md | 4 +- packages/support/acp-snapshot/README.md | 4 +- packages/support/invariants/README.md | 4 +- packages/ui/app-boot/README.md | 4 +- packages/ui/user-interaction/README.md | 4 +- packages/util/brand/README.md | 4 +- packages/util/timeout/README.md | 4 +- packages/web/web/README.md | 4 +- packages/workflow/workflow/README.md | 4 +- .../verify-package-readme-model-experience.ts | 80 ++++++++++++++++++- 20 files changed, 105 insertions(+), 60 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index a837908d90..d051ac3e13 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -35,12 +35,12 @@ Placement: bugs → postmortems; rationale → RFCs; procedures → cookbooks; t ## Package Model Experience -Every package README ends with this table immediately before `## Known Limitations and Deferred Work`; [allowlisted packages](../scripts/verify-readme-limitations.ts) end after it: +Every package README ends with `## Model Experience` immediately before `## Known Limitations and Deferred Work`; [allowlisted no-limitations packages](../scripts/verify-readme-limitations.ts) end after Model Experience. Packages with direct, multi-surface, conditional, capped, or lifetime effects use this table: | Context surface | What the model sees | Token effect | |---|---|---| -Rows state what reaches which model and classify token cost or lifetime. Treat prompt text and tool schemas separately when their visibility conditions differ; zero-direct rows name the indirect path. `verify-package-readme-model-experience` gates shape and order, while review owns accuracy ([rationale](rfc/implemented/process/2026-07-12-package-model-experience-contract.md)). +Rows state what reaches which model and classify token cost or lifetime; prompt text and tool schemas stay separate when visibility differs. Packages explicitly classified in [`SENTENCE_MODEL_EXPERIENCE`](../scripts/verify-package-readme-model-experience.ts) instead carry one sentence beginning `None, as ` or `Indirectly, through ` and ending with a period. The verifier gates the sentence allowlist, table shape, and section order; review owns factual accuracy ([rationale](rfc/implemented/process/2026-07-12-package-model-experience-contract.md)). ## Wordcount Budgets diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index d8def5ebfe..b87baf8aba 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -14,7 +14,7 @@ packages/// src/index.ts # service default export or plugin (name/inject/apply/Config) tests/.spec.ts README.md # service API, events, extension points, design notes, - # + the required Model Experience table + # + the gated Model Experience table or short sentence # + the gated "Known Limitations and Deferred Work" section # (or a whitelist entry in scripts/verify-readme-limitations.ts) ``` @@ -56,7 +56,9 @@ Keep package-specific service API, config, events, extension points, and design - **Consumer-visible gap** — exact boundary or deliberately deferred work. ``` -Fill [Model Experience](../AGENTS.md#package-model-experience) from the implementation: name every direct request contribution and token-growth condition, or state zero direct tokens and its indirect path. Do not infer prompt visibility from tool-schema visibility; independently registered guidance can remain after a scoped tool restriction. Every package participates, including type-only libraries and backend seams. A package with genuinely no limitations joins the justified allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. +Fill [Model Experience](../AGENTS.md#package-model-experience) from the implementation. Direct, multi-surface, conditional, capped, or lifetime effects use the table; name each request contribution and token-growth condition. Do not infer prompt visibility from tool-schema visibility because independently registered guidance can remain after a scoped tool restriction. + +An audited package with no context effect or one simple consumer-owned path joins [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and replaces the table with one line beginning `None, as ` or `Indirectly, through `. Every package outside that allowlist must keep the exact table. A package with genuinely no limitations joins the separate allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. ## 5. Verify diff --git a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md index 8fb3e45a83..76dcb15763 100644 --- a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md +++ b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md @@ -8,20 +8,21 @@ A package README can explain APIs and runtime mechanics without answering the qu ## Decision -Every workspace package README ends with the canonical [Model Experience table](../../../AGENTS.md#package-model-experience), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other. +Every workspace package README ends with the canonical [Model Experience section](../../../AGENTS.md#package-model-experience), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Packages with direct, multi-surface, conditional, capped, or lifetime effects use the three-column table. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other. -Every package participates. A service seam, storage backend, test helper, or type-only library that contributes no prompt text, tool schema, message, or auxiliary request records zero direct tokens and names the consumer or control path through which it can still change model-visible material. This explicit negative contract prevents readers from having to infer whether the section was forgotten. +Every package participates. A package with no model-context effect, or one simple effect rendered entirely by another package, can join the verifier's audited sentence allowlist. It then uses exactly one sentence beginning `None, as ` or `Indirectly, through ` instead of stretching a negative fact across a three-column table. Implementations that shape results, caps, history, lifetimes, or more than one request surface keep the table even when they add zero direct prompt tokens. -`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README, the canonical final-section order, one exact `## Model Experience` heading and three-column table header outside fenced code, and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns structural presence, shape, and order; implementation review owns coverage and the truth of the prose. +`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README and the canonical final-section order, and validates one of two package-classified bodies outside fenced code. An allowlisted package must carry exactly one sentence with its assigned prefix; every other package must carry only the exact three-column header and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns package classification, structural presence, shape, and order; implementation review owns coverage and the truth of the prose. ## Alternatives considered - **Document only packages that register prompts or tools** — rejected because backends, policy plugins, adapters, persistence, scoping, and compaction change the content or lifetime of tokens without owning a model-facing schema. - **Generate one central context-cost catalog from source** — rejected because an AST can find registrations but cannot infer semantic conditions such as history retention, output truncation, parent-versus-child visibility, or an auxiliary model boundary. The package README is the implementation-local contract; a central copy would add another drift surface. - **Require numeric token counts** — rejected because exact counts depend on the selected model tokenizer, adapter serialization, configuration, and runtime data. The stable contract is the growth shape: fixed per request, conditional per call, retained, replaced, capped, or zero-direct. -- **Allow zero-impact packages to omit the section** — rejected because absence is ambiguous between an audited zero and forgotten documentation. One explicit row is cheap and mechanically distinguishable. +- **Allow zero-impact packages to omit the section** — rejected because absence is ambiguous between an audited zero and forgotten documentation. One explicit sentence is cheap and mechanically distinguishable. +- **Require the full table for audited zero or simple indirect packages** — rejected because it spreads one fact across three cells and encourages repetitive prose. A gated sentence preserves explicit coverage without the ceremony. - **Convention without a gate** — rejected because a repo-wide contract must also cover every future package; review memory cannot reliably detect an omitted README section. ## Consequences -A reviewer can start at any package and see its contribution to the conversation model, child models, and auxiliary calls without reconstructing the full plugin graph. Token-budget work can distinguish repeated request overhead from data-dependent history, and agent-scoped changes have an explicit documentation checkpoint. Package authors pay for one small table and must update it whenever model-visible behavior changes. The table deliberately does not promise provider-exact token counts; measurements remain model- and workload-specific, while the documented growth and visibility contract stays stable. +A reviewer can start at any package and see its contribution to the conversation model, child models, and auxiliary calls without reconstructing the full plugin graph. Token-budget work can distinguish repeated request overhead from data-dependent history, and agent-scoped changes have an explicit documentation checkpoint. Package authors pay for one small table or one classified sentence and must update it whenever model-visible behavior changes. Tables deliberately do not promise provider-exact token counts; measurements remain model- and workload-specific, while the documented growth and visibility contract stays stable. diff --git a/packages/bash/bash/README.md b/packages/bash/bash/README.md index 97c1b91dff..ddb9b92d1d 100644 --- a/packages/bash/bash/README.md +++ b/packages/bash/bash/README.md @@ -38,9 +38,7 @@ The seam also owns the per-session mode override vocabulary (the sandbox RFC § ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This interface registers no prompt, tool schema, or message. `dsh-tool-bash` turns an implementation's stdout, stderr, task state, and sandbox facts into model-visible tool results and guidance. | Zero direct tokens. Result size and sandbox state affect input tokens only when a consumer renders them. | +Indirectly, through `dsh-tool-bash`, which turns executor output and sandbox facts into guidance and retained tool-result tokens. ## Known Limitations and Deferred Work diff --git a/packages/code-runtime/code-runtime/README.md b/packages/code-runtime/code-runtime/README.md index 9c256a31dc..94c3bad61c 100644 --- a/packages/code-runtime/code-runtime/README.md +++ b/packages/code-runtime/code-runtime/README.md @@ -20,9 +20,7 @@ Semantics every implementation must honor (contract details in the class JSDoc): ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The seam receives a program and host bindings but registers no prompt, schema, or message. Code Mode in `dsh-tools` exposes the SDK and `run_code`, then converts `CodeRunResult` into the outer tool result. | Zero direct tokens. Program logs, values, and failures affect the conversation only through the Code Mode consumer. | +Indirectly, through Code Mode in `dsh-tools`, which exposes `run_code` and returns program logs, values, or failures as retained tool-result tokens. ## Known Limitations and Deferred Work diff --git a/packages/fs/fs/README.md b/packages/fs/fs/README.md index 851a509cfd..bad4133b72 100644 --- a/packages/fs/fs/README.md +++ b/packages/fs/fs/README.md @@ -45,9 +45,7 @@ This package declares three events (see the generated [events catalog](../../../ ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The provider seam registers no prompt or tool. `dsh-tool-fs` converts provider text and structured `FsError` values into model-visible read, write, and edit results; policy listeners can change which outcome it receives. | Zero direct tokens. File content and errors enter context only through a consumer, whose window and byte caps determine result size. | +Indirectly, through `dsh-tool-fs`, which renders provider text and errors as bounded, retained filesystem tool results. ## Known Limitations and Deferred Work diff --git a/packages/hooks/hook-protocol/README.md b/packages/hooks/hook-protocol/README.md index 754d6ee6f1..e43aa85f5a 100644 --- a/packages/hooks/hook-protocol/README.md +++ b/packages/hooks/hook-protocol/README.md @@ -31,9 +31,7 @@ Like every event they must sit inside an open turn. The mid-turn points (`PreToo ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This library registers nothing. Its `hook/invoked` and `hook/result` events are log-only and do not enter derived messages; bridge packages decide whether parsed `additionalContext`, blocks, or continuation feedback reach the model. | Zero direct tokens. Persisted hook audit records add no context tokens. | +Indirectly, through `dsh-hooks-claude` and `dsh-hooks-codex`, which can turn parsed hook output into prompt context, blocked outcomes, or continuation feedback. ## Known Limitations and Deferred Work diff --git a/packages/sandbox/sandbox/README.md b/packages/sandbox/sandbox/README.md index 2a1eeb9e7c..8864897c84 100644 --- a/packages/sandbox/sandbox/README.md +++ b/packages/sandbox/sandbox/README.md @@ -12,9 +12,7 @@ Implementations: [`@deepseek-ai/dsh-sandbox-local`](../sandbox-local/) (Linux: ` ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This seam registers no prompt, schema, or message. A consumer may expose configured mode, enforcement, denial, or `SANDBOX_UNAVAILABLE` facts in its own guidance or result. | Zero direct tokens. Confinement changes model-visible text only through the consuming capability. | +Indirectly, through consumers such as `dsh-bash-sandbox`, which may expose enforcement, denial, or sandbox-unavailable facts in schemas or results. ## Known Limitations and Deferred Work diff --git a/packages/skill/skill/README.md b/packages/skill/skill/README.md index 39219396d6..5d25d1cb5d 100644 --- a/packages/skill/skill/README.md +++ b/packages/skill/skill/README.md @@ -37,9 +37,7 @@ The registry does not render model guidance or register model-facing tools. [`@d ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The registry renders nothing and registers no tool. `dsh-tool-skill` turns `list()` summaries into a session prefix and a selected `get()` body into a tool result; provider failures can remove entries from that request's catalog. | Zero direct tokens. Catalog size, descriptions, and loaded body length affect context only through the consumer. | +Indirectly, through `dsh-tool-skill`, which renders provider summaries into the session prefix and loaded instructions into retained tool results. ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-subprocess/README.md b/packages/subagent/subagent-subprocess/README.md index b09df7b2aa..a3c0905422 100644 --- a/packages/subagent/subagent-subprocess/README.md +++ b/packages/subagent/subagent-subprocess/README.md @@ -41,9 +41,7 @@ A per-run isolated config directory for an external CLI child (the target of `CL ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This process utility registers no provider, prompt, tool, or message. A consuming backend's child application decides the child's model context; environment scrubbing and isolated config directories prevent ambient credentials and user state from silently changing that composition. | Zero direct tokens. It can indirectly stabilize child context, but it adds no text to parent or child requests. | +Indirectly, through process-based subagent backends, whose child composition is constrained by credential scrubbing and isolated config directories. ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index 0bb0df7285..c85a6a4566 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -62,9 +62,7 @@ The current model-facing tool collects synchronously: it awaits the child result ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The provider registry registers no prompt or tool. Provider lifecycle makes a bound `dsh-tool-subagent` schema appear or disappear, and `inheritsParentContext` selects truthful fresh-versus-fork wording. Run events are observe-only. | Zero direct tokens. Child prompts and final results enter model contexts only through a provider and consumer. | +Indirectly, through `dsh-tool-subagent` and registered providers, which expose delegation schemas, child contexts, and retained parent results. ## Known Limitations and Deferred Work diff --git a/packages/support/acp-snapshot/README.md b/packages/support/acp-snapshot/README.md index 6aef2f3a4d..b9f51bb2b9 100644 --- a/packages/support/acp-snapshot/README.md +++ b/packages/support/acp-snapshot/README.md @@ -43,9 +43,7 @@ Constraints: `suite.ts` imports vitest, so the package is importable only inside ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None in production | The test harness records, normalizes, scrubs, and compares request headers and ACP transcripts but does not alter the agent's assembled context. Replay scenarios obtain assistant chunks from `dsh-llm-replay`; record mode uses the real composition. | Zero production tokens. Replay spends no provider tokens, while record mode pays the composition's ordinary model cost; golden scrubbing changes files only, never the live request. | +None, as this test-only harness records, normalizes, and compares ACP transcripts without changing the agent's assembled model request. ## Known Limitations and Deferred Work diff --git a/packages/support/invariants/README.md b/packages/support/invariants/README.md index b0b9e29668..7a4f9fc263 100644 --- a/packages/support/invariants/README.md +++ b/packages/support/invariants/README.md @@ -53,9 +53,7 @@ A seeded or forked session arrives with events already in its log because constr ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None | The plugin observes and validates session events, agent states, and frozen model requests; it does not rewrite a prompt, schema, message, or stream. An invariant failure aborts the faulty turn instead of adding guidance. | Zero model tokens when checks pass; a failure prevents or ends a request rather than contributing context. | +None, as this observer only validates events and frozen requests and never rewrites prompts, schemas, messages, or streams. ## Known Limitations and Deferred Work diff --git a/packages/ui/app-boot/README.md b/packages/ui/app-boot/README.md index b9fc41c130..fdea712274 100644 --- a/packages/ui/app-boot/README.md +++ b/packages/ui/app-boot/README.md @@ -16,9 +16,7 @@ Bare plugin specifiers in a config (`@deepseek-ai/dsh-*`) resolve through the co ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | Boot and environment helpers load the configured plugin tree but register no prompt, schema, or message of their own. `.env`, loader diagnostics, and config-path selection are process concerns, not model context. | Zero direct tokens. The selected configuration indirectly determines which other packages contribute context. | +Indirectly, through the plugin tree it loads, which determines the prompts, schemas, messages, and model adapter in the resulting application. ## Known Limitations and Deferred Work diff --git a/packages/ui/user-interaction/README.md b/packages/ui/user-interaction/README.md index 378e7a626f..b4a93ce5bf 100644 --- a/packages/ui/user-interaction/README.md +++ b/packages/ui/user-interaction/README.md @@ -25,9 +25,7 @@ This is the interface package. Model-facing consumers such as `@deepseek-ai/dsh- ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This UI-neutral seam registers no prompt or tool. A consumer such as `dsh-tool-ask-user` turns a model call into an `ask()` request and converts the provider's human answer into a model-visible tool result. | Zero direct tokens. Question and answer size affect context only through the consumer. | +Indirectly, through consumers such as `dsh-tool-ask-user`, which return human answers as retained tool-result tokens. ## Known Limitations and Deferred Work diff --git a/packages/util/brand/README.md b/packages/util/brand/README.md index 5c7fa07272..47c349ffd5 100644 --- a/packages/util/brand/README.md +++ b/packages/util/brand/README.md @@ -27,6 +27,4 @@ This package owns ONLY the primitive — no concrete id, no runtime code beyond ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None | `Branded` is erased at compile time and registers no runtime plugin, prompt, schema, event, or message. Branded ids serialize exactly as their underlying strings when another package logs or renders them. | Zero direct or indirect token overhead beyond the string another package already chose to expose. | +None, as `Branded` is erased at compile time and registers no runtime behavior. diff --git a/packages/util/timeout/README.md b/packages/util/timeout/README.md index 86c76ca5b4..ff56b65535 100644 --- a/packages/util/timeout/README.md +++ b/packages/util/timeout/README.md @@ -43,9 +43,7 @@ Local file `read`/`write`/`edit` take no `timeoutMs`: a syscall is best-effort-a ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | This library only creates and classifies abort signals. It registers no prompt, schema, or message; consumers decide whether a timeout becomes a marker, a structured error, or no model-visible change. | Zero direct tokens. It can indirectly cap or replace a consumer's result when that consumer renders a timeout. | +Indirectly, through consumers such as `dsh-timeout-policy`, which may replace a provider result with a retained timeout error or suppress a late result. ## Known Limitations and Deferred Work diff --git a/packages/web/web/README.md b/packages/web/web/README.md index 3582ca9d1d..514a6aa51d 100644 --- a/packages/web/web/README.md +++ b/packages/web/web/README.md @@ -45,9 +45,7 @@ The failure branches throw `WebError`, whose structured code (plus message detai ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The seam registers providers, not tools or prompt text. `dsh-tool-web` renders normalized search answers, sources, fetched bodies, and structured `WebError` values. Provider selection details stay internal except for an execution error. | Zero direct tokens. The seam indirectly bounds search result tokens by truncating sources to `maxResults`; all rendered size comes through a consumer. | +Indirectly, through `dsh-tool-web`, which renders normalized search or fetch data and errors as bounded, retained tool results. ## Known Limitations and Deferred Work diff --git a/packages/workflow/workflow/README.md b/packages/workflow/workflow/README.md index 1c02267ed4..397822108e 100644 --- a/packages/workflow/workflow/README.md +++ b/packages/workflow/workflow/README.md @@ -40,9 +40,7 @@ A child that resolves normally with a non-completed stop reason is not an infras ## Model Experience -| Context surface | What the model sees | Token effect | -|---|---|---| -| None directly | The service seam and `workflow/*` observer events register no prompt, schema, or message. `dsh-tool-workflow` renders the parent-facing contract and final value; an engine decides which child prompts run. | Zero direct tokens. Parent result and child contexts affect tokens only through the consumer and implementation. | +Indirectly, through `dsh-tool-workflow` and a workflow engine, which create child-agent requests and return a retained parent tool result. ## Known Limitations and Deferred Work diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 33f383b4f1..2d517c9368 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -1,6 +1,8 @@ /** * Doc-sync gate: require every workspace package README to explain its exact - * model-visible context surface and token behavior in the canonical table. + * model-visible context surface and token behavior. Most packages require the + * canonical table; an audited allowlist requires one concise zero-effect or + * indirect-only sentence instead. * * Run: `tsx scripts/verify-package-readme-model-experience.ts`. */ @@ -15,6 +17,37 @@ const TABLE_HEADER = '| Context surface | What the model sees | Token effect |' const TABLE_DIVIDER = '|---|---|---|' const H2_HEADING = /^## .+$/ +type SentenceKind = 'none' | 'indirect' + +interface SentenceContract { + kind: SentenceKind + reason: string +} + +/** + * Packages whose Model Experience is simple enough for one gated sentence. + * Every other package must carry the canonical table. A package moves on or + * off this list in the same change that changes its context behavior. + */ +const SENTENCE_MODEL_EXPERIENCE: Readonly> = { + 'packages/bash/bash': { kind: 'indirect', reason: 'The service interface delegates all model rendering to dsh-tool-bash.' }, + 'packages/code-runtime/code-runtime': { kind: 'indirect', reason: 'The service interface delegates model rendering to Code Mode in dsh-tools.' }, + 'packages/fs/fs': { kind: 'indirect', reason: 'The service interface delegates model rendering to dsh-tool-fs.' }, + 'packages/hooks/hook-protocol': { kind: 'indirect', reason: 'Only the hook bridge plugins render decoded hook output to a model.' }, + 'packages/sandbox/sandbox': { kind: 'indirect', reason: 'Only sandbox-consuming capabilities render enforcement facts.' }, + 'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' }, + 'packages/subagent/subagent': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-subagent.' }, + 'packages/subagent/subagent-subprocess': { kind: 'indirect', reason: 'Only process-based subagent backends compose a child model request.' }, + 'packages/support/acp-snapshot': { kind: 'none', reason: 'The test harness observes and normalizes transcripts without changing live requests.' }, + 'packages/support/invariants': { kind: 'none', reason: 'The observer validates requests but never rewrites their context.' }, + 'packages/ui/app-boot': { kind: 'indirect', reason: 'Only the loaded plugin tree contributes model context.' }, + 'packages/ui/user-interaction': { kind: 'indirect', reason: 'Only a model-facing consumer renders human answers.' }, + 'packages/util/brand': { kind: 'none', reason: 'The type-only primitive is erased at compile time.' }, + 'packages/util/timeout': { kind: 'indirect', reason: 'Only timeout consumers render timeout outcomes.' }, + 'packages/web/web': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-web.' }, + 'packages/workflow/workflow': { kind: 'indirect', reason: 'The service delegates parent and child model rendering to its consumer and engine.' }, +} + interface Failure { path: string message: string @@ -47,8 +80,22 @@ function proseLines(text: string): Line[] { const failures: Failure[] = [] const packageJsons = globSync('packages/*/*/package.json', { cwd: root }).sort() +const scannedPackages = new Set(packageJsons.map(path => path.slice(0, -'/package.json'.length))) +let tableCount = 0 +let noneCount = 0 +let indirectCount = 0 + +for (const [pkg, contract] of Object.entries(SENTENCE_MODEL_EXPERIENCE)) { + if (!scannedPackages.has(pkg)) { + failures.push({ path: `${pkg}/README.md`, message: 'sentence allowlist entry does not name a scanned package' }) + } + if (contract.reason.trim().length === 0) { + failures.push({ path: `${pkg}/README.md`, message: 'sentence allowlist entry must justify why a table is unnecessary' }) + } +} for (const packageJson of packageJsons) { + const pkg = packageJson.slice(0, -'/package.json'.length) const readme = packageJson.replace(/package\.json$/, 'README.md') const abs = resolve(root, readme) if (!existsSync(abs)) { @@ -86,10 +133,30 @@ for (const packageJson of packageJsons) { const body = lines.slice(lines.indexOf(modelHeading) + 1) const nextH2 = body.findIndex(line => H2_HEADING.test(line.raw)) const section = nextH2 < 0 ? body : body.slice(0, nextH2) + const content = section.filter(line => line.raw.trim().length > 0) + const sentenceContract = SENTENCE_MODEL_EXPERIENCE[pkg] + if (sentenceContract !== undefined) { + const pattern = sentenceContract.kind === 'none' ? /^None, as .+\.$/ : /^Indirectly, through .+\.$/ + if (content.length !== 1 || !pattern.test(content[0]?.raw ?? '')) { + const prefix = sentenceContract.kind === 'none' ? 'None, as ' : 'Indirectly, through ' + failures.push({ path: readme, message: `must contain exactly one sentence beginning ${JSON.stringify(prefix)} and ending with a period` }) + continue + } + if (sentenceContract.kind === 'none') noneCount += 1 + else indirectCount += 1 + continue + } + + const shortSentence = content.find(line => /^None, as |^Indirectly, through /.test(line.raw)) + if (shortSentence !== undefined) { + failures.push({ path: readme, message: `line ${shortSentence.index}: short Model Experience form requires an audited entry in SENTENCE_MODEL_EXPERIENCE` }) + continue + } + const headers = section.filter(line => line.raw === TABLE_HEADER) const header = headers[0] const headerIndex = header === undefined ? -1 : section.indexOf(header) - if (headers.length !== 1 || headerIndex < 0 || section[headerIndex + 1]?.raw !== TABLE_DIVIDER) { + if (header === undefined || headers.length !== 1 || headerIndex < 0 || section[headerIndex + 1]?.raw !== TABLE_DIVIDER) { failures.push({ path: readme, message: `must contain the exact table header ${TABLE_HEADER}` }) continue } @@ -109,10 +176,17 @@ for (const packageJson of packageJsons) { failures.push({ path: readme, message: `line ${row.index}: invalid three-column Model Experience row: ${row.raw}` }) } } + const tableLines = new Set([header, section[headerIndex + 1], ...rows]) + const extra = content.find(line => !tableLines.has(line)) + if (extra !== undefined) { + failures.push({ path: readme, message: `line ${extra.index}: Model Experience table section contains non-table content: ${extra.raw}` }) + continue + } + tableCount += 1 } if (failures.length === 0) { - console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) carry the canonical ${HEADING} table.`) + console.log(`verify-package-readme-model-experience: ${packageJsons.length} README(s) checked (${tableCount} tables, ${noneCount} none, ${indirectCount} indirect), all conform.`) process.exit(0) } From 3fc2a4ca8201ca781ab7e55827c287f3ff3b7474 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:13:46 +0800 Subject: [PATCH 26/38] fix: harden model experience documentation gate --- docs/AGENTS.md | 12 +-------- docs/cookbook/adding-a-package.md | 6 ++--- ...07-12-package-model-experience-contract.md | 2 +- packages/AGENTS.md | 2 +- packages/README.md | 2 +- packages/bash/bash-sandbox/README.md | 5 ++-- .../verify-package-readme-model-experience.ts | 27 +++++++++++++++++-- 7 files changed, 35 insertions(+), 21 deletions(-) diff --git a/docs/AGENTS.md b/docs/AGENTS.md index d051ac3e13..94438f06b1 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -15,7 +15,7 @@ Each fact has one owning tier; other tiers link to it. Restated rules drift, whi | [rfc/](rfc/README.md) | Decision records: the why and the what-was-given-up; `implemented/` RFCs describe shipped reality in present tense | Migration plans, test checklists, and spec-speak ("should…") once the decision has shipped | | [postmortem/](postmortem/README.md) | Incident stories — the only tier where war-story narrative belongs | — | | [cookbook/](cookbook/adding-a-package.md) | Step-by-step how-tos with numbered verify steps | Design rationale (→ the RFC each guide links) | -| Package README | The per-package contract: config, semantics, limitations, extension points, and [Model Experience](#package-model-experience) | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns | +| Package README | The per-package contract: config, semantics, limitations, extension points, and [Model Experience](cookbook/adding-a-package.md#4-write-the-package-readme) | JSDoc restatement, generated-catalog restatement (event/tool tables), other packages' concerns | | [development.md](development.md) | First-stop contributor onboarding: local setup, daily workflow, and CI shape at summary level; a bilingual pair under the [i18n contract](i18n/README.md) | Runtime/version rationale (→ RFCs), gate-by-gate enumerations that drift from `package.json` scripts | | Generated catalogs: [cordis events](cordis-catalog/events.md), [cordis services](cordis-catalog/services.md), [tool-catalog](tool-catalog.md), [config-catalog](config-catalog.md), [persistence-catalog](persistence-catalog.md), [module-graph.md](module-graph.md) | Exhaustive enumerations regenerated from source, freshness-gated | Hand edits of any kind | | Skills (`.agents/skills/`) | Workflows: how to carry out a recurring task against the contracts | The contracts themselves (→ docs) | @@ -28,20 +28,10 @@ Placement: bugs → postmortems; rationale → RFCs; procedures → cookbooks; t - **A decision worth re-litigating gets an RFC in the same PR.** The test: would a maintainer six months out ask "why was it done this way?" and find no answer in the code? If yes, write one ([when to write one](rfc/README.md)); mechanical or self-evident changes need none. - **One physical line per paragraph** (`verify-md-wrap`): the editor soft-wraps; hard breaks make a one-word edit re-diff the whole paragraph. Prose only — code blocks, tables, and list structure stay; code comments stay under the linter's column limit. - **Fenced `ts` blocks must compile** (`doc-typecheck`); a pasted type definition is fenced ` ```ts type-equiv ` and registered in the manifest so it cannot drift ([mechanics](development.md#documenting-types-verbatim-ts-type-equiv)). -- **Every new event's JSDoc carries an `@mode` tag** (emit | waterfall | parallel | serial); the catalog generator hard-errors without it. Write the JSDoc to stand alone — it becomes the catalog entry ([catalog RFC](rfc/implemented/process/2026-06-20-generated-cordis-catalog.md)). - **The [core-data-structures catalog](core-data-structures/core.md) updates in the same change** that reshapes a documented type. `verify-type-equiv` catches drifted pastes, not never-documented new types ([what counts as core](core-data-structures/core.md#what-counts-as-core)). - **Bilingual pairs update together**: editing either side obligates the counterpart and a re-record in the same change ([i18n contract](i18n/README.md)). - Your audience is professional programmers. Prefer concise and straight-forward English over metaphor. Do not overuse words like "gate", "vocabulary", "surface", "seams". -## Package Model Experience - -Every package README ends with `## Model Experience` immediately before `## Known Limitations and Deferred Work`; [allowlisted no-limitations packages](../scripts/verify-readme-limitations.ts) end after Model Experience. Packages with direct, multi-surface, conditional, capped, or lifetime effects use this table: - -| Context surface | What the model sees | Token effect | -|---|---|---| - -Rows state what reaches which model and classify token cost or lifetime; prompt text and tool schemas stay separate when visibility differs. Packages explicitly classified in [`SENTENCE_MODEL_EXPERIENCE`](../scripts/verify-package-readme-model-experience.ts) instead carry one sentence beginning `None, as ` or `Indirectly, through ` and ending with a period. The verifier gates the sentence allowlist, table shape, and section order; review owns factual accuracy ([rationale](rfc/implemented/process/2026-07-12-package-model-experience-contract.md)). - ## Wordcount Budgets Every PR has a lesson it wants to append, and without pressure nothing leaves. [scripts/doc-budgets.manifest.json](../scripts/doc-budgets.manifest.json) stores the allowed word-count ceiling for each budgeted standing doc; `pnpm run verify-doc-budgets` fails when a doc exceeds its ceiling or a budgeted file is missing. diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index b87baf8aba..8af4824310 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -49,16 +49,16 @@ Keep package-specific service API, config, events, extension points, and design | Context surface | What the model sees | Token effect | |---|---|---| -| Request surface and condition | Exact context visible to the model | Fixed, conditional, retained, replaced, capped, or zero-direct token effect | +| Request surface and condition | Verbatim stable text, or the exact data-dependent shape visible to the model | Fixed, conditional, retained, replaced, capped, or zero-direct token effect | ## Known Limitations and Deferred Work - **Consumer-visible gap** — exact boundary or deliberately deferred work. ``` -Fill [Model Experience](../AGENTS.md#package-model-experience) from the implementation. Direct, multi-surface, conditional, capped, or lifetime effects use the table; name each request contribution and token-growth condition. Do not infer prompt visibility from tool-schema visibility because independently registered guidance can remain after a scoped tool restriction. +Fill Model Experience from the implementation. Direct, multi-surface, conditional, capped, or lifetime effects use the table; name each request contribution and token-growth condition. Quote stable model-visible source literals verbatim in inline code, using named placeholders such as `` only for interpolated values. Summarize only data-dependent payloads, provider-owned text, or schemas too large to reproduce, and identify their exact shape and renderer. Do not infer prompt visibility from tool-schema visibility because independently registered guidance can remain after a scoped tool restriction. -An audited package with no context effect or one simple consumer-owned path joins [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and replaces the table with one line beginning `None, as ` or `Indirectly, through `. Every package outside that allowlist must keep the exact table. A package with genuinely no limitations joins the separate allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. +An audited package with no context effect or one simple consumer-owned path joins [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and replaces the table with one line beginning `None, as ` or `Indirectly, through `. Every package outside that allowlist must keep the exact table. A package with genuinely no limitations joins the separate allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. The [Model Experience RFC](../rfc/implemented/process/2026-07-12-package-model-experience-contract.md) records the rationale. ## 5. Verify diff --git a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md index 76dcb15763..bfe88952a9 100644 --- a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md +++ b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md @@ -8,7 +8,7 @@ A package README can explain APIs and runtime mechanics without answering the qu ## Decision -Every workspace package README ends with the canonical [Model Experience section](../../../AGENTS.md#package-model-experience), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Packages with direct, multi-surface, conditional, capped, or lifetime effects use the three-column table. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other. +Every workspace package README ends with the canonical [Model Experience section](../../../cookbook/adding-a-package.md#4-write-the-package-readme), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Packages with direct, multi-surface, conditional, capped, or lifetime effects use the three-column table. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. Stable source literals are quoted verbatim, with named placeholders only for interpolated values; summaries are reserved for data-dependent payloads, provider-owned text, or schemas too large to reproduce. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other. Every package participates. A package with no model-context effect, or one simple effect rendered entirely by another package, can join the verifier's audited sentence allowlist. It then uses exactly one sentence beginning `None, as ` or `Indirectly, through ` instead of stretching a negative fact across a three-column table. Implementations that shape results, caps, history, lifetimes, or more than one request surface keep the table even when they add zero direct prompt tokens. diff --git a/packages/AGENTS.md b/packages/AGENTS.md index e883361bf8..3e640683f3 100644 --- a/packages/AGENTS.md +++ b/packages/AGENTS.md @@ -14,5 +14,5 @@ Naming notes: - `src/types.ts` contains only types — no runtime code. - Tests live at package level under `tests/`, not `src/__tests__/`. - A package's README and JSDoc are part of the change: altered behavior (config keys, defaults, error codes, wire fields) updates them in the same commit. `doc-sync` gates what it can; prose accuracy stays on the author ([the documentation standard](../docs/AGENTS.md)). -- Package READMEs document model/token effects in [Model Experience](../docs/AGENTS.md#package-model-experience). +- Package READMEs document model/token effects using the [canonical Model Experience format](../docs/cookbook/adding-a-package.md#4-write-the-package-readme). - Package READMEs carry `## Known Limitations and Deferred Work` or a justified [allowlist entry](../scripts/verify-readme-limitations.ts) ([rationale](../docs/rfc/implemented/process/2026-07-10-readme-known-limitations-gate.md)). diff --git a/packages/README.md b/packages/README.md index 651ba370e6..58c8f47384 100644 --- a/packages/README.md +++ b/packages/README.md @@ -37,4 +37,4 @@ The inter-package dependency graph is generated: [docs/module-graph.md](../docs/ The rule it must obey: **extension plugins depend on interfaces, never on the concrete loop.** `dsh-agent-loop` is swappable — UI/hook/tool plugins keep working against the `dsh-agent` vocabulary if the loop is replaced. The sanctioned exception is a **composition/bundle** package like `dsh-agent-core`, whose whole job is to assemble the concrete spine: it depends on `dsh-agent-loop` (and the other concrete spine plugins) on purpose. The rule constrains plugins that EXTEND the system, not the bundle that COMPOSES it. A swappable capability splits into interface / implementation / consumer packages (the bash trio is the template — see [capability seams](../docs/rfc/implemented/architecture/2026-06-13-capability-seams.md)). -Each package has its own `README.md` with purpose, service API, events, extension points, deliberate non-goals, and the standard [Model Experience table](../docs/AGENTS.md#package-model-experience). +Each package has its own `README.md` with purpose, service API, events, extension points, deliberate non-goals, and the standard [Model Experience section](../docs/cookbook/adding-a-package.md#4-write-the-package-readme). diff --git a/packages/bash/bash-sandbox/README.md b/packages/bash/bash-sandbox/README.md index 99cb3b2423..b632a0cc48 100644 --- a/packages/bash/bash-sandbox/README.md +++ b/packages/bash/bash-sandbox/README.md @@ -36,8 +36,9 @@ The keyless consumer-integration proofs are `tests/bwrap.e2e.ts`, `tests/landloc | Context surface | What the model sees | Token effect | |---|---|---| -| Bash tool schema, indirectly | By advertising a confining `sandboxMode`, this backend makes `dsh-tool-bash` expose `sandbox_permissions` and `justification`. The backend adds no prompt prose, and the session's effective mode remains unstated. | Small fixed schema increment on requests where `bash` is visible; mode switches add no context tokens. | -| Bash tool result, indirectly | The model sees ordinary bounded command output plus denial markers, the mode used, and sandbox-unavailable failures shaped by `dsh-tool-bash`; runner details stay internal. | Zero additional tokens on an unremarkable allowed run beyond ordinary output. Denial or failure adds a small conditional marker or error retained until compaction. | +| Bash tool schema, indirectly | By advertising a confining `sandboxMode`, this backend makes `dsh-tool-bash` expose `sandbox_permissions` with enum `workspace-write` \| `danger-full-access` and `justification`. The backend adds no prompt prose, and the session's effective mode remains unstated. | Small fixed schema increment on requests where `bash` is visible; mode switches add no context tokens. | +| Bash tool result, indirectly | After ordinary bounded output, a denied call appends exactly `[sandbox: file access denied under mode]`. When escalation is available it next appends `[sandbox: escalation available — retry this exact command once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]`. A settled background runner failure instead appends `[sandbox: the sandbox runner itself failed under mode — the command did not run; this is a sandbox problem, not a command failure]`. | Zero additional tokens on an unremarkable allowed run beyond ordinary output. Denial or failure adds the quoted conditional marker, retained until compaction. | +| Bash tool error, indirectly | If no runner can enforce a confined mode, the foreground call fails with code `SANDBOX_UNAVAILABLE` and the exact message `sandbox mode "" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS) — Windows has no confinement backend yet — or switch the consumer to danger-full-access.` An execution-time runner failure appends ` Runner failure: `. | Conditional error text is visible for that call and retained in history until compaction. | ## Known Limitations and Deferred Work diff --git a/scripts/verify-package-readme-model-experience.ts b/scripts/verify-package-readme-model-experience.ts index 2d517c9368..6a805c3f26 100644 --- a/scripts/verify-package-readme-model-experience.ts +++ b/scripts/verify-package-readme-model-experience.ts @@ -78,6 +78,29 @@ function proseLines(text: string): Line[] { return kept } +/** Split a canonical table row without treating escaped pipes as delimiters. */ +function tableCells(raw: string): string[] | undefined { + const last = raw.length - 1 + if (!raw.startsWith('|') || !raw.endsWith('|') || isEscaped(raw, last)) return undefined + + const cells: string[] = [] + let start = 1 + for (let index = 1; index < last; index += 1) { + if (raw[index] !== '|' || isEscaped(raw, index)) continue + cells.push(raw.slice(start, index).trim()) + start = index + 1 + } + cells.push(raw.slice(start, last).trim()) + return cells +} + +/** Whether the character at `index` follows an odd-length backslash run. */ +function isEscaped(text: string, index: number): boolean { + let backslashes = 0 + for (let cursor = index - 1; cursor >= 0 && text[cursor] === '\\'; cursor -= 1) backslashes += 1 + return backslashes % 2 === 1 +} + const failures: Failure[] = [] const packageJsons = globSync('packages/*/*/package.json', { cwd: root }).sort() const scannedPackages = new Set(packageJsons.map(path => path.slice(0, -'/package.json'.length))) @@ -171,8 +194,8 @@ for (const packageJson of packageJsons) { continue } for (const row of rows) { - const cells = row.raw.split('|').slice(1, -1).map(cell => cell.trim()) - if (cells.length !== 3 || cells.some(cell => cell.length === 0)) { + const cells = tableCells(row.raw) + if (cells === undefined || cells.length !== 3 || cells.some(cell => cell.length === 0)) { failures.push({ path: readme, message: `line ${row.index}: invalid three-column Model Experience row: ${row.raw}` }) } } From 646f3d2d0c553402b6e352cbcd70f1530c4de883 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:33:23 +0800 Subject: [PATCH 27/38] docs: apply verbatim model experience guidance repo-wide --- docs/cookbook/adding-a-package.md | 18 +++- ...07-12-package-model-experience-contract.md | 4 +- packages/bash/bash-local/README.md | 2 +- packages/bash/tool-bash/README.md | 8 +- .../code-runtime-worker/README.md | 2 +- packages/compact/compact-basic/README.md | 51 +++++++++- packages/compact/compact/README.md | 1 + packages/cordis/tool-cordis/README.md | 2 +- packages/core/session/README.md | 3 +- packages/core/tools/README.md | 28 +++++- packages/fs/fs-local/README.md | 2 +- packages/fs/fs-policy/README.md | 2 +- packages/fs/tool-fs/README.md | 28 +++++- packages/guard/repeat-tool-guard/README.md | 21 ++++- packages/hooks/hooks-claude/README.md | 2 +- packages/hooks/hooks-codex/README.md | 2 +- packages/sandbox/sandbox-local/README.md | 2 +- packages/sandbox/sandbox/README.md | 4 +- .../session-persistence-jsonl/README.md | 2 +- .../session-persistence-sqlite/README.md | 2 +- .../session-persistence/README.md | 2 +- packages/skill/tool-skill/README.md | 64 ++++++++++++- packages/subagent/subagent-acp/README.md | 4 +- packages/subagent/subagent-fork/README.md | 2 +- .../subagent/subagent-inprocess/README.md | 12 ++- packages/subagent/subagent-spawn/README.md | 2 +- packages/subagent/subagent/README.md | 4 +- packages/subagent/tool-subagent/README.md | 31 +++++- packages/todo/tool-todo/README.md | 12 ++- packages/ui/acp/README.md | 4 +- packages/ui/stdio-agent/README.md | 1 + packages/ui/tool-ask-user/README.md | 2 +- packages/ui/user-approval/README.md | 2 +- packages/ui/user-interaction/README.md | 4 +- packages/web/tool-web/README.md | 22 ++++- packages/web/web-fetch-local/README.md | 2 +- packages/web/web-search-deepseek/README.md | 4 +- packages/web/web-search-exa/README.md | 2 +- packages/web/web-search-perplexity/README.md | 4 +- packages/web/web/README.md | 4 +- packages/workflow/tool-workflow/README.md | 12 ++- .../workflow/workflow-workerthread/README.md | 4 +- .../verify-package-readme-model-experience.ts | 94 ++++++++++++++++--- 43 files changed, 405 insertions(+), 75 deletions(-) diff --git a/docs/cookbook/adding-a-package.md b/docs/cookbook/adding-a-package.md index 8af4824310..9f409787f7 100644 --- a/docs/cookbook/adding-a-package.md +++ b/docs/cookbook/adding-a-package.md @@ -44,21 +44,29 @@ For a swappable capability, split interface / implementation / consumer into sep Keep package-specific service API, config, events, extension points, and design notes first. End a package README with this canonical sequence: -```markdown +````markdown ## Model Experience | Context surface | What the model sees | Token effect | |---|---|---| -| Request surface and condition | Verbatim stable text, or the exact data-dependent shape visible to the model | Fixed, conditional, retained, replaced, capped, or zero-direct token effect | +| Request surface and condition | Short verbatim text, an exact data-dependent shape, or a link to the long literal below | Fixed, conditional, retained, replaced, capped, or zero-direct token effect | + +### Verbatim model-visible text + +#### Descriptive title matching the table row + +```text +Long stable prompt or schema description, copied exactly from source. +``` ## Known Limitations and Deferred Work - **Consumer-visible gap** — exact boundary or deliberately deferred work. -``` +```` -Fill Model Experience from the implementation. Direct, multi-surface, conditional, capped, or lifetime effects use the table; name each request contribution and token-growth condition. Quote stable model-visible source literals verbatim in inline code, using named placeholders such as `` only for interpolated values. Summarize only data-dependent payloads, provider-owned text, or schemas too large to reproduce, and identify their exact shape and renderer. Do not infer prompt visibility from tool-schema visibility because independently registered guidance can remain after a scoped tool restriction. +Fill Model Experience from the implementation. Direct, multi-surface, conditional, capped, or lifetime effects use the table; name each request contribution and token-growth condition. Quote short stable model-visible source literals verbatim in inline code, using named placeholders such as `` only for interpolated values. Put a long stable prompt or schema description under the optional exact `### Verbatim model-visible text` heading: give each literal an H4 title and its own `text` fence, then link it from the table instead of putting long prose in a cell. Summarize only data-dependent payloads, provider-owned text, or schemas too large to reproduce, and identify their exact shape and renderer. Do not infer prompt visibility from tool-schema visibility because independently registered guidance can remain after a scoped tool restriction. -An audited package with no context effect or one simple consumer-owned path joins [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and replaces the table with one line beginning `None, as ` or `Indirectly, through `. Every package outside that allowlist must keep the exact table. A package with genuinely no limitations joins the separate allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. The [Model Experience RFC](../rfc/implemented/process/2026-07-12-package-model-experience-contract.md) records the rationale. +An audited package with no context effect or one simple consumer-owned path joins [`SENTENCE_MODEL_EXPERIENCE`](../../scripts/verify-package-readme-model-experience.ts) and replaces the table with one line beginning `None, as ` or `Indirectly, through `. Every package outside that allowlist must keep the exact table; the verifier also gates the optional verbatim appendix's heading, H4-plus-`text`-fence shape, and placement after the table. A package with genuinely no limitations joins the separate allowlist in [`verify-readme-limitations.ts`](../../scripts/verify-readme-limitations.ts) and ends after Model Experience. The [Model Experience RFC](../rfc/implemented/process/2026-07-12-package-model-experience-contract.md) records the rationale. ## 5. Verify diff --git a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md index bfe88952a9..9bee0482f5 100644 --- a/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md +++ b/docs/rfc/implemented/process/2026-07-12-package-model-experience-contract.md @@ -8,11 +8,11 @@ A package README can explain APIs and runtime mechanics without answering the qu ## Decision -Every workspace package README ends with the canonical [Model Experience section](../../../cookbook/adding-a-package.md#4-write-the-package-readme), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Packages with direct, multi-surface, conditional, capped, or lifetime effects use the three-column table. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. Stable source literals are quoted verbatim, with named placeholders only for interpolated values; summaries are reserved for data-dependent payloads, provider-owned text, or schemas too large to reproduce. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other. +Every workspace package README ends with the canonical [Model Experience section](../../../cookbook/adding-a-package.md#4-write-the-package-readme), immediately before `## Known Limitations and Deferred Work`; a package on the no-limitations allowlist ends with Model Experience itself. Packages with direct, multi-surface, conditional, capped, or lifetime effects use the three-column table. Each row identifies a concrete request surface, says what the relevant model literally receives and when, and classifies the token effect. Short stable source literals are quoted verbatim in the table, with named placeholders only for interpolated values. Long stable prompts or schema descriptions live in a `### Verbatim model-visible text` appendix immediately after the table, one titled `text` fence per literal, and the table links to them; long prompts do not become table-cell prose. Summaries are reserved for data-dependent payloads, provider-owned text, or schemas too large to reproduce. The default subject is the conversation model; a package that invokes an auxiliary model, such as a summarizer or search provider, names that request separately. Agent-scoped visibility is stated where it changes which agent receives a contribution. Prompt text and tool schemas are described separately whenever configuration or scoping can hide one without the other. Every package participates. A package with no model-context effect, or one simple effect rendered entirely by another package, can join the verifier's audited sentence allowlist. It then uses exactly one sentence beginning `None, as ` or `Indirectly, through ` instead of stretching a negative fact across a three-column table. Implementations that shape results, caps, history, lifetimes, or more than one request surface keep the table even when they add zero direct prompt tokens. -`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README and the canonical final-section order, and validates one of two package-classified bodies outside fenced code. An allowlisted package must carry exactly one sentence with its assigned prefix; every other package must carry only the exact three-column header and at least one complete row. It runs in `doc-sync` and the parallel gate runner. The check owns package classification, structural presence, shape, and order; implementation review owns coverage and the truth of the prose. +`verify-package-readme-model-experience` discovers packages from `packages/*/*/package.json`, requires one sibling README and the canonical final-section order, and validates one of two package-classified bodies. An allowlisted package must carry exactly one sentence with its assigned prefix; every other package must start with the exact three-column header and at least one complete row. Optional long literals must follow that table under the exact appendix heading, with each H4 title paired to one non-empty `text` fence. It runs in `doc-sync` and the parallel gate runner. The check owns package classification, structural presence, table and appendix shape, and order; implementation review owns coverage and the truth of the prose. ## Alternatives considered diff --git a/packages/bash/bash-local/README.md b/packages/bash/bash-local/README.md index 42a2a0618d..7c217dc953 100644 --- a/packages/bash/bash-local/README.md +++ b/packages/bash/bash-local/README.md @@ -29,7 +29,7 @@ Design surveyed against the bash tools of Claude Code, OpenCode, Codex, and pi; | Context surface | What the model sees | Token effect | |---|---|---| -| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the retained stdout and stderr tail, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. | +| Bash tool results, indirectly | Through `dsh-tool-bash`, the conversation model sees the data-dependent stdout and stderr tail inside that consumer's exact result wrappers, exit and timeout markers, background-task state, and a spill-file path when full output is available. This backend adds no prompt or schema itself. An unknown task becomes exactly `Error: unknown bash task ""`; a pre-spawn cancellation becomes `Error: aborted before spawn: `. | Zero tokens until a bash tool runs. Foreground output is bounded per stream by `maxOutputBytes`; background reads return only new output, so polling does not repeat already-delivered text. Results remain in history until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/bash/tool-bash/README.md b/packages/bash/tool-bash/README.md index 8bab38caa5..bc3b21e1a2 100644 --- a/packages/bash/tool-bash/README.md +++ b/packages/bash/tool-bash/README.md @@ -62,9 +62,11 @@ Under a sandboxing executor this plugin makes the session's standing mode overri | Context surface | What the model sees | Token effect | |---|---|---| -| System prompt | Every request in this plugin's registration scope carries the short `tool:bash` exit-code instruction. A sandboxing executor adds no mode statement or switch notice. Scoped tool restrictions can hide the schemas without removing this independently registered section. | Small fixed input cost per request while the plugin is active, unchanged by sandbox mode or mode switches. | -| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields. | -| Tool-call history and results | Calls retain their arguments. Results contain bounded stdout and stderr, status markers, task ids, incremental background output, kill outcomes, and sandbox denial or failure markers. | Data-dependent tokens are added after each call and resent on later steps until compaction. Executor output caps and incremental reads bound each result; spill paths let the model fetch omitted output deliberately. | +| System prompt | Every request in this plugin's registration scope contains exactly `Check the [exit code: N] marker on every bash result; investigate failures before moving on.` A sandboxing executor adds no mode statement or switch notice. Scoped tool restrictions can hide the schemas without removing this independently registered section. | Small fixed input cost per request while the plugin is active, unchanged by sandbox mode or mode switches. | +| Tool schemas | The model sees `bash`, `bash_output`, and `bash_kill`; their full descriptions and JSON schemas are the exact output of the definitions in `src/index.ts`. `sandbox_permissions` and `justification` appear on `bash` only when the mounted executor advertises sandboxing. Agent-scoped tool restrictions can remove the definitions for that agent. | Fixed schema cost on every request where the tools are visible; sandbox support adds the escalation fields and its conditional description paragraph. | +| Foreground result | The renderer emits the data-dependent stdout tail, then optional `[stderr]` and the stderr tail. With no output it emits exactly `(no output)`. Conditional lines are exactly `[output truncated; full output: ]`, `[sandbox: file access denied under mode]`, `[timed out after ms]`, `[killed by signal: ]`, and `[exit code: ]`; the sandbox escalation and runner-failure lines are quoted in [`dsh-bash-sandbox`](../bash-sandbox/README.md). | Zero result tokens before a call. Output is bounded per stream, while each emitted line remains in history until compaction. | +| Background task context and results | Start returns exactly `started background task `. Completion injects exactly `background bash task finished . Read its output with bash_output.` Reads return only the data-dependent delta or `(no new output)`, optionally `[some output was dropped from memory; full output: ]`, then exactly one of `[status: running]`, `[status: killed]`, `[status: killed by ]`, or `[status: completed, exit code: ]`. Kill returns `killed background task ` or `task had already finished`. | Start and status text is small; deltas are data-dependent. The completion notice and every tool result are retained until compaction, but polling does not repeat already-delivered output. | +| Tool errors | Validation and policy failures are normalized as `Error: `. This package's stable messages are `invalid command: expected a non-empty string`, `invalid description: expected a non-empty string`, `invalid timeoutMs: expected a positive number, got `, `invalid escalation: sandbox_permissions requires a justification`, `invalid escalation: justification is only valid together with sandbox_permissions`, `invalid justification: expected a non-empty sentence`, `invalid task_id: expected a string, got `, `task belongs to another session`, `sandbox_permissions is not available in this composition (no sandboxing executor to escalate)`, `sandbox escalation to "" is not strictly wider than this call's current "" mode`, the approval-availability/rejection/cancellation variants, and `command aborted`. | Only the failing call adds these retained tokens; a rejected escalation does not add command output because the command does not run. | ## Known Limitations and Deferred Work diff --git a/packages/code-runtime/code-runtime-worker/README.md b/packages/code-runtime/code-runtime-worker/README.md index 303b9abb90..a462b9e227 100644 --- a/packages/code-runtime/code-runtime-worker/README.md +++ b/packages/code-runtime/code-runtime-worker/README.md @@ -35,7 +35,7 @@ Every field is validated (positive numbers) and defaulted; there are no other tu | Context surface | What the model sees | Token effect | |---|---|---| -| `run_code` result, indirectly | The conversation model sees only what the model-written program prints or returns, or a shaped failure; binding-call traffic and worker internals stay outside its context. This backend contributes no schema or prompt itself. | Zero tokens until Code Mode executes a program. `maxLogBytes` and `maxValueBytes` cap the model-visible result, which then remains in tool history until compaction. | +| `run_code` result, indirectly | Through Code Mode in `dsh-tools`, the conversation model sees only what the program prints or returns. A log cap emits exactly `[dsh-code-runtime-worker] log capture truncated at bytes`; an oversized or non-transferable return rendered as text ends exactly `… [truncated]`. Worker failures become `Error: code run failed (): `; this backend's stable messages include `compute budget exhausted (ms busy)`, `wall-clock ceiling reached (ms)`, `worker error: `, and `worker exited with code before completing`. Binding-call traffic and worker internals stay outside context. | Zero tokens until Code Mode executes a program. `maxLogBytes` and `maxValueBytes` cap the model-visible result, which then remains in tool history until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/compact/compact-basic/README.md b/packages/compact/compact-basic/README.md index 395b3cccd8..c0fea659c8 100644 --- a/packages/compact/compact-basic/README.md +++ b/packages/compact/compact-basic/README.md @@ -61,8 +61,55 @@ Loading the plugin registers `ctx.compact`. With `auto: true` (the default) it c | Context surface | What the model sees | Token effect | |---|---|---| -| Conversation history | Before a step whose estimated system prompt, session prefix, and history exceed the threshold, the conversation model receives one framed summary checkpoint in place of the older balanced surface range, followed by the retained recent units. | The replacement reduces future input history rather than appending a second copy. The summary remains until a later compaction replaces it; one oversized indivisible unit can still exceed the budget. | -| Auxiliary summarizer request | The summarization model sees a fixed checkpoint-writing system instruction and a flattened transcript of the selected range. The conversation model never sees this private request or its reasoning; only returned text is stored. | This is a separate model call with data-dependent input and `maxTokens`-capped output. Convergence retries can pay this cost more than once. | +| Conversation history | Before a step whose estimated envelope and history exceed the threshold, the conversation model receives the exact [checkpoint preamble](#conversation-checkpoint-preamble), a blank line, ``, the data-dependent summary, and ``. This one checkpoint replaces the selected older range and is followed by the retained recent units. | The replacement reduces future input history rather than appending a second copy. The summary remains until a later compaction replaces it; one oversized indivisible unit can still exceed the budget. | +| Auxiliary summarizer user message | The summarization model receives exactly `Summarize this conversation history:` followed by a blank line, the data-dependent [`renderTranscript()`](../compact/README.md) output, another blank line, and `Summary:`. The conversation model never sees this private request or its reasoning; only returned text is stored. | This is a separate model call with data-dependent input and `maxTokens`-capped output. Convergence retries can pay this cost more than once. | +| Auxiliary summarizer system prompt | The summarization model receives the exact [checkpoint-writing instruction](#auxiliary-summarizer-system-prompt). | Fixed auxiliary input cost plus the data-dependent transcript on every summarization attempt. | + +### Verbatim model-visible text + +#### Conversation checkpoint preamble + +```text +This is an automatically generated checkpoint condensing an earlier span of the conversation to free up context. Treat the captured context as established background and build on it without restating it. Continue the task directly from the messages that follow, without acknowledging this checkpoint. +``` + +#### Auxiliary summarizer system prompt + +```text +You are a compaction engine for an AI coding assistant. Condense the conversation transcript into a structured checkpoint that lets another model resume the work with no loss of essential context. + +Output EXACTLY the Markdown structure below: keep every section, in order. Use terse bullets, not prose paragraphs. Write "(none)" for an empty section — never drop a section. + +## Primary Request and Intent +- [the user's original and evolving goals; quote verbatim where the exact wording matters] + +## Key Technical Concepts +- [technologies, frameworks, patterns, and conventions in play] + +## Files and Code +- [exact path: why it matters, key changes or snippets] + +## Errors and Fixes +- [error: how it was resolved, plus any related user feedback] + +## Pending Tasks +- [explicitly requested work not yet completed] + +## Current Work +- [precisely what was in progress at this checkpoint] + +## Next Step +- [the single next action, directly in line with the most recent request, or "(none)"] + +## Critical Context +- [decisions and their rationale, constraints, user preferences, open questions, data needed to continue] + +Rules: +- Preserve exact file paths, commands, error strings, identifiers, and function signatures. +- Capture user feedback and explicit instructions faithfully, especially corrections. +- Do NOT mention this summarization process or that the context was compacted. +- If the transcript already contains a block, it is a PRIOR checkpoint. Do not copy it forward verbatim: preserve still-true facts, drop stale ones, and merge newer information into a single consolidated summary under the same structure. +``` ## Known Limitations and Deferred Work diff --git a/packages/compact/compact/README.md b/packages/compact/compact/README.md index 73d7c22375..52c018d549 100644 --- a/packages/compact/compact/README.md +++ b/packages/compact/compact/README.md @@ -54,6 +54,7 @@ Subclass `CompactService`, implement `compactIfNeeded` and `compactRegion`, and | Context surface | What the model sees | Token effect | |---|---|---| | Conversation history, when a backend is invoked | A successful implementation replaces an older surface range with one user-role summary checkpoint; the raw events stay logged but stop appearing in derived model messages. The seam itself performs no rewrite. | Zero direct tokens from this interface. A backend trades many retained history tokens for one summary and leaves the recent tail unchanged. | +| Transcript supplied to a compaction consumer | `renderTranscript()` joins entries with one blank line and renders them exactly as `User: `, `Assistant: `, `Tool result (call ): `, `Tool error (call ): `, `[Context: ]`, or `[Steering: ]`. Non-text blocks render exactly as `[reasoning: ]`, `[tool-call: ()]`, `[tool-result: ]`, `[tool-result]`, or `[]`. | Data-dependent input tokens are paid only by the auxiliary model or consumer that requests this transcript; the conversation model does not receive a duplicate transcript. | ## Known Limitations and Deferred Work diff --git a/packages/cordis/tool-cordis/README.md b/packages/cordis/tool-cordis/README.md index 1fb20a1906..eab996cc44 100644 --- a/packages/cordis/tool-cordis/README.md +++ b/packages/cordis/tool-cordis/README.md @@ -37,7 +37,7 @@ Namespace plugin: named exports `name` / `inject` / `Config` / `apply`, no defau | Context surface | What the model sees | Token effect | |---|---|---| | Tool schemas | The conversation model sees `cordis_inspect`, `cordis_mount`, and `cordis_unmount` whenever this plugin is visible. | Fixed schema cost on every request in that tool view. | -| Tool-call history and results | Inspect returns selected live services, plugins, tools, or generated API and event references; mount and unmount return lifecycle facts or structured errors. The submitted mount program remains in the assistant tool-call history. | Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small. | +| Tool-call history and results | Inspect joins selected sections exactly as `##
` then a newline and the data-dependent body, with one blank line between sections. Mount returns `mounted (plugin "", state: )`, optionally inserting ` — waiting for service(s): (activates when provided)` before the closing parenthesis. Unmount returns `unmounted (plugin "")`; an unknown id becomes `Error: no dynamic plugin with id "" (list mounts with cordis_inspect what:"dynamic")`. The submitted mount program remains in the assistant tool-call history. | Inspect output and mount code are data-dependent and resent until compaction; lifecycle acknowledgements are small. | | Later requests after a mount | A mounted plugin may register tools, prompt contributions, or listeners that change later requests for the scopes it targets; unmount removes those contributions after quiescence. | Indirect token impact equals the mounted plugin's contributions and lasts only for the mount lifetime. | ## Known Limitations and Deferred Work diff --git a/packages/core/session/README.md b/packages/core/session/README.md index 8c0da875ee..94bddaf501 100644 --- a/packages/core/session/README.md +++ b/packages/core/session/README.md @@ -82,7 +82,8 @@ Every `SessionEvent` carries two optional top-level fields (structural metadata) | Context surface | What the model sees | Token effect | |---|---|---| -| Derived message history | The model receives projections of `user/message`, `assistant/message`, `tool/result`, `context/message`, and `steering/message` surface nodes. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message. | Appended surface nodes are resent on later steps. A `replace` surface operation removes the shadowed nodes from future inputs without deleting their raw log records. | +| Derived message history | The model receives projections of `user/message`, `assistant/message`, and `tool/result` surface nodes verbatim. A `context/message` is a user-role message containing exactly ``, its content blocks, and ``; `steering/message` uses the identical `` / `` wrapper. Tool calls live inside assistant messages. Chunks, boundaries, usage, hook records, todo records, and other log-only events add no message. | Appended surface nodes are resent on later steps. A `replace` surface operation removes the shadowed nodes from future inputs without deleting their raw log records. | +| Crash-repair result | If a persisted turn ended with unanswered tool calls, each synthetic error result contains exactly `Tool call interrupted by a crash; no result was recorded.` | Zero tokens in an intact session. Each repaired call adds this retained error text on resume. | | Logged request header | The session reconstructs the system prompt, tool schemas, call config, and session prefix that the loop actually sent. Header events do not add a second copy to message history; the prefix is prepended outside `deriveMessages()`. | Zero duplicate tokens from logging. The reconstructed prefix, system text, and schemas still incur their normal per-request cost. | ## Known Limitations and Deferred Work diff --git a/packages/core/tools/README.md b/packages/core/tools/README.md index ff09be73ee..3fe9f9c74b 100644 --- a/packages/core/tools/README.md +++ b/packages/core/tools/README.md @@ -142,8 +142,32 @@ The wire collapse is the registry's own contribution (`systemPrompt.tools()` is | Context surface | What the model sees | Token effect | |---|---|---| -| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead contributes one reserved `run_code` wire schema and a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's end-tool set and registry-owned SDK without filtering the transport or another plugin's independently registered prompt sections. The expert `system-prompt/assemble` waterfall can replace the final assembly and then owns preserving Code Mode protocol coherence. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | -| Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | +| Normal tool schemas | In normal mode the model sees each visible definition's exact name, description, and JSON schema. Agent-scoped restrictions and shadows change that agent's end-tool set. | Fixed per-request cost proportional to the visible definitions. Restrictions that hide tools remove their entire schema cost for that agent. | +| Code Mode schema and SDK | Code Mode exposes `run_code` with the exact [tool description](#run_code-tool-description), parameter description `The program: the body of an async TypeScript function.`, and [SDK instructions](#code-mode-sdk-instructions) followed by the generated exact `declare const tools` block. `both` exposes normal schemas and this Code Mode surface. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. | +| Tool-call history and results | The loop retains model-emitted arguments and the registry's final content. Any thrown or denied call becomes exactly `Error: `. Code Mode returns only the outer program's printed lines and rendered return value, `(run_code completed with no output)` when both are empty, or `Error: code run failed (): ` followed conditionally by `Captured output:` and the captured lines. Inner dispatch events stay log-only; post-execute listeners may append source-attributed context after the result. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. | + +### Verbatim model-visible text + +#### `run_code` tool description + +```text +Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it. +``` + +#### Code Mode SDK instructions + +```text +## Writing code for run_code + +Pass `run_code` the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). Inside the program: + +- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's text output as a string. Tool arguments must be JSON-serializable. +- A FAILED tool call rejects with an `Error` carrying the tool's error text — `try/catch` it to handle and continue. +- Calls execute sequentially, even under `Promise.all`. +- Emit results with `return` and/or `console.log(...)`. ONLY what you print or return comes back to you — intermediate tool results never enter the conversation, so extract just what you need. + +The available tools: +``` ## Known Limitations and Deferred Work diff --git a/packages/fs/fs-local/README.md b/packages/fs/fs-local/README.md index b45cbb13dd..cf9b4b43b8 100644 --- a/packages/fs/fs-local/README.md +++ b/packages/fs/fs-local/README.md @@ -25,7 +25,7 @@ The raw I/O lives in `src/fsio.ts` (Cordis-free, independently unit-tested); `sr | Context surface | What the model sees | Token effect | |---|---|---| -| Filesystem tool results, indirectly | Through `dsh-tool-fs`, the model sees line-windowed UTF-8 file content, mutation acknowledgements, or structured filesystem errors. Real paths, versions, atomic-write mechanics, and directory metadata remain internal unless a consumer renders them. | Zero direct tokens. Read tokens are bounded by the tool's line, line-length, and byte caps; mutation results are small and remain in history until compaction. | +| Filesystem tool results, indirectly | Through `dsh-tool-fs`, the model sees line-windowed UTF-8 file content or mutation acknowledgements. This backend's stable failures are normalized as `Error: `; messages include `cannot read "": binary file`, `cannot "": invalid UTF-8 text`, `cannot write "": not a regular file`, `cannot write "": file no longer exists`, `cannot "": file changed since it was read`, `cannot overwrite existing "" without reading it first`, `cannot edit "": binary file`, `old_string must be a non-empty string`, `old_string was not found in ""`, and `old_string matched times in ""; provide a more specific old_string or set replace_all to true`. Real paths, versions, atomic-write mechanics, and directory metadata remain internal unless a consumer renders them. | Zero direct tokens. Read tokens are bounded by the tool's line, line-length, and byte caps; mutation results or errors remain in history until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/fs/fs-policy/README.md b/packages/fs/fs-policy/README.md index df454a74bd..9aa1d8b829 100644 --- a/packages/fs/fs-policy/README.md +++ b/packages/fs/fs-policy/README.md @@ -51,7 +51,7 @@ Because the plugin influences the world only through events, removing it does no | Context surface | What the model sees | Token effect | |---|---|---| -| Filesystem tool outcome | This plugin adds no prompt or schema. It can turn an unobserved or stale write or edit into a structured `FS_NOT_OBSERVED` or `FS_STALE_VERSION` error result instead of a success; observation state itself is never shown. | Zero tokens on allowed operations beyond the ordinary tool result. A denial adds a small retained error result and avoids any success payload. | +| Filesystem tool outcome | This plugin adds no prompt or schema. Through `dsh-tool-fs`, an edit without a prior read becomes exactly `Error: edit requires reading "" first` with code `FS_NOT_OBSERVED`; guarded mutations whose observed version is stale receive the backend's exact `Error: cannot "": file changed since it was read` with code `FS_STALE_VERSION`. Observation state itself is never shown. | Zero tokens on allowed operations beyond the ordinary tool result. A denial adds the small retained error result and avoids any success payload. | ## Known Limitations and Deferred Work diff --git a/packages/fs/tool-fs/README.md b/packages/fs/tool-fs/README.md index 0f0b13ac8f..17fccbe242 100644 --- a/packages/fs/tool-fs/README.md +++ b/packages/fs/tool-fs/README.md @@ -52,9 +52,31 @@ The read rendering (line windowing + output formatting) lives in `src/read-rende | Context surface | What the model sees | Token effect | |---|---|---| -| System prompt | Every request in this plugin's registration scope receives three short sections explaining line-windowed reads, whole-file writes, literal edits, and the default read-before-mutate habit. Scoped tool restrictions can hide schemas without removing these independently registered sections. | Fixed guidance cost per request while the plugin is active, even when a restriction hides one or more tools. | -| Tool schemas | The model sees `read`, `write`, and `edit` with their snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. | -| Tool-call history and results | Read returns numbered UTF-8 lines and a pagination or cap footer; write and edit return concise success text or structured errors. The model-emitted write or edit content also remains in the assistant tool-call arguments. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`. Call arguments and results are resent until compaction, so large write payloads can dominate history even though the success result is small. | +| System prompt | Every request in this plugin's registration scope receives the exact independently registered [read](#read-guidance), [write](#write-guidance), and [edit](#edit-guidance) sections. Scoped tool restrictions can hide schemas without removing these sections. | Fixed guidance cost per request while the plugin is active, even when a restriction hides one or more tools. | +| Tool schemas | The model sees the exact `read`, `write`, and `edit` descriptions and JSON schemas from their definitions, with snake_case arguments. Scoped tool restrictions can remove any definition for one agent. | Fixed schema cost on every request in that tool view. | +| Read result | A successful read is exactly ``, newline, `file`, newline, ``, numbered lines as `: `, a blank line, one footer, and ``. The footer is exactly `(Output capped. Showing lines -. Use offset= to continue.)`, `(Showing lines - of . Use offset= to continue.)`, or `(End of file - total lines)`. A long line ends exactly `... (line truncated to chars)`. | Read output is capped by `readLimit`, `readMaxLineLength`, and `readMaxBytes`; the retained call and result are resent until compaction. | +| Write and edit results | Write returns the exact five-line envelope ``, `file`, ``, `Created file` or `Updated file`, then ``. Edit returns exactly `The file has been updated successfully.` or, for `replace_all`, `The file has been updated. All occurrences were successfully replaced.` The full write or replacement text remains in the assistant tool-call arguments. | Success text is small, but large mutation arguments and any result are resent until compaction. | +| Tool errors | Failures are normalized as `Error: `. This package's stable validation and read messages are `file_path must be a non-empty string`, `limit must be less than or equal to `, `old_string must be a non-empty string`, `old_string and new_string must differ`, `cannot read "": not found`, `cannot read "": not a regular file`, and `offset is out of range for "" ( lines)`; provider and policy templates are quoted in their package READMEs. | Only a failing call adds these retained tokens. | + +### Verbatim model-visible text + +#### Read guidance + +```text +Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files. +``` + +#### Write guidance + +```text +Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes. +``` + +#### Edit guidance + +```text +Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session. +``` ## Known Limitations and Deferred Work diff --git a/packages/guard/repeat-tool-guard/README.md b/packages/guard/repeat-tool-guard/README.md index 8600b3d85e..14ebe530d6 100644 --- a/packages/guard/repeat-tool-guard/README.md +++ b/packages/guard/repeat-tool-guard/README.md @@ -40,7 +40,26 @@ Unit suites drive a real agent loop against a mock adapter (no network) and cove | Context surface | What the model sees | Token effect | |---|---|---| -| Conditional context message | At configured consecutive-repeat thresholds, that agent receives a source-attributed synthetic user reminder after the tool results, asking it to inspect the prior result and change approach or finish. No tool schema or normal-call text is added. | Zero tokens before a threshold. Each reminder is retained history; the detailed form caps the quoted canonical arguments at `argumentsPreviewChars`, while agents keep independent counters. | +| First-threshold context message | At the first configured consecutive-repeat threshold, that agent receives the exact [gentle reminder](#first-threshold-reminder). No tool schema or normal-call text is added. | Zero tokens before the threshold. The reminder is retained history for that agent. | +| Later-threshold context message | A later threshold receives the exact [detailed reminder template](#later-threshold-reminder). A capped argument preview ends exactly `… (+ more chars)`. | Each reminder is retained history; `argumentsPreviewChars` bounds its data-dependent argument text, while agents keep independent counters. | + +### Verbatim model-visible text + +#### First-threshold reminder + +```text +You are repeating the exact same tool call with identical arguments. Carefully analyze the previous result before calling again: if the task is not complete, try a different approach or different arguments instead of repeating the call. +``` + +#### Later-threshold reminder + +```text +Repeated tool call detected: +- tool: +- consecutive_calls: +- arguments: +The repeated calls are not making progress. Do not call this tool with these exact arguments again. Inspect the latest result and choose a different action, different arguments, or finish the task if enough evidence has been gathered. +``` ## Known Limitations and Deferred Work diff --git a/packages/hooks/hooks-claude/README.md b/packages/hooks/hooks-claude/README.md index f55790e9e7..05b13c39b7 100644 --- a/packages/hooks/hooks-claude/README.md +++ b/packages/hooks/hooks-claude/README.md @@ -55,7 +55,7 @@ Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-claude' } | Context surface | What the model sees | Token effect | |---|---|---| | Hook-provided context | `SessionStart`, accepted prompt, post-tool, and live in-process subagent-start hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. Remote-child injection has no local target. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent in later conversation requests until compaction. | -| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny or ask before a tool, block a post-tool result with feedback, or force another model step. `systemMessage` and `updatedInput` are logged or warned but are not model-visible in this implementation. | Blocking a prompt removes that prompt's request tokens; denial or feedback adds a retained error or context result; forced continuation pays another full request. | +| Blocked prompt or tool outcome | Provider-supplied reasons pass through verbatim. When absent, a blocked prompt uses exactly `blocked by UserPromptSubmit hook`, a denied tool becomes `Error: blocked by PreToolUse hook`, blocked post-tool feedback is exactly `blocked by PostToolUse hook`, and a blocking stop adds steering exactly `continue: blocked by Stop hook`. `systemMessage` and `updatedInput` are logged or warned but are not model-visible in this implementation. | Blocking a prompt removes that prompt's request tokens; denial or feedback adds the retained fallback or provider text; forced continuation pays another full request. | ## Known Limitations and Deferred Work diff --git a/packages/hooks/hooks-codex/README.md b/packages/hooks/hooks-codex/README.md index b5ed2ec35a..2b2d43044c 100644 --- a/packages/hooks/hooks-codex/README.md +++ b/packages/hooks/hooks-codex/README.md @@ -59,7 +59,7 @@ Injected context carries an explicit `{ kind: 'plugin', plugin: 'hooks-codex' }` | Context surface | What the model sees | Token effect | |---|---|---| | Hook-provided context | `SessionStart`, accepted prompt, and post-tool hooks can add source-attributed context messages; a blocking `Stop` hook adds its reason as next-step steering. | No cost when hooks return no context. Hook text is data-dependent, logged, and resent until compaction. | -| Blocked prompt or tool outcome | A hook can prevent a user prompt from reaching the model, deny a tool, block a post-tool result with feedback, or force another model step. Codex `systemMessage` is not surfaced. | Blocking a prompt removes its request tokens; denial or feedback adds retained result text; forced continuation pays another full request. | +| Blocked prompt or tool outcome | Provider-supplied reasons pass through verbatim. When absent, a blocked prompt uses exactly `blocked by UserPromptSubmit hook`, a denied tool becomes `Error: blocked by PreToolUse hook`, blocked post-tool feedback is exactly `blocked by PostToolUse hook`, and a blocking stop adds steering exactly `continue: blocked by Stop hook`. Codex `systemMessage` is not surfaced. | Blocking a prompt removes its request tokens; denial or feedback adds the retained fallback or provider text; forced continuation pays another full request. | ## Known Limitations and Deferred Work diff --git a/packages/sandbox/sandbox-local/README.md b/packages/sandbox/sandbox-local/README.md index 6042d1186d..919806ca38 100644 --- a/packages/sandbox/sandbox-local/README.md +++ b/packages/sandbox/sandbox-local/README.md @@ -21,7 +21,7 @@ Consumers: [`@deepseek-ai/dsh-bash-sandbox`](../../bash/bash-sandbox/); see [`ex | Context surface | What the model sees | Token effect | |---|---|---| -| Sandbox result facts, indirectly | This provider adds no prompt or tool. It supplies the selected enforcement and denial dialect to `dsh-bash-sandbox`, which can become a denial marker or sandbox-unavailable error in the bash result. Runner selection and profiles are not shown. | Zero direct tokens; only a conditional small fact or error reaches context through the bash consumer. | +| Sandbox result facts, indirectly | This provider adds no prompt or tool. It supplies the selected enforcement and denial dialect to `dsh-bash-sandbox`, which can become that consumer's exact `[sandbox: file access denied under mode]` marker. If no local runner can confine the command, the model instead receives the exact `SANDBOX_UNAVAILABLE` text quoted in [`dsh-sandbox`](../sandbox/README.md). Runner selection and profiles are not shown. | Zero direct tokens; only the conditional marker or error reaches context through the bash consumer. | ## Known Limitations and Deferred Work diff --git a/packages/sandbox/sandbox/README.md b/packages/sandbox/sandbox/README.md index 8864897c84..5947dab798 100644 --- a/packages/sandbox/sandbox/README.md +++ b/packages/sandbox/sandbox/README.md @@ -12,7 +12,9 @@ Implementations: [`@deepseek-ai/dsh-sandbox-local`](../sandbox-local/) (Linux: ` ## Model Experience -Indirectly, through consumers such as `dsh-bash-sandbox`, which may expose enforcement, denial, or sandbox-unavailable facts in schemas or results. +| Context surface | What the model sees | Token effect | +|---|---|---| +| Sandbox result, indirectly | Through `dsh-bash-sandbox` and `dsh-tool-bash`, enforcement facts may become that consumer's exact denial marker. `SandboxUnavailableError` becomes exactly `Error: sandbox mode "" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS) — Windows has no confinement backend yet — or switch the consumer to danger-full-access.`, optionally followed by ` Runner failure: `. | This package adds no prompt or schema. Only a denial or failed confinement adds retained result tokens. | ## Known Limitations and Deferred Work diff --git a/packages/session-persistence/session-persistence-jsonl/README.md b/packages/session-persistence/session-persistence-jsonl/README.md index 907242b0b9..edcfad2ef9 100644 --- a/packages/session-persistence/session-persistence-jsonl/README.md +++ b/packages/session-persistence/session-persistence-jsonl/README.md @@ -34,7 +34,7 @@ The plugin generalizes the example `session-jsonl.ts`: it subscribes to `session | Context surface | What the model sees | Token effect | |---|---|---| -| Resumed conversation history | JSONL storage contributes no live prompt or schema. Loading restores stored surface history and preserves prior request headers for reconstruction; the new loop composes its current envelope. An interrupted tail is balanced with error tool results. Raw `assistant/chunk` records do not duplicate messages. | Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus small repair results only after an interrupted tool turn. | +| Resumed conversation history | JSONL storage contributes no live prompt or schema. Loading restores stored surface history and preserves prior request headers for reconstruction; the new loop composes its current envelope. Each unanswered call in an interrupted tail is balanced with the exact error text `Tool call interrupted by a crash; no result was recorded.` Raw `assistant/chunk` records do not duplicate messages. | Zero live-request tokens. A resumed agent pays for retained history and its current envelope, plus the quoted repair result for each interrupted call. | ## Known Limitations and Deferred Work diff --git a/packages/session-persistence/session-persistence-sqlite/README.md b/packages/session-persistence/session-persistence-sqlite/README.md index 074338699c..acd0942772 100644 --- a/packages/session-persistence/session-persistence-sqlite/README.md +++ b/packages/session-persistence/session-persistence-sqlite/README.md @@ -33,7 +33,7 @@ Like the JSONL backend, the plugin also installs the `session/event` → buffer | Context surface | What the model sees | Token effect | |---|---|---| -| Resumed conversation history | SQLite storage contributes no live prompt or schema. Loading restores the same surface history as JSONL and preserves prior headers for reconstruction; the new loop composes its current envelope. Interrupted rows are balanced with error tool results. Row metadata and raw chunks are not messages. | Zero live-request tokens. Resume restores retained history and pays the current envelope, with small repair-result tokens only for an interrupted tool turn. | +| Resumed conversation history | SQLite storage contributes no live prompt or schema. Loading restores the same surface history as JSONL and preserves prior headers for reconstruction; the new loop composes its current envelope. Each unanswered call in interrupted rows is balanced with the exact error text `Tool call interrupted by a crash; no result was recorded.` Row metadata and raw chunks are not messages. | Zero live-request tokens. Resume restores retained history and pays the current envelope, plus the quoted repair result for each interrupted call. | ## Known Limitations and Deferred Work diff --git a/packages/session-persistence/session-persistence/README.md b/packages/session-persistence/session-persistence/README.md index 44310b6c82..76a28422b8 100644 --- a/packages/session-persistence/session-persistence/README.md +++ b/packages/session-persistence/session-persistence/README.md @@ -54,7 +54,7 @@ Re-exported from `dsh-session`: `SessionHeader` (immutable session metadata: `ve | Context surface | What the model sees | Token effect | |---|---|---| -| Resumed conversation history | This seam adds no prompt or schema. Resume restores stored surface events as message history; stored request headers reconstruct earlier calls, while the new loop composes the current system prompt, tools, and session prefix for its next request. Crash repair inserts an error result for each unanswered tool call. | Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; crash repair can add small error-result tokens that keep the provider transcript valid. | +| Resumed conversation history | This seam adds no prompt or schema. Resume restores stored surface events as message history; stored request headers reconstruct earlier calls, while the new loop composes the current system prompt, tools, and session prefix for its next request. Crash repair inserts exactly `Tool call interrupted by a crash; no result was recorded.` as the error result for each unanswered tool call. | Zero tokens during ordinary persistence. Resume restores retained history cost and pays the current request envelope normally; each repaired call adds the quoted retained error text. | ## Known Limitations and Deferred Work diff --git a/packages/skill/tool-skill/README.md b/packages/skill/tool-skill/README.md index a9ad9e0053..0b2d7df60c 100644 --- a/packages/skill/tool-skill/README.md +++ b/packages/skill/tool-skill/README.md @@ -24,8 +24,68 @@ The tool does not call `agent.inject()` in v1. Its result is already recorded as | Context surface | What the model sees | Token effect | |---|---|---| -| Session prefix | If model-invocable skills exist and this exact `skill` tool is visible, the agent receives one user-role `` listing sorted names and capped descriptions. The composed catalog is frozen for the loop instance and prepended to every request, outside ordinary history. | Repeated input cost scales with skill count and `catalogDescriptionMaxLength`; no catalog tokens are sent when the list is empty or the tool is hidden or shadowed. | -| Tool schema and result | The model sees the fixed `skill(name)` schema. A successful call returns the selected full instructions plus resource-resolution guidance; no duplicate `agent.inject()` copy is made. | Fixed schema cost per request. Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction. | +| Session prefix | If model-invocable skills exist and this exact `skill` tool is visible, the agent receives the exact [catalog template](#skill-catalog-template), with one data-dependent entry per sorted skill. The catalog is a frozen user-role session prefix. | Repeated input cost scales with skill count and `catalogDescriptionMaxLength`; no catalog tokens are sent when the list is empty or the tool is hidden or shadowed. | +| Tool schema | The model sees `skill(name)` with exact description `Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.` and parameter description `The exact skill name from the available skills list.` | Fixed schema cost per request where the tool is visible. | +| Tool result | A successful call uses the exact [result template](#skill-result-template) with the exact [provider-managed](#provider-managed-resource-guidance), [directory](#directory-resource-guidance), [URL](#url-resource-guidance), or [opaque](#opaque-resource-guidance) resource guidance. | Loaded instructions are data-dependent tool-result tokens, resent on later steps until compaction; no duplicate `agent.inject()` copy is made. | +| Tool errors | Invalid or stale selections return exactly `Error: invalid skill name ""`, `Error: skill "" is unknown or no longer available`, or `Error: skill "" is not available for model invocation`. Provider-thrown lookup text is data-dependent and receives the same `Error: ` wrapper. | Only a failing call adds these retained tokens. | + +### Verbatim model-visible text + +#### Skill catalog template + +```text + +A skill is a reusable set of task-specific instructions. The following skills are available in this session: + + +- ``: + + +If the user names a skill, or the task clearly matches a skill's description, call the `skill` tool with the exact skill name before taking task actions. Load all applicable skills, then follow their full instructions. This catalog contains summaries only; do not infer or follow a skill's instructions until it has been loaded. + +``` + +#### Skill result template + +```text + + + + + + + + + +``` + +#### Provider-managed resource guidance + +```text +Resources for this skill are managed by provider "". +Load referenced resources only as needed. +``` + +#### Directory resource guidance + +```text +Base directory for this skill: +Resolve relative paths mentioned by this skill against the base directory before using them. Load referenced resources only as needed. +``` + +#### URL resource guidance + +```text +Base URL for this skill: +Resolve relative URLs mentioned by this skill against the base URL before using them. Load referenced resources only as needed. +``` + +#### Opaque resource guidance + +```text +Resources for this skill: +Load referenced resources only as needed. +``` ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-acp/README.md b/packages/subagent/subagent-acp/README.md index 06c8180f88..d1444b2f5c 100644 --- a/packages/subagent/subagent-acp/README.md +++ b/packages/subagent/subagent-acp/README.md @@ -61,8 +61,8 @@ Keyless tests drive a scripted ACP subprocess over real stdio. The with-key e2e | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent request | The remote child receives the standalone task through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation. This provider advertises no optional start-time capabilities, so the local service rejects requests for persona, tool filtering, depth enforcement, or structured output instead of silently omitting them. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | -| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or a stop-reason error, not intermediate messages or tool traffic. | Parent input grows only by the final result, which is data-dependent and retained until compaction. This provider adds no parent schema itself. | +| Child-agent request | The remote child receives the standalone task content through ACP plus its own process's configured system prompt, tools, and fresh session. It receives no parent conversation. This provider advertises no optional start-time capabilities, so the local service rejects requests for persona, tool filtering, depth enforcement, or structured output instead of silently omitting them. | The child pays for an independent full context and its own multi-step history. These tokens never enter the parent's context. | +| Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final streamed assistant text or that consumer's exact stop-reason error, not intermediate messages or tool traffic. A request already cancelled before publication becomes exactly `Error: subagent request was aborted before the ACP child started`; other start failures pass through as `Error: `. | Parent input grows only by the final result or error, which is data-dependent and retained until compaction. This provider adds no parent schema itself. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-fork/README.md b/packages/subagent/subagent-fork/README.md index bafd7792a2..5e3ee5fec5 100644 --- a/packages/subagent/subagent-fork/README.md +++ b/packages/subagent/subagent-fork/README.md @@ -27,7 +27,7 @@ See [`dsh-subagent-spawn`](../subagent-spawn/README.md) for the run lifecycle, m | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent history and envelope | The child receives the parent's balanced completed-turn surface prefix, then the new task. A configured persona shadows prompt text in the child's fresh scope; a tool restriction filters its global wire schemas, executable lookup, and Code Mode SDK bindings but not standalone guidance. The parent's tool view and authority are not inherited. An optional structured-output request adds its child-only contract. The parent's current in-flight turn is excluded. | Forking duplicates retained completed history into separate child requests; the child then accumulates its own tokens independently. Persona changes repeated prompt cost, filtering changes schema or generated SDK cost, and a first-turn fork has no inherited history. | +| Child-agent history and envelope | The child receives the parent's balanced completed-turn surface prefix, then the new task content verbatim. A configured persona shadows prompt text in the child's fresh scope; a tool restriction filters its global wire schemas, executable lookup, and Code Mode SDK bindings but not standalone guidance. The parent's tool view and authority are not inherited. An optional structured-output request adds its child-only contract. The parent's current in-flight turn is excluded. | Forking duplicates retained completed history into separate child requests; the child then accumulates its own tokens independently. Persona changes repeated prompt cost, filtering changes schema or generated SDK cost, and a first-turn fork has no inherited history. | | Parent tool result, indirectly | The parent receives only the child's own final output through `dsh-tool-subagent`, not the inherited prefix or intermediate work. | Parent input grows by one data-dependent final result retained until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent-inprocess/README.md b/packages/subagent/subagent-inprocess/README.md index 0d7c2886b5..befadf8f6f 100644 --- a/packages/subagent/subagent-inprocess/README.md +++ b/packages/subagent/subagent-inprocess/README.md @@ -44,9 +44,19 @@ A clean turn that never commits the required structured value reports `error`; t | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent request | The shared driver sends the task as the child's user message and, when requested, shadows the persona and restricts global tool schemas, lookup, execution, and Code Mode SDK bindings in the unpublished child's fresh scope; parent restrictions are not inherited, and standalone tool-guidance sections remain. Structured runs add a scoped instruction plus `structured_output` in the visible schema or Code Mode SDK, then stop after a committed capture. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. A persona changes repeated prompt text; filtering changes schema or generated SDK cost but not independently registered guidance. Structured output adds fixed instruction and capability tokens for that child. | +| Child-agent request | The shared driver sends the task verbatim as the child's user message and, when requested, shadows the persona and restricts global tool schemas, lookup, execution, and Code Mode SDK bindings in the unpublished child's fresh scope; parent restrictions are not inherited, and standalone tool-guidance sections remain. Spawn supplies no history; fork supplies its balanced seed. | Child input is isolated from the parent and grows through the child's own steps. A persona changes repeated prompt text; filtering changes schema or generated SDK cost but not independently registered guidance. | +| Structured child request and results | A structured run adds the exact [structured-output instruction](#structured-output-instruction). The tool description is exactly `Report your final structured result. Call this exactly once, when your answer is complete; the arguments must match this tool's parameter schema exactly.` with the requested schema. Success returns `Structured output recorded.`; a later call becomes ``Error: structured output already recorded: the run is complete, so `` is not executed``. | Fixed instruction and capability tokens are paid only by that child. Result text enters the child history, while the captured value alone becomes the parent result. | +| Parent start error, indirectly | Through `dsh-tool-subagent`, invalid depth state becomes exactly `Error: agent subagentDepth must be a non-negative safe integer`, `Error: subagent child depth exceeds the safe-integer range`, or `Error: subagent depth exceeds maxDepth `. A pre-publication cancellation passes its abort reason through the registry's `Error: ` wrapper. | Zero tokens on a successful start; only the failed parent tool call retains this text. | | Parent result, indirectly | The driver extracts only the child's own last assistant output or captured structured value; seeded parent messages and intermediate child work do not become the result. | The parent receives one data-dependent result through the consumer; all other child tokens stay in the child session. | +### Verbatim model-visible text + +#### Structured-output instruction + +```text +When you have your final answer, you MUST report it by calling the `structured_output` tool with arguments matching its parameter schema exactly. Do not finish with a plain text answer: only the tool call counts as your result. +``` + ## Known Limitations and Deferred Work - **Runs expose no `sendMessage`/`resume`** — the optional runtime capabilities are absent on in-process runs. diff --git a/packages/subagent/subagent-spawn/README.md b/packages/subagent/subagent-spawn/README.md index 78ecb4c035..79fe577f0d 100644 --- a/packages/subagent/subagent-spawn/README.md +++ b/packages/subagent/subagent-spawn/README.md @@ -22,7 +22,7 @@ Spawn advertises `{ outputSchema: true, depthLimit: true, toolFilter: true, pers | Context surface | What the model sees | Token effect | |---|---|---| -| Child-agent request | The fresh child receives the standalone task, inherits the parent model and workspace by default, and sees the global prompt with any configured child-scoped persona shadow. A tool filter removes global wire schemas, executable lookup, and Code Mode SDK bindings for that child but leaves independently registered guidance. It receives zero parent conversation messages; the filter is visibility/composition, not an authority grant inherited from the parent. | The child pays for a new independent context and history; no parent-history tokens are duplicated. Persona changes this child's repeated prompt cost, while filtering changes its schema or generated SDK cost. | +| Child-agent request | The fresh child receives the standalone task content verbatim, inherits the parent model and workspace by default, and sees the global prompt with any configured child-scoped persona shadow. A tool filter removes global wire schemas, executable lookup, and Code Mode SDK bindings for that child but leaves independently registered guidance. It receives zero parent conversation messages; the filter is visibility/composition, not an authority grant inherited from the parent. | The child pays for a new independent context and history; no parent-history tokens are duplicated. Persona changes this child's repeated prompt cost, while filtering changes its schema or generated SDK cost. | | Parent tool result, indirectly | Through `dsh-tool-subagent`, the parent receives only the child's final output or stop-reason error. | Parent input grows by one data-dependent result retained until compaction. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index c85a6a4566..cd2e4d8aae 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -62,7 +62,9 @@ The current model-facing tool collects synchronously: it awaits the child result ## Model Experience -Indirectly, through `dsh-tool-subagent` and registered providers, which expose delegation schemas, child contexts, and retained parent results. +| Context surface | What the model sees | Token effect | +|---|---|---| +| Delegation result, indirectly | Through `dsh-tool-subagent`, registered providers create child contexts and return data-dependent final output. A missing provider or unsupported requested capability becomes exactly `Error: no subagent provider registered for ""` or `Error: subagent provider "" does not support the "" capability`. Provider-specific start errors receive the same `Error: ` wrapper. | This seam adds no parent schema itself. The parent retains only the final output or start error; child working tokens remain in the child. | ## Known Limitations and Deferred Work diff --git a/packages/subagent/tool-subagent/README.md b/packages/subagent/tool-subagent/README.md index 416c24af5a..714eb8f62b 100644 --- a/packages/subagent/tool-subagent/README.md +++ b/packages/subagent/tool-subagent/README.md @@ -31,8 +31,35 @@ A non-`completed` stop reason becomes an `isError` tool result; partial child ou | Context surface | What the model sees | Token effect | |---|---|---| -| Tool schema | While the configured provider exists, the parent model sees one `{ description, prompt }` tool under `toolName`. Its description explicitly says whether the child inherits completed turns or needs a standalone prompt; persona, model, filter, depth, and provider choice remain deployment config. For a capable in-process provider, the filter changes the child's global wire schemas, lookup, execution, and Code Mode SDK bindings, not standalone guidance or an inherited authority ceiling. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema; exposing multiple providers adds one independently named schema per load. | -| Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. The result contains only the child's final text or a stop-reason error, never intermediate child steps. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | +| Standalone-provider schema | While a fresh-context provider exists, the configured tool uses the exact [standalone tool](#standalone-provider-tool-description) and [`prompt` parameter](#standalone-provider-prompt-description) descriptions. | Fixed schema cost per parent request while mounted. Removing the provider removes the whole schema. | +| Inherited-context-provider schema | A provider that seeds completed turns uses the exact [inherited-context tool](#inherited-context-provider-tool-description) and [`prompt` parameter](#inherited-context-provider-prompt-description) descriptions. Both variants describe `description` exactly as `A short (3-5 word) description of the delegated task, for display.` | Fixed schema cost per parent request while mounted. Exposing multiple providers adds one independently named schema per load. | +| Tool-call history and result | The task description and full prompt remain in the parent assistant tool call. Success contains only the child's data-dependent final text. Other stop reasons become exactly `Error: subagent run was cancelled`, `Error: subagent run failed`, `Error: subagent run hit its token limit before finishing`, `Error: subagent declined the task`, or `Error: subagent run ended abnormally ()`; a call without an owning agent becomes `Error: subagent tool requires a calling agent (exec.agent was undefined)`. Intermediate child steps never enter the parent. | Prompt and final output are data-dependent retained tokens. All child working context is paid in the child and omitted from the parent. | + +### Verbatim model-visible text + +#### Standalone-provider tool description + +```text +Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. +``` + +#### Standalone-provider prompt description + +```text +The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. +``` + +#### Inherited-context-provider tool description + +```text +Delegate a task to a subagent that INHERITS this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn), returning only its final result. Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive only its final answer, not its intermediate steps. +``` + +#### Inherited-context-provider prompt description + +```text +The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. +``` ## Known Limitations and Deferred Work diff --git a/packages/todo/tool-todo/README.md b/packages/todo/tool-todo/README.md index 32bb39c860..260218e643 100644 --- a/packages/todo/tool-todo/README.md +++ b/packages/todo/tool-todo/README.md @@ -28,8 +28,16 @@ A function/namespace plugin: it exports `name` / `inject` / `apply` and NO defau | Context surface | What the model sees | Token effect | |---|---|---| -| Tool schema | The model sees `todo_write` with the complete-list array and the three status values. | Fixed schema cost on every request where the tool is visible. | -| Tool-call history and result | Each assistant tool call retains the entire replacement list in its arguments. The tool result reports only pending, in-progress, and completed counts; the full `todo/write` session event is UI and replay state, not a second model message. | Token growth scales with every full list the model submits, and those call arguments remain until compaction. The result itself is small and fixed-shape. | +| Tool schema | The model sees `todo_write` with the exact [tool description](#todo_write-tool-description). The `todos` parameter says `The COMPLETE task list, replacing any previous list.`; each item uses `What the task is — a short imperative line.` and `pending (not started) \| in_progress (now) \| completed (done).` | Fixed schema cost on every request where the tool is visible. | +| Tool-call history and result | Each assistant tool call retains the entire replacement list in its arguments. Success returns exactly `Updated todo list: pending, in progress, completed.` Stable failures are ``Error: invalid todo: `content` must be a non-empty string``, `Error: invalid todos: duplicate content ""`, `Error: invalid todos: at most one task may be in_progress, got `, and `Error: todo_write requires an owning agent session`. The full `todo/write` session event is UI and replay state, not a second model message. | Token growth scales with every full list the model submits, and those call arguments remain until compaction. The result itself is small and fixed-shape. | + +### Verbatim model-visible text + +#### `todo_write` tool description + +```text +Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Keep AT MOST ONE todo `in_progress` at a time; while work remains, exactly one active task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished). +``` ## Known Limitations and Deferred Work diff --git a/packages/ui/acp/README.md b/packages/ui/acp/README.md index ac3def1b2f..ae512689ef 100644 --- a/packages/ui/acp/README.md +++ b/packages/ui/acp/README.md @@ -104,8 +104,8 @@ The JSON-RPC frames go on stdout, so this plugin MUST run in an example that loa | Context surface | What the model sees | Token effect | |---|---|---| -| User messages | Each ACP `session/prompt` becomes an agent user message: text passes through and a `resource_link` is rendered as text. Unsupported image, audio, and embedded-resource blocks are rejected rather than silently omitted. | Prompt tokens are data-dependent and remain in that session's history until compaction. Concurrent ACP sessions keep separate contexts. | -| Human answers and permissions | When optional consumers are loaded, ACP form answers become `ask_user_question` tool results and permission decisions control whether a tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only. | Answer and denial text enters context only through the owning tool result; presentation metadata adds zero model tokens. | +| User messages | Each ACP `session/prompt` becomes an agent user message: text passes through verbatim and each `resource_link` becomes exactly a leading newline, `[resource_link name= uri=]`, and a trailing newline. Unsupported image, audio, and embedded-resource blocks are rejected rather than silently omitted. | Prompt tokens are data-dependent and remain in that session's history until compaction. Concurrent ACP sessions keep separate contexts. | +| Human answers and permissions | When optional consumers are loaded, ACP form answers become the exact JSON shape documented by `dsh-tool-ask-user`. Failures become `Error: ACP user questions must come from an agent-owned request`, `Error: ACP user question has no matching session`, `Error: ACP elicitation request failed`, `Error: ask_user_question was cancelled by the user`, `Error: ask_user_question returned no answer`, or `Error: ask_user_question was aborted before the user answered`. Permission decisions control whether another tool yields success or denial. ACP tool cards, terminal output, diffs, and streamed session updates are UI-only. | Answer, error, and denial text enters context only through the owning tool result; presentation metadata adds zero model tokens. | | Loaded sessions | `session/load` resumes the persisted log, after which the loop sends its reconstructed history and request header. Replaying that log to the editor is not an extra model message. | Restored context has the persistence and session packages' normal retained cost; ACP replay to the client adds none. | ## Known Limitations and Deferred Work diff --git a/packages/ui/stdio-agent/README.md b/packages/ui/stdio-agent/README.md index 38254f5f5d..da0f1f1fcd 100644 --- a/packages/ui/stdio-agent/README.md +++ b/packages/ui/stdio-agent/README.md @@ -71,6 +71,7 @@ Swap `llm-deepseek` for a `mock-llm` leaf plugin and you have the echo demo — | Context surface | What the model sees | Token effect | |---|---|---| | Composed terminal agent request | Through `dsh-agent-core`, the `main` agent receives the harness identity, configured persona, skill catalog, and visible tools; this app also composes the `ask_user_question` schema. Each readline submission becomes a user message. | Child prompt and schema costs repeat per request; user input and tool history grow until compaction. The welcome banner, logger output, and rendered transcript are terminal-only and add zero model tokens. | +| Human-answer result | Through `dsh-tool-ask-user`, successful terminal answers use that package's exact compact JSON shape. Interruption becomes exactly `Error: ask_user_question was interrupted before the user answered`; a closed stdin becomes `Error: ask_user_question cannot be answered because stdin is closed`. | Only a completed or failed tool call adds retained result tokens; prompts printed while waiting are terminal-only. | ## Known Limitations and Deferred Work diff --git a/packages/ui/tool-ask-user/README.md b/packages/ui/tool-ask-user/README.md index c41e43d920..8c504d8de6 100644 --- a/packages/ui/tool-ask-user/README.md +++ b/packages/ui/tool-ask-user/README.md @@ -24,7 +24,7 @@ This is the consumer package for the user-interaction seam. It does not render U | Context surface | What the model sees | Token effect | |---|---|---| | Tool schema | The model sees `ask_user_question` with question ids, prompts, headings, options, and multi-select flags. | Fixed schema cost on every request where the tool is visible. | -| Tool-call history and result | The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees JSON containing selected labels and optional custom text. UI interaction while the call is pending is not model context. | Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human. | +| Tool-call history and result | The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees compact JSON in the exact shape `{"answers":[{"id":"","selected":["