mode bundles 在 persona 里,TaoToken 写 system-prompt

发布时间:2026/9/18 16:22:11
mode bundles 在 persona 里,TaoToken 写 system-prompt 1. 先看一个“覆盖成功但字段消失”的现场persona 替换为什么让人误判你在 DSH 里执行pnpm dsh --profile web --dump-config --patch /tmp/persona.patch.yml发现system-prompt的 persona 已经换成新文本但同一行里的templateFile、variables甚至inject也一起消失时先别怀疑 patch 没生效。这通常不是覆盖失败而是 DSH 组合树里的“整行替换”在起作用。本文以 DSH 配置维护者视角拆练习 2用--patch覆盖 persona确认system-prompt行被替换同时解释为什么 patch 不是深合并。凭据侧从 TaoToken 官网创建 Key也就是从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentdsh-persona-patch-intro 进入控制台Base URL 在工具配置里统一写https://taotoken.net/api。很多人第一次读 DSH 配置树会把 YAML 当成普通对象合并我 patch 里只写persona加载器应该只改persona其他字段从 base bundle 继承。这个直觉在 DSH 里会踩坑。system-prompt是一行插件声明patch 层匹配到同一行name后按行替换而不是对config子树递归 merge。于是你只给persona这一行的config就只剩persona原来 mode bundle 里的templateFile、variables、credentials如果没有在 patch 里重述就不会自动回来。base 注释里写的mode-specific values live in mode bundles正是提醒你persona 这类 mode-specific 值默认落在 mode bundle 的system-prompt行里跨层覆盖时要按整行重述。这一篇会围绕四个动作展开先读--dump-config的层次差异再准备 TaoToken Key 和 Base URL接着用--patch做 persona 整行替换最后把覆盖持久化到 profile 补丁和家目录补丁。每一步都给出可复制的命令和 YAML。你不需要改 bundle 源码也不要把真实 Key 写进公开仓库。2. 读透 dump-config先分清 bundle 默认、profile 补丁、家目录补丁和 --patch练习 2 之所以容易误判是因为你看到的最终配置不是“原始文件”而是多层叠加后的结果。观察顺序应该是先看默认 bundle 层再看完整组合然后对比差异最后用--patch临时叠加验证。下面命令建议在本地终端执行路径按你的项目调整。# 只看 bundle 默认层不含用户补丁 pnpm dsh --profile web --dump-config --default-only /tmp/base.yml # 看完整组合bundle profile 补丁 家目录补丁等 pnpm dsh --profile web --dump-config /tmp/full.yml # 对比两层差异 diff -u /tmp/base.yml /tmp/full.yml | less读完差异后把full.yml当作事实基线。重点找七行llm、session、agent-loop、tools、system-prompt、sandbox-policy、agent-presets。它们大致对应基础设施、编排、设置凭据、持久化、安全、模型面工具、委派等功能分组。你不需要背字段但要知道每一行属于哪个平面宿主组合还是 agent preset。因为后面改 persona 时改错平面会导致行为不符合预期。system-prompt行尤其特殊。它既包含 mode 相关的 persona也可能包含模板文件、变量、凭据引用、注入依赖。不同 mode bundle 可以给这行不同默认值所以它不是全局常量。你在 patch 里覆盖它时必须把它当一整行来处理而不是只补一个字段。再看一个容易忽略的点bash-sandbox与pwsh-sandbox这类行通常是互斥的因为同一时刻需要选择一种 shell 沙箱策略。它们不是“两个都开更好”的功能而是策略面二选一。调试沙箱问题时先看最终组合里哪一行处于启用状态再看disabled字段不要只看行在文件里的位置。加载顺序与激活顺序也不是一回事。YAML 里行出现的前后位置没有语义真正决定插件何时生效的是inject声明的服务依赖。某行写了inject: [llm, session]它会进入 waiting 状态直到llm、session服务出现。调试激活问题时看服务键而不是看它被插在第几行。3. 准备 TaoToken Key 与 Base URL凭据属于 provider 平面不要塞进 persona 正文在 DSH 里写system-prompt凭据时第一件事不是改 YAML而是拿到可用的 API Key。进入 TaoToken 官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentdsh-persona-patch-key 按控制台流程创建 Key。不要在博客、截图、Git 提交里暴露真实 Key。本文统一用YOUR_API_KEY占位环境变量名建议用TAOTOKEN_API_KEY。Base URL 是工具配置项固定写https://taotoken.net/api注意Base URL 不要追加 UTM 参数。UTM 只用于官网入口和文档入口的归因API 端点保持干净。export TAOTOKEN_API_KEYYOUR_API_KEY接下来要区分两个平面provider 凭据平面Base URL、API Key、模型名、wire API 等通常由llm行、环境变量或工具自身配置文件承接。persona 平面system-prompt行里的角色描述、行为约束、模板变量等。你可以在system-prompt行里写“调用模型时使用 TaoToken 兼容端点”也可以把 Base URL 作为变量注入模板但不要把人真实 Key 明文写进 persona 文本。更稳妥的方式是让system-prompt行只引用环境变量名真实 Key 由运行环境提供。这样即使 profile 补丁被复制到其他机器也不会泄露凭据。如果你确实需要在 DSH 的system-prompt行里带上 provider 元数据可以这样设计variables.providerBaseUrl写https://taotoken.net/apicredentials.apiKeyEnv写TAOTOKEN_API_KEY。真正要覆盖 persona 时仍然要记住整行替换规则这些字段必须在 patch 里重述。4. 练习 2 可复制实操用 --patch 覆盖 persona并验证只有 system-prompt 行变化先看一个示例。假设full.yml里system-prompt行大致长这样实际name和字段以你 dump 出来的为准- name: dsh/system-prompt inject: - llm - session config: persona: default-web-persona templateFile: ./prompts/web-system.md variables: productName: DSH mode: web credentials: apiKeyEnv: TAOTOKEN_API_KEY现在写一个临时 patch 文件。目标是把 persona 换成你的配置维护助手描述同时保留原来其他字段。注意patch 是整行替换所以inject、config.templateFile、config.variables、config.credentials都要重述。只写persona会导致其他字段消失。# /tmp/persona.patch.yml # 注意dsh/system-prompt 请替换成你 full.yml 里实际的行 name - name: dsh/system-prompt inject: - llm - session config: persona: | 你是 DSH web profile 的配置维护助手。 改配置前先执行 --dump-config改完再次 --dump-config 验证。 涉及模型调用时优先读取宿主组合中的 provider 行 TaoToken 兼容 Base URL 为 https://taotoken.net/api API Key 从环境变量 TAOTOKEN_API_KEY 读取不要明文写入补丁。 你需要解释配置来源层bundle 默认、profile 补丁、家目录补丁、--patch。 templateFile: ./prompts/web-system.md variables: productName: DSH mode: web providerBaseUrl: https://taotoken.net/api credentials: apiKeyEnv: TAOTOKEN_API_KEY执行临时叠加pnpm dsh --profile web --dump-config --patch /tmp/persona.patch.yml /tmp/patched.yml验证 persona 是否被替换yq .[] | select(.namedsh/system-prompt) | .config.persona /tmp/patched.yml如果输出里出现你写的“DSH web profile 的配置维护助手”说明 persona 已替换。继续验证其他字段是否被保留yq .[] | select(.namedsh/system-prompt) | .config.templateFile /tmp/patched.yml yq .[] | select(.namedsh/system-prompt) | .config.variables.providerBaseUrl /tmp/patched.yml再验证除system-prompt之外的行没有被影响diff (yq .[] | select(.name ! dsh/system-prompt) /tmp/full.yml) \ (yq .[] | select(.name ! dsh/system-prompt) /tmp/patched.yml)如果diff没有输出说明其他行保持一致。这个验证很关键练习 2 的验收不是“persona 变了”而是“persona 被替换且只替换了这一行”。如果你发现templateFile消失回看 patch确认是不是只写了persona。这里再强调一次--patch是后写的层赢但赢的方式是整行替换。它不是深合并不会帮你合并config子对象。mode-specific values live in mode bundles的含义就是persona 这类值在 mode bundle 中定义跨层覆盖时你要显式重述同一行的其他字段。否则你以为在改一个字段实际是在重定义整行。5. 为什么不是深合并从 YAML 列表匹配和插件行语义解释把 patch 想成一个列表每一行通过name去匹配下层组合中的行。匹配成功后patch 行插入到更高优先级层。最终读取时高优先级行覆盖低优先级行。覆盖单位是“行”不是“行内对象的每个叶子字段”。这意味着你写config.persona这一行的config就由你提供。你没有写的config.templateFile不会自动从 base bundle 深合并回来。你没有写的inject也会按你 patch 行里的内容为准。如果 patch 行只写name和config.persona其他字段就是缺失状态。这跟很多人的“补丁直觉”相反。大家习惯lodash.merge那种递归合并但 DSH 的组合树更接近“按行声明替换”。它的好处是边界清晰你能明确知道最终生效的是哪一层提供的完整行而不是多来源字段拼出来的隐式结果。代价是覆盖时要重述。可以用一个简单对照表理解做法结果patch 只写config.personasystem-prompt行只剩你写的 persona其他字段可能丢失patch 重述inject、templateFile、variables这一行完整替换字段按你写的生效想禁用某行用disabled: true不要删除行想保留 base 其他字段从full.yml拷贝该行改你要改的字段再作为 patch这也是为什么“禁用而非删除”很重要。删除一行可能让上层补丁的匹配目标消失后续补丁静默失效。正确做法是在更高优先级的补丁里把目标行设为disabled: true让行仍然存在但处于禁用状态。对system-prompt行来说重述成本并不高。你从full.yml复制整行只改persona再放进 patch 文件即可。这样既保留模板和变量又能让 persona 按你的版本生效。6. 持久化到 profile 与家目录补丁HMR、优先级和空文件陷阱临时--patch适合验证不适合长期使用。练习 4 的目标是把练习 2 的覆盖持久化到 profile 补丁。web profile 的补丁文件通常在$DSH_HOME/profiles/web/cordis.patch.yml$DSH_HOME默认是~/.dsh。如果目录不存在先确认你的 DSH 安装和 profile 路径。写入内容时必须是合法非空列表。空文件或只有注释会加载失败因为解析结果不是列表。# $DSH_HOME/profiles/web/cordis.patch.yml # 必须是非空 YAML 列表 - name: dsh/system-prompt inject: - llm - session config: persona: | 你是 DSH web profile 的配置维护助手。 改配置前先 --dump-config改完再次 --dump-config 验证。 TaoToken Base URL 使用 https://taotoken.net/api API Key 从 TAOTOKEN_API_KEY 环境变量读取。 templateFile: ./prompts/web-system.md variables: productName: DSH mode: web providerBaseUrl: https://taotoken.net/api credentials: apiKeyEnv: TAOTOKEN_API_KEY保存后不重启观察 Web UI 或下一次--dump-config是否反映新值。用户补丁层支持 HMR所以改文件后可以直接验证pnpm dsh --profile web --dump-config | yq .[] | select(.namedsh/system-prompt) | .config.persona如果你希望所有 profile 都生效可以在家目录级写补丁$DSH_HOME/cordis.patch.yml优先级需要按最终值推导来理解。一个常见顺序是bundle 默认 → profile 补丁 → 家目录补丁 →--patch。家目录级通常会覆盖 profile 级--patch优先级最高。因此你可以用家目录补丁统一设置 provider Base URL 或禁用某行再用 profile 补丁保留 web profile 特有的 persona。# $DSH_HOME/cordis.patch.yml - name: dsh/llm config: baseUrl: https://taotoken.net/api apiKeyEnv: TAOTOKEN_API_KEY但要注意如果家目录补丁也匹配了system-prompt它会覆盖 profile 里的同一行。验证时不要只看文件路径要看最终--dump-config输出。排查“为什么行为是这样”可以按三步走第一步--dump-config看最终值第二步定位来源层第三步修改对应层再验证。别在 bundle 源码里盲改。另外!!js表达式也会影响你的判断。!!js在挂载时求值不是每次读取都重新算。求值环境是 Loader 上下文能访问process、ctx、dshHomePath、baseUrl等。ctx.serviceName只有在对应服务因inject激活后才可引用。如果你把 provider 地址写成!!js表达式先确认它在挂载时能拿到所需上下文。7. 工具侧落地Claude Code、Codex、CC Switch 不要混用变量DSH 配置树解决的是组合与覆盖问题具体到编码工具还要把 TaoToken 的 Base URL 和 Key 配到正确位置。这里有三类常见工具配置方式不同。Claude Code 使用settings.json和ANTHROPIC_*环境变量。示例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: YOUR_API_KEY, ANTHROPIC_MODEL: YOUR_CLAUDE_MODEL } }Codex 使用config.toml不要把 Claude Code 的ANTHROPIC_*套到 Codex 上。Codex 不读那组变量。示例model YOUR_MODEL_NAME model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY wire_api chatCC Switch 这类切换工具通常围绕三件套工作Base URL、API Key、模型映射。填法如下Base URLhttps://taotoken.net/apiAPI KeyYOUR_API_KEY或指向环境变量TAOTOKEN_API_KEY模型映射从 TaoToken 控制台复制可用模型 ID按工具要求填写再把工具配置和 DSH 配置分开看DSH 的system-prompt行负责 persona、模板、变量、行为约束。DSH 的llm行或 provider 配置负责 Base URL、Key 引用、模型选择。Claude Code 的settings.json负责 Claude Code 自身环境。Codex 的config.toml负责 Codex provider。CC Switch 负责桌面端切换入口。如果你在system-prompt里写“使用 TaoToken”那只是给模型的行为提示不是真正的 provider 配置。真正让请求发到 TaoToken 的是llm行或工具配置里的 Base URL。这个边界分清排障会快很多。8. 排错清单从最终值反推来源层再决定改哪一层遇到 persona 不符合预期时按下面顺序排查看最终值。执行pnpm dsh --profile web --dump-config确认system-prompt行实际persona是什么。看来源层。对比--default-only、完整 dump、profile 补丁、家目录补丁和--patch。看整行替换。确认 patch 是否只写了persona导致其他字段被清掉。看优先级。家目录补丁是否覆盖了 profile 补丁--patch是否最高。看禁用方式。想禁用某行时用disabled: true不要删行。看激活依赖。行顺序不重要inject的服务依赖才决定 waiting 还是 active。看!!js求值。挂载时求值不是每次读取ctx.serviceName需要服务激活后才可引用。验收标准可以写成一句你改完 patch 后system-prompt行的 persona 变成你的文本templateFile、variables、credentials等你重述的字段仍在除这行外其他行与full.yml一致。能解释“为什么只改 persona 会丢字段”就说明你真正理解整行替换不是深合并。最后把 Key 和 Base URL 落到可执行路径上模型对话入口https://taotoken.net/models/detail/chat?utm_sourcetaotoken_aicg_blog_endutm_contentdsh-persona-patch-chatCoding Plan 入口https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentdsh-persona-patch-plan创建 API Keyhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentdsh-persona-patch-keysClaude Code 文档https://taotoken.net/doc/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentdsh-persona-patch-claude需要统一查看产品入口时也可以从 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentdsh-persona-patch-profile 进入。配置时记住Base URL 写https://taotoken.net/apiKey 用YOUR_API_KEY占位真实值只放环境变量或本地密钥文件。DSH 的配置树负责组合TaoToken 负责提供模型接入端点两者通过正确的层和平面连接起来persona 替换和 provider 配置才不会互相污染。