Cookbook: adding a workspace package

The file-by-file checklist for a new @deepseek-ai/dsh-<name> package. This checklist is validated against the bash and adapter packages as templates; if it drifts from them, fix it here.

1. Create the package

packages/<group>/<pkg>/
  package.json     # copy from packages/core/tools, adjust name/description/deps
  tsconfig.json    # extends ../../../tsconfig.base.json, rootDir src,
                   # outDir lib/types, references: ../../../vendor/cosmokit,
                   # ../../../vendor/cordis (+ ../../../vendor/schemastery if
                   # you use Config, + ../../<group>/<dep> for each dsh dep)
  src/index.ts     # service default export or plugin (name/inject/apply/Config)
  README.md        # service API, events, extension points, design notes,
                   # + gated Model Experience context blocks or short form
                   # + the gated "Known Limitations and Deferred Work" section
                   # (or a whitelist entry in scripts/verify-package-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.

package.json invariants (enforced by pnpm run constraints / scripts/check-workspace-constraints.ts): private: true, a version matching the root package.json, type: module, main: "lib/index.js", types: "lib/types/index.d.ts", exports["."].types: "./lib/types/index.d.ts", exports["."].default: "./lib/index.js", @deepseek-ai/cordis in BOTH peerDependencies and devDependencies (same range). Mirror every dsh peer dependency in devDependencies. @deepseek-ai/schemastery goes in dependencies (it is a runtime validator), matching agent-loop. The files list contains exactly lib/index.js, lib/invariant.js, lib/types/**/*.d.ts, and package-specific runtime artifacts recognized by the gate; a package whose runtime export points into the emitted tree also includes lib/types/**/*.js. Do not publish src, declaration maps, JS maps, or stale root declaration files. CLI app packages with a package bin include lib/bin.js immediately after lib/index.js in files.

In-package relative imports use explicit .ts specifiers in source (for example, export * from './types.ts'). The compiler rewrites those to .js in emitted JS and leaves explicit .ts specifiers in declarations, which standard NodeNext/Node16 TypeScript consumers resolve to the sibling .d.ts files.

2. Register it in the root configs

