Passer au contenu principal
X

local-lite

xeraph627/local-lite

为短上下文窗口的对话提供更轻量的 Agent 预设,能降低78%的固定开销。由 Deepseek 制作

Installer

dsh plugin --profile web add github:xeraph627/local-lite

README

local-lite · 本地简化

一个为上下文窗口很小的本地模型准备的 DeepSeek Harness Agent 预设。 把每次请求的固定开销从 9,288 token 压到 2,004 token(-78%),工具面收拢为 文件读写、检索与终端命令三类。

An agent preset for DSH that cuts the standing per-request cost from 9,288 to 2,004 tokens (-78%), for local models with small context windows.

装好之后,新建会话的预设选择器里会多出一项 本地简化。


为什么需要它

每次请求在模型真正开口之前,都要先付一笔固定开销:

组成说明
系统提示词每次请求完整重发
工具 schema JSON同样每次请求完整重发,工具面越大越贵

在标准预设的会话里实测(deepseek-account / deepseek-flash):

系统提示词      5,953 字符   ≈  1,490 token
工具 schema   31,191 字节    ≈  7,798 token   (36 个工具)
--------------------------------------------
固定开销                     ≈  9,288 token

单个体积最大的工具:pwsh 860 token、workflow 847、plugin_manager 612、 ask_user_question 312。

对 32k 窗口的本地模型来说,9.3k 的固定开销意味着还没开始聊就用掉了近三成上下文。 关键结论是:裁工具比裁提示词有效得多——提示词全砍也才省 1.5k,而工具面收拢能省 6k。

本预设实测(同一台机器、同一个 lm-studio / qwen/qwen3.5-9b、32k 窗口):

标准预设本地简化
系统提示词1,490 token186 token-87%
工具 schema7,798 token(36 个工具)1,818 token(13 个工具)-77%
每次请求固定开销9,288 token2,004 token-78%

13 个工具 = 本预设声明的 7 个 + read_image(attachments 挂载时自动注册)

  • job_output/job_list/job_kill + 一个宿主层工具 load_workspace_dependencies。

这 2,004 token 是会话日志里的原始字节算出来的,不是估算:标准预设那 9,288 是本机 会话实测,本地简化这 2,004 来自一次真实会话的 request/header 工具 schema 与 system/message 系统提示词。

一个容易误判的地方:会话记录头部的 agentPreset 字段是在建会话时写入的, 早于预设绑定,所以即使这次会话确实跑的是本地简化,那个字段仍写着 standard。 判断依据应该是模型真正收到的东西——tools/measure-session.mjs 现在就是这么做的: 只要 wire 上还留着继承来的全局工具(如 ssh_*),就说明 tool-surface 没生效。


它做了什么

1. 系统提示词只剩一段 persona

@deepseek-ai/dsh-persona 的 complete: true 让那段 prefix 成为整个系统提示词: harness 身份、web-surface、工具指南、文件引用、结构化输出等段落一律不再注入。 配合 includeRuntimeContext: false,sandbox / approval 那几条 user-role 运行时快照也一并关闭。

提示词本身只有 270 字符,写成中文,因为本地小模型通常在中文上表现最好:

你是本地精简模式的编码助手,工作目录 <cwd>。

规则:
- 先读再改。修改文件前先读取它;不确定就先查,不要猜。
- 用工具动手,不要空谈。需要事实就 read/grep/glob/web_search;需要执行就 pwsh。
- 保持简短。一次解决一件事,回答用要点,不要复述已知内容。
- 改了代码要验证;有失败就修到通过,或者如实说明卡在哪里。
- 不要输出大段日志:命令自己过滤(Select-String、-First、-Tail)。
- 项目里有 AGENTS.md 或 README 时,先读它。

2. 工具面收拢到这几件

本预设自己声明 7 个:

工具能力来源
read / write / edit读写文件@deepseek-ai/dsh-tool-fs
glob / grep按路径 / 内容检索@deepseek-ai/dsh-tool-fs-search
web_search / web_fetch联网检索资料@deepseek-ai/dsh-tool-web
pwsh(Windows)/ bash(POSIX)执行终端命令@deepseek-ai/dsh-tool-pwsh-persistent
job_output / job_list / job_kill后台任务控制@deepseek-ai/dsh-tool-jobs

