🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

复现迭代循环 sunstrikes/dsh-reprise

DeepSeek Harness
sunstrikes8 天前有提交已提供中文文档扫描:含敏感能力

先复现论文指标,再受控迭代模型结构并记录花费与判停

35.8/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/27 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add sunstrikes/dsh-reprise
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · tool
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L4 · 真实安装等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 8 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-reprise-workspace(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/3 11:53:48

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件
查看扫描证据(共 104 处,展示前 99 条)
  • 读取本地文件
    packages/core/lib/active-runs.js:51
    export function readHostState(runsRoot, { readFile = (path) => readFileSync(path, 'utf8') } = {}) {
  • 读取本地文件
    packages/core/lib/active-runs.js:54
    if (!existsSync(path)) return { status: 'absent', ids: undefined }
  • 写入本地文件
    packages/core/lib/active-runs.js:112
    export function setRunActive(runsRoot, id, { activate, readFile, writeFile = (path, text) => writeFileSync(path, text) } = {}) {
  • 写入本地文件
    packages/core/lib/active-runs.js:143
    export function recordTrash(runsRoot, entry, { readFile, writeFile = (path, text) => writeFileSync(path, text) } = {}) {
  • 写入本地文件
    packages/core/lib/active-runs.js:161
    renameSync(temporary, path)
  • 删除本地文件
    packages/core/lib/active-runs.js:164
    unlinkSync(temporary)
  • 读取本地文件
    packages/core/lib/anchor.js:255
    names = readdirSync(dir).filter((name) => name.endsWith('.json')).sort()
  • 读取本地文件
    packages/core/lib/anchor.js:262
    const proposal = JSON.parse(readFileSync(join(dir, name), 'utf8'))
  • 写入本地文件
    packages/core/lib/anchor.js:279
    mkdirSync(dir, { recursive: true })
  • 读取本地文件
    packages/core/lib/anchor.js:281
    if (existsSync(path)) return false
  • 写入本地文件
    packages/core/lib/anchor.js:283
    writeFileSync(path, `${JSON.stringify(proposal, null, 2)}\n`, { flag: 'wx' })
  • 写入本地文件
    packages/core/lib/artifacts.js:52
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    packages/core/lib/artifacts.js:55
    writeFileSync(temporary, document, 'utf8')
  • 写入本地文件
    packages/core/lib/artifacts.js:56
    renameSync(temporary, path)
  • 读取本地文件
    packages/core/lib/artifacts.js:61
    return JSON.parse(readFileSync(pathFor(name), 'utf8'))
  • 读取本地文件
    packages/core/lib/artifacts.js:70
    entries = readdirSync(dir, { recursive: true })
  • 读取本地文件
    packages/core/lib/domain.js:198
    if (!existsSync(packDir) || !statSync(packDir).isDirectory()) {
  • 读取本地文件
    packages/core/lib/domain.js:198
    if (!existsSync(packDir) || !statSync(packDir).isDirectory()) {
  • 读取本地文件
    packages/core/lib/domain.js:205
    if (!existsSync(manifestPath)) {
  • 读取本地文件
    packages/core/lib/domain.js:214
    manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
  • 读取本地文件
    packages/core/lib/domain.js:263
    if (!existsSync(path)) {
  • 读取本地文件
    packages/core/lib/domain.js:271
    value = JSON.parse(readFileSync(path, 'utf8'))
  • 执行外部命令来自配置/变量
    packages/core/lib/gate.js:175
    const result = spawnSync(process.execPath, ['--check', path], { encoding: 'utf8' })
  • 读取本地文件
    packages/core/lib/gate.js:250
    readFile = (path) => readFileSync(path, 'utf8'),
  • 写入本地文件
    packages/core/lib/intents.js:98
    for (const dir of [paths.pending, paths.applied, paths.refused]) mkdirSync(dir, { recursive: true })
  • 写入本地文件
    packages/core/lib/intents.js:215
    writeFileSync(path, `${JSON.stringify(placed, null, 2)}\n`, { flag: 'wx' })
  • 读取本地文件
    packages/core/lib/intents.js:225
    const readJson = (path) => JSON.parse(readFileSync(path, 'utf8'))
  • 读取本地文件
    packages/core/lib/intents.js:253
    return readdirSync(dir).filter((name) => name.endsWith('.json')).sort()
  • 写入本地文件
    packages/core/lib/intents.js:301
    writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, { flag: 'wx' })
  • 读取本地文件
    packages/core/lib/intents.js:312
    if (!existsSync(path)) continue
  • 读取本地文件
    packages/core/lib/intents.js:363
    for (const name of readdirSync(paths.pending).filter((entry) => entry.endsWith('.json')).sort()) {
  • 读取本地文件
    packages/core/lib/intents.js:368
    raw = readFileSync(path, 'utf8')
  • 删除本地文件
    packages/core/lib/intents.js:392
    unlinkSync(path)
  • 读取本地文件
    packages/core/lib/intents.js:606
    if (!existsSync(path)) return undefined
  • 写入本地文件
    packages/core/lib/intents.js:720
    mkdirSync(context.paths.experiences, { recursive: true })
  • 写入本地文件
    packages/core/lib/intents.js:721
    writeFileSync(join(context.paths.experiences, `${intent.experience_id}.json`), `${JSON.stringify(artifact, null, 2)}\n`)
  • 读取本地文件
    packages/core/lib/ports.js:80
    return existsSync(candidate) ? candidate : replaced
  • 执行外部命令固定命令
    packages/core/lib/ports.js:126
    const gitRun = git ?? ((args, options) => spawnSync('git', args, { encoding: 'utf8', ...options }))
  • 读取本地文件
    packages/core/lib/ports.js:169
    if (created.has(candidateId) && existsSync(path)) return path
  • 写入本地文件
    packages/core/lib/ports.js:172
    mkdirSync(worktreesDir, { recursive: true })
  • 读取本地文件
    packages/core/lib/ports.js:173
    if (!existsSync(path)) {
  • 读取本地文件
    packages/core/lib/ports.js:220
    if (!existsSync(path)) {
  • 读取本地文件
    packages/core/lib/ports.js:281
    const existed = existsSync(path)
  • 读取本地文件
    packages/core/lib/ports.js:580
    if (!existsSync(path)) return undefined
  • 读取本地文件
    packages/core/lib/projection.js:1198
    const document = JSON.parse(readFileSync(join(dirname(store.path), 'baseline.json'), 'utf8'))
  • 读取本地文件
    packages/core/lib/run-config.js:57
    export function loadConfigFile({ raw = {}, cwd = process.cwd(), env = process.env, readFile = (path) => readFileSync(path, 'utf8') } = {}) {
  • 读取本地文件
    packages/core/lib/runs.js:64
    return statSync(dir).isDirectory()
  • 读取本地文件
    packages/core/lib/runs.js:103
    names = readdirSync(root).sort()
  • 读取本地文件
    packages/core/lib/runs.js:177
    export function readRunConfig(dir, { readFile = (path) => readFileSync(path, 'utf8') } = {}) {
  • 读取本地文件
    packages/core/lib/runs.js:179
    if (!existsSync(path)) return { path, status: 'missing' }
  • 读取本地文件
    packages/core/lib/runs.js:293
    if (!existsSync(join(domain, 'domain.json'))) {
  • 读取本地文件
    packages/core/lib/runs.js:303
    if (existsSync(dir)) throw new Error(`a run directory already exists at ${dir}`)
  • 写入本地文件
    packages/core/lib/runs.js:307
    mkdirSync(dir, { recursive: true })
  • 执行外部命令固定命令
    packages/core/lib/runs.js:311
    const gitRun = git ?? ((args, options) => spawnSync('git', args, { encoding: 'utf8', ...options }))
  • 写入本地文件
    packages/core/lib/runs.js:323
    writeFileSync(
  • 写入本地文件
    packages/core/lib/runs.js:420
    mkdirSync(trash, { recursive: true })
  • 写入本地文件
    packages/core/lib/runs.js:424
    renameSync(dir, target)
  • 写入本地文件
    packages/core/lib/runs.js:431
    renameSync(target, dir)
  • 读取本地文件
    packages/core/lib/samples.js:55
    return readFileSync(fileURLToPath(uri), 'utf8')
  • 读取本地文件
    packages/core/lib/settings.js:70
    export function readSettings({ env = process.env, readFile = (path) => readFileSync(path, 'utf8') } = {}) {
  • 读取本地文件
    packages/core/lib/settings.js:73
    if (!existsSync(path)) return { status: 'absent', path, value: {} }
  • 写入本地文件
    packages/core/lib/settings.js:136
    export function writeSettings(patch, { env = process.env, readFile, writeFile = (path, text) => writeFileSync(path, text) } = {}) {
  • 写入本地文件
    packages/core/lib/settings.js:176
    mkdirSync(dirname(path), { recursive: true })
  • 写入本地文件
    packages/core/lib/settings.js:180
    renameSync(temporary, path)
  • 删除本地文件
    packages/core/lib/settings.js:183
    unlinkSync(temporary)
  • 读取本地文件
    packages/core/lib/skills.js:109
    text = readFileSync(path, 'utf8')
  • 写入本地文件
    packages/core/lib/store.js:169
    mkdirSync(workspaceDir, { recursive: true })
  • 读取本地文件
    packages/core/lib/worktrees.js:155
    exists: path === null ? false : existsSync(path),
  • 执行外部命令固定命令
    packages/exec/lib/archive.js:120
    return spawnSync('git', args, { cwd, encoding: 'utf8' })
  • 读取本地文件
    packages/exec/lib/archive.js:163
    lstatSync(join(worktreeDir, path))
  • 读取本地文件
    packages/exec/lib/archive.js:183
    entries = readdirSync(directory, { withFileTypes: true })
  • 读取本地文件
    packages/exec/lib/archive.js:212
    const stats = statSync(absolute)
  • 读取本地文件
    packages/exec/lib/archive.js:267
    const VERIFIER_SOURCE = readFileSync(fileURLToPath(new URL('./verify-inventory.py', import.meta.url)), 'utf8')
  • 读取本地文件
    packages/exec/lib/archive.js:316
    content = readFileSync(file.absolute)
  • 执行外部命令固定命令
    packages/exec/lib/harness.js:447
    const result = spawnSync('git', args, { cwd, env, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
  • 读取本地文件
    packages/exec/lib/harness.js:583
    stat = statSync(worktreeDir)
  • 写入本地文件
    packages/exec/lib/local-runner.js:38
    writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`)
  • 写入本地文件
    packages/exec/lib/local-runner.js:39
    renameSync(temporary, path)
  • 读取本地文件
    packages/exec/lib/local-runner.js:42
    const spec = JSON.parse(readFileSync(join(jobDir, 'spec.json'), 'utf8'))
  • 写入本地文件
    packages/exec/lib/local-runner.js:43
    const log = createWriteStream(join(jobDir, 'driver.log'), { flags: 'a' })
  • 执行外部命令来自配置/变量
    packages/exec/lib/local-runner.js:48
    const child = spawn(spec.command[0], spec.command.slice(1), {
  • 读取本地文件
    packages/exec/lib/platform-local.js:34
    return JSON.parse(readFileSync(path, 'utf8'))
  • 写入本地文件
    packages/exec/lib/platform-local.js:42
    writeFileSync(temporary, `${JSON.stringify(value, null, 2)}\n`)
  • 写入本地文件
    packages/exec/lib/platform-local.js:43
    renameSync(temporary, path)
  • 写入本地文件
    packages/exec/lib/platform-local.js:104
    mkdirSync(rootDir, { recursive: true })
  • 读取本地文件
    packages/exec/lib/platform-local.js:197
    if (!existsSync(path)) continue
  • 读取本地文件
    packages/exec/lib/platform-local.js:198
    artifacts.push({ name: fileName, uri: `file://${path}`, bytes: statSync(path).size })
  • 写入本地文件
    packages/exec/lib/platform-local.js:249
    mkdirSync(directory, { recursive: true })
  • 执行外部命令来自配置/变量
    packages/exec/lib/platform-local.js:264
    const child = spawn(process.execPath, [SUPERVISOR, directory], {
  • 读取本地文件
    packages/exec/lib/platform-local.js:316
    for (const entry of readdirSync(rootDir, { withFileTypes: true })) {
  • 读取本地文件
    packages/exec/lib/platform-ray.js:51
    return JSON.parse(readFileSync(path, 'utf8'))
  • 读取本地文件
    packages/exec/lib/platform-ray.js:848
    if (metricsPath !== undefined && existsSync(metricsPath)) {
  • 读取本地文件
    packages/exec/lib/platform-ray.js:849
    artifacts.push({ name: 'metrics.json', uri: `file://${metricsPath}`, bytes: statSync(metricsPath).size })
  • 写入本地文件
    packages/exec/lib/platform-ray.js:860
    mkdirSync(artifactsDir, { recursive: true })
  • 写入本地文件
    packages/exec/lib/platform-ray.js:862
    writeFileSync(path, logs)
  • 读取本地文件
    packages/memory/lib/experiences.js:55
    names = readdirSync(dir).filter((name) => name.endsWith('.json'))
  • 读取本地文件
    packages/memory/lib/experiences.js:69
    parsed = JSON.parse(readFileSync(path, 'utf8'))
  • 读取本地文件
    packages/ui/lib/routes.js:283
    return new Response(readFileSync(join(PACKAGE_DIR, 'client', file), 'utf8'), {
  • 读取本地文件
    packages/ui/lib/runs.js:74
    if (!existsSync(join(workspace, DB_FILENAME))) return undefined
扫描于 2026/11/2(1 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

一个 AutoResearch DSH 插件,用来复现生成式推荐方向的论文,支持可控的实验迭代和训练代码复现。

README

由 DeepSeek 最新模型翻译生成
description: "把模型结构探索循环做成 DeepSeek Harness 插件:确定性阶段、冻结基线、花费账本,以及只读取事实的判停。" kind: "package-bundle" dsh-reprise 一个结论经得起复核的探索循环。 一个 DeepSeek Harness 插件包:先复现一个已发表的数字,再在这份复现之上受控地迭代——确定性阶段、由运行自己跑出来再冻结的基线、先预扣再花费的花费账本,以及由已记录事实(而不是记忆中的意图)算出的判停。 摘要 dsh-reprise 复现一个已发表的实验,然后在这份复现之上受控地迭代:确定性阶段、走与候选相同的执行器路径跑出来再冻结的基线、先预扣再花费的预算,以及由已记录事实(而不是记忆中的意图)算出的判停决定。 一个候选(candidate)是一个结构假设,加上它落地所需的那一批代码改动——不是一个超参数取值。循环本身是普通代码;模型只在恰好五个位置被咨询,而它说的话只有变成一个已被校验器接受的 artifact(产物)时,才会进入事实面(fact plane)。
展开完整文档(README 剩余 1 节)
description: "把模型结构探索循环做成 DeepSeek Harness 插件:确定性阶段、冻结基线、花费账本,以及只读取事实的判停。"
kind: "package-bundle"

dsh-reprise

一个结论经得起复核的探索循环。

一个 DeepSeek Harness 插件包:先复现一个已发表的数字,再在这份复现之上受控地迭代——确定性阶段、由运行自己跑出来再冻结的基线、先预扣再花费的花费账本,以及由已记录事实(而不是记忆中的意图)算出的判停。

摘要

dsh-reprise 复现一个已发表的实验,然后在这份复现之上受控地迭代:确定性阶段、走与候选相同的执行器路径跑出来再冻结的基线、先预扣再花费的预算,以及由已记录事实(而不是记忆中的意图)算出的判停决定。

一个候选(candidate)是一个结构假设,加上它落地所需的那一批代码改动——不是一个超参数取值。循环本身是普通代码;模型只在恰好五个位置被咨询,而它说的话只有变成一个已被校验器接受的 artifact(产物)时,才会进入事实面(fact plane)。

挂载这个插件包只新增三个面(surface),别的什么也不加:一个在没有会话打开的工作区里推进阶段机的 tick,一个在会话内读取同一份投影的 reprise_status 工具,以及侧边栏里的 Reprise 面板(背后是下面那些只读 GET 路由)。它不碰你的仓库,不开端口,也不调用模型——除非你在 model.command 里点名一个。

如果你要的是 prompt 搜索、超参数扫描,或者一个可以手动操作的看板,就不要用它:v1 完全没有写端点,浏览器那一半在构造上就是只读的,而且循环每轮只优化一个声明过的指标,而不是优化一批 prompt 组成的种群。

目录

- 何时使用
- 安装与挂载
- 配置
- 它新增了什么
- 五分钟路径
- 检查与本地开发
- 设计
- 源码地图
- 进一步阅读
- 模型体验
- 已知局限与推迟的工作

使用本包

何时使用

你有一个可以测两次的指标,一条你愿意冻结的基线,以及一个信念:出问题的地方是模型的结构,而不是它的学习率。你希望「这个候选为什么停了?」的答案来自一个文件,而不是来自当时盯着终端的那个人。

当运行必须扛得住无人值守时,dsh-reprise 是合适的:阶段中途崩溃会表现为事实面上一段可检测的滞后,被暂停的预算是被记录下来的决定,页面上的每个数字都能只从事实推导出来(dump --rebuild 会证明这一点)。当指标是人的判断、当基线无法做到可复现、或者当你需要一个工作区上并发跑多个运行时,它都不合适——一个工作区只属于一次运行。

安装与挂载

装发布用的 tarball。它把兄弟包带在自己身上——放在 vendor/packages/ 下,并把每个 @dsh-reprise/ specifier 改写成相对路径——所以安装不需要克隆、不需要解析 workspace,也没有构建步骤:

dsh plugin --profile web add ./dsh-reprise-<version>.tgz # 最新一次 release 上的那个资产

这个文件由仓库里的 node scripts/build-release.mjs 生成到 dist/,用路径安装效果一样。从 git 装并非看上去的捷径:github:sunstrikes/dsh-reprise#path:/packages/core 会同时撞上三件事——包之间靠 workspace: 互相依赖,只有 monorepo 解析得了;pnpm 拒绝 root 以下的任何 git 依赖(ERR_PNPM_EXOTIC_SUBDEP);packages/ui 用相对路径 import packages/core,任何包管理器的目录布局都保不住它。外人能装的就是那个 tarball。

要在上面开发,就克隆并链接——profile 链接那个目录而不是复制它,所以改动是活的,兄弟包就是它旁边的那些:

git clone https://github.com/sunstrikes/dsh-reprise.git
cd dsh-reprise
node scripts/link-workspace.mjs # Node 22.5+; makes packages/* importable, idempotent

用宿主自己的工具,而不是手工编辑 profile——install_bundle 一步完成依赖安装、链接与插件包选择,把 target 设成 <clone>/packages/core 的绝对路径:

plugin_manager { action: "install_bundle", target: "/absolute/path/to/dsh-reprise/packages/core" }

然后提供你自己的配置。随包发布的 patch 里根本没有 config 块:没有 workspace 与 domain 领域包就无法启动运行,而一份真实配置需要的每个路径都属于拥有这次运行的那台机器。新增一条按 id 指向 reprise-core 的条目,并把整份配置重述一遍——一个匹配的 override 会整块替换 config,而不是深合并。把它写进你 profile 的 cordis.patch.yml(或 $DSH_HOME/cordis.patch.yml,后者的优先级高于 profile 层); packages/core/cordis.example.yml 是下面这个形状的填好版本,连两条各花掉一轮调试的路径规则都包含在内。

- id: reprise-core
name: dsh-reprise
config:
workspace: /absolute/path/to/my-run # required; created facts live here
domain: /absolute/path/to/my-domain-pack # required; 指标契约、先验、可调面白名单、五段提示词、执行器入口 interval_ms: 5000 candidate: kind: command
command: ['node', '/absolute/path/to/apply-change.mjs', '{candidate}']
runner: { repo: model-repo, dir: worktrees }
model: { command: ['node', '/absolute/path/to/my-model-port.mjs'] }
rounds: { metric: { name: auc, direction: higher }, max_rounds: 3 }
metrics: { primary: auc, objectives: { auc: up } }
budget: { candidate_gpu_hours: 0.5, gpu_hours_limit: 0 }

按任务起一次运行的操作者,其实不必改这个 patch。下面两种做法都可以,而且可以叠加——优先级是 内联 config → config_file → 环境变量:

- 每次运行一个文件。 在 patch 的 config 里写 config_file: /path/to/run.json(或者让 DSH_REPRISE_CONFIG 指向它,适合不写 YAML 的启动器)。这个文件在挂载时读取一次:它是基础配置,任何内联 config 键都会覆盖它——按 key 的浅合并。文件内部的 workspace 相对文件自身所在目录解析,所以文件可以放在它所描述的那次运行旁边;其它路径仍然遵循下方「相对宿主 cwd」的规则。
文件缺失、不可读或不是 JSON 时,启动日志会给出一条点名的拒绝(含路径与解析错误),绝不会悄悄退回默认值。
- 一个环境变量。 配置里没有 workspace 时,DSH_REPRISE_WORKSPACE 就是它;相对路径按宿主 cwd 解析,绝对路径原样使用。内联的 workspace 仍然优先。

无论走哪条路,启动日志里的 run: 行都用一行回答这个宿主到底在跑什么:解析后的工作区(以及它来自哪里)、候选分支自哪个仓库、工作树建在哪里、是否保留,以及配置读自哪个文件。

重启宿主(插件包清单只在启动时读取;开着 HMR 时,单独改 patch 就会立即生效)。当日志里出现 boot 那一半的 applied 行、接着出现 tick 那一半的 tick-applied、侧边栏显示 Reprise、并且模型能用 reprise_status 时,你就挂载好了。

三种缺失会降级而不是失败,这是设计如此:没有 timer 服务意味着 tick 那一半被推迟,而不是失败(cordis 对这两种情况的报告一模一样,这也是本包有两个入口的原因);没有 connection 服务意味着没有看板;打不开的工作区或跑不起来的配置只是一行日志,宿主照常启动。这三种情况都不会以错误的形式出现。

被校验拒绝的跑配置不属于上面三种缺失,也不会再连看板一起带走:看板仍会挂到配置点名的工作区上,并把「循环为什么静默」写进 overview 的 notes——两个界面上的条带在任何状态下都会画它,空页也不例外。所以看板照旧显示工作区里已有的事实,但不会假装循环在跑。连工作区都点不名的配置,是唯一仍然挂不上看板的情况。

@dsh-reprise/memory 插件包是可选的、独立的(行 reprise-memory),它把注入包(injection packet)作为一个 dsh-mnemon Source 暴露出来。packages/core 里没有任何东西 import 它;它自己也没有依赖,所以这一个可以直接从仓库子目录装,用不上 tarball: dsh plugin --profile web add github:sunstrikes/dsh-reprise#path:/packages/memory。

配置

所有领域特定的东西都是数据,而且都在一个领域包里——一个目录,它的 domain.json 指明自己的指标契约、先验、可调面白名单、五段提示词和执行器入口(examples/feed-structure 就是一个完整的包)。内核不知道任何指标名、模型仓库或提示词:它从包里读;包缺了某一部分会在启动时被点名拒绝,而不是半配置地跑起来。

| 字段 | 默认值 | 含义 |
| workspace | 必填,或 DSH_REPRISE_WORKSPACE | 运行拥有的目录;事实面就是它里面的 workspace.db。相对路径按宿主 cwd 解析,绝对路径原样使用 |
| config_file | —(或 DSH_REPRISE_CONFIG) | 一份 JSON 运行配置文件,挂载时读一次:基础配置,任何内联键都覆盖它(按 key 的浅合并)。文件里的 workspace 相对文件所在目录解析。文件缺失或格式错误会是点名的启动拒绝 |
| domain | 必填 | 领域包目录;没有包就没有指标契约、没有提示词 |
| interval_ms | 5000 | tick 节律 |
| executor.kind | 取领域包 | local(子进程)或 ray(Job Submission REST) |
| executor.command | 取领域包,local 时必填 | 一个作业的 argv,在候选的工作树(worktree)里解析 |
| executor.base_url | ray 时必填 | Ray dashboard URL |
| executor.root_dir / metrics_required | 'jobs' / true | 作业目录所在位置;没有指标的作业算失败 |
| candidate.kind | 'command' | command、claude 或 dsh——LAND 的实现者 |
| candidate.command | command 时必填 | argv;{candidate} 和 {worktree} 会被替换 |
| runner.repo / runner.dir | 配置 candidate 时必填 | 工作区里的仓库;工作树创建的位置(<workspace>/worktrees/<candidateId>) |
| runner.keep | true | 保留策略。为 true 时内核里没有任何东西会删掉候选工作树;为 false 本身也不会删,只是允许显式执行 worktrees prune |
| runner.base_ref | git 默认值 | 候选从中拉分支的那个 commit;代码基滚动之后以锚点为准(见 rounds.anchor) |
| model.command | 配置了 rounds 时必填 | 每个判断阶段都通过这个 argv 触达模型 |
| rounds.metric.name / .direction | 取领域包的契约 | 轮次闸门优化的那一个指标;重述它就是让这次运行指向另一个目标 |
| rounds.metric.contract_ref | 契约自己的 name | 指向已冻结的指标契约(metric contract)的指针 |
| rounds.max_rounds / .candidates_per_round | 3 / 2 | 轮次工作流的形状 |
| rounds.anchor | 'automatic' | 谁可以把代码基往前滚:automatic 在轮次之间按双支配规则(同时支配当前锚点与已冻结的基线)自动滚;human 只在 anchor-pending/ 写一份待审批,等 dsh-reprise intents anchor --candidate-id … --commit …。两种模式都不动差分分母 |
| rounds.retry.base_ms / .max_ms / .max_attempts | 60000 / 3600000 / 3 | artifact 被拒后的退避,退到最后交给人 |
| stop.max_rounds | 3 | 0 关闭这条规则 |
| stop.max_no_improvement_rounds / .consecutive_empty_rounds | 0(关闭) | 可选启用的判停规则 |
| stop.max_wall_hours / .max_windows | 0(关闭) | 工作区级上限:开得太久或窗口数到顶即结束整次运行——记在 _workspace 上,与「否掉一个候选」不是同一个结论 |
| stop.regression_windows / .regression_floor | 0(关闭)/ 0 | 尾部 N 个窗口逐个都比基线差才杀掉候选;下限只抬高容差、从不放宽 |
| budget.gpu_hours_limit | 0(不限) | 硬上限;未设上限的远程执行器每次读取都会被警告 |
| budget.soft_ratio / .candidate_gpu_hours | 0.8 / 0.5 | 到这个比例就停止派发;一次派发预扣多少 |
| budget.max_runtime_hours / .judge_tokens | 0(不限)/ false | 实际耗时闸门;token 只记录,从不裁决 |
| metrics.primary / .objectives | 取领域包的契约 | 在 INIT 时记录;会与轮次契约交叉核对 |
| poll.base_ms / .max_ms / .grace_attempts | 1000 / 60000 / 2 | 作业轮询退避 |
| limits.max_wall_ms / .max_idle_ms | 3600000 / 1800000 | 单作业的实际耗时与静默期限 |

校验在 packages/core/lib/config.js 里,启动时只跑一次。本包不导出 Config 对象,所以宿主对这条配置行没有 schema:配置有问题只是一行日志,永远不会变成启动失败。有两处拒绝值得在你撞上之前先知道——有 rounds 却没有 model.command,以及有 candidate 却没有 runner——否则替代方案就是一次在第一轮就卡住的运行,而不是在启动时卡住。metrics、stop、budget、executor 与 runner 会在 INIT 时被复制进工作区,之后只从那里读取,所以运行中途改 patch 不会改变看板报告的任何东西(dump 也仍然能只凭工作区目录说出工作树去了哪里)。

候选工作树是有意保留的。 一个已落地(LAND)的候选,它的树在 <workspace>/<runner.dir>/<candidateId>——判定它时看的那份 diff 就是那个目录里的文件——所以没有任何阶段、恢复路径或清理例程会删掉它。runner.keep(默认 true)是契约而不是巧合;dump --candidate <id> 会打印路径以及它是否还在磁盘上,worktrees list 是整次运行的清单,而 worktrees prune --candidate <id> 是唯一可以删树的东西——它会拒绝尚未结束的候选、不认识的 id,以及声明了 keep: true 的那次运行里的每一个候选。

它新增了什么

| 面 | 标识符 | 作用 |
| 模型工具 | reprise_status | 运行进度以 JSON 返回,只读,取自页面绘制所用的同一份投影 |
| 侧边栏面板 | sidebar.panellist id reprise,order 25,外加 main key reprise | Reprise 按钮(中英同名:产品名不翻译)以及它打开的页面;文案跟随当前语言(en、zh) |
| HTTP 路由 | GET /api/reprise/{overview,rounds,candidate,tree,conclusion,calls,call} | 四个视图、搜索树与模型调用审计,各自通过自己的只读句柄读取 |
| 静态资源 | GET /api/reprise/page、/api/reprise/dashboard.js、/api/reprise/format.js | 给没有客户端 slot 的宿主用的独立页面,以及页面与面板共用的格式化模块 |
| CLI | node packages/core/lib/cli/run.js --workspace <dir> --config <file> | 与宿主 tick 的同一套接线,由循环驱动;退出码 0 表示有结论、1 表示没有可比的指标、2 用法错误、3 超时 |
| CLI | node packages/core/lib/cli/dump.js --candidate <id> | 时间线、每个闸门的结论、候选工作树路径以及它是否还在磁盘上、身份绑定、前沿(frontier)位置,以及运行为什么停下来 |
| CLI | node packages/core/lib/cli/dump.js --tree | 整次搜索作为一棵血统树:谁从谁分出来,以及每条分支为什么结束 |
| CLI | node packages/core/lib/cli/worktrees.js list / prune --candidate <id> | list:每个候选一行——轮次、阶段、落地文件数、树是否还在磁盘上、以及它的路径。prune:唯一能删掉工作树的方式(git worktree remove 加一条 worktree_pruned 事实),且只在运行为 runner.keep: false 时可用 |
| CLI | node packages/core/lib/cli/intents.js place <kind> / list | 人的意图(proposal、grant、anchor、pause、resume、approval、experience)写入 <workspace>/pending/ |
| Skills | <domain>/prompts/{land,verify,cand-analyze,round-plan,round-reflect}/SKILL.md | 模型在五个调用点各自被告知什么;磁盘上的 markdown,属于领域包,每次调用都重新读取——不是 DSH skills,也没有按 DSH skill 注册 |

本包不提供服务、不发事件:它消费 timer、connection 和 tools。所有路由都是 GET;没有写端点可以去找,包括那种能让页面修改它正在报告的那次运行的端点。

五分钟路径

一台笔记本、一次克隆,别的什么也不要:没有 GPU,没有集群,没有模型服务商。整条路径由 packages/core/test/five-minute-path.test.js 在一个临时目录里跑一遍——它执行的就是本节印出的这些命令,所以这五行是契约,不是示意。

Step 1 — 让工作区的各个包可被导入

node scripts/link-workspace.mjs

它检查 Node 版本,为 packages/ 建链接使仓库根目录能 import 它们,最后真的 import 一次内核:一个连内核都 import 不了的克隆,别的什么也做不了。不联网,不用包管理器,可重复执行。

Step 2 — 建一个工作区

node examples/feed-structure/init.mjs .workspace/feed

一次真实的搭建,不是 fixture:它复制玩具模型仓库并提交(候选从那个 commit 拉分支),冻结一份记录运行起点的 sha256 清单,并通过真实的本地执行器跑 metric.mjs 测出基线。那个数字只是旁证:运行自己在 BASELINE 阶段跑一次基线(提交、等待、采集)并冻结成事实,分母与所有跟它比的东西出自同一次执行器运行。

Step 3 — 看你拥有的领域包

node examples/feed-structure/inspect.mjs

包的清单、指标契约(方向和角色)、可调面白名单、先验与假设分类、本轮的结构假设、执行器入口,以及接线。改动其中任何一个都是配置变更;内核对 AUC、点击,或你的模型一无所知。

Step 4 — 跑循环

node packages/core/lib/cli/run.js --workspace .workspace/feed --config examples/feed-structure/config.json

每个阶段步一行,然后是胜出者、它的指标、它打败的基线,以及结束这次运行的那条规则。这就是宿主插件用的同一套接线——workspace.js 和 tick()——只是由循环而不是宿主的 timer 驱动,所以你刚看到的就是插件的行为。

Step 5 — 追问原因

node packages/core/lib/cli/dump.js --candidate r1-h2 --workspace .workspace/feed

时间线、每个闸门的结论、身份绑定、前沿位置,以及运行为什么停下来——只从事实面得到。r1-h2 是随包示例产出的胜出者;用第 4 步印出的那个。不需要 --config:运行在 INIT 时把指标契约和判停规则写进工作区,所以只有一个目录的读者会得到同样的答案。加上 --rebuild 可以重放整个事件流,检查物化视图是否仍然能从事实推出来。

示例没有覆盖的:活的模型(recorded/judgement.json 重放四个回答)和真实的编码 harness (apply-change.mjs 机械地应用假设)。两者都是配置——model.command 和 candidate.command。
examples/feed-structure/README.md 说了要改什么、以及什么仍未验证。

检查与本地开发

npm test # scripts/check.mjs, then the full suite
npm run check # manifests, export targets, and a parse of every module under lib/

scripts/check.mjs 覆盖套件看不见的东西:不存在的导出目标、指向宿主无法 import 的 TypeScript 的导出、没有任何东西 import 的已声明依赖、没有声明来源的 import,以及没有测试恰好加载到的模块。

这些包从宿主 profile 解析 cordis,而不是再装一份,所以请链接 profile 的模块树,而不要跑 pnpm install:

ln -s ~/.dsh/profiles/node_modules node_modules

packages/core/test/host-plugin.test.js 里有五个测试驱动 cordis 的真实生命周期。cordis 是插件宿主(MIT,随 Harness profile 发布),不是本仓库的依赖,所以在没有它的机器上,这五个测试把自己报告为 跳过,而不是加载失败。每个克隆接线一次 pre-commit hook:git config core.hooksPath .githooks。

这里的任何东西都不允许被宿主当作 TypeScript import:每个插件包都暴露 main: lib/.js,随包发布的代码是纯 ESM,没有构建步骤。

理解实现

实现内部细节——点击展开

设计

- 要有两个入口,是因为依赖缺失只会推迟一个插件,而不会让它失败。 boot.js 什么都不声明,只做报告; tick.js 声明 inject: ['timer'] 并 tick;dashboard.js 声明 inject: ['connection']。如果合并成一个插件,「包从未加载」和「timer 从未出现」会产生同一份空日志。
- 事实先于状态,永远如此。 一条事实是由 (candidate_id, local_seq) 标识的追加行,在它所支撑的状态之前、在自己的事务里写入,所以崩溃留下的是可检测的滞后,而不是静默的偏差。用相同 payload 重放一条事实是 duplicate;同一身份、不同 payload 是 conflict,会被拒绝。
- 视图要么可推导,要么就是错的。 前沿、轮次历史和看板的四个页面(外加把整次搜索画成一棵树的搜索树页)全部由事实算出。
dump --rebuild 拒绝认可一个不再能从事实推出的视图。
- 模型只有五个调用点,没有别的。 land、verify、cand_analyze、round_plan、 round_reflect。每次调用是一个独立进程,prompt 由一份 markdown skill 加上作为 JSON 的事实渲染而成;回答的形状写在代码里、紧挨着它的解析器,所以 markdown 里的示例不会漂移到与判定器(judge)所接受的内容不一致。
- 只读是结构性的,不是一句承诺。 每次读取都打开自己的 readOnly SQLite 句柄并关掉:工具在物理上无法记录事实,两个浏览器窗口无法互相干扰,页面在所有会话都关掉时也能用——它只需要工作区目录,别的都不要。
- 先预扣,再结算。 一次派发在作业开始前预扣 budget.candidate_gpu_hours,之后按实测成本结算,所以作业中途崩溃会被计费而不是被遗忘。token 只记录、从不裁决,因为各家 harness 上报 token 的口径差得太多,不能当作闸门。
- 把危险的默认值说出来。 未设上限的远程执行器是合法的、是随包默认值,并且每次读取都会被警告——启动日志、run 的横幅,以及 overview().warnings,两个客户端都会画出来。

源码地图

| 路径 | 角色 |
| packages/contract | 四态校验、身份绑定链、artifact schema、指标契约 |
| packages/core | 阶段机、tick、事实存储、派生视图、判停策略、插件接线、CLI |
| packages/exec | 候选实现者与外部作业适配器(local、ray、claude、dsh),带一致性套件 |
| packages/memory | 注入包,以 dsh-mnemon Source 暴露 |
| packages/ui | 只读看板:宿主侧对路由的投影,加一个纯 ESM 客户端 |
| packages/exec-python | 可选的代理指标桥(需要 torch)——只有 manifest,还没有代码 |
| examples/feed-structure | 一个完整的领域包,也是一个走通的场景:domain.json 加上指标契约、先验、可调面白名单、五段提示词、执行器入口、结构假设与本地指标 |

依赖方向按包名无环:contract 不依赖这里的任何东西;exec 和 memory 依赖 contract;core 依赖这三者。有一处刻意的例外:ui 用相对路径读 core 的 store 与 projection 模块,因为看板必须无法对事实面打开写句柄,而对只读读取器的相对 import 恰好就把这句话写在了代码里。

进一步阅读

- CONTEXT.md — 代码依赖的十个词,以及它假定的精确含义
- docs/extending.md — 加一个执行器;加一种 artifact 类型
- docs/license-audit.md — 每个依赖和借鉴的想法,对照 Apache-2.0
- docs/adr/0001-acceptance-is-a-reproduced-public-experiment.md — 对这个项目而言「完成」意味着什么
- examples/feed-structure/README.md — 那个走通的场景的细节
- CODING_STANDARDS.md — 这里怎么审一个 diff

模型体验

模型看到什么

两条路,都是只读,都不在关键路径上。

reprise_status 是本包唯一注册的工具。它的输入是 candidate(一个可选 id,用来看那个候选的页面而不是总览)和 events(0–20,默认 6)。它的回答就是 HTTP 路由所服务的那个信封——view、 workspace、cursor、data、一个 incremental.events 列表——裁到六条事实和十个前沿成员;当工作区里还没有事实时,它用 data: null 加一个原因说明这件事,而不是失败。拒绝会带 ok: false 和 reason。整条事实流永远不会成为一个工具结果。

在循环内部,模型是一个子进程:在五个调用点的每一个,它读领域包里的一份 SKILL.md 加上 JSON 事实(候选身份、L1 检查结果、监视器上报的指标),并以判定器能解析的 wire format 作答。它永远拿不到事实面、会话,或另一个候选的结果。

Token 影响

很小而且有界。一次状态回答是一页投影,裁到六条事件和十个前沿成员;工具是在有人问运行进展时被调用,不是每轮都调。五次判断调用各带一份 markdown 提示词(领域包的 prompts/ 一共 246 行)加 JSON,而且每次都是新进程,所以一次运行里不会有东西累积。

KV Cache 影响

会话内没有:这次工具调用落在正常的工具调用序列里,不新增 system 前缀。判断调用之间也没有——它们是按调用点分别启动的独立进程,没有共享前缀可缓存,也没有缓存需要失效。插件里没有任何东西会改写 system prompt。

已知局限与推迟的工作

这些局限定义了它何时不合适——它们不是一份任务清单。

- 只在离线环境验证过。 阶段机、校验器、身份链、事实面、账本、前沿、判停规则、执行器与 harness 端口、dump,以及那五条命令组成的路径,全都在 npm test 里跑。未在这里端到端验证的:活的模型、真实的编码 harness、真实集群,以及浏览器真正对着一个真实宿主加载页面。
- 不在 npm 上。 每个包都是 private,版本 0.0.0,通过 workspace:* 依赖它的兄弟包。能装的是 scripts/build-release.mjs 打出的发布 tarball——不是 registry 包,也不是那个 git 地址。
- 随包发布的 bundle patch 故意不带配置。 包无法知道你的工作区在哪,所以它只发这一行、别的什么也不发;你 profile patch 里的 reprise-core 条目提供其余全部,而 patch 旁边的 cordis.example.yml 是一份填好的完整示例。没有配置的运行只是一行日志,不是一次挂载。
- 宿主侧没有配置 schema。 包不导出 Config,所以 Plugin Manager 无法渲染或校验这一行; config.js 是唯一的校验器,它的结论是一行日志。
- 没有写入口。 暂停、恢复、授权和审批以 <workspace>/pending/ 下的文件形式到达(intents.js),不是 HTTP 调用。
- 一个工作区,一次运行。 一个目录里两次运行不是受支持的配置;两个工作区是两个独立的看板。
- packages/exec-python 只是一个 manifest。 没有代码,没有 import torch;代理指标桥被推迟。

Dev Note

非权威的维护者说明——点击展开

本仓库是六个包的 pnpm workspace,在 Node 24 上测试(Node 22 能跑但会警告,因为那里的 node:sqlite 还是实验性的)。npm test 就是全部闸门;pre-commit hook 跑的正是它,只是剥掉了 git 的环境变量,因为测试会创建自己的临时仓库,而继承来的 GIT_DIR 会把它们指向本仓库。

值得在评审时不要重新发明的独特机制:阶段级的 artifact 契约、四态结论词汇(ok / conflict / missing / invalid)、身份绑定链,以及写成代码而不是策略配置的前沿与判停规则。一个决定的权威链就是代码;代码是「什么被接受、什么被拒绝」的记录。

代码注释里还会出现 spec: 相位机、issue 06 这样的引用。那是这套代码成长过程中形成的简写,指的是作者留在仓库之外的设计记录。这类注释里的每一条论断都自成一句——读那句话,不必读它的引用。

许可证

Apache-2.0 — 见 LICENSE。
预览分享卡片长什么样 ↓sunstrikes/dsh-reprise
分享 · DeepSeek Harness Hub
sunstrikes/dsh-reprise

先复现论文指标,再受控迭代模型结构并记录花费与判停

这是你的仓库?认领条目

回到顶部