插件:连 agent 循环都是插件
pi 的扩展是挂在固定内核外面的工厂函数 (pi) => void:想收回注册的东西,得自己留着 pi.on() 返回的撤销函数,或者整体 reload1;循环、会话、工具注册表换不掉,一个进程只有一套组合。DeepSeek Harness 的底座是 Cordis 插件框架,没有特权内核,连 agent 循环都是插件,注册的东西卸载时由框架撤销。下面每节贴源码模版和用 dsh 自己的包跑出的效果,构造的插件和工具都注明了。
卸载就撤销:注册都挂在插件上
问题:插件管理器关掉一行、配置热重载、agent 结束,都要把插件装进来的工具、提示词段、监听器收干净——漏一样,模型就还看得见已经卸掉的工具。
Cordis 里每个注册都是当前插件上的一个 effect:执行时交回撤销函数,插件卸载时框架按逆序全跑一遍。监听器这样挂:
/**
* Store a listener record as an effect on the current fiber.
*
* @param label — effect label shown in fiber diagnostics.
* @param hooks — the listener list for one event.
* @param callback — the listener to store.
* @param options — placement and filtering options.
* @returns a disposer that unregisters the listener.
*/
register(label: string, hooks: Hook[], callback: any, options: EventOptions): () => void {
const method = options.prepend ? 'unshift' : 'push'
return this.ctx.fiber.effect(() => {
hooks[method]({ ctx: this.ctx, callback, ...options })
return () => this.unregister(hooks, callback)
}, label)
}
dsh 的工具注册表走同一条路:
return this.layers.effect(
this.ctx,
layer => layer.tools.insert(name, definition),
{ label: 'tools.register()' },
)
构造一个插件 weather,注册工具、提示词段、监听器和一个带清理的 effect,挂到真实的注册表上再卸掉:
before mount
tools: []
prompt sections: ["harness:identity","deployment:persona-prefix","deployment:persona-suffix"]
effect: open cache
after mount
tools: ["weather"]
prompt sections: ["harness:identity","deployment:persona-prefix","weather:guidance","deployment:persona-suffix"]
weather plugin heard ping
effect: close cache
after dispose
tools: []
prompt sections: ["harness:identity","deployment:persona-prefix","deployment:persona-suffix"]
插件没写一行反注册代码,三样注册随卸载消失。
换提供方不动消费方:依赖到齐才启动
问题:约定、实现、给模型用的接口变化节奏不同,打包在一起,换实现就牵动接口。决策记录2原话:
把本地执行器换成沙箱化执行器时,模型看到的工具 schema 也会被搅动,尽管面向模型的约定从未改变。
dsh 把可替换能力(项目叫 seam)拆成三个角色,bash 是范本。定义是占住 ctx.shell 的抽象类:
export abstract class ShellExecutor extends Service {
constructor(ctx: Context) {
super(ctx, 'shell')
}
提供方继承它,沙箱版又继承本地版,多依赖两项沙箱服务:
export class LocalBashExecutor extends ShellExecutor {
static inject = ['subprocess']
/**
* Registers as `ctx.shell` in place of the local executor and requires a
* `ctx.sandbox` provider plus `ctx.sandboxPolicy`; the tool layer is
* unchanged. Tool calls pass the calling session's resolved policy; direct
* calls fall back to deployment policy. `result.sandbox` reports the mode and
* enforcement actually used.
*/
export class SandboxBashExecutor extends LocalBashExecutor {
static override inject = ['subprocess', 'sandbox', 'sandboxPolicy']
消费方是 bash 工具,只声明要 shell:
export const name = 'tool-bash'
export const inject = ['tools', 'shell', 'systemPrompt', 'shellEnv', 'workingDirectory']
启停由 inject 决定:依赖缺一个就卸载自己,实现换了就重跑:
_refresh() {
let epoch: string | boolean = false
epoch = ''
for (const name of Object.keys(this.inject)) {
const impl = this._store[name]
if (!impl) {
epoch = INACTIVE
break
}
epoch += ':' + impl.fiber.uid
}
this._setEpoch(epoch)
}
private _setEpoch(epoch: string) {
const oldEpoch = this._runner.epoch
if (epoch === oldEpoch) return
this._runner.epoch = epoch
if (this.inertia) return
this._updateState(() => {
if (epoch !== INACTIVE && oldEpoch === INACTIVE) {
this.inertia = this._reload()
return FiberState.LOADING
} else {
this.inertia = this._unload()
return FiberState.UNLOADING
}
})
写一个只 inject shell 的消费方,依次挂、卸、换提供方。提供方一是真实的 LocalBashExecutor;DryRunBash 是构造的,照沙箱版继承本地版、只改 resolve:
1. mount consumer (no shell provider yet)
run: (no consumer listening)
2. mount subprocess-local + LocalBashExecutor
consumer: activated, ctx.shell is LocalBashExecutor
run: LocalBashExecutor -> exit 0, stdout "hello"
3. dispose LocalBashExecutor
consumer: deactivated
run: (no consumer listening)
4. mount DryRunBash in its place
consumer: activated, ctx.shell is DryRunBash
run: DryRunBash -> exit 0, stdout "[dry-run] echo hello"
消费方代码没动:没有提供方时连监听器都没装上,换了提供方自己重新激活。默认挂的是沙箱版,换本地版只改配置一行(下一节实演);packages/ssh/ 的一组 SSH 提供方也靠换行把文件和进程搬到远端。
拦截点是事件:守卫只能否决
问题:hook 若能改参数、靠注册顺序定结果,后注册的监听器就能把前面的拒绝翻回来,日志里记的调用也和实际跑的对不上。pi 的 tool_call 处理器能直接改 event.input;dsh 没开这个口子,改参数的方案还停在提案,要求日志、界面、执行三处同时改写。
dsh 的说法是「原生 hook 不是一个包」:hook 就是监听规范事件的普通插件。拦截用 waterfall——外层先跑,调 next() 交给里层,不调就短路:
/**
* Compose listeners around the final `next` callback.
*
* The last dispatch argument is treated as the innermost `next`. Listeners
* run outermost-first; a listener that does not call `next()` vetoes the
* rest of the chain, including the built-in behavior.
*
* @param args — optional `this`, the event name, listener arguments, then `next`.
* @returns the outermost listener's return value.
*/
waterfall(...args: any[]) {
const cbs = this.dispatch('waterfall', args)
const inner = args.pop()
const next = () => {
const cb = cbs.shift() ?? inner
return cb(...args)
}
args.push(next)
return next()
}
调用前的决定只有四种,没有改参数——参数已经记进日志、上了屏:
/**
* Pre-dispatch decision. `allow` runs the call; `deny` materializes its
* model-facing reason and optional structured error identity; `cancel` selects
* the canonical cancellation result without presenting a policy denial; `ask`
* runs only after an approval service returns `allowed-once` and otherwise
* denies; its `reason` is the audited approval reason and its optional
* `displayReason` is the localized prompt text. Input rewriting is excluded because arguments are already logged and
* presented.
*/
export type PreToolDecision =
| { kind: 'allow' }
| { kind: 'deny'; reason: string; info?: ToolErrorInfo }
| { kind: 'cancel' }
| { kind: 'ask'; reason?: string; displayReason?: { readonly en: string; readonly [locale: string]: string } }
waterfall 之后还有一层守卫,签名里没有「放行」:
/**
* A monotonic execution guard evaluated after every `tools/pre-execute`
* listener and before the tool body. Returning a reason denies the call;
* returning `undefined` leaves it unchanged. Because guards have no allow
* result, listener ordering cannot turn a denial back into permission.
* @param execution - the identity-protected call after extensible pre-execute policy completed.
* @returns a final denial reason, or `undefined` to leave the call allowed.
*/
export type ToolGuard = (execution: Readonly<ToolExecution>) => string | undefined
const carrier = scopeTarget(this, exec.agent)
const gate = await this.ctx.waterfall(
carrier, 'tools/pre-execute', exec,
() => Promise.resolve<PreToolDecision>({ kind: 'allow' }),
)
const askResolution = gate.kind === 'ask'
? await this.serviceAsk(exec, gate)
: { decision: gate, approvalCancelled: false }
const { decision } = askResolution
if (this.callerCancelled(exec) && askResolution.approvalCancelled) {
return await next({ kind: 'post-result', exec, result: toolAbortedBeforeDispatchResult() })
}
if (decision.kind === 'cancel') {
return await next({ kind: 'post-result', exec, result: toolAbortedBeforeDispatchResult() })
}
const denialReason = decision.kind === 'allow' ? this.guardReason(exec) : decision.reason
在真实工具管线上构造工具 rm_cache 和三个插件:记日志的 audit、拒绝策略、插到最外层直接放行的 allow-all。拒绝先写成监听器,再写成守卫:
1. no plugins
-> cache deleted
2. + audit (wraps, calls next)
[audit] before rm_cache
[audit] decision allow
-> cache deleted
3. + deny as a waterfall listener
[audit] before rm_cache
[audit] decision deny
-> isError: Error: rm_cache is disabled by policy
4. + allow-all prepended outermost: the listener denial is skipped
-> cache deleted
5. same denial as a guard, allow-all still mounted
-> isError: Error: rm_cache is disabled by policy
6. guard and allow-all unmounted
[audit] before rm_cache
[audit] decision allow
-> cache deleted
第 4 步,allow-all 不调 next(),里层的拒绝连同 audit 都没跑;第 5 步写成守卫,翻不回来。硬禁令写守卫,waterfall 留给包装和可协商的策略;管线后几站和审批见工具与安全边界。
组装是一叠 patch:agent 循环也只是一行
问题:启动器把组合写死,装一个树外插件(TUI、新的模型 provider)就得改仓库,第三方包也没处贡献默认组合。
profile 是 $DSH_HOME/profiles/<名字> 目录:一张组合包(bundle)清单,加用户自己的 cordis.patch.yml。随发行版带的模板:
/** The shipped profile templates auto-initialized on first use, by name. */
export const PROFILE_TEMPLATES: Record<string, ProfileTemplate> = {
acp: {
bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-acp-app'],
},
web: {
bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-web-app'],
},
headless: {
bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-headless'],
},
sdk: {
bundles: ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-sdk-app'],
},
'sdk-minimal': {
bundles: ['@deepseek-ai/dsh-sdk-minimal'],
},
}
启动时从空列表起,依次叠组合包、profile patch、home patch、--patch。patch 按 id 找行、整体替换写到的字段,包名对不上就跳过:
const target = entryMap.get(id)
if (!target) {
warn('patch: entry %C not found', id)
continue
}
if (name && name !== target.name) {
warn('patch: name mismatch for %C (expected %C, got %C), skipping', id, target.name, name)
continue
}
for (const [key, value] of Object.entries(overrides)) {
if (key === 'id') continue
target[key] = value
}
公共底座 dsh-base 开头写明了这套语义,agent 循环在里面是普通的一行:
# The dsh-base bundle patch: the shared core of each base-backed profile, applied as
# ONE insert over the empty profile root. Later bundle patches and the user's
# profile cordis.patch.yml address these rows by id, with the last write
# winning per row.
#
# A patch replaces the targeted row's whole `config` rather than merging into
# it, so a row whose value differs by mode does NOT live here: it belongs to
# each mode bundle, keeping any single row down to one bundle layer plus the
# user's. Mode-specific rows appear below only with shared plugin identity and
# neutral defaults; each mode bundle restates its complete configuration.
#
# Row order carries no load semantics (activation is service-availability
# driven); the grouping is for readers.
# Agents created at startup. The base stays empty; raw overlays may create
# agents, while Web creates sessions on client request.
- id: agent-loop
name: '@deepseek-ai/dsh-agent-loop'
config:
agents: []
dsh --dump-config 不启动,只调同一个叠加函数,打出来的就是会挂的。在临时 DSH_HOME 里真跑 dsh --profile web --dump-config,展开是 190 行顶层插件,每段标着来自哪一层:
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tools
name: '@deepseek-ai/dsh-tools'
config:
mode: !!js process.env.DSH_TOOLS_MODE
- id: system-prompt
name: '@deepseek-ai/dsh-system-prompt'
config:
personaPrefix: You are a coding agent powered by the {{model}} model.
# == @deepseek-ai/dsh-base
- id: agent-loop
name: '@deepseek-ai/dsh-agent-loop'
config:
agents: []
再放一份构造的用户 patch:沙箱版 bash 换本地版,直接改 agent-loop 的包名,换人设:
# 构造的用户 patch,放进 $DSH_HOME/profiles/web/cordis.patch.yml
# 1. 带沙箱的 bash 关掉,插入不带沙箱的本地实现
- id: bash-sandbox
disabled: true
- insert:
- id: bash-local
name: '@deepseek-ai/dsh-bash-local'
config:
timeoutMs: 60000
# 2. 想直接把 agent-loop 这一行换成别的包
- id: agent-loop
name: '@example/my-agent-loop'
# 3. 换人设
- id: system-prompt
config:
personaPrefix: You are a careful code reviewer.
dsh: [$DSH_HOME/profiles/web/cordis.patch.yml] patch: name mismatch for "agent-loop" (expected "@deepseek-ai/dsh-agent-loop", got "@example/my-agent-loop"), skipping
137a138
> # == @deepseek-ai/dsh-base, patched by $DSH_HOME/profiles/web/cordis.patch.yml
140c141
< disabled: !!js process.platform === 'win32'
---
> disabled: true
142a144
> # == @deepseek-ai/dsh-base
389a392
> # == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app, $DSH_HOME/profiles/web/cordis.patch.yml
393c396
< personaPrefix: You are a coding agent powered by the {{model}} model.
---
> personaPrefix: You are a careful code reviewer.
1229a1233,1237
> # == $DSH_HOME/profiles/web/cordis.patch.yml
> - id: bash-local
> name: '@deepseek-ai/dsh-bash-local'
> config:
> timeoutMs: 60000
被改的行标上了来源,插入的行排在最后;改包名那条被跳过——同一个 id 不能偷换插件,想换循环就 disabled 这一行、insert 自己的。
循环插件占注册表里的工厂槽位,同时只能有一个:
/** Thrown when create/resume is called before an agent factory is registered. */
const NO_FACTORY_MESSAGE = 'no agent factory registered (load an agent-loop plugin)'
setFactory(factory: AgentFactory): () => void {
const dispose = this.ctx.effect(() => {
if (this.factory !== undefined) throw new Error('an agent factory is already registered')
// Avoid stacking two Cordis shadow layers when a caller passes a Service
// already read through a context. Calls are re-traced through their
// actual owner context below.
const target = (factory as AgentFactory & { [symbols.original]?: AgentFactory })[symbols.original] ?? factory
this.factory = { target }
return () => { this.factory = undefined }
}, 'agents.setFactory()')
ctx.effect(() => ctx.agents.setFactory(this), 'agentLoop.setFactory()')
在真实的 dsh-agent 注册表上挂、卸一个构造的循环 TinyLoop:
without a loop plugin: no agent factory registered (load an agent-loop plugin)
with TinyLoop mounted: TinyLoop.createAgent got model=deepseek-flash (a real loop builds the agent here)
a second loop plugin: an agent factory is already registered
after TinyLoop unmount: no agent factory registered (load an agent-loop plugin)
能替换只在结构上成立:仓库只带 dsh-agent-loop 一个实现,目标续跑、Ralph 都做成循环之上的插件,见多 agent 与长时工作。
agent 作用域:同一进程里各 agent 看到的不一样
问题:一个进程里几个 agent 共享适配器、持久化和界面,人设、工具变体、监听器却只属于其中一个。全局一张注册表,专属工具就漏给别人;每个 agent 一整套服务,又把基础设施复制一遍。3
agent 对象本身就是作用域的 key:
this.scope = createScope(loopCtx, this)
this.ctx = this.scope.ctx
作用域是一个空插件加一个标签,经它注册的东西只对这个 agent 可见,也随它释放:
export function createScope(ctx: Context, key: ScopeKey, options?: CreateScopeOptions): Scope {
if (options?.parent !== undefined) bindScopeParent(key, options.parent)
const fiber = ctx.plugin(scope)
const scoped: Context = fiber.ctx.extend({ [kScope]: key })
let disposing: Promise<void> | undefined
return {
ctx: scoped,
rawDispose: fiber.dispose,
dispose: () => (disposing ??= quiesceFiber(fiber)),
}
}
注册表合并全局层和本 agent 层,同名的本层胜出;带标签的监听器只收本 agent 的事件;收窄工具只能对某个 agent 做:
/**
* Restrict global tools for the calling agent scope. Empty filters, unknown
* names, scope-local names, and reserved transport names fail. Restrictions
* intersect; scoped registrations remain visible.
* @param filter - global-tool mask: `allow` (keep only) and/or `deny` (remove).
* @returns the exact disposer that lifts this restriction.
*/
restrict(filter: ToolRestriction): () => void {
const scope = scopeOf(this.ctx)
if (scope === undefined) {
throw new Error('tools.restrict() requires a scoped context (agent.ctx): a context-global restriction would mask every agent — deny the tool for the intended agent instead')
}
子 agent 就这样定制:同名段换人设,restrict 去工具,加一条委派说明:
function applyChildOverrides(childCtx: Context, composition: ChildComposition): void {
childCtx.systemPrompt.context({
name: 'subagent:delegation',
order: childCtx.systemPrompt.getContextOrder('SUBAGENT_DELEGATION'),
text: SUBAGENT_DELEGATION_CONTEXT,
})
if (composition.persona !== undefined) {
childCtx.systemPrompt.section({
name: 'deployment:persona-prefix',
order: childCtx.systemPrompt.getSectionOrder('DEPLOYMENT_PERSONA_PREFIX'),
text: composition.persona,
})
}
if (composition.toolFilter !== undefined) childCtx.tools.restrict(composition.toolFilter)
}
构造三个全局工具和三个 agent:child 走真实的 applyChildComposition,换人设、去 bash;other 注册自己的 read,再挂一个监听器:
parent
tools: ["read","bash","web_fetch"]
prompt: "You are an AI agent powered by DeepSeek Harness.\n\nYou are a coding agent powered by the deepseek-flash model."
contexts: []
child (persona + deny bash via applyChildComposition)
tools: ["read","web_fetch"]
prompt: "You are an AI agent powered by DeepSeek Harness.\n\nYou are a read-only reviewer subagent."
contexts: ["subagent:delegation"]
other (own read shadows the global one)
tools: ["read","bash","web_fetch"]
prompt: "You are an AI agent powered by DeepSeek Harness.\n\nYou are a coding agent powered by the deepseek-flash model."
contexts: []
parent calls read
-> read: global implementation
other calls read
[other's listener] saw read
-> read: other agent's own implementation
restrict on the global ctx:
tools.restrict() requires a scoped context (agent.ctx): a context-global restriction would mask every agent — deny the tool for the intended agent instead
child after its scope is disposed
tools: ["read","bash","web_fetch"]
prompt: "You are an AI agent powered by DeepSeek Harness.\n\nYou are a coding agent powered by the deepseek-flash model."
contexts: []
child 换了人设、少了 bash,多出的委派说明走运行时上下文、不进 system prompt(见拼 prompt);other 的监听器只听见自己的调用;全局限制直接抛错;child 一释放,视图回到全局。作用域只管看得见什么,不管能做什么——决策记录明写「安全与权限是非目标」。
agent 预设:一个进程跑几套组合
问题:启动时那份组合决定了 agent 的工具、人设、提示词段。要让跑分用的精简 agent 和完整的编码 agent 并存,就得开两个进程;在进程级配置里关几行工具,又会改掉所有会话。4
预设是一份插件清单,写法同 profile 的行。Web 默认带 standard、ptc、minimal、cordis 四套,minimal 这样写:
# Agent preset minimal: one `@deepseek-ai/dsh-agent-preset` declaration inserted
# after the web patch. Edits saved from the Web editor override this row's
# `config.plugins` by id from the profile patch.
- insert:
- id: preset-minimal
name: '@deepseek-ai/dsh-agent-preset'
config:
id: minimal
order: 3
plugins:
- id: persona
name: '@deepseek-ai/dsh-persona'
config:
prefix: You are a helpful software engineer assistant.
complete: true
includeRuntimeContext: false
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
config:
read: false
readImage: true
write: false
edit: false
- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
config:
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
每套预设的每个修订版在进程里只常驻一份,有自己的作用域 key:
private async activate(record: Definition): Promise<void> {
const key = {}
const scope = createScope(this.owner, key)
try {
const problem = entryListProblem(record.config.plugins)
if (problem !== undefined) throw new Error(problem)
const context = scope.ctx.extend({ baseUrl: record.context.baseUrl })
const mount = await mountPreset(context, record.config.id, record.config.plugins)
const generation: Generation = { scope, key, mount, users: 0, retired: false }
context.effect(() => {
this.generations.set(key, generation)
return () => { this.generations.delete(key) }
}, 'agent-preset.mount')
record.generation = generation
} catch (error) {
record.broken = (error as Error).message
this.owner.logger.warn(`agent preset ${record.config.id}: ${record.broken}`)
await scope.dispose()
}
}
agent 用作用域父链挂到它下面——注册向下继承,事件向上放行,兄弟预设互不可见:
private join(ctx: Context, key: ScopeKey, generation: Generation): void {
const binding = { parent: bindScopeParent(key, generation.key), generation }
generation.users++
this.bindings.set(key, binding)
ctx.effect(() => async () => {
this.bindings.delete(key)
binding.generation.users--
await this.collect(binding.generation)
}, 'agent-preset.binding')
}
预设自带的服务(minimal 里的压缩、持久终端)必须放进独立的 isolate 命名空间,否则挂载被拒:
export async function mountPreset(ctx: Context, id: string, plugins: PresetDefinition['plugins']): Promise<PresetMount> {
if (scopeOf(ctx) === undefined) throw new Error('agent-preset: mounting requires a scope')
await ctx.fiber.await()
const tree = new PresetTree(ctx)
ctx.effect(() => () =>{ tree.root.stop() }, 'agent-preset.tree')
await tree.root.update(prepareProfileEntries(ctx, plugins as EntryOptions[], ctx.baseUrl))
const audit = await auditRows(tree)
const leaked = leakedServices(ctx, ctx.fiber)
if (audit.failed.length > 0) throw new Error(audit.failed.join('\n'))
if (leaked.length > 0) throw new Error(`Preset services require isolate realms: ${leaked.join(', ')}`)
const mount = { presetId: id, fiber: ctx.fiber, tree, key: scopeOf(ctx) }
return mount
注册表本身(加载 YAML、逐行审计)没整套跑;脚本用它底下的作用域父链和 isolate 搭两套构造的预设:standard 带 bash、web_fetch 和真实的本地 shell,minimal 只带 DryRunBash:
a1: tools ["read","bash","web_fetch"]
a2: tools ["read","bash","web_fetch"]
b1: tools ["read"]
shell inside preset standard: LocalBashExecutor
shell inside preset minimal: DryRunBash
shell at the root realm: (none)
without isolate, second shell provider: service "shell" has been registered at <LocalBashExecutor>
a1、a2 共用 standard 那一份,b1 只看得见共享的 read;两套预设各有 shell,根上没有。不 isolate,第二个 shell 就撞在根上——预设挂载拒绝的正是这种情况。
谁管什么
- 程序:注册即 effect,卸载按逆序撤销;
inject决定谁什么时候启动、重启;patch 的叠加顺序和整行替换;作用域的可见性和事件路由;守卫只能否决 - 模型:默认不碰这一层,只看到组装的结果——工具表和提示词段,不知道背后是哪个提供方、哪套预设。只有 cordis 预设给了它插件管理工具,每次操作都要用户审批,或者会话本来就开着完全放开的权限
- 用户:写 profile 和 home 的
cordis.patch.yml、命令行--patch;在插件管理器里开关一行、装树外的组合包;在 Web 里给会话选预设
实跑原件
dsh --profile web --dump-config 全文
# == @deepseek-ai/dsh-base
- id: tool-plugin-manager
name: '@deepseek-ai/dsh-plugin-manager/tools'
disabled: true
- id: plugin-manager
name: '@deepseek-ai/dsh-plugin-manager'
disabled: !!js '!ctx.get(''profileContext'')'
- id: timer
name: '@deepseek-ai/cordis-plugin-timer'
- id: hmr
name: '@deepseek-ai/dsh-hmr'
disabled: !!js '!ctx.get(''profileContext'')'
config:
root: []
- id: llm
name: '@deepseek-ai/dsh-llm'
- id: deepseek-llm-api-extensions
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
- id: session
name: '@deepseek-ai/dsh-session'
- id: session-log-deepseek
name: '@deepseek-ai/dsh-session-log-deepseek'
- id: typert
name: '@deepseek-ai/dsh-typert-registry'
- id: typert-loader
name: '@deepseek-ai/dsh-typert-loader'
- id: typert-gateway
name: '@deepseek-ai/dsh-api-gateway'
- id: remote-access
name: '@deepseek-ai/dsh-api-remote-access'
- id: session-title
name: '@deepseek-ai/dsh-session-title'
config:
fallbackMaxWords: 5
fallbackMaxBytes: 40
maxTitleBytes: 80
- id: session-title-llm
name: '@deepseek-ai/dsh-session-title-first-prompt-llm'
config:
targetWords: 5
targetCjkCharacters: 10
maxInputBytes: 4096
maxOutputTokens: 4096
timeoutMs: 60000
- id: user-questions
name: '@deepseek-ai/dsh-user-questions'
- id: agent
name: '@deepseek-ai/dsh-agent'
- id: plugin-package-inventory-deepseek
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
- id: agent-default-model
name: '@deepseek-ai/dsh-agent-default-model'
config:
provider: deepseek-official
model: deepseek-flash
- id: jobs
name: '@deepseek-ai/dsh-jobs-local'
- id: llm-retry
name: '@deepseek-ai/dsh-llm-retry'
- id: config-editor
name: '@deepseek-ai/dsh-config-editor'
disabled: !!js '!ctx.get(''profileContext'')'
- id: settings
name: '@deepseek-ai/dsh-settings'
disabled: !!js '!ctx.get(''profileContext'')'
- id: authorization
name: '@deepseek-ai/dsh-authorization'
- id: deepseek-account
name: '@deepseek-ai/dsh-deepseek-account-platform'
config:
desktopPlatform: !!js >-
ctx.get('profileContext')?.name === 'desktop' && ['darwin',
'win32'].includes(process.platform) ? process.platform : null
- id: credentials
name: '@deepseek-ai/dsh-credentials-local'
- id: llm-pi-ai
name: '@deepseek-ai/dsh-llm-pi-ai'
- id: session-persistence-jsonl
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js dshHomePath('sessions')
- id: attachment-local
name: '@deepseek-ai/dsh-attachment-local'
- id: session-query-sqlite
name: '@deepseek-ai/dsh-session-query-sqlite'
config:
path: ':memory:'
openAt: never
- id: working-directory
name: '@deepseek-ai/dsh-working-directory'
- id: tool-working-directory
name: '@deepseek-ai/dsh-tool-working-directory'
- id: session-projection
name: '@deepseek-ai/dsh-session-projection'
- id: storage
name: '@deepseek-ai/dsh-storage'
- id: storage-json
name: '@deepseek-ai/dsh-storage-json'
config:
root: !!js dshHomePath('storages')
- id: storage-domain
name: '@deepseek-ai/dsh-storage-domain'
config:
backend: json
- id: session-projection-cache
name: '@deepseek-ai/dsh-session-projection-cache'
config:
writeEveryEvents: 200
writeIntervalMs: 5000
- id: otel
name: '@deepseek-ai/dsh-otel'
- id: session-telemetry-otel
name: '@deepseek-ai/dsh-session-telemetry-otel'
config:
mode: !!js process.env.DSH_TELEMETRY_MODE || 'FEEDBACK_ONLY'
shutdownTimeoutMillis: 3000
maxRequestBytes: 4000000
exporter:
url: !!js >-
process.env.DSH_TELEMETRY_OTLP_URL ??
'https://dsh-otel-collector.deepseeksvc.com/v1/logs'
compression: gzip
timeoutMillis: 1000
processor:
scheduledDelayMillis: 10000
maxQueueSize: 2048
maxExportBatchSize: 2048
exportTimeoutMillis: 1500
- id: subprocess
name: '@deepseek-ai/dsh-subprocess-local'
- id: sandbox
name: '@deepseek-ai/dsh-sandbox-local'
- id: sandbox-policy
name: '@deepseek-ai/dsh-sandbox-policy'
config:
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
workspaceRoot: !!js process.cwd()
- id: bash-sandbox
name: '@deepseek-ai/dsh-bash-sandbox'
disabled: !!js process.platform === 'win32'
config:
timeoutMs: 60000
- id: pwsh-sandbox
name: '@deepseek-ai/dsh-pwsh-sandbox'
disabled: !!js process.platform !== 'win32'
- id: approval
name: '@deepseek-ai/dsh-user-approval'
config:
policy: !!js >-
(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') ===
'danger-full-access' ? 'never' : 'ask'
- id: permission
name: '@deepseek-ai/dsh-permission-presets'
config:
presets:
read-only:
sandbox: read-only
approval: ask
workspace-write:
sandbox: workspace-write
approval: ask
danger-full-access:
sandbox: danger-full-access
approval: never
- id: shell-env
name: '@deepseek-ai/dsh-shell-env'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-bash
name: '@deepseek-ai/dsh-tool-bash'
disabled: true
- id: tool-pwsh
name: '@deepseek-ai/dsh-tool-pwsh'
disabled: true
- id: tool-jobs
name: '@deepseek-ai/dsh-tool-jobs'
disabled: true
# == @deepseek-ai/dsh-base
- id: fs-observation-policy
name: '@deepseek-ai/dsh-fs-observation-policy'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
disabled: true
- id: tool-fs-search
name: '@deepseek-ai/dsh-tool-fs-search'
config:
sampleOverCapGlobResults: false
disabled: true
- id: agent-instructions
name: '@deepseek-ai/dsh-agent-instructions'
config:
maxBytes: 65536
disabled: true
# == @deepseek-ai/dsh-base
- id: skill
name: '@deepseek-ai/dsh-skill'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: skill-filesystem
name: '@deepseek-ai/dsh-skill-filesystem'
disabled: true
- id: tool-skill
name: '@deepseek-ai/dsh-tool-skill'
disabled: true
# == @deepseek-ai/dsh-base
- id: commands
name: '@deepseek-ai/dsh-commands'
- id: command-feedback
name: '@deepseek-ai/dsh-command-feedback'
- id: goal
name: '@deepseek-ai/dsh-goal'
- id: goal-round-driver
name: '@deepseek-ai/dsh-goal-round-driver'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: command-goal
name: '@deepseek-ai/dsh-command-goal'
disabled: true
- id: plan-mode
name: '@deepseek-ai/dsh-plan-mode'
config:
section: >
You are in plan mode. Stay in plan mode until the user approves your plan
through exit_plan_mode or switches the session mode. Imperative language
to implement changes means plan the implementation, not execute it. A
user's conversational agreement — including an answer confirming something
you asked — approves nothing and does not end plan mode; fold the
confirmed decision into the plan and submit it through exit_plan_mode.
Explore first. Use non-mutating reads, searches, static analysis, and
checks to ground the plan in the actual repository. Do not edit or write
files, change configuration, run formatters or code generation that
rewrites tracked files, commit, or otherwise carry out the plan. Prefer
existing functions and patterns over new machinery.
The tool catalog stays the same across modes for request-cache stability.
These plan-mode rules override any later tool description or guidance that
suggests using mutation tools; those tools remain listed only to keep the
request shape stable. Do not use todo_write to track this planning phase:
it tracks implementation after an approved plan, while the plan itself
belongs in exit_plan_mode.
Resolve discoverable facts by inspection. Use ask_user_question only for
user-owned choices or material ambiguity that inspection cannot answer. Do
not ask the user where code lives or how current behavior works when you
can find out.
Make the plan decision-complete: state the goal and success criteria;
group implementation changes by subsystem; identify public API, schema,
and data-flow changes; cover edge cases, failure modes, tests, acceptance
criteria, and explicit assumptions. Keep it concise enough to review but
detailed enough that another engineer can implement it without making
design decisions.
When ready, call exit_plan_mode with the complete plan markdown, starting
with a # title. Make exit_plan_mode the only and final tool call in that
assistant response: it presents the plan for approval, and implementation
begins only in a later step after approval. Do not paste the final plan as
a plain reply or ask "should I proceed?" through prose or
ask_user_question. If review rejects it, incorporate the feedback and
present again. If the review channel is unavailable or aborted, stay in
plan mode and ask the user to switch modes manually; do not proceed with
implementation.
disabled: true
# == @deepseek-ai/dsh-base
- id: token-meter
name: '@deepseek-ai/dsh-token-meter'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
disabled: true
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
disabled: true
# == @deepseek-ai/dsh-base
- id: subagent
name: '@deepseek-ai/dsh-subagent'
- id: subagent-spawn-in-process
name: '@deepseek-ai/dsh-subagent-spawn-in-process'
config:
providerName: spawn
- id: subagent-fork-in-process
name: '@deepseek-ai/dsh-subagent-fork-in-process'
config:
providerName: fork
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-subagent-control
name: '@deepseek-ai/dsh-tool-subagent-control'
disabled: true
- id: tool-subagent-list-agents
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
disabled: true
- id: tool-subagent
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: spawn
toolName: subagent
disabled: true
- id: tool-subagent-fork
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: fork
toolName: subagent_fork
disabled: true
# == @deepseek-ai/dsh-base
- id: ptc-runtime
name: '@deepseek-ai/dsh-ptc-runtime-node'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: workflow-ptc
name: '@deepseek-ai/dsh-workflow-ptc'
config:
provider: spawn
disabled: true
- id: tool-workflow
name: '@deepseek-ai/dsh-tool-workflow'
disabled: true
# == @deepseek-ai/dsh-base
- id: timeout-policy
name: '@deepseek-ai/dsh-tool-call-timeout-policy'
- id: spill-local
name: '@deepseek-ai/dsh-spill-local'
- id: spill-policy
name: '@deepseek-ai/dsh-spill-policy'
config:
maxInlineTokens: 12500
- id: session-checkpoint-policy
name: '@deepseek-ai/dsh-session-checkpoint-policy'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-result-pruner
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
config:
thresholdChars: 8192
headChars: 4096
tailChars: 1024
disabled: true
# == @deepseek-ai/dsh-base
- id: image-offload
name: '@deepseek-ai/dsh-compaction-image-offload'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-todo
name: '@deepseek-ai/dsh-tool-todo'
config:
allowParallelInProgress: true
disabled: true
- id: tool-goal
name: '@deepseek-ai/dsh-tool-goal'
disabled: true
# == @deepseek-ai/dsh-base
- id: repeat-tool-reminder
name: '@deepseek-ai/dsh-repeat-tool-reminder'
config:
thresholds:
- 3
- 5
- 8
argumentsPreviewChars: 500
- id: web
name: '@deepseek-ai/dsh-web'
config:
searchProvider: deepseek-official
fetchProvider: http
- id: web-search-deepseek
name: '@deepseek-ai/dsh-web-search-deepseek'
config:
apiKeyEnv: DEEPSEEK_API_KEY
- id: web-deepseek-api
name: '@deepseek-ai/dsh-web-deepseek-api'
config:
apiKeyEnv: DEEPSEEK_API_KEY
- id: web-fetch-http
name: '@deepseek-ai/dsh-web-fetch-http'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tool-web
name: '@deepseek-ai/dsh-tool-web'
config:
fetch: true
searchTimeoutMs: 60000
disabled: true
# == @deepseek-ai/dsh-base
- id: mcp-resources
name: '@deepseek-ai/dsh-mcp-resources'
# == @deepseek-ai/dsh-base, patched by @deepseek-ai/dsh-web-app
- id: tools
name: '@deepseek-ai/dsh-tools'
config:
mode: !!js process.env.DSH_TOOLS_MODE
- id: system-prompt
name: '@deepseek-ai/dsh-system-prompt'
config:
personaPrefix: You are a coding agent powered by the {{model}} model.
# == @deepseek-ai/dsh-base
- id: agent-loop
name: '@deepseek-ai/dsh-agent-loop'
config:
agents: []
- id: fs-sandbox
name: '@deepseek-ai/dsh-fs-sandbox'
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek-api-key'
- id: llm-deepseek-account
name: '@deepseek-ai/dsh-llm-deepseek-account'
# == @deepseek-ai/dsh-web-app
- id: desktop-product-telemetry
name: '@deepseek-ai/dsh-host-product-telemetry-otel'
disabled: !!js ctx.get('profileContext')?.name !== 'desktop'
config:
scheduledDelayMillis: 30000
timeoutMillis: 15000
exportTimeoutMillis: 20000
shutdownTimeoutMillis: 2000
serviceName: deepseek-harness-desktop
serviceVersion: !!js process.env.DSH_CLIENT_VERSION
endpoint: !!js process.env.DSH_PRODUCT_ANALYTICS_OTLP_URL
- id: product-analytics
name: '@deepseek-ai/dsh-client-product-analytics'
disabled: !!js ctx.get('profileContext')?.name !== 'desktop'
config:
enabled: true
appVersion: !!js process.env.DSH_CLIENT_VERSION
- id: subagent-model-selection-settings
name: '@deepseek-ai/dsh-tool-subagent/model-selection-settings'
- id: message-feedback
name: '@deepseek-ai/dsh-message-feedback'
config:
maxNoteBytes: 8192
- id: session-log-download
name: '@deepseek-ai/dsh-session-log-export'
- id: open-in-app
name: '@deepseek-ai/dsh-host-open-in-app'
config:
probeTimeoutMs: 10000
iconTimeoutMs: 10000
launchWatchMs: 1000
- id: ui-open-in-app
name: '@deepseek-ai/dsh-client-ui-open-in-app'
- id: workspace
name: '@deepseek-ai/dsh-workspace'
- id: session-reference
name: '@deepseek-ai/dsh-session-reference'
- id: file-reference-local
name: '@deepseek-ai/dsh-file-reference-local'
- id: session-stats
name: '@deepseek-ai/dsh-session-stats'
- id: session-turn-outline
name: '@deepseek-ai/dsh-session-turn-outline'
- id: directory-picker
name: '@deepseek-ai/dsh-host-directory-picker-auto'
- id: plugin-inventory
name: '@deepseek-ai/dsh-host-plugin-inventory'
- id: session-controller
name: '@deepseek-ai/dsh-api-session-controller'
- id: job-controller
name: '@deepseek-ai/dsh-api-job-controller'
- id: terminal-controller
name: '@deepseek-ai/dsh-api-terminal-controller'
- id: workspace-files
name: '@deepseek-ai/dsh-api-workspace-files'
- id: schedule
name: '@deepseek-ai/dsh-schedule'
- id: ui-settings-account
name: '@deepseek-ai/dsh-client-ui-settings-account'
- id: account-controller
name: '@deepseek-ai/dsh-api-account-controller'
- id: settings-controller
name: '@deepseek-ai/dsh-api-settings-controller'
- id: workspace-controller
name: '@deepseek-ai/dsh-api-workspace-controller'
- id: cordis-host-runner
name: '@deepseek-ai/dsh-cordis-host-runner'
- id: cordis-inspect-providers
name: '@deepseek-ai/dsh-tool-cordis/host'
- id: web-startup
name: '@deepseek-ai/dsh-web-app/startup'
- id: webserver
name: '@deepseek-ai/dsh-host-webserver'
inject:
- webStartup
config:
host: !!js ctx.webStartup.host ?? '127.0.0.1'
port: !!js ctx.webStartup.port ?? 3080
tls: !!js ctx.webStartup.tls
compression: gzip
compressionLevel: 1
compressionThresholdBytes: 1024
- id: web-runtime
name: '@deepseek-ai/dsh-web-app'
inject:
- webStartup
config:
openBrowser: !!js ctx.webStartup.openBrowser
printUrl: true
publicUrl: !!js ctx.webStartup.publicUrl
surfaceContext: true
- id: harness-dev-context
name: '@deepseek-ai/dsh-harness-dev-context'
- id: client-hmr
name: '@deepseek-ai/dsh-client-hmr'
- id: modules
name: '@deepseek-ai/dsh-client-modules'
- id: connection
name: '@deepseek-ai/dsh-client-connection'
inject:
- webStartup
config:
trustedHosts: !!js ctx.webStartup.trustedHosts
- id: file-upload
name: '@deepseek-ai/dsh-client-file-upload'
- id: api-remotes
name: '@deepseek-ai/dsh-api-remotes'
- id: cordis-client-runner
name: '@deepseek-ai/dsh-cordis-client-runner'
- id: ui-theme
name: '@deepseek-ai/dsh-client-ui-theme'
- id: locale
name: '@deepseek-ai/dsh-client-locale'
- id: shortcuts
name: '@deepseek-ai/dsh-client-shortcuts'
- id: ui-shortcuts
name: '@deepseek-ai/dsh-client-ui-shortcuts'
- id: ui-layout
name: '@deepseek-ai/dsh-client-ui-layout'
- id: ui-renderer
name: '@deepseek-ai/dsh-client-ui-renderer'
- id: ui-session
name: '@deepseek-ai/dsh-client-ui-session'
- id: resources
name: '@deepseek-ai/dsh-client-resources'
- id: ui-sidebar
name: '@deepseek-ai/dsh-client-ui-sidebar'
- id: ui-sidebar-right
name: '@deepseek-ai/dsh-client-ui-sidebar-right'
- id: office-to-pdf
name: '@deepseek-ai/dsh-office-to-pdf'
- id: ui-sidebar-documentpreview
name: '@deepseek-ai/dsh-client-ui-sidebar-documentpreview'
- id: ui-sidebar-browser
name: '@deepseek-ai/dsh-client-ui-sidebar-browser'
disabled: !!js ctx.get('profileContext')?.name !== 'desktop'
- id: ui-sidebar-terminal
name: '@deepseek-ai/dsh-client-ui-sidebar-terminal'
- id: ui-sidebar-files
name: '@deepseek-ai/dsh-client-ui-sidebar-files'
- id: ui-settings
name: '@deepseek-ai/dsh-client-ui-settings'
- id: ui-settings-general
name: '@deepseek-ai/dsh-client-ui-settings-general'
- id: ui-settings-models
name: '@deepseek-ai/dsh-client-ui-settings-models'
- id: ui-plugin-manager
name: '@deepseek-ai/dsh-client-ui-plugin-manager'
- id: ui-settings-plugin-inventory
name: '@deepseek-ai/dsh-client-ui-settings-plugin-inventory'
- id: ui-conversation
name: '@deepseek-ai/dsh-client-ui-conversation'
- id: ui-approval
name: '@deepseek-ai/dsh-client-ui-approval'
- id: ui-chat
name: '@deepseek-ai/dsh-client-ui-chat'
- id: ui-brand-official
name: '@deepseek-ai/dsh-client-ui-brand-official'
- id: ui-attachment
name: '@deepseek-ai/dsh-client-ui-attachment'
- id: ui-tool
name: '@deepseek-ai/dsh-client-ui-tool'
- id: ui-cordis
name: '@deepseek-ai/dsh-client-ui-cordis'
- id: ui-deliverables
name: '@deepseek-ai/dsh-client-ui-deliverables'
- id: workspace-changes
name: '@deepseek-ai/dsh-workspace-changes'
- id: ui-workspace
name: '@deepseek-ai/dsh-client-ui-workspace'
- id: ui-workflow-run
name: '@deepseek-ai/dsh-client-ui-workflow-run'
- id: ui-input-trigger
name: '@deepseek-ai/dsh-client-ui-input-trigger'
- id: ui-commands
name: '@deepseek-ai/dsh-client-ui-commands'
- id: ui-skill
name: '@deepseek-ai/dsh-client-ui-skill'
- id: ui-subagent
name: '@deepseek-ai/dsh-client-ui-subagent'
- id: ui-reference
name: '@deepseek-ai/dsh-client-ui-reference'
- id: ui-secret-mask
name: '@deepseek-ai/dsh-client-ui-secret-mask'
- id: ui-jobs
name: '@deepseek-ai/dsh-client-ui-jobs'
- id: ui-schedule
name: '@deepseek-ai/dsh-client-ui-schedule'
- id: ui-goal
name: '@deepseek-ai/dsh-client-ui-goal'
- id: ui-message-feedback
name: '@deepseek-ai/dsh-client-ui-message-feedback'
- id: ui-model-selection
name: '@deepseek-ai/dsh-client-ui-model-selection'
- id: ui-deepseek-model-label-1017
name: '@deepseek-ai/dsh-client-ui-deepseek-model-label-1017'
- id: ui-permission
name: '@deepseek-ai/dsh-client-ui-permission-presets'
- id: ui-agent-preset
name: '@deepseek-ai/dsh-client-ui-agent-preset'
- id: ui-settings-session-log
name: '@deepseek-ai/dsh-client-ui-settings-session-log'
- id: ui-settings-plugins
name: '@deepseek-ai/dsh-client-ui-settings-plugins'
- id: ui-settings-shell
name: '@deepseek-ai/dsh-client-ui-settings-shell'
- id: ui-settings-agent-loop
name: '@deepseek-ai/dsh-client-ui-settings-agent-loop'
- id: ui-settings-subagent
name: '@deepseek-ai/dsh-client-ui-settings-subagent'
- id: ui-settings-web-search
name: '@deepseek-ai/dsh-client-ui-settings-web-search'
- id: ui-plan
name: '@deepseek-ai/dsh-client-ui-plan'
- id: ui-user-questions
name: '@deepseek-ai/dsh-client-ui-user-questions'
- id: ui-trajectory
name: '@deepseek-ai/dsh-client-ui-trajectory'
- id: agent-preset-registry
name: '@deepseek-ai/dsh-agent-preset-registry'
config:
default: standard
- id: preset-standard
name: '@deepseek-ai/dsh-agent-preset'
config:
id: standard
order: 1
plugins:
- id: persona
name: '@deepseek-ai/dsh-persona'
config:
prefix: You are a coding agent powered by the {{model}} model.
- id: agent-instructions
name: '@deepseek-ai/dsh-agent-instructions'
config:
maxBytes: 65536
- id: time-context
name: '@deepseek-ai/dsh-time-context'
- id: tool-bash
name: '@deepseek-ai/dsh-tool-bash'
disabled: !!js process.platform === 'win32'
- id: tool-pwsh
name: '@deepseek-ai/dsh-tool-pwsh'
disabled: !!js process.platform !== 'win32'
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
- id: tool-fs-search
name: '@deepseek-ai/dsh-tool-fs-search'
config:
sampleOverCapGlobResults: false
- id: tool-jobs
name: '@deepseek-ai/dsh-tool-jobs'
- id: tool-schedule
name: '@deepseek-ai/dsh-tool-schedule'
- id: skill-filesystem
name: '@deepseek-ai/dsh-skill-filesystem'
- id: tool-skill
name: '@deepseek-ai/dsh-tool-skill'
- id: command-goal
name: '@deepseek-ai/dsh-command-goal'
- id: tool-goal
name: '@deepseek-ai/dsh-tool-goal'
- id: planning
name: cordis:group
group: true
isolate:
planMode: true
config:
- id: plan-mode
name: '@deepseek-ai/dsh-plan-mode'
config:
section: >
You are in plan mode. Stay in plan mode until the user approves
your plan through exit_plan_mode or switches the session mode.
Imperative language to implement changes means plan the
implementation, not execute it. A user's conversational
agreement — including an answer confirming something you asked —
approves nothing and does not end plan mode; fold the confirmed
decision into the plan and submit it through exit_plan_mode.
Explore first. Use non-mutating reads, searches, static
analysis, and checks to ground the plan in the actual
repository. Do not edit or write files, change configuration,
run formatters or code generation that rewrites tracked files,
commit, or otherwise carry out the plan. Prefer existing
functions and patterns over new machinery.
The tool catalog stays the same across modes for request-cache
stability. These plan-mode rules override any later tool
description or guidance that suggests using mutation tools;
those tools remain listed to keep the tool catalog unchanged. Do
not use todo_write to track this planning phase: it tracks
implementation after an approved plan, while the plan itself
belongs in exit_plan_mode.
Resolve discoverable facts by inspection. Use ask_user_question
only for user-owned choices or material ambiguity that
inspection cannot answer. Do not ask the user where code lives
or how current behavior works when you can find out.
Make the plan decision-complete: state the goal and success
criteria; group implementation changes by subsystem; identify
public API, schema, and data-flow changes; cover edge cases,
failure modes, tests, acceptance criteria, and explicit
assumptions. Keep it concise enough to review but detailed
enough that another engineer can implement it without making
design decisions.
When ready, call exit_plan_mode with the complete plan markdown,
starting with a # title. Make exit_plan_mode the only and final
tool call in that assistant response: it presents the plan for
approval, and implementation begins only in a later step after
approval. Do not paste the final plan as a plain reply or ask
"should I proceed?" through prose or ask_user_question. If
review rejects it, incorporate the feedback and present again.
If the review channel is unavailable or aborted, stay in plan
mode and ask the user to switch modes manually; do not proceed
with implementation.
- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
toolResultPruner: true
config:
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
- id: tool-result-pruner
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
config:
thresholdChars: 8192
headChars: 4096
tailChars: 1024
- id: delegation
name: cordis:group
group: true
isolate:
workflowEngine: true
config:
- id: tool-subagent-control
name: '@deepseek-ai/dsh-tool-subagent-control'
- id: tool-subagent-list-agents
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
- id: tool-subagent
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: spawn
toolName: subagent
modelSelectionSettings: true
- id: tool-subagent-fork
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: fork
toolName: subagent_fork
- id: workflow-ptc
name: '@deepseek-ai/dsh-workflow-ptc'
config:
provider: spawn
- id: tool-workflow
name: '@deepseek-ai/dsh-tool-workflow'
- id: tool-ask-user
name: '@deepseek-ai/dsh-tool-ask-user'
- id: tool-todo
name: '@deepseek-ai/dsh-tool-todo'
config:
allowParallelInProgress: true
- id: tool-web
name: '@deepseek-ai/dsh-tool-web'
config:
fetch: true
searchTimeoutMs: 60000
- id: present
name: '@deepseek-ai/dsh-tool-present'
- id: tool-plugin-manager
name: '@deepseek-ai/dsh-plugin-manager/tools'
disabled: true
- id: tool-plugin-market
name: '@deepseek-ai/dsh-plugin-market/tools'
disabled: true
- id: preset-ptc
name: '@deepseek-ai/dsh-agent-preset'
config:
id: ptc
order: 2
plugins:
- id: persona
name: '@deepseek-ai/dsh-persona'
config:
prefix: You are a coding agent powered by the {{model}} model.
- id: agent-instructions
name: '@deepseek-ai/dsh-agent-instructions'
config:
maxBytes: 65536
- id: time-context
name: '@deepseek-ai/dsh-time-context'
- id: tool-bash
name: '@deepseek-ai/dsh-tool-bash'
disabled: !!js process.platform === 'win32'
- id: tool-pwsh
name: '@deepseek-ai/dsh-tool-pwsh'
disabled: !!js process.platform !== 'win32'
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
- id: tool-fs-search
name: '@deepseek-ai/dsh-tool-fs-search'
config:
sampleOverCapGlobResults: false
- id: tool-jobs
name: '@deepseek-ai/dsh-tool-jobs'
- id: tool-schedule
name: '@deepseek-ai/dsh-tool-schedule'
- id: skill-filesystem
name: '@deepseek-ai/dsh-skill-filesystem'
- id: tool-skill
name: '@deepseek-ai/dsh-tool-skill'
- id: command-goal
name: '@deepseek-ai/dsh-command-goal'
- id: tool-goal
name: '@deepseek-ai/dsh-tool-goal'
- id: planning
name: cordis:group
group: true
isolate:
planMode: true
config:
- id: plan-mode
name: '@deepseek-ai/dsh-plan-mode'
config:
section: >
You are in plan mode. Stay in plan mode until the user approves
your plan through exit_plan_mode or switches the session mode.
Imperative language to implement changes means plan the
implementation, not execute it. A user's conversational
agreement — including an answer confirming something you asked —
approves nothing and does not end plan mode; fold the confirmed
decision into the plan and submit it through exit_plan_mode.
Explore first. Use non-mutating reads, searches, static
analysis, and checks to ground the plan in the actual
repository. Do not edit or write files, change configuration,
run formatters or code generation that rewrites tracked files,
commit, or otherwise carry out the plan. Prefer existing
functions and patterns over new machinery.
The tool catalog stays the same across modes for request-cache
stability. These plan-mode rules override any later tool
description or guidance that suggests using mutation tools;
those tools remain listed to keep the tool catalog unchanged. Do
not use todo_write to track this planning phase: it tracks
implementation after an approved plan, while the plan itself
belongs in exit_plan_mode.
Resolve discoverable facts by inspection. Use ask_user_question
only for user-owned choices or material ambiguity that
inspection cannot answer. Do not ask the user where code lives
or how current behavior works when you can find out.
Make the plan decision-complete: state the goal and success
criteria; group implementation changes by subsystem; identify
public API, schema, and data-flow changes; cover edge cases,
failure modes, tests, acceptance criteria, and explicit
assumptions. Keep it concise enough to review but detailed
enough that another engineer can implement it without making
design decisions.
When ready, call exit_plan_mode with the complete plan markdown,
starting with a # title. Make exit_plan_mode the only and final
tool call in that assistant response: it presents the plan for
approval, and implementation begins only in a later step after
approval. Do not paste the final plan as a plain reply or ask
"should I proceed?" through prose or ask_user_question. If
review rejects it, incorporate the feedback and present again.
If the review channel is unavailable or aborted, stay in plan
mode and ask the user to switch modes manually; do not proceed
with implementation.
- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
toolResultPruner: true
config:
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
- id: tool-result-pruner
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
config:
thresholdChars: 8192
headChars: 4096
tailChars: 1024
- id: delegation
name: cordis:group
group: true
isolate:
workflowEngine: true
config:
- id: tool-subagent-control
name: '@deepseek-ai/dsh-tool-subagent-control'
- id: tool-subagent-list-agents
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
- id: tool-subagent
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: spawn
toolName: subagent
modelSelectionSettings: true
- id: tool-subagent-fork
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: fork
toolName: subagent_fork
- id: workflow-ptc
name: '@deepseek-ai/dsh-workflow-ptc'
disabled: true
config:
provider: spawn
- id: tool-workflow
name: '@deepseek-ai/dsh-tool-workflow'
disabled: true
- id: tool-ask-user
name: '@deepseek-ai/dsh-tool-ask-user'
- id: tool-todo
name: '@deepseek-ai/dsh-tool-todo'
config:
allowParallelInProgress: true
- id: tool-web
name: '@deepseek-ai/dsh-tool-web'
config:
fetch: true
searchTimeoutMs: 60000
- id: tool-presentation
name: '@deepseek-ai/dsh-agent-tool-presentation'
config:
mode: ptc
- id: present
name: '@deepseek-ai/dsh-tool-present'
- id: tool-plugin-manager
name: '@deepseek-ai/dsh-plugin-manager/tools'
disabled: true
- id: tool-plugin-market
name: '@deepseek-ai/dsh-plugin-market/tools'
disabled: true
- id: preset-minimal
name: '@deepseek-ai/dsh-agent-preset'
config:
id: minimal
order: 3
plugins:
- id: persona
name: '@deepseek-ai/dsh-persona'
config:
prefix: You are a helpful software engineer assistant.
complete: true
includeRuntimeContext: false
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
config:
read: false
readImage: true
write: false
edit: false
- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
config:
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
- id: persistent-shell
name: cordis:group
group: true
isolate:
terminals: true
config:
- id: pty
name: '@deepseek-ai/dsh-terminal'
- id: terminal-bash
name: '@deepseek-ai/dsh-terminal-bash'
disabled: !!js process.platform === 'win32'
config:
timeoutMs: 300000
- id: persistent-bash
name: '@deepseek-ai/dsh-tool-bash-persistent'
disabled: !!js process.platform === 'win32'
config:
timeoutMs: 300000
description: >-
Run commands in a bash shell
* When invoking this tool, the contents of the "command"
parameter does NOT need to be XML-escaped.
* Network access depends on the task environment. Prefer
configured mirrors/proxies when they are available.
* State is persistent across command calls and discussions with
the user.
* To inspect a particular line range of a file, e.g. lines
10-25, try 'sed -n 10,25p /path/to/the/file'.
* Please avoid commands that may produce a very large amount of
output.
* Please run long lived commands in the background, e.g. 'sleep
10 &' or start a server in the background.
- id: terminal-pwsh
name: '@deepseek-ai/dsh-terminal-bash'
disabled: !!js process.platform !== 'win32'
config:
shellDialect: pwsh
timeoutMs: 300000
- id: persistent-pwsh
name: '@deepseek-ai/dsh-tool-pwsh-persistent'
disabled: !!js process.platform !== 'win32'
config:
timeoutMs: 300000
description: >-
Run commands in a PowerShell shell
* When invoking this tool, the contents of the "command"
parameter does NOT need to be XML-escaped.
* You don't have access to the internet via this tool.
* State is persistent across command calls and discussions with
the user.
* Use native Windows paths (C:\...) and $env:NAME variables;
this is PowerShell, not bash.
* Please avoid commands that may produce a very large amount of
output.
* Please run long lived commands in the background, e.g.
'Start-Job' or start a server with Start-Process.
- id: preset-cordis
name: '@deepseek-ai/dsh-agent-preset'
config:
id: cordis
order: 4
plugins:
- id: persona
name: '@deepseek-ai/dsh-persona'
config:
prefix: You are a coding agent powered by the {{model}} model.
- id: agent-instructions
name: '@deepseek-ai/dsh-agent-instructions'
config:
maxBytes: 65536
- id: time-context
name: '@deepseek-ai/dsh-time-context'
- id: tool-bash
name: '@deepseek-ai/dsh-tool-bash'
disabled: !!js process.platform === 'win32'
- id: tool-pwsh
name: '@deepseek-ai/dsh-tool-pwsh'
disabled: !!js process.platform !== 'win32'
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
- id: tool-fs-search
name: '@deepseek-ai/dsh-tool-fs-search'
config:
sampleOverCapGlobResults: false
- id: tool-jobs
name: '@deepseek-ai/dsh-tool-jobs'
- id: tool-schedule
name: '@deepseek-ai/dsh-tool-schedule'
- id: command-goal
name: '@deepseek-ai/dsh-command-goal'
- id: tool-goal
name: '@deepseek-ai/dsh-tool-goal'
- id: planning
name: cordis:group
group: true
isolate:
planMode: true
config:
- id: plan-mode
name: '@deepseek-ai/dsh-plan-mode'
config:
section: >
You are in plan mode. Stay in plan mode until the user approves
your plan through exit_plan_mode or switches the session mode.
Imperative language to implement changes means plan the
implementation, not execute it. A user's conversational
agreement — including an answer confirming something you asked —
approves nothing and does not end plan mode; fold the confirmed
decision into the plan and submit it through exit_plan_mode.
Explore first. Use non-mutating reads, searches, static
analysis, and checks to ground the plan in the actual
repository. Do not edit or write files, change configuration,
run formatters or code generation that rewrites tracked files,
commit, or otherwise carry out the plan. Prefer existing
functions and patterns over new machinery.
The tool catalog stays the same across modes for request-cache
stability. These plan-mode rules override any later tool
description or guidance that suggests using mutation tools;
those tools remain listed to keep the tool catalog unchanged. Do
not use todo_write to track this planning phase: it tracks
implementation after an approved plan, while the plan itself
belongs in exit_plan_mode.
Resolve discoverable facts by inspection. Use ask_user_question
only for user-owned choices or material ambiguity that
inspection cannot answer. Do not ask the user where code lives
or how current behavior works when you can find out.
Make the plan decision-complete: state the goal and success
criteria; group implementation changes by subsystem; identify
public API, schema, and data-flow changes; cover edge cases,
failure modes, tests, acceptance criteria, and explicit
assumptions. Keep it concise enough to review but detailed
enough that another engineer can implement it without making
design decisions.
When ready, call exit_plan_mode with the complete plan markdown,
starting with a # title. Make exit_plan_mode the only and final
tool call in that assistant response: it presents the plan for
approval, and implementation begins only in a later step after
approval. Do not paste the final plan as a plain reply or ask
"should I proceed?" through prose or ask_user_question. If
review rejects it, incorporate the feedback and present again.
If the review channel is unavailable or aborted, stay in plan
mode and ask the user to switch modes manually; do not proceed
with implementation.
- id: compaction
name: cordis:group
group: true
isolate:
compaction: true
toolResultPruner: true
config:
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
- id: command-compact
name: '@deepseek-ai/dsh-command-compact'
- id: tool-result-pruner
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
config:
thresholdChars: 8192
headChars: 4096
tailChars: 1024
- id: delegation
name: cordis:group
group: true
isolate:
workflowEngine: true
config:
- id: tool-subagent-control
name: '@deepseek-ai/dsh-tool-subagent-control'
- id: tool-subagent-list-agents
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
- id: tool-subagent
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: spawn
toolName: subagent
modelSelectionSettings: true
- id: tool-subagent-fork
name: '@deepseek-ai/dsh-tool-subagent'
config:
provider: fork
toolName: subagent_fork
- id: workflow-ptc
name: '@deepseek-ai/dsh-workflow-ptc'
config:
provider: spawn
- id: tool-workflow
name: '@deepseek-ai/dsh-tool-workflow'
- id: tool-ask-user
name: '@deepseek-ai/dsh-tool-ask-user'
- id: tool-todo
name: '@deepseek-ai/dsh-tool-todo'
config:
allowParallelInProgress: true
- id: tool-web
name: '@deepseek-ai/dsh-tool-web'
config:
fetch: true
searchTimeoutMs: 60000
- id: tool-cordis
name: '@deepseek-ai/dsh-tool-cordis'
- id: skill-filesystem
name: '@deepseek-ai/dsh-skill-filesystem'
config:
customSkillDirs:
- !!js >-
process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:path').dirname(process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('@deepseek-ai/dsh-agent-preset/package.json')),
'skills')
- id: tool-skill
name: '@deepseek-ai/dsh-tool-skill'
- id: present
name: '@deepseek-ai/dsh-tool-present'
- id: tool-plugin-manager
name: '@deepseek-ai/dsh-plugin-manager/tools'
disabled: !!js '!ctx.get(''profileContext'')'
- id: tool-plugin-market
name: '@deepseek-ai/dsh-plugin-market/tools'
disabled: !!js '!ctx.get(''profileContext'')'
dsh --profile sdk-minimal --dump-config 全文
# == @deepseek-ai/dsh-sdk-minimal
- id: sdk-app-startup
name: '@deepseek-ai/dsh-sdk-app'
config:
profile: sdk-minimal
- id: sdk-jsonrpc-server
name: '@deepseek-ai/dsh-sdk-jsonrpc-server'
inject:
- sdkAppStartup
- loader
config:
maxTokensAsSuccess: false
- id: deepseek-llm-api-extensions
name: '@deepseek-ai/dsh-deepseek-llm-api-extensions'
- id: session-log-deepseek
name: '@deepseek-ai/dsh-session-log-deepseek'
- id: plugin-package-inventory-deepseek
name: '@deepseek-ai/dsh-plugin-package-inventory-deepseek'
- id: llm-deepseek
name: '@deepseek-ai/dsh-llm-deepseek-api-key'
config:
apiKeyEnv: DEEPSEEK_API_KEY
defaultContextWindow: !!js Number(process.env.DSH_CONTEXT_WINDOW ?? 1000000)
streamIdleTimeoutMs: 172800000
- id: sandbox
name: '@deepseek-ai/dsh-sandbox-local'
- id: filesystem
name: '@deepseek-ai/dsh-fs-local'
- id: attachment-local
name: '@deepseek-ai/dsh-attachment-local'
- id: tool-fs
name: '@deepseek-ai/dsh-tool-fs'
config:
read: false
readImage: true
write: false
edit: false
- id: working-directory
name: '@deepseek-ai/dsh-working-directory'
- id: tool-working-directory
name: '@deepseek-ai/dsh-tool-working-directory'
- id: session-projection
name: '@deepseek-ai/dsh-session-projection'
- id: token-meter
name: '@deepseek-ai/dsh-token-meter'
- id: compaction-basic
name: '@deepseek-ai/dsh-compaction-basic'
- id: image-offload
name: '@deepseek-ai/dsh-compaction-image-offload'
- id: sandbox-policy
name: '@deepseek-ai/dsh-sandbox-policy'
config:
mode: danger-full-access
workspaceRoot: !!js process.cwd()
- id: subprocess
name: '@deepseek-ai/dsh-subprocess-local'
- id: pty
name: '@deepseek-ai/dsh-terminal'
- id: terminal-bash
name: '@deepseek-ai/dsh-terminal-bash'
disabled: !!js process.platform === 'win32'
config:
timeoutMs: 300000
- id: terminal-pwsh
name: '@deepseek-ai/dsh-terminal-bash'
disabled: !!js process.platform !== 'win32'
config:
shellDialect: pwsh
timeoutMs: 300000
- id: timer
name: '@deepseek-ai/cordis-plugin-timer'
- id: llm
name: '@deepseek-ai/dsh-llm'
- id: session
name: '@deepseek-ai/dsh-session'
- id: session-title
name: '@deepseek-ai/dsh-session-title'
config:
fallbackMaxWords: 5
fallbackMaxBytes: 40
maxTitleBytes: 80
- id: system-prompt
name: '@deepseek-ai/dsh-system-prompt'
config:
includeHarnessIdentity: false
includeRuntimeContext: false
personaPrefix: !!js >-
process.env.DSH_SYSTEM_PROMPT ?? 'You are a helpful software engineer
assistant.'
- id: tools
name: '@deepseek-ai/dsh-tools'
- id: mcp-resources
name: '@deepseek-ai/dsh-mcp-resources'
- id: agent
name: '@deepseek-ai/dsh-agent'
- id: llm-retry
name: '@deepseek-ai/dsh-llm-retry'
- id: jobs
name: '@deepseek-ai/dsh-jobs-local'
- id: agent-loop
name: '@deepseek-ai/dsh-agent-loop'
config:
agents: []
- id: persistent-bash
name: '@deepseek-ai/dsh-tool-bash-persistent'
disabled: !!js process.platform === 'win32'
config:
timeoutMs: 300000
description: >-
Run commands in a bash shell
* When invoking this tool, the contents of the "command" parameter does
NOT need to be XML-escaped.
* Network access depends on the task environment. Prefer configured
mirrors/proxies when they are available.
* State is persistent across command calls and discussions with the user.
* To inspect a particular line range of a file, e.g. lines 10-25, try 'sed
-n 10,25p /path/to/the/file'.
* Please avoid commands that may produce a very large amount of output.
* Please run long lived commands in the background, e.g. 'sleep 10 &' or
start a server in the background.
- id: persistent-pwsh
name: '@deepseek-ai/dsh-tool-pwsh-persistent'
disabled: !!js process.platform !== 'win32'
config:
timeoutMs: 300000
description: >-
Run commands in a PowerShell shell
* When invoking this tool, the contents of the "command" parameter does
NOT need to be XML-escaped.
* You don't have access to the internet via this tool.
* State is persistent across command calls and discussions with the user.
* Use native Windows paths (C:\...) and $env:NAME variables; this is
PowerShell, not bash.
* Please avoid commands that may produce a very large amount of output.
* Please run long lived commands in the background, e.g. 'Start-Job' or
start a server with Start-Process.
- id: sessions
name: '@deepseek-ai/dsh-session-persistence-jsonl'
config:
root: !!js dshHomePath('sessions')
compression: none
-
dsh 的决策记录能力 seam——Service Definition / Service Provider / Consumer 角色,2026-10-20 读。 ↩
-
dsh 的决策记录agent 即注册作用域,2026-10-20 读。 ↩
-
dsh 的决策记录会话的 agent 由一份 preset cordis.yml 组装而成,2026-10-20 读。 ↩