最终 wire 上是 13 个,差额都是无法由预设声明的:

  • read_image:宿主 attachments 挂载时自动注册,读图片用。
  • load_workspace_dependencies:宿主层工具,给出随包 Python/Node 载荷的绝对路径。 想连它也去掉的话,在 cordis.patch.yml 的 tool-surface 行的 alsoDeny 里加上即可。

三个取舍:

  • 用持久 shell,不用每次新进程的 shell。 持久行可以覆写 description(自带默认值很长), schema 也只有一个 command 参数、没有 sandbox 升级字段(省 671 token)。cd、变量、函数 跨调用保持,对小模型尤其重要——它省掉了「把命令拼成一条复合命令」这一步推理。 maxOutputChars: 12000 保证原始日志挤不掉提示词。
  • 输出上限在工具层收窄。 readLimit: 1200、searchMaxResults: 5、 fetchMaxOutputChars: 24000(出厂默认 200,000 字符,比本地模型整个窗口还大)。
  • 保留 compaction-basic。 短上下文正是要设计的失效场景,自动压缩必须能工作。

3. 遮蔽继承来的全局工具

这一条是本预设唯一的自研插件 tool-surface,也是收益最大的一块。

DSH 把 host 层挂载的工具对每个 agent 都可见,与预设无关。所以即使预设只声明 7 个工具, 实测仍有 19 个——多出来的是一个 SSH 插件(6 个 ssh_*,≈1,090 token)和 load_workspace_dependencies(158 token),跟这个预设毫无关系。

tool-surface 用 ctx.tools.restrict({ deny }) 把它们从这个预设的 scope 上摘掉。注意 名单是算出来的,不是写死的:

deny = { 实时全局视图里的名字 } - { 本预设自己注册的 } - { keep } + { alsoDeny }

restrict() 遇到未知工具名会直接抛错,所以写死的名单一旦 profile 变化(比如你启用了 SSH 插件)就会反过来让预设挂不上。算出来的名单不会过期。所有失败路径都只记日志、不抛错: 削不掉只是多花 token,不该让预设组不起来。

故意不放的东西

  • plan 模式:plan:policy 段落约 1,000 token 常驻系统提示词且无法缩短。另外 complete: true 的 persona 与它会构成两个 complete 段,assembly 会直接失败——两者互斥。
  • subagent / workflow / ralph / goal / skill / todo / ask_user_question / present。
  • str_replace_editor:与 read/write/edit 功能重复。
  • tool-result-pruner:shell 和 read 已在工具层设了上限,而在这个上下文规模上, 悄悄裁掉小模型刚主动要来的结果,比它省下的空间更糟。

安装

需要 DeepSeek Harness(Desktop 或 CLI)以及它自带的 plugin_manager 工具。

plugin_manager { action: "install_bundle", target: "<本仓库的 git 地址或 npm 包名>" }

本地开发时直接指向目录:

plugin_manager { action: "install_bundle", target: "D:\\path\\to\\local-lite" }

装完后重启 DSH(预设定义在启动时按 patch 解析),新建会话时在预设选择器里选 本地简化。

卸载

plugin_manager { action: "remove_bundle", target: "@Xeraph627/dsh-local-lite-preset" }

本机开发时注意

  • 若 profile 里记录的是 link: 指向你的工作目录,不要删除或移动该目录,否则预设失效。

  • 改完 cordis.patch.yml 或 lib/index.js 后要让 Loader 重读。改行插件名必须重启; 只改插件代码体时,关掉再打开预设行通常就够:

    plugin_manager { action: "set_plugin", target: "include:preset-local-lite", enabled: false }
    plugin_manager { action: "set_plugin", target: "include:preset-local-lite", enabled: true }
    
  • 用 install_bundle 重装时 target 要用解析后的包名而不是路径,否则报 ambiguous-install(它按「依赖有没有变化」判断装了什么,路径匹配不上任何依赖键)。


技术栈

层用了什么
运行载体Node.js ESM("type": "module")
插件框架Cordis —— 插件是 export const name / inject / apply(ctx, config)
配置方言Cordis loader patch YAML(insert 列表、!!js 表达式、cordis:group、isolate realm)
扩展点DSH ctx.tools 注册表(schemas() 读全局视图、restrict() 按 scope 遮蔽全局工具)
预设声明@deepseek-ai/dsh-agent-preset(dsh.bundle.patch 指向的 patch 里 insert 一行)
提示词@deepseek-ai/dsh-persona(complete: true + includeRuntimeContext: false)
开发工具纯 Node 内置模块,零开发依赖:node:zlib(zstd)、node:module、js-yaml(借自 profile)