File Change
tsconfig.base.json no edit for an existing group; for a new group, add a ./packages/<group>/*/src candidate to the @deepseek-ai/dsh-* wildcard
tsconfig.host.json (Host package) or tsconfig.client.json (Client package) add { "path": "./packages/<group>/<pkg>" } to references — an ordinary package belongs to exactly one aggregate, never both. api/remotes uses a repository-specific split because the Host generates a contract that the Client consumes in a later phase; new packages must not copy it (layout)
knip.json only if the package has entrypoints that repository discovery does not already cover

A packages/client/* package additionally extends tsconfig.base.client.json instead of tsconfig.base.json, and a client plugin package declares dsh.client in package.json, exports ./client, and calls the shared tsdown preset (packages/client/tsdown.client.ts) — see packages/client/AGENTS.md for the client-side contract.

Covered automatically by globs or package-manifest discovery — no edits needed: root package.json workspaces, scripts/publint-all.ts, tsdown.config.ts, .oxlintrc.json, scripts/check-workspace-constraints.ts.

3. Decide the package topology

For a swappable capability, separate Service Definition / Service Provider / Consumer roles into packages when they evolve independently (see docs/architecture.md § "Capability seams" — the shell trio is the template). A single-purpose plugin stays one package.

Name the role that exists

Name the stable current responsibility. Do not name the first implementation, a possible future expansion, or the Cordis base class. An interface package names the capability. An implementation package adds the mechanism, protocol, environment, or vendor that distinguishes it. Use local only when same-host execution is part of the contract.

Use a singular ctx key for one engine, runtime, policy, controller, resolver, store, or current configuration. Use a plural key for a registry or a service that owns multiple named members. The class role and key number must agree. Do not reuse one Cordis Context key for incompatible host and client declarations. TypeScript declaration merging sees both faces even when they use separate runtime contexts. Add the role suffix when the natural plural already belongs to another face.

Word Use it when Do not use it when
Controller It accepts commands or user intent and changes one existing domain or presentation state. It executes arbitrary work, owns a provider fleet, or only converts values for display.
Store It owns one data set and mainly offers CRUD, snapshot, or subscription operations for that data. It validates a state machine, arbitrates authority, dispatches work, or owns provider precedence. A map does not make a class a store.
Directory It exposes entries and metadata for discovery or selection. Producers register arbitrary implementations into it, or callers execute work through it.
Presenter It is a pure conversion from domain values or tool arguments to render intent. It performs I/O, subscribes, mutates state, or owns lifecycle.
Registry It owns a dynamic set of named registrations, including lookup, duplicate or precedence rules, lifetime, and disposal. Its main contract is dispatch, execution, cancellation, policy, or orchestration.
Runtime It runs live work and owns dispatch, cancellation, provider coordination, or operation lifecycle across calls. It only stores records, returns a catalog, resolves one value, or holds configuration.
Resolver It computes or locates one answer from supplied inputs without owning that answer's lifecycle. It owns a mutable collection or long-running execution.
Binder It attaches one declared interface to a caller context or lifecycle and returns the bound value. It owns the value as a collection, controls its domain state, or only converts data.
Engine It implements a domain algorithm or stateful execution model. It only selects a provider or forwards across a protocol boundary.
Policy It decides what is allowed, selected, limited, or observed. It performs the mechanism that the decision permits.
Executor It runs one explicit request or resolved specification in one capability. It owns a broad application lifecycle or provider catalog.
Gateway It adapts a process, network, RPC, or API boundary. It only registers same-process services or stores metadata.
Provider It supplies one implementation of a capability definition. Add a mechanism or vendor qualifier when several can exist. It is the capability definition, provider registry, or consumer runtime.
Backend It implements replaceable lower-level persistence, transport, or execution behind a defined interface. It is a user-facing service or one returned live-resource reference.
Handle It refers to one live resource and controls or observes that resource. It creates and manages the complete resource pool.
Config It owns one resolved configuration value or one tightly bounded record and its update contract. It stores a general collection, executes work, or exposes unrelated settings.
Service It owns a cohesive domain service that no sharper role above states honestly. The name exists only because the class extends Cordis Service.

Use SDK only for the JSON-RPC client/server protocol used by the supported Python and TypeScript SDKs. DeepSeek Harness itself is an agent harness, not an SDK project. Use the canonical product spelling Typert, never TypeRT or typeRT.

4. Write the package README

Keep package-specific service API, config, events, extension points, and design notes first. The limitations section records durable consumer gaps and non-obvious maintainer constraints owned by this package; ordinary cleanup stays in its source TODO or Agent Note. An indirect Model Experience sentence may name the consumer that surfaces this package's contribution, but it does not restate that consumer's implementation. End a package README with this canonical sequence:

## Model Experience

### Request context and condition

#### What the model sees

The exact data-dependent fields, an anchored generated-catalog link, or an introduction to the verbatim literal below.

##### Verbatim text for this field, when needed

```markdown
Stable system-prompt prose of any length, or another long non-generated literal, copied exactly from source.
```

#### Token effect

Fixed, conditional, retained, replaced, capped, or zero-direct token effect.

#### KV Cache effect

Append-only, prefix-stable, replacing, or independent behavior, including the exact conditions that may invalidate reuse.

## Known Limitations and Deferred Work

- **Consumer-visible gap** — exact missing operation or case, its consequence, and any maintainer constraint.

Fill Model Experience from the implementation. Use one H3 per direct, conditional, capped, lifetime, or auxiliary model-context entry, with the three ordered H4 fields shown above and one prose paragraph under each. Quote stable text owned by the package: system-prompt prose goes in a titled H5 plus markdown fence under the field that introduces it—normally What the model sees—other short literals stay inline with named placeholders, and other long literals use the same nested form. Summarize only data-dependent or provider-owned text. A tool-schema entry links its anchored section in the generated tool catalog and states only deltas absent there. Keep prompt and schema entries separate when scoping can hide one without the other. In KV Cache effect, distinguish append-only growth, a stable repeated prefix, replacement of earlier request tokens, and an independent model request, then name the package-owned changes that can invalidate reuse. “Does not invalidate” means the package preserves an already-reusable prefix; provider cache availability and eviction remain outside the package contract. The prose standard governs completeness and ownership; the verifier enforces the required section structure.

A package with no context effect or one consumer-owned path uses the audited None, as or Indirectly, through sentence in SENTENCE_MODEL_EXPERIENCE, followed by a KV Cache effect H4 and one non-empty paragraph; a model-agnostic generic package may instead join NO_MODEL_EXPERIENCE_SECTION. Do not expand either case into a description of another package's work. The limitations allowlist is independent. The Model Experience Agent Note records the rationale.

5. Verify

pnpm install        # registers the workspace
pnpm run doc-sync
pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run build && pnpm run hygiene

Follow the repository testing policy for the behavior-specific checks and coverage required by the new package.

实操手册:添加 workspace 包

为新建 @deepseek-ai/dsh-<name> 包提供的逐文件清单。本清单以 bash 和适配器这两个包为模板进行验证;如果清单与模板有出入,请在此修正。

1. 创建包

packages/<group>/<pkg>/
  package.json     # copy from packages/core/tools, adjust name/description/deps
  tsconfig.json    # extends ../../../tsconfig.base.json, rootDir src,
                   # outDir lib/types, references: ../../../vendor/cosmokit,
                   # ../../../vendor/cordis (+ ../../../vendor/schemastery if
                   # you use Config, + ../../<group>/<dep> for each dsh dep)
  src/index.ts     # service default export or plugin (name/inject/apply/Config)
  README.md        # service API, events, extension points, design notes,
                   # + gated Model Experience context blocks or short form
                   # + the gated "Known Limitations and Deferred Work" section
                   # (or a whitelist entry in scripts/verify-package-readme-limitations.ts)

当已有分组与包的角色匹配时,选择该分组(corellmbashcompactsubagenttodosession-persistenceuiutilsupport)。允许新建分组,但分组只是纯容器:没有 package.json,没有源文件,包仍然恰好位于其下一层。

package.json 不变式(由 pnpm run constraints / scripts/check-workspace-constraints.ts 强制执行):private: trueversion 与根 package.json 一致,type: modulemain: "lib/index.js"types: "lib/types/index.d.ts"exports["."].types: "./lib/types/index.d.ts"exports["."].default: "./lib/index.js"@deepseek-ai/cordis 同时出现在 peerDependencies 和 devDependencies 中(相同范围)。每个 dsh 对等依赖(peer dependency)都要在 devDependencies 中镜像。@deepseek-ai/schemastery 放在 dependencies 中(它是运行时校验器),与 agent-loop 保持一致。files 列表精确包含 lib/index.jslib/invariant.jslib/types/**/*.d.ts 以及门禁认可的包专用运行时产物;如果包的运行时 export 指向输出树,还要包含 lib/types/**/*.js。不要发布 src、声明映射、JS map 或陈旧的根声明文件。带有 bin 的 CLI 应用包在 files 中将 lib/bin.js 紧跟在 lib/index.js 之后。

包内的相对导入在源码中使用显式 .ts 后缀(例如 export * from './types.ts')。编译器在输出的 JS 中将其重写为 .js,在声明文件中保留显式 .ts 后缀;标准的 NodeNext/Node16 TypeScript 消费方会将其解析到同目录的 .d.ts 文件。

2. 在根配置中注册

文件 变更
tsconfig.base.json 已有分组无需编辑;新分组需为 @deepseek-ai/dsh-* 通配符添加 ./packages/<group>/*/src 候选路径
tsconfig.host.json(Host 包)或 tsconfig.client.json(Client 包) references 中添加 { "path": "./packages/<group>/<pkg>" }——普通包恰好属于一个 aggregate,绝不两个都加。api/remotes 因 Host 生成约定与 Client 消费约定之间存在顺序依赖而使用仓库专属拆分,新增包不得仿照(布局
knip.json 仅当包有仓库发现机制尚未覆盖的入口时需要

packages/client/* 包改为 extends tsconfig.base.client.json(而非 tsconfig.base.json);client 插件包还需在 package.json 声明 dsh.client、导出 ./client、调用共享 tsdown preset(packages/client/tsdown.client.ts)——client 侧见 packages/client/AGENTS.md

以下内容由 glob 或包 manifest(元数据清单)发现机制自动覆盖,无需手动编辑:根 package.json workspaces、scripts/publint-all.tstsdown.config.ts.oxlintrc.jsonscripts/check-workspace-constraints.ts

3. 确定包拓扑

对于可替换的能力,当 Service Definition/Service Provider/Consumer 角色需要独立演进时,将它们拆分到不同包中(见 docs/architecture.md § "Capability seams"——shell 三组件是模板)。单一用途的插件保持为一个包。

使用符合实际的角色名称

名称必须描述当前稳定职责。不要用首个实现、可能的未来扩展或 Cordis 基类命名。接口包使用能力名称。实现包加上能够区分实现的机制、协议、环境或厂商限定词。只有同主机执行属于约定时,才使用 local

一个 engine、runtime、policy、controller、resolver、store 或当前配置使用单数 ctx key。registry 或拥有多个具名成员的服务使用复数 key。类的角色与 key 的单复数必须一致。不得让不兼容的 host 与 client 声明复用同一个 Cordis Context key。即使二者使用独立的运行时 context,TypeScript 声明合并仍会同时看到两种类型。如果自然复数已经属于另一个端面,就增加职责后缀。

适用条件 不适用条件
Controller 接受命令或用户意图,并改变一项既有领域状态或展示状态。 执行任意工作、拥有一组 provider,或只把值转换为展示形式。
Store 拥有一组数据,主要提供该数据的 CRUD、snapshot 或 subscription 操作。 校验状态机、裁决权限、分派工作或拥有 provider 优先级。类中有 map 不等于 store。
Directory 暴露供发现或选择的条目及其元数据。 producer 向其中注册任意实现,或调用方通过它执行工作。
Presenter 将领域值或工具参数纯转换为渲染意图。 执行 I/O、订阅、修改状态或拥有生命周期。
Registry 拥有一组动态具名注册,以及查询、重复项或优先级规则、生命周期和释放。 主要约定是分派、执行、取消、策略或编排。
Runtime 运行实时工作,并跨调用拥有分派、取消、provider 协调或操作生命周期。 只存储记录、返回目录、解析一个值或保存配置。
Resolver 根据输入计算或定位一个答案,但不拥有该答案的生命周期。 拥有可变集合或长时间运行的执行过程。
Binder 把一个已声明接口绑定到调用方的 context 或生命周期,并返回绑定值。 把该值作为集合持有、控制其领域状态,或只转换数据。
Engine 实现领域算法或有状态执行模型。 只选择 provider 或跨协议边界转发请求。
Policy 决定允许、选择、限制或观察什么。 执行该决定所允许的机制。
Executor 在一项能力中运行一个明确请求或已解析 spec。 拥有广泛应用生命周期或 provider 目录。
Gateway 适配进程、网络、RPC 或 API 边界。 只注册同进程服务或存储元数据。
Provider 提供一项能力定义的一个实现。存在多个实现时,加上机制或厂商限定词。 表示能力定义、provider registry 或消费方 runtime。
Backend 在已定义接口之后实现可替换的底层持久化、传输或执行。 表示面向用户的服务或一个已返回的实时资源引用。
Handle 引用一个实时资源,并控制或观察该资源。 创建并管理完整资源池。
Config 拥有一个已解析配置值,或一项边界严格的配置记录及其更新约定。 存储通用集合、执行工作或暴露无关设置。
Service 拥有一项无法用以上更精确角色诚实描述的内聚领域服务。 只因为类继承 Cordis Service 而使用该名称。

只对受支持的 Python 与 TypeScript SDK 所使用的 JSON-RPC 客户端/服务器协议使用 SDK。DeepSeek Harness 本身是 agent harness,不是 SDK 项目。产品拼写统一使用 Typert,不得使用 TypeRTtypeRT

4. 编写包 README

将包特有的服务 API、配置、事件、扩展点和设计说明放在前面。limitations 部分记录持久的消费方缺口和本包拥有的非显而易见的维护者约束;日常清理事项留在源码 TODO 或 Agent Note 中。间接的 Model Experience 语句可以点名暴露本包贡献的消费方,但不重述该消费方的实现。包 README 以如下规范序列结尾:

## Model Experience

### Request context and condition

#### What the model sees

The exact data-dependent fields, an anchored generated-catalog link, or an introduction to the verbatim literal below.

##### Verbatim text for this field, when needed

```markdown
Stable system-prompt prose of any length, or another long non-generated literal, copied exactly from source.
```

#### Token effect

Fixed, conditional, retained, replaced, capped, or zero-direct token effect.

#### KV Cache effect

Append-only, prefix-stable, replacing, or independent behavior, including the exact conditions that may invalidate reuse.

## Known Limitations and Deferred Work

- **Consumer-visible gap** — exact missing operation or case, its consequence, and any maintainer constraint.

根据实现填写 Model Experience。每个直接、条件、上限、生命周期或辅助的模型上下文条目使用一个 H3,包含上述三个有序 H4 字段,每个字段下有一个正文段落。引用包拥有的稳定文本:系统提示词放在引出它的字段下,用带标题的 H5 加 markdown 围栏表示,通常归入 What the model sees;其他短文本以命名占位符内联,其他长文本使用相同的嵌套形式。仅概述数据依赖或提供方拥有的文本。工具 schema 条目链接到生成的工具目录中对应的锚定章节,仅说明该处缺失的差异。当作用域可以隐藏 prompt 或 schema 其中之一而不影响另一个时,将二者分开。填写 KV Cache effect 时,应区分仅追加增长、稳定重复的前缀、替换既有请求 token 和独立模型请求,并列出会使缓存复用失效、且由本包拥有的变化。“不使缓存失效”仅表示本包保留了已有的可复用前缀;缓存是否可用以及何时淘汰不属于本包约定。行文标准约束完整性与归属;验证器强制执行所需章节结构。

没有上下文效果或仅有消费方拥有路径的包使用 SENTENCE_MODEL_EXPERIENCE 中经过审计的 None, as Indirectly, through 语句,随后添加 KV Cache effect H4 和一个非空正文段落;与模型无关的通用包可以改为加入 NO_MODEL_EXPERIENCE_SECTION。两种情况都不要展开为对另一个包工作的描述。limitations allowlist 独立管理。Model Experience Agent Note 记录了设计动机。

5. 验证

pnpm install        # registers the workspace
pnpm run doc-sync
pnpm run constraints && pnpm run typecheck && pnpm run lint
pnpm run build && pnpm run hygiene

请遵循仓库测试政策,执行新包所需的行为专项检查并达到相应覆盖率。