为什么没有构建步骤:插件只有 lib/index.js 一个文件、零 runtime 依赖,不需要打包器。 包体就是源码,pnpm pack 的产物正好是运行所需的 4 个文件。

项目结构

local-lite/
├─ package.json          # 既是 bundle 清单,也是插件入口(exports)
├─ cordis.patch.yml      # 预设声明本体
├─ lib/index.js          # tool-surface 插件:按实时全局视图遮蔽继承来的工具
├─ README.md
└─ tools/                # 开发期工具,不参与运行(在 files 白名单之外)
   ├─ validate-patch.mjs     # 装之前校验:声明形状 / 禁止路径行 / 行名真能解析 / isolate 完整
   ├─ patch-anchoring.mjs    # 复刻 Host 的行名锚定规则,供校验器使用
   ├─ test-tool-surface.mjs  # 用假注册表单测插件逻辑
   ├─ measure-session.mjs    # 实测某个会话的固定开销(提示词 + 工具 schema)
   ├─ session-log.mjs        # 解码会话日志(zstd 分帧 JSONL)
   └─ asar.mjs               # 只读 app.asar(Desktop 里无法用 shell 打开)

开发

零依赖,直接跑:

# 1. 声明校验:形状 / 禁止路径行 / 行名解析 / isolate 完整性 —— 必须全绿才能装
node tools/validate-patch.mjs cordis.patch.yml

# 2. 插件逻辑单测:deny 名单应恰好是非本预设的全局工具,且不应有 warning
node tools/test-tool-surface.mjs

# 3. 实测某个会话的固定开销
node tools/measure-session.mjs <session.v4.jsonl.zstd>

# 4. 会话日志概览(事件类型统计 / 读某类事件)
node tools/session-log.mjs <session.v4.jsonl.zstd> [eventType]

会话日志位置:$DSH_HOME/sessions/<转义后的工作区目录>/<session-id>/session.v4.jsonl.zstd

三个踩过的坑(都已在工具里固化成检查)

1. 预设声明里的行名绝不能是路径。 失败的不是那一行,而是整个预设——UI 上整张卡片 显示「加载失败」加一句 tool-surface (...): never started。原因在 Host 的 anchorInsertedPluginNames 只锚定 insert 行和 group: true 行的子行;预设声明的 config.plugins 不是 config,解析器不会下钻,于是相对路径原样传下去、绝对路径以裸文件 系统路径(而非 file:// URL)传下去,两种都解析不到。必须用裸包名 specifier。 validate-patch.mjs 现在把路径行当硬错误拒绝(三种形式都有回归覆盖)。

2. 插件不能是 bundle 的子依赖。 曾经把插件放成同仓库子包、用 link:./tool-surface 引用—— 这在别的机器上必然坏:bundle 的依赖不会被传递安装,profile 只是把 bundle 目录符号链接 进来,不会装它自己的依赖。所以插件改成放在包内、由 exports 暴露,patch 里直接用包名本身。 一个包,一个 specifier,零依赖。

3. install_bundle 会跑 pnpm,pnpm 会去问 registry。 网络不通时实测挂了近 4 小时才被 终止,失败时会回滚 profile 的 package.json。网络不可用时改用 set_plugin 关掉再打开预设行。


兼容性

  • 在 DeepSeek Harness 0.2.0-rc.2(Windows / Electron Desktop,lm-studio / qwen3.5-9b) 上开发并实测通过:系统提示词 270 字符,wire 上 13 个工具。
  • shell 行按平台二选一(!!js process.platform 门控):Windows 用 pwsh,POSIX 用 bash。 两个套餐的 description 都覆写成中文。POSIX 分支尚未在真机验证过。
  • 预设 id 是 local-lite,roster order 5,选择器显示名 本地简化。
  • tool-surface 依赖 ctx.tools.schemas() / ctx.tools.restrict() 这两个 API; 上游若改名,它会只记日志、不抛错(预设照常挂载,只是少省 1.2k token), 所以升级 DSH 后建议用 measure-session.mjs 复查一次工具数量。

许可

MIT。声明部分改编自 DeepSeek Harness 内置的 minimal 与 standard 预设(MIT); lib/index.js、tools/* 为本项目自有实现。

Plugins associés